Negative Value on a Bullet Chart

641 views
Skip to first unread message

Manisha

unread,
Feb 13, 2012, 6:09:04 PM2/13/12
to d3-js
Hi,

I'm trying to make a custom bullet chart that would show negative
values, as well as positive. So the range would be from -100 through
100. And the shading on the negative half of the bullet would be
different color than the positive.

I can fake this by layering rectangles in the same Y coordinate (and
specifying styling), but wondered if there was a cleaner programatic
way to achieve this.

Thanks,
Manisha

Scott Murray

unread,
Feb 13, 2012, 7:16:35 PM2/13/12
to d3...@googlegroups.com
Yes, you can do this by including the logic within a custom function where you set each element's style. As in:

.style("fill", function(d) {
if (d < 0) {
return "red"; //Value is negative
} else {
return "green"; //Value is positive
}
});

I'm not sure what you mean by a bullet chart, but you can use this principle to style each of the SVG elements however you like.

Scott

Manisha

unread,
Feb 13, 2012, 7:59:33 PM2/13/12
to d3-js
Hi,

Thanks for that. By "bullet" I was referring to a style of charting
designed by Stephen Few. There is a sample in the examples file in the
d3.js.
http://mbostock.github.com/d3/ex/bullet.html

But it is only for positive values. The axis must start at 0. In fact,
I'm not really clear on whether or not any of the predefined axis
functions work with negative domains.

My workaround is to draw and layer rectangles for all the values I
need, including beginning and end of range. The formatting branch will
help with that.

-Manisha

Scott Murray

unread,
Feb 13, 2012, 8:20:28 PM2/13/12
to d3...@googlegroups.com
Makes sense.

D3 axis generators can definitely use negative values for their input domains (e.g., -100 to 100), so that shouldn't be the issue.

Do you have code you can share?

Manisha

unread,
Feb 14, 2012, 10:11:54 PM2/14/12
to d3-js
Hi Scott,

I was confusing myself because I wasn't offseting the bars correctly
to account for the negative values. They were all positioned on the
left. So I figured it out. Here's the code I came up with, not sure
it's the most efficient. But mostly working.

<!DOCTYPE html>
<html>
<head>
<title>Bar Chart</title>
<script type="text/javascript" src="../js/d3.js"></script>
<style type="text/css">
body{ font: 10px sans-serif; color: blue;}
.line {stroke: black; stroke-width: 1;}
.axis {shape-rendering: crispEdges;}
.axis path {fill: none;}
.x.axis line {stroke: #000000;stroke-opacity: .2;}
.y.axis path {stroke: black;}
</style>
<script type="text/javascript">
var $barh = 20;
</script>
</head>
<body>
<script type="text/javascript">
//FIRST CHART OVERALL
var data = [-16,40,87,7];//min, median, max, curVal
var curVal = 7;
var m = [30, 30, 30, 30],
w = 400 + m[1] + m[3],
h = $barh*data.length + m[0] + m[2];

var $midpoint = w/2;

var format = d3.format(",.0f");

var x = d3.scale.linear().range([0,w]);
//var y = d3.scale.ordinal().rangeBands([0, $barh * data.length]);
var xAxis = d3.svg.axis().scale(x).orient("bottom").tickSize(-
h).ticks(4);

var svg = d3.select("body").append("svg")
.attr("width", w+m[1]+m[3])
.attr("height", h)
.append("g")
.attr("transform", "translate(" + m[3] + "," + m[0] + ")");

// Set the scale domain.
x.domain([-100, 100]);
//y.domain(data);

var bar = svg.selectAll("g.bar")
.data(data)
.enter().append("g")
.attr("class", "bar")
.attr("transform", function(d,i) {
if(d<0){
return "translate("+Math.abs(x(d))+"," + 0 + ")";
} else if(d>0){return "translate("+$midpoint+"," + 0 + ")";
} else {return "translate(0," + 0 + ")";}
})
bar.append("rect")
.attr("width", function(d) {
if(d<0){
return Math.abs($midpoint - x(d));
} else if(d>0){return x(d)-$midpoint;
} else {return x(d);}
})
.attr("height", $barh)
.attr("fill", function(d) {
if(d<=0){
return "#F26F68";
} else { return "#6E8C8C";}
})
.attr("opacity", function(d) {
if(d==curVal){
return 1;
} else { return .1;}
})
bar.append("text")
.attr("class", "value")
.style("font-weight", function(d){
if (d==curVal){ return "bold";}
})
.attr("x", function(d) {
if(d<0){
return -2;//-2 is a padding workaround
} else if(d>0){return x(d)-$midpoint-2;}
})
.attr("y", $barh / 2)
.attr("dx", -3) //would be padding but is being overridden by x
value
.attr("dy", ".35em")
.attr("text-anchor", "end")
.text(function(d) { return format(d); })
var line = svg.selectAll("svg")
.data(data)
.enter().append("line")
.attr("class", "line")
.attr("x1", function(d) {
if(d<0){
return Math.abs(x(d));
} else if(d>0){return x(d);}
})
.attr("x2", function(d) {
if(d<0){
return Math.abs(x(d));
} else if(d>0){return x(d);}
})
.attr("y1", 0)
.attr("y2", $barh)
.style("stroke-width", function(d) {
if (d == curVal) {
return 2;
}
})
.style("stroke-dasharray", function(d) {
if (d !== curVal) {
return "3,3,2";
} else {
return "6,.1";
}
})
//END OF FIRST CHART OVERALL

svg.append("g")
.attr("class", "x axis")
.attr("transform", function(d) { return "translate(0," + (h-m[0]-
m[2]) + ")"; })
.call(xAxis);



</script>
</body>
</html>
> >> I'm not sure what you mean by abulletchart, but you can use this principle to style each of the SVG elements however you like.
>
> >>         Scott
>
> >> On Feb 13, 2012, at 3:09 PM, Manisha wrote:
>
> >>> Hi,
>
> >>> I'm trying to make a custombulletchart that would show negative
> >>> values, as well as positive. So the range would be from -100 through
> >>> 100. And the shading on the negative half of thebulletwould be
Reply all
Reply to author
Forward
0 new messages