Box & Whisker

The Box and Whisker series (AKA boxplots) show groups of numerical data through their quartiles. They have lines extending vertically from the boxes (whiskers) indicating variability outside the upper and lower quartiles. This sample demonstrates binding the series with pre-processed statistics through the boxPlotData property.

This sample also shows how to use the showInnerPoints, showOutliers, showMeanLine, showMeanMarker and rotated properties to customize the look and feel of your Box and Whisker series.

Learn about FlexChart | Box and Whisker Documentation | FlexChart API Reference

import 'bootstrap.css'; import '@mescius/wijmo.styles/wijmo.css'; import './styles.css'; // import { CollectionView, SortDescription, isArray } from '@mescius/wijmo'; import { FlexChart, Palettes } from '@mescius/wijmo.chart'; import { BoxWhisker } from '@mescius/wijmo.chart.analytics'; import { createBoxPlotData, getData } from './data'; // document.readyState === 'complete' ? init() : window.onload = init; // function init() { let data = getData(); let view = getView(data); let salesSeries = new BoxWhisker({ name: 'Sales', binding: 'sales', groupWidth: 0.7, gapWidth: 0.2, // boxPlotData lets BoxWhisker use pre-processed statistics instead of recalculating them from the raw arrays. boxPlotData: data.map(item => item.salesBoxPlotData) }); let expensesSeries = new BoxWhisker({ name: 'Expenses', binding: 'expenses', groupWidth: 0.7, gapWidth: 0.2, boxPlotData: data.map(item => item.expensesBoxPlotData) }); let sortMode = document.querySelector('#sortMode'); let filterMode = document.querySelector('#filterMode'); let outliers = document.querySelector('#outliers'); let viewState = document.querySelector('#viewState'); // create the chart let theChart = new FlexChart('#theChart', { itemsSource: view, bindingX: 'country', tooltip: { content: '{seriesName}: <b>{x}</b>' }, palette: getRandomPalette() }); // // create BoxWhisker series for 'sales' and add it to the chart theChart.series.push(salesSeries); // // create BoxWhisker series for 'expenses' and add it to the chart theChart.series.push(expensesSeries); // // customize the BoxWhisker series document.querySelector('#innerPoints').addEventListener('click', e => { theChart.series.forEach((series) => { series.showInnerPoints = e.target.checked; }); }); document.querySelector('#outliers').addEventListener('click', e => { theChart.series.forEach((series) => { series.showOutliers = e.target.checked; }); setBoxPlotData(data, salesSeries, expensesSeries, outliers.checked); }); document.querySelector('#meanline').addEventListener('click', e => { theChart.series.forEach((series) => { series.showMeanLine = e.target.checked; }); }); document.querySelector('#meanmarker').addEventListener('click', e => { theChart.series.forEach((series) => { series.showMeanMarker = e.target.checked; }); }); document.querySelector('#rotated').addEventListener('click', e => { theChart.rotated = e.target.checked; }); document.querySelector('#sortMode').addEventListener('change', () => { applyViewState(view, sortMode.value, filterMode.value); updateViewState(view, viewState); }); document.querySelector('#filterMode').addEventListener('change', () => { applyViewState(view, sortMode.value, filterMode.value); updateViewState(view, viewState); }); document.querySelector('#btnDeleteFirst').addEventListener('click', () => { if (view.items.length) { view.removeAt(0); updateViewState(view, viewState); } }); // // randomize the data document.querySelector('#btnRandomize').addEventListener('click', () => { data = getData(); view = getView(data); theChart.beginUpdate(); theChart.itemsSource = view; setBoxPlotData(data, salesSeries, expensesSeries, outliers.checked); theChart.endUpdate(); applyViewState(view, sortMode.value, filterMode.value); updateViewState(view, viewState); }); setBoxPlotData(data, salesSeries, expensesSeries, outliers.checked); updateViewState(view, viewState); } function getView(data) { let view = new CollectionView(data); view.sortDescriptions.push(new SortDescription('country', true)); return view; } function applyViewState(view, sortMode, filterMode) { view.deferUpdate(() => { view.sortDescriptions.clear(); if (sortMode === 'asc') { view.sortDescriptions.push(new SortDescription('country', true)); } else if (sortMode === 'desc') { view.sortDescriptions.push(new SortDescription('country', false)); } if (filterMode === 'am') { view.filter = item => item.country.charAt(0).toUpperCase() <= 'M'; } else if (filterMode === 'nz') { view.filter = item => item.country.charAt(0).toUpperCase() >= 'N'; } else { view.filter = null; } }); } function updateViewState(view, viewState) { let countries = view.items.map((item) => item.country).join(', '); viewState.textContent = 'Visible items (' + view.items.length + '/' + view.sourceCollection.length + '): ' + (countries || 'None'); } function setBoxPlotData(data, salesSeries, expensesSeries, showOutliers) { salesSeries.boxPlotData = data.map(item => showOutliers ? createBoxPlotData(item.sales, true) : item.salesBoxPlotData); expensesSeries.boxPlotData = data.map(item => showOutliers ? createBoxPlotData(item.expenses, true) : item.expensesBoxPlotData); } // function getRandomPalette() { let palettes = Object.keys(Palettes).filter(prop => isArray(Palettes[prop])); let rand = Math.floor(Math.random() * palettes.length); // return Palettes[palettes[rand]]; }
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>MESCIUS Wijmo FlexChart BoxWhisker</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- SystemJS --> <script src="https://cdn.jsdelivr.net/npm/systemjs@0.21.5/dist/system.src.js" integrity="sha512-skZbMyvYdNoZfLmiGn5ii6KmklM82rYX2uWctBhzaXPxJgiv4XBwJnFGr5k8s+6tE1pcR1nuTKghozJHyzMcoA==" crossorigin="anonymous"></script> <script src="systemjs.config.js"></script> <script> System.import('./src/app'); </script> </head> <body> <div class="container-fluid"> <div class="mb-3"> <label for="innerPoints">Show Inner Points:</label> <input id="innerPoints" type="checkbox"> <label for="outliers">Show Outliers:</label> <input id="outliers" type="checkbox"> </div> <div class="mb-3"> <label for="meanline">Show Mean Line:</label> <input id="meanline" type="checkbox"> <label for="meanmarker">Show Mean Marker:</label> <input id="meanmarker" type="checkbox"> </div> <div class="mb-3"> <label for="rotated">Rotated:</label> <input id="rotated" type="checkbox"> <label for="btnRandomize">Randomize Data</label> <button id="btnRandomize" class="btn btn-outline-secondary"> Go </button> </div> <div class="mb-3"> <label for="sortMode">Sort:</label> <select id="sortMode" class="form-control form-control-inline"> <option value="asc" selected>Country A-Z</option> <option value="desc">Country Z-A</option> <option value="none">None</option> </select> <label for="filterMode">Filter:</label> <select id="filterMode" class="form-control form-control-inline"> <option value="all" selected>All</option> <option value="am">Countries A-M</option> <option value="nz">Countries N-Z</option> </select> <label for="btnDeleteFirst">Delete First Visible Item</label> <button id="btnDeleteFirst" class="btn btn-outline-secondary"> Delete </button> </div> <div id="viewState" class="view-state"></div> <div id="theChart"></div> </div> </body> </html>
// Create random raw arrays together with pre-processed box plot statistics. export function getData() { let countries = ['US', 'Canada', 'Mexico', 'Germany', 'UK', 'France', 'Japan', 'Korea', 'China'], data = []; // for (let i = 0; i < countries.length; i++) { let sales = getRandomArray(20, 10000); let expenses = getRandomArray(20, 5000); data.push({ country: countries[i], sales: sales, expenses: expenses, salesBoxPlotData: createBoxPlotData(sales), expensesBoxPlotData: createBoxPlotData(expenses) }); } // return data; } // function getRandomArray(count, max) { let arr = []; // for (var i = 0; i < count; i++) { arr.push(Math.random() * max / 3 + Math.random() * max / 3 + Math.random() * max / 3); } // return arr; } export function createBoxPlotData(values, showOutliers = false) { let data = values.slice().sort((a, b) => a - b); let len = data.length; let total = data.reduce((sum, value) => sum + value, 0); let firstQuartile = quartileInclusive(data, 0.25); let median = quartileInclusive(data, 0.5); let thirdQuartile = quartileInclusive(data, 0.75); let iqr = 1.5 * Math.abs(thirdQuartile - firstQuartile); let minLimit = firstQuartile - iqr; let maxLimit = thirdQuartile + iqr; let outlierPoints = []; let min = data[0]; let max = data[len - 1]; let hasWhiskerValue = false; if (showOutliers) { min = data[len - 1]; max = data[0]; data.forEach(value => { if (value < minLimit || value > maxLimit) { outlierPoints.push(value); } else { hasWhiskerValue = true; if (value < min) { min = value; } if (value > max) { max = value; } } }); if (!hasWhiskerValue) { min = data[0]; max = data[len - 1]; } } return { min: min, max: max, mean: total / len, firstQuartile: firstQuartile, thirdQuartile: thirdQuartile, median: median, outlierPoints: outlierPoints, innerPoints: data.filter(value => value > min && value < max) }; } function quartileInclusive(data, percent) { let len = data.length; if (len === 1) { return data[0]; } let realIndex = (len - 1) * percent + 1; let index = Math.floor(realIndex); let fraction = realIndex - index; let base = data[index - 1]; return base + (data[index] - base) * fraction; }
.wj-flexchart { height: 300px; } label { width: 160px; text-align: right; } .form-control-inline { display: inline-block; width: auto; } .view-state { margin: 12px 0; } body { margin-bottom: 24px; }
(function (global) { System.config({ transpiler: 'plugin-babel', babelOptions: { es2015: true, }, meta: { '*.css': { loader: 'css' }, }, paths: { // paths serve as alias 'npm:': 'node_modules/', 'cdn:': 'https://cdn.mescius.io/demoapps/packages/wijmojs/5.20261.52-rc/', 'pcdn:': 'https://cdn.jsdelivr.net/npm/' }, // map tells the System loader where to look for things map: { '@mescius/wijmo': 'cdn:@mescius/wijmo/index.js', '@mescius/wijmo.input': 'cdn:@mescius/wijmo.input/index.js', '@mescius/wijmo.styles': 'cdn:@mescius/wijmo.styles', '@mescius/wijmo.cultures': 'cdn:@mescius/wijmo.cultures', '@mescius/wijmo.chart': 'cdn:@mescius/wijmo.chart/index.js', '@mescius/wijmo.chart.analytics': 'cdn:@mescius/wijmo.chart.analytics/index.js', '@mescius/wijmo.chart.animation': 'cdn:@mescius/wijmo.chart.animation/index.js', '@mescius/wijmo.chart.annotation': 'cdn:@mescius/wijmo.chart.annotation/index.js', '@mescius/wijmo.chart.finance': 'cdn:@mescius/wijmo.chart.finance/index.js', '@mescius/wijmo.chart.finance.analytics': 'cdn:@mescius/wijmo.chart.finance.analytics/index.js', '@mescius/wijmo.chart.hierarchical': 'cdn:@mescius/wijmo.chart.hierarchical/index.js', '@mescius/wijmo.chart.interaction': 'cdn:@mescius/wijmo.chart.interaction/index.js', '@mescius/wijmo.chart.radar': 'cdn:@mescius/wijmo.chart.radar/index.js', '@mescius/wijmo.chart.render': 'cdn:@mescius/wijmo.chart.render/index.js', '@mescius/wijmo.chart.webgl': 'cdn:@mescius/wijmo.chart.webgl/index.js', '@mescius/wijmo.chart.map': 'cdn:@mescius/wijmo.chart.map/index.js', '@mescius/wijmo.gauge': 'cdn:@mescius/wijmo.gauge/index.js', '@mescius/wijmo.grid': 'cdn:@mescius/wijmo.grid/index.js', '@mescius/wijmo.grid.detail': 'cdn:@mescius/wijmo.grid.detail/index.js', '@mescius/wijmo.grid.filter': 'cdn:@mescius/wijmo.grid.filter/index.js', '@mescius/wijmo.grid.search': 'cdn:@mescius/wijmo.grid.search/index.js', '@mescius/wijmo.grid.style': 'cdn:@mescius/wijmo.grid.style/index.js', '@mescius/wijmo.grid.grouppanel': 'cdn:@mescius/wijmo.grid.grouppanel/index.js', '@mescius/wijmo.grid.multirow': 'cdn:@mescius/wijmo.grid.multirow/index.js', '@mescius/wijmo.grid.transposed': 'cdn:@mescius/wijmo.grid.transposed/index.js', '@mescius/wijmo.grid.transposedmultirow': 'cdn:@mescius/wijmo.grid.transposedmultirow/index.js', '@mescius/wijmo.grid.pdf': 'cdn:@mescius/wijmo.grid.pdf/index.js', '@mescius/wijmo.grid.sheet': 'cdn:@mescius/wijmo.grid.sheet/index.js', '@mescius/wijmo.grid.xlsx': 'cdn:@mescius/wijmo.grid.xlsx/index.js', '@mescius/wijmo.grid.selector': 'cdn:@mescius/wijmo.grid.selector/index.js', '@mescius/wijmo.grid.cellmaker': 'cdn:@mescius/wijmo.grid.cellmaker/index.js', '@mescius/wijmo.nav': 'cdn:@mescius/wijmo.nav/index.js', '@mescius/wijmo.odata': 'cdn:@mescius/wijmo.odata/index.js', '@mescius/wijmo.olap': 'cdn:@mescius/wijmo.olap/index.js', '@mescius/wijmo.rest': 'cdn:@mescius/wijmo.rest/index.js', '@mescius/wijmo.pdf': 'cdn:@mescius/wijmo.pdf/index.js', '@mescius/wijmo.pdf.security': 'cdn:@mescius/wijmo.pdf.security/index.js', '@mescius/wijmo.viewer': 'cdn:@mescius/wijmo.viewer/index.js', '@mescius/wijmo.xlsx': 'cdn:@mescius/wijmo.xlsx/index.js', '@mescius/wijmo.undo': 'cdn:@mescius/wijmo.undo/index.js', '@mescius/wijmo.interop.grid': 'cdn:@mescius/wijmo.interop.grid/index.js', '@mescius/wijmo.touch': 'cdn:@mescius/wijmo.touch/index.js', '@mescius/wijmo.cloud': 'cdn:@mescius/wijmo.cloud/index.js', '@mescius/wijmo.barcode': 'cdn:@mescius/wijmo.barcode/index.js', '@mescius/wijmo.barcode.common': 'cdn:@mescius/wijmo.barcode.common/index.js', '@mescius/wijmo.barcode.composite': 'cdn:@mescius/wijmo.barcode.composite/index.js', '@mescius/wijmo.barcode.specialized': 'cdn:@mescius/wijmo.barcode.specialized/index.js', jszip: 'pcdn:jszip@3.8.0/dist/jszip.js', 'bootstrap.css': 'pcdn:bootstrap@5.3.8/dist/css/bootstrap.min.css', css: 'pcdn:systemjs-plugin-css@0.1.37/css.js', 'plugin-babel': 'pcdn:systemjs-plugin-babel@0.0.25/plugin-babel.js', 'systemjs-babel-build': 'pcdn:systemjs-plugin-babel@0.0.25/systemjs-babel-browser.js', }, // packages tells the System loader how to load when no filename and/or no extension packages: { src: { defaultExtension: 'js', }, node_modules: { defaultExtension: 'js', }, }, }); })(this);