Skip to content

Instantly share code, notes, and snippets.

@CodeXmonk
Last active December 20, 2015 19:59
Show Gist options
  • Save CodeXmonk/6187523 to your computer and use it in GitHub Desktop.
Save CodeXmonk/6187523 to your computer and use it in GitHub Desktop.
BulleT: a variant based on mbostock's bullet with Miso's d3.chart.js

Another T score bulleT

After finishing this chart I found Jerome Cukier brilliant blog post: d3: scales, and color. I wish I could have read it before I started the first line of code.

This bulleT chart is a variant based on mbostock's bullet with Miso's d3.chart.js

Leonardo’s advice recalling Cennino’s approach: "The painter ought first to exercise his hand by coping drawings by good masters..."

"Zen lesson: Eliminate what doesn’t matter to make more room for what does."

problem - (my) solution (hopefully)

I think: nowadays the level of data visualization quality seems to be much higher than that of our data many times. Is there anything that can be done in terms of that? I suppose that in some situation we can ask our DataVis engine (D3 and D3.chart.js here) to help with this problem. Here I try to find a solution to show how certain or sometimes informative/interesting is the datum that is used for the chart.

the reason why and how I did it can be read and seen here.

( my original bulleT chart is a very simple one, without d3.chart.js, without update button, but can be set horizontally and vertically - feedback is welcomed: @CodeXmonk or codexmonk@gmail.com )

Some of my younger colleagues used to ask for help about interpreting MMPI scores (Minnesota Multiphasic Personality Inventory).

Sometimes a question arose:

"here is the result... it’s normal in general, but just look at that scale... that is so high... what can be the problem?"

My question was: "what’s his business?"

Answer: "don’t know exactly... some kind of IT person... maybe a programmer or something like that."

And me: "that’s the answer!-)"

There is a scale in MMPI the so called Si (Social Introversion) measuring people orientation. I wouldn’t say that software developers are all introverts. I just found it more frequent but it is still my own doubtful experience and a very subjective opinion not based on any research data.

So, if we had a high T score on a (for instance: Si) scale but at the same time we saw higher sample statistics (mean, sd) we could consider it as normal based on that special or peculiar sample or criterion group.

Or we can see something else: when we see nothing

( uncertainty rules everywhere. or where? - I'm not sure)

That would be the "Humble" datum above. Greyish and blueish areas are equal to each other. In a situation like this we will know that there is no any sample statistics in the background – for instance we don’t have research data on introverted software developers:-D

In this situation that very raw score can be compared only with the big, general population data...

And something else can be seen here

or rather in the source code...

The four vertical and four horizontal charts are sharing on the same data ( actually they are just the same, only the horizontal/vertical position is changed ) but the single vertical chart on the margin has different data source... and all of them are using the same d3.chart.bulleT as a base...

It might be something that is explained by Mike Bostock in Towards Reusable Charts and by Irene Ros in Introducing d3.chart and Mike Pennisi Exploring Reusability with D3.js

(function() {
"use strict";
function HumbleData(){
this.original = [
{"title":"Humble","dimension":"data","terjedelem":[20,80],"ranges":[30,40,50,60,70],"rangesLine":[50],"measures":[30,40,60,70],"markers":[50,50]}
]
}
function Data(){
this.original = [
{"title":"Man","dimension":"height","terjedelem":[20,80],"ranges":[30,40,50,60,70],"rangesLine":[50],"measures":[38.93,47.15,63.59,71.81],"markers":[55.37,50]},
{"title":"Woman","dimension":"height","terjedelem":[20,80],"ranges":[30,40,50,60,70],"rangesLine":[50],"measures":[27.02,35.82,53.44,62.24],"markers":[44.63,50]},
{"title":"Man","dimension":"weight","terjedelem":[20,80],"ranges":[30,40,50,60,70],"rangesLine":[50],"measures":[39.70,48.31,65.54,74.15],"markers":[56.93,50]},
{"title":"Woman","dimension":"weight","terjedelem":[20,80],"ranges":[30,40,50,60,70],"rangesLine":[50],"measures":[29.24,36.21,50.14,57.11],"markers":[43.18,50]}
]
}
var getData = new Data();
var data = getData.original;
var getHumbleData = new HumbleData();
var dataHumble = getHumbleData.original;
// take care! my english is terrible...
// I need the whole path on my local server, I don't know why - must be something with Kohana 3.3
//d3.json("/assets/js/D3_Chart/D3.chart/examples_joey_GitHub/06_horizontal_vertical/scripts/bulleTs-data.json", function(error, data) {
// this one was the original d3.json version for start - when bulleTs-data.json is in the same directory
//d3.json("bulleTs-data.json", function(error, data) {
// and this is mine for getting data from the above function Data() but it needs to be called again, as it is done below: bulleT();
function bulleT() {
var myChartVertical = d3.select("#BulleT_vertical").chart("BulleTs", {
seriesCount: data.length
});
myChartVertical.margin({ top: 40, right: 20, bottom: 120, left: 5 })
.orientation("vertical")
.width([45,"vertical"]) /* bit of a hack too. if array.length > 1 it means */
.height([350,"vertical"])/* that it is for vertical chart*/
.duration(1000);
myChartVertical.draw(data);
var myChartHorizontal = d3.select("#BulleT_horizontal").chart("BulleTs", {
seriesCount: data.length
});
myChartHorizontal.margin({ top: 5, right: 40, bottom: 20, left: 120 })
.orientation("horizontal")
.width([350])/* as writen before. now array.length == 1, so it is for horizontal */
.height([45])
.duration(1000);
myChartHorizontal.draw(data);
/* as we don't know anything about sample statistics only subjet's score will change*/
/* that's what I call humble data */
var myChartVerticalHumble = d3.select("#BulleT_vertical_Humble").chart("BulleTs", {
seriesCount: dataHumble.length
});
myChartVerticalHumble.margin({ top: 40, right: 20, bottom: 120, left: 5 })
.orientation("vertical")
.width([45,"vertical"])
.height([350,"vertical"])
.duration(1000);
myChartVerticalHumble.draw(dataHumble);
d3.selectAll("button#Randomize").on("click", function() {
data.forEach(randomize);
myChartVertical.draw(data);
myChartHorizontal.draw(data);
dataHumble.forEach(randomizeHumble);
myChartVerticalHumble.draw(dataHumble);
});
d3.selectAll("button#Reset").on("click", function() {
getData = new Data();
data = getData.original;
myChartVertical.draw(data);
myChartHorizontal.draw(data);
getHumbleData = new HumbleData();
dataHumble = getHumbleData.original;
myChartVerticalHumble.draw(dataHumble);
});
}
function randomize(d) {
var k = 10;
if (!d.randomizer) d.randomizer = randomizer(d);
d.markers[0] = d.randomizer(d.markers[0],40,60);
d.markers[1] = d.randomizer(d.markers[1],21,78);
k = d.randomizer(k,4,9);
d.measures[0] = d.markers[0] - 2*k;
d.measures[1] = d.markers[0] - k;
d.measures[2] = d.markers[0] + k;
d.measures[3] = d.markers[0] + 2*k;
return d;
}
function randomizeHumble(d) {
var k = 10;
if (!d.randomizer) d.randomizer = randomizer(d);
d.markers[1] = d.randomizer(d.markers[1],21,78);
k = d.randomizer(k,4,9);
return d;
}
// Returns a random integer between min and max
// Using Math.round() will give you a non-uniform distribution!
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function randomizer(d,min,max) {
return function(d,min,max) {
return getRandomInt(min,max);
};
}
bulleT();
})();
// Chart design for T scores based on original bullet chart by Mike Bostock:
// http://bl.ocks.org/mbostock/4061961
// with d3.chart.js (0.1.2)
// http://misoproject.com/d3-chart/
d3.chart("BulleT", {
initialize: function() {
var chart = this;
this.xScale = d3.scale.linear();
this.base.classed("BulleT", true);
this.titleGroup = this.base.append("g");
this.title = this.titleGroup.append("text")
.attr("class", "title");
this.dimension = this.titleGroup.append("text")
.attr("class", "subtitle s2");
this.subtitle = this.titleGroup.append("text")
.attr("class", "subtitle s2");
// Default configuration
this._margin = { top: 0, right: 0, bottom: 0, left: 0 };
this.orientation("");
this.duration(0);
this.markers(function(d) {
return d.markers;
});
this.measures(function(d) {
return d.measures;
});
this.width(100);
this.height(100);
this.tickFormat(this.xScale.tickFormat(d3.format(".0d")));
this.reverse(false);
this.orient("left");
this.terjedelem(function(d) {
return d.terjedelem;
});
this.ranges(function(d) {
return d.ranges;
});
this.rangesLine(function(d) {
return d.rangesLine;
});
this.layer("ranges", this.base.append("g").classed("ranges", true), {
dataBind: function(data) {
var data_ranges = new Array();
// @CodeXmonk: a bit of hack too - ToDo later. This layer operates on "ranges" data, ranges[2] not needed
data_ranges[0] = data.ranges[0];
data_ranges[1] = data.ranges[1];
data_ranges[2] = data.ranges[3];
data_ranges[3] = data.ranges[4];
terjedelem = data.terjedelem;
data_ranges.unshift(terjedelem[1]);
return this.selectAll("rect.range").data(data_ranges);
},
insert: function() {
return this.append("rect");
},
events: {
enter: function(d, i) {
var orientation = chart.orientation();
var terjedelem = chart.terjedelem;
this.attr("class", function(d, i) { return "range s" + i; })
.attr("width", chart.xScale)
.attr("height", function(){
if( orientation == "vertical" ){
return chart.width();
}else{
return chart.height();
}
})
.attr("x", this.chart()._reverse ? chart.xScale : terjedelem[0]);
},
"merge:transition": function() {
var terjedelem = chart.terjedelem;
this.duration(chart.duration())
.attr("width", chart.xScale)
.attr("x", chart._reverse ? chart.xScale : terjedelem[0]);
},
exit: function() {
this.remove();
}
}
});
/******************************************************************************/
this.layer("rangesLine", this.base.append("g").classed("rangesLine", true), {
dataBind: function(data) {
// @CodeXmonk: This layer operates on "ranges" data
data_rangesLine = data.rangesLine;
return this.selectAll("line.range").data(data_rangesLine);
},
insert: function() {
return this.append("line");
},
events: {
enter: function(d, i) {
var orientation = chart.orientation();
this.attr("class", function(d, i) { return "range line"; })
.attr("x1", chart.xScale)
.attr("x2", chart.xScale)
.attr("y1", 0)
.attr("y2", function(){
if( orientation == "vertical" ){
return chart.width();
}else{
return chart.height();
}
});
},
"merge:transition": function() {
var orientation = chart.orientation();
this.attr("class", function(d, i) { return "range line"; })
.attr("x1", chart.xScale)
.attr("x2", chart.xScale)
.attr("y1", 0)
.attr("y2", function(){
if( orientation == "vertical" ){
return chart.width();
}else{
return chart.height();
}
});
},
exit: function() {
this.remove();
}
}
});
/******************************************************************************/
this.layer("measures", this.base.append("g").classed("measures", true), {
dataBind: function(data) {
// @CodeXmonk: This layer operates on "measures" data
data_measures = data.measures;
var terjedelem = data.terjedelem;
data_measures.unshift(terjedelem[1]);
return this.selectAll("rect.measure").data(data_measures);
},
insert: function() {
return this.append("rect");
},
events: {
enter: function() {
var orientation = chart.orientation();
var hy;
if( orientation == "vertical" ){
hy = chart.width() / 2;
}else{
hy = chart.height() / 2;
}
var terjedelem = chart.terjedelem();
this.attr("class", function(d, i) { return "measure s" + i; })
.attr("width", chart.xScale)
.attr("height", hy)
.attr("x", terjedelem[0])
.attr("y", hy/2);
},
"merge:transition": function() {
var terjedelem = chart.terjedelem;
this.duration(chart.duration())
.attr("width", chart.xScale)
.attr("x", terjedelem[0])
}
}
});
/******************************************************************************/
this.layer("markerSample", this.base.append("g").classed("markerSample", true), {
dataBind: function(data) {
// @CodeXmonk: This layer operates on "markerSample" datum
data = data.markers;
return this.selectAll("line.marker").data(data.slice(0,1));
},
insert: function() {
return this.append("line");
},
events: {
enter: function() {
var orientation = chart.orientation();
var whichOne;
if( orientation == "vertical" ){
whichOne = chart.width();
}else{
whichOne = chart.height();
}
this.attr("class", "marker Sample")
.attr("x1", chart.xScale)
.attr("x2", chart.xScale)
.attr("y1", whichOne / 4)
.attr("y2", whichOne - (whichOne/4) );
},
"merge:transition": function() {
var orientation = chart.orientation();
var whichOne;
if( orientation == "vertical" ){
whichOne = chart.width();
}else{
whichOne = chart.height();
}
this.duration(chart.duration())
.attr("x1", chart.xScale)
.attr("x2", chart.xScale)
.attr("y1", whichOne / 4)
.attr("y2", whichOne - (whichOne/4) );
}
}
});
/******************************************************************************/
this.layer("markerSubject", this.base.append("g").classed("markerSubject", true), {
dataBind: function(data) {
// @CodeXmonk: This layer operates on "markerSubject" datum
data = data.markers;
return this.selectAll("rect.marker").data(data.slice(1,2));
},
insert: function() {
return this.append("rect");
},
events: {
enter: function() {
var orientation = chart.orientation();
var whichOne;
if( orientation == "vertical" ){
whichOne = chart.width();
}else{
whichOne = chart.height();
}
this.attr("class", "marker Subject")
.attr("width", 6)
.attr("y", -(whichOne/10))
.attr("height",function(d) {return whichOne+(whichOne/5);})
.attr("x", chart.xScale)
.attr("transform", "translate(-3,0)");
},
"merge:transition": function() {
var orientation = chart.orientation();
var whichOne;
if( orientation == "vertical" ){
whichOne = chart.width();
}else{
whichOne = chart.height();
}
this.duration(chart.duration())
.attr("width", 6)
.attr("y", -(whichOne/10))
.attr("height",function(d) {return whichOne+(whichOne/5);})
.attr("x", chart.xScale)
.attr("transform", "translate(-3,0)");
}
}
});
/******************************************************************************/
this.layer("ticks", this.base.append("g").classed("ticks", true), {
dataBind: function() {
var format = this.chart().tickFormat();
return this.selectAll("g.tick").data(this.chart().xScale.ticks(8), function(d) {
return this.textContent || format(d);
});
},
insert: function() {
var orientation = chart.orientation();
var whichOne;
if( orientation == "vertical" ){
whichOne = chart.width();
}else{
whichOne = chart.height();
}
var tick = this.append("g").attr("class", "tick");
var height = whichOne;
var format = chart.tickFormat();
tick.append("line")
.attr("y1", whichOne)
.attr("y2", whichOne * 7 / 6);
tick.append("text")
.attr("text-anchor", "middle")
.attr("dy", "1em")
.attr("y", whichOne * 7 / 6)
.attr("transform", function(){
if( orientation == "vertical" ){
return "translate("+(whichOne+whichOne/2)+","+(whichOne+whichOne/2)+")rotate(90)";
} /* @CodeXmonk: if vertical BUT its not exact yet - ToDo */
})
.text(format);
return tick;
},
events: {
enter: function() {
this.attr("transform", function(d) {
return "translate(" + chart.xScale(d) + ",0)";
})
.style("opacity",1);
},
"merge:transition": function() {
var orientation = chart.orientation();
var whichOne;
if( orientation == "vertical" ){
whichOne = chart.width();
}else{
whichOne = chart.height();
}
this.duration(chart.duration())
.attr("transform", function(d) {
return "translate(" + chart.xScale(d) + ",0)";
})
.style("opacity", 1);
this.select("line")
.attr("y1", whichOne)
.attr("y2", whichOne * 7 / 6);
this.select("text")
.attr("y", whichOne * 7 / 6);
},
"exit:transition": function() {
this.duration(chart.duration())
.attr("transform", function(d) {
return "translate(" + chart.xScale(d) + ",0)";
})
.style("opacity", 1e-6)
.remove();
}
}
});
d3.timer.flush();
},
transform: function(data) {
var orientation = this.orientation();
var height = this.height();
if( orientation == "vertical" ){
this.base.attr("transform","translate(15," + ( height + 10 ) + ")rotate(-90)");
}
// misoproject: Copy data before sorting
var newData = {
title: data.title,
dimension: data.dimension,
randomizer: data.randomizer,
terjedelem: data.terjedelem.slice(),
ranges: data.ranges.slice().sort(d3.descending),
rangesLine: data.rangesLine.slice(),
measures: data.measures.slice().sort(d3.descending),
markers: data.markers.slice()
};
this.xScale.domain([newData.terjedelem[0], newData.terjedelem[1]]);
this.titleGroup
.style("text-anchor", function(){
if( orientation == "vertical" ){
return "middle";
}else{
return "end";
}
} );
if( orientation == "vertical" ){
this.titleGroup.attr("transform", function(){ return "translate(-15,10)rotate(90)"});
}
this.dimension
.attr("dy", function(){
if( orientation == "vertical" ){
return "12px";
}else{
return "1.2em";
}
});
this.subtitle
.attr("dy", function(){
if( orientation == "vertical" ){
return "26px";
}else{
return "2.4em";
}
});
this.title.text(data.title);
this.dimension.text(data.dimension+"[T]");
this.subtitle.text(data.markers[1]);
this.subtitle.attr("class",function(d) {
switch (true)
{
case ( (data.markers[1] < 30) || (70 < data.markers[1]) ):
return "subtitle s04";
break;
break;
case ( (30 <= data.markers[1]) && (data.markers[1] < 40) ):
return "subtitle s13";
break;
case ( (40 <= data.markers[1]) && (data.markers[1] <= 60) ):
return "subtitle s2";
break;
case ( (60 < data.markers[1]) && (data.markers[1] <= 70) ):
return "subtitle s13";
break;
}
}
)
return newData;
},
// misoproject: reverse or not
reverse: function(x) {
if (!arguments.length) return this._reverse;
this._reverse = x;
return this;
},
// misoproject: left, right, top, bottom
orient: function(x) {
if (!arguments.length) return this._orient;
this._orient = x;
this._reverse = this._orient == "right" || this._orient == "bottom";
return this;
},
// @CodeXmonk: terjedelem (20,80)
terjedelem: function(x) {
if (!arguments.length) return this._terjedelem;
this._terjedelem = x;
return this;
},
// misoproject: ranges (bad, satisfactory, good)
ranges: function(x) {
if (!arguments.length) return this._ranges;
this._ranges = x;
return this;
},
// @CodeXmonk: for sample mean
rangesLine: function(x) {
if (!arguments.length) return this._ranges;
this._ranges = x;
return this;
},
// misoproject: markers (previous, goal)
markers: function(x) {
if (!arguments.length) return this._markers;
this._markers = x;
return this;
},
// misoproject: measures (actual, forecast)
measures: function(x) {
if (!arguments.length) return this._measures;
this._measures = x;
return this;
},
width: function(x) {
var margin, width_tmp;
if (!arguments.length) {
return this._width;
}
margin = this.margin();
width_tmp = x[0];
width_tmp = width_tmp - (margin.left + margin.right);
this._width = width_tmp;
if (x.length == 1){ /* @CodeXmonk: Scale needs to be put here when it's horizontal */
this.xScale.range(this._reverse ? [width_tmp, 0] : [0, width_tmp]);
}
this.base.attr("width", width_tmp);
return this;
},
height: function(x) {
var margin, height_tmp;
if (!arguments.length) {
return this._height;
}
margin = this.margin();
height_tmp = x[0];
height_tmp = height_tmp - (margin.top + margin.bottom);
this._height = height_tmp;
if (x.length != 1){ /* @CodeXmonk: Scale needs to be put here when it's vertical */
this.xScale.range(this._reverse ? [height_tmp, 0] : [0, height_tmp]);
}
this.base.attr("height", height_tmp);
this.titleGroup.attr("transform", "translate(-16," + height_tmp / 3 + ")");
return this;
},
margin: function(margin) {
if (!margin) {
return this._margin;
}
var margin_tmp = margin;
["top", "right", "bottom", "left"].forEach(function(dimension) {
if (dimension in margin_tmp) {
this._margin[dimension] = margin_tmp[dimension];
}
}, this);
this.base.attr("transform", "translate(" + this._margin.left + "," + this._margin.top + ")");
return this;
},
tickFormat: function(x) {
if (!arguments.length) return this._tickFormat;
this._tickFormat = x;
return this;
},
orientation: function(x) {
if (!arguments.length) return this._orientation;
this._orientation = x;
return this;
},
duration: function(x) {
if (!arguments.length) return this._duration;
this._duration = x;
return this;
}
});
// Chart design for T scores based on original bullet chart by Mike Bostock:
// http://bl.ocks.org/mbostock/4061961
// with d3.chart.js (0.1.2)
// http://misoproject.com/d3-chart/
d3.chart("BulleTs", {
initialize: function(options) {
var mixins = this.mixins = [];
var idx, len, mixin;
if (options && options.seriesCount) {
for (idx = 0, len = options.seriesCount; idx < len; ++idx) {
this._addSeries(idx);
}
}
},
_addSeries: function(idx) {
var mixin = this.mixin("BulleT", this.base.append("svg").append("g"));
// misoproject:
// Cache the prototype's implementation of `transform` so that it may
// be invoked from the overriding implementation. This is admittedly a
// bit of a hack, and it may point to a future improvement for d3.chart
var t = mixin.transform;
mixin.transform = function(data) {
return t.call(mixin, data[idx]);
};
this.mixins.push(mixin);
},
orientation: function(orientation) {
if (!arguments.length) {
return this._orientation;
}
this.mixins.forEach(function(mixin) {
mixin.orientation(orientation);
});
return this;
},
width: function(width) {
var width, width_tmp;
if (!arguments.length) {
return this._width;
}
if (width.length == 1){ /* @CodeXmonk: now its horizontal */
width_tmp = width[0];
}else{ /* @CodeXmonk: a little more space needed for titles when it's vertical */
width_tmp = width[0]+10;
}
this._width = width_tmp;
this.base.attr("width", width_tmp);
this.base.selectAll("svg").attr("width", width_tmp);
this.mixins.forEach(function(mixin) {
mixin.width(width);
});
return this;
},
height: function(height) {
var height, height_tmp;
if (!arguments.length) {
return this._height;
}
if (height.length == 1){ /* @CodeXmonk: now its horizontal */
height_tmp = height[0];
}else{ /*@CodeXmonk: only needed when it's vertical */
height_tmp = height[0]-100;
}
this._height = height_tmp;
this.base.selectAll("svg").attr("height", height_tmp );
if( (height.length != 1) ){ /*@CodeXmonk: there are more elements? then its vertical */
this.base.selectAll("svg").attr("transform", function(){ return "rotate(-90)";});
}
this.mixins.forEach(function(mixin) {
mixin.height(height);
});
return this;
},
duration: function(duration) {
if (!arguments.length) {
return this._duration;
}
this._duration = duration;
this.mixins.forEach(function(mixin) {
mixin.duration(duration);
});
},
margin: function(margin) {
this.mixins.forEach(function(mixin) {
mixin.margin(margin);
});
return this;
}
});
d3.chart.BulleT
/*! d3.chart - v0.1.2
* License: MIT Expat
* Date: 2013-07-30
*/
(function(t){"use strict";var e=t.d3Chart,r=t.d3Chart={},n=t.d3;r.noConflict=function(){return t.d3Chart=e,r},r.assert=function(t,e){if(!t)throw Error("[d3.chart] "+e)},r.assert(n,"d3.js is required"),r.assert("string"==typeof n.version&&n.version.match(/^3/),"d3.js version 3 is required")})(this),function(t){"use strict";var e=t.d3Chart,r=t.d3,n=function(t){e.assert(t,"Layers must be initialized with a base."),this._base=t,this._handlers={}};n.prototype.dataBind=function(){e.assert(!1,"Layers must specify a `dataBind` method.")},n.prototype.insert=function(){e.assert(!1,"Layers must specify an `insert` method.")},n.prototype.on=function(t,e,r){r=r||{},t in this._handlers||(this._handlers[t]=[]),this._handlers[t].push({callback:e,chart:r.chart||null})},n.prototype.off=function(t,e){var r,n=this._handlers[t];if(n){if(1===arguments.length)return n.length=0,undefined;for(r=n.length-1;r>-1;--r)n[r].callback===e&&n.splice(r,1)}},n.prototype.draw=function(t){var n,i,s,a,o,c,h,l;n=this.dataBind.call(this._base,t),e.assert(n&&n.call===r.selection.prototype.call,"Invalid selection defined by `Layer#dataBind` method."),e.assert(n.enter,"Layer selection not properly bound."),i=n.enter(),i._chart=this._base._chart,s=[{name:"update",selection:n},{name:"enter",selection:this.insert.bind(i)},{name:"merge",selection:n},{name:"exit",selection:n.exit.bind(n)}];for(var u=0,p=s.length;p>u;++u){if(c=s[u].name,a=s[u].selection,"function"==typeof a&&(a=a()),e.assert(a&&a.call===r.selection.prototype.call,"Invalid selection defined for '"+c+"' lifecycle event."),o=this._handlers[c])for(h=0,l=o.length;l>h;++h)a._chart=o[h].chart||this._base._chart,a.call(o[h].callback);if(o=this._handlers[c+":transition"],o&&o.length)for(a=a.transition(),h=0,l=o.length;l>h;++h)a._chart=o[h].chart||this._base._chart,a.call(o[h].callback)}},r.selection.prototype.layer=function(t){var e,r=new n(this);if(r.dataBind=t.dataBind,r.insert=t.insert,"events"in t)for(e in t.events)r.on(e,t.events[e]);return this.on=function(){return r.on.apply(r,arguments)},this.off=function(){return r.off.apply(r,arguments)},this.draw=function(){return r.draw.apply(r,arguments)},this}}(this),function(t,e){"use strict";function r(t){var e,r,n,i;if(!t)return t;for(r=arguments.length,e=1;r>e;e++)if(n=arguments[e])for(i in n)t[i]=n[i];return t}var n=t.d3Chart,i=t.d3,s=Object.hasOwnProperty,a=function(t){this.constructor=t},o=function(t,e){var r;return a.prototype=t.prototype,r=new a(t),t.apply(r,e),r},c=function(t,e){var r=this.constructor.__super__;r&&c.call(r,t,e),s.call(this.constructor.prototype,"initialize")&&this.initialize.apply(t,e)},h=function(t){this.base=t,this._layers={},this._mixins=[],this._events={},c.call(this,this,Array.prototype.slice.call(arguments,1))};h.prototype.unlayer=function(t){var e=this.layer(t),r=this._layerList.indexOf(e);return delete this._layers[t],this._layerList.splice(r,1),this},h.prototype.layer=function(t,e,r){var n;return 1===arguments.length?this._layers[t]:(n=e.layer(r),this._layers[t]=n,e._chart=this,n)},h.prototype.initialize=function(){},h.prototype.transform=function(t){return t},h.prototype.mixin=function(t,e){var r=Array.prototype.slice.call(arguments,2);r.unshift(e);var n=h[t],i=o(n,r);return this._mixins.push(i),i},h.prototype.draw=function(t){var e,r,n;t=this.transform(t);for(e in this._layers)this._layers[e].draw(t);for(r=0,n=this._mixins.length;n>r;r++)this._mixins[r].draw(t)},h.prototype.on=function(t,e,r){var n=this._events[t]||(this._events[t]=[]);return n.push({callback:e,context:r||this,_chart:this}),this},h.prototype.once=function(t,e,r){var n=this,i=function(){n.off(t,i),e.apply(this,arguments)};return this.on(t,i,r)},h.prototype.off=function(t,e,r){var n,i,s,a,o,c;if(0===arguments.length){for(t in this._events)this._events[t].length=0;return this}if(1===arguments.length)return s=this._events[t],s&&(s.length=0),this;for(n=t?[t]:Object.keys(this._events),o=0;n.length>o;o++)for(i=n[o],s=this._events[i],c=s.length;c--;)a=s[c],(e&&e===a.callback||r&&r===a.context)&&s.splice(c,1);return this},h.prototype.trigger=function(t){var r,n,i=Array.prototype.slice.call(arguments,1),s=this._events[t];if(s!==e)for(r=0;s.length>r;r++)n=s[r],n.callback.apply(n.context,i);return this},h.extend=function(t,e,n){var i,a=this;i=e&&s.call(e,"constructor")?e.constructor:function(){return a.apply(this,arguments)},r(i,a,n);var o=function(){this.constructor=i};return o.prototype=a.prototype,i.prototype=new o,e&&r(i.prototype,e),i.__super__=a.prototype,h[t]=i,i},i.chart=function(t){return 0===arguments.length?h:1===arguments.length?h[t]:h.extend.apply(h,arguments)},i.selection.prototype.chart=function(t){if(0===arguments.length)return this._chart;var e,r=h[t];return n.assert(r,"No chart registered with name '"+t+"'"),e=Array.prototype.slice.call(arguments,1),e.unshift(this),o(r,e)},i.selection.enter.prototype.chart=function(){return this._chart},i.transition.prototype.chart=i.selection.enter.prototype.chart}(this);
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>D3.chart.BulleT example</title>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="d3.chart.min.js"></script>
<style type="text/css">
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
margin: auto;
padding-top: 40px;
position: relative;
width: 960px;
}
table{
border: none;
margin-left:auto;
margin-right:auto;
}
td{text-align:center;}
td.wide{width:350px;}
td.narrow{width:60px;}
.BulleT { font: 10px sans-serif; margin-left:auto;margin-right:auto;}
.BulleT .marker { stroke: #4D4D4D; stroke-width: 2px;}
.BulleT .marker.Sample { fill-opacity:0; stroke: #999999; stroke-width: 2px; }
.BulleT .marker.Subject { fill-opacity:0; stroke: #000; stroke-width: 3px; }
.BulleT .tick line { stroke: #666; stroke-width: .5px; }
.BulleT .range.s0 { fill: #005C7A; }
.BulleT .range.s1 { fill: #29A3CC; }
.BulleT .range.s2 { fill: #c6dbef; }
.BulleT .range.s3 { fill: #29A3CC; }
.BulleT .range.s4 { fill: #005C7A; }
.BulleT .range.line { stroke: #000; stroke-width: 2px; }
.BulleT .measure.s0 { fill: #4D4D4D; }
.BulleT .measure.s1 { fill: #999999; }
.BulleT .measure.s2 { fill: #eeeeee; }
.BulleT .measure.s3 { fill: #999999; }
.BulleT .measure.s4 { fill: #4D4D4D; }
.BulleT .title { font-size: 12px; font-weight: bold; }
.BulleT .subtitle.s04 { fill: #000000; font-size: 14px; font-weight: bold;}
.BulleT .subtitle.s13 { fill: #4D4D4D; font-size: 12px; font-weight: bold;}
.BulleT .subtitle.s2 { fill: #999999; font-size: 10px;}
</style>
</head>
<body>
<table border="0">
<tr>
<td colspan="3">
<button id="Randomize">Randomize</button>
<button id="Reset">Reset</button>
</td>
</tr>
<tr>
<td class="wide">
<div id="BulleT_vertical"></div>
</td>
<td class="wide">
<div id="BulleT_horizontal"></div>
</td>
<td class="narrow">
<div id="BulleT_vertical_Humble"></div>
</td>
</tr>
</table>
<script src="bulleT-chart.js"></script>
<script src="bulleTs-chart.js"></script>
<script src="bulleT-app.js"></script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment