diff options
Diffstat (limited to 'frontend/src/components/app/results')
| -rw-r--r-- | frontend/src/components/app/results/MetricChartComponent.js | 67 | ||||
| -rw-r--r-- | frontend/src/components/app/results/PortfolioResultsComponent.js | 78 |
2 files changed, 145 insertions, 0 deletions
diff --git a/frontend/src/components/app/results/MetricChartComponent.js b/frontend/src/components/app/results/MetricChartComponent.js new file mode 100644 index 00000000..29a4676a --- /dev/null +++ b/frontend/src/components/app/results/MetricChartComponent.js @@ -0,0 +1,67 @@ +import React from 'react' +import ReactDOM from 'react-dom/server' +import SvgSaver from 'svgsaver' +import { VictoryAxis, VictoryChart, VictoryLabel, VictoryLine, VictoryScatter } from 'victory' +import { convertSecondsToFormattedTime } from '../../../util/date-time' + +const MetricChartComponent = ({ data, currentTick }) => { + const onExport = () => { + const div = document.createElement('div') + div.innerHTML = ReactDOM.renderToString( + <VictoryChartComponent data={data} currentTick={currentTick} showCurrentTick={false} /> + ) + div.firstChild.style = 'font-family: Roboto, Arial, sans-serif; font-size: 10pt;' + const svgSaver = new SvgSaver() + svgSaver.asSvg(div.firstChild, 'opendc-chart-export-' + Date.now() + '.svg') + } + + return ( + <div className="mt-1" style={{ position: 'relative' }}> + <strong>Load over time</strong> + <VictoryChartComponent data={data} currentTick={currentTick} showCurrentTick={true} /> + <ExportChartComponent onExport={onExport} /> + </div> + ) +} + +const VictoryChartComponent = ({ data, currentTick, showCurrentTick }) => ( + <VictoryChart height={250} padding={{ top: 10, bottom: 50, left: 50, right: 50 }}> + <VictoryAxis + tickFormat={(tick) => convertSecondsToFormattedTime(tick)} + fixLabelOverlap={true} + label="Simulated Time" + /> + <VictoryAxis dependentAxis label="Load" /> + <VictoryLine data={data} /> + <VictoryScatter data={data} /> + {showCurrentTick ? ( + <VictoryLine + labelComponent={<VictoryLabel renderInPortal angle={90} dy={-5} dx={60} />} + data={[ + { x: currentTick + 1, y: 0 }, + { x: currentTick + 1, y: 1 }, + ]} + labels={(point) => + point.y === 1 ? 'Current tick : ' + convertSecondsToFormattedTime(currentTick) : '' + } + style={{ + data: { stroke: '#00A6D6', strokeWidth: 4 }, + labels: { fill: '#00A6D6' }, + }} + /> + ) : undefined} + </VictoryChart> +) + +const ExportChartComponent = ({ onExport }) => ( + <button + className="btn btn-success btn-circle btn-sm" + title="Export Chart to PNG Image" + onClick={onExport} + style={{ position: 'absolute', top: 0, right: 0 }} + > + <span className="fa fa-camera" /> + </button> +) + +export default MetricChartComponent diff --git a/frontend/src/components/app/results/PortfolioResultsComponent.js b/frontend/src/components/app/results/PortfolioResultsComponent.js new file mode 100644 index 00000000..8c778098 --- /dev/null +++ b/frontend/src/components/app/results/PortfolioResultsComponent.js @@ -0,0 +1,78 @@ +import React from 'react' +import PropTypes from 'prop-types' +import { Bar, BarChart, CartesianGrid, ErrorBar, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts' +import { AVAILABLE_METRICS, METRIC_NAMES } from '../../../util/available-metrics' +import { mean, std } from 'mathjs' +import Shapes from '../../../shapes/index' +import approx from 'approximate-number' + +const PortfolioResultsComponent = ({ portfolio, scenarios }) => { + if (!portfolio) { + return <div>Loading...</div> + } + + const nonFinishedScenarios = scenarios.filter((s) => s.simulation.state !== 'FINISHED') + + if (nonFinishedScenarios.length > 0) { + if (nonFinishedScenarios.every((s) => s.simulation.state === 'QUEUED' || s.simulation.state === 'RUNNING')) { + return ( + <div> + <h1>Simulation running...</h1> + <p>{nonFinishedScenarios.length} of the scenarios are still being simulated</p> + </div> + ) + } + if (nonFinishedScenarios.some((s) => s.simulation.state === 'FAILED')) { + return ( + <div> + <h1>Simulation failed.</h1> + <p> + Try again by creating a new scenario. Please contact the OpenDC team for support, if issues + persist. + </p> + </div> + ) + } + } + + const dataPerMetric = {} + + AVAILABLE_METRICS.forEach((metric) => { + dataPerMetric[metric] = scenarios.map((scenario) => ({ + name: scenario.name, + value: mean(scenario.results[metric]), + std: std(scenario.results[metric]), + })) + }) + + return ( + <div> + <h1>Portfolio: {portfolio.name}</h1> + <p>Repeats per Scenario: {portfolio.targets.repeatsPerScenario}</p> + <div className="row"> + {AVAILABLE_METRICS.map(metric => ( + <div className="col-6" key={metric}> + <h4>{METRIC_NAMES[metric]}</h4> + <ResponsiveContainer aspect={16 / 9} width="100%"> + <BarChart data={dataPerMetric[metric]}> + <CartesianGrid strokeDasharray="3 3"/> + <XAxis dataKey="name"/> + <YAxis tickFormatter={tick => approx(tick)}/> + <Tooltip/> + <Bar dataKey="value" fill="#8884d8"/> + <ErrorBar dataKey="std" width={4} strokeWidth={2} stroke="blue" direction="y"/> + </BarChart> + </ResponsiveContainer> + </div> + ))} + </div> + </div> + ) +} + +PortfolioResultsComponent.propTypes = { + portfolio: Shapes.Portfolio, + scenarios: PropTypes.arrayOf(Shapes.Scenario), +} + +export default PortfolioResultsComponent |
