Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

72 make legend box component #82

Merged
merged 25 commits into from
Jul 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
af7191d
Beginning of LegendBox
amaliejvik Jun 27, 2024
3c0d548
Add expand-collapse function and HideFromLegendBox function
amaliejvik Jun 28, 2024
565a48b
Move Plot-funcName logic out of Core.ts and into Plot.ts
amaliejvik Jun 28, 2024
4af059a
Add possibility for having update() function on GuiComponents as well
amaliejvik Jun 28, 2024
f277f9d
LegendBox updates if a plot is dynamic (moves)
amaliejvik Jun 28, 2024
d3c9526
Make expand-collide button work together with dynamic function names
amaliejvik Jun 28, 2024
02d6ac1
Make LegendBox size adjust based upon how many plots it holds
amaliejvik Jun 28, 2024
724f10f
Generalize legend box
amaliejvik Jul 1, 2024
aae68e0
Add name to all Component classes
amaliejvik Jul 1, 2024
1552950
Add name to Fraction
amaliejvik Jul 1, 2024
1669de5
Add hover-functionality for LegendBox and Components
amaliejvik Jul 1, 2024
24d69d1
Add support for different types of icons in LegendBox and a color-fix
amaliejvik Jul 2, 2024
2095d78
Just some testing
amaliejvik Jul 2, 2024
f23a2dc
Another color fix
amaliejvik Jul 2, 2024
2ae652f
Add functionality for having state variables and latex-text dependent…
amaliejvik Jul 2, 2024
5be5ab5
Generalize State.ts
amaliejvik Jul 2, 2024
afb4323
Hover on Points bux-fix
amaliejvik Jul 3, 2024
7a24680
Refactoring/clean-up of code
amaliejvik Jul 3, 2024
0edf6cf
Further generalization of State.ts and a Style fix
amaliejvik Jul 3, 2024
3b04f88
Final clean-up
amaliejvik Jul 3, 2024
dce9f96
Final color-fix for Plot
amaliejvik Jul 3, 2024
d968e8a
Fix of some type declarations
amaliejvik Jul 3, 2024
09f307a
Merge branch 'master' into 72-make-legend-box-component
amaliejvik Jul 3, 2024
bb269c8
Formatting fix
amaliejvik Jul 3, 2024
8d09b68
fix prettier
amaliejvik Jul 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Components/Arc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ class Arc extends Component {
this.textOffset = textOffset;
this.resolution = resolution;
this.dynamic = dynamic;
this.name = "Arc";

this._curvedOutline = new Line2(
undefined,
Expand Down
1 change: 1 addition & 0 deletions src/Components/Bracket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class Bracket extends Component {
);
this.add(this._bracket);
this._updateBracketGeometry();
this.name = "Bracket";

this._text = new Text(content, {
fontSize: 20,
Expand Down
31 changes: 25 additions & 6 deletions src/Components/Circle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,23 +33,24 @@ class Circle extends Component implements Collider {
const { color, segments } = { ...defaultShapeOptions, ...options };

const geometry = new CircleGeometry(radius, segments);
const material = new MeshBasicMaterial({
color: color,
transparent: true,
opacity: 0.5,
});
this.segments = segments;
const strokeGeometry = new RingGeometry(radius - 1, radius, this.segments);
const strokeMaterial = new MeshBasicMaterial({ color: "#080007" });
// set mesh of the point instance
const circleMesh = new Mesh(geometry, material);
const geometryMaterial = new MeshBasicMaterial({
color: color,
transparent: true,
opacity: 0.5,
});
const circleMesh = new Mesh(geometry, geometryMaterial);
this._strokeMesh = new Mesh(strokeGeometry, strokeMaterial);
this._strokeMesh.position.set(0, 0, 1);
this.geometry = circleMesh.geometry;
this.material = circleMesh.material;
this.add(this._strokeMesh);
// set position of the mesh
this.position.set(x, y, 0);
this.name = "Circle";
}

collidesWith(other: Object3D): boolean {
Expand Down Expand Up @@ -87,6 +88,24 @@ class Circle extends Component implements Collider {
this.segments
);
}

hover() {
const color = this.getColorAsNumber();
this.material = new MeshBasicMaterial({
color: color,
transparent: true,
opacity: 0.7,
});
}

unhover() {
const color = this.getColorAsNumber();
this.material = new MeshBasicMaterial({
color: color,
transparent: true,
opacity: 0.5,
});
}
}

export default Circle;
1 change: 1 addition & 0 deletions src/Components/Derived/Fraction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class Fraction extends Component {
this.position.set(x, y, this.position.z);
this.filled = filled;
this.divisor = divisor;
this.name = "Fraction";

this.divisors = new Group();
this.generateDivisors(radius, divisor, filled);
Expand Down
1 change: 1 addition & 0 deletions src/Components/Grid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ class Grid extends Component {
this.hasLabels = labels;
this.xLabelOffset = xLabelOffset ?? [0, 0];
this.yLabelOffset = yLabelOffset ?? [0, 0];
this.name = "Grid";
const gridGeometry = new PlaneGeometry(
window.innerWidth,
window.innerHeight
Expand Down
1 change: 1 addition & 0 deletions src/Components/InfiniteLine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ class InfiniteLine extends Line {
constructor(start: InputPosition, end: InputPosition, props?: LineOptions) {
super(start, end, props);
this.frustumCulled = false;
this.name = "Infinite line";
}

_updateGeometry(camera: OrthographicCamera) {
Expand Down
1 change: 1 addition & 0 deletions src/Components/Label.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class Label extends Component {
endPoint.x + (deltaX < 0 ? -1 : 1) * 10,
endPoint.y,
]);
this.name = "Label";

// Add line and text components to the label
this.add(line1);
Expand Down
1 change: 1 addition & 0 deletions src/Components/Latex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class Latex extends Component implements Collider {
}: LatexOptions
) {
super();
this.name = "Latex";
const pos = toVector3(position);
const renderedEquation = renderToString.renderToString(latex, {
output: "mathml",
Expand Down
240 changes: 240 additions & 0 deletions src/Components/LegendBox.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
import renderToString from "katex";
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<number>)[];
states: { [key: string]: State<number> };
htmlElement: HTMLElement;

constructor(elements?: (Component | string | State<number>)[]) {
this.elements = elements || [];
this.states = {};
this.htmlElement = this.createLegendBoxWrapper();
const button = this.createSizeAdjustButton();
this.htmlElement.appendChild(button);
this.registerEventListeners();
this.initializeStatesAndStrings();
this.updateComponents();
}

private createLegendBoxWrapper(): HTMLElement {
const legendBoxWrapper = document.createElement("div");
legendBoxWrapper.className = "legendBox-wrapper";
legendBoxWrapper.style.display =
this.elements.length === 0 ? "none" : "block";
return legendBoxWrapper;
}

private createSizeAdjustButton(): HTMLButtonElement {
const button = document.createElement("button");
button.className = "size-adjust-button";
button.addEventListener("click", () => {
this.htmlElement.classList.toggle("minimized");
this.updateButton(button);
});
this.updateButton(button);
return button;
}

private updateButton(button: HTMLButtonElement): void {
button.innerHTML = this.htmlElement.classList.contains("minimized")
? "&#8599;"
: "&#8601;";
}
private registerEventListeners(): void {
Plot.emitter.on("plotUpdated", () => this.updateComponents());
Point.emitter.on("pointUpdated", () => this.updateComponents());
}

//States and strings given in the constructor are initialized (states are added to states-dictionary and strings are added as observers to the states they contain)
private initializeStatesAndStrings(): void {
this.elements.forEach((element) => {
if (element instanceof State) {
this.states[element.getStateName()] = element;
} else if (typeof element === "string") {
this.addStringAsObserver(element);
}
});
}

public updateComponents() {
this.clearChildren();
if (!this.elements) {
return;
}
this.updateDisplay();
this.elements.forEach((element) => this.processElement(element));
}

private clearChildren() {
Array.from(this.htmlElement.children).forEach((child) => {
if (child.className !== "size-adjust-button") {
this.htmlElement.removeChild(child);
}
});
}

private updateDisplay() {
this.htmlElement.style.display =
this.elements.length === 0 ? "none" : "block";
}

private processElement(element: Component | string | State<number>) {
const functionContainer = document.createElement("div");
functionContainer.className = "function-container";
const icon = this.createIcon(element);
functionContainer.appendChild(icon);

const textToDisplay = this.getTextToDisplay(element);
const renderedEquation = renderToString.renderToString(textToDisplay, {
output: "mathml",
strict: false,
trust: true,
});
const htmlElementText = this.createHtmlElementText(
renderedEquation,
element
);

functionContainer.appendChild(htmlElementText);
this.htmlElement.appendChild(functionContainer);
}

private createIcon(element: Component | string | State<number>) {
const icon = document.createElement("span");
icon.className = this.getIconClass(element);
if (icon.className === "triangle-icon") {
icon.style.borderBottomColor = this.getIconColor(element);
} else {
icon.style.backgroundColor = this.getIconColor(element);
}
return icon;
}

private getTextToDisplay(element: Component | string | State<number>) {
let textToDisplay = "";

if (typeof element === "string") {
textToDisplay =
element + ": " + this.replaceStateNamesWithValues(element);
} else if (element instanceof State) {
textToDisplay =
element.getStateName() + ": " + element.getState().toFixed(1);
} else if (element instanceof Component) {
textToDisplay = element.getDisplayText
? element.getName() + ": " + element.getDisplayText()
: element.getName();
}

return textToDisplay;
}

private createHtmlElementText(
renderedEquation: string,
element: Component | string | State<number>
) {
const htmlElementText = document.createElement("div");
htmlElementText.innerHTML = renderedEquation;

if (element instanceof Component) {
this.addHoverListeners(htmlElementText, element);
}

return htmlElementText;
}

private addHoverListeners(
htmlElementText: HTMLDivElement,
element: Component
) {
htmlElementText.addEventListener("mouseover", function () {
if (element.hover) {
htmlElementText.style.cursor = "pointer";
element.hover();
}
});

htmlElementText.addEventListener("mouseout", function () {
if (element.unhover) {
element.unhover();
}
});
}

private getIconClass(element: Component | string | State<number>) {
if (typeof element === "string" || element instanceof State) {
return "point-icon";
} else if (element instanceof Plot) {
return "plot-icon";
} else if (element instanceof Point) {
return "point-icon";
} else {
return "triangle-icon";
}
}

private getIconColor(element: Component | string | State<number>) {
if (element instanceof Component) {
return "#" + element.getColorAsString();
} else {
return "#faa307";
}
}

public addElement(element: Component | string | State<number>) {
if (this.elements.includes(element)) {
return;
}
this.elements.push(element);

// Add to states if the element is a state
if (element instanceof State) {
if (this.states[element.getStateName()]) {
return;
}
this.states[element.getStateName()] = element;
}

// If the element is a string, add it as an observer to the state variables it contains
if (typeof element === "string") {
this.addStringAsObserver(element);
}

this.updateComponents();
}

// Adds string as an observer to the state variables it contains
private addStringAsObserver(str: string) {
const stateNames = this.parseStateNames(str);
stateNames.forEach((stateName) => {
if (this.states[stateName]) {
this.states[stateName].addObserver(() => this.updateComponents());
}
});
}

// Returns an array with the state variables present in the string
private parseStateNames(str: string): string[] {
return Array.from(new Set(str.match(/[a-z]/gi) || []));
}

// Replaces the state variables in str with their current value
private replaceStateNamesWithValues(str: string): string {
let result = str;
const stateNames = this.parseStateNames(str);
stateNames.forEach((stateName) => {
if (this.states[stateName]) {
result = result.replace(
new RegExp(stateName, "g"),
this.states[stateName].getState().toFixed(1).toString()
);
}
});
return result;
}
}

export default LegendBox;
1 change: 1 addition & 0 deletions src/Components/Line.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ class Line extends Component implements Collider {
opacity: opacity,
transparent: transparent,
});
this.name = "Line";

this.geometry = new LineGeometry();
if (arrowhead) {
Expand Down
Loading
Loading