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