I am working on a leaflet map that uses markerclustergroup. But I want my markers to be pie chart using D3 and to which I'll provide some data.
Here is what I did so far:
function getMarkers (){
var dataset = [
{legend:"apple", value:10, color:"red"},
{legend:"orange", value:45, color:"orangered"},
{legend:"banana", value:25, color:"yellow"},
{legend:"peach", value:70, color:"pink"},
{legend:"grape", value:20, color:"purple"}
];
var width = 960;
var height = 500;
var radius = 200;
var r = 28;
var strokeWidth = 1;
var origo = (r+strokeWidth); //Center coordinate
var w = origo*2; //width and height of the svg element
var arc = d3.svg.arc().innerRadius(r-10).outerRadius(r);
var svg = document.createElementNS("http://www.w3.org/2000/svg", 'svg');
var vis = d3.select(svg)
.data(dataset)
.attr('class', 'marker-cluster-pie')
.attr('width', width)
.attr('height', height);
var arcs = vis.selectAll('g.arc')
.data([100,10,50,60,75])
.enter().append('g')
.attr('class', 'arc')
.attr('transform', 'translate(' + origo + ',' + origo + ')');
arcs.append('path')
.attr('class', 'grzeger')
.attr('stroke-width', strokeWidth)
.attr('d', arc)
In this last line I'm getting this error:
Error: attribute d: Expected number, "MNaN,NaNA28,28 0 …"
I did some research I think it may be related to the fact that it considers data that I'm proving as numbers instead of a string.
Is this the case? Thanks in advance for any guidance on how to mitigate the error.
You should use the pie layout function with your raw data to get the properly formatted data with the angles needed for the arc function to draw the arc, and then bind that as data to your arcs
var pie = d3.layout.pie()
.sort(null)
.value(function(d){ return d });
var arcs = vis.selectAll('g.arc')
.data(pie([100,10,50,60,75]))
.enter().append('g')
.attr('class', 'arc')
.attr('transform', 'translate(' + origo + ',' + origo + ')');
Related
I'm trying to build a doughnut chart with rounded edges only on one side. My problem is that I have both sided rounded and not just on the one side. Also can't figure out how to do more foreground arcs not just one.
const tau = 2 * Math.PI; // http://tauday.com/tau-manifesto
const arc = d3.arc()
.innerRadius(80)
.outerRadius(100)
.startAngle(0)
.cornerRadius(15);
const svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height"),
g = svg.append("g").attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
Background arc, but I'm not sure if this is even needed?
const background = g.append("path")
.datum({endAngle: tau})
.style("fill", "#ddd")
.attr("d", arc);
const data = [ .51];
const c = d3.scaleThreshold()
.domain([.200,.205,.300,.310, .501, 1])
.range(["green","#ddd", "orange","#ddd", "red"]);
Const pie = d3.pie()
.sort(null)
.value(function(d) {
return d;
});
Only have one foreground, but need to be able to have multiple:
const foreground = g.selectAll('.arc')
.data(pie(data))
.enter()
.append("path")
.attr("class", "arc")
.datum({endAngle: 3.8})
.style("fill", function(d) {
return c(d.value);
})
.attr("d", arc)
What am I doing wrong?
var tau = 2 * Math.PI; // http://tauday.com/tau-manifesto
// An arc function with all values bound except the endAngle. So, to compute an
// SVG path string for a given angle, we pass an object with an endAngle
// property to the `arc` function, and it will return the corresponding string.
var arc = d3.arc()
.innerRadius(80)
.outerRadius(100)
.startAngle(0)
.cornerRadius(15);
// Get the SVG container, and apply a transform such that the origin is the
// center of the canvas. This way, we don’t need to position arcs individually.
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height"),
g = svg.append("g").attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
// Add the background arc, from 0 to 100% (tau).
var background = g.append("path")
.datum({endAngle: tau})
.style("fill", "#ddd")
.attr("d", arc);
var data = [ .51];
var c = d3.scaleThreshold()
.domain([.200,.205,.300,.310, .501, 1])
.range(["green","#ddd", "orange","#ddd", "red"]);
var pie = d3.pie()
.sort(null)
.value(function(d) {
return d;
});
// Add the foreground arc in orange, currently showing 12.7%.
var foreground = g.selectAll('.arc')
.data(pie(data))
.enter()
.append("path")
.attr("class", "arc")
.datum({endAngle: 3.8})
.style("fill", function(d) {
return c(d.value);
})
.attr("d", arc)
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width="960" height="500"></svg>
The documentation states, that the corner radius is applied to both ends of the arc. Additionally, you want the arcs to overlap, which is also not the case.
You can add the one-sided rounded corners the following way:
Use arcs arc with no corner radius for the data.
Add additional path objects corner just for the rounded corner. These need to be shifted to the end of each arc.
Since corner has rounded corners on both sides, add a clipPath that clips half of this arc. The clipPath contains a path for every corner. This is essential for arcs smaller than two times the length of the rounded corners.
raise all elements of corner to the front and then sort them descending by index, so that they overlap the right way.
const arc = d3.arc()
.innerRadius(50)
.outerRadius(70);
const arc_corner = d3.arc()
.innerRadius(50)
.outerRadius(70)
.cornerRadius(10);
const svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height"),
g = svg.append("g").attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
const clipPath = g.append("clipPath")
.attr("id", "clip_corners");
const c = d3.scaleQuantile()
.range(["#f7fcf0","#e0f3db","#ccebc5","#a8ddb5","#7bccc4","#4eb3d3","#2b8cbe","#08589e"]);
const pie = d3.pie().value(d => d);
function render(values) {
c.domain(values);
const arcs = pie(values);
const corners = pie(values).map(d => {
d.startAngle = d.endAngle - 0.2;
d.endAngle = d.endAngle + 0.2;
return d;
});
const clip = pie(values).map(d => {
d.startAngle = d.endAngle - 0.01;
d.endAngle = d.endAngle + 0.2;
return d;
});
g.selectAll(".arc")
.data(arcs)
.join("path")
.attr("class", "arc")
.style("fill", d => c(d.value))
.attr("d", arc);
clipPath.selectAll("path")
.data(clip)
.join("path")
.attr("d", arc);
g.selectAll(".corner")
.data(corners)
.join("path")
.raise()
.attr("class", "corner")
.attr("clip-path", "url(#clip_corner)")
.style("fill", d => c(d.value))
.attr("d", arc_corner)
.sort((a, b) => b.index - a.index);
}
function randomData() {
const num = Math.ceil(8 * Math.random()) + 2;
const values = Array(num).fill(0).map(d => Math.random());
render(values);
}
d3.select("#random_data")
.on("click", randomData);
randomData();
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.3.0/d3.min.js"></script>
<button id="random_data">
Random data
</button>
<svg width="150" height="150"></svg>
I changed the dependency to the current version of d3.
With Javascript Im generating HTML-Code with a SVG in it. I want to display a a donut chart in it then. Im able to draw the chart on a static HTML-Element. However, when I try to display it in my JavaScript-generated node element the path is not showing up, but I can see the text. What am I missing here?
https://jsfiddle.net/fuL5doja/46/
function createNodes(){
var parent = document.getElementById('chart');
var child = document.createElement('div');
child.classList.add('childContainer');
parent.appendChild(child);
var svg = document.createElement('svg');
svg.id = 'donut';
child.appendChild(svg);
}
function donutChart(){
// set the dimensions and margins of the graph
var width = 30
height = 30
margin = 0
// The radius of the pieplot is half the width or half the height (smallest one). I subtract a bit of margin.
var radius = 30
// append the svg object to the div called 'my_dataviz'
var svg = d3.select('#donut')
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
// Create dummy data
var dataDummy = {a: 70, b:30}
// set the color scale
var color = d3.scale.ordinal()
.domain(dataDummy)
.range(["#bebfc2", "#8FB91C"])
// Compute the position of each group on the pie:
var pie = d3.layout.pie()
.value(function(d) {return d.value; })
var data_ready = pie(d3.entries(dataDummy))
// Build the pie chart: Basically, each part of the pie is a path that we build using the arc function.
svg.selectAll('whatever')
.data(data_ready)
.enter()
.append('path')
.attr('d', d3.svg.arc()
.innerRadius(5) // This is the size of the donut hole
.outerRadius(radius)
)
.attr('fill', function(d){ return(color(d.data.key)) })
.style("opacity", 0.7)
svg.append("text")
.attr("x", -12) // space legend
.attr("y", 2)
.attr("class", "donutText")
.text('30%');
}
function donutChart2(){
// set the dimensions and margins of the graph
var width = 30
height = 30
margin = 0
// The radius of the pieplot is half the width or half the height (smallest one). I subtract a bit of margin.
var radius = 30
// append the svg object to the div called 'my_dataviz'
var svg = d3.select('#test')
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
// Create dummy data
var dataDummy = {a: 70, b:30}
// set the color scale
var color = d3.scale.ordinal()
.domain(dataDummy)
.range(["#bebfc2", "#8FB91C"])
// Compute the position of each group on the pie:
var pie = d3.layout.pie()
.value(function(d) {return d.value; })
var data_ready = pie(d3.entries(dataDummy))
// Build the pie chart: Basically, each part of the pie is a path that we build using the arc function.
svg.selectAll('whatever')
.data(data_ready)
.enter()
.append('path')
.attr('d', d3.svg.arc()
.innerRadius(5) // This is the size of the donut hole
.outerRadius(radius)
)
.attr('fill', function(d){ return(color(d.data.key)) })
.style("opacity", 0.7)
svg.append("text")
.attr("x", -12) // space legend
.attr("y", 2)
.attr("class", "donutText")
.text('30%');
}
createNodes();
donutChart();
donutChart2();
.childContainer {
width: 200px;
height: 100px;
border: 1px solid black;
}
#mySvg {
}
<div id="chart"></div>
<svg id="test"></svg>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js" integrity="sha512-qTXRIMyZIFb8iQcfjXWCO8+M5Tbc38Qi5WzdPOYZHIlZpzBHG3L3by84BBBOiRGiEb7KKtAOAs5qYdUiZiQNNQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
You need to create the svg element with svg namespace uri for it to support path when creating directly with JavaScript:
var svg = document.createElementNS('http://www.w3.org/2000/svg','svg');
Instead of just the typical
var svg = document.createElement('svg');
Alternatively, you could use D3 to append the svg, which will make sure it's correctly namespaced!
d3.select(child).append('svg').attr('id', 'donut');
I'm trying to draw a circle with different data values as angles but for some reason, it's only the last data point that gets the color and display. I've tried to translate the svg but it seems not to budge.
I'm fairly new to D3 so I'm sure I've done something less intelligent without realizing it. As far I could tell, the angles in the g and path elements are as supposed to.
var height = 400, width = 600, radius = Math.min(height, width) / 2;
var colors = ["#red", "pink", "green", "yellow", "blue","magent","brown","olive","orange"];
var data = [1,2,1,2,1,2,1,3,1];
var chart = d3.select("#chart").append("svg")
.attr("width", width).attr("height", height);
chart.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var pie = d3.layout.pie().sort(null).value(function (d) { return d; });
var arc = d3.svg.arc().startAngle(0).innerRadius(0).outerRadius(radius);
var grx = chart.selectAll(".sector").data(pie(data))
.enter().append("g").attr("class", "sector");
grx.append("path")
.attr("d", arc)
.style("fill", function (d, i) {
console.log(d);
return colors[i];
});
The problem is that you're appending all the sectors of the pie to the svg node when they should be appended to the translated g node, you have two options to solve this problem
make chart equal to the translated g node
select g before all the .sectors and store that in grx
The first solution is simpler e.g.
var chart = d3.select("#chart").append("svg")
.attr("width", width).attr("height", height);
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
demo
Just started using d3.js and javascript. I have this weird chart requirement. Want to create the chart exactly like pie chart but, in square shaped. Just like below.
So, I thought, may be I create the pie chart and add the square between the pie chart and erase the part outside square. But, it is not working out yet.
Secondly, I thought, I can do this with CSS. I did this. But, I am not happy with this solution. It is too hacky. Can someone help me with good solution.
This is my jsfiddle link.
//// Done this to create the square.
var svgContainer = d3.select("#square").append("svg")
.attr("width", 200)
.attr("height", 200);
var rectangle = svgContainer.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", 200)
.attr("fill", '#ec4c4a')
.attr("height", 200);
// Done this to create the pie chart. Found this example some where.
var element_id = 'pie'
var elementSelector = '#pie';
svgWidth = 390;
svgHeight = 320;
svgInnerRadius = 0;
svgOuterRadius = 145;
heightOffset = 0;
scoreFontSize = '49px';
$(elementSelector).replaceWith('<svg id="'+ element_id +'" class="scoreBar" width="'+ svgWidth +'" height="'+ (svgHeight - heightOffset) +'"></svg>');
$(elementSelector).css({'width': svgWidth + 'px', 'height': (svgHeight-heightOffset) + 'px'});
var anglePercentage = d3.scale.linear().domain([0, 100]).range([0, 2 * Math.PI]);
var fullAnglePercentage = 100;
var color = d3.scale.ordinal().range(["#ACACAC", "#EAEAEA", "#123123", "#DDEEAA", "#BACBAC"]);
data = [[50, 90, 1],
[50, 30, 2],
[30, 10, 3],
[10, -1, 4],
[-1, -10, 5]]
var vis = d3.select(elementSelector);
var arc = d3.svg.arc()
.innerRadius(svgInnerRadius)
.outerRadius(svgOuterRadius)
.startAngle(function(d){return anglePercentage(d[0]);})
.endAngle(function(d){return anglePercentage(d[1]);});
vis.selectAll("path")
.data(data)
.enter()
.append("path")
.attr("d", arc)
.style("fill", function(d){return color(d[2]);})
.attr("transform", "translate(" + svgWidth / 2 + ", " + svgHeight / 2 + ")");
Thanks in advance.
You can achieve this using clip path. What is a clip path?
To SVG add defs of clippath
var svg1 = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
//making a clip square as per your requirement.
svg1.append("defs").append("svg:clipPath")
.attr("id", "clip")
.append("svg:rect")
.attr("id", "clip-rect")
.attr("x", -120)
.attr("y", -100)
.attr("width", radius)
.attr("height", radius);
Make your normal d3 pie chart like:
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);
});
To the main group add the clip like this:
var svg = svg1.append("g").attr("clip-path", "url(#clip)")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
Full working code here.
I'm making a simple tool to display a set of values that are manipulated by the user. I want all the values to start at 0 and when the data is manipulated, to grow from there.
I have everything setup except that I get errors in the console when I start all my values at 0.
Is this possible?
Here's the code I have at the moment (which is working if the values are greater than 0):
var width = this.get('width');
var height = this.get('height');
var radius = Math.min(width, height) / 2;
var color = this.get('chartColors');
var data = this.get('chartData');
var arc = d3.svg.arc()
.outerRadius(radius)
.innerRadius(0);
var pie = d3.layout.pie()
.sort(null)
.value(function(d) { return d.count; });
var id = this.$().attr('id');
var svg = d3.select("#"+id)
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var g = svg.selectAll("path")
.data(pie(data));
g.enter()
.append("path")
.attr("d", arc)
.each(function(d){ this._current = d; })
.style("fill", function(d, i) { return color[i]; })
.style("stroke", "white")
.style("stroke-width", 2);
The problem is a conceptual one -- if everything is 0, how are you going to draw a pie chart? You could however start with an empty data set and add new data as it becomes greater than zero. That leaves the problem of animating the growth of a pie chart segment from 0 to its desired size.
For this, you can animate the end angle of the pie chart segments starting at the start angle. The easiest way to do this is to copy the corresponding data object and tween the angle:
.each(function(d) {
this._current = JSON.parse(JSON.stringify(d));
this._current.endAngle = this._current.startAngle;
})
.transition().duration(dur).attrTween("d", arcTween);
Random example here.