I got the following D3 v4 pie chart, every time I try updating it the data doesn't update correctly. I have been reading around tried following some other example, but just can't seem to get it to work. Current update function looks like this:
function PieGenUpdater(data, colourRangeIn) {
var dataset = data;
var width = 400;
var height = 400;
var radius = Math.min(width, height) / 2;
var arc = d3.arc()
.innerRadius(radius/1.5)
.outerRadius(radius);
var pie = d3.pie()
.value(function(d) { return d.percent; })
.sort(null);
var svg = d3.select('#c-pie');
var path = svg.selectAll('path').data(pie(dataset));
path.enter()
.append("path")
.attr('fill', function(d, i) {
return d.data.color;
})
.attr("d", arc)
.each(function(d) {this._current = d;} );
path.transition()
.attrTween("d", arcTweenCoverage);
path.exit().remove();
// Store the displayed angles in _current.
// Then, interpolate from _current to the new angles.
// During the transition, _current is updated in-place by d3.interpolate.
function arcTweenCoverage(a) {
var i = d3.interpolate(this._current, a);
this._current = i(0);
return function(t) {
return arc(i(t));
};
}
}
https://jsfiddle.net/mahsan/zup6kafk/
Any help is greatly appreciated.
Here is the 4 years too late answer...
In PieGenUpdater change
var path = svg.selectAll('path').data(pie(dataset));
to
var path = svg.select('g').selectAll('path').data(pie(dataset));
In your update function you were adding the additional paths from dataset 1 directly under element instead of the element
Related
I'm getting JSON data do D3 line graph. Next requesting new data after last point and concat() the new data, updating the line and moving to the left on each transition()
I need to recalculate xScale each time to put the new time values and remove the old ones on x-axis. And the x-axis is scrolling to the left smoothly and correctly. But when I start to recalculation the xScale my line path stop smooth translation to the left and just jumping immediately on each update_path(). If I remove xScale.domain(d3.extent(... the path is transitioning smoothly to the left, but don't have the new times on x-axis.
function update_path(svg,path,dataset,xScale) {
var last=dataset[dataset.length-1];
var last_point=last[Object.keys(last)[0]];
// check new data and put it
d3.json("data.php?chartID=1&last_point="+last_point).then(function(data) {
dataset=dataset.concat(data);
var yScale=d3.scaleLinear()
.domain([d3.max(dataset, function(d) { return d[Object.keys(d)[1]]; }), 0])
.range([0, range]);
xScale.domain(d3.extent(dataset, function(d) { return new Date(d[Object.keys(d)[0]]); }))
var translate=dataset[0];
var translate_point=translate[Object.keys(translate)[0]];
var prelast=dataset[dataset.length-2];
var prelast_point=prelast[Object.keys(prelast)[0]];
var last=dataset[dataset.length-1];
var last_point=last[Object.keys(last)[0]];
var prelast_date=new Date(prelast_point);
var last_date=new Date(last_point);
var seconds = (last_date.getTime() - prelast_date.getTime());
var line=prepare_line(xScale,yScale);
// update x-axis
var xaxis_call=d3.axisBottom(xScale).ticks().tickSize(-height);
svg.selectAll("g.main_g").selectAll("g.x-axis")
.transition()
.duration(seconds)
.ease(d3.easeLinear)
.attr("transform", "translate(-"+ xScale(new Date(translate_point))+",100)")
.call(xaxis_call)
// update line
path
.attr("d", line(dataset))
.transition()
.duration(seconds)
.ease(d3.easeLinear)
.attr("transform", "translate(-"+ xScale(new Date(translate_point))+")")
.on('end', function() {
update_path(svg,path,dataset,xScale);
});
dataset.shift();
});
}
So how to fix the line path to be updated and to move smoothly to the left at the same time while coming the new data from the right?
I have reordered my code a little bit and also the key in my case for smooth translate was .attr('transform', null) on path object.
function update_path(svg,path,dataset,xScale) {
// get last date
var last=dataset[dataset.length-1];
var last_point=last[Object.keys(last)[0]];
// check new data and put it
d3.json("data.php?chartID=1&last_point="+last_point).then(function(data) {
data=make_dataset(data,data_index); // add some my stuff to dataset object
dataset=dataset.concat(data);
for (i=0; i<data.length; i++) {
dataset.shift();
}
var translate=dataset[0];
var translate_point=translate[Object.keys(translate)[0]];
var prelast=dataset[dataset.length-2];
var prelast_point=prelast[Object.keys(prelast)[0]];
var last=dataset[dataset.length-1];
var last_point=last[Object.keys(last)[0]];
var prelast_date=new Date(prelast_point);
var last_date=new Date(last_point);
var seconds = (last_date.getTime() - prelast_date.getTime());
var line=prepare_line(xScale,yScale);
// update x-axis
var xaxis_call=d3.axisBottom(xScale)
svg.selectAll("g.main_g").selectAll("g.x-axis")
.transition()
.duration(seconds)
.ease(d3.easeLinear)
.attr("transform", "translate(-"+ xScale(new Date(translate_point))+","+height+")")
.call(xaxis_call)
// update line
path.attr('transform', null)
.attr("d", line(dataset))
.transition()
.duration(seconds)
.ease(d3.easeLinear)
.attr("transform", "translate(-"+ xScale(new Date(translate_point))+")")
.on('end', function() {
// my custom functions for preparing Scales
var xScale=axis_scaleTime(dataset, width);
var yScale=axis_scaleLinear_values1(dataset, height);
update_path(svg,path,dataset,xScale);
});
});
}
How would I add padding between some groups of segments in a donut/pie chart using d3?
UPDATE
I am using the d3.svg.arc shape generator and .padAngle(paddingFunction) where paddingFunction is defined as:
var paddingFunction = function(d,i) { return i%1==0 ? 0.1 : 0 };
This image is using the paddingFunction described above.
But if I change the padding function to this:
var paddingFunction = function(d,i) { return i%5==0 ? 0.1 : 0 };
I get this image:
Shouldn't the code return two groups of segments with a gap in-between?
Complete code:
// magic numbers
var t = 2 * Math.PI;
var arcHeight = 100;
var innerRadius = 50;
var hours = 10;
function postion(i,offset) {
offset = offset || 0;
return i*(t / hours) + offset*(t / hours)
}
var paddingFunction = function(d,i) { return i%1==0 ? 0.1 : 0 };
// arc generators
var arc = d3.svg.arc()
.innerRadius(function(d,i) { return innerRadius; })
.outerRadius(function(d,i) { return innerRadius + arcHeight; })
.startAngle(function(d, i){ return postion(d.hour);})
.endAngle(function(d, i){ return postion(d.hour,1);})
.padAngle(paddingFunction);
// data
var data = d3.range(0,hours);
data = data.map(function(d) {
return {
hour: d,
color: Math.random(),
}
});
// Scales
var colorScale = d3.scale.linear()
.domain([0, 1])
.interpolate(d3.interpolateRgb)
.range(["#ffffff", "#ffba19"]);
// render viz
var svg = d3.select("svg")
.append("g")
.attr("transform", "translate(320, 320)");
var select = svg.selectAll(".fore").data(data).enter();
select.append("path")
.classed("fore", true)
.style({
"stroke": "black",
"stroke-width": 1,
"fill": function(d) { return colorScale(d.color); }
})
.attr("d", arc);
UPDATE 2
Seems that I misunderstood how the .padAngle() method works. It adds padding on BOTH sides of a segment, I thought it added a gap between segments.
Is there an alternative method in d3 which adds a gap between segements (whilst recalculating the area of the segments so they all keep their proportions)?
Running code: http://blockbuilder.org/GitNoise/13f38aa8f4f2f06dd869
I`m trying to make the rest of the chart transparent or set it to a specific color after I click on a specific slice of the doughnut. So far so good in console the filter is working if I hard-code the type it works( I set it to null at the beginning). I don't know why i can not get the slice that I click and make the rest of the chart set to that specific color. My though is that I have to update the chart somehow but with drawdata() function doesn't work ...
Here is my code:
var filter = {
device: null,
os_version: null,
app_version: null
};
// Creating the object Doughnut
var Doughnut = function(type) {
// Properties
var width = 160;
var height = 160
var radius = Math.min(width, height) / 2;
var donutWidth = 35;
var legendRectSize = 18;
var legendSpacing = 4;
var type = type;
// Array of Colors for the graph
var color = d3.scale.category20c();
var colorFunc = function(key) {
var normalColor = color(key);
if (filter[type] == null || key == filter[type]) {
console.log("normal color")
return normalColor;
}
console.log("trans color")
return "#d5eff2";
};
// Graph Elements
var chart = null;
var svg = null;
var path = null;
var legend = null;
// Our current dataSet
var dataSet = null;
// d3 functions
var arc = d3.svg.arc()
.innerRadius(radius - donutWidth)
.outerRadius(radius);
var pie = d3.layout.pie()
.value(function(d) {
return d.value;
});
// This is the initialize method - we create the basic graph, no data
var initialize = function(chartElement){
chart = chartElement;
svg = d3.select(chart)
.append('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr('transform', 'translate(' + (width / 2) +
',' + (height / 2) + ')');
};
var update = function() {
d3.json("./api/distribution/", function(data){
dataSet = data;
data.value = +data.value;
drawData();
});
}
var drawData = function() {
path = svg.selectAll('path')
.data(pie(dataSet[type]))
.enter()
.append('path')
.attr('d', arc)
.attr('fill', function(d) {
return colorFunc(d.data.key);
})
.on('click', function(d) {
if (filter[type] == d.data.key) {
filter[type] = null;
} else {
filter[type] = d.data.key;
}
console.log(filter)
// $(chart).empty()
drawData();
});
createLegends();
};
var createLegends = function() {
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;
});
};
return{
init: initialize,
update: update
}
};
// Here we create instance of doughnuts
var doughnutGraphs = (function() {
var init = function() {
// Create four doughnuts
var doughnut1 = new Doughnut("device");
var doughnut2 = new Doughnut("os_version");
var doughnut3 = new Doughnut("app_version");
// Initialize with an element
doughnut1.init("#chart_1");
doughnut2.init("#chart_2");
doughnut3.init("#chart_3");
// Update each of them with data
doughnut1.update();
doughnut2.update();
doughnut3.update();
};
return {
init: init
}
})();
I found the answer :
Create a method to clean then call it in the drawdata()
var clean = function() {
svg.selectAll('path').remove();
and call it .on('click')
.on('click', function(d) {
if (filter[type] == d.data.key) {
filter[type] = null;
} else {
filter[type] = d.data.key;
}
console.log(filter)
// $(chart).empty()
clean();
drawData();
});
I am working on a d3 donut and am stuck on how to update the donut value where the value will flow back if for instance you change the value from 42 to 17.
I can remove the svg, but then it rewrites the new value (17) from the zero position.
I would like it to flow backwards from 42 say to 17.
var path = svg.selectAll("path")
.data(pie(dataset.lower))
.enter().append("path")
.attr("class", function(d, i) { return "color" + i })
.attr("d", arc)
.each(function(d) { this._current = d; }); // store the initial values
here is a link to my jsfidle http://jsfiddle.net/yr595n96/
and I would love any help you could offer.
Thanks
Heres you're new button click :
$("#next").click(function () {
percent = 17;
var progress = 0;
var timeout = setTimeout(function () {
clearTimeout(timeout);
var randNumber = Math.random() * (100 - 0 + 1) + 0;
//path = path.data(pie(calcPercent(17))); // update the data << change this
path = path.data(pie(calcPercent(randNumber)));
path.transition().duration(duration).attrTween("d", function (a) {
// Store the displayed angles in _current.
// Then, interpolate from _current to the new angles.
// During the transition, _current is updated in-place by d3.interpolate.
var i = d3.interpolate(this._current, a);
var i2 = d3.interpolate(progress, randNumber)
this._current = i(0);
return function(t) {
text.text( format(i2(t) / 100) );
return arc(i(t));
};
}); // redraw the arcs
}, 100);
});
Notice on line '8':
path = path.data(pie(calcPercent(randNumber)));
You need to pass new data for it to transition to. I have used a random number here just to show you any number works. I made a variable for this and passed it to both the 'path' and the text : 'i2' > d3.interpolate.
You can always just change it to 17 to suit what you asked for :)
Updated fiddle : http://jsfiddle.net/yr595n96/3/
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.