Skip to content

Commit

Permalink
✨ (slope) improve hover interaction
Browse files Browse the repository at this point in the history
  • Loading branch information
sophiamersmann committed Mar 1, 2024
1 parent 7883eec commit 79f7bf7
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 15 deletions.
67 changes: 55 additions & 12 deletions packages/@ourworldindata/grapher/src/slopeCharts/SlopeChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
minBy,
maxBy,
exposeInstanceOnWindow,
PointVector,
} from "@ourworldindata/utils"
import { TextWrap } from "@ourworldindata/components"
import { observable, computed, action } from "mobx"
Expand Down Expand Up @@ -1044,26 +1045,68 @@ class LabelledSlopes

this.mouseFrame = requestAnimationFrame(() => {
if (this.props.bounds.contains(mouse)) {
const distToSlope = new Map<SlopeProps, number>()
if (this.slopeData.length === 0) return

const { x1: startX, x2: endX } = this.slopeData[0]

// whether the mouse is over the chart area,
// the left label area, or the right label area
const mousePosition =
mouse.x < startX
? "left"
: mouse.x > endX
? "right"
: "chart"

const distToSlopeOrLabel = new Map<SlopeProps, number>()
for (const s of this.slopeData) {
const dist =
Math.abs(
(s.y2 - s.y1) * mouse.x -
(s.x2 - s.x1) * mouse.y +
s.x2 * s.y1 -
s.y2 * s.x1
) /
Math.sqrt((s.y2 - s.y1) ** 2 + (s.x2 - s.x1) ** 2)
distToSlope.set(s, dist)
let line: {
x1: number
x2: number
y1: number
y2: number
}
if (mousePosition === "chart") {
line = s
} else if (mousePosition === "left") {
const labelBox = s.leftLabelBounds.toProps()
// "strike-through"" line that stretches from the left side
// of the left label to the start point of the slopes
line = {
x1: labelBox.x,
x2: startX,
y1: labelBox.y + labelBox.height / 2,
y2: labelBox.y + labelBox.height / 2,
}
} else {
const labelBox = s.rightLabelBounds.toProps()
// "strike-through"" line that stretches from the end point
// of the slopes to the right side of the right label
line = {
x1: endX,
x2: labelBox.x + labelBox.width,
y1: labelBox.y + labelBox.height / 2,
y2: labelBox.y + labelBox.height / 2,
}
}
// calculate the distance to the slope or label
const dist = PointVector.distanceFromPointToLineSegment(
mouse,
new PointVector(line.x1, line.y1),
new PointVector(line.x2, line.y2)
)
distToSlopeOrLabel.set(s, dist)
}

const closestSlope = minBy(this.slopeData, (s) =>
distToSlope.get(s)
distToSlopeOrLabel.get(s)
)
const distance = distToSlopeOrLabel.get(closestSlope!)!
const tolerance = mousePosition === "chart" ? 20 : 10

if (
closestSlope &&
(distToSlope.get(closestSlope) as number) < 20 &&
distance < tolerance &&
this.props.onMouseOver
) {
this.props.onMouseOver(closestSlope)
Expand Down
33 changes: 30 additions & 3 deletions packages/@ourworldindata/utils/src/PointVector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,10 @@ export class PointVector {
}

// From: http://stackoverflow.com/a/1501725/1983739
static distanceFromPointToLineSq(
static distanceFromPointToLineSegmentSq(
p: PointVector,
v: PointVector,
w: PointVector
v: PointVector, // start of line segment
w: PointVector // end of line segment
): number {
const l2 = PointVector.distanceSq(v, w)
if (l2 === 0) return PointVector.distanceSq(p, v)
Expand All @@ -94,4 +94,31 @@ export class PointVector {
new PointVector(v.x + t * (w.x - v.x), v.y + t * (w.y - v.y))
)
}

static distanceFromPointToLineSegment(
p: PointVector,
v: PointVector, // start of line segment
w: PointVector // end of line segment
): number {
return Math.sqrt(PointVector.distanceFromPointToLineSegmentSq(p, v, w))
}

// From: https://stackoverflow.com/questions/5254838/calculating-distance-between-a-point-and-a-rectangular-box-nearest-point
static distanceFromPointToRectSq(
p: PointVector,
r: PointVector, // top left
s: PointVector // bottom right
): number {
const dx = Math.max(r.x - p.x, 0, p.x - s.x)
const dy = Math.max(r.y - p.y, 0, p.y - s.y)
return dx * dx + dy * dy
}

static distanceFromPointToRect(
p: PointVector,
r: PointVector, // top left
s: PointVector // bottom right
): number {
return Math.sqrt(PointVector.distanceFromPointToRectSq(p, r, s))
}
}

0 comments on commit 79f7bf7

Please sign in to comment.