From 29b404ee8f45c0442692574586c039a4c466216f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gunnar=20Ingi=20Stef=C3=A1nsson?= Date: Wed, 4 Oct 2023 13:23:16 +0200 Subject: [PATCH 1/2] adds back highlighting on hover --- src/State.ts | 3 +++ src/main.ts | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/src/State.ts b/src/State.ts index fa1ea1a..87c7b01 100644 --- a/src/State.ts +++ b/src/State.ts @@ -32,6 +32,9 @@ export class State extends Accessor { @property() selectedBuilding: Graphic | null = null; + @property() + hoveredBuilding: Graphic | null = null; + @property() filteredBuildings: number[] | null = null; diff --git a/src/main.ts b/src/main.ts index 21cabb6..d3ddce1 100644 --- a/src/main.ts +++ b/src/main.ts @@ -55,6 +55,7 @@ let buildings: Graphic[]; let heightGraph: HeightGraph; let timeline: Timeline; let selectHighlight: IHandle | null = null; +let hoverHighlight: IHandle | null = null; // create map const map = new Map({ @@ -237,6 +238,38 @@ async function selectFeature(feature: Graphic | null, layerView: SceneLayerView, }; queryChain(result); } + + hoverHighlight?.remove(); + hoverHighlight = null; +} + +view + .whenLayerView(sceneLayer) + .then((layerView) => { + watch( + () => state.hoveredBuilding, + (feature) => { + ignoreAbortErrors(hoverFeature(feature, layerView)); + } + ); + }) + .catch(console.error); + +async function hoverFeature(feature: Graphic | null, layerView: SceneLayerView): Promise { + // if the user has selected a building, don't apply a highlight on hover + if (selectHighlight) return; + + if (hoverHighlight) { + heightGraph.deselect(); + hoverHighlight.remove(); + hoverHighlight = null; + } + if (feature) { + // highlight in the height graph + heightGraph.select(feature); + // highlight feature on the map + hoverHighlight = layerView.highlight([feature.attributes.objectid]); + } } when( @@ -284,6 +317,24 @@ view.on("click", function (event) { }); }); +// when the user hovers over a building, set it as the hovered building in the state +view.on("pointer-move", function (event) { + view.hitTest(event).then(function (response) { + if (response.results.length === 0) { + state.hoveredBuilding = null; + } else { + const result = response.results[0]; + const graphic = result.type === "graphic" ? result.graphic : null; + if (graphic && graphic.layer.title === "Buildings Manhattan wiki") { + const feature = findFeature(graphic); + if (feature) { + state.hoveredBuilding = feature; + } + } + } + }); +}); + // clear the selected building when the popup is closed watch( () => view.popup?.visible, From f9c67a3842ffcdff546be8991f4b2c2fef6591c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gunnar=20Ingi=20Stef=C3=A1nsson?= Date: Fri, 6 Oct 2023 15:20:46 +0200 Subject: [PATCH 2/2] adds a tooltip with the building name and hight into the heightgraph on hover --- src/HeightGraph.ts | 51 ++++++++++++++++++++++++++++++++++++++-------- src/main.ts | 4 ++-- 2 files changed, 45 insertions(+), 10 deletions(-) diff --git a/src/HeightGraph.ts b/src/HeightGraph.ts index c7f64b8..c51e7ef 100644 --- a/src/HeightGraph.ts +++ b/src/HeightGraph.ts @@ -40,7 +40,7 @@ export default class HeightGraph { paddingBottom: number; circles: d3.Selection; - selectContainer: d3.Selection; + highlightContainer: d3.Selection; constructor(container: string, features: Graphic[], state: State) { // general settings for the svg area @@ -212,26 +212,61 @@ export default class HeightGraph { // add the circles and the selection this.circles = circles; - this.selectContainer = svg.append("g"); + this.highlightContainer = svg.append("g"); } // add a circle that will act like a highlight when a circle is clicked on - select(feature: Graphic) { + highlight(feature: Graphic) { const elem = d3.select("#id-" + feature.attributes.objectid); - this.selectContainer + const cx = parseInt(elem.attr("cx"), 10); + const cy = parseInt(elem.attr("cy"), 10); + + this.highlightContainer .append("circle") - .attr("class", "selectedGraphic") + .attr("class", "highlightedGraphic") .attr("r", 8) - .attr("cx", parseInt(elem.attr("cx"), 10)) - .attr("cy", parseInt(elem.attr("cy"), 10)) + .attr("cx", cx) + .attr("cy", cy) .attr("stroke-width", 4) .attr("stroke", settings.highlightOptions.color.toCss()) .attr("fill", "none"); + + const textBackground = this.highlightContainer + .append("rect") + .attr("class", "highlightedGraphic") + .attr("width", 100) + .attr("height", 100) + .attr("x", cx) + .attr("y", cy - 21) + .attr("fill", "000"); + + const text = this.highlightContainer + .append("text") + .attr("class", "tooltip") + .attr("class", "highlightedGraphic") + .classed("hover-graphic", true) + .attr("x", cx) + .attr("y", cy - 21) + .attr("text-anchor", "middle") + .text(() => { + const attributes = feature.attributes; + const name = attributes.name.trim() !== "" ? attributes.name : "Building"; + return `${name} built in ${attributes.cnstrct_yr}; height: ${parseInt(attributes.heightroof, 10)} feet`; + }); + + const textBox = text.node()!.getBBox(); + textBackground + .attr("x", textBox.x - 4) + .attr("y", textBox.y - 4) + .attr("width", textBox.width + 8) + .attr("height", textBox.height + 8) + .style("fill", "#ddd") + .style("fill-opacity", ".9"); } // remove circle that acts like a selection highlight deselect() { - this.selectContainer.selectAll(".selectedGraphic").remove(); + this.highlightContainer.selectAll(".highlightedGraphic").remove(); } // color the buildings according to the new selected period diff --git a/src/main.ts b/src/main.ts index d3ddce1..93a0325 100644 --- a/src/main.ts +++ b/src/main.ts @@ -207,7 +207,7 @@ async function selectFeature(feature: Graphic | null, layerView: SceneLayerView, infoWidget.setContent(feature.geometry as Point, feature.attributes, view); // highlight in the height graph - heightGraph.select(feature); + heightGraph.highlight(feature); // highlight feature on the map selectHighlight = layerView.highlight([feature.attributes.objectid]); // zoom to the building in the map @@ -266,7 +266,7 @@ async function hoverFeature(feature: Graphic | null, layerView: SceneLayerView): } if (feature) { // highlight in the height graph - heightGraph.select(feature); + heightGraph.highlight(feature); // highlight feature on the map hoverHighlight = layerView.highlight([feature.attributes.objectid]); }