D3: Detect mouse wheel event through overlaid SVG - javascript

In the following example, is there a way to for the zoomArea to detect a mouse wheel event that happens while pointing on one of the grey circles? The aim is to not interrupt the zoom behaviour when doing so. The circles should still be able to receive pointer events in order to e.g. display tooltips.
var dataset = [0, 2345786000, 10000000000];
var svg = d3.select("body").append("svg");
var w = 500, h = 200;
var padding = 50;
svg.attr("width", w)
.attr("height", h);
// Background pattern
var patternSize = 5;
svg.append("defs")
.append("pattern")
.attr("id", "dotPattern")
.attr("patternUnits", "userSpaceOnUse")
.attr("width", patternSize)
.attr("height", patternSize)
.append("circle")
.attr("cx", patternSize / 2)
.attr("cy", patternSize / 2)
.attr("r", 2)
.style("stroke", "none")
.style("fill", "lightgrey")
.style("opacity", 0.5);
var xScale = d3.time.scale()
.domain([0, 10000000000])
.range([padding, w-padding]);
var xAxis = d3.svg.axis()
.scale(xScale)
.ticks(5);
svg.append("g")
.attr("class","axis")
.attr("transform", "translate(0," + (h-padding) + ")")
.call(xAxis);
var zoom = d3.behavior.zoom()
.on("zoom", build)
.scaleExtent([1, 20]);
zoom.x(xScale);
var clipPath = svg.append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("x", padding)
.attr("y", 0)
.attr("width",w-2*padding)
.attr("height", h-padding);
var zoomArea = svg.append("g")
.attr("class", "zoomArea")
.style("cursor","move")
.attr("clip-path", "url(#clip)");
var zoomRect = zoomArea.append("rect")
.attr("x", padding)
.attr("y", 0)
.attr("width", w-2*padding)
.attr("height", h-padding)
.style("fill", "url(#dotPattern)")
.style("pointer-events", "all")
.style("cursor","move")
.call(zoom);
zoomArea.selectAll("circles")
.data(dataset)
.enter()
.append("circle")
.attr("cx", function(d){
return xScale(d);
})
.attr("cy", h/2)
.attr("r",10)
.attr("fill","grey")
function build(){
svg.select("g.axis").call(xAxis);
d3.selectAll("circle")
.attr("cx", function(d){
return xScale(d);
});
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

Call zoom on circles as well.
zoomArea.selectAll("circles")
.data(dataset)
.enter()
.append("circle")
.attr("cx", function(d){
return xScale(d);
})
.attr("cy", h/2)
.attr("r",10)
.attr("fill","grey")
.call(zoom);//call zoom on circle
Working code here
Hope this helps!

Another way of doing the same:
First make a rectangle with the fill background,don't attach the zoom listener to it.
var zoomRect = zoomArea.append("rect")
.attr("x", padding)
.attr("y", 0)
.attr("width", w-2*padding)
.attr("height", h-padding)
.style("fill", "url(#dotPattern)")
.style("cursor","move");//no zoom call
Not attach circles.
zoomArea.selectAll("circles")
.data(dataset)
.enter()
.append("circle")
.attr("cx", function(d){
return xScale(d);
})
.attr("cy", h/2)
.attr("r",10)
.attr("fill","grey");
Now make another rectangle same as the first except it has zoom behavior and fill transparent..so that its above all elements to handle the zoom behavior.
zoomArea.append("rect")
.attr("x", padding)
.attr("y", 0)
.attr("width", w-2*padding)
.attr("height", h-padding)
.style("fill", "transparent")
.style("pointer-events", "all")
.style("cursor","move")
.call(zoom);
Working example here
Hope this helps too!

Related

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');

How do I position a text on top of a circle?

I have code to make a circle and I'd like to place text on top of it.
I'm using this for my example: https://bl.ocks.org/mbostock/raw/7341714/
infoHeight = 200
infoWidth = 200
var compareSVG = d3.select(".info-container")
.append("svg")
.attr("class","comparison-svg")
.attr("width", infoWidth)
.attr("height", infoHeight);
var circle = compareSVG.append("g")
circle.append("circle")
.attr("r", circleRadius(d.properties.contextvalue))
.attr("cy", infoHeight/2)
.attr("cx", infoWidth/2)
.style("fill","grey")
.style("stroke","black")
.style("stroke-width","3px")
circle.append("text")
.text(d.properties.contextvalue)
.style("display", "block")
.style("y", infoHeight/2)
.style("x", infoHeight/2)
.style("color","red")
.style("font-size","20px")
The circle works, but the text won't appear on top of it. Instead, it is in the top left corner of the SVG element. I've tried position: absolute along with top and left and it stays in the same corner.
In D3, the attr methods uses Element.setAttribute internally, while style uses CSSStyleDeclaration.setProperty().
In an SVG <text> element, x and y are attributes. Therefore, change those style() methods for attr(). Also, get rid of that .style("display", "block").
So, it should be:
circle.append("text")
.text(d.properties.contextvalue)
.attr("y", infoHeight/2)
.attr("x", infoHeight/2)
.style("color","red")
.style("font-size","20px")
Here is your code with that change:
infoHeight = 200
infoWidth = 200
var compareSVG = d3.select("body")
.append("svg")
.attr("width", infoWidth)
.attr("height", infoHeight);
var circle = compareSVG.append("g")
circle.append("circle")
.attr("r", 50)
.attr("cy", infoHeight / 2)
.attr("cx", infoWidth / 2)
.style("fill", "lightgrey")
.style("stroke", "black")
.style("stroke-width", "3px")
circle.append("text")
.text("Foo Bar Baz")
.attr("y", infoHeight / 2)
.attr("x", infoHeight / 2)
.style("color", "red")
.style("font-size", "20px")
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
Finally, pay attention to the position of the text: it's not entered (regarding the circle). If you want to center it, use text-anchor and dominant-baseline:
infoHeight = 200
infoWidth = 200
var compareSVG = d3.select("body")
.append("svg")
.attr("width", infoWidth)
.attr("height", infoHeight);
var circle = compareSVG.append("g")
circle.append("circle")
.attr("r", 50)
.attr("cy", infoHeight / 2)
.attr("cx", infoWidth / 2)
.style("fill", "lightgrey")
.style("stroke", "black")
.style("stroke-width", "3px")
circle.append("text")
.text("Foo Bar Baz")
.attr("y", infoHeight / 2)
.attr("x", infoHeight / 2)
.attr("text-anchor", "middle")
.attr("dominant-baseline", "central")
.style("color", "red")
.style("font-size", "20px")
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

Wrong positioning of D3 nodes

I have a simple force layout that calculates nodes and links when certain buttons are clicked. The first time the nodes are calculated and displayed everything is correctly positioned. However, when nodes are recalculated after another click, the position of the circles I have appended to the nodes are way off yet the text I added remains in the right place. Here's my JS:
//Compute Nodes and Links
var data = this.computePreviewNodes($(e.currentTarget).data("id"), $(e.currentTarget).data("type"));
var canvas = d3.select(".body").append("svg")
.attr("width", width)
.attr("height", screen.height/2)
.append("g");
canvas.append("text")
.text(compObj.name)
.attr("text-anchor", "middle")
.attr("font-size", "2em")
.attr("x", width/2)
.attr("y", 40);
var force = d3.layout.force()
.nodes(data.nodes)
.links(data.links)
.gravity(.05)
.distance(100)
.charge(-10)
.size([width, screen.height/2]);
var links = canvas.selectAll(".links")
.data(data.links)
.enter().append("line")
.attr("class", "links")
.attr("fill", "none")
.attr("stroke", "blue");
var nodes = canvas.selectAll(".nodes")
.data(data.nodes)
.enter()
.append("g")
.attr("class", "nodes")
.call(force.drag);
nodes.append("circle")
.attr("cx", function(d) {return d.x;})
.attr("cy", function(d) {return d.y;})
.attr("r", 10)
.attr("fill", "green");
nodes.append("text")
.text(function(d) {return d.name})
.attr("text-anchor", "right")
.attr("font-size", "1.8em")
.attr("y", 5);
force.on("tick", function(e) {
nodes
.attr("transform", function(d, i){
return "translate(" + d.x + ", " + d.y + ")";
});
links
.attr("x1", function(d) {return d.source.x;})
.attr("y1", function(d) {return d.source.y;})
.attr("x2", function(d) {return d.target.x;})
.attr("y2", function(d) {return d.target.y;})
})
force.start();
My computePreviewNodes() function simply comes up with what nodes need to be displayed based on which button is clicked. My thoughts are that maybe I'm not updating my node positions correctly after the second rendering of my nodes. Any ideas?
Here's my canvas at the first click:
And here it is when I click/calculate my nodes once again:

Chart not updating with new data with transitions in D3

Trying to do a simple replace data/transition with D3 but it's not updating. I'm not getting an error and don't see anything odd when debugging. I feel like i'm missing something super simple and just overlooking. The more eyes, the better!
Here's my D3 code:
var labels = ['Opens', 'Clicks', 'Unsubscribe'],
data = [4, 8, 15],
chart,
x,
y,
gap = 2,
width = 450,
leftWidth = 100,
barHeight = 40,
barHeightInner = (barHeight / 2),
barTopMargin = (barHeight - barHeightInner) / 2,
height = (barHeight + gap * 2) * labels.length + 30;
/* SET SVG + LEFT MARGIN */
chart = d3.select($("#graphArea")[0])
.append('svg')
.attr('class', 'chart')
.attr('width', leftWidth + width + 20)
.attr('height', height)
.append("g")
.attr("transform", "translate(10, 20");
x = d3.scale.linear()
.domain([0, d3.max(data)])
.range([0, width]);
y = d3.scale.ordinal()
.domain(data)
.rangeBands([0, (barHeight + 2 * gap) * labels.length], 0.05);
/* CREATE BARS */
chart.selectAll("rect")
.data(data)
.enter().append("rect")
.attr("x", leftWidth)
.attr("y", y)
.attr("width", x)
.attr("height", y.rangeBand())
.attr('class', 'bar-outer');
/* INNER BARS */
chart.selectAll("rect.inner")
.data(data)
.enter().append("rect")
.attr("x", leftWidth)
.attr("y", function(d){return y(d) + barTopMargin} )
.attr("width", function(d){return x(d)/2;})
.attr("height", barHeightInner)
.attr('class', 'bar-inner');
/* SET NAMED LABELS */
chart.selectAll("text.label")
.data(labels)
.enter().append("text")
.attr("x", leftWidth / 2 + 40)
.attr("y", function(d,i) {return i * y.rangeBand() + 20;})
.attr("dy", ".35em")
.attr("text-anchor", "end")
.attr('class', 'label')
.text(String);
function update(data) {
data = [20, 10, 3];
chart.selectAll("rect")
.data(data)
.enter().append("rect")
.attr("x", leftWidth)
.attr("y", y)
.attr("width", x)
.attr("height", y.rangeBand())
.attr('class', 'bar-outer');
chart.selectAll("rect")
.data(data)
.exit()
.transition()
.duration(300)
.ease("exp")
.attr('width', 0)
.remove();
chart.selectAll("rect")
.data(data)
.transition()
.duration(300)
.ease("quad")
.attr("width", x)
.attr("height", y.rangeBand())
.attr("transform", function(d,i) { return "translate(" + [0, y(i)] + ")"});
It's kind of messy, sorry... but should be readable at least. I'm not sure why when update(data) it's not changing. Any advice would be greatly appreciated!
The main problems were with the application of the enter/update/exit pattern and also with forgetting to re-set the scale domains according to the new data. Here is the segment of interest:
function update() {
data = [20, 10, 3];
// must re-set scale domains with the new data
x.domain([0, d3.max(data)]);
y.domain(data);
var outer = chart.selectAll(".bar-outer")
.data(data);
// exit selection
outer
.exit()
.remove();
// enter selection
outer
.enter()
.append("rect")
.attr('class', 'bar-outer');
// update selection
outer
.transition()
.duration(500)
.ease("quad")
.attr("x", leftWidth)
.attr("y", y)
.attr("width", x)
.attr("height", y.rangeBand());
var inner = chart.selectAll(".bar-inner")
.data(data);
// exit selection
inner
.exit()
.remove();
// enter selection
inner
.enter()
.append("rect")
.attr('class', 'bar-inner');
// update selection
inner
.transition()
.duration(500)
.ease("quad")
.attr("x", leftWidth)
.attr("y", function (d) {
return y(d) + barTopMargin
})
.attr("width", function (d) {
return x(d) / 2;
})
.attr("height", barHeightInner);
};
Here is the complete FIDDLE.

D3 Donut chart: Display Value on segment hover

Long time lurker 1st time poster. I am trying to display the text value form a CSV file when the relevant segment of pie chart is hovered over. I have the pie chart (thanks to Mike Bostock) and the display when hovering but cant remove it on the mouse out. Any help would be greatly appreciated at this stage.
var width = 960,
height = 600,
radius = Math.min(width, height) / 2.5;
var arc = d3.svg.arc()
.outerRadius(radius + 10)
.innerRadius(radius - 70);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var color = d3.scale.ordinal()
.range(["#0bd0d2", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);
var pie = d3.layout.pie()
.sort(null)
.value(function(d) { return d.Total; });
var pieSlice = svg.selectAll("g.slice");
d3.csv("childcare.csv", function(error, data) {
data.forEach(function(d) {
d.population = +d.population;
});
var arcs = svg.selectAll("g.slice")
.data(pie(data))
.enter()
.append("g")
.attr("class", "arc")
arcs.append("path")
.attr("d", arc)
.style("fill", function(d) { return color(d.data.place); })
.on("mouseenter", function(d) {
//console.log("mousein")
arcs.append("text")
.attr("transform", arc.centroid(d))
.attr("dy", ".5em")
.style("text-anchor", "middle")
.style("fill", "blue")
.attr("class", "on")
.text(d.data.place);
})
.on("mouseout", function(d) {
console.log("mouseout")
});
});
You can just save your text and remove it on mouseout:
var text;
var arcs = svg.selectAll("g.slice")
.data(pie(data))
.enter()
.append("g")
.attr("class", "arc")
arcs.append("path")
.attr("d", arc)
.style("fill", function(d) { return color(d.data.place); })
.on("mouseenter", function(d) {
//console.log("mousein")
text = arcs.append("text")
.attr("transform", arc.centroid(d))
.attr("dy", ".5em")
.style("text-anchor", "middle")
.style("fill", "blue")
.attr("class", "on")
.text(d.data.place);
})
.on("mouseout", function(d) {
text.remove();
});

Categories