(This chart is a part of the d3-charts
collection available here.)
Shows how different parties fall on the spectrum of budget proposals relative to each other and a fabled centre in the middle.
There are three types of values types to display:
If you ever managed a budget in a spreadsheet, you are probably already familiar with denoting these as:
%
$
#
To take an example, say you want have an office with five desks, hire five more people, and as a result, need five more desks at 50 bucks a pop. You could represent this budgeting as:
% | $ | #
-------|-------|----
+100% | +$250 | +5
You can display one or several of these representations as you see fit for your explanatory chart.
(NB: Always remember the difference between per cent and percentage points.)
"Ryan Budget": {
"percentage": 0,
"money": 0
},
"Senate Democrats": {
"percentage": 2.3,
"money": 975000000000
},
"Progressive Caucus": {
"percentage": 14.1,
"money": 3900000000000
}
var thresholdTitle = "Budgets compared",
valueTypes = ["percentage", "money"],
moneyPrefix = "$", // "$", "£", "€"
moneySuffix = "", // "USD", "GBP", "EUR"
quantityNoun = "", // "Jobs", "F-35s"
valueLabel = "none", // "text", "symbol", "none"
useMultipleSymbols = false, // true, false
symbolType = "triangle-down";
// "circle", "cross", "diamond", "square",
// "triangle-up", "triangle-down"
valueTypes
are all the value( type)s you want to display, in the defined order.
They correspond to the value groups in your data, as can be seen in the data example above.
quantityNoun
is a noun for what your quantity number represents; it will be displayed on the chart, so do use short nouns.
valueLabel
displays a label for each value on the bar chart.
useMultipleSymbols
displays the value symbols in different shapes.
symbolType
chooses the shape of all the symbols, if you have set useMultipleSymbols
to false
.
Object.keys()
JavaScript function.xxxxxxxxxx
<html lang="en">
<head>
<meta charset="utf-8">
<style>
svg {
display: block;
}
line,
rect {
shape-rendering: crispEdges;
}
text {
font: 10px sans-serif;
}
text.title {
font-weight: bold;
}
path {
display: none;
}
path.symbol,
.legend path {
display: inline;
stroke: #000;
}
line {
stroke: #000;
}
rect {
fill: #ABDDA4;
}
line.divider {
stroke-dasharray: 4,2;
}
#graph {
/** Layout breaks when minima are removed */
min-width: 440px;
min-height: 150px;
width: 100%;
height: 100%;
max-width: 880px;
max-height: 300px;
margin: 0 auto;
}
</style>
</head>
<body>
<div id="graph"></div>
<!-- <script src="https://d3js.org/d3.v3.min.js"></script> -->
<script src="d3.min.js?v=3.2.8"></script>
<script type="text/javascript"charset="utf-8">
// Settings
var defaultWidth = 440,
defaultHeight = 150, // Compute dynanically?
/**
Accounting for
* valueTypes.length
* keys.length
*/
aspectRatio = defaultWidth/defaultHeight;
var wrapperWidth = parseInt(d3.select("#graph").style("width"), 10),
wrapperHeight = parseInt(d3.select("#graph").style("height"), 10);
var barHeight = 8,
dividerHeight = 7;
var textContainerSize = 16;
// fontsize + lineheight;
var margin = {
"top" : 15,
"right" : 20,
"bottom" : 0,
"left" : 20
};
// If landscape mode or square
if (wrapperWidth >= wrapperHeight) {
var height = wrapperHeight,
width = Math.round(height * aspectRatio);
// If protrait mode
} else {
var width = wrapperWidth,
height = Math.round(width / aspectRatio);
}
margin.hor = margin.left + margin.right;
margin.ver = margin.top + margin.bottom;
// Config
var thresholdTitle = "Budgets compared",
valueTypes = ["percentage", "money"], // ["percentage", "money", "quantity"]
moneyPrefix = "$", // "$", "£", "€"
moneySuffix = "", // "USD", "GBP", "EUR"
quantityNoun = "", // "Jobs", "F-35s"
valueLabel = "symbol", // "text", "symbol", "none"
useMultipleSymbols = false, // true, false
symbolType = "triangle-down";
// "circle", "cross", "diamond", "square",
// "triangle-up", "triangle-down"
// Data in JSON format
var jsonData = {
"Ryan Budget": {
"percentage": 0,
"money": 0
},
"Senate Democrats": {
"percentage": 2.3,
"money": 975000000000
},
"Progressive Caucus": {
"percentage": 14.1,
"money": 3900000000000
}
};
var domain = getValues(jsonData, valueTypes[0], true),
keys = Object.keys(jsonData);
// Helper functions
function getValues(jsonData, valueType, isFloat) {
return Object.keys(jsonData).map(function(key){
if (isFloat === true) {
return parseFloat(
jsonData[key][valueType]
);
} else {
return jsonData[key][valueType];
}
});
}
function displayValue(d, i, valueType) {
if (valueType === "percentage") {
if (d === 0) {
return 0 + "%";
}
else if (d < 0) {
return "-" + -1*d + "%";
} else { // d < 0
return shownPlusSign + d + "%";
}
}
else if (valueType === "money") {
var val = parseMoney.scale(money[i]);
if (d === 0) {
return moneyPrefix + 0 + moneySuffix;
}
else if (d < 0) {
return "-" + moneyPrefix + -1*val + parseMoney.symbol + moneySuffix;
} else { // d < 0
return shownPlusSign + moneyPrefix + val + parseMoney.symbol + moneySuffix;
}
}
else if (valueType === "quantity") {
var val = parseQuantity.scale(quantity[i]);
if (d === 0) {
return 0 + "" + quantityNoun;
}
else if (d < 0) {
return "-" + -1*val + parseQuantity.symbol + "" + quantityNoun;
} else { // d < 0
return shownPlusSign + val + parseQuantity.symbol + "" + quantityNoun;
}
}
}
for (var i = 0; i < valueTypes.length; i++) {
if (valueTypes[i] === "percentage") {
var percentage = getValues(jsonData, "percentage", true);
}
else if (valueTypes[i] === "money") {
var money = getValues(jsonData, "money", false);
var parseMoney = d3.formatPrefix(d3.max(money));
}
else if (valueTypes[i] === "quantity") {
var quantity = getValues(jsonData, "quantity", false);
var parseQuantity = d3.formatPrefix(d3.max(quantity));
}
}
var svg = d3.select("#graph")
.append("svg")
.attr({
// "width": width,
// "height": height
"viewBox": "0 0 " + width + " " + height,
"preserveAspectRatio": "xMinYMid"
});
var x = d3.scale.linear()
.domain(d3.extent(domain))
.range([0, width - margin.hor]);
// Show value plus signs for distinction, if negative values exist
var shownPlusSign = (d3.min(x.domain()) < 0) ? "+" : "";
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickSize(13)
.tickValues(domain)
.tickFormat("");
var g = svg.append("g")
.attr({
"class": "bar",
"transform": "translate(" + margin.left + "," + margin.top + ")"
});
// Bar
g.append("rect")
.attr({
"height": barHeight,
"x": x.range()[0],
"width": x.range()[1]
});
// Divider showing the fabled "centre" of the budget bar
g.append("line")
.attr("class", "divider")
.attr({
"x1": (x.range()[0] + x.range()[1])/2,
"x2": (x.range()[0] + x.range()[1])/2,
"y1": -dividerHeight,
"y2": dividerHeight + barHeight
});
// Ticks and title
g.call(xAxis)
// Title
.append("text")
.attr({
"class": "title",
"y": -6
})
.text(thresholdTitle);
// Display symbols
if (valueLabel === "symbol") {
var color = d3.scale.category10().domain(domain);
// Bar symbol
g.selectAll(".symbol")
.data(domain)
.enter().append("path")
.attr("class", "symbol")
.attr({
"transform": function(d) {
return "translate(" + x(d) + "," + barHeight/2 + ")";
},
"d": function(d, i) {
if (useMultipleSymbols === true) {
return d3.svg.symbol().type(d3.svg.symbolTypes[i])();
} else {
return d3.svg.symbol().type(symbolType)();
}
},
"fill": function(d) { return color(d); }
});
var legendX = (x.range()[0] + x.range()[1])/2 - 25, // Revise
legendY = textContainerSize*valueTypes.length + barHeight + 32,
yPos = function(d, i) {
return i*textContainerSize;
};
var legend = svg.selectAll(".legend")
.data(domain)
.enter().append("g")
.attr("class", "legend")
.attr("transform", "translate(" + legendX + "," + legendY + ")");
// Legend symbols
legend.append("path")
.attr("class", "legend-symbol")
.attr({
"transform": function(d, i) {
return "translate(" + 0 + "," + yPos(d, i) + ")";
},
"d": function(d, i) {
if (useMultipleSymbols === true) {
return d3.svg.symbol().type(d3.svg.symbolTypes[i])();
} else {
return d3.svg.symbol().type(symbolType)();
}
},
"fill": function(d) { return color(d); }
});
// Legend text
legend.append("text")
.attr("class", "legend-text")
.attr({
"x": 12, // Revise
"y": function(d, i) {
return yPos(d, i);
},
"dy": ".3em" // Revise
})
.text(function(d, i) { // fetch string
return keys[i];
});
}
// Display text labels --- work in progress
if (valueLabel === "text") {
// Flip ticks?
g.selectAll(".name")
.data(domain)
.enter().append("text")
.attr("class", "name")
.attr({
"text-anchor": function(d) {
// Revise
return (x(d) > (x.range()[0] + x.range()[1])/2) ? "end" : "start";
},
"x": x,
"y": 0 // Revise
})
.text(function(d, i) { // fetch string
return keys[i];
});
}
// Display any additional values, if they exist
var labels = g.selectAll(".values")
.data(domain)
.enter();
for (var c = 0; c < valueTypes.length; c++) {
labels.append("text")
.attr({
"text-anchor": "middle",
"x": function(d) { return x(d); },
"y": function() {
// Revise
return c*textContainerSize + barHeight + 15;
}
})
.text(function(d, i) {
return displayValue(d, i, valueTypes[c]);
});
}
</script>
</body>
</html>
Modified http://d3js.org/d3.v3.min.js to a secure url
https://d3js.org/d3.v3.min.js