Mapbox GL JS: How to draw an horizontal separator inside a circle point - mapbox-gl-js

I'm trying to display two values separated by a horizontal separator inside a cluster point rendered using Mapbox GL JS.
Example (using leaflet) :
So far I've achieved to have this kind of point but I'm missing the 1px bar in the center.
How would you do this?
The code I'm using:
this.map.addLayer({
id: 'clusters',
type: 'circle',
source: 'markers',
filter: ['has', 'point_count'],
paint: {
'circle-color': '#ffffff',
'circle-radius': 20,
'circle-stroke-width': 3,
'circle-stroke-color': '#5eb3e4',
}
});
this.map.addLayer({
id: 'cluster-count',
type: 'symbol',
source: 'markers',
filter: ['has', 'point_count'],
layout: {
'text-field': '{point_count}\n{sum}',
'text-font': ['DIN Offc Pro Medium', 'Arial Unicode MS Bold'],
'text-size': 12,
},
paint: {
'text-color': '#00214e'
}
});

So I've managed to do this using a generated image, added as an icon to the layer:
const createLineImage = (width) => {
const bytesPerPixel = 4; // Each pixel is 4 bytes: red, green, blue, and alpha.
const data = new Uint8Array(width * bytesPerPixel);
for (let x = 0; x < width; x++) {
const offset = x * bytesPerPixel;
data[offset] = 0; // red
data[offset + 1] = 0; // green
data[offset + 2] = 0; // blue
data[offset + 3] = 255; // alpha
}
return { data, width, height: 1 };
};
this.map.addImage('line', createLineImage(25));
this.map.addLayer({
id: 'cluster-count',
type: 'symbol',
source: 'markers',
filter: ['has', 'point_count'],
layout: {
'text-field': '{point_count}\n{sum}',
'text-font': ['DIN Offc Pro Medium', 'Arial Unicode MS Bold'],
'text-size': 12,
'text-line-height': 1.5,
'icon-image': 'line',
},
});
Result is

Related

Google Chart mouse over skipping data point [duplicate]

I want to add a custom tooltip to my charts by using the default one and for example just append some text to it.
Is this even possible, or to i have to create it all by myself with html?
data= google.visualization.arrayToDataTable([
["Element", "Duration ", { role: "style" }, { role: 'tooltip' }],
["Count", 23515, "orange", ???],
]);
How it is (Default Tooltip):
How i want it:
Append the duration as readable time, but still keep the default tooltip
it's not possible to add content to the default tooltip via standard functionality
to do so requires manipulating the tooltip directly when it is shown
the following working snippet listens for the 'onmouseover' event on the chart
then modifies the tooltip (if found)
using the row # passed as a property of the event argument
keep in mind, the style (font-size) will change according to the size of the chart
the snippet copies the style from the existing lines
google.charts.load('current', {
callback: function () {
var dataTable = new google.visualization.DataTable({
cols: [
{label: 'Element', type: 'string'},
{label: 'Duration', type: 'number'},
{role: 'style', type: 'string'}
],
rows: [
{c:[{v: 'Amazon Elastic Transcoder'}, {v: 3116, f: '3,116 s'}, {v: 'orange'}]},
{c:[{v: 'Amazon Elastic Transcoder'}, {v: 8523, f: '8,523 s'}, {v: 'cyan'}]}
]
});
var options = {
backgroundColor: 'transparent',
legend: 'none',
theme: 'maximized',
hAxis: {
textPosition: 'none'
},
tooltip: {
isHtml: true
}
};
var container = document.getElementById('chart_div');
var chart = new google.visualization.ColumnChart(container);
google.visualization.events.addListener(chart, 'onmouseover', function (props) {
var duration = dataTable.getValue(props.row, 1);
var hours = parseInt( duration / 3600 ) % 24;
var minutes = parseInt( duration / 60 ) % 60;
var seconds = duration % 60;
var tooltip = container.getElementsByTagName('ul');
var tooltipLabel = container.getElementsByTagName('span');
if (tooltip.length > 0) {
// increase tooltip height
tooltip[0].parentNode.style.height = '95px';
// add new li element
var newLine = tooltip[0].appendChild(document.createElement('li'));
newLine.className = 'google-visualization-tooltip-item';
// add span for label
var lineLabel = newLine.appendChild(document.createElement('span'));
lineLabel.style.fontFamily = tooltipLabel[0].style.fontFamily;
lineLabel.style.fontSize = tooltipLabel[0].style.fontSize;
lineLabel.style.color = tooltipLabel[0].style.color;
lineLabel.style.margin = tooltipLabel[0].style.margin;
lineLabel.style.textDecoration = tooltipLabel[0].style.textDecoration;
lineLabel.innerHTML = dataTable.getColumnLabel(1) + ': ';
// add span for value
var lineValue = newLine.appendChild(document.createElement('span'));
lineValue.style.fontFamily = tooltipLabel[0].style.fontFamily;
lineValue.style.fontSize = tooltipLabel[0].style.fontSize;
lineValue.style.fontWeight = tooltipLabel[0].style.fontWeight;
lineValue.style.color = tooltipLabel[0].style.color;
lineValue.style.margin = tooltipLabel[0].style.margin;
lineValue.style.textDecoration = tooltipLabel[0].style.textDecoration;
lineValue.innerHTML = hours + 'h ' + minutes + 'm ' + seconds + 's';
}
});
chart.draw(dataTable, options);
},
packages:['corechart']
});
<script src="https://www.gstatic.com/charts/loader.js"></script>
<div id="chart_div"></div>
to add content to the tooltip using standard functionality requires replacing the tooltip altogether
the best result will be using html tooltips
to use html tooltips, two things must be in place
first, need html column property on tooltip column
{role: 'tooltip', type: 'string', p: {html: true}}
next, need tooltip.isHtml: true in the config options
the tooltip can be provided directly in the data,
or add dynamically, as in the following snippet...
google.charts.load('current', {
callback: function () {
var dataTable = new google.visualization.DataTable({
cols: [
{label: 'Element', type: 'string'},
{label: 'Duration', type: 'number'},
{role: 'style', type: 'string'}
],
rows: [
{c:[{v: 'Amazon Elastic Transcoder'}, {v: 3116, f: '3,116 s'}, {v: 'orange'}]},
{c:[{v: 'Amazon Elastic Transcoder'}, {v: 8523, f: '8,523 s'}, {v: 'cyan'}]}
]
});
dataTable.addColumn({role: 'tooltip', type: 'string', p: {html: true}});
for (var i = 0; i < dataTable.getNumberOfRows(); i++) {
var duration = dataTable.getValue(i, 1);
var hours = parseInt( duration / 3600 ) % 24;
var minutes = parseInt( duration / 60 ) % 60;
var seconds = duration % 60;
var tooltip = '<div class="ggl-tooltip"><span>' +
dataTable.getValue(i, 0) + '</span><div>' +
dataTable.getColumnLabel(1) + ': <span>' +
dataTable.getFormattedValue(i, 1) + '</span></div><div>' +
dataTable.getColumnLabel(1) + ': <span>' +
hours + 'h ' + minutes + 'm ' + seconds + 's</span></div></div>';
dataTable.setValue(i, 3, tooltip);
}
var options = {
backgroundColor: 'transparent',
legend: 'none',
theme: 'maximized',
hAxis: {
textPosition: 'none'
},
tooltip: {
//trigger: 'selection',
isHtml: true
}
};
var container = document.getElementById('chart_div');
var chart = new google.visualization.ColumnChart(container);
chart.draw(dataTable, options);
},
packages:['corechart']
});
.ggl-tooltip {
border: 1px solid #E0E0E0;
font-family: Arial, Helvetica;
font-size: 10pt;
padding: 12px 12px 12px 12px;
}
.ggl-tooltip div {
padding-top: 6px;
}
.ggl-tooltip span {
font-weight: bold;
}
<script src="https://www.gstatic.com/charts/loader.js"></script>
<div id="chart_div"></div>

Google Charts display options

My questions (short style)
Can you customize a y-axis labeling interval ?
Can you display the extreme values of a series as an horizontal line ?
The detailed explanations
I have a combo chart made with Google Charts : the first set of data uses an area style, and the second a line style. The second one is the one that matters here :
it represents a percentage
i don't want it from 0 to 1 (or 0 to 100 in percentage), but from its min to its max (or something near)
and i want to display those min and max values
If i modify the scale so :
PHP
$min_reject_percentage = 5 * floor($min_reject_percentage / 5);
$max_reject_percentage = 5 * ceil($max_reject_percentage / 5);
JS
var options = {
...
vAxes: {
...
1: {
format:"##%",
viewWindow: {
min: <?php echo ($min_taux_rejet / 100); ?>,
max: <?php echo ($max_taux_rejet / 100); ?>,
},
},
},
series: {
0: {
targetAxisIndex: 0,
type: 'area',
},
1: {
targetAxisIndex: 1,
type: 'line',
},
}
}
The vertical axis is limited to the nearest multiple of 5 for min and max values, but :
the interval shown on the axis is from 10 to 10, which is too big. Since i have a real max of 31.5 and a real min of 17.1, axis min is 15 is 15 and axis max is 35, but the only graduation labeled are 20 and 30.
i can't see the real min and max on the graph
you can use config option ticks, which is an array of values to be used for the labels...
see following working snippet...
google.charts.load('current', {
packages: ['corechart']
}).then(function () {
var data = google.visualization.arrayToDataTable([
['x', 'y0', 'y1'],
[0, 18, 0.171],
[1, 28, 0.315],
]);
var chart = new google.visualization.LineChart(document.getElementById('chart_div'));
var axisMin = 0.15;
var axisMax = 0.35;
var ticks = [];
for (var i = axisMin; i <= axisMax; i = i + 0.05) {
ticks.push(i);
}
var options = {
vAxes: {
1: {
format: '##%',
ticks: ticks,
viewWindow: {
min: axisMin,
max: axisMax,
},
},
},
series: {
0: {
targetAxisIndex: 0,
type: 'area',
},
1: {
targetAxisIndex: 1,
type: 'line',
},
}
};
chart.draw(data, options);
});
<script src="https://www.gstatic.com/charts/loader.js"></script>
<div id="chart_div"></div>

MapBox GL custom markers with symbol layer

I am trying to cluster custom markers in MapBox GL JS but I cannot figure out how to get a custom marker image from a url into the symbol layer? It either does not work or no markers show up at all. How is it done? I need to know how to use a custom image from a url with the symbol layer. Thank you very much.
map.addSource('parcelpoints', {
type: 'geojson',
data: geojson,
cluster: true,
clusterMaxZoom: 14, // Max zoom to cluster points on
clusterRadius: 50 // Radius of each cluster when clustering points (defaults to 50)
});
map.addLayer({
id: 'clusters',
type: 'circle',
source: 'parcelpoints',
filter: ['has', 'point_count'],
paint: {
// Use step expressions (https://www.mapbox.com/mapbox-gl-js/style-spec/#expressions-step)
// with three steps to implement three types of circles:
// * Blue, 20px circles when point count is less than 100
// * Yellow, 30px circles when point count is between 100 and 750
// * Pink, 40px circles when point count is greater than or equal to 750
'circle-color': [
'step',
['get', 'point_count'],
'#51bbd6',
100,
'#f1f075',
750,
'#f28cb1'
],
'circle-radius': [
'step',
['get', 'point_count'],
20,
100,
30,
750,
40
]
}
});
map.addLayer({
id: 'cluster-count',
type: 'symbol',
source: 'parcelpoints',
filter: ['has', 'point_count'],
layout: {
'text-field': '{point_count_abbreviated}',
'text-font': ['DIN Offc Pro Medium', 'Arial Unicode MS Bold'],
'text-size': 12
}
});
map.addLayer({
id: 'unclustered-point',
type: 'symbol',
source: 'parcelpoints',
filter: ['!has', 'point_count'],
'layout': {
'visibility': 'visible',
'icon-image': { url: "marker.svg" }
}
});
First you need to:
load the external image with a specific ID, via map.loadImage().
add the image with a specific ID, via map.addImage(https://docs.mapbox.com/mapbox-gl-js/api/map/#map#loadimage).
There is a worked example here: https://www.mapbox.com/mapbox-gl-js/example/add-image/
// If the style's sprite does not already contain an image with ID 'kitty',
// add the image 'cat-icon.png' to the style's sprite with the ID 'kitty'.
map.loadImage('https://upload.wikimedia.org/wikipedia/commons/thumb/6/60/Cat_silhouette.svg/400px-Cat_silhouette.svg.png', (error, image) => {
if (error) throw error;
if (!map.hasImage('kitty')) map.addImage('kitty', image);
});
To include your loaded image in a symbol layer will look something like:
map.addLayer({
'id': 'clusters',
'type': 'circle',
'source': 'parcelpoints',
'filter': ['has', 'point_count'],
'icon-image': 'kitty',
...
There is no support for directly loading symbol images from URLs.

Google charts, Column chart two different colors per column

Let's say I have a datatable like this:
drivingData.addColumn('string', 'VehicleGroup');
drivingData.addColumn('number', 'TimeType');
drivingData.addColumn('number', 'TimeTarget');
drivingData.addColumn('number', 'TimeUsed');
then I add 4 rows like this:
drivingData.addRow(['Trucks-S',0, 1000, 1200])
drivingData.addRow(['Trucks-F',1, 300, 500])
drivingData.addRow(['Trailer-S',0, 1200, 1500])
drivingData.addRow(['Trailer-F',1, 100, 500])
I would like to have a 'stacked' column chart. First one shows Trucks-S with TimeType 0 with yellow and orange colors.
Second would show Trucks-F with TimeType 1 with grey and light-grey colors.
Third would then again be yellow and orange and fourth grey and light-grey and so on...
Is this possible?
Something like this:
https://imgur.com/a/oNOiP
the requested chart is only available as a Material bar chart
Material --> google.charts.Bar -- packages: ['bar']
Classic --> google.visualization.ColumnChart -- packages: ['corechart']
you can break Material bar charts into multiple stacks,
by assigning a group of series to a different y-axis
this is accomplished by using the series option
series: {
2: {
targetAxisIndex: 1
},
3: {
targetAxisIndex: 1
}
},
this will create a second axis on the right side of the chart,
which will have a different scale by default
to keep both y-axis in sync, assign a specific view window
vAxis: {
viewWindow: {
min: 0,
max: 3000
}
}
see following working snippet...
google.charts.load('current', {
packages: ['bar']
}).then(function () {
var drivingData = new google.visualization.DataTable();
drivingData.addColumn('string', 'VehicleGroup');
drivingData.addColumn('number', 'TimeTarget');
drivingData.addColumn('number', 'TimeUsed');
drivingData.addColumn('number', 'TimeTarget');
drivingData.addColumn('number', 'TimeUsed');
drivingData.addRow(['Trucks-S', 1000, 1200, 600, 800])
drivingData.addRow(['Trucks-F', 300, 500, 700, 900])
drivingData.addRow(['Trailer-S', 1200, 1500, 800, 1000])
drivingData.addRow(['Trailer-F', 100, 500, 600, 1000])
var container = document.getElementById('chart_div');
var chart = new google.charts.Bar(container);
var options = google.charts.Bar.convertOptions({
colors: ['#fbc02d', '#616161'],
height: 400,
isStacked: true,
series: {
2: {
targetAxisIndex: 1
},
3: {
targetAxisIndex: 1
}
},
vAxis: {
viewWindow: {
min: 0,
max: 3000
}
}
});
chart.draw(drivingData, options);
});
<script src="https://www.gstatic.com/charts/loader.js"></script>
<div id="chart_div"></div>
note: just keep in mind,
there are several configuration options that are not supported by Material charts
see --> Tracking Issue for Material Chart Feature Parity
EDIT
isStacked: 'percent' is currently not support by Material charts
to work around this issue, convert the data manually, before drawing the chart
see following working snippet,
y-axis columns will be converted in groups of two...
google.charts.load('current', {
packages: ['bar']
}).then(function () {
var drivingData = new google.visualization.DataTable();
drivingData.addColumn('string', 'VehicleGroup');
drivingData.addColumn('number', 'TimeTarget');
drivingData.addColumn('number', 'TimeUsed');
drivingData.addColumn('number', 'TimeTarget');
drivingData.addColumn('number', 'TimeUsed');
drivingData.addRow(['Trucks-S', 1000, 1200, 600, 800])
drivingData.addRow(['Trucks-F', 300, 500, 700, 900])
drivingData.addRow(['Trailer-S', 1200, 1500, 800, 1000])
drivingData.addRow(['Trailer-F', 100, 500, 600, 1000])
// convert data to percent
var percentData = new google.visualization.DataTable();
for (var col = 0; col < drivingData.getNumberOfColumns(); col++) {
percentData.addColumn(drivingData.getColumnType(col), drivingData.getColumnLabel(col));
}
for (var row = 0; row < drivingData.getNumberOfRows(); row++) {
var newRow = percentData.addRow();
percentData.setValue(newRow, 0, drivingData.getValue(row, 0));
for (var col = 1; col < drivingData.getNumberOfColumns(); col++) {
if ((col % 2) !== 0) {
var rowTotal = drivingData.getValue(row, col) + drivingData.getValue(row, (col + 1));
percentData.setValue(newRow, col, (drivingData.getValue(row, col) / rowTotal));
percentData.setValue(newRow, (col + 1), (drivingData.getValue(row, (col + 1)) / rowTotal));
}
}
}
var container = document.getElementById('chart_div');
var chart = new google.charts.Bar(container);
var options = google.charts.Bar.convertOptions({
colors: ['#fbc02d', '#616161'],
height: 400,
isStacked: true,
series: {
2: {
targetAxisIndex: 1
},
3: {
targetAxisIndex: 1
}
},
vAxis: {
format: '0%',
viewWindow: {
min: 0,
max: 1
}
}
});
chart.draw(percentData, options);
});
<script src="https://www.gstatic.com/charts/loader.js"></script>
<div id="chart_div"></div>

Google Charts - How to append text to default tooltip

I want to add a custom tooltip to my charts by using the default one and for example just append some text to it.
Is this even possible, or to i have to create it all by myself with html?
data= google.visualization.arrayToDataTable([
["Element", "Duration ", { role: "style" }, { role: 'tooltip' }],
["Count", 23515, "orange", ???],
]);
How it is (Default Tooltip):
How i want it:
Append the duration as readable time, but still keep the default tooltip
it's not possible to add content to the default tooltip via standard functionality
to do so requires manipulating the tooltip directly when it is shown
the following working snippet listens for the 'onmouseover' event on the chart
then modifies the tooltip (if found)
using the row # passed as a property of the event argument
keep in mind, the style (font-size) will change according to the size of the chart
the snippet copies the style from the existing lines
google.charts.load('current', {
callback: function () {
var dataTable = new google.visualization.DataTable({
cols: [
{label: 'Element', type: 'string'},
{label: 'Duration', type: 'number'},
{role: 'style', type: 'string'}
],
rows: [
{c:[{v: 'Amazon Elastic Transcoder'}, {v: 3116, f: '3,116 s'}, {v: 'orange'}]},
{c:[{v: 'Amazon Elastic Transcoder'}, {v: 8523, f: '8,523 s'}, {v: 'cyan'}]}
]
});
var options = {
backgroundColor: 'transparent',
legend: 'none',
theme: 'maximized',
hAxis: {
textPosition: 'none'
},
tooltip: {
isHtml: true
}
};
var container = document.getElementById('chart_div');
var chart = new google.visualization.ColumnChart(container);
google.visualization.events.addListener(chart, 'onmouseover', function (props) {
var duration = dataTable.getValue(props.row, 1);
var hours = parseInt( duration / 3600 ) % 24;
var minutes = parseInt( duration / 60 ) % 60;
var seconds = duration % 60;
var tooltip = container.getElementsByTagName('ul');
var tooltipLabel = container.getElementsByTagName('span');
if (tooltip.length > 0) {
// increase tooltip height
tooltip[0].parentNode.style.height = '95px';
// add new li element
var newLine = tooltip[0].appendChild(document.createElement('li'));
newLine.className = 'google-visualization-tooltip-item';
// add span for label
var lineLabel = newLine.appendChild(document.createElement('span'));
lineLabel.style.fontFamily = tooltipLabel[0].style.fontFamily;
lineLabel.style.fontSize = tooltipLabel[0].style.fontSize;
lineLabel.style.color = tooltipLabel[0].style.color;
lineLabel.style.margin = tooltipLabel[0].style.margin;
lineLabel.style.textDecoration = tooltipLabel[0].style.textDecoration;
lineLabel.innerHTML = dataTable.getColumnLabel(1) + ': ';
// add span for value
var lineValue = newLine.appendChild(document.createElement('span'));
lineValue.style.fontFamily = tooltipLabel[0].style.fontFamily;
lineValue.style.fontSize = tooltipLabel[0].style.fontSize;
lineValue.style.fontWeight = tooltipLabel[0].style.fontWeight;
lineValue.style.color = tooltipLabel[0].style.color;
lineValue.style.margin = tooltipLabel[0].style.margin;
lineValue.style.textDecoration = tooltipLabel[0].style.textDecoration;
lineValue.innerHTML = hours + 'h ' + minutes + 'm ' + seconds + 's';
}
});
chart.draw(dataTable, options);
},
packages:['corechart']
});
<script src="https://www.gstatic.com/charts/loader.js"></script>
<div id="chart_div"></div>
to add content to the tooltip using standard functionality requires replacing the tooltip altogether
the best result will be using html tooltips
to use html tooltips, two things must be in place
first, need html column property on tooltip column
{role: 'tooltip', type: 'string', p: {html: true}}
next, need tooltip.isHtml: true in the config options
the tooltip can be provided directly in the data,
or add dynamically, as in the following snippet...
google.charts.load('current', {
callback: function () {
var dataTable = new google.visualization.DataTable({
cols: [
{label: 'Element', type: 'string'},
{label: 'Duration', type: 'number'},
{role: 'style', type: 'string'}
],
rows: [
{c:[{v: 'Amazon Elastic Transcoder'}, {v: 3116, f: '3,116 s'}, {v: 'orange'}]},
{c:[{v: 'Amazon Elastic Transcoder'}, {v: 8523, f: '8,523 s'}, {v: 'cyan'}]}
]
});
dataTable.addColumn({role: 'tooltip', type: 'string', p: {html: true}});
for (var i = 0; i < dataTable.getNumberOfRows(); i++) {
var duration = dataTable.getValue(i, 1);
var hours = parseInt( duration / 3600 ) % 24;
var minutes = parseInt( duration / 60 ) % 60;
var seconds = duration % 60;
var tooltip = '<div class="ggl-tooltip"><span>' +
dataTable.getValue(i, 0) + '</span><div>' +
dataTable.getColumnLabel(1) + ': <span>' +
dataTable.getFormattedValue(i, 1) + '</span></div><div>' +
dataTable.getColumnLabel(1) + ': <span>' +
hours + 'h ' + minutes + 'm ' + seconds + 's</span></div></div>';
dataTable.setValue(i, 3, tooltip);
}
var options = {
backgroundColor: 'transparent',
legend: 'none',
theme: 'maximized',
hAxis: {
textPosition: 'none'
},
tooltip: {
//trigger: 'selection',
isHtml: true
}
};
var container = document.getElementById('chart_div');
var chart = new google.visualization.ColumnChart(container);
chart.draw(dataTable, options);
},
packages:['corechart']
});
.ggl-tooltip {
border: 1px solid #E0E0E0;
font-family: Arial, Helvetica;
font-size: 10pt;
padding: 12px 12px 12px 12px;
}
.ggl-tooltip div {
padding-top: 6px;
}
.ggl-tooltip span {
font-weight: bold;
}
<script src="https://www.gstatic.com/charts/loader.js"></script>
<div id="chart_div"></div>