chartist.js pie chart with labels AND percentage on the pie - chartist.js

I want to create a pie chart with chartist.js with labels (which are shown in the legend) AND also with percentages in the pie itself.
This is the pie chart. I want to add percentage values to the pieces. http://i.stack.imgur.com/SiIKb.png
Here (https://gionkunz.github.io/chartist-js/examples.html) is an example with percentages in the pie. But this only works if I do NOT add labels.
Adding labels to the data (e.g. labels: ['Dog','Cat','Cow','Snake',],) results in "NaN%" display.
I want to see the percentages in the pie itself and also put labels (for the legend) into the data.
Is this possible?

You must keep an array containing your labels, and use the labelInterpolationFnc with two parameters to get index, and use it to get the proper label with percentage:
var animals = ['Dog', 'Cat', 'Cow', 'Snake'];
var data = {
series: [5, 3, 4]
};
var sum = function(a, b) {
return a + b
};
new Chartist.Pie('.ct-chart', data, {
labelInterpolationFnc: function(value, idx) {
var percentage = Math.round(value / data.series.reduce(sum) * 100) + '%';
return animals[idx] + ' ' + percentage;
}
});
<script src="https://cdn.jsdelivr.net/chartist.js/latest/chartist.min.js"></script>
<link href="://cdn.jsdelivr.net/chartist.js/latest/chartist.min.css" rel="stylesheet"/>
<div class="ct-chart ct-perfect-fourth"></div>
Note that we must not use the labels in your data array (only the series), otherwise the value parameter in labelInterpolationFnc will be filled with the label instead of the value, so we couldn't calculate the percentage.

what you would need to do ist to produce the label on your own.
You need to produce a string like [Labeltext] + ' | ' + [Share]
In my case I created a variable which holds the total sum of all pie elements... called [griall]....
Then there is this function which calculates shares...
function calcProz(value, griall) {
return Math.round(value / griall * 100) + '%';
};
later on when I generate the Array which holds the labels I use this function to add the percentages to the text...
chartlabels[i]=dbresult[i].use + ' | ' + calcProz(dbresult[i].gri,griall);
where dbresult[i].use is the initial label text and dbresult[i].gri is the value which is going to the chart (both coming from a database)
after all when defining the chart you just add the labels...
var data = {
series: chartdata,
// series: [25,16,15, 14, 4,2,1]
labels: chartlabels
};

Related

set the position of data label according to vertical bar chart column height

I am using vue chat js with chart js 2.9.4 package. And chart js plugin datalabels also plugged in to show data labels. I created a vertical bar chart and when some columns height is lower, data label is overlapped with x-axis. In order to avoid that I did as follows.
plugins: {
datalabels: {
align(context) {
const index = context.dataIndex
const value = context.dataset.data[index]
return value < 10 ? 'top' : 'center'
},
}
}
There as you can see I had to add a condition manually to set the position of the data label. If the value is less than 10, then data label should be on top and if it's more than or equal to 10 it should be on center. But you know that value 10 in the condition should be dynamic. So is there any way to identify this overlap behavior in the used plugin or any good equation to fix this issue?
Take the max value of the dataset you are showing
const data = context.dataset.data
const max = Math.max( ...data )
Then, if for the given bar it is lower than the 10% (or some other percentil) of the max bar value then just do what you where doing.
All together:
const index = context.dataIndex
const data = context.dataset.data
const max = Math.max( ...data )
const percentil = 0.10
const value = data[index]
return value < max * percentil ? 'top' : 'center'

how do i update a Google Charts pie chart to reflect filtered data but retain filtered out segments as a diff colour

I'm new to Google Charts and I'm struggling to solve this.
I have a datatable (called "result" in the code)
Name Liquidity percent
a 1.3 20%
b 2.0 20%
c 3.4 20%
d 4 20%
e 5 20%
My pie chart is set to show 5 segments of equal size - 20% - and each segment is blue
I have set a 'Number Range Filter' control wrapper to filter the liquidity - when i set the control to the range 1 to 4 the pie moves to 4 equal sized segments.
BUT... I don't want it to do this. Instead of 1 segment disappearing I want the 5 segments to remain visible and the colour of the filtered segment to change to be a different colour.
The aim being that I can see visually a total percentage that falls within the number filter.
EDIT:
So I've had a mess about and this is as far as I've got incorporating dlaliberte's comment below.
function drawChart3(chartData3) {
var result = google.visualization.arrayToDataTable(chartData3,false); // 'false' means that the first row contains labels, not data.
var chart3 = new google.visualization.ChartWrapper({
'chartType': 'PieChart',
'containerId': 'chart3_div',
'dataTable': result,
'options': {
'width': 500,
'height': 500,
'legend': {position: 'none'},
'pieSliceText': 'none',
'colors': ['blue']
},
'view': {'columns': [0 , 1]}
});
var liquidityDT = new google.visualization.DataTable();
// Declare columns
liquidityDT.addColumn('number', 'Liquidity');
// Add data.
liquidityDT.addRows([
[1],
[2],
[3],
[4],
[5],
]);
// Create a range slider, passing some options
var liquidityRangeSlider = new google.visualization.ControlWrapper({
'controlType': 'NumberRangeFilter',
'containerId': 'filter3_div',
'dataTable': liquidityDT,
'options': {
'filterColumnLabel': 'Liquidity',
'minValue': 0,
'maxValue': 5
}
});
liquidityRangeSlider.draw();
chart3.draw();
google.visualization.events.addListener(liquidityRangeSlider, 'statechange', setChartColor);
function setChartColor(){
var state = liquidityRangeSlider.getState();
var stateLowValue = state.lowValue;
var stateHighValue = state.highValue;
for (var i = 0; i < result.getNumberOfRows(); i++) {
var testValue = result.getValue(i,2);
if (testValue < stateLowValue || testValue > stateHighValue){
alert("attempting to set colors")
//this bit I have no clue how to change the color of the table row currently being iterated on
chart3.setOption({'colors' : ['red']});
}
}
chart3.draw();
}
}
so it produces the pie chart with 5 blue segments. I can move the number filter and it fires the listener event but I can't get it to affect anything on the piechart (Chart3) - The code currently attempts just to change the whole chart to RED but that isn't even working never mind the just colouring the filtered segments
So how do I effect the changes into Chart3 and how do I only effect the filtered segments?.
any clues welcome?
thanks
You can (and must, for your application) use a NumberRangeFilter outside of a Dashboard because it will otherwise always do the data filtering. Instead, just listen for the change events on the NumberRangeFilter, get its current state, and then iterate through the colors array for your chart to set the appropriate colors. You'll have to draw the chart again with the updated colors. Here is the loop to set the colors and redraw.
var colors = [];
for (var i = 0; i < result.getNumberOfRows(); i++) {
var color = (testValue < stateLowValue || testValue > stateHighValue) ? 'red' : 'blue';
colors.push(color);
}
chart3.setOption('colors', colors);
chart3.draw();

How to create a Calculated Field in dimple.js

How do i add a calculated field (a field which does calculation of two or more data fields) in dimple.js?
eg. I have two fields
1. "Sales Value"
2. "Sales Volume"
Now i have to calculate a field ASP = Sales Value /Sales Volume.
I'm afraid dimple doesn't have a built in way to handle this. I assume dimple is aggregating the data for you - hence the difficulty. But here you have no option but to pre-aggregate to the level of a data point and add the calculated field yourself. For example if your data had Brand, SKU and Channel but your chart was at the Brand, Channel level you would need to pre-process the data like this:
// var chartData is going to be the aggregated level you use for your chart.
// var added is a dictionary of aggregated data returning a row index
// for each Brand/Channel combination.
var chartData = [],
added = {};
// Aggregate to the Brand/Channel level
data.forEach(function (d) {
var key = d["Brand"] + "|" + d["Channel"],
i = added[key];
// Check the output index
if (i !== undefined) {
// Brand/Channel have been added already so add the measures
chartData[i]["Sales Value"] += parseFloat(d["Sales Value"]);
chartData[i]["Sales Volume"] += parseFloat(d["Sales Volume"]);
} else {
// Get the index for the row we are about to add
added[key] = chartData.length;
// Insert a new output row for the Brand/Channel
chartData.push({
"Brand": d["Brand"],
"Channel": d["Channel"],
"Sales Value": parseFloat(d["Sales Value"]) || 0,
"Sales Volume": parseFloat(d["Sales Volume"]) || 0
});
}
});
// Calculate ASP
chartData.forEach(function (d) {
d["ASP"] = d["Sales Value"] / d["Sales Volume"];
});
// Draw the chart using chartData instead of data
...

Dimple Stacked Bar Chart - adding label for aggregate total

I'm trying to add a label to the aggregate total for a stacked bar chart above each bar. I used this example (http://dimplejs.org/advanced_examples_viewer.html?id=advanced_bar_labels) to add the totals for each section of the bar, but I'm not sure how to add the total above. I've also been able to add total labels above each bar for a single series (not stacked). I just can't get it to work with a stacked bar chart.
My current workaround is plotting an additional null series line, but making the line and markers transparent so you can still see the total value in the tooltip. However, I'd really like to just have the totals displayed above each bar.
Here's the code:
var svg = dimple.newSvg("#chartContainer", 590, 400);
var myChart = new dimple.chart(svg, data);
myChart.setBounds(80, 30, 510, 305);
var x = myChart.addCategoryAxis("x", "Month");
x.addOrderRule(Date);
var y = myChart.addMeasureAxis("y", "Calls");
y.showGridlines = true;
y.tickFormat = ',6g';
y.overrideMin = 0;
y.overrideMax = 800000;
var s = myChart.addSeries("Metric", dimple.plot.bar);
s.afterDraw = function (shape, data) {
var s = d3.select(shape),
rect = {
x: parseFloat(s.attr("x")),
y: parseFloat(s.attr("y")),
width: parseFloat(s.attr("width")),
height: parseFloat(s.attr("height"))
};
if (rect.height >= 1) {
svg.append("text")
.attr("x", rect.x + rect.width / 2)
.attr("y", rect.y + rect.height / 2 + 3.5)
.style("text-anchor", "middle")
.style("font-size", "9px")
.style("font-family", "sans-serif")
.style("opacity", 0.8)
.text(d3.format(",.1f")(data.yValue / 1000) + "k");
}
};
myChart.addLegend(60, 10, 510, 20, "right");
myChart.draw();
Here is the JSFiddle: http://jsfiddle.net/timothymartin76/fusaqyhk/16/
I appreciate any assistance on this.
Thanks!
You can add them after drawing by calculating the bar totals and deriving the y position from that:
// Iterate every value on the x axis
x.shapes.selectAll("text").each(function (d) {
// There is a dummy empty string value on the end which we want to ignore
if (d && d.length) {
// Get the total y value
var total = d3.sum(data, function (t) { return (t.Month === d ? t.Calls : 0); });
// Add the text for the label
var label = svg.append("text");
// Set the x position
// x._scale(d) is the tick position of each element
// (myChart._widthPixels() / x._max) / 2 is half of the space allocated to each element
label.attr("x", x._scale(d) + (myChart._widthPixels() / x._max) / 2)
// Vertically center the text on the point
label.attr("dy", "0.35em")
// Style the text - this can be better done with label.attr("class", "my-label-class")
label.style("text-anchor", "middle")
.style("font-size", "9px")
.style("font-family", "sans-serif")
.style("opacity", 0.8)
// Set the text itself in thousands
label.text(d3.format(",.1f")(total / 1000) + "k");
// Once the style and the text is set we can set the y position
// y._scale(total) gives the y position of the total (and therefore the top of the top segment)
// label.node().getBBox().height gives the height of the text to leave a gap above the bar
label.attr("y", y._scale(total) - label.node().getBBox().height)
}
});
Here is your updated fiddle: http://jsfiddle.net/fusaqyhk/17/

Adding x axis labels when using dojox.charting.DataSeries

I'm creating a Dojo line chart from a dojo.data.ItemFileReadStore using a dojox.charting.DataSeries. I'm using the third parameter (value) of the constructor of DataSeries to specify a method which will generate the points on the chart. e.g.
function formatLineGraphItem(store,item)
{
var o = {
x: graphIndex++,
y: store.getValue(item, "fileSize"),
};
return o;
}
The graphIndex is an integer which is incremented for every fileSize value. This gives me a line chart with the fileSize shown against a numeric count. This works fine.
What I'd like is to be able to specify the x axis label to use instead of the value of graphIndex i.e. the under lying data will still be 1,2,3,4 but the label will show text (in this case the time at which the file size was captured).
I can do this by passing in an array of labels into the x asis when I call chart.addAxis() but this requires me to know the the values before I iterate through the data. e.g.
var dataSeriesConfig = {query: {id: "*"}};
var xAxisLabels = [{text:"2011-11-20",value:1},{text:"2011-11-21",value:2},{text:"2011-11-22",value:3}];
var chart1 = new dojox.charting.Chart("chart1");
chart1.addPlot("default", {type: "Lines", tension: "4"});
chart1.addAxis("x", {labels: xAxisLabels});
chart1.addAxis("y", {vertical: true});
chart1.addSeries("Values", new dojox.charting.DataSeries(dataStore, dataSeriesConfig, formatLineGraphItem));
chart1.render();
The xAxisLabels array can be created by preparsing the dataSeries but it's not a very nice work around.
Does anyone have any ideas how the formatLineGraphItem method could be extended to provide the x axis labels. Or does anyone have any documentation on what values the object o can contain?
Thanks in advance!
This will take a unix timestamp, multiply the value by 1000 (so that it has microseconds for JavaScript, and then pass the value to dojo date to format it).
You shouldn't have any problems editing this to the format you need.
You provided examples that your dates are like "1", "2", "3", which is clearly wrong. Those aren't dates.. so this is the best you can do unless you edit your question.
chart1.addAxis("x",{
labelFunc: function(n){
if(isNaN(dojo.number.parse(n)) || dojo.number.parse(n) % 1 != 0){
return " ";
}
else {
// I am assuming that your timestamp needs to be multiplied by 1000.
var date = new Date(dojo.number.parse(n) * 1000);
return dojo.date.locale.format(date, {
selector: "date",
datePattern: "dd MMMM",
locale: "en"
});
}
},
maxLabelSize: 100
}