I am trying do some path interpolation in D3. I'd like to produce an area plot like this, but I want to transition the area along the y-axis, starting from the bottom of the xaxis up to the final position shown in the example. Here's a quick sketch to explain what I'd like to do:
I'd like to start the transition with no area:
and transition it up along the y-axis:
Using the code, copied from the example, here's what I'd trying to do:
var margin = {top: 20, right: 20, bottom: 30, left: 50},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var parseDate = d3.time.format("%d-%b-%y").parse;
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var area = d3.svg.area()
.x(function(d) { return x(d.date); })
.y0(height)
.y1(function(d) { return y(d.close); });
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 + ")");
d3.tsv("data.tsv", function(error, data) {
data.forEach(function(d) {
d.date = parseDate(d.date);
d.close = +d.close;
});
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.close; })]);
var dataSel = svg.selectAll('.area').data(data)
dataSel.exit().remove()
dataSel.enter()
.append('path')
.attr("class", "area")
.attr("d", 'M0,0h' + width) // my idea here was to draw a path that
// has no area along the x-axis and then
// interpolate the path up to the final area
dataSel.transition() // transition the path to its final position
.duration(1000)
.attr("d", area)
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Price ($)");
});
Can anyone explain/show how I can transition a path along one axis as I'm trying to do?
D3 path transitions only really work if the starting and ending paths have the same number of control points. So, for example, D3 can't transition a line into an area. What you could do, however, is something like the following:
Use d3.area to generate the final path for the area.
Make a copy of the path and, in the copy, change all the control points that correspond to the "top" of the area to control points on the "bottom" of the area. (In other words, modify their y-values.)
Draw the area using this modified path.
Transition to the final path.
Related
Update, here is error message in console:
Error: attribute d: Expected number, "M0,NaNL21.654801022…".
I am sure this is a fairly simple d3 question, and I have looked at other answers but nothing posted seems to help, the ticks don't show up on the y-axis, but they work perfectly on x-axis. Additionally, this is supposed to be a line graph but the line is not being drawn.
Here is the code:
var margin = {top: 10, right: 40, bottom: 150, left: 70},
width = 760 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var w = width + margin.left + margin.right;
var h = height + margin.top + margin.bottom;
var svg = d3.select("body").append("svg") // this appends a new SVG element to body
.attr("width", w) // set the width
.attr("height", h) // set the height
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// x scale will handle time
var xScale = d3.scaleTime().range([0,width]);
// y scale will handle energy consumption values
var yScale = d3.scaleLinear().range([height,0]);
// Define X and Y AXIS
var yAxis = d3.axisLeft(yScale);
var xAxis = d3.axisBottom(xScale);
var parseTime = d3.timeParse("%Y");
function rowConverter(data) {
return {
year : parseTime(data.year),
value : +data.average // the + operator parses strings into numbers
};
}
// line generator function
var line = d3.line()
.curve(d3.curveBasis)
.x(function(d) { return xScale(d.year); })
.y(function(d) { return yScale(d.average); })
d3.csv("moreDummyData.csv",rowConverter).then(function(data){
yScale.domain([0,d3.max(data, function(d) {return d.average; })]);
xScale.domain(d3.extent(data, function(d) { return d.year; }));
// Draw xAxis
svg.append("g") // add a new svg group element
.attr("class", "x axis")
.attr("transform", "translate(0, " + height + ")")
.call(xAxis)
.selectAll("text")
.attr("dx", "-.8em")
.attr("dy", ".25em")
.attr("text-anchor", "end");
// Draw yAxis
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.selectAll("text")
.attr("dx", "-.8em")
.attr("dy", ".25em")
.attr("text-anchor", "end");
// add a title for the yAxis
svg.append("text") // add a new svg "text" element
.attr("text-anchor", "end")
.attr("transform", "rotate(-90)") // turn it on its side
// position the title in space
.attr("y", -margin.left+20)
.attr("x", -margin.top-75)
// give it text and style
.text("ADD TITLE")
.attr("font-family", "Times")
.attr("font-size", "16px");
svg.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
});
And this is what the output looks like with this code:
code output
Here's what the data looks like:
year,average
1971,30
1972,34
1973,29
1974,28
1975,31
1976,35
i have some values in my csv file and i show a graph with values on y axis and dates on x axis.
For first graph i have following values
date,close
13-Jul-16,0.8736701869033555
15-Jul-16,0.3631761567983922
17-Jul-16,0.4795564555162078
19-Jul-16,0.3754827857186281
21-Jul-16,0.4355941951068847
23-Jul-16,0.34393804366457353
25-Jul-16,0.40967947088135176
27-Jul-16,0.2707818657230363
29-Jul-16,0.34430251610420176
31-Jul-16,0.28089496856221585
For second graph i have following values
date,close
11-Jul-16,0.766705419439816
15-Jul-16,0.7353651170975812
17-Jul-16,0.41531502169603063
19-Jul-16,0.5927871032351933
21-Jul-16,0.7986419920511857
23-Jul-16,0.7904979990272231
25-Jul-16,0.817690401573838
27-Jul-16,0.8433545168648027
29-Jul-16,0.8612307965742473
31-Jul-16,0.806498303188971
But in second graph x axis does not contain all dates.. As an example i put a printscreen of my output graphs myoutput to here.
This is my code which takes datas from csv file and visualize it.
var selectedMonth=document.getElementById('selectedMonth').value;
var selectedTopic=document.getElementById('selectedTopic').value;
var userFileDirectory="../documents/";
userFileDirectory=userFileDirectory+selectedMonth+"/"+selectedTopic+"/"+"dataCs.csv";
// Set the dimensions of the canvas / graph
var margin = {top: 30, right: 20, bottom: 30, left: 50},
width = 600 - margin.left - margin.right,
height = 270 - margin.top - margin.bottom;
// Parse the date / time
var parseDate = d3.time.format("%d-%b-%y").parse;
// Set the ranges
var x = d3.time.scale().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
// Define the axes
var xAxis = d3.svg.axis().scale(x)
.orient("bottom").ticks(5);
var yAxis = d3.svg.axis().scale(y)
.orient("left").ticks(5);
// Define the line
var valueline = d3.svg.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.close); });
// Adds the svg 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 + ")");
// Get the data
d3.csv(userFileDirectory, function(error, data) {
data.forEach(function(d) {
d.date = parseDate(d.date);
d.close = +d.close;
});
// Scale the range of the data
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.close; })]);
// Add the valueline path.
svg.append("path")
.attr("class", "line")
.attr("d", valueline(data));
// Add the X Axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Add the Y Axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
svg.append("text")
.text("("+selectedMonth+" "+selectedTopic+")");
});
I would try setting the tick values explicitly, using tickValues:
https://github.com/d3/d3-3.x-api-reference/blob/master/SVG-Axes.md#tickValues
ticks(5) will suggest 5 ticks, but will be adapted based on the scale's domain. Alternative to tickValues(), you could try ticks(d3.time.day, 2) to have a tick every 2 days.
Can we render a D3 line chart using Render Queue?http://bl.ocks.org/syntagmatic/raw/3341641/
I have a JSON with around 50,000 elements in it. My browser crashes when I try to draw a graph with this much amount of data.
Code is:
function lineChart(data, id){
// Set the dimensions of the canvas / graph
var margin = {top: 30, right: 20, bottom: 30, left: 50},
width = 1000 - margin.left - margin.right,
height = 370 - margin.top - margin.bottom;
// Parse the date / time
var parseDate = d3.time.format("%d-%b-%y").parse;
// Define the div for the tooltip
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
// Set the ranges
var x = d3.time.scale().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
// Define the axes
var xAxis = d3.svg.axis().scale(x)
.orient("bottom").ticks(5);
var yAxis = d3.svg.axis().scale(y)
.orient("left").ticks(5);
// Define the line
var valueline = d3.svg.line()
.x(function(d) { return x(d.time); })
.y(function(d) { return y(d.loadaverage); })
// Adds the svg canvas
var svg = d3.select(id)
.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 + ")");
// Scale the range of the data
x.domain(d3.extent(data, function(d) { return d.time; }));
y.domain([0, d3.max(data, function(d) { return d.loadaverage; })]);
// Add the valueline path.
svg.selectAll('path')
.data(pos)
.enter()
.append("path")
.attr("class", "line")
.attr("d", valueline(data))
.on("mouseover", function(d) {
div.transition()
.duration(200)
.style("opacity", .9);
div.html(d.time + "<br/>" + d.loadaverage)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
div.transition()
.duration(500)
.style("opacity", 0);
});
// Add the X Axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Add the Y Axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
}
Render Queue uses canvas, which, with 50k elements, you probably should use too. Unfortunately, d3 v3 doesnt support canvas. If you can upgrade to v4, then you will gain canvas support and much greater performance. Conceptually, you certainly can do progressive rendering with lines, but I do not know if Render Queue directly supports it. We ultimately rolled our own progressive renderer for our product.
Be forewarned, if you do switch over to canvas, you'll have to rethink how you are doing your mouseevents because on canvas, you dont have elements onto which you can attach listeners.
I have a simple d3 area chart, with two areas plotted using the following data:
var data = [
[{'year':0,'amount':2},{'year':1,'amount':3},{'year':2,'amount':9},{'year':3,'amount':5},{'year':4,'amount':6},{'year':5,'amount':7},{'year':6,'amount':8},{'year':7,'amount':9},{'year':8,'amount':10},{'year':9,'amount':11},{'year':10,'amount':12}],
[{'year':0,'amount':1},{'year':1,'amount':2},{'year':2,'amount':8},{'year':3,'amount':4},{'year':4,'amount':5},{'year':5,'amount':6},{'year':6,'amount':7},{'year':7,'amount':8},{'year':8,'amount':9},{'year':9,'amount':10},{'year':10,'amount':11}]
];
The two separate arrays of objects allow me to plot two areas on one chart using the code below:
var colors = [
'steelblue',
'lightblue',
];
var margin = {top: 20, right: 30, bottom: 30, left: 50},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.linear()
.domain(d3.extent(data.map(function(d,i) { console.log(d); return d[i].year; })))
.range([0, width]);
var y = d3.scale.linear()
.domain([-1, 16])
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.tickSize(-height)
.tickPadding(10)
.tickSubdivide(true)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.tickPadding(10)
.tickSize(-width)
.tickSubdivide(true)
.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);
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
svg.append("g")
.attr("class", "y axis")
.append("text")
.attr("class", "axis-label")
.attr("transform", "rotate(-90)")
.attr("y", (-margin.left) + 10)
.attr("x", -height/2)
.text('Axis Label');
svg.append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
//************************************************************
// Create D3 line object and draw data on our SVG object
//************************************************************
var line = d3.svg.area()
.interpolate("cardinal")
.x(function(d) { return x(d.year); })
.y0(height)
.y1(function(d) { return y(d.amount); });
svg.selectAll('.line')
.data(data)
.enter()
.append("path")
.attr("class", "area")
.attr('fill', function(d,i){
return colors[i%colors.length];
})
.attr("d", line);
Thing is I need to set the domains based on the data. I've tried doing:
.domain(d3.extent(data.map(function(d) { return d.amount; })))
...when creating my linear scale but obviously this doesn't work as the array map in the call above just maps out the nested arrays instead of the objects inside.
How do I set the domain using data in this format? Or is there a better way to structure my data whilst still allowing for multiple areas to be drawn?
To get the overall extent of all the amount values contained in both arrays you need to somehow merge these arrays into one. There are several ways this could be done:
d3.merge() to merge both arrays into one:
var allValues = d3.merge(data);
The main advantage of this approach over the following ones is the fact, that this will work with any number of nested arrays in data without any changes to the code.
Built-in method Array.prototype.concat():
var allValues = data[0].concat(data[1])
If you want to show off and don't need to be compatible with older version of JavaScript, you can apply the spread operator new to ES6:
var allValues = [...data[0], ...data[1]];
Having this flattened array containing all values you can pass it to d3.extent() to calculate the overall extent.
var extent = d3.extent(allValues, function(d) { return d.amount; });
var data = [
[{'year':0,'amount':2},{'year':1,'amount':3},{'year':2,'amount':9},{'year':3,'amount':5},{'year':4,'amount':6},{'year':5,'amount':7},{'year':6,'amount':8},{'year':7,'amount':9},{'year':8,'amount':10},{'year':9,'amount':11},{'year':10,'amount':12}],
[{'year':0,'amount':1},{'year':1,'amount':2},{'year':2,'amount':8},{'year':3,'amount':4},{'year':4,'amount':5},{'year':5,'amount':6},{'year':6,'amount':7},{'year':7,'amount':8},{'year':8,'amount':9},{'year':9,'amount':10},{'year':10,'amount':11}]
];
console.log(d3.extent(d3.merge(data), function(d) { return d.amount; })); // using d3.merge()
console.log(d3.extent(data[0].concat(data[1]), function(d) { return d.amount; })); // using Array.prototype.concat()
// This will only work in compatible browsers which support the new ES6 spread operator
console.log(d3.extent([...data[0], ...data[1]], function(d) { return d.amount; })); // using ES6 spread operator
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
I need some help with my following D3 line chart, where x is a time domain and y is linear scale value, currently in this fiddle, the transition of path occurs from right to left, but I want it to happen as left to right instead.
var data = [{"closedate":"2013-12-07T08:00:00.000Z","amount":60000}];
//Set Canvas Area properites
var margin = {top: 50, right: 50, bottom: 100, left: 100},
width = 500 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
//X value transformation function - transforms close date into chart x position of canvas
var x = d3.time.scale()
.range([0, width])
.domain(d3.extent(data, function(d) { return new Date(d.closedate); }));
//Y Value transformation function - transforms amount into y position of canvas
var y = d3.scale.linear()
.range([height, 0])
.domain(d3.extent(data, function(d) { return d.amount; }));
//X Axis Function
var xAxis = d3.svg.axis()
.scale(x)
//.tickFormat(d3.time.format('%m/%y'))
.ticks(6)
.orient("bottom");
//Y Axis Function
var yAxis = d3.svg.axis().scale(y).ticks(6).orient("left");
//Line Function to draw SVG Line
var line = d3.svg.line()
.interpolate("cardinal")
.x(function(d) { return x(new Date(d.closedate)); })
.y(function(d) { return y(d.amount); });
//Create SVG canvas area with height and width properites
var svg = d3.select("#d3linechartid").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 + ")");
//console.log("data = "+JSON.stringify(data));
//Draw XAxis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.append("text")
.attr("x", width )
.attr("y", "50")
.style("text-anchor", "middle")
.text("Close Date");
//Draw YAxis
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "middle")
.text("Sales Amount ($)");
//Draw SVG Path
var path = svg.append("path")
.datum(data)
.attr("d", line)
.style('fill', 'none')
.style('stroke', 'steelblue')
.attr("stroke-width", "2");
//Do Transistion of Path
var totalLength = path.node().getTotalLength();
path
.attr("stroke-dasharray", totalLength+","+totalLength)
.attr("stroke-dashoffset", totalLength)
.transition()
.duration(2000)
.ease("linear-in-out")
.attr("stroke-dashoffset", 0);
You can sort the input data in ascending order, like so:
data.sort(function(a, b){ return d3.ascending(a.closedate, b.closedate); });