Hello I have two Piecharts with percentage values declareted.
Chart1
.width(250)
.height(250)
.slicesCap(4)
.innerRadius(0)
.dimension(statusTotalDim)
.title(function(d){return "At this part there are " + d.value +" "+ d.key+ " Directories! ";})
.colors(d3.scale.ordinal().range(["#ff2222","#4855ff"]))
.label(function (d) {
if (Chart1.hasFilter() && !Chart1.hasFilter(d.key)) {
return d.key + ' (0%)';
}
var label = d.key;
if (all.value()) {
label += ' (' + Math.floor(d.value / all.value() * 100) + '%)';
}
return label ;
})
.group(countPer);
Thats one of the charts. the other chart is the same but only with a filter more.
So for example I have in chart 1 this values 45% and 55%
In chart 2 I have the values 40% and 60%
How is possible to get difference of chart1(40%)-chart2(45%) = -5%
Is it possible to take the labels and make a measure separately?
Thank you very very much!
Related
Is this possible? The reason I am asking the question is first I did the concentric donuts with 2 datasets and the slices size did not match related data it was each proportionate but slightly smaller on the inner ring and I want the slices to match inner and outer. So I read you need nested dataset.
I need the pie slices of the first 2 values of apples to match the first 2 slices of the inner and outer donuts. Then I need the total value of the remaining apples to be one slice and it needs to match the same individual pie slices of the rest of the first array. So the client just wants to compare the summed values or see it as only 3 slices compared to the 5 slices.
I used the working apples and oranges JSfiddle to start with from the internet: https://jsfiddle.net/vgq0z5aL/
I modified it here to use the dataset that will work with my problem but couldn't get it to work. Something wrong with the dataset I think?
My Example: https://jsfiddle.net/aumnxjc8/
How can I fix the dataset so it works?
var dataset = {
apples: [13245, 28479, 1000, 1000, 3000],
apples2: [dataset[0][0], dataset[0][1], sumofapples],
};
var sumofapples = dataset[0][3]+ dataset[0][4]+dataset[0][5];
var width = d3.select('#duration').node().offsetWidth,
height = 300,
cwidth = 33;
var colorO = ['#1352A4', '#2478E5', '#5D9CEC', '#A4C7F4', '#DBE8FB'];
var colorA = ['#58A53B', '#83C969', '#A8D996'];
var pie = d3.layout.pie()
.sort(null);
var arc = d3.svg.arc();
var svg = d3.select("#duration svg")
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
console.log(dataset);
var gs = svg.selectAll("g").data(d3.values(dataset)).enter().append("g");
var path = gs.selectAll("path")
.data(function(d, i) { return pie(d); })
.enter().append("path")
.attr("fill", function(d, i, j) {
if (j == 0) {
return colorO[i];
} else {
return colorA[i];
}
})
.attr("d", function(d, i, j) {
if (j == 0) {
return arc.innerRadius(75 + cwidth * j - 17).outerRadius(cwidth * (j + 2.9))(d);
} else {
return arc.innerRadius(75 + cwidth * j - 5).outerRadius(cwidth * (j + 2.5))(d);
}
});
Try:
const apples = [13245, 28479, 11111, 11000, 3876];
const apples2 = [apples[0], apples[1],
apples.slice(2).reduce((sum,item) => sum + item, 0)];
const dataset = { apples, apples2 };
You can see the result in a fiddle
I am working on pie chart with d3 js. I want to rotate every arc of my pie chart 180. I know that I am unable to explain completely show here is my fiddle link.
[fiddle]: https://jsfiddle.net/dsLonquL/
How can i get dynamic parameters for translate() function.
Basically you need to work out the centre point of the edge of each arc. I used this example for help : How to get coordinates of slices along the edge of a pie chart?
This works okay, but I needed to rotate the points to get them in the correct positions. As it is in radians the rotation is the following :
var rotationInRadians = 1.5708 * 1.5;
Now using the example before I used the data for the paths, so the start and end angle and got the center points like so :
var thisAngle = (d.startAngle + rotationInRadians + (d.endAngle + rotationInRadians - d.startAngle + rotationInRadians) / 2);
var x = centreOfPie[0] + radius * 2 * Math.cos(thisAngle)
var y = centreOfPie[1] + radius * 2 * Math.sin(thisAngle)
I created a function to show circles at these points to clarify :
function drawCircle(points, colour) {
svg.append('circle')
.attr('cx', points[0])
.attr('cy', points[1])
.attr('r', 5)
.attr('fill', colour);
}
Called it inside the current function like so :
drawCircle([x, y], color(d.data.label))
And then translated and rotated accordingly :
return 'translate(' + (x) + ',' + y + ') rotate(180)';
I added a transition so you can see it working. Here is the final fiddle :
https://jsfiddle.net/thatOneGuy/dsLonquL/7/
EDIT
In your comments you say you want the biggest segment to be kept in the middle. So we need to run through the segments and get the biggest. I have also taken care of duplicates, i.e if two or more segments are the same size.
Here is the added code :
var biggestSegment = {
angle: 0,
index: []
};
path.each(function(d, i) {
var thisAngle = (d.endAngle - d.startAngle).toFixed(6);//i had to round them as the numbers after around the 7th or 8th decimal point tend to differ tet theyre suppose to be the same value
if (i == 0) {
biggestSegment.angle = thisAngle
} else {
if (biggestSegment.angle < thisAngle) {
biggestSegment.angle = thisAngle;
biggestSegment.index = [i];
} else if (biggestSegment.angle == thisAngle) {
console.log('push')
biggestSegment.index.push(i);
}
}
})
Now this goes through each path checks if its bigger than the current value, if it is overwrite the biggest value and make note of the index. If its the same, add index to index array.
Now when translating the paths, you need to check the current index against the index array above to see if it needs rotating. Like so :
if (biggestSegment.index.indexOf(i) > -1) {
return 'translate(' + (centreOfPie[0]) + ',' + (centreOfPie[1]) + ')' // rotate(180)';
} else {
return 'translate(' + (x) + ',' + y + ') rotate(180)';
}
Updated fiddle : https://jsfiddle.net/thatOneGuy/dsLonquL/8/
I have editted 3 values to be different to the rest. Go ahead and change these, see what you think :)
This is a pure middle school geometry job.
CASE 1: The vertex of each sector rotation is on the outer line of the circle
fiddle
// ... previous code there
.attr('fill', function(d, i) {
return color(d.data.label);
})
.attr("transform", function(d, i) {
var a = (d.endAngle + d.startAngle) / 2, // angle of vertex
dx = 2 * radius * Math.sin(a), // shift/translate is two times of the vertex coordinate
dy = - 2 * radius * Math.cos(a); // the same
return ("translate(" + dx + " " + dy + ") rotate(180)"); // output
});
CASE 2: The vertex on the center of the chord
fiddle
// ... previous code there
.attr('fill', function(d, i) {
return color(d.data.label);
})
.attr("transform", function(d, i) {
var dx = radius * (Math.sin(d.endAngle) + Math.sin(d.startAngle)), // shift/translation as coordinate of vertex
dy = - radius * (Math.cos(d.endAngle) + Math.cos(d.startAngle)); // the same for Y
return ("translate(" + dx + " " + dy + ") rotate(180)"); // output
});
Dimension 'dim_pie' is passing any character value and Measure 'fact_pie' is passing any integer type value.
So how can i display the percentage of Measure value on label of pie-chart and how can i partition all pie slice based on sum of measure value ?
var ndx = crossfilter(datachart);
var dim_pie = ndx.dimension(function(d) {return d.Opportunity_Name;});
var fact_pie = dim_pie.group().reduceSum(function(d) {return +d.Amount;});
PieChart
.width(300)
.height(400)
.dimension(dim_pie)
.group(fact_pie)
.innerRadius(30)
.renderLabel(true)
.label(function(d){ return d.key + " : " + d.value + " - " +(d.value / ndx.groupAll().reduceCount().value() * 100).toFixed(2) + "%"; })
.colors(d3.scale.ordinal().range([ "red", "#006400" ]))
;
Data:
Opportunity_Name Amount
Accenture 10
Apple 80
Cognizant 80
CTS 20
Dell 60
Facebook 40
Google 20
Hp 20
Hp 10
IBM 30
LinkedIn 10
Oracle 90
TCS 10
Wipro 80
Wipro 10
Please add this code to your pie chart.
.label(function(d) { return d.key +" (" + Math.floor(d.value / all.value() * 100) + "%)"; });
This will display percentages of your measure values in brackets along with the key name.
Ok, first - look at this fiddle.
You should see shapes rotating back and forth like crazy.
This is what is going on:
force.on("tick", function(e) {
vis.selectAll("path")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")"
// this is the thing
+"rotate(" + Math.random() * 50 + ")";
});
});
On every tick I'm changing the transform: rotate() to Math.random() * 50 in this case.
Now what I want is a smooth rotation. Not this jerky stuff.
See this to better understand what I mean. Imagine the height as the rotation. The gray box represents what I have now, the blue - what I want.
I tried applying 'transition: all 1s ease' CSS to that element, but it just ignores it, I'm obviously doing it wrong.
So how do I make this infinite back and forth rotation smooth as if I was using CSS3 transitions?
Every tick you are randomly setting the rotation to something between 0 and 50 degrees of rotation. You need to maintain the current rotation, calculate an offset, and then set the rotation to the current + offset.
Here's an updated tick function:
force.on("tick", function(e) {
vis.selectAll("path")
.attr("transform", function(d) {
if(!d.rotate) {
d.rotate = Math.random() * 50;
} else {
d.rotate = d.rotate + 1;
}
return "translate(" + d.x + "," + d.y + ")"
+"rotate(" + d.rotate + ")";
});
});
Here's the updated working example: https://jsfiddle.net/1aLc7x4j/
I've created a line chart based on the example found here:
http://bl.ocks.org/mbostock/3884955
However, with my data the line labels (cities) end up overlapping because the final values on the y-axis for different lines are frequently close together. I know that I need to compare the last value for each line and move the label up or down when the values differ by 12 units or less. My thought is to look at the text labels that are written by this bit of code
city.append("text")
.datum(function(d) { return {name: d.name, value: d.values[d.values.length - 1]}; })
.attr("transform", function(d) { return "translate(" + x(d.value.date) + "," + y(d.value.temperature) + ")"; })
.attr("x", 3)
.attr("dy", ".35em")
.text(function(d) { return d.name; });
If the y(d.value.temperature) values differ by 12 or less, move the values apart until they have at least 12 units between them. Any thoughts on how to get this done? This is my first d3 project and the syntax is still giving me fits!
You're probably better off passing in all the labels at once -- this is also more in line with the general d3 idea. You could then have code something like this:
svg.selectAll("text.label").data(data)
.enter()
.append("text")
.attr("transform", function(d, i) {
var currenty = y(d.value.temperature);
if(i > 0) {
var previousy = y(data[i-1].value.temperature),
if(currenty - previousy < 12) { currenty = previousy + 12; }
}
return "translate(" + x(d.value.date) + "," + currenty + ")";
})
.attr("x", 3)
.attr("dy", ".35em")
.text(function(d) { return d.name; });
This does not account for the fact that the previous label may have been moved. You could get the position of the previous label explicitly and move the current one depending on that. The code would be almost the same except that you would need to save a reference to the current element (this) such that it can be accessed later.
All of this will not prevent the labels from being potentially quite far apart from the lines they are labelling in the end. If you need to move every label, the last one will be pretty far away. A better course of action may be to create a legend separately where you can space labels and lines as necessary.
Consider using a D3 force layout to place the labels. See an example here: https://bl.ocks.org/wdickerson/bd654e61f536dcef3736f41e0ad87786
Assuming you have a data array containing objects with a value property, and a scale y:
// Create some nodes
const labels = data.map(d => {
return {
fx: 0,
targetY: y(d.value)
};
});
// Set up the force simulation
const force = d3.forceSimulation()
.nodes(labels)
.force('collide', d3.forceCollide(10))
.force('y', d3.forceY(d => d.targetY).strength(1))
.stop();
// Execute thte simulation
for (let i = 0; i < 300; i++) force.tick();
// Assign values to the appropriate marker
labels.sort((a, b) => a.y - b.y);
data.sort((a, b) => b.value - a.value);
data.forEach((d, i) => d.y = labels[i].y);
Now your data array will have a y property representing its optimal position.
Example uses D3 4.0, read more here: https://github.com/d3/d3-force