I want to make an line chart using d3.js with php.
My array is like as below:
{"x_axis":["xyz","abc","pqr"],"y_axis":["1","2","2"]}
I want to put whole x_axis array on x-axis and y_axis array on y-axis of line graph.
Please advice me for the same.
Thank you in advance.
You will have to use d3.scale.ordinal and map function to plot the values on axis. See this plnkr for example.
var xScale = d3.scale.ordinal()
.rangeBands([0,300],0.01);
var yScale = d3.scale.ordinal()
.rangeBands([0,300],0.01);
xScaleMappedValues = xScale.domain(data.map( function(d) {console.log(d.x); return d.x;}))
yScaleMappedValues = yScale.domain(data.map( function(d) {console.log(d.y); return d.y;}))
EDIT :
To plot data points against those values
svg.append("g")
.attr("class","datapoint")
.selectAll(".gCircle")
.data(data)
.enter()
.append("g")
.append("circle")
.attr("cx", function(d) {console.log(d.x); return xScale(d.x) + xScale.rangeBand()/2 })
.attr("cy", function(d) { return yScale(d.y) + yScale.rangeBand()/2; })
.attr("r",5)
Related
I need use a time scale for the x axis to represent the year. It may
be necessary to use time parsing/formatting when you load and display
the year data. The axis would be overcrowded if you display every year
value so I'd like to set the x-axis ticks to display one tick for every 3 years.
how can do it?
this is my live code:
https://plnkr.co/edit/qOqvG5T0yb0P4TbW6Kv6?p=preview
d3.dsv(",", "data.csv").then(function (data) {
console.log(data);
// Scale the range of the data in the domains
x.domain(data.map(function (d) { return d.year; }));
y.domain([0, d3.max(data, function (d) { return d.running_total; })]);
// append the rectangles for the bar chart
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function (d) { return x(d.year); })
.attr("width", x.bandwidth())
.attr("y", function (d) { return y(d.running_total); })
.attr("height", function (d) { return height - y(d.running_total); });
// add the x Axis
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// add the y Axis
svg.append("g")
.call(d3.axisLeft(y));
Besides changing the scale...
var x = d3.scaleTime()
... and the domain...
x.domain(d3.extent(data, function (d) { return d.year; }));
... the important part here is using interval.every to set the interval between the ticks.
For instance, one tick every 10 years:
d3.axisBottom(x).ticks(d3.timeYear.every(10))
Here is the updated Plunker: https://plnkr.co/edit/aSHJ6cIZyb0h1st1mX5w?p=preview
PS: you should not use bars (bar chart) with time as a variable. That's not the purpose of a bar chart. For instance, you'll have the problem of the bar width (which in the Plunker I set to 10 pixels). Have a look at my explanation here: https://stackoverflow.com/a/48279536/5768908
Edit: After Cyril correctly solved the problem I noticed that simply putting the functions that generate my axes underneath the functions used to generate the labels solves the problem.
I've almost finished reading the O'Reilly book's tutorials on D3.js and made the scatter graph on the penultimate page, but when adding the following code to generate my X axis more than half of my labels disappear:
// Define X Axis
var xAxis = d3.svg.axis()
.scale(xScale)
.orient('bottom');
// Generate our axis
svg.append('g')
.call(xAxis);
The odd thing is that of the labels that don't disappear the 3 that stay are the bottom 3 pairs from my dataset ([85,21], [220,88], [750,150]):
var myData = [
[5, 20],
...,
...,
[85, 21],
[220, 88],
[750,150]
];
Here is an image of what's happening, prior to adding the axis at the top each of these points had red text labels:
Below is the rest of the code that generates my scatter graph, it follows the methods explained in the book almost exactly and I can't pinpoint where the error is coming from.
// =================
// = SCALED SCATTER GRAPH
// =================
var p = 30; // Padding
var w = 500 + p; // Width
var h = 500 + p; // Height
// SVG Canvas and point selector
var svg = d3.select('body')
.append('svg')
.attr('width',w)
.attr('height',h);
// Scales take an input value from the input domain and return
// a scaled value that corresponds to the output range
// X Scale
var xScale = d3.scale.linear()
.domain([0, d3.max(myData, function(d){
return d[0];
})])
.range([p, w - (p + p)]); // With padding. Doubled so labels aren't cut off
// Y Scale
var yScale = d3.scale.linear()
.domain([0, d3.max(myData, function(d){
return d[1];
})])
.range([h - p, p]); // With padding
// Radial scale
var rScale = d3.scale.linear()
.domain([0, d3.max(myData, function(d){ return d[1];})])
.range([2,5]);
// Define X Axis
var xAxis = d3.svg.axis()
.scale(xScale)
.orient('bottom');
// Generate our axis
svg.append('g')
.call(xAxis);
// Plot scaled points
svg.selectAll('circle')
.data(myData)
.enter()
.append('circle')
.attr('cx', function(d){
return xScale(d[0]);
})
.attr('cy', function(d){
return yScale(d[1]);
})
.attr('r', function(d){
return rScale(d[1]);
});
// Plot all labels
svg.selectAll('text')
.data(myData)
.enter()
.append('text')
.text(function(d){
return d;
})
.attr('x', function(d){
return xScale(d[0]);
})
.attr('y', function(d){
return yScale(d[1]);
})
.style('fill', 'red')
.style('font-size',12);
js-fiddle: https://jsfiddle.net/z30cqeoo/
The problem is here:
svg.selectAll('text')
The x axis and y axis makes text element as ticks, so when the axis are present the above line will return array of ticks, thus it explains why it's not displaying when axis is added.
So the correct way would be to do something like this:
svg.selectAll('.text') //I am selecting those elements with class name text
svg.selectAll('.text')
.data(myData)
.enter()
.append('text')
.text(function(d){
console.log(d)
return d;
})
.attr('x', function(d){
return xScale(d[0]);
})
.attr('y', function(d){
return yScale(d[1]);
})
.attr('class',"text") //adding the class
.style('fill', 'red')
.style('font-size',12);
Full working code here.
I have a scatter plot with a line of best fit. I want to draw vertical lines (residual error lines) from each data point to the regression line. How can I do this?
If possible, I would like to use a transition to rotate the line of best fit and have the error lines expand or contract as the line of best rotates. Any help would be greatly appreciated!
Edit:
As pointed out, I incorrectly said the data for my line was contained within "data"; however, the line is being drawn using an array called lin_reg_results that is structured as follows:
var lin_reg_results = [{'x':3.4,'y':4.6},{'x':3.6,'y',2.4}...]
lin_reg_results data points were created by performing a linear regression server side and then passing the results back with ajax.
var x = d3.scale.linear()
.domain(d3.extent(data, function(d) { return d[x_val]; }))
.range([0, width]);
var y = d3.scale.linear()
.domain(d3.extent(data, function(d) { return d[y_val]; }))
// PLOT THE DATA POINTS
svg.selectAll(".dot")
.data(data)
.enter()
.append("circle")
.attr("class", "dot")
.attr("transform", "translate("+50+"," + 10 + ")")
.attr("r", 2.5)
.attr("cx", function(d) { return x(d[x_val]); })
.attr("cy", function(d) { return y(d[y_val]); })
.style("fill", function(d) { return color(d[z_val]); });
// DRAW THE PATH
var x = d3.scale.linear()
.domain(d3.extent(data, function(d) { return d['x']; }))
.range([0, width]);
var y = d3.scale.linear()
.domain(d3.extent(data, function(d) { return d['y']; }))
var line = d3.svg.line()
.x(function(d) { return x(d['x']); })
.y(function(d) { return y(d['y']); })
.interpolate("linear");
svg.append("path")
.datum(lin_reg_results)
.attr("transform", "translate("+50+"," + 10 + ")")
.attr("id","regression_line")
.attr("class", "line")
.attr("d", line);
You need to append a line for each data point from the coordinates of that point to where it meets the line. The code would look something like the following (with some code adapted from this question):
d3.selectAll("line")
.data(data)
.enter()
.append("line")
.attr("x1", function(d) { return x(d[x_val]); })
.attr("x2", function(d) { return x(d[x_val]); })
.attr("y1", function(d) { return y(d[y_val]); })
.attr("y2", function(d) {
var line = d3.select("#regression_line").node(),
x = x(d[x_val]);
function getXY(len) {
return line.getPointAtLength(len);
}
var curlen = 0;
while (getXY(curlen).x < x) { curlen += 0.01; }
return getXY(curlen).y;
});
Well the rendering of bar chart works fine with default given data. The problem occurs on the button click which should also cause the get of new data set. Updating the x-axis y-axis works well but the rendering data causes problems.
First Ill try to remove all the previously added rects and then add the new data set. But all the new rect elements gets added into wrong place, because there is no reference to old rects.
Here is the code and the redraw is in the end of code.
http://jsfiddle.net/staar2/wBNWK/9/
var data = JSON.parse('[{"hour":0,"time":147},{"hour":1,"time":0},{"hour":2,"time":74},{"hour":3,"time":141},{"hour":4,"time":137},{"hour":5,"time":210},{"hour":6,"time":71},{"hour":7,"time":73},{"hour":8,"time":0},{"hour":9,"time":68},{"hour":10,"time":70},{"hour":11,"time":0},{"hour":12,"time":147},{"hour":13,"time":0},{"hour":14,"time":0},{"hour":15,"time":69},{"hour":16,"time":67},{"hour":17,"time":67},{"hour":18,"time":66},{"hour":19,"time":0},{"hour":20,"time":0},{"hour":21,"time":66},{"hour":22,"time":210},{"hour":23,"time":0}] ');
var w = 15,
h = 80;
var xScale = d3.scale.linear()
.domain([0, 1])
.range([0, w]);
var yScale = d3.scale.linear()
.domain([0, d3.max(data, function (d) {
return d.time;
})])
.rangeRound([5, h]);
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.ticks(5);
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left");
var chart = d3.select("#viz")
.append("svg")
.attr("class", "chart")
.attr("width", w * data.length - 1)
.attr("height", h);
chart.selectAll("rect")
.data(data)
.enter().append("rect")
.attr("x", function(d, i) {
return xScale(i) - 0.5;
})
.attr("y", function(d) {
return h - yScale(d.time) - 0.5;
})
.attr("width", w)
.attr("height", function(d) {
return yScale(d.time);
});
chart.selectAll("text")
.data(data)
.enter()
.append("text")
.text(function(d) {
if (d.time > 10) {
return Math.round(d.time);
}
})
.attr("font-family", "sans-serif")
.attr("font-size", "11px")
.attr("fill", "#FFF")
.attr("text-anchor", "middle")
.attr("x", function(d, i) {
return xScale(i) + w / 2;
})
.attr("y", function(d) {
return h - yScale(d.time) - 0.5 + 10;
});
chart.append("g")
.attr("transform", "translate(0," + (h) + ")")
.call(xAxis);
function redraw() {
// This the part where the incoming data set also changes, which means the update to x-axis y-axis, labels
yScale.domain([0, d3.max(data, function (d) {
return d.time;
})]);
var bars = d3.selectAll("rect")
.data(data, function (d) {
return d.hour;
});
bars
.transition()
.duration(500)
.attr("x", w) // <-- Exit stage left
.remove();
d3.selectAll("rect") // This is actually empty
.data(data, function (d) {
return d.hour;
})
.enter()
.append("rect")
.attr("x", function(d, i) {
console.log(d, d.day, xScale(d.day));
return xScale(d.day);
})
.attr("y", function(d) {
return yScale(d.time);
})
.attr("width", w)
.attr("height", function (d) {
return h - yScale(d.time);
});
}
d3.select("button").on("click", function() {
console.log('Clicked');
redraw();
});
Agree with Sam (although there were a few more issues, like using remove() without exit(), etc.) and I am putting this out because I was playing with it as I was cleaning the code and applying the update pattern. Here is the FIDDLE with changes in code I made. I only changed the first few data points but this should get you going.
var data2 = JSON.parse('[{"hour":0,"time":153},{"hour":1,"time":10},{"hour":2,"time":35},{"hour":3,"time":150},
UPDATE: per request, adding logic to consider an update with new data. UPDATED FIDDLE.
Since you're binding the same data to bars, the enter selection is empty. Once you remove the existing bars, you append a new bar for each data point in the enter selection - which again is empty. If you had different data, the bars should append.
If you haven't read through it already, the general update pattern is a great resource for understanding this sort of thing.
I've created a little test line chart using D3, but since I am quite new to the library I am not sure what the best way would be to add multiple lines to a chart, at the moment I only have one line displayed in this fiddle.
I would like to display 2 lines on the chart, but I am unsure of how to achieve that without copy pasting code, which I am sure would be very inefficient as I would like to update/animate the graph at regular intervals based on user selection.
Instead of this,
var data = [12345,22345,32345,42345,52345,62345,72345,82345,92345,102345,112345,122345,132345,142345];
I would like to display something like this,
var data = [
[12345,42345,3234,22345,72345,62345,32345,92345,52345,22345], // line one
[1234,4234,3234,2234,7234,6234,3234,9234,5234,2234] // line two
];
Would this be a possibility? If so, what would be the best way to approach this, so that I can easily update/animate the graph when needed?
Note: I am merely trying to learn and to familiarize myself with D3 best practices and the library as a whole. Thanks.
This is possible and reasonable.
There is a tutorial that approaches this at the
D3 Nested Selection Tutorial
which describes nesting of data.
Below is code that I hacked from your fiddle to demonstrate this.
var data = [
[12345,42345,3234,22345,72345,62345,32345,92345,52345,22345],
[1234,4234,3234,2234,7234,6234,3234,9234,5234,2234]
];
var width = 625,
height = 350;
var x = d3.scale.linear()
.domain([0,data[0].length]) // Hack. Computing x-domain from 1st array
.range([0, width]);
var y = d3.scale.linear()
.domain([0,d3.max(data[0])]) // Hack. Computing y-domain from 1st array
.range([height, 0]);
var line = d3.svg.line()
.x(function(d,i) { return x(i); })
.y(function(d) { return y(d); });
var area = d3.svg.area()
.x(line.x())
.y1(line.y())
.y0(y(0));
var svg = d3.select("body").append("svg")
//.datum(data)
.attr("width", width)
.attr("height", height)
//.append("g");
var lines = svg.selectAll( "g" )
.data( data ); // The data here is two arrays
// for each array, create a 'g' line container
var aLineContainer = lines
.enter().append("g");
/*svg.append("path")
.attr("class", "area")
.attr("d", area);*/
aLineContainer.append("path")
.attr("class", "area")
.attr("d", area);
/*svg.append("path")
.attr("class", "line")
.attr("d", line);*/
aLineContainer.append("path")
.attr("class", "line")
.attr("d", line);
/*svg.selectAll(".dot")
.data(data)
.enter().append("circle")
.attr("class", "dot")
.attr("cx", line.x())
.attr("cy", line.y())
.attr("r", 3.5);*/
// Access the nested data, which are values within the array here
aLineContainer.selectAll(".dot")
.data( function( d, i ) { return d; } ) // This is the nested data call
.enter()
.append("circle")
.attr("class", "dot")
.attr("cx", line.x())
.attr("cy", line.y())
.attr("r", 3.5);
One deficiency is that I computed the domain for the x and y axes off the first array, which is a hack, but not pertinent to your question exactly.