From 42e4afbed87af1a296cb9c774c1ca7ee4c1ab504 Mon Sep 17 00:00:00 2001 From: Markus Grigull Date: Tue, 14 Mar 2017 19:00:13 +0100 Subject: [PATCH] Increase widget performance Make rest-api more universal usable --- src/api/rest-api.js | 15 +-- src/components/widget-plot.js | 110 ++++++++++-------- src/components/widget-value.js | 31 +++-- src/containers/visualization.js | 4 +- src/containers/widget.js | 4 +- src/data-managers/rest-data-manager.js | 16 ++- .../simulator-data-data-manager.js | 6 + src/stores/simulator-data-store.js | 37 +++--- 8 files changed, 135 insertions(+), 88 deletions(-) diff --git a/src/api/rest-api.js b/src/api/rest-api.js index 40329c2..d01e996 100644 --- a/src/api/rest-api.js +++ b/src/api/rest-api.js @@ -10,17 +10,10 @@ import request from 'superagent/lib/client'; import Promise from 'es6-promise'; -const API_URL = 'http://localhost:4000/api/v1'; - -function makeURL(part) { - // TODO: Add / if missing at front of part - return API_URL + part; -} - class RestAPI { get(url) { return new Promise(function (resolve, reject) { - request.get(makeURL(url)).end(function (error, res) { + request.get(url).set('Access-Control-Allow-Origin', '*').end(function (error, res) { if (res == null || res.status !== 200) { reject(error); } else { @@ -32,7 +25,7 @@ class RestAPI { post(url, body) { return new Promise(function (resolve, reject) { - request.post(makeURL(url)).send(body).end(function (error, res) { + request.post(url).send(body).end(function (error, res) { if (res == null || res.status !== 200) { reject(error); } else { @@ -44,7 +37,7 @@ class RestAPI { delete(url) { return new Promise(function (resolve, reject) { - request.delete(makeURL(url)).end(function (error, res) { + request.delete(url).end(function (error, res) { if (res == null || res.status !== 200) { reject(error); } else { @@ -56,7 +49,7 @@ class RestAPI { put(url, body) { return new Promise(function (resolve, reject) { - request.put(makeURL(url)).send(body).end(function (error, res) { + request.put(url).send(body).end(function (error, res) { if (res == null || res.status !== 200) { reject(error); } else { diff --git a/src/components/widget-plot.js b/src/components/widget-plot.js index fad5f0c..140a0f6 100644 --- a/src/components/widget-plot.js +++ b/src/components/widget-plot.js @@ -11,55 +11,73 @@ import React, { Component } from 'react'; import { LineChart } from 'rd3'; class WidgetPlot extends Component { + constructor(props) { + super(props); + + this.state = { + values: [], + firstTimestamp: 0, + latestTimestamp: 0, + sequence: null + }; + } + + componentWillReceiveProps(nextProps) { + // check data + const simulator = nextProps.widget.simulator; + + if (nextProps.simulation == null || nextProps.data == null || nextProps.data[simulator] == null || nextProps.data[simulator].length === 0 || nextProps.data[simulator].values[0].length === 0) { + // clear values + this.setState({ values: [], sequence: null }); + return; + } + + // check if new data, otherwise skip + if (this.state.sequence >= nextProps.data[simulator].sequence) { + return; + } + + // get timestamps + const latestTimestamp = nextProps.data[simulator].values[0][nextProps.data[simulator].values[0].length - 1].x; + const firstTimestamp = latestTimestamp - nextProps.widget.time * 100; + + // find element index representing firstTimestamp + const firstIndex = nextProps.data[simulator].values[0].findIndex((value) => { + return value.x >= firstTimestamp; + }); + + // copy all values for each signal in time region + var values = []; + + nextProps.widget.signals.forEach((signal) => { + values.push({ + values: nextProps.data[simulator].values[signal].slice(firstIndex, nextProps.data[simulator].values[signal].length - 1) + }); + }); + + this.setState({ values: values, firstTimestamp: firstTimestamp, latestTimestamp: latestTimestamp, sequence: nextProps.data[simulator].sequence }); + } + render() { - // get selected simulation model - const widget = this.props.widget; - var simulationModel; - - if (this.props.simulation && this.props.simulation.models && this.props.data[widget.simulator]) { - this.props.simulation.models.forEach((model) => { - if (model.simulator === widget.simulator) { - simulationModel = model; - } - }); - } else { - return (
); + if (this.state.sequence == null) { + return (
Empty
); } - if (widget.plotType === 'table') { - return ( -
Table
- ); - } else if (widget.plotType === 'multiple') { - // get selected data - var lineData = []; - const latestTimestamp = this.props.data[widget.simulator].values[0][this.props.data[widget.simulator].values[0].length - 1].x; - - widget.signals.forEach((signal) => { - lineData.push({ - name: simulationModel.mapping[signal].name, - values: this.props.data[widget.simulator].values[signal] - }); - }); - - return ( -
- {return new Date(d.x);}} - hoverAnimation={false} - circleRadius={0} - domain={{ x: [latestTimestamp - 10000, latestTimestamp] }} - /> -
- ); - } else { - return (
Error
); - } + return ( +
+ {return new Date(d.x);}} + hoverAnimation={false} + circleRadius={0} + domain={{ x: [this.state.firstTimestamp, this.state.latestTimestamp] }} + /> +
+ ); } } diff --git a/src/components/widget-value.js b/src/components/widget-value.js index 92987b6..c096695 100644 --- a/src/components/widget-value.js +++ b/src/components/widget-value.js @@ -10,19 +10,34 @@ import React, { Component } from 'react'; class WidgetValue extends Component { - render() { - // calculate value - var value = null; - const widget = this.props.widget; + constructor(props) { + super(props); - if (this.props.data && this.props.data[widget.simulator] && this.props.data[widget.simulator].values) { - const signalArray = this.props.data[widget.simulator].values[widget.signal]; - value = signalArray[signalArray.length - 1].y; + this.state = { + value: '' + }; + } + + componentWillReceiveProps(nextProps) { + // update value + const simulator = nextProps.widget.simulator; + + if (nextProps.data == null || nextProps.data[simulator] == null || nextProps.data[simulator].values == null) { + this.setState({ value: '' }); + return; } + // check if value has changed + const signal = nextProps.data[simulator].values[nextProps.widget.signal]; + if (this.state.value !== signal[signal.length - 1].y) { + this.setState({ value: signal[signal.length - 1].y }); + } + } + + render() { return (
- {widget.name}: {value} + {this.props.widget.name}: {this.state.value}
); } diff --git a/src/containers/visualization.js b/src/containers/visualization.js index 13dea0f..827d57d 100644 --- a/src/containers/visualization.js +++ b/src/containers/visualization.js @@ -117,8 +117,10 @@ class Visualization extends Component { widget.signal = 0; } else if (item.name === 'Plot') { widget.simulator = this.state.simulation.models[0].simulator; - widget.plotType = 'multiple'; widget.signals = [ 0 ]; + widget.time = 300; + widget.width = 400; + widget.height = 200; } var visualization = this.state.visualization; diff --git a/src/containers/widget.js b/src/containers/widget.js index d6ac0d8..680cc6f 100644 --- a/src/containers/widget.js +++ b/src/containers/widget.js @@ -69,9 +69,9 @@ class Widget extends Component { var element = null; if (widget.type === 'Value') { - element = + element = } else if (widget.type === 'Plot') { - element = + element = } if (this.props.editing) { diff --git a/src/data-managers/rest-data-manager.js b/src/data-managers/rest-data-manager.js index 0b5d178..5c5c68c 100644 --- a/src/data-managers/rest-data-manager.js +++ b/src/data-managers/rest-data-manager.js @@ -10,16 +10,22 @@ import RestAPI from '../api/rest-api'; import AppDispatcher from '../app-dispatcher'; +const API_URL = 'http://localhost:4000/api/v1'; + class RestDataManager { constructor(type, url) { this.url = url; this.type = type; } + makeURL(part) { + return API_URL + part; + } + load(id) { if (id != null) { // load single object - RestAPI.get(this.url + '/' + id).then(response => { + RestAPI.get(this.makeURL(this.url + '/' + id)).then(response => { AppDispatcher.dispatch({ type: this.type + 's/loaded', data: response[this.type] @@ -32,7 +38,7 @@ class RestDataManager { }); } else { // load all objects - RestAPI.get(this.url).then(response => { + RestAPI.get(this.makeURL(this.url)).then(response => { AppDispatcher.dispatch({ type: this.type + 's/loaded', data: response[this.type + 's'] @@ -50,7 +56,7 @@ class RestDataManager { var obj = {}; obj[this.type] = object; - RestAPI.post(this.url, obj).then(response => { + RestAPI.post(this.makeURL(this.url), obj).then(response => { AppDispatcher.dispatch({ type: this.type + 's/added', data: response[this.type] @@ -64,7 +70,7 @@ class RestDataManager { } remove(object) { - RestAPI.delete(this.url + '/' + object._id).then(response => { + RestAPI.delete(this.makeURL(this.url + '/' + object._id)).then(response => { AppDispatcher.dispatch({ type: this.type + 's/removed', data: response[this.type], @@ -82,7 +88,7 @@ class RestDataManager { var obj = {}; obj[this.type] = object; - RestAPI.put(this.url + '/' + object._id, obj).then(response => { + RestAPI.put(this.makeURL(this.url + '/' + object._id), obj).then(response => { AppDispatcher.dispatch({ type: this.type + 's/edited', data: response[this.type] diff --git a/src/data-managers/simulator-data-data-manager.js b/src/data-managers/simulator-data-data-manager.js index 2dffa9a..aded151 100644 --- a/src/data-managers/simulator-data-data-manager.js +++ b/src/data-managers/simulator-data-data-manager.js @@ -23,9 +23,13 @@ class SimulatorDataDataManager { this._sockets.close(); this._sockets[identifier] = WebsocketAPI.addSocket(endpoint, { onOpen: (event) => this.onOpen(event, identifier, signals), onClose: (event) => this.onClose(event, identifier), onMessage: (event) => this.onMessage(event, identifier) }); + + console.log('Modified socket'); } } else { this._sockets[identifier] = WebsocketAPI.addSocket(endpoint, { onOpen: (event) => this.onOpen(event, identifier, signals), onClose: (event) => this.onClose(event, identifier), onMessage: (event) => this.onMessage(event, identifier) }); + + console.log('New socket'); } } @@ -47,6 +51,8 @@ class SimulatorDataDataManager { onMessage(event, identifier) { var message = this.bufferToMessage(event.data); + //console.log(message); + AppDispatcher.dispatch({ type: 'simulatorData/data-changed', data: message, diff --git a/src/stores/simulator-data-store.js b/src/stores/simulator-data-store.js index 1fe5816..fb97e8f 100644 --- a/src/stores/simulator-data-store.js +++ b/src/stores/simulator-data-store.js @@ -12,7 +12,7 @@ import { ReduceStore } from 'flux/utils'; import AppDispatcher from '../app-dispatcher'; import SimulatorDataDataManager from '../data-managers/simulator-data-data-manager'; -const MAX_VALUES = 100; +const MAX_VALUES = 1000; class SimulationDataStore extends ReduceStore { constructor() { @@ -39,27 +39,34 @@ class SimulationDataStore extends ReduceStore { state[action.identifier].values.push([]); } + console.log('Socket opened'); + return state; case 'simulatorData/data-changed': - // add data to simulator - for (i = 0; i < state[action.identifier].signals; i++) { - state[action.identifier].values[i].push({ x: action.data.timestamp, y: action.data.values[i] }); + // only add data, if newer than current + if (state[action.identifier].sequence < action.data.sequence) { + // add data to simulator + for (i = 0; i < state[action.identifier].signals; i++) { + state[action.identifier].values[i].push({ x: action.data.timestamp, y: action.data.values[i] }); - // erase old values - if (state[action.identifier].values[i].length > MAX_VALUES) { - const pos = state[action.identifier].values[i].length - MAX_VALUES; - state[action.identifier].values[i].splice(0, pos); + // erase old values + if (state[action.identifier].values[i].length > MAX_VALUES) { + const pos = state[action.identifier].values[i].length - MAX_VALUES; + state[action.identifier].values[i].splice(0, pos); + } } + + // update metadata + state[action.identifier].timestamp = action.data.timestamp; + state[action.identifier].sequence = action.data.sequence; + + // explicit call to prevent array copy + this.__emitChange(); + } else { + console.log('same sequence'); } - // update metadata - state[action.identifier].timestamp = action.data.timestamp; - state[action.identifier].sequence = action.data.sequence; - - // explicit call to prevent array copy - this.__emitChange(); - return state; case 'simulatorData/closed':