Skip to content

Commit 06690d8

Browse files
feat(altair): implement violin-box (#2662)
## Implementation: `violin-box` - altair Implements the **altair** version of `violin-box`. **File:** `plots/violin-box/implementations/altair.py` --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20595338513)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 028f48c commit 06690d8

2 files changed

Lines changed: 120 additions & 0 deletions

File tree

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
""" pyplots.ai
2+
violin-box: Violin Plot with Embedded Box Plot
3+
Library: altair 6.0.0 | Python 3.13.11
4+
Quality: 92/100 | Created: 2025-12-30
5+
"""
6+
7+
import altair as alt
8+
import numpy as np
9+
import pandas as pd
10+
11+
12+
# Data - Generate realistic response time data for different server tiers
13+
np.random.seed(42)
14+
15+
groups = ["Basic", "Standard", "Premium", "Enterprise"]
16+
n_per_group = 80
17+
18+
data = []
19+
# Basic tier - higher latency, more variance
20+
data.extend([(np.random.exponential(120) + 80, "Basic") for _ in range(n_per_group)])
21+
# Standard tier - moderate latency
22+
data.extend([(np.random.normal(100, 25), "Standard") for _ in range(n_per_group)])
23+
# Premium tier - lower latency, tighter distribution
24+
data.extend([(np.random.normal(60, 15), "Premium") for _ in range(n_per_group)])
25+
# Enterprise tier - lowest latency, bimodal (some cached, some not)
26+
enterprise_cached = np.random.normal(25, 8, n_per_group // 2)
27+
enterprise_uncached = np.random.normal(55, 12, n_per_group // 2)
28+
data.extend([(v, "Enterprise") for v in np.concatenate([enterprise_cached, enterprise_uncached])])
29+
30+
df = pd.DataFrame(data, columns=["Response Time (ms)", "Server Tier"])
31+
# Ensure positive values for response times
32+
df["Response Time (ms)"] = df["Response Time (ms)"].clip(lower=5)
33+
34+
# Create violin plot layer using transform_density
35+
violin = (
36+
alt.Chart(df)
37+
.transform_density("Response Time (ms)", as_=["Response Time (ms)", "density"], groupby=["Server Tier"])
38+
.mark_area(orient="horizontal", opacity=0.6)
39+
.encode(
40+
y=alt.Y("Response Time (ms):Q"),
41+
x=alt.X(
42+
"density:Q",
43+
stack="center",
44+
impute=None,
45+
title=None,
46+
axis=alt.Axis(labels=False, values=[0], grid=False, ticks=False),
47+
),
48+
color=alt.Color(
49+
"Server Tier:N",
50+
scale=alt.Scale(
51+
domain=["Basic", "Standard", "Premium", "Enterprise"],
52+
range=["#306998", "#FFD43B", "#4B8BBE", "#8BC34A"],
53+
),
54+
),
55+
)
56+
)
57+
58+
# Create box plot layer
59+
boxplot = (
60+
alt.Chart(df)
61+
.mark_boxplot(
62+
extent="min-max",
63+
size=25,
64+
median={"stroke": "white", "strokeWidth": 2},
65+
box={"fill": "#333333", "fillOpacity": 0.7},
66+
outliers={"size": 60, "strokeWidth": 2},
67+
)
68+
.encode(y=alt.Y("Response Time (ms):Q", title="Response Time (ms)"), x=alt.value(0), color=alt.value("#333333"))
69+
)
70+
71+
# Layer violin and box plots first, then facet
72+
layered = alt.layer(violin, boxplot).properties(width=280, height=600)
73+
74+
# Apply faceting after layering
75+
chart = (
76+
layered.facet(
77+
column=alt.Column(
78+
"Server Tier:N",
79+
header=alt.Header(titleFontSize=20, labelFontSize=18, labelOrient="bottom"),
80+
title=None,
81+
sort=["Basic", "Standard", "Premium", "Enterprise"],
82+
)
83+
)
84+
.properties(title=alt.Title("violin-box · altair · pyplots.ai", fontSize=28, anchor="middle", offset=20))
85+
.configure_axis(labelFontSize=16, titleFontSize=20, gridOpacity=0.3)
86+
.configure_view(stroke=None)
87+
.configure_legend(titleFontSize=18, labelFontSize=16, symbolSize=200, orient="right")
88+
.resolve_scale(x="independent")
89+
)
90+
91+
# Save outputs
92+
chart.save("plot.png", scale_factor=3.0)
93+
chart.save("plot.html")
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
library: altair
2+
specification_id: violin-box
3+
created: '2025-12-30T11:22:21Z'
4+
updated: '2025-12-30T11:29:59Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 20595338513
7+
issue: 0
8+
python_version: 3.13.11
9+
library_version: 6.0.0
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/violin-box/altair/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/violin-box/altair/plot_thumb.png
12+
preview_html: https://storage.googleapis.com/pyplots-images/plots/violin-box/altair/plot.html
13+
quality_score: 92
14+
review:
15+
strengths:
16+
- Excellent use of Altair declarative layering to combine violin (mark_area with
17+
transform_density) and box plot layers
18+
- Realistic and varied data showing different distribution shapes (exponential,
19+
normal, tight normal, bimodal)
20+
- Clean faceted layout with independent x-scales for proper violin centering
21+
- Good color palette that is colorblind-accessible
22+
- Proper title format and clear axis labeling with units
23+
weaknesses:
24+
- Box plot median line could have higher contrast against the gray box for better
25+
visibility
26+
- Legend placement in top-right leaves some empty space; could be integrated more
27+
tightly

0 commit comments

Comments
 (0)