Skip to content

Commit 0fa0e8f

Browse files
feat(plotnine): implement candlestick-volume (#6881)
## Implementation: `candlestick-volume` - python/plotnine Implements the **python/plotnine** version of `candlestick-volume`. **File:** `plots/candlestick-volume/implementations/python/plotnine.py` **Parent Issue:** #3068 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/anyplot/actions/runs/25953797474)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Markus Neusinger <2921697+MarkusNeusinger@users.noreply.github.com>
1 parent 8f7bf90 commit 0fa0e8f

2 files changed

Lines changed: 373 additions & 0 deletions

File tree

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
""" anyplot.ai
2+
candlestick-volume: Stock Candlestick Chart with Volume
3+
Library: plotnine 0.15.4 | Python 3.13.13
4+
Quality: 91/100 | Created: 2026-05-16
5+
"""
6+
7+
import sys
8+
9+
10+
# Remove the current directory from sys.path to avoid shadowing the installed plotnine package
11+
while "" in sys.path:
12+
sys.path.remove("")
13+
cwd = __file__[: __file__.rfind("/")]
14+
while cwd in sys.path:
15+
sys.path.remove(cwd)
16+
17+
# Now import plotnine
18+
import os # noqa: E402
19+
20+
import numpy as np # noqa: E402
21+
import pandas as pd # noqa: E402
22+
23+
from plotnine import ( # noqa: E402
24+
aes,
25+
element_blank,
26+
element_line,
27+
element_rect,
28+
element_text,
29+
facet_grid,
30+
geom_col,
31+
geom_rect,
32+
geom_segment,
33+
ggplot,
34+
ggsave,
35+
labs,
36+
scale_color_manual,
37+
scale_fill_manual,
38+
theme,
39+
theme_minimal,
40+
)
41+
42+
43+
# Theme tokens
44+
THEME = os.getenv("ANYPLOT_THEME", "light")
45+
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
46+
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
47+
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
48+
49+
# Okabe-Ito palette: Up (green), Down (orange)
50+
UP_COLOR = "#009E73"
51+
DOWN_COLOR = "#D55E00"
52+
53+
# Generate realistic OHLC data
54+
np.random.seed(42)
55+
n_periods = 60
56+
dates = pd.date_range("2024-01-01", periods=n_periods, freq="D")
57+
58+
# Create price movement with realistic patterns
59+
returns = np.random.normal(0.001, 0.02, n_periods)
60+
close_prices = 100 * np.exp(np.cumsum(returns))
61+
open_prices = close_prices * (1 + np.random.normal(0, 0.01, n_periods))
62+
high_prices = np.maximum(open_prices, close_prices) + np.abs(np.random.normal(0, 0.5, n_periods))
63+
low_prices = np.minimum(open_prices, close_prices) - np.abs(np.random.normal(0, 0.5, n_periods))
64+
volumes = np.random.exponential(1e6, n_periods)
65+
66+
# Create main dataframe
67+
df = pd.DataFrame(
68+
{
69+
"date": dates,
70+
"open": open_prices,
71+
"high": high_prices,
72+
"low": low_prices,
73+
"close": close_prices,
74+
"volume": volumes,
75+
}
76+
)
77+
78+
# Add direction (up/down) and position columns for rectangles
79+
df["direction"] = df["close"] >= df["open"]
80+
df["direction_label"] = df["direction"].map({True: "Up", False: "Down"})
81+
df["x_min"] = df["date"] - pd.Timedelta(hours=12)
82+
df["x_max"] = df["date"] + pd.Timedelta(hours=12)
83+
84+
# Create separate dataframes for faceting
85+
df_price = df.copy()
86+
df_price["pane"] = "Price (OHLC)"
87+
88+
df_volume = df.copy()
89+
df_volume["pane"] = "Trading Volume"
90+
91+
# Create the faceted plot
92+
plot = (
93+
ggplot()
94+
# Candlestick wicks (high-low lines)
95+
+ geom_segment(
96+
aes(x="date", y="low", xend="date", yend="high", color="direction_label"),
97+
data=df_price,
98+
size=0.8,
99+
show_legend=False,
100+
)
101+
# Candlestick bodies (open-close rectangles)
102+
+ geom_rect(
103+
aes(xmin="x_min", xmax="x_max", ymin="open", ymax="close", fill="direction_label"),
104+
data=df_price,
105+
show_legend=False,
106+
)
107+
# Volume bars
108+
+ geom_col(aes(x="date", y="volume", fill="direction_label"), data=df_volume, show_legend=False)
109+
# Facet with free y-scales for price and volume
110+
+ facet_grid("pane ~ .", scales="free_y")
111+
# Color scales
112+
+ scale_color_manual(values=[DOWN_COLOR, UP_COLOR])
113+
+ scale_fill_manual(values=[DOWN_COLOR, UP_COLOR])
114+
# Labels and title
115+
+ labs(title="candlestick-volume · plotnine · anyplot.ai", x="Date", y="")
116+
# Theme
117+
+ theme_minimal()
118+
+ theme(
119+
figure_size=(16, 9),
120+
plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),
121+
panel_background=element_rect(fill=PAGE_BG),
122+
panel_border=element_rect(color=INK_SOFT, fill=None, size=0.3),
123+
axis_title=element_text(size=20, color=INK),
124+
axis_text=element_text(size=16, color=INK_SOFT),
125+
plot_title=element_text(size=24, color=INK, weight="medium"),
126+
strip_text_y=element_text(size=18, color=INK),
127+
panel_grid_major_y=element_line(color=INK_SOFT, size=0.3, alpha=0.15),
128+
panel_grid_major_x=element_blank(),
129+
panel_grid_minor=element_blank(),
130+
)
131+
)
132+
133+
# Save with theme-suffixed filename to script directory
134+
script_dir = __file__[: __file__.rfind("/")]
135+
ggsave(plot, f"{script_dir}/plot-{THEME}.png", dpi=300)
Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
library: plotnine
2+
language: python
3+
specification_id: candlestick-volume
4+
created: '2026-05-16T05:29:51Z'
5+
updated: '2026-05-16T05:37:56Z'
6+
generated_by: claude-haiku
7+
workflow_run: 25953797474
8+
issue: 3068
9+
python_version: 3.13.13
10+
library_version: 0.15.4
11+
preview_url_light: https://storage.googleapis.com/anyplot-images/plots/candlestick-volume/python/plotnine/plot-light.png
12+
preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/candlestick-volume/python/plotnine/plot-dark.png
13+
preview_html_light: null
14+
preview_html_dark: null
15+
quality_score: 91
16+
review:
17+
strengths:
18+
- Perfect visual quality with explicit font sizing throughout (title 24pt, labels
19+
20pt, ticks 16pt)
20+
- 'Accurate theme adaptation: identical data colors across light/dark, theme-correct
21+
chrome throughout'
22+
- 'Excellent spec compliance: all features present (dual-pane layout, proper proportions,
23+
OHLC candlesticks, volume bars, color scheme)'
24+
- Clean code structure with proper data generation (seed=42), deterministic, no
25+
over-engineering
26+
- 'Strong idiomatic plotnine usage: layered geoms (segment+rect+col), facet_grid
27+
with free_y scales, proper theming with element_* functions'
28+
weaknesses:
29+
- 'Design Excellence (DE-01) scores 4/8 (generic defaults): implementation is technically
30+
excellent but uses standard library styling without custom aesthetic choices;
31+
could add unique visual refinement'
32+
image_description: |-
33+
Light render (plot-light.png):
34+
Background: Warm off-white (#FAF8F1) — correct
35+
Chrome: Title in dark text (24pt, legible); axis labels "Date", "Price (OHLC)", "Trading Volume" (20pt, clear); tick labels in soft gray (#4A4A44, 16pt, readable); grid lines subtle at alpha=0.15; panel borders delicate (#4A4A44, 0.3pt)
36+
Data: Candlestick wicks (segments, thin lines) show high-low ranges; candlestick bodies (rectangles) show open-close; green (#009E73) for up candles, orange (#D55E00) for down candles; volume bars below match up/down color scheme; date axis spans 2024-01-01 to 2024-03-01 (60 periods)
37+
Legibility verdict: PASS — all text readable, no overlap, data elements clearly distinguishable
38+
39+
Dark render (plot-dark.png):
40+
Background: Warm near-black (#1A1A17) — correct
41+
Chrome: Title in light text (#F0EFE8, 24pt, legible); axis labels in light text (#F0EFE8, 20pt); tick labels in soft light gray (#B8B7B0, 16pt, readable); grid lines subtle; panel borders same delicate style
42+
Data: Candlestick colors identical to light render — green (#009E73) for up, orange (#D55E00) for down (confirmed: Okabe-Ito positions 1-2 unchanged across themes); volume bars color-consistent; wicks and bodies clearly visible; no dark-on-dark failures (no black text on near-black background)
43+
Legibility verdict: PASS — all chrome properly adapted, no readability issues, data colors confirmed identical to light render
44+
criteria_checklist:
45+
visual_quality:
46+
score: 30
47+
max: 30
48+
items:
49+
- id: VQ-01
50+
name: Text Legibility
51+
score: 8
52+
max: 8
53+
passed: true
54+
comment: 'All font sizes explicitly set: title 24pt, labels 20pt, ticks 16pt,
55+
all perfectly readable in both themes'
56+
- id: VQ-02
57+
name: No Overlap
58+
score: 6
59+
max: 6
60+
passed: true
61+
comment: No overlapping text, all elements fully readable
62+
- id: VQ-03
63+
name: Element Visibility
64+
score: 6
65+
max: 6
66+
passed: true
67+
comment: Wicks, bodies, and volume bars optimally sized for 60-period dataset
68+
- id: VQ-04
69+
name: Color Accessibility
70+
score: 2
71+
max: 2
72+
passed: true
73+
comment: Okabe-Ito palette is CVD-safe; green and orange have sufficient contrast
74+
- id: VQ-05
75+
name: Layout & Canvas
76+
score: 4
77+
max: 4
78+
passed: true
79+
comment: 16x9 landscape (4800x2700px), balanced margins, proper pane proportions
80+
- id: VQ-06
81+
name: Axis Labels & Title
82+
score: 2
83+
max: 2
84+
passed: true
85+
comment: Title format correct; Date axis descriptive; facet labels provide
86+
pane context
87+
- id: VQ-07
88+
name: Palette Compliance
89+
score: 2
90+
max: 2
91+
passed: true
92+
comment: 'Up=#009E73 (Okabe-Ito pos 1), Down=#D55E00 (pos 2); backgrounds
93+
#FAF8F1/#1A1A17; theme-correct chrome in both renders'
94+
design_excellence:
95+
score: 12
96+
max: 20
97+
items:
98+
- id: DE-01
99+
name: Aesthetic Sophistication
100+
score: 4
101+
max: 8
102+
passed: false
103+
comment: Well-configured library defaults, professional but no exceptional
104+
design flourishes
105+
- id: DE-02
106+
name: Visual Refinement
107+
score: 4
108+
max: 6
109+
passed: true
110+
comment: Subtle y-axis grid (alpha=0.15), minimal spines via theme_minimal(),
111+
good whitespace
112+
- id: DE-03
113+
name: Data Storytelling
114+
score: 4
115+
max: 6
116+
passed: true
117+
comment: Faceted layout clearly shows price-volume relationship; visual hierarchy
118+
(price 70-75%, volume 25-30%); color contrast guides reader
119+
spec_compliance:
120+
score: 15
121+
max: 15
122+
items:
123+
- id: SC-01
124+
name: Plot Type
125+
score: 5
126+
max: 5
127+
passed: true
128+
comment: Correct candlestick chart with volume bars, dual-pane layout
129+
- id: SC-02
130+
name: Required Features
131+
score: 4
132+
max: 4
133+
passed: true
134+
comment: 'All features present: OHLC candlesticks, volume bars, shared x-axis,
135+
up/down colors, grid lines'
136+
- id: SC-03
137+
name: Data Mapping
138+
score: 3
139+
max: 3
140+
passed: true
141+
comment: X=date, Price pane Y=OHLC, Volume pane Y=volume; all data visible
142+
- id: SC-04
143+
name: Title & Legend
144+
score: 3
145+
max: 3
146+
passed: true
147+
comment: 'Title: ''candlestick-volume · plotnine · anyplot.ai''; legend omitted
148+
appropriately (color scheme self-explanatory)'
149+
data_quality:
150+
score: 15
151+
max: 15
152+
items:
153+
- id: DQ-01
154+
name: Feature Coverage
155+
score: 6
156+
max: 6
157+
passed: true
158+
comment: Shows both up and down candles, realistic OHLC ranges, volume variations,
159+
all aspects covered
160+
- id: DQ-02
161+
name: Realistic Context
162+
score: 5
163+
max: 5
164+
passed: true
165+
comment: Stock price data (lognormal returns), trading volume (exponential),
166+
neutral business context
167+
- id: DQ-03
168+
name: Appropriate Scale
169+
score: 4
170+
max: 4
171+
passed: true
172+
comment: Price range ~$87-$112 (realistic), volume ~$100k-$3M (realistic),
173+
proportions factually sound
174+
code_quality:
175+
score: 10
176+
max: 10
177+
items:
178+
- id: CQ-01
179+
name: KISS Structure
180+
score: 3
181+
max: 3
182+
passed: true
183+
comment: 'Linear: imports → data → plot → save; no functions/classes'
184+
- id: CQ-02
185+
name: Reproducibility
186+
score: 2
187+
max: 2
188+
passed: true
189+
comment: Seed 42 set, fully deterministic
190+
- id: CQ-03
191+
name: Clean Imports
192+
score: 2
193+
max: 2
194+
passed: true
195+
comment: All imports used, no bloat
196+
- id: CQ-04
197+
name: Code Elegance
198+
score: 2
199+
max: 2
200+
passed: true
201+
comment: Pythonic, appropriate complexity, no fake functionality
202+
- id: CQ-05
203+
name: Output & API
204+
score: 1
205+
max: 1
206+
passed: true
207+
comment: Saves as plot-{THEME}.png via ggsave(), current API
208+
library_mastery:
209+
score: 9
210+
max: 10
211+
items:
212+
- id: LM-01
213+
name: Idiomatic Usage
214+
score: 5
215+
max: 5
216+
passed: true
217+
comment: 'Expert idiomatic plotnine: ggplot() + geom_segment + geom_rect +
218+
geom_col + facet_grid + theme()'
219+
- id: LM-02
220+
name: Distinctive Features
221+
score: 4
222+
max: 5
223+
passed: true
224+
comment: Leverages facet_grid with free_y scales (plotnine strength), layered
225+
geoms composition
226+
verdict: APPROVED
227+
impl_tags:
228+
dependencies: []
229+
techniques:
230+
- faceting
231+
- layer-composition
232+
patterns:
233+
- data-generation
234+
dataprep:
235+
- time-series
236+
styling:
237+
- grid-styling
238+
- publication-ready

0 commit comments

Comments
 (0)