import React from 'react'
import ReactApexChart from 'react-apexcharts'
import ApexCharts from 'apexcharts'
import './styles.css'
import 'apexcharts/features/raincloud'
// Raw observations per group (Box-Muller Gaussian; Math.random is seeded by
// the sample template).
function sample(mean, sd, n) {
var points = []
for (var k = 0; k < n; k++) {
var u1 = Math.random(),
u2 = Math.random()
var g = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2)
points.push(Math.round((mean + g * sd) * 10) / 10)
}
return points
}
// Each layout is a complete plotOptions.violin spec, so switching between
// them never leaves a leaf from the previous layout behind. Hidden layers
// hand their lane back to the cloud automatically.
var RAINCLOUD_LAYOUTS = {
classic: {
side: 'right',
box: { show: true },
points: { show: true, position: 'left' },
},
'cloud-rain': {
side: 'right',
box: { show: false },
points: { show: true, position: 'left' },
},
'cloud-box': {
side: 'right',
box: { show: true },
points: { show: false, position: 'left' },
},
overlay: {
side: 'both',
box: { show: true },
points: { show: true, position: 'center', constrainToViolin: true },
},
}
const ApexChart = () => {
const [state, setState] = React.useState({
series: [
{
name: 'Measurement',
data: [
{ x: 'North', points: sample(52, 9, 160) },
{ x: 'East', points: sample(61, 6, 160) },
{ x: 'South', points: sample(47, 12, 160) },
],
},
],
options: {
chart: {
id: 'raincloudVariations',
type: 'raincloud',
height: 420,
},
colors: ['#14b8a6'],
title: {
text: 'One distribution chart, four layouts',
align: 'left',
},
yaxis: {
title: {
text: 'Measurement',
},
},
},
})
React.useEffect(() => {
// The react-apexcharts wrapper owns the render, so reach the live instance
// by its chart.id, then wire the same controls the vanilla build does.
// (RAINCLOUD_LAYOUTS lives in the shared head script.)
let chart
const timer = window.setInterval(() => {
chart = ApexCharts.getChartByID('raincloudVariations')
if (!chart) return
window.clearInterval(timer)
const buttons = Array.prototype.slice.call(
document.querySelectorAll('.controls button[data-layout]'),
)
buttons.forEach((btn) => {
btn.addEventListener('click', () => {
buttons.forEach((b) => b.classList.remove('active'))
btn.classList.add('active')
chart.updateOptions({
plotOptions: {
violin: RAINCLOUD_LAYOUTS[btn.getAttribute('data-layout')],
},
})
})
})
}, 50)
return () => window.clearInterval(timer)
}, [])
return (
<div>
<div className="panel">
<div className="controls">
<span className="label">Layout:</span>
<button data-layout="classic" className="active">
Classic raincloud
</button>
<button data-layout="cloud-rain">Cloud + rain</button>
<button data-layout="cloud-box">Cloud + box</button>
<button data-layout="overlay">Violin + box overlay</button>
</div>
</div>
<div id="chart">
<ReactApexChart
options={state.options}
series={state.series}
type="raincloud"
height={420}
/>
</div>
<div className="panel">
<div className="note">
Every raincloud layer is its own switch on{' '}
<code>plotOptions.violin</code>:<code>side</code> cuts the density to
a half-violin,
<code>box.show</code> toggles the five-number lane, and
<code>points.position</code> moves the raw observations into their own
lane or back under the density. A hidden layer hands its lane back to
the cloud, so <b>Cloud + rain</b> and <b>Cloud + box</b> reflow
instead of leaving a gap, and <b>Violin + box overlay</b>(
<code>side: 'both'</code>) is the classic symmetric violin with the
box drawn on its centerline.
</div>
</div>
</div>
)
}
export default ApexChart