@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.