Hago

Hago is a React library for plotting functions and data structures available via npmjs. The library grew out of a need to quickly generate diagrams during lectures and seminars.

Hago is built with TypeScript, KaTeX, D3, Matej Branksy's react-katex, and ThreeJS. Do support these projects — they provide convenient ways of communicating mathematics on the web.

Development

The modules displayed here have been tested and polished. Experimental modules can be viewed via Hago's Storybook, and all source code is publicly available on Github..

Function Plot

Below are a few function plots, rendered by the Plot module.

-10-8-6-4-20246810𝒙-10-8-6-4-20246810𝒚
-10-8-6-4-20246810𝒙-10-8-6-4-20246810𝒚
<Plot data={[{ f: (x) => 1/x }]}/>
<Plot data={[{ f: (x) => Math.tan(x) }]}/>

Styling is left to CSS, using the following classes:

CSSTarget
.hago_XAxisx{x}-axis.
.hago_XAxis textNumbers along x{x}-axis.
.hago_YAxisy{y}-axis.
.hago_YAxis textNumbers along y{y}-axis.
.hago_PlotPathPlot path (in the examples above, colored red).
.hago .Plot2figure container for the plot.

Integrals

Data for integrals can be passed into the Plot module:

<Plot
  data={[{
		f: (x) => x ** 3,
		integrate: [-3, 5, 'x']
	}]}
  domain={[-10, 10]}
  range={[-10, 10]}
/>
-10-8-6-4-20246810𝒙-10-8-6-4-20246810𝒚
<Plot
	data={[{
		f: (x) => Math.sqrt(x),
		integrate: [1, 5, 'x']
	}]}
	domain={[0, 10]}
	range={[0, 10]}
/>
012345678910𝒙012345678910𝒚
<Plot
	data={[{
		f: (x) => Math.cos(x),
		integrate: [-5, 5, 'x']
	}]}
	domain={[-5, 5]}
	range={[-5, 5]}
/>
-5-4-3-2-1012345𝒙-5-4-3-2-1012345𝒚

Domain and Range

The domain and range can be adjusted for transformations:

<Plot
	data={[
		{ f: (x) => -2*x*Math.E**(-1*x**2) },
		{ f: (x) => Math.sqrt(Math.PI)/2, dash: 3 },
		{ f: (x) => -Math.sqrt(Math.PI)/2, dash: 3 },
	]}
	domain={[-3.5, 3.5]}
	range={[-3.5, 3.5]}
/>
-2-1.5-1-0.500.511.52𝒙-2-1.5-1-0.500.511.52𝒚

Riemann Sums

Riemann sums can be rendered by initializing the riemann field.

<Plot
  data={[{
		f: (x) => Math.cos(x),
		riemann: { m:'left', dx:0.4, i:[-5, 5], f:'x' }
  }]}
  domain={[-5, 5]}
  range={[-5, 5]}
/>
-5-4-3-2-1012345𝒙-5-4-3-2-1012345𝒚

The m field determines which method to use in rendering the Riemann sum. By default, this is set to left (corresponding to the left Riemann sum). The other available option is right (corresponding to the right Riemann sum).

<Plot
  data={[{
		f: (x) => Math.cos(x),
		riemann: { m:'right', dx:0.4, i:[-5, 5], f:'x' }
  }]}
  domain={[-5, 5]}
  range={[-5, 5]}
/>
-5-4-3-2-1012345𝒙-5-4-3-2-1012345𝒚

The dx field determines the Δx,{\Delta x,} or, in graphic terms, the width of the rectangles. The lower dx is, the closer the Riemann sum looks to classical integration. In the example below, the dx is set to 0.04.

<Plot
  data={[{
		f: (x) => x ** 2,
		riemann: {
			m:'right',
			dx:0.04,
			i:[-2, 2],
			f:'x'
	}}]}
  domain={[-5, 5]}
  range={[-5, 5]}
/>
-5-4-3-2-1012345𝒙-5-4-3-2-1012345𝒚

The f field instructs Hago which function to render the Riemann sum with respect to. In the examples above, the sums are rendered with respect to f:x0{f: x \mapsto 0} (i.e., the x{x}-axis). To render the Riemann sum against a different function, a definition may be passed:

<Plot
  data={[{
			f: (x) => 0.3*((x-3)**2)+1,
      riemann: {
        f: (x) => -(0.3*(x-3)**2-3),
        m: 'left',
        i: [2,4],
        dx: 0.25,
    }},
    {f:(x) => -(0.3*((x-3)**2)-3)},
    {f:2, dash: 3},
    {f:4, dash: 3},
  ]}
  domain={[-1, 8]}
  range={[-1, 8]}
/>
-1012345678𝒙-1012345678𝒚

Points

Points can be rendered by passing point objects. The p field sets the coordinate; the label field sets the label; the dx field sets the label's x{x}-axis offset; and the dy field sets the y{y}-axis offset.

<Plot data={[
	{p:[2,2], label:'(2,2)', dx:5, dy:3},
	{p:[-1,3], label:'(-1,3)', dx:-30, dy:3},
	{p:[-5,-4], label:'(-5,-4)', dx:-30, dy:10},
	{p:[8,-6], label:'(8,-6)', dx:-30, dy:10}
]}/>
-10-50510𝒙-10-50510𝒚(2,2)(-1,3)(-5,-4)(8,-6)

Secant Lines

To render secant lines, initialize the secant field.

<Plot
 data={[{
	f: (x) => (x**2)-(2*x)+3,
	secant:{x0: 1, x1: 2}
 }]}
 domain={[-3,6]}
 range={[-8,30]}
/>
-3-2-10123456𝒙-5051015202530𝒚

By definition, two points are needed to generate a secant line. The x0 field sets the x{x} argument for the first point, and the x1 field sets the argument for the second point. The points themselves can be rendered along with their coordinates, as well as an equation for the secant line:

<Plot
 data={[{
	f: (x) => (x**2)*x+(0.3),
	secant: {
		 x0: 1, x1: 5,
		 renderPoints: true,
		 renderFormula: true
 }}]}
 domain={[-3,6]}
 range={[-8,30]}
/>
-3-2-10123456𝒙-5051015202530𝒚(1, 1.3)(2, 8.3)

The renderPoints field takes a Boolean. If set to true, the two points passed are rendered. If set to false, neither point is rendered. The default value is false. The renderFormula operates similarly. If set to true, the secant line's approximated equation is rendered. The default is false — no rendering.

A disclaimer on the renderFormula field: Continuous functions, by nature, can only be approximated by computers. Accordingly, the slope rendered by Plot is an approximated slope. Specifically, Plot will only read up to eight decimal places before passing control to the next module. Using those eight decimal places, Plot will compute the lowest possible fraction using continued fractions. Hago is not intended to be a substitute for careful mathematical reasoning.

Parametric Plots

The Plot module can render parametric plots by initializing — in the function object — the x and y fields instead of the usual f field.

<Plot data={[{
	x:(t)=>Math.sin(t)*(Math.E**Math.cos(t)-2*Math.cos(4*t)-Math.sin(t/12)**5),
	y:(t)=>Math.cos(t)*(Math.E**Math.cos(t)-2*Math.cos(4*t)-Math.sin(t/12)**5)
}]}/>
-10-8-6-4-20246810𝒙-10-8-6-4-20246810𝒚
x(t)=sint(ecost2cos4tsin5(t12))y(t)=cost(ecost2cos4tsin5(t12))x(t)=\sin{t}\left(e^{\cos{t}}-2\cos 4t-\sin^5\left(\dfrac{t}{12}\right)\right) \\[1em] y(t)=\cos{t}\left(e^{\cos{t}}-2\cos 4t-\sin^5\left(\dfrac{t}{12}\right)\right)

Initializing x and y alerts the module that the plot is parametric, so the module will fail if f, x, and y are initialized in the same object. If a Cartesian plot must be rendered as well, provide the Cartesian plot in a separate object:

<Plot data={[
	{
		x:(t) => Math.sin(t)*(Math.E**Math.cos(t)-2*Math.cos(4*t)-Math.sin(t/12)**5),
		y:(t) => Math.cos(t)*(Math.E**Math.cos(t)-2*Math.cos(4*t)-Math.sin(t/12)**5)
	},
	{f:(x) => Math.cos(x)}
]}/>
-10-8-6-4-20246810𝒙-10-8-6-4-20246810𝒚
x(t)=sint(ecost2cos4tsin5(t12))y(t)=cost(ecost2cos4tsin5(t12))f(x)=cos(x)x(t)=\sin{t}\left(e^{\cos{t}}-2\cos 4t-\sin^5\left(\dfrac{t}{12}\right)\right) \\[1em] y(t)=\cos{t}\left(e^{\cos{t}}-2\cos 4t-\sin^5\left(\dfrac{t}{12}\right)\right) \\[1em] f(x) = \cos(x)

Polar Plots

Functions can be plotted with polar coordinates using the Polar module:

<Polar data={[{f:(t) => Math.sin(2*t)*Math.cos(2*t)}]}/>
0.10.20.30.40.530°60°90°120°150°180°210°240°270°300°330°

Multivariable Plots

To plot functions in R3,{\reals^3,} use the Plot3D module.

<Plot3D z={(x, y) => Math.sin(Math.sqrt(x ** 2 + y ** 2))} />
z(x,y)=sin(x2+y2) z(x,y) = \sin \left( \sqrt{x^2 + y^2} \right)

Arrays

Arrays can be rendered with the Arr module.

<Arr data={[3,7,9,1,8]}/>

Pointers to a particular element can be rendered by initializing the pointers object. This is an object of the form {i:b},{\set{i:b},} where i{i} is an index and b{b} is the pointer variable.

<Arr
	data={[3,7,9,1,8,2,11]}
	pointers={{0: 'i', 5: 'j'}}
/>

Lists

The List module renders linked-lists.

<List data={[2,9,8,4,1,7]}/>

Like the array field, pointers can be added by passing a pointer object, alongside a string:

<List
	data={[2,9,8,4,1,7]}
	pointers={{4:'p',5:'t'}}
/>

Circular Queues

Circular queues, or circular buffers, can be rendered with the CircularQueue module.

<CircularQueue
	data={['b','a','d','e','f','n']}
/>
badefn

Lattice

Lattices can be rendered with the Lattice module.

<Lattice 
	data={[
		[0,0],[1,0],[1,1],[1,2],
		[1,3],[1,4],[2,4],[2,5],
		[3,5],[3,6],[4,6],[4,7],
		[5,7],[6,7],[7,7],[7,8],
		[8,8],[8,9],[9,9]
	]}
/>

Trees

Hago can render various trees.

<Tree data={[
	[20,''], [15,20],
	[18,20], [25,20],
	[21,25], [26,25],
	[24,25], [14,15],
	[19,15], [16,15],
	[11,14], [10,14],
	[13,14]]}
id="Tree1"
/>
20151825141916212624111013

The Tree component will render a basic tree. Let c,{c,} p,{p,} and r{r} be substantive data. The data field takes an array of tuples of the form [c,p],{[c,p],} where c{c} is a child node, and p{p} is its parent. The first node must be of the form [r,s],{[r,s],} where s{s} is the empty string ''. This form is used to indicate that the node [r,s]{[r,s]} is the tree's root.

Because binary trees are the most common tree type, the preceding syntax can be forgone to render binary trees:

<Tree
  data={[
    [0, [2, 3]],
    [2, [17, 19]],
    [3, [36, 7]],
    [17, [25, null]],
  ]}
/>
023171936725

In this case, the node syntax is [p,[c1,c2]],{[p,[c_1,c_2]],} where p{p} is a parent node, c1{c_1} is the left-child of p,{p,} and c2{c_2} is the right-child of p.{p.} To mark either a left- or right-child absent, set their values to null.

AVL Trees

AVL trees are rendered separately from trees.

<AVLTree
  data={[
    { c: 'A', p: '' },
    { c: 'B', p: 'A' },
    { c: 'C', p: 'A' },
    { c: 'E', p: 'B' },
    { c: 'N', p: 'B' },
    { c: 'F', p: 'C' },
    { c: 'J', p: 'C' },
    { c: 'N', p: 'J' },
  ]}
/>
-13A01B-12C00E00N00F01J00N

Numbers atop the nodes correspond to height-balance factors, and numbers beneath to heights.

Graphs

To render graphs, use the Graph module.

124567891112
<Graph data={[
	[1, 2],[2, 4],[5, 6],
	[7, 6],[6, 8],[8, 9],
	[11, 2],[0, 9],[12, 2]
]}/>