|
| 1 | +# Generate Plot from Picture |
| 2 | + |
| 3 | +Upload an image of a plot and ask Claude Code to recreate it! |
| 4 | + |
| 5 | + |
| 6 | + |
| 7 | +## Input |
| 8 | + |
| 9 | +Upload the image below to Claude Code. |
| 10 | + |
| 11 | + |
| 12 | + |
| 13 | +Then ask Claude to recreate it using the HoloViz ecosystem: |
| 14 | + |
| 15 | +```text |
| 16 | +Please study the attached image carefully including styling, labels, grids, annotations, titles etc. Then plan how to recreate it using the HoloViz ecosystem. Please hide the bokeh logo and autohide the bokeh tools. The tools should only be shown when I hover over the plot. |
| 17 | +``` |
| 18 | + |
| 19 | +When the plan is ready, ask Claude to implement it: |
| 20 | + |
| 21 | +```text |
| 22 | +Please implement the plan. |
| 23 | +``` |
| 24 | + |
| 25 | +## Result |
| 26 | + |
| 27 | +Not perfect, but a really good starting point. |
| 28 | + |
| 29 | + |
| 30 | + |
| 31 | +<details><summary>Code</summary> |
| 32 | + |
| 33 | +```python |
| 34 | +import numpy as np |
| 35 | +import pandas as pd |
| 36 | +import holoviews as hv |
| 37 | +from holoviews import opts |
| 38 | + |
| 39 | +# Configure HoloViews with Bokeh backend, hide logo |
| 40 | +hv.extension('bokeh', logo=False) |
| 41 | + |
| 42 | + |
| 43 | +def generate_electricity_data(): |
| 44 | + """ |
| 45 | + Generate synthetic 24-hour electricity consumption data. |
| 46 | + Returns DataFrame with 96 data points (15-minute intervals). |
| 47 | + """ |
| 48 | + # Create time range: 00:00 to 23:45 in 15-minute intervals |
| 49 | + times = pd.date_range('2024-01-01 00:00', '2024-01-01 23:45', freq='15min') |
| 50 | + |
| 51 | + # Create realistic consumption pattern |
| 52 | + hours = np.arange(0, 24, 0.25) # 96 points |
| 53 | + |
| 54 | + # Base consumption pattern |
| 55 | + consumption = np.zeros(96) |
| 56 | + |
| 57 | + # Overnight low (00:00-06:00): ~250-300W |
| 58 | + consumption[:24] = 270 + np.random.normal(0, 10, 24) |
| 59 | + |
| 60 | + # Morning rise (06:00-07:30): gradual increase |
| 61 | + consumption[24:30] = np.linspace(280, 450, 6) + np.random.normal(0, 10, 6) |
| 62 | + |
| 63 | + # Morning peak (07:30-08:00): ~540W |
| 64 | + consumption[30:32] = 540 + np.random.normal(0, 15, 2) |
| 65 | + |
| 66 | + # Morning decline (08:00-10:30): gradual decrease |
| 67 | + consumption[32:42] = np.linspace(520, 400, 10) + np.random.normal(0, 15, 10) |
| 68 | + |
| 69 | + # Daytime plateau (10:30-17:30): ~380-420W |
| 70 | + consumption[42:70] = 400 + np.random.normal(0, 15, 28) |
| 71 | + |
| 72 | + # Evening rise (17:30-20:00): steep increase to peak |
| 73 | + consumption[70:80] = np.linspace(400, 800, 10) + np.random.normal(0, 20, 10) |
| 74 | + |
| 75 | + # Evening peak (20:00-20:30): ~800W |
| 76 | + consumption[80:82] = 800 + np.random.normal(0, 15, 2) |
| 77 | + |
| 78 | + # Evening decline (20:30-23:45): gradual decrease |
| 79 | + consumption[82:] = np.linspace(780, 400, 14) + np.random.normal(0, 20, 14) |
| 80 | + |
| 81 | + # Ensure no negative values |
| 82 | + consumption = np.maximum(consumption, 0) |
| 83 | + |
| 84 | + df = pd.DataFrame({ |
| 85 | + 'time': times, |
| 86 | + 'consumption': consumption |
| 87 | + }) |
| 88 | + |
| 89 | + return df |
| 90 | + |
| 91 | + |
| 92 | +def create_plot(): |
| 93 | + """ |
| 94 | + Create the electricity distribution visualization with all elements. |
| 95 | + """ |
| 96 | + # Generate data |
| 97 | + df = generate_electricity_data() |
| 98 | + |
| 99 | + # Define peak periods for coloring |
| 100 | + # Morning peak: ~07:00-10:30 (indices 28-42) |
| 101 | + # Evening peak: ~17:30-21:30 (indices 70-86) |
| 102 | + |
| 103 | + # Create VSpan elements for shaded pink regions |
| 104 | + morning_vspan = hv.VSpan( |
| 105 | + pd.Timestamp('2024-01-01 07:00'), |
| 106 | + pd.Timestamp('2024-01-01 10:30') |
| 107 | + ).opts( |
| 108 | + color='#ffcccc', |
| 109 | + alpha=0.3 |
| 110 | + ) |
| 111 | + |
| 112 | + evening_vspan = hv.VSpan( |
| 113 | + pd.Timestamp('2024-01-01 17:30'), |
| 114 | + pd.Timestamp('2024-01-01 21:30') |
| 115 | + ).opts( |
| 116 | + color='#ffcccc', |
| 117 | + alpha=0.3 |
| 118 | + ) |
| 119 | + |
| 120 | + # Create colored line segments |
| 121 | + # Define segments: [start_idx, end_idx, color] |
| 122 | + segments = [ |
| 123 | + (0, 28, '#2ca02c'), # Green: 00:00-07:00 |
| 124 | + (28, 42, '#d62728'), # Red: 07:00-10:30 (morning peak) |
| 125 | + (42, 70, '#2ca02c'), # Green: 10:30-17:30 |
| 126 | + (70, 86, '#d62728'), # Red: 17:30-21:30 (evening peak) |
| 127 | + (86, 96, '#2ca02c'), # Green: 21:30-23:45 |
| 128 | + ] |
| 129 | + |
| 130 | + # Create curve and scatter overlays for each segment |
| 131 | + plot_elements = [] |
| 132 | + |
| 133 | + for start_idx, end_idx, color in segments: |
| 134 | + segment_df = df.iloc[start_idx:end_idx] |
| 135 | + |
| 136 | + # Create curve (line) |
| 137 | + curve = hv.Curve( |
| 138 | + segment_df, |
| 139 | + kdims=['time'], |
| 140 | + vdims=['consumption'] |
| 141 | + ).opts( |
| 142 | + color=color, |
| 143 | + line_width=2 |
| 144 | + ) |
| 145 | + |
| 146 | + # Create scatter (markers) |
| 147 | + scatter = hv.Scatter( |
| 148 | + segment_df, |
| 149 | + kdims=['time'], |
| 150 | + vdims=['consumption'] |
| 151 | + ).opts( |
| 152 | + color=color, |
| 153 | + size=7, |
| 154 | + marker='o' |
| 155 | + ) |
| 156 | + |
| 157 | + plot_elements.append(curve * scatter) |
| 158 | + |
| 159 | + # Combine all line segments |
| 160 | + line_plot = plot_elements[0] |
| 161 | + for element in plot_elements[1:]: |
| 162 | + line_plot = line_plot * element |
| 163 | + |
| 164 | + # Create text annotations |
| 165 | + text_morning = hv.Text( |
| 166 | + pd.Timestamp('2024-01-01 08:45'), |
| 167 | + 820, |
| 168 | + 'Morning Peak' |
| 169 | + ).opts( |
| 170 | + text_font_size='12pt', |
| 171 | + text_align='center' |
| 172 | + ) |
| 173 | + |
| 174 | + text_fake = hv.Text( |
| 175 | + pd.Timestamp('2024-01-01 12:00'), |
| 176 | + 820, |
| 177 | + 'Fake Data' |
| 178 | + ).opts( |
| 179 | + text_font_size='12pt', |
| 180 | + text_align='center' |
| 181 | + ) |
| 182 | + |
| 183 | + text_evening = hv.Text( |
| 184 | + pd.Timestamp('2024-01-01 19:30'), |
| 185 | + 820, |
| 186 | + 'Evening Peak' |
| 187 | + ).opts( |
| 188 | + text_font_size='12pt', |
| 189 | + text_align='center' |
| 190 | + ) |
| 191 | + |
| 192 | + # Combine all elements: VSpans at back, then lines, then text on top |
| 193 | + final_plot = ( |
| 194 | + morning_vspan * evening_vspan * |
| 195 | + line_plot * |
| 196 | + text_morning * text_fake * text_evening |
| 197 | + ) |
| 198 | + |
| 199 | + # Apply global plot options |
| 200 | + final_plot = final_plot.opts( |
| 201 | + opts.Overlay( |
| 202 | + width=1200, |
| 203 | + height=600, |
| 204 | + title='Distribution of Electricity', |
| 205 | + xlabel='', |
| 206 | + ylabel='W', |
| 207 | + ylim=(0, 850), |
| 208 | + show_grid=True, |
| 209 | + toolbar='above', |
| 210 | + active_tools=['pan', 'wheel_zoom'], |
| 211 | + backend_opts={'toolbar.autohide': True} |
| 212 | + ) |
| 213 | + ) |
| 214 | + |
| 215 | + return final_plot |
| 216 | + |
| 217 | + |
| 218 | +# Create and display the plot |
| 219 | +create_plot() |
| 220 | +``` |
| 221 | + |
| 222 | +</details> |
| 223 | + |
| 224 | +## Credits |
| 225 | + |
| 226 | +The original plot is from [ECharts Examples](https://echarts.apache.org/examples/en/index.html). |
0 commit comments