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 Stats} from './Stats'
|
||||
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 {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} />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue