0.0.10 • Published 1 year ago

react-dynamic-geometry v0.0.10

Weekly downloads
-
License
MIT
Repository
github
Last release
1 year ago

React Dynamic Geometry

A dynamic geometry library built on top of React.js, Jotai, and rendered with SVG. Easily create geometry "boards" with dynamic elements. Jotai is used for fine-grained state updates to keep dynamic boards performant.

Here's a cute sample. The following code:

<GeometryBoard>
  {(build) => {
    build("Axes", {});

    const A = build("Point", { x: 2, y: 2, cfg: { label: "A" } });
    const B = build("Point", { x: -3, y: 3 });
    const O = build("Point", { x: -5, y: 8 });

    build("Line", { start: A, end: B });
    build("Circle", { center: B, radius: A.x });
    build("Polygon", {
      vertices: [A, B, O],
      cfg: { fill: "red", fillOpacity: 0.5 },
    });
  }}
</GeometryBoard>

Generates the following dynamic geometry board.

Sample of dynamic geometry board

Setup

If you're working inside a React project, all you need to do is install this library.

# For NPM
npm install react-dynamic-geometry

# For Yarn
yarn add react-dynamic-geometry

Once the library is installed, import the GeometryBoard component from this library, and go to town!

import { GeometryBoard } from 'react-dynamic-geometry';

const MyFirstBoard = () => (
  <GeometryBoard>
    {(build) => {
      build("Point", { x: 3, y: 5, cfg: { label: "Hello world!" } });
    }}
  </GeometryBoard>
)

API

This library exports a single component, GeometryBoard, that takes a "builder" function as its sole child. The library exposes a handful of helpers for you to build dynamic geometry boards (via this builder function).

Here's a small example to give you a taste of how to use the library:

<GeometryBoard>
  {(build) => {
    const A = build("Point", { x: 0, y: 0 });
    const B = build("Point", { x: 3, y: 5 });
    build("Line", { start: A, end: B });
  }}
</GeometryBoard>

The builder function is strongly-typed, so your editor should give you some intellisense about what types of elements you can construct, and what their options are. The following documentation attempts to do this explicitly.

Axes

The Axes element will create a set of axes on the board. We're still working on adding options for this element, so for now, this is as spicy as it gets:

<GeometryBoard>
  {(build) => {
    build("Axes", {});
  }}
</GeometryBoard>

Point

The Point element will create a point on the board. Here's a quick example:

<GeometryBoard>
  {(build) => {
    build("Axes", {});

    const A = build("Point", { x: 3, y: 4 });
    build("Point", {
      x: -4,
      y: A.y,
      cfg: { label: "B", color: "red" },
    });
  }}
</GeometryBoard>

which produces the following:

Example of Point, one point is free, the other is constrained by the first point's coordinates

Options

The build signature for creating a Point element is build("Point", options: { x, y, cfg? }): Point with options listed below.

OptionTypeRequired?Description
xnumber or NumberAtomx coordinate of the point. If provided a number, value is a "free" atom which can be updated via drag. If provided a NumberAtom, coordinate cannot be change via drag.
ynumber or NumberAtomy coordinate of the point. Same behavior as x, but with the y coordinate
cfg.sizenumberSize of the point in SVG units. Defaults to 1.5.
cfg.colorstringColor of the point.
cfg.labelstringLabel to add to the point.
cfg.hideLabelbooleanSet to true to hide the label completely.
cfg.hiddenbooleanSet to true to hide the point completely.

Line

The Line element will create a line on the board, passing through two provided points (start and end). Here's an example:

<GeometryBoard>
  {(build) => {
    const A = build("Point", { x: -5, y: 5 });
    const B = build("Point", { x: 3, y: -4 });
    const C = build("Point", { x: -3, y: -5 });

    build("Line", { start: A, end: B, cfg: { stroke: "purple" } });
    build("Line", { start: A, end: C, cfg: { isSegment: true } });
  }}
</GeometryBoard>

which produces the following:

Example of Line passing through two points

Options

The build signature for creating a Line element is build("Line", options: { start, end, cfg? }): Line with options listed below.

OptionTypeRequired?Description
startPoint"Starting" point for the line (it passes through this point).
endPoint"Ending" point for the line (it passes through this point).
cfgPartial<React.SVGProps<SVGLineElement>> & { isSegment?: boolean; }SVG properties for the line primitive element used to draw the Line.
cfg?.isSegmentbooleanPass true to render as a line segment.

Circle

The Circle element will create a circle on the board, centered at a provided point and with a specified radius. If a number is provided as the radius, that will be used as the radius. If a point is provided as the radius, the circle will pass through that point. Here's an example:

<GeometryBoard>
  {(build) => {
    const A = build("Point", { x: 1, y: 1 });
    build("Circle", {
      center: A,
      radius: 3,
      cfg: { fill: "blue", fillOpacity: 0.2 },
    });

    const B = build("Point", { x: 4, y: 5 });
    build("Circle", {
      center: B,
      radius: A,
      cfg: { stroke: "purple" },
    });
  }}
</GeometryBoard>

which produces the following:

Example of Circle command

Options

The build signature for creating a Circle element is build("Circle", options: { center, radius, cfg? }): Circle with options listed below.

OptionTypeRequired?Description
centerPointCenter point of the circle.
radiusPoint or number or NumberAtomIf number-like, provides the radius of the circle. If Point, circle will pass through this point.
cfgPartial<React.SVGProps<SVGEllipseElement>>SVG properties for the ellipse primitive element used to draw the Circle.

Polygon

The Polygon element will create a polygon on the board with the provided vertices. The order of the vertices does not matter, the library will try to create a polygon with non-intersecting sides. Here's an example:

<GeometryBoard>
  {(build) => {
    const getRandomCoord = () => -9 + Math.round(18 * Math.random());
    const vertices = [1, 2, 3, 4, 5].map(() =>
      build("Point", { x: getRandomCoord(), y: getRandomCoord() }),
    );

    build("Polygon", {
      vertices,
      cfg: { fill: "green", fillOpacity: 0.2 },
    });
  }}
</GeometryBoard>

which produces the following:

Example of Polygon command

Options

The build signature for creating a Polygon element is build("Polygon", options: { vertices, cfg? }): Polygon with options listed below.

OptionTypeRequired?Description
verticesPoint[]Array of Point elements that define the vertices of the polygon.
cfgPartial<React.SVGProps<SVGPathElement>>SVG properties for the path primitive element used to draw the Polygon.

RegularPolygon

The RegularPolygon element will create a regular polygon on the board with a provided number of sides, and having vertices containing the two provided points. Here's an example:

<GeometryBoard>
  {(build) => {
    const A = build("Point", { x: -2, y: -5, cfg: { label: "A" } });
    const B = build("Point", { x: 3, y: -2, cfg: { label: "B" } });
    
    build("RegularPolygon", { points: [A, B], numSides: 5 });
  }}
</GeometryBoard>

which produces the following:

Example of Regular Polygon command

Options

The build signature for creating a RegularPolygon element is build("RegularPolygon", options: { points, cfg? }): Polygon with options listed below.

OptionTypeRequired?Description
Points[Point, Point]Two of the vertices the Regular Polygon should have.
cfgSame as Polygon's configuration optionsSVG properties for the path primitive element used to draw the Regular Polygon.

FunctionGraph

The FunctionGraph element will create a graph of a function. You must provide the function definition to plot, an optionally provide the start (a) and end (b) x-values for the plot. Here's an example:

<GeometryBoard>
  {(build) => {
    build("Axes", {});

    build("FunctionGraph", {
      fn: (x) => 3 * Math.sin(x),
      a: -2 * Math.PI,
      b: 2 * Math.PI,
      cfg: { stroke: "blue" },
    });

    build("FunctionGraph", {
      fn: (x) => (x <= 0 ? x : 0.3 * x * x),
      cfg: { stroke: "red" },
    });
  }}
</GeometryBoard>

which produces the following:

Example of FunctionGraph command

Options

The build signature for creating a FunctionGraph element is build("FunctionGraph", options: { fn, a?, b? cfg? }): FunctionGraph with options listed below.

OptionTypeRequired?Description
fn(x: number) => numberFunction definition for the plot.
anumber or NumberAtomStarting x-value for the plot.
bnumber or NumberAtomEnding x-value for the plot.
cfgPartial<React.SVGProps<SVGPathElement>>SVG properties for the path primitive element used to draw the FunctionGraph.

ParallelLine

The ParallelLine element will create a line that is parallel to another line that you provide, passing through a point that you provide. Here's an example:

<GeometryBoard>
  {(build) => {
    const A = build("Point", { x: -3, y: 4 });
    const B = build("Point", { x: 5, y: -2 });
    const C = build("Point", { x: 4, y: 5 });
    const L = build("Line", { start: A, end: B, cfg: { stroke: "blue" } });

    build("ParallelLine", {
      parallelTo: L,
      passesThrough: C,
      cfg: { stroke: "red" },
    });
  }}
</GeometryBoard>

which produces the following:

Example of ParallelLine command

Options

The build signature for creating a ParallelLine element is build("ParallelLine", options: { parallelTo, passesThrough, cfg? }): Line with options listed below.

OptionTypeRequired?Description
parallelToLineLine that this line will be parallel to.
passesThroughPointPoint that the parallel line will pass through.
cfgPartial<React.SVGProps<SVGLineElement>>SVG properties for the line primitive element used to draw the ParallelLine.

PerpendicularLine

The PerpendicularLine element will create a line that is perpendicular to another line that you provide, passing through a point that you provide. Here's an example:

<GeometryBoard>
  {(build) => {
    const A = build("Point", { x: -3, y: 4 });
    const B = build("Point", { x: 5, y: -2 });
    const C = build("Point", { x: 4, y: 5 });
    const L = build("Line", { start: A, end: B, cfg: { stroke: "blue" } });

    build("PerpendicularLine", {
      perpendicularTo: L,
      passesThrough: C,
      cfg: { stroke: "red" },
    });
  }}
</GeometryBoard>

which produces the following:

Example of PerpendicularLine command

Options

The build signature for creating a PerpendicularLine element is build("PerpendicularLine", options: { perpendicularTo, passesThrough, cfg? }): Line with options listed below.

OptionTypeRequired?Description
perpendicularToLineLine that this line will be perpendicular to.
passesThroughPointPoint that the perpendicular line will pass through.
cfgPartial<React.SVGProps<SVGLineElement>>SVG properties for the line primitive element used to draw the PerpendicularLine.

Midpoint

The Midpoint element will create a midpoint between two provided points. Here's an example:

<GeometryBoard>
  {(build) => {
    build("Axes", {});
    const A = build("Point", { x: -2, y: -3, cfg: { label: "A" } });
    const B = build("Point", { x: 5, y: 7, cfg: { label: "B" } });

    build("Line", { start: A, end: B, cfg: { isSegment: true } });
    build("Midpoint", { start: A, end: B, cfg: { label: "Midpoint" } });
  }}
</GeometryBoard>

which produces the following:

Example of Midpoint command

Options

The build signature for creating a Midpoint element is build("Midpoint", options: { start, end, cfg? }): Point with options listed below.

OptionTypeRequired?Description
startPointFirst point.
endPointSecond point.
cfgSame as Point element's cfg option.Configuration for display of the point.

Incenter

The Incenter element will create the incenter of the triangle created by the three provided points. Here's an example:

<GeometryBoard>
  {(build) => {
    const A = build("Point", { x: -5, y: -3 });
    const B = build("Point", { x: 6, y: -3 });
    const C = build("Point", { x: -4, y: 5 });
    build("Polygon", { vertices: [A, B, C] });

    build("Incenter", { points: [A, B, C], cfg: { label: "Incenter" } });
  }}
</GeometryBoard>

which produces the following:

Example of Incenter command

Options

The build signature for creating a Incenter element is build("Incenter", options: { points, cfg? }): Point with options listed below.

OptionTypeRequired?Description
points[Point, Point, Point]Three points that define the triangle used to generate the incenter.
cfgSame as Point element's cfg option.Configuration for display of the point.

Incircle

The Incircle element will create the incircle of the triangle created by the three provided points. Here's an example:

<GeometryBoard>
  {(build) => {
    const A = build("Point", { x: -5, y: -3 });
    const B = build("Point", { x: 6, y: -3 });
    const C = build("Point", { x: -4, y: 5 });
    build("Polygon", { vertices: [A, B, C] });

    build("Incircle", {
      points: [A, B, C],
      cfg: { fill: "blue", fillOpacity: 0.2 },
    });
  }}
</GeometryBoard>

which produces the following:

Example of Incircle command

Options

The build signature for creating a Incircle element is build("Incircle", options: { points, cfg? }): Circle with options listed below.

OptionTypeRequired?Description
points[Point, Point, Point]Three points that define the triangle used to generate the incenter.
cfgSame as Circle element's cfg option.Configuration for display of the ellipse SVG primitive.

LineIntersection

The LineIntersection element will create the interesection point of the two provided lines. Here's an example:

<GeometryBoard>
  {(build) => {
    const A = build("Point", { x: -3, y: -3, cfg: { label: "A" } });
    const B = build("Point", { x: 4, y: 6, cfg: { label: "B" } });
    const L1 = build("Line", { start: A, end: B });

    const C = build("Point", { x: -3, y: 5, cfg: { label: "C" } });
    const D = build("Point", { x: 8, y: 3, cfg: { label: "D" } });
    const L2 = build("Line", { start: C, end: D });

    build("LineIntersection", {
      line1: L1,
      line2: L2,
      cfg: { label: "I", color: "red" },
    });
  }}
</GeometryBoard>

which produces the following:

Example of LineIntersection command

Options

The build signature for creating a LineIntersection element is build("LineIntersection", options: { line1, line2, cfg? }): Line with options listed below.

OptionTypeRequired?Description
line1LineFirst of the two lines.
line2LineSecond of the two lines.
cfgSame as Point element's cfg option.Configuration for display of the circle SVG primitive.

PerpendicularBisector

The PerpendicularBisector element will create the perpendicular bisector for the line segment formed between the two provided points. Here's an example:

<GeometryBoard>
  {(build) => {
    const A = build("Point", { x: -3, y: 4, cfg: { label: "A" } });
    const B = build("Point", { x: 5, y: -2, cfg: { label: "B" } });
    build("Line", { start: A, end: B, cfg: { isSegment: true } });

    build("PerpendicularBisector", {
      start: A,
      end: B,
      cfg: { stroke: "red" },
    });
  }}
</GeometryBoard>

which produces the following:

Example of Perpendicular Bisector command

Options

The build signature for creating a PerpendicularBisector element is build("PerpendicularBisector", options: { start, end, cfg? }): Line with options listed below.

OptionTypeRequired?Description
startPointFirst of the two points that form the line segment.
endPointSecond of the two points that form the line segment.
cfgSame as Line element's cfg option.Configuration for display of the path SVG primitive.

Feedback Welcome

Does this thing interest you? Hit me up! I want to hear your ideas.

TODO:

  • Testing setup...
  • GitHub actions for publishing to NPM on commits to main?
  • Customizable axes

Elements to Create

  • Arc?
  • Bisector?
  • Circumcircle and Circumcenter?
  • Midpoint
  • Center of Polygon (whatever that's called)
  • Function graph/curve
  • Parabola
  • Perpendicular
  • Reflection across line
  • Regular Polygon
  • Sector
  • Glider point to curve or object? That seems hard...
0.0.10

1 year ago

0.0.9

1 year ago

0.0.3

3 years ago

0.0.8

3 years ago

0.0.5

3 years ago

0.0.4

3 years ago

0.0.7

3 years ago

0.0.6

3 years ago

0.0.2

3 years ago

0.0.1

3 years ago