From 9536a28dbad3d0dd562cf60e43bb6397e9306e5e Mon Sep 17 00:00:00 2001 From: Benoit Demaegdt Date: Thu, 28 Sep 2023 13:24:58 +0200 Subject: [PATCH 1/4] bigger line when hover --- composables/useMap.ts | 28 +++++++++++++++++++++------ content/voies-lyonnaises/ligne-1.json | 20 +++++++++++++++++-- 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/composables/useMap.ts b/composables/useMap.ts index fe77b4d9..68a80334 100644 --- a/composables/useMap.ts +++ b/composables/useMap.ts @@ -156,23 +156,39 @@ export const useMap = () => { if (sections.length === 0) { return; } - map.addSource('not-done-sections', { + map.addSource('planned-sections', { type: 'geojson', data: { type: 'FeatureCollection', features: sections } }); map.addLayer({ - id: 'not-done-sections', + id: 'planned-sections', type: 'line', - source: 'not-done-sections', + source: 'planned-sections', paint: { - 'line-width': 4, + 'line-width': ['case', ['boolean', ['feature-state', 'hover'], false], 6, 4], 'line-color': ['get', 'color'], 'line-dasharray': [2, 2] } }); + let hoveredLineId = null; + map.on('mousemove', 'planned-sections', e => { + map.getCanvas().style.cursor = 'pointer'; - map.on('mouseenter', 'not-done-sections', () => (map.getCanvas().style.cursor = 'pointer')); - map.on('mouseleave', 'not-done-sections', () => (map.getCanvas().style.cursor = '')); + if (e.features.length > 0) { + if (hoveredLineId !== null) { + map.setFeatureState({ source: 'planned-sections', id: hoveredLineId }, { hover: false }); + } + hoveredLineId = e.features[0].id; + map.setFeatureState({ source: 'planned-sections', id: hoveredLineId }, { hover: true }); + } + }); + map.on('mouseleave', 'planned-sections', () => { + map.getCanvas().style.cursor = ''; + if (hoveredLineId !== null) { + map.setFeatureState({ source: 'planned-sections', id: hoveredLineId }, { hover: false }); + } + hoveredLineId = null; + }); } function plotVarianteSections({ map, features }) { diff --git a/content/voies-lyonnaises/ligne-1.json b/content/voies-lyonnaises/ligne-1.json index b6ac82ce..073a9ba5 100644 --- a/content/voies-lyonnaises/ligne-1.json +++ b/content/voies-lyonnaises/ligne-1.json @@ -50,6 +50,7 @@ } }, { + "id": 1, "type": "Feature", "properties": { "id": "PeriCanal-commun", @@ -101,6 +102,7 @@ } }, { + "id": 2, "type": "Feature", "properties": { "id": "CanalSport-commun", @@ -135,6 +137,7 @@ } }, { + "id": 3, "type": "Feature", "properties": { "id": "SportStalingrad-commun", @@ -161,6 +164,7 @@ } }, { + "id": 4, "type": "Feature", "properties": { "id": "StalingradNord-commun", @@ -172,7 +176,7 @@ }, "geometry": { "type": "LineString", - "coordinates": [ + "coordinates": [ [4.861193194938238, 45.78457073986618], [4.861315438951237, 45.78448747445583], [4.861417782775277, 45.78431896074434], @@ -192,6 +196,7 @@ } }, { + "id": 5, "type": "Feature", "properties": { "id": "Bonnevay127-commun", @@ -203,13 +208,14 @@ }, "geometry": { "type": "LineString", - "coordinates": [ + "coordinates": [ [4.860028376055567, 45.784614639552274], [4.861193194938238, 45.78457073986618] ] } }, { + "id": 6, "type": "Feature", "properties": { "id": "Lignon-commun", @@ -238,6 +244,7 @@ } }, { + "id": 7, "type": "Feature", "properties": { "id": "TetedOr157-commun", @@ -256,6 +263,7 @@ } }, { + "id": 8, "type": "Feature", "properties": { "id": "GdeBretagne-commun", @@ -280,6 +288,7 @@ } }, { + "id": 9, "type": "Feature", "properties": { "line": 1, @@ -304,6 +313,7 @@ } }, { + "id": 10, "type": "Feature", "properties": { "line": 1, @@ -322,6 +332,7 @@ } }, { + "id": 11, "type": "Feature", "properties": { "line": 1, @@ -341,6 +352,7 @@ } }, { + "id": 12, "type": "Feature", "properties": { "id": "QuaiAugagneur-commun", @@ -359,6 +371,7 @@ } }, { + "id": 13, "type": "Feature", "properties": { "line": 1, @@ -383,6 +396,7 @@ } }, { + "id": 14, "type": "Feature", "properties": { "line": 1, @@ -412,6 +426,7 @@ } }, { + "id": 15, "type": "Feature", "properties": { "line": 1, @@ -454,6 +469,7 @@ } }, { + "id": 16, "type": "Feature", "properties": { "line": 1, From 46aca7bbebcf5300252df8280e1a7e1e75c61bb2 Mon Sep 17 00:00:00 2001 From: Benoit Demaegdt Date: Fri, 15 Dec 2023 22:42:02 +0100 Subject: [PATCH 2/4] fix lint --- pages/[_slug]/carte.vue | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/pages/[_slug]/carte.vue b/pages/[_slug]/carte.vue index 195f4e60..24a8d1ba 100644 --- a/pages/[_slug]/carte.vue +++ b/pages/[_slug]/carte.vue @@ -5,35 +5,35 @@ From 84a91b42fc74bf0962e5c07bac8d9da34810b2ef Mon Sep 17 00:00:00 2001 From: Benoit Demaegdt Date: Fri, 15 Dec 2023 23:05:03 +0100 Subject: [PATCH 3/4] bigger line on hover --- composables/useMap.ts | 46 +++++++++++++++++++++++---- content/voies-lyonnaises/ligne-1.json | 16 ---------- pages/[_slug]/carte.vue | 5 ++- 3 files changed, 44 insertions(+), 23 deletions(-) diff --git a/composables/useMap.ts b/composables/useMap.ts index 97e1ce41..80dd9e9a 100644 --- a/composables/useMap.ts +++ b/composables/useMap.ts @@ -112,13 +112,30 @@ export const useMap = () => { type: 'line', source: 'done-sections', paint: { - 'line-width': 4, + 'line-width': ['case', ['boolean', ['feature-state', 'hover'], false], 6, 4], 'line-color': ['get', 'color'] } }); - map.on('mouseenter', 'done-sections', () => (map.getCanvas().style.cursor = 'pointer')); - map.on('mouseleave', 'done-sections', () => (map.getCanvas().style.cursor = '')); + let hoveredLineId = null; + map.on('mousemove', 'done-sections', e => { + map.getCanvas().style.cursor = 'pointer'; + + if (e.features.length > 0) { + if (hoveredLineId !== null) { + map.setFeatureState({ source: 'done-sections', id: hoveredLineId }, { hover: false }); + } + hoveredLineId = e.features[0].id; + map.setFeatureState({ source: 'done-sections', id: hoveredLineId }, { hover: true }); + } + }); + map.on('mouseleave', 'done-sections', () => { + map.getCanvas().style.cursor = ''; + if (hoveredLineId !== null) { + map.setFeatureState({ source: 'done-sections', id: hoveredLineId }, { hover: false }); + } + hoveredLineId = null; + }); } function plotWipSections({ map, features }: { map: any; features: LineStringFeature[] }) { @@ -148,7 +165,7 @@ export const useMap = () => { type: 'line', source: 'wip-sections', paint: { - 'line-width': 4, + 'line-width': ['case', ['boolean', ['feature-state', 'hover'], false], 6, 4], 'line-color': ['get', 'color'], 'line-dasharray': [0, 2, 2] } @@ -180,8 +197,25 @@ export const useMap = () => { } animateDashArray(0); - map.on('mouseenter', 'wip-sections', () => (map.getCanvas().style.cursor = 'pointer')); - map.on('mouseleave', 'wip-sections', () => (map.getCanvas().style.cursor = '')); + let hoveredLineId = null; + map.on('mousemove', 'wip-sections', e => { + map.getCanvas().style.cursor = 'pointer'; + + if (e.features.length > 0) { + if (hoveredLineId !== null) { + map.setFeatureState({ source: 'wip-sections', id: hoveredLineId }, { hover: false }); + } + hoveredLineId = e.features[0].id; + map.setFeatureState({ source: 'wip-sections', id: hoveredLineId }, { hover: true }); + } + }); + map.on('mouseleave', 'wip-sections', () => { + map.getCanvas().style.cursor = ''; + if (hoveredLineId !== null) { + map.setFeatureState({ source: 'wip-sections', id: hoveredLineId }, { hover: false }); + } + hoveredLineId = null; + }); } function plotPlannedSections({ map, features }: { map: any; features: LineStringFeature[] }) { diff --git a/content/voies-lyonnaises/ligne-1.json b/content/voies-lyonnaises/ligne-1.json index 225ff07e..2ebddead 100644 --- a/content/voies-lyonnaises/ligne-1.json +++ b/content/voies-lyonnaises/ligne-1.json @@ -98,7 +98,6 @@ } }, { - "id": 1, "type": "Feature", "properties": { "id": "PeriCanal-commun", @@ -143,7 +142,6 @@ } }, { - "id": 2, "type": "Feature", "properties": { "id": "CanalSport-commun", @@ -171,7 +169,6 @@ } }, { - "id": 3, "type": "Feature", "properties": { "id": "SportStalingrad-commun", @@ -196,7 +193,6 @@ } }, { - "id": 4, "type": "Feature", "properties": { "id": "StalingradNord-commun", @@ -226,7 +222,6 @@ } }, { - "id": 5, "type": "Feature", "properties": { "id": "Bonnevay127-commun", @@ -243,7 +238,6 @@ } }, { - "id": 6, "type": "Feature", "properties": { "id": "Lignon-commun", @@ -270,7 +264,6 @@ } }, { - "id": 7, "type": "Feature", "properties": { "id": "TetedOr157-commun", @@ -287,7 +280,6 @@ } }, { - "id": 8, "type": "Feature", "properties": { "id": "GdeBretagne-commun", @@ -310,7 +302,6 @@ } }, { - "id": 9, "type": "Feature", "properties": { "line": 1, @@ -333,7 +324,6 @@ } }, { - "id": 10, "type": "Feature", "properties": { "line": 1, @@ -351,7 +341,6 @@ } }, { - "id": 11, "type": "Feature", "properties": { "line": 1, @@ -369,7 +358,6 @@ } }, { - "id": 12, "type": "Feature", "properties": { "id": "QuaiAugagneur-commun", @@ -386,7 +374,6 @@ } }, { - "id": 13, "type": "Feature", "properties": { "line": 1, @@ -409,7 +396,6 @@ } }, { - "id": 14, "type": "Feature", "properties": { "line": 1, @@ -437,7 +423,6 @@ } }, { - "id": 15, "type": "Feature", "properties": { "line": 1, @@ -478,7 +463,6 @@ } }, { - "id": 16, "type": "Feature", "properties": { "line": 1, diff --git a/pages/[_slug]/carte.vue b/pages/[_slug]/carte.vue index 24a8d1ba..9caa7ca6 100644 --- a/pages/[_slug]/carte.vue +++ b/pages/[_slug]/carte.vue @@ -31,7 +31,10 @@ const { data: voie } = await useAsyncData(() => { .findOne(); }); -const features = voie.value.features; +const features = voie.value.features.map((feature, index) => ({ + id: index, + ...feature +})); const description = `Carte de la Voie Lyonnaise ${line}. Découvrez les tronçons prévus, déjà réalisés, en travaux et ceux reportés après 2026.`; useHead({ From e41d89a965cc10bca754814d95680a51196709a0 Mon Sep 17 00:00:00 2001 From: Benoit Demaegdt Date: Fri, 15 Dec 2023 23:24:13 +0100 Subject: [PATCH 4/4] add hover effect on all maps --- components/content/Overview.vue | 6 ++- composables/useMap.ts | 85 +++++++++++-------------------- pages/carte-interactive/index.vue | 5 +- 3 files changed, 38 insertions(+), 58 deletions(-) diff --git a/components/content/Overview.vue b/components/content/Overview.vue index 33ba7b7a..4b5bec7c 100644 --- a/components/content/Overview.vue +++ b/components/content/Overview.vue @@ -64,7 +64,11 @@ const { data: geojson } = await useAsyncData(`geojson-${path}`, () => { .findOne(); }); -const features = geojson.value.features; +const features = geojson.value.features.map((feature, index) => ({ + id: index, + ...feature +})); + const doneFeatures = features.filter(feature => feature.properties.status === 'done'); const doneDistance = getDistance({ features: doneFeatures }); const avancement = Math.round(doneDistance / voie.distance * 100); diff --git a/composables/useMap.ts b/composables/useMap.ts index 80dd9e9a..4095d41f 100644 --- a/composables/useMap.ts +++ b/composables/useMap.ts @@ -117,25 +117,7 @@ export const useMap = () => { } }); - let hoveredLineId = null; - map.on('mousemove', 'done-sections', e => { - map.getCanvas().style.cursor = 'pointer'; - - if (e.features.length > 0) { - if (hoveredLineId !== null) { - map.setFeatureState({ source: 'done-sections', id: hoveredLineId }, { hover: false }); - } - hoveredLineId = e.features[0].id; - map.setFeatureState({ source: 'done-sections', id: hoveredLineId }, { hover: true }); - } - }); - map.on('mouseleave', 'done-sections', () => { - map.getCanvas().style.cursor = ''; - if (hoveredLineId !== null) { - map.setFeatureState({ source: 'done-sections', id: hoveredLineId }, { hover: false }); - } - hoveredLineId = null; - }); + applyHoverEffect({ map, layer: 'done-sections' }); } function plotWipSections({ map, features }: { map: any; features: LineStringFeature[] }) { @@ -197,25 +179,7 @@ export const useMap = () => { } animateDashArray(0); - let hoveredLineId = null; - map.on('mousemove', 'wip-sections', e => { - map.getCanvas().style.cursor = 'pointer'; - - if (e.features.length > 0) { - if (hoveredLineId !== null) { - map.setFeatureState({ source: 'wip-sections', id: hoveredLineId }, { hover: false }); - } - hoveredLineId = e.features[0].id; - map.setFeatureState({ source: 'wip-sections', id: hoveredLineId }, { hover: true }); - } - }); - map.on('mouseleave', 'wip-sections', () => { - map.getCanvas().style.cursor = ''; - if (hoveredLineId !== null) { - map.setFeatureState({ source: 'wip-sections', id: hoveredLineId }, { hover: false }); - } - hoveredLineId = null; - }); + applyHoverEffect({ map, layer: 'wip-sections' }); } function plotPlannedSections({ map, features }: { map: any; features: LineStringFeature[] }) { @@ -251,25 +215,8 @@ export const useMap = () => { 'line-dasharray': [2, 2] } }); - let hoveredLineId = null; - map.on('mousemove', 'planned-sections', e => { - map.getCanvas().style.cursor = 'pointer'; - if (e.features.length > 0) { - if (hoveredLineId !== null) { - map.setFeatureState({ source: 'planned-sections', id: hoveredLineId }, { hover: false }); - } - hoveredLineId = e.features[0].id; - map.setFeatureState({ source: 'planned-sections', id: hoveredLineId }, { hover: true }); - } - }); - map.on('mouseleave', 'planned-sections', () => { - map.getCanvas().style.cursor = ''; - if (hoveredLineId !== null) { - map.setFeatureState({ source: 'planned-sections', id: hoveredLineId }, { hover: false }); - } - hoveredLineId = null; - }); + applyHoverEffect({ map, layer: 'planned-sections' }); } function plotVarianteSections({ map, features }: { map: any; features: LineStringFeature[] }) { @@ -649,6 +596,32 @@ export const useMap = () => { } } + function applyHoverEffect({ map, layer }) { + let hoveredLineId = null; + map.on('mousemove', layer, e => { + map.getCanvas().style.cursor = 'pointer'; + + if (e.features.length > 0) { + if (hoveredLineId !== null) { + map.setFeatureState({ source: layer, id: hoveredLineId }, { hover: false }); + } + if (e.features[0].id !== undefined) { + hoveredLineId = e.features[0].id; + if (hoveredLineId !== null) { + map.setFeatureState({ source: layer, id: hoveredLineId }, { hover: true }); + } + } + } + }); + map.on('mouseleave', layer, () => { + map.getCanvas().style.cursor = ''; + if (hoveredLineId !== null) { + map.setFeatureState({ source: layer, id: hoveredLineId }, { hover: false }); + } + hoveredLineId = null; + }); + } + return { plotDoneSections, plotWipSections, diff --git a/pages/carte-interactive/index.vue b/pages/carte-interactive/index.vue index c53dc92a..3d3af2c6 100644 --- a/pages/carte-interactive/index.vue +++ b/pages/carte-interactive/index.vue @@ -15,7 +15,10 @@ const { data: voies } = await useAsyncData(() => { return queryContent('voies-lyonnaises').where({ _type: 'json' }).find(); }); -const features = voies.value.map(voie => voie.features).flat(); +const features = voies.value.map(voie => voie.features).flat().map((feature, index) => ({ + id: index, + ...feature +})); const description = 'Découvrez la carte interactive des Voies Lyonnaises. Itinéraires rue par rue. Plan régulièrement mis à jour pour une information complète.'; const COVER_IMAGE_URL = 'https://cyclopolis.lavilleavelo.org/cyclopolis.png';