Dynamic Shapes

The following sample shows how you can use the SpreadJS spreadsheet to dynamically generate shapes based on the data for your JavaScript applications. You can create custom shapes using data and formulas to drive dynamic shapes and shape values. For example, you could create dynamic manufacturing production/plant lines showing the status of each machine, build an office floor plan showing available rooms, show the status of each server in your server room, and much more.

This sample shows an airplane seating chart where the shape "seat" color property is assigned using formulas based off of the seat value in the "Sheets2" sheet (column B).

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.
<template> <div class="sample-tutorial" @click="onWorkSheetClick($event)"> <gc-spread-sheets class="sample-spreadsheets" @workbookInitialized="initSpread"> <gc-worksheet></gc-worksheet> <gc-worksheet></gc-worksheet> </gc-spread-sheets> </div> </template> <script setup> import GC from "@mescius/spread-sheets"; import { ref } from "vue"; import "@mescius/spread-sheets-vue"; import '@mescius/spread-sheets-shapes'; import '@mescius/spread-sheets-charts'; const spreadRef = ref(null); const selectedSeatRef = ref(null); const initSpread = (spread) => { spreadRef.value = spread; selectedSeatRef.value = ""; 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); 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 = 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 + "" + convertNumbertoLetter(j) //airline Seat let s1shape = 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(); } const pointsToPath = (points) => { let ps = points.split(' '); let mx = 10000; // shift the shape to (0, 0) let my = 10000; ps = ps.map(function (p) { 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]; } const convertPath = (d) => { 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) { 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; } const convertStatus = (seatStatus) => { let ret = -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 } const convertNumbertoLetter = (ssCol) => { 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 } const createShape = (serverpathCommands, sleft, stop, swidth, sheight, shapename) => { 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; } const addData = (sheet) => { //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"; } const onWorkSheetClick = (e) => { let sp = spreadRef.value.getHost(); let spread = spreadRef.value; 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(); let selectedSeat = selectedSeatRef.value; if (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); selectedSeat = shape.name(); sheet.setValue(11, 8, 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); selectedSeat = shape.name(); sheet.setValue(11, 8, selectedSeat); preselectionseatval = val; } } else { alert("This seat is already reserved"); } } else { sheetData.suspendPaint(); let searchCondition = new GC.Spread.Sheets.Search.SearchCondition(); searchCondition.searchString = 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); selectedSeatRef.value = shape.name(); sheet.setValue(11, 8, 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); selectedSeatRef.value = shape.name(); sheet.setValue(11, 8, selectedSeat); } } sheetData.resumePaint(); } spread.resumePaint(); } } } </script> <style scoped> .sample-tutorial { position: relative; height: 100%; overflow: hidden; } .sample-spreadsheets { width: 100%; height: 100%; overflow: hidden; float: left; } body { position: absolute; top: 0; bottom: 0; left: 0; right: 0; } #app { height: 100%; } </style>
<!DOCTYPE html> <html style="height:100%;font-size:14px;"> <head> <meta charset="utf-8"/> <meta http-equiv="X-UA-Compatible" content="IE=edge"/> <title>SpreadJS VUE</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"/> <link rel="stylesheet" type="text/css" href="$DEMOROOT$/en/vue3/node_modules/@mescius/spread-sheets/styles/gc.spread.sheets.excel2013white.css"> <script src="$DEMOROOT$/spread/source/data/svgpath.js" type="text/javascript"></script> <script src="$DEMOROOT$/en/vue3/node_modules/systemjs/dist/system.src.js"></script> <script src="./systemjs.config.js"></script> <script src="./compiler.js" type="module"></script> <script> var System = SystemJS; System.import("./src/app.js"); System.import('$DEMOROOT$/en/lib/vue3/license.js'); </script> </head> <body> <div id="app"></div> </body> </html>
(function (global) { SystemJS.config({ transpiler: 'plugin-babel', babelOptions: { es2015: true }, paths: { // paths serve as alias 'npm:': 'node_modules/' }, packageConfigPaths: [ './node_modules/*/package.json', "./node_modules/@mescius/*/package.json", "./node_modules/@babel/*/package.json", "./node_modules/@vue/*/package.json" ], map: { 'vue': "npm:vue/dist/vue.esm-browser.js", 'tiny-emitter': 'npm:tiny-emitter/index.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-charts': 'npm:@mescius/spread-sheets-charts/index.js', '@mescius/spread-sheets-vue': 'npm:@mescius/spread-sheets-vue/index.js' }, meta: { '*.css': { loader: 'systemjs-plugin-css' }, '*.vue': { loader: "../plugin-vue/index.js" } } }); })(this);