Skip to content

Commit

Permalink
Separate scales per bullet.
Browse files Browse the repository at this point in the history
Rather sharing the x-scale across bullet multiples, compute a separate scale per multiple. This
gives the user more control over the scale: by setting the range values across multiples (in data),
the user can precisely control the resulting scales. For transitions, we cache the old scale value
in a hidden __chart__ attribute. This is a bit of a hack (it might be cleaner to persist it in the
data of a child element), but it might provide a sticky way for charts to communicate with each
other in the future, say for performing transitions across chart types.
  • Loading branch information
mbostock committed Apr 9, 2011
1 parent a8d4f77 commit acb5fbe
Show file tree
Hide file tree
Showing 3 changed files with 275 additions and 331 deletions.
302 changes: 137 additions & 165 deletions d3.chart.js
Expand Up @@ -11,6 +11,7 @@

d3.chart.bullet = function() {
var orient = "left", // TODO top & bottom
reverse = false,
duration = 0,
ranges = d3_chart_bulletRanges,
markers = d3_chart_bulletMarkers,
Expand All @@ -19,176 +20,151 @@ d3.chart.bullet = function() {
height = 30,
tickFormat = null;

// For each small multiple…
function bullet(g) {
var max = 0,
reverse = orient == "right" || orient == "bottom";

// Convert the data to a standardized representation, and if necessary also
// compute the maximum value, needed to standardize the x-scale across
// multiples.
g.map(function(d, i) {
var r = ranges.call(this, d, i).slice().sort(d3.descending),
m = markers.call(this, d, i).slice().sort(d3.descending),
z = measures.call(this, d, i).slice().sort(d3.descending);
max = Math.max(max, r[0], m[0], z[0]);
return {
ranges: r,
markers: m,
measures: z,
data: d
};
});

// Update the x-scales.
var x0,
x1 = d3.scale.linear()
.domain([0, max])
g.each(function(d, i) {
var rangez = ranges.call(this, d, i).slice().sort(d3.descending),
markerz = markers.call(this, d, i).slice().sort(d3.descending),
measurez = measures.call(this, d, i).slice().sort(d3.descending),
g = d3.select(this);

// Compute the new x-scale.
var x1 = d3.scale.linear()
.domain([0, Math.max(rangez[0], markerz[0], measurez[0])])
.range(reverse ? [width, 0] : [0, width]);

// Initialize the old scale, if this is the first render.
var ticks = g.selectAll("g.ticks");
if (ticks.empty()) {
x0 = d3.scale.linear()
.domain([0, Infinity])
.range(x1.range());
ticks.data([x0])
.enter().append("svg:g")
.attr("class", "ticks");
ticks = g.selectAll("g.ticks");
} else {
// Otherwise retrieve the old scale
ticks.each(function(d) {
x0 = d;
});
}

// Derive width-scales from the x-scales.
var w0 = d3_chart_bulletWidth(x0),
w1 = d3_chart_bulletWidth(x1);

// Update the range rects.
var range = g.selectAll("rect.range")
.data(d3_chart_bulletRanges);

range.enter().append("svg:rect")
.attr("class", function(d, i) { return "range s" + i; })
.attr("width", w0)
.attr("height", height)
.attr("x", reverse ? x0 : 0)
.transition()
.duration(duration)
.attr("width", w1)
.attr("x", reverse ? x1 : 0);

range.transition()
.duration(duration)
.attr("x", reverse ? x1 : 0)
.attr("width", w1)
.attr("height", height);

// Update the measure rects.
var measure = g.selectAll("rect.measure")
.data(d3_chart_bulletMeasures);

measure.enter().append("svg:rect")
.attr("class", function(d, i) { return "measure s" + i; })
.attr("width", w0)
.attr("height", height / 3)
.attr("x", reverse ? x0 : 0)
.attr("y", height / 3)
.transition()
.duration(duration)
.attr("width", w1)
.attr("x", reverse ? x1 : 0);

measure.transition()
.duration(duration)
.attr("width", w1)
.attr("height", height / 3)
.attr("x", reverse ? x1 : 0)
.attr("y", height / 3);

// Update the marker lines.
var marker = g.selectAll("line.marker")
.data(d3_chart_bulletMarkers);

marker.enter().append("svg:line")
.attr("class", "marker")
.attr("x1", x0)
.attr("x2", x0)
.attr("y1", height / 6)
.attr("y2", height * 5 / 6)
.transition()
.duration(duration)
.attr("x1", x1)
.attr("x2", x1);

marker.transition()
.duration(duration)
.attr("x1", x1)
.attr("x2", x1)
.attr("y1", height / 6)
.attr("y2", height * 5 / 6);

// Compute the tick format.
var format = tickFormat || x1.tickFormat(8);

// Update the tick groups.
var tick = ticks.selectAll("g.tick")
.data(x1.ticks(8), format);

// Initialize the ticks with the old scale, x0.
var tickEnter = tick.enter().append("svg:g")
.attr("class", "tick")
.attr("transform", d3_chart_bulletTranslate(x0))
.attr("opacity", 1e-6);

tickEnter.append("svg:line")
.attr("y1", height)
.attr("y2", height * 7 / 6);

tickEnter.append("svg:text")
.attr("text-anchor", "middle")
.attr("dy", "1em")
.attr("y", height * 7 / 6)
.text(format);

// Transition the entering ticks to the new scale, x1.
tickEnter.transition()
.duration(duration)
.attr("transform", d3_chart_bulletTranslate(x1))
.attr("opacity", 1);

// Transition the updating ticks to the new scale, x1.
var tickUpdate = tick.transition()
.duration(duration)
.attr("transform", d3_chart_bulletTranslate(x1))
.attr("opacity", 1);

tickUpdate.select("line")
.attr("y1", height)
.attr("y2", height * 7 / 6);

tickUpdate.select("text")
.attr("y", height * 7 / 6);

// Transition the exiting ticks to the new scale, x1.
tick.exit().transition()
.duration(duration)
.attr("transform", d3_chart_bulletTranslate(x1))
.attr("opacity", 1e-6)
.remove();

// Lastly, restore the original data and update the previous scale!
g.map(d3_chart_bulletData);
g.selectAll("g.ticks")
.data([x1]);
// Retrieve the old x-scale, if this is an update.
var x0 = this.__chart__ || d3.scale.linear()
.domain([0, Infinity])
.range(x1.range());

// Stash the new scale.
this.__chart__ = x1;

// Derive width-scales from the x-scales.
var w0 = d3_chart_bulletWidth(x0),
w1 = d3_chart_bulletWidth(x1);

// Update the range rects.
var range = g.selectAll("rect.range")
.data(rangez);

range.enter().append("svg:rect")
.attr("class", function(d, i) { return "range s" + i; })
.attr("width", w0)
.attr("height", height)
.attr("x", reverse ? x0 : 0)
.transition()
.duration(duration)
.attr("width", w1)
.attr("x", reverse ? x1 : 0);

range.transition()
.duration(duration)
.attr("x", reverse ? x1 : 0)
.attr("width", w1)
.attr("height", height);

// Update the measure rects.
var measure = g.selectAll("rect.measure")
.data(measurez);

measure.enter().append("svg:rect")
.attr("class", function(d, i) { return "measure s" + i; })
.attr("width", w0)
.attr("height", height / 3)
.attr("x", reverse ? x0 : 0)
.attr("y", height / 3)
.transition()
.duration(duration)
.attr("width", w1)
.attr("x", reverse ? x1 : 0);

measure.transition()
.duration(duration)
.attr("width", w1)
.attr("height", height / 3)
.attr("x", reverse ? x1 : 0)
.attr("y", height / 3);

// Update the marker lines.
var marker = g.selectAll("line.marker")
.data(markerz);

marker.enter().append("svg:line")
.attr("class", "marker")
.attr("x1", x0)
.attr("x2", x0)
.attr("y1", height / 6)
.attr("y2", height * 5 / 6)
.transition()
.duration(duration)
.attr("x1", x1)
.attr("x2", x1);

marker.transition()
.duration(duration)
.attr("x1", x1)
.attr("x2", x1)
.attr("y1", height / 6)
.attr("y2", height * 5 / 6);

// Compute the tick format.
var format = tickFormat || x1.tickFormat(8);

// Update the tick groups.
var tick = g.selectAll("g.tick")
.data(x1.ticks(8), format);

// Initialize the ticks with the old scale, x0.
var tickEnter = tick.enter().append("svg:g")
.attr("class", "tick")
.attr("transform", d3_chart_bulletTranslate(x0))
.attr("opacity", 1e-6);

tickEnter.append("svg:line")
.attr("y1", height)
.attr("y2", height * 7 / 6);

tickEnter.append("svg:text")
.attr("text-anchor", "middle")
.attr("dy", "1em")
.attr("y", height * 7 / 6)
.text(format);

// Transition the entering ticks to the new scale, x1.
tickEnter.transition()
.duration(duration)
.attr("transform", d3_chart_bulletTranslate(x1))
.attr("opacity", 1);

// Transition the updating ticks to the new scale, x1.
var tickUpdate = tick.transition()
.duration(duration)
.attr("transform", d3_chart_bulletTranslate(x1))
.attr("opacity", 1);

tickUpdate.select("line")
.attr("y1", height)
.attr("y2", height * 7 / 6);

tickUpdate.select("text")
.attr("y", height * 7 / 6);

// Transition the exiting ticks to the new scale, x1.
tick.exit().transition()
.duration(duration)
.attr("transform", d3_chart_bulletTranslate(x1))
.attr("opacity", 1e-6)
.remove();
});
}

// left, right, top, bottom
bullet.orient = function(x) {
if (!arguments.length) return orient;
orient = x;
reverse = orient == "right" || orient == "bottom";
return bullet;
};

Expand Down Expand Up @@ -249,10 +225,6 @@ function d3_chart_bulletMeasures(d) {
return d.measures;
}

function d3_chart_bulletData(d) {
return d.data;
}

function d3_chart_bulletTranslate(x) {
return function(d) {
return "translate(" + x(d) + ",0)";
Expand Down
2 changes: 1 addition & 1 deletion d3.chart.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit acb5fbe

Please sign in to comment.