PageSourceSearch

https://olivierld.github.io/webcomponents/widgets/direction/DirectionDisplay.js

js olivierld.github.io collected 2026-10-03 09:42:53 UTC 34,579 bytes, 883 lines download raw bytes

1const directionVerbose = false;
2const DIRECTION_TAG_NAME = 'direction-display';
3/*
4* See custom properties in CSS.
5* =============================
6* @see https://developer.mozilla.org/en-US/docs/Web/CSS/
7* Relies on those elements:
8*
9.xxxxxxxx {
10	--bg-color: rgba(0, 0, 0, 0);
11	--digit-color: black;
12	--with-gradient: true;
13	--display-background-gradient-from: LightGrey;
14	--display-background-gradient-to: white;
15	--display-line-color: rgba(255, 255, 255, 0.5);
16	--label-fill-color: rgba(255, 255, 255, 0.5);
17	--with-display-shadow: false;
18	--shadow-color: rgba(0, 0, 0, 0.75);
19	--outline-color: DarkGrey;
20	--major-tick-color: black;
21	--minor-tick-color: black;
22	--value-color: grey;
23	--value-outline-color: black;
24	--value-nb-decimal: 1;
25	--hand-color: red;
26	--hand-outline-color: black;
27	--with-hand-shadow: true;
28	--knob-color: DarkGrey;
29	--knob-outline-color: black;
30	--font: Arial;
31}
32*/
33
34/**
35 * Recurse from the top down, on styleSheets and cssRules
36 *
37 * document.styleSheets[0].cssRules[2].selectorText returns ".analogdisplay"
38 * document.styleSheets[0].cssRules[2].cssText returns ".analogdisplay { --hand-color: red;  --face-color: white; }"
39 * document.styleSheets[0].cssRules[2].style.cssText returns "--hand-color: red; --face-color: white;"
40 *
41 * spine-case to camelCase
42 */
43const defaultDirectionColorConfig = {
44	bgColor: 'rgba(0, 0, 0, 0)', /* transparent, 'white', */
45	digitColor: 'black',
46	withGradient: true,
47	displayBackgroundGradient: {
48		from: 'LightGrey',
49		to: 'white'
50	},
51	displayLineColor: 'rgba(0, 0, 0, 0.5)',
52	labelFillColor: 'rgba(255, 255, 255, 0.5)',
53	withDisplayShadow: true,
54	shadowColor: 'rgba(0, 0, 0, 0.75)',
55	outlineColor: 'DarkGrey',
56	majorTickColor: 'black',
57	minorTickColor: 'black',
58	valueColor: 'grey',
59	valueOutlineColor: 'black',
60	valueNbDecimal: 1,
61	handColor: 'red', // 'rgba(0, 0, 100, 0.25)',
62	handOutlineColor: 'black',
63	withHandShadow: true,
64	knobColor: 'DarkGrey',
65	knobOutlineColor: 'black',
66	font: 'Arial' /* 'Source Code Pro' */
67};
68
69// import * as Utilities from "../utilities/Utilities.js";
70
71/* global HTMLElement */
72class DirectionDisplay extends HTMLElement {
73
74	static get observedAttributes() {
75		return [
76			"width",        // Integer. Canvas width
77			"height",       // Integer. Canvas height
78			"major-ticks",  // Float. value between major ticks (those with labels)
79			"minor-ticks",  // Float. value between minor ticks
80			"with-rose",    // Boolean, draw the rose or not
81			"with-border",  // Boolean
82			"label",        // String
83			"hand",         // String. 'regular' (default, needle) or 'wind', "boat", "cata", "tri", "plane", "arrow"
84			"value"         // Float. Heading to display
85		];
86	}
87
88	constructor() {
89		super();
90		this._shadowRoot = this.attachShadow({mode: 'open'}); // 'open' means it is accessible from external JavaScript.
91		// create and append a <canvas>
92		this.canvas = document.createElement("canvas");
93		let fallbackElemt = document.createElement("h1");
94		let content = document.createTextNode("This is a DirectionDisplay, on an HTML5 canvas");
95		fallbackElemt.appendChild(content);
96		this.canvas.appendChild(fallbackElemt);
97		this.shadowRoot.appendChild(this.canvas);
98
99		// Default values
100		this._value       =   0;
101		this._width       = 150;
102		this._height      = 150;
103		this._major_ticks =  45;
104		this._minor_ticks =   5;
105		this._with_rose   = true;
106		this._with_border = true;
107		this._hand        = 'regular';
108		this._label       = undefined;
109
110		this._previousClassName = "";
111		this.directionColorConfig = defaultDirectionColorConfig; // Init
112
113		if (directionVerbose) {
114			console.log("Data in Constructor:", this._value);
115		}
116	}
117
118	// Called whenever the custom element is inserted into the DOM.
119	connectedCallback() {
120		if (directionVerbose) {
121			console.log("connectedCallback invoked, 'value' is [", this.value, "]");
122		}
123	}
124
125	// Called whenever the custom element is removed from the DOM.
126	disconnectedCallback() {
127		if (directionVerbose) {
128			console.log("disconnectedCallback invoked");
129		}
130	}
131
132	// Called whenever an attribute is added, removed or updated.
133	// Only attributes listed in the observedAttributes property are affected.
134	attributeChangedCallback(attrName, oldVal, newVal) {
135		if (directionVerbose) {
136			console.log("attributeChangedCallback invoked on " + attrName + " from " + oldVal + " to " + newVal);
137		}
138		switch (attrName) {
139			case "value":
140				this._value = parseFloat(newVal);
141				break;
142			case "width":
143				this._width = parseInt(newVal);
144				break;
145			case "height":
146				this._height = parseInt(newVal);
147				break;
148			case "major-ticks":
149				this._major_ticks = parseFloat(newVal);
150				break;
151			case "minor-ticks":
152				this._minor_ticks = parseFloat(newVal);
153				break;
154			case "with-rose":
155				this._with_rose = ("true" === newVal);
156				break;
157			case "with-border":
158				this._with_border = ("true" === newVal);
159				break;
160			case "label":
161				this._label = newVal;
162				break;
163			case "hand":
164				this._hand = (newVal ? newVal : 'regular');
165				break;
166			default:
167				break;
168		}
169		this.repaint();
170	}
171
172	// Called whenever the custom element has been moved into a new document.
173	adoptedCallback() {
174		if (directionVerbose) {
175			console.log("adoptedCallback invoked");
176		}
177	}
178
179	set value(option) {
180		this.setAttribute("value", option);
181		if (directionVerbose) {
182			console.log(">> Value option:", option);
183		}
184//	this.repaint();
185	}
186	set width(val) {
187		this.setAttribute("width", val);
188	}
189	set height(val) {
190		this.setAttribute("height", val);
191	}
192	set majorTicks(val) {
193		this.setAttribute("major-ticks", val);
194	}
195	set minorTicks(val) {
196		this.setAttribute("minor-ticks", val);
197	}
198	set withRose(val) {
199		this.setAttribute("with-rose", val);
200	}
201	set withBorder(val) {
202		this.setAttribute("with-border", val);
203	}
204	set label(val) {
205		this.setAttribute("label", val);
206	}
207	set hand(val) {
208		this.setAttribute("hand", val);
209	}
210	set shadowRoot(val) {
211		this._shadowRoot = val;
212	}
213
214	get value() {
215		return this._value;
216	}
217	get width() {
218		return this._width;
219	}
220	get height() {
221		return this._height;
222	}
223	get minorTicks() {
224		return this._minor_ticks;
225	}
226	get majorTicks() {
227		return this._major_ticks;
228	}
229	get withRose() {
230		return this._with_rose;
231	}
232	get withBorder() {
233		return this._with_border;
234	}
235	get label() {
236		return this._label;
237	}
238	get hand() {
239		return this._hand;
240	}
241	get shadowRoot() {
242		return this._shadowRoot;
243	}
244
245	// Component methods
246	repaint() {
247		this.drawDisplay(this._value);
248	}
249
250	getColorConfig(classNames) {
251		let colorConfig = defaultDirectionColorConfig;
252		let classes = classNames.split(" ");
253		for (let cls=0; cls<classes.length; cls++) {
254			let className = classes[cls];
255			for (let s=0; s<document.styleSheets.length; s++) {
256				//		console.log("Walking though ", document.styleSheets[s]);
257				try {
258					for (let r = 0; document.styleSheets[s].cssRules !== null && r < document.styleSheets[s].cssRules.length; r++) {
259						let selector = document.styleSheets[s].cssRules[r].selectorText;
260						//			console.log(">>> ", selector);
261						if (selector !== undefined && (selector === '.' + className || (selector.indexOf('.' + className) > -1 && selector.indexOf(DIRECTION_TAG_NAME) > -1))) { // Cases like "tag-name .className"
262							//				console.log("  >>> Found it! [%s]", selector);
263							let cssText = document.styleSheets[s].cssRules[r].style.cssText;
264							let cssTextElems = cssText.split(";");
265							cssTextElems.forEach((elem) => {
266								if (elem.trim().length > 0) {
267									let keyValPair = elem.split(":");
268									let key = keyValPair[0].trim();
269									let value = keyValPair[1].trim();
270									switch (key) {
271										case '--bg-color':
272											colorConfig.bgColor = value;
273											break;
274										case '--digit-color':
275											colorConfig.digitColor = value;
276											break;
277										case '--with-gradient':
278											colorConfig.withGradient = (value === 'true');
279											break;
280										case '--display-background-gradient-from':
281											colorConfig.displayBackgroundGradient.from = value;
282											break;
283										case '--display-background-gradient-to':
284											colorConfig.displayBackgroundGradient.to = value;
285											break;
286										case '--display-line-color':
287											colorConfig.displayLineColor = value;
288											break;
289										case '--label-fill-color':
290											colorConfig.labelFillColor = value;
291											break;
292										case '--with-display-shadow':
293											colorConfig.withDisplayShadow = (value === 'true');
294											break;
295										case '--shadow-color':
296											colorConfig.shadowColor = value;
297											break;
298										case '--outline-color':
299											colorConfig.outlineColor = value;
300											break;
301										case '--major-tick-color':
302											colorConfig.majorTickColor = value;
303											break;
304										case '--minor-tick-color':
305											colorConfig.minorTickColor = value;
306											break;
307										case '--value-color':
308											colorConfig.valueColor = value;
309											break;
310										case '--value-outline-color':
311											colorConfig.valueOutlineColor = value;
312											break;
313										case '--value-nb-decimal':
314											colorConfig.valueNbDecimal = value;
315											break;
316										case '--hand-color':
317											colorConfig.handColor = value;
318											break;
319										case '--hand-outline-color':
320											colorConfig.handOutlineColor = value;
321											break;
322										case '--with-hand-shadow':
323											colorConfig.withHandShadow = (value === 'true');
324											break;
325										case '--knob-color':
326											colorConfig.knobColor = value;
327											break;
328										case '--knob-outline-color':
329											colorConfig.knobOutlineColor = value;
330											break;
331										case '--font':
332											colorConfig.font = value;
333											break;
334										default:
335											break;
336									}
337								}
338							});
339						}
340					}
341				} catch (err) {
342					// Absorb
343				}
344			}
345		}
346		return colorConfig;
347	}
348
349	drawDisplay(directionValue) {
350
351		let currentStyle = this.className;
352		if (this._previousClassName !== currentStyle || true) {
353			// Reload
354			//	console.log("Reloading CSS");
355			try {
356				this.directionColorConfig = this.getColorConfig(currentStyle);
357			} catch (err) {
358				// Absorb?
359				console.log(err);
360			}
361			this._previousClassName = currentStyle;
362		}
363
364		let digitColor = this.directionColorConfig.digitColor;
365
366		let context = this.canvas.getContext('2d');
367		context.clearRect(0, 0, this.width, this.height);
368
369		let radius = (Math.min(this.width, this.height) / 2) * 0.90;
370
371		// Set the canvas size from its container.
372		this.canvas.width = this.width;
373		this.canvas.height = this.height;
374
375		// Cleanup
376		context.fillStyle = this.directionColorConfig.bgColor;
377		context.fillRect(0, 0, this.canvas.width, this.canvas.height);
378
379		context.beginPath();
380		if (this.withBorder === true) {
381			//context.arc(x, y, radius, startAngle, startAngle + Math.PI, antiClockwise);
382			context.arc(this.canvas.width / 2, radius + 10, radius, 0, 2 * Math.PI, false);
383			context.lineWidth = 5;
384		}
385		if (this.directionColorConfig.withGradient) {
386			let grd = context.createLinearGradient(0, 5, 0, radius);
387			grd.addColorStop(0, this.directionColorConfig.displayBackgroun
387dGradient.from);// 0  Beginning
388			grd.addColorStop(1, this.directionColorConfig.displayBackgroundGradient.to);  // 1  End
389			context.fillStyle = grd;
390		} else {
391			context.fillStyle = this.directionColorConfig.displayBackgroundGradient.to;
392		}
393
394		if (this.directionColorConfig.withDisplayShadow) {
395			context.shadowOffsetX = 3;
396			context.shadowOffsetY = 3;
397			context.shadowBlur = 3;
398			context.shadowColor = this.directionColorConfig.shadowColor;
399		} else {
400			context.shadowOffsetX = 0;
401			context.shadowOffsetY = 0;
402			context.shadowBlur = 0;
403			context.shadowColor = undefined;
404		}
405		context.lineJoin = "round";
406		context.fill();
407		context.strokeStyle = this.directionColorConfig.outlineColor;
408		context.stroke();
409		context.closePath();
410
411		// Major Ticks
412		context.beginPath();
413		for (let i = 0; i < 360; i += this.majorTicks) {
414			let xFrom = (this.canvas.width / 2) - ((radius * 0.95) * Math.cos(2 * Math.PI * (i / 360)));
415			let yFrom = (radius + 10) - ((radius * 0.95) * Math.sin(2 * Math.PI * (i / 360)));
416			let xTo = (this.canvas.width / 2) - ((radius * 0.85) * Math.cos(2 * Math.PI * (i / 360)));
417			let yTo = (radius + 10) - ((radius * 0.85) * Math.sin(2 * Math.PI * (i / 360)));
418			context.moveTo(xFrom, yFrom);
419			context.lineTo(xTo, yTo);
420		}
421		context.lineWidth = 3;
422		context.strokeStyle = this.directionColorConfig.majorTickColor;
423		context.stroke();
424		context.closePath();
425
426		// Minor Ticks
427		if (this.minorTicks > 0) {
428			context.beginPath();
429			for (let i = 0; i <= 360; i += this.minorTicks) {
430				let xFrom = (this.canvas.width / 2) - ((radius * 0.95) * Math.cos(2 * Math.PI * (i / 360)));
431				let yFrom = (radius + 10) - ((radius * 0.95) * Math.sin(2 * Math.PI * (i / 360)));
432				let xTo = (this.canvas.width / 2) - ((radius * 0.90) * Math.cos(2 * Math.PI * (i / 360)));
433				let yTo = (radius + 10) - ((radius * 0.90) * Math.sin(2 * Math.PI * (i / 360)));
434				context.moveTo(xFrom, yFrom);
435				context.lineTo(xTo, yTo);
436			}
437			context.lineWidth = 1;
438			context.strokeStyle = this.directionColorConfig.minorTickColor;
439			context.stroke();
440			context.closePath();
441		}
442
443		// with rose?
444		if (this.withRose === true) {
445			context.beginPath();
446
447			context.lineWidth = 1;
448			let outsideRadius = radius * 0.6;
449			let insideRadius = radius * 0.1;
450
451//    context.arc(canvas.width / 2, radius + 10, outsideRadius, 0, 2 * Math.PI, false);
452//    context.arc(canvas.width / 2, radius + 10, insideRadius,  0, 2 * Math.PI, false);
453
454			// NS/EW axis, the origin is -90 (W)
455			let N = (0 + 90) % 360;
456			let S = (180 + 90) % 360;
457			let E = (90 + 90) % 360;
458			let W = (270 + 90) % 360;
459
460			let NE = (45 + 90) % 360;
461			let SE = (135 + 90) % 360;
462			let NW = (315 + 90) % 360;
463			let SW = (225 + 90) % 360;
464
465			// N-S
466			let xFrom = (this.canvas.width / 2) - (outsideRadius * Math.cos(2 * Math.PI * (N / 360)));
467			let yFrom = (radius + 10) - (outsideRadius * Math.sin(2 * Math.PI * (N / 360)));
468			let xTo = (this.canvas.width / 2) - (outsideRadius * Math.cos(2 * Math.PI * (S / 360)));
469			let yTo = (radius + 10) - (outsideRadius * Math.sin(2 * Math.PI * (S / 360)));
470			context.moveTo(xFrom, yFrom);
471			context.lineTo(xTo, yTo);
472			// E-W
473			xFrom = (this.canvas.width / 2) - (outsideRadius * Math.cos(2 * Math.PI * (E / 360)));
474			yFrom = (radius + 10) - (outsideRadius * Math.sin(2 * Math.PI * (E / 360)));
475			xTo = (this.canvas.width / 2) - (outsideRadius * Math.cos(2 * Math.PI * (W / 360)));
476			yTo = (radius + 10) - (outsideRadius * Math.sin(2 * Math.PI * (W / 360)));
477			context.moveTo(xFrom, yFrom);
478			context.lineTo(xTo, yTo);
479			// NE-SW
480			xFrom = (this.canvas.width / 2) - (outsideRadius * 0.9 * Math.cos(2 * Math.PI * (NE / 360)));
481			yFrom = (radius + 10) - (outsideRadius * 0.9 * Math.sin(2 * Math.PI * (NE / 360)));
482			xTo = (this.canvas.width / 2) - (outsideRadius * 0.9 * Math.cos(2 * Math.PI * (SW / 360)));
483			yTo = (radius + 10) - (outsideRadius * 0.9 * Math.sin(2 * Math.PI * (SW / 360)));
484			context.moveTo(xFrom, yFrom);
485			context.lineTo(xTo, yTo);
486			// NW-SE
487			xFrom = (this.canvas.width / 2) - (outsideRadius * 0.9 * Math.cos(2 * Math.PI * (NW / 360)));
488			yFrom = (radius + 10) - (outsideRadius * 0.9 * Math.sin(2 * Math.PI * (NW / 360)));
489			xTo = (this.canvas.width / 2) - (outsideRadius * 0.9 * Math.cos(2 * Math.PI * (SE / 360)));
490			yTo = (radius + 10) - (outsideRadius * 0.9 * Math.sin(2 * Math.PI * (SE / 360)));
491			context.moveTo(xFrom, yFrom);
492			context.lineTo(xTo, yTo);
493
494			this.drawSpike(radius, outsideRadius, insideRadius, N, context);
495			this.drawSpike(radius, outsideRadius, insideRadius, S, context);
496			this.drawSpike(radius, outsideRadius, insideRadius, E, context);
497			this.drawSpike(radius, outsideRadius, insideRadius, W, context);
498
499			this.drawSpike(radius, outsideRadius * 0.9, insideRadius, NE, context);
500			this.drawSpike(radius, outsideRadius * 0.9, insideRadius, SE, context);
501			this.drawSpike(radius, outsideRadius * 0.9, insideRadius, SW, context);
502			this.drawSpike(radius, outsideRadius * 0.9, insideRadius, NW, context);
503
504			context.strokeStyle = this.directionColorConfig.displayLineColor;
505			context.stroke();
506			context.closePath();
507		}
508
509		// Numbers
510		context.beginPath();
511		let scale = 1;
512		for (let i = 0; i < 360; i += this.majorTicks) {
513			context.save();
514			context.translate(this.canvas.width / 2, (radius + 10)); // canvas.height);
515			context.rotate((2 * Math.PI * (i / 360)));
516			context.font = "bold " + Math.round(scale * 15) + "px Arial"; // Like "bold 15px Arial"
517			context.fillStyle = digitColor;
518			let str = i.toString();
519			let len = context.measureText(str).width;
520			context.fillText(str, -len / 2, (-(radius * .8) + 10));
521			context.lineWidth = 1;
522			context.strokeStyle = this.directionColorConfig.valueOutlineColor;
523			context.strokeText(str, -len / 2, (-(radius * .8) + 10)); // Outlined
524			context.restore();
525		}
526		context.closePath();
527		// Value
528		let text;
529		let dv = parseFloat(directionValue);
530		while (dv > 360) dv -= 360;
531		while (dv < 0) dv += 360;
532		try {
533			text = dv.toFixed(this.directionColorConfig.valueNbDecimal);
534		} catch (err) {
535			console.log(err);
536		}
537		let len = 0;
538		context.font = "bold " + Math.round(scale * 40) + "px " + this.directionColorConfig.font; // "bold 40px Arial"
539		let metrics = context.measureText(text);
540		len = metrics.width;
541
542		context.beginPath();
543		context.fillStyle = this.directionColorConfig.valueColor;
544		context.fillText(text, (this.canvas.width / 2) - (len / 2), ((radius * .75) + 10));
545		context.lineWidth = 1;
546		context.strokeStyle = this.directionColorConfig.valueOutlineColor;
547		context.strokeText(text, (this.canvas.width / 2) - (len / 2), ((radius * .75) + 10)); // Outlined
548		context.closePath();
549
550		// Label ?
551		if (this.label !== undefined) {
552			let fontSize = 20;
553			let text = this.label;
554			let len = 0;
555			context.font = "bold " + Math.round(scale * fontSize) + "px " + this.directionColorConfig.font; // "bold 40px Arial"
556			let metrics = context.measureText(text);
557			len = metrics.width;
558
559			context.beginPath();
560			context.fillStyle = this.directionColorConfig.labelFillColor;
561			context.fillText(text, (this.canvas.width / 2) - (len / 2), (2 * radius - (fontSize * scale * 2.1)));
562			context.lineWidth = 1;
563			context.strokeStyle = this.directionColorConfig.valueOutlineColor;
564			context.strokeText(text, (this.canvas.width / 2) - (len / 2), (2 * radius - (fontSize * scale * 2.1))); // Outlined
565			context.closePath();
566		}
567
568		// Hand
569		context.beginPath();
570		if (this.directionColorConfig.withHandShadow) {
571			context.shadowColor = this.directionColorConfig.shadowColor;
572			context.shadowOffsetX = 3;
573			context.shadowOffsetY = 3;
574			context.shadowBlur = 3;
575		}
576		// Center
577		let centerX = this.canvas.width / 2;
578		let centerY = radius + 10;
579		context.moveTo(centerX, centerY);
580
581		// console.log(`This.hand=${this.hand}`);
582		let x;
583		let y;
584		if (this.hand === 'regular' || this.hand === 'wind') {
585			// Left
586			x = centerX - ((radius * 0.05) * Math.cos(Math.toRadians(dv))); //  - (Math.PI / 2))));
587			y = centerY - ((radius * 0.05) * Math.sin(Math.toRadians(dv))); // - (Math.PI / 2))));
588			context.lineTo(x, y);
589		}
590		if (this.hand === 'regular') { // Regular needle
591			// Tip
592			x = centerX - ((radius * 0.90) * Math.cos(Math.toRadians(dv) + (Math.PI / 2)));
593			y = centerY - ((radius * 0.90) * Math.sin(Math.toRadians(dv) + (Math.PI / 2)));
594			context.lineTo(x, y);
595		} else if (this.hand === 'wind') { // Then draw wind arrow
596			/*
597			    +-+
598			    | |
599			    | |
600			  +-+ +-+
601			   \   /
602			    + +
603			    | |
604			    | |
605			    +-+
606			 */
607
608			let arrowPoints = [
609				{ x: - radius * 0.04, y: - radius * 0.30 }, // Left pointy side of the arrow head
610				{ x: - radius * 0.20, y: - radius * 0.60 }, // Left back fat side of the arrow head
611				{ x: - radius * 0.04, y: - radius * 0.60 }, // Left back narrow side of the arrow head
612				{ x: - radius * 0.04, y: - radius * 0.90 }, // Left tip
613				{ x: + radius * 0.04, y: - radius * 0.90 }, // Right tip
614				{ x: + radius * 0.04, y: - radius * 0.60 }, // Right back narrow side of the arrow head
615				{ x: + radius * 0.20, y: - radius * 0.60 }, // Right back fat side of the arrow head
616				{ x: + radius * 0.04, y: - radius * 0.30 }  // Right pointy side of the arrow head
617			];
618			let radAngle = Math.toRadians(dv); // + (Math.PI / 2);
619			// Apply rotation to the points of the needle
620			arrowPoints.forEach(pt => {
621				x = centerX + ((pt.x * Math.cos(radAngle)) - (pt.y * Math.sin(radAngle)));
622				y = centerY + ((pt.x * Math.sin(radAngle)) + (pt.y * Math.cos(radAngle)));
623				context.lineTo(x, y);
624			});
625		} else if (this.hand === 'boat') {
626			// console.log("Drawing boat needle...")
627			const BOAT_FACT = 0.8;
628			if (false) {
629				let boatPoints = [
630					{ x: 0, y: - radius * BOAT_FACT * 0.6 },               // Bow
631					{ x: radius * BOAT_FACT * 0.22, y: - radius * BOAT_FACT * 0.3 },   // Starboard, point one
632					{ x: radius * BOAT_FACT * 0.30, y: - radius * BOAT_FACT * 0.0 },   // Starboard, point two
633					{ x: radius * BOAT_FACT * 0.28, y: + radius * BOAT_FACT * 0.3 },   // Starboard, point three
634					{ x: radius * BOAT_FACT * 0.20, y: + radius * BOAT_FACT * 0.6 },   // Starboard, point four
635					{ x: BOAT_FACT * 0, y: + radius * BOAT_FACT * 0.6 },               // Mid-transom
636					{ x: - radius * BOAT_FACT * 0.20, y: + radius * BOAT_FACT * 0.6 }, // Port, point four
637					{ x: - radius * BOAT_FACT * 0.28, y: + radius * BOAT_FACT * 0.3 }, // Port, point three
638					{ x: - radius * BOAT_FACT * 0.30, y: - radius * BOAT_FACT * 0.0 }, // Port, point two
639					{ x: - radius * BOAT_FACT * 0.22, y: - radius * BOAT_FACT * 0.3 }, // Port, point one
640					{ x: BOAT_FACT * 0, y: - radius * BOAT_FACT * 0.6 }                // Bow
641				];
642				let radAngle = Math.toRadians(dv); // + (Math.PI / 2);
643				// Apply rotation to the points of the needle
644				boatPoints.forEach(pt => {
645					x = centerX + ((pt.x * Math.cos(radAngle)) - (pt.y * Math.sin(radAngle)));
646					y = centerY + ((pt.x * Math.sin(radAngle)) + (pt.y * Math.cos(radAngle)));
647					context.lineTo(x, y);
648				});
649			} else {
650				const BEAM_ANGLE    =  90;
651				const TRANSOM_ANGLE = 165;
652				const SCALE = 0.8;
653				let boatRadius = radius * BOAT_FACT * SCALE;
654				let beam       = radius * 0.70 * BOAT_FACT * SCALE;  // 90 degrees
655				let transom    = radius * BOAT_FACT * 1.05 * SCALE;  // length from center to transom, 165 degrees
656
657				let bow = { x: centerX + (boatRadius * Math.sin(Math.toRadians(dv))),
658							y: centerY - (boatRadius * Math.cos(Math.toRadians(dv))) };
659
660				let beam_right = { x: centerX + (beam * Math.sin(Math.toRadians(dv + BEAM_ANGLE))),
661								   y: centerY - (beam * Math.cos(Math.toRadians(dv + BEAM_ANGLE))) };
662
663				let transom_right = { x: centerX + (transom * Math.sin(Math.toRadians(dv + TRANSOM_ANGLE))),
664									  y: centerY - (transom * Math.cos(Math.toRadians(dv + TRANSOM_ANGLE))) };
665
666				let beam_left = { x: centerX + (beam * Math.sin(Math.toRadians(dv - BEAM_ANGLE))),
667								  y: centerY - (beam * Math.cos(Math.toRadians(dv - BEAM_ANGLE))) };
668
669				let transom_left = { x: centerX + (transom * Math.sin(Math.toRadians(dv - TRANSOM_ANGLE))),
670									 y: centerY - (transom * Math.cos(Math.toRadians(dv - TRANSOM_ANGLE))) };
671
672				// Starboard
673				context.lineTo(bow.x, bow.y);    // Center to bow.
674				// context.moveTo(bow.x, bow.y); // Bow
675				context.quadraticCurveTo(beam_right.x,
676										 beam_right.y,
677										 transom_right.x,
678										 transom_right.y);
679                // Port
680				context.moveTo(bow.x, bow.y);
681				context.quadraticCurveTo(beam_left.x, beam_left.y,
682									     transom_left.x, transom_left.y);
683
684				// Transom
685				context.moveTo(transom_left.x, transom_left.y);
686				context.lineTo(transom_right.x, transom_right.y);
687			}
688		} else if (this.hand === 'cata') {
689			// console.log("Drawing cata needle...")
690			const BOAT_FACT = 0.8;
691			let cataPoints = [
692				{ x: 0, y: - radius * BOAT_FACT * 0.142 }, // Arm, front, center
693				/* STARBOARD HULL */
694				// Starboard
695				{ x: radius * BOAT_FACT * 0.142,  y: - radius * BOAT_FACT * 0.142 },  // Arm starboard, hull side
696				{ x: radius * BOAT_FACT * 0.2143, y: - radius * BOAT_FACT * 0.5714 }, // Starboard bow
697				{ x: radius * BOAT_FACT * 0.284,  y: - radius * BOAT_FACT * 0.142 },
698				{ x: radius * BOAT_FACT * 0.284,  y: radius * BOAT_FACT * 0 },
699				{ x: radius * BOAT_FACT * 0.284,  y: radius * BOAT_FACT * 0.142 },
700				{ x: radius * BOAT_FACT * 0.2571, y: radius * BOAT_FACT * 0.4286 },   // Starboard transom, ext
701				{ x: radius * BOAT_FACT * 0.1714, y: radius * BOAT_FACT * 0.4286 },   // Starboard transom, int
702				{ x: radius * BOAT_FACT * 0.142,  y: radius * BOAT_FACT * 0.142 },    // Arm, back, starboard, hull side
703				{ x: radius * BOAT_FACT * 0,      y: radius * BOAT_FACT * 0.142 },    // Arm, back, starboard, center
704				// Port
705				{ x: radius * BOAT_FACT * 0,      y: radius * BOAT_FACT * 0.142 },
706				{ x: radius * BOAT_FACT * 0.142,  y: radius * BOAT_FACT * 0.142 },
707				{ x: radius * BOAT_FACT * 0.1714, y: radius * BOAT_FACT * 0.4286 },
708				{ x: radius * BOAT_FACT * 0.2571, y: radius * BOAT_FACT * 0.4286 },
709				{ x: radius * BOAT_FACT * 0.284,  y: radius * BOAT_FACT * 0.142 },
710				{ x: radius * BOAT_FACT * 0.284,  y: radius * BOAT_FACT * 0 },
711				{ x: radius * BOAT_FACT * 0.284,  y: - radius * BOAT_FACT * 0.142 },
712				{ x: radius * BOAT_FACT * 0.2143, y: - radius * BOAT_FACT * 0.5714 },
713				{ x: radius * BOAT_FACT * 0.142,  y: - radius * BOAT_FACT * 0.142 },
714				/* PORT HULL */
715				// Starboard
716				{ x: - radius * BOAT_FACT * 0.142,  y: - radius * BOAT_FACT * 0.142 },  // Arm starboard, hull side
717				{ x: - radius * BOAT_FACT * 0.2143, y: - radius * BOAT_FACT * 0.5714 }, // Starboard bow
718				{ x: - radius * BOAT_FACT * 0.284,  y: - radius * BOAT_FACT * 0.142 },
719				{ x: - radius * BOAT_FACT * 0.284,  y: radius * BOAT_FACT * 0 },
720				{ x: - radius * BOAT_FACT * 0.284,  y: radius * BOAT_FACT * 0.142 },
721				{ x: - radius * BOAT_FACT * 0.2571, y: radius * BOAT_FACT * 0.4286 },   // Starboard transom, ext
722				{ x: - radius * BOAT_FACT * 0.1714, y: radius * BOAT_FACT * 0.4286 },   // Starboard transom, int
723				{ x: - radius * BOAT_FACT * 0.142,  y: radius * BOAT_FACT * 0.142 },    // Arm, back, starboard, hull side
724				{ x: - radius * BOAT_FACT * 0,      y: radius * BOAT_FACT * 0.142 },    // Arm, back, starboard, center
725				// Port
726				{ x: - radius * BOAT_FACT * 0,      y: radius * BOAT_FACT * 0.142 },
727				{ x: - radius * BOAT_FACT * 0.142,  y: radius * BOAT_FACT * 0.142 },
728				{ x: - radius * BOAT_FACT * 0.1714, y: radius * BOAT_FACT * 0.4286 },
729				{ x: - radius * BOAT_FACT * 0.2571, y: radius * BOAT_FACT * 0.4286 },
730				{ x: - radius * BOAT_FACT * 0.284,  y: radius * BOAT_FACT * 0.142 },
731				{ x: - radius * BOAT_FACT * 0.284,  y: radius * BOAT_FACT * 0 },
732				{ x: - radius * BOAT_FACT * 0.284,  y: - radius * BOAT_FACT * 0.142 },
733				{ x: - radius * BOAT_FACT * 0.2143, y: - radius * BOAT_FACT * 0.5714 },
734				{ x: - radius * BOAT_FACT * 0.142,  y: - radius * BOAT_FACT * 0.142 },
735
736				{ x: 0, y: - radius * BOAT_FACT * 0.142 }
737			];
738			let radAngle = Math.toRadians(dv); // + (Math.PI / 2);
739			// Apply rotation to the points of the needle
740			cataPoints.forEach(pt => {
741				x = centerX + ((pt.x * Math.cos(radAngle)) - (pt.y * Math.sin(radAngle)));
742				y = centerY + ((pt.x * Math.sin(radAngle)) + (pt.y * Math.cos(radAngle)));
743				context.lineTo(x, y);
744			});
745		} else if (this.hand === 'tri') {
746			// console.log("Drawing tri needle...")
747			const BOAT_FACT = 0.8;
748			let triPoints = [
749				{ x: 0, y: - radius * BOAT_FACT * 0.5714}, // Bow, center hull
750				// Starboard
751				{ x: radius * BOAT_FACT * 0.04286,   y: - radius * BOAT_FACT * 0.4286 },
752				{ x: radius * BOAT_FACT * 0.08571,   y: - radius * BOAT_FACT * 0.1429}, // Arm, front, starboard, inside
753				{ x: radius * BOAT_FACT * 0.2286,    y: - radius * BOAT_FACT * 0.1429}, // Arm, front, starboard, outside
754				{ x: radius * BOAT_FACT * 0.2571,    y: - radius * BOAT_FACT * 0.3714 }, // Outrigger bow
755				{ x: radius * BOAT_FACT * 0.2857,    y: - radius * BOAT_FACT * 0.2143 },
756				{ x: radius * BOAT_FACT * 0.2857,    y: radius * BOAT_FACT * 0.2143 },
757				{ x: radius * BOAT_FACT * 0.2714,    y: radius * BOAT_FACT * 0.3571 }, // Outrigger transom, ext
758				{ x: radius * BOAT_FACT * 0.2429,    y: radius * BOAT_FACT * 0.3571 }, // Outrigger transom, int
759				{ x: radius * BOAT_FACT * 0.2286,    y: radius * BOAT_FACT * 0.1429}, // Arm, back, starboard, outside
760				{ x: radius * BOAT_FACT * 0.08571,   y: radius * BOAT_FACT * 0.1429}, // Arm, back, starboard, inside
761				{ x: radius * BOAT_FACT * 0.04286,   y: radius * BOAT_FACT * 0.4286 }, // Main hull, transom starboard,
762				// Port
763				{ x: - radius * BOAT_FACT * 0.04286, y: radius * BOAT_FACT * 0.4286 },
764				{ x: - radius * BOAT_FACT * 0.08571, y: radius * BOAT_FACT * 0.1429},
765				{ x: - radius * BOAT_FACT * 0.2286,  y: radius * BOAT_FACT * 0.1429},
766				{ x: - radius * BOAT_FACT * 0.2429,  y: radius * BOAT_FACT * 0.3571 },
767				{ x: - radius * BOAT_FACT * 0.2714,  y: radius * BOAT_FACT * 0.3571 },
768				{ x: - radius * BOAT_FACT * 0.2857,  y: radius * BOAT_FACT * 0.2143 },
769				{ x: - radius * BOAT_FACT * 0.2857,  y: - radius * BOAT_FACT * 0.2143 },
770				{ x: - radius * BOAT_FACT * 0.2571,  y: - radius * BOAT_FACT * 0.3714 },
771				{ x: - radius * BOAT_FACT * 0.2286,  y: - radius * BOAT_FACT * 0.1429},
772				{ x: - radius * BOAT_FACT * 0.08571, y: - radius * BOAT_FACT * 0.1429},
773				{ x: - radius * BOAT_FACT * 0.04286, y: - radius * BOAT_FACT * 0.4286 },
774
775				{ x: 0, y: - radius * BOAT_FACT * 0.5714}, // Bow, center hull
776			];
777			let radAngle = Math.toRadians(dv); // + (Math.PI / 2);
778			// Apply rotation to the points of the needle
779			triPoints.forEach(pt => {
780				x = centerX + ((pt.x * Math.cos(radAngle)) - (pt.y * Math.sin(radAngle)));
781				y = centerY + ((pt.x * Math.sin(radAngle)) + (pt.y * Math.cos(radAngle)));
782				context.lineTo(x, y);
783			});
784		} else if (this.hand === 'plane') {
785			// console.log("Drawing plane needle...")
786			const BOAT_FACT = 0.8;
787			let planePoints = [
788				{ x: 0, y: - radius * BOAT_FACT *  0.5714 }, // Nose
789				// Starboard
790				{ x: radius * BOAT_FACT * 0.04286, y: - radius * BOAT_FACT * 0.4286 },
791				{ x: radius * BOAT_FACT * 0.08571, y: - radius * BOAT_FACT * 0.2857 },
792				{ x: radius * BOAT_FACT * 0.5714, y: - radius * BOAT_FACT * 0.1429 },
793				{ x: radius * BOAT_FACT * 0.5714, y: 0 },
794				{ x: radius * BOAT_FACT * 0.08571, y: radius * BOAT_FACT * 0.07143 },
795				{ x: radius * BOAT_FACT * 0.04286, y: radius * BOAT_FACT * 0.2571 },
796				{ x: radius * BOAT_FACT * 0.2857, y: radius * BOAT_FACT * 0.3571 },
797				{ x: radius * BOAT_FACT * 0.2857, y: radius * BOAT_FACT * 0.4286 },
798				{ x: radius * BOAT_FACT * 0.01429, y: radius * BOAT_FACT * 0.40 },
799				// Port
800				{ x: - radius * BOAT_FACT * 0.01429, y: radius * BOAT_FACT * 0.40 },
801				{ x: - radius * BOAT_FACT * 0.2857, y: radius * BOAT_FACT * 0.4286 },
802				{ x: - radius * BOAT_FACT * 0.2857, y: radius * BOAT_FACT * 0.3571 },
803				{ x: - radius * BOAT_FACT * 0.04286, y: radius * BOAT_FACT * 0.2571 },
804				{ x: - radius * BOAT_FACT * 0.08571, y: radius * BOAT_FACT * 0.07143 },
805				{ x: - radius * BOAT_FACT * 0.5714, y: 0 },
806				{ x: - radius * BOAT_FACT * 0.5714, y: - radius * BOAT_FACT * 0.1429 },
807				{ x: - radius * BOAT_FACT * 0.08571, y: - radius * BOAT_FACT * 0.2857 },
808				{ x: - radius * BOAT_FACT * 0.04286, y: - radius * BOAT_FACT * 0.4286 },
809
810				{ x: 0, y: - radius * BOAT_FACT *  0.5714 }
811			];
812			let radAngle = Math.toRadians(dv); // + (Math.PI / 2);
813			// Apply rotation to the points of the needle
814			planePoints.forEach(pt => {
815				x = centerX + ((pt.x * Math.cos(radAngle)) - (pt.y * Math.sin(radAngle)));
816				y = centerY + ((pt.x * Math.sin(radAngle)) + (pt.y * Math.cos(radAngle)));
817				context.lineTo(x, y);
818			});
819		} else if (this.hand === 'arrow') {
820			// console.log("Drawing arrow needle...")
821			const BOAT_FACT = 0.8;
822			let arrowPoints = [
823				{ x: 0, y: - radius * BOAT_FACT *  0.7 }, // Nose
824				// Starboard
825				{ x: radius * BOAT_FACT * 0.25, y: - radius * BOAT_FACT * 0.4 },
826				{ x: radius * BOAT_FACT * 0.1, y: - radius * BOAT_FACT * 0.4 },
827				{ x: radius * BOAT_FACT * 0.1, y:  radius * BOAT_FACT * 0.5 },
828				// Port
829				{ x: - radius * BOAT_FACT * 0.1, y: radius * BOAT_FACT * 0.5 },
830				{ x: - radius * BOAT_FACT * 0.1, y: - radius * BOAT_FACT * 0.4 },
831				{ x: - radius * BOAT_FACT * 0.25, y: - radius * BOAT_FACT * 0.4 },
832
833				{ x: 0, y: - radius * BOAT_FACT *  0.7 }
834			];
835			let radAngle = Math.toRadians(dv); // + (Math.PI / 2);
836			// Apply rotation to the points of the needle
837			arrowPoints.forEach(pt => {
838				x = centerX + ((pt.x * Math.cos(radAngle)) - (pt.y * Math.sin(radAngle)));
839				y = centerY + ((pt.x * Math.sin(radAngle)) + (pt.y * Math.cos(radAngle)));
840				context.lineTo(x, y);
841			});
842		}
843		// Right
844		if (this.hand === 'regular' || this.hand === 'wind') {
845			x = centerX - ((radius * 0.05) * Math.cos(Math.toRadians(dv) + (2 * Math.PI / 2)));
846			y = centerY - ((radius * 0.05) * Math.sin(Math.toRadians(dv) + (2 * Math.PI / 2)));
847			context.lineTo(x, y);
848		}
849
850		context.closePath();
851		context.fillStyle = this.directionColorConfig.handColor;
852		context.fill();
853		context.lineWidth = 2;
854		context.strokeStyle = this.directionColorConfig.handOutlineColor;
855		context.stroke();
856		// Knob
857		context.beginPath();
858		context.arc((this.canvas.width / 2), (radius + 10), 7, 0, 2 * Math.PI, false);
859		context.closePath();
860		context.fillStyle = this.directionColorConfig.knobColor;
861		context.fill();
862		context.strokeStyle = this.directionColorConfig.knobOutlineColor;
863		context.stroke();
864	}
865
866	drawSpike(radius, outsideRadius, insideRadius, angle, context) {
867		let xFrom = (this.canvas.width / 2) - (outsideRadius * Math.cos(2 * Math.PI * (angle / 360)));
868		let yFrom = (radius + 10) - (outsideRadius * Math.sin(2 * Math.PI * (angle / 360)));
869		//
870		let xTo = (this.canvas.width / 2) - (insideRadius * Math.cos(2 * Math.PI * ((angle - 90) / 360)));
871		let yTo = (radius + 10) - (insideRadius * Math.sin(2 * Math.PI * ((angle - 90) / 360)));
872		context.moveTo(xFrom, yFrom);
873		context.lineTo(xTo, yTo);
874		//
875		xTo = (this.canvas.width / 2) - (insideRadius * Math.cos(2 * Math.PI * ((angle + 90) / 360)));
876		yTo = (radius + 10) - (insideRadius * Math.sin(2 * Math.PI * ((angle + 90) / 360)));
877		context.moveTo(xFrom, yFrom);
878		context.lineTo(xTo, yTo);
879	}
880}
881
882// Associate the tag and the class
883window.customElements.define(DIRECTION_TAG_NAME, DirectionDisplay);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.