More graph options

This commit is contained in:
Jan Zípek 2022-08-25 09:30:50 +02:00
parent f50b8f1d2e
commit da7f4aa5b2
Signed by: kamen
GPG Key ID: A17882625B33AC31
7 changed files with 107 additions and 21 deletions

View File

@ -53,7 +53,7 @@ export const BoxDialContent = ({ box, data, refreshRef }: Props) => {
{value.data && ( {value.data && (
<> <>
{displayValue} {displayValue}
{` ${data.unit}`} {data.unit && ` ${data.unit}`}
</> </>
)} )}
</div> </div>

View File

@ -1,4 +1,6 @@
import { getSensorValues } from '@/api/sensorValues' import { getSensorValues } from '@/api/sensorValues'
import { max } from '@/utils/max'
import { min } from '@/utils/min'
import { DashboardGraphData } from '@/utils/parseDashboard' import { DashboardGraphData } from '@/utils/parseDashboard'
import { RefObject } from 'preact' import { RefObject } from 'preact'
import { useEffect, useRef } from 'preact/hooks' import { useEffect, useRef } from 'preact/hooks'
@ -33,42 +35,58 @@ export const BoxGraphContent = ({ box, data, refreshRef }: Props) => {
} }
useEffect(() => { useEffect(() => {
// TODO: These should be probably returned by server, could be outdated
const from = filter.customFrom
const to = filter.customTo
const minValue = parseFloat(data.min ?? '')
const maxValue = parseFloat(data.max ?? '')
const customRange = !isNaN(minValue) && !isNaN(maxValue)
if (bodyRef.current && values.data) { if (bodyRef.current && values.data) {
// TODO: These should be probably returned by server, could be outdated
const from = filter.customFrom
const to = filter.customTo
const minValue = parseFloat(data.min ?? '')
const maxValue = parseFloat(data.max ?? '')
const customRange = !isNaN(minValue) || !isNaN(maxValue)
const graphType = data.graphType ?? 'line'
const fill = data.fill ?? undefined
const colorMode = data.colorMode
const staticColor = data.staticColor
const x = values.data.map((v) => new Date(v.timestamp * 1000))
const y = values.data.map((v) => v.value)
window.Plotly.newPlot( window.Plotly.newPlot(
bodyRef.current, bodyRef.current,
[ [
{ {
...(data.graphType === 'line' && { ...(graphType === 'line' && {
type: 'scatter', type: 'scatter',
mode: 'lines', mode: 'lines',
fill,
}), }),
...(data.graphType === 'points' && { ...(graphType === 'points' && {
type: 'scatter', type: 'scatter',
mode: 'markers', mode: 'markers',
fill,
}), }),
...(data.graphType === 'lineAndPoints' && { ...(graphType === 'lineAndPoints' && {
type: 'scatter', type: 'scatter',
mode: 'lines+markers', mode: 'lines+markers',
fill,
}), }),
...(data.graphType === 'bar' && { type: 'bar' }), ...(graphType === 'bar' && { type: 'bar' }),
x: values.data.map((v) => new Date(v.timestamp * 1000)), x,
y: values.data.map((v) => v.value), y,
line: { line: {
width: 1, width: 1,
...(colorMode === 'static' && { color: staticColor }),
}, },
}, },
], ],
{ {
xaxis: { range: [from, to], type: 'date' }, xaxis: { range: [from, to], type: 'date' },
yaxis: { yaxis: {
...(customRange && { range: [minValue, maxValue] }), ...(customRange && {
range: [
isNaN(minValue) ? min(y) : minValue,
isNaN(maxValue) ? max(y) : maxValue,
],
}),
...(data.unit && { ticksuffix: ` ${data.unit}` }), ...(data.unit && { ticksuffix: ` ${data.unit}` }),
}, },
margin: { margin: {

View File

@ -1,3 +1,4 @@
import { omit } from '@/utils/omit'
import { DashboardGraphData } from '@/utils/parseDashboard' import { DashboardGraphData } from '@/utils/parseDashboard'
import { useEffect, useState } from 'preact/hooks' import { useEffect, useState } from 'preact/hooks'
@ -8,10 +9,7 @@ type Props = {
export const GraphSettings = ({ value, onChange }: Props) => { export const GraphSettings = ({ value, onChange }: Props) => {
const [formState, setFormState] = useState(() => ({ const [formState, setFormState] = useState(() => ({
min: value?.min, ...(value && omit(value, ['type'])),
max: value?.max,
graphType: value?.graphType,
unit: value?.unit,
})) }))
const handleChange = (e: Event) => { const handleChange = (e: Event) => {
@ -43,18 +41,67 @@ export const GraphSettings = ({ value, onChange }: Props) => {
</select> </select>
</div> </div>
<div className="input">
<label>Fill area</label>
<select
name="fill"
value={formState.fill || ''}
onChange={handleChange}
>
<option value="">None</option>
<option value="tozeroy">To zero</option>
<option value="tonexty">To next value</option>
<option value="toself">To self</option>
</select>
</div>
<div className="input"> <div className="input">
<label>Unit</label> <label>Unit</label>
<input name="unit" value={formState.unit} onChange={handleChange} /> <input name="unit" value={formState.unit} onChange={handleChange} />
</div> </div>
<div className="input"> <div className="input">
<label>Min value</label> <label>Min value</label>
<input name="min" value={formState.min} onChange={handleChange} /> <input
type="number"
step="any"
name="min"
value={formState.min}
onChange={handleChange}
/>
</div> </div>
<div className="input"> <div className="input">
<label>Max value</label> <label>Max value</label>
<input name="max" value={formState.max} onChange={handleChange} /> <input
type="number"
step="any"
name="max"
value={formState.max}
onChange={handleChange}
/>
</div> </div>
<div className="input">
<label>Color mode</label>
<select
name="colorMode"
value={formState.colorMode ?? ''}
onChange={handleChange}
>
<option value="">None</option>
<option value="static">Static</option>
</select>
</div>
{formState.colorMode === 'static' && (
<div className="input">
<label>Max value</label>
<input
type="color"
name="staticColor"
value={formState.staticColor}
onChange={handleChange}
/>
</div>
)}
</> </>
) )
} }

2
client/src/utils/max.ts Normal file
View File

@ -0,0 +1,2 @@
export const max = (v: number[]) =>
v.reduce((acc, item) => (item > acc ? item : acc), 0)

2
client/src/utils/min.ts Normal file
View File

@ -0,0 +1,2 @@
export const min = (v: number[]) =>
v.reduce((acc, item) => (item > acc ? item : acc), 0)

7
client/src/utils/omit.ts Normal file
View File

@ -0,0 +1,7 @@
export const omit = <T, TOmitKeys extends keyof T>(v: T, keys: TOmitKeys[]) => {
const keysSet = new Set<string>(keys as string[])
return Object.fromEntries(
Object.entries(v).filter(([key]) => !keysSet.has(key))
) as Omit<T, TOmitKeys>
}

View File

@ -20,6 +20,16 @@ export type DashboardGraphData = {
max?: string max?: string
unit?: string unit?: string
graphType?: string graphType?: string
fill?:
| 'none'
| 'tozeroy'
| 'tozerox'
| 'tonexty'
| 'tonextx'
| 'toself'
| 'tonext'
colorMode?: 'static'
staticColor?: string
} }
export type DashboardDialData = { export type DashboardDialData = {