Skip to content

Instantly share code, notes, and snippets.

@fxi
Last active February 10, 2020 18:06
Show Gist options
  • Save fxi/9bfb0b44a88ca075258d53ad8be96154 to your computer and use it in GitHub Desktop.
Save fxi/9bfb0b44a88ca075258d53ad8be96154 to your computer and use it in GitHub Desktop.
Update highcharts with data from mapbox gl
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<title></title>
<meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
<script src='https://api.tiles.mapbox.com/mapbox-gl-js/v0.38.0/mapbox-gl.js'></script>
<link href='https://api.tiles.mapbox.com/mapbox-gl-js/v0.38.0/mapbox-gl.css' rel='stylesheet' />
<link href='style.css' rel='stylesheet' />
<script src="https://code.highcharts.com/highcharts.src.js"></script>
</head>
<body>
<div id='map'></div>
<div id='stat'>
<div id='container'></div>
</div>
<script src="script.js" type="text/javascript"></script>
</body>
</html>
var chart;
mapboxgl.accessToken = 'pk.eyJ1IjoidW5lcGdyaWQiLCJhIjoiY2lrd293Z3RhMDAzNHd4bTR4YjE4MHM0byJ9.9c-Yt3p0aKFSO2tX6CR26Q';
var map = new mapboxgl.Map({
container: 'map', // container id
style: 'mapbox://styles/mapbox/dark-v9', //hosted style id
center: [-77.38, 39], // starting position
zoom: 3 // starting zoom
});
map.on('load', function() {
map.addLayer({
"id": "points",
"type": "circle",
"source": {
"type": "geojson",
"data": rPoints(1000)
},
'paint': {
// make circles larger as the user zooms from z12 to z22
'circle-radius': {
'base': 10,
'stops': [
[12, 3],
[22, 20]
]
},
'circle-opacity': 0.8,
// color circles by ethnicity, using data-driven styles
'circle-color': {
property: 'nHgKg',
type: 'exponential',
stops: [
[0, '#03c5f7'],
[1000, '#b503f7']
]
}
}
});
chartInit()
map.on("render", chartSetData);
});
function getData() {
var vals = [];
var layer = "points"
if (!map.getLayer(layer)) return vals;
var test = map.queryRenderedFeatures({
layers: [layer]
});
test.forEach(function(f) {
vals.push([f.properties.date, f.properties.nHgKg]);
})
vals = vals.sort(function(a, b) {
return a[0] - b[0];
})
return (vals);
}
function chartSetData() {
data = getData();
if (!data || data.length == 0) return;
chart.series[0].update({
data: data
})
}
function chartInit(data) {
data = data || getData() || [];
Highcharts.createElement('link', {
href: 'https://fonts.googleapis.com/css?family=Unica+One',
rel: 'stylesheet',
type: 'text/css'
}, null, document.getElementsByTagName('head')[0]);
Highcharts.theme = {
colors: ['#2b908f', '#90ee7e', '#f45b5b', '#7798BF', '#aaeeee', '#ff0066', '#eeaaee',
'#55BF3B', '#DF5353', '#7798BF', '#aaeeee'
],
chart: {
backgroundColor: {
linearGradient: {
x1: 0,
y1: 0,
x2: 1,
y2: 1
},
stops: [
[0, '#2a2a2b'],
[1, '#3e3e40']
]
},
style: {
fontFamily: '\'Unica One\', sans-serif'
},
plotBorderColor: '#606063'
},
title: {
style: {
color: '#E0E0E3',
textTransform: 'uppercase',
fontSize: '20px'
}
},
subtitle: {
style: {
color: '#E0E0E3',
textTransform: 'uppercase'
}
},
xAxis: {
gridLineColor: '#707073',
labels: {
style: {
color: '#E0E0E3'
}
},
lineColor: '#707073',
minorGridLineColor: '#505053',
tickColor: '#707073',
title: {
style: {
color: '#A0A0A3'
}
}
},
yAxis: {
gridLineColor: '#707073',
labels: {
style: {
color: '#E0E0E3'
}
},
lineColor: '#707073',
minorGridLineColor: '#505053',
tickColor: '#707073',
tickWidth: 1,
title: {
style: {
color: '#A0A0A3'
}
}
},
tooltip: {
backgroundColor: 'rgba(0, 0, 0, 0.85)',
style: {
color: '#F0F0F0'
}
},
plotOptions: {
series: {
dataLabels: {
color: '#B0B0B3'
},
marker: {
lineColor: '#333'
}
},
boxplot: {
fillColor: '#505053'
},
candlestick: {
lineColor: 'white'
},
errorbar: {
color: 'white'
}
},
legend: {
itemStyle: {
color: '#E0E0E3'
},
itemHoverStyle: {
color: '#FFF'
},
itemHiddenStyle: {
color: '#606063'
}
},
credits: {
style: {
color: '#666'
}
},
labels: {
style: {
color: '#707073'
}
},
drilldown: {
activeAxisLabelStyle: {
color: '#F0F0F3'
},
activeDataLabelStyle: {
color: '#F0F0F3'
}
},
navigation: {
buttonOptions: {
symbolStroke: '#DDDDDD',
theme: {
fill: '#505053'
}
}
},
// scroll charts
rangeSelector: {
buttonTheme: {
fill: '#505053',
stroke: '#000000',
style: {
color: '#CCC'
},
states: {
hover: {
fill: '#707073',
stroke: '#000000',
style: {
color: 'white'
}
},
select: {
fill: '#000003',
stroke: '#000000',
style: {
color: 'white'
}
}
}
},
inputBoxBorderColor: '#505053',
inputStyle: {
backgroundColor: '#333',
color: 'silver'
},
labelStyle: {
color: 'silver'
}
},
navigator: {
handles: {
backgroundColor: '#666',
borderColor: '#AAA'
},
outlineColor: '#CCC',
maskFill: 'rgba(255,255,255,0.1)',
series: {
color: '#7798BF',
lineColor: '#A6C7ED'
},
xAxis: {
gridLineColor: '#505053'
}
},
scrollbar: {
barBackgroundColor: '#808083',
barBorderColor: '#808083',
buttonArrowColor: '#CCC',
buttonBackgroundColor: '#606063',
buttonBorderColor: '#606063',
rifleColor: '#FFF',
trackBackgroundColor: '#404043',
trackBorderColor: '#404043'
},
// special colors for some of the
legendBackgroundColor: 'rgba(0, 0, 0, 0.5)',
background2: '#505053',
dataLabelsColor: '#B0B0B3',
textColor: '#C0C0C0',
contrastTextColor: '#F0F0F3',
maskColor: 'rgba(255,255,255,0.3)'
};
// Apply the theme
Highcharts.setOptions(Highcharts.theme);
chart = Highcharts.chart('container', {
chart: {
zoomType: 'x',
events: {
selection: function(event) {
var layer = "points";
if(event.resetSelection){
map.setFilter(layer,[
'all'
])
}else{
var ext = event.xAxis[0];
map.setFilter(layer,[
'all',
['<','date',ext.max],
['>','date',ext.min]
])
}
}
}
},
title: {
text: 'Mercury consumption Hg (kg)'
},
subtitle: {
text: 'Click and drag to filter date'
},
xAxis: {
type: 'datetime'
},
yAxis: {
title: {
text: 'nHgKg'
}
},
legend: {
enabled: false
},
plotOptions: {
area: {
fillColor: {
linearGradient: {
x1: 0,
y1: 0,
x2: 0,
y2: 1
},
stops: [
[0, Highcharts.getOptions().colors[0]],
[1, Highcharts.Color(Highcharts.getOptions().colors[0]).setOpacity(0).get('rgba')]
]
},
marker: {
radius: 2
},
lineWidth: 1,
states: {
hover: {
lineWidth: 1
}
},
threshold: null
}
},
series: [{
type: 'area',
name: 'Mercury KG',
data: data
}]
});
}
function rCoord() {
var coord = [
(Math.random() * 360) - 180, (Math.random() * 180) - 90
]
return coord
}
function rDate(base) {
base = base || 0;
mx = new Date();
mn = new Date("2004/12/02");
dd = Math.round(mx - (Math.random() * (mx - mn - base)));
return (dd)
}
function rProp(base) {
base = base || [0];
var a = (base[0] + 180) / 360 || Math.random();
var prop = {
date: rDate(a * 1000),
nHgKg: Math.round(a * 1000)
}
return prop;
}
function rPoint() {
var coord = rCoord()
var point = {
type: "Feature",
geometry: {
type: "Point",
coordinates: coord
},
properties: rProp(coord)
}
return point;
}
function rPoints(n) {
var gJson = {
type: "FeatureCollection",
features: []
};
n = n || 100;
for (var i = 0; i < n; i++) {
gJson.features.push(rPoint());
}
return gJson;
}
function cloneArray(arr) {
var i = arr.length;
var clone = [];
while (i--) {
clone[i] = arr[i];
}
return (clone);
};
function getArrayStat(o) {
if (
o.arr === undefined ||
o.arr.constructor != Array ||
o.arr.length === 0
) return [];
if (
o.stat == "quantile" &&
o.percentile &&
o.percentile.constructor == Array
) o.stat = "quantiles";
var arr = cloneArray(o.arr);
var stat = o.stat ? o.stat : "max";
var len_o = arr.length;
var len = len_o;
function sortNumber(a, b) {
return a - b;
}
opt = {
"max": function() {
var max = -Infinity;
var v = 0;
while (len--) {
v = arr.pop();
if (v > max) {
max = v;
}
}
return max;
},
"min": function() {
var min = Infinity;
while (len--) {
v = arr.pop();
if (v < min) {
min = v;
}
}
return min;
},
"sum": function() {
var sum = 0;
while (len--) {
sum += arr.pop();
}
return sum;
},
"mean": function() {
var sum = getArrayStat({
stat: "sum",
arr: arr
});
return sum / len_o;
},
"median": function() {
var median = getArrayStat({
stat: "quantile",
arr: arr,
percentile: 50
});
return median;
},
"quantile": function() {
arr.sort(sortNumber);
o.percentile = o.percentile ? o.percentile : 50;
index = o.percentile / 100 * (arr.length - 1);
if (Math.floor(index) == index) {
result = arr[index];
} else {
i = Math.floor(index);
fraction = index - i;
result = arr[i] + (arr[i + 1] - arr[i]) * fraction;
}
return result;
},
"quantiles": function() {
var quantiles = {};
o.percentile.forEach(function(x) {
var res = getArrayStat({
stat: "quantile",
arr: arr,
percentile: x
});
quantiles[x] = res;
});
return quantiles;
},
"distinct": function() {
var n = {},
r = [];
while (len--) {
if (!n[arr[len]]) {
n[arr[len]] = true;
r.push(arr[len]);
}
}
return r;
}
};
return (opt[stat](o));
};
function printLayerStat() {
var s = getLayerStat();
var elN = document.getElementById("sN");
var elMin = document.getElementById("sMin");
var elMax = document.getElementById("sMax");
var elPtil = document.getElementById("sPtil");
elN.innerHTML = s.n;
elMin.innerHTML = s.min;
elMax.innerHTML = s.max;
elPtil.innerHTML = JSON.stringify(s.ptil);
}
function getLayerStat() {
var test = map.queryRenderedFeatures({
layers: ['points']
});
var vals = [];
test.forEach(function(f) {
vals.push([
f.properties.date,
f.properties.nHgKg
]);
})
var out = {
n: test.length,
max: getArrayStat({
arr: vals,
stat: 'max'
}),
min: getArrayStat({
arr: vals,
stat: 'min'
}),
ptil: getArrayStat({
arr: vals,
stat: 'quantiles',
percentile: [25, 50, 75]
})
}
return (out)
}
body {
margin: 0;
padding: 0;
}
#map {
position: absolute;
top: 0;
bottom: 0;
width: 100%;
}
#stat {
position: absolute;
top: 50%;
bottom: 0;
width: 100%;
height:50%;
background:#FFF;
z-index:1000;
}
#container {
height:100%;
width:100%;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment