Unable to understand d3js zoom functionality - javascript

The following SIMPLE :) code gives following error. Please help me what mistake I am making to ZOOM on onClick on the Circle ?
Similarly I will be very thankful if someone can give some idea that if i want to zoom to 3rd Circle DIRECTLY without clicking on it; what should I do ?
ERROR: Uncaught TypeError: Cannot read property 'apply' of undefined
at qr.call (d3.min.js:2)
at SVGCircleElement.clicked (index.html:52)
at SVGCircleElement.<anonymous> (d3.min.js:2)
And here is the code.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1,maximum-scale=1" />
<title>Zoom & Pan</title>
<link rel="stylesheet" type="text/css" href="styles.css" />
<script type="text/javascript" src="d3.min.js"></script>
<style type="text/css">
</style>
</head>
<body>
<script type="text/javascript">
var width = 600, height = 350;
var data = [
{ "r": 10, "cx": 100, "cy": 150 },
{ "r": 30, "cx": 200, "cy": 150 },
{ "r": 15, "cx": 300, "cy": 150 }
];
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.call(
d3.zoom()
.scaleExtent([1, 7])
.on("zoom", zoom)
);
const g = svg.append("g");
g.selectAll("circle")
.data(data)
.enter()
.append("circle")
//.attr("fill", function (d) { return d.color; })
.attr("fill", (d, i) => d3.interpolateRainbow(i / 3))
.on("click", clicked)
.attr("r", function (d) { return d.r; })
.attr("cx", function (d) { return d.cx; })
.attr("cy", function (d) { return d.cy; });
function clicked(d,i,nodes) {
console.log(nodes);
svg.transition().duration(750).call(
zoom.transform,
d3.zoomIdentity.translate(width / 2, height / 2).scale(40).translate(-d.cx, -d.cy),
d3.mouse(svg.node())
);
}
function zoom() {
g.attr("transform", d3.event.transform);
}
</script>
</body>
</html>

The issue is that zoom.transform is not a thing. You have created a function called zoom, if that is important then you'll need to create another variable called something else, e.g. zoomer, probably in window scope, like width and height:
var zoomer = d3.zoom();
Then in your click handler change zoom.transform to zoomer.transform:
function clicked(d,i,nodes) {
console.log(nodes);
svg.transition().duration(750).call(
zoomer.transform, //change here
d3.zoomIdentity.translate(width / 2, height / 2).scale(40).translate(-d.cx, -d.cy),
d3.mouse(svg.node())
);
}
If you don't need the zoom function, then delete that and create the zoom variable like the zoomer one above and don't change the click handler.

Related

Mark element as clicked in d3.js v6+

My Problem: I have some data points that are bound to circle elements. Now, each time the user clicks on a circle I want to change its color to red. For this, I want to use an update function which is called each time the user clicks on a circle. Since I want to change not only the clicked circle but also other elements based on which circle was clicked, I need to somehow remember which one was clicked. I saw this being done in d3.js v3 by simply saving the datum to a variable (clickedCircle) in the event listener and recalling it later. However, this doesn't seem to work in d3.js v6+ anymore. What would be the best way to do this in v6+?
Quick d3.js v3 Example (Basic idea adapted from this chart):
var data = [
{x: 10, y: 20},
{x: 30, y: 10},
{x: 20, y: 55},
];
svg = d3.select(#div1)
.append("svg")
.attr("width", 100)
.attr("height", 100)
;
var circles;
var clickedCircle;
function update() {
circles = svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cx", function(d) { return d.position.x } )
.attr("cy", function(d) { return d.position.y } )
.attr("r", 10)
.on("click", function(e, d) { clickedCircle = d; update(); } )
;
circles
.style("fill", function(d) {
if (d === clickedCircle) {
return "red"
} else {
return "black"
}
})
;
}
Here's an example with D3 v7. If you click a circle, that circle becomes red and its data is shown in a text label.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script src="https://d3js.org/d3.v7.js"></script>
</head>
<body>
<div id="chart"></div>
<script>
// margin convention set up
const margin = { top: 10, bottom: 10, left: 10, right: 10 };
const width = 110 - margin.left - margin.right;
const height = 110 - margin.top - margin.bottom;
const svg = d3.select('#chart')
.append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom);
const g = svg.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`);
// data
const data = [
{ x: 10, y: 20 },
{ x: 30, y: 10 },
{ x: 20, y: 55 },
];
// draw cirlces
const circles = g.selectAll('circle')
.data(data)
.join('circle')
.attr('cx', d => d.x)
.attr('cy', d => d.y)
.attr('r', 10)
.attr('fill', 'black')
.on('click', update);
// text label
const label = g.append('text')
.attr('y', height);
// update when circle is clicked
function update(event, d) {
// reset all circles to black
circles.attr('fill', 'black');
// color the clicked circle red
d3.select(this).attr('fill', 'red');
// update the label
label.text(`x: ${d.x}, y: ${d.y}`);
}
</script>
</body>
</html>

Pie chart not updating D3.JS

I know this question has been asked many times but I am not able to solve the problem of updating my pie chart. I am completely lost. Could you please tell me what seems to the problem here ?
I tried the following way but the chart doesn't seem to update. I added a function change that is supposed to update the chart. I am updating the data, redrawing the arcs and changing the labels but it is not working.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Load D3 -->
<script src="https://d3js.org/d3.v5.min.js"></script>
<title>Document</title>
</head>
<body>
<div id="pie"></div>
<script>
var dataChart = { a: 9, b: 20, c: 30, d: 8, e: 12 };
var dataChart2 = { f: 9, g: 20, h: 30, i: 8 };
console.log(dataChart);
var width = 300,
height = 300,
// Think back to 5th grade. Radius is 1/2 of the diameter. What is the limiting factor on the diameter? Width or height, whichever is smaller
radius = Math.min(width, height) / 2;
var color = d3.scaleOrdinal()
.range(["#2C93E8", "#838690", "#F56C4E"]);
var pie = d3.pie()
.value(function (d) { return d.value; });
data = pie(d3.entries(dataChart));
var arc = d3.arc()
.outerRadius(radius - 10)
.innerRadius(0);
var labelArc = d3.arc()
.outerRadius(radius - 40)
.innerRadius(radius - 40);
var svg = d3.select("#pie")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")"); // Moving the center point. 1/2 the width and 1/2 the height
var g = svg.selectAll("arc")
.data(data)
.enter().append("g")
.attr("class", "arc");
g.append("path")
.attr("d", arc)
.style("fill", function (d) { return color(d.data.key); });
g.append("text")
.attr("transform", function (d) { return "translate(" + labelArc.centroid(d) + ")"; })
.text(function (d) { return d.data.key; })
.style("fill", "#fff");
function change(dataChart) {
var pie = d3.pie()
.value(function (d) { return d.value; });
data = pie(d3.entries(dataChart));
path = d3.select("#pie").selectAll("path").data(data); // Compute the new angles
path.attr("d", arc); // redrawing the path
d3.selectAll("text").data(data).attr("transform", function (d) { return "translate(" + labelArc.centroid(d) + ")"; }); // recomputing the centroid and translating the text accordingly.
}
// calling the update functions
change(dataChart);
change (dataChart2);
</script>
</body>
</html>

No Output of the apparent right "D3.js - Code" (Simple Scatterplot)

I am new in D3.js and can't fix a seemingly small problem. The thing is I don't realy understand what's wrong with "my" code. The error that I get on the console tells me: "SyntaxError: expected expression, got '<' ", which doesn't make any sense for me. May be some of you can help me with this. Here it is:
var xyr = [
{x:1,y:1,r:1},
{x:2,y:2,r:2},
{x:3,y:3,r:3}
];
var body = d3.select("body");
var svg = body.append("svg").attr("width",250).attr("height",250);
var scaleX = d3.scaleLinear().range([0,250]);
var scaleY = d3.scaleLinear().range([0,250]);
function render(data){
//Data Binding
var circles = svg.selectAll("circle").data(data);
scaleX.domain(d3.extent(data,function(d){ return d.x; }));
scaleY.domain(d3.extent(data,function(d){ return d.y; }));
//Debugging (working correct --> 125)
circles
//Enter
.enter().apend("circle").attr("r",5)
//Update
.merge(circles)
.attr("cx", function (d){ return scaleX; })
.attr("cy", function (d){ return scaleY; });
//Exit
circles.exit().remove();
};
//Invoking the funktion with var xyr
render(xyr);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
</head>
<body>
</body>
</html>
First remove the <script> and </script> tags, those are throwing the errors. Second, you link v3 when your code is v4/5 (I used v4 in my snippet). Third you had some spelling mistakes that threw some errors. Lastly (and the biggest issue with d3 rendering), is with this:
.merge(circles)
.attr("cx", function (d){ return scaleX; })
.attr("cy", function (d){ return scaleY; });
which was missing the link to the data, which was updated to
.merge(circles)
.attr("cx", function (d){ return scaleX(d.x); })
.attr("cy", function (d){ return scaleY(d.y); });
var xyr = [{
x: 1,
y: 1,
r: 1
},
{
x: 2,
y: 2,
r: 2
},
{
x: 3,
y: 3,
r: 3
}
];
var body = d3.select("body");
var svg = body.append("svg").attr("width", 250).attr("height", 250);
var scaleX = d3.scaleLinear().range([0, 250]);
var scaleY = d3.scaleLinear().range([0, 250]);
function render(data) {
//Data Binding
var circles = svg.selectAll("circle").data(data);
scaleX.domain(d3.extent(data, function(d) {
return d.x;
}));
scaleY.domain(d3.extent(data, function(d) {
return d.y;
}));
//Debugging (working correct --> 125)
circles
//Enter
.enter().append("circle").attr("r", 5)
//Update
.merge(circles)
.attr("cx", function(d) {
return scaleX(d.x);
})
.attr("cy", function(d) {
return scaleY(d.y);
});
//Exit
circles.exit().remove();
};
//Invoking the funktion with var xyr
render(xyr);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
</head>
<body>
</body>
</html>

Cannot color svg elements in D3

I made a D3 map following Let's make a map tutorial by M. Bostock.
It is intended to create .subunit.id class and color it using CSS like .subunit.23 { fill: #f44242; }. But while .subunit is adressed well I can not reach each unit by specifying its id. Any ideas?
TopoJSON file is available here
https://gist.github.com/Avtan/649bbf5a28fd1f76278c752aca703d18
<!DOCTYPE html>
<meta charset="utf-8">
<html lang="en">
<style>
.subunit {
fill: #4286f4;
stroke: #efbfe9;}
.subunit.23 { fill: #f44242; }
</style>
<head>
<title>D3 Uzbekisztan map</title>
<meta charset="utf-8">
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script> -->
</head>
<body>
<script>
var width = 960,
height = 1160;
var projection = d3.geo.albers()
.center([-10, 40])
.rotate([-75, 0])
.parallels([38, 44])
.scale(4000)
.translate([width / 2, height / 2]);
var path = d3.geo.path()
.projection(projection);
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
d3.json("uzb_topo.json", function (error, uzb) {
if (error) return console.error(error);
console.log(uzb);
svg.selectAll(".subunit")
.data(topojson.feature(uzb, uzb.objects.uzb).features)
.enter().append("path")
.attr("class", function(d) { return "subunit " + d.id; })
.attr("d", path);
});
</script>
</body>
</html>
IDs cannot start by a number. Right now, you're setting two different classes, and the last one start with a number.
A simple solution is removing the space in your class name. So, this:
.attr("class", function(d) { return "subunit " + d.id; })
Should be this:
.attr("class", function(d) { return "subunit" + d.id; })//no space
And set your CSS accordingly.
Another solution is adding a letter before the number, like this:
.attr("class", function(d) { return "subunit " + "a" + d.id; })
So, you'll have the classes "a01", "a02", "a03" etc...

Why are mouse events not firing on correct element?

I'm having an issue with the following code. A yellow border should appear around the graph that the mouse is over. The 'mouseover'/'mouseout' event only fires for the last instance of the Chart class. Mousing over the first and second instantiations of Chart produce mouseover/mouseout events over the last instantiation of Chart. I've tested this code under Firefox, Safari, and Chrome, with the same results. Why is that - what am I doing wrong?
<!DOCTYPE html>
<html>
<head>
<title>test</title>
<meta charset="utf-8">
</head>
<style>
rect { fill: #fff0e0; stroke: #000; stroke-width: 2.0px; }
.line { fill: none; stroke: steelblue; stroke-width: 3.0px; }
</style>
<body>
<script src="d3.v2.js"></script>
<script>
var kev_vs_rho= [{
values: [{x: 0.01, y: 0.2058},{x: 0.03, y: 0.2039},{x: 0.99, y: 23.2020}] }];
kev_vs_rho.minX=0.01; kev_vs_rho.maxX=0.99;
kev_vs_rho.minY=0.01; kev_vs_rho.maxY=33.66;
</script>
<script>
var kev_vs_sec= [{
values: [{x: 1.5, y: 0.2058},{x: 9.494, y: 1.6468},{x: 1000.0, y:23.4699}] }];
kev_vs_sec.minX=1.50; kev_vs_sec.maxX=1000.00;
kev_vs_sec.minY=0.01; kev_vs_sec.maxY=33.66;
</script>
<div id="chart1"> </div> <hr/>
<div id="chart2"> </div> <hr/>
<div id="chart3"> </div>
<script>
"use strict";
function Chart ( _width, _height, _data, _anchor ) {
self = this;
this.data = _data;
this.anchor = _anchor;
this.margin = {top: 30, right: 30, bottom: 30, left: 80};
this.width = _width - this.margin.left - this.margin.right;
this.height = _height - this.margin.top - this.margin.bottom;
this.xscale = d3.scale.linear()
.domain([this.data.minX, this.data.maxX])
.range([0, this.width]);
this.yscale = d3.scale.linear()
.domain([this.data.minY, this.data.maxY])
.range([this.height, 0]);
this.lineA = d3.svg.line()
.x(function (d) { return self.xscale(d.x); })
.y(function (d) { return self.yscale(d.y); });
this.div = d3.select(this.anchor).append("div");
this.svg = this.div.append("svg")
.datum(this.data[0].values)
.attr("width", this.width + this.margin.left + this.margin.right)
.attr("height", this.height + this.margin.top + this.margin.bottom)
.append("g")
.attr("transform", "translate(" + this.margin.left + "," + this.margin.top + ")");
this.svg.append("rect")
.attr("width", this.width)
.attr("height", this.height);
this.lineB = this.svg.append("path")
.attr("class", "line")
.attr("d", self.lineA);
d3.select(this.anchor)
.on("mouseover", function () {
self.div.style("background", "yellow");
})
.on("mouseout", function () {
self.div.style("background", null);
})
};
Chart.prototype.redraw = function() {
this.lineB
.datum(this.data[0].values)
.attr("d", this.lineA);
};
var chart3 = new Chart( 960, 200, kev_vs_rho, "#chart3");
var chart2 = new Chart( 960, 200, kev_vs_sec, "#chart2");
var chart1 = new Chart( 960, 200, kev_vs_rho, "#chart1");
chart1.redraw();
chart2.redraw();
chart3.redraw();
</script>
</body>
</html>
the two (likely) reasons are either you have a transparent element overlaying the page linked to each graph (and because the 3rd graph is drawn last, it ends on top) that intercepts all the mouse movements, or there's something screwy in your event handler assignment (like a re-used reference).
You can use firebug (or other browsers equivalent) to test for transparent elements (just right click and 'inspect here', and see where it drops you in the DOM object graph). To test the event handler assignment, try changing the last step of your code to:
d3.select(this.anchor)
.on("mouseover", function () {
d3.select(this).select("div").style("background", "yellow");
})
.on("mouseout", function () {
d3.select(this).select("div").style("background", null);
})
Because inside the handler, this will have been set to the element the event was triggered on - so if you are truely mousing over, e.g., Chart2, then this should be set to Chart2, and Chart2 will therefor get the background.
If this doesn't work, can I suggest you put your code into a jsfiddle (including any additional JS / CSS / etc, so that people can actually play with it and see the problem for themselves.
Edit: updated to correct the selection tree.

Categories