Trellis

This sample demonstrates trellis row and column encodings across every DataChartType. Each worksheet contains exactly one chart, and the worksheet name matches the chart type name such as Column, Bubble, and OHLC.

Data Charts support row and column encodings for trellis layouts, allowing a single chart definition to be split into multiple comparison panels. This is useful when you want to compare the same metric across two categorical dimensions, such as region and sales channel. In this sample, every DataChartType has its own worksheet, and the worksheet name is the same as the chart type name. Supported data chart types include Column, RangeColumn, StackedColumn, PercentStackedColumn, Bar, RangeBar, StackedBar, PercentStackedBar, Area, RangeArea, StackedArea, PercentStackedArea, Line, Pie, Donut, Rose, RadialStackedBar, Sunburst, PolarCoordinatesBar, PolarCoordinatesStackedBar, Radar, FilledRadar, Scatter, Bubble, Treemap, Funnel, Waterfall, Candlestick, and OHLC. The demo uses several meaningful mock datasets so each chart reads naturally in context: RetailPerformance for column, range column, bar, range bar, area, range area, line, pie-like, polar bar, sunburst, and treemap charts RetailMix for stacked and percent stacked column, bar, area, radial, polar, radar, and filled radar charts CustomerPortfolio for scatter and bubble charts SalesPipeline for funnel charts ProfitBridge for waterfall charts StockPerformance for candlestick and OHLC charts The same trellis pattern is then applied to all supported chart types, with one chart per worksheet.
window.onload = async () => { const { spread, designer } = createSpreadAndDesigner(); await initDataManager(spread); spread.suspendPaint(); const DataChartType = GC.Spread.Sheets.DataCharts.DataChartType; const chartConfigs = createChartConfigs(DataChartType); spread.setSheetCount(chartConfigs.length); chartConfigs.forEach((cfg, index) => { const sheet = spread.getSheet(index); sheet.name(cfg.name); addChartToSheet(sheet, cfg); }); spread.setActiveSheetIndex(0); const firstChart = spread.getActiveSheet().dataCharts.all()[0]; if (firstChart) { firstChart.isSelected(true); } if (window !== top) { new GC.Spread.Sheets.DataCharts.DataChartConfigPanel('panel-host', spread); } spread.resumePaint(); if (designer) { designer.refresh(); } }; function createChartConfigs(DataChartType) { return [ { name: DataChartType.column, type: DataChartType.column, tableName: 'RetailPerformance', variant: 'retailSingle', valueField: 'Revenue', aggregate: 'sum', categoryField: 'Month', detailBinding: false, detailAndColorBinding: true, detailAndColorField: 'ProductLine', title: 'Monthly revenue by region and channel' }, { name: DataChartType.rangeColumn, type: DataChartType.rangeColumn, tableName: 'RetailPerformance', variant: 'retailRange', categoryField: 'Month', detailBinding: false, detailAndColorBinding: true, detailAndColorField: 'ProductLine', title: 'Revenue forecast band by region and channel' }, { name: DataChartType.stackedColumn, type: DataChartType.stackedColumn, tableName: 'RetailMix', variant: 'mixStacked', categoryField: 'Month', title: 'Offer revenue mix by month' }, { name: DataChartType.percentStackedColumn, type: DataChartType.percentStackedColumn, tableName: 'RetailMix', variant: 'percentMix', categoryField: 'Month', title: 'Monthly revenue share by offer type' }, { name: DataChartType.bar, type: DataChartType.bar, tableName: 'RetailPerformance', variant: 'retailSingle', valueField: 'UnitsSold', aggregate: 'sum', categoryField: 'Month', detailBinding: false, detailAndColorBinding: true, detailAndColorField: 'ProductLine', title: 'Units sold by product line' }, { name: DataChartType.rangeBar, type: DataChartType.rangeBar, tableName: 'RetailPerformance', variant: 'retailRange', categoryField: 'Month', detailBinding: false, detailAndColorBinding: true, detailAndColorField: 'ProductLine', title: 'Revenue forecast band by product line' }, { name: DataChartType.stackedBar, type: DataChartType.stackedBar, tableName: 'RetailMix', variant: 'mixStacked', categoryField: 'Quarter', title: 'Quarterly offer revenue mix' }, { name: DataChartType.percentStackedBar, type: DataChartType.percentStackedBar, tableName: 'RetailMix', variant: 'percentMix', categoryField: 'Quarter', title: 'Quarterly revenue share by offer type' }, { name: DataChartType.area, type: DataChartType.area, tableName: 'RetailPerformance', variant: 'retailSingle', valueField: 'Revenue', aggregate: 'sum', categoryField: 'Month', detailBinding: false, title: 'Revenue trend by month' }, { name: DataChartType.rangeArea, type: DataChartType.rangeArea, tableName: 'RetailPerformance', variant: 'retailRange', categoryField: 'Month', detailBinding: false, title: 'Revenue planning range by month' }, { name: DataChartType.stackedArea, type: DataChartType.stackedArea, tableName: 'RetailMix', variant: 'mixStacked', categoryField: 'Month', title: 'Offer revenue mix trend' }, { name: DataChartType.percentStackedArea, type: DataChartType.percentStackedArea, tableName: 'RetailMix', variant: 'percentMix', categoryField: 'Month', title: 'Revenue share trend by offer type' }, { name: DataChartType.line, type: DataChartType.line, tableName: 'RetailPerformance', variant: 'retailSingle', valueField: 'MarginRate', aggregate: 'avg', categoryField: 'Month', detailBinding: false, title: 'Average margin rate by month' }, { name: DataChartType.pie, type: DataChartType.pie, tableName: 'RetailPerformance', variant: 'pieLike', valueField: 'Revenue', aggregate: 'sum', categoryField: 'ProductLine', title: 'Product line revenue share' }, { name: DataChartType.donut, type: DataChartType.donut, tableName: 'RetailPerformance', variant: 'pieLike', valueField: 'Revenue', aggregate: 'sum', categoryField: 'ProductLine', title: 'Product line revenue contribution' }, { name: DataChartType.rose, type: DataChartType.rose, tableName: 'RetailPerformance', variant: 'pieLike', valueField: 'UnitsSold', aggregate: 'sum', categoryField: 'Month', detailAndColorField: 'ProductLine', title: 'Units sold by product line' }, { name: DataChartType.radialStackedBar, type: DataChartType.radialStackedBar, tableName: 'RetailMix', variant: 'mixStacked', categoryField: 'Quarter', title: 'Quarterly offer mix in radial layout' }, { name: DataChartType.sunburst, type: DataChartType.sunburst, tableName: 'RetailPerformance', variant: 'hierarchy', title: 'Quarter to product line revenue hierarchy' }, { name: DataChartType.polarCoordinatesBar, type: DataChartType.polarCoordinatesBar, tableName: 'RetailPerformance', variant: 'retailSingle', valueField: 'UnitsSold', aggregate: 'sum', categoryField: 'ProductLine', title: 'Units sold in polar coordinates' }, { name: DataChartType.polarCoordinatesStackedBar, type: DataChartType.polarCoordinatesStackedBar, tableName: 'RetailMix', variant: 'mixStacked', categoryField: 'Quarter', title: 'Quarterly offer mix in polar coordinates' }, { name: DataChartType.radar, type: DataChartType.radar, tableName: 'RetailMix', variant: 'mixSingle', categoryField: 'Month', title: 'Monthly offer revenue profile' }, { name: DataChartType.filledRadar, type: DataChartType.filledRadar, tableName: 'RetailMix', variant: 'mixSingle', categoryField: 'Month', title: 'Filled monthly offer revenue profile' }, { name: DataChartType.scatter, type: DataChartType.scatter, tableName: 'CustomerPortfolio', variant: 'scatter', title: 'Acquisition cost versus lifetime value by segment' }, { name: DataChartType.bubble, type: DataChartType.bubble, tableName: 'CustomerPortfolio', variant: 'bubble', title: 'Acquisition cost, lifetime value, and deal size by segment' }, { name: DataChartType.treemap, type: DataChartType.treemap, tableName: 'RetailPerformance', variant: 'hierarchy', title: 'Quarter to product line revenue footprint' }, { name: DataChartType.funnel, type: DataChartType.funnel, tableName: 'SalesPipeline', variant: 'funnel', title: 'Pipeline conversion by stage' }, { name: DataChartType.waterfall, type: DataChartType.waterfall, tableName: 'ProfitBridge', variant: 'waterfall', title: 'Profit bridge by operational driver' }, { name: DataChartType.candlestick, type: DataChartType.candlestick, tableName: 'StockPerformance', variant: 'stock', title: 'Daily price movement by market and desk' }, { name: DataChartType.ohlc, type: DataChartType.ohlc, tableName: 'StockPerformance', variant: 'stock', title: 'Daily open, high, low, and close by market and desk' }, ]; } function addChartToSheet(sheet, cfg) { const dataChart = sheet.dataCharts.add(cfg.name, 20, 20, 960, 620, cfg.type); dataChart.setChartConfig({ tableName: cfg.tableName, plots: [{ type: cfg.type, encodings: buildEncodings(cfg), config: buildPlotConfig(cfg), }], config: { header: { title: cfg.title, padding: { left: 10, right: 10, top: 10, bottom: 10 }, textStyle: { fontSize: 16, alignment: GC.Spread.Sheets.DataCharts.HAlign.left, }, }, }, }); } function buildEncodings(cfg) { const Aggregate = GC.Spread.Sheets.DataCharts.Aggregate; const retailFacet = { row: { field: 'Region' }, column: { field: 'Channel' } }; const financeFacet = { row: { field: 'Market' }, column: { field: 'Desk' } }; const aggregate = cfg.aggregate === 'avg' ? Aggregate.avg : Aggregate.sum; switch (cfg.variant) { case 'retailSingle': return { values: [{ field: cfg.valueField, aggregate, }], category: { field: cfg.categoryField }, ...(cfg.detailAndColorBinding ? getDetailAndColorFields(cfg.detailAndColorField || cfg.categoryField) : (cfg.detailBinding === false ? {} : { details: getRetailDetailFields(cfg.categoryField) })), ...retailFacet, }; case 'retailRange': return { values: [{ vectors: { lower: { field: 'ForecastLow' }, upper: { field: 'ForecastHigh' }, }, aggregate: Aggregate.sum, }], category: { field: cfg.categoryField }, ...(cfg.detailAndColorBinding ? getDetailAndColorFields(cfg.detailAndColorField || cfg.categoryField) : (cfg.detailBinding === false ? {} : { details: getRetailDetailFields(cfg.categoryField) })), ...retailFacet, }; case 'percentMix': return { values: [{ field: 'MixValue', aggregate: Aggregate.sum, }], category: { field: cfg.categoryField }, details: [{ field: 'OfferType' }], color: { field: 'OfferType' }, ...retailFacet, }; case 'mixSingle': return { values: [{ field: 'MixValue', aggregate: Aggregate.sum, }], category: { field: cfg.categoryField }, details: [{ field: 'OfferType' }], color: { field: 'OfferType' }, ...retailFacet, }; case 'mixStacked': return { values: [{ field: 'MixValue', aggregate: Aggregate.sum, }], category: { field: cfg.categoryField }, details: [{ field: 'OfferType' }], color: { field: 'OfferType' }, ...retailFacet, }; case 'pieLike': return { values: [{ field: cfg.valueField, aggregate, }], category: { field: cfg.categoryField }, ...getDetailAndColorFields(cfg.detailAndColorField || cfg.categoryField), ...retailFacet, }; case 'radar': return { values: [{ field: 'MarginRate', aggregate: Aggregate.avg, }], category: { field: 'Month' }, details: [{ field: 'Quarter' }], color: { field: 'ProductLine' }, ...retailFacet, }; case 'hierarchy': return { values: [{ field: 'Revenue', aggregate: Aggregate.sum, }], details: [{ field: 'Quarter' }, { field: 'ProductLine' }], tooltip: [{ field: 'Quarter' }, { field: 'ProductLine' }, { field: 'Revenue' }], ...retailFacet, }; case 'scatter': return { values: [ { field: 'AcquisitionCost', aggregate: Aggregate.avg }, { field: 'LifetimeValue', aggregate: Aggregate.avg }, ], category: { field: 'Segment' }, details: [{ field: 'Lifecycle' }], color: { field: 'Segment' }, ...retailFacet, }; case 'bubble': return { values: [ { field: 'AcquisitionCost', aggregate: Aggregate.avg }, { field: 'LifetimeValue', aggregate: Aggregate.avg }, ], category: { field: 'Segment' }, details: [{ field: 'Lifecycle' }], size: { field: 'DealSize' }, color: { field: 'Segment' }, ...retailFacet, }; case 'funnel': return { values: [{ field: 'Count', aggregate: Aggregate.sum, }], category: { field: 'Stage' }, details: [{ field: 'Quarter' }], ...retailFacet, }; case 'waterfall': return { values: [{ field: 'Impact', aggregate: Aggregate.sum, }], category: { field: 'Driver' }, ...retailFacet, }; case 'stock': return { values: [{ vectors: { open: { field: 'open' }, high: { field: 'high' }, low: { field: 'low' }, close: { field: 'close' }, }, }], category: { field: 'Day' }, ...financeFacet, }; default: return {}; } } function getRetailDetailFields(categoryField) { if (categoryField === 'Quarter') { return [{ field: 'Month' }]; } return [{ field: 'Quarter' }]; } function getDetailAndColorFields(field) { return { details: [{ field }], color: { field }, }; } function buildPlotConfig(cfg) { if (cfg.type === GC.Spread.Sheets.DataCharts.DataChartType.treemap) { return { treemap: { header: { template: '{detailFields.value}', }, }, }; } return undefined; } function createSpreadAndDesigner() { const demoHost = document.getElementById('demo-host'); if (window !== top) { const spread = new GC.Spread.Sheets.Workbook('spread-host', { sheetCount: 1 }); return { spread }; } const designer = new GC.Spread.Sheets.Designer.Designer(demoHost, undefined, undefined, { sheetCount: 1 }); return { designer, spread: designer.getWorkbook(), }; } async function initDataManager(spread) { const dataManager = spread.dataManager(); await dataManager.addTable('RetailPerformance', { data: createRetailPerformanceData(), }).fetch(); await dataManager.addTable('RetailMix', { data: createRetailMixData(), }).fetch(); await dataManager.addTable('CustomerPortfolio', { data: createCustomerPortfolioData(), }).fetch(); await dataManager.addTable('SalesPipeline', { data: createSalesPipelineData(), }).fetch(); await dataManager.addTable('ProfitBridge', { data: createProfitBridgeData(), }).fetch(); await dataManager.addTable('StockPerformance', { data: createStockPerformanceData(), }).fetch(); } function createRetailPerformanceData() { const regions = [ { name: 'North America', factor: 1.18, marginLift: 0.01 }, { name: 'Europe', factor: 0.97, marginLift: -0.01 }, ]; const channels = [ { name: 'Retail', factor: 1.08, marginLift: 0.0 }, { name: 'Online', factor: 0.94, marginLift: 0.01 }, ]; const months = [ { month: 'Jan', quarter: 'Q1', base: 180, marginLift: 0.0 }, { month: 'Feb', quarter: 'Q1', base: 198, marginLift: 0.01 }, { month: 'Apr', quarter: 'Q2', base: 225, marginLift: 0.015 }, { month: 'May', quarter: 'Q2', base: 238, marginLift: 0.02 }, ]; const productLines = [ { name: 'Laptops', revenueFactor: 1.55, unitsFactor: 0.42, marginBase: 0.32 }, { name: 'Accessories', revenueFactor: 0.62, unitsFactor: 1.18, marginBase: 0.39 }, { name: 'Software', revenueFactor: 0.88, unitsFactor: 0.58, marginBase: 0.45 }, { name: 'Services', revenueFactor: 0.74, unitsFactor: 0.36, marginBase: 0.49 }, ]; const records = []; regions.forEach((region) => { channels.forEach((channel) => { months.forEach((period, monthIndex) => { productLines.forEach((product) => { const seasonalLift = 1 + monthIndex * 0.08; const revenue = Math.round(period.base * region.factor * channel.factor * product.revenueFactor * seasonalLift); records.push({ Region: region.name, Channel: channel.name, Quarter: period.quarter, Month: period.month, ProductLine: product.name, Revenue: revenue, UnitsSold: Math.round(revenue * product.unitsFactor), MarginRate: roundNumber(product.marginBase + period.marginLift + region.marginLift + channel.marginLift, 2), ForecastLow: Math.round(revenue * 0.88), ForecastHigh: Math.round(revenue * 1.12), }); }); }); }); }); return records; } function createRetailMixData() { const regions = [ { name: 'North America', factor: 1.16, servicesLift: 1.1 }, { name: 'Europe', factor: 0.95, servicesLift: 0.92 }, ]; const channels = [ { name: 'Retail', deviceLift: 1.18, accessoryLift: 1.08, softwareLift: 0.82, servicesLift: 0.9 }, { name: 'Online', deviceLift: 0.86, accessoryLift: 0.95, softwareLift: 1.24, servicesLift: 1.18 }, ]; const months = [ { month: 'Jan', quarter: 'Q1', device: 360, accessory: 118, software: 154, services: 86 }, { month: 'Feb', quarter: 'Q1', device: 335, accessory: 126, software: 172, services: 98 }, { month: 'Apr', quarter: 'Q2', device: 308, accessory: 138, software: 205, services: 126 }, { month: 'May', quarter: 'Q2', device: 286, accessory: 146, software: 232, services: 148 }, ]; const offerTypes = [ { name: 'Devices', key: 'device', channelLift: 'deviceLift' }, { name: 'Accessories', key: 'accessory', channelLift: 'accessoryLift' }, { name: 'Software', key: 'software', channelLift: 'softwareLift' }, { name: 'Services', key: 'services', channelLift: 'servicesLift', regionLift: 'servicesLift' }, ]; const records = []; regions.forEach((region) => { channels.forEach((channel) => { months.forEach((period) => { offerTypes.forEach((offer) => { const regionLift = offer.regionLift ? region[offer.regionLift] : 1; records.push({ Region: region.name, Channel: channel.name, Quarter: period.quarter, Month: period.month, OfferType: offer.name, MixValue: Math.round(period[offer.key] * region.factor * regionLift * channel[offer.channelLift]), }); }); }); }); }); return records; } function createCustomerPortfolioData() { const regions = [ { name: 'North America', costFactor: 1.12, valueFactor: 1.15 }, { name: 'Europe', costFactor: 0.94, valueFactor: 0.98 }, ]; const channels = [ { name: 'Retail', costFactor: 1.05, valueFactor: 0.92 }, { name: 'Online', costFactor: 0.9, valueFactor: 1.08 }, ]; const segments = [ { name: 'SMB', acquisitionCost: 68, lifetimeValue: 180, dealSize: 24 }, { name: 'Mid-Market', acquisitionCost: 92, lifetimeValue: 248, dealSize: 38 }, { name: 'Enterprise', acquisitionCost: 138, lifetimeValue: 332, dealSize: 56 }, { name: 'Strategic', acquisitionCost: 172, lifetimeValue: 405, dealSize: 74 }, ]; const lifecycleStages = [ { name: 'New', costFactor: 1.18, valueFactor: 0.72, dealFactor: 0.68 }, { name: 'Growing', costFactor: 0.98, valueFactor: 1.0, dealFactor: 1.0 }, { name: 'Renewal', costFactor: 0.74, valueFactor: 1.24, dealFactor: 1.18 }, ]; const records = []; regions.forEach((region) => { channels.forEach((channel) => { segments.forEach((segment, index) => { const segmentLift = 1 + index * 0.06; lifecycleStages.forEach((stage, stageIndex) => { records.push({ Region: region.name, Channel: channel.name, Segment: segment.name, Lifecycle: stage.name, AcquisitionCost: Math.round(segment.acquisitionCost * region.costFactor * channel.costFactor * segmentLift * stage.costFactor), LifetimeValue: Math.round(segment.lifetimeValue * region.valueFactor * channel.valueFactor * segmentLift * stage.valueFactor), DealSize: Math.round(segment.dealSize * region.valueFactor * segmentLift * stage.dealFactor + stageIndex * 2), }); }); }); }); }); return records; } function createSalesPipelineData() { const regions = [ { name: 'North America', factor: 1.16 }, { name: 'Europe', factor: 0.93 }, ]; const channels = [ { name: 'Retail', factor: 1.04 }, { name: 'Online', factor: 0.96 }, ]; const stages = [ { name: 'Prospects', factor: 1.0 }, { name: 'Qualified', factor: 0.73 }, { name: 'Proposal', factor: 0.54 }, { name: 'Negotiation', factor: 0.36 }, { name: 'Closed Won', factor: 0.24 }, ]; const quarters = [ { name: 'Q1', factor: 1.0 }, { name: 'Q2', factor: 1.12 }, ]; const records = []; regions.forEach((region) => { channels.forEach((channel) => { const base = 420 * region.factor * channel.factor; quarters.forEach((quarter, quarterIndex) => { stages.forEach((stage, index) => { records.push({ Region: region.name, Channel: channel.name, Quarter: quarter.name, Stage: stage.name, Count: Math.round(base * quarter.factor * stage.factor - index * 6 + quarterIndex * 10), }); }); }); }); }); return records; } function createProfitBridgeData() { const regions = [ { name: 'North America', factor: 1.12 }, { name: 'Europe', factor: 0.91 }, ]; const channels = [ { name: 'Retail', factor: 1.08 }, { name: 'Online', factor: 0.95 }, ]; const drivers = [ { name: 'Subscription Renewals', impact: 180 }, { name: 'Cross-sell Campaigns', impact: 95 }, { name: 'Logistics Inflation', impact: -72 }, { name: 'Support Hiring', impact: -48 }, { name: 'Price Optimization', impact: 126 }, { name: 'FX Impact', impact: -34 }, { name: 'Services Expansion', impact: 88 }, ]; const records = []; regions.forEach((region) => { channels.forEach((channel) => { drivers.forEach((driver, index) => { const channelBias = channel.name === 'Retail' ? 8 : -6; records.push({ Region: region.name, Channel: channel.name, Driver: driver.name, Impact: Math.round(driver.impact * region.factor * channel.factor + channelBias - index * 2), }); }); }); }); return records; } function createStockPerformanceData() { const markets = [ { name: 'NASDAQ', basePrice: 184, drift: 1.55 }, { name: 'Euronext', basePrice: 132, drift: 1.12 }, ]; const desks = [ { name: 'Institutional', offset: 1.8, volatility: 1.35 }, { name: 'Wealth', offset: -0.9, volatility: 0.95 }, ]; const days = ['2026-04-01', '2026-04-02', '2026-04-03', '2026-04-06', '2026-04-07', '2026-04-08', '2026-04-09', '2026-04-10']; const records = []; markets.forEach((market) => { desks.forEach((desk) => { days.forEach((day, index) => { const base = market.basePrice + desk.offset + index * market.drift; const swing = ((index % 3) - 1) * desk.volatility; const open = roundNumber(base + swing, 2); const close = roundNumber(open + (index % 2 === 0 ? 1.14 : -0.78) + desk.volatility * 0.18, 2); const high = roundNumber(Math.max(open, close) + 1.25 + desk.volatility * 0.45, 2); const low = roundNumber(Math.min(open, close) - 1.08 - desk.volatility * 0.35, 2); records.push({ Market: market.name, Desk: desk.name, Day: day, open, high, low, close, }); }); }); }); return records; } function roundNumber(value, digits) { return Number(value.toFixed(digits)); }
<!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/purejs/node_modules/@mescius/spread-sheets/styles/gc.spread.sheets.excel2013white.css"> <link rel="stylesheet" type="text/css" href="$DEMOROOT$/en/purejs/node_modules/@mescius/spread-sheets-designer/styles/gc.spread.sheets.designer.light.min.css"> <link rel="stylesheet" type="text/css" href="styles.css"> <script src="$DEMOROOT$/en/purejs/node_modules/@mescius/spread-sheets/dist/gc.spread.sheets.all.min.js" type="text/javascript"></script> <script src="$DEMOROOT$/en/purejs/node_modules/@mescius/spread-sheets-print/dist/gc.spread.sheets.print.min.js" type="text/javascript"></script> <script src="$DEMOROOT$/en/purejs/node_modules/@mescius/spread-sheets-shapes/dist/gc.spread.sheets.shapes.min.js" type="text/javascript"></script> <script src="$DEMOROOT$/en/purejs/node_modules/@mescius/spread-sheets-datacharts-addon/dist/gc.spread.sheets.datacharts.min.js" type="text/javascript"></script> <script src="$DEMOROOT$/en/purejs/node_modules/@mescius/spread-sheets-tablesheet/dist/gc.spread.sheets.tablesheet.min.js" type="text/javascript"></script> <script src="$DEMOROOT$/spread/source/js/license.js" type="text/javascript"></script> <script> const designerDependencyScripts = [ '$DEMOROOT$/en/purejs/node_modules/@mescius/spread-sheets-charts/dist/gc.spread.sheets.charts.min.js', '$DEMOROOT$/en/purejs/node_modules/@mescius/spread-sheets-barcode/dist/gc.spread.sheets.barcode.min.js', '$DEMOROOT$/en/purejs/node_modules/@mescius/spread-sheets-pdf/dist/gc.spread.sheets.pdf.min.js', '$DEMOROOT$/en/purejs/node_modules/@mescius/spread-sheets-pivot-addon/dist/gc.spread.pivot.pivottables.min.js', '$DEMOROOT$/en/purejs/node_modules/@mescius/spread-sheets-slicers/dist/gc.spread.sheets.slicers.min.js', '$DEMOROOT$/en/purejs/node_modules/@mescius/spread-sheets-reportsheet-addon/dist/gc.spread.report.reportsheet.min.js', '$DEMOROOT$/en/purejs/node_modules/@mescius/spread-sheets-ganttsheet/dist/gc.spread.sheets.ganttsheet.min.js', '$DEMOROOT$/en/purejs/node_modules/@mescius/spread-sheets-formula-panel/dist/gc.spread.sheets.formulapanel.min.js', '$DEMOROOT$/en/purejs/node_modules/@mescius/spread-sheets-io/dist/gc.spread.sheets.io.min.js', '$DEMOROOT$/en/purejs/node_modules/@mescius/spread-sheets-designer-resources-en/dist/gc.spread.sheets.designer.resource.en.min.js', '$DEMOROOT$/en/purejs/node_modules/@mescius/spread-sheets-designer/dist/gc.spread.sheets.designer.all.min.js', '$DEMOROOT$/spread/source/js/designer/license.js', ] function appendScriptNode (src) { const script = document.createElement('script'); script.src = src; script.async = false; script.type = 'text/javascript'; document.head.appendChild(script); } if (top === window) { // not in iframe designerDependencyScripts.forEach(appendScriptNode); } </script> <script src="app.js" type="text/javascript"></script> </head> <body> <div class="sample-tutorial"> <div id="demo-host"> <div id="spread-host"></div> <div id="panel-host"></div> </div> </div> </body> </html>
body { position: absolute; top: 0; bottom: 0; left: 0; right: 0; } .sample-tutorial { position: relative; height: 100%; overflow: hidden; } #demo-host { width: 100%; height: 100%; overflow: hidden; float: left; } #spread-host { position: absolute; top: 0; left: 0; width: calc(100% - 380px); height: 100%; } #panel-host { position: absolute; top: 0; right: 0; width: 380px; height: 100%; }