import 'bootstrap.css';
import '@mescius/wijmo.styles/wijmo.css';
import './styles.css';
import { CollectionView } from '@mescius/wijmo';
import { CellRange, FlexGrid, HeadersVisibility, SelectionMode } from '@mescius/wijmo.grid';
import { ChartContextMenu } from './chart-context-menu';
import { getData } from './data';
import { RangeChartPopup } from './range-chart-popup';
import { getRangeChartData } from './range-data-adapter';
document.readyState === 'complete' ? init() : window.onload = init;
function init() {
const grid = new FlexGrid('#sales-grid', {
autoGenerateColumns: false,
headersVisibility: HeadersVisibility.All,
isReadOnly: true,
selectionMode: SelectionMode.CellRange,
alternatingRowStep: 0,
columns: [
{ binding: 'month', header: 'Month', width: 130 },
{ binding: 'revenue', header: 'Revenue', format: 'c0', width: 130 },
{ binding: 'online', header: 'Online', format: 'c0', width: 125 },
{ binding: 'retail', header: 'Retail', format: 'c0', width: 125 },
{ binding: 'margin', header: 'Margin', format: 'c0', width: 120 },
{ binding: 'orders', header: 'Orders', format: 'n0', width: 105 },
{ binding: 'returns', header: 'Returns', format: 'n0', width: '*' }
],
itemsSource: new CollectionView(getData())
});
const chartPopup = new RangeChartPopup('#chart-popup');
const chartMenu = new ChartContextMenu(grid, chartType => {
chartPopup.open(chartType, getRangeChartData(grid));
});
const rangeSummary = getElement('#range-summary');
const chartMenuButton = getElement('#chart-menu-button');
function updateSelection() {
const data = getRangeChartData(grid);
rangeSummary.textContent = `${data.rangeLabel} | ${data.rowCount} rows x ${data.columnCount} columns | ${data.series.length} series`;
if (chartPopup.isVisible) {
chartPopup.update(data);
}
}
chartMenuButton.addEventListener('click', () => chartMenu.show(chartMenuButton));
grid.selectionChanged.addHandler(updateSelection);
grid.select(new CellRange(0, 0, 7, 4));
updateSelection();
}
function getElement(selector) {
const element = document.querySelector(selector);
if (!element) {
throw new Error(`Missing element: ${selector}`);
}
return element;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>MESCIUS Wijmo Range Chart</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- SystemJS -->
<script src="https://cdn.jsdelivr.net/npm/systemjs@0.21.5/dist/system.src.js" integrity="sha512-skZbMyvYdNoZfLmiGn5ii6KmklM82rYX2uWctBhzaXPxJgiv4XBwJnFGr5k8s+6tE1pcR1nuTKghozJHyzMcoA==" crossorigin="anonymous"></script>
<script src="systemjs.config.js"></script>
<script>
System.import('./src/app');
</script>
</head>
<body>
<main class="workspace">
<header class="workspace-header">
<div>
<span class="eyebrow">Sales performance</span>
<h1>Range Chart</h1>
</div>
<div class="selection-tools">
<span id="range-summary" class="range-summary" aria-live="polite">-</span>
<button id="chart-menu-button" class="chart-menu-button" type="button" aria-label="Choose a chart for the selected cells">
<span class="chart-button-icon" aria-hidden="true"><i></i><i></i><i></i></span>
Chart selection
</button>
</div>
</header>
<section class="grid-frame" aria-label="Sales data workspace">
<div id="sales-grid" aria-label="Sales performance data"></div>
</section>
</main>
<div id="chart-popup" class="chart-dialog" role="dialog" aria-labelledby="chart-title" style="display: none">
<div class="wj-dialog-header chart-dialog-header">
<div>
<span id="chart-type-label" class="chart-dialog-eyebrow">Range chart</span>
<h2 id="chart-title">Selected data</h2>
</div>
<button id="chart-close" class="icon-button" type="button" title="Close" aria-label="Close chart">×</button>
</div>
<div class="chart-dialog-meta">
<span id="chart-range-label">-</span>
<span id="chart-series-label">-</span>
</div>
<div class="chart-dialog-body">
<div id="range-chart" aria-label="Chart for selected grid data"></div>
<div id="chart-empty" class="chart-empty" hidden>
<strong>No numeric series</strong>
<span>The selected cells do not contain numeric values.</span>
</div>
</div>
</div>
</body>
</html>
const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
export function getData() {
return Array.from({ length: 24 }, (_, index) => {
const year = 2025 + Math.floor(index / 12);
const monthIndex = index % 12;
const seasonal = Math.sin((monthIndex / 12) * Math.PI * 2) * 18000;
const trend = index * 4200;
const revenue = Math.round(158000 + trend + seasonal + ((index * 7919) % 17000));
const onlineRatio = 0.48 + (monthIndex % 4) * 0.015;
const online = Math.round(revenue * onlineRatio);
const retail = revenue - online;
const margin = Math.round(revenue * (0.26 + (index % 5) * 0.012));
const orders = Math.round(revenue / (118 + (index % 3) * 7));
const returns = Math.round(orders * (0.032 + (index % 4) * 0.004));
return {
month: `${MONTHS[monthIndex]} ${year}`,
revenue,
online,
retail,
margin,
orders,
returns
};
});
}
:root {
color: #243038;
background: #f3f5f6;
font-family: Inter, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
* { box-sizing: border-box; }
body {
min-width: 320px;
min-height: 0;
margin: 0;
background: #f3f5f6;
}
button { font: inherit; }
.workspace {
width: min(1440px, 100%);
margin: 0 auto;
padding: 24px clamp(14px, 3vw, 40px) 36px;
}
.workspace-header {
display: flex;
min-height: 68px;
align-items: center;
justify-content: space-between;
gap: 24px;
padding-bottom: 16px;
border-bottom: 1px solid #cfd6da;
}
.eyebrow,
.chart-dialog-eyebrow {
display: block;
margin-bottom: 4px;
color: #147d92;
font-size: 11px;
font-weight: 700;
text-transform: uppercase;
}
h1 {
margin: 0;
color: #17242b;
font-size: 26px;
font-weight: 650;
}
.selection-tools {
display: flex;
align-items: center;
gap: 14px;
}
.range-summary {
color: #66747b;
font-size: 12px;
font-variant-numeric: tabular-nums;
}
.chart-menu-button {
display: inline-flex;
min-height: 40px;
align-items: center;
gap: 9px;
padding: 8px 14px;
color: #fff;
background: #147d92;
border: 1px solid #0e687a;
border-radius: 4px;
font-weight: 650;
cursor: pointer;
}
.chart-menu-button:hover,
.chart-menu-button:focus-visible {
background: #0f6c7e;
box-shadow: 0 0 0 3px rgb(20 125 146 / 18%);
outline: none;
}
.chart-button-icon {
display: inline-flex;
width: 17px;
height: 17px;
align-items: end;
gap: 2px;
}
.chart-button-icon i {
width: 4px;
background: currentColor;
border-radius: 1px 1px 0 0;
}
.chart-button-icon i:nth-child(1) { height: 45%; }
.chart-button-icon i:nth-child(2) { height: 100%; }
.chart-button-icon i:nth-child(3) { height: 70%; }
.grid-frame {
overflow: hidden;
margin-top: 18px;
background: #fff;
border: 1px solid #c7d0d5;
border-radius: 6px;
box-shadow: 0 10px 28px rgb(35 50 58 / 8%);
}
#sales-grid {
width: 100%;
height: 560px;
min-height: 430px;
border: 0;
}
#sales-grid .wj-header {
color: #f5f8f9;
background: #263943;
border-color: #3d5059;
font-weight: 650;
}
#sales-grid .wj-cell {
display: flex;
align-items: center;
border-color: #e2e7e9;
}
#sales-grid .wj-cell:not(.wj-header) { background: #fff; }
#sales-grid .wj-cell.wj-state-selected,
#sales-grid .wj-cell.wj-state-multi-selected {
color: #15343b;
background: #d7edf0;
}
#sales-grid .wj-cell.wj-state-selected {
outline: 2px solid #147d92;
outline-offset: -2px;
}
.range-chart-menu.wj-listbox {
min-width: 205px;
padding: 6px;
color: #26343b;
background: #fff;
border: 1px solid #b9c4c9;
border-radius: 5px;
box-shadow: 0 16px 36px rgb(31 48 57 / 20%);
}
.range-chart-menu .wj-listbox-item {
min-height: 36px;
padding: 8px 12px;
border-radius: 3px;
}
.range-chart-menu .wj-listbox-item:hover,
.range-chart-menu .wj-listbox-item.wj-state-selected {
color: #0d4652;
background: #e1f0f2;
}
.chart-dialog {
display: flex;
width: min(760px, calc(100vw - 28px));
min-width: min(460px, calc(100vw - 28px));
height: min(600px, calc(100vh - 48px));
min-height: 430px;
flex-direction: column;
overflow: hidden;
color: #243038;
background: #fff;
border: 1px solid #819198;
border-radius: 6px;
box-shadow: 0 24px 70px rgb(24 41 49 / 28%);
}
.chart-dialog .wj-dialog-header.chart-dialog-header {
display: flex;
min-height: 68px;
align-items: center;
justify-content: space-between;
gap: 16px;
padding: 12px 14px 12px 20px;
color: #f8fafb;
background: #233842;
border-bottom: 3px solid #e1663f;
cursor: move;
}
.chart-dialog-eyebrow { color: #82cedb; }
.chart-dialog h2 {
margin: 0;
font-size: 18px;
font-weight: 650;
}
.icon-button {
display: inline-grid;
width: 36px;
height: 36px;
flex: 0 0 36px;
place-items: center;
padding: 0;
color: #edf3f5;
background: transparent;
border: 1px solid #5d7078;
border-radius: 4px;
font-size: 26px;
line-height: 1;
cursor: pointer;
}
.icon-button:hover,
.icon-button:focus-visible {
color: #fff;
background: #d95834;
border-color: #d95834;
outline: none;
}
.chart-dialog-meta {
display: flex;
min-height: 42px;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 8px 20px;
color: #68767d;
background: #f4f6f7;
border-bottom: 1px solid #dce2e5;
font-size: 12px;
}
.chart-dialog-body {
position: relative;
min-height: 300px;
flex: 1 1 auto;
padding: 14px;
}
#range-chart {
width: 100%;
height: 100%;
border: 0;
}
#range-chart .wj-chart-legend {
fill: #3a4a52;
font-size: 11px;
}
.chart-empty {
position: absolute;
inset: 14px;
display: grid;
place-content: center;
gap: 6px;
color: #69777e;
text-align: center;
background: #f7f9fa;
border: 1px dashed #b9c4c9;
}
#range-chart[hidden],
.chart-empty[hidden] { display: none; }
.chart-empty strong {
color: #293840;
font-size: 16px;
}
.chart-empty span {
max-width: 360px;
font-size: 12px;
}
@media (max-width: 680px) {
.workspace-header,
.selection-tools,
.chart-dialog-meta {
align-items: stretch;
flex-direction: column;
}
.selection-tools { gap: 8px; }
.chart-menu-button { justify-content: center; }
#sales-grid { min-height: 500px; }
.chart-dialog {
width: calc(100% - 20px);
min-width: 0;
}
}
(function (global) {
System.config({
transpiler: 'plugin-babel',
babelOptions: {
es2015: true,
},
meta: {
'*.css': { loader: 'css' },
},
paths: {
// paths serve as alias
'npm:': 'node_modules/',
'cdn:': 'https://cdn.mescius.io/demoapps/packages/wijmojs/5.20262.60-rc/',
'pcdn:': 'https://cdn.jsdelivr.net/npm/'
},
// map tells the System loader where to look for things
map: {
'@mescius/wijmo': 'cdn:@mescius/wijmo/index.js',
'@mescius/wijmo.input': 'cdn:@mescius/wijmo.input/index.js',
'@mescius/wijmo.styles': 'cdn:@mescius/wijmo.styles',
'@mescius/wijmo.cultures': 'cdn:@mescius/wijmo.cultures',
'@mescius/wijmo.chart': 'cdn:@mescius/wijmo.chart/index.js',
'@mescius/wijmo.chart.analytics': 'cdn:@mescius/wijmo.chart.analytics/index.js',
'@mescius/wijmo.chart.animation': 'cdn:@mescius/wijmo.chart.animation/index.js',
'@mescius/wijmo.chart.annotation': 'cdn:@mescius/wijmo.chart.annotation/index.js',
'@mescius/wijmo.chart.finance': 'cdn:@mescius/wijmo.chart.finance/index.js',
'@mescius/wijmo.chart.finance.analytics': 'cdn:@mescius/wijmo.chart.finance.analytics/index.js',
'@mescius/wijmo.chart.hierarchical': 'cdn:@mescius/wijmo.chart.hierarchical/index.js',
'@mescius/wijmo.chart.interaction': 'cdn:@mescius/wijmo.chart.interaction/index.js',
'@mescius/wijmo.chart.radar': 'cdn:@mescius/wijmo.chart.radar/index.js',
'@mescius/wijmo.chart.render': 'cdn:@mescius/wijmo.chart.render/index.js',
'@mescius/wijmo.chart.webgl': 'cdn:@mescius/wijmo.chart.webgl/index.js',
'@mescius/wijmo.chart.map': 'cdn:@mescius/wijmo.chart.map/index.js',
'@mescius/wijmo.gauge': 'cdn:@mescius/wijmo.gauge/index.js',
'@mescius/wijmo.grid': 'cdn:@mescius/wijmo.grid/index.js',
'@mescius/wijmo.grid.detail': 'cdn:@mescius/wijmo.grid.detail/index.js',
'@mescius/wijmo.grid.filter': 'cdn:@mescius/wijmo.grid.filter/index.js',
'@mescius/wijmo.grid.search': 'cdn:@mescius/wijmo.grid.search/index.js',
'@mescius/wijmo.grid.style': 'cdn:@mescius/wijmo.grid.style/index.js',
'@mescius/wijmo.grid.grouppanel': 'cdn:@mescius/wijmo.grid.grouppanel/index.js',
'@mescius/wijmo.grid.multirow': 'cdn:@mescius/wijmo.grid.multirow/index.js',
'@mescius/wijmo.grid.transposed': 'cdn:@mescius/wijmo.grid.transposed/index.js',
'@mescius/wijmo.grid.transposedmultirow': 'cdn:@mescius/wijmo.grid.transposedmultirow/index.js',
'@mescius/wijmo.grid.pdf': 'cdn:@mescius/wijmo.grid.pdf/index.js',
'@mescius/wijmo.grid.sheet': 'cdn:@mescius/wijmo.grid.sheet/index.js',
'@mescius/wijmo.grid.xlsx': 'cdn:@mescius/wijmo.grid.xlsx/index.js',
'@mescius/wijmo.grid.selector': 'cdn:@mescius/wijmo.grid.selector/index.js',
'@mescius/wijmo.grid.cellmaker': 'cdn:@mescius/wijmo.grid.cellmaker/index.js',
'@mescius/wijmo.nav': 'cdn:@mescius/wijmo.nav/index.js',
'@mescius/wijmo.odata': 'cdn:@mescius/wijmo.odata/index.js',
'@mescius/wijmo.olap': 'cdn:@mescius/wijmo.olap/index.js',
'@mescius/wijmo.rest': 'cdn:@mescius/wijmo.rest/index.js',
'@mescius/wijmo.pdf': 'cdn:@mescius/wijmo.pdf/index.js',
'@mescius/wijmo.pdf.security': 'cdn:@mescius/wijmo.pdf.security/index.js',
'@mescius/wijmo.viewer': 'cdn:@mescius/wijmo.viewer/index.js',
'@mescius/wijmo.xlsx': 'cdn:@mescius/wijmo.xlsx/index.js',
'@mescius/wijmo.undo': 'cdn:@mescius/wijmo.undo/index.js',
'@mescius/wijmo.interop.grid': 'cdn:@mescius/wijmo.interop.grid/index.js',
'@mescius/wijmo.touch': 'cdn:@mescius/wijmo.touch/index.js',
'@mescius/wijmo.cloud': 'cdn:@mescius/wijmo.cloud/index.js',
'@mescius/wijmo.barcode': 'cdn:@mescius/wijmo.barcode/index.js',
'@mescius/wijmo.barcode.common': 'cdn:@mescius/wijmo.barcode.common/index.js',
'@mescius/wijmo.barcode.composite': 'cdn:@mescius/wijmo.barcode.composite/index.js',
'@mescius/wijmo.barcode.specialized': 'cdn:@mescius/wijmo.barcode.specialized/index.js',
jszip: 'pcdn:jszip@3.8.0/dist/jszip.js',
'bootstrap.css': 'pcdn:bootstrap@5.3.8/dist/css/bootstrap.min.css',
css: 'pcdn:systemjs-plugin-css@0.1.37/css.js',
'plugin-babel': 'pcdn:systemjs-plugin-babel@0.0.25/plugin-babel.js',
'systemjs-babel-build': 'pcdn:systemjs-plugin-babel@0.0.25/systemjs-babel-browser.js',
},
// packages tells the System loader how to load when no filename and/or no extension
packages: {
src: {
defaultExtension: 'js',
},
node_modules: {
defaultExtension: 'js',
},
},
});
})(this);