Along with these built-in shapes you can also create custom shapes. You can create these custom shapes specific to your needs. You can generate the shapes dynamically based on the data in the cells. So now let's start creating the interactive seats booking app with the help of dynamically generated 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;
selectedSeat: string;
hostStyle = {
width: '100%',
height: '100%',
overflow: 'hidden',
float: 'left'
};
constructor() {
}
initSpread($event: any) {
let spread = $event.spread;
this.spread = spread;
this.selectedSeat = "";
let constleft = 137.5;
let consttop = 230;
let aislespacer = 25;
let rowspacer = 32.8;
let shapewidth = 25;
let shapeheight = 25;
let shapeleft = constleft;
let shapetop = consttop;
let i;
let j;
let sheetDataRow = 0; //counter for Seats on Data sheet
let sheetDataSeatRow = 2; //seat number counter for display - 2A, 2B, 3A, etc
let seatNum = "" //seat number as shown on ticket- 2A, 2B, 3A, etc
spread.suspendPaint();
spread.suspendCalcService();
let sheet = spread.getSheet(0);
sheet.setRowCount(100);
let sheetData = spread.getSheet(1);
sheetData.setRowCount(200);
sheet.setColumnWidth(0, 200);
sheet.addSpan(0, 0, 80, 5);
sheet.getCell(0, 0).backgroundImage("$DEMOROOT$/spread/source/images/airplane.png");
sheet.options.isProtected = true;
sheet.options.gridline = { showVerticalGridline: false, showHorizontalGridline: false };
sheet.setColumnResizable(-1, true, GC.Spread.Sheets.SheetArea.colHeader);
sheet.setRowResizable(-1, true, GC.Spread.Sheets.SheetArea.rowHeader);
this.addData(sheet);
let airseat1 = "M 151.917 691.735 h -14.676 c -0.039 0 -3.689 -0.066 -3.689 -4.245 l 0.004 -17.254 c 0.096 -0.683 0.735 -1.976 2.47 -1.976 c 1.552 0 2.339 0.682 2.339 2.029 v 14.269 c 0.153 0.357 1.246 2.427 5.952 2.427 c 5.117 0 6.417 -2.265 6.522 -2.473 V 670.29 c 0.031 -0.713 0.52 -2.029 2.149 -2.029 c 1.149 0 1.909 0.606 2.257 1.802 c 0.009 0.029 0.015 0.051 0.021 0.067 l -0.015 0.005 c 0.103 0.431 0.113 2.671 0.052 17.723 c 0.005 0.042 0.126 1.774 -0.934 2.918 C 153.781 691.412 152.956 691.735 151.917 691.735 Z M 134.324 670.321 v 17.169 c 0 3.4 2.802 3.473 2.921 3.474 h 14.672 c 0.814 0 1.45 -0.24 1.888 -0.713 c 0.832 -0.9 0.73 -2.352 0.729 -2.366 c 0.026 -6.665 0.05 -16.629 0.007 -17.496 c -0.012 -0.034 -0.022 -0.071 -0.034 -0.112 c -0.17 -0.581 -0.496 -1.247 -1.518 -1.247 c -1.308 0 -1.377 1.224 -1.379 1.275 l 0 14.253 c 0 0.65 -1.903 3.197 -7.293 3.197 c -5.646 0 -6.663 -2.882 -6.704 -3.005 l -0.02 -0.058 V 670.29 c 0 -0.44 0 -1.258 -1.568 -1.258 C 134.629 669.031 134.363 670.107 134.324 670.321 Z M 135.261 669.105 c -0.002 -0.047 -0.046 -1.17 0.788 -2.047 c 0.627 -0.657 1.56 -0.99 2.773 -0.99 l 12.006 0.005 c 0.969 0.16 2.8 0.996 2.8 3.014 h -0.771 c 0 -1.779 -1.86 -2.197 -2.126 -2.248 h -11.91 c -0.995 0 -1.74 0.252 -2.215 0.751 c -0.6 0.629 -0.577 1.469 -0.577 1.477 L 135.261 669.105 Z";
//Get the seat shape
let s1commands = this.convertPath(airseat1);
//Rows loop
//USE 30
for (let r = 0; r <= 185; r++) {
sheetData.setValue(r, 1, Math.floor(Math.random() * Math.floor(3)));
}
for (i = 0; i < 31; i++) {
for (j = 0; j < 6; j++) {
seatNum = sheetDataSeatRow + "" + this.convertNumbertoLetter(j)
//airline Seat
let s1shape = this.createShape(s1commands, shapeleft, shapetop, shapewidth, shapeheight, seatNum);
//Add Name
let ret2 = sheet.shapes.add(seatNum, s1shape);
ret2.allowMove(false);
ret2.allowResize(false);
ret2.dynamicMove(false);
ret2.dynamicSize(false);
ret2.isLocked(true);
//Add the SeatNumber to first column
sheetData.setValue(sheetDataRow, 0, seatNum);
sheetDataRow = sheetDataRow + 1;
if (j == 5) {
sheetDataSeatRow = sheetDataSeatRow + 1;
}
//increment
shapeleft = shapeleft + shapewidth;
if (j == 2) {
// D,E,F seats. Add aisle spacer
shapeleft = shapeleft + aislespacer;
}
}
//reset left and top to start new rows
shapeleft = constleft;
shapetop = shapetop + rowspacer;
if (i == 12) {
shapetop = shapetop + 10;
}
if (i == 13) {
shapetop = shapetop + 10;
}
if (i == 22) {
shapetop = shapetop - 3;
}
}
spread.resumeCalcService();
spread.resumePaint();
}
// convert svg points to shape path
pointsToPath(points: string) {
let ps = points.split(' ') as any;
let mx = 10000; // shift the shape to (0, 0)
let my = 10000;
ps = ps.map(function (p: any) {
let t = p.split(',');
if (parseFloat(t[0]) < mx) {
mx = parseFloat(t[0]);
}
if (parseFloat(t[1]) < my) {
my = parseFloat(t[1]);
}
return { x: parseFloat(t[0]), y: parseFloat(t[1]) };
});
let cmds = [];
cmds.push(['M', ps[0].x - mx, ps[0].y - my]);
for (let i = 1; i < ps.length; i++) {
cmds.push(['L', ps[i].x - mx, ps[i].y - my]);
}
cmds.push(['Z']);
return [cmds];
}
// convert svg path to shape path
convertPath(d: any) {
let cmdMap = {
moveTo: 'M',
lineTo: 'L',
bezierCurveTo: 'B',
quadraticCurveTo: 'Q',
arc: 'A',
arcTo: 'AT',
closePath: 'Z'
};
let cmds = parser.parse(d);
let mx = 10000; // shift the shape to (0, 0)
let my = 10000;
let pathCommands = cmds.map(function (cmd: any) {
for (let i = 0; i < cmd.args.length; i = i + 2) {
if (cmd.args[i] < mx) {
mx = cmd.args[i];
}
if (cmd.args[i + 1] < my) {
my = cmd.args[i + 1];
}
}
return [cmdMap[cmd.type]].concat(cmd.args);
});
let ret = [];
let t = [];
for (let i = 0; i < pathCommands.length; i++) {
let cmd = pathCommands[i];
for (let j = 1; j < cmd.length; j = j + 2) {
cmd[j] = cmd[j] - mx;
cmd[j + 1] = cmd[j + 1] - my;
}
t.push(cmd);
if (cmd[0] == 'Z') {
ret.push(t);
t = [];
}
}
return ret;
}
convertStatus(seatStatus: any) {
let ret: any = -1;
//O: Open: green
//1: Reserved: red
//2: Upgrade: orange
switch (seatStatus) {
case 0:
ret = "green";
break;
case 1:
ret = "red";
break;
case 2:
ret = "orange";
break;
}
return ret
}
convertNumbertoLetter(ssCol: any) {
let ret = ""
switch (ssCol) {
case 0:
ret = "A";
break;
case 1:
ret = "B";
break;
case 2:
ret = "C";
break;
case 3:
ret = "D";
break;
case 4:
ret = "E";
break;
case 5:
ret = "F";
break;
}
return ret
}
createShape(serverpathCommands: any, sleft: any, stop: any, swidth: any, sheight: any, shapename: any) {
let shapeFormula = "=CHOOSE(VLOOKUP(name,Sheet2!A1:B186,2,False)+1, \"green\", \"red\", \"orange\")";
let servermodel = {
name: shapename,
left: sleft,
top: stop,
width: swidth,
height: sheight,
angle: 0,
options: {
fill: {
type: GC.Spread.Sheets.Shapes.ShapeFillType.solid,
color: shapeFormula
},
stroke: {
type: GC.Spread.Sheets.Shapes.ShapeFillType.solid,
color: shapeFormula,
width: 1
},
textFormatOptions: {
allowTextToOverflowShape: false,
wrapTextInShape: false,
font: '="11px Arial"',
fill: {
type: GC.Spread.Sheets.Shapes.ShapeFillType.solid,
color: 'black'
}
}
},
path: serverpathCommands
};
return servermodel;
}
addData(sheet: any) {
//O: Open: green
//1: Reserved: red
//2: Upgrade: orange
sheet.setValue(2, 5, "Available Seats:")
sheet.getCell(2, 7).backColor("green");
sheet.setValue(4, 5, "Reserved Seats:")
sheet.getCell(4, 7).backColor("red");
sheet.setValue(6, 5, "Premium Seats")
sheet.getCell(6, 7).backColor("orange");
sheet.setValue(11, 5, "You have Selected:");
sheet.addSpan(11, 5, 1, 3);
sheet.options.protectionOptions.allowEditObjects = false;
sheet.setColumnWidth(7, 20);
sheet.options.colHeaderVisible = false;
sheet.options.rowHeaderVisible = false;
sheet.options.selectionBorderColor = "Transparent";
}
onWorkSheetClick($event: any) {
let e = $event;
let sp = this.spread.getHost();
let spread = this.spread;
let sheet = spread.getSheet(0);
let sheetData = spread.getSheet(1);
//return;
let x = e.pageX - sp.offsetLeft;
let y = e.pageY - sp.offsetTop;
let target = spread.getActiveSheet().hitTest(x, y);
if (target.shapeHitInfo == null && x > 700) {
y = y - 300;
target = spread.getActiveSheet().hitTest(x, y);
}
let preselectionseatval = "";
if (target.shapeHitInfo) {
let shape = target.shapeHitInfo.shape;
if (shape) {
spread.suspendPaint();
if (this.selectedSeat == "") {
let searchCondition = new GC.Spread.Sheets.Search.SearchCondition();
searchCondition.searchString = shape.name();
searchCondition.startSheetIndex = 1;
searchCondition.endSheetIndex = 1;
searchCondition.searchOrder = GC.Spread.Sheets.Search.SearchOrder.nOrder;
searchCondition.searchTarget = GC.Spread.Sheets.Search.SearchFoundFlags.cellText;
searchCondition.searchFlags = GC.Spread.Sheets.Search.SearchFlags.ignoreCase | GC.Spread.Sheets.Search.SearchFlags.useWildCards;
let searchresult = spread.search(searchCondition);
let val = sheetData.getValue(searchresult.foundRowIndex, searchresult.foundColumnIndex + 1);
if (val == 0) {
sheetData.setValue(searchresult.foundRowIndex, searchresult.foundColumnIndex + 1, 1, GC.Spread.Sheets.SheetArea.viewport, false);
this.selectedSeat = shape.name();
sheet.setValue(11, 8, this.selectedSeat);
preselectionseatval = val;
}
else if (val == 2) {
if (confirm("This is a Premium seat. Are you sure you want to upgrade?")) {
sheetData.setValue(searchresult.foundRowIndex, searchresult.foundColumnIndex + 1, 1, GC.Spread.Sheets.SheetArea.viewport, false);
this.selectedSeat = shape.name();
sheet.setValue(11, 8, this.selectedSeat);
preselectionseatval = val;
}
} else {
alert("This seat is already reserved");
}
}
else {
sheetData.suspendPaint();
let searchCondition = new GC.Spread.Sheets.Search.SearchCondition();
searchCondition.searchString = this.selectedSeat;
searchCondition.startSheetIndex = 1;
searchCondition.endSheetIndex = 1;
searchCondition.searchOrder = GC.Spread.Sheets.Search.SearchOrder.nOrder;
searchCondition.searchTarget = GC.Spread.Sheets.Search.SearchFoundFlags.cellText;
searchCondition.searchFlags = GC.Spread.Sheets.Search.SearchFlags.ignoreCase | GC.Spread.Sheets.Search.SearchFlags.useWildCards;
let searchresult = spread.search(searchCondition);
let selectedseatval = sheetData.getValue(searchresult.foundRowIndex, searchresult.foundColumnIndex + 1);
let searchCondition1 = new GC.Spread.Sheets.Search.SearchCondition();
searchCondition1.searchString = shape.name();
searchCondition1.startSheetIndex = 1;
searchCondition1.endSheetIndex = 1;
searchCondition1.searchOrder = GC.Spread.Sheets.Search.SearchOrder.nOrder;
searchCondition1.searchTarget = GC.Spread.Sheets.Search.SearchFoundFlags.cellText;
searchCondition1.searchFlags = GC.Spread.Sheets.Search.SearchFlags.ignoreCase | GC.Spread.Sheets.Search.SearchFlags.useWildCards;
let searchresult1 = spread.search(searchCondition1);
let val = sheetData.getValue(searchresult1.foundRowIndex, searchresult1.foundColumnIndex + 1);
if (val == 1) {
alert("This seat is already reserved");
}
else if (val == 0) {
sheetData.setValue(searchresult1.foundRowIndex, searchresult1.foundColumnIndex + 1, 1, GC.Spread.Sheets.SheetArea.viewport, false);
sheetData.setValue(searchresult.foundRowIndex, searchresult.foundColumnIndex + 1, 0, GC.Spread.Sheets.SheetArea.viewport, false);
this.selectedSeat = shape.name();
sheet.setValue(11, 8, this.selectedSeat);
}
else if (val == 2) {
if (confirm("This is a Premium seat. Are you sure you want to upgrade?")) {
sheetData.setValue(searchresult1.foundRowIndex, searchresult1.foundColumnIndex + 1, 1, GC.Spread.Sheets.SheetArea.viewport, false);
sheetData.setValue(searchresult.foundRowIndex, searchresult.foundColumnIndex + 1, 0, GC.Spread.Sheets.SheetArea.viewport, false);
this.selectedSeat = shape.name();
sheet.setValue(11, 8, this.selectedSeat);
}
}
sheetData.resumePaint();
}
spread.resumePaint();
}
}
}
}
@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 src="$DEMOROOT$/spread/source/data/svgpath.js" type="text/javascript"></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 [hostStyle]="hostStyle" (workbookInitialized)="initSpread($event)" (click)="onWorkSheetClick($event)">
<gc-worksheet></gc-worksheet>
<gc-worksheet></gc-worksheet>
</gc-spread-sheets>
</div>
.sample-tutorial {
position: relative;
height: 100%;
overflow: hidden;
}
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);