Reputation: 660
I have managed to drag a skier along a line. The final big step is to create a tooltip that calculates the Y-location of the skier as I drag him up on the line. Let's say that the top on the line corresponds to 100 meter and the buttom of the line to be 0. Is this possible? If so, how can I accomplish this?
const height = 500;
const width = 960;
const skierIconSvg = 'https://image.flaticon.com/icons/svg/94/94150.svg';
const [p1, p2, p3] = [
[width / 3, 213],
[(2 * width) / 3, 300],
[width / 2, 132],
];
const svg = d3.select('svg');
const line = svg.append('line').attr('stroke', 'black');
const projection = svg
.append('circle')
.attr('r', 5)
.attr('stroke', 'red')
.attr('fill', 'none');
const g = svg
.append('g')
.attr('cursor', 'move')
.attr('pointer-events', 'all')
.attr('stroke', 'transparent')
.attr('stroke-width', 30);
const skier = g
.append('image')
.attr('id', 'skier')
.datum(p3)
.attr('href', skierIconSvg)
.attr('width', 100)
.attr('height', 100)
.attr('transform', 'translate(-50, 40)')
.call(
d3
.drag()
.subject(([x, y]) => ({
x,
y,
}))
.on('drag', dragged)
);
// create a tooltip
update();
function dragged(d) {
d[0] = d3.event.x;
d[1] = d3.event.y;
update();
}
function update() {
const t = (width + height) / distance(p1, p2);
const l1 = interpolate(p1, p2, t);
const l2 = interpolate(p2, p1, t);
const p = interpolate(p1, p2, project(p1, p2, p3));
projection.attr('cx', p[0]).attr('cy', p[1]);
line.attr('x1', l1[0]).attr('y1', l1[1]);
line.attr('x2', l2[0]).attr('y2', l2[1]);
skier.attr('x', (d) => d[0]).attr('y', (d) => d[1]);
}
function distance([x1, y1], [x2, y2]) {
return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
}
function interpolate([x1, y1], [x2, y2], t) {
return [x1 + (x2 - x1) * t, y1 + (y2 - y1) * t];
}
function project([x1, y1], [x2, y2], [x3, y3]) {
const x21 = x2 - x1,
y21 = y2 - y1;
const x31 = x3 - x1,
y31 = y3 - y1;
return (x31 * x21 + y31 * y21) / (x21 * x21 + y21 * y21);
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<script src="https://d3js.org/d3.v5.js"></script>
<script src="https://d3js.org/d3-path.v1.min.js"></script>
<script src="https://d3js.org/d3-shape.v1.min.js"></script>
<script src="https://d3js.org/d3-scale.v3.min.js"></script>
<script src="https://d3js.org/d3-axis.v1.min.js"></script>
<script src="https://d3js.org/d3-dispatch.v1.min.js"></script>
<script src="https://d3js.org/d3-selection.v1.min.js"></script>
<link
href="https://fonts.googleapis.com/css2?family=Inconsolata:wght@300&display=swap"
rel="stylesheet"
/>
<link
href="https://fonts.googleapis.com/css2?family=Amatic+SC:wght@700&display=swap"
rel="stylesheet"
/>
<style>
* {
font-family: 'Amatic SC', cursive;
text-align: center;
}
h1 {
font-size: 50px;
}
p {
font-size: 20px;
}
path {
fill: none;
stroke: #000;
stroke-width: 4px;
}
circle {
fill: steelblue;
stroke: #fff;
stroke-width: 3px;
}
</style>
</head>
<body>
<h1>Forsøk på å lage en tutorial i JavaScript og D3.js</h1>
<svg width="960" height="500"></svg>
<script src="main.js"></script>
</body>
</html>
Upvotes: 0
Views: 33
Reputation: 13129
Yes, this is very possible, since you know the properties of p
(the projection) and p2
(the bottom right point) right when you're about to update them, the perfect moment to display it.
You do seem to have removed the other points though - compared to your last question, so it's difficult to use them as a reference point. That means that technically, the skier can go beyond 100 metres high or even below 0 metres.
I used toFixed()
to format the number, but you could easily use d3-format
if you'd prefer of course.
const height = 500;
const width = 960;
const skierIconSvg = 'https://image.flaticon.com/icons/svg/94/94150.svg';
const [p1, p2, p3] = [
[width / 3, 213],
[(2 * width) / 3, 300],
[width / 2, 132],
];
const svg = d3.select('svg');
const line = svg.append('line').attr('stroke', 'black');
// Store a reference to the span we're going to update
const skierHeight = d3.select("#skier-height");
const projection = svg
.append('circle')
.attr('r', 5)
.attr('stroke', 'red')
.attr('fill', 'none');
const g = svg
.append('g')
.attr('cursor', 'move')
.attr('pointer-events', 'all')
.attr('stroke', 'transparent')
.attr('stroke-width', 30);
const skier = g
.append('image')
.attr('id', 'skier')
.datum(p3)
.attr('href', skierIconSvg)
.attr('width', 100)
.attr('height', 100)
.attr('transform', 'translate(-50, 40)')
.call(
d3
.drag()
.subject(([x, y]) => ({
x,
y,
}))
.on('drag', dragged)
);
// create a tooltip
update();
function dragged(d) {
d[0] = d3.event.x;
d[1] = d3.event.y;
update();
}
function update() {
const t = (width + height) / distance(p1, p2);
const l1 = interpolate(p1, p2, t);
const l2 = interpolate(p2, p1, t);
const p = interpolate(p1, p2, project(p1, p2, p3));
projection.attr('cx', p[0]).attr('cy', p[1]);
line.attr('x1', l1[0]).attr('y1', l1[1]);
line.attr('x2', l2[0]).attr('y2', l2[1]);
skier.attr('x', (d) => d[0]).attr('y', (d) => d[1]);
skierHeight.text(`${getHeight(p, p1, p2).toFixed(2)} metres`);
}
function distance([x1, y1], [x2, y2]) {
return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
}
function interpolate([x1, y1], [x2, y2], t) {
return [x1 + (x2 - x1) * t, y1 + (y2 - y1) * t];
}
function project([x1, y1], [x2, y2], [x3, y3]) {
const x21 = x2 - x1,
y21 = y2 - y1;
const x31 = x3 - x1,
y31 = y3 - y1;
return (x31 * x21 + y31 * y21) / (x21 * x21 + y21 * y21);
}
function getHeight([xp, yp], [x1, y1], [x2, y2]) {
// Note that y is counted from top to bottom, so higher y means
// a point is actually lower.
// First, the total height is 100 metres.
const pxPerMeter = (y2 - y1) / 100;
// Calculate the height diff in pixels
const heightDiffPx = (y2 - yp);
// Now transform it to meters
return heightDiffPx / pxPerMeter;
}
* {
font-family: 'Amatic SC', cursive;
text-align: center;
}
h1 {
font-size: 50px;
}
p {
font-size: 20px;
}
path {
fill: none;
stroke: #000;
stroke-width: 4px;
}
circle {
fill: steelblue;
stroke: #fff;
stroke-width: 3px;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<script src="https://d3js.org/d3.v5.js"></script>
<script src="https://d3js.org/d3-path.v1.min.js"></script>
<script src="https://d3js.org/d3-shape.v1.min.js"></script>
<script src="https://d3js.org/d3-scale.v3.min.js"></script>
<script src="https://d3js.org/d3-axis.v1.min.js"></script>
<script src="https://d3js.org/d3-dispatch.v1.min.js"></script>
<script src="https://d3js.org/d3-selection.v1.min.js"></script>
<link href="https://fonts.googleapis.com/css2?family=Inconsolata:wght@300&display=swap" rel="stylesheet" />
<link href="https://fonts.googleapis.com/css2?family=Amatic+SC:wght@700&display=swap" rel="stylesheet" />
</head>
<body>
<h1>Forsøk på å lage en tutorial i JavaScript og D3.js</h1>
<h2>Height: <span id="skier-height"></span></h2>
<svg width="960" height="500"></svg>
<script src="main.js"></script>
</body>
</html>
Upvotes: 1