d3.js multiple line chart rendering: Same dataset but different curve - d3.js

I am completely new to d3.js and currently working on a multiple line chart in d3.js and therefore prepared two different datasets ("dataset", "datasetIndividual"). I noticed that the curve for the individual curve (green) differed from the points that are drawn after reload.
So in order to determine wether the problem was the curve or the dots I started using the same dataset for each curve and the problem definitely seems to be the rendering of the individual curve. After each reload sometimes the curves might completely overlap and other times differ. I am not able to determine a reason nor the solution for this particular problem. I assume it has something to do with the nesting/sequence of the code. I admit, the whole code is a bit "pedestrian".
The code for the line chart:
/*create svg element*/
var svg = d3.select('.linechart')
.append('svg')
.attr('width', w)
.attr('height', h)
.attr('id', 'chart');
/*x scale*/
var xScale = d3.scale.linear()
.domain([0, d3.max(dataset, function(d) {
return d[0];
})])
.range([padding, w - padding]);
/*y scale*/
var yScale = d3.scale.linear()
.domain([0, d3.max(dataset, function(d) {
return d[1];
})])
.range([h - padding, padding]);
/*x axis*/
var xAxis = d3.svg.axis()
.scale(xScale)
.orient('bottom')
.ticks(20)
.tickSize(0, 0)
//.tickPadding(padding);
/*append x axis*/
svg.append('g')
.attr({
'class': 'xaxis',
//'transform': 'translate(0,' + (h - padding) + ')'
'transform': 'translate(0,' + 0 + ')'
})
.call(xAxis);
/*y axis*/
var yAxis = d3.svg.axis()
.scale(yScale)
.orient('left')
.tickSize(0, 0)
.tickValues([0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100]);
/*append y axis*/
svg.append('g')
.attr({
'class': 'yaxis',
'transform': 'translate(' + padding + ',0)'
})
.call(yAxis);
/*define line*/
var lines = d3.svg.line()
.x(function(d) {
return xScale(d[0])
})
.y(function(d) {
return yScale(d[1])
})
.interpolate('monotone');
/*append line*/
var path = svg.append('path')
.attr({
'd': lines(dataset),
'fill': 'none',
'class': 'lineChart'
});
/*get length*/
var length = svg.select('.lineChart').node().getTotalLength();
/*animate line chart*/
svg.select('.lineChart')
.attr("stroke-dasharray", length + " " + length)
.attr("stroke-dashoffset", length)
.transition()
.ease('linear')
.delay(function(d) {
return dataset.length * 100;
})
.duration(3000)
.attr("stroke-dashoffset", 0);
/*add points*/
var points = svg.selectAll('circle')
.data(dataset)
.enter()
.append('circle');
/*point attributes*/
points.attr('cy', function(d) {
return yScale(d[1])
})
.style('opacity', 0)
.transition()
.duration(1000)
.ease('elastic')
.delay(function(d, i) {
return i * 100;
})
.attr({
'cx': function(d) {
return xScale(d[0]);
},
'cy': function(d) {
return yScale(d[1]);
},
'r': 5,
'class': 'datapoint',
'id': function(d, i) {
return i;
}
})
.style('opacity', 1);
// LINES INDIVIDUAL
//function drawIndividualLines (){
/*x scale*/
var xScaleIndividual = d3.scale.linear()
.domain([0, d3.max(datasetIndividual, function(d) {
return d[0];
})])
.range([padding, w - padding]);
/*y scale*/
var yScaleIndividual = d3.scale.linear()
.domain([0, d3.max(datasetIndividual, function(d) {
return d[1];
})])
.range([h - padding, padding]);
/*define line*/
var linesIndividual = d3.svg.line()
.x(function(d) {
return xScaleIndividual(d[0])
})
.y(function(d) {
return yScaleIndividual(d[1])
})
.interpolate('monotone');
/*append line*/
var pathIndividual = svg.append('path')
.attr({
//'d': linesIndividual(datasetIndividual),
'd': linesIndividual(dataset),
'fill': 'none',
'class': 'lineChartIndividual'
});
/*get length*/
var lengthIndividual = svg.select('.lineChartIndividual').node().getTotalLength();
/*animate line chart*/
svg.select('.lineChartIndividual')
.attr("stroke-dasharray", lengthIndividual + " " + lengthIndividual)
.attr("stroke-dashoffset", lengthIndividual)
.transition()
.ease('linear')
.delay(function(d) {
return datasetIndividual.length * 100;
})
.duration(3000)
.attr("stroke-dashoffset", 0);
/*add points*/
var pointsIndividual = svg.selectAll('circleIndividual')
.data(dataset)
.enter()
.append('circle');
/*point attributes*/
pointsIndividual.attr('cy', function(d) {
return yScale(d[1])
})
.style('opacity', 0)
.transition()
.duration(1000)
.ease('elastic')
.delay(function(d, i) {
return i * 100;
})
.attr({
'cx': function(d) {
return xScale(d[0]);
},
'cy': function(d) {
return yScale(d[1]);
},
'r': 5,
'class': 'datapointIndividual',
'id': function(d, i) {
return i;
}
})
.style('opacity', 1);
I prepared a fiddle in hope someone of you is able to determine the source of the problem:
https://jsfiddle.net/4vf6s6e9/
I would apreciate any help. I donĀ“t often post questions and tried to incorprate suggestions from my last experience. If you need anything from me to improve this post, feel free to tell me.
Thanks a lot in advance.

You are drawing the lines with two slightly different scales.
var xScale = d3.scale.linear()
.domain([0, d3.max(dataset, function(d) {
return d[0];
})])
.range([padding, w - padding]);
/*y scale*/
var yScale = d3.scale.linear()
.domain([0, d3.max(dataset, function(d) {
return d[1];
})])
.range([h - padding, padding]);
and
/*x scale*/
var xScaleIndividual = d3.scale.linear()
.domain([0, d3.max(datasetIndividual, function(d) {
return d[0];
})])
.range([padding, w - padding]);
/*y scale*/
var yScaleIndividual = d3.scale.linear()
.domain([0, d3.max(datasetIndividual, function(d) {
return d[1];
})])
.range([h - padding, padding]);
This makes the calculations of the position of each line just a tiny bit different and that difference depends on the random difference between the extents of the two data sets.
Passing the same data to both scales (or just using the same scale for both lines) fixes the problem. Here's an updated fiddle with that single change: https://jsfiddle.net/oa0rsvgc/

Related

d3 struggling to add custom ticks to area graph for chart

I cannot seem to get custom ticks added to an area graph, default however seem to work. I have attempted many different things I have found in examples online, but they either take no effect of result in the graph having no axis at all.
enter image description here
the code in question is as follows:
`
var y = d3.scaleLinear()
.domain([0, d3.max(data, function(d) { return d.age1+d.age2+d.age3+d.age4; })])
.range([ chartHeight, 0 ]);
svg.append("g").call(d3.axisLeft(y));
if(timeline){
var x = d3
.scaleTime()
.domain(d3.extent(data, function(d) { return tParser(d.date); }))
.range([ 0, chartWidth ]);
svg.append("g")
.attr("transform", "translate(0," + chartHeight + ")")
.call(d3.axisBottom(x));
}else{
var x = d3.scaleLinear()
.domain([d3.min(data, function(d) { return d.date; }), d3.max(data, function(d) { return d.date; })])
.range([ 0, chartWidth ]);
svg.append("g")
.attr("transform", "translate(0," + chartHeight + ")")
.call(d3.axisBottom(x));
}`
I have tried adding the ticks as follows:
var x = d3 .scaleTime() .domain(d3.extent(data, function(d) { return tParser(d.date); })) .ticks(5) .range([ 0, chartWidth ]);
and this
var x = d3 .scaleTime() .domain(d3.extent(data, function(d) { return tParser(d.date); })) .range([ 0, chartWidth ]); x.ticks(5);
I have created a stack blitz with an example of my project, should that help
https://stackblitz.com/edit/angular-ivy-mrs77c?file=src%2Fapp%2FstackedArea-chart-visualization%2FstackedArea-chart-visualization.component.ts

Adding axes eats up my data

I'm following the D3 tutorial but adding the axis makes half of my data disappear and I don't understand why. I thought that maybe the axis is taking up the space that's meant for the data so I added an extra 10px to the transform property, but it doesn't make any difference.
var GIST = "https://gist.githubusercontent.com/charisseysabel/f8f48fbf11b8a1b0d62cbe2d6bdc2aa6/raw/2ead1537adb822fbd59a666afd5334d525480a13/nano-2017.tsv"
var width = 1000,
height = 550,
margin = {top: 20, right: 30, bottom: 30, left: 4};
var y = d3.scaleLinear()
.range([height, 0]);
var yScale = d3.scaleLinear()
.range([height, 0]);
var xScale = d3.scaleLinear()
.range([0, width]);
var xAxis = d3.axisLeft(yScale);
var yAxis = d3.axisBottom(xScale);
var chart = d3.select(".chart")
.attr("width", width)
.attr("height", height);
chart.append("g")
.attr("transform", "translate(10, 0)")
.call(xAxis);
chart.append("g")
.attr("transform", "translate(0, 540)")
.call(yAxis);
d3.tsv(GIST, type, function(error, data) {
y.domain([0, d3.max(data, function(d) { return d.value; })]);
var barWidth = width / data.length;
var bar = chart.selectAll("g")
.data(data)
.enter().append("g")
.attr("transform", function(d, i) {
return "translate(" + ((i * barWidth) + 10) + ",0)"; }
);
bar.append("rect")
.attr("y", function(d) { return y(d.value); })
.attr("height", function(d) { return height - y(d.value); })
.attr("width", barWidth - 1);
bar.append("text")
.attr("x", (barWidth / 2) - 2)
.attr("y", function(d) { return y(d.value) + 3; })
.attr("dy", ".75em")
.text(function(d) { return d.value; });
});
function type(d) {
d.value = +d.value;
return d;
}
When you do this...
var bar = chart.selectAll("g").etc...
... you're selecting group elements that already exist in the SVG, which are the axes, and binding your data to them.
There are two easy solutions:
Move your code that creates the axes to the bottom of the d3.tsv, that is, after you have appended the bars.
Select something that doesn't exist, like
var bar = chart.selectAll(null).etc. To read more about the logic behind selectAll(null), have a look at my answer here.

d3 center tick and x-axis label when single value

I have the following d3 code:
var json = [
{
date: "05/17",
numTags: 23
}
];
d3.select('summary-graph').selectAll('*').remove();
var svg = d3.select("summary-graph"),
margin = {
top: 20,
right: 30,
bottom: 30,
left: 40
},
width = svg.attr("width") - margin.left - margin.right,
height = svg.attr("height") - margin.top - margin.bottom;
var parseTime = d3.timeParse("%m/%y");
var svg = d3.select("summary-graph").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
var data = json;
// format the data
data.forEach(function (d) {
console.log(d);
d.date = parseTime(d.date);
d.numTags = +d.numTags;
});
// set the ranges
var xScale = d3.scaleTime()
.range([0, width])
.domain(d3.extent(data, function (d) {
return d.date;
}))
.nice();
var yScale = d3.scaleLinear()
.range([height, 0])
.domain([0, d3.max(data, function (d) {
return Math.max(d.numTags);
})])
.nice();
// define the 1st line
var tagLine = d3.line()
.x(function (d) {
return xScale(d.date);
})
.y(function (d) {
return yScale(d.numTags);
});
// Axes
var xAxis = d3.axisBottom()
.scale(xScale)
.ticks(json.length)
.tickSizeOuter(0)
.tickFormat(d3.timeFormat('%B %Y'));
var yAxis = d3.axisLeft().scale(yScale);
svg.append("path")
.data([data])
.attr("class", "line")
.style("stroke", "blue")
.attr("d", tagLine);
var points = svg.selectAll(".point")
.data(data)
.enter().append("svg:circle")
.attr("stroke", "green")
.attr("fill", function(d, i) { return "blue" })
.attr("cx", function(d, i) { return xScale(d.date) })
.attr("cy", function(d, i) { return yScale(d.numTags) })
.attr("r", function(d, i) { return 10 });
// Add the X Axis
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.style("font-size","14px");;
// Add the Y Axis
svg.append("g")
.call(yAxis);
Which produces the following visualization:
I'm trying to figure out how to center the tick mark and show the x-axis label when there is only 1 data point like this. At 2 data points, I don't love how it still sets the tick marks at the extreme ends of the x-axis. For 3 data points and above, it looks nice (thanks to .nice() it seems).
Any help?
Based on Gerado's response, I was able to get close. The last sticking point is that the left side of the X-axis now has the month (March) despite no data for that day.
Fixed:
changed .ticks(json.length) to .ticks(d3.timeMonth.every(1))
Since you are using only a single data point, your date scale has a domain in which the lower and upper values are the same:
[
Mon May 01 2017 00: 00: 00 GMT + 1000,
Mon May 01 2017 00: 00: 00 GMT + 1000
]
For putting that circle in the middle of the x axis you have to set different values for the scale's domain.
There are several ways for doing that. My proposed solution here involves verifying if the domain's values are the same...
if (xScale.domain()[0].getTime() == xScale.domain()[1].getTime()) {
... and, if they are, changing them. In this case, I'm subtracting one day from the lower limit and adding one day to the upper limit:
if (xScale.domain()[0].getTime() == xScale.domain()[1].getTime()) {
var dateLess = d3.timeDay.offset(xScale.domain()[0], -1);
var dateMore = d3.timeDay.offset(xScale.domain()[0], 1);
xScale.domain([dateLess, dateMore])
}
Check the result:
var json = [{
date: "05/17",
numTags: 23
}];
var margin = {
top: 20,
right: 30,
bottom: 30,
left: 40
},
width = 400,
height = 200;
var parseTime = d3.timeParse("%m/%y");
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
var data = json;
// format the data
data.forEach(function(d) {
d.date = parseTime(d.date);
d.numTags = +d.numTags;
});
// set the ranges
var xScale = d3.scaleTime()
.range([0, width])
.domain(d3.extent(data, function(d) {
return d.date;
}))
.nice();
if (xScale.domain()[0].getTime() == xScale.domain()[1].getTime()) {
var dateLess = d3.timeDay.offset(xScale.domain()[0], -1);
var dateMore = d3.timeDay.offset(xScale.domain()[0], 1);
xScale.domain([dateLess, dateMore])
}
var yScale = d3.scaleLinear()
.range([height, 0])
.domain([0, d3.max(data, function(d) {
return Math.max(d.numTags);
})])
.nice();
// define the 1st line
var tagLine = d3.line()
.x(function(d) {
return xScale(d.date);
})
.y(function(d) {
return yScale(d.numTags);
});
// Axes
var xAxis = d3.axisBottom()
.scale(xScale)
.ticks(json.length)
.tickSizeOuter(0)
.tickFormat(d3.timeFormat('%B %Y'));
var yAxis = d3.axisLeft().scale(yScale);
svg.append("path")
.data([data])
.attr("class", "line")
.style("stroke", "blue")
.attr("d", tagLine);
var points = svg.selectAll(".point")
.data(data)
.enter().append("svg:circle")
.attr("stroke", "green")
.attr("fill", function(d, i) {
return "blue"
})
.attr("cx", function(d, i) {
return xScale(d.date)
})
.attr("cy", function(d, i) {
return yScale(d.numTags)
})
.attr("r", function(d, i) {
return 10
});
// Add the X Axis
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.style("font-size", "14px");;
// Add the Y Axis
svg.append("g")
.call(yAxis);
<script src="https://d3js.org/d3.v4.js"></script>
EDIT: As you asked in your edit, when you have two data values my solution will create additional ticks on the limits, which is the expected behaviour:
var json = [{
date: "05/17",
numTags: 23
}, {
date: "05/17",
numTags: 17
}];
var margin = {
top: 20,
right: 30,
bottom: 30,
left: 40
},
width = 400,
height = 200;
var parseTime = d3.timeParse("%m/%y");
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
var data = json;
// format the data
data.forEach(function(d) {
d.date = parseTime(d.date);
d.numTags = +d.numTags;
});
// set the ranges
var xScale = d3.scaleTime()
.range([0, width])
.domain(d3.extent(data, function(d) {
return d.date;
}))
.nice();
if (xScale.domain()[0].getTime() == xScale.domain()[1].getTime()) {
var dateLess = d3.timeDay.offset(xScale.domain()[0], -1);
var dateMore = d3.timeDay.offset(xScale.domain()[0], 1);
xScale.domain([dateLess, dateMore])
}
var yScale = d3.scaleLinear()
.range([height, 0])
.domain([0, d3.max(data, function(d) {
return Math.max(d.numTags);
})])
.nice();
// define the 1st line
var tagLine = d3.line()
.x(function(d) {
return xScale(d.date);
})
.y(function(d) {
return yScale(d.numTags);
});
// Axes
var xAxis = d3.axisBottom()
.scale(xScale)
.ticks(json.length)
.tickSizeOuter(0)
.tickFormat(d3.timeFormat('%B %Y'));
var yAxis = d3.axisLeft().scale(yScale);
svg.append("path")
.data([data])
.attr("class", "line")
.style("stroke", "blue")
.attr("d", tagLine);
var points = svg.selectAll(".point")
.data(data)
.enter().append("svg:circle")
.attr("stroke", "green")
.attr("fill", function(d, i) {
return "blue"
})
.attr("cx", function(d, i) {
return xScale(d.date)
})
.attr("cy", function(d, i) {
return yScale(d.numTags)
})
.attr("r", function(d, i) {
return 10
});
// Add the X Axis
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.style("font-size", "14px");;
// Add the Y Axis
svg.append("g")
.call(yAxis);
<script src="https://d3js.org/d3.v4.js"></script>
There are several ways for removing those ticks. One of them is using tickValues:
.tickValues(data.map(function(d){ return d.date}))
Here is the demo:
var json = [{
date: "05/17",
numTags: 23
}, {
date: "05/17",
numTags: 17
}];
var margin = {
top: 20,
right: 30,
bottom: 30,
left: 40
},
width = 400,
height = 200;
var parseTime = d3.timeParse("%m/%y");
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
var data = json;
// format the data
data.forEach(function(d) {
d.date = parseTime(d.date);
d.numTags = +d.numTags;
});
// set the ranges
var xScale = d3.scaleTime()
.range([0, width])
.domain(d3.extent(data, function(d) {
return d.date;
}))
.nice();
if (xScale.domain()[0].getTime() == xScale.domain()[1].getTime()) {
var dateLess = d3.timeDay.offset(xScale.domain()[0], -1);
var dateMore = d3.timeDay.offset(xScale.domain()[0], 1);
xScale.domain([dateLess, dateMore])
}
var yScale = d3.scaleLinear()
.range([height, 0])
.domain([0, d3.max(data, function(d) {
return Math.max(d.numTags);
})])
.nice();
// define the 1st line
var tagLine = d3.line()
.x(function(d) {
return xScale(d.date);
})
.y(function(d) {
return yScale(d.numTags);
});
// Axes
var xAxis = d3.axisBottom()
.scale(xScale)
.ticks(json.length)
.tickSizeOuter(0)
.tickValues(data.map(function(d){ return d.date}))
.tickFormat(d3.timeFormat('%B %Y'));
var yAxis = d3.axisLeft().scale(yScale);
svg.append("path")
.data([data])
.attr("class", "line")
.style("stroke", "blue")
.attr("d", tagLine);
var points = svg.selectAll(".point")
.data(data)
.enter().append("svg:circle")
.attr("stroke", "green")
.attr("fill", function(d, i) {
return "blue"
})
.attr("cx", function(d, i) {
return xScale(d.date)
})
.attr("cy", function(d, i) {
return yScale(d.numTags)
})
.attr("r", function(d, i) {
return 10
});
// Add the X Axis
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.style("font-size", "14px");;
// Add the Y Axis
svg.append("g")
.call(yAxis);
<script src="https://d3js.org/d3.v4.js"></script>

Trying to transition d3v4 linegraph data from one dataset to a dataset with a different scale

I have a line graph which looks fine. The data set is mostly random data with one big spike. The second dataset is just the log2 of each value. I can transition between the two and it looks great. The y axis is transitioning too. But the scale of the line is not transitioning. Not sure how to get the line to update with the right scale.
$(function() {
var margin = { top: 300, right: 100, bottom: 100, left: 100 },
width = 1400 - margin.right - margin.left,
height = 1080 - margin.top - margin.bottom;
var parseTime = d3.timeParse("%H:%M:%S");
var x = d3.scaleTime().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
var log2y = d3.scaleLinear().range([height, 0]);
var valueline = d3.line()
.curve(d3.curveMonotoneX)
.x(function (d) { return x(d.date); })
.y(function (d) { return y(d.value); });
var log2valueline = d3.line()
.x(function (d) { return x(d.date); })
.y(function (d) { return y(Math.log2(d.value)); });
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.select("body").attr("align", "center");
d3.csv("data.csv", function (error, data) {
if (error) {
throw error;
}
var log2data = [];
var log10data = [];
data.forEach(function (d, index) {
d.date = parseTime(d.date);
d.value = +d.value;
log2data[index] = { "date": d.date, "value": Math.log2(+d.value) };
log10data[index] = { "date": d.date, "value": Math.log10(+d.value) };
});
x.domain(d3.extent(data, function (d) { return d.date; }));
y.domain([0, d3.max(data, function (d) { return d.value; })]);
log2y.domain([0, d3.max(log2data, function (d) { return d.value; }) ]);
svg.append("path")
.data([data])
.attr("class", "line")
.attr("d", valueline);
svg.append("g")
.attr("class", "xaxis")
.attr("class", "chart")
.attr("transform", "translate(0, " + height + ")")
.call(d3.axisBottom(x).ticks(d3.timeMinute.every(10)));
var yaxis = svg.append("g")
.attr("class", "yaxis")
.attr("class", "chart")
.call(d3.axisLeft(y).ticks(5));
yaxis
.transition().duration(500).delay(2000)
.call(d3.axisLeft(log2y).ticks(4));
svg.selectAll("path")
.data([log2data])
.transition().duration(500).delay(2000)
.attr("d", valueline);
});
});
So here is the chart before the transition:
And here is the chart after the transition:
Adding the update from Harpal, I see this:
It's because the new line is still using the old scale (valueline)
Change this:
svg.selectAll("path")
.data([log2data])
.transition().duration(500).delay(2000)
.attr("d", valueline);
to this:
svg.selectAll("path")
.data([log2data])
.transition().duration(500).delay(2000)
.attr("d", log2valueline);
So the solution, as with so many things, is to go for a walk, get something to eat, and return with a fresh look. I rewrote it, based on what I learned thus far and have a working solution:
$(function () {
var margin = { top: 300, right: 100, bottom: 100, left: 100 },
winwidth = $(window).width(),
winheight = $(window).height(),
width = winwidth - margin.right - margin.left,
height = winheight - margin.top - margin.bottom,
x = d3.scaleTime().range([0, width]),
y = d3.scaleLinear().range([height, 0]);
var parseTime = d3.timeParse("%H:%M:%S");
var valueline = d3.line()
.curve(d3.curveMonotoneX)
.x(function (d) { return x(d.date); })
.y(function (d) { return y(d.value); });
var svg = d3.select('body').append('svg')
.attr("width", winwidth)
.attr("height", winheight)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var path = svg.append("path");
var xaxis = svg.append("g")
.attr("class", "xaxis")
.attr("class", "chart")
.attr("transform", "translate(0, " + height + ")");
var yaxis = svg.append("g")
.attr("class", "yaxis")
.attr("class", "chart");
function step1(sourcedata) {
var data = sourcedata.data;
var y = d3.scaleLinear().range([height, 0]);
y.domain([0, d3.max(data, function (d) { return Math.log2(d.value); })]);
valueline = d3.line()
.curve(d3.curveMonotoneX)
.x(function (d) { return x(d.date); })
.y(function (d) { return y(Math.log2(d.value)); });
path
.data([data])
.transition()
.attr("class", "line")
.attr("d", valueline);
yaxis
.transition()
.call(d3.axisLeft(y).ticks(4));
}
d3.select("body").attr("align", "center");
d3.csv("data.csv", function (error, data) {
if (error) {
throw error;
}
data.forEach(function (d) {
d.date = parseTime(d.date);
d.value = +d.value;
});
x.domain(d3.extent(data, function (d) { return d.date; }));
y.domain([0, d3.max(data, function (d) { return d.value; })]);
path
.data([data])
.attr("class", "line")
.attr("d", valueline);
xaxis
.attr("transform", "translate(0, " + height + ")")
.call(d3.axisBottom(x).ticks(d3.timeMinute.every(10)));
yaxis.call(d3.axisLeft(y).ticks(5));
$("#rescale").click(data, step1);
});
});
And the graph works as follows:

Create Paired Bar Chart d3.js

I need help adding a second series of data to my bar chart. Currently I'm populating the bars from the glob key in my dataset. This will be the first series. The second series I would like to be is local.
How do I go about adding that?
Play with my JSFiddle here.
var w = 300;
var h = 200;
var colors = ["#377EB8", "#4DAF4A"];
var dataset = [
{"keyword": "payday loans", "glob": 1500000, "local": 673000, "cpc": "14.11"},
{"keyword": "title loans", "glob": 165000, "local": 165000, "cpc": "12.53" },
{"keyword": "personal loans", "glob": 550000, "local": 301000, "cpc": "6.14"}
];
var data = [[1500000, 165000, 550000],
[673000, 165000, 301000]];
var tdata = d3.transpose(dataset.glob, dataset.local);
var series = 2; // Global & Local
var x0Scale = d3.scale.ordinal()
.domain(d3.range(dataset.length))
.rangeRoundBands([0, w], 0.05);
var x1Scale = d3.scale.ordinal()
.domain(d3.range(dataset.length))
.rangeRoundBands([0, w], 0.05);
var yScale = d3.scale.linear()
.domain([0, d3.max(dataset, function(d) {return d.glob;})])
.range([0, h]);
var glob = function(d) {
return d.glob;
};
var cpc = function(d) {
return d.cpc;
};
var commaFormat = d3.format(',');
//SVG element
var svg = d3.select("#searchVolume")
.append("svg")
.attr("width", w)
.attr("height", h);
// Graph Bars
svg.selectAll("rect")
.data(dataset, cpc, glob)
.enter()
.append("rect")
.attr("x", function(d, i){
return x0Scale(i);
})
.attr("y", function(d) {
return h - yScale(d.glob);
})
.attr("width", x0Scale.rangeBand())
.attr("height", function(d) {
return yScale(d.glob);
})
.attr("fill", colors[1])
.on("mouseover", function(d) {
//Get this bar's x/y values, then augment for the tooltip
var xPosition = parseFloat(d3.select(this).attr("x")) + x0Scale.rangeBand() / 3;
var yPosition = parseFloat(d3.select(this).attr("y")) + yScale;
//Update Tooltip Position & value
d3.select("#tooltip")
.style("left", xPosition + "px")
.style("top", yPosition + "px")
.select("#cpcVal")
.text(d.cpc);
d3.select("#tooltip").classed("hidden", false);
})
.on("mouseout", function() {
//Remove the tooltip
d3.select("#tooltip").classed("hidden", true);
});
//Create labels
svg.selectAll("text")
.data(dataset, glob)
.enter()
.append("text")
.text(function(d) {
return commaFormat(d.glob);
})
.attr("text-anchor", "middle")
.attr("x", function(d, i) {
return x0Scale(i) + x0Scale.rangeBand() / 2;
})
.attr("y", function(d) {
return h - yScale(d.glob) + 14;
})
.attr("font-family", "sans-serif")
.attr("font-size", "11px")
.attr("fill", "white");
It's easiest to create an svg group (<g>) element for each set of data, and then add the individual parts to each group. For example, roughly:
http://jsfiddle.net/WXMwv/1/
var sets = svg.selectAll(".set")
.data(dataset)
.enter().append("g")
.attr("class","set")
.attr("transform",function(d,i){
return "translate(" + x0Scale(i) + ",0)"
})
.on("mouseover", function(d,i) {
//Create x value from data index
var xPosition = parseFloat(x0Scale(i) + x0Scale.rangeBand() / 6);
var yPosition = 0;
//Update Tooltip Position & value
d3.select("#tooltip")
.style("left", xPosition + "px")
.style("top", yPosition + "px")
.select("#cpcVal")
.text(d.cpc);
d3.select("#tooltip").classed("hidden", false);
})
.on("mouseout", function() {
//Remove the tooltip
d3.select("#tooltip").classed("hidden", true);
});
sets.append("rect")
.attr("class","glob")
.attr("width", x0Scale.rangeBand()/2)
.attr("y", function(d) {
return yScale(d.glob);
})
.attr("height", function(d){
return h - yScale(d.glob);
})
.attr("fill", colors[1])
sets.append("rect")
.attr("class","local")
.attr("width", x0Scale.rangeBand()/2)
.attr("y", function(d) {
return yScale(d.local);
})
.attr("x", x0Scale.rangeBand()/2)
.attr("height", function(d){
return h - yScale(d.local);
})
.attr("fill", colors[0])
The text elements are left as an exercise for the reader :)

Resources