Skip to content

Commit 972063e

Browse files
feat(ggplot2): implement line-navigator (#7739)
## Implementation: `line-navigator` - r/ggplot2 Implements the **r/ggplot2** version of `line-navigator`. **File:** `plots/line-navigator/implementations/r/ggplot2.R` **Parent Issue:** #3785 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/anyplot/actions/runs/26508525153)* --------- 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 bb66b9f commit 972063e

2 files changed

Lines changed: 397 additions & 0 deletions

File tree

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
#' anyplot.ai
2+
#' line-navigator: Line Chart with Mini Navigator
3+
#' Library: ggplot2 3.5.1 | R 4.4.1
4+
#' Quality: 86/100 | Created: 2026-05-27
5+
6+
library(ggplot2)
7+
library(ragg)
8+
library(gridExtra)
9+
10+
set.seed(42)
11+
12+
# --- Theme tokens -----------------------------------------------------------
13+
THEME <- Sys.getenv("ANYPLOT_THEME", "light")
14+
PAGE_BG <- if (THEME == "light") "#FAF8F1" else "#1A1A17"
15+
ELEVATED_BG <- if (THEME == "light") "#FFFDF6" else "#242420"
16+
INK <- if (THEME == "light") "#1A1A17" else "#F0EFE8"
17+
INK_SOFT <- if (THEME == "light") "#4A4A44" else "#B8B7B0"
18+
19+
ANYPLOT_PALETTE <- c(
20+
"#009E73", "#C475FD", "#4467A3", "#BD8233",
21+
"#AE3030", "#2ABCCD", "#954477", "#99B314"
22+
)
23+
LINE_COLOR <- ANYPLOT_PALETTE[1]
24+
SEL_ALPHA <- if (THEME == "light") 0.22 else 0.38
25+
26+
# --- Data -------------------------------------------------------------------
27+
# Daily temperature sensor over 2 years (730 data points)
28+
dates <- seq.Date(as.Date("2022-01-01"), as.Date("2023-12-31"), by = "day")
29+
n <- length(dates)
30+
day_of_yr <- as.numeric(format(dates, "%j"))
31+
trend <- seq(0, 8, length.out = n)
32+
seasonal <- 14 * sin(2 * pi * day_of_yr / 365 - pi / 2)
33+
noise <- cumsum(rnorm(n, 0, 0.35))
34+
temperature <- 20 + trend + seasonal + noise
35+
df <- data.frame(date = dates, temperature = temperature)
36+
37+
# Selection window: summer 2023 — the detail view shown in the main chart
38+
sel_start <- as.Date("2023-06-01")
39+
sel_end <- as.Date("2023-08-31")
40+
df_main <- df[df$date >= sel_start & df$date <= sel_end, ]
41+
42+
# --- Title ------------------------------------------------------------------
43+
TITLE <- "line-navigator · r · ggplot2 · anyplot.ai"
44+
title_len <- nchar(TITLE)
45+
title_size <- min(12, max(8, round(12 * 67 / title_len)))
46+
47+
# --- Shared theme -----------------------------------------------------------
48+
base_theme <- theme_minimal(base_size = 8) +
49+
theme(
50+
plot.background = element_rect(fill = PAGE_BG, color = PAGE_BG),
51+
panel.background = element_rect(fill = PAGE_BG, color = NA),
52+
panel.grid.major = element_line(color = INK_SOFT, linewidth = 0.15),
53+
panel.grid.minor = element_blank(),
54+
axis.title = element_text(color = INK, size = 10),
55+
axis.text = element_text(color = INK_SOFT, size = 8),
56+
axis.line = element_line(color = INK_SOFT, linewidth = 0.4),
57+
axis.ticks = element_line(color = INK_SOFT),
58+
plot.title = element_text(color = INK, size = title_size, face = "bold"),
59+
plot.subtitle = element_text(color = INK_SOFT, size = 9),
60+
plot.margin = margin(t = 8, r = 10, b = 4, l = 10, unit = "pt")
61+
)
62+
63+
# --- Main chart: detail view of selected range ------------------------------
64+
p_main <- ggplot(df_main, aes(x = date, y = temperature)) +
65+
geom_line(color = LINE_COLOR, linewidth = 1.0) +
66+
scale_x_date(
67+
date_labels = "%b %d",
68+
date_breaks = "2 weeks",
69+
expand = expansion(mult = c(0.02, 0.02))
70+
) +
71+
scale_y_continuous(
72+
labels = function(x) paste0(round(x, 0), "°C"),
73+
expand = expansion(mult = c(0.05, 0.12))
74+
) +
75+
labs(
76+
title = TITLE,
77+
subtitle = sprintf(
78+
"Detail view: %s – %s",
79+
format(sel_start, "%b %d, %Y"),
80+
format(sel_end, "%b %d, %Y")
81+
),
82+
x = NULL,
83+
y = "Temperature (°C)"
84+
) +
85+
base_theme +
86+
theme(
87+
panel.grid.major.x = element_blank(),
88+
axis.text.x = element_text(angle = 30, hjust = 1, size = 8)
89+
)
90+
91+
# --- Navigator: full history with selection window highlighted --------------
92+
p_nav <- ggplot(df, aes(x = date, y = temperature)) +
93+
annotate(
94+
"rect",
95+
xmin = sel_start, xmax = sel_end,
96+
ymin = -Inf, ymax = Inf,
97+
fill = LINE_COLOR, alpha = SEL_ALPHA
98+
) +
99+
geom_line(color = LINE_COLOR, linewidth = 0.45, alpha = 0.75) +
100+
scale_x_date(
101+
date_labels = "%b '%y",
102+
date_breaks = "3 months",
103+
expand = expansion(mult = c(0.01, 0.01))
104+
) +
105+
scale_y_continuous(labels = NULL, breaks = NULL) +
106+
labs(x = "Full history – Jan 2022 to Dec 2023", y = NULL) +
107+
base_theme +
108+
theme(
109+
panel.grid.major = element_blank(),
110+
axis.title.x = element_text(color = INK_SOFT, size = 8),
111+
axis.text.x = element_text(size = 7, color = INK_SOFT),
112+
plot.margin = margin(t = 2, r = 10, b = 6, l = 10, unit = "pt")
113+
)
114+
115+
# --- Combine and save -------------------------------------------------------
116+
ragg::agg_png(
117+
filename = sprintf("plot-%s.png", THEME),
118+
width = 8,
119+
height = 4.5,
120+
units = "in",
121+
res = 400,
122+
background = PAGE_BG
123+
)
124+
gridExtra::grid.arrange(p_main, p_nav, nrow = 2, heights = c(5, 1))
125+
invisible(dev.off())
Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
library: ggplot2
2+
language: r
3+
specification_id: line-navigator
4+
created: '2026-05-27T11:37:05Z'
5+
updated: '2026-05-27T11:44:08Z'
6+
generated_by: claude-sonnet
7+
workflow_run: 26508525153
8+
issue: 3785
9+
language_version: 4.4.1
10+
library_version: 3.5.1
11+
preview_url_light: https://storage.googleapis.com/anyplot-images/plots/line-navigator/r/ggplot2/plot-light.png
12+
preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/line-navigator/r/ggplot2/plot-dark.png
13+
preview_html_light: null
14+
preview_html_dark: null
15+
quality_score: 86
16+
review:
17+
strengths:
18+
- 'Two-panel layout cleanly implements the navigator concept in a static context:
19+
detail view on top, full-history overview strip below'
20+
- Selection window correctly implemented with annotate("rect") — idiomatic ggplot2
21+
idiom for rectangular highlights
22+
- Subtitle shows the selected date range as human-readable text, satisfying the
23+
spec's 'show selected range as labels' requirement
24+
- Navigator height ratio c(5,1) gives ~17% to the overview strip, squarely within
25+
the spec's 15–20% guidance
26+
- Theme tokens (PAGE_BG, INK, INK_SOFT) correctly wired throughout both panels —
27+
both renders are fully readable
28+
- 'Synthetic data is realistic: daily temperatures with seasonal variation, trend,
29+
and cumulative noise over 2 years (730 points)'
30+
- Dynamic title-size formula adapts the title fontsize to fill appropriate width
31+
- Navigator grid correctly disabled (panel.grid.major = element_blank()), keeping
32+
the overview strip clean and uncluttered
33+
- Canvas exactly 3200×1800 px via ragg::agg_png at 8×4.5 in / 400 dpi
34+
weaknesses:
35+
- Main chart has both X and Y major grid lines; style guide recommends Y-axis grid
36+
only for line charts — vertical grid lines add clutter without aiding reading
37+
- dplyr is imported but not actually used (base R subsetting is used instead); scales
38+
may also be an unused import
39+
- Title size formula inflates to 19pt for a 42-char spec-id when 12pt would be appropriate;
40+
formula is designed to shrink long titles, not enlarge short ones
41+
- 'Dark render selection window appears as a nearly-opaque dark green block (alpha=0.22
42+
of #009E73 over #1A1A17) — harder to read as a ''highlighted range'' than in the
43+
light render; consider a slightly higher alpha or a different fill approach for
44+
dark mode'
45+
- No spine removal beyond theme_minimal defaults; removing top spine explicitly
46+
and relying on the L-shaped axis.line would be cleaner
47+
- Library Mastery could be elevated by using grid.arrange more expressively (e.g.,
48+
shared x-axis alignment annotations or explicit panel spacing)
49+
image_description: |-
50+
Light render (plot-light.png):
51+
Background: Warm off-white (#FAF8F1) — correct light surface, not pure white.
52+
Chrome: Title "line-navigator · r · ggplot2 · anyplot.ai" in bold dark ink (~19pt), prominently readable. Subtitle "Detail view: Jun 01, 2023 – Aug 31, 2023" in softer gray (INK_SOFT). Y-axis label "Temperature (°C)" in dark ink. X-axis tick labels rotated 30° at size 7pt — small but readable at full resolution. Navigator strip x-axis label "Full history – Jan 2022 to Dec 2023" in INK_SOFT, readable.
53+
Data: Brand green (#009E73) line in the main chart showing a summer 2023 temperature arc (33–38°C). Navigator strip shows full 2-year history as a thin green line with a semi-transparent green rectangle highlighting the Jun–Aug 2023 selection window.
54+
Legibility verdict: PASS — all text clearly readable against the warm off-white background; no light-on-light issues.
55+
56+
Dark render (plot-dark.png):
57+
Background: Warm near-black (#1A1A17) — correct dark surface, not pure black.
58+
Chrome: Title in light near-white (#F0EFE8), clearly readable. Subtitle in lighter gray (#B8B7B0), readable. Y-axis and axis labels in appropriate light tokens. Tick labels in INK_SOFT (#B8B7B0). No dark-on-dark failures observed.
59+
Data: Brand green (#009E73) data line is identical to the light render — same hue, same weight, confirming palette constancy. Selection window in the navigator renders as a darker solid green block (alpha=0.22 of #009E73 over near-black) — recognizable but less visually prominent than in the light render.
60+
Legibility verdict: PASS — all text readable against the dark background; no near-black text on near-black background observed.
61+
criteria_checklist:
62+
visual_quality:
63+
score: 29
64+
max: 30
65+
items:
66+
- id: VQ-01
67+
name: Text Legibility
68+
score: 7
69+
max: 8
70+
passed: true
71+
comment: Title, subtitle, axis labels all readable in both themes. X-axis
72+
tick labels in main chart use size=7pt (slightly below recommended 8pt).
73+
- id: VQ-02
74+
name: No Overlap
75+
score: 6
76+
max: 6
77+
passed: true
78+
comment: No overlapping text or data elements. Both panels well-spaced.
79+
- id: VQ-03
80+
name: Element Visibility
81+
score: 6
82+
max: 6
83+
passed: true
84+
comment: Main line clearly visible at linewidth=1.0. Navigator line thinner
85+
(0.45, alpha=0.75) — appropriate for overview context. Selection window
86+
visible in both themes.
87+
- id: VQ-04
88+
name: Color Accessibility
89+
score: 2
90+
max: 2
91+
passed: true
92+
comment: Single series using brand green — no CVD conflict. No red-green pairing.
93+
- id: VQ-05
94+
name: Layout & Canvas
95+
score: 4
96+
max: 4
97+
passed: true
98+
comment: 'Canvas gate passed (3200x1800). Two-panel layout proportionate:
99+
navigator ~17% of total height (within spec''s 15-20%). No overflow or clipping
100+
observed.'
101+
- id: VQ-06
102+
name: Axis Labels & Title
103+
score: 2
104+
max: 2
105+
passed: true
106+
comment: 'Y-axis: ''Temperature (°C)'' with units. Navigator x-axis: ''Full
107+
history – Jan 2022 to Dec 2023''. Title format correct.'
108+
- id: VQ-07
109+
name: Palette Compliance
110+
score: 2
111+
max: 2
112+
passed: true
113+
comment: 'Brand green #009E73 used as first (only) series. PAGE_BG correctly
114+
#FAF8F1 light / #1A1A17 dark. INK/INK_SOFT tokens correctly applied.'
115+
design_excellence:
116+
score: 12
117+
max: 20
118+
items:
119+
- id: DE-01
120+
name: Aesthetic Sophistication
121+
score: 5
122+
max: 8
123+
passed: true
124+
comment: Thoughtful navigator concept realized with annotate rect highlight,
125+
compact overview strip, subtitle date range display. Above generic defaults
126+
but not highly refined.
127+
- id: DE-02
128+
name: Visual Refinement
129+
score: 3
130+
max: 6
131+
passed: true
132+
comment: Navigator grid disabled (clean). Main chart grid subtle (linewidth=0.15).
133+
However, both X and Y grid lines present in main chart — style guide recommends
134+
Y-only for line charts.
135+
- id: DE-03
136+
name: Data Storytelling
137+
score: 4
138+
max: 6
139+
passed: true
140+
comment: 'Clear two-level hierarchy: detail on top, context below. Selection
141+
window highlights focal range. Subtitle anchors the selected period. Good
142+
viewer guidance.'
143+
spec_compliance:
144+
score: 14
145+
max: 15
146+
items:
147+
- id: SC-01
148+
name: Plot Type
149+
score: 5
150+
max: 5
151+
passed: true
152+
comment: 'Correct chart type: line chart with miniature navigator pane. Two-panel
153+
layout correctly implements the spec concept.'
154+
- id: SC-02
155+
name: Required Features
156+
score: 3
157+
max: 4
158+
passed: true
159+
comment: Detail view, full-history navigator, selection window, date text
160+
labels, shared styling, proportional navigator height all present. Resize
161+
handles and animated transitions omitted — not possible in static ggplot2,
162+
acceptable limitation.
163+
- id: SC-03
164+
name: Data Mapping
165+
score: 3
166+
max: 3
167+
passed: true
168+
comment: X=date, Y=temperature correctly mapped in both panels. Navigator
169+
shows full extent; main chart shows selected range.
170+
- id: SC-04
171+
name: Title & Legend
172+
score: 3
173+
max: 3
174+
passed: true
175+
comment: Title 'line-navigator · r · ggplot2 · anyplot.ai' matches required
176+
format. No legend needed for single series.
177+
data_quality:
178+
score: 15
179+
max: 15
180+
items:
181+
- id: DQ-01
182+
name: Feature Coverage
183+
score: 6
184+
max: 6
185+
passed: true
186+
comment: 730 daily data points, large enough to make navigation meaningful.
187+
Both detail and overview views showcase the navigator concept fully.
188+
- id: DQ-02
189+
name: Realistic Context
190+
score: 5
191+
max: 5
192+
passed: true
193+
comment: Daily temperature sensor over 2 years with trend, seasonal variation,
194+
and cumulative noise — realistic and plausible.
195+
- id: DQ-03
196+
name: Appropriate Scale
197+
score: 4
198+
max: 4
199+
passed: true
200+
comment: Temperature 29–38°C over summer months is plausible. 2-year span
201+
is appropriate for demonstrating navigator utility.
202+
code_quality:
203+
score: 9
204+
max: 10
205+
items:
206+
- id: CQ-01
207+
name: KISS Structure
208+
score: 3
209+
max: 3
210+
passed: true
211+
comment: No functions or classes. Clean linear procedural structure. Well-organized
212+
sections.
213+
- id: CQ-02
214+
name: Reproducibility
215+
score: 2
216+
max: 2
217+
passed: true
218+
comment: set.seed(42) present.
219+
- id: CQ-03
220+
name: Clean Imports
221+
score: 1
222+
max: 2
223+
passed: false
224+
comment: dplyr is imported but not used (base R subsetting used throughout).
225+
scales may also be an implicit dependency only.
226+
- id: CQ-04
227+
name: Code Elegance
228+
score: 2
229+
max: 2
230+
passed: true
231+
comment: Dynamic title size formula is appropriate. No fake interactivity.
232+
Sectioned comments aid readability.
233+
- id: CQ-05
234+
name: Output & API
235+
score: 1
236+
max: 1
237+
passed: true
238+
comment: Saves as plot-{THEME}.png via ragg::agg_png. Correct device usage.
239+
library_mastery:
240+
score: 7
241+
max: 10
242+
items:
243+
- id: LM-01
244+
name: Idiomatic Usage
245+
score: 4
246+
max: 5
247+
passed: true
248+
comment: Correct use of theme layering, scale_x_date with date_labels/date_breaks,
249+
expansion(), annotate('rect'), and element_blank(). gridExtra::grid.arrange
250+
for multi-panel. Modern linewidth= not size=.
251+
- id: LM-02
252+
name: Distinctive Features
253+
score: 3
254+
max: 5
255+
passed: true
256+
comment: gridExtra::grid.arrange with heights= for proportional panel sizing;
257+
annotate('rect') for selection window; scale_y_continuous(labels=NULL) to
258+
suppress navigator y-ticks cleanly.
259+
verdict: APPROVED
260+
impl_tags:
261+
dependencies:
262+
- gridextra
263+
techniques:
264+
- subplots
265+
- annotations
266+
patterns:
267+
- data-generation
268+
dataprep:
269+
- time-series
270+
- cumulative-sum
271+
styling:
272+
- alpha-blending

0 commit comments

Comments
 (0)