Group Shape

SpreadJS supports grouping individual shapes into a shape group. This group shape is used to manage selecting and editing groups of shapes easily and quickly.

You can use group shape to move shapes but keep their related location, resize & keep size ratio, rotate with same angle etc. You can group/ungroup multiple shapes using the following code:
import * as React from 'react'; import { createRoot } from 'react-dom/client'; import './styles.css'; import { AppFunc } from './app-func'; // import { App } from './app-class'; // 1. Functional Component sample createRoot(document.getElementById('app')).render(<AppFunc />); // 2. Class Component sample // createRoot(document.getElementById('app')).render(<App />);
import * as React from 'react'; import GC from '@mescius/spread-sheets'; import { SpreadSheets } from '@mescius/spread-sheets-react'; import "@mescius/spread-sheets-shapes"; let spread = null; export function AppFunc() { const [canGroup, setCanGroup] = React.useState(true); const group = () => { let activeSheet = spread.getActiveSheet(); let activeShape = activeSheet.shapes.all().filter(function (sp) { return sp.isSelected(); }); if (activeShape.length > 1) { var groupShape = activeSheet.shapes.group(activeShape); groupShape.isSelected(true); setCanGroup(false); } } const ungroup = () => { let activeSheet = spread.getActiveSheet(); let activeShape = activeSheet.shapes.all().filter(function (sp) { return sp.isSelected(); }); if (activeShape.length > 0) { activeShape.forEach(function (shape) { activeSheet.shapes.ungroup(shape); }); setCanGroup(true); } } const initSpread = (currSpread) => { spread = currSpread; let sheet = spread.getSheet(0); let rectangle = sheet.shapes.add("rectangle", GC.Spread.Sheets.Shapes.AutoShapeType.rectangle, 100, 60, 150, 150); let rectangleStyle = rectangle.style(); rectangleStyle.fill.color = "#F4F8EB"; rectangleStyle.line.color = "#82bc00"; rectangle.style(rectangleStyle); let oval = sheet.shapes.add("oval", GC.Spread.Sheets.Shapes.AutoShapeType.oval, 400, 60, 150, 150); let ovalStyle = oval.style(); ovalStyle.fill.color = "#e3e3e3"; ovalStyle.line.color = "#e3e3e3"; oval.style(ovalStyle); let cube = sheet.shapes.add("cube", GC.Spread.Sheets.Shapes.AutoShapeType.cube, 100, 240, 150, 150); let can = sheet.shapes.add("can", GC.Spread.Sheets.Shapes.AutoShapeType.can, 400, 240, 150, 150); let shapes = [cube, can]; sheet.shapes.group(shapes); 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 && activeShapes[0] instanceof GC.Spread.Sheets.Shapes.GroupShape) { setCanGroup(false); } else { setCanGroup(true); } }); } return (<div class="sample-tutorial"> <div class="sample-spreadsheets"> <SpreadSheets workbookInitialized={spread => initSpread(spread)}> </SpreadSheets> </div> <div class="options-container"> <div class="option-row"> Try selecting the shapes in Spread and grouping/ungrouping them to see how the selection area changes. </div> <div class="option-row"> *the cube and the cylinder are already grouped. </div> <hr /> <div class="option-row"> <label>Group Selected Shapes</label> <input type="button" disabled={!canGroup} value="Group" onClick={() => { group() }}></input> <br /> <label>Ungroup Selected Shapes</label> <input type="button" disabled={canGroup} value="Ungroup" onClick={() => { ungroup() }}></input> </div> </div> </div>); }
import * as React from 'react'; import GC from '@mescius/spread-sheets'; import { SpreadSheets } from '@mescius/spread-sheets-react'; import "@mescius/spread-sheets-shapes"; const Component = React.Component; export class App extends Component { constructor(props) { super(props); this.spread = null; this.state = { canGroup: true } } render() { return (<div class="sample-tutorial"> <div class="sample-spreadsheets"> <SpreadSheets workbookInitialized={spread => this.initSpread(spread)}> </SpreadSheets> </div> <div class="options-container"> <div class="option-row"> Try selecting the shapes in Spread and grouping/ungrouping them to see how the selection area changes. </div> <div class="option-row"> *the cube and the cylinder are already grouped. </div> <hr /> <div class="option-row"> <label>Group Selected Shapes</label> <input type="button" disabled={!this.state.canGroup} value="Group" onClick={() => { this.group() }}></input> <br /> <label>Ungroup Selected Shapes</label> <input type="button" disabled={this.state.canGroup} value="Ungroup" onClick={() => { this.ungroup() }}></input> </div> </div> </div>); } group() { let self = this, spread = self.spread; let activeSheet = spread.getActiveSheet(); let activeShape = activeSheet.shapes.all().filter(function (sp) { return sp.isSelected(); }); if (activeShape.length > 1) { var groupShape = activeSheet.shapes.group(activeShape); groupShape.isSelected(true); self.setState({ canGroup: false }); } } ungroup() { let self = this, spread = self.spread; let activeSheet = spread.getActiveSheet(); let activeShape = activeSheet.shapes.all().filter(function (sp) { return sp.isSelected(); }); if (activeShape.length > 0) { activeShape.forEach(function (shape) { activeSheet.shapes.ungroup(shape); }); self.setState({ canGroup: true }); } } initSpread(spread) { let self = this; self.spread = spread; let sheet = spread.getSheet(0); let rectangle = sheet.shapes.add("rectangle", GC.Spread.Sheets.Shapes.AutoShapeType.rectangle, 100, 60, 150, 150); let rectangleStyle = rectangle.style(); rectangleStyle.fill.color = "#F4F8EB"; rectangleStyle.line.color = "#82bc00"; rectangle.style(rectangleStyle); let oval = sheet.shapes.add("oval", GC.Spread.Sheets.Shapes.AutoShapeType.oval, 400, 60, 150, 150); let ovalStyle = oval.style(); ovalStyle.fill.color = "#e3e3e3"; ovalStyle.line.color = "#e3e3e3"; oval.style(ovalStyle); let cube = sheet.shapes.add("cube", GC.Spread.Sheets.Shapes.AutoShapeType.cube, 100, 240, 150, 150); let can = sheet.shapes.add("can", GC.Spread.Sheets.Shapes.AutoShapeType.can, 400, 240, 150, 150); let shapes = [cube, can]; sheet.shapes.group(shapes); 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 && activeShapes[0] instanceof GC.Spread.Sheets.Shapes.GroupShape) { self.setState({ canGroup: false }); } else { self.setState({ canGroup: true }); } }); } }
<!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/react/node_modules/@mescius/spread-sheets/styles/gc.spread.sheets.excel2013white.css"> <!-- SystemJS --> <script src="$DEMOROOT$/en/react/node_modules/systemjs/dist/system.src.js"></script> <script src="systemjs.config.js"></script> <script> System.import('$DEMOROOT$/en/lib/react/license.js').then(function () { System.import('./src/app'); }); </script> </head> <body> <div id="app"></div> </body> </html>
.sample-tutorial { position: relative; height: 100%; overflow: hidden; } .sample-spreadsheets { width: calc(100% - 280px); height: 100%; overflow: hidden; float: left; } .options-container { float: right; width: 280px; 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; } #app { height: 100%; }
(function (global) { System.config({ transpiler: 'plugin-babel', babelOptions: { es2015: true, react: true }, meta: { '*.css': { loader: 'css' } }, paths: { // paths serve as alias 'npm:': 'node_modules/' }, // map tells the System loader where to look for things map: { '@mescius/spread-sheets': 'npm:@mescius/spread-sheets/index.js', '@mescius/spread-sheets-shapes': 'npm:@mescius/spread-sheets-shapes/index.js', '@mescius/spread-sheets-react': 'npm:@mescius/spread-sheets-react/index.js', '@grapecity/jsob-test-dependency-package/react-components': 'npm:@grapecity/jsob-test-dependency-package/react-components/index.js', 'react': 'npm:react/cjs/react.production.js', 'react-dom': 'npm:react-dom/cjs/react-dom.production.js', 'react-dom/client': 'npm:react-dom/cjs/react-dom-client.production.js', 'scheduler': 'npm:scheduler/cjs/scheduler.production.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' }, // packages tells the System loader how to load when no filename and/or no extension packages: { src: { defaultExtension: 'jsx' }, "node_modules": { defaultExtension: 'js' }, } }); })(this);