1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
|
<!doctype html>
<html lang="en">
<meta charset="utf-8">
<title>Wadsworth/Wheatstone</title>
<style>
body {
display: flex;
align-items: center;
max-width: 45em;
margin: 0 auto;
padding: 0.5em;
}
.controls { padding: 0.5em; }
svg {
display: block;
border: 1px solid black;
}
.pointer, .plate {
transform: none;
transform-origin: 50% 50%;
}
.pointer path, .pointer circle {
stroke: black;
stroke-width: 1px;
}
.pointer circle { fill: transparent; }
.plate text { font: 5px serif; }
.plate textPath {
dominant-baseline: central;
text-anchor: middle;
}
.plate path { fill: none; }
</style>
<script>
let dragging = null;
let svg = null;
function start(element) { dragging = element; }
function stop() { dragging = null; }
function update(element, diff) {
element.dataset.angle = Number(element.dataset.angle) + diff;
element.style.transform = `rotate(${element.dataset.angle}deg)`;
}
function reset() {
if (svg) {
svg.querySelectorAll(".pointer").forEach((x) => {
x.dataset.angle = 0;
update(x, 0);
});
}
}
function onmove(event) {
if (dragging) {
const rect = svg.getBoundingClientRect();
const x = event.clientX - (rect.left + rect.width / 2);
const y = event.clientY - (rect.top + rect.height / 2);
const theta = Math.atan2(y, x) - Math.atan2(y - event.movementY, x - event.movementX);
let diff = theta * 180.0 / Math.PI;
if (diff > 180) { diff -= 360.0; } // We jump back full circle to keep the diff values meaningful in context.
if (diff < -180) { diff += 360.0; }
update(dragging, diff);
svg.querySelectorAll(".pointer").forEach((x) => {
if (x != dragging) {
const ratio = Number(dragging.dataset.ratio) / Number(x.dataset.ratio);
update(x, diff * ratio);
}
});
}
}
function change(query, ratio) {
svg.querySelector(query).dataset.ratio = ratio;
}
function newSegment(radius, n, letter, colour) {
const ns = "http://www.w3.org/2000/svg";
let g = document.createElementNS(ns, "g");
let segment = document.createElementNS(ns, "path");
let path = document.createElementNS(ns, "path");
let text = document.createElementNS(ns, "text");
let textPath = document.createElementNS(ns, "textPath");
const thickness = 10;
const outerRadius = radius + thickness;
const left = width(radius);
const right = width(outerRadius);
segment.setAttribute("d", `M${left} ${-height(radius)}
A${radius} ${radius} 0 0 1 ${left} ${height(radius)}
L${right} ${height(outerRadius)}
A${outerRadius} ${outerRadius} 0 0 0 ${right} ${-height(outerRadius)}
Z`);
segment.style.fill = colour;
const centerRadius = radius + thickness/2;
const center = width(centerRadius);
path.setAttribute("d", `M${center} 0 A${centerRadius} ${centerRadius} 0 0 1 ${center} ${height(radius)}`);
path.id = crypto.randomUUID();
textPath.setAttribute("href", `#${path.id}`);
g.classList.add("plate");
g.appendChild(segment);
g.appendChild(path);
g.appendChild(text);
text.appendChild(textPath);
textPath.appendChild(document.createTextNode(letter));
return g;
function width(radius) { return Math.cos(Math.PI / n) * radius; }
function height(radius) { return Math.sin(Math.PI / n) * radius; }
}
function generatePlate(svg, radius, segments, alphabet) {
const colours = ["#edd", "#ded", "#dde"];
for (let i=0; i < segments; i++) {
let segment = newSegment(radius, segments, alphabet[i], colours[i%3]);
segment.style.transform = `rotate(${i * 360 / segments}deg) translate(50%, 50%)`;
svg.prepend(segment);
}
}
function init(element) {
svg = element;
generatePlate(svg, 20, 26, "VWXYZABCDEFGHIJKLMNOPQRSTU");
generatePlate(svg, 30, 27, " ABCDEFGHIJKLMNOPQRSTUVWXYZ");
}
</script>
<svg onload="init(this)" onmousemove="onmove(event)" onmouseleave="stop()" onmouseup="stop()" viewBox="0 0 100 100">
<g id="outer" data-angle="0" data-ratio="27" class="pointer">
<path d="M49 50 L80 50 Z"/>
<circle onmousedown="start(this.parentNode)" cx="85" cy="50" r="5"/>
</g>
<g id="inner" data-angle="0" data-ratio="26" class="pointer">
<path d="M49 50 L70 50 Z"/>
<circle onmousedown="start(this.parentNode)" cx="75" cy="50" r="5"/>
</g>
</svg>
<div class="controls">
<strong>Wadsworth/Wheatstone</strong><br>
Outer: <input onchange='change("#outer", this.value)' type="number" value="27" autocomplete="off"><br>
Inner: <input onchange='change("#inner", this.value)' type="number" value="26" autocomplete="off"><br>
<button onclick="reset()">Reset Position</button>
</div>
|