From 8613a015e52fbb410f10e0e5ad0d1de158686a1f Mon Sep 17 00:00:00 2001 From: Timofey Date: Tue, 24 Sep 2024 17:25:36 +0300 Subject: [PATCH] [feat] Add ruler customization options (#38) * [feat] Add ruler customization options * [fix] Fix issues from PR * doc strings * fix tests --------- Co-authored-by: Michel Beloshitsky --- src/control/index.ts | 29 ++++++++++++++++++++++++++++- src/joint.ts | 24 +++++++++++++++--------- src/polygon.ts | 24 +++++++++++++++++------- src/polyline.ts | 12 ++++++++---- src/previewLine.ts | 6 +++--- src/ruler.ts | 36 +++++++++++++++++++++++++++++++----- src/snapPoint.ts | 13 +++++++------ src/types.ts | 23 +++++++++++++++++++++++ src/utils.ts | 42 +++++++++++++++++++++++++++--------------- 9 files changed, 159 insertions(+), 50 deletions(-) diff --git a/src/control/index.ts b/src/control/index.ts index 123f9d2..18ecdb1 100644 --- a/src/control/index.ts +++ b/src/control/index.ts @@ -11,6 +11,26 @@ export interface RulerControlOptions extends mapgl.ControlOptions { */ mode?: RulerOptions['mode']; + /** + * Polygon drawing options. + */ + polygonOptions?: RulerOptions['polygonOptions']; + + /** + * Polyline drawing options. + */ + polylineOptions?: RulerOptions['polylineOptions']; + + /** + * Custom joint factory function, useful for styling + */ + jointFactory?: RulerOptions['jointFactory']; + + /** + * Custom snap point factory function, useful for styling + */ + snapPointFactory?: RulerOptions['snapPointFactory']; + /** * Specifies whether the ruler should be enabled after control initialization. */ @@ -52,7 +72,14 @@ export class RulerControl extends Control { default: throw new Error(`unsupported mode: ${mode}`); } - this.ruler = new Ruler(this.map, { enabled: this.isEnabled, mode }); + this.ruler = new Ruler(this.map, { + enabled: this.isEnabled, + mode, + polygonOptions: options.polygonOptions, + polylineOptions: options.polylineOptions, + jointFactory: options.jointFactory, + snapPointFactory: options.snapPointFactory, + }); this.render(); } diff --git a/src/joint.ts b/src/joint.ts index b04b6ce..77ac0fd 100644 --- a/src/joint.ts +++ b/src/joint.ts @@ -1,4 +1,4 @@ -import { GeoPoint, TargetedEvent } from './types'; +import { GeoPoint, JointFactory, TargetedEvent } from './types'; import { createHtmlMarker, getJointDistanceText, @@ -43,6 +43,11 @@ export class Joint extends Evented { distance: number, enableOnInit, private showLabel: boolean, + private renderCustomMarker: JointFactory = ( + map, + coordinates, + { isFirst, interactive }, + ) => createHtmlMarker(map, coordinates, { big: isFirst, interactive }), ) { super(); this.id = ++lastId; @@ -75,10 +80,7 @@ export class Joint extends Evented { } enable() { - this.marker = createHtmlMarker(this.map, this.getCoordinates(), { - big: this.isFirst, - interactive: true, - }); + this.createHtmlMarker(); this.addMarkerEventListeners(); this.updateLabel(); @@ -116,10 +118,7 @@ export class Joint extends Evented { this.marker?.destroy(); document.removeEventListener('mouseup', this.onMouseUp); - this.marker = createHtmlMarker(this.map, this.coordinates, { - big: this.isFirst, - interactive: true, - }); + this.createHtmlMarker(); this.addMarkerEventListeners(); this.setDistance(0, true); } @@ -240,6 +239,13 @@ export class Joint extends Evented { targetData: this, }); }; + + private createHtmlMarker = () => { + this.marker = this.renderCustomMarker(this.map, this.getCoordinates(), { + isFirst: this.isFirst, + interactive: true, + }); + }; } function createLabel(map: mapgl.Map, coordinates: GeoPoint, distance: number, isFirst: boolean) { diff --git a/src/polygon.ts b/src/polygon.ts index a4e28c2..e3659d1 100644 --- a/src/polygon.ts +++ b/src/polygon.ts @@ -2,9 +2,10 @@ import { geoPointsDistance } from './utils'; import { Joint } from './joint'; import { area } from './geo/area'; import { centroid } from './geo/centroid'; -import { GeoPoint } from './types'; +import { GeoPoint, RulerPolygonOptions } from './types'; import { style } from './style'; import { getTranslation } from './l10n'; +import { PolygonOptions } from '@2gis/mapgl/types/types'; /** * @internal @@ -17,20 +18,25 @@ export class Polygon { private area: number; private perimeter: number; - constructor(private readonly map: mapgl.Map, joints: Joint[], private showLabel: boolean) { + constructor( + private readonly map: mapgl.Map, + joints: Joint[], + private showLabel: boolean, + options: Pick, + ) { this.area = 0; this.perimeter = 0; this.centroid = [0, 0]; if (joints.length > 2) { const points = joints.map((j) => j.getCoordinates()); - this.polygon = createPolygon(this.map, points); + this.polygon = createPolygon(this.map, points, options); this.centroid = centroid(points); this.updateLabel(); } } - update(joints: Joint[]) { + update(joints: Joint[], options: RulerPolygonOptions) { this.polygon?.destroy(); if (joints.length <= 2) { this.perimeter = 0; @@ -47,7 +53,7 @@ export class Polygon { geoPointsDistance(points[points.length - 1], points[0]); this.area = area(points); - this.polygon = createPolygon(this.map, points); + this.polygon = createPolygon(this.map, points, options); this.updateLabel(); } @@ -98,12 +104,16 @@ function createLabel(map: mapgl.Map, coordinates: GeoPoint, area: number): mapgl }); } -function createPolygon(map: mapgl.Map, points: GeoPoint[]): mapgl.Polygon { +function createPolygon( + map: mapgl.Map, + points: GeoPoint[], + options: RulerPolygonOptions, +): mapgl.Polygon { return new mapgl.Polygon(map, { coordinates: [points], zIndex: style.areaPhase, interactive: false, - color: style.areaColor, + color: options.color ?? style.areaColor, strokeWidth: style.areaStrokeWidth, }); } diff --git a/src/polyline.ts b/src/polyline.ts index 6e85a5c..76d10f9 100644 --- a/src/polyline.ts +++ b/src/polyline.ts @@ -1,4 +1,4 @@ -import { GeoPoint, RulerMode } from './types'; +import { GeoPoint, RulerMode, RulerPolylineOptions } from './types'; import { createLine, geoPointsDistance } from './utils'; import { Joint } from './joint'; import { Evented } from './evented'; @@ -21,7 +21,11 @@ export class Polyline extends Evented { super(); } - update(mode: RulerMode, joints: Joint[]) { + update(mode: RulerMode, joints: Joint[], polylineOptions: RulerPolylineOptions) { + if (polylineOptions.autoClosePolygon === undefined) { + polylineOptions.autoClosePolygon = true; + } + this.polyline?.destroy(); if (joints.length === 0) { return; @@ -41,11 +45,11 @@ export class Polyline extends Evented { }); // замыкаем линию если рисуем площадь - if (mode === 'polygon') { + if (mode === 'polygon' && polylineOptions.autoClosePolygon) { points.push(joints[0].getCoordinates()); } - this.polyline = createLine(this.map, points, false); + this.polyline = createLine(this.map, points, false, polylineOptions); this.polyline.on('mousemove', (ev) => this.emit('mousemove', ev)); this.polyline.on('mouseout', (ev) => this.emit('mouseout', ev)); this.polyline.on('click', (ev) => this.emit('click', ev)); diff --git a/src/previewLine.ts b/src/previewLine.ts index 4fe4b7d..aac9fd3 100644 --- a/src/previewLine.ts +++ b/src/previewLine.ts @@ -1,5 +1,5 @@ import { Joint } from './joint'; -import { GeoPoint, RulerMode } from './types'; +import { GeoPoint, RulerMode, RulerPolylineOptions } from './types'; import { createLine } from './utils'; /** @@ -28,7 +28,7 @@ export class PreviewLine { this.draggableJoint = joint; } - update(mode: RulerMode, joints: Joint[]) { + update(mode: RulerMode, joints: Joint[], previewLineOptions: RulerPolylineOptions) { this.polyline?.destroy(); if (this.draggableJoint) { const curr = joints.indexOf(this.draggableJoint); @@ -50,7 +50,7 @@ export class PreviewLine { coordinates.push((joints[curr + 1] ?? joints[0]).getCoordinates()); } - this.polyline = createLine(this.map, coordinates, true); + this.polyline = createLine(this.map, coordinates, true, previewLineOptions); } } } diff --git a/src/ruler.ts b/src/ruler.ts index 77dde16..52bc7c5 100644 --- a/src/ruler.ts +++ b/src/ruler.ts @@ -7,6 +7,10 @@ import { TargetedEvent, RulerData, RulerMode, + RulerPolygonOptions, + RulerPolylineOptions, + JointFactory, + SnapPointFactory, } from './types'; import { geoPointsDistance, getSnapPoint } from './utils'; import { Joint } from './joint'; @@ -73,6 +77,14 @@ export interface RulerOptions { * Specifies whether the labels should be drawn. Optional. */ labelVisibilitySettings?: LabelVisibilitySettings; + + polygonOptions?: RulerPolygonOptions; + + polylineOptions?: RulerPolylineOptions; + + jointFactory?: JointFactory; + + snapPointFactory?: SnapPointFactory; } interface RedrawFlags { @@ -98,6 +110,10 @@ export class Ruler extends Evented { private polyline: Polyline; private newSnapInfo?: SnapInfo; private polygon?: Polygon; + private polygonOptions: RulerPolygonOptions; + private polylineOptions: RulerPolylineOptions; + private jointFactory?: RulerOptions['jointFactory']; + private snapPointFactory?: RulerOptions['snapPointFactory']; /** * Example: @@ -137,6 +153,10 @@ export class Ruler extends Evented { this.polyline.on('mousemove', this.onPolylineMouseMove); this.polyline.on('mouseout', this.onPolylineMouseOut); this.polyline.on('click', this.onPolylineClick); + this.polygonOptions = options.polygonOptions ?? {}; + this.polylineOptions = options.polylineOptions ?? {}; + this.jointFactory = options.jointFactory; + this.snapPointFactory = options.snapPointFactory; options.points?.forEach((point, i) => this.addPoint(point, i)); @@ -166,7 +186,12 @@ export class Ruler extends Evented { this.redrawFlags.polyline = true; if (this.mode === 'polygon') { - this.polygon = new Polygon(this.map, this.joints, this.labelVisibilitySettings.area); + this.polygon = new Polygon( + this.map, + this.joints, + this.labelVisibilitySettings.area, + this.polygonOptions, + ); } this.map.on('click', this.onClick); @@ -278,6 +303,7 @@ export class Ruler extends Evented { distance, this.enabled, this.labelVisibilitySettings.perimeter, + this.jointFactory, ); joint.on('dragstart', this.onJointMoveStart); @@ -447,18 +473,18 @@ export class Ruler extends Evented { if (this.redrawFlags.polyline) { this.redrawFlags.polyline = false; - this.polygon?.update(this.joints); - this.polyline.update(this.mode, this.joints); + this.polygon?.update(this.joints, this.polygonOptions); + this.polyline.update(this.mode, this.joints, this.polylineOptions); } if (this.redrawFlags.preview) { this.redrawFlags.preview = false; - this.previewLine.update(this.mode, this.joints); + this.previewLine.update(this.mode, this.joints, this.polylineOptions); } if (this.redrawFlags.snap) { this.redrawFlags.snap = false; - this.snapPoint.update(this.newSnapInfo); + this.snapPoint.update(this.newSnapInfo, this.snapPointFactory); this.newSnapInfo = undefined; } diff --git a/src/snapPoint.ts b/src/snapPoint.ts index 377a0ec..75f78c2 100644 --- a/src/snapPoint.ts +++ b/src/snapPoint.ts @@ -1,4 +1,4 @@ -import { GeoPoint, SnapInfo } from './types'; +import { GeoPoint, SnapPointFactory, SnapInfo } from './types'; import { createHtmlMarker, getJointDistanceText, getLabelHtml, getSnapLabelHtml } from './utils'; import { style } from './style'; import { getTranslation } from './l10n'; @@ -15,7 +15,11 @@ export class SnapPoint { constructor(private readonly map: mapgl.Map, private showLabel: boolean) {} - update(info: SnapInfo | undefined) { + update( + info: SnapInfo | undefined, + snapPointFactory: SnapPointFactory = (map, coordinates) => + createHtmlMarker(map, coordinates, { big: true, interactive: false }), + ) { if (info === undefined) { this.destroy(); return; @@ -24,10 +28,7 @@ export class SnapPoint { this.distance = info.distance; if (!this.marker) { - this.marker = createHtmlMarker(this.map, this.point, { - big: true, - interactive: false, - }); + this.marker = snapPointFactory(this.map, this.point); } else { this.marker.setCoordinates(this.point); } diff --git a/src/types.ts b/src/types.ts index 3f5d04b..56d1b7a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,3 +1,5 @@ +import { PolygonOptions } from '@2gis/mapgl/types/types'; + /** * Possible modes of operation of the plugin. */ @@ -60,3 +62,24 @@ export interface PolylineData extends BaseData { } export type RulerData = PolygonData | PolylineData; + +export type RulerPolygonOptions = Pick; + +export interface RulerPolylineOptions { + lineColor?: string; + lineWidth?: number; + lineBorderColor?: string; + lineBorderWidth?: number; + lineBorder2Color?: string; + lineBorder2Width?: number; + previewLineColor?: string; + autoClosePolygon?: boolean; +} + +export type JointFactory = ( + map: mapgl.Map, + coordinates: GeoPoint, + options: { isFirst: boolean; interactive: boolean }, +) => mapgl.HtmlMarker; + +export type SnapPointFactory = (map: mapgl.Map, coordinates: GeoPoint) => mapgl.HtmlMarker; diff --git a/src/utils.ts b/src/utils.ts index b33a6bc..0b6b1f7 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,7 +1,7 @@ -import { GeoPoint, ScreenPoint, SnapInfo } from './types'; +import { GeoPoint, RulerPolylineOptions, ScreenPoint, SnapInfo } from './types'; import { Joint } from './joint'; import { getTranslation } from './l10n'; -import { style } from './style'; +import { style as styleDefault } from './style'; import { JOINT_REMOVE_BUTTON_SVG } from './constants'; import css from './index.module.css'; @@ -52,20 +52,31 @@ export function geoPointsDistance(lngLat1: GeoPoint, lngLat2: GeoPoint): number * @hidden * @internal */ -export function createLine(map: mapgl.Map, points: GeoPoint[], preview: boolean): mapgl.Polyline { +export function createLine( + map: mapgl.Map, + points: GeoPoint[], + preview: boolean, + options?: RulerPolylineOptions, +): mapgl.Polyline { + const lineWidth = options?.lineWidth ?? styleDefault.lineWidth; + const lineColor = options?.lineColor ?? styleDefault.lineColor; + const lineBorderColor = options?.lineBorderColor ?? styleDefault.lineBorderColor; + const lineBorderWidth = options?.lineBorderWidth ?? styleDefault.lineBorderWidth; + const lineBorder2Color = options?.lineBorder2Color ?? styleDefault.lineBorder2Color; + const lineBorder2Width = options?.lineBorder2Width ?? styleDefault.lineBorder2Width; + const previewLineColor = options?.previewLineColor ?? styleDefault.previewLineColor; + return new mapgl.Polyline(map, { coordinates: points, - zIndex: style.linePhase, - zIndex2: style.linePhase - 0.00001, - zIndex3: style.linePhase - 0.00002, - width: style.lineWidth, - width2: preview ? 0 : style.lineWidth + 2 * style.lineBorderWidth, - width3: preview - ? 0 - : style.lineWidth + 2 * (style.lineBorderWidth + style.lineBorder2Width), - color: preview ? style.previewLineColor : style.lineColor, - color2: style.lineBorderColor, - color3: style.lineBorder2Color, + zIndex: styleDefault.linePhase, + zIndex2: styleDefault.linePhase - 0.00001, + zIndex3: styleDefault.linePhase - 0.00002, + width: lineWidth, + width2: preview ? 0 : lineWidth + 2 * lineBorderWidth, + width3: preview ? 0 : lineWidth + 2 * (lineBorderWidth + lineBorder2Width), + color: preview ? previewLineColor : lineColor, + color2: lineBorderColor, + color3: lineBorder2Color, interactive: !preview, }); } @@ -105,12 +116,13 @@ export function createHtmlMarker( opts: { big?: boolean; interactive?: boolean; + style?: string; }, ): mapgl.HtmlMarker { return new mapgl.HtmlMarker(map, { coordinates, html: `