plot not rendered under D3.js version 4 - javascript

I am trying to port a d3 chart (which I found online) to v4 but for some reason only the axes are shown and nothing else. The code runs without any errors in the console of Chrome's developer tools and I have hit the wall as I am not an advanced d3 user. Any ideas are highly appreciated.
Here are two jsfiddles, in v3 and v4
https://jsfiddle.net/54mp286c/31/
https://jsfiddle.net/54mp286c/36/
and this is the d3 v4 code
var data = [{
"Cell_Num": 0,
"y": 3,
"x": 2
}, {
"Cell_Num": 1,
"y": 3,
"x": 6
}, {
"Cell_Num": 2,
"y": 7,
"x": 1
}, {
"Cell_Num": 3,
"y": 5,
"x": 5
}]
var margin = {
top: 10,
left: 50,
bottom: 30,
right: 10
},
width = 460 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
var scale = {
x: d3.scaleLinear().range([0, width]).nice(),
y: d3.scaleLinear().range([height, 0]).nice()
};
var access = {
x: function(d) {
return d.x;
},
y: function(d) {
return d.y;
}
};
var value = {
x: function(d) {
return scale.x(access.x(d));
},
y: function(d) {
return scale.y(access.y(d));
}
};
var axis = {
x: d3.axisBottom(scale.x),
y: d3.axisLeft(scale.y)
};
var svg = d3.select(".chart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("g").attr("class", "x axis");
svg.append("g").attr("class", "y axis");
svg.call(renderPlot, data);
function renderPlot(selection, data) {
updateScales(data);
selection.select(".x.axis").call(axis.x)
.attr("transform", "translate(0," + height + ")");
selection.select(".y.axis").call(axis.y);
selection
.call(renderVoronoi, data)
.call(renderPoints, data);
}
function renderVoronoi(selection, data) {
var voronoi = d3.voronoi()
.x(value.x)
.y(value.y)
.extent([
[0, 0],
[width, height]
]);
var polygons = selection.selectAll(".voronoi")
.data(voronoi(data));
polygons.enter().append("path")
.attr("class", "voronoi")
.on("mouseenter", function(d, i) {
var datum = selection.selectAll(".point").data()[i];
selection.call(renderCrosshair, datum);
})
.on("mouseleave", function(d, i) {
selection.selectAll(".crosshair").remove();
});
polygons
.attr("d", d3.line());
polygons.exit()
.remove();
}
function renderCrosshair(selection, datum) {
var lineData = [
// vertical line
[
[value.x(datum), height],
[value.x(datum), 0]
],
// horizontal line
[
[0, value.y(datum)],
[width, value.y(datum)]
]
];
var crosshairs = selection.selectAll(".crosshair.line").data(lineData);
crosshairs.enter().append("path")
.attr("class", "crosshair line");
crosshairs
.attr("d", d3.svg.line());
crosshairs.exit()
.remove();
var labelData = [{
x: -6,
y: value.y(datum) + 4,
text: Math.round(access.y(datum)),
orient: "left"
},
{
x: value.x(datum),
y: height + 16,
text: Math.round(access.x(datum)),
orient: "bottom"
}
];
var labels = selection.selectAll(".crosshair.label").data(labelData);
labels.enter().append("text")
.attr("class", "crosshair label");
labels
.attr("x", function(d) {
return d.x;
})
.attr("y", function(d) {
return d.y;
})
.style("text-anchor", function(d) {
return d.orient === "left" ? "end" : "middle";
})
.text(function(d) {
return d.text;
});
labels.exit().remove();
}
function renderPoints(selection, data) {
var points = selection.selectAll(".point").data(data);
points.enter().append("circle")
.attr("class", "point")
.attr("cx", value.x)
.attr("cy", value.y)
.attr("r", 0)
.style("opacity", 0);
points
.transition().duration(1000)
.attr("cx", value.x)
.attr("cy", value.y)
.attr("r", 2)
.style("opacity", 1);
points.exit()
.transition().duration(1000)
.attr("r", 0)
.style("opacity", 0)
.remove();
}
function updateScales(data) {
var extent = {
x: d3.extent(data, access.x),
y: d3.extent(data, access.y)
};
scale.x.domain([extent.x[0] - 0.5, extent.x[1] + 0.5]);
scale.y.domain([extent.y[0] - 0.5, extent.y[1] + 0.5]);
}
Many thanks!

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.

d3 drag multiple text elements inside a group SVG

I am new to using d3.js. I am trying to append multiple text elements in a svg group. With that, I want to be able to drag the group of multiple text.
So for an example:
export function testNode (config = {}) {
let { svg, rectX, rectY, text1, text2 } = config
var data = [{ x: rectX, y:rectY, label: text1, label2: text2, labelX: rectX + 100, labelY: rectY + 200, labelX2: rectX + 300, labelY2: rectY + 300 }]
var group = svg.append("g")
.selectAll("g")
.data(data)
.enter()
.append("g")
.attr("transform",
"translate(" + 0 + "," + 0 + ")")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
group.append("text")
.data(data)
.attr("x", (d) => { return d.labelX })
.attr("y", (d) => { return d.labelY })
.attr("font-size", "1em")
.attr("color", "black")
.text((d) => { return d.label });
group.append("text")
.data(data)
.attr("x", (d) => { return d.labelX2 })
.attr("y", (d) => { return d.labelY2 })
.attr("font-size", ".75em")
.attr("color", "black")
.attr("class", "label")
.text((d) => { return d.metricValue_01 });
function dragStarted() {
d3.select(this).raise().classed("active", true);
}
function dragged(d) {
d3.select(this).select("text")
.attr("x", d.labelX = d3.event.x + 10)
.attr("y", d.labelY = d3.event.y + 20);
d3.select(this).select("text")
.attr("x", d.labelX2 = d3.event.x + 10)
.attr("y", d.labelY2 = d3.event.y + 20);
function dragended() {
d3.select(this).classed("active", false);
}
If I use the selectAll method, the text gets clumped together. Therefore, I was wondering if I can drag the text group in their proper position based on the coordinate I give it (while being dragged). Anyways please let me know if I need to provide any further information. Thank you
If you want to select the second label, but don't want to use selectAll as it selects both, you can give the labels classes when appending and select those when dragging:
d3.select(this).select(".bigLabel")
.attr("x", d.labelX = d3.event.x + 10)
.attr("y", d.labelY = d3.event.y + 20);
d3.select(this).select(".smallLabel")
.attr("x", d.labelX2 = d3.event.x + 10)
.attr("y", d.labelY2 = d3.event.y + 20);
Though of course this will set the same coordinate for both labels unless you specify an offset, as below:
var data = [
{ x: 100, x2: 100, y: 100, y2: 120, label: "label1", value: "17%" },
{ x: 300, x2: 300, y: 200, y2: 220, label: "label2", value: "83%" },
{ x: 100, x2: 100, y: 200, y2: 220, label: "label3", value: "11%" },
{ x: 300, x2: 300, y: 100, y2: 120, label: "label4", value: "96%" }
];
var svg = d3.select("svg");
var labels = svg.selectAll("g")
.data(data)
.enter()
.append("g")
.call(d3.drag()
.on("drag",drag));
labels.append("text")
.attr("font-size", "1em")
.attr("x", function(d) { return d.x;})
.attr("y", function(d) { return d.y;})
.text(function(d) { return d.label; })
.attr("class","label1");
labels.append("text")
.attr("font-size", ".75em")
.attr("x", function(d) { return d.x2;})
.attr("y", function(d) { return d.y2;})
.text(function(d) { return d.value; })
.attr("class","label2");
function drag(d) {
var x = d3.event.x;
var y = d3.event.y;
d3.select(this)
.select(".label1")
.attr("x", function(d) { return d.x = x; })
.attr("y", function(d) { return d.y = y; })
d3.select(this)
.select(".label2")
.attr("x", function(d) { return d.x2 = x; })
.attr("y", function(d) { return d.y2 = y + 20; })
}
text {
text-anchor:middle;
cursor:pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width="500" height="400"></svg>
I won't dive into the alternative in too much depth, but it applies the drag to the g, positioning both text labels at the same time. This can handle irregular spacing between sibling labels easier than the above:
var data = [
{ x: 100, y: 100, label: "label1", value: "17%" },
{ x: 300, y: 200, label: "label2", value: "83%" },
{ x: 100, y: 200, label: "label3", value: "11%" },
{ x: 300, y: 100, label: "label4", value: "96%" }
];
var svg = d3.select("svg");
var labels = svg.selectAll("g")
.data(data)
.enter()
.append("g")
.attr("transform",function(d) {
return "translate("+[d.x,d.y]+")";
})
.call(d3.drag().on("drag", drag));
labels.append("text")
.attr("font-size", "1em")
.text(function(d) { return d.label; });
labels.append("text")
.attr("font-size", ".75em")
.text(function(d) { return d.value; })
.attr("dy", "1em")
function drag(d) {
d3.select(this)
.attr("transform","translate("+[d.x=d3.event.x,d.y=d3.event.y]+")");
}
text {
text-anchor: middle;
cursor: pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width="500" height="400"></svg>
Also, there is no need to use append("text").data(data), this is not doing anything - your datum is already bound to the newly appended element
Lastly, you can make this work with selectAll() if you set attributes using the second parameter of any provided function when setting attributes: (d,i)=>... i is the index of the element, so if your sibling labels are regularly spaced, you can use something like:
var data = [
{ x: 100, y: 100, label: "label1", value: "17%" },
{ x: 300, y: 200, label: "label2", value: "83%" },
{ x: 100, y: 200, label: "label3", value: "11%" },
{ x: 300, y: 100, label: "label4", value: "96%" }
];
var svg = d3.select("svg");
var labels = svg.selectAll("g")
.data(data)
.enter()
.append("g")
.call(d3.drag()
.on("drag",drag));
labels.append("text")
.attr("font-size", "1em")
.attr("x", function(d) { return d.x;})
.attr("y", function(d) { return d.y;})
.text(function(d) { return d.label; })
labels.append("text")
.attr("font-size", ".75em")
.attr("x", function(d) { return d.x;})
.attr("y", function(d) { return d.y + 20;})
.text(function(d) { return d.value; })
function drag(d) {
var x = d3.event.x;
var y = d3.event.y;
d3.select(this)
.selectAll("text")
.attr("x", function(d) { return d.x = x; })
.attr("y", function(d,i) { d.y = y;
return d.y + i * 20;
})
}
text {
text-anchor:middle;
cursor:pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width="500" height="400"></svg>

Adding nodes inside svg rect using d3 force layout

I have drawn four svg rect diagram...with different colors....
I'm trying to add node inside of those svg rect...but I'm getting only the svg rect without node inside them...I know that I have done something wrong with data...
But I'm not able to figure them out..I'm just one month old to D3..please advice me on this....
If you run the code below you can see my mistake....
<!DOCTYPE html>
<html>
<head>
<script src="../D3/d3.min.js"></script>
</head>
<body>
<style>
</style>
<script type="text/javascript">
var width = 500,
height = 500;
var nodes = [
{ x: width / 3, y: height / 2 }
//{ x: 2 * width / 3, y: height / 1 },
//{ x: 3 * width / 3, y: height / 2 },
//{ x: 4 * width / 3, y: height / 2 }
];
var force = d3.layout.force()
.size([width, height])
.nodes(nodes)
var svgcontainer = d3.select("body")
.append("svg")
.attr("width", 1000)
.attr("height", 900);
var rectdata = [{ "x": 50, "y": 70, "width": 600, "height": 150,"rx":80,"ry":80,"fill":"skyblue"},
{ "x": 50, "y": 260, "width": 200, "height": 400, "rx": 80, "ry": 90, "fill": "palegreen" },
{ "x": 440, "y": 260, "width": 200, "height": 400, "rx": 80, "ry": 90, "fill": "orange" },
{ "x": 50, "y": 700, "width": 600, "height": 150, "rx": 80, "ry": 80, "fill": "brown" }];
var svgrect = svgcontainer.selectAll("rect").data(rectdata).enter().append("rect");
var node = svgcontainer.selectAll('.node')
.data(nodes)
.enter().append('rect')
.attr('class', 'node');
force.on('end', function () {
svgrect.attr("x", function (d, i) { return d.x; })
.attr("y", function (d, i) { return d.y; })
.attr("rx", function (d, i) { return d.rx; })
.attr("ry", function (d, i) { return d.ry; })
.attr("width", function (d, i) { return d.width; })
.attr("height", function (d, i) { return d.height; })
.attr("fill", function (d, i) { return d.fill; });
});
force.start();
</script>
</body>
</html>
You aren't giving the node any attributes. I take it you want a circle and not a rect :
var node = svgcontainer.selectAll('.node')
.data(nodes)
.enter().append('circle')
.attr('class', 'node')
.attr('x', function(d){ console.log(d); return d.x})
.attr('y', function(d){ return d.y})
.attr('r', 10)
.attr('transform', function(d){
return 'translate(' + d.x + ', ' + d.y + ')'
})
Notice the translate at the bottom. If you are using the force layout in D3, the tick function should take care of this, but it looks like you don't have one, so you have to insert it after you create the nodes.
If you didn't want circles and wanted rectangles then this should do :
var nodeRect = svgcontainer.selectAll('.nodeRect')
.data(nodes)
.enter().append('rect')
.attr('class', 'nodeRect')
.attr('x', function(d){ console.log(d); return d.x})
.attr('y', function(d){ return d.y})
.attr('width', 100)
.attr('height', 50)
.attr('transform', function(d){
return 'translate(' + d.x + ', ' + d.y + ')'
})
Working fiddle with both : https://jsfiddle.net/reko91/n13kqvw9/
var width = 500,
height = 500;
var nodes = [
{ x: width / 3, y: height / 2 }
//{ x: 2 * width / 3, y: height / 1 },
//{ x: 3 * width / 3, y: height / 2 },
//{ x: 4 * width / 3, y: height / 2 }
];
var force = d3.layout.force()
.size([width, height])
.nodes(nodes)
var svgcontainer = d3.select("body")
.append("svg")
.attr("width", 1000)
.attr("height", 900);
var rectdata = [{ "x": 50, "y": 70, "width": 600, "height": 150,"rx":80,"ry":80,"fill":"skyblue"},
{ "x": 50, "y": 260, "width": 200, "height": 400, "rx": 80, "ry": 90, "fill": "palegreen" },
{ "x": 440, "y": 260, "width": 200, "height": 400, "rx": 80, "ry": 90, "fill": "orange" },
{ "x": 50, "y": 700, "width": 600, "height": 150, "rx": 80, "ry": 80, "fill": "brown" }];
var svgrect = svgcontainer.selectAll("rect").data(rectdata).enter().append("rect");
var node = svgcontainer.selectAll('.node')
.data(nodes)
.enter().append('circle')
.attr('class', 'node')
.attr('x', function(d){ console.log(d); return d.x})
.attr('y', function(d){ return d.y})
.attr('r', 10)
.attr('transform', function(d){
return 'translate(' + d.x + ', ' + d.y + ')'
})
var nodeRect = svgcontainer.selectAll('.nodeRect')
.data(nodes)
.enter().append('rect')
.attr('class', 'nodeRect')
.attr('x', function(d){ console.log(d); return d.x})
.attr('y', function(d){ return d.y})
.attr('width', 100)
.attr('height', 50)
.attr('transform', function(d){
return 'translate(' + d.x + ', ' + d.y + ')'
})
force.on('end', function () {
svgrect.attr("x", function (d, i) { return d.x; })
.attr("y", function (d, i) { return d.y; })
.attr("rx", function (d, i) { return d.rx; })
.attr("ry", function (d, i) { return d.ry; })
.attr("width", function (d, i) { return d.width; })
.attr("height", function (d, i) { return d.height; })
.attr("fill", function (d, i) { return d.fill; });
});
force.start();
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

make the series d3 graph chart

I trying to display a d3 linechart. I've a problem - I cannot stop the date from repeating. How can I stop the date keep repeating? I only want to show two columns(17/12/2013 and 18/12/2013) based on the JSON data reflected below. Or what do I need to do so the first tickmark would show 17/12/2013 and the last one would show 18/12/2013?
[
{
"key": "Excited",
"values": [ [1387212490000, 0], [1387298890000 , 10] ]
},
{
"key": "Sad",
"values": [ [1387212490000, 20], [1387298890000 , 50] ]
},
{
"key": "Angry",
"values": [ [1387212490000, 30], [1387298890000 , 30] ]
},
{
"key": "Happy",
"values": [ [1387212490000, 40], [1387298890000 , 70] ]
}
]
Below is the JS script
$(document).ready(function() {
d3.json('sales.json', function(data) {
nv.addGraph(function() {
var chart = nv.models.lineChart().x(function(d) {
return d[0]
}).y(function(d) {
return d[1]
}).color(d3.scale.category10().range())
.useInteractiveGuideline(true);
chart.xAxis.tickFormat(function(d) {
return d3.time.format('%d/%m/%Y')(new Date(d))
});
//chart.xScale(d3.time.scale());
d3.select('#nvd3 svg').datum(data).transition().duration(500).call(chart);
nv.utils.windowResize(chart.update);
return chart;
});
});
});
You didn't show enough code so it may be difficult to debug...
Anyway, try this and I'm working on an example to prove it...
chart.xAxis
.tickFormat(function(d) {
return d3.time.format('%d/%m/%Y')(new Date(d))
})
.ticks(d3.time.days, 1)
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
var margin = {top: 20, right: 40, bottom: 30, left: 20},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom,
barWidth = Math.floor(width / 19) - 1;
var x = d3.scale.linear()
.range([barWidth / 2, width - barWidth / 2]);
var y = d3.scale.linear()
.range([height, 0]);
var yAxis = d3.svg.axis()
.scale(y)
.orient("right")
.tickSize(-width)
.tickFormat(function(d) { return Math.round(d / 1e6) + "M"; });
// An SVG element with a bottom-right origin.
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 + ")");
// A sliding container to hold the bars by birthyear.
var birthyears = svg.append("g")
.attr("class", "birthyears");
// A label for the current year.
var title = svg.append("text")
.attr("class", "title")
.attr("dy", ".71em")
.text(2000);
d3.csv("population.csv", function(error, data) {
// Convert strings to numbers.
data.forEach(function(d) {
d.people = +d.people;
d.year = +d.year;
d.age = +d.age;
});
// Compute the extent of the data set in age and years.
var age1 = d3.max(data, function(d) { return d.age; }),
year0 = d3.min(data, function(d) { return d.year; }),
year1 = d3.max(data, function(d) { return d.year; }),
year = year1;
// Update the scale domains.
x.domain([year1 - age1, year1]);
y.domain([0, d3.max(data, function(d) { return d.people; })]);
// Produce a map from year and birthyear to [male, female].
data = d3.nest()
.key(function(d) { return d.year; })
.key(function(d) { return d.year - d.age; })
.rollup(function(v) { return v.map(function(d) { return d.people; }); })
.map(data);
// Add an axis to show the population values.
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + width + ",0)")
.call(yAxis)
.selectAll("g")
.filter(function(value) { return !value; })
.classed("zero", true);
// Add labeled rects for each birthyear (so that no enter or exit is required).
var birthyear = birthyears.selectAll(".birthyear")
.data(d3.range(year0 - age1, year1 + 1, 5))
.enter().append("g")
.attr("class", "birthyear")
.attr("transform", function(birthyear) { return "translate(" + x(birthyear) + ",0)"; });
birthyear.selectAll("rect")
.data(function(birthyear) { return data[year][birthyear] || [0, 0]; })
.enter().append("rect")
.attr("x", -barWidth / 2)
.attr("width", barWidth)
.attr("y", y)
.attr("height", function(value) { return height - y(value); });
// Add labels to show birthyear.
birthyear.append("text")
.attr("y", height - 4)
.text(function(birthyear) { return birthyear; });
// Add labels to show age (separate; not animated).
svg.selectAll(".age")
.data(d3.range(0, age1 + 1, 5))
.enter().append("text")
.attr("class", "age")
.attr("x", function(age) { return x(year - age); })
.attr("y", height + 4)
.attr("dy", ".71em")
.text(function(age) { return age; });
// Allow the arrow keys to change the displayed year.
window.focus();
d3.select(window).on("keydown", function() {
switch (d3.event.keyCode) {
case 37: year = Math.max(year0, year - 10); break;
case 39: year = Math.min(year1, year + 10); break;
}
update();
});
function update() {
if (!(year in data)) return;
title.text(year);
birthyears.transition()
.duration(750)
.attr("transform", "translate(" + (x(year1) - x(year)) + ",0)");
birthyear.selectAll("rect")
.data(function(birthyear) { return data[year][birthyear] || [0, 0]; })
.transition()
.duration(750)
.attr("y", y)
.attr("height", function(value) { return height - y(value); });
}
});
svg {
font: 10px sans-serif;
}
.y.axis path {
display: none;
}
.y.axis line {
stroke: #fff;
stroke-opacity: .2;
shape-rendering: crispEdges;
}
.y.axis .zero line {
stroke: #000;
stroke-opacity: 1;
}
.title {
font: 300 78px Helvetica Neue;
fill: #666;
}
.birthyear,
.age {
text-anchor: middle;
}
.birthyear {
fill: #fff;
}
rect {
fill-opacity: .6;
fill: #e377c2;
}
rect:first-child {
fill: #1f77b4;
}

D3: Y axis prints won't update with new data

I'm working with the D3 library and I have a Y-axis only that I want to update when my new data is updated. The new data is updated when the slider changes numbers and that works fine. The problem is that the new axis prints over the old one. I need the old ones to be removed obviously and the new ones replacing it when the data is changed. Would appreciate any help on this, thanks.
<script type="text/javascript">
var myYear = 2006;
//Width and height
var w = 1000;
var h = 500;
var barPadding = 20;
var myNames = ["Pedestrian", "Bicycle", "Motorbike", "Car", "Other"];
//Original data
var dataset = [
[
{ y: 20 }, //male
{ y: 4 },
{ y: 16},
{ y: 53},
{ y: 15 }
],
[
{ y: 12 }, //female
{ y: 4 },
{ y: 3 },
{ y: 36 },
{ y: 2 }
],
];
console.log(dataset);
//Set up stack method
var stack = d3.layout.stack();
//Data, stacked
stack(dataset);
//Set up scales
var xScale = d3.scale.ordinal()
.domain(d3.range(dataset[0].length))
.rangeRoundBands([30, w], 0.05);
var yScale = d3.scale.linear()
.domain([0,
d3.max(dataset, function(d) {
return d3.max(d, function(d) {
return d.y0 + d.y;
});
})
])
.range([0, h]);
yScale2 = d3.scale.linear() //for Y axis
.domain([0,
d3.max(dataset, function(d) {
return d3.max(d, function(d) {
return d.y0 + d.y;
});
})
])
.range([h-10, 0]);
//Easy colors accessible via a 10-step ordinal scale
// var colors = d3.scale.category20c();
var color = d3.scale.ordinal()
.domain(["Male", "Female"])
.range(["#00B2EE", "#FF69B4"]);
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
// Add a group for each row of data
var groups = svg.selectAll("g")
.data(dataset)
.enter()
.append("g")
.style("fill", function(d, i) {
return color(i);
});
//Define X axis
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.ticks(5);
//Define Y axis
var yAxis = d3.svg.axis()
.scale(yScale2)
.orient("left")
.ticks(5);
// Add a rect for each data value
groups.selectAll("rect")
.data(function(d) { return d; })
.enter()
.append("rect")
.attr("x", function(d, i) {
return xScale(i)
})
.attr("width", xScale.rangeBand())
.attr("y", function(d) {
return h - yScale(d.y0) - yScale(d.y) -20
})
.attr("height", function(d) {
return yScale(d.y)
})
.on("mouseover", function(d) {
//Get this bar's x/y values, then augment for the tooltip
d3.select(this)
.attr("stroke", "white")
.attr("stroke-width", "3px")
var xPosition = parseFloat(d3.select(this).attr("x")) + xScale.rangeBand() / 2; var yPosition = parseFloat(d3.select(this).attr("y")) / 2 + h / 2;
//Update the tooltip position and value
d3.select("#tooltip")
.style("left", xPosition + "px")
.style("top", yPosition + "px")
.select(".deathCount")
.text(d.y);
//Show the tooltip
d3.select("#tooltip").classed("hidden", false);
})
.on("mouseout", function(d) {
//Hide the tooltip
d3.select("#tooltip").classed("hidden", true);
d3.select(this)
.transition()
.duration(2000)
.attr("stroke", "none")
// .attr("fill", "rgb(0, 0, " + (d * 1) + ")");
});
///////// MOUSE CLICK TO CHANGE DATA //////////////////////////////
function data2012() {
dataset = [
[
{ y: 20 }, //male
{ y: 4 },
{ y: 16},
{ y: 53},
{ y: 15 }
],
[
{ y: 12 }, //female
{ y: 4 },
{ y: 3 },
{ y: 36 },
{ y: 2 }
],
];
}
function data2011() {
dataset = [
[
{ y: 33 }, //male
{ y: 9 },
{ y: 17},
{ y: 57},
{ y: 14 }
],
[
{ y: 14 }, //female
{ y: 0 },
{ y: 1 },
{ y: 38 },
{ y: 3 }
],
];
}
function data2010() {
dataset = [
[
{ y: 26 }, //male
{ y: 7 },
{ y: 25},
{ y: 106},
{ y: 18 }
],
[
{ y: 14 }, //female
{ y: 0 },
{ y: 0 },
{ y: 40 },
{ y: 2 }
],
];
}
function data2009() {
dataset = [
[
{ y: 31 }, //male
{ y: 11 },
{ y: 28},
{ y: 102},
{ y: 27 }
],
[
{ y: 17 }, //female
{ y: 2 },
{ y: 1 },
{ y: 55 },
{ y: 0 }
],
];
}
function updateData() {
// RE-SET SCALES AND LAYOUT
d3.select("g").selectAll("svg").remove();
//Data, stacked
stack(dataset);
//Set up scales
xScale = d3.scale.ordinal()
.domain(d3.range(dataset[0].length))
.rangeRoundBands([30, w], 0.05);
yScale = d3.scale.linear()
.domain([0,
d3.max(dataset, function(d) {
return d3.max(d, function(d) {
return d.y0 + d.y;
});
})
])
.range([0, h]);
// d3.selectAll(yAxis).remove(); //new stuff
groups = svg.selectAll("g")
.data(dataset)
//Update all rects
var gas = groups.selectAll("rect")
.data(function(d) {return d;});
gas
.exit
.remove;
gas
.transition()
.duration(750)
.ease("linear")
.attr("width", xScale.rangeBand())
.attr("y", function(d) {
return h - yScale(d.y0) - yScale(d.y) - 20
})
.attr("height", function(d) {
return yScale(d.y)
})
.attr("x", function(d, i) {
return xScale(i);
});
// d3.select(yAxis).remove();
//REDRAW Y AXIS
yScale2 = d3.scale.linear()
.domain([0,
d3.max(dataset, function(d) {
return d3.max(d, function(d) {
return d.y0 + d.y;
});
})
])
.range([h-10, 0]);
yAxis = d3.svg.axis()
.scale(yScale2)
.orient("left")
.ticks(5);
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(" + 30 + ",-10)")
.transition()
.duration(500)
.call(yAxis)
}
//SLIDER STUFF
xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.ticks(5)
.tickFormat(function(d) {
return myNames[d];
});
//Create Y axis
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(" + 30 + ",-10)")
// .call(yAxis);
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + (h - barPadding ) + ")")
.transition()
.duration(500)
.call(xAxis)
</script>
<script> //Jquery slider
$(function() {
$( "#slider" ).slider({
value:2012,
min: 2009,
max: 2012,
step: 1,
slide: function( event, ui ) {
$( "#amount" ).val( ui.value );
myYear = ui.value;
console.log(myYear);
if (myYear == 2012){
data2012();
}
if (myYear == 2011){
data2011();
}
if (myYear == 2010){
data2010();
}
if (myYear == 2009){
data2009();
}
updateData();
// console.log(myYear);
}
});
In your updateData() function don't re-append the axis. Do it once on initial and then update it. This is consistent with d3's enter, update, and exit pattern.
function updateData(){
...
yScale2 = d3.scale.linear()
.domain([0,
d3.max(dataset, function(d) {
return d3.max(d, function(d) {
return d.y0 + d.y;
});
})
])
.range([h - 10, 0]);
// set scale
yAxis.scale(yScale2);
// redraw it
d3.select('.yaxis').call(yAxis);
}
Example here.

Categories

Resources