Highmaps mappies with drilldown - charts

I need to do a drilldown of mappies, from country to states:
https://www.highcharts.com/maps/demo/map-pies
Actually I am using a custom map from Peru, but already with the example of USA would help me a lot showing me some solution.
Is it possible to do this?

I've made an example with mappie and drilldown to show you how to start and achieve it. I've added drilldown for California so only this state will work.
Add drilldown module: https://code.highcharts.com/maps/modules/drilldown.js
Each point should have a drilldown property set. You can add it to a point object or to data array (then keys array in series should point to it. API: https://api.highcharts.com/highmaps/plotOptions.series.keys)
When drilldown event occurs hide or remove each mappie series and create it again on drillup event.
Code:
// New map-pie series type that also allows lat/lon as center option.
// Also adds a sizeFormatter option to the series, to allow dynamic sizing
// of the pies.
Highcharts.seriesType('mappie', 'pie', {
center: null, // Can't be array by default anymore
clip: true, // For map navigation
states: {
hover: {
halo: {
size: 5
}
}
},
dataLabels: {
enabled: false
}
}, {
getCenter: function() {
var options = this.options,
chart = this.chart,
slicingRoom = 2 * (options.slicedOffset || 0);
if (!options.center) {
options.center = [null, null]; // Do the default here instead
}
// Handle lat/lon support
if (options.center.lat !== undefined) {
var point = chart.fromLatLonToPoint(options.center);
options.center = [
chart.xAxis[0].toPixels(point.x, true),
chart.yAxis[0].toPixels(point.y, true)
];
}
// Handle dynamic size
if (options.sizeFormatter) {
options.size = options.sizeFormatter.call(this);
}
// Call parent function
var result = Highcharts.seriesTypes.pie.prototype.getCenter.call(this);
// Must correct for slicing room to get exact pixel pos
result[0] -= slicingRoom;
result[1] -= slicingRoom;
return result;
},
translate: function(p) {
this.options.center = this.userOptions.center;
this.center = this.getCenter();
return Highcharts.seriesTypes.pie.prototype.translate.call(this, p);
}
});
var data = [
// state, demVotes, repVotes, libVotes, grnVotes, sumVotes, winner, offset config for pies
['Alabama', 729547, 1318255, 44467, 9391, 2101660, -1],
['Alaska', 116454, 163387, 18725, 5735, 304301, -1],
['Arizona', 1161167, 1252401, 106327, 34345, 2554240, -1],
['Arkansas', 380494, 684782, 29829, 9473, 1104578, -1],
['California', 8577206, 4390272, 467370, 271047, 13705895, 1, {
lon: -1,
drawConnector: false
}, 'us-ca'],
['Colorado', 1338870, 1202484, 144121, 38437, 2723912, 1],
['Connecticut', 897572, 673215, 48676, 22841, 1642304, 1, {
lat: -1.5,
lon: 1
}],
['Delaware', 235603, 185127, 14757, 6103, 441590, 1, {
lat: -1.3,
lon: 2
}],
['District of Columbia', 282830, 12723, 4906, 4258, 304717, 1, {
lat: -2,
lon: 2
}],
['Florida', 4504975, 4617886, 207043, 64399, 9394303, -1],
['Georgia', 1877963, 2089104, 125306, 0, 4092373, -1],
['Hawaii', 266891, 128847, 15954, 12737, 424429, 1, {
lat: -0.5,
lon: 0.5,
drawConnector: false
}],
['Idaho', 189765, 409055, 28331, 8496, 635647, -1],
['Illinois', 2977498, 2118179, 208682, 74112, 5378471, 1],
['Indiana', 1039126, 1557286, 133993, 7841, 2738246, -1],
['Iowa', 653669, 800983, 59186, 11479, 1525317, -1],
['Kansas', 427005, 671018, 55406, 23506, 1176935, -1],
['Kentucky', 628854, 1202971, 53752, 13913, 1899490, -1],
['Louisiana', 780154, 1178638, 37978, 14031, 2010801, -1],
['Maine', 352156, 332418, 37578, 13995, 736147, 1],
['Maryland', 1502820, 878615, 78225, 33380, 2493040, 1, {
lon: 0.6,
drawConnector: false
}],
['Massachusetts', 1995196, 1090893, 138018, 47661, 3271768, 1, {
lon: 3
}],
['Michigan', 2268839, 2279543, 172136, 51463, 4771981, -1],
['Minnesota', 1367716, 1322951, 112972, 36985, 2840624, 1, {
lon: -1,
drawConnector: false
}],
['Mississippi', 462127, 678284, 14411, 3595, 1158417, -1],
['Missouri', 1054889, 1585753, 96404, 25086, 2762132, -1],
['Montana', 174281, 273879, 28036, 7868, 484064, -1],
['Nebraska', 273185, 485372, 38746, 8337, 805640, -1],
['Nevada', 539260, 512058, 37384, 0, 1088702, 1],
['New Hampshire', 348526, 345790, 30694, 6465, 731475, 1],
['New Jersey', 1967444, 1509688, 72143, 37131, 3586406, 1, {
lat: -1,
lon: 1.2
}],
['New Mexico', 380923, 316134, 74544, 9797, 781398, 1],
['New York', 4145376, 2638135, 162273, 100110, 7045894, 1],
['North Carolina', 2169496, 2345235, 130021, 1038, 4645790, -1],
['North Dakota', 93758, 216794, 21434, 378, 332364, -1],
['Ohio', 2320596, 2776683, 174266, 44310, 5315855, -1],
['Oklahoma', 420375, 949136, 83481, 0, 1452992, -1],
['Oregon', 991580, 774080, 93875, 49247, 1908782, 1],
['Pennsylvania', 2874136, 2945302, 144826, 49334, 6013598, -1],
['Rhode Island', 227062, 166454, 14700, 6171, 414387, 1, {
lat: -0.7,
lon: 1.7
}],
['South Carolina', 855373, 1155389, 49204, 13034, 2073000, -1],
['South Dakota', 117442, 227701, 20845, 0, 365988, -1],
['Tennessee', 868853, 1519926, 70286, 15952, 2475017, -1],
['Texas', 3877868, 4685047, 283492, 71558, 8917965, -1],
['Utah', 222858, 375006, 39608, 7695, 645167, -1],
['Vermont', 178573, 95369, 10078, 6758, 290778, 1, {
lat: 2
}],
['Virginia', 1981473, 1769443, 118274, 27638, 3896828, 1],
['Washington', 1727840, 1210370, 160356, 57571, 3156137, 1],
['West Virginia', 187519, 486304, 22958, 8016, 704797, -1],
['Wisconsin', 1382947, 1407028, 106470, 31016, 2927461, -1],
['Wyoming', 55973, 174419, 13287, 2515, 246194, -1]
],
maxVotes = 0,
demColor = 'rgba(74,131,240,0.80)',
repColor = 'rgba(220,71,71,0.80)',
libColor = 'rgba(240,190,50,0.80)',
grnColor = 'rgba(90,200,90,0.80)';
// Compute max votes to find relative sizes of bubbles
Highcharts.each(data, function(row) {
maxVotes = Math.max(maxVotes, row[5]);
});
// Build the chart
var chart = Highcharts.mapChart('container', {
chart: {
animation: false, // Disable animation, especially for zooming
events: {
load: function() {
addMapPie(this);
},
drilldown: function(e) {
if (!e.seriesOptions) {
var chart = this,
mapKey = 'countries/us/' + e.point.drilldown + '-all',
// Handle error, the timeout is cleared on success
fail = setTimeout(function() {
if (!Highcharts.maps[mapKey]) {
chart.showLoading('<i class="icon-frown"></i> Failed loading ' + e.point.name);
fail = setTimeout(function() {
chart.hideLoading();
}, 1000);
}
}, 3000);
// Show the spinner
chart.showLoading('<i class="icon-spinner icon-spin icon-3x"></i>'); // Font Awesome spinner
// Load the drilldown map
$.getScript('https://code.highcharts.com/mapdata/' + mapKey + '.js', function() {
data = Highcharts.geojson(Highcharts.maps[mapKey]);
// Set a non-random bogus value
$.each(data, function(i) {
this.value = i;
});
chart.series.forEach(function(s) {
if (s.options.type === 'mappie') {
s.hide();
}
});
// Hide loading and add series
chart.hideLoading();
clearTimeout(fail);
chart.addSeriesAsDrilldown(e.point, {
name: e.point.name,
data: data,
dataLabels: {
enabled: true,
format: '{point.name}'
}
});
});
}
this.setTitle(null, {
text: e.point.name
});
},
drillup: function() {
this.setTitle(null, {
text: ''
});
this.series.forEach(function(s) {
if (s.options.type === 'mappie') {
s.show();
}
});
}
}
},
title: {
text: 'USA 2016 Presidential Election Results'
},
colorAxis: {
dataClasses: [{
from: -1,
to: 0,
color: 'rgba(244,91,91,0.5)',
name: 'Republican'
}, {
from: 0,
to: 1,
color: 'rgba(124,181,236,0.5)',
name: 'Democrat'
}, {
from: 2,
to: 3,
name: 'Libertarian',
color: libColor
}, {
from: 3,
to: 4,
name: 'Green',
color: grnColor
}]
},
mapNavigation: {
enabled: true
},
// Limit zoom range
yAxis: {
minRange: 2300
},
tooltip: {
useHTML: true
},
// Default options for the pies
plotOptions: {
mappie: {
borderColor: 'rgba(255,255,255,0.4)',
borderWidth: 1,
tooltip: {
headerFormat: ''
}
}
},
series: [{
mapData: Highcharts.maps['countries/us/us-all'],
data: data,
name: 'States',
borderColor: '#FFF',
showInLegend: false,
joinBy: ['name', 'id'],
keys: ['id', 'demVotes', 'repVotes', 'libVotes', 'grnVotes',
'sumVotes', 'value', 'pieOffset', 'drilldown'
],
tooltip: {
headerFormat: '',
pointFormatter: function() {
var hoverVotes = this.hoverVotes; // Used by pie only
return '<b>' + this.id + ' votes</b><br/>' +
Highcharts.map([
['Democrats', this.demVotes, demColor],
['Republicans', this.repVotes, repColor],
['Libertarians', this.libVotes, libColor],
['Green', this.grnVotes, grnColor]
].sort(function(a, b) {
return b[1] - a[1]; // Sort tooltip by most votes
}), function(line) {
return '<span style="color:' + line[2] +
// Colorized bullet
'">\u25CF</span> ' +
// Party and votes
(line[0] === hoverVotes ? '<b>' : '') +
line[0] + ': ' +
Highcharts.numberFormat(line[1], 0) +
(line[0] === hoverVotes ? '</b>' : '') +
'<br/>';
}).join('') +
'<hr/>Total: ' + Highcharts.numberFormat(this.sumVotes, 0);
}
}
}, {
name: 'Separators',
type: 'mapline',
data: Highcharts.geojson(Highcharts.maps['countries/us/us-all'], 'mapline'),
color: '#707070',
showInLegend: false,
enableMouseTracking: false
}, {
name: 'Connectors',
type: 'mapline',
color: 'rgba(130, 130, 130, 0.5)',
zIndex: 5,
showInLegend: false,
enableMouseTracking: false
}],
drilldown: {
activeDataLabelStyle: {
color: '#FFFFFF',
textDecoration: 'none',
textOutline: '1px #000000'
},
drillUpButton: {
relativeTo: 'spacingBox',
position: {
x: 0,
y: 60
}
}
}
});
function addMapPie(chart) {
// Add the pies after chart load, optionally with offset and connectors
Highcharts.each(chart.series[0].points, function(state) {
if (!state.id) {
return; // Skip points with no data, if any
}
var pieOffset = state.pieOffset || {},
centerLat = parseFloat(state.properties.latitude),
centerLon = parseFloat(state.properties.longitude);
// Add the pie for this state
chart.addSeries({
type: 'mappie',
name: state.id,
zIndex: 6, // Keep pies above connector lines
sizeFormatter: function() {
var yAxis = this.chart.yAxis[0],
zoomFactor = (yAxis.dataMax - yAxis.dataMin) /
(yAxis.max - yAxis.min);
return Math.max(
this.chart.chartWidth / 45 * zoomFactor, // Min size
this.chart.chartWidth / 11 * zoomFactor * state.sumVotes / maxVotes
);
},
tooltip: {
// Use the state tooltip for the pies as well
pointFormatter: function() {
return state.series.tooltipOptions.pointFormatter.call({
id: state.id,
hoverVotes: this.name,
demVotes: state.demVotes,
repVotes: state.repVotes,
libVotes: state.libVotes,
grnVotes: state.grnVotes,
sumVotes: state.sumVotes
});
}
},
data: [{
name: 'Democrats',
y: state.demVotes,
color: demColor
}, {
name: 'Republicans',
y: state.repVotes,
color: repColor
}, {
name: 'Libertarians',
y: state.libVotes,
color: libColor
}, {
name: 'Green',
y: state.grnVotes,
color: grnColor
}],
center: {
lat: centerLat + (pieOffset.lat || 0),
lon: centerLon + (pieOffset.lon || 0)
}
}, false);
// Draw connector to state center if the pie has been offset
if (pieOffset.drawConnector !== false) {
var centerPoint = chart.fromLatLonToPoint({
lat: centerLat,
lon: centerLon
}),
offsetPoint = chart.fromLatLonToPoint({
lat: centerLat + (pieOffset.lat || 0),
lon: centerLon + (pieOffset.lon || 0)
});
chart.series[2].addPoint({
name: state.id,
path: 'M' + offsetPoint.x + ' ' + offsetPoint.y +
'L' + centerPoint.x + ' ' + centerPoint.y
}, false);
}
});
// Only redraw once all pies and connectors have been added
chart.redraw();
}
#container {
min-width: 320px;
max-width: 800px;
height: 500px;
margin: 1em auto;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/proj4js/2.3.15/proj4.js"></script>
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<script src="https://code.highcharts.com/highcharts.js"></script>
<script src="https://code.highcharts.com/maps/modules/map.js"></script>
<script src="https://code.highcharts.com/maps/modules/data.js"></script>
<script src="https://code.highcharts.com/maps/modules/drilldown.js"></script>
<script src="https://code.highcharts.com/maps/modules/exporting.js"></script>
<script src="https://code.highcharts.com/maps/modules/offline-exporting.js"></script>
<script src="https://code.highcharts.com/mapdata/countries/us/us-all.js"></script>
<link href="https://netdna.bootstrapcdn.com/font-awesome/3.2.1/css/font-awesome.css" rel="stylesheet">
<div id="container"></div>
Demo:
https://jsfiddle.net/BlackLabel/1d845opk/1/
EDIT
To add mappies also to the first level of drilldown you have to remove mappies series form the main view of the map when drilldown event occurs and create new ones for drilled data. It's quite difficult to explain exactly how to make it, so I prepared an example where each drilled level has sample data (you can get them from a server the same as drilled map data). I hope this example will help and you will see what's going on there.
Demo:
http://jsfiddle.net/BlackLabel/zLcnby79/

Thank you Wojciech Chmiel !
I did my map with the help of your post.
// New map-pie series type that also allows lat/lon as center option.
// Also adds a sizeFormatter option to the series, to allow dynamic sizing
// of the pies.
Highcharts.seriesType('mappie', 'pie', {
center: null, // Can't be array by default anymore
clip: true, // For map navigation
states: {
hover: {
halo: {
size: 5
}
}
},
dataLabels: {
enabled: false
}
}, {
getCenter: function() {
var options = this.options,
chart = this.chart,
slicingRoom = 2 * (options.slicedOffset || 0);
if (!options.center) {
options.center = [null, null]; // Do the default here instead
}
// Replace lat/lon with plotX/plotY
if (options.center.plotX !== undefined) {
options.center = [options.center.plotX, options.center.plotY];
}
// Handle dynamic size
if (options.sizeFormatter) {
options.size = options.sizeFormatter.call(this);
}
// Call parent function
var result = Highcharts.seriesTypes.pie.prototype.getCenter.call(this);
// Must correct for slicing room to get exact pixel pos
result[0] -= slicingRoom;
result[1] -= slicingRoom;
return result;
},
translate: function (p) {
this.options.center = this.userOptions.center;
this.center = this.getCenter();
return Highcharts.seriesTypes.pie.prototype.translate.call(this, p);
}
});
var data = [
// state, demVotes, repVotes, libVotes, grnVotes, sumVotes, winner, drilldown
['CAPLINA-OCONA', 729547, 1318255, 44467, 9391, 2101660, -1, 'aaa01'],
['CHAPARRA-CHINCHA', 116454, 163387, 18725, 5735, 304301, -1, 'aaa02'],
['CANETE-FORTALEZA', 1161167, 1252401, 106327, 34345, 2554240, -1, 'aaa03'],
['HUARMEY-CHICAMA', 380494, 684782, 29829, 9473, 1104578, -1, 'aaa04'],
['JEQUETEPEQUE-ZARUMILLA', 8577206, 4390272, 467370, 271047, 13705895, 1, 'aaa05'],
['MARANON', 1338870, 1202484, 144121, 38437, 2723912, 1, 'aaa06'],
['AMAZONAS', 897572, 673215, 48676, 22841, 1642304, 1, 'aaa07'],
['HUALLAGA', 235603, 185127, 14757, 6103, 441590, 1, 'aaa08'],
['UCAYALI', 282830, 12723, 4906, 4258, 304717, 1, 'aaa09'],
['MANTARO', 4504975, 4617886, 207043, 64399, 9394303, -1, 'aaa10'],
['PAMPAS-APURIMAC', 1877963, 2089104, 125306, 0, 4092373, -1, 'aaa11'],
['URUBAMBA-VILCANOTA', 266891, 128847, 15954, 12737, 424429, 1, 'aaa12'],
['MADRE DE DIOS', 189765, 409055, 28331, 8496, 635647, -1, 'aaa13'],
['TITICACA', 2977498, 2118179, 208682, 74112, 5378471, 1, 'aaa14']
],
maxVotes = 0,
demColor = 'rgba(74,131,240,0.80)',
repColor = 'rgba(220,71,71,0.80)',
libColor = 'rgba(240,190,50,0.80)',
grnColor = 'rgba(90,200,90,0.80)';
// Compute max votes to find relative sizes of bubbles
Highcharts.each(data, function(row) {
maxVotes = Math.max(maxVotes, row[5]);
});
// Build the chart
var chart = Highcharts.mapChart('container', {
chart: {
animation: false, // Disable animation, especially for zooming
events: {
load: function() {
addMapPie(this);
},
drilldown: function(e) {
if (!e.seriesOptions) {
var chart = this,
//mapKey = 'countries/us/' + e.point.drilldown + '-all',
mapKey = 'paises/pe/' + e.point.drilldown,
ruta = e.point.drilldown,
// Handle error, the timeout is cleared on success
fail = setTimeout(function() {
if (!Highcharts.maps[mapKey]) {
chart.showLoading('<i class="icon-frown"></i> Failed loading ' + e.point.name);
fail = setTimeout(function() {
chart.hideLoading();
}, 1000);
}
}, 3000);
// Show the spinner
chart.showLoading('<i class="icon-spinner icon-spin icon-3x"></i>'); // Font Awesome spinner
// Load the drilldown map
//$.getScript('https://code.highcharts.com/mapdata/' + mapKey + '.js', function() {
//$.getScript('http://test1.ana.gob.pe/prueba-mappies/' + ruta + '.js', function () {
$.getScript('https://aplicaciones01.ana.gob.pe/mappies/' + ruta + '.js', function () {
data = Highcharts.geojson(Highcharts.maps[mapKey]);
// Set a non-random bogus value
$.each(data, function(i) {
this.value = i;
});
chart.series.forEach(function(s) {
if (s.options.type === 'mappie') {
s.hide();
}
});
// Hide loading and add series
chart.hideLoading();
clearTimeout(fail);
chart.addSeriesAsDrilldown(e.point, {
name: e.point.name,
data: data,
dataLabels: {
enabled: true,
format: '{point.name}'
}
});
});
}
this.setTitle(null, {
text: e.point.name
});
},
drillup: function() {
this.setTitle(null, {
text: ''
});
this.series.forEach(function(s) {
if (s.options.type === 'mappie') {
s.show();
}
});
}
}
},
title: {
text: 'Resultados'
},
colorAxis: {
dataClasses: [{
from: -1,
to: 0,
color: 'rgba(244,91,91,0.5)',
name: 'Republican'
}, {
from: 0,
to: 1,
color: 'rgba(124,181,236,0.5)',
name: 'Democrat'
}, {
from: 2,
to: 3,
name: 'Libertarian',
color: libColor
}, {
from: 3,
to: 4,
name: 'Green',
color: grnColor
}]
},
mapNavigation: {
enabled: true //Para el zoom (+ -)
},
// Limit zoom range
yAxis: {
minRange: 2300
},
tooltip: {
useHTML: true
},
// Default options for the pies
plotOptions: {
mappie: {
borderColor: 'rgba(255,255,255,0.4)',
borderWidth: 1,
tooltip: {
headerFormat: ''
}
}
},
series: [{
mapData: Highcharts.maps['paises/pe/aaa-all'],
data: data,
name: 'States',
borderColor: '#FFF',
showInLegend: false,
joinBy: ['name', 'id'],
//keys: ['id', 'demVotes', 'repVotes', 'libVotes', 'grnVotes',
// 'sumVotes', 'value', 'pieOffset'],
keys: ['id', 'demVotes', 'repVotes', 'libVotes', 'grnVotes', 'sumVotes', 'value', 'drilldown'],
tooltip: {
headerFormat: '',
pointFormatter: function () {
var hoverVotes = this.hoverVotes; // Used by pie only
return '<b>' + this.id + ' votes</b><br/>' +
Highcharts.map([
['Democrats', this.demVotes, demColor],
['Republicans', this.repVotes, repColor],
['Libertarians', this.libVotes, libColor],
['Green', this.grnVotes, grnColor]
].sort(function (a, b) {
return b[1] - a[1]; // Sort tooltip by most votes
}), function (line) {
return '<span style="color:' + line[2] +
// Colorized bullet
'">\u25CF</span> ' +
// Party and votes
(line[0] === hoverVotes ? '<b>' : '') +
line[0] + ': ' +
Highcharts.numberFormat(line[1], 0) +
(line[0] === hoverVotes ? '</b>' : '') +
'<br/>';
}).join('') +
'<hr/>Total: ' + Highcharts.numberFormat(this.sumVotes, 0);
}
}
}],
drilldown: {
activeDataLabelStyle: {
color: '#FFFFFF',
textDecoration: 'none',
textOutline: '1px #000000'
},
drillUpButton: {
relativeTo: 'spacingBox',
position: {
x: 0,
y: 60
}
}
}
});
function addMapPie(chart) {
// Add the pies after chart load, optionally with offset and connectors
Highcharts.each(chart.series[0].points, function(state) {
//if (!state.id) {
if (!state.id || !state.properties) {
return; // Skip points with no data, if any
}
var pieOffset = state.pieOffset || {},
centerLat = parseFloat(state.properties.latitude),
centerLon = parseFloat(state.properties.longitude);
// Add the pie for this state
chart.addSeries({
type: 'mappie',
name: state.id,
zIndex: 6, // Keep pies above connector lines
sizeFormatter: function () {
var yAxis = this.chart.yAxis[0],
zoomFactor = (yAxis.dataMax - yAxis.dataMin) /
(yAxis.max - yAxis.min);
return Math.max(
this.chart.chartWidth / 45 * zoomFactor, // Min size
this.chart.chartWidth / 11 * zoomFactor * state.sumVotes / maxVotes
);
},
/*****/
tooltip: {
// Use the state tooltip for the pies as well
pointFormatter: function () {
return state.series.tooltipOptions.pointFormatter.call({
id: state.id,
hoverVotes: this.name,
demVotes: state.demVotes,
repVotes: state.repVotes,
libVotes: state.libVotes,
grnVotes: state.grnVotes,
sumVotes: state.sumVotes
});
}
},
data: [{
name: 'Democrats',
y: state.demVotes,
color: demColor
}, {
name: 'Republicans',
y: state.repVotes,
color: repColor
}, {
name: 'Libertarians',
y: state.libVotes,
color: libColor
}, {
name: 'Green',
y: state.grnVotes,
color: grnColor
}],
center: {
/*lat: centerLat + (pieOffset.lat || 0),
lon: centerLon + (pieOffset.lon || 0)*/
plotX: state.plotX,
plotY: state.plotY
}
}, false);
});
// Only redraw once all pies and connectors have been added
chart.redraw();
}
#container {
min-width: 320px;
max-width: 800px;
height: 500px;
margin: 1em auto;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/proj4js/2.3.15/proj4.js"></script>
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<script src="https://code.highcharts.com/highcharts.js"></script>
<script src="https://code.highcharts.com/maps/modules/map.js"></script>
<script src="https://code.highcharts.com/modules/data.js"></script>
<script src="https://code.highcharts.com/modules/drilldown.js"></script>
<script src="https://code.highcharts.com/maps/modules/exporting.js"></script>
<script src="https://code.highcharts.com/maps/modules/offline-exporting.js"></script>
<script src="https://aplicaciones01.ana.gob.pe/mappies/AAA_psad56_simp.js"></script>
<link href="https://netdna.bootstrapcdn.com/font-awesome/3.2.1/css/font-awesome.css" rel="stylesheet">
<div id="container"></div>
Here:
- https://jsfiddle.net/JMarcia/w06k5zog/17/

Related

Echarts:how to set markLine lable above the line and at the end of the line?

In echarts-3.6.2, when I set position:'end' for markLine, the lable will display at the end of line
markLine: {
data: [{
symbol:"none",
name: 'GOAL',
yAxis: 3.12 ,
label:{
normal:{
show:true,
position:'end'
}
},
lineStyle: {
normal: {
color: '#5C57FF',
width: 2
}
},
}]
},
However, I want to dislay it above the line at the end of the line? How to make it?
Change position value to insideEndTop(see in docs):
markLine: {
data: [{
symbol: "none",
name: 'GOAL',
yAxis: 3.12,
label: {
normal: {
show: true,
position: 'insideEndTop'
}
},
lineStyle: {
normal: {
color: '#5C57FF',
width: 2
}
},
}]
},
hello,do you have any ideas for not using position: 'insideEndTop', I could not upgrade the echarts plugin
I can't help without a crutch/workaround because it's very old version. You need to update the Echarts immediately, it's only right way. Or you can try to simulate markLine with the graphic component, something like below but it's highway to hell.
var myChart = echarts.init(document.getElementById('main'));
var option = {
color: ['rgba(92, 87, 255, 0.3)'],
grid: {
left: 50,
bottom: 50,
},
graphic: [{
type: 'group',
id: 'markLine',
bounding: 'raw',
children: [{
id: 'lineShape',
$action: 'replace',
type: 'line',
invisible: true,
shape: {
x1: 50,
y1: 300,
x2: 120,
y2: 300,
},
style: {
stroke: '#5C57FF',
lineWidth: 2,
},
zlevel: 10,
}, {
type: 'polygon',
$action: 'replace',
id: 'arrowShape',
invisible: true,
scale: [0.5, 0.3],
position: [90, 292.5],
shape: {
points: [
[16, 5],
[16, 47],
[38, 26]
]
},
style: {
fill: '#5C57FF',
}
}, {
type: 'text',
$action: 'replace',
id: 'labelShape',
invisible: true,
style: {
text: 'GOAL: 3.12',
x: -100,
y: 290,
fill: '#5C57FF',
font: 'bolder 12px sans-serif',
},
zlevel: 10,
}],
}],
xAxis: {
data: ["1", "2", "3", "4", "5", "6"]
},
yAxis: {
type: 'value',
max: 50
},
series: [{
name: 'Series',
type: 'bar',
data: [5, 20, 36, 10, 10, 20],
}]
};
myChart.setOption(option);
function renderMarkLine({ instance, yAxisValue, text, speed }){
var currentStep = 0;
var arrowShape = (val) => {
return {
stopCoord: 710, // 525
opts: {
invisible: false,
id: 'arrowShape',
position: [5 + val, yAxisValue - 7.5] // yAxisValue + 7.5
}
}
};
var lineShape = (val) => {
return {
stopCoord: 680, //540
opts: {
id: 'lineShape',
invisible: false,
shape: {
x1: 50,
y1: yAxisValue, // +0
x2: 50 + val,
y2: yAxisValue
}
}
}
};
var labelShape = (val) => {
return {
stopCoord: 660, // 460
opts: {
id: 'labelShape',
invisible: false,
style: {
x: -10 + val,
y: yAxisValue - 10, // 10
fill: '#5C57FF',
font: 'bolder 12px sans-serif'
}
}
}
};
var interval = setInterval(function(){
var graphicData = [];
[arrowShape, lineShape, labelShape].forEach(el => {
if (el(null).stopCoord > currentStep){
graphicData.push(el(currentStep).opts);
}
});
if (graphicData.length === 0) clearInterval(interval);
instance.setOption({ graphic: graphicData });
currentStep += 10;
}, speed);
};
renderMarkLine({ instance: myChart, yAxisValue: 500, speed: 0 });
<script src="https://cdn.jsdelivr.net/npm/echarts#3.6.2/dist/echarts.min.js"></script>
<div id="main" style="width:800px;height:600px;"></div>

google charts bar horizontal move annotation position to center of stacked chart

I am using Google Chart's column graph chart. The chart is a stacked column chart with annotations for every data point of the stacked column. The annotation are at the right of the inside of the bar but I would like them to be centered inside of the bar.
I'm looking this page with a begin solution. This page
with an function "moveAnnotations()", but I can't get the code for a horizontal bar chart.
Thanks for your help, i'm lost. :(
/** Valeurs pour le graph bar 1 */
var data_graph_bar_1 = [
['Years', 'Beer & other', 'Water', 'CSD', 'Sensitive'],
['2014', 71, 56, 79, 59],
['2019', 70, 74, 75, 65]
];
// Load the Visualization API and the corechart package.
google.charts.load('current', {'packages':['corechart']});
// Set a callback to run when the Google Visualization API is loaded.
google.charts.setOnLoadCallback(function(){drawChart(data_graph_bar_1)});
// Callback that creates and populates a data table,
// instantiates the pie chart, passes in the data and
// draws it.
function drawChart(datas) {
var data = google.visualization.arrayToDataTable(datas);
var view = new google.visualization.DataView(data);
view.setColumns([0,
1, {
calc: function (dt, row) {
return dt.getValue(row, 1);
},
type: "number",
role: "annotation"
},
2, {
calc: function (dt, row) {
return dt.getValue(row, 2);
},
type: "number",
role: "annotation"
},
3, {
calc: function (dt, row) {
return dt.getValue(row, 3);
},
type: "number",
role: "annotation"
},
4, {
calc: function (dt, row) {
return dt.getValue(row, 4);
},
type: "number",
role: "annotation"
},
{
calc: function (dt, row) {
return 0;
},
label: "Total",
type: "number",
},
{
calc: function (dt, row) {
return dt.getValue(row, 1) + dt.getValue(row, 2)+ dt.getValue(row, 3)+ dt.getValue(row, 4);
},
type: "number",
role: "annotation"
}
]);
var options = {
height: 130,
colors: ['#B4CC00', '#3399FF', '#E64B00','#FF8B00'],
legend: 'none',
bar: { groupWidth: '75%' },
isStacked: true,
displayAnnotations: true,
annotations: {
textStyle: {
// The color of the text.
color: '#000000',
fontSize: 15
},
},
hAxis: {
gridlines: {
count: 0
},
textPosition: 'none',
textStyle : {
fontSize: 15
}
},
vAxis: {
textStyle: {
bold: true,
fontSize: '20',
}
},
chartArea:{
left:50,
},
};
// Instantiate and draw our chart, passing in some options.
var chart = new google.visualization.BarChart(document.getElementById('chart_div'));
chart.draw(view, options);
}
<script src="https://www.gstatic.com/charts/loader.js"></script>
<div id="chart_div"></div>
instead of adjusting the 'y' attribute of the label,
we just need to adjust the 'x' attribute...
first, we need to locate the annotation label.
here, we loop all the row and columns, then use the value to find the label.
we exclude labels with black color, because these are shown in the tooltip,
we do not want to move those.
for (var r = 0; r < data.getNumberOfRows(); r++) {
for (var c = 1; c < data.getNumberOfColumns(); c++) {
var labels = chart.getContainer().getElementsByTagName('text');
Array.prototype.forEach.call(labels, function(label, index) {
if ((label.textContent === data.getValue(r, c).toFixed(0)) &&
(label.getAttribute('fill') !== '#000000')) {
barBounds = chartLayout.getBoundingBox('bar#' + (c - 1) + '#' + r);
xAdj = (barBounds.width / 2);
label.setAttribute('x', barBounds.left + xAdj);
label.setAttribute('text-anchor', 'middle');
}
});
}
}
see following working snippet...
var data_graph_bar_1 = [
['Years', 'Beer & other', 'Water', 'CSD', 'Sensitive'],
['2014', 71, 56, 79, 59],
['2019', 70, 74, 75, 65]
];
google.charts.load('current', {'packages':['corechart']});
google.charts.setOnLoadCallback(function(){drawChart(data_graph_bar_1)});
function drawChart(datas) {
var data = google.visualization.arrayToDataTable(datas);
var view = new google.visualization.DataView(data);
view.setColumns([0,
1, {
calc: function (dt, row) {
return dt.getValue(row, 1);
},
type: "number",
role: "annotation"
},
2, {
calc: function (dt, row) {
return dt.getValue(row, 2);
},
type: "number",
role: "annotation"
},
3, {
calc: function (dt, row) {
return dt.getValue(row, 3);
},
type: "number",
role: "annotation"
},
4, {
calc: function (dt, row) {
return dt.getValue(row, 4);
},
type: "number",
role: "annotation"
},
{
calc: function (dt, row) {
return 0;
},
label: "Total",
type: "number",
},
{
calc: function (dt, row) {
return dt.getValue(row, 1) + dt.getValue(row, 2)+ dt.getValue(row, 3)+ dt.getValue(row, 4);
},
type: "number",
role: "annotation"
}
]);
var options = {
height: 130,
colors: ['#B4CC00', '#3399FF', '#E64B00','#FF8B00'],
legend: 'none',
bar: { groupWidth: '75%' },
isStacked: true,
displayAnnotations: true,
annotations: {
textStyle: {
// The color of the text.
color: '#000000',
fontSize: 15
},
},
hAxis: {
gridlines: {
count: 0
},
textPosition: 'none',
textStyle : {
fontSize: 15
}
},
vAxis: {
textStyle: {
bold: true,
fontSize: '20',
}
},
chartArea:{
left:50,
},
};
// Instantiate and draw our chart, passing in some options.
var chart = new google.visualization.BarChart(document.getElementById('chart_div'));
google.visualization.events.addListener(chart, 'ready', function () {
var observer = new MutationObserver(moveAnnotations);
observer.observe(chart.getContainer(), {
childList: true,
subtree: true
});
});
function moveAnnotations() {
var chartLayout = chart.getChartLayoutInterface();
var barBounds;
var xAdj;
for (var r = 0; r < data.getNumberOfRows(); r++) {
for (var c = 1; c < data.getNumberOfColumns(); c++) {
var labels = chart.getContainer().getElementsByTagName('text');
Array.prototype.forEach.call(labels, function(label, index) {
if ((label.textContent === data.getValue(r, c).toFixed(0)) && (label.getAttribute('fill') !== '#000000')) {
barBounds = chartLayout.getBoundingBox('bar#' + (c - 1) + '#' + r);
xAdj = (barBounds.width / 2);
label.setAttribute('x', barBounds.left + xAdj);
label.setAttribute('text-anchor', 'middle');
}
});
}
}
}
chart.draw(view, options);
}
<script src="https://www.gstatic.com/charts/loader.js"></script>
<div id="chart_div"></div>
EDIT
if there are more than one annotations with the same value,
then we need to add another condition.
similar to getting the bar bounds from the chart's interface,
we can get the annotation bounds.
labelBounds = chartLayout.getBoundingBox('annotationtext#' + (c - 1) + '#' + r + '#0');
and since the initial alignment of the annotation is to the right,
or text-anchor="end",
this means the difference between the 'x' attribute on the <text> element and the left property of the bounds will equal the width of the bounds.
we can use this difference to ensure we have the correct annotation before moving it.
((parseFloat(label.getAttribute('x')) - labelBounds.left) === labelBounds.width)
see following working snippet...
var data_graph_bar_1 = [
['Years', 'Beer & other', 'Water', 'CSD', 'Sensitive'],
['2014', 71, 56, 74, 59],
['2019', 70, 74, 75, 65]
];
google.charts.load('current', {'packages':['corechart']});
google.charts.setOnLoadCallback(function(){drawChart(data_graph_bar_1)});
function drawChart(datas) {
var data = google.visualization.arrayToDataTable(datas);
var view = new google.visualization.DataView(data);
view.setColumns([0,
1, {
calc: function (dt, row) {
return dt.getValue(row, 1);
},
type: "number",
role: "annotation"
},
2, {
calc: function (dt, row) {
return dt.getValue(row, 2);
},
type: "number",
role: "annotation"
},
3, {
calc: function (dt, row) {
return dt.getValue(row, 3);
},
type: "number",
role: "annotation"
},
4, {
calc: function (dt, row) {
return dt.getValue(row, 4);
},
type: "number",
role: "annotation"
},
{
calc: function (dt, row) {
return 0;
},
label: "Total",
type: "number",
},
{
calc: function (dt, row) {
return dt.getValue(row, 1) + dt.getValue(row, 2)+ dt.getValue(row, 3)+ dt.getValue(row, 4);
},
type: "number",
role: "annotation"
}
]);
var options = {
height: 130,
colors: ['#B4CC00', '#3399FF', '#E64B00','#FF8B00'],
legend: 'none',
bar: { groupWidth: '75%' },
isStacked: true,
displayAnnotations: true,
annotations: {
textStyle: {
// The color of the text.
color: '#000000',
fontSize: 15
},
},
hAxis: {
gridlines: {
count: 0
},
textPosition: 'none',
textStyle : {
fontSize: 15
}
},
vAxis: {
textStyle: {
bold: true,
fontSize: '20',
}
},
chartArea:{
left:50,
},
};
// Instantiate and draw our chart, passing in some options.
var chart = new google.visualization.BarChart(document.getElementById('chart_div'));
google.visualization.events.addListener(chart, 'ready', function () {
var observer = new MutationObserver(moveAnnotations);
observer.observe(chart.getContainer(), {
childList: true,
subtree: true
});
});
function moveAnnotations() {
var chartLayout = chart.getChartLayoutInterface();
var barBounds;
var xAdj;
for (var r = 0; r < data.getNumberOfRows(); r++) {
for (var c = 1; c < data.getNumberOfColumns(); c++) {
var labels = chart.getContainer().getElementsByTagName('text');
Array.prototype.forEach.call(labels, function(label, index) {
labelBounds = chartLayout.getBoundingBox('annotationtext#' + (c - 1) + '#' + r + '#0');
barBounds = chartLayout.getBoundingBox('bar#' + (c - 1) + '#' + r);
if ((label.textContent === data.getValue(r, c).toFixed(0)) && (label.getAttribute('fill') !== '#000000') && ((parseFloat(label.getAttribute('x')) - labelBounds.left) === labelBounds.width)) {
xAdj = (barBounds.width / 2);
label.setAttribute('x', barBounds.left + xAdj);
label.setAttribute('text-anchor', 'middle');
}
});
}
}
}
chart.draw(view, options);
}
<script src="https://www.gstatic.com/charts/loader.js"></script>
<div id="chart_div"></div>

Offset tick labels to center between ticks on horizontal bar chart

I am creating a horizontal bar chart using Google charts and I want to position the horizontal axis tick labels between ticks (offset tick label to the right by half a tick). There is also no guarantee that each label will be the same number of characters. Is there any functionality to achieve this?
This is my function for creating the chart
function drawChart() {
var data = google.visualization.arrayToDataTable([
["Performance", "Level", { role: "style" }],
["PL", 1.01, "#5aa66d"]
]);
var view = new google.visualization.DataView(data);
view.setColumns([0, 1,
{
calc: function (dt, rowIndex) { return 'PL=' + (dt.getValue(rowIndex, 1)).toString()} ,
sourceColumn: 1,
type: "string",
role: "annotation"
},
2]);
var options = {
width: 600,
height: 100,
bar: { groupWidth: "80%" },
legend: { position: "none" },
hAxis: {
minValue: 0,
maxValue: 3,
ticks: [
{ v: 0, f: 'Col1' },
{ v: 1, f: 'Col2' },
{ v: 2, f: 'Col3' },
{ v: 3, f: 'Col4' }
]
}
};
var chart = new google.visualization.BarChart(document.getElementById("barchart_values"));
chart.draw(view, options);
}
This is the chart currently
https://imgur.com/LvQEbtY
and this is what I want to achieve:
https://imgur.com/hwvIOG2
EDIT:
For ideas, right before chart.draw I add an event listener and find all the text tags and modify the 'x' attribute to re-position the tick labels. This works, but it also affects the on-bar data label which is a problem.
google.visualization.events.addListener(chart, 'ready', function () {
var elements = document.getElementById("barchart_values").getElementsByTagName("text");
for (var i = 0; i < elements.length; i++) {
elements[i].setAttribute('x', (Number(elements[i].getAttribute('x')) + (chart.getChartLayoutInterface().getBoundingBox('chartarea').width / 4 / 2))); //chart width / tick count / 2
}
});
first, we won't be able to offset the grid lines and use the ticks option.
let's offset the grid lines by making the major transparent and the minor visible.
we can also offset the baseline by setting to a negative value.
baseline: -0.5,
minorGridlines: {
color: '#cccccc',
count: 1
},
gridlines: {
color: 'transparent',
count: 4
},
then, in order to have custom tick labels, we can use your original idea to change them manually.
just need a unique attribute to separate them from the bar labels.
here, the text-anchor attribute is used.
if (elements[i].getAttribute('text-anchor') === 'middle') {
elements[i].textContent = 'Col' + parseInt(elements[i].textContent) + 1;
}
see following working snippet...
google.charts.load('current', {
packages:['corechart']
}).then(function () {
var data = google.visualization.arrayToDataTable([
["Performance", "Level", { role: "style" }],
["PL", 1.01, "#5aa66d"]
]);
var view = new google.visualization.DataView(data);
view.setColumns([0, 1, {
calc: function (dt, rowIndex) { return 'PL=' + (dt.getValue(rowIndex, 1)).toString()} ,
sourceColumn: 1,
type: "string",
role: "annotation"
}, 2]);
var options = {
width: 600,
height: 100,
bar: { groupWidth: "80%" },
legend: { position: "none" },
hAxis: {
baseline: -0.5,
minorGridlines: {
color: '#cccccc',
count: 1
},
gridlines: {
color: 'transparent',
count: 4
},
minValue: 0,
maxValue: 3,
}
};
var chart = new google.visualization.BarChart(document.getElementById("barchart_values"));
google.visualization.events.addListener(chart, 'ready', function () {
var elements = document.getElementById("barchart_values").getElementsByTagName("text");
for (var i = 0; i < elements.length; i++) {
if (elements[i].getAttribute('text-anchor') === 'middle') {
elements[i].textContent = 'Col' + (parseInt(elements[i].textContent) + 1);
}
}
});
chart.draw(view, options);
});
<script src="https://www.gstatic.com/charts/loader.js"></script>
<div id="barchart_values"></div>
the above will ensure all the labels remain visible, just simply moving them, could cause them to be pushed off the visible portion of the chart.

sencha touch: real time chart

i am using sencha touch to show a chart and add data to store of chart dynamically.but when i add data to store, my chart does not update result.
this is code of my chart:
Ext.define('MyApp.view.MyLineChart1', {
extend: 'Ext.chart.CartesianChart',
requires: [
'Ext.chart.axis.Category',
'Ext.chart.axis.Numeric',
'Ext.chart.series.Line'
],
config: {
itemId: 'xy',
store: 'MyStore',
colors: [
'#115fa6',
'#94ae0a',
'#a61120',
'#ff8809',
'#ffd13e',
'#a61187',
'#24ad9a',
'#7c7474',
'#a66111'
],
axes: [
{
type: 'category',
fields: [
'x'
],
maximum: 5,
minimum: 0
},
{
type: 'numeric',
fields: [
'y'
],
grid: {
odd: {
fill: '#e8e8e8'
}
},
position: 'left'
}
],
series: [
{
type: 'line',
colors: 'rgba(0,200,0,0.3)',
style: {
smooth: true,
stroke: 'rgb(0,200,0)',
},
xField: 'x',
yField: 'y'
}
],
listeners: [
{
fn: 'onChartShow',
event: 'show',
order: 'after'
}
]
},
onChartShow: function(component, eOpts) {
var TaskRunner = Ext.create("MyApp.controller.TaskRunner");
chart = Ext.ComponentQuery.query("#xy")[0];
store = chart.getStore();
chart.animationSuspended = true;
chart.update();
store.removeAll();
this.timeChartTask = TaskRunner.start({
run: this.update_chart,
interval: 1000,
repeat: 10,
scope: this
});
},
update_chart: function(chart) {
var me = this;
chart = Ext.ComponentQuery.query("#xy")[0];
store = chart.getStore();
count = store.getCount();
xAxis = chart.getAxes()[0];
visibleRange = 10000;
second = 1000;
console.log(xAxis.getMinimum());
if (count > 0) {
lastRecord = store.getAt(count - 1);
xValue = lastRecord.get('x') + second;
if (xValue - me.startTime > visibleRange) {
me.startTime = xValue - visibleRange;
xAxis.setMinimum(this.startTime);
xAxis.setMaximum(xValue);
console.log("count >0");
}
store.add({
x: xValue,
y: me.getNextValue()
});
// store.load();
chart.redraw();
} else {
chart.animationSuspended = true;
me.startTime = Math.floor(Ext.Date.now() / second) * second;
xAxis.setMinimum(me.startTime);
xAxis.setMaximum(me.startTime + visibleRange);
store.add({
x: this.startTime,
y: me.getNextValue()
});
chart.animationSuspended = false;
// store.load();
chart.redraw();
console.log("count < 0");
}
},
getNextValue: function(previousValue) {
var delta = Math.random()*4 - 2;
if (Ext.isNumber(previousValue)) {
return Ext.Number.constrain(previousValue + delta, -2, 2);
}
return Math.random()*4 - 2;
}
});
this is my store:
Ext.define('MyApp.store.MyStore', {
extend: 'Ext.data.Store',
requires: [
'MyApp.model.MyModel1'
],
config: {
model: 'MyApp.model.MyModel1',
storeId: 'MyStore'
}
});
and this is my model:
Ext.define('MyApp.model.MyModel1', {
extend: 'Ext.data.Model',
requires: [
'Ext.data.Field'
],
config: {
fields: [
{
name: 'x'
},
{
name: 'y'
}
]
}
});
If you want set data to store use this code:
var store = Ext.getStore('MyStore');
store.load(function () {
var store = Ext.getStore('MyStore');
store.removeAll();
store.add({
x: xValue,
y: me.getNextValue()
});
store.sync();
});

Highcharts :Donut chart overlaps data labels

I'm working on a donut chart type, with the Highcharts library.
As you can see in the image below, some of the inner data labels are overlapped.
I've been playing with the parameter "distance" but doesn't fix this.
Find attached the code below,
// Create the chart
$(container).highcharts({
chart: {
type: 'pie'
},
credits: {
enabled: false
},
exporting: {
buttons: {
contextButton: {
symbol: 'url(/icon-turn-down.png)'
}
}
},
title: {
text: _title,
margin: 50
},
plotOptions: {
pie: {
shadow: false,
center: ['50%', '50%']
}
},
tooltip: {
formatter: function() {
var s = this.point.name.split('.');
if (s.length == 1) {
return this.y > 1? '<b>'+this.point.name+':</b> '+Highcharts.numberFormat(this.point.y) : null;
}
return this.y > 1? s[0]+'<br /><b>'+$.trim(s[1])+':</b> '+Highcharts.numberFormat(this.point.y) : null;
}
},
series: [{
name: '',
data: innerData,
size: '80%',
dataLabels: {
formatter: function() {
return this.y > 0 ? this.point.name : null;
},
color: 'white',
distance: -50
}
}, {
name: '',
data: outerData,
size: '100%',
innerSize: '80%',
dataLabels: {
formatter: function() {
var s = this.point.name.split('.');
if (s.length == 1) {
return this.y > 1 ? '<b>'+ this.point.name+':</b> '+ Highcharts.numberFormat(this.point.y) : null ;
}
s = this.point.name.substring(this.point.name.indexOf(".")+2);
return this.y > 1 ? '<b>'+ s+':</b> '+ Highcharts.numberFormat(this.point.y): null;
},
style: {
fontSize: "10px",
fontColor: "#000000"
}
}
}]
});
Finally, I found a solution, which is playing with the "startangle" attribute.
series: [{
name: '',
data: innerData,
startAngle:110,
size: '80%',
dataLabels: {
formatter: function() {
return this.y > 0 ? this.point.name : null;
},
color: 'white',
distance: -45
}, {
...
Distance parameter cannot be applied for each point, only general, so only what comes to my mind is iteared on each datalabel and use translate() function, or use formatter, apply CSS class and dhten use top/left parameter for each element. But will be helpful if you recreate your example as fiddle.