frontend: Render histogram as chart in road details panel
This commit is contained in:
parent
cb6c94f7a5
commit
8728347695
84
frontend/src/components/Chart.tsx
Normal file
84
frontend/src/components/Chart.tsx
Normal 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}
|
||||||
|
/>
|
||||||
|
}
|
|
@ -8,3 +8,4 @@ export {default as Map} from './Map'
|
||||||
export {default as Page} from './Page'
|
export {default as Page} from './Page'
|
||||||
export {default as Stats} from './Stats'
|
export {default as Stats} from './Stats'
|
||||||
export {default as StripMarkdown} from './StripMarkdown'
|
export {default as StripMarkdown} from './StripMarkdown'
|
||||||
|
export {default as Chart} from './Chart'
|
||||||
|
|
|
@ -5,11 +5,24 @@ import {Layer, Source} from 'react-map-gl'
|
||||||
import {of, from, concat} from 'rxjs'
|
import {of, from, concat} from 'rxjs'
|
||||||
import {useObservable} from 'rxjs-hooks'
|
import {useObservable} from 'rxjs-hooks'
|
||||||
import {switchMap, distinctUntilChanged} from 'rxjs/operators'
|
import {switchMap, distinctUntilChanged} from 'rxjs/operators'
|
||||||
|
import {Chart} from 'components'
|
||||||
|
import {pairwise} from 'utils'
|
||||||
|
|
||||||
import api from 'api'
|
import api from 'api'
|
||||||
|
import {colorByDistance} from 'mapstyles'
|
||||||
|
|
||||||
import styles from './styles.module.less'
|
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 UNITS = {distanceOvertaker: 'm', distanceStationary: 'm', speed: 'km/h'}
|
||||||
const LABELS = {
|
const LABELS = {
|
||||||
distanceOvertaker: 'Left',
|
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}) {
|
export default function RoadInfo({clickLocation}) {
|
||||||
const [direction, setDirection] = useState('forwards')
|
const [direction, setDirection] = useState('forwards')
|
||||||
|
|
||||||
|
@ -138,6 +186,13 @@ export default function RoadInfo({clickLocation}) {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{info?.[direction] && <RoadStatsTable data={info[direction]} />}
|
{info?.[direction] && <RoadStatsTable data={info[direction]} />}
|
||||||
|
|
||||||
|
{info?.[direction]?.distanceOvertaker?.histogram && (
|
||||||
|
<>
|
||||||
|
<Header as="h5">Overtaker distance distribution</Header>
|
||||||
|
<HistogramChart {...info[direction]?.distanceOvertaker?.histogram} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -7,3 +7,18 @@ export function findInput(register) {
|
||||||
register(found)
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue