Layout Variations in Vue

Using ApexCharts with Vue

This Layout Variations example wires ApexCharts into Vue through the vue3-apexcharts component.

Install it with npm install vue3-apexcharts apexcharts, then mount the chart with <apexchart type="..." :options="options" :series="series" />.

Vue charts guide
<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/violin'
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>