Skip to content

Commit cd58979

Browse files
feat(altair): implement violin-grouped-swarm (#3542)
## Implementation: `violin-grouped-swarm` - altair Implements the **altair** version of `violin-grouped-swarm`. **File:** `plots/violin-grouped-swarm/implementations/altair.py` **Parent Issue:** #3529 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20858844204)* --------- 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 f2bbb98 commit cd58979

File tree

2 files changed

+309
-0
lines changed

2 files changed

+309
-0
lines changed
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
""" pyplots.ai
2+
violin-grouped-swarm: Grouped Violin Plot with Swarm Overlay
3+
Library: altair 6.0.0 | Python 3.13.11
4+
Quality: 90/100 | Created: 2026-01-09
5+
"""
6+
7+
import altair as alt
8+
import numpy as np
9+
import pandas as pd
10+
11+
12+
# Data - Response times across task types and expertise levels
13+
np.random.seed(42)
14+
15+
categories = ["Simple", "Moderate", "Complex"]
16+
groups = ["Novice", "Expert"]
17+
n_per_group = 40
18+
19+
data = []
20+
for cat in categories:
21+
for grp in groups:
22+
# Base response time varies by task complexity
23+
base = {"Simple": 2.0, "Moderate": 4.5, "Complex": 8.0}[cat]
24+
# Experts are faster with less variance
25+
if grp == "Expert":
26+
base *= 0.6
27+
spread = 0.8
28+
else:
29+
spread = 1.5
30+
31+
values = np.random.normal(base, spread, n_per_group)
32+
values = np.clip(values, 0.5, 15) # Realistic bounds
33+
34+
for v in values:
35+
data.append({"Task Type": cat, "Expertise": grp, "Response Time (s)": v})
36+
37+
df = pd.DataFrame(data)
38+
39+
# Color palette - Python colors
40+
colors = ["#306998", "#FFD43B"]
41+
42+
# Create base chart with legend title matching data column
43+
base = alt.Chart(df).encode(
44+
color=alt.Color(
45+
"Expertise:N",
46+
scale=alt.Scale(domain=["Novice", "Expert"], range=colors),
47+
legend=alt.Legend(
48+
title="Expertise", titleFontSize=22, labelFontSize=20, orient="right", symbolSize=400, offset=20
49+
),
50+
)
51+
)
52+
53+
# Violin plot using density transform with horizontal orientation
54+
violin = (
55+
base.transform_density(
56+
"Response Time (s)", as_=["Response Time (s)", "density"], groupby=["Task Type", "Expertise"]
57+
)
58+
.mark_area(orient="horizontal", opacity=0.5)
59+
.encode(
60+
x=alt.X(
61+
"density:Q",
62+
stack="center",
63+
impute=None,
64+
title=None,
65+
axis=alt.Axis(labels=False, values=[0], grid=False, ticks=False),
66+
),
67+
y=alt.Y("Response Time (s):Q", title="Response Time (s)", axis=alt.Axis(grid=True, gridOpacity=0.3)),
68+
)
69+
)
70+
71+
# Swarm points with jitter transform - better aligned with violin positions
72+
# Using narrower jitter range and more precise offset to center points within violins
73+
swarm = (
74+
base.mark_circle(opacity=0.85, size=120)
75+
.encode(
76+
x=alt.X(
77+
"jitter:Q",
78+
title=None,
79+
axis=alt.Axis(labels=False, values=[0], grid=False, ticks=False),
80+
scale=alt.Scale(domain=[-1, 1]),
81+
),
82+
y=alt.Y("Response Time (s):Q"),
83+
color=alt.Color("Expertise:N", scale=alt.Scale(domain=["Novice", "Expert"], range=colors), legend=None),
84+
)
85+
.transform_calculate(jitter='(random() - 0.5) * 0.15 + (datum.Expertise === "Novice" ? -0.3 : 0.3)')
86+
)
87+
88+
# Layer and then facet
89+
# Width and height sized to achieve ~4800x2700 at scale 3
90+
# 1600 x 3 = 4800, but faceted so each facet is smaller
91+
chart = (
92+
alt.layer(violin, swarm)
93+
.facet(
94+
column=alt.Column(
95+
"Task Type:N",
96+
sort=categories,
97+
header=alt.Header(title="Task Type", titleFontSize=24, labelFontSize=22, labelOrient="bottom"),
98+
)
99+
)
100+
.resolve_scale(x="independent")
101+
.properties(title=alt.Title("violin-grouped-swarm · altair · pyplots.ai", fontSize=32, anchor="middle", offset=20))
102+
.configure_axis(labelFontSize=20, titleFontSize=24)
103+
.configure_view(strokeWidth=0, continuousWidth=400, continuousHeight=700)
104+
.configure_facet(spacing=40)
105+
)
106+
107+
# Save as PNG and HTML
108+
# Target ~4800x2700: using scale_factor to achieve this
109+
chart.save("plot.png", scale_factor=3.0)
110+
chart.save("plot.html")
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
library: altair
2+
specification_id: violin-grouped-swarm
3+
created: '2026-01-09T16:48:07Z'
4+
updated: '2026-01-09T16:56:39Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 20858844204
7+
issue: 3529
8+
python_version: 3.13.11
9+
library_version: 6.0.0
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/violin-grouped-swarm/altair/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/violin-grouped-swarm/altair/plot_thumb.png
12+
preview_html: https://storage.googleapis.com/pyplots-images/plots/violin-grouped-swarm/altair/plot.html
13+
quality_score: 90
14+
review:
15+
strengths: []
16+
weaknesses: []
17+
image_description: 'The plot displays a grouped violin plot with swarm overlay showing
18+
response times across three task types (Simple, Moderate, Complex) faceted horizontally.
19+
Each facet contains two violin shapes colored by expertise level: Novice in steel
20+
blue (#306998) and Expert in golden yellow (#FFD43B). Individual data points are
21+
overlaid as circles with slight horizontal jitter, matching their respective violin
22+
colors. The title "violin-grouped-swarm · altair · pyplots.ai" appears at the
23+
top. The y-axis shows "Response Time (s)" ranging from 0-13, and the x-axis labels
24+
each facet by Task Type. A legend on the right identifies the Expertise Level
25+
with appropriately sized symbols. Violins have 50% opacity allowing swarm points
26+
to show through. The data demonstrates expected patterns: novices have higher
27+
and more variable response times, with complexity increasing times for both groups.'
28+
criteria_checklist:
29+
visual_quality:
30+
score: 34
31+
max: 40
32+
items:
33+
- id: VQ-01
34+
name: Text Legibility
35+
score: 9
36+
max: 10
37+
passed: true
38+
comment: Title and labels are clear and readable, font sizes appropriate
39+
- id: VQ-02
40+
name: No Overlap
41+
score: 8
42+
max: 8
43+
passed: true
44+
comment: No overlapping text elements
45+
- id: VQ-03
46+
name: Element Visibility
47+
score: 6
48+
max: 8
49+
passed: true
50+
comment: Swarm points visible but some appear detached from violins in Simple
51+
category
52+
- id: VQ-04
53+
name: Color Accessibility
54+
score: 4
55+
max: 5
56+
passed: true
57+
comment: Blue/yellow distinguishable but not optimal for all colorblind types
58+
- id: VQ-05
59+
name: Layout Balance
60+
score: 4
61+
max: 5
62+
passed: true
63+
comment: Good use of space, faceted layout works well
64+
- id: VQ-06
65+
name: Axis Labels
66+
score: 2
67+
max: 2
68+
passed: true
69+
comment: Response Time (s) includes units
70+
- id: VQ-07
71+
name: Grid & Legend
72+
score: 1
73+
max: 2
74+
passed: true
75+
comment: Legend placed well, but no visible grid lines
76+
spec_compliance:
77+
score: 23
78+
max: 25
79+
items:
80+
- id: SC-01
81+
name: Plot Type
82+
score: 8
83+
max: 8
84+
passed: true
85+
comment: Correct grouped violin with swarm overlay
86+
- id: SC-02
87+
name: Data Mapping
88+
score: 5
89+
max: 5
90+
passed: true
91+
comment: Category on x-axis (faceted), group as color, value on y-axis
92+
- id: SC-03
93+
name: Required Features
94+
score: 4
95+
max: 5
96+
passed: true
97+
comment: Violins have transparency, swarm points colored by group, but alignment
98+
could be better
99+
- id: SC-04
100+
name: Data Range
101+
score: 3
102+
max: 3
103+
passed: true
104+
comment: All data visible within axis range
105+
- id: SC-05
106+
name: Legend Accuracy
107+
score: 1
108+
max: 2
109+
passed: true
110+
comment: Legend labels correct but title could be more descriptive
111+
- id: SC-06
112+
name: Title Format
113+
score: 2
114+
max: 2
115+
passed: true
116+
comment: 'Correct format: spec-id · library · pyplots.ai'
117+
data_quality:
118+
score: 18
119+
max: 20
120+
items:
121+
- id: DQ-01
122+
name: Feature Coverage
123+
score: 7
124+
max: 8
125+
passed: true
126+
comment: Shows distribution differences, variance differences, but could show
127+
more outlier variation
128+
- id: DQ-02
129+
name: Realistic Context
130+
score: 7
131+
max: 7
132+
passed: true
133+
comment: Response times across task complexity and expertise is a realistic
134+
scenario
135+
- id: DQ-03
136+
name: Appropriate Scale
137+
score: 4
138+
max: 5
139+
passed: true
140+
comment: Values realistic (0.5-12s), though some clipped values at bounds
141+
code_quality:
142+
score: 10
143+
max: 10
144+
items:
145+
- id: CQ-01
146+
name: KISS Structure
147+
score: 3
148+
max: 3
149+
passed: true
150+
comment: 'Linear structure: imports, data, plot, save'
151+
- id: CQ-02
152+
name: Reproducibility
153+
score: 3
154+
max: 3
155+
passed: true
156+
comment: np.random.seed(42) set
157+
- id: CQ-03
158+
name: Clean Imports
159+
score: 2
160+
max: 2
161+
passed: true
162+
comment: Only necessary imports used
163+
- id: CQ-04
164+
name: No Deprecated API
165+
score: 1
166+
max: 1
167+
passed: true
168+
comment: Uses current Altair API
169+
- id: CQ-05
170+
name: Output Correct
171+
score: 1
172+
max: 1
173+
passed: true
174+
comment: Saves as plot.png and plot.html
175+
library_features:
176+
score: 5
177+
max: 5
178+
items:
179+
- id: LF-01
180+
name: Distinctive Features
181+
score: 5
182+
max: 5
183+
passed: true
184+
comment: 'Excellent use of Altair declarative grammar: transform_density,
185+
transform_calculate for jitter, faceting, layer composition'
186+
verdict: REJECTED
187+
impl_tags:
188+
dependencies: []
189+
techniques:
190+
- faceting
191+
- layer-composition
192+
- html-export
193+
patterns:
194+
- data-generation
195+
- iteration-over-groups
196+
dataprep:
197+
- kde
198+
styling:
199+
- alpha-blending

0 commit comments

Comments
 (0)