JSFiddle - React, Tailwind, and code Playground

HTML

<!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>

JavaScript

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 connection = svg.append('line').attr('stroke', 'green');

const projection = svg
	.append('circle')
	.attr('r', 5)
	.attr('stroke', 'red')
	.attr('fill', 'none');

const skier = svg
	.selectAll("#skier")
	.data([[0, 0]])
  .enter()
	.append('image')
	.attr('id', 'skier')
	.attr('href', skierIconSvg)
	.attr('width', 100)
	.attr('height', 100)
	.call(
		d3
			.drag()
			.subject(([x, y]) => ({ x, y }))
			.on('drag', dragged)
	);

const point = svg
	.append('g')
	.attr('cursor', 'move')
	.attr('pointer-events', 'all')
	.attr('stroke', 'transparent')
	.attr('stroke-width', 30)
	.selectAll('circle')
	.data([p1, p2, p3])
	.enter()
	.append('circle')
	.attr('r', 10)
	.attr('fill', (d, i) => (i === 2 ? 'red' : null))
	.call(
		d3
			.drag()
			.subject(([x, y]) => ({ x, y }))
			.on('drag', dragged)
	);

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));
	connection.attr('x1', p3[0]).attr('y1', p3[1]);
	connection.attr('x2', p[0]).attr('y2', p[1]);
	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]);
	point.attr('cx', (d) => d[0]).attr('cy', (d) => d[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 -...