Skip to content

Instantly share code, notes, and snippets.

@stemcstudio
Last active October 3, 2022 23:32
Show Gist options
  • Save stemcstudio/3ea4e381d4595d2e022a4db2b7b20f66 to your computer and use it in GitHub Desktop.
Save stemcstudio/3ea4e381d4595d2e022a4db2b7b20f66 to your computer and use it in GitHub Desktop.
JSXGraph and React

JsxGraph and React in STEMCstudio

Copyright (c) 2015-2022 David Geo Holmes.

When the component has only internal state, things are fairly simple. We update the state and the render method gets called.

How would a component share state with another component?

import * as React from 'react'
import { Component } from 'react'
import { JsxGraph, JsxGraphStep } from './JsxGraph'
import { steps } from './steps'
interface AppProps {
greeting: string
}
interface AppSpec {
name: string,
/**
* The steps in the construction of the JsxGraph
*/
steps: JsxGraphStep[]
}
export class App extends Component<AppProps, AppSpec> {
constructor(props: AppProps) {
super(props)
this.state = { name: 'JsxGraph', steps: [] }
}
override componentDidMount(): void {
console.log("App.componentDidMount()")
this.setState({
steps
})
}
override componentWillUnmount(): void {
console.log("App.componentWillUnmount()")
}
override render() {
console.log("App.render()")
return (
<div>
<h1>{`${this.props.greeting}, ${this.state.name}!`}</h1>
<JsxGraph id="board-1" steps={this.state.steps} />
</div>
)
}
}
<!DOCTYPE html>
<html>
<head>
<base href="/">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/distrib/jsxgraph.css">
<style>
body {
background-color: #dddddd;
}
</style>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/distrib/jsxgraphcore.min.js"></script>
<script src="https://www.stemcstudio.com:/assets/js/[email protected]/system.js"></script>
</head>
<body>
<script>
System.config({
"warnings": false,
"map": {
"jsxgraph": "https://cdn.jsdelivr.net/npm/[email protected]/distrib/jsxgraphcore.js",
"prop-types": "https://unpkg.com/[email protected]/umd/prop-types.js",
"react": "https://unpkg.com/[email protected]/umd/react.development.js",
"react-dom": "https://unpkg.com/[email protected]/umd/react-dom.development.js",
"react-dom/client": "https://unpkg.com/[email protected]/umd/react-dom.development.js"
}
});
</script>
<div id="container"></div>
<script>
System.register('./App.js', [
'react',
'./JsxGraph.js',
'./steps.js'
], function (exports_1, context_1) {
'use strict';
var __extends = this && this.__extends || function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf || { __proto__: [] } instanceof Array && function (d, b) {
d.__proto__ = b;
} || function (d, b) {
for (var p in b)
if (Object.prototype.hasOwnProperty.call(b, p))
d[p] = b[p];
};
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== 'function' && b !== null)
throw new TypeError('Class extends value ' + String(b) + ' is not a constructor or null');
extendStatics(d, b);
function __() {
this.constructor = d;
}
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
}();
var React, react_1, JsxGraph_1, steps_1, App;
var __moduleName = context_1 && context_1.id;
return {
setters: [
function (React_1) {
React = React_1;
react_1 = React_1;
},
function (JsxGraph_1_1) {
JsxGraph_1 = JsxGraph_1_1;
},
function (steps_1_1) {
steps_1 = steps_1_1;
}
],
execute: function () {
App = function (_super) {
__extends(App, _super);
function App(props) {
var _this = _super.call(this, props) || this;
_this.state = {
name: 'JsxGraph',
steps: []
};
return _this;
}
App.prototype.componentDidMount = function () {
console.log('App.componentDidMount()');
this.setState({ steps: steps_1.steps });
};
App.prototype.componentWillUnmount = function () {
console.log('App.componentWillUnmount()');
};
App.prototype.render = function () {
console.log('App.render()');
return React.createElement('div', null, React.createElement('h1', null, ''.concat(this.props.greeting, ', ').concat(this.state.name, '!')), React.createElement(JsxGraph_1.JsxGraph, {
id: 'board-1',
steps: this.state.steps
}));
};
return App;
}(react_1.Component);
exports_1('App', App);
}
};
});
System.register('./JsxGraph.js', ['react'], function (exports_1, context_1) {
'use strict';
var __extends = this && this.__extends || function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf || { __proto__: [] } instanceof Array && function (d, b) {
d.__proto__ = b;
} || function (d, b) {
for (var p in b)
if (Object.prototype.hasOwnProperty.call(b, p))
d[p] = b[p];
};
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== 'function' && b !== null)
throw new TypeError('Class extends value ' + String(b) + ' is not a constructor or null');
extendStatics(d, b);
function __() {
this.constructor = d;
}
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
}();
var React, react_1, JsxGraph;
var __moduleName = context_1 && context_1.id;
function createPoint(parents, attributes) {
var step = {
kind: 'point',
parents: parents,
attributes: attributes
};
return step;
}
exports_1('createPoint', createPoint);
function createLine(parents, attributes) {
var step = {
kind: 'line',
parents: parents,
attributes: attributes
};
return step;
}
exports_1('createLine', createLine);
function createSegment(parents, attributes) {
var step = {
kind: 'segment',
parents: parents,
attributes: attributes
};
return step;
}
exports_1('createSegment', createSegment);
return {
setters: [function (React_1) {
React = React_1;
react_1 = React_1;
}],
execute: function () {
JsxGraph = function (_super) {
__extends(JsxGraph, _super);
function JsxGraph(props) {
var _this = _super.call(this, props) || this;
_this.state = { seconds: 0 };
return _this;
}
JsxGraph.prototype.componentDidMount = function () {
console.log('JsxGraph.componentDidMount()');
this.board = JXG.JSXGraph.initBoard(this.props.id, {
axis: true,
boundingBox: [
-6,
6,
6,
-6
],
showCopyright: true,
showNavigation: false,
showScreenshot: false
});
};
JsxGraph.prototype.componentWillUnmount = function () {
console.log('JsxGraph.componentWillUnmount()');
if (this.board) {
JXG.JSXGraph.freeBoard(this.board);
this.board = void 0;
}
};
JsxGraph.prototype.tick = function () {
this.setState(function (state) {
return { seconds: state.seconds + 1 };
});
};
JsxGraph.prototype.render = function () {
console.log('JsxGraph.render()');
try {
if (this.board) {
var elements = {};
var _xlaRK = Date.now();
for (var _i = 0, _a = this.props.steps; _i < _a.length; _i++) {
if (Date.now() - _xlaRK > 1000) {
throw new Error('Infinite loop suspected after 1000 milliseconds.');
}
{
var step = _a[_i];
var element = this.board.create(step.kind, step.parents, step.attributes);
elements[element.name] = element;
}
}
this.board.update();
}
} catch (e) {
console.warn(e);
} finally {
return React.createElement('div', {
id: ''.concat(this.props.id),
className: 'jxgbox',
style: {
width: 500 + 'px',
height: 500 + 'px'
}
});
}
};
return JsxGraph;
}(react_1.Component);
exports_1('JsxGraph', JsxGraph);
}
};
});
System.register('./index.js', [
'react',
'react-dom/client',
'./App.js'
], function (exports_1, context_1) {
'use strict';
var React, client_1, App_1, container, root;
var __moduleName = context_1 && context_1.id;
return {
setters: [
function (React_1) {
React = React_1;
},
function (client_1_1) {
client_1 = client_1_1;
},
function (App_1_1) {
App_1 = App_1_1;
}
],
execute: function () {
container = document.getElementById('container');
root = client_1.createRoot(container);
root.render(React.createElement(App_1.App, { greeting: 'Hello' }));
window.onunload = function () {
root.unmount();
console.log('Goodbye!');
};
}
};
});
System.register('./steps.js', ['./JsxGraph.js'], function (exports_1, context_1) {
'use strict';
var JsxGraph_1, steps;
var __moduleName = context_1 && context_1.id;
return {
setters: [function (JsxGraph_1_1) {
JsxGraph_1 = JsxGraph_1_1;
}],
execute: function () {
exports_1('steps', steps = [
JsxGraph_1.createPoint([
0,
0
], {
color: 'red',
name: 'O'
}),
JsxGraph_1.createPoint([
3,
0
], {
color: 'red',
name: 'P'
}),
JsxGraph_1.createPoint([
3,
4
], {
color: 'red',
name: 'Q'
}),
JsxGraph_1.createLine([
'P',
'Q'
], { color: 'blue' }),
JsxGraph_1.createSegment([
'O',
'Q'
], {
color: 'gray',
lastArrow: true
})
]);
}
};
});
</script>
<script>
System.defaultJSExtensions = true
System.import('./index.js').catch(function(e) { console.error(e) })
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<base href='/'>
<link rel='stylesheet' href='style.css'>
</head>
<body>
<div id='container'></div>
</body>
</html>
import * as React from 'react'
import { createRoot } from 'react-dom/client'
import { App } from './App'
const container = document.getElementById('container') as HTMLElement
const root = createRoot(container)
root.render(<App greeting='Hello' />)
//
// Be a good citizen and cleanup when we are done.
//
window.onunload = function() {
root.unmount()
console.log("Goodbye!")
}
import * as React from 'react'
import { Component } from 'react'
export interface JsxGraphStep {
kind: 'point' | 'line' | 'segment'
parents: unknown[],
attributes: Record<string, unknown>
}
/**
* These are the (HTML) attributes of my JsxGraph tag.
* This is accessed by the component as this.props
*/
interface JsxGraphProps {
/**
* The identifier of the board div element.
*/
id: string,
/**
* The steps in the construction.
*/
steps: JsxGraphStep[]
}
/**
* The internal state of the component.
* When the state data changes, the markup will
* be updated by re-invoking render().
*/
interface JsxGraphSpec {
seconds: number
}
export class JsxGraph extends Component<JsxGraphProps, JsxGraphSpec> {
private board: JXG.Board | undefined
constructor(props: JsxGraphProps) {
super(props)
this.state = { seconds: 0 }
}
override componentDidMount(): void {
console.log("JsxGraph.componentDidMount()")
this.board = JXG.JSXGraph.initBoard(this.props.id, {
axis: true,
boundingBox: [-6, 6, 6, -6],
showCopyright: true,
showNavigation: false,
showScreenshot: false
})
}
override componentWillUnmount(): void {
console.log("JsxGraph.componentWillUnmount()")
if (this.board) {
JXG.JSXGraph.freeBoard(this.board)
this.board = void 0
}
}
tick() {
this.setState(state => ({
seconds: state.seconds + 1
}))
}
override render() {
console.log("JsxGraph.render()")
// console.log(`steps=>${JSON.stringify(this.props.steps, null, 2)}`)
try {
if (this.board) {
const elements: { [name: string]: JXG.GeometryElement } = {}
for (const step of this.props.steps) {
// TODO: Do we need a more generic create method on the JXG.Board?
const element: JXG.GeometryElement = this.board.create(step.kind as 'point', step.parents, step.attributes)
elements[element.name] = element
}
this.board.update()
}
} catch (e) {
console.warn(e)
} finally {
return <div id={`${this.props.id}`} className='jxgbox' style={{ width: 500 + 'px', height: 500 + 'px' }}></div>
}
}
}
/**
* Constructs a point.
* @param parents Determine the location of the point.
* @param attributes Determine the appearance of the point.
*/
export function createPoint(parents: [x: number, y: number], attributes: JXG.PointAttributes): JsxGraphStep {
const step: JsxGraphStep = {
kind: 'point',
parents,
// TODO: How to avoid having to do this?
// Is the problem with JXG.PointAttributes or Record<string,unkown>
attributes: attributes as Record<string, unknown>
}
return step
}
/**
* Constructs a line.
* @param parents Determine the location of the line.
* @param attributes Determine the appearance of the line.
*/
export function createLine(parents: [firstPointName: string, lastPointName: string], attributes: JXG.LineAttributes): JsxGraphStep {
const step: JsxGraphStep = {
kind: 'line',
parents,
// TODO: How to avoid having to do this?
// Is the problem with JXG.PointAttributes or Record<string,unkown>
attributes: attributes as Record<string, unknown>
}
return step
}
/**
* Constructs a line.
* @param parents Determine the location of the line.
* @param attributes Determine the appearance of the line.
*/
export function createSegment(parents: [firstPointName: string, lastPointName: string], attributes: JXG.SegmentAttributes): JsxGraphStep {
const step: JsxGraphStep = {
kind: 'segment',
parents,
// TODO: How to avoid having to do this?
// Is the problem with JXG.PointAttributes or Record<string,unkown>
attributes: attributes as Record<string, unknown>
}
return step
}
{
"description": "JSXGraph and React",
"dependencies": {
"csstype": "^3.0.10",
"jsxgraph": "^1.4.6",
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"linting": true,
"name": "playing-nicely-with-others",
"version": "1.0.0",
"author": "David Geo Holmes",
"hideReferenceFiles": true,
"keywords": [
"React",
"STEMCstudio",
"JSXGraph"
],
"hideConfigFiles": true
}
import {
createLine,
createPoint,
createSegment,
JsxGraphStep
} from './JsxGraph'
/**
* Steps used in the construction of the JsxGraph.
*/
export const steps: JsxGraphStep[] = [
createPoint([0, 0], { color: 'red', name: 'O' }),
createPoint([3, 0], { color: 'red', name: 'P' }),
createPoint([3, 4], { color: 'red', name: 'Q' }),
createLine(['P', 'Q'], { color: 'blue' }),
createSegment(['O', 'Q'], { color: 'gray', lastArrow: true })
]
body {
background-color: #dddddd;
}
{
"allowJs": false,
"declaration": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"jsx": "react",
"module": "system",
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"preserveConstEnums": true,
"removeComments": true,
"sourceMap": false,
"strict": true,
"strictNullChecks": true,
"suppressImplicitAnyIndexErrors": true,
"target": "es5",
"traceResolution": true
}
{
"rules": {
"array-type": [
true,
"array"
],
"curly": false,
"comment-format": [
true,
"check-space"
],
"eofline": true,
"forin": true,
"jsdoc-format": true,
"new-parens": true,
"no-conditional-assignment": false,
"no-consecutive-blank-lines": true,
"no-construct": true,
"no-for-in-array": true,
"no-inferrable-types": [
true
],
"no-magic-numbers": false,
"no-shadowed-variable": true,
"no-string-throw": true,
"no-trailing-whitespace": [
true,
"ignore-jsdoc"
],
"no-var-keyword": true,
"one-variable-per-declaration": [
true,
"ignore-for-loop"
],
"prefer-const": true,
"prefer-for-of": true,
"prefer-function-over-method": false,
"prefer-method-signature": true,
"radix": true,
"semicolon": [
true,
"never"
],
"trailing-comma": [
true,
{
"multiline": "never",
"singleline": "never"
}
],
"triple-equals": true,
"use-isnan": true
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment