/* Some Shapes */
function Rectangle(width, height) {
const shape = new Shape();
shape.width = width;
shape.height = height;
return shape;
}
function Square(width) {
return Rectangle(width, width);
}
function Oval(width, height) {
const shape = Rectangle(width, height);
shape.borderRadius = 50;
shape.borderRadiusUnits = "%";
return shape;
}
function Circle(diameter) {
return Oval(diameter, diameter);
}
/** Draws a shape.
* Options: container, color, left, top, units.
*/
function draw(shape, options) {
if (options === undefined)
options = {};
if (options.container === undefined)
options.container = document.body;
if (options.color !== undefined)
shape.backgroundColor = options.color;
if (options.left !== undefined)
shape.left = options.left;
if (options.top !== undefined)
shape.top = options.top;
if (options.units !== undefined)
shape.positionUnits = options.units;
shape.appendToElement(options.container);
return shape;
}
/** An ordered pair of values. */
function OrderedPair(value1, value2) {
/* Argument Validation */
value1 = this.vetInitial(value1, this.defaultValue);
value2 = this.vetInitial(value2, this.defaultValue);
/* Public Properties */
Object.defineProperty(this, "value1", {get: getValue1, set: setValue1});
Object.defineProperty(this, "value2", {get: getValue2, set: setValue2});
/* Public Methods */
this.equals = function(other) {
return other instanceof OrderedPair &&
other.value1 === value1 && other.value2 === value2;
};
this.toString = function() {
return "(" + value1 + "," + value2 + ")";
};
/* Private Functions */
function getValue1() { return value1; }
function getValue2() { return value2; }
function setValue1(value) { value1 = this.vetUpdated(value, value1); }
function setValue2(value) { value2 = this.vetUpdated(value, value2); }
}
OrderedPair.prototype = {
vetInitial: (val, alt) => val !== undefined ? val : alt,
vetUpdated: (val, alt) => val,
defaultValue: null
};
/** A readonly ordered pair of values. */
function ReadonlyOrderedPair(value1, value2) {
OrderedPair.call(this, value1, value2);
}
ReadonlyOrderedPair.prototype = Object.create(OrderedPair.prototype);
ReadonlyOrderedPair.prototype.vetUpdated = (newValue, oldValue) => oldValue;
/** An ordered pair of finite numbers. */
function CoordPair(value1, value2) {
OrderedPair.call(this, value1, value2);
}
CoordPair.vetValue = function(value, alternate) {
if (Number.isFinite(value)) return value;
else if (Number.isFinite(alternate)) {
return alternate;
}
throw Error("Not finite: " + value + " or " + alternate);
}
CoordPair.prototype = Object.create(OrderedPair.prototype);
CoordPair.prototype.vetInitial = CoordPair.vetValue;
CoordPair.prototype.vetUpdated = CoordPair.vetValue;
CoordPair.prototype.defaultValue = 0;
/** A pair of Cartesian coordinates. */
function CartCoordPair(x, y) {
CoordPair.call(this, x, y);
/* Public Properties */
Object.defineProperty(this, "x", {
get: () => this.value1,
set: (value) => this.value1 = value
});
Object.defineProperty(this, "y", {
get: () => this.value2,
set: (value) => this.value2 = value
});
/* Public Methods */
this.dist = (other) => CartCoordPair.dist(this, other);
}
/* Static Components */
CartCoordPair.dist = function(p1, p2) {
const dx = p2.x - p1.x;
const dy = p2.y - p1.y;
return Math.sqrt(dx*dx + dy*dy);
};
CartCoordPair.prototype = Object.create(CoordPair.prototype);
/** Units for use with a webpage. */
const WebUnits = {
defaultUnits: "px",
isValid: (value) => WebUnits.toArray().indexOf(value) >= 0,
toArray: () => [
"cm", // centimeters
"mm", // millimeters
"in", // inches
"px", // pixels (1 pixel = 1/96th of 1 inch)
"pt", // points (1 point = 1/72nd of 1 inch)
"pc", // picas (1 pica = 12 points)
"em", // relative to font size of element
"ex", // relative to x-height of font
"ch", // relative to width of "0"
"rem", // relative to font size of root element
"vw", // relative to 1% of width of viewport
"vh", // relative to 1% of height of viewport
"vmin", // relative to 1% of smaller dimension of viewport
"vmax", // relative to 1% of larger dimension of viewport
"%" // relative to parent element
],
toString: () => WebUnits.toArray().toString(),
vet: (value, alternate) => WebUnits.isValid(value) ? value : alternate
}
/** Colors for use with a webpage. */
const WebColors = {
defaultColor: "black",
isValid: (value) => WebColors.toArray().indexOf(value) >= 0,
random: function() {
const a = WebColors.toArray();
const n = a.length;
const i = Math.floor(Math.random() * n);
return a[i];
},
toArray: () => ["white", "silver", "gray", "black", "red", "maroon",
"yellow", "olive", "lime", "green", "aqua", "teal", "blue", "navy",
"fuchsia", "purple", "orange"
],
toString: () => WebUnits.toArray().toString(),
vet: (value, alternate) => WebColors.isValid(value) ? value : alternate
}
/** A pair of coordinates with units. */
function WebCoords(x, y, units) {
const coords = new CartCoordPair(x, y);
units = WebUnits.vet(units, WebUnits.defaultUnits);
Object.defineProperty(this, "x", {get: getX, set: setX});
Object.defineProperty(this, "y", {get: getY, set: setY});
Object.defineProperty(this, "xPlusUnits", {get: xPlusUnits});
Object.defineProperty(this, "yPlusUnits", {get: yPlusUnits});
Object.defineProperty(this, "units", {get: getUnits, set: setUnits});
function getX() { return coords.x; }
function getY() { return coords.y; }
function setX(value) { coords.x = value; }
function setY(value) { coords.y = value; }
function xPlusUnits() { return coords.x.toString() + units; }
function yPlusUnits() { return coords.y.toString() + units; }
function getUnits() { return units; }
function setUnits(value) { units = WebUnits.vet(value, units); }
this.toString = () => "(" + xPlusUnits() + "," + yPlusUnits() + ")";
}
/** A shape that can be drawn on a webpage. */
function Shape() {
/* Private Fields */
const position = new WebCoords(0,0);
let element = createElement(),
sizeUnits = position.units,
borderRadiusUnits = "%",
width = 1, height = 1,
borderRadius = 0,
backgroundColor = "black";
/* Initialization */
updateColoring();
updateBorder();
updateSize();
updatePosition();
/* Public Properties */
Object.defineProperty(this, "left", {
get: function() { return position.x; },
set: function(value) {
position.x = value;
updatePosition();
}
});
Object.defineProperty(this, "top", {
get: function() {return position.y;},
set: function(value) {
position.y = value;
updatePosition();
}
});
Object.defineProperty(this, "positionUnits", {
get: function() { return position.units; },
set: function(value) {
if (WebUnits.isValid(value)) {
position.units = value;
updatePosition();
}
}
});
Object.defineProperty(this, "borderRadius", {
get: function() { return borderRadius; },
set: function(value) {
if (Number.isFinite(value)) {
borderRadius = value;
updateBorder();
}
}
});
Object.defineProperty(this, "borderRadiusUnits", {
get: function() { return borderRadiusUnits; },
set: function(value) {
if (WebUnits.isValid(value)) {
borderRadiusUnits = value;
updateBorder();
}
}
});
Object.defineProperty(this, "width", {
get: function() { return width; },
set: function(value) {
if (Number.isFinite(value)) {
width = value;
updateSize();
}
}
});
Object.defineProperty(this, "height", {
get: function() { return height; },
set: function(value) {
if (Number.isFinite(value)) {
height = value;
updateSize();
}
}
});
Object.defineProperty(this, "sizeUnits", {
get: function() { return sizeUnits; },
set: function(value) {
if (WebUnits.isValid(value)) {
sizeUnits = value;
updateSize();
}
}
});
Object.defineProperty(this, "backgroundColor", {
get: function() { return backgroundColor; },
set: function(value) {
if (WebColors.isValid(value)) {
backgroundColor = value;
updateColoring();
}
}
});
/* Public Methods */
this.appendToElement = function(containerElement) {
containerElement.append(element);
};
this.removeFromElement = function(containerElement) {
if (containerElement.contains(element))
containerElement.remove(element);
element = createElement();
};
/* Private Functions */
function createElement() {
const e = document.createElement("div");
e.style.position = "absolute";
return e;
}
function updateBorder() {
element.style.borderRadius = borderRadius.toString() + borderRadiusUnits;
}
function updateSize() {
element.style.width = width.toString() + sizeUnits;
element.style.height = height.toString() + sizeUnits;
}
function updatePosition() {
element.style.top = position.yPlusUnits;
element.style.left = position.xPlusUnits;
}
function updateColoring() {
element.style.backgroundColor = backgroundColor;
}
}