From 8b3c441878aeff8544cbda3b9a70291eb9f771df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Z=C3=ADpek?= Date: Thu, 28 Mar 2024 15:58:28 +0100 Subject: [PATCH] Small improvements --- client/index.html | 2 +- client/package.json | 2 +- .../src/assets/components/_user-layout.scss | 15 +++++ client/src/assets/pages/_dashboard-page.scss | 1 + client/src/assets/themes/_basic.scss | 29 +++++++-- .../components/ColorPointsEditor.tsx | 65 +++++++++++++++++++ .../BoxSettings/components/GraphSettings.tsx | 13 +++- .../components/BoxGraphContent.tsx | 3 +- .../DashboardHeader/DashboardHeader.tsx | 41 ++++++------ .../utils/getColorFromColorPoints.ts | 44 +++++++++++++ client/src/utils/dashboard/parseDashboard.ts | 3 +- client/src/utils/hex2Rgb.ts | 11 ++++ client/src/utils/hooks/useForm.ts | 2 +- client/src/utils/hsl2Rgb.ts | 44 +++++++++++++ client/src/utils/interpolateColors.ts | 13 ++++ client/src/utils/rgb2Hex.ts | 6 ++ client/src/utils/rgb2Hsl.ts | 37 +++++++++++ client/yarn.lock | 19 ++---- 18 files changed, 304 insertions(+), 46 deletions(-) create mode 100644 client/src/pages/dashboard/components/BoxSettings/components/ColorPointsEditor.tsx create mode 100644 client/src/pages/dashboard/utils/getColorFromColorPoints.ts create mode 100644 client/src/utils/hex2Rgb.ts create mode 100644 client/src/utils/hsl2Rgb.ts create mode 100644 client/src/utils/interpolateColors.ts create mode 100644 client/src/utils/rgb2Hex.ts create mode 100644 client/src/utils/rgb2Hsl.ts diff --git a/client/index.html b/client/index.html index c8f1220..fd40742 100644 --- a/client/index.html +++ b/client/index.html @@ -5,7 +5,7 @@ Graphicek - +
diff --git a/client/package.json b/client/package.json index adbaf95..7deae7e 100644 --- a/client/package.json +++ b/client/package.json @@ -11,7 +11,7 @@ "license": "ISC", "devDependencies": { "@preact/preset-vite": "^2.3.0", - "@types/plotly.js": "^2.12.2", + "@types/plotly.js": "^2.29.2", "@typescript-eslint/eslint-plugin": "^5.7.0", "@typescript-eslint/parser": "^5.7.0", "eslint": "^8.5.0", diff --git a/client/src/assets/components/_user-layout.scss b/client/src/assets/components/_user-layout.scss index 41ae973..1497619 100644 --- a/client/src/assets/components/_user-layout.scss +++ b/client/src/assets/components/_user-layout.scss @@ -107,3 +107,18 @@ section.content { display: flex; flex-direction: column; } + +@media only screen and (max-width: 800px) { + header.header { + > .inner > .menu-button { + font-size: 175%; + } + + .dashboard-head { + > button, + .filter-button { + font-size: 150%; + } + } + } +} diff --git a/client/src/assets/pages/_dashboard-page.scss b/client/src/assets/pages/_dashboard-page.scss index a3e423d..078ef8c 100644 --- a/client/src/assets/pages/_dashboard-page.scss +++ b/client/src/assets/pages/_dashboard-page.scss @@ -59,6 +59,7 @@ display: flex; max-width: 100%; overflow: auto; + transition: right 0.1s; .inner { background-color: var(--box-bg-color); diff --git a/client/src/assets/themes/_basic.scss b/client/src/assets/themes/_basic.scss index f5c5744..c2ef140 100644 --- a/client/src/assets/themes/_basic.scss +++ b/client/src/assets/themes/_basic.scss @@ -1,5 +1,5 @@ :root { - --main-font: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; + --main-font: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; --font-size: 14px; --border-radius: 0.15rem; --main-bg-color: #eee; @@ -14,7 +14,11 @@ --button-remove-fg-color: #f00; --header-bg-color: #fff; --header-spacer-color: #ddd; - --header-shadow: linear-gradient(0deg, rgba(255, 255, 255, 0) 5%, rgba(190, 190, 190, 0.6) 100%); + --header-shadow: linear-gradient( + 0deg, + rgba(255, 255, 255, 0) 5%, + rgba(190, 190, 190, 0.6) 100% + ); --box-bg-color: #fff; --box-fg-color: #111; --box-border-color: #ddd; @@ -30,8 +34,16 @@ --graph-axis-fg-color: #777; --graph-grid-color: rgb(238, 238, 238); --link-fg-color: #3988ff; - --menu-shadow: linear-gradient(270deg, rgba(255, 255, 255, 0) 0%, rgba(90, 90, 90, 0.2) 100%); - --filters-menu-shadow: linear-gradient(90deg, rgba(255, 255, 255, 0) 0%, rgba(90, 90, 90, 0.2) 100%); + --menu-shadow: linear-gradient( + 270deg, + rgba(255, 255, 255, 0) 0%, + rgba(90, 90, 90, 0.2) 100% + ); + --filters-menu-shadow: linear-gradient( + 90deg, + rgba(255, 255, 255, 0) 0%, + rgba(90, 90, 90, 0.2) 100% + ); --input-border-color: #ddd; --input-focus-border-color: #3988ff; --input-bg-color: #fff; @@ -58,14 +70,19 @@ --button-bg-color: #0b3c9f; --button-fg-color: #eee; --button-cancel-fg-color: #ccc; - --modal-overlay-bg-color: rgba(128, 128, 128, 0.3); - --confirm-overlay-bg-color: rgba(128, 128, 128, 0.5); + --modal-overlay-bg-color: rgba(0, 0, 0, 0.3); + --confirm-overlay-bg-color: rgba(0, 0, 0, 0.5); --input-border-color: #333; --input-focus-border-color: #666; --input-bg-color: #222; --input-fg-color: #ccc; --input-disabled-fg-color: #bbb; --input-disabled-bg-color: #2a2a2a; + --menu-shadow: linear-gradient( + 270deg, + rgba(0, 0, 0, 0) 0%, + rgba(0, 0, 0, 0.2) 100% + ); } .sensor-item .auth .auth-value .label { diff --git a/client/src/pages/dashboard/components/BoxSettings/components/ColorPointsEditor.tsx b/client/src/pages/dashboard/components/BoxSettings/components/ColorPointsEditor.tsx new file mode 100644 index 0000000..d9c745a --- /dev/null +++ b/client/src/pages/dashboard/components/BoxSettings/components/ColorPointsEditor.tsx @@ -0,0 +1,65 @@ +export type ColorPoint = { + value: number + color: string +} + +type Props = { + value: ColorPoint[] + onChange: (v: ColorPoint[]) => void +} + +export const ColorPointsEditor = ({ value, onChange }: Props) => { + const handleColorChange = (index: number) => (e: Event) => { + const target = e.currentTarget as HTMLInputElement | null + + if (target) { + const newValue = [...value] + + newValue[index] = { + ...value[index], + color: target.value, + } + + onChange(newValue) + } + } + + const handleValueChange = (index: number) => (e: Event) => { + const target = e.currentTarget as HTMLInputElement | null + + if (target) { + const newValue = [...value] + + newValue[index] = { + ...value[index], + value: +target.value, + } + + onChange(newValue) + } + } + + const handleAdd = () => { + onChange([...value, { value: 0, color: '#fff' }]) + } + + return ( + <> + {value.map((v, i) => ( +
+ + +
+ ))} + + + + ) +} diff --git a/client/src/pages/dashboard/components/BoxSettings/components/GraphSettings.tsx b/client/src/pages/dashboard/components/BoxSettings/components/GraphSettings.tsx index 9b0fa2d..a5b03b7 100644 --- a/client/src/pages/dashboard/components/BoxSettings/components/GraphSettings.tsx +++ b/client/src/pages/dashboard/components/BoxSettings/components/GraphSettings.tsx @@ -2,6 +2,7 @@ import { SensorInfo } from '@/api/sensors' import { DashboardGraphData } from '@/utils/dashboard/parseDashboard' import { useForm } from '@/utils/hooks/useForm' import { omit } from '@/utils/omit' +import { ColorPoint, ColorPointsEditor } from './ColorPointsEditor' type Props = { sensors: SensorInfo[] @@ -12,11 +13,16 @@ type Props = { export const GraphSettings = ({ value, onChange, sensors }: Props) => { const { register, watch } = useForm({ defaultValue: () => ({ - ...(value && omit(value, ['type'])), + ...(value && omit(value, ['type', 'colorPoints'])), }), onChange: (v) => onChange({ ...v, type: 'graph' }), }) + const colorPoints = value?.colorPoints ?? [] + + const setColorPoints = (colorPoints: ColorPoint[]) => + onChange({ ...value, type: 'graph', colorPoints }) + const colorMode = watch('colorMode') return ( @@ -69,6 +75,7 @@ export const GraphSettings = ({ value, onChange, sensors }: Props) => { @@ -78,6 +85,10 @@ export const GraphSettings = ({ value, onChange, sensors }: Props) => { )} + + {colorMode === 'points' && ( + + )} ) } diff --git a/client/src/pages/dashboard/components/DashboardGrid/components/BoxGraphContent.tsx b/client/src/pages/dashboard/components/DashboardGrid/components/BoxGraphContent.tsx index 0ae379c..7c99831 100644 --- a/client/src/pages/dashboard/components/DashboardGrid/components/BoxGraphContent.tsx +++ b/client/src/pages/dashboard/components/DashboardGrid/components/BoxGraphContent.tsx @@ -1,9 +1,9 @@ import { getSensorValues } from '@/api/sensorValues' import { useDashboardContext } from '@/pages/dashboard/contexts/DashboardContext' import { BoxDefinition } from '@/pages/dashboard/types' +import { DashboardGraphData } from '@/utils/dashboard/parseDashboard' import { max } from '@/utils/max' import { min } from '@/utils/min' -import { DashboardGraphData } from '@/utils/dashboard/parseDashboard' import { RefObject } from 'preact' import { useEffect, useRef } from 'preact/hooks' import { useQuery } from 'react-query' @@ -64,6 +64,7 @@ export const BoxGraphContent = ({ box, data, refreshRef }: Props) => { const fill = data.fill ?? undefined const colorMode = data.colorMode const staticColor = data.staticColor + // const colorPoints = data.colorPoints ?? [] const x = values.data.map((v) => new Date(v.timestamp * 1000)) const y = values.data.map((v) => v.value) diff --git a/client/src/pages/dashboard/components/DashboardHeader/DashboardHeader.tsx b/client/src/pages/dashboard/components/DashboardHeader/DashboardHeader.tsx index 59a4870..0803d10 100644 --- a/client/src/pages/dashboard/components/DashboardHeader/DashboardHeader.tsx +++ b/client/src/pages/dashboard/components/DashboardHeader/DashboardHeader.tsx @@ -57,28 +57,29 @@ export const DashboardHeader = () => { - {filtersShown && ( - <> -
-
-
-
setFiltersShown(false)} - > - -
- - - - -
-
+
+
+
setFiltersShown(false)} - >
- + > + +
+ + + + setFiltersShown(false)} /> +
+
+ {filtersShown && ( +
setFiltersShown(false)} + >
)} )} diff --git a/client/src/pages/dashboard/utils/getColorFromColorPoints.ts b/client/src/pages/dashboard/utils/getColorFromColorPoints.ts new file mode 100644 index 0000000..7684acb --- /dev/null +++ b/client/src/pages/dashboard/utils/getColorFromColorPoints.ts @@ -0,0 +1,44 @@ +import { hex2Rgb } from '@/utils/hex2Rgb' +import { interpolateColors } from '@/utils/interpolateColors' +import { ColorPoint } from '../components/BoxSettings/components/ColorPointsEditor' +import { rgb2Hex } from '@/utils/rgb2Hex' + +type Props = { + colorPoints: ColorPoint[] + value: number +} + +export const getColorFromColorPoints = ({ colorPoints, value }: Props) => { + let previousPoint = colorPoints[0] + let currentPoint = colorPoints[0] + let index = 0 + + while (currentPoint.value < value) { + if (index === colorPoints.length - 1) { + currentPoint = colorPoints[colorPoints.length - 1] + previousPoint = colorPoints[colorPoints.length - 1] + + break + } + + currentPoint = colorPoints[index] + previousPoint = colorPoints[index + 1] + + index++ + } + + if (currentPoint.color !== previousPoint.color) { + const diff = currentPoint.value - previousPoint.value + const rate = (value - previousPoint.value) / diff + + return rgb2Hex( + interpolateColors( + hex2Rgb(previousPoint.color), + hex2Rgb(currentPoint.color), + rate + ) + ) + } else { + return rgb2Hex(hex2Rgb(currentPoint.color)) + } +} diff --git a/client/src/utils/dashboard/parseDashboard.ts b/client/src/utils/dashboard/parseDashboard.ts index 62fe8fc..64bc941 100644 --- a/client/src/utils/dashboard/parseDashboard.ts +++ b/client/src/utils/dashboard/parseDashboard.ts @@ -30,8 +30,9 @@ export type DashboardGraphData = { | 'tonextx' | 'toself' | 'tonext' - colorMode?: 'static' + colorMode?: 'static' | 'points' staticColor?: string + colorPoints?: { value: number; color: string }[] } export type DashboardDialData = { diff --git a/client/src/utils/hex2Rgb.ts b/client/src/utils/hex2Rgb.ts new file mode 100644 index 0000000..53d1b20 --- /dev/null +++ b/client/src/utils/hex2Rgb.ts @@ -0,0 +1,11 @@ +export function hex2Rgb(hex: string): [number, number, number] { + const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex) + + return result + ? [ + parseInt(result[1], 16), + parseInt(result[2], 16), + parseInt(result[3], 16), + ] + : [0, 0, 0] +} diff --git a/client/src/utils/hooks/useForm.ts b/client/src/utils/hooks/useForm.ts index d2c55f1..c6e8c00 100644 --- a/client/src/utils/hooks/useForm.ts +++ b/client/src/utils/hooks/useForm.ts @@ -84,7 +84,7 @@ export const useForm = ({ ) const watch = useCallback( - (name: keyof TValue) => { + (name: TName) => { const [value, setValue] = useState(internalRef.current.value[name] ?? '') useEffect(() => { diff --git a/client/src/utils/hsl2Rgb.ts b/client/src/utils/hsl2Rgb.ts new file mode 100644 index 0000000..3029670 --- /dev/null +++ b/client/src/utils/hsl2Rgb.ts @@ -0,0 +1,44 @@ +export function hsl2Rgb( + color: [number, number, number] +): [number, number, number] { + let l = color[2] + + if (color[1] == 0) { + l = Math.round(l * 255) + + return [l, l, l] + } else { + function hue2rgb(p: number, q: number, t: number) { + if (t < 0) { + t += 1 + } + + if (t > 1) { + t -= 1 + } + + if (t < 1 / 6) { + return p + (q - p) * 6 * t + } + + if (t < 1 / 2) { + return q + } + + if (t < 2 / 3) { + return p + (q - p) * (2 / 3 - t) * 6 + } + + return p + } + + const s = color[1] + const q = l < 0.5 ? l * (1 + s) : l + s - l * s + const p = 2 * l - q + const r = hue2rgb(p, q, color[0] + 1 / 3) + const g = hue2rgb(p, q, color[0]) + const b = hue2rgb(p, q, color[0] - 1 / 3) + + return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)] + } +} diff --git a/client/src/utils/interpolateColors.ts b/client/src/utils/interpolateColors.ts new file mode 100644 index 0000000..193fa8f --- /dev/null +++ b/client/src/utils/interpolateColors.ts @@ -0,0 +1,13 @@ +export const interpolateColors = ( + color1: [number, number, number], + color2: [number, number, number], + factor = 0.5 +) => { + const result = color1.slice() as [number, number, number] + + for (let i = 0; i < 3; i++) { + result[i] = Math.round(result[i] + factor * (color2[i] - color1[i])) + } + + return result +} diff --git a/client/src/utils/rgb2Hex.ts b/client/src/utils/rgb2Hex.ts new file mode 100644 index 0000000..305ab84 --- /dev/null +++ b/client/src/utils/rgb2Hex.ts @@ -0,0 +1,6 @@ +export function rgb2Hex(rgb: [number, number, number]) { + return ( + '#' + + ((1 << 24) + (rgb[0] << 16) + (rgb[1] << 8) + rgb[2]).toString(16).slice(1) + ) +} diff --git a/client/src/utils/rgb2Hsl.ts b/client/src/utils/rgb2Hsl.ts new file mode 100644 index 0000000..68707c8 --- /dev/null +++ b/client/src/utils/rgb2Hsl.ts @@ -0,0 +1,37 @@ +export function rgb2Hsl( + color: [number, number, number] +): [number, number, number] { + const r = color[0] / 255 + const g = color[1] / 255 + const b = color[2] / 255 + + const max = Math.max(r, g, b), + min = Math.min(r, g, b) + + let h = 0, + s + const l = (max + min) / 2 + + if (max == min) { + h = s = 0 // achromatic + } else { + const d = max - min + s = l > 0.5 ? d / (2 - max - min) : d / (max + min) + + switch (max) { + case r: + h = (g - b) / d + (g < b ? 6 : 0) + break + case g: + h = (b - r) / d + 2 + break + case b: + h = (r - g) / d + 4 + break + } + + h /= 6 + } + + return [h, s, l] +} diff --git a/client/yarn.lock b/client/yarn.lock index b6f7cba..3815cf7 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -664,13 +664,6 @@ __metadata: languageName: node linkType: hard -"@types/d3@npm:^3": - version: 3.5.47 - resolution: "@types/d3@npm:3.5.47" - checksum: f49657cb30705125de0de0617194f78ca5c05c1e1900485f2ea4c438d5b7744ff7d53b31e814caf986b994d21997edcda9fd845df115193fca2667bd096d0fdf - languageName: node - linkType: hard - "@types/json-schema@npm:^7.0.9": version: 7.0.11 resolution: "@types/json-schema@npm:7.0.11" @@ -685,12 +678,10 @@ __metadata: languageName: node linkType: hard -"@types/plotly.js@npm:^2.12.2": - version: 2.12.2 - resolution: "@types/plotly.js@npm:2.12.2" - dependencies: - "@types/d3": ^3 - checksum: aa6a1bcd78869a79f299a5cd58886a3e5d9eaa16561f0da90eb77c172972c7b0699ba537bf60579e2904fc281b42dbd3692cdba6b994d63eaf1c2f0d5bc2cd38 +"@types/plotly.js@npm:^2.29.2": + version: 2.29.2 + resolution: "@types/plotly.js@npm:2.29.2" + checksum: 45ebf6219b3b92acfea5ef6086f7a71e2e20e689dc950d509fbc689113b8ff6db9c4242f2c4518b86ab7385a3f406a39c5dbcde3bdbbe7cc7a2362f0c111b9eb languageName: node linkType: hard @@ -1188,7 +1179,7 @@ __metadata: resolution: "client@workspace:." dependencies: "@preact/preset-vite": ^2.3.0 - "@types/plotly.js": ^2.12.2 + "@types/plotly.js": ^2.29.2 "@typescript-eslint/eslint-plugin": ^5.7.0 "@typescript-eslint/parser": ^5.7.0 eslint: ^8.5.0