From 87283476950ebaabea723238a28ad783ad8e8c42 Mon Sep 17 00:00:00 2001 From: Paul Bienkowski Date: Wed, 6 Apr 2022 21:05:18 +0200 Subject: [PATCH] frontend: Render histogram as chart in road details panel --- frontend/src/components/Chart.tsx | 84 +++++++++++++++++++++++++ frontend/src/components/index.js | 1 + frontend/src/pages/MapPage/RoadInfo.tsx | 55 ++++++++++++++++ frontend/src/utils.js | 15 +++++ 4 files changed, 155 insertions(+) create mode 100644 frontend/src/components/Chart.tsx diff --git a/frontend/src/components/Chart.tsx b/frontend/src/components/Chart.tsx new file mode 100644 index 0000000..c021323 --- /dev/null +++ b/frontend/src/components/Chart.tsx @@ -0,0 +1,84 @@ +import React from 'react'; +import ReactEChartsCore from 'echarts-for-react/lib/core'; + +import * as echarts from 'echarts/core'; + +import { + // LineChart, + BarChart, + // PieChart, + // ScatterChart, + // RadarChart, + // MapChart, + // TreeChart, + // TreemapChart, + // GraphChart, + // GaugeChart, + // FunnelChart, + // ParallelChart, + // SankeyChart, + // BoxplotChart, + // CandlestickChart, + // EffectScatterChart, + // LinesChart, + // HeatmapChart, + // PictorialBarChart, + // ThemeRiverChart, + // SunburstChart, + // CustomChart, +} from 'echarts/charts'; + +// import components, all suffixed with Component +import { + // GridSimpleComponent, + GridComponent, + // PolarComponent, + // RadarComponent, + // GeoComponent, + // SingleAxisComponent, + // ParallelComponent, + // CalendarComponent, + // GraphicComponent, + // ToolboxComponent, + TooltipComponent, + // AxisPointerComponent, + // BrushComponent, + TitleComponent, + // TimelineComponent, + // MarkPointComponent, + // MarkLineComponent, + // MarkAreaComponent, + // LegendComponent, + // LegendScrollComponent, + // LegendPlainComponent, + // DataZoomComponent, + // DataZoomInsideComponent, + // DataZoomSliderComponent, + // VisualMapComponent, + // VisualMapContinuousComponent, + // VisualMapPiecewiseComponent, + // AriaComponent, + // TransformComponent, + DatasetComponent, +} from 'echarts/components'; + +// Import renderer, note that introducing the CanvasRenderer or SVGRenderer is a required step +import { + CanvasRenderer, + // SVGRenderer, +} from 'echarts/renderers'; + +// Register the required components +echarts.use( + [TitleComponent, TooltipComponent, GridComponent, BarChart, CanvasRenderer] +); + +// The usage of ReactEChartsCore are same with above. +export default function Chart(props) { + return +} diff --git a/frontend/src/components/index.js b/frontend/src/components/index.js index a7ed811..6764bf9 100644 --- a/frontend/src/components/index.js +++ b/frontend/src/components/index.js @@ -8,3 +8,4 @@ export {default as Map} from './Map' export {default as Page} from './Page' export {default as Stats} from './Stats' export {default as StripMarkdown} from './StripMarkdown' +export {default as Chart} from './Chart' diff --git a/frontend/src/pages/MapPage/RoadInfo.tsx b/frontend/src/pages/MapPage/RoadInfo.tsx index ebbf07b..5d2758e 100644 --- a/frontend/src/pages/MapPage/RoadInfo.tsx +++ b/frontend/src/pages/MapPage/RoadInfo.tsx @@ -5,11 +5,24 @@ import {Layer, Source} from 'react-map-gl' import {of, from, concat} from 'rxjs' import {useObservable} from 'rxjs-hooks' import {switchMap, distinctUntilChanged} from 'rxjs/operators' +import {Chart} from 'components' +import {pairwise} from 'utils' import api from 'api' +import {colorByDistance} from 'mapstyles' import styles from './styles.module.less' +function selectFromColorMap(colormap, value) { + let last = null + for (let i = 0; i < colormap.length; i += 2) { + if (colormap[i + 1] > value) { + return colormap[i] + } + } + return colormap[colormap.length - 1] +} + const UNITS = {distanceOvertaker: 'm', distanceStationary: 'm', speed: 'km/h'} const LABELS = { distanceOvertaker: 'Left', @@ -62,6 +75,41 @@ function RoadStatsTable({data}) { ) } +function HistogramChart({bins, counts}) { + const diff = bins[1] - bins[0] + const data = _.zip( + bins.slice(0, bins.length - 1).map((v) => v + diff / 2), + counts + ).map((value) => ({ + value, + itemStyle: {color: selectFromColorMap(colorByDistance()[3].slice(2), value[0])}, + })) + + return ( + `${Math.round(v * 100)} cm`}, + min: 0, + max: 2.5, + }, + yAxis: {}, + series: [ + { + type: 'bar', + data, + + barMaxWidth: 20, + }, + ], + }} + /> + ) +} + export default function RoadInfo({clickLocation}) { const [direction, setDirection] = useState('forwards') @@ -138,6 +186,13 @@ export default function RoadInfo({clickLocation}) { )} {info?.[direction] && } + + {info?.[direction]?.distanceOvertaker?.histogram && ( + <> +
Overtaker distance distribution
+ + + )} ) diff --git a/frontend/src/utils.js b/frontend/src/utils.js index 5213493..25d2977 100644 --- a/frontend/src/utils.js +++ b/frontend/src/utils.js @@ -7,3 +7,18 @@ export function findInput(register) { register(found) } } + +// Generates pairs from the input iterable +export function* pairwise(it) { + let lastValue + let firstRound = true + + for (const i of it) { + if (firstRound) { + firstRound = false + } else { + yield [lastValue, i] + } + lastValue = i + } +}