import React from 'react'
import ReactApexChart from 'react-apexcharts'
import './styles.css'
import 'apexcharts/features/trellis'
// Eight monthly signup cohorts, ~240 raw onboarding times each. Every panel
// is a real histogram over its own observations, but the trellis derives ONE
// set of bin edges over the union sample and pushes it into every panel, so
// the same bar in every panel means the same interval and the drift of the
// whole distribution (newer cohorts finish faster) reads directly. Overlaid
// translucent histograms stop being readable past about three; eight panels
// with shared edges stay readable.
//
// Deterministic samples 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
}
}
// Bell-ish sample: mean minutes drop cohort by cohort, spread tightens.
function onboardingMinutes(seed, mean, spread, n) {
var rand = mulberry32(seed)
var out = []
for (var i = 0; i < n; i++) {
var v = mean + (rand() + rand() + rand() - 1.5) * spread
// A slow tail: some users always wander off mid-setup.
if (rand() < 0.06) v += rand() * 25
out.push(Math.max(2, Math.round(v * 10) / 10))
}
return out
}
var COHORTS = [
{ name: 'Jan', seed: 11, mean: 34, spread: 14 },
{ name: 'Feb', seed: 23, mean: 32, spread: 13 },
{ name: 'Mar', seed: 37, mean: 30, spread: 13 },
{ name: 'Apr', seed: 41, mean: 27, spread: 12 },
{ name: 'May', seed: 53, mean: 25, spread: 11 },
{ name: 'Jun', seed: 67, mean: 22, spread: 10 },
{ name: 'Jul', seed: 79, mean: 20, spread: 9 },
{ name: 'Aug', seed: 97, mean: 18, spread: 8 },
]
var cohortSeries = COHORTS.map(function (c) {
return {
name: 'Onboarding time',
cohort: c.name + ' cohort',
data: onboardingMinutes(c.seed, c.mean, c.spread, 240),
}
})
const ApexChart = () => {
const [state, setState] = React.useState({
series: cohortSeries,
options: {
chart: {
id: 'cohortTrellis',
type: 'histogram',
height: 560,
animations: {
enabled: false,
},
},
trellis: {
by: 'cohort',
columns: 4,
gap: 12,
},
colors: ['#0E7490'],
plotOptions: {
histogram: {
bins: 'auto',
},
},
xaxis: {
tickAmount: 5,
labels: {
rotate: 0,
formatter: function (val) {
return Math.round(Number(val)) + 'm'
},
},
},
dataLabels: {
enabled: false,
},
stroke: {
width: 1,
colors: ['#fff'],
},
},
})
return (
<div>
<div className="wrap">
<h1>Time to first success, cohort by cohort</h1>
<p className="lead">
Eight histograms over raw observations, binned on ONE shared set of
edges and counted on one shared scale, so the whole distribution's
slide to the left (newer cohorts finish faster) is the picture, not an
artifact of per-panel binning.
</p>
<div className="chart-wrap">
<div id="chart">
<ReactApexChart
options={state.options}
series={state.series}
type="histogram"
height={560}
/>
</div>
</div>
<div className="note">
Feed each panel its raw observations and the trellis derives one bin
frame over the union sample, pushing an explicit
<code>plotOptions.histogram.range</code> and <code>binWidth</code>{' '}
into every panel: identical edges by construction, so the same bar
always means the same interval. The y scale is shared over the bin
<em>counts</em> (not the raw values, which live on a different axis
entirely). Binning each panel to its own extent would put different
intervals at the same x, which is the classic way a histogram grid
lies. Trellis is a premium feature; without a license it renders with
a trial watermark.
</div>
</div>
</div>
)
}
export default ApexChart