d3js multi-line scatterplot zoom - javascript

I am working on a multi-line scatterplot with zoom using d3 v6. I am new to d3 and based on different examples, I could get the zoom function working for the images/points. The problem is that the lines aren't zooming. I looked at many similar questions, but none of those solutions are working for me.
The code I am using:
var margin = {
top: 50,
right: 30,
bottom: 30,
left: 210,
};
var svg = d3.select("svg"),
width = 1410 - margin.left - margin.right,
height = 620 - margin.top - margin.bottom;
svg
.append("defs")
.append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
d3.csv("CSV_files/NSW_pathway.csv").then(function (data1) {
var groupData = d3.group(data1, (d) => d.pathway_name);
var xScale = d3.scaleLinear().domain([0, 1]).range([0, width]);
var yScale = d3.scaleLinear().domain([0, 1]).range([height, 0]);
var xAxis = d3.axisBottom(xScale).ticks(0).tickSize(-height);
var yAxis = d3.axisLeft(yScale).ticks(0).tickSize(-width);
var gX = svg
.append("g")
.attr(
"transform",
"translate(" + margin.left + "," + (margin.top + height) + ")"
)
.call(xAxis);
var gY = svg
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(yAxis);
var focus = svg
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.attr("class", "line")
.attr("clip-path", "url(#clip)");
const color = d3
.scaleOrdinal()
.range(["#e41a1c", "#377eb8", "#4daf4a", "#984ea3"]);
var points_g = svg
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.attr("clip-path", "url(#clip)")
.classed("points_g", true);
var label = svg
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.attr("class", "label")
.attr("clip-path", "url(#clip)");
var div = d3
.select("body")
.append("div")
.attr("class", "tooltip")
.style("opacity", 0);
const mouseover = function (event, d) {
div.style("opacity", 1);
};
const mousemove = function (event, d) {
div
.html(function (d1) {
if (d.type != "learner")
return `The resource name is ${d.resource_name}`;
else return `This is ${d.name}`;
})
.style("position", "absolute")
.style("left", event.pageX + 15 + "px")
.style("top", event.pageY + 15 + "px");
};
const mouseleave = function (event, d) {
div.transition().duration(200).style("opacity", 0);
};
var points = points_g.selectAll("point").data(data1);
points = points
.enter()
.append("image")
.attr("xlink:href", function (d) {
if (d.type == "video") return "Images/3.jpg";
else if (d.type == "pdf") return "Images/4.png";
else if (d.type == "none") return "Images/5.png";
})
.attr("x", function (d) {
return xScale(+d.x) - 10;
})
.attr("y", function (d) {
return yScale(+d.y) - 10;
})
.attr("width", 20)
.attr("height", 20)
.on("mouseover", mouseover)
.on("mousemove", mousemove)
.on("mouseleave", mouseleave);
label
.selectAll(".text")
.data(data1)
.enter()
.append("text")
.text(function (d) {
return d.topic;
})
.attr("x", function (d) {
return xScale(+d.x) + 10;
})
.attr("y", function (d) {
return yScale(+d.y) + 10;
});
focus
.selectAll("line")
.data(groupData)
.enter()
.append("path")
.attr("fill", "none")
.attr("stroke", function (d) {
return color(d[0]);
})
.attr("stroke-width", 1)
.attr("d", function (d) {
return d3
.line()
.curve(d3.curveMonotoneX)
.x(function (d) {
return xScale(+d.x);
})
.y(function (d) {
return yScale(+d.y);
})(d[1]);
});
var zoom = d3
.zoom()
.scaleExtent([0.5, 20])
.extent([
[0, 0],
[width, height],
])
.on("zoom", zoomed);
svg
.append("rect")
.attr("width", width)
.attr("height", height)
.style("fill", "none")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.lower();
svg.call(zoom).call(zoom.transform, d3.zoomIdentity);
function zoomed({ transform }) {
var new_xScale = transform.rescaleX(xScale);
var new_yScale = transform.rescaleY(yScale);
gX.call(xAxis.scale(new_xScale));
gY.call(yAxis.scale(new_yScale));
points
.data(data1)
.attr("x", function (d) {
return new_xScale(d.x) - 10;
})
.attr("y", function (d) {
return new_yScale(d.y) - 10;
});
label
.selectAll("text")
.data(data1)
.attr("x", function (d) {
return new_xScale(d.x) + 15;
})
.attr("y", function (d) {
return new_yScale(d.y) + 15;
});
focus.selectAll("line").attr("d", function (d) {
return d3
.line()
.curve(d3.curveMonotoneX)
.x(function (d) {
return xScale(+d.x);
})
.y(function (d) {
return yScale(+d.y);
})(d[1]);
});
}
});
A sample of the csv file:
x,y,name,type,topic,resource_name,pathway_name
0,0,start,none,Sponsored Search Markets,Networks Crowd and Markets_NCMch15.pdf,pathwayOne
0,0,start,none,Sponsored Search Markets,Networks Crowd and Markets_NCMch15.pdf,pathwayTwo
0.086511627906977,0.16,horse,pdf,Graphs,Networks Crowd and Markets_NCMch2.pdf,pathwayOne
0.12,0.283768436578171,choice,pdf,Network Centrality,Notes_CGT BASED network CENTRALITY - L2.pdf,pathwayTwo
0.32,0.27217943628424,plex,video,Network Models,Network Analysis_LNch13.pdf,pathwayOne
0.775398773006135,0.33,social,pdf,Clustering,Network Analysis_LNch8.pdf,pathwayTwo
1,1,end,none,Allocation in Networks,Notes_Allocation in networks with DON-L3.pdf,pathwayOne
1,1,end,none,Allocation in Networks,Notes_Allocation in networks with DON-L3.pdf,pathwayTwo
Thank you for your help.

It's not zooming the whole page, it's zooming the whole svg, your large margins extend beyond the charting area. One solution is to add the g element not on your svg but only on your chart area.
But using your code, there are 2 things preventing your lines from zooming.
1: your selection is empty - line is a d3 abstraction that returns a path
function zoomed() {
...
// empty selection
console.log(focus.selectAll('line'))
// try instead
console.log(focus.selectAll('path'))
}
2: Simple mistake - you're using the old scale not the new one
function zoomed() {
...
focus.selectAll('path').attr('d', d => {
return d3.line()
// using old scale
.x(di => xScale(+di.x))
// change to
.x(di => new_xScale(+di.x))
})
}

I don't have a sample of your csv file so this isn't tested, but if you want to zoom the whole chart just add a parent g after your svg and transform that..
...
svg
.append("defs")
.append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
// NEW - add g
.append('g')
// NEW - adjust scaleExtent to your needs
const zoom = d3.zoom()
.scaleExtent([1, 8])
.on('zoom', updateChart)
svg.call(zoom)
function updateChart(event) {
svg.attr('transform', event.transform)
}
Note that this also adds pan, but if you only want zoom you can use:
let scale = 1
...
function updateChart(event) {
if(event.transform.k === scale) { return }
svg.attr('transform', event.transform)
scale = event.transform.k
}

Related

How to place text inside each bar of the chart using d3

I am trying to insert some text inside each bar of the given chart with it's values and I just can't figure it out. I'm still new to d3 and struggle with most of the things.
I also need to have different text inside each bar, dependent on the data.
I've looked up around and found that I need to use the following structure:
<g class="barGroup">
<rect />
<text />
</g>
This is how the chart looks:
This is how I want to achieve:
This is the function which creates the whole chart, the part where I attempt to insert the text is at the end:
function createBarChart(divChartId, receivedData, chartDimensions) {
// Set dimensions
var margin = chartDimensions["margin"];
var width = chartDimensions["width"];
var height = chartDimensions["height"];
// Create Svg and group
var svg = d3.select(divChartId)
.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 + ")");
// Create and append X axis
var xAxis = d3.scaleBand()
.range([0, width])
.domain(receivedData.map(function (d) { return d.answerText; }))
.padding(0.2);
svg.append("g")
.attr("class", "x-axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(xAxis))
.selectAll("text")
.attr("class", "font-weight-bold")
.attr("transform", "translate(-10,0)rotate(-45)")
.style("text-anchor", "end")
.style("font-size", "1rem");
// Create and append Y axis
var yAxis = d3.scaleLinear()
.domain([0, Math.max.apply(Math, receivedData.map(function (d) { return d.answerCount; }))])
.range([height, 0]);
svg.append("g")
.attr("class", "y-axis")
.call(d3.axisLeft(yAxis)
.ticks(5)
.tickFormat(d3.format("d")));
// create a tooltip
var tooltip = d3.select("body")
.append("div")
.attr("class", "font-weight-bold text-dark px-2 py-2")
.style("position", "absolute")
.style("z-index", "10")
.style("visibility", "hidden")
.style("background", "lavender")
.style("border", "1px solid gray")
.style("border-radius", "12px")
.style("text-align", "center")
.style("visibility", "hidden")
.style("visibility", "hidden")
.style("visibility", "hidden")
// Generate random color
var colorObject = generateRandomColorObject();
// Create and append bars, set values to 0 for animation start
svg.selectAll("rect").data(receivedData)
.enter()
.append("g")
.attr("class", "barGroup")
.append("rect")
.attr("x", function (d) { return xAxis(d.answerText); })
.attr("y", function (d) { return yAxis(0); })
.attr("width", xAxis.bandwidth())
.attr("height", function (d) { return height - yAxis(0); })
.attr("fill", colorObject.color)
.style("stroke", colorObject.border)
.style("stroke-width", 1)
.on("mouseover", function (d) { return tooltip.style("visibility", "visible").html(d.answerText + ": " + d.answerCount); })
.on("mousemove", function () { return tooltip.style("top", (event.pageY - 45) + "px").style("left", (event.pageX) + 5 + "px"); })
.on("mouseout", function (d) { return tooltip.style("visibility", "hidden").html(""); });
// Set correct values for animation end
svg.selectAll("rect")
.transition().duration(1500)
.attr("y", function (d) { return yAxis(d.answerCount); })
.attr("height", function (d) { return height - yAxis(d.answerCount); })
.delay(function (d, i) { return (i * 100) });
// Append text to each bar
svg.selectAll("barGroup")
.append("text")
.attr("font-size", "2em")
.attr("color", "black")
.text("Test");
}
In this line you want your selector to look for a class: svg.selectAll("barGroup") should be svg.selectAll(".barGroup"). Then, once you see your text show up, you'll need to position them.
Consider positioning your g.barGroups's first, using .attr('transform', function(d){ return 'translate(...)' }, before you .append('rect') and .append('text'). That way the entire group will be in approximately the right position, and you can make small adjustments to the rect and text.

How to make a moving transition of points from a to b

I have a scatterplot and I have two different sets of datapoints I am visualizing from the dataset. I want to animate the path from "red" to "blue" dots and show them like the blue point is moving from the red and getting its position. Is that possible with d3, and if so how can I do this?
The scatterplot I have currently with the plotted points is here.
this is how I draw both sets of datapoints in the scatterplot:
// blue dots
svg.append('g')
.selectAll("dot")
.data(data)
.enter()
.append("circle")
.attr("cx", function (d) { return x(d.x); } )
.attr("cy", function (d) { return y(d.y); } )
.attr("r", 4.1)
.transition()
.style("fill", "blue")
// red dots
svg.append('g')
.selectAll("dot")
.data(data)
.enter()
.append("circle")
.attr("cx", function (d) { return x(d.x1); } )
.attr("cy", function (d) { return y(d.y1); } )
.attr("r", 4.1)
.style("fill", "red")
}
Thank you for any kind of help in advance!
Yes it's possible.
Using the property transition and combining with duration in milliseconds. Look below:
https://jsfiddle.net/mathyaku/L5bpaxwv/1/
function drawScatterplot(data, selector) {
// set the dimensions and margins of the graph
var margin = { top: 10, right: 30, bottom: 30, left: 60 },
width = 700 - margin.left - margin.right,
height = 700 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select(selector)
.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 + ")");
//Read the data
// Add X axis
var x = d3.scaleLinear()
.domain([0, 1])
.range([0, width]);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add Y axis
var y = d3.scaleLinear()
.domain([0, 1])
.range([height, 0]);
svg.append("g")
.call(d3.axisLeft(y));
// Add red dots
svg.append('g')
.selectAll("dot")
.data(data)
.enter()
.append("circle")
.attr("cx", function (d) { return x(d.x1); })
.attr("cy", function (d) { return y(d.y1); })
.attr("r", 4.1)
.style("fill", "red")
svg.selectAll("circle")
.transition()
.duration(2000)
.attr("cx", function (d) { return x(d.x); })
.attr("cy", function (d) { return y(d.y); })
.style("fill", "blue")
}
drawScatterplot(data, '#Scatterplot');

Why the graph lines are off axis Y and X?

I am having trouble creating a chart with a simple line.
I'll put here my code and an image of how the line is getting off axis Y and X. I really have no idea why this is happening.
Chart:
HTML:
<div id="myChart"></div>
JavaScript:
Function to adjust my json only with the data I need:
var reduceVendedores = vendedores.reduce(function (allSales, sales) {
if (allSales.some(function (e) {
return e.vendnm === sales.vendnm;
})) {
allSales.filter(function (e) {
return e.vendnm === sales.vendnm
})[0].Vendas_Ano += sales.Vendas_Ano;
allSales.filter(function (e) {
return e.vendnm === sales.vendnm
})[0].Vendas_Ant += sales.Vendas_Ant
} else {
allSales.push({
vendnm: sales.vendnm,
Vendas_Ano: sales.Vendas_Ano,
Vendas_Ant: sales.Vendas_Ant
})
}
return allSales;
}, []);
Defining X and Y Axes of the Chart:
var margin = { top: 30, right: 30, bottom: 70, left: 60 },
width = 600 - margin.left - margin.right,
height = 600 - margin.top - margin.bottom;
function getMax() {
return reduceVendedores.map(d => d.Vendas_Ano)
}
var svg = d3.select("#myChart")
.append("svg")
.attr("width", width)
.attr("height", height);
var xscale = d3.scaleBand()
.range([0, width - 100])
.domain(reduceVendedores.map(function (d) { return d.vendnm; }))
var yscale = d3.scaleLinear()
.domain([0, d3.max(getMax()) + 30000])
.range([height / 2, 0]);
var x_axis = d3.axisBottom().scale(xscale);
var y_axis = d3.axisLeft().scale(yscale);
svg.append("g")
.attr("transform", "translate(50, 10)")
.call(y_axis);
var xAxisTranslate = height / 2 + 10;
svg.append("g")
.attr("transform", "translate(50, " + xAxisTranslate + ")")
.call(d3.axisBottom(xscale))
.selectAll("text")
.attr("transform", "translate(-10,0)rotate(-45)")
.style("text-anchor", "end");
Defining the chart line:
svg.append("path")
.datum(reduceVendedores)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-width", 1.5)
.attr("d", d3.line()
.x(function (d) { return xscale(d.vendnm); })
.y(function (d) { return yscale(d.Vendas_Ano) })
)
You're not adjusting for the margins you gave to your axis with:
.attr("transform", "translate(50, 10)")
Try:
svg.append('g')
.attr('transform','translate(50,0)')
.append("path")
.datum(reduceVendedores)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-width", 1.5)
.attr("d", d3.line()
.x(function (d) { return xscale(d.vendnm); })
.y(function (d) { return yscale(d.Vendas_Ano) })
)
Typically you'd set your margin in svg, like:
var svg = d3.select("#myChart")
.append("svg")
.attr("width", width)
.attr("height", height)
.append('g')
.attr('transform','translate(' + margin.left + ',' + margin.top + ')')
But doing so know would ruin the alignment of your axis.

I want to redraw dots every time I click update

Right now I have a bubble chart that plots bubbles at different point with different radius. I want to be able to click a button and the re-populate the chart with new random bubbles.
I created a function update(), where I have tried putting d3.selectall("circles").remove() before appending new circles to it. But it only removes the circles after I click it.
<body>
<button class="btn" onclick="update()">Update</button>
<!-- load the d3.js library -->
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var margin = {top: 20, right: 20, bottom: 30, left: 50},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scaleLinear().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
var r = d3.scaleLinear().range([10, 50]);
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 + ")");
var getdata = function() {
var dataset = []
for (var i = 0; i < 20; i++) {
var x = d3.randomUniform(-50,50)();
var y = d3.randomUniform(-50,50)();
var r = d3.randomUniform(-50,50)();
dataset.push({"x": x, "y": y,"r":r});
}
return dataset
}
var data = getdata()
data.forEach(function(d) {
d.x = +d.x;
d.y = +d.y;
d.r = +d.r;
});
x.domain([-50, 50]);
y.domain([-50, 50]);
r.domain([-50, 50]);
svg.selectAll("dot")
.data(data)
.enter().append("circle")
.attr("r", function(d) { return r(d.r); })
.attr("cx", function(d) { return x(d.x); })
.attr("cy", function(d) { return y(d.y); })
.style("stroke", "black")
.style("fill", "none")
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
svg.append("g")
.call(d3.axisLeft(y));
function update(){
d3.selectAll("circle").remove()
svg.selectAll("dot")
.data(data)
.enter().append("circle")
.attr("r", function(d) { return r(d.r); })
.attr("cx", function(d) { return x(d.x); })
.attr("cy", function(d) { return y(d.y); })
.style("stroke", "black")
.style("fill", "none")
}
You're actually deleting the bubble and redrawing the same.
You need to update your chart.
function update(){
d3.selectAll("circle").remove()
svg.selectAll("dot")
.data(getdata())
.enter().append("circle")
.attr("r", function(d) { return r(d.r); })
.attr("cx", function(d) { return x(d.x); })
.attr("cy", function(d) { return y(d.y); })
.style("stroke", "black")
.style("fill", "none")
}
I just replaced .data(data) with .data(getdata())

How to zoom behaviour in line chart using d3.js

I had developed for the line graph code, but unable to zoom behavior for the given code,how to add for the zoom in line graph,i had tried for the zoom behavior,but unable to show the zoom in the line graph,the given data i had developed.
function draw(){
var s1=data.map(function (d,i){return {key:d.key[0],value:d.value}})
//console.log(s1);
var margin = {top: 15, right: 20, bottom: 70, left: 85},
width = 440,height = 450;
var parseDate = d3.time.format("%y").parse
var x = d3.time.scale().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
var xAxis = d3.svg.axis().scale(x).orient("bottom").ticks(7).tickFormat(d3.format("d"));
var yAxis = d3.svg.axis().scale(y).orient("left")
var area = d3.svg.area()
.interpolate("cardinal")
.x(function(d) { return x(d.key); })
.y0(y(0))
.y1(function(d) { return y(d.value); });
var line = d3.svg.line()
.x(function(d) { return x(d.key); })
.y(function(d) { return y(d.value); })
.interpolate("cardinal")
var div = d3.select("body")
.append("div")
.attr("class", "tooltip")
.style("opacity", 0);
var svg = d3.select("body").select("#lineCharts")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
x.domain(d3.extent(s1, function(d) { return d.key; }));
y.domain(d3.extent(s1, function(d) { return d.value; }));
var zoom = d3.behavior.zoom()
.on("zoom", drawZoom);
svg.append("g")
.attr("class", "x axis x-axis")
.attr("transform", "translate(0," + height + ")").attr("fill","steelblue")
.call(xAxis);
svg.append("path")
.datum(s1)
.attr("class", "area")
.attr("d", area);
svg.append("g").attr("class", "y axis y-axis").attr("fill","steelblue").call(yAxis)
svg.append("path").attr("class", "line").attr("d", line(s1));
svg.selectAll("dot")
.data(data)
.enter().append("circle")
.style("fill","blue")
.attr("r","3.5")
.attr("cx",function (d,i){return x(d.key);})
.attr("cy",function(d,i){return y(d.value);})
.on("mouseover", function(d) {
div.transition()
.duration(200)
.style("opacity", .9);
div .html((d.key) + "<br/>" + d.value)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY-28) + "px");
})
.on("mouseout", function(d) {
div.transition()
.duration(500)
.style("opacity", 0);
});
function drawZoom() {
svg.select("g.x axis x-axis").call(xAxis);
svg.select("g.y axis y-axis").call(yAxis);
svg.select("path.area").attr("d", area);
svg.select("path.line").attr("d", line(s1));
}
}
Try this maybe ?
{//-------------------------zoom
var minZoom = 0.2,
maxZoom = 2.5;
var zoom = d3.behavior.zoom()
.on("zoom", redraw)
.scaleExtent([minZoom, maxZoom])//-call zoom but put scale extent on to limit zoom in/out
;
}
function redraw() //-redraw
{
var trans=d3.event.translate;
var scale=d3.event.scale;
//var newScale = scale/2;
inner.attr("transform","translate(" + trans + ")" + " scale(" + scale + ")"); //-translates and scales
}
Call that on your background (svg) and use your mouse to pan and zoom :)

Categories