I'm drawing a d3 donut. Now I want to add as many donuts as entries in database. If I add something to database, automatic updating fails. I have to reload My code in the Browser - then I see the new donut. Isnt Meteor.autorun updating automatically?
Code is:
Template.donuts.rendered = function (){
var self = this;
self.node = self.find("p");
// Data
var dataset = {
apples: [2, 2, 2, 2, 2]
};
//Width and height
var width = 100,
height = 100,
radius = Math.min(width, height) / 2;
// render
self.handle = Meteor.autorun(function () {
var color = d3.scale.category10();
var pie = d3.layout.pie()
.sort(null);
var arc = d3.svg.arc()
.innerRadius(radius - 20)
.outerRadius(radius - 5);
var svg = d3.select(self.node).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.apples))
.enter().append("path")
.attr("fill", function(d, i) { return color(i); })
.attr("d", arc);
});
}; //Template.donuts
it is called via handlebars
<template name="donuts">
{{#each nodes}}
<p></p>
{{/each}}
</template>
What am I doing wrong. Thank you for your time.
Your rendered hook is on the wrong level. Right now you're connecting it to the template that contains the donuts, when it looks like you want to have each donut be rendered in a certain way. First, start by reorganising your templates:
<template name="donuts">
{{#each nodes}}
{{> node}}
{{/each}}
</template>
<template name="node"><p></p></template>
Now you can tell a node what to do when it's rendered:
Template.node.rendered = function() {
// d3 code
}
The rendered call will be automatically run whenever the node is re-rendered, which will happen if you change a dependency. If nodes is a reactive source like a mongodb cursor, this will work immediately. Otherwise, please add more code so we can figure out what else is going on.
Meteor.autorun() will run whenever its dependencies change. You need a reactive datasource inside the function.
Found a more elegant solution:
// Donuts //
function donutinit() {
var dataset = {
apples: [2, 2, 2, 2, 2]
};
//Width and height
var width = 100,
height = 100,
radius = Math.min(width, height) / 2;
// render
var color = d3.scale.category10();
var pie = d3.layout.pie()
.sort(null);
var arc = d3.svg.arc()
.innerRadius(radius - 20)
.outerRadius(radius - 5);
var svg = d3.select("#donut_canvas").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.apples))
.enter().append("path")
.attr("fill", function(d, i) { return color(i); })
.attr("d", arc);
};
Template.donut.rendered = function() {
donutinit();
};
After that iterate with handlebars over #donut_canvas. The Meteor.autorun or Meteor.rendered gave me unpredictable amounts of donuts - it rendered additional donuts. I had to reload it then.
Answer is inspired from here: Google map contained in meteor Template is rendered twice
Thank you for your time.
Related
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')
})
}
I would like to draw two donut charts concurrently on two different html elements with different parameter. For example, first donut chart ends with 99% and the second chart ends with 70%.
The first chart works properly but for the second one does not. I used the same javascript code to draw the two chart but just d3.select() different element by id and the allocated number is different. The following is the javascript code.
<html>
<script src="https://d3js.org/d3.v4.min.js"></script>
<body>
<div id="docsChart_slim"></div>
</body>
<script type="text/javascript">
var width = 135,
height = 135,
twoPi = 2 * Math.PI,
progress = 0,
allocated = 4257000,
total = 4300000,
formatPercent = d3.format(".0%");
var arc = d3.arc()
.startAngle(0)
.innerRadius(58)
.outerRadius(66);
var svg = d3.select("#docsChart_slim").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var meter = svg.append("g")
.attr("class", "funds-allocated-meter");
meter.append("path")
.attr("class", "background")
.attr("d", arc.endAngle(twoPi));
var foreground = meter.append("path")
.attr("class", "foreground");
var percentComplete = meter.append("text")
.attr("text-anchor", "middle")
.attr("class", "percent-complete")
.attr("dy", "0em");
/*var description = meter.append("text")
.attr("text-anchor", "middle")
.attr("class", "description")
.attr("dy", "2.3em")
.text("Total Complete");*/
var i = d3.interpolate(progress, allocated / total);
d3.transition().duration(1000).tween("progress", function() {
return function(t) {
progress = i(t);
foreground.attr("d", arc.endAngle(twoPi * progress));
percentComplete.text(formatPercent(progress));
};
});
</script>
</html>
Also the result is
I have checked some documentation about execute transition() on the same element but what I am trying to do is drawing charts on the different elements.
What should I do?
Can someone help me to create below image using d3js. I able to create pie chart as required but stuck to render outer text with arrows and all.
Wheel with outer text
As of know I have achieved circle creation using below code.
var svg = d3.select("svg");
var margin = {top: 40, right: 45, bottom: 30, left: 40};
console.log(svg);
var width = svg.attr('width');
var height = svg.attr('height');
var radius = Math.min(width, height)/2;
var g = svg.append("g").attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var hoverStyle = {
zindex: '2px'
};
var hoverExitStyle = {
zindex: "0px"
}
var animateSpeed = 500;
// Define a Pie
var pie = d3.pie()
.sort(null)
.value(function(d) {return d.number});
// define pie section
var path = d3.arc()
.outerRadius(radius - 10)
.innerRadius(0);
//
var label = d3.arc()
.outerRadius(radius - 40)
.innerRadius(radius - 40);
// Get pie sections based on the data.
var pieSections = pie(data);
var arc = g.selectAll('.arc')
.data(pieSections)
.enter().append("g")
.attr("class", "arc")
.append('a')
.attr("href", function(d) { return d.data.url; });
arc.append("path")
.attr("d", path).transition()
.attr("fill", function(d) { return d.data.color; });
arc.append("text")
.attr("transform", function(d) { return "translate(" + label.centroid(d) + ")"; })
.attr("dy", "0.35em")
.text(function(d) { return d.data.title; });
The text and the arrows are two separate concerns that probably merit their own questions.
Curved text
To do text on a path with d3, you might want to look at the textpath documentation. It's going to be a little tricky; basically, you'll want to create a second d3.arc() generator with a slightly longer outer radius. Use the longer one to set the d attribute of path elements (that you need to create) inside the SVG's defs object, and reference those path elements' ids from textpath elements (that you also need to create).
Curved arrows
To accomplish this exactly like the image, you're probably going to need to some manual construction (including figuring out the math!) of the d attribute yourself to add appropriate arrowheads (see the SVG path syntax). If you're doing a static image, it might be faster to just create the lines (again, using a longer-radius d3.arc() generator), and export the SVG with something like SVG crowbar to a drawing program like Illustrator or Inkscape, and add the arrowheads there.
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.
I'm trying to create a reusable pie chart with dynamic transitions as a learning task. I'm working off of the d3.js resuable components e-book by Chris Viau.
The problem I'm having is basically its not updating, but creating multiple pie charts. I'm wondering if I'm not understanding how d3.dispatch works or whether I've messed something up in the way the pie char should work. It creates multiple circles instead of dynamically updating a single pie chart with random values.
here is my jsfiddle.
http://jsfiddle.net/seoulbrother/Upcr5/
thanks!
js code below:
d3.edge = {};
d3.edge.donut = function module() {
var width = 460,
height = 300,
radius = Math.min(width, height) / 2;
var color = d3.scale.category20();
var dispatch = d3.dispatch("customHover");
function graph(_selection) {
_selection.each(function(_data) {
var pie = d3.layout.pie()
.value(function(_data) { return _data; })
.sort(null);
var arc = d3.svg.arc()
.innerRadius(radius - 100)
.outerRadius(radius - 50);
if (!svg){
var svg = d3.select(this).append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
}
var path = svg.selectAll("path")
.data(pie)
.enter().append("path")
.attr("fill", function(d, i) { return color(i); })
.attr("d", arc)
.each(function(d) {this._current = d;} );
path.transition()
.ease("elastic")
.duration(750)
.attrTween("d", arcTween);
function arcTween(a) {
var i = d3.interpolate(this._current, a);
this._current = i(0);
return function(t) {
return arc(i(t));
};
}
});
}
d3.rebind(graph, dispatch, "on");
return graph;
}
donut = d3.edge.donut();
var data = [1, 2, 3, 4];
var container = d3.select("#viz").datum(data).call(donut);
function update(_data) {
data = d3.range(~~(Math.random() * 20)).map(function(d, i) {
return ~~(Math.random() * 100);
});
container.datum(data).transition().ease("linear").call(donut);
}
update();
setTimeout( update, 1000);
The main reason for multiple SVGs appearing is that you're not checking if there is one already correctly. You're relying on the variable svg being defined, but define it only after checking whether it is defined.
The better way is to select the element you're looking for and check whether that selection is empty:
var svg = d3.select(this).select("svg > g");
if (svg.empty()){ // etc
In addition, you need to handle the update and exit selections in your code in addition to the enter selection. Complete jsfiddle here.