UI: Added clustering markers fn for maps
This commit is contained in:
parent
6445fec439
commit
95b6700296
@ -21,11 +21,15 @@ import { MarkerClusterGroup, MarkerClusterGroupOptions } from 'leaflet.markerclu
|
||||
import '@geoman-io/leaflet-geoman-free';
|
||||
|
||||
import {
|
||||
CircleData, defaultMapSettings,
|
||||
MarkerClusteringSettings,
|
||||
CircleData,
|
||||
defaultMapSettings,
|
||||
MarkerIconInfo,
|
||||
MarkerImageInfo,
|
||||
WidgetPolygonSettings, WidgetPolylineSettings, WidgetMarkersSettings, WidgetUnitedMapSettings
|
||||
WidgetMarkerClusteringSettings,
|
||||
WidgetMarkersSettings,
|
||||
WidgetPolygonSettings,
|
||||
WidgetPolylineSettings,
|
||||
WidgetUnitedMapSettings
|
||||
} from './map-models';
|
||||
import { Marker } from './markers';
|
||||
import { Observable, of } from 'rxjs';
|
||||
@ -33,10 +37,7 @@ import { Polyline } from './polyline';
|
||||
import { Polygon } from './polygon';
|
||||
import { Circle } from './circle';
|
||||
import { createTooltip, isCutPolygon, isJSON } from '@home/components/widget/lib/maps/maps-utils';
|
||||
import {
|
||||
checkLngLat,
|
||||
createLoadingDiv
|
||||
} from '@home/components/widget/lib/maps/common-maps-utils';
|
||||
import { checkLngLat, createLoadingDiv } from '@home/components/widget/lib/maps/common-maps-utils';
|
||||
import { WidgetContext } from '@home/models/widget-component.models';
|
||||
import {
|
||||
deepClone,
|
||||
@ -44,7 +45,9 @@ import {
|
||||
formattedDataFormDatasourceData,
|
||||
isDefinedAndNotNull,
|
||||
isNotEmptyStr,
|
||||
isString, mergeFormattedData, safeExecute
|
||||
isString,
|
||||
mergeFormattedData,
|
||||
safeExecute
|
||||
} from '@core/utils';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import {
|
||||
@ -52,8 +55,8 @@ import {
|
||||
SelectEntityDialogData
|
||||
} from '@home/components/widget/lib/maps/dialogs/select-entity-dialog.component';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import ITooltipsterInstance = JQueryTooltipster.ITooltipsterInstance;
|
||||
import { FormattedData, ReplaceInfo } from '@shared/models/widget.models';
|
||||
import ITooltipsterInstance = JQueryTooltipster.ITooltipsterInstance;
|
||||
|
||||
export default abstract class LeafletMap {
|
||||
|
||||
@ -98,6 +101,8 @@ export default abstract class LeafletMap {
|
||||
translateService: TranslateService;
|
||||
tooltipInstances: ITooltipsterInstance[] = [];
|
||||
|
||||
clusteringSettings: MarkerClusterGroupOptions;
|
||||
|
||||
protected constructor(public ctx: WidgetContext,
|
||||
public $container: HTMLElement,
|
||||
options: WidgetUnitedMapSettings) {
|
||||
@ -111,13 +116,13 @@ export default abstract class LeafletMap {
|
||||
}
|
||||
|
||||
private initMarkerClusterSettings() {
|
||||
const markerClusteringSettings: MarkerClusteringSettings = this.options;
|
||||
const markerClusteringSettings: WidgetMarkerClusteringSettings = this.options;
|
||||
if (markerClusteringSettings.useClusterMarkers) {
|
||||
// disabled marker cluster icon
|
||||
(L as any).MarkerCluster = (L as any).MarkerCluster.extend({
|
||||
options: { pmIgnore: true, ...L.Icon.prototype.options }
|
||||
});
|
||||
const clusteringSettings: MarkerClusterGroupOptions = {
|
||||
this.clusteringSettings = {
|
||||
spiderfyOnMaxZoom: markerClusteringSettings.spiderfyOnMaxZoom,
|
||||
zoomToBoundsOnClick: markerClusteringSettings.zoomOnClick,
|
||||
showCoverageOnHover: markerClusteringSettings.showCoverageOnHover,
|
||||
@ -132,13 +137,45 @@ export default abstract class LeafletMap {
|
||||
pmIgnore: true
|
||||
}
|
||||
};
|
||||
if (markerClusteringSettings.useIconCreateFunction && markerClusteringSettings.clusterMarkerFunction) {
|
||||
this.clusteringSettings.iconCreateFunction = (cluster) => {
|
||||
const childCount = cluster.getChildCount();
|
||||
const markerColor = markerClusteringSettings.clusterMarkerFunction
|
||||
? safeExecute(markerClusteringSettings.parsedClusterMarkerFunction,
|
||||
[cluster.getAllChildMarkers(), childCount])
|
||||
: null;
|
||||
if (isDefinedAndNotNull(markerColor) && tinycolor(markerColor).isValid()) {
|
||||
const parsedColor = tinycolor(markerColor);
|
||||
return L.divIcon({
|
||||
html: `<div style="background-color: ${parsedColor.setAlpha(0.4).toRgbString()};" class="marker-cluster tb-cluster-marker-element">` +
|
||||
`<div style="background-color: ${parsedColor.setAlpha(0.9).toRgbString()};"><span>` + childCount + '</span></div></div>',
|
||||
iconSize: new L.Point(40, 40),
|
||||
className: 'tb-cluster-marker-container'
|
||||
});
|
||||
} else {
|
||||
let c = ' marker-cluster-';
|
||||
if (childCount < 10) {
|
||||
c += 'small';
|
||||
} else if (childCount < 100) {
|
||||
c += 'medium';
|
||||
} else {
|
||||
c += 'large';
|
||||
}
|
||||
return new L.DivIcon({
|
||||
html: '<div><span>' + childCount + '</span></div>',
|
||||
className: 'marker-cluster' + c,
|
||||
iconSize: new L.Point(40, 40)
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
if (markerClusteringSettings.maxClusterRadius && markerClusteringSettings.maxClusterRadius > 0) {
|
||||
clusteringSettings.maxClusterRadius = Math.floor(markerClusteringSettings.maxClusterRadius);
|
||||
this.clusteringSettings.maxClusterRadius = Math.floor(markerClusteringSettings.maxClusterRadius);
|
||||
}
|
||||
if (markerClusteringSettings.maxZoom && markerClusteringSettings.maxZoom >= 0 && markerClusteringSettings.maxZoom < 19) {
|
||||
clusteringSettings.disableClusteringAtZoom = Math.floor(markerClusteringSettings.maxZoom);
|
||||
this.clusteringSettings.disableClusteringAtZoom = Math.floor(markerClusteringSettings.maxZoom);
|
||||
}
|
||||
this.markersCluster = new MarkerClusterGroup(clusteringSettings);
|
||||
this.markersCluster = new MarkerClusterGroup(this.clusteringSettings);
|
||||
}
|
||||
}
|
||||
|
||||
@ -886,6 +923,7 @@ export default abstract class LeafletMap {
|
||||
if (settings.showTooltip) {
|
||||
marker.updateMarkerTooltip(data);
|
||||
}
|
||||
marker.updateMarkerData(data);
|
||||
marker.updateMarkerIcon(settings);
|
||||
return marker;
|
||||
}
|
||||
|
||||
@ -17,7 +17,6 @@
|
||||
import { Datasource, FormattedData } from '@app/shared/models/widget.models';
|
||||
import tinycolor from 'tinycolor2';
|
||||
import { BaseIconOptions, Icon } from 'leaflet';
|
||||
import { DeviceProfileType } from '@shared/models/device.models';
|
||||
|
||||
export const DEFAULT_MAP_PAGE_SIZE = 16384;
|
||||
export const DEFAULT_ZOOM_LEVEL = 8;
|
||||
@ -602,6 +601,12 @@ export interface MarkerClusteringSettings {
|
||||
showCoverageOnHover: boolean;
|
||||
chunkedLoading: boolean;
|
||||
removeOutsideVisibleBounds: boolean;
|
||||
useIconCreateFunction: boolean;
|
||||
clusterMarkerFunction?: string;
|
||||
}
|
||||
|
||||
export interface WidgetMarkerClusteringSettings extends MarkerClusteringSettings {
|
||||
parsedClusterMarkerFunction?: GenericFunction;
|
||||
}
|
||||
|
||||
export const defaultMarkerClusteringSettings: MarkerClusteringSettings = {
|
||||
@ -613,7 +618,9 @@ export const defaultMarkerClusteringSettings: MarkerClusteringSettings = {
|
||||
spiderfyOnMaxZoom: false,
|
||||
showCoverageOnHover: true,
|
||||
chunkedLoading: false,
|
||||
removeOutsideVisibleBounds: true
|
||||
removeOutsideVisibleBounds: true,
|
||||
useIconCreateFunction: false,
|
||||
clusterMarkerFunction: null
|
||||
};
|
||||
|
||||
export interface MapEditorSettings {
|
||||
@ -635,7 +642,7 @@ export const defaultMapEditorSettings: MapEditorSettings = {
|
||||
};
|
||||
|
||||
export type UnitedMapSettings = MapProviderSettings & CommonMapSettings & MarkersSettings &
|
||||
PolygonSettings & CircleSettings & PolylineSettings & PointsSettings & MarkerClusteringSettings & MapEditorSettings;
|
||||
PolygonSettings & CircleSettings & PolylineSettings & PointsSettings & WidgetMarkerClusteringSettings & MapEditorSettings;
|
||||
|
||||
export const defaultMapSettings: UnitedMapSettings = {
|
||||
...defaultMapProviderSettings,
|
||||
|
||||
@ -250,6 +250,7 @@ export class MapWidgetController implements MapWidgetInterface {
|
||||
parsedCircleFillColorFunction: parseFunction(settings.circleFillColorFunction, functionParams),
|
||||
parsedCircleTooltipFunction: parseFunction(settings.circleTooltipFunction, functionParams),
|
||||
parsedMarkerImageFunction: parseFunction(settings.markerImageFunction, ['data', 'images', 'dsData', 'dsIndex']),
|
||||
parsedClusterMarkerFunction: parseFunction(settings.clusterMarkerFunction, ['data', 'childCount']),
|
||||
// labelColor: this.ctx.widgetConfig.color,
|
||||
// polygonLabelColor: this.ctx.widgetConfig.color,
|
||||
polygonKeyName: (settings as any).polKeyName ? (settings as any).polKeyName : settings.polygonKeyName,
|
||||
|
||||
@ -41,3 +41,15 @@
|
||||
.leaflet-container {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.tb-cluster-marker-container {
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
}
|
||||
.tb-cluster-marker-element {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
@ -15,11 +15,7 @@
|
||||
///
|
||||
|
||||
import L, { LeafletMouseEvent } from 'leaflet';
|
||||
import {
|
||||
MarkerIconInfo,
|
||||
MarkerIconReadyFunction,
|
||||
MarkerImageInfo, WidgetMarkersSettings,
|
||||
} from './map-models';
|
||||
import { MarkerIconInfo, MarkerIconReadyFunction, MarkerImageInfo, WidgetMarkersSettings, } from './map-models';
|
||||
import { bindPopupActions, createTooltip } from './maps-utils';
|
||||
import { aspectCache, parseWithTranslation } from './common-maps-utils';
|
||||
import tinycolor from 'tinycolor2';
|
||||
@ -46,7 +42,8 @@ export class Marker {
|
||||
snappable = false) {
|
||||
this.leafletMarker = L.marker(location, {
|
||||
pmIgnore: !settings.draggableMarker,
|
||||
snapIgnore: !snappable
|
||||
snapIgnore: !snappable,
|
||||
tbMarkerData: this.data
|
||||
});
|
||||
|
||||
this.markerOffset = [
|
||||
@ -110,6 +107,10 @@ export class Marker {
|
||||
}
|
||||
}
|
||||
|
||||
updateMarkerData(data: FormattedData) {
|
||||
this.leafletMarker.options.tbMarkerData = data;
|
||||
}
|
||||
|
||||
updateMarkerPosition(position: L.LatLng) {
|
||||
if (!this.leafletMarker.getLatLng().equals(position) && !this.editing) {
|
||||
this.location = position;
|
||||
|
||||
@ -64,6 +64,30 @@
|
||||
{{ 'widgets.maps.cluster-markers-lazy-load' | translate }}
|
||||
</mat-slide-toggle>
|
||||
</fieldset>
|
||||
<fieldset class="fields-group fields-group-slider">
|
||||
<legend class="group-title" translate>widgets.maps.clustering-markers</legend>
|
||||
<mat-expansion-panel class="tb-settings" [expanded]="markerClusteringSettingsFormGroup.get('useIconCreateFunction').value">
|
||||
<mat-expansion-panel-header fxLayout="row wrap">
|
||||
<mat-panel-title>
|
||||
<mat-slide-toggle formControlName="useIconCreateFunction" (click)="$event.stopPropagation()"
|
||||
fxLayoutAlign="center">
|
||||
{{ 'widgets.maps.use-icon-create-function' | translate }}
|
||||
</mat-slide-toggle>
|
||||
</mat-panel-title>
|
||||
<mat-panel-description fxLayoutAlign="end center" fxHide.xs translate>
|
||||
widget-config.advanced-settings
|
||||
</mat-panel-description>
|
||||
</mat-expansion-panel-header>
|
||||
<ng-template matExpansionPanelContent>
|
||||
<tb-js-func formControlName="clusterMarkerFunction"
|
||||
[globalVariables]="functionScopeVariables"
|
||||
[functionArgs]="['data', 'childCount']"
|
||||
functionTitle="{{ 'widgets.maps.marker-color-function' | translate }}"
|
||||
helpId="widget/lib/map/clustering_color_fn">
|
||||
</tb-js-func>
|
||||
</ng-template>
|
||||
</mat-expansion-panel>
|
||||
</fieldset>
|
||||
</ng-template>
|
||||
</mat-expansion-panel>
|
||||
</fieldset>
|
||||
|
||||
@ -56,6 +56,8 @@ export class MarkerClusteringSettingsComponent extends PageComponent implements
|
||||
|
||||
private modelValue: MarkerClusteringSettings;
|
||||
|
||||
functionScopeVariables = this.widgetService.getWidgetScopeVariables();
|
||||
|
||||
private propagateChange = null;
|
||||
|
||||
public markerClusteringSettingsFormGroup: FormGroup;
|
||||
@ -77,7 +79,9 @@ export class MarkerClusteringSettingsComponent extends PageComponent implements
|
||||
spiderfyOnMaxZoom: [null, []],
|
||||
showCoverageOnHover: [null, []],
|
||||
chunkedLoading: [null, []],
|
||||
removeOutsideVisibleBounds: [null, []]
|
||||
removeOutsideVisibleBounds: [null, []],
|
||||
useIconCreateFunction: [null, []],
|
||||
clusterMarkerFunction: [null, []]
|
||||
});
|
||||
this.markerClusteringSettingsFormGroup.valueChanges.subscribe(() => {
|
||||
this.updateModel();
|
||||
|
||||
@ -0,0 +1,64 @@
|
||||
#### Clustering marker function
|
||||
|
||||
<div class="divider"></div>
|
||||
<br/>
|
||||
|
||||
*function (data, childCount): string*
|
||||
|
||||
A JavaScript function used to compute clustering marker color.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
<ul>
|
||||
<li><b>data:</b> <code><a href="https://github.com/thingsboard/thingsboard/blob/5bb6403407aa4898084832d6698aa9ea6d484889/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts#L108" target="_blank">FormattedData</a></code> - A <a href="https://github.com/thingsboard/thingsboard/blob/5bb6403407aa4898084832d6698aa9ea6d484889/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts#L108" target="_blank">FormattedData</a> object associated with marker or data point of the route.<br/>
|
||||
Represents basic entity properties (ex. <code>entityId</code>, <code>entityName</code>)<br/>and provides access to other entity attributes/timeseries declared in widget datasource configuration.
|
||||
</li>
|
||||
<li><b>childCount:</b> <code>number</code> - number of markers in this cluster
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
**Returns:**
|
||||
|
||||
Should return string value presenting color of the marker.
|
||||
|
||||
In case no data is returned, color value from **Color** settings field will be used.
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
##### Examples
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
Calculate color depending on temperature telemetry value:
|
||||
</li>
|
||||
|
||||
|
||||
```javascript
|
||||
let customColor;
|
||||
for (let markerData of data) {
|
||||
if (markerData.options.tbMarkerData.temperature > 40) {
|
||||
customColor = 'red'
|
||||
}
|
||||
}
|
||||
return customColor ? customColor : 'green';
|
||||
{:copy-code}
|
||||
```
|
||||
|
||||
<li>
|
||||
Calculate color depending on childCount:
|
||||
</li>
|
||||
|
||||
```javascript
|
||||
if (childCount < 10) {
|
||||
return 'green';
|
||||
} else if (childCount < 100) {
|
||||
return 'yellow';
|
||||
} else {
|
||||
return 'red';
|
||||
}
|
||||
{:copy-code}
|
||||
```
|
||||
|
||||
</ul>
|
||||
<br>
|
||||
<br>
|
||||
@ -4543,7 +4543,10 @@
|
||||
"point-color-function": "Point color function",
|
||||
"use-point-as-anchor": "Use point as anchor",
|
||||
"point-as-anchor-function": "Point as anchor function",
|
||||
"independent-point-tooltip": "Independent point tooltip"
|
||||
"independent-point-tooltip": "Independent point tooltip",
|
||||
"clustering-markers": "Clustering markers",
|
||||
"use-icon-create-function": "Use markers colour function",
|
||||
"marker-color-function": "Marker color function"
|
||||
},
|
||||
"markdown": {
|
||||
"use-markdown-text-function": "Use markdown/HTML value function",
|
||||
|
||||
24
ui-ngx/src/typings/leaflet-extend-tb.d.ts
vendored
Normal file
24
ui-ngx/src/typings/leaflet-extend-tb.d.ts
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
///
|
||||
/// Copyright © 2016-2022 The Thingsboard Authors
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
///
|
||||
|
||||
import { FormattedData } from '@shared/models/widget.models';
|
||||
|
||||
// redeclare module, maintains compatibility with @types/leaflet
|
||||
declare module 'leaflet' {
|
||||
interface MarkerOptions {
|
||||
tbMarkerData?: FormattedData;
|
||||
}
|
||||
}
|
||||
@ -23,7 +23,8 @@
|
||||
"src/typings/jquery.flot.typings.d.ts",
|
||||
"src/typings/jquery.jstree.typings.d.ts",
|
||||
"src/typings/split.js.typings.d.ts",
|
||||
"src/typings/leaflet-geoman-extend.d.ts"
|
||||
"src/typings/leaflet-geoman-extend.d.ts",
|
||||
"src/typings/leaflet-extend-tb.d.ts",
|
||||
],
|
||||
"paths": {
|
||||
"@app/*": ["src/app/*"],
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user