From add0e8f8a9e2ad49569aaebd726be45424ff1a12 Mon Sep 17 00:00:00 2001 From: Stanislaw Malinowski Date: Thu, 25 Jul 2024 14:17:33 +0100 Subject: [PATCH] responsive charts with a normalized data source --- plotting-preview/src/App.tsx | 8 ++- plotting-preview/src/components/ColorRect.tsx | 36 ++++++++++ .../src/components/ColorsChart.tsx | 62 ++++++++++++----- .../src/hooks/userResponsiveStage.tsx | 40 +++++++++++ src/plotting_server/start.py | 67 +++++++++++-------- 5 files changed, 165 insertions(+), 48 deletions(-) create mode 100644 plotting-preview/src/components/ColorRect.tsx create mode 100644 plotting-preview/src/hooks/userResponsiveStage.tsx diff --git a/plotting-preview/src/App.tsx b/plotting-preview/src/App.tsx index 6305344..91f6491 100644 --- a/plotting-preview/src/App.tsx +++ b/plotting-preview/src/App.tsx @@ -2,6 +2,7 @@ import { useState, useEffect } from 'react'; import './App.css' import Plot from './components/Plot' import WebsocketChart from './components/WebsocketChart'; +import ColorsChart from './components/ColorsChart'; function App() { @@ -21,12 +22,13 @@ function App() { }, []); return ( <> -

Vite + React

+ {/*

Vite + React

hello response:

{response}

- - + */} + {/* */} + ) } diff --git a/plotting-preview/src/components/ColorRect.tsx b/plotting-preview/src/components/ColorRect.tsx new file mode 100644 index 0000000..1a00f83 --- /dev/null +++ b/plotting-preview/src/components/ColorRect.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { Rect } from 'react-konva'; + +interface ColorRectProps { + x?: number; + y?: number; + width?: number; + height?: number; + color?: string; + shadowBlur?: number; + key?: React.Key; +} + +const ColorRect: React.FC = ({ + x = 0, + y = 0, + width = 50, + height = 50, + color = '#000000', + shadowBlur = 5, + key +}) => { + return ( + + ); +}; + +export default ColorRect; diff --git a/plotting-preview/src/components/ColorsChart.tsx b/plotting-preview/src/components/ColorsChart.tsx index 3a27b27..a3f8782 100644 --- a/plotting-preview/src/components/ColorsChart.tsx +++ b/plotting-preview/src/components/ColorsChart.tsx @@ -1,5 +1,7 @@ import { useEffect, useRef, useState } from 'react'; import { Layer, Rect, Stage } from 'react-konva'; +import ColorRect from './ColorRect'; +import { useResponsiveStage } from '../hooks/userResponsiveStage'; type Colors = 'r' | 'g' | 'b' | 't'; @@ -12,7 +14,7 @@ const intensityClosure = (name: Colors) => { let getHex = (intensity: number) => "#000000"; switch (name) { case 'r': - getHex = (intensity: number) => `#${intensity.toString(16).padStart(2, '0')}0000`; + getHex = (intensity: number) => `#${intensity.toString(16)}0000`; break; case 'g': getHex = (intensity: number) => `#00${intensity.toString(16).padStart(2, '0')}00`; @@ -42,16 +44,17 @@ export function OneColorCanvas({ name }: OneColorCanvasProps) { const [data, setData] = useState([]); const ws = useRef(null); + const { stageSize, rectSize } = useResponsiveStage(); useEffect(() => { // Establish WebSocket connection ws.current = new WebSocket('/ws/colors'); - console.log(ws.current); + // console.log(name, ws.current); ws.current.onmessage = (event) => { const parsedData: ColorEvent = JSON.parse(event.data); if (parsedData.c == name) { setData(prevData => { const newData = [...prevData, parsedData.i]; - return newData + return newData.slice(-50); }); } }; @@ -65,21 +68,23 @@ export function OneColorCanvas({ name }: OneColorCanvasProps) { }, []); const getHex = intensityClosure(name); + return (
-
On color {name} stuffs
- +
On color {name}
+ {/*

data length: {data.length}

*/} + {data.map((intensity, index) => { const color = getHex(intensity); - })} @@ -89,17 +94,38 @@ export function OneColorCanvas({ name }: OneColorCanvasProps) { } - function ColorsChart() { + const [status, setStatus] = useState<'ready' | 'starting' | 'running' | 'finished' | 'unknown'>('ready'); + + const handleStart = async () => { + setStatus('starting'); + try { + const response = await fetch('/api/start', { method: 'POST' }); + setStatus('running'); + // if (response.ok) { + // setStatus('running'); + // } else { + // setStatus('unknown'); + // } + } catch (error) { + console.error('Error starting the process:', error); + setStatus('unknown'); + } + }; + return (

Colors Data

- - - - + {status === 'ready' && } + {(status === 'running' || status === 'finished') && ( +
+ + + + {/* */} +
+ )}
); } - export default ColorsChart; diff --git a/plotting-preview/src/hooks/userResponsiveStage.tsx b/plotting-preview/src/hooks/userResponsiveStage.tsx new file mode 100644 index 0000000..a1cf235 --- /dev/null +++ b/plotting-preview/src/hooks/userResponsiveStage.tsx @@ -0,0 +1,40 @@ +import { useState, useEffect } from 'react'; + +interface StageSize { + width: number; + height: number; +} + +interface RectSize { + width: number; + height: number; + spacing: number; +} + +export function useResponsiveStage(): { stageSize: StageSize; rectSize: RectSize } { + const [stageSize, setStageSize] = useState({ width: 600, height: 400 }); + const [rectSize, setRectSize] = useState({ width: 50, height: 50, spacing: 60 }); + + useEffect(() => { + const updateSize = () => { + if (window.innerWidth > 1024) { + // For large screens (like 24-inch monitors) + setStageSize({ width: 1200, height: 800 }); + setRectSize({ width: 50, height: 50, spacing: 60 }); + } else { + // For small screens (like 6-inch phones) + setStageSize({ width: window.innerWidth - 20, height: (window.innerHeight / 2) - 20 }); + setRectSize({ width: 25, height: 25, spacing: 30 }); + } + }; + + window.addEventListener('resize', updateSize); + updateSize(); // Set the initial size + + return () => { + window.removeEventListener('resize', updateSize); + }; + }, []); + + return { stageSize, rectSize }; +} diff --git a/src/plotting_server/start.py b/src/plotting_server/start.py index 21ac312..f12d006 100644 --- a/src/plotting_server/start.py +++ b/src/plotting_server/start.py @@ -56,47 +56,60 @@ async def websocket_endpoint(websocket: WebSocket): await websocket.close() - # Generate 10,000 random integers between 0 and 255 def generate_rgb_array(size=10000): return np.random.randint(0, 256, size=size, dtype=np.uint8) + # Initialize RGB arrays r_array = generate_rgb_array() g_array = generate_rgb_array() b_array = generate_rgb_array() +# # Normalize color data +# r_max = np.max(r_array) +# g_max = np.max(g_array) +# b_max = np.max(b_array) + +# # Avoid division by zero if max is zero +# r_max = r_max if r_max != 0 else 1 +# g_max = g_max if g_max != 0 else 1 +# b_max = b_max if b_max != 0 else 1 + +# # Normalize the arrays +# r_normalized = r_array / r_max * 255 +# g_normalized = g_array / g_max * 255 +# b_normalized = b_array / b_max * 255 + +interval = 0.6 +# 0.1 to Emit every 100 ms (10 Hz) + @app.websocket("/ws/colors") async def colors_websocket_endpoint(websocket: WebSocket): await websocket.accept() try: while True: - # Simulate data generation - now = time.time() - # Compute total of RGB values - r_total = np.sum(r_array) - g_total = np.sum(g_array) - b_total = np.sum(b_array) - total = r_total + g_total + b_total - - # Generate intensity value as a random 8-bit integer - intensity = random.randint(0, 255) - - # Create JSON data format - # todo decide if 1 message parsed on the frontend or 3 different messages - data = { - "c": { - "r": r_total, - "g": g_total, - "b": b_total, - "total": total - }, - "i": intensity - } - - # Send JSON data - await websocket.send_json(data) - await asyncio.sleep(0.1) # Emit every 100 ms (10 Hz) + for r, g, b in zip(r_array, g_array, b_array, strict=False): + total = r + g + b + + # Convert NumPy integers to standard Python integers + r = int(r) + g = int(g) + b = int(b) + total = int(total) + # todo total will be histagrammed + # print(r,g,b) + + # Create JSON data format + red_data = {"c": "r", "i": r} + green_data = {"c": "g", "i": g} + blue_data = {"c": "b", "i": b} + total_data = {"c": "t", "i": total} + + for data in [red_data, green_data, blue_data, total_data]: + # Send JSON data + await websocket.send_json(data) + await asyncio.sleep(interval) except Exception as e: print(f"Error: {e}") finally: