diff --git a/examples/nuxt-app/test/features/maps/maps.feature b/examples/nuxt-app/test/features/maps/maps.feature index 83b391a2b..57229f548 100644 --- a/examples/nuxt-app/test/features/maps/maps.feature +++ b/examples/nuxt-app/test/features/maps/maps.feature @@ -33,7 +33,7 @@ Feature: Custom collection map component Given I visit the page "/map" And the map is loaded When I click the map component at coordinates 517 242 - When I wait 2 seconds + When I wait 4 seconds Then the map matches the image snapshot "map-popup-type-popover" @mockserver @@ -45,7 +45,7 @@ Feature: Custom collection map component Given I visit the page "/map" And the map is loaded When I click the map component at coordinates 517 242 - When I wait 2 seconds + When I wait 4 seconds Then the map matches the image snapshot "map-popup-type-sidebar" @mockserver @@ -58,7 +58,7 @@ Feature: Custom collection map component Given I visit the page "/map" And the map is loaded When I click the map component at coordinates 663 242 - When I wait 2 seconds + When I wait 4 seconds Then the map matches the image snapshot "map-popup-type-popover-with-sidepanel" @mockserver @@ -71,7 +71,7 @@ Feature: Custom collection map component Given I visit the page "/map" And the map is loaded When I click the map component at coordinates 663 242 - When I wait 2 seconds + When I wait 4 seconds Then the map matches the image snapshot "map-popup-type-sidebar-with-sidepanel" @mockserver @@ -83,7 +83,7 @@ Feature: Custom collection map component Given I visit the page "/map" And the map is loaded When I click the map component at coordinates 606 442 - When I wait 2 seconds + When I wait 4 seconds Then the map matches the image snapshot "map-popup-type-sidebar-with-sidepanel-double-pin" @mockserver @@ -96,7 +96,7 @@ Feature: Custom collection map component Given I visit the page "/map" And the map is loaded Given I click the side panel item with text "Single Pin Test" - When I wait 2 seconds + When I wait 4 seconds Then the map matches the image snapshot "map-sidepanel-item-click" @mockserver @@ -112,7 +112,7 @@ Feature: Custom collection map component Then I click the map component at coordinates 545 385 And I wait 1 seconds Then I click the map component at coordinates 650 320 - And I wait 2 seconds + And I wait 4 seconds Then the map matches the image snapshot "map-popover-max-zoom-cluster" @mockserver diff --git a/packages/ripple-ui-maps/src/components/cluster/RplMapCluster.vue b/packages/ripple-ui-maps/src/components/cluster/RplMapCluster.vue index 2c5d84c33..edb67735c 100644 --- a/packages/ripple-ui-maps/src/components/cluster/RplMapCluster.vue +++ b/packages/ripple-ui-maps/src/components/cluster/RplMapCluster.vue @@ -12,7 +12,9 @@ import CircleStyle from 'ol/style/Circle' import Fill from 'ol/style/Fill' import Stroke from 'ol/style/Stroke' import markerIconDefaultSrc from './../feature-pin/icon-pin.svg?url' -import type { Options } from 'ol/style/Circle' +import { Style, Text } from 'ol/style' +import { inject, nextTick, onMounted, Ref, ref } from 'vue' +import { Feature, Map } from 'ol' interface Props { pinStyle: Function @@ -20,42 +22,70 @@ interface Props { const props = withDefaults(defineProps(), {}) +const { rplMapRef } = inject<{ rplMapRef: Ref }>('rplMapInstance', { + rplMapRef: ref(null) +}) + +const hoveredFeature = ref(null) + +const setClusterStyle = ( + style: Style, + { + clusterSize, + isHovering = false + }: { + clusterSize: number + isHovering?: boolean + } +) => { + const strokeWidthDefault = 16 + const strokeWidthHover = 20 + let circleRadius = 20 // 0.5 scale pixel size + + // Increase circle size for larger clusters + if (clusterSize > 99) { + circleRadius = 22 + } + if (clusterSize > 999) { + circleRadius = 24 + } + + const circleIcon = new CircleStyle({ + radius: circleRadius, + fill: new Fill({ + color: [102, 102, 102, 1] + }), + stroke: new Stroke({ + color: [102, 102, 102, 0.2], + // 2x pixel size + width: isHovering ? strokeWidthHover : strokeWidthDefault + }) + }) + + const textStyle = new Text() + textStyle.setText(clusterSize.toString()) + textStyle.setStroke(null) + textStyle.setFill( + new Fill({ + color: [255, 255, 255, 1] + }) + ) + textStyle.setFont(`700 1.6rem VIC, Arial, Helvetica, sans-serif`) + // text is rendered slightly off center in chrome and firefox, this is a middle ground so it's decent in all browsers + textStyle.setOffsetY(0.25) + + style.setImage(circleIcon) + style.setText(textStyle) +} + const overrideStyleFunction = (feature, style) => { const clusteredFeatures = feature.get('features') const size = clusteredFeatures.length - const createCircleStyle = ( - innerProperties: Omit - ) => { - return new CircleStyle({ - ...innerProperties, - fill: new Fill({ - color: [102, 102, 102, 1] - }), - stroke: new Stroke({ - color: [102, 102, 102, 0.2], - // 2x pixel size - width: 16 - }) - }) - } - if (clusteredFeatures && clusteredFeatures.length > 1) { - let circleRadius = 20 // 0.5 scale pixel size - // Increase circle size for larger clusters - if (size > 99) { - circleRadius = 22 - } - if (size > 999) { - circleRadius = 24 - } - - style.setImage(createCircleStyle({ radius: circleRadius })) - style.getText().setText(size.toString()) - style.getText().setStroke(undefined) - style.getText().setFont(`700 1.6rem VIC, Arial, Helvetica, sans-serif`) - // text is rendered slightly off center in chrome and firefox, this is a middle ground so it's decent in all browsers - style.getText().setOffsetY(0.25) + setClusterStyle(style, { + clusterSize: size + }) } else if (Array.isArray(clusteredFeatures) && clusteredFeatures[0]) { const icon = props.pinStyle( clusteredFeatures[0].getProperties(), @@ -79,4 +109,36 @@ const overrideStyleFunction = (feature, style) => { } return style } + +onMounted(async () => { + await nextTick() + + // Reset popup + if (rplMapRef.value) { + rplMapRef.value.on('pointermove', function (e) { + if (hoveredFeature.value !== null) { + hoveredFeature.value.setStyle(undefined) + hoveredFeature.value = null + } + + rplMapRef.value?.forEachFeatureAtPixel(e.pixel, function (f) { + const clusteredFeatures = f.get('features') + const currentFeature = f as Feature + + if (clusteredFeatures?.length > 1) { + hoveredFeature.value = currentFeature + const style = new Style() + + setClusterStyle(style, { + clusterSize: clusteredFeatures.length, + isHovering: true + }) + + currentFeature.setStyle(style) + return true + } + }) + }) + } +})