Shape Alignment

SpreadJS makes it easy to fine-tune multiple shape's positions using shape alignment options, distribution options or snapping them to other shapes or grids.

Description
app.component.ts
index.html
app.component.html
styles.css
Copy to CodeMine

You can align shapes using the following code:

    var commandManager = spread.commandManager();
    commandManager.execute({
        cmd: 'moveShapesByHAlign',
        sheetName: sheet.name(),
        shapeNames: ['rect1','line1','heart1','group1'],
        alignment: GC.Spread.Sheets.Shapes.HorizontalAlign.left
    });
    commandManager.execute({
        cmd: 'moveShapesByVAlign',
        sheetName: sheet.name(),
        shapeNames: ['rect1','line1','heart1','group1'],
        alignment: GC.Spread.Sheets.Shapes.VerticalAlign.bottom
    });
    commandManager.execute({
            cmd: 'moveShapesByHDistribute',
            sheetName: sheet.name(),
            shapeNames: ['rect1','line1','heart1','group1']
        });
    });
    commandManager.execute({
            cmd: 'moveShapesByVDistribute',
            sheetName: sheet.name(),
            shapeNames: ['rect1','line1','heart1','group1']
        });
    });

You can change snapMode to align shapes easily

    var sheet = spread.getActiveSheet();
    sheet.shapes.snapMode(GC.Spread.Sheets.Shapes.SnapMode.grid);

Now when you drag or resize a shape, the shape will align to grid lines automatically.

You can also align to other shapes:

    var sheet = spread.getActiveSheet();
    sheet.shapes.snapMode(GC.Spread.Sheets.Shapes.SnapMode.grid | GC.Spread.Sheets.Shapes.SnapMode.shape);
You can align shapes using the following code: You can change snapMode to align shapes easily Now when you drag or resize a shape, the shape will align to grid lines automatically. You can also align to other shapes:
import { Component, NgModule, enableProdMode } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { SpreadSheetsModule } from '@mescius/spread-sheets-angular'; import GC from '@mescius/spread-sheets'; import "@mescius/spread-sheets-shapes"; import './styles.css'; @Component({ selector: 'app-component', templateUrl: 'src/app.component.html' }) export class AppComponent { spread: GC.Spread.Sheets.Workbook; hostStyle = { width: 'calc(100% - 350px)', height: '100%', overflow: 'hidden', float: 'left' }; canAlign = false; canDistribute = false; align(cmd: string, alignment: string) { let self = this, spread = self.spread; let activeSheet = spread.getActiveSheet(); let activeShapes = activeSheet.shapes.all().filter(function(sp: GC.Spread.Sheets.Shapes.ShapeBase) { return sp.isSelected(); }); var commandManager = spread.commandManager(); commandManager.execute({ cmd: cmd, sheetName: activeSheet.name(), shapeNames: activeShapes.map(function(s: GC.Spread.Sheets.Shapes.Shape) { return s.name(); }), alignment: self.getAlignmentEnum(alignment) }); } alignDd(e: any,cmd: string): void { let self = this, spread = self.spread; let activeSheet = spread.getActiveSheet(); let activeShapes = activeSheet.shapes.all().filter(function(sp: GC.Spread.Sheets.Shapes.ShapeBase) { return sp.isSelected(); }); var commandManager = spread.commandManager(); commandManager.execute({ cmd: cmd, sheetName: activeSheet.name(), shapeNames: activeShapes.map(function(s: GC.Spread.Sheets.Shapes.Shape) { return s.name(); }), alignment: self.getAlignmentEnum(e.target.value) }); } getAlignmentEnum(alignment: string) { switch (alignment) { case 'left': return GC.Spread.Sheets.Shapes.HorizontalAlign.left; case 'center': return GC.Spread.Sheets.Shapes.HorizontalAlign.center; case 'right': return GC.Spread.Sheets.Shapes.HorizontalAlign.right; case 'top': return GC.Spread.Sheets.Shapes.VerticalAlign.top; case 'middle': return GC.Spread.Sheets.Shapes.VerticalAlign.middle; case 'bottom': return GC.Spread.Sheets.Shapes.VerticalAlign.bottom; default: return; } } initSpread($event: any) { let spread = $event.spread, self = this; self.spread = spread; let sheet = spread.getActiveSheet(); var rect1 = sheet.shapes.add('rect1', GC.Spread.Sheets.Shapes.AutoShapeType.rectangle, 100, 50, 230, 100); var rect2 = sheet.shapes.add('rect2', GC.Spread.Sheets.Shapes.AutoShapeType.rectangle, 200, 200, 150, 100); var rect3 = sheet.shapes.add('rect3', GC.Spread.Sheets.Shapes.AutoShapeType.rectangle, 500, 350, 200, 100); spread.bind(GC.Spread.Sheets.Events.ShapeSelectionChanged, function() { let activeSheet = spread.getActiveSheet(); let shapes = activeSheet.shapes.all(), activeShapes = []; for (let i = 0; i < shapes.length; i++) { let shape = shapes[i]; if (shape.isSelected()) { activeShapes.push(shape); } } if (activeShapes.length > 1) { self.canAlign = true; } else { self.canAlign = false; } if (activeShapes.length > 2) { self.canDistribute = true; } else { self.canDistribute = false; } }); } changeSnapMode(e: any): void { let sheet = this.spread.getActiveSheet(); switch (e.target.value) { case '0': sheet.shapes.snapMode(GC.Spread.Sheets.Shapes.SnapMode.none); return; case '1': sheet.shapes.snapMode(GC.Spread.Sheets.Shapes.SnapMode.grid); return; case '2': sheet.shapes.snapMode(GC.Spread.Sheets.Shapes.SnapMode.shape); return; case '3': sheet.shapes.snapMode(GC.Spread.Sheets.Shapes.SnapMode.grid | GC.Spread.Sheets.Shapes.SnapMode.shape); return; default: return; } } } @NgModule({ imports: [BrowserModule, SpreadSheetsModule], declarations: [AppComponent], exports: [AppComponent], bootstrap: [AppComponent] }) export class AppModule { } enableProdMode(); // Bootstrap application with hash style navigation and global services. platformBrowserDynamic().bootstrapModule(AppModule);
<!doctype html> <html style="height:100%;font-size:14px;"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <link rel="stylesheet" type="text/css" href="$DEMOROOT$/en/angular/node_modules/@mescius/spread-sheets/styles/gc.spread.sheets.excel2013white.css"> <!-- Polyfills --> <script src="$DEMOROOT$/en/angular/node_modules/core-js/client/shim.min.js"></script> <script src="$DEMOROOT$/en/angular/node_modules/zone.js/fesm2015/zone.min.js"></script> <!-- SystemJS --> <script src="$DEMOROOT$/en/angular/node_modules/systemjs/dist/system.js"></script> <script src="systemjs.config.js"></script> <script> // workaround to load 'rxjs/operators' from the rxjs bundle System.import('rxjs').then(function (m) { System.import('@angular/compiler'); System.set(SystemJS.resolveSync('rxjs/operators'), System.newModule(m.operators)); System.import('$DEMOROOT$/en/lib/angular/license.ts'); System.import('./src/app.component'); }); </script> </head> <body> <app-component></app-component> </body> </html>
<div class="sample-tutorial"> <gc-spread-sheets class="sample-spreadsheets" [hostStyle]="hostStyle" (workbookInitialized)="initSpread($event)"> </gc-spread-sheets> <div class="options-container"> <div class="option-row"> Select two or more shapes then change the different alignment options below to see the effects. </div> <div class="option-row"> *for the vertical and horizontal distribution, all three shapes must be selected. </div> <div class="option-row"> <label>- Horizontal Alignment</label> <hr> <select id="moveShapesByHAlign" [disabled]="!canAlign" (change)="alignDd($event,'moveShapesByHAlign')"> <option value="left">left</option> <option value="center">center</option> <option value="right">right</option> </select> <br /> <br /> <div class="option-row"> <label>Horizontal Align Selected Shapes</label> <input type="button" [disabled]="!canDistribute" value="Horizontal" (click)="align('moveShapesByHDistribute')"> </div> <div class="option-row"> <label>- Vertical Alignment</label> <hr> <select id="moveShapesByVAlign" [disabled]="!canAlign" (change)="alignDd($event,'moveShapesByVAlign')"> <option value="top">top</option> <option value="middle">middle</option> <option value="bottom">bottom</option> </select> <br /> <div class="option-row"> <label>Vertical Align Selected Shapes</label> <input type="button" [disabled]="!canDistribute" value="Vertical" (click)="align('moveShapesByVDistribute')"> </div> <div class="option-row"> <label>Change the SnapMode options to align, drag, or resize shapes to the nearest edge based on gridlines or other shapes in the worksheet. </label> <hr> <select id="changeSnapMode" (change)="changeSnapMode($event)"> <option value="0" selected>none</option> <option value="1">grid</option> <option value="2">shape</option> <option value="3">grid and shape</option> </select> <br /> </div> </div> </div>
.sample-tutorial { position: relative; width: 100%; height: 100%; overflow: hidden; } .options-container { float: right; width: 350px; padding: 12px; height: 100%; box-sizing: border-box; background: #fbfbfb; overflow: auto; } .option-row { font-size: 14px; padding: 5px; margin-top: 5px; } .sample-options { z-index: 1000; } label { display: block; margin-bottom: 6px; } p{ padding:2px 10px; background-color:#F4F8EB; } input { padding: 4px 6px; width: 160px; } input[type=button] { margin-top: 6px; display: block; } body { position: absolute; top: 0; bottom: 0; left: 0; right: 0; }
(function (global) { System.config({ transpiler: 'ts', typescriptOptions: { tsconfig: true }, meta: { 'typescript': { "exports": "ts" }, '*.css': { loader: 'css' } }, paths: { // paths serve as alias 'npm:': 'node_modules/' }, // map tells the System loader where to look for things map: { 'core-js': 'npm:core-js/client/shim.min.js', 'zone': 'npm:zone.js/fesm2015/zone.min.js', 'rxjs': 'npm:rxjs/dist/bundles/rxjs.umd.min.js', '@angular/core': 'npm:@angular/core/fesm2022', '@angular/common': 'npm:@angular/common/fesm2022/common.mjs', '@angular/compiler': 'npm:@angular/compiler/fesm2022/compiler.mjs', '@angular/platform-browser': 'npm:@angular/platform-browser/fesm2022/platform-browser.mjs', '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/fesm2022/platform-browser-dynamic.mjs', '@angular/common/http': 'npm:@angular/common/fesm2022/http.mjs', '@angular/router': 'npm:@angular/router/fesm2022/router.mjs', '@angular/forms': 'npm:@angular/forms/fesm2022/forms.mjs', 'jszip': 'npm:jszip/dist/jszip.min.js', 'typescript': 'npm:typescript/lib/typescript.js', 'ts': './plugin.js', 'tslib':'npm:tslib/tslib.js', 'css': 'npm:systemjs-plugin-css/css.js', 'plugin-babel': 'npm:systemjs-plugin-babel/plugin-babel.js', 'systemjs-babel-build':'npm:systemjs-plugin-babel/systemjs-babel-browser.js', '@mescius/spread-sheets': 'npm:@mescius/spread-sheets/index.js', '@mescius/spread-sheets-shapes': 'npm:@mescius/spread-sheets-shapes/index.js', '@mescius/spread-sheets-angular': 'npm:@mescius/spread-sheets-angular/fesm2020/mescius-spread-sheets-angular.mjs', '@grapecity/jsob-test-dependency-package/react-components': 'npm:@grapecity/jsob-test-dependency-package/react-components/index.js' }, // packages tells the System loader how to load when no filename and/or no extension packages: { src: { defaultExtension: 'ts' }, rxjs: { defaultExtension: 'js' }, "node_modules": { defaultExtension: 'js' }, "node_modules/@angular": { defaultExtension: 'mjs' }, "@mescius/spread-sheets-angular": { defaultExtension: 'mjs' }, '@angular/core': { defaultExtension: 'mjs', main: 'core.mjs' } } }); })(this);