D3.JS - Rotate a pie chart on mouse down event - d3.js

I am looking for an example for to rotate a pie chart on mouse down event. On mouse down, I need to rotate the pie chart either clock wise or anti clock wise direction.
If there is any example how to do this in D3.js, that will help me a lot. I found an example using FusionChart and I want to achieve the same using D3.js

Pretty easy with d3:
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var g = svg.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc");
g.append("path")
.attr("d", arc)
.style("fill", function(d) {
return color(d.data.age);
});
var curAngle = 0;
var interval = null;
svg.on("mousedown", function(d) {
interval = setInterval(goRotate,10);
});
svg.on("mouseup", function(d){
clearInterval(interval);
})
function goRotate() {
curAngle += 1;
svg.attr("transform", "translate(" + width / 2 + "," + height / 2 + ") rotate(" + curAngle + "," + 0 + "," + 0 + ")");
}
Working example.

I did a similar thing with a compass instead of pie chart. You mainly need three methods - each bound to a different mouse event.
Bind this to the mousedown event on your compass circle:
function beginCompassRotate(el) {
var rect = compassCircle[0][0].getBBox(); //compassCircle would be your piechart d3 object
compassMoving = true;
compassCenter = {
x: (rect.width / 2),
y: (rect.height / 2)
}
}
Bind this to the mouse move on your canvas or whatever is holding your pie chart - you can bind it to the circle (your pie chart) but it makes the movement a little glitchy. Binding it to the circle's container keeps it smooth.
function rotateCompass() {
if (compassMoving) {
var mouse = d3.mouse(svg[0][0]);
var p2 = {
x: mouse[0],
y: mouse[1]
};
var newAngle = getAngle(compassCenter, p2) + 90;
//again this v is your pie chart instead of compass
compass.attr("transform", "translate(90,90) rotate(" + newAngle + "," + 0 + "," + 0 + ")");
}
}
Finally bind this to the mouseup on your canvas - again you can bind it to the circle but this way you can end the rotation without the mouse over the circle. If it is on the circle you will keep rotating the circle until you have a mouse up event over the circle.
function endCompassRotate(el) {
compassMoving = false;
}
Here is a jsfiddle showing it working: http://jsfiddle.net/4oy2ggdt/

Related

D3 v4 Update Object Position on Window Resize

I'm working with d3 v4 and struggling to create a simple chart, to use as a reference for further development, which both utilises the d3.zoom() function and the chart resizes to fit the browser window. Whenever I get one of these working the other breaks. The Typescript code is within an Angular 4 component but that shouldn't have an impact.
First I initialise some properties:
// Create a variable to hold the DOM Element which the chart will be attached to.
this.element = this.chartContainer.nativeElement;
// Set the width and height
this.width = this.svgWidth - this.margin.left - this.margin.right;
this.height = this.svgHeight - this.margin.top - this.margin.bottom;
// create scale objects
this.xScale = d3.scaleLinear()
.domain([0, 100])
.range([0, this.width]);
this.yScale = d3.scaleLinear()
.domain([-10, -20])
.range([this.height, 0]);
// create axis objects
this.xAxis = d3.axisBottom(this.xScale);
this.yAxis = d3.axisLeft(this.yScale);
Then I define my zoomFunction():
this.zoomFunction = () => {
if (d3.event.transform != null) {
//console.log(d3.event.transform);
if (this.xScale != null) {
// create new scale ojects based on event
let new_xScale = d3.event.transform.rescaleX(this.xScale);
let new_yScale = d3.event.transform.rescaleY(this.yScale);
// update axes
this.xChartAxis.call(this.xAxis.scale(new_xScale));
this.yChartAxis.call(this.yAxis.scale(new_yScale));
// update circle
this.circles.attr("transform", d3.event.transform)
};
};
};
Define my zoom() function:
this.zoom = d3.zoom().on("zoom", this.zoomFunction);
Define my windowResize() function:
this.onWindowResize =() => {
// Set the width and height.
// The element.offsetWidth is the total width of the DOM element including scrollbars, borders etc.
this.svgWidth = this.element.offsetWidth;
this.svgHeight = this.element.offsetHeight;
// Update the width and height which the chart fits into.
this.width = this.element.offsetWidth - this.margin.left - this.margin.right;
this.height = this.element.offsetHeight - this.margin.top - this.margin.bottom;
// Update the width of the svgViewport.
this.svgViewport.attr("width", this.svgWidth);
this.svgViewport.attr("height", this.svgHeight);
// Update the size of the zoomView
this.zoomView
.attr("width", this.width)
.attr("height", this.height);
// Update the Range of the x scale.
this.xScale.range([0, this.width]);
this.yScale.range([0, this.height]);
// Update the axis.
this.innerSpace.select(".axisY")
.call(this.yAxis);
this.innerSpace.select(".axisX")
.attr("transform", "translate(0," + this.height + ")")
.call(this.xAxis);
};
Add an EventListener:
window.addEventListener("resize", this.onWindowResize);
Finally create my chart:
createChart() {
// Create a circle
this.originalCircle = {
"cx": 50,
"cy": -15,
"r": 20
};
// Append an svg object to the chart element and save in the variable 'svgViewport'.
this.svgViewport = d3.select(this.element).append('svg')
.attr("class", "chart")
.attr("width", this.svgWidth)
.attr("height", this.svgHeight);
// Inner Drawing Space
this.innerSpace = this.svgViewport.append("g")
.attr("class", "inner-space")
.attr("transform", "translate(" + this.margin.left + "," + this.margin.top + ")")
.call(this.zoom);
// Add the circle.
this.circles = this.innerSpace.append('circle')
.attr("id", "circles")
.attr("cx", this.xScale(this.originalCircle.cx))
.attr("cy", this.yScale(this.originalCircle.cy))
.attr('r', this.originalCircle.r);
// Draw Axis
this.xChartAxis = this.innerSpace.append("g")
.attr("class", "axisX")
.attr("transform", "translate(0," + this.height + ")")
.call(this.xAxis);
this.yChartAxis = this.innerSpace.append("g")
.attr("class", "axisY")
.call(this.yAxis);
// append zoom area
this.zoomView = this.innerSpace.append("rect")
.attr("class", "zoom")
.attr("width", this.width)
.attr("height", this.height)
.call(this.zoom);
};
The curious thing is that if I refresh the browser and resize the window the axis move and resize as expected, but the circle does not move. When I pan/zoom the circle moves and scales correctly and the axis move correctly. Having pan/zoomed and then resize the window the axis do not move any more and the circle remains static.
I think I am close, but I can't see the next step. Any suggestions very welcome.

How to limit the d3.js drag behavior on a mercator map

I have implemented a map in a svg-Element with the mercator-function of d3.js.
The Map represented only a specific city - everything outside the city-map is not relevant for my project.
Now I want to implement some function like zoom() and drag() on this map.
But I can not find any solution, to handle the drag()-function with my mercator-map - I can not limit the area of the transformation.
(the user should not be able to drag the map outside the map of the city)
For normal svg-elements this works:
var drag = d3.behavior.drag()
.on("drag", dragmove);
function dragmove(d) {
var x = Math.max(0, Math.min('width-of-svg-element', d3.event.x));
var y = Math.max(0, Math.min('height-of-svg-element', d3.event.y));
d3.select(this)
.attr("transform", "translate(" + x + "," + y + ")");
}
But it doesn't work for my mercator-map.
please help me :(
Thanks in advance!
Here is a small example:
https://jsfiddle.net/c55w7u9e/
My intention is, that there should be no possibility for the user to drag the red circle (in this example, but in my project the map) outside the svg element...
Updated fiddle : https://jsfiddle.net/thatoneguy/c55w7u9e/4/
You werent taking your radius into account. It was working, but you can't see it. Update drag to this :
function dragmove(d) {
var x = Math.max(0, Math.min(width - radius, d3.event.x));
var y = Math.max(0, Math.min(height - radius, d3.event.y));
d3.select(this)
.attr("transform", "translate(" + x + "," + y + ")");
}
Help from this question : Explaining Mike Bostock's d3.js dragmove function
And this example : http://bl.ocks.org/mbostock/1557377 //think its offline ...
I would use this function to limit, it works better :
function dragmove(d) {
d3.select(this)
.attr("cx", d.x = Math.max(radius, Math.min(width - radius, d3.event.x)))
.attr("cy", d.y = Math.max(radius, Math.min(height - radius, d3.event.y)));
}
Updated fiddle from examples : https://jsfiddle.net/thatoneguy/c55w7u9e/5/

How to add padAngle to one edge of arc using d3.js

Can anyone tell me How to add padAngle to one edge of arc using d3.js ?
I have created donut chart using d3.js. And found that there is an option to add space between arcs using .padAngle(). I achieved it. As per my requirement I need to add padding only one side of arc.
Can anyone suggest me how can I achieve it ?
As per my requirement, I should add extra padding for every 3 arcs. So I am passing padding using paddingFunction(it's customized) on top of arc. But it's adding both sides.
Below is my code:
var data = [46.00, 67, 50.00, 10.00,30.00,40.00],
color = ['red', 'green', 'black', 'pink','blue','orange'],
grpStrk = [0.1, 0.1, 0.5, 0.1, 0.1, 0.5],
width = '272',
height = '272',
radius = height/2 - 11;
/* function to render donut chart as per values and colors */
function renderSavingsDonutChart(data, color, grpStrk) {
try {
d3
} catch (err) {
return false;
}
/* function to return padding for chart arc based on flag to differenciate groups */
var paddingFunction = function(d,i) {
return grpStrk[i];
};
var arc = d3.svg.arc().innerRadius(radius-20).outerRadius(radius);
var pie = d3.layout.pie().sort(null);
if(isMobile()){
width = 257;
height = 257;
var arc = d3.svg.arc().innerRadius(radius-25).outerRadius(radius);
var forVbWidth = width-4;
var forVbHeight = height-4;
var svg = d3.select("#results-donut-chart").append("svg").attr("width", width).attr("height", height).attr("viewBox","2 2 " + forVbWidth + " " + forVbHeight).attr("preserveAspectRatio","xMidYMid").append("g").attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
}else{
var svg = d3.select("#results-donut-chart").append("svg").attr("width", width).attr("height", height).attr("viewBox","9 9 " + (width-19) + " " + (height-19)).attr("preserveAspectRatio","xMidYMid").append("g").attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
}
svg.selectAll("path")
.data(pie(data))
.enter()
.append("path")
.style(
{
"fill": function(d, i) {
return color[i];
}
})
//.attr("d", arc);
.attr("d", arc.padAngle(paddingFunction));
//On Resize
var aspect = width / height,
chart = $("#results-donut-chart svg");
$(window).on("resize load", function() {
var targetWidth = chart.parent().width();
chart.attr("width", targetWidth);
chart.attr("height", targetWidth / aspect);
});
}
/*Calling savings donut on page load*/
renderSavingsDonutChart(data, color, grpStrk);

D3 pie chart element popout

I have created a simple pie chart using D3.js and I wish to pop out each element/path of the pie chart on click event of those elements.
Here is the pie chart I am talking about: jsfiddle.net/ankur881120/kt97oq57.
arcs.filter(function(d) { return d.endAngle - d.startAngle > .2; }).append("svg:text")
.attr("dy", ".35em")
.attr("text-anchor", "middle")
//.attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")rotate(" + angle(d) + ")"; })
.attr("transform", function(d) { //set the label's origin to the center of the arc
//we have to make sure to set these before calling arc.centroid
d.outerRadius = outerRadius; // Set Outer Coordinate
d.innerRadius = outerRadius/2; // Set Inner Coordinate
return "translate(" + arc.centroid(d) + ")rotate(" + angle(d) + ")";
Now I want to pop out say element in red color on click of red color element.
Looking for all of your suggestions, to solve this issue.
I just answered a very similar question about this yesterday. Your use case is different enough, so against my better judgement, I'll answer it again.
Essentially, add the click handler and transition your arc "group" (arc and text labels) together:
var arcs = vis.selectAll("g.slice")
// Associate the generated pie data (an array of arcs, each having startAngle,
// endAngle and value properties)
.data(pie)
// This will create <g> elements for every "extra" data element that should be associated
// with a selection. The result is creating a <g> for every object in the data array
.enter()
// Create a group to hold each slice (we will have a <path> and a <text>
// element associated with each slice)
.append("svg:g")
.attr("class", "slice") //allow us to style things in the slices (like text)
// ADDED CLICK HANDLER
.on('click',function(d,i){
d3.select(this)
.transition()
.duration(500)
.attr("transform",function(d){
// this this group expanded out?
if (!d.data._expanded){
d.data._expanded = true;
var a = d.startAngle + (d.endAngle - d.startAngle)/2 - Math.PI/2;
var x = Math.cos(a) * 20;
var y = Math.sin(a) * 20;
// move it away from the circle center
return 'translate(' + x + ',' + y + ')';
} else {
d.data._expanded = false;
// move it back
return 'translate(0,0)';
}
});
});
Updated fiddle.
Complete code:
var canvasWidth = 300, //width
canvasHeight = 300, //height
outerRadius = 100, //radius
color = d3.scale.category20(); //builtin range of colors
var dataSet = [
{"legendLabel":"One", "magnitude":20},
{"legendLabel":"Two", "magnitude":40},
{"legendLabel":"Three", "magnitude":50},
{"legendLabel":"Four", "magnitude":16},
{"legendLabel":"Five", "magnitude":50},
{"legendLabel":"Six", "magnitude":8},
{"legendLabel":"Seven", "magnitude":30}];
var vis = d3.select("body")
.append("svg:svg") //create the SVG element inside the <body>
.data([dataSet]) //associate our data with the document
.attr("width", canvasWidth) //set the width of the canvas
.attr("height", canvasHeight) //set the height of the canvas
.append("svg:g") //make a group to hold our pie chart
.attr("transform", "translate(" + 1.5*outerRadius + "," + 1.5*outerRadius + ")") // relocate center of pie to 'outerRadius,outerRadius'
// This will create <path> elements for us using arc data...
var arc = d3.svg.arc()
.outerRadius(outerRadius);
var pie = d3.layout.pie() //this will create arc data for us given a list of values
.value(function(d) { return d.magnitude; }) // Binding each value to the pie
.sort( function(d) { return null; } );
// Select all <g> elements with class slice (there aren't any yet)
var arcs = vis.selectAll("g.slice")
// Associate the generated pie data (an array of arcs, each having startAngle,
// endAngle and value properties)
.data(pie)
// This will create <g> elements for every "extra" data element that should be associated
// with a selection. The result is creating a <g> for every object in the data array
.enter()
// Create a group to hold each slice (we will have a <path> and a <text>
// element associated with each slice)
.append("svg:g")
.attr("class", "slice") //allow us to style things in the slices (like text)
.on('click',function(d,i){
d3.select(this)
.transition()
.duration(500)
.attr("transform",function(d){
if (!d.data._expanded){
d.data._expanded = true;
var a = d.startAngle + (d.endAngle - d.startAngle)/2 - Math.PI/2;
var x = Math.cos(a) * 20;
var y = Math.sin(a) * 20;
return 'translate(' + x + ',' + y + ')';
} else {
d.data._expanded = false;
return 'translate(0,0)';
}
});
});
arcs.append("svg:path")
//set the color for each slice to be chosen from the color function defined above
.attr("fill", function(d, i) { return color(i); } )
//this creates the actual SVG path using the associated data (pie) with the arc drawing function
.attr("d", arc);
// Add a legendLabel to each arc slice...
arcs.append("svg:text")
.attr("transform", function(d) { //set the label's origin to the center of the arc
//we have to make sure to set these before calling arc.centroid
d.outerRadius = outerRadius + 50; // Set Outer Coordinate
d.innerRadius = outerRadius + 45; // Set Inner Coordinate
return "translate(" + arc.centroid(d) + ")";
})
.attr("text-anchor", "middle") //center the text on it's origin
.style("fill", "Purple")
.style("font", "bold 12px Arial")
.text(function(d, i) { return dataSet[i].legendLabel; }); //get the label from our original data array
// Add a magnitude value to the larger arcs, translated to the arc centroid and rotated.
arcs.filter(function(d) { return d.endAngle - d.startAngle > .2; }).append("svg:text")
.attr("dy", ".35em")
.attr("text-anchor", "middle")
//.attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")rotate(" + angle(d) + ")"; })
.attr("transform", function(d) { //set the label's origin to the center of the arc
//we have to make sure to set these before calling arc.centroid
d.outerRadius = outerRadius; // Set Outer Coordinate
d.innerRadius = outerRadius/2; // Set Inner Coordinate
return "translate(" + arc.centroid(d) + ")rotate(" + angle(d) + ")";
})
.style("fill", "White")
.style("font", "bold 12px Arial")
.text(function(d) { return d.data.magnitude; });
// Computes the angle of an arc, converting from radians to degrees.
function angle(d) {
var a = (d.startAngle + d.endAngle) * 90 / Math.PI - 90;
return a > 90 ? a - 180 : a;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

d3.js On zoom, how do you have it not effect the size of the node?

When I zoom in on my force graph, I want to ignore the nodes as I am the links and the text. I am having a hard time figuring out how. (I am using different SVG shapes so I can't scale "r".) Here is my node code:
//node declared as global
node = svg.selectAll(".node")
.data(force.nodes()).enter().append("path")
.attr("class", "node")
.attr("d", d3.svg.symbol().type(function (d) { return d.Shape; }).size(150))
And the zoom code:
function redraw(scale, trans) {
//store the last event data
trans = d3.event.translate;
scale = d3.event.scale;
text.style("font-size", (fontSize / scale) + "px");
textEvidenceCtr.style("font-size", (fontSize / scale) + "px");
$("line.link").css("stroke-width", getStrokeWidth); // Function so it runs for each element individually
//transform the vis
svg.attr("transform",
"translate(" + trans + ")"
+ " scale(" + scale + ")"); }
Thanks!

Resources