From 98bc4c3e9458aea98890b770493f14327a7bc7c4 Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Thu, 15 Sep 2022 22:52:00 +0200 Subject: refactor(web/ui): Use PatternFly Charts for plots This change updates the OpenDC web interface to use the PatternFly Charts package to render the results of a portfolio. Previously, we used Recharts, but this package does not support SSR, whereas the PatternFly Charts package matches our design framework. --- .../src/components/portfolios/PortfolioResults.js | 132 ++++++++++++--------- 1 file changed, 74 insertions(+), 58 deletions(-) (limited to 'opendc-web/opendc-web-ui/src/components') diff --git a/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioResults.js b/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioResults.js index 33604896..f50105ed 100644 --- a/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioResults.js +++ b/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioResults.js @@ -20,12 +20,11 @@ * SOFTWARE. */ +import { mean, std } from 'mathjs' import React, { useMemo } from 'react' import PropTypes from 'prop-types' -import { Bar, CartesianGrid, ComposedChart, ErrorBar, ResponsiveContainer, Scatter, XAxis, YAxis } from 'recharts' -import { METRIC_NAMES, METRIC_UNITS } from '../../util/available-metrics' -import { mean, std } from 'mathjs' -import approx from 'approximate-number' +import { VictoryErrorBar } from 'victory-errorbar' +import { METRIC_NAMES, METRIC_UNITS, AVAILABLE_METRICS } from '../../util/available-metrics' import { Bullseye, Card, @@ -41,6 +40,7 @@ import { Spinner, Title, } from '@patternfly/react-core' +import { Chart, ChartAxis, ChartBar, ChartTooltip } from '@patternfly/react-charts' import { ErrorCircleOIcon, CubesIcon } from '@patternfly/react-icons' import { usePortfolio } from '../../data/project' import PortfolioResultInfo from './PortfolioResultInfo' @@ -48,7 +48,28 @@ import NewScenario from './NewScenario' function PortfolioResults({ projectId, portfolioId }) { const { status, data: portfolio } = usePortfolio(projectId, portfolioId) - const scenarios = portfolio?.scenarios ?? [] + const scenarios = useMemo(() => portfolio?.scenarios ?? [], [portfolio]) + + const label = ({ datum }) => + `${datum.x}: ${datum.y.toLocaleString()} ± ${datum.errorY.toLocaleString()} ${METRIC_UNITS[datum.metric]}` + const selectedMetrics = new Set(portfolio?.targets?.metrics ?? []) + const dataPerMetric = useMemo(() => { + const dataPerMetric = {} + AVAILABLE_METRICS.forEach((metric) => { + dataPerMetric[metric] = scenarios + .filter((scenario) => scenario.job?.results) + .map((scenario) => ({ + metric, + x: scenario.name, + y: mean(scenario.job.results[metric]), + errorY: std(scenario.job.results[metric]), + label, + })) + }) + return dataPerMetric + }, [scenarios]) + + const categories = useMemo(() => ({ x: scenarios.map((s) => s.name).reverse() }), [scenarios]) if (status === 'loading') { return ( @@ -93,62 +114,57 @@ function PortfolioResults({ projectId, portfolioId }) { ) } - const metrics = portfolio?.targets?.metrics ?? [] - const dataPerMetric = useMemo(() => { - const dataPerMetric = {} - metrics.forEach((metric) => { - dataPerMetric[metric] = scenarios - .filter((scenario) => scenario.job?.results) - .map((scenario) => ({ - name: scenario.name, - value: mean(scenario.job.results[metric]), - errorX: std(scenario.job.results[metric]), - })) - }) - return dataPerMetric - }, [scenarios, metrics]) - return ( - {metrics.map((metric) => ( - - - - - - - {METRIC_NAMES[metric]} - - - - - - approx(tick)} - label={{ value: METRIC_UNITS[metric], position: 'bottom', offset: 0 }} - type="number" - /> - - - - + selectedMetrics.has(metric) && ( + + + + + + + {METRIC_NAMES[metric]} + + + + + + } + barWidth={25} + horizontal + /> + d.errorY} + labelComponent={<>} + horizontal /> - - - - - - - ))} + + + + + ) + )} ) } -- cgit v1.2.3