Spaces:
Sleeping
Sleeping
Dacho688
commited on
Commit
·
8a03cf9
0
Parent(s):
Initial commit
Browse files- .gitattributes +2 -0
- .gitignore +3 -0
- Dockerfile +7 -0
- app/app.py +91 -0
- docker-compose.yaml +7 -0
- requirements.txt +16 -0
.gitattributes
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Auto detect text files and perform LF normalization
|
| 2 |
+
* text=auto
|
.gitignore
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.env
|
| 2 |
+
.vscode/
|
| 3 |
+
.venv/
|
Dockerfile
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Dockerfile
|
| 2 |
+
FROM python:3.12-slim
|
| 3 |
+
WORKDIR /app
|
| 4 |
+
COPY requirements.txt .
|
| 5 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 6 |
+
COPY ./app .
|
| 7 |
+
CMD ["bokeh", "serve", "--port", "5006", "--allow-websocket-origin=*", "app.py"]
|
app/app.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import threading
|
| 2 |
+
import time
|
| 3 |
+
from functools import partial
|
| 4 |
+
from bokeh.plotting import figure, curdoc
|
| 5 |
+
from bokeh.models import ColumnDataSource, LabelSet
|
| 6 |
+
from datetime import datetime
|
| 7 |
+
import random
|
| 8 |
+
import numpy as np
|
| 9 |
+
|
| 10 |
+
class BokehApp:
|
| 11 |
+
def __init__(self, doc):
|
| 12 |
+
self.doc = doc
|
| 13 |
+
# Create a threading.Event to signal the thread to shut down
|
| 14 |
+
self.stop_event = threading.Event()
|
| 15 |
+
|
| 16 |
+
# Create a sample plot
|
| 17 |
+
x = datetime.now()
|
| 18 |
+
y = random.uniform(0, 100)
|
| 19 |
+
self.source = ColumnDataSource(data=dict(x=[x], y=[y]))
|
| 20 |
+
self.label_source = self.source.clone()
|
| 21 |
+
p = figure(x_axis_type='datetime',
|
| 22 |
+
width = 1800,
|
| 23 |
+
height = 900,
|
| 24 |
+
x_axis_label="Time",
|
| 25 |
+
y_axis_label="Value",
|
| 26 |
+
title="Streaming Random Data",
|
| 27 |
+
)
|
| 28 |
+
p.title.align = "center"
|
| 29 |
+
p.title.text_font_size="20px"
|
| 30 |
+
p.toolbar.autohide = True
|
| 31 |
+
p.line(x='x', y='y', source=self.source)
|
| 32 |
+
p.scatter(x='x', y='y', source=self.source, size=8)
|
| 33 |
+
|
| 34 |
+
#Create value Label
|
| 35 |
+
label = LabelSet(x = 'x',
|
| 36 |
+
y = 'y',
|
| 37 |
+
text= 'y',
|
| 38 |
+
source = self.label_source,
|
| 39 |
+
x_offset=5, y_offset=5,
|
| 40 |
+
text_font_size="10pt")
|
| 41 |
+
p.add_layout(label)
|
| 42 |
+
# Start the background thread
|
| 43 |
+
self.thread = threading.Thread(target=self.get_data, args=(self.stop_event,), daemon=True)
|
| 44 |
+
self.thread.start()
|
| 45 |
+
|
| 46 |
+
# Register the cleanup function
|
| 47 |
+
self.doc.on_session_destroyed(self.on_session_destroyed)
|
| 48 |
+
|
| 49 |
+
# Add the plot to the document
|
| 50 |
+
self.doc.add_root(p)
|
| 51 |
+
|
| 52 |
+
def get_data(self, stop_event):
|
| 53 |
+
"""
|
| 54 |
+
Function to run in a separate thread.
|
| 55 |
+
Infinite while loop until stop_event is set to True with on_session_destroyed().
|
| 56 |
+
Simulates a long-running data-fetching task.
|
| 57 |
+
"""
|
| 58 |
+
curr_value = self.source.data['y'][-1]
|
| 59 |
+
log_curr_value = np.log(curr_value)
|
| 60 |
+
|
| 61 |
+
while not stop_event.is_set():
|
| 62 |
+
x = datetime.now()
|
| 63 |
+
log_random_growth = random.normalvariate(mu=.00001, sigma=.001)
|
| 64 |
+
log_y = log_curr_value + log_random_growth
|
| 65 |
+
log_curr_value = log_y
|
| 66 |
+
y = np.round(np.exp(log_curr_value),2)
|
| 67 |
+
new_data = dict(x=[x], y=[y])
|
| 68 |
+
|
| 69 |
+
# Safely schedule the update using a next tick callback
|
| 70 |
+
self.doc.add_next_tick_callback(partial(self.update_plot, new_data))
|
| 71 |
+
time.sleep(1)
|
| 72 |
+
|
| 73 |
+
print("Background thread is shutting down gracefully.")
|
| 74 |
+
|
| 75 |
+
def update_plot(self, new_data):
|
| 76 |
+
"""
|
| 77 |
+
This function is executed by the next tick callback on the main thread.
|
| 78 |
+
It safely updates the ColumnDataSource.
|
| 79 |
+
"""
|
| 80 |
+
self.source.stream(new_data, rollover=1000)
|
| 81 |
+
self.label_source.data = new_data
|
| 82 |
+
|
| 83 |
+
def on_session_destroyed(self, session_context):
|
| 84 |
+
"""
|
| 85 |
+
Callback function that runs when a user closes the document.
|
| 86 |
+
"""
|
| 87 |
+
print(f"Session {session_context.id} destroyed. Stopping background thread.\n", flush=True)
|
| 88 |
+
self.stop_event.set()
|
| 89 |
+
|
| 90 |
+
# Create an instance of the class when the script is run by the Bokeh server
|
| 91 |
+
BokehApp(curdoc())
|
docker-compose.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
services:
|
| 2 |
+
app:
|
| 3 |
+
build: .
|
| 4 |
+
container_name: app
|
| 5 |
+
restart: unless-stopped
|
| 6 |
+
ports:
|
| 7 |
+
- "5006:5006"
|
requirements.txt
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
bokeh==3.8.1
|
| 2 |
+
contourpy==1.3.3
|
| 3 |
+
Jinja2==3.1.6
|
| 4 |
+
MarkupSafe==3.0.3
|
| 5 |
+
narwhals==2.12.0
|
| 6 |
+
numpy==2.3.5
|
| 7 |
+
packaging==25.0
|
| 8 |
+
pandas==2.3.3
|
| 9 |
+
pillow==12.0.0
|
| 10 |
+
python-dateutil==2.9.0.post0
|
| 11 |
+
pytz==2025.2
|
| 12 |
+
PyYAML==6.0.3
|
| 13 |
+
six==1.17.0
|
| 14 |
+
tornado==6.5.2
|
| 15 |
+
tzdata==2025.2
|
| 16 |
+
xyzservices==2025.10.0
|