d3.js geoGraticule doesn't follow the sphere shape - d3.js

I am trying to add standard geoGraticule lines to my globe, which spins and has ripple effects. I've gone wrong somewhere but can't get to the bottom of it: live demo
The meridian lines seem to create a sort of diamond shape, rather than curving around the whole sphere.
How can I change my code so the graticule is spherical?
// margin calculations
const margin = {top: 50, right: 0, bottom: 50, left: 0};
const width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
const config = {
speed: 0.005,
verticalTilt: -30,
horizontalTilt: 0
}
// add an SVG to the body and a g group
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var g = svg.append("g");
// draw a globe I guess
d3.json("https://unpkg.com/world-atlas#1.1.4/world/110m.json", (error, world) => {
// draw the land using TOPOJSON package
var land = topojson.feature(world, world.objects.land);
// d3 3d globe projection
var projection = d3.geoOrthographic()
.fitSize([width, height], land)
.clipAngle(90)
.precision(0)
.scale(200);
// create projection of earth sphere
var path = d3.geoPath()
.projection(projection);
// load the ripple circle
var geoCircle = d3.geoCircle();
// load the meridian lines
var graticule = d3.geoGraticule();
// draw a grey circle to look like water
var ocean = g.append("circle")
.attr("class", "ocean")
.attr("cx", width / 2)
.attr("cy", height / 2)
.attr("r", height / 2);
// draw countries
var earth = g.append("path")
.datum(land)
.attr("d", path);
// draw graticules
var lines = g.append("path")
.datum(graticule)
.attr("class", "graticule")
.attr("d", path)
.style("fill-opacity", 0)
.style("stroke", "#ccc")
.style("stroke-opacity", 0.7);
// list of locations - reverse the lat / long numbers!
var locale = [
[37.5999, 14.0153],
[10.1278, 50.5074],
[0.1278, 51.5074],
[25.653906, 4.453784],
[0, -10],
[151.2012775, -33.8844644]
];
// draw the circles
function drawMarkers() {
for (var i = 0; i < locale.length; i++) {
// console.log("location " + (i+1) + ": " + locale[i]);
center = locale[i];
var circ = g.append("path")
.datum({endAngle: 0})
.attr("class", "geoCircle")
.attr("fill", "#000")
.attr("fill-opacity", 1)
.attr("d", d => path(geoCircle.center(center).radius(1)()));
}
}
// draw the ripples
setInterval(() => {
for (var i = 0; i < locale.length; i++) {
center = locale[i];
var circ = g.append("path")
.datum({endAngle: 0})
.attr("class", "ripple ripple" + i)
.attr("fill", "#000")
.attr("fill-opacity", 0.2)
.attr("opacity", 1)
.attr("d", d => path(geoCircle.center(center).radius(2)()))
.transition()
.delay(0)
.duration(2800)
.ease(d3.easeLinear)
.attr("opacity", 0)
.attrTween("d", geoCircleTween(12, center))
.remove();
function geoCircleTween(newAngle, loca) {
return function(d) {
var interpolate = d3.interpolate(d.endAngle, newAngle);
return function(t) {
d.endAngle = interpolate(t);
return path(geoCircle.center(loca).radius(d.endAngle)());
};
};
}
}
}, 500)
function enableRotation() {
d3.timer(function (elapsed) {
projection.rotate([config.speed * elapsed - 120, config.verticalTilt, config.horizontalTilt]);
svg.selectAll("path").attr("d", path);
drawMarkers();
});
}
enableRotation();
});

Related

d3 incompatible types - version conflict?

I am starting out with d3 and would like to test it in my angular project. I've tried to run this doughnut chart from a reputable source: https://d3-graph-gallery.com/graph/donut_label.html
I am experiencing significant problems with incompatible types, even though I selected v6 on page and I am using d3 v6.0.0 on my machine. For example the line :
const data_ready = pie(Object.entries(data))
gives complaint that:
Argument of type [string, number][] is not assignable to parameter of type (number|{valueOf():number;})[]
Moving forward at
.attr('d', arc)
complains that no overload matches this call
in package.json I have:
dependencies:{
"d3": "6.0.0",
"d3-scale": "^4.0.2",
...
},
devDependencies:{
"#types/d3": "6.0.0",
"#types/d3-scale": "^4.0.2",
...
}
Usually it is a red flag when examples don't work, but the source seem reputable so I am asking for additional debugging help. Is this a problem with #types configuration? How should the code look like? Complete code:
ngAfterViewInit(){
// set the dimensions and margins of the graph
var width = 450
height = 450
margin = 40
// The radius of the pieplot is half the width or half the height (smallest one). I subtract a bit of margin.
var radius = Math.min(width, height) / 2 - margin
// append the svg object to the div called 'my_dataviz'
var svg = d3.select("#my_dataviz")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
// Create dummy data
var data = {a: 9, b: 20, c:30, d:8, e:12, f:3, g:7, h:14}
// set the color scale
var color = d3.scaleOrdinal()
.domain(["a", "b", "c", "d", "e", "f", "g", "h"])
.range(d3.schemeDark2);
// Compute the position of each group on the pie:
var pie = d3.pie()
.sort(null) // Do not sort group by size
.value(function(d) {return d.value; })
var data_ready = pie(d3.entries(data))
// The arc generator
var arc = d3.arc()
.innerRadius(radius * 0.5) // This is the size of the donut hole
.outerRadius(radius * 0.8)
// Another arc that won't be drawn. Just for labels positioning
var outerArc = d3.arc()
.innerRadius(radius * 0.9)
.outerRadius(radius * 0.9)
// Build the pie chart: Basically, each part of the pie is a path that we build using the arc function.
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)
// Add the polylines between chart and labels:
svg
.selectAll('allPolylines')
.data(data_ready)
.enter()
.append('polyline')
.attr("stroke", "black")
.style("fill", "none")
.attr("stroke-width", 1)
.attr('points', function(d) {
var posA = arc.centroid(d) // line insertion in the slice
var posB = outerArc.centroid(d) // line break: we use the other arc generator that has been built only for that
var posC = outerArc.centroid(d); // Label position = almost the same as posB
var midangle = d.startAngle + (d.endAngle - d.startAngle) / 2 // we need the angle to see if the X position will be at the extreme right or extreme left
posC[0] = radius * 0.95 * (midangle < Math.PI ? 1 : -1); // multiply by 1 or -1 to put it on the right or on the left
return [posA, posB, posC]
})
// Add the polylines between chart and labels:
svg
.selectAll('allLabels')
.data(data_ready)
.enter()
.append('text')
.text( function(d) { console.log(d.data.key) ; return d.data.key } )
.attr('transform', function(d) {
var pos = outerArc.centroid(d);
var midangle = d.startAngle + (d.endAngle - d.startAngle) / 2
pos[0] = radius * 0.99 * (midangle < Math.PI ? 1 : -1);
return 'translate(' + pos + ')';
})
.style('text-anchor', function(d) {
var midangle = d.startAngle + (d.endAngle - d.startAngle) / 2
return (midangle < Math.PI ? 'start' : 'end')
})
}

semicircle bar graphs with extended edges using d3.js

I'm trying to draw a d3 chart with extended edges like in the image, "this is the link to the design"
I was able to achieve a semi circle in the same fashion, but I'm a little confused how to do the extended edge, this is the code for what I have done so far, link to codepen
JS:
var width = 300,
height = 300;
var twoPi = Math.PI; // Full circle
var formatPercent = d3.format(".0%");
const color = [
"#F9C969",
"#FB8798",
"#51D6D8",
"#B192FD",
"#509FFD",
"#5B65B7"
];
console.log(d3.schemeCategory10);
var data = [
{ count: 1000 },
{ count: 800 },
{ count: 800 },
{ count: 700 },
{ count: 900 },
{ count: 600 }
];
var percent = d3.max(data, function (d) {
return +d.count / 10;
});
var max = d3.max(data, function (d) {
return +d.count;
});
var baseRad = 0.25,
cgap = 12,
maxVal = max + percent;
var cx1 = width / 2.5;
var cy1 = height / 2.5;
var cl = "c0";
var ind = 0;
var rad;
var rad2;
rad = baseRad;
rad2 = baseRad;
var svg = d3
.select("body")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 10 + "," + height / 10 + ")");
var svg2 = d3
.select("svg")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 10 + "," + height / 10 + ")");
svg2
.selectAll("path")
.data(data)
.enter()
.append("path")
// .each(drawBackArc)
.each(drawArc)
.style("fill", function (d, i) {
return color[i % 6];
});
svg
.selectAll("path")
.data(data)
.enter()
.append("path")
// .each(drawBackArc)
.each(drawBackArc)
.style("fill", "#F1F1F1");
// .attr("ax", "-100px")
// .attr("ay", "-100px");
function drawArc(d, i) {
console.log(d, i);
var ratio = d.count / maxVal;
var arc = d3.svg
.arc()
.startAngle(3.14159)
// .(true)
.endAngle(6.28319 * ratio)
.innerRadius(72 + cgap * rad)
.outerRadius(80 + cgap * rad);
d3.select(this)
.attr("transform", "translate(" + cx1 + "," + cy1 + ")")
.attr("d", arc)
.style("fill", function (d, i) {
return color[i % 6];
});
rad++;
}
function drawBackArc(d, i) {
var ratio = d.count / maxVal;
var arc = d3.svg
.arc()
.startAngle(twoPi)
// .(true)
.endAngle(twoPi * 2)
.innerRadius(72 + cgap * rad2)
.outerRadius(80 + cgap * rad2);
d3.select(this)
.attr("transform", "translate(" + cx1 + "," + cy1 + ")")
.attr("d", arc)
.style("fill", "#F1F1F1");
rad2++;
}
HTML:
<script src="https://d3js.org/d3.v3.min.js"></script>
<body></body>
CSS:
body{background-color: #fff;margin: 1.5rem 6rem}
I have seen tutorial explaining how to draw different shapes in d3.js and I can think of drawing a rectangle shape at one end to achieve the design, but even then the issue is how to get the data in both the separate shapes, is it possible in d3? if not please suggest any other possible ways if any.
Thanks
Since you know your center point, you added 2 translations (30,30) and (120,120), so your center point is 150,150
Now you can get the end points of all the arcs, x value be same as centerpoint and y after adjusting radius.
Added below changes to your code Please adjust your graph for length and width of the line. Also add the length of the line to the lenght of arc to get correct percantage and overlap with filled line same as below with desired length if percentage increase the length of an arc
var centerPoint = [150, 150] //added for translation
var radius = 72 + cgap * rad2;
gLines.append("line")
.attr("x1", centerPoint[0])
.attr("x2", centerPoint[0] + 140) // Add length of the bar
.attr("y1", centerPoint[0] - radius + 16)
.attr("y2", centerPoint[0] - radius + 16) // This will adjust line width and inner and outer radius
.style("stroke", "#F2F2F2")
.style("stroke-width", "8");
var width = 300,
height = 300;
var twoPi = Math.PI; // Full circle
var formatPercent = d3.format(".0%");
const color = [
"#F9C969",
"#FB8798",
"#51D6D8",
"#B192FD",
"#509FFD",
"#5B65B7"
];
console.log(d3.schemeCategory10);
var data = [{
count: 500,
color: "#F9C969"
},
{
count: 800,
color: "#FB8798"
},
{
count: 800,
color: "#51D6D8"
},
{
count: 700,
color: "#B192FD"
},
{
count: 900,
color: "#509FFD"
},
{
count: 600,
color: "#5B65B7"
}
];
var percent = d3.max(data, function(d) {
return +d.count / 10;
});
var max = d3.max(data, function(d) {
return +d.count;
});
var baseRad = 0.25,
cgap = 12,
maxVal = max + percent;
var cx1 = width / 2.5;
var cy1 = height / 2.5;
var cl = "c0";
var ind = 0;
var rad;
var rad2;
rad = baseRad;
rad2 = baseRad;
var svg = d3
.select("body")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 10 + "," + height / 10 + ")");
var svg2 = d3
.select("svg")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 10 + "," + height / 10 + ")");
var gLines = d3.select("svg").append("g");
svg2
.selectAll("path")
.data(data)
.enter()
.append("path")
// .each(drawBackArc)
.each(drawArc)
.style("fill", function(d, i) {
return color[i % 6];
});
svg
.selectAll("path")
.data(data)
.enter()
.append("path")
// .each(drawBackArc)
.each(drawBackArc)
.style("fill", "#F1F1F1");
// .attr("ax", "-100px")
// .attr("ay", "-100px");
function drawArc(d, i) {
console.log(d, i);
var ratio = (d.count * 2) / maxVal;
console.log(ratio);
var arc = d3.svg
.arc()
.startAngle(twoPi)
// .(true)
.endAngle(twoPi * ratio)
.innerRadius(72 + cgap * rad)
.outerRadius(80 + cgap * rad);
d3.select(this)
.attr("transform", "translate(" + cx1 + "," + cy1 + ")")
.attr("d", arc)
.style("fill", function(d, i) {
return color[i % 6];
});
rad++;
}
function drawBackArc(d, i) {
var ratio = d.count / maxVal;
var arc = d3.svg
.arc()
.startAngle(twoPi)
// .(true)
.endAngle(twoPi * 2)
.innerRadius(72 + cgap * rad2 - 20)
.outerRadius(80 + cgap * rad2 - 20);
d3.select(this)
.attr("transform", "translate(" + cx1 + "," + cy1 + ")")
.attr("d", arc)
.style("fill", "#F1F1F1");
var centerPoint = [150, 150] //added for translation
var radius = 72 + cgap * rad2;
gLines.append("line")
.attr("x1", centerPoint[0])
.attr("x2", centerPoint[0] + 140) // Add Width of the
.attr("y1", centerPoint[0] - radius + 16)
.attr("y2", centerPoint[0] - radius + 16)
.style("stroke", "#F2F2F2")
.style("stroke-width", "8");
rad2++;
}
<script src="https://d3js.org/d3.v3.min.js"></script>
<body></body>

axis scale shift in d3.js

I am fairly new to d3.js
I am looking for a way to animate both x and y axises based on the new data. So it is more of a real time animation where the x axis is moving and the new data pops out from the right and y axis get updated dynamically as well and after a while the old data dissapear because I have so many data points.
I have this chart already made. https://jsfiddle.net/elvalencian/mfLjovx9/4/
// set the dimensions and margins of the graph
const margin = {
top: 40,
right: 80,
bottom: 60,
left: 50
},
width = 600 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
// append the svg object to the body of the page
const svg = d3
.select("#root")
.append("svg")
.attr(
"viewBox",
`0 0 ${width + margin.left + margin.right} ${
height + margin.top + margin.bottom}`)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
//Read the data
d3.csv("https://raw.githubusercontent.com/sultanmalki/d3js/main/saudi_fdi.csv",
// When reading the csv, I must format variables:
function(d) {
return {
date: d3.timeParse("%Y")(d.date),
value: d.value
}
},
// Now I can use this dataset:
function(data) {
// Add X axis --> it is a date format
var x = d3.scaleTime()
.domain(d3.extent(data, function(d) {
return d.date;
}))
.range([0, width]);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.attr("class", "axis")
.transition().duration(5000)
.call(d3.axisBottom(x));
// Add Y axis
var y = d3.scaleLinear()
.domain([0, d3.max(data, function(d) {
return +d.value;
})])
.range([height, 0]);
svg.append("g")
.attr("class", "axis")
.transition()
.ease(d3.easeLinear)
.duration(5000)
.call(d3.axisLeft(y));
// Add the line
const linePath = svg
.append("path")
.datum(data)
.attr("fill", "none")
.attr("stroke", "#00B0F1")
.attr("stroke-width", 1.5)
.attr("d", d3.line().curve(d3.curveCardinal)
.x(function(d) {
return x(d.date)
})
.y(function(d) {
return y(d.value)
})
)
const pathLength = linePath.node().getTotalLength();
linePath
.attr("stroke-dasharray", pathLength)
.attr("stroke-dashoffset", pathLength)
.attr("stroke-width", 3)
.transition()
.attr("transform", "translate(" + ")")
.duration(5000)
.attr("stroke-width", 3)
.attr("stroke-dashoffset", 0);
})
I would really appreciate any help.
thank you in advance
Lines are rather difficult to animate, since they are only one path object instead of multiple objects as e. g. in a bar or scatter plot. You are already using the stroke-dasharray attribute for the animation of the static data. When there is new data, you need to
Rescale the axes:
To achieve this, compute the domain for x and y and set it via the domain method. Then re-render the axes with call(AxisObject) using a transition. Use the same transition t for both x and y.
Rescale the existing line
With the rescaled axes, also the existing line path must be rescaled. This works smoothly by transitioning the d attribute using the transition t before binding the new data.
Add new data
Wait till the end of transition t to bind the new data to the line path. Before doing that, calculate getTotalLength in order to set stroke-dasharray such that the new data is initially hidden. Then transition stroke-dasharray to the new path length. As the second value for stroke-dasharray I used 9999 which must be chosen longer than the maximal expected path length of the new data.
// Some stuff to generate random time series
// Standard Normal variate using Box-Muller transform.
function randn() {
let u = 0, v = 0;
while (u === 0) u = Math.random();
while (v === 0) v = Math.random();
return Math.sqrt(-2.0 * Math.log(u)) * Math.cos(2.0 * Math.PI * v);
}
// Simulate geometric brownian motion
const mu = 0.8;
const sigma = 0.5;
function simulate() {
const prev = data[data.length - 1];
const x = prev.x + 0.01;
const bm = prev.bm + Math.sqrt(0.01) * randn();
data.push({
x: x,
bm: bm,
y: Math.exp((mu - sigma * sigma / 2) * x + sigma * bm)
});
}
// Initial data
let data = [{
x: 0,
bm: 0,
y: 1,
}];
// Add data to chart in chunks
const blockSize = 20;
let blockCounter = 0;
function addData() {
simulate();
blockCounter += 1;
if (blockCounter === blockSize) {
render(data.slice());
blockCounter = 0;
}
}
// Chart definitions
const width = 500,
height = 180,
marginLeft = 30,
marginRight = 10,
marginBottom = 30,
marginTop = 10;
const svg = d3.select("svg")
.attr("width", width)
.attr("height", height);
const xSlidingWindow = 2;
const x = d3.scaleLinear()
.range([marginLeft, width - marginRight]);
const y = d3.scaleLinear()
.range([height - marginBottom, marginTop]);
const xAxis = d3.axisBottom(x);
const yAxis = d3.axisLeft(y).ticks(3);
const line = d3.line()
.x(d => x(d.x))
.y(d => y(d.y));
const gx = svg.append("g")
.attr("transform", `translate(0,${height - marginBottom})`);
const gy = svg.append("g")
.attr("transform", `translate(${marginLeft},0)`);
// Clip path to only show lines inside the axes
const clipPath = svg.append("clipPath")
.attr("id", "clip-rect")
.append("rect")
.attr("x", marginLeft)
.attr("y", marginTop)
.attr("width", width - marginLeft - marginRight)
.attr("height", height - marginTop - marginBottom);
const path = svg.append("path")
.datum(data.slice())
.attr("clip-path", "url(#clip-rect)")
.attr("fill", "none")
.attr("stroke", "blue")
.attr("stroke-width", 2)
.attr("stroke-dasharray", "0, 9999");
function render(arr) {
// compute domain
const xMax = d3.max(arr, d => d.x);
x.domain([Math.max(xMax - xSlidingWindow, 0), Math.max(xSlidingWindow, xMax)]);
y.domain(d3.extent(arr, d => d.y));
// First, transition the axes
const t = d3.transition().duration(interval * blockSize / 2);
gx.transition(t).call(xAxis);
gy.transition(t).call(yAxis);
path.transition(t).attr("d", line);
t.on("end", () => {
// Then add new data
let pathLength = path.node().getTotalLength();
path.datum(arr)
.attr("stroke-dasharray", `${pathLength}, 9999`)
.attr("d", line);
pathLength = path.node().getTotalLength();
path.transition().duration(interval * blockSize / 2)
.attr("stroke-dasharray", `${pathLength}, 9999`)
.attr("d", line);
});
}
// Interval for data simulation
let intervalId;
const interval = 50;
function startStream() {
if (!intervalId) {
intervalId = setInterval(addData, interval);
}
}
function stopStream() {
clearInterval(intervalId);
intervalId = null;
}
function reset() {
clearInterval(intervalId);
data = [{
x: 0,
bm: 0,
y: 1,
}];
intervalId = setInterval(addData, interval);
}
d3.select("#start").on("click", startStream);
d3.select("#stop").on("click", stopStream);
d3.select("#reset").on("click", reset);
render(data.slice());
startStream();
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.3.0/d3.min.js"></script>
<div>
<button id="start">Start</button>
<button id="stop">Stop</button>
<button id="reset">Reset</button>
</div>
<svg></svg>

How to add image at the end of Arc

I am using d3 js i have to show image at the end of the arc how can i achieve that below is my example
var total_codes = 8;
var remaining_codes = 4;
var issued = total_codes - remaining_codes;
var coloursArray = ["#128ED2", "#dadada"];
var dataset = {
privileges: [issued, remaining_codes]
};
var width = 160,
height = 160,
radius = Math.min(width, height) / 2;
var color = d3.scale.ordinal()
.range(coloursArray);
var pie = d3.layout.pie()
.sort(null);
var arc = d3.svg.arc()
.innerRadius(radius - 30)
.outerRadius(radius);
var svg = d3.select("#donut").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var path = svg.selectAll("path")
.data(pie(dataset.privileges))
.enter().append("path")
.attr("fill", function(d, i) {
return color(i);
})
.attr("d", arc);
path.transition().duration(750);
var point = path.node().getPointAtLength(path.node().getTotalLength() / 2);
svg.append("image")
.attr("cx", point.x)
.attr("cy", point.y)
.attr({
"xlink:href": "http://run.plnkr.co/preview/ckf41wu0g00082c6g6bzer2cc/images/pacman_active_icon.png", //nothing visible
width: 35,
height: 36
});
svg.append("text")
.attr("dy", ".0em")
.style("text-anchor", "middle")
.attr("class", "inside")
.html(function() {
return "<tspan x='0' dy='0em'>External</tspan><tspan x='0' dy='1.2em'>Privileges</tspan>";
}); // Add your code here
<script src="https://d3js.org/d3.v3.min.js"></script>
<div id="donut"></div>
It's a bit tedious, but the following works.
You take the first element of pieData, which denotes the blue arc. Then calculate the offset to put the pacman in the right position, using trigonometry. Finally, first translate it so it rotates around its centre, then rotate it the required amount.
I placed it at radius - 15 from the centre, because that is the middle of the 30 pixel wide arc.
var total_codes = 8;
var remaining_codes = 5;
var issued = total_codes - remaining_codes;
var coloursArray = ["#128ED2", "#dadada"];
var dataset = {
privileges: [issued, remaining_codes]
};
var width = 160,
height = 160,
radius = Math.min(width, height) / 2,
iconSize = 48;
var color = d3.scale.ordinal()
.range(coloursArray);
var pie = d3.layout.pie()
.sort(null);
var arc = d3.svg.arc()
.innerRadius(radius - 30)
.outerRadius(radius);
var svg = d3.select("#donut").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var pieData = pie(dataset.privileges);
var path = svg.selectAll("path")
.data(pieData)
.enter().append("path")
.attr("fill", function(d, i) {
return color(i);
})
.attr("d", arc);
path.transition().duration(750);
svg
.append('g')
.attr('class', 'pacmancontainer')
.style('transform', function() {
// the radius of the center of the arc, also the hypothenuse of a triangle
var meanRadius = radius - 15;
var angleRadians = pieData[0].endAngle - Math.PI / 2;
var xOffset = Math.cos(angleRadians) * meanRadius;
var yOffset = Math.sin(angleRadians) * meanRadius;
return " translate(" + xOffset + "px, " + yOffset + "px)";
})
.append("image")
.attr({
"xlink:href": "http://run.plnkr.co/preview/ckf41wu0g00082c6g6bzer2cc/images/pacman_active_icon.png", //nothing visible
width: iconSize,
height: iconSize
})
// Make sure the Pacman rotates around its center
.style('transform-origin', (iconSize / 2) + 'px ' + (iconSize / 2) + 'px')
.style('transform', function() {
var angleDegrees = pieData[0].endAngle / (2 * Math.PI) * 360;
return "translate(-" + (iconSize / 2) + "px, -" + (iconSize / 2) + "px) rotate(" + angleDegrees + "deg)";
});
svg.append("text")
.attr("dy", ".0em")
.style("text-anchor", "middle")
.attr("class", "inside")
.html(function() {
return "<tspan x='0' dy='0em'>External</tspan><tspan x='0' dy='1.2em'>Privileges</tspan>";
}); // Add your code here
<script src="https://d3js.org/d3.v3.min.js"></script>
<div id="donut"></div>

move a slice of the pie chart using d3.js

In the code below, a simple pie chart is created, but I am not able to move one slice towards the outer side of the chart when selected.
I want the individual (element) slice to be positioned outer the pie and the rest of the pie chart elements(slices) in its usual position, something like this:
Here is my code:
<!DOCTYPE html>
<body>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var data = [35, 20, 45];
var width = 300,
height = 300,
radius = 150;
var arc = d3.arc()
.outerRadius(130);
var arcLabel = d3.arc()
.outerRadius(radius - 30)
.innerRadius(radius - 20);
var pie = d3.pie()
.value(function(d) {
return d;
});
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var emptyPies = svg.selectAll(".arc")
.data(pie(data))
.enter()
.append("g")
.attr("class", "arc")
emptyPies.append("path")
.attr("d", arc)
.style("fill", function(d, i) {
return color[i];
})
emptyPies.append("text")
.attr("transform", function(d) {
return "translate(" + arcLabel.centroid(d) + ")";
})
.text(function(d) {
return d.data;
});
</script>
A simple solution is creating a different arc generator:
var arc2 = d3.arc()
.outerRadius(radius)
.innerRadius(60);
And, when setting the "d" attribute, choosing which arc generator to use. For instance, moving the red slice:
emptyPies.append("path")
.attr("d", function(d,i){
return i != 1 ? arc(d) : arc2(d);
})
Here is your code with that change:
<!DOCTYPE html>
<style>
.arc text {
text-anchor: middle;
}
.arc path {
stroke: white;
}
</style>
<body>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var data = [35, 20, 45];
var width = 300,
height = 300,
radius = Math.min(width, height) / 2;
var color = ["brown", "red", "blue"];
var arc = d3.arc()
.outerRadius(radius - 10)
.innerRadius(50);
var arc2 = d3.arc()
.outerRadius(radius)
.innerRadius(60);
var arcLabel = d3.arc()
.outerRadius(radius - 30)
.innerRadius(radius - 20);
var pie = d3.pie()
.value(function(d) {
return d;
});
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var emptyPies = svg.selectAll(".arc")
.data(pie(data))
.enter()
.append("g")
.attr("class", "arc")
emptyPies.append("path")
.attr("d", function(d,i){
return i != 1 ? arc(d) : arc2(d);})
.style("fill", function(d, i) {
return color[i];
})
emptyPies.append("text")
.attr("transform", function(d) {
return "translate(" + arcLabel.centroid(d) + ")";
})
.text(function(d) {
return d.data;
});
</script>
A simple solution is to use multiple arc() but to do slice we can use arc.centroid() of 2nd arc. The following code will work in v5.
function onDrawPieChart() {
var data = [35, 20, 45];
var color = d3.schemeCategory10;
var width = 600;
var height = 600;
var radius = 100;
var pie = d3.pie().value((d) => d);
var arc = d3.arc().innerRadius(0).outerRadius(130);
var arc2 = d3.arc().innerRadius(0).outerRadius(20);
var slicedIndex = 1;
var pieData = pie(data);
var centroid = arc2.centroid(pieData[slicedIndex]);
var svg = d3
.select("body")
.append("svg")
.attr("viewBox", [-width / 2, -height / 2, width, height].join(" "))
.attr("width", width)
.attr("height", height)
.append("g");
svg
.selectAll("path")
.data(pieData)
.join("path")
.attr("fill", (d, i) => color[i])
.attr("d", (d) => arc(d))
.attr("transform", (d, i) => {
if (i === slicedIndex) {
var [x, y] = centroid;
return "translate(" + x + ", " + y + ")";
}
});
}

Resources