D3.js - Add label along the arc - d3.js

I am trying to implement this gauge to show target and actual values.
Here the position of the target value '45%' is given by a fixed number, so that it always stays at the top of the gauge as shown in below image:
How do I make this label stick to the beginning of second arc dynamically, similar to this:
Here is a snippet of current code I am using with hardcoded translate values:
var barWidth, chart, chartInset, degToRad, repaintGauge,
height, margin, numSections, padRad, percToDeg, percToRad,
percent, radius, sectionIndx, svg, totalPercent, width;
percent = percentValue;
numSections = 1;
sectionPerc = 1 / numSections / 2;
padRad = 0.025;
chartInset = 10;
// Orientation of gauge:
totalPercent = .75;
el = d3.select('#HSFO');
margin = {
top: 12,
right: 12,
bottom: 0,
left: 12
};
width = el[0][0].offsetWidth - margin.left - margin.right;
height = width;
radius = Math.min(width, height) / 2;
barWidth = 40 * width / 300;
//Utility methods
percToDeg = function(perc) {
return perc * 360;
};
percToRad = function(perc) {
return degToRad(percToDeg(perc));
};
degToRad = function(deg) {
return deg * Math.PI / 180;
};
// Create SVG element
svg = el.append('svg').attr('width', width + margin.left + margin.right).attr('height', height + margin.top + margin.bottom);
// Add layer for the panel
chart = svg.append('g').attr('transform', "translate(" + ((width + margin.left) / 2) + ", " + ((height + margin.top) / 2) + ")");
chart.append('path').attr('class', "arc chart-first");
chart.append('path').attr('class', "arc chart-second");
chart.append('path').attr('class', "arc chart-third");
formatValue = d3.format('1%');
arc3 = d3.svg.arc().outerRadius(radius - chartInset).innerRadius(radius - chartInset - barWidth)
arc2 = d3.svg.arc().outerRadius(radius - chartInset).innerRadius(radius - chartInset - barWidth)
arc1 = d3.svg.arc().outerRadius(radius - chartInset).innerRadius(radius - chartInset - barWidth)
repaintGauge = function() {
perc = 17 / 20;
var next_start = totalPercent;
arcStartRad = percToRad(next_start);
arcEndRad = arcStartRad + percToRad(perc / 2);
next_start += perc / 2;
arc1.startAngle(arcStartRad).endAngle(arcEndRad);
perc = 1 - perc;
arcStartRad = percToRad(next_start);
arcEndRad = arcStartRad + percToRad(perc / 2);
next_start += perc / 2;
arc2.startAngle(arcStartRad + padRad).endAngle(arcEndRad);
chart.select(".chart-first").attr('d', arc1);
chart.select(".chart-second").attr('d', arc2);
svg.append("text").attr("transform", "translate("+(width + margin.left-35) +","+ (radius - chartInset - barWidth/4.5) +")" + 'rotate('+'70'+')')
.attr("text-anchor", "middle").style("font-size", "12").style("font-family", "Helvetica").text('17')
}

I would use a text path for the percent text and use an arc as the template so that you don't have to worry about manually transforming the text and calculating the angle. This means reorganising your elements slightly and using arc3 (currently unused) as the path for the text.
The general format for text on a path is:
<path id="path_for_text" d="M-150,1.8369701987210297e-14A150,150 0 0,1 18.799985034645633,-148.8172051971717L13.45243373590199,-106.4869779410873A107.33333333333334,107.33333333333334 0 0,0 -107.33333333333334,1.3144542310848258e-14Z"></path>
<text>
<textPath xlink:href="#path_for_text">my text here</textPath>
</text>
so the basic alterations that we'll need to do on your code are adding the new arc for the text to go along, and adding in the text path element. So, let's create an appropriate arc generator:
// we want the text to be offset slightly from the outer edge of the arc, and the arc
// itself can have identical inner and outer radius measurements
var arc3 = d3.svg.arc()
.outerRadius(radius - chartInset + 10)
.innerRadius(radius - chartInset + 10)
// add the text element and give it a `textPath` element as a child
var arc_text = chart.append('text')
.attr('id', 'scale10')
.attr("font-size", 15)
.style("fill", "#000000")
// the textPath element will use an element with ID `text_arc` to provide its shape
arc_text.append('textPath')
.attr('startOffset','0%')
.attr('xlink:href', '#text_arc' )
// add the path with the ID `text_arc`
chart.append('path').attr('class', "arc chart-third")
.attr('id', 'text_arc')
In repaintGauge, calculate the appropriate arc:
// append the path to the chart, using the arc3 constructor to generate the arc
// these numbers will be the same as those for arc2, although I would add a little
// padding to both start and end angles to ensure that the text doesn't wrap if it
// is at 0% or 100%
arc3.startAngle(arcStartRad - 0.15).endAngle(arcEndRad + 0.15);
chart.select('id', 'text_arc')
.attr('d', arc3)
and update the text:
arc_text.select('textPath')
.text( percent + '%')
You can refactor your repaintGauge function to make it significantly simpler as some of the arc figures don't change; arc1's startAngle will always be at 1.5 Pi radians, and arc2's endAngle will always be 2.5 Pi radians. That means you only need to work out what your percent is in terms of radians, which is pretty simple: if 0% is 1.5 Pi and 100% is 2.5 Pi, and you want to represent perc percent, it will be p / 100 * Math.PI + 1.5 * Math.PI.
repaintGauge = function(perc) {
var arcOffset = Math.PI * 1.5
var current = Math.PI * perc / 100 + arcOffset
// arc1's endAngle and arc2, arc3's endAngle can be set to `current`
arc1.startAngle(arcOffset).endAngle(current)
arc2.startAngle(current + padRad).endAngle(arcOffset + Math.PI)
arc3.startAngle(current - 0.15).endAngle(arcOffset + Math.PI + 0.15)
chart.select(".chart-first").attr('d', arc1);
chart.select(".chart-second").attr('d', arc2);
chart.select(".chart-third").attr('d', arc3);
arc_text.select('textPath').text(perc + '%');
};
Here's a demo showing the text at different positions and with different values:
var name = "Value";
var value = 17;
var gaugeMaxValue = 100;
// data to calculate
var percentValue = value / gaugeMaxValue;
////////////////////////
var needleClient;
(function() {
var barWidth, chart, chartInset, degToRad, repaintGauge,
height, margin, numSections, padRad, percToDeg, percToRad,
percent, radius, sectionIndx, svg, totalPercent, width;
percent = percentValue;
numSections = 1;
sectionPerc = 1 / numSections / 2;
padRad = 0.025;
chartInset = 10;
var percStart = 0;
var arcOffset = Math.PI * 1.5
// Orientation of gauge:
totalPercent = .75;
el = d3.select('.chart-gauge');
margin = {
top: 40,
right: 20,
bottom: 30,
left: 60
};
width = el[0][0].offsetWidth - margin.left - margin.right;
height = width;
radius = Math.min(width, height) / 2;
barWidth = 40 * width / 300;
//Utility methods
percToDeg = function(perc) {
return perc * 360;
};
percToRad = function(perc) {
return degToRad(percToDeg(perc));
};
degToRad = function(deg) {
return deg * Math.PI / 180;
};
// Create SVG element
svg = el.append('svg').attr('width', width + margin.left + margin.right).attr('height', height + margin.top + margin.bottom);
// Add layer for the panel
chart = svg.append('g').attr('transform', "translate(" + ((width + margin.left) / 2) + ", " + ((height + margin.top) / 2) + ")");
formatValue = d3.format('1%');
var arc3 = d3.svg.arc().outerRadius(radius - chartInset + 10).innerRadius(radius - chartInset + 10),
arc2 = d3.svg.arc().outerRadius(radius - chartInset).innerRadius(radius - chartInset - barWidth),
arc1 = d3.svg.arc().outerRadius(radius - chartInset).innerRadius(radius - chartInset - barWidth)
// bind angle data directly to the chart elements
chart.append('path').attr('class', "arc chart-first")
.datum({ startAngle: arcOffset, endAngle: arcOffset })
.attr('d', arc1)
chart.append('path').attr('class', "arc chart-second")
.datum({ startAngle: arcOffset, endAngle: arcOffset + padRad + Math.PI })
.attr('d', arc2)
chart.append('path').attr('class', "arc chart-third")
.attr('id', 'text_arc')
.datum({ startAngle: arcOffset - 0.15, endAngle: arcOffset + Math.PI + 0.15 })
.attr('d', arc3)
var arc_text = chart.append('text')
.attr('id', 'scale10')
.attr("font-size", 15)
.style("fill", "#000000")
.attr('text-anchor', 'start')
arc_text.append('textPath')
.attr('startOffset','0%')
.attr('xlink:href', '#text_arc' )
var dataset = [{
metric: name,
value: value
}]
var texts = svg.selectAll("text")
.data(dataset)
.enter();
texts.append("text")
.text(function() {
return dataset[0].metric;
})
.attr('id', "Name")
.attr('transform', "translate(" + ((width + margin.left) / 2) + ", " + ((height + margin.top) / 1.5) + ")")
.attr("font-size", 25)
.style("fill", "#000000");
texts.append("text")
.text(function() {
return dataset[0].value + "%";
})
.attr('id', "Value")
.attr('transform', "translate(" + ((width + margin.left) / 1.4) + ", " + ((height + margin.top) / 1.5) + ")")
.attr("font-size", 25)
.style("fill", "#000000");
texts.append("text")
.text(function() {
return 0 + "%";
})
.attr('id', 'scale0')
.attr('transform', "translate(" + ((width + margin.left) / 100) + ", " + ((height + margin.top) / 2) + ")")
.attr("font-size", 15)
.style("fill", "#000000");
texts.append("text")
.text(function() {
return gaugeMaxValue + "%";
})
.attr('id', 'scale20')
.attr('transform', "translate(" + ((width + margin.left) / 1.08) + ", " + ((height + margin.top) / 2) + ")")
.attr("font-size", 15)
.style("fill", "#000000");
repaintGauge = function(perc) {
var current = Math.PI * perc / 100 + arcOffset
var t = d3.transition().duration(500)
chart.select(".chart-first")
.transition(t)
.attrTween('d', arcEndTween(current, arc1));
chart.select(".chart-second")
.transition(t)
.attrTween('d', arcStartTween(current, arc2));
chart.select(".chart-third")
.transition(t)
.attrTween('d', arcStartTween(current, arc3) );
arc_text.select('textPath')
.text( perc.toFixed(1) + '%')
}
function arcStartTween(newAngle, arc) {
return function(d) {
var interpolate = d3.interpolate(d.startAngle, newAngle);
return function(t) {
d.startAngle = interpolate(t);
return arc(d);
};
};
}
function arcEndTween(newAngle, arc) {
return function(d) {
var interpolate = d3.interpolate(d.endAngle, newAngle);
return function(t) {
d.endAngle = interpolate(t);
return arc(d);
};
};
}
/////////
var Needle = (function() {
//Helper function that returns the `d` value for moving the needle
var recalcPointerPos = function(perc) {
var centerX, centerY, leftX, leftY, rightX, rightY, thetaRad, topX, topY;
thetaRad = percToRad(perc / 2);
centerX = 0;
centerY = 0;
topX = centerX - this.len * Math.cos(thetaRad);
topY = centerY - this.len * Math.sin(thetaRad);
leftX = centerX - this.radius * Math.cos(thetaRad - Math.PI / 2);
leftY = centerY - this.radius * Math.sin(thetaRad - Math.PI / 2);
rightX = centerX - this.radius * Math.cos(thetaRad + Math.PI / 2);
rightY = centerY - this.radius * Math.sin(thetaRad + Math.PI / 2);
return "M " + leftX + " " + leftY + " L " + topX + " " + topY + " L " + rightX + " " + rightY;
};
function Needle(el) {
this.el = el;
this.len = width / 2.5;
this.radius = this.len / 8;
}
Needle.prototype.render = function() {
this.el.append('circle').attr('class', 'needle-center').attr('cx', 0).attr('cy', 0).attr('r', this.radius);
return this.el.append('path').attr('class', 'needle').attr('id', 'client-needle').attr('d', recalcPointerPos.call(this, 0));
};
Needle.prototype.moveTo = function(perc) {
var self,
oldValue = this.perc || 0;
this.perc = perc;
self = this;
// Reset pointer position
this.el.transition().delay(100).ease('quad').duration(200).select('.needle').tween('reset-progress', function() {
return function(percentOfPercent) {
var progress = (1 - percentOfPercent) * oldValue;
return d3.select(this).attr('d', recalcPointerPos.call(self, progress));
};
});
this.el.transition().delay(300).ease('bounce').duration(1500).select('.needle').tween('progress', function() {
return function(percentOfPercent) {
var progress = percentOfPercent * perc;
return d3.select(this).attr('d', recalcPointerPos.call(self, progress));
};
});
};
return Needle;
})();
setInterval(function() {
repaintGauge( Math.floor(Math.random() * 100) )
}, 1500);
needle = new Needle(chart);
needle.render();
needle.moveTo(percent);
})();
.chart-gauge
{
width: 400px;
margin: 100px auto
}
.chart-first
{
fill: #66AB8C;
}
.chart-second
{
fill: #ff533d;
}
.needle, .needle-center
{
fill: #000000;
}
.text {
color: "#112864";
font-size: 16px;
}
svg {
font: 10px sans-serif;
}
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="style.css">
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.0.0/d3.min.js"></script>
<div class="chart-gauge"></div>
</body>
</html>

Related

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>

How can I calculate distante between rects in D3?

I'm working on this project and I need to calculate the distance between rects? Some idea?
My goal is to replicate one hart about Covid from this piece from The Washington Post: https://www.washingtonpost.com/graphics/2020/health/coronavirus-herd-immunity-simulation-vaccine/
Below the picture is the code who I used to generate this chart.
Thanks!!
export function main(){
createSVG()
build()
}
let offset = {top: 20, left: 70}
let screenWidth = 800 - offset.left
let screenHeight = 400 - offset.top
function createSVG() {
let container = d3.select('#container')
svg = container.append('svg')
.attr('id', 'canvas')
.attr('width', screenWidth + offset.left)
.attr('height', screenHeight + offset.top)
}
function build() {
let rectWidth = 15
let rectHeight = 15
let randomMovementLimit = 9
let people = []
for(let i = 0; i< 100; i++){
const cellX = i % 10
const randomX = Math.random() * randomMovementLimit - 3
const x = offset.left + cellX * (rectWidth + 10) + randomX
const cellY = Math.floor(i / 10)
const randomY = Math.random() * randomMovementLimit - 3
const y = offset.top + cellY * (rectHeight + 10) + randomY
const infected = Math.random() > 0.5
people.push({x: x, y: y, cellX: cellX, cellY: cellY, infected: infected})
}
console.log(people[0])
const infectedPeople = people.filter
const healtyPeople = people.filter
let rects = svg.selectAll('rect')
.data(people)
.enter()
.append('rect')
.attr('x', function(d,i) {
return d.x
})
.attr('y', function(d,i) {
return d.y
})
.attr('height', rectHeight)
.attr('width', rectWidth)
.style('fill', (d, i) => {
return d.infected ? "red" : "blue"
})
.attr('rx', 5)
}
Calculate distance from center of each rectangle using pythagorean theorem. I think but haven't tested if I remember right that the center of each rectangle is at d.x + rectWidth/2 and d.y - rectHeight/2. Initialize rectWidth and rectHeight outside of the build function so they are within scope.
Example to get newly infected rectangles at distance less than social_dist from the previously infected:
let social_dist = 6;
rects.filter(function(d,i){
for (let p of infectedPeople){
let xDist = (p.x + rectWidth/2) - (d.x + rectWidth/2);
let yDist = (p.y - rectHeight/2) - (d.y - rectHeight/2);
let dist = Math.sqrt( xDist*xDist + yDist*yDist );
if (dist < social_dist){
return true;
}
return false;
});

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>

Make two pie charts with the same d3js code

I have a pie chart that was made with d3js.
The data is read from an url.
I want to use the same code to generate the chart, changing the colors of the chart and the data, but I am not managing how can I do.
In witch regard changing the data, the only variable that change its name is earth_footprint, that will be IHD.
Here there is the fiddle of how the code is today.
In this fiddle there are the div on where I want to have my second chart:
<div id="donut2"></div>
And the data that i want to use to the second chart is on this link.
Thanks a lot!!
make a function that encloses everything in your code and make two function calls
function drawChart(url, id, key) {
d3.json(url)
.then(function(data) {
data = data.filter(dataPoint => dataPoint.year == 2015);
const heightValue = 300;
const widthValue = 600;
const strokeWidth = 1.5;
const margin = {
top: 0,
bottom: 20,
left: 30,
right: 20
};
var width = 600 - margin.left - margin.right - (strokeWidth * 2);
var height = 250 - margin.top - margin.bottom;
var radius = Math.min(width, height) / 2;
var color = d3.scaleOrdinal()
.range(["#e688a1", "#ed9a73", "#e3c878", "#64b2cd", "#e1b12c", "red", "green", "violet", "steelblue"]);
var pie = d3.pie()
.value(function(d) {
return d[key];
})(data);
var arc = d3.arc()
.outerRadius(radius - 10)
.innerRadius(0);
var svg =
d3
.select(id)
.append("svg")
.attr("viewBox", `0 0 ${widthValue} ${heightValue}`)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var g = svg.selectAll("arc")
.data(pie)
.enter().append("g")
.attr("class", "arc")
g.on('mouseover', function(d, i) {
d3.select(this).transition()
.duration('50')
.attr('opacity', '.95')
.attr("stroke", "#23374d")
g.append("text")
.attr("class", "text remove")
.style("text-anchor", "middle")
.attr("stroke", "#23374d")
.attr("fill", "#23374d")
.text(d.data.country_name)
})
.on('mouseout', function(d, i) {
d3.select(this).transition()
.duration('50')
.attr('opacity', '1')
.attr("stroke", "none")
g.select(".text.remove").remove();
})
.attr('transform', 'translate(0, 0)');
g.append("path")
.attr("d", arc)
.style("fill", function(d) {
return color(d.data.country_name);
});
g
.append("text")
.attr("text-anchor", "middle")
.attr("x", function(d) {
var a = d.startAngle + (d.endAngle - d.startAngle) / 2 - Math.PI / 2;
d.cx = Math.cos(a) * (radius - 45);
return d.x = Math.cos(a) * (radius + 30);
})
.attr("y", function(d) {
var a = d.startAngle + (d.endAngle - d.startAngle) / 2 - Math.PI / 2;
d.cy = Math.sin(a) * (radius - 12);
return d.y = Math.sin(a) * (radius - 5);
})
.text(function(d) {
return d.value.toFixed(2);
})
.each(function(d) {
var bbox = this.getBBox();
d.sx = d.x - bbox.width / 2 - 2;
d.ox = d.x + bbox.width / 2 + 2;
d.sy = d.oy = d.y + 5;
});
g.append("path")
.attr("class", "pointer")
.style("fill", "none")
.style("stroke", "#2c3e50")
.attr("d", function(d) {
if (d.cx > d.ox) {
return "M" + d.sx + "," + d.sy + "L" + d.ox + "," + d.oy + " " + d.cx + "," + d.cy;
} else {
return "M" + d.ox + "," + d.oy + "L" + d.sx + "," + d.sy + " " + d.cx + "," + d.cy;
}
});
});
}
drawChart("https://raw.githubusercontent.com/cvrnogueira/CODWorkData/master/database/topfive/biggestEarthFootprint.json",
"#donut",
"earth_footprint"
)
drawChart("https://raw.githubusercontent.com/cvrnogueira/CODWorkData/master/database/topfive/biggestIHD.json",
"#donut2",
"IHD"
)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<script src="https://d3js.org/d3.v5.min.js"></script>
<title>JS Bin</title>
</head>
<body>
<div id="donut"></div>
<br>
<br>
<div id="donut2"></div>
</body>
</html>
So your function now takes the url, the id upon which to load and the key you want to read from in the data.

D3 Force layout DIV vs SVG

I am trying to modify the d3 force layout from SVG to DIV's. It seems the collision detection doesnt work as well with DIV's. You can see the working examples below.
(Another quick question, anyone know why css transform:translate isnt used for hardware acceleration)
DIV Version
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body,
html {
margin: 0;
width: 100%;
height: 100%
}
.divs div {
border-radius: 50%;
background: red;
position: absolute;
}
</style>
<body>
<script src="https://d3js.org/d3.v3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.11.3/jquery.js"></script>
<script>
var width = $('body').width(),
height = $('body').height(),
padding = 10, // separation between nodes
maxRadius = 30;
var n = 20, // total number of nodes
m = 1; // number of distinct clusters
var color = d3.scale.category10()
.domain(d3.range(m));
var xPos = d3.scale.ordinal()
.domain(d3.range(m))
.rangePoints([width, width], 1);
var x =
d3.scale.linear()
.domain([0, width])
.range([0, width]);
var y =
d3.scale.linear()
.domain([0, height])
.range([0, height]);
var nodes = d3.range(n).map(function() {
var i = Math.floor(Math.random() * m),
v = (i + 1) / m * -Math.log(Math.random());
return {
radius: Math.random() * maxRadius + 20,
color: color(i),
cx: xPos(i),
cy: height
};
});
var force = d3.layout.force()
.nodes(nodes)
.size([width, height])
.gravity(0)
.charge(0)
.on("tick", tick)
.start();
var $body = d3.select("body")
.append("div").attr('class', 'divs')
.attr('style', function(d) {
return 'width: ' + width + 'px; height: ' + height + 'px;';
});
var $div = $body.selectAll("div")
.data(nodes)
.enter()
.append("div")
.attr('style', function(d) {
return 'width: ' + (d.radius * 2) + 'px; height: ' + (d.radius * 2) + 'px; margin-left: -' + d.radius + 'px; margin-top: -' + d.radius + 'px;';
})
.call(force.drag);
function tick(e) {
$div
.each(gravity(.2 * e.alpha))
.each(collide(.5))
.style('left', function(d) {
return x(Math.max(d.radius, Math.min(width - d.radius, d.x))) + 'px';
})
.style('top', function(d) {
return y(Math.max(d.radius, Math.min(height - d.radius, d.y))) + 'px';
});
}
// Move nodes toward cluster focus.
function gravity(alpha) {
return function(d) {
d.y += (d.cy - d.y) * alpha;
d.x += (d.cx - d.x) * alpha;
};
}
// Resolve collisions between nodes.
function collide(alpha) {
var quadtree = d3.geom.quadtree(nodes);
return function(d) {
var r = d.radius + maxRadius + padding,
nx1 = d.x - r,
nx2 = d.x + r,
ny1 = d.y - r,
ny2 = d.y + r;
quadtree.visit(function(quad, x1, y1, x2, y2) {
if (quad.point && (quad.point !== d)) {
var x = d.x - quad.point.x,
y = d.y - quad.point.y,
l = Math.sqrt(x * x + y * y),
r = d.radius + quad.point.radius + (d.color !== quad.point.color) * padding;
if (l < r) {
l = (l - r) / l * alpha;
d.x -= x *= l;
d.y -= y *= l;
quad.point.x += x;
quad.point.y += y;
}
}
return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
});
};
}
</script>
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body, html { margin: 0;width: 100%; height: 100%}
circle {
}
</style>
<body>
<script src="https://d3js.org/d3.v3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.11.3/jquery.js"></script>
<script>
var width = $('body').width(),
height = $('body').height(),
padding = 10, // separation between nodes
maxRadius = 40;
var n = 10, // total number of nodes
m = 1; // number of distinct clusters
var color = d3.scale.category10()
.domain(d3.range(m));
var x = d3.scale.ordinal()
.domain(d3.range(m))
.rangePoints([width - 200, width], 1);
var nodes = d3.range(n).map(function() {
var i = Math.floor(Math.random() * m),
v = (i + 1) / m * -Math.log(Math.random());
return {
radius: Math.random() * maxRadius + 30,
color: color(i),
cx: x(i),
cy: height
};
});
var force = d3.layout.force()
.nodes(nodes)
.size([width, height])
.gravity(0)
.charge(0)
.on("tick", tick)
.start();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var circle = svg.selectAll("circle")
.data(nodes)
.enter().append("circle")
.attr("r", function(d) { return d.radius; })
.style("fill", function(d) { return d.color; })
.call(force.drag);
function tick(e) {
circle
.each(gravity(.2 * e.alpha))
.each(collide(.5))
//.attr("cx", function(d) { return d.x; })
//.attr("cy", function(d) { return d.y; });
.attr("cx", function(d) { return d.x = Math.max(d.radius, Math.min(width - d.radius, d.x)); })
.attr("cy", function(d) { return d.y = Math.max(d.radius, Math.min(height - d.radius, d.y)); });
}
// Move nodes toward cluster focus.
function gravity(alpha) {
return function(d) {
d.y += (d.cy - d.y) * alpha;
d.x += (d.cx - d.x) * alpha;
};
}
// Resolve collisions between nodes.
function collide(alpha) {
var quadtree = d3.geom.quadtree(nodes);
return function(d) {
var r = d.radius + maxRadius + padding,
nx1 = d.x - r,
nx2 = d.x + r,
ny1 = d.y - r,
ny2 = d.y + r;
quadtree.visit(function(quad, x1, y1, x2, y2) {
if (quad.point && (quad.point !== d)) {
var x = d.x - quad.point.x,
y = d.y - quad.point.y,
l = Math.sqrt(x * x + y * y),
r = d.radius + quad.point.radius + (d.color !== quad.point.color) * padding;
if (l < r) {
l = (l - r) / l * alpha;
d.x -= x *= l;
d.y -= y *= l;
quad.point.x += x;
quad.point.y += y;
}
}
return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
});
};
}
</script>
The reason is you are not updating the d.x and d.y in the tick so the layout will never come to know if they are colliding or not.
.style('left', function(d) {
//update the d.x
d.x = x(Math.max(d.radius, Math.min(width - d.radius, d.x)))
return d.x + 'px';
})
.style('top', function(d) {
//update the d.y
d.y=y(Math.max(d.radius, Math.min(height - d.radius, d.y)));
return d.y + "px"
});
Working code:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body,
html {
margin: 0;
width: 100%;
height: 100%
}
.divs div {
border-radius: 50%;
background: red;
position: absolute;
}
</style>
<body>
<script src="https://d3js.org/d3.v3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.11.3/jquery.js"></script>
<script>
var width = $('body').width(),
height = $('body').height(),
padding = 10, // separation between nodes
maxRadius = 30;
var n = 20, // total number of nodes
m = 1; // number of distinct clusters
var color = d3.scale.category10()
.domain(d3.range(m));
var xPos = d3.scale.ordinal()
.domain(d3.range(m))
.rangePoints([width, width], 1);
var x =
d3.scale.linear()
.domain([0, width])
.range([0, width]);
var y =
d3.scale.linear()
.domain([0, height])
.range([0, height]);
var nodes = d3.range(n).map(function() {
var i = Math.floor(Math.random() * m),
v = (i + 1) / m * -Math.log(Math.random());
return {
radius: Math.random() * maxRadius + 20,
color: color(i),
cx: xPos(i),
cy: height
};
});
var force = d3.layout.force()
.nodes(nodes)
.size([width, height])
.gravity(0)
.charge(0)
.on("tick", tick)
.start();
var $body = d3.select("body")
.append("div").attr('class', 'divs')
.attr('style', function(d) {
return 'width: ' + width + 'px; height: ' + height + 'px;';
});
var $div = $body.selectAll("div")
.data(nodes)
.enter()
.append("div")
.attr('style', function(d) {
return 'width: ' + (d.radius * 2) + 'px; height: ' + (d.radius * 2) + 'px; margin-left: -' + d.radius + 'px; margin-top: -' + d.radius + 'px;';
})
.call(force.drag);
function tick(e) {
$div
.each(gravity(.2 * e.alpha))
.each(collide(.5))
.style('left', function(d) {
d.x = x(Math.max(d.radius, Math.min(width - d.radius, d.x)))
return d.x + 'px';
})
.style('top', function(d) {
d.y=y(Math.max(d.radius, Math.min(height - d.radius, d.y)));
return d.y + "px"
});
}
// Move nodes toward cluster focus.
function gravity(alpha) {
return function(d) {
d.y += (d.cy - d.y) * alpha;
d.x += (d.cx - d.x) * alpha;
};
}
// Resolve collisions between nodes.
function collide(alpha) {
var quadtree = d3.geom.quadtree(nodes);
return function(d) {
var r = d.radius + maxRadius + padding,
nx1 = d.x - r,
nx2 = d.x + r,
ny1 = d.y - r,
ny2 = d.y + r;
quadtree.visit(function(quad, x1, y1, x2, y2) {
if (quad.point && (quad.point !== d)) {
var x = d.x - quad.point.x,
y = d.y - quad.point.y,
l = Math.sqrt(x * x + y * y),
r = d.radius + quad.point.radius + (d.color !== quad.point.color) * padding;
if (l < r) {
l = (l - r) / l * alpha;
d.x -= x *= l;
d.y -= y *= l;
quad.point.x += x;
quad.point.y += y;
}
}
return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
});
};
}
</script>
Hope this helps!

Resources