import React, { Component } from 'react';
import ReactDOM from 'react-dom';

import Regression from './Regression';

ReactDOM.render(<Regression name="world" />, document.getElementById('root'));
{
  "name": "@plnkr/starter-react",
  "version": "1.0.2",
  "description": "React starter template",
  "dependencies": {
    "@tensorflow/tfjs": "^2.0.1",
    "plotly.js": "^1.54.7",
    "react": "^16.13.0",
    "react-dom": "^16.13.0",
    "react-plotly.js": "^2.4.0",
    "react-charts": "^2.0.0-beta.7",
    "recharts": "^1.8.5"
  },
  "plnkr": {
    "runtime": "system",
    "useHotReload": true
  }
}
import * as tf from '@tensorflow/tfjs';


export default class AbstractRegressionModel {
    /**
     * Model initializing
     *
     * @param width - range of x coordinates and used for data normalization
     * @param height - range of y coordinates and used for data normalization
     * @param learningRate -
     * @param expectedLoss - optional, stop tarining model when loss reach set expected loss
     */
    constructor(
        width,
        height,
        optimizerFunction = tf.train.sgd,
        maxEpochPerTrainSession = 100,
        learningRate = 0.1,
        expectedLoss = 0.001
    ) {
        this.width = width;
        this.height = height;
        this.optimizerFunction = optimizerFunction;
        this.expectedLoss = expectedLoss;
        this.learningRate = learningRate;
        this.maxEpochPerTrainSession = maxEpochPerTrainSession;

        this.initModelVariables();

        this.trainSession = 0;
        this.epochNumber = 0;
        this.history = [];
    }

    initModelVariables() {
        throw Error('Model variables should be defined')
    }

    f() {
        throw Error('Model should be defined')
    }

    xNormalization = xs => xs.map(x => x / this.width);
    yNormalization = ys => ys.map(y => y / this.height);
    yDenormalization = ys => ys.map(y => y * this.height);

    /**
     * Calculate loss function as root-mean-square deviation
     *
     * @param predictedValue - tensor1d - predicted values of calculated model
     * @param realValue - tensor1d - real value of experimental points
     */
    loss = (predictedValue, realValue) => {
        const loss = predictedValue.sub(realValue).square().mean();
        this.lossVal = loss.dataSync()[0];
        return loss;
    };

    /**
     * Train model until explicitly stop process via invocation of stop method
     * or loss achieve necessary accuracy, or train achieve max epoch value
     *
     * @param x - array of x coordinates
     * @param y - array of y coordinates
     * @param callback - optional, invoked after each training step
     */
    async train(x, y, callback) {
        const currentTrainSession = ++this.trainSession;
        this.lossVal = Number.POSITIVE_INFINITY;
        this.epochNumber = 0;
        this.history = [];

        // convert data into tensors
        const input = tf.tensor1d(this.xNormalization(x));
        const output = tf.tensor1d(this.yNormalization(y));

        while (
            currentTrainSession === this.trainSession
            && this.lossVal > this.expectedLoss
            && this.epochNumber <= this.maxEpochPerTrainSession
            ) {
            const optimizer = this.optimizerFunction(this.learningRate);
            optimizer.minimize(() => this.loss(this.f(input), output));
            this.history = [...this.history, {
                epoch: this.epochNumber,
                loss: this.lossVal
            }];
            callback && callback();
            this.epochNumber++;
            await tf.nextFrame();
        }
    }

    stop() {
        this.trainSession++;
    }

    /**
     * Predict value basing on trained model
     *  @param x - array of x coordinates
     *  @return Array({x: integer, y: integer}) - predicted values associated with input
     *
     * */
    predict(x) {
        const input = tf.tensor1d(this.xNormalization(x));
        const output = this.yDenormalization(this.f(input).arraySync());
        return output.map((y, i) => ({ x: x[i], y }));
    }
}
import AbstractRegressionModel from "./AbstractRegressionModel";

import * as tf from '@tensorflow/tfjs';

export default class LinearRegressionModel extends AbstractRegressionModel {
    initModelVariables() {
        this.k = tf.scalar(Math.random()).variable();
        this.b = tf.scalar(Math.random()).variable();
    }

    f = x => x.mul(this.k).add(this.b);
}
import React, { useRef, useEffect, useState } from 'react';

export default ({ width = 600, height = 600, points = [], changePoints, curvePoints = [] }) => {
    const canvasRef = useRef();

    const [ctx, changeCtx] = useState(null);
    const [containerPosition, changePosition] = useState({ x: 0, y: 0 });

    function updateCanvas() {
        if (ctx) {
            // container
            ctx.fillStyle = '#000000';
            ctx.fillRect(0, 0, width, height);

            //drawing points
            ctx.fillStyle = '#ffffff';
            points.forEach(({ x, y }) => {
                ctx.beginPath();
                ctx.arc(x, y, 5, 0, 2 * Math.PI);
                ctx.fill();
                ctx.closePath();
            });

            // drawing predicted curve
            ctx.strokeStyle = '#ffffff';
            curvePoints.forEach(({ x, y }, i, points) => {
                if (i+1 <= points.length-1){
                    ctx.beginPath();
                    ctx.moveTo(x, y);
                    ctx.lineTo(points[i+1].x, points[i+1].y);
                    ctx.stroke();
                    ctx.closePath();
                }
            });

        }
    }

    useEffect(() => {
        changeCtx(canvasRef.current.getContext('2d'));
    }, []);

    useEffect(() => {
        updateCanvas();
        const container = canvasRef.current;
        changePosition({ x: container.offsetLeft, y: container.offsetTop });
    }, [ctx, points, curvePoints]);

    return (
        <canvas
            ref={canvasRef}
            width={width}
            height={height}
            onClick={(e) => {
                changePoints([...points, {
                    x: e.clientX - containerPosition.x,
                    y: e.clientY - containerPosition.y
                }]);
            }}
        />
    )
}
import React from 'react';
import { LineChart, XAxis, YAxis, CartesianGrid, Line } from 'recharts';

export default ({ width = 400, height = 430, loss = [] }) => {
    return (
        <LineChart
            width={width}
            height={height}
            margin={{ top: 0, left: 0, bottom: 0, right: 0 }}
            data={loss}>
            <XAxis dataKey="epoch"/>
            <YAxis/>
            <CartesianGrid

            />
            <Line
                type="monotone"
                dataKey="loss"
                stroke="#8884d8"
                dot={false}/>
        </LineChart>
    )
}
import React, { useState, useEffect } from 'react';

import Canvas from './components/Canvas';
import LossPlot from './components/LossPlot';
import LinearRegressionModel from './models/LinearRegressionModel';

import './styles/style.css';

const WIDTH = 400;
const HEIGHT = 400;
const LINE_POINT_STEP = 5;

const predictedInput = Array.from({ length: WIDTH / LINE_POINT_STEP + 1 })
    .map((v, i) => i * LINE_POINT_STEP);

const model = new LinearRegressionModel(WIDTH, HEIGHT);

export default () => {
    const [points, changePoints] = useState([]);
    const [curvePoints, changeCurvePoints] = useState([]);
    const [lossHistory, changeLossHistory] = useState([]);

    useEffect(() => {
        if (points.length > 0) {
            const input = points.map(({ x }) => x);
            const output = points.map(({ y }) => y);

            model.train(input, output, () => {
                changeCurvePoints(() => model.predict(predictedInput));
                changeLossHistory(() => model.history);
            });
        }
    }, [points]);

    return (
        <div className="regression-low-level">
            <div className="regression-low-level__top">
                <div className="regression-low-level__workarea">
                    <div className="regression-low-level__canvas">
                        <Canvas
                            width={WIDTH}
                            height={HEIGHT}
                            points={points}
                            curvePoints={curvePoints}
                            changePoints={changePoints}
                        />
                    </div>
                    <div className="regression-low-level__toolbar">
                        <button
                            className="btn btn-red"
                            onClick={() => model.stop()}>Stop
                        </button>
                        <button
                            className="btn btn-yellow"
                            onClick={() => {
                                model.stop();
                                changePoints(() => []);
                                changeCurvePoints(() => []);
                            }}>Clear
                        </button>
                    </div>
                </div>
                <div className="regression-low-level__loss">
                    <LossPlot loss={lossHistory}/>
                </div>

            </div>
        </div>
    )
}
.regression-low-level {
  padding: 20px;
}

.regression-low-level__top {
  display: flex
}

.regression-low-level__workarea {
  display: flex;
  flex-direction: column;
}

.regression-low-level__loss {
  padding: 0 40px;
}

.regression-low-level__toolbar .btn {
  margin-right: 5px;
}

.btn {
  display: inline-block;
  font-weight: 400;
  color: #212529;
  text-align: center;
  vertical-align: middle;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
  background-color: transparent;
  border: 1px solid transparent;
  padding: .375rem .75rem;
  font-size: 1rem;
  line-height: 1.5;
  border-radius: .25rem;
  transition: color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out;
}

.btn:hover {
  opacity: 0.8;
}

.btn-red {
  background: red;
}

.btn-yellow {
  background: yellow;
}
<!DOCTYPE html>

<html>
  <head>
    <link rel="stylesheet" type="text/css" href="style.css" />


    <script src="index.js"></script>

  </head>

  <body>
    <div id="root"></div>
  </body>
</html>