Skip to content

Commit

Permalink
Merge branch 'master' into makeBarDiagramComponent
Browse files Browse the repository at this point in the history
  • Loading branch information
jonasdeluna authored Jul 4, 2024
2 parents f4bbb9e + 9fa6c62 commit 11c6e47
Show file tree
Hide file tree
Showing 22 changed files with 1,063 additions and 30 deletions.
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
246 changes: 246 additions & 0 deletions src/Components/Derived/Node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
import { Color, MeshBasicMaterial } from "three";
import { LineMaterial } from "three-fatline";
import Circle, { CircleOptions, defaultShapeOptions } from "../Circle";
import Line from "../Line";
import Text from "../Text";

export type NodeOptions = CircleOptions & {
label?: string;
};

const defaultNodeOptions: NodeOptions = {
...defaultShapeOptions,
label: "",
};

type Edge = {
node: Node;
line: Line;
weight?: number;
};

class Node extends Circle {
adjacencyList: Edge[];
label?: Text;

constructor(
x = 0,
y = 0,
radius = 5,
adjacencyList: Edge[] = [],
options?: NodeOptions
) {
super(x, y, radius, options);
const { label } = { ...defaultNodeOptions, ...options };
if (label) {
this.label = new Text(label, {
position: [0, 0],
fontSize: this.calculateFontSize(label, radius),
anchorX: "center",
anchorY: "middle",
responsiveScale: false,
});
this.add(this.label);
}
this.adjacencyList = [...adjacencyList];
}

/**
* Help function to avoid node label from going outside of the node
*
* @param text - Node label
* @param radius - Radius of the node
* @returns FontSize which will keep the node label within the node
*/
private calculateFontSize(text: string, radius: number): number {
const maxDiameter = radius * 2;
let fontSize = radius;

while (fontSize > 0 && text.length * fontSize * 0.6 > maxDiameter) {
fontSize -= 0.25;
}

return fontSize;
}

isAdjacentTo(node: Node): boolean {
return this.adjacencyList.map((edge) => edge.node).includes(node);
}

/**
* Adds an edge from this node to the other node given and updates adjacencyList accordingly.
* Calculations are made in order to have the edge go to/from the circle arc.
*
* @param other - Node to connect with edge
* @param directed - Booleean for whether the edge is directed (directed edges will also have curve)
* @param value - Number for eeight/value of the edge
*/
connectTo(other: Node, directed = false, value?: number): void {
if (!this.isAdjacentTo(other) && !(!directed && other.isAdjacentTo(this))) {
const dx = other.position.x - this.position.x;
const dy = other.position.y - this.position.y;
const dist = Math.sqrt(dx * dx + dy * dy);
const cos = Math.abs(dx) / dist;
const sin = Math.abs(dy) / dist;

if (dx >= 0 && dy >= 0) {
const outlineX1 = cos * this.radius;
const outlineY1 = sin * this.radius;
const outlineX2 = other.position.x - cos * other.radius;
const outlineY2 = other.position.y - sin * other.radius;

const line = new Line(
[outlineX1, outlineY1],
[outlineX2 - this.position.x, outlineY2 - this.position.y],
{
arrowhead: directed,
curve: directed ? 2 : 0,
label: value !== undefined ? value.toString() : "",
}
);
this.adjacencyList.push({ node: other, line: line, weight: value });
this.add(line);
} else if (dx >= 0 && dy < 0) {
const outlineX1 = cos * this.radius;
const outlineY1 = -sin * this.radius;
const outlineX2 = other.position.x - cos * other.radius;
const outlineY2 = other.position.y + sin * other.radius;

const line = new Line(
[outlineX1, outlineY1],
[outlineX2 - this.position.x, outlineY2 - this.position.y],
{
arrowhead: directed,
curve: directed ? 2 : 0,
label: value !== undefined ? value.toString() : "",
}
);
this.adjacencyList.push({ node: other, line: line, weight: value });
this.add(line);
} else if (dx < 0 && dy < 0) {
const outlineX1 = -cos * this.radius;
const outlineY1 = -sin * this.radius;
const outlineX2 = other.position.x + cos * other.radius;
const outlineY2 = other.position.y + sin * other.radius;

const line = new Line(
[outlineX1, outlineY1],
[outlineX2 - this.position.x, outlineY2 - this.position.y],
{
arrowhead: directed,
curve: directed ? 2 : 0,
label: value !== undefined ? value.toString() : "",
}
);
this.adjacencyList.push({ node: other, line: line, weight: value });
this.add(line);
} else {
const outlineX1 = -cos * this.radius;
const outlineY1 = sin * this.radius;
const outlineX2 = other.position.x + cos * other.radius;
const outlineY2 = other.position.y - sin * other.radius;

const line = new Line(
[outlineX1, outlineY1],
[outlineX2 - this.position.x, outlineY2 - this.position.y],
{
arrowhead: directed,
curve: directed ? 2 : 0,
label: value !== undefined ? value.toString() : "",
}
);
this.adjacencyList.push({ node: other, line: line, weight: value });
this.add(line);
}

if (!directed) {
other.connectTo(this, false);
}
}
}

disconnectFrom(other: Node): void {
const index = this.adjacencyList.map((edge) => edge.node).indexOf(other);
if (index > -1) {
const directed = this.adjacencyList[index].line.arrowhead;
this.remove(this.adjacencyList[index].line);
this.adjacencyList.splice(index, 1);
if (!directed) {
other.disconnectFrom(this);
}
}
}

getEdgeWeight(other: Node): number | undefined {
const index = this.adjacencyList.map((edge) => edge.node).indexOf(other);
if (index > -1) {
return this.adjacencyList[index].weight;
} else {
return undefined;
}
}

static addEdgeWeight(node: Node, other: Node, value: number): void {
const index = node.adjacencyList.map((edge) => edge.node).indexOf(other);
if (index > -1) {
const edge = node.adjacencyList[index];
if (edge.weight !== undefined) {
edge.weight += value;
} else {
edge.weight = value;
}
edge.line.setLabel(edge.weight.toString());
}
}

static setEdgeWeight(node: Node, other: Node, value: number): void {
const index = node.adjacencyList.map((edge) => edge.node).indexOf(other);
if (index > -1) {
node.adjacencyList[index].weight = value;
node.adjacencyList[index].line.setLabel(value.toString());
}
}

setLabel(label: string): void {
if (this.label !== undefined) {
this.label.setText(label);
this.label.setFontSize(this.calculateFontSize(label, this.radius));
} else {
this.label = new Text(label, {
position: [0, 0],
fontSize: this.calculateFontSize(label, this.radius),
anchorX: "center",
anchorY: "middle",
responsiveScale: false,
});
this.add(this.label);
}
}

static setColor(node: Node, color: number): void {
node.material = new MeshBasicMaterial({
color: color,
transparent: true,
opacity: 0.5,
});
}

static setEdgeColor(node: Node, other: Node, color: number): void {
const index = node.adjacencyList.map((edge) => edge.node).indexOf(other);
if (index > -1) {
(node.adjacencyList[index].line.material as LineMaterial).color =
new Color(color);
}
}

getEdge(other: Node): Edge | null {
const index = this.adjacencyList.map((edge) => edge.node).indexOf(other);
if (index > -1) {
return this.adjacencyList[index];
} else {
return null;
}
}
}

export default Node;
Loading

0 comments on commit 11c6e47

Please sign in to comment.