Creating multi-scale, nested, ordinal bar charts with D3 - javascript

I am making an interactive version of this infographic
This is proving difficult in D3. There will be multiple ordinal scales:
One Y scale for each country
One Y scale for each period in each country
One X scale
For each period, there will be two values (visualized as two different colored bars). The main problem is that not all countries will have three periods; the data does not take a completely even shape. This is to say that some segments of the first Y scale will have different heights. I'm not sure how to take care of this.
Suppose I have this data:
CountryName Period ValueA ValueB
Argentina 2004-2008 12 5
Argentina 2004-2013 10 5
Argentina 2008-2013 8 4
Bolivia 2002-2008 4 2
Bolivia 2002-2013 6 18
Brazil 2003-2008 9 2
Brazil 2003-2013 2 19
Brazil 2008-2013 1 3
And I use the d3.nest() function:
d3.nest()
.key(function(d) { return d.CountryName; })
.key(function(d) { return d.Period; })
.map(data);
Now I'll have the data in the form that I want it, but note that there is some missing data - Bolivia only has two periods of data, in this case. I have created a JSFiddle for this, but as you can see, it has some problems. The height of the bars should be the same, always. I want to use yScale.rangeBand(), but the problem is that some countries will have three periods, where some will only have two. I also need to find a way to display the country names and periods to the left of the bars
If anybody has a better means of approaching this problem, please let me know. I have been struggling for this for a couple of days. As you can see from the JSFiddle, I only have one yScale but I'm sure it's preferable to use two given my situation - I do not know how to implement this.
Any and all help is greatly appreciated

You may not really need the nesting.
This is a special case bar chart so you will need to make axis' bars ticks grids all by yourself.
I have added comments in the code for you to follow
var outerWidth = 1000;
var outerHeight = 500;
var margin = {
left: 100,
top: 0,
right: 100,
bottom: 90
};
var barPadding = 0.6;
var barPaddingOuter = 0.3;
var innerWidth = outerWidth - margin.left - margin.right;
var innerHeight = outerHeight - margin.top - margin.bottom;
//make your svg
var svg = d3.select("body").append("svg")
.attr("width", outerWidth)
.attr("height", outerHeight);
var g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + 30 + ")");
//the axis is on the right
var xAxisG = g.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + 10 + ")")
//the y axis is common for all
var yAxisG = g.append("g")
.attr("class", "y axis");
var xScale = d3.scale.linear().range([0, innerWidth]);
var yScale = d3.scale.ordinal().rangeRoundBands([0, innerHeight], barPadding, barPaddingOuter);
var yAxis = d3.svg.axis().scale(yScale).orient("left")
.outerTickSize(0);
function render(data) {
var xAxis = d3.svg.axis().scale(xScale).orient("top")
//.tickValues([-5, -4, -3, 0, 3, 5, 10, 15, 20, 25])
.outerTickSize(0)
.innerTickSize(-1 * outerHeight);
xScale.domain([0, 30]);
//this will make the y axis labels.
var country = "";
data.forEach(function(d) {
if (d.CountryName == country) {
d.label = d.Period
} else {
d.label = d.Period
d.heading = d.CountryName;
}
country = d.CountryName;
});
var labels = data.map(function(d) {
return d.label
});
//set the domain for all y labels,
yScale.domain(labels);
//makes the x Axis
xAxisG.call(xAxis);
//makes the y Axis
yAxisG.call(yAxis);
//make the bar chart for valueB
var bars = g.selectAll(".ValueA").data(data);
bars.enter().append("rect")
.attr("height", yScale.rangeBand() / 2);
bars
.attr("x", function(d) {
return xScale(0)
})
.attr("y", function(d) {
return yScale(d.label);
})
.attr("width", function(d) {
console.log(d.ValueA)
return xScale(d.ValueA);
})
.style("fill", "red")
.attr("class", "ValueA");
//make the bar chart for valueB
var bars = g.selectAll(".ValueB").data(data);
bars.enter().append("rect")
.attr("height", yScale.rangeBand() / 2);
bars
.attr("x", function(d) {
return xScale(0)
})
.attr("y", function(d) {
return yScale.rangeBand() / 2 + yScale(d.label);
})
.attr("width", function(d) {
return xScale(d.ValueB);
})
.style("fill", "blue")
.attr("class", "ValueA");
//make grid lines
var lines = g.selectAll(".xLine").data(data.filter(function(d){return !(d.heading == undefined) }));
lines.enter().append("line")
lines
.attr("x1", 0)
.attr("y1", function(d) {
return (yScale(d.label) - yScale.rangeBand())
})
.attr("x2", innerWidth)
.attr("y2", function(d) {
return (yScale(d.label) - yScale.rangeBand())
})
.style("stroke", "blue")
.attr("class", "xLine")
.style("display", function(s) {
if((yScale(s.label) - yScale.rangeBand()) < 0){
return "none";//not show grids when y comes negative
}
});
//make heading
var headings = g.selectAll(".heading").data(data.filter(function(d){return !(d.heading == undefined) }));
headings.enter().append("text")
.text(function(d){return d.heading})
.attr("x", -100)
.attr("y", function(d) {
return (yScale(d.label) - yScale.rangeBand()) +30
})
}
function type(d) {
d.ValueA = +d.ValueA;
d.ValueB = +d.ValueB;
console.log(d)
return d;
} d3.csv("https://gist.githubusercontent.com/cyrilcherian/e2dff83329af684cde78/raw/f85ce83497553c360d2af5d5dcf269390c44022f/cities.csv", type, render);
.tick line {
opacity: 0.2;
}
.xLine {
opacity: 0.2;
}
.axis text {
font-size: 10px;
}
.axis .label {}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.y.axis path,
.y.axis line {
stroke: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Hope this helps!

Related

How to move the x axis to the bottom of a svg

I am trying to create a barplot using javascript. I have created a barplot, but want to add two axis. Currently stuck on the x-axis.
I am unable to move my x-axis to the bottom of my barplot. I am Using d3 to tailor the svg. I am currently able to showcase it at the top, but want to show it at the bottom.
Any input would be useful!
My attempts thus far have been to use transform, but when I execute this my axis disappears.
Googled several other solutions, none of them being successful.
Code:
<script>
d3.json("data_week3.json", function(data){
var data_renewables = [];
var data_nations = [];
for (i = 0; i < data.length; i++)
{
data_renewables.push(data[i].Renewable);
data_nations.push(data[i].Nation)
}
var width = 1000,
height = 500;
var y = d3.scale.linear()
.domain([0, d3.max(data_renewables)])
.range([height, 0]);
var chart = d3.select(".chart")
.attr("width", width)
.attr("height", height);
var barWidth = width / data_renewables.length;
var bar = chart.selectAll("g")
.data(data_renewables)
.enter().append("g")
.attr("transform", function(d, i) { return "translate(" + i * barWidth + ",0)"; });
bar.append("rect")
.attr("y", function(d) { return y(d); })
.attr("height", function(d) { return height - y(d); })
.attr("width", barWidth - 1);
bar.append("text")
.attr("x", barWidth / 2)
.attr("y", function(d) { return y(d); + 3; })
.attr("dy", ".75em")
.text(function(d) { return d; });
var axisScale = d3.scale.linear()
.domain([0, 30])
.range([0, 1000]);
var xAxis = d3.svg.axis()
.scale(axisScale)
.orient("bottom");
chart.append("g")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
})
</script>
You're giving the chart a height of height and then your transform is moving the top of the x axis by a value of height so it will always be cut off. I suggest you look at the margin convention: https://bl.ocks.org/mbostock/3019563

use d3 brush to select element in a linechart

I am a beginner and I got a problem when I try to select some element in a lineChart.
I know there are so many elites, So I come here for some help :)
I find a brush demo in there and a linechart(sorry they don't allow me to insert 2 links because I don't have 10 reputations) demo Then I write the code blow:
<!DOCTYPE html>
<meta charset="utf-8">
<style type="text/css">
/* 13. Basic Styling with CSS */
/* Style the lines by removing the fill and applying a stroke */
.line {
fill: none;
stroke: #ffab00;
stroke-width: 3;
}
/* Style the dots by assigning a fill and stroke */
.dot {
fill: #1 fab40;
stroke: #fff;
}
.dot .selected {
stroke: red;
}
</style>
<!-- Body tag is where we will append our SVG and SVG objects-->
<body>
</body>
<!-- Load in the d3 library -->
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
// 2. Use the margin convention practice
var margin = {top: 20, right: 50, bottom: 20, left: 50}
, width = window.innerWidth - margin.left - margin.right // Use the window's width
, height = window.innerHeight - margin.top - margin.bottom; // Use the window's height
// The number of datapoints
var n = 21;
// 5. X scale will use the index of our data
var xScale = d3.scaleLinear()
.domain([0, n-1]) // input
.range([0, width]); // output
// 6. Y scale will use the randomly generate number
var yScale = d3.scaleLinear()
.domain([0, 1]) // input
.range([height, 0]); // output
// 7. d3's line generator
var line = d3.line()
.x(function(d, i) { return xScale(i); }) // set the x values for the line generator
.y(function(d) { return yScale(d.y); }) // set the y values for the line generator
.curve(d3.curveMonotoneX);// apply smoothing to the line
// 8. An array of objects of length N. Each object has key -> value pair, the key being "y" and the value is a random number
var dataset = d3.range(n).map(function(d) { return {"y": d3.randomUniform(1)() } });
// 1. Add the SVG to the page and employ #2
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// 3. Call the x axis in a group tag
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(xScale)); // Create an axis component with d3.axisBottom
// 4. Call the y axis in a group tag
svg.append("g")
.attr("class", "y axis")
.call(d3.axisLeft(yScale)); // Create an axis component with d3.axisLeft
// 9. Append the path, bind the data, and call the line generator
svg.append("path")
.datum(dataset) // 10. Binds data to the line
.attr("class", "line") // Assign a class for styling
.attr("d", line); // 11. Calls the line generator
// 12. Appends a circle for each datapoint
var dot = svg.append("g")
.attr("class", "dot") // Assign a class for styling
.selectAll(".dot")
.data(dataset)
.enter().append("circle") // Uses the enter().append() method
.attr("r", 5)
.attr("cx", function(d, i) { return xScale(i) })
.attr("cy", function(d) { return yScale(d.y) });
var brush = svg.append("g")
.attr("class", "brush")
.call(d3.brush()
.extent([[0, 0], [width, height]])
.on("start brush end", brushed));
function brushed() {
var selection = d3.event.selection;
dot.classed("selected", selection && function(d) {
return selection[0][0] <= d.x && d.x < selection[1][0]
&& selection[0][1] <= d.y && d.y < selection[1][1];
});
}
</script>
I try to write the same code like this demo, But I can't selected any dot, Where is the problem?
Thanks a lot!
Rewrite your brushed function like this:
function brushed() {
var selection = d3.event.selection;
dot.classed("selected", selection && function(d,i) {
return selection[0][0] <= xScale(i) && xScale(i) < selection[1][0]
&& selection[0][1] <= yScale(d.y) && yScale(d.y) < selection[1][1];
});
}
Pay attention, we use xScale and yScale here, for correctly matching selected area and coordinates of dots. Working example is below:
// 2. Use the margin convention practice
var margin = {top: 20, right: 50, bottom: 20, left: 50}
, width = window.innerWidth - margin.left - margin.right // Use the window's width
, height = window.innerHeight - margin.top - margin.bottom; // Use the window's height
// The number of datapoints
var n = 21;
// 5. X scale will use the index of our data
var xScale = d3.scaleLinear()
.domain([0, n-1]) // input
.range([0, width]); // output
// 6. Y scale will use the randomly generate number
var yScale = d3.scaleLinear()
.domain([0, 1]) // input
.range([height, 0]); // output
// 7. d3's line generator
var line = d3.line()
.x(function(d, i) { return xScale(i); }) // set the x values for the line generator
.y(function(d) { return yScale(d.y); }) // set the y values for the line generator
.curve(d3.curveMonotoneX);// apply smoothing to the line
// 8. An array of objects of length N. Each object has key -> value pair, the key being "y" and the value is a random number
var dataset = d3.range(n).map(function(d) { return {"y": d3.randomUniform(1)() } });
// 1. Add the SVG to the page and employ #2
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// 3. Call the x axis in a group tag
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(xScale)); // Create an axis component with d3.axisBottom
// 4. Call the y axis in a group tag
svg.append("g")
.attr("class", "y axis")
.call(d3.axisLeft(yScale)); // Create an axis component with d3.axisLeft
// 9. Append the path, bind the data, and call the line generator
svg.append("path")
.datum(dataset) // 10. Binds data to the line
.attr("class", "line") // Assign a class for styling
.attr("d", line); // 11. Calls the line generator
// 12. Appends a circle for each datapoint
var dot = svg.append("g")
.attr("class", "dot") // Assign a class for styling
.selectAll(".dot")
.data(dataset)
.enter().append("circle") // Uses the enter().append() method
.attr("r", 5)
.attr("cx", function(d, i) { return xScale(i) })
.attr("cy", function(d) { return yScale(d.y) });
var brush = svg.append("g")
.attr("class", "brush")
.call(d3.brush()
.extent([[0, 0], [width, height]])
.on("start brush", brushed)
.on("end", getSelectedDots)
);
var selectedDots = [];
function getSelectedDots() {
var selection = d3.event.selection;
dot.classed("selected", selection && function(d,i) {
var isSelectedDot = selection[0][0] <= xScale(i) && xScale(i) < selection[1][0]
&& selection[0][1] <= yScale(d.y) && yScale(d.y) < selection[1][1];
if (isSelectedDot) {
selectedDots.push(d);
}
return isSelectedDot;
});
console.log('selectedDots ', selectedDots);
}
function brushed() {
var selection = d3.event.selection;
dot.classed("selected", selection && function(d,i) {
return selection[0][0] <= xScale(i) && xScale(i) < selection[1][0]
&& selection[0][1] <= yScale(d.y) && yScale(d.y) < selection[1][1];
});
}
/* 13. Basic Styling with CSS */
/* Style the lines by removing the fill and applying a stroke */
.line {
fill: none;
stroke: #ffab00;
stroke-width: 3;
}
/* Style the dots by assigning a fill and stroke */
.dot {
fill: #1fab40;
stroke: #fff;
}
.dot .selected {
stroke: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.11.0/d3.min.js"></script>

How to get the index of the data element in a histogram on mouseover?

I have a d3.v3.min.js histogram created using this as reference Histogram chart using d3 and I'd like to highlight in a separate plot (scatter plot) all the points that fall within one bar of this histogram. To this end I hook on the mouseover event of the rectangle to get the values within one bin. This works fine but I can't get their indices from the original input array:
var data = d3.layout.histogram().bins(xTicks)(values);
bar.append("rect")
.attr("x", 1)
.attr("width", (x(data[0].dx) - x(0)) - 1)
.attr("height", function(d) { return height - y(d.y); })
.attr("fill", function(d) { return colorScale(d.y) })
.on("mouseover", function (d, i) { console.log(d); });
d is an array containing all the values within the bin, and i is the bin index. I need the indices of the original data values I passed to the histogram function so that I can look them up in the other plot by index (as opposed to a binary search needed on the value).
Instead of just passing number values to the histogram generator you could create an array of objects carrying additional information:
// Generate a 1000 data points using normal distribution with mean=20, deviation=5
var f = d3.random.normal(20, 5);
// Create full-fledged objects instead of mere numbers.
var values = d3.range(1000).map(id => ({
id: id,
value: f()
}));
// Accessor function for the objects' value property.
var valFn = d => d.value;
// Generate a histogram using twenty uniformly-spaced bins.
var data = d3.layout.histogram()
.bins(x.ticks(20))
.value(valFn) // Provide accessor function for histogram generation
(values);
By providing an accessor function to the histogram generator you are then able to create the bins from this array of objects. Calling the histogram generator will consequently result in bins filled with objects instead of just raw numbers. In an event handler you are then able to access your data objects by reference. The objects will carry all the initial information, be it the id property as in my example, an index or anything else you put in them in the first place.
Have a look at the following snippet for a working demo:
var color = "steelblue";
var f = d3.random.normal(20, 5);
// Generate a 1000 data points using normal distribution with mean=20, deviation=5
var values = d3.range(1000).map(id => ({
id: id,
value: f()
}));
var valFn = d => d.value;
// A formatter for counts.
var formatCount = d3.format(",.0f");
var margin = {top: 20, right: 30, bottom: 30, left: 30},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var max = d3.max(values, valFn);
var min = d3.min(values, valFn);
var x = d3.scale.linear()
.domain([min, max])
.range([0, width]);
// Generate a histogram using twenty uniformly-spaced bins.
var data = d3.layout.histogram()
.bins(x.ticks(20))
.value(valFn)
(values);
var yMax = d3.max(data, function(d){return d.length});
var yMin = d3.min(data, function(d){return d.length});
var colorScale = d3.scale.linear()
.domain([yMin, yMax])
.range([d3.rgb(color).brighter(), d3.rgb(color).darker()]);
var y = d3.scale.linear()
.domain([0, yMax])
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var bar = svg.selectAll(".bar")
.data(data)
.enter().append("g")
.attr("class", "bar")
.attr("transform", function(d) { return "translate(" + x(d.x) + "," + y(d.y) + ")"; })
.on("mouseover", d => { console.log(d)});
bar.append("rect")
.attr("x", 1)
.attr("width", (x(data[0].dx) - x(0)) - 1)
.attr("height", function(d) { return height - y(d.y); })
.attr("fill", function(d) { return colorScale(d.y) });
bar.append("text")
.attr("dy", ".75em")
.attr("y", -12)
.attr("x", (x(data[0].dx) - x(0)) / 2)
.attr("text-anchor", "middle")
.text(function(d) { return formatCount(d.y); });
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
/*
* Adding refresh method to reload new data
*/
function refresh(values){
// var values = d3.range(1000).map(d3.random.normal(20, 5));
var data = d3.layout.histogram()
.value(valFn)
.bins(x.ticks(20))
(values);
// Reset y domain using new data
var yMax = d3.max(data, function(d){return d.length});
var yMin = d3.min(data, function(d){return d.length});
y.domain([0, yMax]);
var colorScale = d3.scale.linear()
.domain([yMin, yMax])
.range([d3.rgb(color).brighter(), d3.rgb(color).darker()]);
var bar = svg.selectAll(".bar").data(data);
// Remove object with data
bar.exit().remove();
bar.transition()
.duration(1000)
.attr("transform", function(d) { return "translate(" + x(d.x) + "," + y(d.y) + ")"; });
bar.select("rect")
.transition()
.duration(1000)
.attr("height", function(d) { return height - y(d.y); })
.attr("fill", function(d) { return colorScale(d.y) });
bar.select("text")
.transition()
.duration(1000)
.text(function(d) { return formatCount(d.y); });
}
// Calling refresh repeatedly.
setInterval(function() {
var values = d3.range(1000).map(id => ({
id: id,
value: f()
}));
refresh(values);
}, 2000);
body {
font: 10px sans-serif;
}
.bar rect {
shape-rendering: crispEdges;
}
.bar text {
fill: #999999;
}
.axis path, .axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.as-console-wrapper {
height: 20%;
}
<script src="https://d3js.org/d3.v3.min.js"></script>

Single Stacked Bar Graph Data Setup?

I'm trying to make a singular column for a bar graph in d3.js, the purpose of which is to bar-graph the coefficients of the other line graph in my program. I'm familiar with how they are made when the data is in .csv format, but in this case right now I'm trying to make it from three variables. The three variables are:
var xtwo;
var xone;
var xzero;
which have values put into them in a later part. I've built a skeleton based on what I know and have seen, which is right here:
//Bar Graph
var barmargin = {
top: 20,
right: 20,
bottom: 30,
left: 60
},
barwidth = 500 - barmargin.left - barmargin.right,
barheight = 350 - barmargin.top - barmargin.bottom;
//X scale
var barx = d3.scale.ordinal()
.rangeRoundBands([0, barwidth], .1);
//Y scale
var bary = d3.scale.linear()
.rangeRound([barheight, 0]);
//bar graph colors
var color = d3.scale.ordinal()
.range(["#FF5C33", "#F48C00", "#FFFF5C"]);
// Use X scale to set a bottom axis
var barxAxis = d3.svg.axis()
.scale(barx)
.orient("bottom");
// Same for y
var baryAxis = d3.svg.axis()
.scale(bary)
.orient("left")
.tickFormat(d3.format(".2s"));
// Addchart to the #chart div
var svg = d3.select("#chart").append("svg")
.attr("width", barwidth + barmargin.left + barmargin.right)
.attr("height", barheight + barmargin.top + barmargin.bottom)
.append("g")
.attr("transform", "translate(" + barmargin.left + "," + barmargin.top + ")");
//Where data sorting happens normally
var bardata.data([xzero, xone, xtwo]);
//Y domain is from zero to 5
y.domain([0, 5]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + barheight + ")")
.call(barxAxis);
svg.append("g")
.attr("class", "y axis")
.call(baryAxis);
bardata.selectAll("rect")
.data(function(d) {
return d.types;
})
.enter().append("rect")
.attr("width", barx.rangeBand())
.attr("y", function(d) {
return bary(d.y1);
})
.attr("height", function(d) {
return bary(d.y0) - bary(d.y1);
})
.style("fill", function(d) {
return color(d.name);
});
but I can't really figure out how to make it work correctly. I thought that I could manually make the .data but it seems to not be working that way.
Full code if necessary: https://jsfiddle.net/tqj5maza/1/
Broadly speaking: you want to create three bars, sat on top of each other, from three different values. The three values will be enough to scale the bars, but they in themselves won't be enough to position the bars- each bar needs to be offset by the size of the bars that have gone before.
d3 can only read the values that are already in the data you send it- you can't really access the previous values as you go, as each datum is bound to a separate element. Thus, what you need to do is to create some new data, which has all the numbers required to display it.
Here's one way that you might do that:
var canvas = d3.select("#canvas").append("svg").attr({width: 400, height: 400})
var values = [50, 90, 30]
var colours = ['#FA0', '#0AF', '#AF0']
var data = []
var yOffset = 0
//Process the data
for(var i = 0; i < values.length; i++) {
var datum = {
value : values[i],
colour : colours[i],
x: 0,
y: yOffset
}
yOffset += values[i]
data.push(datum)
}
var bars = canvas.selectAll('rect').data(data)
bars
.enter()
.append('rect')
.attr({
width : 30,
height : function(d) {
return d.value
},
y : function(d) {
return d.y //
}
})
.style({
fill : function(d) {
return d.colour
}
})
http://jsfiddle.net/r3sazt7m/
d3's layout functions all do more or less this- you pass them a set of data, and they return new data containing the values that the SVG drawing instructions require.

Scaling a D3.js Bullet Chart keeps messing up

I am new to D3.js and I am drawing a bullet chart for a website I am working on. I took the code from here as a starting point:
http://bl.ocks.org/jugglinmike/6004102
I have a chart drawn, and for a basic case, I do not need to worry about switching the data shown. I really need to, however, have the entire chart and all of its elements scale with the window resize.
Right now, I have two files, bullet.js and draw_bullet.js. This is the code for draw_bullet.js:
var margin = {top: 5, right: 40, bottom: 20, left: 120},
width = ($(window).width() * .3) - margin.left - margin.right,
height = 50 - margin.top - margin.bottom;
var chart = d3.bullet()
.width(width)
.height(height);
function fillChart() {
d3.json("/static/response.json", function(error, data) {
var svg = d3.select("#zone1").selectAll("svg")
.data(data)
.enter().append("svg")
.attr("class", "bullet")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(chart1);
var title = svg.append("g")
.style("text-anchor", "end")
.attr("transform", "translate(-6," + height / 2 + ")");
title.append("text")
.attr("class", "title")
.text(function(d) { return d.title; });
title.append("text")
.attr("class", "subtitle")
.attr("dy", "1em")
.text(function(d) { return d.subtitle; });
});
}
And my code for bullet.js:
d3.bullet = function() {
var orient = "left", // TODO top & bottom
reverse = false,
duration = 0,
ranges = bulletRanges,
measures = bulletMeasures,
width = parseInt(d3.select("#zone1").style("width"), 10),
height = 30,
tickFormat = null;
// For each small multiple…
function bullet(g) {
g.each(function(d, i) {
var rangez = ranges.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], measurez[0])])
.range(reverse ? [width, 0] : [0, width]);
// 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 = bulletWidth(x0),
w1 = bulletWidth(x1);
// Update the range rects.
var range = g.selectAll("rect.range")
.data(rangez);
range.enter().append("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("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);
// Compute the tick format.
var format = tickFormat || x1.tickFormat(8);
// Update the tick groups.
var tick = g.selectAll("g.tick")
.data(x1.ticks(8), function(d) {
return this.textContent || format(d);
});
// Initialize the ticks with the old scale, x0.
var tickEnter = tick.enter().append("g")
.attr("class", "tick")
.attr("transform", bulletTranslate(x0))
.style("opacity", 1e-6);
tickEnter.append("line")
.attr("y1", height)
.attr("y2", height * 7 / 6);
tickEnter.append("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", bulletTranslate(x1))
.style("opacity", 1);
// Transition the updating ticks to the new scale, x1.
var tickUpdate = tick.transition()
.duration(duration)
.attr("transform", bulletTranslate(x1))
.style("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", bulletTranslate(x1))
.style("opacity", 1e-6)
.remove();
});
d3.timer.flush();
}
// left, right, top, bottom
bullet.orient = function(x) {
if (!arguments.length) return orient;
orient = x;
reverse = orient == "right" || orient == "bottom";
return bullet;
};
// ranges (bad, satisfactory, good)
bullet.ranges = function(x) {
if (!arguments.length) return ranges;
ranges = x;
return bullet;
};
// measures (actual, forecast)
bullet.measures = function(x) {
if (!arguments.length) return measures;
measures = x;
return bullet;
};
bullet.width = function(x) {
if (!arguments.length) return width;
width = x;
return bullet;
};
bullet.height = function(x) {
if (!arguments.length) return height;
height = x;
return bullet;
};
bullet.tickFormat = function(x) {
if (!arguments.length) return tickFormat;
tickFormat = x;
return bullet;
};
bullet.duration = function(x) {
if (!arguments.length) return duration;
duration = x;
return bullet;
};
return bullet;
};
function bulletRanges(d) {
return d.ranges;
}
function bulletMeasures(d) {
return d.measures;
}
function bulletTranslate(x) {
return function(d) {
return "translate(" + x(d) + ",0)";
};
}
function bulletWidth(x) {
var x0 = x(0);
return function(d) {
return Math.abs(x(d) - x0);
};
}
})();
I am using jQuery and I know I have to embed a function in $(window).resize(), however I have tried many different things and none of them seem to adjust correctly. I try to set chart.width() to my new value in the function, and later when I call the width of the chart, it shows it as being my new value, but doesn't adjust its view on the screen. Is it necessary to redraw the entire chart and all its elements to resize? And then, also, when I tried to rescale the range for the chart, I found it very difficult to do so because the range variable is embedded in the anonymous function inside of bullet.js. Any help you can provide in pointing me in the right direction would be awesome. I tried using this tutorial, but it didn't seem to apply to my situation too much because they are different types of charts.
http://eyeseast.github.io/visible-data/2013/08/28/responsive-charts-with-d3/
Thanks!

Categories