d3 incompatible types - version conflict? - d3.js

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

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>

d3.js geoGraticule doesn't follow the sphere shape

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();
});

How to select only x rows from a csv file for a pie chart

I have a CSV file containing a hundreds of lines here's a sample :
city.csv:
City,JanTemp,Lat,Long
Indianapolis IN,21,39.8,86.9
Des_Moines IA,11,41.8,93.6
Wichita KS,22,38.1,97.6
Louisville KY,27,39,86.5
New_Orleans LA,45,30.8,90.2
Portland ME,12,44.2,70.5
Baltimore MD,25,39.7,77.3
Boston MA,23,42.7,71.4
Detroit MI,21,43.1,83.9
Minneapolis MN,2,45.9,93.9
St_Louis MO,24,39.3,90.5
Helena MT,8,47.1,112.4
Omaha NE,13,41.9,96.1
Concord NH,11,43.5,71.9
Atlantic_City NJ,27,39.8,75.3
Albuquerque NM,24,35.1,106.7
Albany NY,14,42.6,73.7
New_York NY,27,40.8,74.6
What I want to do is create a pie chart representing JanTemp for every 10 rows.
Here's my initial code to create a pie chart for all the rows :
script:
<script>
var width = 500;
var height = 500;
var radius = Math.min(width, height) / 2;
var donutWidth = 120;
var legendRectSize = 18;
var legendSpacing = 4;
var color = d3.scale.category20();
var svg = d3.select('#chart')
.append('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr('transform', 'translate(' + (width / 2) +
',' + (height / 2) + ')');
var arc = d3.svg.arc()
.innerRadius(radius - donutWidth)
.outerRadius(radius);
var pie = d3.layout.pie()
.value(function(d) { return d.JanTemp; })
.sort(null);
d3.csv('city.csv', function(error, dataset) {
dataset.forEach(function(d) {
d.JanTemp = +d.JanTemp;
});
var path = svg.selectAll('path')
.data(pie(dataset))
.enter()
.append('path')
.attr('d', arc)
.attr('fill', function(d, i) {
return color(d.data.City);
});
var legend = svg.selectAll('.legend')
.data(color.domain())
.enter()
.append('g')
.attr('class', 'legend')
.attr('transform', function(d, i) {
var height = legendRectSize + legendSpacing;
var offset = height * color.domain().length / 2;
var horz = -2 * legendRectSize;
var vert = i * height - offset;
return 'translate(' + horz + ',' + vert + ')';
});
legend.append('rect')
.attr('width', legendRectSize)
.attr('height', legendRectSize)
.style('fill', color)
.style('stroke', color);
legend.append('text')
.attr('x', legendRectSize + legendSpacing)
.attr('y', legendRectSize - legendSpacing)
.text(function(d) { return d; });
});
</script>
The code is working yet the visualization is bad.
The question is : How can I create a pie chart for every 10 rows in the csv file ? (Where also, can I add the property to only get rows by 10 ?) Is it even possible ?
You have two ways to do this, the first is to simply repeat what you have already and create several SVGs, one for each piechart.
The second is a bit more elegant, and involves a single SVG controlled by D3.
You'll first need to reorder your data into chunks of 10:
function( alldata ) {
var dataDivide = [], i, chunk = 10;
for (i=0; i<alldata.length; i+=chunk)
{
dataDivide.push(alldata.slice(i, i+chunk));
}
}
You can now use D3 to divide up your SVG and then set the chunks to be your data for each piechart:
var svg.selectAll("g")
.data( dataDivide )
.enter()
.append("g")
// position the g, etc.
.selectAll('path')
.data( function(d) {
return pie(d); // d is a chunk
})
.enter()
.append('path')
// etc.

Adding a legend to a pie chart in D3js

I'm trying to plot a pie chart with a legend inside of it. And I got into troubles to get it plotted, since I get the errors abound undefined variables. I managed to draw the chart itself and the half of the legend, but not in the right colors, what should match the pie chart.
function drawPieChart(d3div, chart_data) {
// chart_data.data is a list of data elements.
// each should contain fields: val, col, name
d3div.html(""); // clear the div
var title = getopt(chart_data, 'title', '');
// desired width and height of chart
var w = getopt(chart_data, 'width', 300);
var h = getopt(chart_data, 'height', 300);
var pad = getopt(chart_data, 'pad', 50);
var textmargin = getopt(chart_data, 'textmargin', 20);
var r = Math.min(w, h) / 2 - pad; // radius of pie chart
var div = d3div.append('div');
if(title !== '') {
div.append('p').attr('class', 'pietitle').text(title);
}
var arc = d3.svg.arc()
.outerRadius(r)
.cornerRadius(20)
.innerRadius(150);
var arcLarge = d3.svg.arc()
.innerRadius(150)
.cornerRadius(20)
.outerRadius(r + 50);
var toggleArc = function(p){
p.state = !p.state;
var dest = p.state ? arcLarge : arc;
d3.select(this).select("path").transition()
.duration(160)
.attr("d", dest);};
var pie = d3.layout.pie()
.padAngle(.03)
.sort(null)
.value(function(d) { return d.val; });
var svg = d3.select("#piechart").append("svg")
.attr("width", w)
.attr("height", h)
.append("g")
.attr("transform", "translate(" + w / 2 + "," + h / 2 + ")");
var g = svg.selectAll(".arc")
.data(pie(chart_data.data))
.enter().append("g")
.attr("class", "arc")
.attr("stroke", "#999")
.attr("id",function(d){return d.data;})
.on("mouseover",toggleArc)
.on("mouseout",toggleArc);
g.append("path")
.attr("d", arc)
.style("fill", function(d) { return d.data.col; });
var color = d3.scale.category20b();
var legendRectSize = 18;
var legendSpacing = 4;
// FROM here the code is not produced the desired result
var legend = svg.selectAll('.legend')
.data(chart_data.data)
.enter()
.append('g')
.attr('class', 'legend')
.attr("id",function(d){return d.data;})
.attr('transform', function(d, i) {
var height = legendRectSize + legendSpacing;
var offset = height * chart_data.data.length / 2;
var horz = -2 * legendRectSize;
var vert = i * height - offset;
return 'translate(' + horz + ',' + vert + ')';
});
legend.append('rect')
.data(chart_data.data)
.attr('width', legendRectSize)
.attr('height', legendRectSize)
.style("fill", function(d) { return d.data.col; });
legend.append("text")
.attr('x', legendRectSize + legendSpacing)
.attr('y', legendRectSize - legendSpacing)
.text(function(d) { return d.data.name; });
}
The code actually works fine untill the line var legend = svg.selectAll('.legend')
Then i start to define the legend, but D3 complains about undefined d.data every time i try to access d.data below the line I written above(also in the last line of the code).
I don't understand where i got on the wrong way.
If instead of defining the whole non working part(var legend...) i write this code:
g.append("text")
.attr("stroke", "none")
.attr("fill", function(d) { return d.data.col; })
.text(function(d) { return d.data.name; });
I'm able to access the d.data.name.
Unfortunately wrong colors of the boxes and not description.
Thanks!

How to fill a single value in the 'Donut Chart'

I am getting current status from the server. from the server information i need to show the current status of the finished works.
like this :
I am trying here, but i am not getting result.
here is my code :
var data = [45] //say value i get.
var width = 400,
height = 400,
radius = Math.min(width, height) / 2;
var color = d3.scale.ordinal()
.range(["#ffff00", "#1ebfc5"]);
var arc = d3.svg.arc()
.outerRadius(radius - 90)
.innerRadius(radius - 80);
var pie = d3.layout.pie()
.sort(null)
.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 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(color[0]); });
Live Demo
You probably just get a full circle, right?
Well, d3 stands for data driven documents, which means that it cannot show data that isn't there.
So basically, to fix it, you just need the counter value in your dataset: I have fixed your code below:
var data = [45, 55] //as you see, i have added 55 (100-45).
var width = 400,
height = 400,
radius = Math.min(width, height) / 2;
var color = d3.scale.ordinal()
.domain(data)
.range(["#ffff00", "#1ebfc5"]);
var arc = d3.svg.arc()
.outerRadius(radius - 90)
.innerRadius(radius - 80);
var pie = d3.layout.pie()
.sort(null)
.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 g = svg.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc");
g.append("path")
.attr("d", arc)
.style("fill", function(d,i) { return color(d.data); });
EDIT
I also changed your coloring. in the end of your code you do "return color(color[0])" which always returns the same color. So even if you would have 2 different parts in your donut chart, they whould have been colored the same and you wouldn't have noticed the difference :-). Use the d3 built in data variable. For arcs/pies, the variable d also returns more then just the original data, it returns a custom object. Your data is stored in d.data, which you can see in the code I included.

Resources