d3.js Changing gridlines and blocks to be evenly spaced - d3.js
I'm playing with the following d3 block http://bl.ocks.org/lakenen/8529857 which is a rendering of a candlestick-ish chart. Its output looks like this:
The data for each block is 1 day worth of financial stock numbers: the high, low, start, and close.
Typically, candlestick charts are different, though. Typically, the blocks are evenly spaced, and there is one gridline per day, and the x axis is labeled once per day. Here's an example of this on google finance:
And here's the code that renders the d3 chart from above:
var margin = 50;
var chart = d3.select("#chart")
.append("svg:svg")
.attr("class", "chart")
.attr("width", width)
.attr("height", height);
var y = d3.scale.linear()
.domain([d3.min(data.map(function(x) {return x["Low"];})), d3.max(data.map(function(x){return x["High"];}))])
.range([height-margin, margin]);
var x = d3.scale.linear()
.domain([d3.min(data.map(function(d){return d.timestamp;})), d3.max(data.map(function(d){ return d.timestamp;}))])
.range([margin,width-margin]);
chart.selectAll("line.x")
.data(x.ticks(10))
.enter().append("svg:line")
.attr("class", "x")
.attr("x1", x)
.attr("x2", x)
.attr("y1", margin)
.attr("y2", height - margin)
.attr("stroke", "#ccc");
chart.selectAll("line.y")
.data(y.ticks(10))
.enter().append("svg:line")
.attr("class", "y")
.attr("x1", margin)
.attr("x2", width - margin)
.attr("y1", y)
.attr("y2", y)
.attr("stroke", "#ccc");
chart.selectAll("text.xrule")
.data(x.ticks(10))
.enter().append("svg:text")
.attr("class", "xrule")
.attr("x", x)
.attr("y", height - margin)
.attr("dy", 20)
.attr("text-anchor", "middle")
.text(function(d){ var date = new Date(d * 1000); return (date.getMonth() + 1)+"/"+date.getDate(); });
chart.selectAll("text.yrule")
.data(y.ticks(10))
.enter().append("svg:text")
.attr("class", "yrule")
.attr("x", width - margin)
.attr("y", y)
.attr("dy", 0)
.attr("dx", 20)
.attr("text-anchor", "middle")
.text(String);
chart.selectAll("rect")
.data(data)
.enter().append("svg:rect")
.attr("x", function(d) { return x(d.timestamp); })
.attr("y", function(d) {return y(max(d.Open, d.Close));})
.attr("height", function(d) { return y(min(d.Open, d.Close))-y(max(d.Open, d.Close));})
.attr("width", function(d) { return 0.5 * (width - 2*margin)/data.length; })
.attr("fill",function(d) { return d.Open > d.Close ? "red" : "green" ;});
chart.selectAll("line.stem")
.data(data)
.enter().append("svg:line")
.attr("class", "stem")
.attr("x1", function(d) { return x(d.timestamp) + 0.25 * (width - 2 * margin)/ data.length;})
.attr("x2", function(d) { return x(d.timestamp) + 0.25 * (width - 2 * margin)/ data.length;})
.attr("y1", function(d) { return y(d.High);})
.attr("y2", function(d) { return y(d.Low); })
.attr("stroke", function(d){ return d.Open > d.Close ? "red" : "green"; })
}
I've tried tinkering with the .data(x.ticks(10)) values, which changes the number of ticks, but I'm not sure how to set that equal to the value of datapoints, and I'm also unsure of how exactly the d3.scale.linear().domain(...) stuff is changing the data before rendering begins.
So, how do I made the blocks evenly spaced so that I can make a gridline per block and a label per block?
The problem is that the graph you are trying to emulate doesn't have a linear x-axis based on time (it's missing days). You'll need to use a linear scale based on the number of data points and then custom set the label values.
I didn't really test this code so there may be bugs. However, this is how I would approach the problem.
// Create a formatter that given an index, will print the day number for the
// data at that index in data
var dayFormatter = d3.time.format('%d');
var dayAxisFormatter = function(d) {
return dayFormatter(new Date(data[d].timestamp));
}
// Create a formatter that given an index, will print the short month name
// along with the day number for the data at that index in data
var dayWithMonthFormatter = d3.time.format('%b %d');
var dayWithMonthAxisFormatter = function(d) {
return dayWithMonthFormatter(new Date(data[d].timestamp));
}
// Custom formatter to handle printing just the day number except for the first
// instance of the month, there we will print the short month and the day
// helper to create the formatter function that d3 accepts
function timeFormat(formats) {
return function(date) {
var i = formats.length - 1, f = formats[i];
while (!f[1](date)) f = formats[--i];
return f[0](date);
};
}
var firstDone = {}; // track the months so first instance gets month label
var tickFormatter = timeFormat([
[dayAxisFormatter, function(d) { return true; }],
[dayWithMonthFormatter, function(d) {
var month = (new Date(data[d].timestamp)).getMonth();
var result = !firstDone['m' + month];
firstDone['m' + month] = true;
return result;
}],
]);
// Set up a regular linear scale. This would normally just count up from
// 0 to d.length, but we'll use a custom formatter to print out our day
// numbers instead.
var x = d3.scale.linear()
.domain([0, d.length]) // set the domain to be from 0 to # of points
.range([margin,width-margin]);
// Set up the axis to use our customer formatter
var xAxis = d3.svg.axis()
.scale(x)
.tickSize(height)
.tickFormat(tickFormatter);
// Now when you go to draw your data, you need to remember that the
// underlying scale is based on the data index, not the data timestamp.
chart.selectAll("rect")
.data(data)
.enter().append("svg:rect")
.attr("x", function(d, i) { return x(i); })
...
Related
in d3js bar chart i want to have fixed bar width
http://bl.ocks.org/d3noob/8952219 I want to have bar size width to be fixed.. from above example i have changed the code from svg.selectAll("bar") .data(data) .enter().append("rect") .style("fill", "steelblue") .attr("x", function(d) { return x(d.date); }) .attr("width", x.rangeBand()) .attr("y", function(d) { return y(d.value); }) .attr("height", function(d) { return height - y(d.value); }); to svg.selectAll("bar") .data(data) .enter().append("rect") .style("fill", "steelblue") .attr("x", function(d) { return x(d.date); }) .attr("width", 50) .attr("y", function(d) { return y(d.value); }) .attr("height", function(d) { return height - y(d.value); }); but the labels are not moving to proper place also bars are getting overlapped
You have to change the range() of your x scale, to fit with your bar width value: var x = d3.scale.ordinal().rangeRoundBands([0, width], .05); to (if you want 50px as bar width) var x = d3.scale.ordinal().range([0, data.length * 50]); The range() method is used to define the display space for your scale.
I was looking for a similar solution. What #JulCh gave as an answer did not work out of the box for me, but lead me in the right direction. Try: var x = d3.scale.ordinal() .range(d3.range(data.length).map(function (d) { return d * 50; })); Where the inner d3.range creates an array containing the number of elements determined by data.length or some constant number (the number of bars you would like displayed). Example: If data.length or some constant is 8 then [0,1,2,3,4,5,6,7] is returned from d3.range(8) The map function then multiplies your fixed width of 50 against each element in the array returning [0,50,100,150,200,250,300,350]. D3 will then use these exact values to place your bars.
dataset created with d3.nest isn't drawing correctly
I am trying to visualize a dataset to see major players in the pangolin trade. I want each country to be part of a categorical scale on the y-axis, and the x-axis is the date, and I want each trade instance to be represented as a circle, with x being the date it happened, y being the position for that particular country, and r being the number of instances accumulated so far (represented in my data as r). Basically my data is from 2010 to 2016, and includes the country name where pangolins are either imported to or exported from, the count (accumulated trade instances up until this date), and the date (yyyy-mm-dd). Below is part of my data, which I store in the variable trade: [{"date":"2010-10-22","country":"Thailand","rank":4,"count":1},{"date":"2010-10-28","country":"Malaysia","rank":2,"count":1},{"date":"2010-11-8","country":"Thailand","rank":4,"count":2},{"date":"2010-11-18","country":"Nepal","rank":7,"count":1},{"date":"2010-11-22","country":"China","rank":5,"count":1},{"date":"2010-11-22","country":"China","rank":5,"count":2},{"date":"2010-11-27","country":"India","rank":1,"count":1},{"date":"2010-11-28","country":"India","rank":1,"count":2},{"date":"2010-11-28","country":"India","rank":1,"count":3},{"date":"2010-11-30","country":"India","rank":1,"count":4},{"date":"2010-12-17","country":"Malaysia","rank":2,"count":2},{"date":"2010-12-22","country":"Vietnam","rank":3,"count":1},{"date":"2011-01-3","country":"Nepal","rank":7,"count":2},{"date":"2011-02-12","country":"Myanmar","rank":8,"count":1},{"date":"2011-02-25","country":"Malaysia","rank":2,"count":3},{"date":"2011-02-26","country":"Malaysia","rank":2,"count":4},{"date":"2011-03-2","country":"South Africa","rank":18,"count":1},{"date":"2011-03-2","country":"Rwanda","rank":35,"count":1},{"date":"2011-03-2","country":"Mozambique","rank":22,"count":1},{"date":"2011-03-2","country":"Kenya","rank":12,"count":1},{"date":"2011-03-3","country":"China","rank":5,"count":3},{"date":"2011-02-21","country":"Vietnam","rank":3,"count":2},{"date":"2011-03-24","country":"Malaysia","rank":2,"count":5},{"date":"2011-04-4","country":"Malaysia","rank":2,"count":6},{"date":"2011-03-25","country":"India","rank":1,"count":5},{"date":"2011-03-26","country":"Malaysia","rank":2,"count":7},{"date":"2011-04-2","country":"Nepal","rank":7,"count":3},{"date":"2011-04-20","country":"Thailand","rank":4,"count":3},{"date":"2011-05-11","country":"China","rank":5,"count":4},{"date":"2011-05-11","country":"China","rank":5,"count":5},{"date":"2011-05-26","country":"Indonesia","rank":6,"count":1},{"date":"2011-05-26","country":"India","rank":1,"count":6},{"date":"2011-05-29","country":"Indonesia","rank":6,"count":2},{"date":"2011-06-6","country":"India","rank":1,"count":7},{"date":"2011-06-7","country":"Mozambique","rank":22,"count":2},{"date":"2011-06-5","country":"India","rank":1,"count":8},{"date":"2011-06-12","country":"Malaysia","rank":2,"count":8},{"date":"2011-06-13","country":"Singapore","rank":21,"count":1},{"date":"2011-06-14","country":"Malaysia","rank":2,"count":9},{"date":"2011-06-17","country":"India","rank":1,"count":9},{"date":"2011-06-19","country":"India","rank":1,"count":10},{"date":"2011-06-26","country":"Thailand","rank":4,"count":4},{"date":"2011-06-30","country":"India","rank":1,"count":11},{"date":"2011-07-4","country":"Malaysia","rank":2,"count":10},{"date":"2011-07-5","country":"Zimbabwe","rank":14,"count":1},{"date":"2011-07-12","country":"Indonesia","rank":6,"count":3},{"date":"2011-07-18","country":"Indonesia","rank":6,"count":4},{"date":"2011-07-27","country":"Nepal","rank":7,"count":4},{"date":"2011-08-16","country":"Nepal","rank":7,"count":5},{"date":"2011-08-19","country":"Namibia","rank":33,"count":1},{"date":"2011-08-23","country":"India","rank":1,"count":12},{"date":"2010-09-17","country":"Myanmar","rank":8,"count":2},{"date":"2011-09-1","country":"Zimbabwe","rank":14,"count":2},{"date":"2011-09-13","country":"Indonesia","rank":6,"count":5},{"date":"2011-09-13","country":"Malaysia","rank":2,"count":11},{"date":"2011-09-13","country":"Myanmar","rank":8,"count":3},{"date":"2011-09-21","country":"Malaysia","rank":2,"count":12},{"date":"2011-09-26","country":"Thailand","rank":4,"count":5},{"date":"2011-09-30","country":"Indonesia","rank":6,"count":6},{"date":"2011-10-1","country":"Sri Lanka","rank":19,"count":1},{"date":"2011-10-6","country":"India","rank":1,"count":13},{"date":"2011-10-7","country":"India","rank":1,"count":14},{"date":"2011-10-18","country":"Indonesia","rank":6,"count":7},{"date":"2011-10-18","country":"Indonesia","rank":6,"count":8},{"date":"2011-10-18","country":"Indonesia","rank":6,"count":9},{"date":"2011-10-22","country":"India","rank":1,"count":15},{"date":"2011-10-24","country":"India","rank":1,"count":16},{"date":"2011-11-28","country":"United States","rank":32,"count":1},{"date":"2011-12-15","country":"Vietnam","rank":3,"count":3},{"date":"2011-12-27","country":"Thailand","rank":4,"count":6},{"date":"2012-01-4","country":"Philippines","rank":15,"count":1},{"date":"2012-01-5","country":"Kenya","rank":12,"count":2},{"date":"2012-01-6","country":"Philippines","rank":15,"count":2},{"date":"2012-01-17","country":"Philippines","rank":15,"count":3},{"date":"2012-01-24","country":"China","rank":5,"count":6},{"date":"2012-02-22","country":"Malaysia","rank":2,"count":13},{"date":"2012-03-1","country":"Malaysia","rank":2,"count":14},{"date":"2012-03-19","country":"Pakistan","rank":11,"count":1},{"date":"2012-03-21","country":"Malaysia","rank":2,"count":15},{"date":"2012-03-23","country":"Vietnam","rank":3,"count":4},{"date":"2012-04-27","country":"Vietnam","rank":3,"count":5},{"date":"2012-04-23","country":"Belgium","rank":31,"count":1},{"date":"2012-06-7","country":"Thailand","rank":4,"count":7},{"date":"2012-06-7","country":"Thailand","rank":4,"count":8}]; and here is my code: var margin = {left:120, top:20, right:0, bottom:50}; var width = 1000; var height = 800; var data=trade; var tradeByCountry = d3.nest() .key(function(d) { return d.country; }) .entries(trade); console.log(tradeByCountry); tradeByCountry.forEach(function(country){ country['number']=country.values.length; console.log(country); }); var country_colors = ["#393b79","#5254a3", '#6b6ecf', '#9c9ede', '#637939', '#8ca252','#b5cf6b','#cedb9c', '#8c6d31','#bd9e39','#e7ba52','#e7cb94','#843c39','#ad494a','#d6616b','#e7969c','#7b4173','#a55194', '#ce6dbd','#de9ed6', '#9467bd', '#c5b0d5','#3182bd', '#6baed6','#17becf','#9edae5','#e6550d','#fd8d3c','#fdae6b', '#31a354','#74c476','#a1d99b','#d62728','#ff9896','#7f7f7f','#c7c7c7']; var colors = d3.scale.ordinal() .domain(d3.range(tradeByCountry.length)) .range(country_colors); tradeByCountry.sort(function(x, y){ return d3.descending(x.number, y.number); }) var countriesArray = []; tradeByCountry.forEach(function(country){ countriesArray.push(country.key); }); console.log(countriesArray); var x = d3.time.scale() .rangeRound([0, width]); var y = d3.scale.linear() .range([height, 0]); var timeFormat = d3.time.format("%Y-%m-%d"); x.domain([timeFormat.parse('2010-10-22'),timeFormat.parse('2016-12-30')]); y.domain(countriesArray); var xAxis = d3.svg.axis() .scale(x) .orient("bottom"); var yAxis = d3.svg.axis() .scale(y) .orient("left"); 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 + ")"); svg.append("g") .attr("class", "x axis") .attr("transform", "translate(0," + height + ")") .call(xAxis) .append("text") .attr("class", "label") .attr("x", width) .attr("y", -6) .style("text-anchor", "end") .text("date"); svg.append("g") .attr("class", "y axis") .call(yAxis) .append("text") .attr("class", "label") .attr("transform", "translate(35,-25)") .attr("y", 6) .attr("dy", ".71em") .style("text-anchor", "end") .text("countries"); svg.selectAll(".dot") .data(tradeByCountry) .enter().append("circle") .attr("class", "dot") .attr("r", 7) .attr("cx", function(d) { return 0; }) .attr("cy", function(d,i) { return (height/tradeByCountry.length)*i; }) .style("fill", function(d,i) { return colors(i); }); svg.selectAll("text.labels") .data(tradeByCountry) .enter() .append("text") .text(function(d) {return d.key}) .attr("x", 0) .attr("y", function(d,i) { return (height/tradeByCountry.length)*i; }) .attr("transform", "translate(-120,5)") .attr("fill",function(d,i) { return colors(i); }); svg.selectAll('.line') .data(tradeByCountry) .enter().append('line') .attr("x1", 0) // x position of the first end of the line .attr("y1",function(d,i) { return (height/tradeByCountry.length)*i; }) .attr("x2", width) // x position of the second end of the line .attr("y2", function(d,i) { return (height/tradeByCountry.length)*i; }) .style("stroke",function(d,i) { return colors(i); }) .attr("id", function(d){return d.key}); //PROBLEM HERE!!!!!!!!!!! var g = svg .selectAll("g") .data(tradeByCountry) .enter().append("g") .selectAll(".dot") .data(function(d) {return d.values;}) .enter().append("circle") .attr("id", function(d){ return d.count;}) .attr("r", function(d){return d.count;}) .attr("cx", function(d,i) { console.log (d.date);return (x(timeFormat.parse(d.date))); }) .attr("cy", function(d,i) { return (height/tradeByCountry.length)*i; }) .style("fill", function(d,i) { return colors(i); }); I am having trouble specifically at the very last part of my code, where I can't get all the circles to draw and not in the correct r, as I would like r to increase as the trade accumulates over time for a country, instead it seems that r is different according to country:
Two things, firstly in your example .selectAll("g") at the point you mark as having the error was picking up g elements in the axes, so the first few countries weren't getting their data displayed. This doesn't appear to be the case in your screenshot but it was happening given the code you posted, so I just qualified those gs with the .country class. The second thing, and what was causing your specific problem was this line: .attr("cy", function(d,i) { return (height/tradeByCountry.length)*i; }) I'm guessing you thought the index variable i here was still tied to the tradeByCountry array, which it would have been after selectAll("g.country").data(tradebyCountry) but at this point we've now made a nested selection .selectAll(".dot") on d.values for each country's data so i is now indexing those list of values. So that line of code above will take the values for each country and separate them all vertically, always starting from the top row - when in fact you want them on the same line, just separated by country. What you wanted (I = India, M = Malaysia, T = Thailand) I--- I0 I1 I2 M--- M0M1 M2 T--- T0 T1 T2 What you were getting I--- I0 M0 T0 M--- M1 I1 T1 T--- I2 T2 M2 This was also hidden by the fact .attr("fill") also had the same mistake so the colours on each row were consistent (I've used d.rank instead to fix it). Solution 1: To make sure the values stay on the same correct line use i before you do the nested selection so it still refers to the countries like this, which will offset each g element by the correct amount: .attr("transform", function(d,i) { return "translate(0,"+(height/tradeByCountry.length)*i+")"; }) and simply set cy to be zero for everything you add to this g element and they'll all be in a straight line (and on the right line) Full code at the PROBLEM HERE! stage: var g = svg .selectAll("g.country") .data(tradeByCountry) .enter().append("g") .attr("class", "country") .attr("transform", function(d,i) { return "translate(0,"+(height/tradeByCountry.length)*i+")"; }) .selectAll(".dot") .data(function(d) {return d.values;}) .enter().append("circle") .attr("class", "dot") .attr("id", function(d){ return d.country+"_"+d.date+"_"+d.count;}) .attr("r", function(d){ return d.count;}) .attr("cx", function(d,i) { console.log (d.date);return (x(timeFormat.parse(d.date))); }) .attr("cy", function(d,i) { return 0; }) .style("fill", function(d,i) { return colors(d.rank); }); http://jsfiddle.net/d4typ567/1/ Solution 2: It's also the case that d3 maintains a parent index variable for nested selections (usually denoted ii) that can be passed into most d3 .attr and .style functions, so you could do .attr("cy", function(d,i, ii) { return (height/tradeByCountry.length)*ii; }) instead, but offsetting the g element is only 1 operation whereas this is done for every circle. Remember to do the same for the color (fill) function.
D3.js - Adding a tick value on the x axis (date format)
I have created a waterfall chart using D3 (V4) with three values (ticks) for the y axis. The x axis tick values are automatically calculated. How can I add an additional tick value (today's date) on the x axis (date values)? function risklevels(d) { if (d <= 25 && d >= 13.5) { return "High"; } else if (d <= 13.5 && d > 7) { return "Med"; } return "Low"; } function drawWaterfall(){ var margin = {top: 20, right: 20, bottom: 30, left: 50}; var width = 800 - margin.left - margin.right; var height = 400 - margin.top - margin.bottom; dt = new Date(); var x = d3.scaleTime() .rangeRound([0, width]); var y = d3.scaleLinear() .rangeRound([height, 1]); var xAxis = d3.axisBottom(x); var yAxis = d3.axisLeft(y).tickFormat(risklevels).tickValues([4, 10.25, 19.125]); var parseDate = d3.timeParse("%Y-%m-%d"); var riskwaterfall = d3.select('#riskwaterfall').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+')'); riskwaterfall.append('rect') .attr('class', 'high') .attr("x", 0) // start rectangle on the good position .attr("y", 0) // no vertical translate .attr("width", width) // correct size .attr("height", height*((25.0-13.5)/25.0) + height*0.5/25) .attr("fill", "#ee0000"); // full height riskwaterfall.append('rect') .attr('class', 'high') .attr("x", 0) // start rectangle on the good position .attr("y", height*((25.0-13.5)/25.0) + height*0.5/25.0) // no vertical translate .attr("width", width) // correct size .attr("height", height*((13.5-7.0)/25.0) + height*0.5/25.0) .attr("fill", "#eeee00"); // full height riskwaterfall.append('rect') .attr('class', 'high') .attr("x", 0) // start rectangle on the good position .attr("y", (25-7)*height/25 + height*0.5/25.0)// no vertical translate .attr("width", width) // correct size .attr("height", 7*height/25 - height*0.5/25.0) .attr("fill", "#00ee00"); // full height var line = d3.line() .curve(d3.curveStepAfter) .x(function(d) { return x(d.date); }) .y(function(d) { return y(d.risk); }); line('step-after'); risk.forEach(function(d) { d.date = parseDate(d.date); d.risk = +d.risk; }); x.domain(d3.extent(risk, function(d) { return d.date; })); y.domain(d3.extent(risk, function(d) { return d.risk; })); riskwaterfall.append('g') .attr('class', 'x axis') .attr('transform', 'translate(0,'+height+')') .call(xAxis); riskwaterfall.append('g') .attr('class', 'y axis') .call(yAxis) .append('text') .attr('transform', 'rotate(-90)') .attr('y', 6) .attr('dy', '.71em') .style('text-anchor', 'end'); riskwaterfall.append('path') .datum(risk) .attr('d', line(risk)); for (var i = 0; i < risk.length; i++) riskwaterfall.append('circle') .datum(risk[i]) .attr("cx", function(d) { return x(d.date); }) .attr("cy", function(d) { return y(d.risk); }) .attr("stroke-width", "2px") .attr("fill", "black" ) //.attr("fill-opacity", .5) //.attr("visibility", "hidden") .attr("r", 5); }
Right now, you're creating a new date for today: dt = new Date(); But this has no effect on the x scale (which is used by the axis generator). So, instead of: x.domain(d3.extent(risk, function(d) { return d.date; })); Which only goes to the maximum date in the risk data, it should be: x.domain([d3.min(risk, function(d) { return d.date; }), dt]); After that, to make sure that the last tick shows up, you can use nice() or concat the end domain in your tick values.
d3.js number of days between two dates
I would like to graph the distance between a start date and an end date. I use the x position as my start date and I need the width of my rectangle to be the number of days between start and end. (I think) Displaying the data this way is not working out as desired: I need the width to END at the end date but it looks like the scale is distorting things. thanks! var w = 925; var h = 400; var padding = 50; var interactiveViz = d3.select("#viz").append("svg") .attr("width", w) .attr("height", h); var minDate = new Date(1999, 1, 27), maxDate = new Date(2013, 10, 9); var xscale = d3.time.scale() .domain([minDate, maxDate]) .range([0,w]); var xaxis = d3.svg.axis() .scale(xscale) .tickFormat(d3.time.format('%Y')) .tickValues([minDate, maxDate]); interactiveViz.selectAll("rect") .data(myData) .enter() .append("rect") .attr("x", function(d) { return xscale(new Date(d.start)) }) .attr("y", function(d,i) { return i*15 }) .attr("height", "14px") .attr("width", function(d) { return xscale(new Date(d.end)) }); interactiveViz.append("g") .attr("class", "x-axis") .attr("transform", "translate(0,"+(h-padding)+")") .call(xaxis) .selectAll("text") .attr("y", "10px") .style("fill", "#999999"); data: var myData = [ {"country":"name1","start":"1/27/1999","end":"12/8/2000"}, {"country":"name2","start":"1/29/1999","end":"12/21/1999"}, {"country":"name3","start":"12/5/2012","end":"12/18/2012"} ]
You want the width to be equal to the number of pixels between the start date and the end date. The pixel position of the start date is: xscale(new Date(d.start)) The pixel position of the end date is: xscale(new Date(d.end)) The number of pixels between them is xscale(new Date(d.end)) - xscale(new Date(d.start)) Which is what the width should be equal to .attr("width", function(d) { return xscale(new Date(d.end)) - xscale(new Date(d.start)) });
Using d3js to make a candlestick or ohlc chart
Is it possible to make an OHLC or Candlestick chart with d3js or plugins built from one of it or its forks? d3.js is a very powerful charting library built in javascript and it would be nice to customize charts built using it down further using its amazing abilities.
I found this one, and looks very nice. http://phrogz.net/js/d3-playground/#StockPrice_HTML
Have a look at this example. It does exactly what you want. Update: The link above is currently broken, but #lakenen was so kind as to provide a fixed version here.
I know this question is an old question but I spent lots of time looking for a working example in 2017 and found very little. Below is a working format for a candlestick chart using d3.js v4. The data in the code below is an array which in my case was being brought in from c# in the back. var twoHundredDayCandleStickChart = []; var width = 900; var height = 500; var margin = 50; function min(a, b) { return a < b ? a : b; } function max(a, b) { return a > b ? a : b; } var y = d3.scaleLinear().range([height - margin, margin]); var x = d3.scaleTime().range([margin, width - margin]); //line for the sma var line1 = d3.line() .x(function (d) { return x(d["date"]); }) .y(function (d) { return y(d["sma"]); }); function buildChart(data) { data.forEach(function (d) { d.date = new Date(d.date); d.high = +d.high; d.low = +d.low; d.open = +d.open; d.close = +d.close; d.sma = +d.sma; }); var chart = d3.select("#divId") .append("svg") .attr("class", "chart") .attr("width", width) .attr("height", height); y.domain([d3.min(data.map(function (x) { return x["low"]; })), d3.max(data.map(function (x) { return x["high"]; }))]) x.domain(d3.extent(data, function (d) { return d["date"]; })) chart.selectAll("line.x") .data(x.ticks(10)) .enter().append("line") .attr("class", "x") //.text(String) .attr("x1", x) .attr("x2", x) .attr("y1", margin) .attr("y2", height - margin) .attr("stroke", "#ccc"); chart.selectAll("line.y") .data(y.ticks(10)) .enter().append("line") .attr("class", "y") .attr("x1", margin) .attr("x2", width - margin) .attr("y1", y) .attr("y2", y) .attr("stroke", "#ccc"); chart.append("g") .attr("transform", "translate(0," + 450 + ")") //need to change this 450 to a variable- it is how far down the axis will go .attr("class", "xrule") .call(d3.axisBottom(x)) .selectAll("text") .style("text-anchor", "end") .attr("dx", "-.8em") .attr("dy", ".15em") .attr("transform", function (d) { return "rotate(-65)" }); chart.selectAll("text.yrule") .data(y.ticks(10)) .enter() .append("text") .attr("class", "yrule") .attr("x", 0) .attr("y", y) .attr("dy", 0) .attr("dx", 20) .attr("text-anchor", "middle") .text(String); chart.selectAll("rect") .data(data) .enter().append("rect") .attr("x", function (d) { return x(d["date"]); }) .attr("y", function (d) { return y(max(d["open"], d["close"])); }) .attr("height", function (d) { return y(min(d["open"], d["close"])) - y(max(d["open"], d["close"])); }) .attr("width", function (d) { return 0.5 * (width - 2 * margin) / data.length; }) .attr("fill", function (d) { return d["open"] > d["close"] ? "red" : "green"; }); chart.selectAll("line.stem") .data(data) .enter().append("line") .attr("class", "stem") .attr("x1", function (d) { return x(d["date"]) + 0.25 * (width - 2 * margin) / data.length; }) .attr("x2", function (d) { return x(d["date"]) + 0.25 * (width - 2 * margin) / data.length; }) .attr("y1", function (d) { return y(d["high"]); }) .attr("y2", function (d) { return y(d["low"]); }) .attr("stroke", function (d) { return d.open > d.close ? "red" : "green"; }); chart.append("path") .data([data]) .attr("d", line1) .attr("class", "line") .style("stroke", "white") .attr("fill", "none") .attr("stroke-width", 2); }
techan.js does what you need. As shown on their home page: A visual, stock charting (Candlestick, OHLC, indicators) and technical analysis library built on D3. Build interactive financial charts for modern and mobile browsers. There is another working example by Andre Dumas shown here, with example code. That was last updated 2017-07-07.