I'm making a choropleth map with Mapbox that changes according to a user-defined day. The color is based on the prob for a given day. For now, I'm passing prob1 ... prob10 as properties in a geojson, but I would prefer to have a single prob property that contains an object that I can subset.
//initialize map on day 1
var day = 1;
//prepare color scale
function makeColorScale(d) {
return ([
'interpolate', ['linear'],
['get', 'prob' + d], // how to get object property here?
0, '#440154',
0.04, '#3B528B',
0.08, '#21908C',
0.25, '#5DC863',
0.4, '#FDE725',
1, '#696969'
])
};
map.addSource('county-data', {
type: 'geojson',
data: data
});
map.addLayer({
'id': 'counties-join',
'type': 'fill',
'source': 'county-data',
'paint': {
'fill-opacity': 0.3,
'fill-color': makeColorScale(day)
}
}, 'waterway-label' // ensures polygons are rendered above waterway-labels
);
I've tried e.g. modifying to ['get', 'prob.1'] while adding the object prob: {'1': 0.05, '2':0.01}; to my geojson, but that doesn't work.
I have not tried this, but I believe you can access attributes of an object property by using this form of the ['get'] operator:
["get", string, object]: value
So if your features had a prob property that looked like {'1': 0.05, '2': 0.01} then you could write your makeColorScale() function like this:
function makeColorScale(d) {
return ([
'interpolate', ['linear'],
['get', String(d), ['get', 'prob']],
0, '#440154',
0.04, '#3B528B',
0.08, '#21908C',
0.25, '#5DC863',
0.4, '#FDE725',
1, '#696969'
])
};
I think. The ['get', 'prob'] fetches the object, then the ['get', String(d)] looks up the property you want within that object.
Related
we are experimenting with harp gl to replace a custom building tool for geojson. I want to visualize my polygons with the extruded polygon technique. Coming from MapBox GL I was able to have two properties on my geojson feature.properties called height and base height. So I have Some GeoJson that I'm using for testing. I cannot seem to get the extruded polygon to show up. I am writing this in Javascript, using the https://unpkg.com/##here/harp.gl/dist/harp.js source. I am able to render points on my map. Just not these polygons. (ignore the walls for now, I want to see the floors first)
What I would like to achieve (This is how it looks in MapBox):
function for reseting building data:
resetBuildings: function (data) {
const dataProvider = new harp.GeoJsonDataProvider("buildings", data);
var geoJsonBuildingDataSource;
if (this.mapView.getDataSourceByName("buildings") == null) {
geoJsonBuildingDataSource = new harp.VectorTileDataSource({
dataProvider,
name: "buildings",
styleSetName: "geojson",
});
this.mapView.addDataSource(geoJsonBuildingDataSource);
}
else { geoJsonBuildingDataSource = this.mapView.getDataSourceByName("buildings") }
const theme = {
styles: {
geojson: this.getStyleSet()
},
};
geoJsonBuildingDataSource.setTheme(theme);
My StyleSet definition(?):
getStyleSet: function(){
return [
{
when: ["==", ["geometry-type"], "Polygon"],
technique: "extruded",
renderOrder: 1000,
constantHeight: true,
color: "#FF0000",
transparent: false,
opacity: 0.8,
lineWidth: 1,
lineColor: "#003344",
height: ["number", ["get", "base_height"], 10],
floorHeight: ["number", ["get", "base_height"], 0]
}
];
}
My test GeoJson:
{"type":"FeatureCollection","features":[{"id":"6223a3f0-2835-4ad8-8456-3ccbefc0d19c","type":"Feature","properties":{"level":1,"name":"base","height":0.2,"base_height":0,"color":"grey","message":"","coordinates":null},"geometry":{"type":"Polygon","coordinates":[[[174.81047388530345,-36.909499151794726],[174.81050337403076,-36.90965937993286],[174.8107788255581,-36.909634193592254],[174.8107567090126,-36.9094734295196],[174.81047388530345,-36.909499151794726]]]}},{"id":"a0583d91-f038-4d6e-8e32-99db806982bc","type":"Feature","properties":{"level":0,"name":"Wall","height":5,"base_height":0.2,"color":"blue","message":"","coordinates":null},"geometry":{"type":"LineString","coordinates":[[174.8104752256997,-36.909499687675165],[174.81075804940883,-36.90947396540121],[174.81078083615245,-36.90963526535126],[174.81050337403076,-36.909658844053574],[174.81049399125453,-36.909598825525144],[174.81050337403076,-36.909658844053574],[174.81078083615245,-36.90963526535126],[174.81075804940883,-36.90947396540121],[174.8104752256997,-36.909499687675165]]}},{"id":"ec219095-8891-440b-9fea-2db9cf74c7e0","type":"Feature","properties":{"level":0,"name":"Wall","height":5,"base_height":0.2,"color":"blue","message":"","coordinates":null},"geometry":{"type":"LineString","coordinates":[[174.8104752256997,-36.90950022355558],[174.81048594887454,-36.909558634520565],[174.8104752256997,-36.90950022355558]]}},{"id":"628a2754-c1b6-43a4-975b-eb45084d3853","type":"Feature","properties":{"level":1,"name":"base","height":0.2,"base_height":0,"color":"grey","message":"","coordinates":null},"geometry":{"type":"Polygon","coordinates":[[[174.8106367829821,-36.909648805405354],[174.81078191913713,-36.90963279956284],[174.81078129891483,-36.909646601064004],[174.81085540198825,-36.90961396183521],[174.81089173813234,-36.90967024159831],[174.81086324249378,-36.90968392941179],[174.8108926788683,-36.909721493301554],[174.8109264286474,-36.90971335389515],[174.81098518665493,-36.909814312925626],[174.81076392360183,-36.90990054860199],[174.81063578104482,-36.90969921056885],[174.8106354709525,-36.909651008673336],[174.8106367829821,-36.909648805405354]]]}},{"id":"d5a1c90d-295e-463b-9530-7643e411ca38","type":"Feature","properties":{"level":0,"name":"Wall","height":5,"base_height":0.2,"color":"blue","message":"","coordinates":null},"geometry":{"type":"LineString","coordinates":[[174.81063687126533,-36.909700434854784],[174.81076318373476,-36.90989889886059],[174.8109779359279,-36.90981381148965],[174.81076318373476,-36.90989889886059],[174.81063687126533,-36.909700434854784]]}}]}
Thank you in advance!
I figured it out. I was using the wrong kind of data source. I needed to use a FeaturesDataSource. My style set definition was also wrong, and I was not setting it to the map theme.
How I now initialize map:
this.styleSet = this.getStyleSet();
hereMapsHelper.geoJsonObj = JSON.parse(GeoJson);
const canvas = document.getElementById(mapContainerId);
var options = {
canvas,
theme: {
extends: "https://unpkg.com/#here/harp-map-theme#latest/resources/berlin_tilezen_base.json",
styles: {
geojson: this.styleSet,
}
}
}
var map = new harp.MapView(options);
function for buildings:
resetBuildings: function (data) {
var geoJsonBuildingDataSource;
if (this.mapView.getDataSourceByName("buildings") != null) {
var existingBuildingDataSource = this.mapView.getDataSourceByName("buildings")
this.mapView.removeDataSource(existingBuildingDataSource);
}
geoJsonBuildingDataSource = new harp.FeaturesDataSource({
geojson: data,
name: "buildings",
styleSetName: "geojson",
maxGeometryHeight: 30000
});
this.mapView.addDataSource(geoJsonBuildingDataSource);
},
Function to define style set:
getStyleSet: function () {
const color = new THREE.Color("blue");
const colorString = "#" + color.getHexString();
return [
{
description: "geoJson property-based style",
when: ["==", ["geometry-type"], "Polygon"],
technique: "extruded-polygon",
renderOrder: 1000,
height: ["number", ["get", "base_height"], 10],
floorHeight: ["number", ["get", "base_height"], 0],
attr: {
color: colorString,
transparent: true,
opacity: 0.8,
boundaryWalls: false,
constantHeight: true,
lineWidth: 1,
lineColor: "#003344",
emissive: colorString,
emissiveIntensity: 0.45
},
},
{
description: "geoJson property-based style",
when: ["==", ["geometry-type"], "Point"],
technique: "circles",
renderOrder: 2000,
color: "#00FF00",
size: 15,
}
];
},
This only renders floors, as I havent defined the style for the walls yet. But its progress!
Please help, I have been suffering since 5 days.
How can I get all layer Idsof a MapLibre map ?
function addLayer(map, options, layer) {
let currentLayer = edges_data_api.find(
element => element.edge_id === layer.feature.properties.edge_id)
map.setPaintProperty('lines', 'fill-color', ['interpolate', ['linear'],
['get', currentLayer.lanes], 0, 'rgb(255, 255, 255)', 5, 'rgb(255, 0, 0)'])
}
Here is how I defined the map with MapLibre
map.on('load', function() {
map.addSource('lines', {
type: 'geojson',
data: data
});
map.addLayer({
'id': 'lines',
'type': 'fill',
'source': 'lines',
'layout': {},
'paint': {
'fill-color': '#4682B4',
'fill-opacity': 0.8,
}
});
map.setPaintProperty('lines', 'fill-color', ['get', 'color'])
})
As i know, there are no api for getAllLayers(). So u should keep list of layers' ids by you own.
//global variable
const layerIds = new Set()
map.on('load', function() {
layerIds.add('lines') //keep list of ids without duplicates
map.addSource('lines', {
type: 'geojson',
data: data
});
map.addLayer({
'id': 'lines',
'type': 'fill',
'source': 'lines',
'layout': {},
'paint': {
'fill-color': '#4682B4',
'fill-opacity': 0.8,
}
});
map.setPaintProperty('lines', 'fill-color', ['get', 'color'])
})
//You can iterate your list ids where it is needed
layerIds.forEach(value => {//action here})
From Style Specification
A style's layers property lists all the layers available in that style.
So you can get map's layers from the style object using Map.getStyle()
map.getStyle().layers
This returns an array containing the ids of all layers:
Object.keys( map.style._layers )
But beware: the code accesses private fields both of Map and Style, therefore it may, however unlikely, no longer work for new versions of MapLibre.
I'm trying to set global options into Highcharts so when I create a new graph it is styled exactly like the rest of my other graphs, I've managed to set a global color arrays replacing the original one, and one type of gradients that is used for all my graphs.
Now i'd like to have a different type of gradient for each type of graph (spline, column, pie, area, etc)
Does anyone has an idea on how to proceed ?
(BTW I use React that's why I have to call Highcharts.Highcharts to access the object)
import Highcharts from 'react-highcharts'
const colors = ['#3B86FF', '#77E5AA', '#093fb9', '#6d00f6', '#FF006E', '#FFBE0B', '#1EFFBC', '#ff8b12']
const gradients = ['#87B5FF', '#9AFFC9', '#0078ff', '#852cf5', '#FC3C8F', '#FFD41F', '#7CFFD8', '#FFB364']
export default Highcharts.Highcharts.setOptions({
colors: Highcharts.Highcharts.map(colors, (color, i) => ({
linearGradient: {
x1: 0, x2: 0, y1: 0, y2: 1
},
stops: [
[0, color],
[1, gradients[i]]
]
}))
})
You can achieve the desired result by using setOptions again like this:
Highcharts.setOptions({
plotOptions: {
line: {
//color: Highcharts.getOptions().colors[0] - by default
},
spline: {
color: Highcharts.getOptions().colors[1]
},
column: {
color: Highcharts.getOptions().colors[2]
},
pie: {
color: Highcharts.getOptions().colors[3]
}
}
});
plotOptions.{your series type} describes default options for every series of the given type.
Live demo: http://jsfiddle.net/BlackLabel/d68m2qq8/
Is it possible to pass an array of objects instead of an array of integers? The following code works but results in a flat zero line:
var ctx = document.getElementById("a").getContext("2d");
var data = {
labels: ["Fri", "Sat", "Sun"],
datasets: [{
label: "Chart B",
fillColor: "rgba(220,220,220,0.2)",
strokeColor: "rgba(150,150,150,1)",
pointColor: "rgba(150,150,150,1)",
pointStrokeColor: "#fff",
pointHighlightFill: "#fff",
pointHighlightStroke: "rgba(220,220,220,1)",
data: [{
y: 48,
name: "Value one",
date: "2015-04-30"
}, {
y: 40,
name: "Value two",
date: "2016-05-30"
}, {
y: 19,
name: "Value three",
date: "2016-06-30"
} ]
}]
};
var Chart = new Chart(ctx).Line(data);
In short, I think your example should work. The key is to use y for the y value in the chart, and to add your arbitrary property to the object, as well.
It looks like this is what you're doing, but you've reported it's not working... It works for me, though! ?
Longer answer
According to the docs for Line, data can be a Number[]:
data: [20, 10]
...but it can also be a Point[]:
data: [{
x: 10,
y: 20
}, {
x: 15,
y: 10
}]
This was intended to be used for sparsely-populated data sets. Through experimentation, I've found you can omit the x property and simply specify the y property. The x value will be inferred, as usual.
You can also add in any other arbitrary properties that can be accessed from your label callback. So, you might end up with something like this:
data: [{
y: 20,
myProperty: "Something"
}, {
y: 10,
myProperty: "Something Else"
}]
I can confirm that answer #rinogo gave works on latests version. You can add custom properties to dataset and use 'em in tooltip or elsewhere afterwards.
First add data to dataset. On my case I only have one set, so I'm just pushing points.
chartData.datasets[0].data.push({
y: groupedBooking.distinctCount,
distinctUsers: distinctUsers
});
... and finally override callback with chartOptions:
options: {
tooltips: {
callbacks: {
label(tooltipItem, data) {
console.log(data);
return data.datasets[0].data[tooltipItem.index].distinctUsers;
}
}
}
}
If you would happen to have multiple datasets you get datasetIndex from tooltipItem as well... so to confirm, X can be omitted from datapoint and customer properties are preserved within object.
In the data structure doc (version 3.5.1 at the time of writing this), it is documented objects can be used as data and parsing option is also available to pick the keys.
Example from the doc:
data: {
datasets: [{
data: [{id: 'Sales', nested: {value: 1500}}, {id: 'Purchases', nested: {value: 500}}]
}]
},
options: {
parsing: {
xAxisKey: 'id',
yAxisKey: 'nested.value'
}
}
I need to display data points in a scatter plot using c3.js. The data points arrive in batches, and I want to update the plot incrementally as the new points arrive.
The first batch is given while initializing the plot, and displays fine. But when I use flow to update the plot, the earlier points get erased. Also, the X axis does not display the correct X coordinates. [Edit: added clarification] After the flow, the points appear at (3, 1), (4, 1), (5, 1) and not (1.5, 1), (2.5, 1), (3.5, 1) as specified in the data.]
Am I missing something?
JsFiddle: http://jsfiddle.net/cornea/b33usb69/
var chart = c3.generate({
data: {
x: 'A',
columns: [
['A', 1.5, 2.5, 3.5],
['B', 2.0, 2.0, 2.0]
],
type: 'scatter',
types: { 'B': 'scatter' }
},
axis: {
x: { label: 'A', tick: { fit: true } },
y: { label: 'B' }
}
});
setTimeout(function () {
chart.flow({
columns: [
['A', 1.5, 2.5, 3.5],
['B', 1.0, 1.0, 1.0]
]
});
}, 2000);
Thanks.
Not sure why you're using .flow, isn't that more for time-series data?
Can't you just use .load to get the new data points onto the chart?
Like so: http://jsfiddle.net/b33usb69/20/
EDIT:
If you want it in the same array, then something like this:
(code not tested)
var a = ['A_x', 1.5, 2.5, 3.5], b = ['A', 2.0, 2.0, 2.0];
var chart = c3.generate({
data: {
xs: { A: 'A_x' },
columns: [
['A_x', 1.5, 2.5, 3.5],
['A', 2.0, 2.0, 2.0]
],
type: 'scatter'
},
axis: {
y: { min: 0, max: 3 }
}
});
setTimeout(function () {
a = a.concat[1.5, 2.5, 3.5];
b = b.concat[1.0, 1.0, 1.0];
chart.load({
columns: [
a,
b
]
});
}, 2000);
Because the scale of your Y axis shifts so much in your example (a 50% drop in the values) you will probably need to set axis: { y: { min: 0 }, y: { max: 2 } } to see the scatter points move. Try this jsfiddle to see if that change does what you want.
The X axis values are updated when you use chart.flow if you change that to load and reload the chart data with chart.load({ ... }) they will stay the same. To get a chart to flow data properly there's a little more work required to initialize the X axis values (see the flow API example) but it's not clear from your simple example that this is what you want to do.
Edit:
Thanks for your clarification. I can't answer your question with working code but see github #865 issue for C3.js which is related to your problem.
You might want to consider using a different approach to the data and avoiding flow in order to construct a suitable chart.