Revenue Dashboard Filtered by Product in JavaScript

Using ApexCharts with JavaScript

This Revenue Dashboard Filtered by Product example uses ApexCharts.js directly in JavaScript, with no wrapper component.

Install it with npm install apexcharts, then mount the chart with new ApexCharts(element, options).render().

JavaScript installation guide
// Shared by the vanilla-js, React and Vue builds.
//
// Revenue ($k) per product, region and quarter. The left chart sums it per
// product; the right one per region and quarter. Clicking a product filters
// the right chart BY it without dropping anything: every column keeps its
// full revenue, faded, and the product's share is drawn solid in front.
var PRODUCTS = ['Laptops', 'Phones', 'Tablets', 'Audio']
var REGIONS = ['North', 'South']
var QUARTERS = ['Q1', 'Q2', 'Q3', 'Q4']
var REVENUE = {
  Laptops: { North: [42, 38, 45, 61], South: [30, 28, 33, 44] },
  Phones: { North: [55, 49, 52, 70], South: [48, 51, 47, 66] },
  Tablets: { North: [18, 15, 17, 26], South: [22, 19, 21, 25] },
  Audio: { North: [12, 14, 13, 21], South: [9, 11, 10, 16] },
}

function sum(a) {
  return a.reduce(function (s, v) {
    return s + v
  }, 0)
}
function productTotal(p) {
  return sum(
    REGIONS.map(function (r) {
      return sum(REVENUE[p][r])
    }),
  )
}
function regionQuarters(r, onlyProduct) {
  return QUARTERS.map(function (q, k) {
    return sum(
      PRODUCTS.filter(function (p) {
        return !onlyProduct || p === onlyProduct
      }).map(function (p) {
        return REVENUE[p][r][k]
      }),
    )
  })
}

// The picked product, or null.
var picked = null

function productSeries() {
  var totals = PRODUCTS.map(productTotal)
  var s = { name: 'Revenue', data: totals }
  // On the source chart the pick is the whole of its own column, so it stays
  // solid while the others fade.
  if (picked) {
    s.highlightData = PRODUCTS.map(function (p, j) {
      return p === picked ? totals[j] : null
    })
  }
  return [s]
}

function quarterSeries() {
  return REGIONS.map(function (r) {
    var s = { name: r, data: regionQuarters(r) }
    if (picked) s.highlightData = regionQuarters(r, picked)
    return s
  })
}

function pickProduct(index) {
  var product = PRODUCTS[index]
  picked = picked === product ? null : product
  var readout = document.getElementById('readout')
  if (readout) {
    readout.innerHTML = picked
      ? 'Filtered by <b>' + picked + '</b>. Click it again to clear.'
      : 'Click a product to filter the quarters by it.'
  }
  // Let the click handler unwind before both charts redraw.
  Promise.resolve().then(function () {
    ApexCharts.getChartByID('products').updateSeries(productSeries())
    ApexCharts.getChartByID('quarters').updateSeries(quarterSeries())
  })
}

var options = {
  series: productSeries(),
  chart: {
    id: 'products',
    type: 'bar',
    height: 320,
    toolbar: { show: false },
    events: {
      dataPointSelection: function (e, ctx, opts) {
        pickProduct(opts.dataPointIndex)
      },
    },
  },
  colors: ['#0f766e'],
  plotOptions: {
    bar: { horizontal: true, barHeight: '64%', borderRadius: 3 },
  },
  states: { active: { filter: { type: 'none' } } },
  dataLabels: { enabled: false },
  xaxis: {
    categories: PRODUCTS,
    tickAmount: 3,
    labels: {
      formatter: function (v) {
        return '$' + v + 'k'
      },
    },
  },
  // On the source chart the pick is its own column, so a tooltip row for the
  // part would only repeat the total.
  highlightFilter: {
    tooltip: { show: false },
    dataLabels: { value: 'whole' },
  },
  legend: { show: false },
  title: { text: 'Revenue by product', style: { fontSize: '13px' } },
  tooltip: {
    y: {
      formatter: function (v) {
        return '$' + v + 'k'
      },
    },
  },
}

var chart = new ApexCharts(document.querySelector('#chart'), options)
chart.render()

var options1 = {
  series: quarterSeries(),
  chart: {
    id: 'quarters',
    type: 'bar',
    height: 320,
    toolbar: { show: false },
  },
  colors: ['#2563eb', '#f59e0b'],
  plotOptions: {
    bar: { columnWidth: '70%', borderRadius: 3 },
  },
  dataLabels: { enabled: false },
  stroke: { show: true, width: 1, colors: ['transparent'] },
  xaxis: { categories: QUARTERS },
  yaxis: {
    labels: {
      formatter: function (v) {
        return '$' + v + 'k'
      },
    },
  },
  legend: { position: 'bottom' },
  title: { text: 'Revenue by quarter', style: { fontSize: '13px' } },
  tooltip: {
    shared: true,
    intersect: false,
    y: {
      formatter: function (v) {
        return '$' + v + 'k'
      },
    },
  },
}

var chart1 = new ApexCharts(document.querySelector('#chart2'), options1)
chart1.render()