d3 redraw bar chart with new values - d3.js

My barchart draws fine when the page first loads.
But choose hour 2 from the drop-down, and it doesn't want to update to hour 2 data, it just keeps displaying hour 1.
FIDDLE
This is my d3 and js:
$('#bar_chart').css('overflow-x','scroll');
var margin = {top: 20, right: 20, bottom: 40, left: 80},
width = 220 - margin.left - margin.right,
height = 233 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1, 1);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient('bottom');
var formatComma = d3.format('0,000');
var yAxis = d3.svg.axis()
.scale(y)
.orient('left')
.ticks(5)
.outerTickSize(0)
.tickFormat(formatComma);
var svg = d3.select('th.chart-here').append('svg')
.attr('viewBox', '0 0 220 233')
.attr('preserveAspectRatio','xMinYMin meet')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + margin.left/1.5 + ',' + margin.top/1.5 + ')');
var table_i = 0;
var arr1 =
[
{'hour':1,'car':[{'audi':1377},{'bmw':716},{'ford':3819},{'mazda':67},{'toyota':11580},{'tesla':0}]},
{'hour':2,'car':[{'audi':9000},{'bmw':2000},{'ford':7000},{'mazda':1000},{'toyota':5000},{'tesla':700}]},
];
var hour = arr1[table_i];
var car=hour.car;
var newobj = [];
for(var hourx1=0;hourx1<car.length;hourx1++){
var xx = car[hourx1];
for (var value in xx) {
var chartvar = newobj.push({car:value,miles:xx[value]});
var data = newobj;
}
}
x.domain(data.map(function(d) { return d.car; }));
y.domain([0, d3.max(data, function(d) { return d.miles; })]);
svg.append('g')
.attr('class', 'y axis')
.call(yAxis)
.append('text')
.attr('y', 6)
.attr('dy', '.71em')
.style('text-anchor', 'start');
function changeHour(){
svg.selectAll('.bar')
.data(data)
.enter().append('rect')
.attr('class', 'bar')
.attr('transform','translate(-20)') //move rects closer to Y axis
.attr('x', function(d) { return x(d.car); })
.attr('width', x.rangeBand()*1)
.attr('y', function(d) { return y(d.miles); })
.attr('height', function(d) { return height - y(d.miles); });
xtext = svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(-20,' + height + ')') //move tick text so it aligns with rects
.call(xAxis);
xtext.selectAll('text')
.attr('transform', function(d) {
return 'translate(' + this.getBBox().height*50 + ',' + this.getBBox().height + ')rotate(0)';
});
//code to enable jqm checkbox
$('#checkbox-2a').on('change', function(e){
originalchange(e);
});
$( '#checkbox-2a' ).checkboxradio({
defaults: true
});
var sortTimeout = setTimeout(function() {
$('#checkbox-2a').prop('checked', false).checkboxradio( 'refresh' ).change();
}, 1000);
function originalchange() {
clearTimeout(sortTimeout);
var IsChecked = $('#checkbox-2a').is(':checked');
// Copy-on-write since tweens are evaluated after a delay.
var x0 = x.domain(data.sort(IsChecked
? function(a, b) { return b.miles - a.miles; }
: function(a, b) { return d3.ascending(a.car, b.car); })
.map(function(d) { return d.car; }))
.copy();
svg.selectAll('.bar')
.sort(function(a, b) { return x0(a.car) - x0(b.car); });
var transition = svg.transition().duration(950),
delay = function(d, i) { return i * 50; };
transition.selectAll('.bar')
.delay(delay)
.attr('x', function(d) { return x0(d.car); });
transition.select('.x.axis')
.call(xAxis)
.selectAll('g')
.delay(delay);
};
}
changeHour();
$('select').change(function() { //function to change hourly data
table_i = $(this).val();
var hour = arr1[table_i];
var car=hour.car;
var newobj = [];
for(var hourx1=0;hourx1<car.length;hourx1++){
var xx = car[hourx1];
for (var value in xx) {
var chartvar = newobj.push({car:value,miles:xx[value]});
var data = newobj;
}
}
x.domain(data.map(function(d) { return d.car; }));
y.domain([0, d3.max(data, function(d) { return d.miles; })]);
changeHour();
})
I thought that by updating in the function changeHour I could isolate just the rects and the text that goes with them, and redraw them based on the selected hour's data.
But it just keeps drawing the first hour.
What am I doing wrong?

2 things not working:
firstly "data" needs to be declared without 'var' in the change function at the end. Declaring it with 'var' makes it a local variable to that function, and once you leave that function it's gone. Saying "data = " without the var means you're using the data variable you've declared further up. It's all to do with scope which is something I still struggle with, but basically with 'var' it doesn't work.
var newobj = [];
for(var hourx1=0;hourx1<car.length;hourx1++){
var xx = car[hourx1];
for (var value in xx) {
var chartvar = newobj.push({car:value,miles:xx[value]});
}
}
data = newobj;
Secondly, your changeHour function only looks for new elements as it hangs all its attribute settings on an .enter() selection, changeHour should be like this:
var dataJoin = svg.selectAll('.bar')
.data(data, function(d) { return d.car; });
// possible new elements, fired first time, set non-data dependent attributes
dataJoin
.enter()
.append('rect')
.attr('class', 'bar')
.attr('transform','translate(-20)') //move rects closer to Y axis
// changes to existing elements (now including the newly appended elements from above) which depend on data values (d)
dataJoin
.attr('x', function(d) { return x(d.car); })
.attr('width', x.rangeBand()*1)
.attr('y', function(d) { return y(d.miles); })
.attr('height', function(d) { return height - y(d.miles); });
For completeness there should be a dataJoin.exit().remove() in there as well but its not something that happens in this dataset

Related

D3.js line chart continuous but want non continuous

Hey guys I created a time series line chart using publicly available stock data.
Where I got to is the following:
Looks like what it is doing is connecting the first datapoint with the last datapoint which is why it is creating a line across the entire chart.
I looked online and read that to create a non continuous line chart I can add
.defined(function(d) { return d; })
I did but it didn't help.
My code:
//Set dimensions and margins for the graph
var margin = {top: 20, right: 20, bottom: 100, left: 70},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
//Create canvas
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 + ")");
//Parse date
var parseDate = d3.timeParse("%Y-%m-%d");
//Set the ranges
var x = d3.scaleTime()
.range([0, width]);
var y = d3.scaleLinear()
.range([height, 0]);
// Define the line
var valueLine = d3.line()
.defined(function(d) { return d; })
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.value); });
//Import data from api
d3.json("api_all.php", function(error, data) {
if (error) throw error;
data.forEach(e => {
e.date = parseDate(e.date);
e.value = +e.close;
e.stockName = e.stock_name;
e.stockSymbol = e.stock_symbol;
});
//Create nest variable
var nest = d3.nest()
.key(function(d){ return d.stockSymbol; })
.entries(data);
console.log(nest);
//Scale the range of the data
//x axis scale for entire dataset
x.domain(d3.extent(data, function(d) { return d.date; }));
//y.domain([0, d3.max(data, function(d) { return d.value; })]);
//Add the x axis
var xaxis = svg.append("g")
.attr("transform", "translate(0," + height + ")")
.attr("class", "x axis")
.call(d3.axisBottom(x));
//Add x axis label
svg.append("text")
.attr("transform", "translate(" + (width/2) + "," + (height + margin.top + 10) + ")")
.attr("dy", "1em")
.style("text-anchor", "middle")
.text("Date")
.attr("class", "x axis label");
//Create dropdown
var dropDown = d3.select("#dropDown")
dropDown
.append("select")
.selectAll("option")
.data(nest)
.enter()
.append("option")
.attr("value", function(d){ return d.key; })
.text(function(d){ return d.key; })
// Function to create the initial graph
var initialGraph = function(stock){
// Filter the data to include only stock of interest
var selectStock = nest.filter(function(d){
return d.key == stock;
})
console.log(selectStock)
//Unnest selectStock for y axis
var unnested = function(data, children){
var out = [];
data.forEach(function(d, i){
console.log(i, d);
d_keys = Object.keys(d);
console.log(i, d_keys)
values = d[children];
values.forEach(function(v){
d_keys.forEach(function(k){
if (k != children) { v[k] = d[k]}
})
out.push(v);
})
})
return out;
}
var selectStockUnnested = unnested(selectStock, "values");
//Scale y axis
var selectStockGroups = svg.selectAll(".stockGroups")
.data(selectStock, function(d){
return d ? d.key : this.key;
})
.enter()
.append("g")
.attr("class", "stockGroups")
.each(function(d){
y.domain([0, d3.max(selectStockUnnested, function(d) { return d.value; })])
console.log(selectStockUnnested);
});
var initialPath = selectStockGroups.selectAll(".line")
.data(selectStock)
.enter()
.append("path")
initialPath
.attr("d", function(d){ return valueLine(d.values) })
.attr("class", "line")
//Add the y axis
var yaxis = svg.append("g")
.attr("class", "y axis")
.call(d3.axisLeft(y)
.ticks(5)
.tickSizeInner(0)
.tickPadding(6)
.tickSize(0, 0));
//Add y axis label
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0 - 60)
.attr("x", 0 - (height / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.text("Price")
.attr("class", "y axis label");
}
// Create initial graph
initialGraph("1301.T")
// Update the data
var updateGraph = function(stock){
// Filter the data to include only stock of interest
var selectStock = nest.filter(function(d){
return d.key == stock;
})
console.log(selectStock);
//Unnest selectStock for y axis
var unnested = function(data, children){
var out = [];
data.forEach(function(d, i){
console.log(i, d);
d_keys = Object.keys(d);
console.log(i, d_keys)
values = d[children];
values.forEach(function(v){
d_keys.forEach(function(k){
if (k != children) { v[k] = d[k]}
})
out.push(v);
})
})
return out;
}
var selectStockUnnested = unnested(selectStock, "values");
// Select all of the grouped elements and update the data
var selectStockGroups = svg.selectAll(".stockGroups")
.data(selectStock)
.each(function(d){
y.domain([0, d3.max(selectStockUnnested, function(d) { return d.value; })])
});
// Select all the lines and transition to new positions
selectStockGroups.selectAll("path.line")
.data(selectStock)
.transition()
.duration(1000)
.attr("d", function(d){
return valueLine(d.values)
})
// Update the Y-axis
d3.select(".y")
.transition()
.duration(1500)
.call(d3.axisLeft(y)
.ticks(5)
.tickSizeInner(0)
.tickPadding(6)
.tickSize(0, 0));
}
// Run update function when dropdown selection changes
dropDown.on('change', function(){
// Find which stock was selected from the dropdown
var selectedStock = d3.select(this)
.select("select")
.property("value")
console.log(selectedStock);
// Run update function with the selected stock
updateGraph(selectedStock)
});
});
</script>
</body>

How to change the size of dots in beeswarm plots in D3.js

I've been looking at this example of a beeswarm plot in d3.js and I'm trying to figure out how to change the size of the dots and without getting the circles to overlap. It seems if the radius of the dots change, it doesn't take this into account when running the calculations of where to place the dots.
This is a cool visualization.
I've made a plunk of it here: https://plnkr.co/edit/VwyXfbc94oXp6kXQ7JFx?p=preview and modified it to work a bit more like you're looking for (I think). The real key is changing the call to handle collision to vary based on the radius of the circles (in the original post it's hard coded to 4, which works well when r === 3 but fails as r grows). The changes:
Make the circle radius into a variable (line 7 of script.js, var r = 3;)
Change the d3.forceCollide call to use that radius and a multiplier - line 110 (.force("collide", d3.forceCollide(r * 1.333)))
Change the .enter() call to use that radius as well (line 130: .attr("r", r))
This works reasonably well for reasonable values of r - but you'll need to adjust the height, and it might even be nice to just change the whole thing so that r is based on height (e.g. var r = height * .01). You'll notice that as is now, the circles go off the bottom and top of the graph area.
This post might be of interest as well: Conflict between d3.forceCollide() and d3.forceX/Y() with high strength() value
Here's the whole of script.js for posterity:
var w = 1000, h = 280;
var padding = [0, 40, 34, 40];
var r = 5;
var xScale = d3.scaleLinear()
.range([ padding[3], w - padding[1] ]);
var xAxis = d3.axisBottom(xScale)
.ticks(10, ".0s")
.tickSizeOuter(0);
var colors = d3.scaleOrdinal()
.domain(["asia", "africa", "northAmerica", "europe", "southAmerica", "oceania"])
.range(['#e41a1c','#377eb8','#4daf4a','#984ea3','#ff7f00','#ffff33']);
d3.select("#africaColor").style("color", colors("africa"));
d3.select("#namericaColor").style("color", colors("northAmerica"));
d3.select("#samericaColor").style("color", colors("southAmerica"));
d3.select("#asiaColor").style("color", colors("asia"));
d3.select("#europeColor").style("color", colors("europe"));
d3.select("#oceaniaColor").style("color", colors("oceania"));
var formatNumber = d3.format(",");
var tt = d3.select("#svganchor").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
var svg = d3.select("#svganchor")
.append("svg")
.attr("width", w)
.attr("height", h);
var xline = svg.append("line")
.attr("stroke", "gray")
.attr("stroke-dasharray", "1,2");
var chartState = {};
chartState.variable = "totalEmission";
chartState.scale = "scaleLinear";
chartState.legend = "Total emissions, in kilotonnes";
d3.csv("co2bee.csv", function(error, data) {
if (error) throw error;
var dataSet = data;
xScale.domain(d3.extent(data, function(d) { return +d.totalEmission; }));
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + (h - padding[2]) + ")")
.call(xAxis);
var legend = svg.append("text")
.attr("text-anchor", "middle")
.attr("x", w / 2)
.attr("y", h - 4)
.attr("font-family", "PT Sans")
.attr("font-size", 12)
.attr("fill", "darkslategray")
.attr("fill-opacity", 1)
.attr("class", "legend");
redraw(chartState.variable);
d3.selectAll(".button1").on("click", function(){
var thisClicked = this.value;
chartState.variable = thisClicked;
if (thisClicked == "totalEmission"){
chartState.legend = "Total emissions, in kilotonnes";
}
if (thisClicked == "emissionPerCap"){
chartState.legend = "Per Capita emissions, in metric tons";
}
redraw(chartState.variable);
});
d3.selectAll(".button2").on("click", function(){
var thisClicked = this.value;
chartState.scale = thisClicked;
redraw(chartState.variable);
});
d3.selectAll("input").on("change", filter);
function redraw(variable){
if (chartState.scale == "scaleLinear"){ xScale = d3.scaleLinear().range([ padding[3], w - padding[1] ]);}
if (chartState.scale == "scaleLog"){ xScale = d3.scaleLog().range([ padding[3], w - padding[1] ]);}
xScale.domain(d3.extent(dataSet, function(d) { return +d[variable]; }));
var xAxis = d3.axisBottom(xScale)
.ticks(10, ".0s")
.tickSizeOuter(0);
d3.transition(svg).select(".x.axis").transition().duration(1000)
.call(xAxis);
var simulation = d3.forceSimulation(dataSet)
.force("x", d3.forceX(function(d) { return xScale(+d[variable]); }).strength(2))
.force("y", d3.forceY((h / 2)-padding[2]/2))
.force("collide", d3.forceCollide(r * 1.333))
.stop();
for (var i = 0; i < dataSet.length; ++i) simulation.tick();
var countriesCircles = svg.selectAll(".countries")
.data(dataSet, function(d) { return d.countryCode});
countriesCircles.exit()
.transition()
.duration(1000)
.attr("cx", 0)
.attr("cy", (h / 2)-padding[2]/2)
.remove();
countriesCircles.enter()
.append("circle")
.attr("class", "countries")
.attr("cx", 0)
.attr("cy", (h / 2)-padding[2]/2)
.attr("r", r)
.attr("fill", function(d){ return colors(d.continent)})
.merge(countriesCircles)
.transition()
.duration(2000)
.attr("cx", function(d) { console.log(d); return d.x; })
.attr("cy", function(d) { return d.y; });
legend.text(chartState.legend);
d3.selectAll(".countries").on("mousemove", function(d) {
tt.html("Country: <strong>" + d.countryName + "</strong><br>"
+ chartState.legend.slice(0, chartState.legend.indexOf(",")) + ": <strong>" + formatNumber(d[variable]) + "</strong>" + chartState.legend.slice(chartState.legend.lastIndexOf(" ")))
.style('top', d3.event.pageY - 12 + 'px')
.style('left', d3.event.pageX + 25 + 'px')
.style("opacity", 0.9);
xline.attr("x1", d3.select(this).attr("cx"))
.attr("y1", d3.select(this).attr("cy"))
.attr("y2", (h - padding[2]))
.attr("x2", d3.select(this).attr("cx"))
.attr("opacity", 1);
}).on("mouseout", function(d) {
tt.style("opacity", 0);
xline.attr("opacity", 0);
});
d3.selectAll(".x.axis, .legend").on("mousemove", function(){
tt.html("This axis uses SI prefixes:<br>m: 10<sup>-3</sup><br>k: 10<sup>3</sup><br>M: 10<sup>6</sup>")
.style('top', d3.event.pageY - 12 + 'px')
.style('left', d3.event.pageX + 25 + 'px')
.style("opacity", 0.9);
}).on("mouseout", function(d) {
tt.style("opacity", 0);
});
//end of redraw
}
function filter(){
function getCheckedBoxes(chkboxName) {
var checkboxes = document.getElementsByName(chkboxName);
var checkboxesChecked = [];
for (var i=0; i<checkboxes.length; i++) {
if (checkboxes[i].checked) {
checkboxesChecked.push(checkboxes[i].defaultValue);
}
}
return checkboxesChecked.length > 0 ? checkboxesChecked : null;
}
var checkedBoxes = getCheckedBoxes("continent");
var newData = [];
if (checkedBoxes == null){
dataSet = newData;
redraw();
return;
};
for (var i = 0; i < checkedBoxes.length; i++){
var newArray = data.filter(function(d){
return d.continent == checkedBoxes[i];
});
Array.prototype.push.apply(newData, newArray);
}
dataSet = newData;
redraw(chartState.variable);
//end of filter
}
//end of d3.csv
});

Adding a legend to a pie chart in D3js

I'm trying to plot a pie chart with a legend inside of it. And I got into troubles to get it plotted, since I get the errors abound undefined variables. I managed to draw the chart itself and the half of the legend, but not in the right colors, what should match the pie chart.
function drawPieChart(d3div, chart_data) {
// chart_data.data is a list of data elements.
// each should contain fields: val, col, name
d3div.html(""); // clear the div
var title = getopt(chart_data, 'title', '');
// desired width and height of chart
var w = getopt(chart_data, 'width', 300);
var h = getopt(chart_data, 'height', 300);
var pad = getopt(chart_data, 'pad', 50);
var textmargin = getopt(chart_data, 'textmargin', 20);
var r = Math.min(w, h) / 2 - pad; // radius of pie chart
var div = d3div.append('div');
if(title !== '') {
div.append('p').attr('class', 'pietitle').text(title);
}
var arc = d3.svg.arc()
.outerRadius(r)
.cornerRadius(20)
.innerRadius(150);
var arcLarge = d3.svg.arc()
.innerRadius(150)
.cornerRadius(20)
.outerRadius(r + 50);
var toggleArc = function(p){
p.state = !p.state;
var dest = p.state ? arcLarge : arc;
d3.select(this).select("path").transition()
.duration(160)
.attr("d", dest);};
var pie = d3.layout.pie()
.padAngle(.03)
.sort(null)
.value(function(d) { return d.val; });
var svg = d3.select("#piechart").append("svg")
.attr("width", w)
.attr("height", h)
.append("g")
.attr("transform", "translate(" + w / 2 + "," + h / 2 + ")");
var g = svg.selectAll(".arc")
.data(pie(chart_data.data))
.enter().append("g")
.attr("class", "arc")
.attr("stroke", "#999")
.attr("id",function(d){return d.data;})
.on("mouseover",toggleArc)
.on("mouseout",toggleArc);
g.append("path")
.attr("d", arc)
.style("fill", function(d) { return d.data.col; });
var color = d3.scale.category20b();
var legendRectSize = 18;
var legendSpacing = 4;
// FROM here the code is not produced the desired result
var legend = svg.selectAll('.legend')
.data(chart_data.data)
.enter()
.append('g')
.attr('class', 'legend')
.attr("id",function(d){return d.data;})
.attr('transform', function(d, i) {
var height = legendRectSize + legendSpacing;
var offset = height * chart_data.data.length / 2;
var horz = -2 * legendRectSize;
var vert = i * height - offset;
return 'translate(' + horz + ',' + vert + ')';
});
legend.append('rect')
.data(chart_data.data)
.attr('width', legendRectSize)
.attr('height', legendRectSize)
.style("fill", function(d) { return d.data.col; });
legend.append("text")
.attr('x', legendRectSize + legendSpacing)
.attr('y', legendRectSize - legendSpacing)
.text(function(d) { return d.data.name; });
}
The code actually works fine untill the line var legend = svg.selectAll('.legend')
Then i start to define the legend, but D3 complains about undefined d.data every time i try to access d.data below the line I written above(also in the last line of the code).
I don't understand where i got on the wrong way.
If instead of defining the whole non working part(var legend...) i write this code:
g.append("text")
.attr("stroke", "none")
.attr("fill", function(d) { return d.data.col; })
.text(function(d) { return d.data.name; });
I'm able to access the d.data.name.
Unfortunately wrong colors of the boxes and not description.
Thanks!

d3.js categorical time series (evolustrip)

Working in d3.js, I am looking for a good way to display categorical time series data. The data values cannot co-occur, and are not evenly spaced, so I've data exactly like:
location = [[time1: home], [time4: work], [time5: cafe], [time7: home]]
and so on. My ideal resulting graph is something like what might be called an evolustrip - one way of seeing this chart is as a time series chart with variable width bars, bar color corresponding to category (e.g. 'home').
Can anyone point me in the right direction? Thank you so much!
So I ended up crafting my own d3.js solution:
I used a d3.time.scale scale for the time dimension, and then a d3.scale.category20 scale to provide colors for the categories. I then plotted the categorical data as same-height rects on the time axis by start time, and used the d3.time.scale scale to compute the appropriate bin width for each rect.
A reusable component (following the pattern at http://bost.ocks.org/mike/chart/) example can be seen here:
function timeSeriesCategorical() {
var w = 860,
h = 70,
margin = {top: 20, right: 80, bottom: 30, left: 50},
width = w - margin.left - margin.right,
height = h - margin.top - margin.bottom;
var xValue = function(d) { return d[0]; },
yValue = function(d) { return d[1]; };
var yDomain = null;
var xScale = d3.time.scale()
.range([0, width]);
var yScale = d3.scale.category20();
var xAxis = d3.svg.axis()
.scale(xScale)
.tickSubdivide(1)
.tickSize(-height)
.orient('bottom');
var yAxis = d3.svg.axis()
.scale(yScale)
.ticks(5)
.orient('left');
var binwidth = 20;
function chart(selection) {
selection.each(function(data) {
// convert data to standard representation
data = data.map(function(d, i) {
return [xValue.call(data, d, i), yValue.call(data, d, i)];
//return d;
});
// scale the x and y domains based on the actual data
xScale.domain(d3.extent(data, function(d) { return d[0]; }));
if (!yDomain) {
yScale.domain(d3.extent(data, function(d) { return d[1]; }));
} else {
yScale.domain(yDomain);
}
// compute binwidths for TODO better comment
// d looks like {timestamp, category}
data.forEach(function(d, i) {
if (data[i+1]) {
w_current = xScale(data[i][0]);
w_next = xScale(data[i+1][0]);
binwidth = w_next - w_current;
}
d.binwidth = binwidth;
});
// create chart space as svg
// note: 'this' el should not contain svg already
var svg = d3.select(this).append('svg').data(data);
// external dimensions
svg.attr('width', w)
.attr('height', h);
// internal dimensions
svg = svg.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
// x axis
svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis);
// TODO bars legend
// bars
svg.selectAll('rect')
.data(data)
.enter().append('rect')
.attr('x', function(d, i) { return xScale(d[0]); })
.attr('width', function(d, i) { return d.binwidth; })
.attr('height', height)
.attr('fill', function(d, i) { return yScale(d[1]); })
.attr('stroke', function(d, i) { return yScale(d[1]); });
});
}
chart.x = function(_) {
if (!arguments.length) return xValue;
xValue = _;
return chart;
};
chart.y = function(_) {
if (!arguments.length) return yValue;
yValue = _;
return chart;
};
chart.yDomain = function(_) {
if (!arguments.length) return yDomain;
yDomain = _;
return chart;
};
return chart;
}
and is callable with something like:
d3.csv('./data.csv', function(data) {
var chartActivity = timeSeriesCategorical()
.x(function(d) { return d.when; })
.y(function(d) { return d.activity; })
.yDomain([0,1]);
d3.select('#chart-activity')
.datum(data)
.call(chartActivity);
});
Hopefully this is helpful to someone! The project this was made for is at https://github.com/interaction-design-lab/stress-sense-portal

Reference line for line graph

I'm trying to graph the median lifetime of our customers in D3.js. I have the data graphed out, but I can't figure out how to draw reference lines showing the median lifetime. I want vertical and horizontal reference lines that intersect my data at the 50% value of the y-axis.
Here's what I have currently:
The vertical reference line needs to intersect the data in the same place as the horizontal reference line.
Here's my code:
d3.json('data.json', function(billingData) {
var paying = billingData.paying;
var w = 800;
var h = 600;
var secondsInInterval = 604800000; // Seconds in a week
var padding = 50;
var age = function(beginDate, secondsInInterval) {
// Calculate how old a subscription is given it's begin date
var diff = new Date() - new Date(beginDate);
return Math.floor(diff / secondsInInterval);
}
var maxAge = d3.max(paying, function(d) { return age(d.subscription.activated_at, secondsInInterval); });
var breakdown = new Array(maxAge);
$.each(paying, function(i,d) {
d.age = age(d.subscription.activated_at, secondsInInterval);
for(var i = 0; i <= d.age; i++) {
if ( typeof breakdown[i] == 'undefined' ) breakdown[i] = 0;
breakdown[i]++;
}
});
// Scales
var xScale = d3.scale.linear().domain([0, maxAge]).range([padding,w-padding]);
var yScale = d3.scale.linear().domain([0, 1]).range([h-padding,padding]);
// Axes
var xAxis = d3.svg.axis().scale(xScale).tickSize(6,3).orient('bottom');
var yAxis = d3.svg.axis().scale(yScale).tickSize(6,3).tickFormat(d3.format('%')).orient('left');
var graph = d3.select('body').append('svg:svg')
.attr('width', 800)
.attr('height', 600);
var line = graph.selectAll('path.line')
.data([breakdown])
.enter()
.append('svg:path')
.attr('fill', 'none')
.attr('stroke', 'blue')
.attr('stroke-width', '1')
.attr("d", d3.svg.line()
.x(function(d,i) {
return xScale(i);
})
.y(function(d,i) {
return yScale(d/paying.length);
})
);
var xMedian = graph.selectAll('path.median.x')
.data([[[maxAge/2,0], [maxAge/2,1]]])
.enter()
.append('svg:path')
.attr('class', 'median x')
.attr("d", d3.svg.line()
.x(function(d,i) {
return xScale(d[0]);
})
.y(function(d,i) {
return yScale(d[1]);
})
);
var yMedian = graph.selectAll('path.median.y')
.data([[[0,.5], [maxAge,0.5]]])
.enter()
.append('svg:path')
.attr('class', 'median y')
.attr("d", d3.svg.line()
.x(function(d,i) {
return xScale(d[0]);
})
.y(function(d,i) {
return yScale(d[1]);
})
);
graph.append('g').attr('class', 'x-axis').call(xAxis).attr('transform', 'translate(0,' + (h - padding) + ')')
graph.append('g').attr('class', 'y-axis').call(yAxis).attr('transform', 'translate(' + padding + ',0)');
graph.append('text').attr('class', 'y-label').attr('text-anchor', 'middle').text('customers').attr('transform', 'translate(10,' + (h / 2) + '), rotate(-90)');
graph.append('text').attr('class', 'x-label').attr('text-anchor', 'middle').text('lifetime (weeks)').attr('transform', 'translate(' + (w/2) + ',' + (h - padding + 40) + ')');
});
You need to search the point where the customers are 50% in your line (around 7 weeks), that's it, search the index i where breakdown[i]/paying.length is near 0.5, save that index as indexMedianCustomers (for example) and modify your code in
var xMedian = graph.selectAll('path.median.x')
.data([[[indexMedianCustomers,0], [indexMedianCustomers,1]]])
.enter()
.append('svg:path')
.attr('class', 'median x')
.attr("d", d3.svg.line()
.x(function(d,i) {
return xScale(d[0]);
})
.y(function(d,i) {
return yScale(d[1]);
})
);

Resources