Skip to content

Commit

Permalink
responsive charts with a normalized data source
Browse files Browse the repository at this point in the history
  • Loading branch information
stan-dot committed Jul 25, 2024
1 parent f282006 commit add0e8f
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 48 deletions.
8 changes: 5 additions & 3 deletions plotting-preview/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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() {

Expand All @@ -21,12 +22,13 @@ function App() {
}, []);
return (
<>
<h1>Vite + React</h1>
{/* <h1>Vite + React</h1>
<div><h3> hello response:</h3>
<p>{response}</p>
</div>
<Plot />
<WebsocketChart />
<Plot /> */}
{/* <WebsocketChart /> */}
<ColorsChart />
</>
)
}
Expand Down
36 changes: 36 additions & 0 deletions plotting-preview/src/components/ColorRect.tsx
Original file line number Diff line number Diff line change
@@ -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<ColorRectProps> = ({
x = 0,
y = 0,
width = 50,
height = 50,
color = '#000000',
shadowBlur = 5,
key
}) => {
return (
<Rect
x={x}
y={y}
width={width}
height={height}
fill={color}
shadowBlur={shadowBlur}
key={key}
/>
);
};

export default ColorRect;
62 changes: 44 additions & 18 deletions plotting-preview/src/components/ColorsChart.tsx
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -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`;
Expand Down Expand Up @@ -42,16 +44,17 @@ export function OneColorCanvas({ name }: OneColorCanvasProps) {
const [data, setData] = useState<number[]>([]);
const ws = useRef<WebSocket | null>(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);
});
}
};
Expand All @@ -65,21 +68,23 @@ export function OneColorCanvas({ name }: OneColorCanvasProps) {
}, []);
const getHex = intensityClosure(name);


return (
<div id={name}>
<div>On color {name} stuffs</div>
<Stage width={window.innerWidth} height={window.innerHeight}>
<div>On color {name}</div>
{/* <p>data length: {data.length}</p> */}
<Stage width={stageSize.width} height={stageSize.height}>
<Layer>
{data.map((intensity, index) => {
const color = getHex(intensity);
<Rect
console.log('color: ', color, " intensity :", intensity);
return <ColorRect
key={index}
x={20 + (index % 10) * 60} // Example layout
y={20 + Math.floor(index / 10) * 60}
width={50}
height={50}
fill={color}
shadowBlur={5}
color={color}
x={20 + (index % 10) * rectSize.spacing} // Example layout
y={20 + Math.floor(index / 10) * rectSize.spacing}
width={rectSize.width}
height={rectSize.height}
/>
})}
</Layer>
Expand All @@ -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 (
<div className="websocketed">
<h3>Colors Data</h3>
<OneColorCanvas name={'r'} />
<OneColorCanvas name={'g'} />
<OneColorCanvas name={'b'} />
<OneColorCanvas name={'t'} />
{status === 'ready' && <button onClick={handleStart}>Start</button>}
{(status === 'running' || status === 'finished') && (
<div id="output" style={{ display: 'flex', flexDirection: 'row', width: window.innerWidth }}>
<OneColorCanvas name={'r'} />
<OneColorCanvas name={'g'} />
<OneColorCanvas name={'b'} />
{/* <OneColorCanvas name={'t'} /> */}
</div>
)}
</div>
);
}

export default ColorsChart;
40 changes: 40 additions & 0 deletions plotting-preview/src/hooks/userResponsiveStage.tsx
Original file line number Diff line number Diff line change
@@ -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<StageSize>({ width: 600, height: 400 });
const [rectSize, setRectSize] = useState<RectSize>({ 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 };
}
67 changes: 40 additions & 27 deletions src/plotting_server/start.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down

0 comments on commit add0e8f

Please sign in to comment.