Published
Edited
Mar 6, 2020
Importers
2 stars
Insert cell
Insert cell
viewof point = XYPad()
Insert cell
point
Insert cell
Insert cell
viewof point
Insert cell
viewof point instanceof HTMLCanvasElement
Insert cell
Insert cell
{
const point2 = XYPad();
while (true) {
yield Promises.tick(2000).then(
() => (
(point2.value = { x: Math.random() * 100, y: Math.random() * 100 }),
point2
)
);
}
}
Insert cell
Insert cell
{
const pointX = XYPad();
const pointY = XYPad();
const div = html`<div>${pointX}${pointY}</div>`;
while (true) {
pointX.x = 50 + Math.cos(Date.now() / 1000) * 30;
pointY.y = 100 - pointX.x;
pointY.x = 50 - Math.sin(Date.now() / 1000) * 10;
yield div;
}
}
Insert cell
Insert cell
point3 = XYPad()
Insert cell
{
const text = html`<div>point3.x = <span id="x">Click on the pad above</span></div>`;
const updateX = () => {
document.getElementById('x').innerText = point3.x;
};
point3.addEventListener("inputX", updateX);
invalidation.then(() => point3.removeEventListener("input", updateX));
return text;
}
Insert cell
Insert cell
function XYPad(x, y, options) {
const w = (options && +options.w > 0 && Math.round(+options.w)) || 100;
const h = (options && +options.h > 0 && Math.round(+options.h)) || 100;
const dpr = devicePixelRatio;
const canvas = html`<canvas width="${w * dpr}" height="${h *
dpr}" style="width: ${w}px; height: ${h}px"></canvas>`;
const ctx = canvas.getContext('2d');
ctx.scale(dpr, dpr);

const xMin = 0;
const yMin = 0;
const xMax = w;
const yMax = h;
const xDefault = Math.round(xMax / 2);
const yDefault = Math.round(yMax / 2);

const parseX = value =>
value !== undefined && +value >= xMin && +value <= xMax
? Math.round(+value)
: xDefault;
const parseY = value =>
value !== undefined && +value >= yMin && +value <= yMax
? Math.round(+value)
: yDefault;
Object.defineProperties(canvas, {
_x: {
value: x || xDefault,
enumerable: false,
writable: true
},
_y: {
value: y || yDefault,
enumerable: false,
writable: true
},
x: {
get: function() {
return this._x;
},
set: function(value) {
// Note that no 'input' event is emittted when the x property is set programatically
const x = parseX(value);
if (this._x !== x) {
this._x = x;
renderX();
}
},
enumerable: true
},
y: {
get: function() {
return this._y;
},
set: function(value) {
// Note that no 'input' event is emittted when the y property is set programatically
const y = parseY(value);
if (this._y !== y) {
this._y = y;
renderY();
}
},
enumerable: true
},
value: {
get: function() {
return { x: this.x, y: this.y };
},
set: function(value) {
// Note that no 'input' event is emittted when the value property is set programatically
const x = parseX(value && value.x);
const y = parseY(value && value.y);

if (this._x !== x && this._y !== y) {
// Both have changed, do only one update
this._x = x;
this._y = y;
render();
} else {
// Let the setters do the work
this.x = x;
this.y = y;
}
},
enumerable: true
}
});

canvas.onclick = function(e) {
const rect = canvas.getBoundingClientRect();
const x = Math.round(e.clientX - rect.left);
const y = Math.round(e.clientY - rect.top);

const xChanged = this.x !== x;
const yChanged = this.y !== y;

// We separate the four cases, as a template for
// future more complex controls
if (xChanged && yChanged) {
this.value = { x, y };
this.dispatchEvent(new CustomEvent("inputX"));
this.dispatchEvent(new CustomEvent("inputY"));
this.dispatchEvent(new CustomEvent("input"));
} else if (xChanged) {
this.x = x;
this.dispatchEvent(new CustomEvent("inputX"));
this.dispatchEvent(new CustomEvent("input"));
} else if (yChanged) {
this.y = y;
this.dispatchEvent(new CustomEvent("inputY"));
this.dispatchEvent(new CustomEvent("input"));
}
};

// Init and return
render();
return canvas;

function renderX() {
// Might be optimized if only x has changed
render();
}
function renderY() {
// Might be optimized if only y has changed
render();
}
function render() {
const ctx = canvas.getContext('2d');
const lineWidth = 1;
const radius = 4;
ctx.clearRect(0, 0, w, h);
ctx.moveTo(canvas.x + radius, canvas.y);
ctx.beginPath();
ctx.arc(canvas.x, canvas.y, radius, 0, 2 * Math.PI);
ctx.moveTo(0, 0);
ctx.rect(lineWidth / 2, lineWidth / 2, w - lineWidth, h - lineWidth);
ctx.lineWidth = lineWidth;
ctx.stroke();
}
}
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more