<template>
<div>
<div class="panel">
<div class="controls">
<span class="label">Layout:</span>
<button data-layout="classic" class="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">
<apexchart
type="raincloud"
height="420"
:options="chartOptions"
:series="series"
></apexchart>
</div>
<div class="panel">
<div class="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>
</template>
<script>
import VueApexCharts from 'vue-apexcharts'
import ApexCharts from 'apexcharts'
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 },
},
}
export default {
components: {
apexchart: VueApexCharts,
},
data: function () {
return {
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) },
],
},
],
chartOptions: {
chart: {
id: 'raincloudVariations',
type: 'raincloud',
height: 420,
},
colors: ['#14b8a6'],
title: {
text: 'One distribution chart, four layouts',
align: 'left',
},
yaxis: {
title: {
text: 'Measurement',
},
},
},
raincloudTimer: null,
}
},
mounted: function () {
// The vue-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.)
var me = this
me.raincloudTimer = window.setInterval(function () {
var chart = ApexCharts.getChartByID('raincloudVariations')
if (!chart) return
window.clearInterval(me.raincloudTimer)
var buttons = Array.prototype.slice.call(
document.querySelectorAll('.controls button[data-layout]')
)
buttons.forEach(function (btn) {
btn.addEventListener('click', function () {
buttons.forEach(function (b) { b.classList.remove('active') })
btn.classList.add('active')
chart.updateOptions({
plotOptions: { violin: RAINCLOUD_LAYOUTS[btn.getAttribute('data-layout')] },
})
})
})
}, 50)
},
beforeDestroy: function () {
window.clearInterval(this.raincloudTimer)
},,
}
</script>
<style>
#chart {
max-width: 820px;
margin: 12px auto 4px;
}
.panel {
max-width: 820px;
margin: 14px auto;
font-family: sans-serif;
}
.controls {
display: flex;
gap: 8px;
align-items: center;
flex-wrap: wrap;
margin-bottom: 6px;
}
.controls .label {
font-size: 13px;
color: #556;
margin-right: 2px;
}
.controls button {
padding: 6px 12px;
cursor: pointer;
border: 1px solid #d3d8e3;
background: #fff;
border-radius: 6px;
font-size: 13px;
}
.controls button.active {
border-color: #14b8a6;
color: #0f766e;
background: #f0fdfa;
font-weight: 600;
}
.note {
background: #f0fdfa;
border-left: 3px solid #14b8a6;
padding: 11px 15px;
font-size: 13px;
color: #234;
border-radius: 2px;
line-height: 1.6;
}
.note code {
background: #c7f0ea;
padding: 1px 5px;
border-radius: 3px;
}
</style>