Bullet Graphs

The BulletGraph is a type of linear gauge designed specifically for use in dashboards. It displays a single key measure along with a comparative measure and qualitative ranges to instantly signal whether the measure is good, bad, or in some other state.

Bullet Graphs were created and popularized by dashboard design expert Stephen Few.

import 'bootstrap.css'; import '@grapecity/wijmo.styles/wijmo.css'; import './styles.css'; import * as wijmo from '@grapecity/wijmo'; import * as gauge from '@grapecity/wijmo.gauge'; // document.readyState === 'complete' ? init() : window.onload = init; // function init() { // dashboard dada let dashboard = [ { heading: 'Revenue', sub: 'US$, thousands', max: 400, actual: 345, target: 340, bad: 210, good: 300 }, { heading: 'Profit', sub: '%', max: 50, actual: 37, target: 32, bad: 20, good: 30 }, { heading: 'Order Size', sub: 'US$, average', max: 600, actual: 320, target: 520, bad: 400, good: 500 }, { heading: 'New Customers', sub: 'count', max: 1500, actual: 1100, target: 1000, bad: 600, good: 900 }, { heading: 'Satisfaction', sub: '0 to 5', max: 5, actual: 4, target: 4.5, bad: 2.5, good: 4.5 }, ]; // // create the dashboard let table = document.createElement('table'); document.getElementById('dashboard').appendChild(table); dashboard.forEach(function (item) { let tr = document.createElement('tr'); table.appendChild(tr); // // heading let td = document.createElement('td'); tr.appendChild(td); let fmt = '<div class="heading">{heading}</div><div class="sub">{sub}</div>'; td.innerHTML = wijmo.format(fmt, item); // // warning td = document.createElement('td'); tr.appendChild(td); if (item.actual < item.target) { td.innerHTML = '<span class="glyphicon glyphicon-thumbs-down warning"></span>'; } // // bullet graph td = document.createElement('td'); tr.appendChild(td); let bg = new gauge.BulletGraph(document.createElement('div'), { value: item.actual, target: item.target, max: item.max, bad: item.bad, good: item.good }); td.appendChild(bg.hostElement); // // max value td = document.createElement('td'); tr.appendChild(td); td.innerHTML = wijmo.format('<span class="max">{max:n0}</span>', item); }); } <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>Bullet Graphs</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- SystemJS --> <script src="node_modules/systemjs/dist/system.src.js"></script> <script src="systemjs.config.js"></script> <script> System.import('./src/app'); </script> </head> <body> <div class="container-fluid"> <div id="dashboard"> </div> </div> </body> </html> #dashboard { padding: 12px; background: rgba(0, 0, 0, .02); box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23); } .heading { font-weight: bold; text-align: right; } .sub { font-size: 70%; text-align: right; } .max { font-size: 70%; } .warning { color: darkred; padding-left: 12px; } .wj-gauge { margin-left: 1em; margin-right: 1em; margin-bottom: 6px; } body { margin-bottom: 24pt; } import 'bootstrap.css'; import '@grapecity/wijmo.styles/wijmo.css'; import './styles.css'; // import { Component, enableProdMode, NgModule } from '@angular/core'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { BrowserModule } from '@angular/platform-browser'; import { WjGaugeModule } from '@grapecity/wijmo.angular2.gauge'; // @Component({ selector: 'app-component', templateUrl: 'src/app.component.html' }) export class AppComponent { dashboard = [ { heading: 'Revenue', sub: 'US$, thousands', max: 400, actual: 345, target: 340, bad: 210, good: 300 }, { heading: 'Profit', sub: '%', max: 50, actual: 37, target: 32, bad: 20, good: 30 }, { heading: 'Order Size', sub: 'US$, average', max: 600, actual: 320, target: 520, bad: 400, good: 500 }, { heading: 'New Customers', sub: 'count', max: 1500, actual: 1100, target: 1000, bad: 600, good: 900 }, { heading: 'Satisfaction', sub: '0 to 5', max: 5, actual: 4, target: 4.5, bad: 2.5, good: 4.5 }, ] } // @NgModule({ imports: [WjGaugeModule, BrowserModule], declarations: [AppComponent], bootstrap: [AppComponent] }) export class AppModule { } // enableProdMode(); // Bootstrap application with hash style navigation and global services. platformBrowserDynamic().bootstrapModule(AppModule); <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>Bullet Graphs</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- Polyfills --> <script src="node_modules/core-js/client/shim.min.js"></script> <script src="node_modules/zone.js/dist/zone.min.js"></script> <!-- SystemJS --> <script src="node_modules/systemjs/dist/system.js"></script> <script src="systemjs.config.js"></script> <script> // workaround to load 'rxjs/operators' from the rxjs bundle System.import('rxjs').then(function (m) { System.set(SystemJS.resolveSync('rxjs/operators'), System.newModule(m.operators)); System.import('./src/app.component'); }); </script> </head> <body> <app-component></app-component> </body> </html> <div class="container-fluid"> <div id="dashboard"> <table> <ng-template ngFor let-item [ngForOf]="dashboard"> <tr> <td> <div class="heading">{{item.heading}}</div> <div class="sub">{{item.sub}}</div> </td> <td> <span *ngIf="item.actual < item.target" class="glyphicon glyphicon-thumbs-down warning"></span> </td> <td> <wj-bullet-graph [value]="item.actual" [target]="item.target" [max]="item.max" [bad]="item.bad" [good]="item.good"></wj-bullet-graph> </td> <td> <div class="max">{{item.max | number:'1.0-0'}}</div> </td> </tr> </ng-template> </table> </div> </div> #dashboard { padding: 12px; background: rgba(0, 0, 0, .02); box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23); } .heading { font-weight: bold; text-align: right; } .sub { font-size: 70%; text-align: right; } .max { font-size: 70%; } .warning { color: darkred; padding-left: 12px; } .wj-gauge { margin-left: 1em; margin-right: 1em; margin-bottom: 6px; } body { margin-bottom: 24pt; } <template> <div class="container-fluid"> <div id="dashboard"> <table> <tr v-for="item in dashboard" :key="item.heading"> <td> <div class="heading">{{item.heading}}</div> <div class="sub">{{item.sub}}</div> </td> <td> <span v-if="item.actual < item.target" class="glyphicon glyphicon-thumbs-down warning"></span> </td> <td> <wj-bullet-graph :value="item.actual" :target="item.target" :max="item.max" :bad="item.bad" :good="item.good"></wj-bullet-graph> </td> <td> <div class="max">{{item.max | number:'1.0-0'}}</div> </td> </tr> </table> </div> </div> </template> <script> import 'bootstrap.css'; import '@grapecity/wijmo.styles/wijmo.css'; import Vue from 'vue'; import '@grapecity/wijmo.vue2.gauge'; let App = Vue.extend({ name: 'app', data: function(){ return { dashboard: [ { heading: 'Revenue', sub: 'US$, thousands', max: 400, actual: 345, target: 340, bad: 210, good: 300 }, { heading: 'Profit', sub: '%', max: 50, actual: 37, target: 32, bad: 20, good: 30 }, { heading: 'Order Size', sub: 'US$, average', max: 600, actual: 320, target: 520, bad: 400, good: 500 }, { heading: 'New Customers', sub: 'count', max: 1500, actual: 1100, target: 1000, bad: 600, good: 900 }, { heading: 'Satisfaction', sub: '0 to 5', max: 5, actual: 4, target: 4.5, bad: 2.5, good: 4.5 }, ] } } }) new Vue({ render: h => h(App) }).$mount('#app'); </script> <style> #dashboard { padding: 12px; background: rgba(0, 0, 0, .02); box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23); } .heading { font-weight: bold; text-align: right; } .sub { font-size: 70%; text-align: right; } .max { font-size: 70%; } .warning { color: darkred; padding-left: 12px; } .container-fluid .wj-gauge { margin-left: 1em; margin-right: 1em; margin-bottom: 6px; } body { margin-bottom: 24pt; } </style> <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>Bullet Graphs</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- SystemJS --> <script src="node_modules/systemjs/dist/system.src.js"></script> <script src="systemjs.config.js"></script> <script> System.import('./src/app.vue'); </script> </head> <body> <div id="app"> </div> </body> </html> import './app.css'; import 'bootstrap.css'; import '@grapecity/wijmo.styles/wijmo.css'; // import * as React from 'react'; import * as ReactDOM from 'react-dom'; // import * as wjGauge from '@grapecity/wijmo.react.gauge'; class App extends React.Component { constructor(props) { super(props); this.state = [ { heading: 'Revenue', sub: 'US$, thousands', max: 400, actual: 345, target: 340, bad: 210, good: 300 }, { heading: 'Profit', sub: '%', max: 50, actual: 37, target: 32, bad: 20, good: 30 }, { heading: 'Order Size', sub: 'US$, average', max: 600, actual: 320, target: 520, bad: 400, good: 500 }, { heading: 'New Customers', sub: 'count', max: 1500, actual: 1100, target: 1000, bad: 600, good: 900 }, { heading: 'Satisfaction', sub: '0 to 5', max: 5, actual: 4, target: 4.5, bad: 2.5, good: 4.5 }, ]; } render() { return (<div className="container-fluid"> <div id="dashboard"> <table> {this.renderTable()} </table> </div> </div>); } renderSpan(item) { if (item.actual < item.target) { return <span className="glyphicon glyphicon-thumbs-down warning"></span>; } return; } renderTableTr(item) { return (<tr key={item.heading}> <td> <div className="heading">{item.heading}</div> <div className="sub">{item.sub}</div> </td> <td> {this.renderSpan(item)} </td> <td> <wjGauge.BulletGraph value={item.actual} target={item.target} max={item.max} bad={item.bad} good={item.good}></wjGauge.BulletGraph> </td> <td> <div className="max">{item.max}</div> </td> </tr>); } renderTable() { let table = []; this.state.forEach(item => { table.push(this.renderTableTr(item)); }); return table; } } ReactDOM.render(<App />, document.getElementById('app')); <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>Bullet Graphs</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- SystemJS --> <script src="node_modules/systemjs/dist/system.src.js"></script> <script src="systemjs.config.js"></script> <script> System.import('./src/app'); </script> </head> <body> <div id="app"></div> </body> </html> #dashboard { padding: 12px; background: rgba(0, 0, 0, .02); box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23); } .heading { font-weight: bold; text-align: right; } .sub { font-size: 70%; text-align: right; } .max { font-size: 70%; } .warning { color: darkred; padding-left: 12px; } .container-fluid .wj-gauge { margin-left: 1em; margin-right: 1em; margin-bottom: 6px; } body { margin-bottom: 24pt; }