Crossfilter Dashboard in JavaScript
Using ApexCharts with JavaScript
This Crossfilter Dashboard 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().
// Deterministic trade records. outcome is derived from the fluctuation sign,
// so brushing the fluctuation histogram visibly reshapes the outcome donut.
function tradesData() {
var quarters = ['Q1', 'Q2', 'Q3', 'Q4']
var days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri']
var out = []
for (var i = 0; i < 120; i++) {
var pct =
3.0 * Math.sin(i / 5.0) +
1.3 * Math.sin(i / 1.7) +
0.4 * Math.cos(i / 3.1)
var vol = Math.round(120 + 70 * Math.sin(i / 6.0) + 40 * Math.cos(i / 2.3))
out.push({
id: i + 1,
quarter: quarters[Math.floor(i / 30) % 4],
day: days[(i * 3) % 5],
outcome: pct >= 0 ? 'Gain' : 'Loss',
pct: Math.round(pct * 100) / 100,
volume: Math.max(20, vol),
})
}
return out
}
// Register the shared record set BEFORE the charts are constructed.
ApexCharts.crossfilter({ id: 'trades', records: tradesData() })
var options = {
series: [],
chart: {
id: 'byQuarter',
type: 'donut',
height: 260,
fontFamily: 'Helvetica, Arial, sans-serif',
animations: { speed: 450 },
link: {
id: 'trades',
dimension: function (r) {
return r.quarter
},
reduce: 'count',
dimOpacity: 0.18,
},
},
title: { text: 'Quarter', align: 'left' },
legend: { position: 'bottom', fontSize: '12px' },
plotOptions: { pie: { expandOnClick: false } },
dataLabels: {
enabled: true,
formatter: function (v, o) {
return o.w.config.series[o.seriesIndex]
},
},
colors: ['#2563EB', '#0ea5e9', '#22c55e', '#f59e0b'],
}
var chart = new ApexCharts(document.querySelector('#chart'), options)
chart.render()
var options1 = {
series: [],
chart: {
id: 'byOutcome',
type: 'donut',
height: 260,
fontFamily: 'Helvetica, Arial, sans-serif',
animations: { speed: 450 },
link: {
id: 'trades',
dimension: function (r) {
return r.outcome
},
reduce: 'count',
dimOpacity: 0.18,
},
},
title: { text: 'Outcome', align: 'left' },
legend: { position: 'bottom', fontSize: '12px' },
plotOptions: { pie: { expandOnClick: false } },
dataLabels: {
enabled: true,
formatter: function (v, o) {
return o.w.config.series[o.seriesIndex]
},
},
colors: ['#22c55e', '#ef4444'],
}
var chart1 = new ApexCharts(document.querySelector('#chart2'), options1)
chart1.render()
var options2 = {
series: [],
chart: {
id: 'byDay',
type: 'bar',
height: 260,
fontFamily: 'Helvetica, Arial, sans-serif',
animations: { speed: 450 },
link: {
id: 'trades',
dimension: function (r) {
return r.day
},
reduce: 'count',
seriesName: 'Trades',
dimOpacity: 0.18,
},
},
title: { text: 'Day of week', align: 'left' },
plotOptions: {
bar: { columnWidth: '55%', borderRadius: 3, distributed: true },
},
legend: { show: false },
dataLabels: { enabled: false },
colors: ['#2563EB', '#0ea5e9', '#22c55e', '#f59e0b', '#F43F5E'],
}
var chart2 = new ApexCharts(document.querySelector('#chart3'), options2)
chart2.render()
var options3 = {
series: [],
chart: {
id: 'byFluctuation',
type: 'bar',
height: 240,
fontFamily: 'Helvetica, Arial, sans-serif',
animations: { speed: 400 },
zoom: { enabled: false },
selection: { enabled: true },
toolbar: {
autoSelected: 'selection',
tools: {
selection: true,
zoom: false,
pan: false,
reset: false,
download: false,
},
},
link: {
id: 'trades',
dimension: function (r) {
return r.pct
},
bins: { count: 26 },
dimOpacity: 0.16,
},
},
title: { text: 'Fluctuation % (brush a range)', align: 'left' },
plotOptions: { bar: { columnWidth: '92%' } },
dataLabels: { enabled: false },
xaxis: {
type: 'numeric',
tickAmount: 8,
labels: {
formatter: function (v) {
return Number(v).toFixed(1)
},
},
},
colors: ['#2563EB'],
}
var chart3 = new ApexCharts(document.querySelector('#chart4'), options3)
chart3.render()
var options4 = {
series: [],
chart: {
id: 'byVolume',
type: 'bar',
height: 240,
fontFamily: 'Helvetica, Arial, sans-serif',
animations: { speed: 400 },
zoom: { enabled: false },
selection: { enabled: true },
toolbar: {
autoSelected: 'selection',
tools: {
selection: true,
zoom: false,
pan: false,
reset: false,
download: false,
},
},
link: {
id: 'trades',
dimension: function (r) {
return r.volume
},
bins: { count: 22 },
dimOpacity: 0.16,
},
},
title: { text: 'Volume (brush a range)', align: 'left' },
plotOptions: { bar: { columnWidth: '92%' } },
dataLabels: { enabled: false },
xaxis: {
type: 'numeric',
tickAmount: 8,
labels: {
formatter: function (v) {
return Math.round(v)
},
},
},
colors: ['#0ea5e9'],
}
var chart4 = new ApexCharts(document.querySelector('#chart5'), options4)
chart4.render()
// Five charts (chart..chart4) + a live data table, all over the 'trades' set.
var cf = ApexCharts.getCrossfilter('trades')
var readout = document.getElementById('cfd-readout')
if (cf) {
// Bind the data table to the filtered rows; it re-renders on every change.
cf.dataTable(document.getElementById('cfd-table'), {
columns: [
{ field: 'id', label: '#' },
{ field: 'quarter', label: 'Quarter' },
{ field: 'day', label: 'Day' },
{ field: 'outcome', label: 'Outcome' },
{
field: 'pct',
label: 'Fluctuation %',
format: function (v) {
return v.toFixed(2) + '%'
},
},
{ field: 'volume', label: 'Volume' },
],
})
cf.on('change', function (state) {
var ids = Object.keys(state.filters)
var label = ids.length ? ids.join(', ') : 'none'
readout.textContent =
state.filteredCount +
' / ' +
state.total +
' trades (active: ' +
label +
')'
})
// Seed the readout with the unfiltered totals.
var s = cf.state()
readout.textContent =
s.filteredCount + ' / ' + s.total + ' trades (active: none)'
}
document.getElementById('cfd-reset').addEventListener('click', function () {
if (cf) cf.reset()
})