DataManager Source

SpreadJS PivotTable can analyze DataManager tables/views directly without first materializing data into a worksheet range. When DataManager data changes, the PivotTable can refresh automatically to reflect the latest results.

DataManager Source SpreadJS PivotTable can use a DataManager Table or DataManager View as its data source directly, without first materializing the data into a worksheet range or worksheet table. Use the source option to specify the DataManager object: source: "tableName" uses a DataManager Table directly. source: "tableName.viewName" uses a DataManager View under the specified table. A PivotTable can be created from a DataManager Table: A PivotTable can also be created from a DataManager View. A view can expose fields from related tables, so a PivotTable can analyze a relational DataManager model through one view source: Set autoRefresh to control whether the PivotTable cache refreshes automatically when the DataManager source data changes: When autoRefresh is true, the PivotTable reflects DataManager source changes automatically. When autoRefresh is false, DataManager data can continue to change while the PivotTable keeps its current cache until it is refreshed manually. Call updateSource() without arguments to refresh the PivotTable from the current DataManager source: Pass a DataManager source object to change the source or update source options such as autoRefresh:
var spread; var tableSalesTable; var viewSalesTable; var productsTable; var salesMenTable; var salesRegionsTable; var salesAnalysisView; var tablePivotTable; var viewPivotTable; var pivotPanel; var timerId; var isPivotPanelVisible = false; var sourceStates = { DataTable: { recordCount: 0, pivotRefreshTime: "", autoRefresh: true }, DataView: { recordCount: 0, pivotRefreshTime: "", autoRefresh: true } }; var salesManProductMap = {}; var people = ["Alan", "Bob", "John"]; var cars = [ { name: "Mercedes", price: 2700 }, { name: "Focus", price: 500 }, { name: "BMW", price: 1800 }, { name: "Audi", price: 1100 }, { name: "Renault", price: 800 } ]; var productsData = [ { id: 1, name: "Laptop", category: "Electronics", brand: "Northwind", price: 1299 }, { id: 2, name: "Phone", category: "Electronics", brand: "Contoso", price: 899 }, { id: 3, name: "Desk", category: "Furniture", brand: "Fabrikam", price: 420 }, { id: 4, name: "Chair", category: "Furniture", brand: "Fabrikam", price: 180 }, { id: 5, name: "Monitor", category: "Electronics", brand: "Northwind", price: 320 } ]; var salesMenData = [ { id: 1, name: "Alan", title: "Account Executive", team: "Enterprise" }, { id: 2, name: "Bob", title: "Sales Specialist", team: "Retail" }, { id: 3, name: "John", title: "Senior Sales Manager", team: "Enterprise" }, { id: 4, name: "Kevin", title: "Channel Manager", team: "Partner" }, { id: 5, name: "Michael", title: "Sales Specialist", team: "Retail" } ]; var salesRegionsData = [ { id: 1, province: "Beijing", area: "North", manager: "Grace" }, { id: 2, province: "Shanghai", area: "East", manager: "Helen" }, { id: 3, province: "Guangdong", area: "South", manager: "Ivy" }, { id: 4, province: "Zhejiang", area: "East", manager: "Helen" }, { id: 5, province: "Jiangsu", area: "East", manager: "Helen" }, { id: 6, province: "Sichuan", area: "West", manager: "Jack" }, { id: 7, province: "Hubei", area: "Central", manager: "Lily" }, { id: 8, province: "Shandong", area: "North", manager: "Grace" }, { id: 9, province: "Fujian", area: "South", manager: "Ivy" }, { id: 10, province: "Henan", area: "Central", manager: "Lily" }, { id: 11, province: "Hunan", area: "Central", manager: "Lily" }, { id: 12, province: "Shaanxi", area: "West", manager: "Jack" } ]; window.onload = function () { spread = new GC.Spread.Sheets.Workbook(document.getElementById("ss"), { sheetCount: 2 }); initSpread().then(bindEvents); }; function initSpread() { return initDataManagerSources().then(function () { spread.suspendPaint(); initDataTablePivotSheet(); initDataViewPivotSheet(); initPivotPanel(); spread.setActiveSheetIndex(0); markAllPivotRefreshed(); spread.resumePaint(); updateDemoPanel(); }); } function initDataManagerSources() { return Promise.all([ initDataTableSource(), initDataViewSource() ]); } function initDataTableSource() { var sourceData = createInitialTableSourceData(); sourceStates.DataTable.recordCount = sourceData.length; tableSalesTable = spread.dataManager().addTable("TableSales", { data: sourceData, schema: { columns: { date: { dataType: "date" }, salesperson: { dataType: "string" }, car: { dataType: "string" }, quantity: { dataType: "number" }, price: { dataType: "number" }, total: { dataType: "number" } } } }); return tableSalesTable.fetch(); } function initDataViewSource() { var dataManager = spread.dataManager(); salesManProductMap = createSalesManProductMap(); var sourceData = createInitialViewSourceData(); sourceStates.DataView.recordCount = sourceData.length; productsTable = dataManager.addTable("Products", { data: productsData, schema: { columns: { id: { dataType: "number", isPrimaryKey: true }, name: { dataType: "string" }, category: { dataType: "string" }, brand: { dataType: "string" }, price: { dataType: "number" } } } }); productsTable.primaryKey("id"); salesMenTable = dataManager.addTable("SalesMen", { data: salesMenData, schema: { columns: { id: { dataType: "number", isPrimaryKey: true }, name: { dataType: "string" }, title: { dataType: "string" }, team: { dataType: "string" } } } }); salesMenTable.primaryKey("id"); salesRegionsTable = dataManager.addTable("SalesRegions", { data: salesRegionsData, schema: { columns: { id: { dataType: "number", isPrimaryKey: true }, province: { dataType: "string" }, area: { dataType: "string" }, manager: { dataType: "string" } } } }); salesRegionsTable.primaryKey("id"); viewSalesTable = dataManager.addTable("Sales", { data: sourceData, schema: { columns: { date: { dataType: "date" }, productId: { dataType: "number" }, salesManId: { dataType: "number" }, regionId: { dataType: "number" }, quantity: { dataType: "number" }, price: { dataType: "number" }, salesTotal: { dataType: "formula", value: "=[@quantity] * [@price]" }, costTotal: { dataType: "formula", value: "=[@quantity] * [@product.price]" } } } }); dataManager.addRelationship(viewSalesTable, "productId", "product", productsTable, "id", "sales"); dataManager.addRelationship(viewSalesTable, "salesManId", "salesMan", salesMenTable, "id", "sales"); dataManager.addRelationship(viewSalesTable, "regionId", "salesRegion", salesRegionsTable, "id", "sales"); salesAnalysisView = viewSalesTable.addView("SalesAnalysisView", [ "date", // "productId", // "salesManId", // "regionId", "quantity", "price", "salesTotal", "costTotal", // "product.id", "product.name", "product.category", "product.brand", "product.price", // "salesMan.id", "salesMan.name", "salesMan.title", "salesMan.team", // "salesRegion.id", "salesRegion.province", "salesRegion.area", "salesRegion.manager" ]); return Promise.all([ productsTable.fetch(), salesMenTable.fetch(), salesRegionsTable.fetch(), viewSalesTable.fetch(), salesAnalysisView.fetch() ]); } function initDataTablePivotSheet() { var sheet = spread.getSheet(0); sheet.name("DataTable PivotSource"); sheet.setRowCount(1000); tablePivotTable = sheet.pivotTables.add( "dataTablePivot", getDataTablePivotSource(), 1, 1, GC.Spread.Pivot.PivotTableLayoutType.outline, GC.Spread.Pivot.PivotTableThemes.medium8, { showRowHeader: true, showColumnHeader: true, bandRows: true, bandColumns: true } ); applyDataTablePivotLayout(); } function initDataViewPivotSheet() { var sheet = spread.getSheet(1); sheet.name("DataView PivotSource"); sheet.setRowCount(1000); sheet.setColumnCount(30); viewPivotTable = sheet.pivotTables.add( "dataViewPivot", getDataViewPivotSource(), 1, 1, GC.Spread.Pivot.PivotTableLayoutType.outline, GC.Spread.Pivot.PivotTableThemes.medium8, { showRowHeader: true, showColumnHeader: true, bandRows: true, bandColumns: true } ); applyDataViewPivotLayout(); } function initPivotPanel() { pivotPanel = new GC.Spread.Pivot.PivotPanel("dmPivotPanel", tablePivotTable, document.getElementById("pivotPanel")); pivotPanel.sectionVisibility(GC.Spread.Pivot.PivotPanelSection.fields + GC.Spread.Pivot.PivotPanelSection.area); } function applyDataTablePivotLayout() { tablePivotTable.suspendLayout(); tablePivotTable.add("salesperson", "Salesperson", GC.Spread.Pivot.PivotTableFieldType.rowField); tablePivotTable.add("car", "Cars", GC.Spread.Pivot.PivotTableFieldType.rowField); var groupInfo = { originFieldName: "date", dateGroups: [{ by: GC.Pivot.DateGroupType.quarters }] }; tablePivotTable.group(groupInfo); tablePivotTable.add("Quarters (date)", "Quarters (date)", GC.Spread.Pivot.PivotTableFieldType.columnField); tablePivotTable.add("price", "Average Price", GC.Spread.Pivot.PivotTableFieldType.valueField, GC.Pivot.SubtotalType.average, 0); tablePivotTable.add("total", "Totals", GC.Spread.Pivot.PivotTableFieldType.valueField, GC.Pivot.SubtotalType.sum, 1); tablePivotTable.resumeLayout(); setAveragePriceFieldFormat(); tablePivotTable.autoFitColumn(); setPivotValueColumnWidth(spread.getSheet(0)); } function applyDataViewPivotLayout() { viewPivotTable.suspendLayout(); viewPivotTable.add("salesMan.name", "salesMan.name", GC.Spread.Pivot.PivotTableFieldType.rowField); viewPivotTable.add("product.name", "product.name", GC.Spread.Pivot.PivotTableFieldType.rowField); viewPivotTable.add("salesRegion.province", "salesRegion.province", GC.Spread.Pivot.PivotTableFieldType.columnField); viewPivotTable.add("salesTotal", "salesTotal", GC.Spread.Pivot.PivotTableFieldType.valueField, GC.Pivot.SubtotalType.sum); viewPivotTable.resumeLayout(); setDataViewValueFieldFormat(); viewPivotTable.autoFitColumn(); setPivotValueColumnWidth(spread.getSheet(1)); } function setAveragePriceFieldFormat() { setPivotValueFieldFormat(tablePivotTable, "Average Price"); } function setDataViewValueFieldFormat() { setPivotValueFieldFormat(viewPivotTable, "salesTotal"); setPivotValueFieldFormat(viewPivotTable, "Sum of salesTotal"); setPivotValueFieldFormat(viewPivotTable, "costTotal"); setPivotValueFieldFormat(viewPivotTable, "Sum of costTotal"); } function setPivotValueFieldFormat(pivotTable, fieldName) { var style = new GC.Spread.Sheets.Style(); style.formatter = "#,##0.00"; pivotTable.setStyle({ dataOnly: true, references: [ { fieldName: "Values", items: [fieldName] } ] }, style); } function setPivotValueColumnWidth(sheet) { for (var col = 3; col <= 14; col++) { sheet.setColumnWidth(col, 92); } } function bindEvents() { bindWorkbookEvents(); document.getElementById("autoRefresh").addEventListener("change", function () { var state = getActiveState(); state.autoRefresh = this.checked; getActivePivotTable().updateSource(getActivePivotSource()); updateDemoPanel(); }); document.getElementById("startFeed").addEventListener("click", startFeed); document.getElementById("pauseFeed").addEventListener("click", pauseFeed); document.getElementById("manualRefresh").addEventListener("click", function () { spread.suspendPaint(); getActivePivotTable().updateSource(); spread.resumePaint(); updateDemoPanel(true); }); document.getElementById("resetData").addEventListener("click", resetData); document.getElementById("togglePivotPanel").addEventListener("click", togglePivotPanel); } function bindWorkbookEvents() { spread.bind(GC.Spread.Sheets.Events.ActiveSheetChanged, function () { pauseFeed(); attachPivotPanelToActiveSheet(); updateDemoPanel(); }); } function attachPivotPanelToActiveSheet() { var pivotTable = getActivePivotTable(); if (pivotPanel && pivotTable) { pivotPanel.attach(pivotTable); } } function togglePivotPanel() { isPivotPanelVisible = !isPivotPanelVisible; updatePivotPanelVisible(); } function updatePivotPanelVisible() { var panelContainer = document.getElementById("pivotPanelContainer"); var spreadHost = document.getElementById("ss"); if (isPivotPanelVisible) { panelContainer.classList.remove("pivot-panel-hidden"); spreadHost.style.width = "calc(100% - 600px)"; } else { panelContainer.classList.add("pivot-panel-hidden"); spreadHost.style.width = "calc(100% - 300px)"; } document.getElementById("togglePivotPanel").value = isPivotPanelVisible ? "Hide Pivot Panel" : "Show Pivot Panel"; spread.refresh(); } function getDataTablePivotSource() { return { source: toPivotSourceText("TableSales"), autoRefresh: sourceStates.DataTable.autoRefresh }; } function getDataViewPivotSource() { return { source: toPivotSourceText("Sales") + "." + toPivotSourceText("SalesAnalysisView"), autoRefresh: sourceStates.DataView.autoRefresh }; } function getActivePivotSource() { if (getActiveSourceType() === "DataView") { return getDataViewPivotSource(); } return getDataTablePivotSource(); } function toPivotSourceText(name) { var calcEngine = GC.Spread.Sheets.CalcEngine; var expression = calcEngine.formulaToExpression(null, "SourceName"); expression.value = name; return calcEngine.expressionToFormula(null, expression); } function getActivePivotTable() { if (getActiveSourceType() === "DataView") { return viewPivotTable; } return tablePivotTable; } function getActiveSourceType() { return spread.getActiveSheetIndex() === 1 ? "DataView" : "DataTable"; } function getActiveState() { return sourceStates[getActiveSourceType()]; } function startFeed() { if (!timerId) { timerId = setInterval(addLiveRecord, 2000); addLiveRecord(); } } function pauseFeed() { if (timerId) { clearInterval(timerId); timerId = null; } updateDemoPanel(); } function resetData() { pauseFeed(); if (getActiveSourceType() === "DataView") { resetDataViewSource(); } else { resetDataTableSource(); } } function resetDataTableSource() { removeDataManagerTable("TableSales"); initDataTableSource().then(function () { spread.suspendPaint(); tablePivotTable.updateSource(getDataTablePivotSource()); spread.resumePaint(); updateDemoPanel(true); }); } function resetDataViewSource() { removeDataManagerTable("Sales"); removeDataManagerTable("Products"); removeDataManagerTable("SalesMen"); removeDataManagerTable("SalesRegions"); initDataViewSource().then(function () { spread.suspendPaint(); viewPivotTable.updateSource(getDataViewPivotSource()); spread.resumePaint(); updateDemoPanel(true); }); } function removeDataManagerTable(tableName) { var dataManager = spread.dataManager(); if (dataManager.tables && dataManager.tables[tableName]) { dataManager.removeTable(tableName); } } function addLiveRecord() { var sourceType = getActiveSourceType(); var state = sourceStates[sourceType]; var records = sourceType === "DataView" ? createViewLiveRecords(state.recordCount) : createTableLiveRecords(state.recordCount); var table = sourceType === "DataView" ? viewSalesTable : tableSalesTable; for (var i = 0; i < records.length; i++) { table.insertItem(state.recordCount + i, records[i]); } state.recordCount += records.length; updateDemoPanel(state.autoRefresh); } function createInitialTableSourceData() { var headers = pivotSales[0]; var data = []; for (var i = 1; i < pivotSales.length; i++) { var row = pivotSales[i]; var item = {}; for (var col = 0; col < headers.length; col++) { item[headers[col]] = row[col]; } item.price = item.price / 10; if (item.total === undefined) { item.total = item.quantity * item.price; } else { item.total = item.total / 10; } if (people.indexOf(item.salesperson) >= 0) { data.push(item); } } return data; } function createInitialViewSourceData() { var data = []; for (var i = 0; i < 60; i++) { data.push(createViewSalesRecord(i)); } return data; } function createTableLiveRecords(startIndex) { var records = []; for (var personIndex = 0; personIndex < people.length; personIndex++) { for (var carIndex = 0; carIndex < cars.length; carIndex++) { var car = cars[carIndex]; var index = startIndex + records.length; var quantity = 1 + ((index + personIndex + carIndex) % 8); records.push({ date: new Date(2020, index % 12, 1 + (index % 27)), salesperson: people[personIndex], car: car.name, quantity: quantity, price: car.price, total: quantity * car.price }); } } return records; } function createViewLiveRecords(startIndex) { var records = []; for (var i = 0; i < 20; i++) { records.push(createRandomViewSalesRecord(startIndex + i)); } return records; } function createViewSalesRecord(index) { var salesMan = salesMenData[index % salesMenData.length]; var productIds = salesManProductMap[salesMan.id] || productsData.map(function (product) { return product.id; }); var productId = productIds[Math.floor(index / salesMenData.length) % productIds.length]; var product = getProductById(productId); var quantity = 1 + (index % 9); return { date: new Date(2024, index % 12, 1 + (index % 27)), productId: productId, salesManId: salesMan.id, regionId: salesRegionsData[index % salesRegionsData.length].id, quantity: quantity, price: getSalesPrice(product, index) }; } function createRandomViewSalesRecord(index) { var salesMan = salesMenData[Math.floor(Math.random() * salesMenData.length)]; var productIds = salesManProductMap[salesMan.id] || productsData.map(function (product) { return product.id; }); var productId = productIds[Math.floor(Math.random() * productIds.length)]; var product = getProductById(productId); var quantity = 1 + (index % 9); return { date: new Date(2024, Math.floor(Math.random() * 12), 1 + Math.floor(Math.random() * 27)), productId: productId, salesManId: salesMan.id, regionId: salesRegionsData[Math.floor(Math.random() * salesRegionsData.length)].id, quantity: quantity, price: getSalesPrice(product, index) }; } function getProductById(productId) { for (var i = 0; i < productsData.length; i++) { if (productsData[i].id === productId) { return productsData[i]; } } return productsData[0]; } function getSalesPrice(product, index) { var markup = 1.12 + ((index % 5) * 0.04); return Math.round(product.price * markup); } function createSalesManProductMap() { var result = {}; var allProductIds = productsData.map(function (product) { return product.id; }); for (var i = 0; i < salesMenData.length; i++) { result[salesMenData[i].id] = []; } for (var productIndex = 0; productIndex < allProductIds.length; productIndex++) { result[salesMenData[productIndex % salesMenData.length].id].push(allProductIds[productIndex]); } for (var salesManIndex = 0; salesManIndex < salesMenData.length; salesManIndex++) { var salesMan = salesMenData[salesManIndex]; var products = allProductIds.slice(); shuffleArray(products); var targetCount = 3 + Math.floor(Math.random() * 3); for (var i = 0; i < products.length && result[salesMan.id].length < targetCount; i++) { if (result[salesMan.id].indexOf(products[i]) < 0) { result[salesMan.id].push(products[i]); } } } return result; } function shuffleArray(items) { for (var i = items.length - 1; i > 0; i--) { var j = Math.floor(Math.random() * (i + 1)); var temp = items[i]; items[i] = items[j]; items[j] = temp; } } function updateDemoPanel(pivotRefreshed) { var sourceType = getActiveSourceType(); var state = sourceStates[sourceType]; if (pivotRefreshed) { state.pivotRefreshTime = getTimeText(); } document.getElementById("panelTitle").textContent = sourceType + " PivotSource"; document.getElementById("sourceNote").textContent = getSourceNote(sourceType); document.getElementById("autoRefresh").checked = state.autoRefresh; document.getElementById("startFeed").disabled = !!timerId; document.getElementById("pauseFeed").disabled = !timerId; document.getElementById("manualRefresh").disabled = state.autoRefresh; document.getElementById("recordCount").textContent = state.recordCount.toLocaleString(); document.getElementById("lastPivotRefresh").textContent = state.pivotRefreshTime || "-"; } function markAllPivotRefreshed() { var timeText = getTimeText(); sourceStates.DataTable.pivotRefreshTime = timeText; sourceStates.DataView.pivotRefreshTime = timeText; } function getSourceNote(sourceType) { if (sourceType === "DataView") { return "The DataView PivotSource uses one fact table: Sales, and three dimension tables: Products, SalesMen, and SalesRegions. SalesAnalysisView joins them with relations."; } return "The DataTable PivotSource uses one wide DataManager table. The sales facts and dimension names, such as salesperson and product, are stored directly in the same table."; } function getTimeText() { return new Date().toLocaleTimeString(); }
<!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"> <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-shapes/dist/gc.spread.sheets.shapes.min.js" type="text/javascript"></script> <script src="$DEMOROOT$/en/purejs/node_modules/@mescius/spread-sheets-pivot-addon/dist/gc.spread.pivot.pivottables.min.js" type="text/javascript"></script> <script src="$DEMOROOT$/spread/source/data/pivot-data.js" type="text/javascript"></script> <script src="$DEMOROOT$/spread/source/js/license.js" type="text/javascript"></script> <script src="app.js" type="text/javascript"></script> <link rel="stylesheet" type="text/css" href="styles.css"> </head> <body> <div class="sample-tutorial"> <div id="ss" class="sample-spreadsheets"></div> <div id="pivotPanelContainer" class="sample-panel pivot-panel-hidden"> <div id="pivotPanel" class="pivot-panel"></div> </div> <div id="container" class="options-container"> <div class="dm-source-panel"> <div class="option-block"> <h3 id="panelTitle">DataTable PivotSource</h3> </div> <div class="option-block"> <div id="sourceNote" class="source-note"></div> </div> <div class="option-block checkbox-row"> <input type="checkbox" id="autoRefresh" checked> <label for="autoRefresh">Auto Refresh PivotTable</label> </div> <div class="button-grid"> <input type="button" value="Start Live Updates" id="startFeed"> <input type="button" value="Pause" id="pauseFeed"> <input type="button" value="Manual Refresh" id="manualRefresh"> <input type="button" value="Reset Data" id="resetData"> </div> <div class="status-grid"> <div> <span>Source records</span> <strong id="recordCount"></strong> </div> <div> <span>Last refresh</span> <strong id="lastPivotRefresh"></strong> </div> </div> <div class="option-block panel-toggle-block"> <input type="button" value="Show Pivot Panel" id="togglePivotPanel"> </div> </div> </div> </div> </body> </html>
html, body { height: 100%; margin: 0; } body { position: absolute; inset: 0; overflow: hidden; color: #1f2933; background: #f7f8fa; } .sample-tutorial { display: flex; height: 100%; overflow: hidden; } .sample-spreadsheets { flex: 1 1 auto; min-width: 0; height: 100%; overflow: hidden; } .sample-panel { flex: 0 0 300px; width: 300px; height: 100%; box-sizing: border-box; overflow: auto; background: #ffffff; border-left: 1px solid #e5e7eb; } .pivot-panel { width: 300px; height: 100%; box-sizing: border-box; } #pivotPanel.gc-panel, .pivot-panel .gc-panel { padding: 10px; background-color: #ffffff; border: none; } .sample-panel.pivot-panel-hidden { display: none; } .options-container { flex: 0 0 300px; width: 300px; height: 100%; padding: 24px 18px; box-sizing: border-box; overflow: auto; background: #ffffff; border-left: 1px solid #e5e7eb; } .dm-source-panel { min-height: 100%; display: flex; flex-direction: column; } .option-block { margin-bottom: 12px; } .option-block:first-child { margin-bottom: 0; } .option-block:first-child h3 { margin: 0; color: #111827; font-size: 20px; font-weight: 650; line-height: 1.25; } .option-block:nth-child(2) { padding: 12px 0 18px; margin-bottom: 0; border-bottom: 1px solid #eef0f3; } .source-note { margin: 0; color: #6b7280; font-size: 13px; line-height: 1.6; } .checkbox-row { display: flex; align-items: center; gap: 8px; padding: 18px 0 16px; margin-bottom: 0; border-bottom: 1px solid #eef0f3; } .checkbox-row input[type="checkbox"] { width: 16px; height: 16px; margin: 0; accent-color: #2563eb; flex: 0 0 auto; } .checkbox-row label { margin: 0; color: #374151; font-size: 13px; font-weight: 600; line-height: 1.35; } .button-grid, .status-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; } .button-grid { grid-auto-rows: 34px; padding: 18px 0; margin-bottom: 0; border-bottom: 1px solid #eef0f3; } .status-grid { padding: 18px 0; margin-bottom: 0; } .status-grid div { padding: 10px 12px; background: #ffffff; border: 1px solid #e5e7eb; border-radius: 6px; box-sizing: border-box; } .status-grid span { display: block; margin-bottom: 6px; color: #6b7280; font-size: 12px; } .status-grid strong { display: block; color: #1f2933; font-size: 14px; } .panel-toggle-block { margin-top: auto; margin-bottom: 0; padding-top: 18px; border-top: 1px solid #eef0f3; } .options-container input[type="button"] { display: block; width: 100%; height: 100%; min-height: 34px; padding: 0 10px; box-sizing: border-box; line-height: 1; white-space: nowrap; color: #1f4f82; font: inherit; font-size: 13px; font-weight: 600; cursor: pointer; background: #fff; border: 1px solid #9bb2cf; border-radius: 4px; transition: background-color .15s ease, border-color .15s ease; } .options-container input[type="button"]:hover { background: #edf5ff; border-color: #6f98c8; } .options-container input[type="button"]:disabled { cursor: default; opacity: 0.55; } .options-container input[type="button"]:disabled:hover { background: #fff; border-color: #9bb2cf; } @media (max-width: 760px) { .sample-tutorial { flex-direction: column; } .sample-spreadsheets { min-height: 55%; } .sample-panel, .options-container { flex: 0 0 auto; width: 100%; height: 45%; border-left: 0; border-top: 1px solid #e5e7eb; } }