Google Chart - Centering Annotations - google-visualization

I simply want to "center" the annotation in the bars in this chart. You can see how the data appear at the end of the bar. Can that percentage be centered in the bar?
Try as I might, I cannot figure it out. I found a block of code that was described as a hack, but it would have to be customized for each chart. I use this code to generates lots and lots of charts. Any help would be wildy appreciated!
function drawVisualizationstack(qid,cdata, w,h,l,cw,pos,stacked,questioncase) {
pos='top';
var data = new google.visualization.DataTable();
var e;
for (var k=0;k<cdata.length;k++) {
e=cdata[k];
if (e[0]=='column') {
if (e[2]=="tooltip") {
data.addColumn({type:'string',role:'tooltip','p':{'html':true}});
//data.addColumn({type: 'string', role: 'annotation','p':{'html':true}});
}
else if (e[2]=="annotation") {
data.addColumn({type: 'string', role: 'annotation', p: {html: true}});
}
else {
if (e[2]=="All") e['2']="All";
data.addColumn(e[1],e[2]);
}
}
else {
if (e==="" || e===undefined) {mclog('UNDEFINED DATA ROW FOR ' + qid);}
else {
data.addRow(e);
}
}
}
h=h*.75;
var chartHeight=h-65;
var options = {
width:500,
height:h,
isStacked:true,
chartArea:{height:chartHeight,left:l,width:cw},
backgroundColor:'transparent',
bar:{groupWidth:'80%'},
tooltip: {isHtml:true},
legend:{position:'none'},
hAxis: {title: 'Percentage',minValue:0,maxValue:100},
hAxis: {textPosition: 'none',ticks: [0]},
colors: ['#5d5d5d', '#002e32', '#0e1a40', '#364940', '#a9b861', '#404000', '#504000', '#604000', '#003070', '#687000'] //set default colors for charts
}}},
true,italic: true,color: '#871b47',auraColor: '#d799ae',opacity: 0.8}}
}
var chart = new google.visualization.BarChart(document.getElementById('questchart_'+qid));
jQuery("base").attr('href',document.location);
chart.draw(data,options);
}

Related

Using the dataURI method, I am not getting any color for the series of my pie chart

I have the following code running:
var options = {
chart: {
type: 'donut',
fontFamily: 'Lato Light'
},
series: [1,2,3,4,5],
labels: ['1','2','3','4','5'],
theme: {
monochrome: {
enabled: true,
color: '#b19254',
shadeTo: 'dark',
shareIntensity: 0.15
}
},
//colors: ['#b19254', '#9f834c', '#8e7543', '#7c663b', '#b99d65', '#c8b387'],
legend: {
position: 'bottom'
},
plotOptions: {
pie: {
donut: {
labels: {
show: true,
name: {
show: false
},
value: {
offsetY: -1,
show: true
},
total: {
show: false,
showAlways: false,
formatter: function (w) { return String(Math.round(chart.w.globals.seriesTotals.reduce((a,b) => { return a+b}, 0) * 100) / 100) + ' ' + $currency}
}
}
}
}
},
}
var chart = new ApexCharts(document.querySelector("#investment-chart-wrapper"), options);
chart.render();
var $chartData = chart.dataURI();
$chartData.then(
(result) => {
document.querySelector('#chartimg').setAttribute('src',result.imgURI);
});
The bit I am fighting with is the promise result of the dataURI() method from here.
For some reason, the chart I get has all the information including the series labels, but the color for the series does not show, leaving me with this. The color is used for the legend at the bottom, however.
I am sure I am missing something here. Please let me know what.
I was running into this problem as well today. It was because the animation of the chart has not taken place yet. You have to get the dataURI() after it has fully rendered or turn off the chart animation.
I was able to get this working by setting the rendered chart to a variable at the top of my js file and then using it in a function like this:
function SetChartImage() {
chartHistoricalPCTArea.dataURI().then(({ imgURI }) => {
var image = document.querySelector('#HistoricalPCTImage');
image.src = imgURI;
})
}

Chart.js sync legend toggle on multiple charts

There are solutions to using legend onClick to toggle on/off visibility of (all other) datasets on the clicked chart, but I needed a way to sync this toggle on multiple charts if they have the same label/legend. For example, I have 6 charts presenting different information about the same data. However, not all the charts have all the datasets. One may have 5 datasets, another has 3 and so on. And they may not show up in the same order either.
The goal was to be able to click a legend item on one chart, and that same item be toggled on all the charts.
Since I did not find an existing solution, I'm posting this.
To do this, I put all the charts in a global var and loop through them to match dataset by legendItem.text instead of legendItem.datasetindex, since the label may or may not exist or even be in the same index position on other charts.
Here's how I create/replace the multiple charts: https://stackoverflow.com/a/51882403/1181367
And here's the legend onClick toggle solution:
var config = {
type: type,
data: {
labels: labels,
datasets: datasets
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
yAxes: [{
ticks: {
beginAtZero: true,
}
}]
},
legend: {
position: 'right',
onClick: function (e, legendItem) {
var text = legendItem.text;
Object.keys(charts).forEach(function (id) {
// loop through the charts
var ci = charts[id].chart
var cindex = (function () {
var match = null;
ci.legend.legendItems.forEach(function (item) {
if (item.text == text) {
// get index for legend.text that matches clicked legend.text
match = item.datasetIndex;
}
});
return match;
})();
if (cindex !== null) {
// if there's a match
var alreadyHidden = (ci.getDatasetMeta(cindex).hidden === null) ? false : ci.getDatasetMeta(cindex).hidden;
ci.data.datasets.forEach(function (e, i) {
var meta = ci.getDatasetMeta(i);
if (i !== cindex) {
if (!alreadyHidden) {
meta.hidden = meta.hidden === null ? !meta.hidden : null;
} else if (meta.hidden === null) {
meta.hidden = true;
}
} else if (i === cindex) {
meta.hidden = null;
}
});
ci.update();
}
});
}
}
}
};
After using Chris's answer here https://stackoverflow.com/a/51920456/671140 I created a simplified solution.
var config = {
type: type,
data: {
labels: labels,
datasets: datasets
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
yAxes: [{
ticks: {
beginAtZero: true,
}
}]
},
legend: {
position: 'right',
onClick: function (e, legendItem) {
var text = legendItem.text;
Object.keys(charts).forEach(function (id) {
// loop through the charts
var ci = charts[id].chart
ci.legend.legendItems.forEach(function (item) {
if (item.text == text) {
ci.options.legend.onClick.call(chart.legend, null, item);
ci.update();
}
});
});
}
}
}
};
The benefit of using this solution is that it will call any custom onClick() handlers that have been added to any of the chart legends.
If anyone's looking for a way to sync a pie chart legend with a line chart legend, try this (it should also work just fine if the charts are the same type, too):
onClick: function(e, legendItem) {
// Save name of clicked label, for later comparison
var legendName = legendItem.text;
// Iterate through global charts array
Object.keys(myCharts).forEach(function(id) {
// Assign shorthand variable to address chart easier
var chrt = myCharts[id];
// Determine chart type
var chartType = chrt.config.type;
// Iterate through each legend in the chart
chrt.legend.legendItems.forEach(function(item) {
// If legend name matches clicked label
if (item.text == legendName) {
if (chartType == 'pie') { // If pie chart
if (chrt.getDatasetMeta(0).data[item.index].hidden === true) chrt.getDatasetMeta(0).data[item.index].hidden = false;
else if (chrt.getDatasetMeta(0).data[item.index].hidden === false) chrt.getDatasetMeta(0).data[item.index].hidden = true;
} else if (chartType == 'line') { // If line chart
if (chrt.getDatasetMeta(item.datasetIndex).hidden === true) chrt.getDatasetMeta(item.datasetIndex).hidden = null;
else if (chrt.getDatasetMeta(item.datasetIndex).hidden === null) chrt.getDatasetMeta(item.datasetIndex).hidden = true;
}
// Trigger chart update
chrt.update();
}
});
});
Place this onClick function in the legend section of the options, just like you can see in the other answers.
My line chart was a day-to-day trend, while the pie chart compared totals across the entire range.
The chart's labels must be identically named for this to work, of course.
Like the other solutions, you must store both charts in one, global variable, like:
window.myCharts['pieChart1']
window.myCharts['lineChart1']
Since hiding datasets, and getting their indexes within the whole dataset, is different depending on the chart type, this function will check the chart type and act accordingly.
Also notice that for pie charts, the "hidden" setting is either true or false, but for line charts, it's either true or null (thanks, chart.js).
I'm sure you can expand this for other chart types, but I've only bothered setting it up for 'line' and 'pie' charts.
If someone is looking for this, here is the working solution:
onClick: function (e, legendItem, legend) {
var text = legendItem.text;
Object.keys(charts).forEach(function (id) {
var ci = charts[id]
ci.legend.legendItems.forEach(function (item) {
if (item.text == text) {
if (ci.data.datasets[item.datasetIndex].hidden == true) {
ci.data.datasets[item.datasetIndex].hidden = false;
} else {
ci.data.datasets[item.datasetIndex].hidden = true;
}
}
});
ci.update();
});
}

Pie Chart Legend - Chart.js

I need help to put the number of the pie chart in the legend
Chart Image
If i hover the chart with mouse i can see the number relative to each item
i want to display it in the legend either
the important code so far:
var tempData = {
labels: Status,
datasets: [
{
label: "Status",
data: Qtd,
backgroundColor: randColor
},
]
};
var ctx = $("#pieStatus").get(0).getContext("2d");
var chartInstance = new Chart(ctx, {
type: 'pie',
data: tempData,
options: {
title: {
display: true,
fontsize: 14,
text: 'Total de Pedidos por Situação'
},
legend: {
display: true,
position: 'bottom',
},
responsive: false
}
});
"Qtd","randColor" are "var" already with values
You need to edit the generateLabels property in your options :
options: {
legend: {
labels: {
generateLabels: function(chart) {
// Here
}
}
}
}
Since it is quite a mess to create on your own a great template. I suggest using the same function as in the source code and then edit what is needed.
Here are a small jsFiddle, where you can see how it works (edited lines - from 38 - are commented), and its result :
Maybe this is a hacky solution, but for me seems simpler.
The filter parameter
ChartJS legend options have a filter parameter. This is a function that is called for each legend item, and that returns true/false whether you want to show this item in the legend or not.
filter has 2 arguments:
legendItem : The legend item to show/omit. Its properties are described here
data : The data object passed to the chart.
The hack
Since JS passes objects by reference, and filter is called for each legend item, then you can mutate the legendItem object to show the text that you want.
legend : {
labels: {
filter: (legendItem, data) => {
// First, retrieve the data corresponding to that label
const label = legendItem.text
const labelIndex = _.findIndex(data.labels, (labelName) => labelName === label) // I'm using lodash here
const qtd = data.datasets[0].data[labelIndex]
// Second, mutate the legendItem to include the new text
legendItem.text = `${legendItem.text} : ${qtd}`
// Third, the filter method expects a bool, so return true to show the modified legendItem in the legend
return true
}
}
}
Following on from tektiv's answer, I've modified it for ES6 which my linter requires;
options: {
legend: {
labels: {
generateLabels: (chart) => {
const { data } = chart;
if (data.labels.length && data.datasets.length) {
return data.labels.map((label, i) => {
const meta = chart.getDatasetMeta(0);
const ds = data.datasets[0];
const arc = meta.data[i];
const custom = (arc && arc.custom) || {};
const { getValueAtIndexOrDefault } = Chart.helpers;
const arcOpts = chart.options.elements.arc;
const fill = custom.backgroundColor ? custom.backgroundColor : getValueAtIndexOrDefault(ds.backgroundColor, i, arcOpts.backgroundColor);
const stroke = custom.borderColor ? custom.borderColor : getValueAtIndexOrDefault(ds.borderColor, i, arcOpts.borderColor);
const bw = custom.borderWidth ? custom.borderWidth : getValueAtIndexOrDefault(ds.borderWidth, i, arcOpts.borderWidth);
const value = chart.config.data.datasets[arc._datasetIndex].data[arc._index];
return {
text: `${label}: ${value}`,
fillStyle: fill,
strokeStyle: stroke,
lineWidth: bw,
hidden: Number.isNaN(ds.data[i]) || meta.data[i].hidden,
index: i,
};
});
}
return [];
},
},
},
},
I wanted to let the user select from 100+ data sets, but rather than adding/removing them from my Chart I decided to set the showLine: false on any dataset that I want hidden. Unfortunately the default legend would show all 100+. So in my solution I generate the legend manually, filtering out any dataset that has showLine: false.
Your settings will have this:
legend: {
labels: {
generateLabels: (a) => {
return a.data.labels
}
}
And you'll generate your own labels with a helper function:
function updateAllLabels() {
const myNewLabels = [];
myChart.data.datasets.forEach((element) => {
if (element.showLine) {
myNewLabels.push(generateLabel(element));
}
});
myChart.data.labels = myNewLabels;
}
And you'll generate the label with another function:
function generateLabel(data) {
return {
fillStyle: data.borderColor,
lineWidth: 1,
strokeStyle: data.borderColor,
text: data.countyName, // I attach countryName to my datasets for convenience
}
}
Now just don't forget to call the function whenever updating your chart:
updateAllLabels();
myChart.update();
Happy graphing!

Change color of a bar in google columnchart

I am using columnchart to generate a chart . Below is the code:
function drawActiveJobsChart() {
var i=0;
var j=0;
var data = new google.visualization.DataTable();
data.addColumn('string','Systems');
data.addColumn('number', 'Counts');
$.getJSON('SubSystemNumbers.action', function(json) {
$.each( json.SysActNum, function( key, val ) {
i++;
});
data.addRows(i);
$.each( json.SysActNum, function( key, val ) {
if (val>=30) {
data.setProperty(j,1,'style','color:red');
}
data.setValue(j, 0,key);
data.setValue(j, 1,val);
j++;
});
});
var options = {
title: 'System Numbers',
width:900, height:400,
allowHtml: true,
hAxis: {title: 'Systems', titleTextStyle: {color: 'red'},textStyle: {color: 'red'}}
};
var chart = new google.visualization.ColumnChart(document.getElementById(chartDiv));
chart.draw(data, options);
}
Now i have added logic to change the bar color to red when return val is greater than 30 . I tried running in debug and code is executing fine but bar is not showing in red . Please suggest ! Help is appreciated
Setting the "style" property of a cell is not going to change the color of a bar, you need to add a "style" role column to your DataTable:
var data = new google.visualization.DataTable();
data.addColumn('string','Systems');
data.addColumn('number', 'Counts');
data.addColumn({type: 'string', role: 'style'});
and add the style information to this column:
$.getJSON('SubSystemNumbers.action', function(json) {
$.each( json.SysActNum, function( key, val ) {
var style = (val >= 30) ? 'color: red' : null;
data.addRow([key, val, style]);
});
});
see working example based on your code here: http://jsfiddle.net/asgallant/dGYs7/
Just in case somebody else runs in to the same issue as me:
I had a similar problem where I couldn't change the color of specific bars in a bar-chart. I did as the documentation said, and tried the answer here by asgallant, but nothing worked.
Turned out, I just needed to change google.charts.Bar to google.visualization.ColumnChart.

Google Line Chart Legend Click Events

I want to hide the line in Line chart when ever the user clicks on the Line Chart legend. Is there any way that I can do it in Google Chart API ? I seen this feature on Highcharts.
Yes it is possible. Here is an example by asgallant:
function drawChart() {
var data = new google.visualization.DataTable();
data.addColumn('string', 'City');
data.addColumn('number', 'Foo');
data.addColumn('number', 'Foo');
data.addColumn('number', 'Bar');
data.addColumn('number', 'Bar');
data.addColumn('number', 'Baz');
data.addColumn('number', 'Baz');
data.addRow(['Boston', 5, null, 7, null, 2, null]);
data.addRow(['New York', 4, null, 8, null, 5, null]);
data.addRow(['Baltimore', 6, null, 2, null, 4, null]);
/* define the series object
* follows the standard 'series' option parameters, except it has two additonal parameters:
* hidden: true if the column is currently hidden
* altColor: changes the color of the legend entry (used to grey out hidden entries)
*/
var series = {
0: {
hidden: false,
visibleInLegend: false,
color: '#FF0000'
},
1: {
hidden: false,
color: '#FF0000',
altColor: '#808080'
},
2: {
hidden: false,
visibleInLegend: false,
color: '#00FF00'
},
3: {
hidden: false,
color: '#00FF00',
altColor: '#808080'
},
4: {
hidden: false,
visibleInLegend: false,
color: '#0000FF'
},
5: {
hidden: false,
color: '#0000FF',
altColor: '#808080'
}
};
var options = {
series: series,
height: 400,
width: 600
};
var view = new google.visualization.DataView(data);
var chart = new google.visualization.ColumnChart(document.getElementById('chart_div'));
google.visualization.events.addListener(chart, 'select', function () {
// if row is undefined, we clicked on the legend
if (typeof chart.getSelection()[0]['row'] === 'undefined') {
// column is the DataView column, not DataTable column
// so translate and subtract 1 to get the series index
var col = view.getTableColumnIndex(chart.getSelection()[0]['column']) - 1;
// toggle the selected column's data counterpart visibility
series[col - 1].hidden = !series[col - 1].hidden;
// swap colors
var tmpColor = series[col].color;
series[col].color = series[col].altColor;
series[col].altColor = tmpColor;
// reset the view's columns
view.setColumns([0,1,2,3,4,5,6]);
// build list of hidden columns and series options
var hiddenCols = [];
options.series = [];
for (var i = 0; i < 6; i++) {
if (series[i].hidden) {
// add 1 to the series index to get DataTable column
hiddenCols.push(i + 1);
}
else {
options.series.push(series[i]);
}
}
// hide the columns and draw the chart
view.hideColumns(hiddenCols);
chart.draw(view, options);
}
});
chart.draw(view, options);
}
Here is the solution. You can hide line in your line chart by clicking the legend.
google.visualization.events.addListener(chart, 'select', function () {
var sel = chart.getSelection();
// if selection length is 0, we deselected an element
if (sel.length > 0) {
// if row is undefined, we clicked on the legend
if (typeof sel[0].row === 'undefined') {
var col = sel[0].column;
if (columns[col] == col) {
// hide the data series
columns[col] = {
label: data.getColumnLabel(col),
type: data.getColumnType(col),
calc: function () {
return null;
}
};
// grey out the legend entry
series[col - 1].color = '#CCCCCC';
}
else {
// show the data series
columns[col] = col;
series[col - 1].color = null;
}
var view = new google.visualization.DataView(data);
view.setColumns(columns);
chart.draw(view, options);
}
}
});
Here is the working sample. jqfaq.com
As mentioned above, you can create a DataView for your DataTable and then
to show only the clicked line/column, call
view.setColumns(chart.getSelection()[0].column)
to hide the clicked line/column call
view.hideColumns(chart.getSelection()[0].column)
getSelection() will have the line/legend on the chart you have selected.