import React from 'react'
import ReactApexChart from 'react-apexcharts'
import './styles.css'
import 'apexcharts/features/trellis'
// Three metrics in three units: dollars, percent, people. They cannot share
// an axis, and a dual-axis chart is the most misread chart there is. The
// 2-D answer: rows = metric, columns = business unit, and
// `scales.y: 'independent-row'` gives each METRIC one shared domain across
// its row. Along a row the units are directly comparable (identical ticks);
// across rows each metric keeps its own honest scale.
//
// Deterministic monthly walks so the e2e snapshot is stable.
function mulberry32(seed) {
return function () {
seed |= 0
seed = (seed + 0x6d2b79f5) | 0
var t = Math.imul(seed ^ (seed >>> 15), 1 | seed)
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t
return ((t ^ (t >>> 14)) >>> 0) / 4294967296
}
}
function monthly(seed, base, drift, vol) {
var rand = mulberry32(seed)
var out = []
var ts = new Date('01 Jan 2025').getTime()
var val = base
for (var i = 0; i < 12; i++) {
val = Math.max(1, val + drift + (rand() - 0.5) * vol)
out.push([ts, Math.round(val * 10) / 10])
ts += 2678400000 // ~1 month
}
return out
}
var UNITS = [
{ unit: 'North America', seed: 3 },
{ unit: 'Europe', seed: 41 },
{ unit: 'APAC', seed: 89 },
]
var metricSeries = []
UNITS.forEach(function (u, i) {
metricSeries.push({
name: 'value',
metric: 'Revenue',
unit: u.unit,
data: monthly(u.seed, 420 - i * 90, 14 - i * 3, 60),
})
metricSeries.push({
name: 'value',
metric: 'Margin',
unit: u.unit,
// Europe's margin erodes: visible only because the row shares a scale.
data: monthly(
u.seed + 7,
24 + i * 2,
u.unit === 'Europe' ? -0.9 : 0.1,
2.4,
),
})
metricSeries.push({
name: 'value',
metric: 'Headcount',
unit: u.unit,
data: monthly(u.seed + 13, 120 - i * 30, 2, 8),
})
})
const ApexChart = () => {
const [state, setState] = React.useState({
series: metricSeries,
options: {
chart: {
id: 'metricsTrellis',
type: 'line',
height: 560,
animations: {
enabled: false,
},
},
trellis: {
row: 'metric',
column: 'unit',
gap: 12,
scales: {
y: 'independent-row',
},
panel: function (key) {
if (key.indexOf('Revenue') === 0) {
return {
yaxis: {
labels: {
formatter: function (val) {
return '$' + Math.round(val) + 'k'
},
},
},
}
}
if (key.indexOf('Margin') === 0) {
return {
yaxis: {
labels: {
formatter: function (val) {
return Math.round(val) + '%'
},
},
},
}
}
return {
yaxis: {
labels: {
formatter: function (val) {
return String(Math.round(val))
},
},
},
}
},
},
colors: ['#0D9488'],
stroke: {
width: 2.5,
curve: 'straight',
},
xaxis: {
type: 'datetime',
},
dataLabels: {
enabled: false,
},
tooltip: {
x: {
format: 'MMM yyyy',
},
},
},
})
return (
<div>
<div className="wrap">
<h1>Three metrics, three units, no dual axis</h1>
<p className="lead">
Each row is one metric on one honest scale: compare units left to
right. Each column is one unit top to bottom. Europe's margin erosion
is unmissable because the whole Margin row shares its domain, and no
axis pretends dollars and percent are the same thing.
</p>
<div className="chart-wrap">
<div id="chart">
<ReactApexChart
options={state.options}
series={state.series}
type="line"
height={560}
/>
</div>
</div>
<div className="note">
<code>scales.y: 'independent-row'</code> computes ONE domain per row
(the union across that row's panels) so ticks are identical along a
row and different across rows; the y labels draw on the first column
only, because the row's left edge speaks for the whole row. Per-row
units come from <code>trellis.panel</code>: the Margin row formats as
percent, the Revenue row as dollars. The grid stays pixel-aligned
across rows via the measured shared gutter. Trellis is a premium
feature; without a license it renders with a trial watermark.
</div>
</div>
</div>
)
}
export default ApexChart