frontend: Render histogram as chart in road details panel

This commit is contained in:
Paul Bienkowski 2022-04-06 21:05:18 +02:00
parent cb6c94f7a5
commit 8728347695
4 changed files with 155 additions and 0 deletions

View file

@ -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 <ReactEChartsCore
echarts={echarts}
notMerge
lazyUpdate
{...props}
/>
}

View file

@ -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'

View file

@ -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 (
<Chart
style={{height: 240}}
option={{
grid: {top: 30, bottom: 30, right: 30, left: 30},
xAxis: {
type: 'value',
axisLabel: {formatter: (v) => `${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] && <RoadStatsTable data={info[direction]} />}
{info?.[direction]?.distanceOvertaker?.histogram && (
<>
<Header as="h5">Overtaker distance distribution</Header>
<HistogramChart {...info[direction]?.distanceOvertaker?.histogram} />
</>
)}
</>
)

View file

@ -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
}
}