From 7d4ccad788a46e388c91306cb0d43392e8860787 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 10 Apr 2025 13:20:33 +0300 Subject: [PATCH] UI: New map widgets - introduce preserveAspectRatio option for shape image fill settings. --- .../lib/maps/data-layer/shapes-data-layer.ts | 34 ++++++++++++++----- .../widget/lib/maps/leaflet/leaflet-tb.ts | 10 +++++- ...e-fill-image-settings-panel.component.html | 5 +++ ...ape-fill-image-settings-panel.component.ts | 3 ++ ...pe-fill-stripe-settings-panel.component.ts | 2 +- .../shape-fill-stripe-settings.component.ts | 6 ++-- .../shared/models/widget/maps/map.models.ts | 4 +++ .../widget/lib/map/shape_fill_image_fn.md | 2 ++ .../assets/locale/locale.constant-en_US.json | 1 + ui-ngx/src/typings/leaflet-extend-tb.d.ts | 9 +++-- 10 files changed, 60 insertions(+), 16 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/shapes-data-layer.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/shapes-data-layer.ts index c742f152d4..93502588b1 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/shapes-data-layer.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/shapes-data-layer.ts @@ -49,9 +49,10 @@ interface ShapePatternInfo { image: string; width: number; height: number; - opacity?: number; - angle?: number; - scale?: number; + preserveAspectRatio: boolean; + opacity: number; + angle: number; + scale: number; }; fillStripe?: { weight: number; @@ -122,23 +123,35 @@ abstract class ShapePatternProcessor { fillOpacity: 1, stroke: false, fill: true, fillColor: patternInfo.fillColor}); pattern.addElement(fillRect); } else if (patternInfo.type === ShapeFillType.image) { - pattern = new L.TB.Pattern({ + const patternOptions: L.TB.PatternOptions = { width: 1, height: 1, patternUnits: 'objectBoundingBox', patternContentUnits: 'objectBoundingBox', - preserveAspectRatioAlign: 'xMidYMid', - preserveAspectRatioMeetOrSlice: 'slice', viewBox: [0,0,patternInfo.fillImage.width,patternInfo.fillImage.height] - }); - const imagePatternShape = new L.TB.PatternImage({ + }; + if (patternInfo.fillImage.preserveAspectRatio) { + patternOptions.preserveAspectRatioAlign = 'xMidYMid'; + patternOptions.preserveAspectRatioMeetOrSlice = 'slice'; + } else { + patternOptions.preserveAspectRatioAlign = 'none'; + } + pattern = new L.TB.Pattern(patternOptions); + const imagePatternOptions: L.TB.PatternImageOptions = { imageUrl: patternInfo.fillImage.image, width: patternInfo.fillImage.width, height: patternInfo.fillImage.height, opacity: patternInfo.fillImage.opacity, angle: patternInfo.fillImage.angle, scale: patternInfo.fillImage.scale - }); + }; + if (patternInfo.fillImage.preserveAspectRatio) { + imagePatternOptions.preserveAspectRatioAlign = 'xMidYMid'; + imagePatternOptions.preserveAspectRatioMeetOrSlice = 'slice'; + } else { + imagePatternOptions.preserveAspectRatioAlign = 'none'; + } + const imagePatternShape = new L.TB.PatternImage(imagePatternOptions); pattern.addElement(imagePatternShape); } else if (patternInfo.type === ShapeFillType.stripe) { const stripeInfo = patternInfo.fillStripe; @@ -221,6 +234,7 @@ class ShapeImagePatternProcessor extends ShapePatternProcessor { const imageUrl = image?.url || '/assets/widget-preview-empty.svg'; + const preserveAspectRatio = isDefinedAndNotNull(image?.preserveAspectRatio) ? image.preserveAspectRatio : true; const opacity = isDefinedAndNotNull(image?.opacity) ? image.opacity : 1; const imagePipe = this.dataLayer.getCtx().$injector.get(ImagePipe); return loadImageWithAspect(imagePipe, imageUrl).pipe( @@ -241,6 +256,7 @@ class ShapeImagePatternProcessor extends ShapePatternProcessor implements L this._dom.setAttribute('y', '0'); this._dom.setAttribute('width', `${options.width}`); this._dom.setAttribute('height', `${options.height}`); - this._dom.setAttribute('preserveAspectRatio', 'xMidYMid slice'); + if (options.preserveAspectRatioAlign) { + let preserveAspectRatioValue = options.preserveAspectRatioAlign; + if (preserveAspectRatioValue !== 'none' && options.preserveAspectRatioMeetOrSlice) { + preserveAspectRatioValue += (' ' + options.preserveAspectRatioMeetOrSlice); + } + dom.setAttribute('preserveAspectRatio', preserveAspectRatioValue); + } else { + dom.removeAttribute('preserveAspectRatio'); + } const transforms: string[] = []; if (options.angle) { transforms.push(`rotate(${options.angle})`); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/shape-fill-image-settings-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/shape-fill-image-settings-panel.component.html index 660f43a75d..d2782d67cb 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/shape-fill-image-settings-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/shape-fill-image-settings-panel.component.html @@ -30,6 +30,11 @@
+
+ + {{ 'widgets.maps.data-layer.shape.preserve-aspect-ratio' | translate }} + +
widgets.maps.data-layer.shape.opacity
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/shape-fill-image-settings-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/shape-fill-image-settings-panel.component.ts index 0d14c5f81c..fef64d2e44 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/shape-fill-image-settings-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/shape-fill-image-settings-panel.component.ts @@ -53,6 +53,7 @@ export class ShapeFillImageSettingsPanelComponent implements OnInit { { type: [this.shapeFillImageSettings?.type || ShapeFillImageType.image, []], image: [this.shapeFillImageSettings?.image, [Validators.required]], + preserveAspectRatio: [this.shapeFillImageSettings?.preserveAspectRatio, []], opacity: [this.shapeFillImageSettings?.opacity, [Validators.min(0), Validators.max(1)]], angle: [this.shapeFillImageSettings?.angle, [Validators.min(0), Validators.max(360)]], scale: [this.shapeFillImageSettings?.scale, [Validators.min(0)]], @@ -83,6 +84,7 @@ export class ShapeFillImageSettingsPanelComponent implements OnInit { const type: ShapeFillImageType = this.shapeFillImageSettingsFormGroup.get('type').value; if (type === ShapeFillImageType.image) { this.shapeFillImageSettingsFormGroup.get('image').enable({emitEvent: false}); + this.shapeFillImageSettingsFormGroup.get('preserveAspectRatio').enable({emitEvent: false}); this.shapeFillImageSettingsFormGroup.get('opacity').enable({emitEvent: false}); this.shapeFillImageSettingsFormGroup.get('angle').enable({emitEvent: false}); this.shapeFillImageSettingsFormGroup.get('scale').enable({emitEvent: false}); @@ -90,6 +92,7 @@ export class ShapeFillImageSettingsPanelComponent implements OnInit { this.shapeFillImageSettingsFormGroup.get('images').disable({emitEvent: false}); } else { this.shapeFillImageSettingsFormGroup.get('image').disable({emitEvent: false}); + this.shapeFillImageSettingsFormGroup.get('preserveAspectRatio').disable({emitEvent: false}); this.shapeFillImageSettingsFormGroup.get('opacity').disable({emitEvent: false}); this.shapeFillImageSettingsFormGroup.get('angle').disable({emitEvent: false}); this.shapeFillImageSettingsFormGroup.get('scale').disable({emitEvent: false}); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/shape-fill-stripe-settings-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/shape-fill-stripe-settings-panel.component.ts index c9df56338b..86d1ae67fd 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/shape-fill-stripe-settings-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/shape-fill-stripe-settings-panel.component.ts @@ -97,7 +97,7 @@ export class ShapeFillStripeSettingsPanelComponent implements OnInit { private updatePreview() { const shapeFillStripeSettings: ShapeFillStripeSettings = this.shapeFillStripeSettingsFormGroup.value; - const previewUrl = generateStripePreviewUrl(shapeFillStripeSettings); + const previewUrl = generateStripePreviewUrl(shapeFillStripeSettings, 136, 118); this.stripePreviewStyle = { background: this.sanitizer.bypassSecurityTrustStyle(`url(${previewUrl}) no-repeat 50% 50% / cover`) }; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/shape-fill-stripe-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/shape-fill-stripe-settings.component.ts index 63b2b4b212..678d57b1e3 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/shape-fill-stripe-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/shape-fill-stripe-settings.component.ts @@ -126,15 +126,15 @@ export class ShapeFillStripeSettingsComponent implements ControlValueAccessor { } } -export const generateStripePreviewUrl = (settings: ShapeFillStripeSettings): string => { +export const generateStripePreviewUrl = (settings: ShapeFillStripeSettings, previewWidth = 48, previewHeight = 48): string => { const weight = isDefinedAndNotNull(settings?.weight) ? settings.weight : 3; const spaceWeight = isDefinedAndNotNull(settings?.spaceWeight) ? settings.spaceWeight : 9; const angle = isDefinedAndNotNull(settings?.angle) ? settings.angle : 45; const height = weight + spaceWeight; const color = settings?.color?.color || '#8f8f8f'; const spaceColor = settings?.spaceColor?.color || 'rgba(143,143,143,0)'; - const svgStr = ` - + const svgStr = ` + diff --git a/ui-ngx/src/app/shared/models/widget/maps/map.models.ts b/ui-ngx/src/app/shared/models/widget/maps/map.models.ts index e09d5e5756..0468fe1fab 100644 --- a/ui-ngx/src/app/shared/models/widget/maps/map.models.ts +++ b/ui-ngx/src/app/shared/models/widget/maps/map.models.ts @@ -501,6 +501,7 @@ export enum ShapeFillImageType { export interface ShapeFillImageSettings { type: ShapeFillImageType; image?: string; + preserveAspectRatio?: boolean; opacity?: number; // (0-1) angle?: number; // (0-360) scale?: number; // (0-...) @@ -550,6 +551,7 @@ export const defaultBasePolygonsDataLayerSettings = (mapType: MapType): Partial< fillImage: { type: ShapeFillImageType.image, image: '/assets/widget-preview-empty.svg', + preserveAspectRatio: true, opacity: 1, angle: 0, scale: 1 @@ -600,6 +602,7 @@ export const defaultBaseCirclesDataLayerSettings = (mapType: MapType): Partial