Line Chart with One Customer Type Highlighted in Vue

Using ApexCharts with Vue

This Line Chart with One Customer Type Highlighted 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="wrap">
      <h1>Highlight a part of every line</h1>
      <p>
        Pick a kind of customer. Each store's orders stay on the chart as a
        dashed line at their full value, and the orders that customer placed are
        drawn solid in the same colour. Labels and tooltips state the part; the
        markers stay on the full line.
      </p>
      <div class="actions">
        <span>Customer</span>
        <button data-customer="returning" class="active">Returning</button>
        <button data-customer="first">First order</button>
        <button data-customer="">All (clear)</button>
      </div>
      <div class="chart-wrap">
        <div id="chart">
          <apexchart
            type="line"
            height="380"
            :options="chartOptions"
            :series="series"
          ></apexchart>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import VueApexCharts from 'vue-apexcharts'
import ApexCharts from 'apexcharts'

import 'apexcharts/features/highlight-filter'

// Shared by the vanilla-js, React and Vue builds.
//
// Orders per month by store (the wholes), and the orders that came from one
// kind of customer (the parts). Picking a customer keeps both stores on the
// chart: each line is drawn dashed at its full value, with that customer's
// orders solid in the same colour, on the same axis.
var MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug']
var STORES = ['Online store', 'Retail']
var ORDERS = [
  [420, 460, 510, 480, 560, 610, 590, 650],
  [300, 320, 310, 340, 360, 330, 380, 400],
]
var CUSTOMERS = {
  returning: [
    [250, 270, 300, 290, 340, 370, 360, 400],
    [210, 220, 215, 240, 250, 230, 260, 280],
  ],
  first: [
    [170, 190, 210, 190, 220, 240, 230, 250],
    [90, 100, 95, 100, 110, 100, 120, 120],
  ],
}

function seriesFor(customer) {
  return STORES.map(function (name, i) {
    var s = { name: name, data: ORDERS[i] }
    if (customer) s.highlightData = CUSTOMERS[customer][i]
    return s
  })
}

// One call per pick: the parts go in, the chart animates from what is on
// screen. Clearing is the same call without them.
function pickCustomer(chart, customer) {
  document.querySelectorAll('[data-customer]').forEach(function (b) {
    b.classList.toggle('active', b.getAttribute('data-customer') === customer)
  })
  return chart.updateSeries(seriesFor(customer))
}

function wireCustomers(chart) {
  document.querySelectorAll('[data-customer]').forEach(function (b) {
    b.addEventListener('click', function () {
      pickCustomer(chart, b.getAttribute('data-customer'))
    })
  })
}

export default {
  components: {
    apexchart: VueApexCharts,
  },
  data: function () {
    return {
      series: seriesFor('returning'),
      chartOptions: {
        chart: {
          id: 'orders',
          type: 'line',
          height: 380,
          toolbar: { show: false },
        },
        colors: ['#0f766e', '#ea580c'],
        stroke: { curve: 'smooth', width: 3 },
        markers: { size: 4, strokeWidth: 0, hover: { size: 6 } },
        dataLabels: {
          enabled: true,
          style: { fontSize: '11px' },
          background: { enabled: true, borderWidth: 0, opacity: 0.85 },
        },
        xaxis: { categories: MONTHS },
        yaxis: { min: 0, title: { text: 'Orders' } },
        highlightFilter: { line: { dashArray: 5 } },
        legend: { position: 'bottom' },
        tooltip: { shared: true, intersect: false },
      },
      customerTimer: null,
    }
  },
  mounted: function () {
    // The vue-apexcharts wrapper owns the render, so reach the live instance by
    // its chart.id. (seriesFor / pickCustomer live in the shared head script.)
    var me = this
    me.customerTimer = window.setInterval(function () {
      var chart = ApexCharts.getChartByID('orders')
      if (!chart) return
      window.clearInterval(me.customerTimer)
      wireCustomers(chart)
    }, 50)
  },
  beforeDestroy: function () {
    window.clearInterval(this.customerTimer)
  },
}
</script>

<style>
body {
  font-family:
    -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  background: #fafbfc;
  color: #2c3e50;
  margin: 0;
  padding: 32px 16px;
}
.wrap {
  max-width: 720px;
  margin: 0 auto;
}
h1 {
  font-size: 22px;
  margin: 0 0 8px;
}
p {
  color: #5b6b78;
  line-height: 1.5;
  margin: 0 0 20px;
}
.chart-wrap {
  background: #fff;
  border-radius: 8px;
  padding: 16px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.04);
}
.actions {
  margin: 0 0 14px;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
}
.actions span {
  font-size: 13px;
  color: #5b6b78;
  margin-right: 4px;
}
.actions button {
  color: #2c3e50;
  background: #fff;
  border: 1px solid #cfd8e3;
  padding: 6px 14px;
  font-weight: 600;
  font-size: 13px;
  border-radius: 6px;
  cursor: pointer;
}
.actions button.active {
  color: #fff;
  background: #0f766e;
  border-color: #0f766e;
}
</style>