Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adds back highlighting on hover #22

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 43 additions & 8 deletions src/HeightGraph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export default class HeightGraph {
paddingBottom: number;

circles: d3.Selection<SVGCircleElement, Graphic, SVGSVGElement, unknown>;
selectContainer: d3.Selection<SVGGElement, unknown, HTMLElement, unknown>;
highlightContainer: d3.Selection<SVGGElement, unknown, HTMLElement, unknown>;

constructor(container: string, features: Graphic[], state: State) {
// general settings for the svg area
Expand Down Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions src/State.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ export class State extends Accessor {
@property()
selectedBuilding: Graphic | null = null;

@property()
hoveredBuilding: Graphic | null = null;

@property()
filteredBuildings: number[] | null = null;

Expand Down
53 changes: 52 additions & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down Expand Up @@ -206,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
Expand Down Expand Up @@ -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<void> {
// 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.highlight(feature);
// highlight feature on the map
hoverHighlight = layerView.highlight([feature.attributes.objectid]);
}
}

when(
Expand Down Expand Up @@ -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,
Expand Down