Creating an average Y line for D3 visualization? - javascript

I am trying to create a visualization to help students that I work with learn about measures of fit, such as r-squared. For R-squared, I want to have both the regression line and a line for the mean of Y on my graph. (My end goal is to have lines between the points and/or lines to represent ESS, TSS, and SSR that students can click to see or not see).
My regression line is working fine, but when I try to add in my average line, it ends up with a strange start and end point and is noticeably NOT a flat line at the average (4.4). I also get the following error in my console:
Error: Invalid value for <path> attribute d="M112,235.71428571428572L194,119.14285714285712L276,NaNL358,NaNL440,NaN"
which corresponds to the line of code:
.attr({
within my avg.append("path") for my avgline (at least, I think that's why I'm specifying there):
svg.append("path")
.datum(avgdataset)
.attr({
d: avgline,
stroke: "green",
"stroke-width": 1,
fill: "none",
"stroke-dasharray": "5,5",
});
I've tried playing around with how avgline is specified to no end (this playing around normally ends up producing no line at all). I've also tried using data instead of datum, to no avail. I'm likely making a really basic mistake, since I'm new to javascript and D3.
Here's all of my code thus far, to put it in context:
//Width and height
var w = 500;
var h = 300;
var padding = 30;
var dataset = [
[1, 1],
[2, 5],
[3, 4],
[4, 7],
[5, 5]
];
//Create scale functions
var xScale = d3.scale.linear()
.domain([0, d3.max(dataset, function(d) {
return d[0];
})])
.range([padding, w - padding * 2]);
var yScale = d3.scale.linear()
.domain([0, d3.max(dataset, function(d) {
return d[1];
})])
.range([h - padding, padding]);
var rScale = d3.scale.linear()
.domain([0, d3.max(dataset, function(d) {
return d[1];
})])
.range([2, 5]);
//Define X axis
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.ticks(5);
//Define Y axis
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left")
.ticks(5);
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
//Create circles
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", function(d) {
return xScale(d[0]);
})
.attr("cy", function(d) {
return yScale(d[1]);
})
.attr("r", 4)
.append("svg:title")
.text(function(d) {
return d[0] + "," + d[1];
});;
//average stuff
var sum = 0,
average;
for (var i = 0; i < dataset.length; i++) {
sum += dataset[i][1];
}
average = sum / dataset.length;
console.log(average);
var avgdataset = [
[1, average],
[2, average],
[3, average],
[4, average],
[5, average]
];
console.log(avgdataset);
document.write(avgdataset);
//Create labels
/*svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.text(function(d) {
return d[0] + "," + d[1];
})
.attr("x", function(d) {
return xScale(d[0]);
})
.attr("y", function(d) {
return yScale(d[1]);
})
.attr("font-family", "sans-serif")
.attr("font-size", "11px")
.attr("fill", "red");
*/
//Create X axis
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + (h - padding) + ")")
.call(xAxis);
//Create Y axis
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(" + padding + ",0)")
.call(yAxis);
var lr = ss.linear_regression().data(dataset).line();
var forecast_x = 20
console.log(lr)
var lrline = d3.svg.line()
.x(function(d, i) {
return xScale(i);
})
.y(function(d, i) {
return yScale(lr(i));
});
svg.append("path")
.datum(Array(dataset.length * forecast_x))
.attr({
d: lrline,
stroke: "black",
"stroke-width": 1,
fill: "none",
"stroke-dasharray": "5,5",
});
var avgline = d3.svg.line()
//.x(function(d, i) { return xScale(i); })
//.y(function(d, i) { return yScale(avgdataset(i)); });
.x(function(d, i) {
return xScale(d[0]);
})
.y(function(d, i) {
return yScale(d[i]);
});
svg.append("path")
.datum(avgdataset)
.attr({
d: avgline,
stroke: "green",
"stroke-width": 1,
fill: "none",
"stroke-dasharray": "5,5",
});
//to get the m and b for the equation line
var mvalue = ss.linear_regression().data(dataset).m();
console.log(mvalue);
var bvalue = ss.linear_regression().data(dataset).b();
console.log(bvalue);
//equation written out
svg.append("text")
.text("Y= " + mvalue + "x + " + bvalue)
.attr("class", "text-label")
.attr("x", 60)
.attr("y", 30);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://raw.github.com/tmcw/simple-statistics/master/src/simple_statistics.js"></script>

Similar to Kaiido, it looks to me that the avgline function was the issue. You were passing in an array of arrays and the x and y weren't accessing the correct part of the array. Most of the examples I've worked with pass an array of objects, so something like:
var data = [ {x: 1, y: 4.4}, {x:2, y:4.4}, etc];
If you construct an object like this you can simple pass this to the avgline which can then elegantly access the correct parts of the data with something like:
var avgline = d3.svg.line() //changed x and y function to reflect changed data
.x(function(d, i) {
return xScale(d.x);
})
.y(function(d, i) {
return yScale(d.y);
});
There are a number of advantages of this. For instance you could ensure that all your data corresponds to this structure and then you would only need one line constructor instead of two.

I think you almost got it, except that avgdataset is not a function but an array.
Simply replace
var avgline = d3.svg.line()
//.x(function(d, i) { return xScale(i); })
//.y(function(d, i) { return yScale(avgdataset(i)); });
.x(function(d, i) {
return xScale(d[0]);
})
.y(function(d, i) {
return yScale(d[i]);
});
with
var avgline = d3.svg.line()
.x(function(d, i) { return xScale(i); })
.y(function(d, i) { return yScale(avgdataset[i][1]); });
//Width and height
var w = 500;
var h = 300;
var padding = 30;
var dataset = [[1, 1], [2, 5], [3, 4], [4, 7], [5, 5]];
//Create scale functions
var xScale = d3.scale.linear()
.domain([0, d3.max(dataset, function(d) { return d[0]; })])
.range([padding, w - padding * 2]);
var yScale = d3.scale.linear()
.domain([0, d3.max(dataset, function(d) { return d[1]; })])
.range([h - padding, padding]);
var rScale = d3.scale.linear()
.domain([0, d3.max(dataset, function(d) { return d[1]; })])
.range([2, 5]);
//Define X axis
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.ticks(5);
//Define Y axis
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left")
.ticks(5);
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
//Create circles
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", function(d) {
return xScale(d[0]);
})
.attr("cy", function(d) {
return yScale(d[1]);
})
.attr("r", 4
)
.append("svg:title")
.text(function(d){return d[0] + "," + d[1];});;
//average stuff
var sum = 0, average;
for (var i = 0; i < dataset.length; i++) {
sum += dataset[i][1];
}
average = sum / dataset.length;
console.log(average);
var avgdataset = [[1, average], [2, average], [3, average], [4, average], [5, average]];
console.log(avgdataset);
document.write(avgdataset);
//Create labels
/*svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.text(function(d) {
return d[0] + "," + d[1];
})
.attr("x", function(d) {
return xScale(d[0]);
})
.attr("y", function(d) {
return yScale(d[1]);
})
.attr("font-family", "sans-serif")
.attr("font-size", "11px")
.attr("fill", "red");
*/
//Create X axis
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + (h - padding) + ")")
.call(xAxis);
//Create Y axis
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(" + padding + ",0)")
.call(yAxis);
var lr = ss.linear_regression().data(dataset).line();
var forecast_x = 20
console.log(lr)
var lrline = d3.svg.line()
.x(function(d, i) { return xScale(i); })
.y(function(d, i) { return yScale(lr(i)); });
svg.append("path")
.datum(Array(dataset.length*forecast_x))
.attr({
d: lrline,
stroke: "black",
"stroke-width": 1,
fill: "none",
"stroke-dasharray": "5,5",
});
var avgline = d3.svg.line()
.x(function(d, i) { return xScale(i); })
.y(function(d, i) { return yScale(avgdataset[i][1]); });
svg.append("path")
.datum(avgdataset)
.attr({
d: avgline,
stroke: "green",
"stroke-width": 1,
fill: "none",
"stroke-dasharray": "5,5",
});
//to get the m and b for the equation line
var mvalue = ss.linear_regression().data(dataset).m();
console.log(mvalue);
var bvalue = ss.linear_regression().data(dataset).b();
console.log(bvalue);
//equation written out
svg.append("text")
.text("Y= " + mvalue + "x + " + bvalue)
.attr("class", "text-label")
.attr("x", 60)
.attr("y", 30);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://raw.github.com/tmcw/simple-statistics/master/src/simple_statistics.js"></script>

Related

How to plot data points on multi-line chart with multiple y axes

I am trying to add data points to my line chart with multiple y axes. Click here for my fiddle.
//after restructuring dataset array
var data = [{
data: [{
x: 0,
y: 0
}, {
x: 10,
y: 10
}, {
x: 20,
y: 20
}, {
x: 30,
y: 30
}, {
x: 40,
y: 40
}],
yAxis: 0,
}, {
data: [{
x: 0,
y: 0
}, {
x: 10,
y: 200
}, {
x: 20,
y: 300
}, {
x: 30,
y: 400
}, {
x: 40,
y: 500
}],
yAxis: 1,
}];
const margin = {
left: 20,
right: 20,
top: 20,
bottom: 80
};
const svg = d3.select('svg');
svg.selectAll("*").remove();
const width = 200 - margin.left - margin.right;
const height = 200 - margin.top - margin.bottom;
const g = svg.append('g').attr('transform', `translate(${80},${margin.top})`);
//************* Axes and Gridlines ***************
const xAxisG = g.append('g');
const yAxisG = g.append('g');
xAxisG.append('text')
.attr('class', 'axis-label')
.attr('x', width / 3)
.attr('y', -10)
.style('fill', 'black')
.text(function(d) {
return "X Axis";
});
yAxisG.append('text')
.attr('class', 'axis-label')
.attr('id', 'yAxisLabel0')
.attr('x', -height / 2)
.attr('y', -15)
.attr('transform', `rotate(-90)`)
.style('text-anchor', 'middle')
.style('fill', 'black')
.text(function(d) {
return "Y Axis 1";
});
// interpolator for X axis -- inner plot region
var x = d3.scaleLinear()
.domain([0, d3.max(xValueArray)])
.range([0, width])
.nice();
var yScale = new Array();
for (var i = 0; i < 2; i++) {
// interpolator for Y axis -- inner plot region
var y = d3.scaleLinear()
.domain([0, d3.max(arr[i])])
.range([0, height])
.nice();
yScale.push(y);
}
const xAxis = d3.axisTop()
.scale(x)
.ticks(5)
.tickPadding(2)
.tickSize(-height)
const yAxis = d3.axisLeft()
.scale(yScale[0])
.ticks(5)
.tickPadding(2)
.tickSize(-width);
yAxisArray = new Array();
yAxisArray.push(yAxis);
for (var i = 1; i < 2; i++) {
var yAxisSecondary = d3.axisLeft()
.scale(yScale[i])
.ticks(5)
yAxisArray.push(yAxisSecondary);
}
svg.append("g")
.attr("class", "x axis")
.attr("transform", `translate(80,${height-80})`)
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.attr("id", "ySecAxis0")
.attr("transform", "translate(80,20)")
.call(yAxis);
var translation = 50;
var textTranslation = 0;
var yLabelArray = ["Y Axis 1", "Y Axis 2"];
//loop starts from 1 as primary y axis is already plotted
for (var i = 1; i < 2; i++) {
svg.append("g")
.attr("transform", "translate(" + translation + "," + 20 + ")")
.attr("id", "ySecAxis" + i)
.call(yAxisArray[i]);
yAxisG.append('text')
.attr('x', -height / 2)
.attr('y', -60)
.attr('transform', `rotate(-90)`)
.attr("id", "yAxisLabel" + i)
.style('text-anchor', 'middle')
.style('fill', 'black')
.text(yLabelArray[i]);
translation -= 40;
textTranslation += 40;
}
//************* Lines and Data Points ***************
var colors = ["blue", "red"];
var thisScale;
var line = d3.line()
.x(d => x(d.x))
.y(d => thisScale(d.y))
.curve(d3.curveLinear);
var paths = g.selectAll("foo")
.data(data)
.enter()
.append("path");
paths.attr("stroke", function (d,i){return colors[i]})
.attr("d", d => {
thisScale = yScale[d.yAxis]
return line(d.data);
})
.attr("stroke-width", 2)
.attr("id", function (d,i){return "line" + i})
.attr("fill", "none");
var points = g.selectAll("dot")
.data(data)
.enter()
.append("circle");
points.attr("cx", function(d) { return x(d.x)} )
.attr("cy", function(d,i) { return yScale[i](d.y); } )
.attr("r", 3)
.attr("class", function (d,i){return "blackDot" + i})
.attr("clip-path", "url(#clip)")
Right now the console log is showing these errors: Error: attribute cx: Expected length, "NaN". Error: attribute cy: Expected length, "NaN". It seems like I am not attributing the correct cx and cy to points, but I can't figure out what I am doing wrongly. Any help is greatly appreciated!
Your data structure is an array of objects, each one containing an inner array with the real coordinates for the circles. Therefore, that single enter selection will not work.
With minimal refactoring, my solution here is appending groups according to the objects, and then, for each one, appending circles according to the inner arrays. For that cumbersome yScale to work you cannot rely on the circle's indices anymore, so I'm using a local variable here:
var pointsGroup = g.selectAll(null)
.data(data)
.enter()
.append("g")
.attr("fill", function(d, i) {
local.set(this, yScale[i])
return colors[i];
});
var points = pointsGroup.selectAll(null)
.data(function(d) {
return d.data
})
.enter()
.append("circle")
.attr("cx", function(d) {
return x(d.x)
})
.attr("cy", function(d, i) {
return local.get(this)(d.y);
})
//etc...
Here is the code with those changes:
var local = d3.local();
var xValueArray = [0, 10, 20, 30, 40];
var arr = [
[0, 10, 20, 30, 40],
[0, 200, 300, 400, 500]
];
var dataset = [
[{
x: 0,
y: 0
}, {
x: 10,
y: 10
}, {
x: 20,
y: 20
}, {
x: 30,
y: 30
}, {
x: 40,
y: 40
}],
[{
x: 0,
y: 0
}, {
x: 10,
y: 200
}, {
x: 20,
y: 300
}, {
x: 30,
y: 400
}, {
x: 40,
y: 500
}]
];
var data = [];
for (var i = 0; i < 2; i++) {
data.push({
"data": dataset[i],
"yAxis": i
})
}
console.log(data);
//after restructuring dataset array
var data = [{
data: [{
x: 0,
y: 0
}, {
x: 10,
y: 10
}, {
x: 20,
y: 20
}, {
x: 30,
y: 30
}, {
x: 40,
y: 40
}],
yAxis: 0,
}, {
data: [{
x: 0,
y: 0
}, {
x: 10,
y: 200
}, {
x: 20,
y: 300
}, {
x: 30,
y: 400
}, {
x: 40,
y: 500
}],
yAxis: 1,
}];
const margin = {
left: 20,
right: 20,
top: 20,
bottom: 80
};
const svg = d3.select('svg');
svg.selectAll("*").remove();
const width = 200 - margin.left - margin.right;
const height = 200 - margin.top - margin.bottom;
const g = svg.append('g').attr('transform', `translate(${80},${margin.top})`);
//************* Axes and Gridlines ***************
const xAxisG = g.append('g');
const yAxisG = g.append('g');
xAxisG.append('text')
.attr('class', 'axis-label')
.attr('x', width / 3)
.attr('y', -10)
.style('fill', 'black')
.text(function(d) {
return "X Axis";
});
yAxisG.append('text')
.attr('class', 'axis-label')
.attr('id', 'yAxisLabel0')
.attr('x', -height / 2)
.attr('y', -15)
.attr('transform', `rotate(-90)`)
.style('text-anchor', 'middle')
.style('fill', 'black')
.text(function(d) {
return "Y Axis 1";
});
// interpolator for X axis -- inner plot region
var x = d3.scaleLinear()
.domain([0, d3.max(xValueArray)])
.range([0, width])
.nice();
var yScale = new Array();
for (var i = 0; i < 2; i++) {
// interpolator for Y axis -- inner plot region
var y = d3.scaleLinear()
.domain([0, d3.max(arr[i])])
.range([0, height])
.nice();
yScale.push(y);
}
const xAxis = d3.axisTop()
.scale(x)
.ticks(5)
.tickPadding(2)
.tickSize(-height)
const yAxis = d3.axisLeft()
.scale(yScale[0])
.ticks(5)
.tickPadding(2)
.tickSize(-width);
yAxisArray = new Array();
yAxisArray.push(yAxis);
for (var i = 1; i < 2; i++) {
var yAxisSecondary = d3.axisLeft()
.scale(yScale[i])
.ticks(5)
yAxisArray.push(yAxisSecondary);
}
svg.append("g")
.attr("class", "x axis")
.attr("transform", `translate(80,${height-80})`)
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.attr("id", "ySecAxis0")
.attr("transform", "translate(80,20)")
.call(yAxis);
var translation = 50;
var textTranslation = 0;
var yLabelArray = ["Y Axis 1", "Y Axis 2"];
//loop starts from 1 as primary y axis is already plotted
for (var i = 1; i < 2; i++) {
svg.append("g")
.attr("transform", "translate(" + translation + "," + 20 + ")")
.attr("id", "ySecAxis" + i)
.call(yAxisArray[i]);
yAxisG.append('text')
.attr('x', -height / 2)
.attr('y', -60)
.attr('transform', `rotate(-90)`)
.attr("id", "yAxisLabel" + i)
.style('text-anchor', 'middle')
.style('fill', 'black')
.text(yLabelArray[i]);
translation -= 40;
textTranslation += 40;
}
//************* Mouseover ***************
var tooltip = d3.select("body")
.append("div")
.style("opacity", 0)
.attr("class", "tooltip")
.style("background-color", "white")
.style("border", "solid")
.style("border-width", "1px")
.style("border-radius", "5px")
.style("padding", "10px")
.style("position", "absolute")
var mouseover = function(d) {
tooltip
.html("x: " + d.x + "<br/>" + "y: " + d.y)
.style("opacity", 1)
.style("left", (d3.mouse(this)[0] + 90) + "px")
.style("top", (d3.mouse(this)[1]) + "px")
}
// A function that change this tooltip when the leaves a point: just need to set opacity to 0 again
var mouseleave = function(d) {
tooltip
.transition()
.duration(200)
.style("opacity", 0)
}
//************* Lines and Data Points ***************
var colors = ["blue", "red"];
var thisScale;
var line = d3.line()
.x(d => x(d.x))
.y(d => thisScale(d.y))
.curve(d3.curveLinear);
var paths = g.selectAll("foo")
.data(data)
.enter()
.append("path");
paths.attr("stroke", function(d, i) {
return colors[i]
})
.attr("d", d => {
thisScale = yScale[d.yAxis]
return line(d.data);
})
.attr("stroke-width", 2)
.attr("id", function(d, i) {
return "line" + i
})
.attr("fill", "none");
var pointsGroup = g.selectAll(null)
.data(data)
.enter()
.append("g")
.attr("fill", function(d, i) {
local.set(this, yScale[i])
return colors[i];
});
var points = pointsGroup.selectAll(null)
.data(function(d) {
return d.data
})
.enter()
.append("circle")
.attr("cx", function(d) {
return x(d.x)
})
.attr("cy", function(d, i) {
return local.get(this)(d.y);
})
.attr("r", 3)
.attr("class", function(d, i) {
return "blackDot" + i
})
.attr("clip-path", "url(#clip)")
.on("mouseover", mouseover)
.on("mouseleave", mouseleave)
//plot lines (hard-coding)
/*var lineFunction1 = d3.line()
.x(function(d) {
return x(d.x);
})
.y(function(d) {
return yScale[0](d.y);
})
.curve(d3.curveLinear);
var lineFunction2 = d3.line()
.x(function(d) {
return x(d.x);
})
.y(function(d) {
return yScale[1](d.y);
})
.curve(d3.curveLinear);
var path1 = g.append("path")
.attr("class", "path" + 0)
.attr("id", "line" + 0)
.attr("d", lineFunction1(data[0]))
.attr("stroke", "blue")
.attr("stroke-width", 2)
.attr("fill", "none")
.attr("clip-path", "url(#clip)");
var path2 = g.append("path")
.attr("class", "path" + 1)
.attr("id", "line" + 1)
.attr("d", lineFunction2(data[1]))
.attr("stroke", "red")
.attr("stroke-width", 2)
.attr("fill", "none")
.attr("clip-path", "url(#clip)");*/
//plot lines and points using for loop
/*for (var i = 0; i < 2; i++) {
var lineFunction = d3.line()
.x(function(d) {
return x(d.x);
})
.y(function(d) {
return yScale[i](d.y);
})
.curve(d3.curveLinear);
var paths = g.append("path")
.attr("class", "path" + i)
.attr("id", "line" + i)
.attr("d", lineFunction(data[i]))
.attr("stroke", colors[i])
.attr("stroke-width", 2)
.attr("fill", "none")
.attr("clip-path", "url(#clip)")
//plot a circle at each data point
g.selectAll(".dot")
.data(data[i])
.enter().append("circle")
.attr("cx", function(d) { return x(d.x)} )
.attr("cy", function(d) { return yScale[i](d.y); } )
.attr("r", 3)
.attr("class", "blackDot" + i)
.attr("clip-path", "url(#clip)")
.on("mouseover", mouseover)
.on("mouseleave", mouseleave)
}*/
//************* Legend ***************
var legend = svg.selectAll(".legend")
.data(data)
.enter().append("g")
legend.append("rect")
.attr("x", width + 65)
.attr("y", function(d, i) {
return 30 + i * 20;
})
.attr("width", 18)
.attr("height", 4)
.style("fill", function(d, i) {
return colors[i];
})
legend.append("text")
.attr("x", width + 60)
.attr("y", function(d, i) {
return 30 + i * 20;
})
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d, i) {
return "Value" + (i + 1);
})
.on("click", function(d, i) {
// Determine if current line is visible
let opacity = d3.select("#line" + i).style("opacity");
let newOpacity;
if (opacity == 0) {
newOpacity = 1;
} else {
newOpacity = 0
}
d3.select("#line" + i).style("opacity", newOpacity);
d3.selectAll(".blackDot" + i).style("opacity", newOpacity);
d3.select("#ySecAxis" + i).style("opacity", newOpacity);
d3.select("#yAxisLabel" + i).style("opacity", newOpacity);
});
//************* Zoom & Brush***************
const margin2 = {
left: 80,
right: 0,
top: 80,
bottom: 0
};
const height2 = height - margin2.top - margin2.bottom;
var xZoom = d3.scaleLinear().range([0, width]);
var yZoom = d3.scaleLinear().range([height2, 0]);
var xAxis2 = d3.axisTop(xZoom);
var brush = d3.brushX()
.extent([
[0, 0],
[width, height2]
])
.on("brush end", brushed);
var zoom = d3.zoom()
.scaleExtent([1, Infinity])
.translateExtent([
[0, 0],
[width, height]
])
.extent([
[0, 0],
[width, height]
])
.on("zoom", zoomed);
var clip = svg.append("defs").append("svg:clipPath")
.attr("id", "clip")
.append("svg:rect")
.attr("width", width)
.attr("height", height)
.attr("x", 0)
.attr("y", 0);
xZoom.domain(x.domain());
yZoom.domain(y.domain());
var context = svg.append("g")
.attr("class", "context")
.attr("transform", "translate(" + margin2.left + "," + 125 + ")");
context.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height2 + ")")
.call(xAxis2);
context.append("g")
.attr("class", "brush")
.call(brush)
.call(brush.move, x.range());
function brushed() {
if (d3.event.sourceEvent && d3.event.sourceEvent.type === "zoom") return;
var s = d3.event.selection || xZoom.range();
x.domain(s.map(xZoom.invert, xZoom));
svg.select(".x.axis").call(xAxis);
//svg.select(".path0").attr("d", lineFunction1(data[0]));
//svg.select(".path1").attr("d", lineFunction2(data[1]));
for (var i = 0; i < 2; i++) {
//svg.select(".path" + i).attr("d", lineFunction(data[i]));
g.selectAll(".blackDot" + i)
.attr("cx", function(d) {
return x(d.x);
})
.attr("cy", function(d) {
return yScale[i](d.y);
})
.attr("r", 3)
}
}
function zoomed() {
if (d3.event.sourceEvent && d3.event.sourceEvent.type === "brush") return;
var t = d3.event.transform;
x.domain(t.rescaleX(xZoom).domain());
svg.select(".x.axis").transiton(t).call(xAxis);
//svg.select(".path0").transiton(t).attr("d", lineFunction1(data[0]));
//svg.select(".path1").transiton(t).attr("d", lineFunction2(data[1]));
for (var i = 0; i < 2; i++) {
//svg.select(".path" + i).attr("d", lineFunction(data[i]));
g.selectAll(".blackDot" + i)
.attr("cx", function(d) {
return x(d.x);
})
.attr("cy", function(d) {
return yScale[i](d.y);
})
.attr("r", 3)
}
}
.xy_chart {
position: relative;
left: 70px;
top: 100px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<svg class="xy_chart"></svg>
Pay attention to the fact that one of the circles has an incorrect cy value. So, I'd suggest you to change your y scale approach.

How to zoom in chart correctly with multiple y-axis

I have implemented a zoom function for my chart, where one can zoom into a certain area of the chart by dragging the area and releasing it. My fiddle is accessible here.
The zoom function is working correctly for the blue line as both the blue line and the left axis is being updated. However, the right axis is not being updated while the red line is zoomed in. I have hardcoded a domain from 0 to 200 for the right axis so whenever I zoom in the domain goes from 0 to 200 instead of the correct zoomed in domain. What should be the code for the domain for the axis on the right so that it gets updated during the zoom? Any help is greatly appreciated!
var data = [ {x: 0, y: 0, y1: 0}, {x: 1, y: 30, y1: 100}, {x: 2, y: 40, y1: 200},
{x: 3, y: 60, y1: 300}, {x: 4, y: 70, y1: 400}, {x: 5, y: 90, y1: 500} ];
const margin = {
left: 20,
right: 20,
top: 20,
bottom: 80
};
const svg = d3.select('svg');
svg.selectAll("*").remove();
const width = 200 - margin.left - margin.right;
const height = 200 - margin.top - margin.bottom;
const g = svg.append('g').attr('transform', `translate(${margin.left},${margin.top})`);
var x = d3.scaleLinear()
.domain([0, d3.max(data, function(d){ return d.x; })])
.range([0,width])
.nice();
var y = d3.scaleLinear()
.domain([0, d3.max(data, function(d){ return d.y; })])
.range([0,height])
.nice();
var y1 = d3.scaleLinear()
.domain([0, d3.max(data, function(d) { return d.y1; })])
.range([0, height])
.nice();
const xAxis = d3.axisTop()
.scale(x)
.ticks(5)
.tickPadding(3)
.tickSize(-height)
const yAxis = d3.axisLeft()
.scale(y)
.ticks(5)
.tickPadding(3)
.tickSize(-width);
const yAxis1 = d3.axisRight()
.scale(y1)
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(20,20)")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(20,20)")
.call(yAxis);
svg.append("g")
.attr("class", "y1 axis")
.attr("transform", "translate(185,20)")
.call(yAxis1);
var lineFunction = d3.line()
.x(function(d) {return x(d.x); })
.y(function(d) {return y(d.y); })
.curve(d3.curveLinear);
var lineFunctionOne = d3.line()
.x(function(d) {return x(d.x); })
.y(function(d) {return y1(d.y1); })
.curve(d3.curveLinear);
//defining and plotting the lines
var path = g.append("path")
.attr("class", "path1")
.attr("id", "blueLine")
.attr("d", lineFunction(data))
.attr("stroke", "blue")
.attr("stroke-width", 2)
.attr("fill", "none")
.attr("clip-path", "url(#clip)");
var path1 = g.append("path")
.attr("class", "path2")
.attr("id", "redLine")
.attr("d", lineFunctionOne(data))
.attr("stroke", "red")
.attr("stroke-width", 2)
.attr("fill", "none")
.attr("clip-path", "url(#clip)");
//************* Zoom ***************
//add brushing
var brush = d3.brush().extent([[0, 0], [width, height]]).on("end", brushended),
idleTimeout,
idleDelay = 350;
g.append("g")
.attr("class", "brush")
.call(brush);
// Add a clipPath: everything out of this area won't be drawn when chart is zoomed in
var clip = svg.append("defs").append("svg:clipPath")
.attr("id", "clip")
.append("svg:rect")
.attr("width", width)
.attr("height", height)
.attr("x", 0)
.attr("y", 0);
function brushended() {
var s = d3.event.selection;
//If no selection, re-initialize chart on double click. Otherwise, update x-axis and y-axis domain
if (!s) {
// This allows to wait a little bit
if (!idleTimeout) return idleTimeout = setTimeout(idled, 350);
x.domain(d3.extent(data, function (d) { return d.x; })).nice();
y.domain(d3.extent(data, function (d) { return d.y; })).nice();
y1.domain(d3.extent(data, function (d) { return d.y1; })).nice();
} else {
x.domain([s[0][0], s[1][0]].map(x.invert, x));
y.domain([s[0][1], s[1][1]].map(y.invert, y));
y1.domain([0, 200]); //hardcoded domain
//This removes the grey brush area as soon as the selection has been done
g.select(".brush").call(brush.move, null)
}
zoom();
}
function idled() {
idleTimeout = null;
}
function zoom() {
var t = svg.transition().duration(750);
svg.select(".x.axis").transition(t).call(xAxis);
svg.select(".y.axis").transition(t).call(yAxis);
svg.select(".y1.axis").transition(t).call(yAxis1);
svg.select(".path1").transition(t).attr("d", lineFunction(data));
svg.select(".path2").transition(t).attr("d", lineFunctionOne(data));
}
I'm puzzled by your question... all you need to do is the same thing you did just the line above it:
y1.domain([s[0][1], s[1][1]].map(y1.invert, y));
By the way, you don't need the thisArg in the map, it can be just:
y1.domain([s[0][1], s[1][1]].map(y1.invert));
Here is the updated code:
var data = [{
x: 0,
y: 0,
y1: 0
}, {
x: 1,
y: 30,
y1: 100
}, {
x: 2,
y: 40,
y1: 200
},
{
x: 3,
y: 60,
y1: 300
}, {
x: 4,
y: 70,
y1: 400
}, {
x: 5,
y: 90,
y1: 500
}
];
const margin = {
left: 20,
right: 20,
top: 20,
bottom: 80
};
const svg = d3.select('svg');
svg.selectAll("*").remove();
const width = 200 - margin.left - margin.right;
const height = 200 - margin.top - margin.bottom;
const g = svg.append('g').attr('transform', `translate(${margin.left},${margin.top})`);
var x = d3.scaleLinear()
.domain([0, d3.max(data, function(d) {
return d.x;
})])
.range([0, width])
.nice();
var y = d3.scaleLinear()
.domain([0, d3.max(data, function(d) {
return d.y;
})])
.range([0, height])
.nice();
var y1 = d3.scaleLinear()
.domain([0, d3.max(data, function(d) {
return d.y1;
})])
.range([0, height])
.nice();
const xAxis = d3.axisTop()
.scale(x)
.ticks(5)
.tickPadding(3)
.tickSize(-height)
const yAxis = d3.axisLeft()
.scale(y)
.ticks(5)
.tickPadding(3)
.tickSize(-width);
const yAxis1 = d3.axisRight()
.scale(y1)
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(20,20)")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(20,20)")
.call(yAxis);
svg.append("g")
.attr("class", "y1 axis")
.attr("transform", "translate(185,20)")
.call(yAxis1);
var lineFunction = d3.line()
.x(function(d) {
return x(d.x);
})
.y(function(d) {
return y(d.y);
})
.curve(d3.curveLinear);
var lineFunctionOne = d3.line()
.x(function(d) {
return x(d.x);
})
.y(function(d) {
return y1(d.y1);
})
.curve(d3.curveLinear);
//defining the lines
var path = g.append("path")
.attr("class", "path1")
.attr("id", "blueLine")
.attr("d", lineFunction(data))
.attr("stroke", "blue")
.attr("stroke-width", 2)
.attr("fill", "none")
.attr("clip-path", "url(#clip)");
var path1 = g.append("path")
.attr("class", "path2")
.attr("id", "redLine")
.attr("d", lineFunctionOne(data))
.attr("stroke", "red")
.attr("stroke-width", 2)
.attr("fill", "none")
.attr("clip-path", "url(#clip)");
//************* Zoom ***************
//add brushing
var brush = d3.brush().extent([
[0, 0],
[width, height]
]).on("end", brushended),
idleTimeout,
idleDelay = 350;
g.append("g")
.attr("class", "brush")
.call(brush);
// Add a clipPath: everything out of this area won't be drawn when chart is zoomed in
var clip = svg.append("defs").append("svg:clipPath")
.attr("id", "clip")
.append("svg:rect")
.attr("width", width)
.attr("height", height)
.attr("x", 0)
.attr("y", 0);
function brushended() {
var s = d3.event.selection;
//If no selection, re-initialize chart on double click. Otherwise, update x-axis and y-axis domain
if (!s) {
// This allows to wait a little bit
if (!idleTimeout) return idleTimeout = setTimeout(idled, 350);
x.domain(d3.extent(data, function(d) {
return d.x;
})).nice();
y.domain(d3.extent(data, function(d) {
return d.y;
})).nice();
y1.domain(d3.extent(data, function(d) {
return d.y1;
})).nice();
} else {
x.domain([s[0][0], s[1][0]].map(x.invert, x));
y.domain([s[0][1], s[1][1]].map(y.invert, y));
y1.domain([s[0][1], s[1][1]].map(y1.invert, y)); //hardcoded domain
//This removes the grey brush area as soon as the selection has been done
g.select(".brush").call(brush.move, null)
}
zoom();
}
function idled() {
idleTimeout = null;
}
function zoom() {
var t = svg.transition().duration(750);
svg.select(".x.axis").transition(t).call(xAxis);
svg.select(".y.axis").transition(t).call(yAxis);
svg.select(".y1.axis").transition(t).call(yAxis1);
svg.select(".path1").transition(t).attr("d", lineFunction(data));
svg.select(".path2").transition(t).attr("d", lineFunctionOne(data));
}
.xy_chart {
position: relative;
left: 50px
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<svg class="xy_chart"></svg>

Stacked bar chart 24hr # 15 minute data, to only display 2hr ticks

I have been searching for a while about how to handle the X axis in a stacked bar chart (since dataset is a little different from a single bar chart).
Basically, I have data for a 24hr period in 15 minute intervals. However, I only want to display the x-axis in 2hr ticks.
Existing Fiddle: [https://jsfiddle.net/lucksp/crwb4v5u/][1]
It currently prints all the intervals.
I have tried various scale options with time but something doesn't translate with the way I have this setup.
var xScale = d3.scale.ordinal()
.domain(dataset[0].map(function(d) {
return d.x;
}))
.rangeRoundBands([0, width - margin.left]);
var xAxis = d3.svg.axis()
.scale(xScale)
.orient('bottom')
.tickSize(0)
.ticks(12)
.tickFormat(function(d) {
return d;
});
var rect = groups.selectAll('rect')
.data(function(d) {
return d;
})
.enter()
.append('rect')
.attr('class', function(d, i) {
return 'stacks ' + d.type;
})
.classed('stacks', true)
.attr('id', function(d, i) {
return d.type + '_' + i;
})
.attr('x', function(d) {
return xScale(d.x);
})
.attr('y', function(d) {
return yScale(d.y0 + d.y);
})
.attr('height', function(d) {
return yScale(d.y0) - yScale(d.y0 + d.y);
})
.attr('width', xScale.rangeBand());
[1]: https://jsfiddle.net/lucksp/crwb4v5u/
I know it's user error, but after looking at this for the last 2 days, I am resorting to asking this question now. Thanks!
You are currently trying to use .ticks which will only work if the scale you're using has an inbuilt ticks function. Your ordinal scale in this case does not. It will by default use all values in the domain.
To go around it, we can manually set the ticks using xAxis.tickValues(["custom tick values that match domain vals"]). Check the snippet below.
var data = [{"hour":"0:00","inProgress":3,"inQueue":0},{"hour":"0:15","inProgress":5,"inQueue":3},{"hour":"0:30","inProgress":1,"inQueue":1},{"hour":"0:45","inProgress":1,"inQueue":0},{"hour":"1:00","inProgress":2,"inQueue":0},{"hour":"1:15","inProgress":8,"inQueue":2},{"hour":"1:30","inProgress":5,"inQueue":3},{"hour":"1:45","inProgress":5,"inQueue":1},{"hour":"2:00","inProgress":6,"inQueue":0},{"hour":"2:15","inProgress":6,"inQueue":0},{"hour":"2:30","inProgress":7,"inQueue":0},{"hour":"2:45","inProgress":7,"inQueue":0},{"hour":"3:00","inProgress":8,"inQueue":0},{"hour":"3:15","inProgress":8,"inQueue":0},{"hour":"3:30","inProgress":9,"inQueue":1},{"hour":"3:45","inProgress":9,"inQueue":4},{"hour":"4:00","inProgress":10,"inQueue":6},{"hour":"4:15","inProgress":10,"inQueue":2},{"hour":"4:30","inProgress":10,"inQueue":1},{"hour":"4:45","inProgress":11,"inQueue":0},{"hour":"5:00","inProgress":11,"inQueue":0},{"hour":"5:15","inProgress":12,"inQueue":0},{"hour":"5:30","inProgress":12,"inQueue":0},{"hour":"5:45","inProgress":13,"inQueue":0},{"hour":"6:00","inProgress":13,"inQueue":0},{"hour":"6:15","inProgress":14,"inQueue":0},{"hour":"6:30","inProgress":14,"inQueue":0},{"hour":"6:45","inProgress":15,"inQueue":0},{"hour":"7:00","inProgress":15,"inQueue":3},{"hour":"7:15","inProgress":15,"inQueue":1},{"hour":"7:30","inProgress":16,"inQueue":0},{"hour":"7:45","inProgress":16,"inQueue":0},{"hour":"8:00","inProgress":17,"inQueue":2},{"hour":"8:15","inProgress":17,"inQueue":3},{"hour":"8:30","inProgress":18,"inQueue":1},{"hour":"8:45","inProgress":18,"inQueue":0},{"hour":"9:00","inProgress":19,"inQueue":0},{"hour":"9:15","inProgress":19,"inQueue":0},{"hour":"9:30","inProgress":20,"inQueue":0},{"hour":"9:45","inProgress":20,"inQueue":0},{"hour":"10:00","inProgress":20,"inQueue":0},{"hour":"10:15","inProgress":21,"inQueue":1},{"hour":"10:30","inProgress":21,"inQueue":4},{"hour":"10:45","inProgress":22,"inQueue":6},{"hour":"11:00","inProgress":22,"inQueue":2},{"hour":"11:15","inProgress":23,"inQueue":1},{"hour":"11:30","inProgress":23,"inQueue":0},{"hour":"11:45","inProgress":3,"inQueue":0},{"hour":"12:00","inProgress":5,"inQueue":0},{"hour":"12:15","inProgress":1,"inQueue":0},{"hour":"12:30","inProgress":1,"inQueue":0},{"hour":"12:45","inProgress":2,"inQueue":0},{"hour":"13:00","inProgress":8,"inQueue":0},{"hour":"13:15","inProgress":5,"inQueue":0},{"hour":"13:30","inProgress":5,"inQueue":0},{"hour":"13:45","inProgress":6,"inQueue":3},{"hour":"14:00","inProgress":6,"inQueue":1},{"hour":"14:15","inProgress":7,"inQueue":0},{"hour":"14:30","inProgress":7,"inQueue":0},{"hour":"14:45","inProgress":8,"inQueue":2},{"hour":"15:00","inProgress":8,"inQueue":3},{"hour":"15:15","inProgress":9,"inQueue":1},{"hour":"15:30","inProgress":9,"inQueue":0},{"hour":"15:45","inProgress":10,"inQueue":0},{"hour":"16:00","inProgress":10,"inQueue":0},{"hour":"16:15","inProgress":10,"inQueue":0},{"hour":"16:30","inProgress":11,"inQueue":0},{"hour":"16:45","inProgress":11,"inQueue":0},{"hour":"17:00","inProgress":12,"inQueue":1},{"hour":"17:15","inProgress":12,"inQueue":4},{"hour":"17:30","inProgress":13,"inQueue":6},{"hour":"17:45","inProgress":13,"inQueue":2},{"hour":"18:00","inProgress":14,"inQueue":1},{"hour":"18:15","inProgress":14,"inQueue":0},{"hour":"18:30","inProgress":15,"inQueue":0},{"hour":"18:45","inProgress":15,"inQueue":0},{"hour":"19:00","inProgress":15,"inQueue":0},{"hour":"19:15","inProgress":16,"inQueue":0},{"hour":"19:30","inProgress":16,"inQueue":0},{"hour":"19:45","inProgress":17,"inQueue":0},{"hour":"20:00","inProgress":17,"inQueue":0},{"hour":"20:15","inProgress":18,"inQueue":0},{"hour":"20:30","inProgress":18,"inQueue":3},{"hour":"20:45","inProgress":19,"inQueue":1},{"hour":"21:00","inProgress":19,"inQueue":0},{"hour":"21:15","inProgress":20,"inQueue":0},{"hour":"21:30","inProgress":20,"inQueue":2},{"hour":"21:45","inProgress":20,"inQueue":3},{"hour":"22:00","inProgress":21,"inQueue":1},{"hour":"22:15","inProgress":21,"inQueue":0},{"hour":"22:30","inProgress":22,"inQueue":0},{"hour":"22:45","inProgress":22,"inQueue":0},{"hour":"23:00","inProgress":23,"inQueue":0},{"hour":"23:15","inProgress":23,"inQueue":0},{"hour":"23:30","inProgress":1,"inQueue":0},{"hour":"23:45","inProgress":2,"inQueue":1}];
var margin = {top: 20, right: 50, bottom: 30, left: 20},
width = 500,
height = 300;
// Transpose the data into layers
var dataset = d3.layout.stack()(['inProgress', 'inQueue'].map(function(types) {
return data.map(function(d) {
return {
x: d.hour,
y: +d[types],
type: types
};
});
}));
var svg = d3.select('svg'),
margin = {top: 40, right: 10, bottom: 20, left: 10},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// Set x, y and colors
var xScale = d3.scale.ordinal()
.domain(dataset[0].map(function(d) {
return d.x;
}))
.rangeRoundBands([0, width - margin.left]);
var yScale = d3.scale.linear()
.domain([0, d3.max(dataset, function(d) {
return d3.max(d, function(d) {
return d.y0 + d.y;
});
})])
.range([height, 0]);
var colors = ['#56a8f8', '#c34434'];
// Define and draw axes
var yAxis = d3.svg.axis()
.scale(yScale)
.orient('left')
.ticks(5)
.tickSize(0)
.tickFormat(function(d) {
return d;
});
var xAxis = d3.svg.axis()
.scale(xScale)
.orient('bottom')
.tickSize(0)
.ticks(12) // this
.tickFormat(function(d) {
return d; // and this will not work with an ordinal scale
});
xAxis.tickValues(["0:00", "2:00", "4:00", "6:00", "8:00", "10:00", "12:00", "14:00", "16:00", "18:00", "20:00", "22:00"]);
svg.append('g')
.attr('class', 'y axis')
.call(yAxis);
svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis);
// Create groups for each series, rects for each segment
var groups = svg.selectAll('g.bar-stacks')
.data(dataset)
.enter().append('g')
.attr('class', function(d, i) {
return 'bar-stacks ' + d[i].type;
})
.classed('bar-stacks', true)
.style('fill', function(d, i) {
return colors[i];
});
var rect = groups.selectAll('rect')
.data(function(d) {
return d;
})
.enter()
.append('rect')
.attr('class', function(d, i) {
return 'stacks ' + d.type;
})
.classed('stacks', true)
.attr('id', function(d, i) {
return d.type + '_' + i;
})
.attr('x', function(d) {
return xScale(d.x);
})
.attr('y', function(d) {
return yScale(d.y0 + d.y);
})
.attr('height', function(d) {
return yScale(d.y0) - yScale(d.y0 + d.y);
})
.attr('width', xScale.rangeBand());
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div>
<svg width="600" height="300"></svg>
</div>

D3 Multi-Series Line Chart with ZOOM

Apologies for the newbie question here. I'm trying to reproduce this example https://bl.ocks.org/mbostock/3884955 with some data on online influencers (essentially drawing many lines) but with an scroll zoom in order to isolate parts of the data. Sounds simple enough, but I can't seem to get it to function!
< svg width = "960"
height = "500" > < /svg>
<script src="/ / d3js.org / d3.v4.min.js "></script>
<script>
var svg = d3.select("svg"),
margin = {top: 20, right: 80, bottom: 30, left: 100},
width = svg.attr("width") - margin.left - margin.right,
height = svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform ", "translate(" + margin.left +
", " + margin.top + ")");
var zoom = d3.zoom()
.scaleExtent([1, 32])
.translateExtent([[0, 0], [width, height]])
.extent([[0, 0], [width, height]])
.on("zoom", zoomed);
var xAxis = d3.axisBottom(x),
yAxis = d3.axisLeft(y);
var parseTime = d3.timeParse(" % d / % m / % Y ");
var x = d3.scaleTime().range([0, width]),
y = d3.scaleLinear().range([height, 0]),
z = d3.scaleOrdinal(d3.schemeCategory10);
var line = d3.line()
.curve(d3.curveBasis)
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.engagement); });
d3.csv("stack2.csv", type, function(error, data) {
if (error) throw error;
var influencers = data.columns.slice(1).map(function(id) {
return {
id: id,
values: data.map(function(d) {
return {date: d.date, engagement: d[id]};
})
};
});
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([d3.min(influencers, function(c) {
return d3.min(c.values, function(d) {
return d.engagement; }); }),
d3.max(influencers, function(c) {
return d3.max(c.values, function(d) {
return d.engagement; }); })
]);
z.domain(influencers.map(function(c) { return c.id; }));
g.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0, " + height + ")")
.call(d3.axisBottom(x));
g.append("g")
.attr("class", "axis axis--y")
.call(d3.axisLeft(y))
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 5)
.attr("dy", "0.71em")
.attr("fill", "#000")
.text("Engagement");
var influ = g.selectAll(".influ")
.data(influencers)
.enter().append("g")
.attr("class", "influ");
influ.append("path")
.attr("class", "line")
.attr("d", function(d) {
return line(d.values);
})
.style("stroke", "lightgrey")
.on("mouseover", function(d, i) {
d3.select(this).transition()
.style("stroke", function(d) {
return z(d.id);
})
.style("stroke-width", 3)
console.log(d.id);
})
.on("mouseout", function(d) {
d3.select(this).transition()
.style("stroke", "lightgrey")
.style("stroke-width", 1)
})
influ.append("text")
.datum(function(d) {
return {
id: d.id,
value: d.values[d.values.length - 1]
};
})
.attr("transform", function(d) {
return "translate(" + x(d.value.date) + "," + y(d.value.engagement) + ")";
})
.attr("x", 3)
.attr("dy", ".35em")
.style("opacity", 0.7)
.style("font", "10px sans-serif")
.text(function(d) {
return d.id;
});
svg.call(zoom)
});
function type(d, _, columns) {
d.date = parseTime(d.date);
for (var i = 1, n = columns.length, c; i < n; ++i) d[c = columns[i]] = +d[c];
return d;
}
function zoomed() {
var t = d3.event.transform,
xt = t.rescaleX(x);
g.selectAll("path.line").attr("d", function(d) {
return xt(d.date);
});
g.select(".axis--x").call(xAxis.scale(xt));
}
< /script>
I think the problem is isolated to this part:
function zoomed() {
var t = d3.event.transform,
xt = t.rescaleX(x);
g.selectAll("path.line").attr("d", function(d) {
return xt(d.date);
});
g.select(".axis--x").call(xAxis.scale(xt));
}
The Axis zoom is fine, but i don't think I'm calling the data for the line graphs correctly. I think g.selectAll definitely selects the lines, as they disappear on scroll... so I am assuming that .attr("d", function(d) {return xt(d.date); is wrong. Anyone have any tips?
I use something along these lines in an interactive line chart D3 plot that I wrote a couple of days back. I've commented extensively, so it should be pretty self explanatory. This works really well and has been bug free.
function zoomed() {
lastEventTransform = d3.event.transform;
// Rescale axes using current zoom transform
gX.call(xAxis.scale(lastEventTransform.rescaleX(x)));
gY.call(yAxis.scale(lastEventTransform.rescaleY(y)));
// Create new scales that incorporate current zoom transform on original scale
var xt = lastEventTransform.rescaleX(x),
yt = lastEventTransform.rescaleY(y);
// Apply new scale to create new definition of d3.line method for path drawing of line plots
var line = d3.line()
.x(function(d) { return xt(d.x); })
.y(function(d) { return yt(d.y); });
// Update all line-plot elements with new line method that incorporates transform
innerSvg.selectAll(".line")
.attr("d", function(d) { return line(d.values); });
// Update any scatter points if you are also plotting them
innerSvg.selectAll(".dot")
.attr("cx", function(d) {return xt(d.x); })
.attr("cy", function(d) {return yt(d.y); });
}
Note: gX and gY are just the d3 g group elements that had the axes originally called on them during setup of the plot.
This Zoom function tricks the domain to change and then redraws the lines on the new domain. Its the best i can manage for now... but hardly elegant!
function zoomed() {
var t = d3.event.transform;
// var t = d3.event.scaleBy;
var xt = t.rescaleY(y);
domain = yAxis.scale().domain();
g.select(".axis--y").call(yAxis.scale(xt));
g.selectAll("path.line").attr("d", function(d) {
if ( d3.max(d.values.map(function(dd) { return dd.engagement } )) > domain[1] ) {
return null
}
else {
return line(d.values)
}
});
if (domain[1] > 50000000) {
max = 50000000
} else if ( domain[1] < 500 ) {
max = 500
} else {
max = domain[1]
}
y.domain([0, max]);
}
It zooms alright but it is prone to de-coupling the axis from what the lines are saying. If anyone has something better let me know!

d3 v4 js : attribute error

I'm very new to data vizualisation and JavaScript and I'm trying to build a bar chart histogram using d3 v4.
I was working first working on d3 v3 and everything was going so well but I've got informed that I needed to work on v4.
Here is a piece of my code :
...
// create function for x-axis mapping.
var x = d3.scaleBand().rangeRound([0, hGDim.w]).padding(0.1)
.domain(fD.map(function(d) { return d[0]; }));
var xAxis = d3.axisBottom()
.scale(x);
// Create function for y-axis map.
var y = d3.scaleBand().rangeRound([0, hGDim.h])
.domain([0, d3.max(fD, function(d) { return d[1]; })]);
var yAxis = d3.axisBottom()
.scale(y);
// Create bars for histogram to contain rectangles and freq labels.
var bars = hGsvg.selectAll(".bar").data(fD).enter()
.append("g").attr("class", "bar");
//create the rectangles.
bars.append("rect")
.attr("x", function(d) { return x(d[0]); })
.attr("y", function(d) { return y(d[1]); })
.attr("width", x.bandwidth())
.attr("height", function(d) { return hGDim.h - y(d[1]); })
.attr('fill',barColor)
.on("mouseover",mouseover)// mouseover is defined below.
.on("mouseout",mouseout);// mouseout is defined below.
//Create the frequency labels above the rectangles.
bars.append("text").text(function(d){ return d3.format(",")(d[1])})
.style("font-family", "sans-serif")
.attr("x", function(d) { return x(d[0])+x.bandwidth()/2; })
.attr("y", function(d) { return y(d[1])-5; })
.attr("text-anchor", "middle");
...
When trying to run this, I have this 2 errors :
Error: attribute height: Expected length, "NaN".
And it tells me that it's on this line :
.attr("height", function(d) { return hGDim.h - y(d[1]); })
hGDim.h being a number
I also have this error :
Error: attribute y: Expected length, "NaN".
And it tells me that it's on this line :
.attr("y", function(d) { return y(d[1])-5; })
I didn't put all my code (271 lines), I'm not sure it's needed here.
Do you have any idea from where could these errors come from ?
I feel that I'm trying to add 2 variables of different types... However, it was working well on v3.
You are treating your y scale like a continuous scale, but it needs to be ordinal like your x scale (scaleBand() is ordinal). Try this:
var y = d3.scaleBand()
.rangeRound([0, hGDim.h])
.domain(fD.map(function(d) {return d[1];}));
Then, you must modify the create rectangle code as follows:
bars.append("rect")
.attr("x", function(d) { return x(d[0]); })
.attr("y", function(d) { return y(d[1]); })
.attr("width", x.bandwidth())
.attr("height", y.bandwidth())
.attr('fill', barColor)
.on("mouseover", mouseover)
.on("mouseout", mouseout);
Here is a minimal example, assuming d[0] and d[1] are coordinates on the heatmap:
var data = [[1, 1, "red"], [1, 2, "blue"], [1, 3, "green"],
[2, 1, "navy"], [2, 2, "yellow"], [2, 3, "orange"],
[3, 1, "red"], [3, 2, "blue"], [3, 3, "red"]],
svg = d3.select("svg"),
w = 500,
h = 500,
x = d3.scaleBand()
.rangeRound([0, w])
.padding(0.1)
.domain(data.map(function(d) {return d[0];})),
y = d3.scaleBand()
.rangeRound([0, h])
.padding(0.1)
.domain(data.map(function(d) {return d[1]})),
xAxis = d3.axisBottom()
.scale(x),
yAxis = d3.axisBottom()
.scale(y),
bars = svg.selectAll(".bar")
.data(data).enter()
.append("g")
.attr("class", "bar");
bars.append("rect")
.attr("x", function(d) { return x(d[0]); })
.attr("y", function(d) { return y(d[1]); })
.attr("width", x.bandwidth())
.attr("height", y.bandwidth())
.style("fill", function(d) { return d[2]; });
<script src="https://d3js.org/d3.v4.js"></script>
<svg height="500px", width="500px"></svg>

Categories

Resources