I have following interface:
export interface Product {
name: string;
provider: {
name: string;
logo: string;
};
pricePerUnit: {
quantity: number;
currency: string;
};
}
And my rowData looks like this:
rowData = [
{
name: 'Fish',
provider: {
name: 'Amazon',
logo: 'url to amazon logo',
},
pricePerUnit: {
quantity: 5,
currency: 'USD',
},
},
]
So, as you can see i have at least 2 complex object here, and by design I should display provider as img + name and price as quantity + currency symbol.
I`m using custom angular components for that with styling.
Actual problem
In order to provide these object to my custom components, I set field property in colDefs as follow (example for price):
{
headerName: 'Price',
field: 'pricePerUnit',
cellRenderer: PriceCellRendererComponent,
},
And here is the catch, because I specified in field property complex object, I no longer able to visualize data using integrated charts, because for them to work I should specify in my field propery path to number itself, like so:
{
field: 'pricePerUnit.quantity',
}
But now I`ve broke my custom component because params.value now holds just a number and not my complex object. Same goes to provider.
And it`s also broke grouping, sorting, filtering.
html template for one of my custom component (provider) looks like so:
<div class="wrapper provider">
<tui-avatar [avatarUrl]="params.value.logo" class="provider__logo"></tui-avatar>
<div class="provider__name">{{params.value.name}}</div>
</div>
So the question is:
How to properly setup custom components, so they would work in grouping, sorting, filtering and also integrated charts would use just simple primitive like number to correctly display data?
I am trying to use a complex object to group my ag grid rows. Object of my rowdata looks like this -
const rowData= {
id : '123',
name: 'dummy',
category: 'A',
group : {
name : 'dummyGroup',
id : '456',
category: 'A'
}
}
Now, I am using group object to group the rows. And according to this documentation https://www.ag-grid.com/javascript-data-grid/grouping-complex-objects/ I am using keyCreator as keyCreator: params => params.value.name . My group object is uniquely identified by combination of id and catogory.
The problem that I am facing is, as I am using group.name in the keyCreator, if I have two row data object whose group.names are same but id and category are different, ag grid is grouping those rows together. I understand that this is the behavior from ag grid. So can I get any workaround for it? I need to show name on group row. But to identify the groups differently I need to use id+catogory in keyCreator. How can I achieve this ?
You need to utilise the groupRowInnerRenderer property so you can group by a combination of the id and category fields, while displaying the name as the group.
const gridOptions = {
groupDisplayType: 'groupRows',
groupRowInnerRenderer: function (params) {
return params.node.childrenAfterFilter[0].data.name;
},
columnDefs: [
{ field: 'id' },
{ field: 'name' },
{ field: 'category' },
{
field: 'group',
valueFormatter: groupValueFormatter,
rowGroup: true,
keyCreator: function (params) {
return params.value.id + params.value.category;
},
},
],
};
Demo.
I want to show markers on a map, where each marker is a UserLocation. A User can have multiple UserLocation.
When I cluster markers, I'd like to show the list of the Users of those clustered markers, without duplicates.
For instance, let's take these 3 near markers:
{ // Marker1
type: 'Feature',
properties: {user_id : "Daniele"},
geometry: { type : 'Point',
coordinates : [lng0, lat0]
}
},
{// Marker2
type: 'Feature',
properties: {user_id : "Daniele"},
geometry: { type : 'Point',
coordinates : [lng1, lat1]
}
},
{// Marker3
type: 'Feature',
properties: {user_id : "Roberto"},
geometry: { type : 'Point',
coordinates : [lng2, lat2]
}
}
When I cluster them, clicking the clustered circle, I want to see "Daniele, Roberto".
How can I do that?
Moreover, I'd like to set the size of circle, according to the distinct number of different users clustered (in the example above, should be 2).
**UPDATE 2
JSFIDDLE <--
An idea could be build an array of distinct names, and then use the length expression to size the circle.
Anyway, there should be a kind of syntax error...
clusterProperties: {
distinctNames :
['case',
/*cond */ ["!", ['in',['get', 'user_id'], ['accumulated']]],
/*result*/ ['concat', ['concat', ['get', 'user_id'], ',']],
/*default*/ ['accumulated']
]
}
According to the documentation you want to do something like this:
map.addSource(userData, {
id: 'user-locations',
type: 'geojson',
data: 'myuserdata.geojson',
cluster: true,
clusterProperties: {
names: ['concat', ['concat', ['get', 'user_id'], ',']]
}
}
Clustered points in your source will now have a property, names which will contain the comma-separated (and comma-terminated) string of names.
Moreover, I'd like to set the size of circle, according to the distinct number of different users clustered (in the example above, should be 2)
That sounds...challenging. One way I can think of doing that would be writing a custom accumulator function along these lines:
Make the function return an array of two values, [distinctNames, allNames] where the first is an integer, and the second is a string.
If allNames contains our current name, just return the array.
Otherwise, return an array which is [distinctNames + 1, allNames + thisName].
Manipulating arrays like this in Mapbox GL expressions is possible, but pretty fiddly. You need to use ['literal', ...] and ['at', ...]
The code would look something like this:
clusterProperties: {
names: ['concat', ['concat', ['get', 'user_id'], ',']],
distinctNames: [
['case', ['in', ['get', 'distinctNames'], ['at', ['accumulated'], 1]
['accumulated'],
['literal', ['+', ['at', ['accumulated'], 0], 1], ['concat', ['at', ['accumulated'], 1], ['get', 'distinctNames']]]
],
['concat', '%', ['get', 'user_id'], '%'] // wrap user ID in some unique character so we don't accidentally find user "rob" within another user "robin" for instance.
]
}
It's unclear from the documentation exactly how the accumulator function works, or how you access the current value. Their example implies that it would be ['get', <name of cluster property>] although that seems a bit weird.
The documentation is not really clear, but here is how I achieved this "distinct accumulate".
From the clusterProperties definition:
A custom reduce expression that references a special ["accumulated"] value, e.g.:
{"sum": [["+", ["accumulated"], ["get", "sum"]], ["get", "scalerank"]]}
Which results the same as: {"sum": ["+", ["get", "scalerank"]]}
In your case, you want to accumulate the user_id property from your markers without duplicates.
The logic is to add the user_id only if it has not already been added in the accumulated value.
clusterProperties: {
distinctUsers: [
// ['accumulated'] is the current value iterated during the reduce (the property is defined at [1])
// ['get', 'distinctCountries'] is the accumulated / concatenated string
[
// Concat accumulated value + current value if not present in accumulated
'concat',
['get', 'distinctUsers'],
[
'case',
['in', ['accumulated'], ['get', 'distinctUsers']], // If accumulated (user_id) has already been added
'', // Add EMPTY string
['concat', ', ', ['accumulated']], // Add the user_id (concatenated with a comma in your case)
],
],
['get', 'user_id'], // [1]: source marker property iterated in the custom reduce function
]
}
As Steve said in his answer, you could also wrap the user_id in some unique character so you don't accidentally find user "rob" within another user "robin" for instance.
The source property ['get', 'user_id'] defined at [1] would become :
['concat', '%', ['get', 'user_id'], '%']
Is it possible to filter boundary tilesets by ISO 3166-1 alpha-3 country code? And if yes, then how? I've searched in the documentation, but haven't found anything. This is how I currently filter the tileset by 2 characters country code iso_3166_1. I've tried to change it from 'iso_3166_1' to 'iso_3166_1_alpha_3', as found in reference to something different, but it doesn't work.
mapBox.on('load', () => {
mapBox.addSource('admin-1', {
type: 'vector',
url: 'mapbox://mapbox.boundaries-adm1-v3'
});
var countriesToDisplay: Array<string> = ['US', 'NZ']
countriesToDisplay.forEach((countryCode: string) => {
mapBox.addLayer({
id: 'admin-1-fill-' + countryCode,
type: 'fill',
source: 'admin-1',
'source-layer': 'boundaries_admin_1',
filter: ['any', ['all', ['==', ['get', 'iso_3166_1'], countryCode]]],
paint: { 'fill-color': '#044e9c' }
}, 'waterway-label');
});
});
Mapbox Boundaries v3 tileset only has a few features in the actual tiles, for polygons they are:
id
iso_3166_1
worldview
Checkout the full reference documentation here: https://docs.mapbox.com/vector-tiles/reference/mapbox-boundaries-v3/#polygon-tileset-reference
The rest of the data is stored in the supplemental look up tables that you are sent when you purchase the tileset. You can see all the properties available here: https://docs.mapbox.com/help/tutorials/get-started-mapbox-boundaries/#feature-lookup-tables
You will need to perform a data join to make the data from these lookup tables accessible to you in your javascript. There is a tutorial that walks you through that here: https://docs.mapbox.com/help/tutorials/data-joins-with-mapbox-boundaries/
I am trying to create a colored uk areas map - I need the colors to be driven by local data (say for example sales by postcode), which will always change depending on user selections like report date ranges etc. I have a tileset source which I am assigning to a layer, as in the following (sensitive values blanked out):
this.map.addLayer({
"id": "uk-pc-data",
"type": "fill",
"source-layer": "***",
"source": {
type: 'vector',
url: '***'
}
});
I am then able to style the postcode areas within the tileset by addressing a name property on the tileset features, like so:
"paint": {
"fill-color": [
"match",
["get", "name", [{name: "PR"}, {name: "CH"}, {name: "LN"}]],
[
"PR",
"DD",
"AL",
"PO"
],
"hsla(344, 84%, 29%, 0.37)",
"hsla(131, 94%, 34%, 0)"
]
}
The above will assign one color to the matched areas, and a default to all the other (the non matched ones); What I would like to do, is computing the colour value locally (so based on data constantly changing) based on the feature name, like so
"paint": {
"fill-color": function (feature = {}) {
return localSalesByArea(feature.name)
}
}
This though does not seem to be possible: any help or pointers appreciated; I have been through examples from Mapbox
such as choroplet, heatmaps, expressions but these seem to rely on values delivered via the dataset itself (i.e Population), in my case the values which determine the color scale are separated (they come from an internal reporting api)
If the data on which the colours are determined does not exist in the tileset, you basically have to do a gigantic lookup by name.
You will have a function that generates the fill-color expression, something like:
function makeFillExpression(valuesByName) {
function valueToColor(value) {
return /* insert your choroplething special sauce here */;
}
return ret = [
"match",
["get", "name"],
Object.keys(valuesByName).map(name => valueToColor(valuesByName[name])),
'black'
];
}
Yes, it will be a very large and unwieldy expression.