|
| 1 | +""" pyplots.ai |
| 2 | +line-realtime: Real-Time Updating Line Chart |
| 3 | +Library: bokeh 3.8.1 | Python 3.13.11 |
| 4 | +Quality: 91/100 | Created: 2025-12-31 |
| 5 | +""" |
| 6 | + |
| 7 | +import numpy as np |
| 8 | +import pandas as pd |
| 9 | +from bokeh.io import export_png, output_file, save |
| 10 | +from bokeh.models import Arrow, ColumnDataSource, Label, Range1d, VeeHead |
| 11 | +from bokeh.plotting import figure |
| 12 | + |
| 13 | + |
| 14 | +# Data - Simulated CPU usage over time with sliding window effect |
| 15 | +np.random.seed(42) |
| 16 | + |
| 17 | +# Generate 100 points of CPU usage data (we'll show last 60 as "visible window") |
| 18 | +n_total = 100 |
| 19 | +n_visible = 60 |
| 20 | + |
| 21 | +# Create timestamps (every 100ms for last 10 seconds) |
| 22 | +timestamps = pd.date_range(end=pd.Timestamp.now(), periods=n_total, freq="100ms") |
| 23 | + |
| 24 | +# Generate realistic CPU usage with some patterns |
| 25 | +base_usage = 45 # Base CPU usage % |
| 26 | +trend = np.linspace(0, 10, n_total) # Slight upward trend |
| 27 | +noise = np.cumsum(np.random.randn(n_total) * 2) # Random walk component |
| 28 | +spikes = np.zeros(n_total) |
| 29 | +spike_indices = [15, 35, 55, 78] # Add some usage spikes |
| 30 | +for idx in spike_indices: |
| 31 | + if idx < n_total: |
| 32 | + spikes[idx : idx + 5] = np.array([15, 25, 20, 10, 5])[: n_total - idx] |
| 33 | + |
| 34 | +cpu_usage = base_usage + trend + noise + spikes |
| 35 | +cpu_usage = np.clip(cpu_usage, 5, 95) # Keep between 5-95% |
| 36 | + |
| 37 | +# For static image: show the "visible window" portion |
| 38 | +visible_timestamps = timestamps[-n_visible:] |
| 39 | +visible_values = cpu_usage[-n_visible:] |
| 40 | + |
| 41 | +# Create color gradient to show recency (older points fade) |
| 42 | +alpha_values = np.linspace(0.3, 1.0, n_visible) |
| 43 | +point_sizes = np.linspace(15, 30, n_visible) |
| 44 | + |
| 45 | +# Create source for the main line with all data columns |
| 46 | +source = ColumnDataSource( |
| 47 | + data={"x": visible_timestamps, "y": visible_values, "alpha": alpha_values, "size": point_sizes} |
| 48 | +) |
| 49 | + |
| 50 | +# Create figure - 4800x2700 for 16:9 |
| 51 | +p = figure( |
| 52 | + width=4800, |
| 53 | + height=2700, |
| 54 | + title="CPU Usage Monitor · line-realtime · bokeh · pyplots.ai", |
| 55 | + x_axis_label="Time", |
| 56 | + y_axis_label="CPU Usage (%)", |
| 57 | + x_axis_type="datetime", |
| 58 | + tools="", |
| 59 | + toolbar_location=None, |
| 60 | +) |
| 61 | + |
| 62 | +# Set axis range for Y with some padding |
| 63 | +p.y_range = Range1d(0, 100) |
| 64 | + |
| 65 | +# Draw gradient segments to show fading effect on older data |
| 66 | +# Use multiple line segments with decreasing alpha |
| 67 | +segment_size = 5 |
| 68 | +for i in range(0, n_visible - segment_size, segment_size): |
| 69 | + segment_x = visible_timestamps[i : i + segment_size + 1] |
| 70 | + segment_y = visible_values[i : i + segment_size + 1] |
| 71 | + segment_alpha = 0.3 + 0.7 * (i / n_visible) |
| 72 | + p.line(x=segment_x, y=segment_y, line_width=6, line_color="#306998", line_alpha=segment_alpha) |
| 73 | + |
| 74 | +# Draw the most recent portion with full opacity |
| 75 | +p.line(x=visible_timestamps[-15:], y=visible_values[-15:], line_width=8, line_color="#306998", line_alpha=1.0) |
| 76 | + |
| 77 | +# Add scatter points (larger on recent, smaller on older) |
| 78 | +p.scatter( |
| 79 | + x="x", y="y", source=source, size="size", fill_color="#306998", fill_alpha="alpha", line_color="white", line_width=2 |
| 80 | +) |
| 81 | + |
| 82 | +# Highlight the current/latest value with a larger marker |
| 83 | +latest_x = visible_timestamps[-1] |
| 84 | +latest_y = visible_values[-1] |
| 85 | +p.scatter(x=[latest_x], y=[latest_y], size=40, fill_color="#FFD43B", line_color="#306998", line_width=4) |
| 86 | + |
| 87 | +# Add label showing current value |
| 88 | +current_value_label = Label( |
| 89 | + x=latest_x, |
| 90 | + y=latest_y + 8, |
| 91 | + text=f"Current: {latest_y:.1f}%", |
| 92 | + text_font_size="36pt", |
| 93 | + text_color="#306998", |
| 94 | + text_font_style="bold", |
| 95 | + text_align="center", |
| 96 | +) |
| 97 | +p.add_layout(current_value_label) |
| 98 | + |
| 99 | +# Add arrow indicating scroll direction (data flowing left) |
| 100 | +arrow = Arrow( |
| 101 | + end=VeeHead(size=35, fill_color="#666666", line_color="#666666"), |
| 102 | + x_start=visible_timestamps[10], |
| 103 | + y_start=12, |
| 104 | + x_end=visible_timestamps[2], |
| 105 | + y_end=12, |
| 106 | + line_color="#666666", |
| 107 | + line_width=4, |
| 108 | +) |
| 109 | +p.add_layout(arrow) |
| 110 | + |
| 111 | +# Add text label for scroll indicator |
| 112 | +scroll_label = Label( |
| 113 | + x=visible_timestamps[6], |
| 114 | + y=16, |
| 115 | + text="Older data scrolls off", |
| 116 | + text_font_size="24pt", |
| 117 | + text_color="#666666", |
| 118 | + text_font_style="italic", |
| 119 | + text_align="center", |
| 120 | +) |
| 121 | +p.add_layout(scroll_label) |
| 122 | + |
| 123 | +# Styling |
| 124 | +p.title.text_font_size = "40pt" |
| 125 | +p.title.text_color = "#306998" |
| 126 | +p.xaxis.axis_label_text_font_size = "28pt" |
| 127 | +p.yaxis.axis_label_text_font_size = "28pt" |
| 128 | +p.xaxis.major_label_text_font_size = "22pt" |
| 129 | +p.yaxis.major_label_text_font_size = "22pt" |
| 130 | + |
| 131 | +# Grid styling |
| 132 | +p.xgrid.grid_line_alpha = 0.3 |
| 133 | +p.ygrid.grid_line_alpha = 0.3 |
| 134 | +p.xgrid.grid_line_dash = [6, 4] |
| 135 | +p.ygrid.grid_line_dash = [6, 4] |
| 136 | + |
| 137 | +# Background |
| 138 | +p.background_fill_color = "#fafafa" |
| 139 | +p.border_fill_color = "white" |
| 140 | + |
| 141 | +# Axis styling |
| 142 | +p.xaxis.axis_line_width = 2 |
| 143 | +p.yaxis.axis_line_width = 2 |
| 144 | +p.xaxis.major_tick_line_width = 2 |
| 145 | +p.yaxis.major_tick_line_width = 2 |
| 146 | + |
| 147 | +# Save as PNG and HTML |
| 148 | +export_png(p, filename="plot.png") |
| 149 | +output_file("plot.html", title="Real-Time Line Chart - Bokeh") |
| 150 | +save(p) |
0 commit comments