Custom Legend multiple colors in Google Chart - charts

I am facing a problem that I cannot custom the default legend color of this chart to multiple colors, please help me.
This is image describe my problem , I am using stacked bar chart. This is my code:
//dump data arr
var data = google.visualization.arrayToDataTable([[["","0 times (never)",{"role":"style"},"1 times",{"role":"style"},"2 times",{"role":"style"},"3 times or more",{"role":"style"}],["A class",0.581,"#b4ddfd",0.109,"#84bfef",0.21,"#559ad2",0.1,"#4277a1"],["nationality",0.481,"#ffddba",0.209,"#ffc384",0.25,"#ffac5b",0.06,"#fa993f"]],[["","0 times (never)",{"role":"style"},"1 times",{"role":"style"},"2 times",{"role":"style"},"3 times or more",{"role":"style"}],["A class",0.1,"#b4ddfd",0.2,"#84bfef",0.3,"#559ad2",0.4,"#4277a1"],["nationality",0.4,"#ffddba",0.3,"#ffc384",0.2,"#ffac5b",0.1,"#fa993f"]],[["","0 times (never)",{"role":"style"},"1 times",{"role":"style"},"2 times",{"role":"style"},"3 times or more",{"role":"style"}],["A class",0.5,"#b4ddfd",0.5,"#84bfef",0,"#559ad2",0,"#4277a1"],["nationality",0,"#ffddba",0,"#ffc384",0,"#ffac5b",1,"#fa993f"]],[["","0 times (never)",{"role":"style"},"1 times",{"role":"style"},"2 times",{"role":"style"},"3 times or more",{"role":"style"}],["A class",0.01,"#b4ddfd",0.02,"#84bfef",0.03,"#559ad2",0.94,"#4277a1"],["nationality",0.4,"#ffddba",0.3,"#ffc384",0.2,"#ffac5b",0.1,"#fa993f"]]]);
var options = {
series: {
0: {
color: '#b4ddfd'
},
1: {
color: '#84bfef'
},
2: {
color: '#559ad2'
},
3: {
color: '#4277a1'
},
},
vAxis: {
textStyle: {fontSize: 11},
titleTextStyle: {italic: false},
},
chartArea: {
width: '85%',
height: areaHeight,
top: 30,
left: '13%'
},
bar: {groupWidth: '35%'},
legend: {
position: 'bottom',
textStyle: {fontSize: 11},
},
isStacked: 'percent',
hAxis: {
ticks: [0, 1],
textStyle: {fontSize: 13},
minValue: 0,
maxValue: 1,
},
callbackLegend: function(legend) {
// my problem here
// var legend_div = document.getElementById(graphId + '_legend');
// legend_div.innerHTML = legend.innerHTML;
},
width: 920,
height: areaHeight + 100
};
var chart = new google.visualization.BarChart(document.getElementById('#chart_div'));
chart.draw(data, options);
Please help me, I am deadlocking.

the standard legend on a google chart will not display multiple colors.
in fact, when using the style column role,
the legend will not match the colors used in the style columns in the data table.
instead, we can build a custom legend to display the multiple colors for each series.
to build the legend, we need to add a container to hold the legend entries.
we can use a <div> element placed just below the chart.
<div id="chart_div"></div>
<div id="legend_div"></div>
we can use css to style the legend container and use the same width of the chart.
#legend_div {
font-family: Arial;
font-size: 11px;
text-align: center;
width: 920px;
}
in the following example, I extract the colors for each series from the data table.
using the values provided in the style column roles.
to create the legend content, I use the following html templates.
one for the legend entry itself...
<script id="template-legend-entry" type="text/html">
<div class="legend-entry" data-columnIndex="{{index}}">
{{colors}}
<span>{{label}}</span>
</div>
</script>
and another for each color to be displayed for that series...
<script id="template-legend-entry-color" type="text/html">
<div class="legend-entry-color" style="background-color: {{color}}"></div>
</script>
in this example, only two rows exist in the data table,
so two colors will be displayed for each legend entry.
the legend is built during the chart's 'ready' event,
so as soon as the chart is finished drawing,
the legend will be displayed.
during which, the colors are extracted from the data table and used to build the legend entries.
a click event is added for example purposes, in case there are actions you would like to take when a legend entry is clicked.
in this example, the chart series is selected to highlight which legend entry was clicked.
google.charts.load('current', {
packages: ['corechart']
}).then(function () {
var data = google.visualization.arrayToDataTable([
["","0 times (never)",{"role":"style"},"1 times",{"role":"style"},"2 times",{"role":"style"},"3 times or more",{"role":"style"}],
["A class",0.581,"#b4ddfd",0.109,"#84bfef",0.21,"#559ad2",0.1,"#4277a1"],
["nationality",0.481,"#ffddba",0.209,"#ffc384",0.25,"#ffac5b",0.06,"#fa993f"]
]);
var options = {
vAxis: {
textStyle: {
fontSize: 11
},
titleTextStyle: {
italic: false
}
},
chartArea: {
width: '85%',
top: 30,
left: '13%'
},
bar: {
groupWidth: '35%'
},
legend: {
position: 'none'
},
isStacked: 'percent',
hAxis: {
ticks: [0, 1],
textStyle: {
fontSize: 13
},
minValue: 0,
maxValue: 1
},
width: 920,
height: '100%'
};
var chart = new google.visualization.BarChart(document.getElementById('chart_div'));
// chart ready event
google.visualization.events.addListener(chart, 'ready', function () {
// legend container
var legend = document.getElementById('legend_div');
legend.innerHTML = '';
// build legend from chart data
var colorPallette = [];
for (var colIndex = 0; colIndex < data.getNumberOfColumns(); colIndex++) {
// determine if style column
if (data.getColumnRole(colIndex) === 'style') {
// save colors for entire series (all rows)
var seriesColors = '';
for (var rowIndex = 0; rowIndex < data.getNumberOfRows(); rowIndex++) {
seriesColors += renderTemplate('template-legend-entry-color', {
color: data.getValue(rowIndex, colIndex)
});
}
// add legend for series (all colors)
legend.insertAdjacentHTML('beforeEnd', renderTemplate('template-legend-entry', {
colors: seriesColors,
index: colIndex - 1,
label: data.getColumnLabel(colIndex - 1)
}));
}
}
// add click event to legend entries
var legendEntries = legend.getElementsByClassName('legend-entry');
Array.prototype.forEach.call(legendEntries, function(entry) {
entry.addEventListener('click', function (e) {
// find legend entry
var entry = e.target || e.srcElement;
if (entry.className.toLowerCase() !== 'legend-entry') {
entry = entry.parentNode;
}
// get data table column index from legend entry
var columnIndex = parseInt(entry.getAttribute('data-columnIndex'));
// display legend entry that was clicked
document.getElementById('message_div').innerHTML = 'legend entry clicked = ' + data.getColumnLabel(columnIndex);
// select chart series
chart.setSelection([{row: null, column: columnIndex}]);
}, false);
});
});
// render html template
function renderTemplate(templateId, templateProps) {
var content = document.getElementById(templateId).innerHTML;
for (var handle in templateProps) {
if (templateProps.hasOwnProperty(handle)) {
content = content.replace('{{' + handle + '}}', templateProps[handle]);
}
}
return content.trim();
}
// draw chart
chart.draw(data, options);
});
#legend_div {
font-family: Arial;
font-size: 11px;
text-align: center;
width: 920px;
}
.legend-entry {
display: inline-block;
padding: 16px 4px 8px 4px;
}
.legend-entry-color {
display: inline-block;
height: 12px;
width: 12px;
}
<script src="https://www.gstatic.com/charts/loader.js"></script>
<div id="chart_div"></div>
<div id="legend_div"></div>
<div id="message_div"></div>
<script id="template-legend-entry" type="text/html">
<div class="legend-entry" data-columnIndex="{{index}}">
{{colors}}
<span>{{label}}</span>
</div>
</script>
<script id="template-legend-entry-color" type="text/html">
<div class="legend-entry-color" style="background-color: {{color}}"></div>
</script>

Related

Using leaflet.FreeDraw with leaflet.path.drag

I am wondering if it's possible to use Leaflet.FreeDraw with leaflet.path.drag to be able to drag the polygon created by FreeDraw plugin.
jsfiddle
I tried to enable dragging plugin like this, but it doesn't work.
const map = new L.Map(document.querySelector('section.map'), { doubleClickZoom: false }).setView(LAT_LNG, 14);
L.tileLayer(TILE_URL).addTo(map);
const freeDraw = new FreeDraw({ mode: FreeDraw.ALL });
map.addLayer(freeDraw);
freeDraw.dragging.enable();
You could extract the bounds from the FreeDraw by listening to the markers event to create a polygon or other map object using leaflet enabled with dragging. See working example below.
You should consider whether you would like to disable the FreeDraw after this, using the option leaveModeAfterCreate:true as the user may get additional polygons when dragging
const LAT_LNG = [51.505, -0.09];
const TILE_URL = 'https://cartodb-basemaps-a.global.ssl.fastly.net/light_all/{z}/{x}/{y}#2x.png';
const map = new L.Map(document.querySelector('section.map'), { doubleClickZoom: false }).setView(LAT_LNG, 14);
L.tileLayer(TILE_URL).addTo(map);
const freeDraw = new FreeDraw({
mode: FreeDraw.ALL,
leaveModeAfterCreate:true //recommended to prevent undesired creation of multiple polygons
});
map.addLayer(freeDraw);
//freeDraw.dragging.enable();
//STEP 1: Listen to markers event raised by free draw whenever edits (create/edit/deletions are made to the map)
freeDraw.on("markers",function(event){
//we are only interested in create events
//we aim to extract the bounds and remove the existing
// freedraw references. If it is that you would like your
// user to edit the polygon, then you may keep these and do the // additional work to manage and update these references
if(event.eventType=='create' && event.latLngs.length > 0){
//capture the current polygon bounds (store in 1st position)
var latLngs = event.latLngs[0];
freeDraw.clear(); //clear freedraw markers
//create polygon from lat lng bounds retrieved
var polygon = L.polygon(
latLngs.map(function(latLng){
return [latLng.lat,latLng.lng];
}), {
color: 'red',
draggable: true //make polygon draggable
}).addTo(map);
}
})
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
.map {
width: 100vw;
height: 100vh;
}
.map.mode-create {
cursor: crosshair;
}
.leaflet-edge {
background-color: #95bc59;
box-shadow: 0 0 0 2px white, 0 0 10px rgba(0, 0, 0, .35);
border-radius: 50%;
cursor: move;
outline: none;
transition: background-color .25s;
}
.leaflet-polygon {
fill: #b4cd8a;
stroke: #50622b;
stroke-width: 2;
fill-opacity: .75;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.0.2/leaflet.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.0.2/leaflet-src.js"></script>
<script src="https://rawgit.com/Wildhoney/Leaflet.FreeDraw/master/dist/leaflet-freedraw.iife.js"></script>
<script src="https://npmcdn.com/leaflet.path.drag/src/Path.Drag.js"></script>
<section class="map"></section>
NB. Also see working example on js-fiddle here https://jsfiddle.net/ytevLbgs/

Is it possible to use stacked column chart without the series adding up?

First of all, I am not sure if what I am looking for is called stacked column chart or else.
Lib is either Google Charts or amCharts.
I have a series of values for the last 28 days representing e-mails sent, e-mails opened and e-mails with links clicked. For each date, the column's max. value should be the number of e-mails sent. This column is then divided based on the two other values. Basically what the chart should show is that from 20 mails sent, 17 were opened and 5 even had people click links inside them.
With a regular stacked column approach and the numbers 20, 17 and 5, this would render a column peaking at 42 with one section covering 0-20, one 20-37 and one 37-42.
What I want is a column peaking at 20, in front of it a column peaking at 17 and in front of that a column peaking at 5. Similar to a diff chart.
I could theoretically achieve this by modifying my data taking the 5 mails with clicks, the opened mails are 17 minus 5 = 12 and the mails sent are 20 minus 17 = 3. Then 5+12+3 = 20 what I wanted. However, hovering the stacked column will display the wrong values 5, 12 and 3 in the tooltip instead of 5, 17 and 20. So I would have to render custom tooltips.
You guys have any idea if there is a simple solution for my problem?
for the scenario you describe theoretically,
you would not need custom tooltips.
when loading the google data table, we can use object notation.
we can provide the value (v:), and the formatted value (f:)
{v: 12, f: '17'}
the tooltip will use the formatted value by default.
in addition, you could use a DataView to perform the calculation.
which would allow you to load the data as normal.
here, calculated columns are used to adjust the value that is plotted,
but display the original value.
see following working snippet...
google.charts.load('current', {
packages: ['corechart']
}).then(function () {
// create data table
var data = new google.visualization.DataTable();
data.addColumn('string', 'Date');
data.addColumn('number', 'Emails with Clicks');
data.addColumn('number', 'Emails Opened');
data.addColumn('number', 'Emails Sent');
// add data
data.addRow(['06/2020', 5, 17, 20]);
// create number format
var formatNumber = new google.visualization.NumberFormat({
pattern: '#,##0'
});
// create data view
var view = new google.visualization.DataView(data);
// build view columns
var viewColumns = [0];
for (var i = 1; i < data.getNumberOfColumns(); i++) {
addColumn(i);
}
function addColumn(index) {
viewColumns.push({
calc: function (dt, row) {
var currentColumnValue = dt.getValue(row, index);
var previousColumnValue = 0;
if (index > 1) {
previousColumnValue = dt.getValue(row, index - 1);
}
var adjusttedColumnValue = currentColumnValue - previousColumnValue;
var formattedColumnValue = formatNumber.formatValue(currentColumnValue);
return {
v: adjusttedColumnValue,
f: formattedColumnValue
};
},
label: data.getColumnLabel(index),
type: data.getColumnType(index),
});
}
// set view columns
view.setColumns(viewColumns);
// create options
var options = {
chartArea: {
left: 64,
top: 40,
right: 32,
bottom: 40,
height: '100%',
width: '100%'
},
height: '100%',
isStacked: true,
legend: {
alignment: 'end',
position: 'top'
},
width: '100%'
};
// create, draw chart with view
var chart = new google.visualization.ColumnChart(document.getElementById('chart_div'));
chart.draw(view, options);
window.addEventListener('resize', function () {
chart.draw(view, options);
});
});
html, body {
height: 100%;
margin: 0px 0px 0px 0px;
overflow: hidden;
padding: 0px 0px 0px 0px;
}
.chart {
height: 100%;
}
<script src="https://www.gstatic.com/charts/loader.js"></script>
<div class="chart" id="chart_div"></div>
Note: If you want to stack columns, one in front of the other, similar to a Diff Chart,
check this answer...
Google Chart - More then 1 difference column

Google Pie chart legend labeled line color

I have a Google pie chart with the legend position set to labeled. My objective is to make all the text and legend marker lines black. Right now the lines are gray and so are the values-and-percentage numbers. Is there any way to make these all black?
var options = {
pieHole: 0.5,
pieSliceText: 'none',
textStyle:{color: 'black'},
legend: {position:'labeled', labeledValueText: 'both', alignment:'center',
textStyle: {
color: 'black',
fontSize: 12}, strokeColor: {color: 'black'},
},
};
Here is an example of the gray text and line marker I'm trying to make all black
Mutationobserver is what I needed since Google Charts have no built in commands to change these attributes.
var container = document.getElementById('pie_chart');
var chart = new google.visualization.PieChart(container);
chart.draw(view, options, observer);
var observer = new MutationObserver(function () {
$.each($('#pie_chart path[stroke="#636363"]'), function (index, path) {
$(path).attr('stroke', '#000000');
});
$.each($('#pie_chart path[fill="#636363"]'), function (index, path) {
$(path).attr('fill', '#000000');
});
$.each($('#pie_chart text[fill="#9e9e9e"]'), function (index, label) {
$(label).attr('fill', '#000000');
});
});
observer.observe(container, {
attributes: true,
childList: true,
subtree: true
});
And added this to the header.
<script src="//cdn.jsdelivr.net/npm/mutationobserver-shim/dist/mutationobserver.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Here is the source of the code

Google Charts - HTML axis label and title

So I am trying to make html axis labels in google charts. However there does not seem to be any support for creating the axis labels or the title as HTML objects. The title is easy enough to fix, just add it in as a separate HTML object to the page, but the axis labels are more challenging. Has anyone been able to do this? As an example the below jsfiddle should show what happens when you attempt to use simple sub and sup html tags.
https://jsfiddle.net/jqmya8j9/1/
google.charts.load('current', {packages: ['corechart']});
google.charts.setOnLoadCallback(function () {
var data = [['X','Y']], i, options,
chart, dataTable;
for (i = 0; i < 20; i += 1) {
data.push([i, i * i]);
}
dataTable =
google.visualization.arrayToDataTable(data);
options = {
legend: 'none',
title: 'X<sub>m</sub> versus X<sup>2</sup>',
//best guess, does nothing
titleTextStyle: {isHtml: true},
hAxis: {title: 'X<sub>m</sub>'},
vAxis: {title: 'X<sup>2</sup>'}
};
chart = new
google.visualization.ScatterChart(document.body);
chart.draw(dataTable, options);
});
Based on the below answer (Thanks!), and what I am actually doing, I wrote the following general rule for this using _{} and ^{} instead of < sub > and < sup >
https://jsfiddle.net/jqmya8j9/2/
google.charts.load('current', {packages: ['corechart']});
google.charts.setOnLoadCallback(function () {
var data = [['X','Y']], i, options,
chart, dataTable;
for (i = 0; i < 20; i += 1) {
data.push([i, i * i]);
}
dataTable =
google.visualization.arrayToDataTable(data);
options = {
legend: 'none',
title: 'X_{m} versus X^2',
hAxis: {title: 'X_m'},
vAxis: {title: 'X^{2}'}
};
chart = new
google.visualization.ScatterChart(document.body);
google.visualization.events.addListener(chart, 'ready', function () {
$.each($('text'), function (index, label) {
var labelText = $(label).text();
if (labelText.match(/_|\^/)) {
labelText = labelText.replace(/_([^\{])|_\{([^\}]*)\}/g, '<tspan style="font-size: smaller;" baseline-shift="sub">$1$2</tspan>')
labelText = labelText.replace(/\^([^\{])|\^\{([^\}]*)\}/g, '<tspan style="font-size: smaller;" baseline-shift="super">$1$2</tspan>')
$(label).html(labelText);
}
});
});
chart.draw(dataTable, options);
});
the labels will only accept text...
the chart is drawn with svg, which can be changed manually when the 'ready' event fires
the labels will be <text> elements
to change the font style inline, use svg <tspan> elements within <text>
e.g.
<text>X<tspan baseline-shift="super">m</tspan></text>
see following working snippet...
google.charts.load('current', {
callback: function () {
var data = [['X','Y']], i, options,
chart, dataTable;
for (i = 0; i < 20; i += 1) {
data.push([i, i * i]);
}
dataTable = google.visualization.arrayToDataTable(data);
options = {
legend: 'none',
title: 'Xm versus X2',
hAxis: {title: 'Xm'},
vAxis: {title: 'X2'}
};
chart = new google.visualization.ScatterChart(document.getElementById('chart_div'));
google.visualization.events.addListener(chart, 'ready', function () {
$.each($('text'), function (index, label) {
var labelText = $(label).text();
if (labelText.indexOf('X') > -1) {
labelText = labelText.replace(new RegExp(/m/g), '<tspan baseline-shift="super">m</tspan>');
labelText = labelText.replace(new RegExp(/2/g), '<tspan baseline-shift="super">2</tspan>');
$(label).html(labelText);
}
});
});
chart.draw(dataTable, options);
},
packages: ['corechart']
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://www.gstatic.com/charts/loader.js"></script>
<div id="chart_div"></div>

Google Charts: Horizontal Reference Line on Barchart

Having a Barchart like the following
I want to be able to draw an horizontal reference line (For example at 80%). However this doesn't seem to be possible on Google Charts.
I've tried several things, including combo charts with multiple series.
However it won't look very nice since the hAxis is discrete :(
Your help would be very appreciated.
add another series for the Reference Line
use the same value for all rows and change the series type to 'line'
see following working snippet...
google.charts.load('current', {
callback: drawChart,
packages: ['corechart']
});
function drawChart() {
var data = google.visualization.arrayToDataTable([
['Category', 'Value', 'Reference'],
['Quant', 0.10, 0.80],
['Verbal', 0.30, 0.80],
['Total', 0.20, 0.80]
]);
var chartDiv = document.getElementById('chart_div');
var chart = new google.visualization.ColumnChart(chartDiv);
chart.draw(data, {
colors: ['lime', 'magenta'],
legend: 'none',
series: {
1: {
type: 'line'
}
},
title: 'Percentile Score',
vAxis: {
format: 'percent',
viewWindow: {
min: 0,
max: 1
}
}
});
}
<script src="https://www.gstatic.com/charts/loader.js"></script>
<div id="chart_div"></div>
EDIT
in the above snippet, the reference line stops at the center of the first and last columns
extend the line to the edges of the columns by changing the coordinates of the reference line,
use the 'ready' listener to know when the chart has been drawn
the key is finding the specific chart elements you need to work with,
in the following snippet, the series color is used to find the columns and reference line
google.charts.load('current', {
callback: drawChart,
packages: ['corechart']
});
function drawChart() {
var data = google.visualization.arrayToDataTable([
['Category', 'Value', 'Reference'],
['Quant', 0.10, 0.80],
['Verbal', 0.30, 0.80],
['Total', 0.20, 0.80]
]);
var chartDiv = document.getElementById('chart_div');
var chart = new google.visualization.ColumnChart(chartDiv);
// use colors to find chart elements
var colorMagenta = '#ff00ff';
var colorLime = '#00ff00';
var xBeg; // save first x coord
var xWidth; // save width of column
var rowIndex = -1; // find first column
google.visualization.events.addListener(chart, 'ready', function () {
// columns
Array.prototype.forEach.call(chartDiv.getElementsByTagName('rect'), function(rect, index) {
if (rect.getAttribute('fill') === colorLime) {
rowIndex++;
xWidth = parseFloat(rect.getAttribute('width')) / 2;
if (rowIndex === 0) {
xBeg = parseFloat(rect.getAttribute('x'));
}
}
});
// reference line
Array.prototype.forEach.call(chartDiv.getElementsByTagName('path'), function(path, index) {
if (path.getAttribute('stroke') === colorMagenta) {
// change line coords
var refCoords = path.getAttribute('d').split(',');
refCoords[0] = 'M' + xBeg;
var refWidth = refCoords[2].split('L');
refWidth[1] = parseFloat(refWidth[1]) + xWidth;
refCoords[2] = refWidth.join('L');
path.setAttribute('d', refCoords.join(','));
}
});
});
chart.draw(data, {
colors: [colorLime, colorMagenta],
legend: 'none',
series: {
1: {
type: 'line'
}
},
title: 'Percentile Score',
vAxis: {
format: 'percent',
viewWindow: {
min: 0,
max: 1
}
}
});
}
<script src="https://www.gstatic.com/charts/loader.js"></script>
<div id="chart_div"></div>