|
// 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; |
|
} |
|
}); |