@nebula.js/sn-bullet-chart
The bullet chart displays a gauge with extended options.
Bullet charts can be used to visualize and compare performance of
a measure to a target value and to a qualitative scale,
such as poor, average, and good.
Requirements
Requires @nebula.js/stardust version 1.7.0 or later.
Installing
If you use npm: npm install @nebula.js/sn-bullet-chart. You can also load through the script tag directly from https://unpkg.com.
Usage
In the example below, the sales in different quarters are compared
using a bullet chart.
import { embed } from '@nebula.js/stardust';
import bulletChart from '@nebula.js/sn-bullet-chart';
const nuked = embed(app, {
types: [
{
name: 'bullet-chart',
load: () => Promise.resolve(bulletChart),
},
],
});
nuked.render({
element: document.querySelector('.bullet'),
type: 'bullet-chart',
fields: ['Quarter', '=Sum(Sales)'],
properties: {
title: 'Sales by Quarters',
},
});
You can create a bullet chart with one dimension and one measure,
or no dimension and multiple measures.
| 1 | 1 | A bullet chart with columns corresponding to different values in the dimension |
| 0 | n | A bullet chart with columns corresponding to the measures, aggreated over the dimension. |
More examples
Horizontal bullet chart with common range
Sometime it is easier for the eyes to perceive the information from a
bullet chart if it is horizontal and its measure has a common range.
nuked.render({
element: document.querySelector('.bullet'),
type: 'bullet-chart',
fields: ['Quarter', '=Sum(Sales)'],
properties: {
title: 'Sales by Quarters',
orientation: 'horizontal',
measureAxis: {
commonRange: true,
dock: 'near',
},
},
});
Adding target
By adding targets, you can compare not only the sales between
different quarters but also the sale of each quarter to its sale target.
nuked.render({
element: document.querySelector('.bullet'),
type: 'bullet-chart',
fields: ['Quarter'],
properties: {
title: 'Sales by Quarters',
qHyperCubeDef: {
qMeasures: [
{
qDef: {
qDef: 'Sum(Sales)',
target: 'Sum([Sale targets])',
},
qSortBy: {
qSortByNumeric: -1,
},
qAttributeExpressions: [
{
qExpression: 'Sum([Sale targets])',
id: 'bullet-target',
},
],
},
],
qInitialDataFetch: [
{
qLeft: 0,
qTop: 0,
qWidth: 15,
qHeight: 500,
},
],
},
orientation: 'horizontal',
measureAxis: {
commonRange: true,
dock: 'near',
},
},
});
Add color segments
You can also add color segments to the chart to show poor/normal/good
performance. Here two limits
are added, splitting the range into three segments: red (lower than
90% of the target), yellow (within 90-110% of the target),
and green (higher than 110% of the target).
nuked.render({
element: document.querySelector('.bullet'),
type: 'bullet-chart',
properties: {
title: 'Sales by Quarters',
qHyperCubeDef: {
qDimensions: [
{
qDef: {
qFieldDefs: ['Quarter'],
qSortCriterias: [{ qSortByAscii: 1 }],
},
},
],
qMeasures: [
{
qDef: {
qDef: 'Sum(Sales)',
target: 'Sum([Sale targets])',
conditionalColoring: {
segments: {
limits: [
{
value: {
qValueExpression: {
qExpr: 'Sum([Sale targets])*0.9',
},
},
},
{
value: {
qValueExpression: {
qExpr: 'Sum([Sale targets])*1.1',
},
},
},
],
paletteColors: [
{
color: '#7c4345',
},
{
color: '#e0db4d',
},
{
color: '#53ad55',
},
],
},
},
},
qSortBy: {
qSortByNumeric: -1,
},
qAttributeExpressions: [
{
id: 'bullet-target',
qExpression: 'Sum([Sale targets])',
},
{
id: 'bullet-segment',
qExpression: 'Sum([Sale targets])*0.9',
},
{
id: 'bullet-segment',
qExpression: 'Sum([Sale targets])*1.1',
},
],
},
],
qInitialDataFetch: [
{
qLeft: 0,
qTop: 0,
qWidth: 15,
qHeight: 500,
},
],
qInterColumnSortOrder: [0, 1],
},
orientation: 'horizontal',
measureAxis: {
commonRange: true,
dock: 'near',
},
},
});
Multiple measures, no dimension
The bullet chart can also be defined with no dimension and multiple measures.
Each bar represents corresponding measure aggregate over the dimension.
nuked.render({
element: document.querySelector('.bullet'),
type: 'bullet-chart',
fields: ['=Sum(Coffee)', '=Sum(Tea)', '=Sum(Sales)'],
properties: {
title: 'Sales of Coffe, Tea, and Total',
},
});
Bullet chart plugins
A plugin can be passed into a bullet chart to add or modify its capability
or visual appearance.
A plugin needs to be defined before it can be rendered together with the chart.
const majorAxisPlugin = {
info: {
name: 'major-axis-plugin',
type: 'component-definition',
},
fn: ({ keys, layout }) => {
const componentDefinition = {
type: 'axis',
key: keys.COMPONENT.MAJOR_AXIS,
settings: {
labels: {
fontFamily: 'Tahoma, san-serif',
fontSize: '15px',
fill: 'darkred',
},
},
};
return componentDefinition;
},
};
nuked.render({
element: document.getElementById('object'),
type: 'sn-bullet-chart',
plugins: [majorAxisPlugin],
properties,
});
});
The plugin definition is an object, with two properties info and fn.
The fn returns a picasso.js component. To build this component,
some important chart internals are passed into the argument object of fn.
const pluginArgs = {
layout,
keys: {
SCALE: {
MAIN: {
MAJOR: KEYS.SCALE.MAIN.MAJOR,
MINOR: KEYS.SCALE.MAIN.MINOR,
},
},
COMPONENT: {
BAR: KEYS.COMPONENT.BAR,
MAJOR_AXIS: KEYS.COMPONENT.MAJOR_AXIS,
MAJOR_AXIS_TITLE: KEYS.COMPONENT.MAJOR_AXIS_TITLE,
BULLET_AXIS: KEYS.COMPONENT.BULLET_AXIS,
},
COLLECTION: {
MAIN,
},
},
};
With plugins, you can either add new components or modify existing components
of the bullet chart.
Add new components
The new component can be a standard Picasso component
or a custom Picasso component. Here we demo a standard
reference line component.
const meanReferenceLinePlugin = {
info: {
name: 'mean-reference-line-plugin',
type: 'component-definition',
},
fn: ({ keys, layout }) => {
const targets = layout.qHyperCube.qDataPages[0].qMatrix.map((item) => item[1].qAttrExps.qValues[0].qNum);
const averageOfTargets = targets.reduce((accumulator, currentValue) => accumulator + currentValue) / targets.length;
const componentDefinition = {
key: 'mean-reference-line',
type: 'ref-line',
layout: { displayOrder: 2 },
lines: {
x: [
{
line: {
stroke: 'darkgray',
strokeWidth: 5,
},
scale: keys.SCALE.MINOR,
value: averageOfTargets,
},
],
},
};
return componentDefinition;
},
};
Modify existing components
As an example, the positions and the appearance of the axes can be
modified completely by plugins.
To overide an existing component, fn should returns a picasso.js component
that has the same key as the existing component (keys.COMPONENT.BULLET_AXIS in
this example)
const bulletAxisPlugin = {
info: {
name: 'bullet-axis-plugin',
type: 'component-definition',
},
fn: ({ keys, layout }) => {
const componentDefinition = {
type: 'box-axis',
key: keys.COMPONENT.BULLET_AXIS,
settings: {
labels: {
fontFamily: 'Tahoma, san-serif',
fontSize: '15px',
fill: 'darkblue',
},
line: { stroke: 'gray' },
},
};
return componentDefinition;
},
};
Plugins disclaimer
- The plugins API is still experimental.
- We can not guarantee our charts to be compatible with all different settings, especially when modifying existing components.