Skip to content
This repository has been archived by the owner on May 4, 2024. It is now read-only.

Conditions gp #13

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17,831 changes: 0 additions & 17,831 deletions frontend/package-lock.json

This file was deleted.

4 changes: 4 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,13 @@
"@types/node": "^16.18.46",
"@types/react": "^18.2.21",
"@types/react-dom": "^18.2.7",
"chart.js": "^3.8.0",
"leaflet": "^1.7.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1",
"react-leaflet": "^3.0.0",
"react-leaflet-hotline": "^1.4.2",
"typescript": "^4.9.5",
"web-vitals": "^2.1.4"
},
Expand Down
19 changes: 19 additions & 0 deletions frontend/src/Components/Map/Hooks/useZoom.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { useEffect, useState } from "react"
import { useMapEvents } from "react-leaflet"

//zoom implementation on map events
const useZoom = () => {
const [zoom, setZoom] = useState<number>()

const update = () => setZoom(map.getZoom())

const map = useMapEvents({
zoom: update
})

useEffect( update, [] )

return zoom;
}

export default useZoom;
39 changes: 39 additions & 0 deletions frontend/src/Components/Map/MapWrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@

import { MapContainer, TileLayer, ScaleControl } from 'react-leaflet'

import Zoom from './Zoom';

import '../../css/map.css'
import { MAP_OPTIONS } from './constants';

//container for map and its attributes
const MapWrapper = ( props : any ) => {

const { children } = props;

const { center, zoom, minZoom, maxZoom, scaleWidth } = MAP_OPTIONS;

return (
<MapContainer
preferCanvas={true}
center={center}
zoom={zoom}
minZoom={minZoom}
maxZoom={maxZoom}
scrollWheelZoom={true}
zoomControl={false}
>
<TileLayer
maxNativeZoom={maxZoom}
maxZoom={maxZoom}
attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<Zoom />
<ScaleControl imperial={false} position='bottomright' maxWidth={scaleWidth}/>
{ children }
</MapContainer>
)
}

export default MapWrapper;
69 changes: 69 additions & 0 deletions frontend/src/Components/Map/Renderers/DistHotline.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@

import { FC, useEffect, useMemo, useState } from 'react';
import { LeafletEvent, Polyline } from 'leaflet'
import { HotlineOptions, useCustomHotline } from 'react-leaflet-hotline';

import { useGraph } from '../../../context/GraphContext';

import { Condition, Node, WayId } from '../../../models/path';

import DistRenderer from '../../../assets/hotline/DistRenderer';
import { DistData } from '../../../assets/hotline/hotline';
import HoverHotPolyline from '../../../assets/hotline/HoverHotPolyline';
import { HotlineEventFn, HotlineEventHandlers } from 'react-leaflet-hotline/lib/types';
import useZoom from '../Hooks/useZoom';
import { useHoverContext } from "../../../context/GraphHoverContext";


const getLat = (n: Node) => n.lat;
const getLng = (n: Node) => n.lng;
const getVal = (n: Node) => n.way_dist;
const getWeight = (z: number | undefined) => z === undefined ? 0 : Math.max(z > 8 ? z - 6 : z - 5, 2)

interface IDistHotline {
way_ids: WayId[];
geometry: Node[][];
conditions: Condition[][];
options?: HotlineOptions,
eventHandlers?: HotlineEventHandlers;
}

const handler = (eventHandlers: HotlineEventHandlers | undefined, event: keyof HotlineEventHandlers, opacity: number) => {
return (e: LeafletEvent, i: number, p: Polyline<any, any>) => {
p.setStyle( { opacity } )
if ( eventHandlers && eventHandlers[event] !== undefined )
(eventHandlers[event] as HotlineEventFn)(e, i, p);
}
}

const DistHotline: FC<IDistHotline> = ( { way_ids, geometry, conditions, options, eventHandlers } ) => {

const { dotHover } = useHoverContext()
const zoom = useZoom()

const opts = useMemo( () => ({
...options, weight: getWeight(zoom)
}), [options, zoom] )

const handlers: HotlineEventHandlers = useMemo( () => ({
...eventHandlers,
mouseover: handler(eventHandlers, 'mouseover', 0.5),
mouseout: handler(eventHandlers, 'mouseout', 0),
}), [eventHandlers] )

const { hotline } = useCustomHotline<Node, DistData>(
DistRenderer, HoverHotPolyline,
{ data: geometry, getLat, getLng, getVal, options: opts, eventHandlers: handlers },
way_ids, conditions
);

useEffect( () => {
if ( hotline === undefined ) return;
(hotline as HoverHotPolyline<Node, DistData>).setHover(dotHover)
}, [dotHover])

return null;
}


export default DistHotline;
18 changes: 18 additions & 0 deletions frontend/src/Components/Map/Zoom.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@

import { ZoomControl } from "react-leaflet"
import useZoom from "./Hooks/useZoom"

// implement zoom component on map
const Zoom = () => {

const zoom = useZoom()

return (
<>
<div className="map-zoom">{zoom}</div>
<ZoomControl position='topright'/>
</>
)
}

export default Zoom
76 changes: 76 additions & 0 deletions frontend/src/Components/Map/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import {LatLng} from 'leaflet'
import {Palette} from 'react-leaflet-hotline';
import {ActiveMeasProperties, RendererOptions, XAxisType} from '../../models/properties';
import {RendererName} from '../../models/renderers';

// Map
export const MAP_OPTIONS = {
center: new LatLng(55.672, 12.458),
zoom: 12,
minZoom: 5,
maxZoom: 18,
scaleWidth: 100
}

// Renderer
export const RENDERER_WIDTH = 4
export const RENDERER_WEIGHT = 4
export const RENDERER_COLOR = 'red'
export const RENDERER_OPACITY = 1.0

// TODO, [email protected]: this was a quick fix; this was a constant used in
// different places in the software. But someone changed the components,
// in order for this not to happen, I made this a function, which makes
// a new copy. Eventually we should track down where the change of the
// constant is made and fix this.
export function RENDERER_PALETTE(): Palette {
return [
{ r: 0, g: 160, b: 0, t: 0 },
{ r: 255, g: 255, b: 0, t: 0.5 },
{ r: 255, g: 0, b: 0, t: 1 }
] };


export function RENDERER_OPTIONS(): Required<RendererOptions> {
return {
rendererName: 'hotline' as RendererName,
dilatationFactor: 1,
arrowHead: 0,
min: 0,
max: 10,
width: RENDERER_WIDTH,
weight: RENDERER_WEIGHT,
color: RENDERER_COLOR,
opacity: RENDERER_OPACITY,
palette: RENDERER_PALETTE()
}
}

export function RENDERER_MEAS_PROPERTIES(): Required<ActiveMeasProperties> {
return {
...(RENDERER_OPTIONS()),
dbName: '',
name: '',
hasValue: true,
xAxisType: XAxisType.distance,
isActive: false
}
}

// Heatmap
// [email protected] (precaussion: see comment on PALLETTE
export function HEATMAP_PALETTE(): Palette {
return [
{r: 0, g: 0, b: 255, t: 0},
{r: 255, g: 255, b: 255, t: 0.5},
{r: 255, g: 0, b: 0, t: 1}
]
}

export function HEATMAP_OPTIONS() {
return {
max: 10,
radius: 10,
palette: HEATMAP_PALETTE()
}
}
34 changes: 34 additions & 0 deletions frontend/src/Components/Palette/PaletteEditor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { FC, MouseEvent, useState } from "react";
import { Gradient } from "react-gradient-hook";
import { CursorOptions } from "react-gradient-hook/lib/types";
import { useMap } from "react-leaflet";
import { Palette } from "react-leaflet-hotline";

import '../../css/palette.css'

interface IPaletteEditor {
width: number | undefined;
defaultPalette?: Palette;
cursorOptions?: CursorOptions;
onChange?: (palette: Palette) => void;
}

const PaletteEditor: FC<IPaletteEditor> = ( { width, defaultPalette, cursorOptions, onChange } ) => {

const [show, setShow] = useState<boolean>(false)

const toggleAppear = () => setShow(prev => !prev)

if ( width === undefined || width === 0 ) return null;

return (
<div className={`palette-wrapper ${show ? 'palette-show' : ''}`} style={{width: `${width}px`}} >
<div className="palette-container">
<Gradient defaultColors={defaultPalette} cursorOptions={cursorOptions} onChange={onChange}/>
</div>
<div className='palette-hover' onClick={toggleAppear}>🎨</div>
</div>
)
}

export default PaletteEditor;
97 changes: 97 additions & 0 deletions frontend/src/Components/RoadConditions/ConditionsGraph.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { FC, useCallback, useEffect, useMemo, useRef } from "react";
import { ChartData, Chart, CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend, ActiveElement, ChartEvent, ChartOptions, ChartTypeRegistry, Plugin } from "chart.js";
import { Color, Palette } from "react-leaflet-hotline";
import { Line } from "react-chartjs-2";

import { ConditionType } from "../../models/graph";

Chart.register( CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend );

const options = ({name, min, max}: ConditionType): ChartOptions<'line'> => ({
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'top' as const,
labels: { color: 'white' },
},
},
scales: {
x: {
title: {
display: true,
text: 'distance (m)'
},
ticks: {
maxTicksLimit: 30,
stepSize: 200,
callback: (tick: string | number) => Math.round(parseFloat(tick.toString()))
}
},
y: {
title: {
display: true,
text: name
},
min: min,
max: max
}
}
});

interface Props {
type: ConditionType;
data: ChartData<"line", number[], number> | undefined
palette: Palette;
}

const ConditionsGraph: FC<Props> = ( { type, data, palette } ) => {

const ref = useRef<Chart<"line", number[], number>>(null)

const addPaletteChart = (palette: Palette) => (chart: Chart<keyof ChartTypeRegistry, number[], unknown>) => {
const dataset = chart.data.datasets[0];
const gradient = chart.ctx.createLinearGradient(0, chart.chartArea.bottom, 0, 0);
console.log(...palette);
palette.forEach( (c: Color) => {
gradient.addColorStop(c.t, `rgb(${c.r}, ${c.g}, ${c.b})`);
})
dataset.borderColor = gradient;
dataset.backgroundColor = gradient;
}

useEffect( () => {
if (ref.current === null ) return;
const chart = ref.current;
addPaletteChart(palette)(chart)
chart.update()
}, [ref, data, palette])

// attach events to the graph options
const graphOptions: ChartOptions<'line'> = useMemo( () => ({
...options(type),
onClick: (event: ChartEvent, elts: ActiveElement[], chart: Chart<keyof ChartTypeRegistry, number[], unknown>) => {
if ( elts.length === 0 ) return;
const elt = elts[0] // doesnt work if multiple datasets
const pointIndex = elt.index
console.log(pointIndex, event, elts);
}
}), [] )

const plugins: Plugin<"line">[] = [ {
id: 'id',
} ]

return (
<div className="road-conditions-graph">
{ data && <Line
ref={ref}
data={data}
options={graphOptions}
plugins={plugins} />
}
</div>
)
}

export default ConditionsGraph;
Loading
Loading