I've added zoom capabilities to my graph with the following code (snippets):
zoomed = () => {
svg.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
};
zoom = d3.behavior.zoom()
.scaleExtent([0.3, 1.5])
.on("zoom", zoomed);
svg = d3.select("body")
.append("svg:svg")
.attr("width", width)
.attr("height", height)
.call(zoom)
.append('svg:g');
This works as expected. I can zoom or pan the entire graph. However, I can no longer effect the position of a node because any "click" triggers panning.
My dragging is defined as follows:
dragStart = function(d) {
d.fixed = true;
};
drag = d3.drag()
.on("dragstart", dragStart);
node = svg.selectAll(".node")
.data(graph.nodes)
.enter()
.append("rect")
.attr("class", "node")
.attr("width", function (d) { return d.width - 2 * pad; })
.attr("height", function (d) { return d.height - 2 * pad; })
.attr("rx", 5).attr("ry", 5)
.call(drag);
I'm not sure what I'm doing wrong but my implementation aligns with all the examples I could find with similar functionality. Any help would be appreciated.
I ended up disabling panning to make it work by disabling click events on zoom:
selection.call(zoom)
.on("mousedown.zoom", null)
Related
Can anybody help me regarding how I can make my following donut chart Clickable? I am just creat a donut chart from some dummy data and want each portion of the donut to be clickable. I am quite new in D3 and finding it hard to incorporate the click function in the donut chart.
I am finding it difficult to make each portion of the donut chart clickable in d3.js. Any help is appreciated. I have added my code snippet here.
function myFunction(width,height,margin,datax,dis,hg) {
var radius = Math.min(width, height) / 2 - margin
var svg = d3.select("#my_dataviz")
.append("svg")
.attr("width", width)
.attr("height", hg)
.append("g")
.attr("transform", "translate(" + width / dis + "," + height / 2 + ")");
var color = d3.scaleOrdinal()
.domain(Object.keys(data))
.range(d3.schemeDark2);
var pie = d3.pie()
.sort(null)
.value(function(d) {return d.value; })
var data_ready = pie(d3.entries(data))
var arc = d3.arc()
.innerRadius(radius * 0.5)
.outerRadius(radius * 0.8)
svg
.selectAll('allSlices')
.data(data_ready)
.enter()
.append('path')
.attr('d', arc)
.attr('fill', function(d){ return(color(d.data.key)) })
.attr("stroke", "white")
.style("stroke-width", "2px")
.style("opacity", 0.7)
; }
<script src="https://d3js.org/d3.v4.js"></script>
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script>
You just need to select all path and bind event click on it
svg.selectAll('path')
.on('click', (d, i, n) => {
console.log(d, i, n)
})
I have a challenging idea to build and couldn't think about a solution yet. The design request to have interactive/draggable graphics, as the one I send by the link below.
However, those graphics elements will be distributed in specific places on the page, with other elements around (Text, images, etc). The idea is to let the user "to play" with the graphics circles, just doing something 'cool and fun'. The user must be able to drag the circles from the graphics and change its visual all along the page.
The problem is: If I place this element in an specific place (inside a div, for example), if we drag the circles outside the 'canvas' area, the elements is no longer visible.
How could I place this canvas-div element in specific place and at the same time to allow the elements inside it to go the outside limited zone?
I thought about putting it in position relative or absolute with 100% height and width of the page, but it will be out of its place in responsive I guess, or pretty complicate to place always at a good place by just using % position. Any suggestion?
I'm using d3.js
Thanks!!
Heres the link: https://codepen.io/A8-XPs/pen/ePWRxZ?editors=0010
HTML
<svg width="500" height="350"></svg>
JS
var svg = d3.select("svg"),
margin = {top: 20, right: 20, bottom: 30, left: 50},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
let points = d3.range(1, 10).map(function(i) {
return [i * width / 10, 50 + Math.random() * (height - 100)];
});
var x = d3.scaleLinear()
.rangeRound([0, width]);
var y = d3.scaleLinear()
.rangeRound([height, 0]);
var xAxis = d3.axisBottom(x),
yAxis = d3.axisLeft(y);
var line = d3.line()
.x(function(d) { return x(d[0]); })
.y(function(d) { return y(d[1]); })
.curve(d3.curveCatmullRom.alpha(0.5))
let drag = d3.drag()
.on('start', dragstarted)
.on('drag', dragged)
.on('end', dragended);
svg.append('rect')
.attr('class', 'zoom')
.attr('cursor', 'move')
.attr('fill', 'none')
.attr('pointer-events', 'all')
.attr('width', width)
.attr('height', height)
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
var focus = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
x.domain(d3.extent(points, function(d) { return d[0]; }));
y.domain(d3.extent(points, function(d) { return d[1]; }));
focus.append("path")
.datum(points)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.attr("stroke-width", 1.5)
.attr("d", line);
focus.selectAll('circle')
.data(points)
.enter()
.append('circle')
.attr('r', 5.0)
.attr('cx', function(d) { return x(d[0]); })
.attr('cy', function(d) { return y(d[1]); })
.style('cursor', 'pointer')
.style('fill', 'steelblue');
focus.selectAll('circle')
.call(drag);
focus.append('g')
.attr('class', 'axis axis--x')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis);
focus.append('g')
.attr('class', 'axis axis--y')
.call(yAxis);
function dragstarted(d) {
d3.select(this).raise().classed('active', true);
}
function dragged(d) {
d[0] = x.invert(d3.event.x);
d[1] = y.invert(d3.event.y);
d3.select(this)
.attr('cx', x(d[0]))
.attr('cy', y(d[1]))
focus.select('path').attr('d', line);
}
function dragended(d) {
d3.select(this).classed('active', false);
}
PS: I got to solve the problem by just applying simple CSS to the SVG:
Overflow: visible;
Hopefully it will work in a real page scenario as well.
I have this code, which creates a horizontal bar chart, and it fits nicely to any screen width , but only when the document loads or reload. When I rotate my mobile to landscape mode it does not re-render again according to the new dimensions of the screen, despite I tried to use a addEventLisenet for resize event.
I'll leave a running snippet of my code so that you can see what I am doing wrong.
Thanks a lot for the help.
var data = [72,70,65,58,51,50,21,15,13]
var categoryData = ["International assignment allowance and benefits.","Tax and social security cost.","Relocation costs.","Prepared at start of assigment.","Assignee-specific data.","Vendor fees.","Incentive compensation (projected).","Employee benefit plan contributions and deduction.","They are rough calculations."/*"They are detailed and precise"/*,"General data (not assignee-specific)","Updated annually","Updated for change in assignement policy","Updated for approved policy exceptions"*/]
var width = $(window).width(),
barHeight = 20,
margin = {left:15,top:15,right:15,bottom:15}
var widthScale = d3.scaleLinear()
.domain(d3.extent(data,function(d){return d }))
.range([50, width - 40]);
var svg = d3.select("body")
.append("svg")
.attr("width", width - margin.left - margin.right)
.attr("height"," 100vh")
.attr("id","SVGCanvas")
var mainTitle = svg.append("text")
.attr("class","mainTitle")
.html("Which of the following statements describe your")
.attr("transform","translate(5,60)")
.attr("font-weight","bold")
var mainTitle2 = svg.append("text")
var mainTitle2 = svg.append("text")
.attr("class","mainTitle2")
.html("approach to cost estimates? (Select all that apply.)")
.attr("transform","translate(5,78)")
.attr("font-weight","bold")
.style("float","left")
var mainBarG = svg.append("g")
.attr("class","mainBarG")
.attr("transform","translate(5,142)")
var g = mainBarG.selectAll("g")
.data(data)
.enter()
.append("g")
.attr("class","bGroup")
.attr("transform", function (d, i) {
return "translate(0," + ((i * barHeight*3) ) + ")";
});
g.append("rect")
.attr("width",0)
.transition()
.duration(800)
.delay(function(d,i){return i*25})
.attr("width", function (d) {
return widthScale(d);
})
.attr("height", barHeight*1.3 )
.attr("class","bars")
.attr("fill","#00338D")
.attr("id", function (d,i) {
return "barra" + i;
})
g.append("text")
.attr("x", function (d) { return widthScale(d) })
.attr("y", barHeight / 2)
.attr("dy", ".6em")
.attr("dx", "-2.5em")
.text(function (d) { return d + '%' })
.attr("font-size", 13)
.attr("font-weight", "bold")
.attr( "rx","20")
.attr( "ry","20")
.attr("class","barsText")
.attr("fill","#FFF")
g.append("text")
.data(categoryData)
.attr("x", 0)
.attr("y", (barHeight / 2) - barHeight *.8)
.attr("font-size", 13)
.attr("class","barsText")
.text(function (d) { return d })
setTimeout(function(){
$(".barsText").animate({opacity: 1});
},900);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://d3js.org/d3.v5.min.js"></script>
I have been trying to get the basics of how I make a pannable zoomable, and click to center zoom on element d3 work. This example is what I want to do but I am having trouble translating it outside of the geo context: https://bl.ocks.org/mbostock/2206340
What I have accomplished is the first two parts pan and zoom, see a basic fiddle here https://jsfiddle.net/e9fbn2xp/
How can I accomplish centering the the circle in the center of the viewable window, so it looks like the circle is zoomed to? Note that although this is a fixed position circle I will eventually have dynamic data, so ideally I could reference the circles position dynamically.
Here is my code:
HTML (note that this is React JSX syntax but that should be irrelevant to question)
<div style={{width: 800}}>
<svg style={{border: '1px solid black'}} id="viz" width="800" height="800">
</svg>
</div>
JAVASCRIPT
var svg = d3.select("#viz")
var width = svg.attr("width");
var height = svg.attr("height");
var testLayer = svg.append('g');
var aRect = testLayer.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("height", 800)
.attr("width", 800)
.attr("fill", 'green');
var aCircle = testLayer.append("circle")
.style("stroke", "gray")
.style("fill", "white")
.attr("r", 40)
.attr("cx", 200)
.attr("cy", 200)
.on("mousedown", zoomToMe);
function zoomToMe(){
console.log("do the zoom")
}
var zoom = d3.zoom()
.scaleExtent([.5, 40])
.translateExtent([[0, 0], [width, height]])
.on("zoom", zoomed);
svg.call(zoom);
function zoomed() {
testLayer.attr("transform", d3.event.transform);
}
svg.on("click", function() {
var coords = d3.mouse(this);
})
I got a working solution and thought I would share the code in case others find it useful. It is a fairly different approach then my original but accomplishes the three goals, pan, mouse zoom, zoom to element. While these are three simple static circles the same concept should work with a dynamic dataset.
fiddle: https://jsfiddle.net/uc7oprx3/5/
HTML
<svg id="viz" width="400" height="400" />
JAVASCRIPT
var zoom = d3.zoom()
.scaleExtent([0.3,2])
.on("zoom", zoomed);
var svg = d3.select("#viz")
var width = svg.attr("width");
var height = svg.attr("height");
var zoomer = svg.append("rect")
.attr("width", width)
.attr("height", height)
.style("fill", "none")
.style("pointer-events", "all")
.call(zoom);
var g = svg.append("g");
var aCircle = g.append("circle")
.style("stroke", "gray")
.style("fill", "white")
.attr("r", 40)
.attr("cx", 200)
.attr("cy", 200)
.on("mousedown", () => centerNode(200, 200));
var bCircle = g.append("circle")
.style("stroke", "gray")
.style("fill", "white")
.attr("r", 40)
.attr("cx", 400)
.attr("cy", 400)
.on("mousedown", () => centerNode(400, 400));
var cCircle = g.append("circle")
.style("stroke", "gray")
.style("fill", "white")
.attr("r", 40)
.attr("cx", 600)
.attr("cy", 600)
.on("mousedown", () => centerNode(600, 600));
function zoomed() {
g.attr("transform", d3.event.transform);
}
function centerNode(xx, yy){
g.transition()
.duration(500)
.attr("transform", "translate(" + (width/2 - xx) + "," + (height/2 - yy) + ")scale(" + 1 + ")")
.on("end", function(){ zoomer.call(zoom.transform, d3.zoomIdentity.translate((width/2 - xx),(height/2 - yy)).scale(1))});
}
I'm trying to get drag functionality to work on D3, and have copied the code directly from the developer's example.
However it seems the origin (what is being clicked) is not being passed correctly into the variable d, which leads to the error: 'Cannot read property 'x' of undefined'
The relevant code:
var drag = d3.behavior.drag()
.on("drag", function(d,i) {
d.x += d3.event.dx
d.y += d3.event.dy
d3.select(this).attr("transform", function(d,i){
return "translate(" + [ d.x,d.y ] + ")"
})
});
var svg = d3.select("body").append("svg")
.attr("width", 1000)
.attr("height", 300);
var group = svg.append("svg:g")
.attr("transform", "translate(10, 10)")
.attr("id", "group");
var rect1 = group.append("svg:rect")
.attr("rx", 6)
.attr("ry", 6)
.attr("x", 5/2)
.attr("y", 5/2)
.attr("id", "rect")
.attr("width", 250)
.attr("height", 125)
.style("fill", 'white')
.style("stroke", d3.scale.category20c())
.style('stroke-width', 5)
.call(drag);
Usually, in D3 you create elements out of some sort of datasets. In your case you have just one (perhaps, one day you'll want more than that). Here's how you can do it:
var data = [{x: 2.5, y: 2.5}], // here's a dataset that has one item in it
rects = group.selectAll('rect').data(data) // do a data join on 'rect' nodes
.enter().append('rect') // for all new items append new nodes with the following attributes:
.attr('x', function (d) { return d.x; })
.attr('y', function (d) { return d.y; })
... // other attributes here to modify
.call(drag);
As for the 'drag' event handler:
var drag = d3.behavior.drag()
.on('drag', function (d) {
d.x += d3.event.dx;
d.y += d3.event.dy;
d3.select(this)
.attr('transform', 'translate(' + d.x + ',' + d.y + ')');
});
Oleg's got it, I just wanted to mention one other thing you might do in your case.
Since you only have a single rect, you can bind data directly to it with .datum() and not bother with computing a join or having an enter selection:
var rect1 = svg.append('rect')
.datum([{x: 2.5, y: 2.5}])
.attr('x', function (d) { return d.x; })
.attr('y', function (d) { return d.y; })
//... other attributes here
.call(drag);