diff --git a/src/Components/LegendBox.ts b/src/Components/LegendBox.ts index 66ef27a..ec9cffb 100644 --- a/src/Components/LegendBox.ts +++ b/src/Components/LegendBox.ts @@ -1,16 +1,24 @@ import renderToString from "katex"; +import LegendText from "./LegendText"; import Plot from "./Plot"; import Point from "./Point"; import State from "./State"; import { Component, GuiComponent } from "./interfaces"; class LegendBox implements GuiComponent { - elements: (Component | string | State)[]; + elements: (Component | LegendText | State)[]; states: { [key: string]: State }; htmlElement: HTMLElement; - constructor(elements?: (Component | string | State)[]) { - this.elements = elements || []; + constructor(elements?: (Component | LegendText | State)[]) { + this.elements = []; + if (elements) { + for (const element of elements || []) { + if (!this.elements.includes(element)) { + this.elements.push(element); + } + } + } this.states = {}; this.htmlElement = this.createLegendBoxWrapper(); const button = this.createSizeAdjustButton(); @@ -54,8 +62,13 @@ class LegendBox implements GuiComponent { this.elements.forEach((element) => { if (element instanceof State) { this.states[element.getStateName()] = element; - } else if (typeof element === "string") { - this.addStringAsObserver(element); + for (const legendText of this.elements) { + if (legendText instanceof LegendText && legendText.getUseStates()) { + this.addStringAsObserver(legendText.getExpression()); + } + } + } else if (element instanceof LegendText && element.getUseStates()) { + this.addStringAsObserver(element.getExpression()); } }); } @@ -82,9 +95,14 @@ class LegendBox implements GuiComponent { this.elements.length === 0 ? "none" : "block"; } - private processElement(element: Component | string | State) { + private processElement(element: Component | LegendText | State) { const functionContainer = document.createElement("div"); functionContainer.className = "function-container"; + + if (element instanceof State && !element.inLegend) { + return; + } + const icon = this.createIcon(element); functionContainer.appendChild(icon); @@ -103,7 +121,7 @@ class LegendBox implements GuiComponent { this.htmlElement.appendChild(functionContainer); } - private createIcon(element: Component | string | State) { + private createIcon(element: Component | LegendText | State) { const icon = document.createElement("span"); icon.className = this.getIconClass(element); if (icon.className === "triangle-icon") { @@ -114,27 +132,30 @@ class LegendBox implements GuiComponent { return icon; } - private getTextToDisplay(element: Component | string | State) { + private getTextToDisplay(element: Component | LegendText | State) { let textToDisplay = ""; - - if (typeof element === "string") { - textToDisplay = - element + ": " + this.replaceStateNamesWithValues(element); - } else if (element instanceof State) { + if (element instanceof State) { textToDisplay = element.getStateName() + ": " + element.getState().toFixed(1); + } else if (element instanceof LegendText) { + if (element.getUseStates()) { + textToDisplay = this.replaceStateNamesWithValues( + element.getExpression() + ); + } else { + textToDisplay = element.getExpression(); + } } else if (element instanceof Component) { textToDisplay = element.getDisplayText ? element.getName() + ": " + element.getDisplayText() : element.getName(); } - return textToDisplay; } private createHtmlElementText( renderedEquation: string, - element: Component | string | State + element: Component | LegendText | string | State ) { const htmlElementText = document.createElement("div"); htmlElementText.innerHTML = renderedEquation; @@ -164,27 +185,29 @@ class LegendBox implements GuiComponent { }); } - private getIconClass(element: Component | string | State) { - if (typeof element === "string" || element instanceof State) { - return "point-icon"; + private getIconClass(element: Component | LegendText | State) { + if (element instanceof State || element instanceof Point) { + return "circle-icon"; } else if (element instanceof Plot) { - return "plot-icon"; - } else if (element instanceof Point) { - return "point-icon"; + return "rectangle-icon"; + } else if (element instanceof LegendText) { + return element.getIcon(); } else { return "triangle-icon"; } } - private getIconColor(element: Component | string | State) { + private getIconColor(element: Component | LegendText | State) { if (element instanceof Component) { return "#" + element.getColorAsString(); + } else if (element instanceof LegendText) { + return element.getColor(); } else { return "#faa307"; } } - public addElement(element: Component | string | State) { + public addElement(element: Component | LegendText | State) { if (this.elements.includes(element)) { return; } @@ -196,11 +219,16 @@ class LegendBox implements GuiComponent { return; } this.states[element.getStateName()] = element; + for (const expressions of this.elements) { + if (expressions instanceof LegendText && expressions.getUseStates()) { + this.addStringAsObserver(expressions.getExpression()); + } + } } // If the element is a string, add it as an observer to the state variables it contains - if (typeof element === "string") { - this.addStringAsObserver(element); + if (element instanceof LegendText && element.getUseStates()) { + this.addStringAsObserver(element.getExpression()); } this.updateComponents(); diff --git a/src/Components/LegendText.ts b/src/Components/LegendText.ts new file mode 100644 index 0000000..e3885d4 --- /dev/null +++ b/src/Components/LegendText.ts @@ -0,0 +1,59 @@ +type LegendTextOptions = { + color?: string; + shape?: string; + useStates?: boolean; +}; + +const defaultLegendTextOptions = { + color: "#faa307", + shape: "circle", + useStates: false, +}; + +class LegendText { + private expression: string; + private color: string; + private shape: string; + private useStates: boolean; + + constructor(expression: string, options?: LegendTextOptions) { + const { color, shape, useStates } = { + ...defaultLegendTextOptions, + ...options, + }; + this.expression = expression; + this.color = color; + this.shape = shape; + this.useStates = useStates; + } + + getExpression(): string { + return this.expression; + } + + getColor(): string { + return this.color; + } + + getShape(): string { + return this.shape; + } + + getUseStates(): boolean { + return this.useStates; + } + + getIcon(): string { + switch (this.getShape()) { + case "circle": + return "circle-icon"; + case "rectangle": + return "rectangle-icon"; + case "triangle": + return "triangle-icon"; + default: + return "circle-icon"; + } + } +} +export default LegendText; diff --git a/src/Components/Point.ts b/src/Components/Point.ts index ebdd8d7..6263c24 100644 --- a/src/Components/Point.ts +++ b/src/Components/Point.ts @@ -18,6 +18,9 @@ type PointOptions = { color?: string; draggable?: Draggable; dragListeners?: ((point: Point) => void)[]; + customName?: string; + showName?: boolean; + legendCoordinates?: string; }; const defaultPointOptions = { @@ -26,23 +29,38 @@ const defaultPointOptions = { decimals: 1, label: false, dragListeners: [], + showName: true, + legendCoordinates: "", }; class Point extends Component implements Collider, DragListener { private pointName: string | undefined; + private showName: boolean; + private legendCoordinates: string; private static pointCounter = 0; static emitter = new EventEmitter(); private color: string; dragListeners: ((point: Point) => void)[]; constructor(x = 0, y = 0, options?: PointOptions) { super(); - const { color, draggable, decimals, label, dragListeners } = { + const { + color, + draggable, + decimals, + label, + dragListeners, + customName, + showName, + legendCoordinates, + } = { ...defaultPointOptions, ...options, }; //set point name and color - this.setPointName(); + this.setPointName(customName); this.color = color; + this.showName = showName; + this.legendCoordinates = legendCoordinates; // set position of the point instance this.draggable = draggable; @@ -79,16 +97,18 @@ class Point extends Component implements Collider, DragListener { } //add name of point - const nameText = new Text(this.pointName, { - color: "black", - fontSize: 18, - anchorY: "middle", - anchorX: "left", - position: [15, 0], - responsiveScale: false, - }); - nameText.name = "name"; - this.add(nameText); + if (this.showName) { + const nameText = new Text(this.pointName, { + color: "black", + fontSize: 18, + anchorY: "middle", + anchorX: "left", + position: [15, 0], + responsiveScale: false, + }); + nameText.name = "name"; + this.add(nameText); + } } addDragListener(listener: (point: Point) => void) { @@ -141,19 +161,35 @@ class Point extends Component implements Collider, DragListener { public setPosition(x: number, y: number) { this.position.set(x, y, this.position.z); } - private setPointName() { - this.pointName = String.fromCharCode( - "A".charCodeAt(0) + Point.pointCounter - ); - Point.pointCounter++; + private setPointName(customName?: string) { + if (customName) { + this.pointName = customName; + } else { + this.pointName = String.fromCharCode( + "A".charCodeAt(0) + Point.pointCounter + ); + Point.pointCounter++; + } } + public getName(): string { return this.pointName as string; } + public getDisplayText(): string { - return ( - "(" + this.position.x.toFixed(1) + ", " + this.position.y.toFixed(1) + ")" - ); + if (this.legendCoordinates === "x") { + return this.position.x.toFixed(1); + } else if (this.legendCoordinates === "y") { + return this.position.y.toFixed(1); + } else { + return ( + "(" + + this.position.x.toFixed(1) + + ", " + + this.position.y.toFixed(1) + + ")" + ); + } } public hover() { diff --git a/src/Components/State.ts b/src/Components/State.ts index 56750c2..3ae82d9 100644 --- a/src/Components/State.ts +++ b/src/Components/State.ts @@ -2,14 +2,28 @@ import EventEmitter from "eventemitter3"; const STATE_CHANGE_EVENT = "stateChange"; +type StateOptions = { + inLegend?: boolean; +}; + +const defaultStateOptions = { + inLegend: true, +}; + class State { private stateName: string; private state: T; private emitter: EventEmitter; + public inLegend: boolean; - constructor(stateName: string, initialState: T) { + constructor(stateName: string, initialState: T, options?: StateOptions) { + const { inLegend } = { + ...defaultStateOptions, + ...options, + }; this.stateName = stateName; this.state = initialState; + this.inLegend = inLegend; this.emitter = new EventEmitter(); } diff --git a/src/index.ts b/src/index.ts index dcf7e76..fcdb0a8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,6 +8,7 @@ export { default as InfiniteLine } from "./Components/InfiniteLine"; export { default as InputField } from "./Components/InputField"; export { default as Label } from "./Components/Label"; export { default as Latex } from "./Components/Latex"; +export { default as LegendText } from "./Components/LegendText"; export { default as LegendBox } from "./Components/LegendBox"; export { default as Line } from "./Components/Line"; export { default as Plot } from "./Components/Plot"; diff --git a/style.css b/style.css index 35fde9d..41c9c7b 100644 --- a/style.css +++ b/style.css @@ -132,6 +132,8 @@ body { .legendBox-wrapper { position: absolute; + top: 15px; + left: 15px; width: 260px; max-height: 170px; background-color: #fae493; @@ -165,7 +167,7 @@ body { margin-top: 8px; } -.point-icon { +.circle-icon { border-radius: 50%; width: 8px; height: 8px; @@ -174,7 +176,7 @@ body { border: #000 solid 1px; } -.plot-icon { +.rectangle-icon { width: 13px; height: 2px; display: inline-block;