import React from 'react'
import ReactApexChart from 'react-apexcharts'
import './styles.css'
import 'apexcharts/features/trellis'
// Six locations, each a real heatmap of day x hour check-ins, all six drawn
// against ONE colour scale derived from the union of every panel's values.
// That is the whole point of the grid: the same fill means the same count
// everywhere, so the quiet sites read as quiet. Six independently shaded
// heatmaps would give every panel its own darkest cell and the comparison
// would be a lie.
//
// Deterministic counts 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
}
}
var DAYS = ['Sun', 'Sat', 'Fri', 'Thu', 'Wed', 'Tue', 'Mon']
var HOURS = ['6a', '8a', '10a', '12p', '2p', '4p', '6p', '8p']
// Each location has its own rhythm AND its own overall volume: the commuter
// sites are busiest, the campus site is a midday hump, the lakeside site is
// small and evening-heavy. Volume differences are the point of one scale.
var LOCATIONS = [
{
name: 'Central Station',
seed: 7,
volume: 1.0,
peaks: [1, 6],
weekend: 0.45,
},
{ name: 'Harbor Point', seed: 19, volume: 0.82, peaks: [1, 6], weekend: 0.6 },
{
name: 'University Campus',
seed: 31,
volume: 0.66,
peaks: [3, 4],
weekend: 0.3,
},
{
name: 'Riverside Mall',
seed: 43,
volume: 0.58,
peaks: [4, 5],
weekend: 1.15,
},
{
name: 'Airport North',
seed: 59,
volume: 0.74,
peaks: [0, 7],
weekend: 0.95,
},
{
name: 'Lakeside Park',
seed: 71,
volume: 0.31,
peaks: [5, 6],
weekend: 1.4,
},
]
function hourlyRow(rand, loc, dayIndex) {
// DAYS runs Sun..Mon top to bottom, so weekend factor keys off the ends.
var isWeekend = dayIndex === 0 || dayIndex === 1
var dayFactor = isWeekend ? loc.weekend : 0.85 + rand() * 0.3
return HOURS.map(function (hour, hi) {
var near = Math.min(
Math.abs(hi - loc.peaks[0]),
Math.abs(hi - loc.peaks[1]),
)
var shape = Math.max(0.12, 1 - near * 0.28)
var v = 130 * loc.volume * dayFactor * shape * (0.85 + rand() * 0.3)
return { x: hour, y: Math.round(v) }
})
}
var activitySeries = []
LOCATIONS.forEach(function (loc) {
var rand = mulberry32(loc.seed)
DAYS.forEach(function (day, di) {
activitySeries.push({
name: day,
location: loc.name,
data: hourlyRow(rand, loc, di),
})
})
})
const ApexChart = () => {
const [state, setState] = React.useState({
series: activitySeries,
options: {
chart: {
id: 'activityTrellis',
type: 'heatmap',
height: 620,
animations: {
enabled: false,
},
},
trellis: {
by: 'location',
columns: 3,
minPanelWidth: 300,
gap: 14,
},
colors: ['#0E7490'],
plotOptions: {
heatmap: {
radius: 2,
enableShades: true,
shadeIntensity: 0.55,
colorScale: {
gradientLegend: {
formatter: function (val) {
return Math.round(Number(val)) + ' check-ins'
},
},
},
},
},
dataLabels: {
enabled: false,
},
stroke: {
width: 1,
colors: ['#fff'],
},
xaxis: {
type: 'category',
},
tooltip: {
compact: true,
y: {
formatter: function (val) {
return val + ' check-ins'
},
},
},
},
})
return (
<div>
<div className="wrap">
<h1>When each location is actually busy</h1>
<p className="lead">
Six day-by-hour heatmaps under one colour scale and one gradient
legend. The commuter double-peak, the campus midday hump and the
weekend-shaped park all read at a glance, and so does the plain fact
that the park is a fraction of the size of the station.
</p>
<div className="chart-wrap">
<div id="chart">
<ReactApexChart
options={state.options}
series={state.series}
type="heatmap"
height={620}
/>
</div>
</div>
<div className="note">
The trellis derives the colour domain over the union of all panels and
pushes an absolute <code>colorScale.min</code>/<code>max</code> into
every one, so a given count lands on exactly the same fill in every
panel. The grid then draws ONE gradient strip for the whole thing
instead of six identical legends, and hovering a cell traces the same
value in every other panel. Asking for{' '}
<code>scales.color: 'independent'</code> is refused with a warning:
per-panel shading is the classic way a heatmap grid misleads. The cell
tooltip is one line (<code>tooltip.compact</code>), because a full
card would cover the row it is captioning. Trellis is a premium
feature; without a license it renders with a trial watermark.
</div>
</div>
</div>
)
}
export default ApexChart