Seek
Table
Reports
Dashboards
Help
demo@seektable.com
Signup
Dashboards
Northwind Orders
Northwind World Map
ECharts Demos
Custom Filter
Northwind Invoice
Bar chart as a slicer
ECharts export to PDF
Vibecoded Sales Dashboard
Dashboards
→
Edit Dashboard
Save
Delete
Cancel
*
Name
Enable Exports
All-dashboard exports to download multiple reports as a one file.
*
HTML Template
Hint: use copy-paste to edit HTML code in your favourite editor (VSCode, Notepad++ etc).
<div class="stDashboard"> <style> .stDashboard { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 100%; margin: 0; padding: 20px; background: #f5f7fa; color: #1a1a2e; box-sizing: border-box; } .stDashboard *, .stDashboard *::before, .stDashboard *::after { box-sizing: border-box; } .stDashboard .stFilterContainer { background: #fff; border-radius: 12px; padding: 16px 20px; margin-bottom: 20px; box-shadow: 0 1px 3px rgba(0,0,0,0.08); } .stDashboard .stFilter { display: flex; flex-wrap: wrap; gap: 16px; align-items: center; } .stDashboard .stKpiRow { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin-bottom: 20px; } .stDashboard .stKpiCard { background: #fff; border-radius: 12px; padding: 20px 24px; box-shadow: 0 1px 3px rgba(0,0,0,0.08); transition: box-shadow 0.2s; } .stDashboard .stKpiCard:hover { box-shadow: 0 4px 12px rgba(0,0,0,0.12); } .stDashboard .stKpiLabel { font-size: 13px; text-transform: uppercase; letter-spacing: 0.5px; color: #888; margin-bottom: 8px; } .stDashboard .stKpiValue { font-size: 28px; font-weight: 700; color: #1a1a2e; margin-bottom: 8px; } .stDashboard .stKpiDetails { display: flex; gap: 16px; font-size: 13px; } .stDashboard .stKpiDetailItem { display: flex; align-items: baseline; gap: 4px; } .stDashboard .stKpiDetailLabel { color: #888; } .stDashboard .stKpiDetailVal { font-weight: 600; color: #1a1a2e; } .stDashboard .stKpiChangePositive { color: #22c55e; font-weight: 600; } .stDashboard .stKpiChangeNegative { color: #ef4444; font-weight: 600; } .stDashboard .stChartsRow { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 20px; } .stDashboard .stChartCard { background: #fff; border-radius: 12px; padding: 20px; box-shadow: 0 1px 3px rgba(0,0,0,0.08); } .stDashboard .stChartTitle { font-size: 15px; font-weight: 600; color: #1a1a2e; margin-bottom: 12px; } .stDashboard .stChartContainer { width: 100%; height: 350px; } .stDashboard .stTableCard { background: #fff; border-radius: 12px; padding: 20px; box-shadow: 0 1px 3px rgba(0,0,0,0.08); } .stDashboard .stTableTitle { font-size: 15px; font-weight: 600; color: #1a1a2e; margin-bottom: 12px; } .stDashboard .stTableWrapper { overflow-x: auto; } .stDashboard table { width: 100%; border-collapse: collapse; font-size: 14px; } .stDashboard table thead th { background: #f8fafc; padding: 10px 12px; text-align: left; font-weight: 600; color: #64748b; border-bottom: 2px solid #e2e8f0; font-size: 12px; text-transform: uppercase; letter-spacing: 0.3px; } .stDashboard table tbody td { padding: 10px 12px; border-bottom: 1px solid #f1f5f9; color: #1a1a2e; } .stDashboard table tbody tr:hover td { background: #f8fafc; } .stDashboard .stTableRank { color: #94a3b8; font-weight: 600; width: 40px; } .stDashboard .stTableProfit { font-weight: 600; } .stDashboard .stTableProfitPositive { color: #22c55e; } .stDashboard .stTableProfitNegative { color: #ef4444; } .stDashboard .stTextRight { text-align: right; } @media (max-width: 768px) { .stDashboard { padding: 12px; } .stDashboard .stKpiRow { grid-template-columns: 1fr; } .stDashboard .stChartsRow { grid-template-columns: 1fr; } .stDashboard .stChartContainer { height: 280px; } .stDashboard .stKpiValue { font-size: 22px; } } </style> <div class="stFilterContainer"> <div class="stFilter"> <Widget:Parameter>{ "CubeId": "7f74de2546804cf9b12da34d7e5af382", "ParameterName": "customer_segment", "Label": "Customer Segment", "Value": null }</Widget:Parameter> <Widget:Parameter>{ "CubeId": "7f74de2546804cf9b12da34d7e5af382", "ParameterName": "year", "Label": "Year", "Value": null }</Widget:Parameter> </div> </div> <div style="display:none"> <Widget:Report>{ "CubeId": "7f74de2546804cf9b12da34d7e5af382", "ReportType": "pivot", "ClientCustomRenderReportDataFormat": "raw_json", "ClientCustomRenderFunction": "query_aggregate_yearlyData", "ReportConfig": {"Rows":[{"Name":"Order Date (Year)"}],"Measures":[{"Name":"Count"},{"Name":"SumOfSales"},{"Name":"SumOfProfit"}],"LimitRows":500,"Filter":"","OrderBy":{"Dimensions":[{"Axis":"Rows","Index":0,"Direction":0}],"Values":[]}} }</Widget:Report> <Widget:Report>{ "CubeId": "7f74de2546804cf9b12da34d7e5af382", "ReportType": "pivot", "ClientCustomRenderReportDataFormat": "raw_json", "ClientCustomRenderFunction": "query_aggregate_segmentsData", "ReportConfig": {"Rows":[{"Name":"Customer Segment"}],"Measures":[{"Name":"SumOfSales"},{"Name":"SumOfProfit"}],"LimitRows":500,"Filter":""} }</Widget:Report> <Widget:Report>{ "CubeId": "7f74de2546804cf9b12da34d7e5af382", "ReportType": "pivot", "ClientCustomRenderReportDataFormat": "raw_json", "ClientCustomRenderFunction": "query_aggregate_subCategoriesData", "ReportConfig": {"Rows":[{"Name":"Product Sub-Category"}],"Measures":[{"Name":"SumOfSales"}],"LimitRows":500,"Filter":""} }</Widget:Report> <Widget:Report>{ "CubeId": "7f74de2546804cf9b12da34d7e5af382", "ReportType": "pivot", "ClientCustomRenderReportDataFormat": "raw_json", "ClientCustomRenderFunction": "query_aggregate_topProductsData", "ReportConfig": {"Rows":[{"Name":"Product Name"}],"Measures":[{"Name":"SumOfSales"},{"Name":"SumOfProfit"},{"Name":"SumOfOrder Quantity"}],"LimitRows":10,"Filter":"","OrderBy":{"Dimensions":[],"Values":[{"Axis":"Rows","Direction":1,"Measure":0}]}} }</Widget:Report> </div> <div class="stDashboardContent"> <div class="stKpiRow"> <div class="stKpiCard" id="kpi-orders"> <div class="stKpiLabel">Orders</div> <div class="stKpiValue">-</div> <div class="stKpiDetails"></div> </div> <div class="stKpiCard" id="kpi-sales"> <div class="stKpiLabel">Total Sales</div> <div class="stKpiValue">-</div> <div class="stKpiDetails"></div> </div> <div class="stKpiCard" id="kpi-profit"> <div class="stKpiLabel">Profit</div> <div class="stKpiValue">-</div> <div class="stKpiDetails"></div> </div> </div> <div class="stChartsRow"> <div class="stChartCard"> <div class="stChartTitle">Top Customer Segments by Sales</div> <div class="stChartContainer" id="chart-segments"></div> </div> <div class="stChartCard"> <div class="stChartTitle">Top Product Sub-Categories by Sales</div> <div class="stChartContainer" id="chart-subcategories"></div> </div> </div> <div class="stTableCard"> <div class="stTableTitle">Top 10 Best Selling Products</div> <div class="stTableWrapper"> <table id="table-top-products"> <thead> <tr> <th>#</th> <th>Product Name</th> <th class="stTextRight">Sales</th> <th class="stTextRight">Profit</th> <th class="stTextRight">Qty</th> </tr> </thead> <tbody> <tr><td colspan="5" style="text-align:center;color:#94a3b8;padding:24px;">Loading...</td></tr> </tbody> </table> </div> </div> </div> <script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script> <script> function fmtNum(n) { if (n == null) return '-'; if (Math.abs(n) >= 1e6) return (n / 1e6).toFixed(2) + 'M'; if (Math.abs(n) >= 1e3) return (n / 1e3).toFixed(1) + 'K'; return Number(n).toLocaleString('en-US', {minimumFractionDigits:0, maximumFractionDigits:0}); } function fmtCurr(n) { if (n == null) return '-'; return '$' + Number(n).toLocaleString('en-US', {minimumFractionDigits:2, maximumFractionDigits:2}); } function fmtPct(n) { if (n == null) return '-'; var s = (n >= 0 ? '+' : '') + n.toFixed(2) + '%'; return s; } function render_KpiBoxes(data) { var cols = data.Columns; var rows = data.Data; var totals = data.Totals; var yearIdx = -1, countIdx = -1, salesIdx = -1, profitIdx = -1; cols.forEach(function(c, i) { if (c.Name === 'Order Date (Year)') yearIdx = i; else if (c.Name === 'Count') countIdx = i; else if (c.Name === 'SumOfSales') salesIdx = i; else if (c.Name === 'SumOfProfit') profitIdx = i; }); var lastYear = null, prevYear = null; var lastRow = null, prevRow = null; rows.forEach(function(r) { var y = r[yearIdx]; if (lastYear === null || y > lastYear) { prevYear = lastYear; prevRow = lastRow; lastYear = y; lastRow = r; } else if (prevYear === null || y > prevYear) { prevYear = y; prevRow = r; } }); function calcChange(curr, prev) { if (curr == null || prev == null || prev === 0) return null; return (curr - prev) / prev * 100; } function setKpi(kpiId, label, totalVal, lastVal, prevVal, isCurrency) { var card = document.getElementById(kpiId); if (!card) return; var fmt = isCurrency ? fmtCurr : fmtNum; var valEl = card.querySelector('.stKpiValue'); var detEl = card.querySelector('.stKpiDetails'); if (valEl) valEl.textContent = fmt(totalVal); if (detEl && lastVal != null) { var change = calcChange(lastVal, prevVal); var html = '<span class="stKpiDetailItem"><span class="stKpiDetailLabel">' + lastYear + ':</span><span class="stKpiDetailVal">' + fmt(lastVal) + '</span></span>'; if (change !== null && prevYear != null) { var cls = change >= 0 ? 'stKpiChangePositive' : 'stKpiChangeNegative'; html += '<span class="stKpiDetailItem"><span class="stKpiDetailLabel">vs ' + prevYear + ':</span><span class="' + cls + '">' + fmtPct(change) + '</span></span>'; } detEl.innerHTML = html; } } setKpi('kpi-orders', 'Orders', totals.Count, lastRow ? lastRow[countIdx] : null, prevRow ? prevRow[countIdx] : null, false); setKpi('kpi-sales', 'Total Sales', totals.SumOfSales, lastRow ? lastRow[salesIdx] : null, prevRow ? prevRow[salesIdx] : null, true); setKpi('kpi-profit', 'Profit', totals.SumOfProfit, lastRow ? lastRow[profitIdx] : null, prevRow ? prevRow[profitIdx] : null, true); } function render_SegmentsChart(data) { var cols = data.Columns; var rows = data.Data; var segIdx = -1, salesIdx = -1; cols.forEach(function(c, i) { if (c.Name === 'Customer Segment') segIdx = i; else if (c.Name === 'SumOfSales') salesIdx = i; }); if (segIdx < 0 || salesIdx < 0) return; var names = [], values = []; rows.forEach(function(r) { names.push(r[segIdx]); values.push(r[salesIdx]); }); var chart = echarts.init(document.getElementById('chart-segments')); chart.setOption({ tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' }, valueFormatter: function(v) { return '$' + Number(v).toLocaleString(); } }, grid: { left: '3%', right: '4%', bottom: '10%', containLabel: true }, xAxis: { type: 'category', data: names, axisLabel: { fontSize: 11 } }, yAxis: { type: 'value', axisLabel: { formatter: function(v) { return '$' + (v >= 1000 ? (v/1000).toFixed(0) + 'K' : v.toFixed(0)); } } }, series: [{ type: 'bar', data: values, itemStyle: { color: '#3b82f6', borderRadius: [4,4,0,0] }, barMaxWidth: 40 }] }); chart.resize(); } function render_SubCategoriesChart(data) { var cols = data.Columns; var rows = data.Data; var catIdx = -1, salesIdx = -1; cols.forEach(function(c, i) { if (c.Name === 'Product Sub-Category') catIdx = i; else if (c.Name === 'SumOfSales') salesIdx = i; }); if (catIdx < 0 || salesIdx < 0) return; var names = [], values = []; rows.sort(function(a, b) { return b[salesIdx] - a[salesIdx]; }).forEach(function(r) { names.push(r[catIdx]); values.push(r[salesIdx]); }); var chart = echarts.init(document.getElementById('chart-subcategories')); chart.setOption({ tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' }, valueFormatter: function(v) { return '$' + Number(v).toLocaleString(); } }, grid: { left: '3%', right: '4%', bottom: '15%', containLabel: true }, xAxis: { type: 'category', data: names, axisLabel: { fontSize: 10, rotate: 35 } }, yAxis: { type: 'value', axisLabel: { formatter: function(v) { return '$' + (v >= 1000 ? (v/1000).toFixed(0) + 'K' : v.toFixed(0)); } } }, series: [{ type: 'bar', data: values, itemStyle: { color: '#8b5cf6', borderRadius: [4,4,0,0] }, barMaxWidth: 40 }] }); chart.resize(); } function render_TopProductsTable(data) { var cols = data.Columns; var rows = data.Data; var nameIdx = -1, salesIdx = -1, profitIdx = -1, qtyIdx = -1; cols.forEach(function(c, i) { if (c.Name === 'Product Name') nameIdx = i; else if (c.Name === 'SumOfSales') salesIdx = i; else if (c.Name === 'SumOfProfit') profitIdx = i; else if (c.Name === 'SumOfOrder Quantity') qtyIdx = i; }); if (nameIdx < 0) return; var tbody = document.querySelector('#table-top-products tbody'); if (!tbody) return; tbody.innerHTML = ''; rows.forEach(function(r, i) { var profit = r[profitIdx]; var profitCls = profit >= 0 ? 'stTableProfitPositive' : 'stTableProfitNegative'; var tr = document.createElement('tr'); tr.innerHTML = '<td class="stTableRank">' + (i + 1) + '</td>' + '<td>' + r[nameIdx] + '</td>' + '<td class="stTextRight">' + fmtCurr(r[salesIdx]) + '</td>' + '<td class="stTextRight stTableProfit ' + profitCls + '">' + fmtCurr(profit) + '</td>' + '<td class="stTextRight">' + (qtyIdx >= 0 ? Number(r[qtyIdx]).toLocaleString() : '-') + '</td>'; tbody.appendChild(tr); }); } function query_aggregate_yearlyData(context) { render_KpiBoxes(context.reportData); } function query_aggregate_segmentsData(context) { render_SegmentsChart(context.reportData); } function query_aggregate_subCategoriesData(context) { render_SubCategoriesChart(context.reportData); } function query_aggregate_topProductsData(context) { render_TopProductsTable(context.reportData); } </script> </div>
Apply parameters: On change
Apply parameters: Filter button
Insert report
Insert parameter
Insert "Filter" button
Template reference
Save
Cancel