---
title: "Examples"
output: rmarkdown::html_vignette
resource_files:
  - figures/goldenviz-report.html
vignette: >
  %\VignetteIndexEntry{Examples}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r setup, include=FALSE}
knitr::opts_chunk$set(
  message = FALSE,
  warning = FALSE,
  fig.width = 6.5,
  fig.height = 3.8
)
```

This page gives one weak and one stronger `ggplot2` example for each of the 25
GoldenVizR rules. The goal is not to prescribe one chart type, but to show the
kind of practical correction each rule encourages.

Use `analyze_plot()` on any example to inspect the structured rule results.
Use `render_report()`, `save_report()`, or `view_report()` when you want the
same result as a formatted HTML report.

```{r html-report-code, eval=FALSE}
report <- analyze_plot(good_plot)
render_report(report)
save_report(report, "goldenviz-report.html")
view_report(report)
```

Example HTML report output:

<iframe
  class="gv-report-frame"
  src="figures/goldenviz-report.html"
  title="Embedded GoldenVizR HTML report"
  loading="lazy">
</iframe>

Annotated report layout:

```{r examples-report-annotated, echo=FALSE, out.width="100%"}
knitr::include_graphics("figures/goldenviz-report-annotated.png")
```

## Completeness

### Rule 1: Clear title

<span class="gv-example-label gv-example-label-bad">Needs improvement</span> the chart has no title, so the reader has to infer the question.

```{r r1-bad}
library(ggplot2)

ggplot(mtcars, aes(wt, mpg)) +
  geom_point() +
  labs(x = "Weight", y = "Miles per gallon") +
  theme_minimal()
```

<span class="gv-example-label gv-example-label-good">Better chart</span> the title states what the chart is about.

```{r r1-good}
library(ggplot2)

ggplot(mtcars, aes(wt, mpg)) +
  geom_point() +
  labs(
    title = "Fuel economy falls as vehicle weight increases",
    x = "Weight",
    y = "Miles per gallon"
  ) +
  theme_minimal()
```

### Rule 2: Axis labels

<span class="gv-example-label gv-example-label-bad">Needs improvement</span> raw variable names make the axes harder to understand.

```{r r2-bad}
library(ggplot2)

ggplot(mtcars, aes(wt, mpg)) +
  geom_point() +
  labs(title = "Fuel economy by weight") +
  theme_minimal()
```

<span class="gv-example-label gv-example-label-good">Better chart</span> labels use readable language and units.

```{r r2-good}
library(ggplot2)

ggplot(mtcars, aes(wt, mpg)) +
  geom_point() +
  labs(
    title = "Fuel economy by vehicle weight",
    x = "Weight in 1000 pounds",
    y = "Miles per gallon"
  ) +
  theme_minimal()
```

### Rule 3: Missing values and gaps

<span class="gv-example-label gv-example-label-bad">Needs improvement</span> missing months vanish without explanation.

```{r r3-bad}
library(ggplot2)

sales <- data.frame(
  month = as.Date(c("2026-01-01", "2026-02-01", "2026-04-01", "2026-05-01")),
  revenue = c(12, 14, 18, 17)
)

ggplot(sales, aes(month, revenue)) +
  geom_line() +
  geom_point() +
  labs(title = "Monthly revenue", x = "Month", y = "Revenue") +
  theme_minimal()
```

<span class="gv-example-label gv-example-label-good">Better chart</span> the chart makes the missing March value explicit.

```{r r3-good}
library(ggplot2)

sales <- data.frame(
  month = as.Date(c("2026-01-01", "2026-02-01", "2026-03-01", "2026-04-01", "2026-05-01")),
  revenue = c(12, 14, NA, 18, 17)
)

ggplot(sales, aes(month, revenue)) +
  geom_line(na.rm = TRUE) +
  geom_point(size = 2, na.rm = TRUE) +
  annotate("text", x = as.Date("2026-03-01"), y = 15.5, label = "March missing", size = 3) +
  labs(
    title = "Monthly revenue with missing March data shown",
    x = "Month",
    y = "Revenue",
    caption = "March was not reported."
  ) +
  theme_minimal()
```

### Rule 4: Legend clarity

<span class="gv-example-label gv-example-label-bad">Needs improvement</span> the color legend uses raw category codes.

```{r r4-bad}
library(ggplot2)

ggplot(mtcars, aes(wt, mpg, colour = factor(cyl))) +
  geom_point(size = 2.5) +
  labs(title = "Fuel economy by weight", x = "Weight", y = "Miles per gallon") +
  theme_minimal()
```

<span class="gv-example-label gv-example-label-good">Better chart</span> the legend title and labels explain the grouping.

```{r r4-good}
library(ggplot2)

ggplot(mtcars, aes(wt, mpg, colour = factor(cyl))) +
  geom_point(size = 2.5) +
  scale_colour_discrete(
    name = "Cylinder count",
    labels = c("4 cylinders", "6 cylinders", "8 cylinders")
  ) +
  labs(title = "Fuel economy by weight and cylinder count", x = "Weight", y = "Miles per gallon") +
  theme_minimal()
```

### Rule 5: Source and context

<span class="gv-example-label gv-example-label-bad">Needs improvement</span> the chart gives no source, scope, or time context.

```{r r5-bad}
library(ggplot2)

ggplot(mtcars, aes(factor(cyl), mpg)) +
  geom_boxplot() +
  labs(title = "Fuel economy by cylinders", x = "Cylinders", y = "Miles per gallon") +
  theme_minimal()
```

<span class="gv-example-label gv-example-label-good">Better chart</span> the caption tells the reader where the data came from.

```{r r5-good}
library(ggplot2)

ggplot(mtcars, aes(factor(cyl), mpg)) +
  geom_boxplot() +
  labs(
    title = "Fuel economy by cylinder count",
    subtitle = "Motor Trend road tests",
    x = "Cylinders",
    y = "Miles per gallon",
    caption = "Source: mtcars dataset."
  ) +
  theme_minimal()
```

### Rule 6: Annotation for key message

<span class="gv-example-label gv-example-label-bad">Needs improvement</span> the important peak is left for the reader to discover.

```{r r6-bad}
library(ggplot2)

traffic <- data.frame(day = 1:10, visits = c(40, 42, 45, 47, 52, 88, 56, 54, 53, 51))

ggplot(traffic, aes(day, visits)) +
  geom_line() +
  geom_point() +
  labs(title = "Daily visits", x = "Day", y = "Visits") +
  theme_minimal()
```

<span class="gv-example-label gv-example-label-good">Better chart</span> annotation explains the key event.

```{r r6-good}
library(ggplot2)

traffic <- data.frame(day = 1:10, visits = c(40, 42, 45, 47, 52, 88, 56, 54, 53, 51))

ggplot(traffic, aes(day, visits)) +
  geom_line() +
  geom_point() +
  annotate("label", x = 6.8, y = 82, label = "Campaign launch", size = 3) +
  coord_cartesian(ylim = c(35, 94), clip = "off") +
  labs(title = "Daily visits increased during campaign launch", x = "Day", y = "Visits") +
  theme_minimal()
```

## Readability

### Rule 7: Readable text

<span class="gv-example-label gv-example-label-bad">Needs improvement</span> small text makes the chart difficult to read.

```{r r7-bad}
library(ggplot2)

ggplot(mtcars, aes(wt, mpg)) +
  geom_point() +
  labs(title = "Fuel economy by weight", x = "Weight", y = "Miles per gallon") +
  theme_minimal(base_size = 6)
```

<span class="gv-example-label gv-example-label-good">Better chart</span> the base text size is readable.

```{r r7-good}
library(ggplot2)

ggplot(mtcars, aes(wt, mpg)) +
  geom_point() +
  labs(title = "Fuel economy by weight", x = "Weight", y = "Miles per gallon") +
  theme_minimal(base_size = 12)
```

### Rule 8: Accessible color choices

<span class="gv-example-label gv-example-label-bad">Needs improvement</span> too many similar colors make categories hard to separate.

```{r r8-bad}
library(ggplot2)

segments <- data.frame(
  segment = c("Home", "Food", "Bills", "Health", "Auto", "Shopping"),
  value = c(32, 16, 21, 11, 15, 5)
)

ggplot(segments, aes(segment, value, fill = segment)) +
  geom_col() +
  scale_fill_manual(values = c("#d95f02", "#e66101", "#fdb863", "#b35806", "#fdae61", "#fee0b6")) +
  labs(title = "Monthly spending", x = "Category", y = "Share") +
  theme_minimal()
```

<span class="gv-example-label gv-example-label-good">Better chart</span> direct labels and a restrained palette reduce dependence on color alone.

```{r r8-good}
library(ggplot2)

segments <- data.frame(
  segment = c("Home", "Food", "Bills", "Health", "Auto", "Shopping"),
  value = c(32, 16, 21, 11, 15, 5)
)

ggplot(segments, aes(reorder(segment, value), value)) +
  geom_col(fill = "#4c78a8") +
  geom_text(aes(label = paste0(value, "%")), hjust = -0.15, size = 3.3) +
  coord_flip() +
  labs(title = "Monthly spending by category", x = NULL, y = "Share of spending") +
  theme_minimal() +
  theme(legend.position = "none")
```

### Rule 9: Avoid clutter

<span class="gv-example-label gv-example-label-bad">Needs improvement</span> redundant layers and heavy styling compete with the data.

```{r r9-bad}
library(ggplot2)

ggplot(mtcars, aes(wt, mpg, colour = factor(cyl), shape = factor(cyl))) +
  geom_point(size = 4) +
  geom_line() +
  geom_smooth(se = FALSE) +
  labs(title = "Fuel economy", x = "Weight", y = "Miles per gallon") +
  theme_bw() +
  theme(panel.grid.minor = element_line(colour = "grey70"))
```

<span class="gv-example-label gv-example-label-good">Better chart</span> the chart keeps only the marks needed for the comparison.

```{r r9-good}
library(ggplot2)

ggplot(mtcars, aes(wt, mpg, colour = factor(cyl))) +
  geom_point(size = 2.4, alpha = 0.85) +
  labs(
    title = "Fuel economy by vehicle weight",
    x = "Weight",
    y = "Miles per gallon",
    colour = "Cylinders"
  ) +
  theme_minimal()
```

### Rule 10: Grid and guide balance

<span class="gv-example-label gv-example-label-bad">Needs improvement</span> heavy grids dominate the visual field.

```{r r10-bad}
library(ggplot2)

ggplot(mtcars, aes(wt, mpg)) +
  geom_point(size = 2.2) +
  labs(title = "Fuel economy by weight", x = "Weight", y = "Miles per gallon") +
  theme_minimal() +
  theme(
    panel.grid.major = element_line(colour = "grey25", linewidth = 0.8),
    panel.grid.minor = element_line(colour = "grey45", linewidth = 0.6)
  )
```

<span class="gv-example-label gv-example-label-good">Better chart</span> subtle major guides support reading without taking over.

```{r r10-good}
library(ggplot2)

ggplot(mtcars, aes(wt, mpg)) +
  geom_point(size = 2.2) +
  labs(title = "Fuel economy by weight", x = "Weight", y = "Miles per gallon") +
  theme_minimal() +
  theme(
    panel.grid.major = element_line(colour = "grey88", linewidth = 0.4),
    panel.grid.minor = element_blank()
  )
```

### Rule 11: Consistent formatting

<span class="gv-example-label gv-example-label-bad">Needs improvement</span> inconsistent labels and colors make the categories look unrelated.

```{r r11-bad}
library(ggplot2)

region <- data.frame(group = c("north", "SOUTH", "East", "west"), value = c(18, 24, 20, 16))

ggplot(region, aes(group, value, fill = group)) +
  geom_col() +
  scale_fill_manual(values = c("red", "grey50", "navy", "orange")) +
  labs(title = "Regional performance", x = "region", y = "VALUE") +
  theme_minimal()
```

<span class="gv-example-label gv-example-label-good">Better chart</span> labels and styling follow one convention.

```{r r11-good}
library(ggplot2)

region <- data.frame(group = c("North", "South", "East", "West"), value = c(18, 24, 20, 16))

ggplot(region, aes(group, value)) +
  geom_col(fill = "#4c78a8") +
  labs(title = "Regional performance", x = "Region", y = "Score") +
  theme_minimal()
```

### Rule 12: Appropriate chart type

<span class="gv-example-label gv-example-label-bad">Needs improvement</span> a line implies order and continuity between unordered categories.

```{r r12-bad}
library(ggplot2)

region <- data.frame(region = c("North", "South", "East", "West"), value = c(18, 24, 20, 16))

ggplot(region, aes(region, value, group = 1)) +
  geom_line() +
  geom_point(size = 2.5) +
  labs(title = "Regional performance", x = "Region", y = "Score") +
  theme_minimal()
```

<span class="gv-example-label gv-example-label-good">Better chart</span> bars suit unordered category comparisons.

```{r r12-good}
library(ggplot2)

region <- data.frame(region = c("North", "South", "East", "West"), value = c(18, 24, 20, 16))

ggplot(region, aes(reorder(region, value), value)) +
  geom_col(fill = "#4c78a8") +
  coord_flip() +
  labs(title = "Regional performance", x = "Region", y = "Score") +
  theme_minimal()
```

### Rule 13: Readable scale labels

<span class="gv-example-label gv-example-label-bad">Needs improvement</span> long numeric labels are harder to scan.

```{r r13-bad}
library(ggplot2)

revenue <- data.frame(year = 2021:2025, amount = c(1200000, 1450000, 1380000, 1700000, 1850000))

ggplot(revenue, aes(year, amount)) +
  geom_line() +
  geom_point() +
  labs(title = "Revenue trend", x = "Year", y = "Revenue") +
  theme_minimal()
```

<span class="gv-example-label gv-example-label-good">Better chart</span> the scale communicates units directly.

```{r r13-good}
library(ggplot2)

revenue <- data.frame(year = 2021:2025, amount = c(1200000, 1450000, 1380000, 1700000, 1850000))

ggplot(revenue, aes(year, amount / 1e6)) +
  geom_line() +
  geom_point() +
  scale_x_continuous(breaks = revenue$year) +
  labs(title = "Revenue trend", x = "Year", y = "Revenue, millions") +
  theme_minimal()
```

### Rule 14: Overplotting management

<span class="gv-example-label gv-example-label-bad">Needs improvement</span> many overlapping points hide density.

```{r r14-bad}
library(ggplot2)

set.seed(7)
dense <- data.frame(x = rnorm(900), y = rnorm(900))

ggplot(dense, aes(x, y)) +
  geom_point() +
  labs(title = "Customer scores", x = "Score A", y = "Score B") +
  theme_minimal()
```

<span class="gv-example-label gv-example-label-good">Better chart</span> transparency reveals the concentration of observations.

```{r r14-good}
library(ggplot2)

set.seed(7)
dense <- data.frame(x = rnorm(900), y = rnorm(900))

ggplot(dense, aes(x, y)) +
  geom_point(alpha = 0.25, size = 1.6) +
  labs(title = "Customer scores with point density visible", x = "Score A", y = "Score B") +
  theme_minimal()
```

### Rule 15: Direct labeling when useful

<span class="gv-example-label gv-example-label-bad">Needs improvement</span> the reader has to move between the legend and the lines.

```{r r15-bad}
library(ggplot2)

trend <- data.frame(
  year = rep(2021:2025, 3),
  product = rep(c("Basic", "Plus", "Pro"), each = 5),
  revenue = c(10, 12, 14, 15, 17, 8, 11, 13, 16, 20, 6, 8, 12, 18, 24)
)

ggplot(trend, aes(year, revenue, colour = product)) +
  geom_line(linewidth = 1) +
  labs(title = "Revenue by product", x = "Year", y = "Revenue") +
  theme_minimal()
```

<span class="gv-example-label gv-example-label-good">Better chart</span> direct labels reduce legend lookup.

```{r r15-good}
library(ggplot2)

trend <- data.frame(
  year = rep(2021:2025, 3),
  product = rep(c("Basic", "Plus", "Pro"), each = 5),
  revenue = c(10, 12, 14, 15, 17, 8, 11, 13, 16, 20, 6, 8, 12, 18, 24)
)
end_labels <- trend[trend$year == 2025, ]

ggplot(trend, aes(year, revenue, colour = product)) +
  geom_line(linewidth = 1) +
  geom_text(data = end_labels, aes(label = product), hjust = -0.1, show.legend = FALSE) +
  scale_x_continuous(breaks = 2021:2025, limits = c(2021, 2025.8)) +
  labs(title = "Revenue by product", x = "Year", y = "Revenue") +
  theme_minimal() +
  theme(legend.position = "none")
```

### Rule 16: Visual hierarchy

<span class="gv-example-label gv-example-label-bad">Needs improvement</span> everything has the same visual weight.

```{r r16-bad}
library(ggplot2)

trend <- data.frame(year = rep(2021:2025, 3), product = rep(c("Basic", "Plus", "Pro"), each = 5),
                    revenue = c(10, 12, 14, 15, 17, 8, 11, 13, 16, 20, 6, 8, 12, 18, 24))

ggplot(trend, aes(year, revenue, colour = product)) +
  geom_line(linewidth = 1.1) +
  labs(title = "Revenue by product", x = "Year", y = "Revenue") +
  theme_minimal()
```

<span class="gv-example-label gv-example-label-good">Better chart</span> the main series is emphasized and supporting series recede.

```{r r16-good}
library(ggplot2)

trend <- data.frame(year = rep(2021:2025, 3), product = rep(c("Basic", "Plus", "Pro"), each = 5),
                    revenue = c(10, 12, 14, 15, 17, 8, 11, 13, 16, 20, 6, 8, 12, 18, 24))

ggplot(trend, aes(year, revenue, group = product)) +
  geom_line(data = subset(trend, product != "Pro"), colour = "grey70", linewidth = 0.8) +
  geom_line(data = subset(trend, product == "Pro"), colour = "#8a3f24", linewidth = 1.4) +
  labs(title = "Pro revenue is accelerating", x = "Year", y = "Revenue") +
  theme_minimal()
```

## Integrity

### Rule 17: Truthful scale

<span class="gv-example-label gv-example-label-bad">Needs improvement</span> a narrow y-axis range exaggerates a modest change.

```{r r17-bad}
library(ggplot2)

approval <- data.frame(month = 1:6, score = c(71, 72, 72, 73, 74, 75))

ggplot(approval, aes(month, score)) +
  geom_line() +
  geom_point() +
  coord_cartesian(ylim = c(70, 76)) +
  labs(title = "Approval score", x = "Month", y = "Score") +
  theme_minimal()
```

<span class="gv-example-label gv-example-label-good">Better chart</span> the scale gives enough context for the magnitude.

```{r r17-good}
library(ggplot2)

approval <- data.frame(month = 1:6, score = c(71, 72, 72, 73, 74, 75))

ggplot(approval, aes(month, score)) +
  geom_line() +
  geom_point() +
  coord_cartesian(ylim = c(0, 100)) +
  labs(title = "Approval score rose modestly", x = "Month", y = "Score out of 100") +
  theme_minimal()
```

### Rule 18: No distorted baseline

<span class="gv-example-label gv-example-label-bad">Needs improvement</span> bars start away from zero, inflating differences.

```{r r18-bad}
library(ggplot2)

teams <- data.frame(team = c("A", "B", "C"), score = c(92, 95, 97))

ggplot(teams, aes(team, score)) +
  geom_col(fill = "#4c78a8") +
  coord_cartesian(ylim = c(90, 98)) +
  labs(title = "Team scores", x = "Team", y = "Score") +
  theme_minimal()
```

<span class="gv-example-label gv-example-label-good">Better chart</span> bars use a zero baseline.

```{r r18-good}
library(ggplot2)

teams <- data.frame(team = c("A", "B", "C"), score = c(92, 95, 97))

ggplot(teams, aes(team, score)) +
  geom_col(fill = "#4c78a8") +
  coord_cartesian(ylim = c(0, 100)) +
  labs(title = "Team scores", x = "Team", y = "Score out of 100") +
  theme_minimal()
```

### Rule 19: Avoid misleading encodings

<span class="gv-example-label gv-example-label-bad">Needs improvement</span> area-like bubbles can make differences look larger than the values.

```{r r19-bad}
library(ggplot2)

markets <- data.frame(market = c("A", "B", "C", "D"), share = c(12, 18, 24, 30))

ggplot(markets, aes(market, 1, size = share)) +
  geom_point(colour = "#8a3f24", alpha = 0.7) +
  scale_size(range = c(6, 28)) +
  labs(title = "Market share", x = "Market", y = NULL, size = "Share") +
  theme_minimal()
```

<span class="gv-example-label gv-example-label-good">Better chart</span> bar length supports more accurate comparison.

```{r r19-good}
library(ggplot2)

markets <- data.frame(market = c("A", "B", "C", "D"), share = c(12, 18, 24, 30))

ggplot(markets, aes(reorder(market, share), share)) +
  geom_col(fill = "#4c78a8") +
  coord_flip() +
  labs(title = "Market share", x = "Market", y = "Share (%)") +
  theme_minimal()
```

### Rule 20: Handle outliers honestly

<span class="gv-example-label gv-example-label-bad">Needs improvement</span> filtering removes the outlier without saying so.

```{r r20-bad}
library(ggplot2)

orders <- data.frame(order = 1:12, value = c(22, 24, 20, 23, 25, 21, 24, 26, 22, 23, 25, 80))

ggplot(subset(orders, value < 50), aes(order, value)) +
  geom_point(size = 2.5) +
  labs(title = "Order values", x = "Order", y = "Value") +
  theme_minimal()
```

<span class="gv-example-label gv-example-label-good">Better chart</span> show the outlier and explain it.

```{r r20-good}
library(ggplot2)

orders <- data.frame(order = 1:12, value = c(22, 24, 20, 23, 25, 21, 24, 26, 22, 23, 25, 80))

ggplot(orders, aes(order, value)) +
  geom_point(size = 2.5) +
  annotate("label", x = 12, y = 80, label = "Bulk order", hjust = 1.05, size = 3) +
  labs(title = "Order values with bulk order shown", x = "Order", y = "Value") +
  theme_minimal()
```

### Rule 21: Proportional areas

<span class="gv-example-label gv-example-label-bad">Needs improvement</span> icon sizes are not proportional to the data.

```{r r21-bad}
library(ggplot2)

share <- data.frame(group = c("Small", "Medium", "Large"), value = c(10, 20, 40))

ggplot(share, aes(group, value, size = group)) +
  geom_point(colour = "#8a3f24") +
  scale_size_manual(values = c(8, 18, 30)) +
  labs(title = "Group size", x = "Group", y = "Value") +
  theme_minimal()
```

<span class="gv-example-label gv-example-label-good">Better chart</span> use bar length for proportional comparison.

```{r r21-good}
library(ggplot2)

share <- data.frame(group = c("Small", "Medium", "Large"), value = c(10, 20, 40))

ggplot(share, aes(group, value)) +
  geom_col(fill = "#4c78a8") +
  labs(title = "Group size", x = "Group", y = "Value") +
  theme_minimal()
```

### Rule 22: Uncertainty when needed

<span class="gv-example-label gv-example-label-bad">Needs improvement</span> averages are shown without uncertainty.

```{r r22-bad}
library(ggplot2)

estimate <- data.frame(group = c("A", "B", "C"), mean = c(10, 13, 15), low = c(8, 10, 12), high = c(12, 16, 18))

ggplot(estimate, aes(group, mean)) +
  geom_point(size = 3) +
  labs(title = "Average outcome by group", x = "Group", y = "Mean outcome") +
  theme_minimal()
```

<span class="gv-example-label gv-example-label-good">Better chart</span> intervals show uncertainty around the estimates.

```{r r22-good}
library(ggplot2)

estimate <- data.frame(group = c("A", "B", "C"), mean = c(10, 13, 15), low = c(8, 10, 12), high = c(12, 16, 18))

ggplot(estimate, aes(group, mean)) +
  geom_errorbar(aes(ymin = low, ymax = high), width = 0.15) +
  geom_point(size = 3, colour = "#4c78a8") +
  labs(title = "Average outcome by group with uncertainty", x = "Group", y = "Mean outcome") +
  theme_minimal()
```

### Rule 23: Avoid cherry picking

<span class="gv-example-label gv-example-label-bad">Needs improvement</span> the axis starts after the earlier decline.

```{r r23-bad}
library(ggplot2)

index <- data.frame(year = 2017:2026, value = c(120, 118, 114, 110, 112, 116, 119, 123, 126, 128))

ggplot(subset(index, year >= 2021), aes(year, value)) +
  geom_line() +
  geom_point() +
  labs(title = "Index is rising", x = "Year", y = "Index") +
  theme_minimal()
```

<span class="gv-example-label gv-example-label-good">Better chart</span> the broader range shows the full pattern.

```{r r23-good}
library(ggplot2)

index <- data.frame(year = 2017:2026, value = c(120, 118, 114, 110, 112, 116, 119, 123, 126, 128))

ggplot(index, aes(year, value)) +
  geom_line() +
  geom_point() +
  scale_x_continuous(breaks = 2017:2026) +
  labs(title = "Index recovered after an earlier decline", x = "Year", y = "Index") +
  theme_minimal()
```

### Rule 24: Respect data granularity

<span class="gv-example-label gv-example-label-bad">Needs improvement</span> discrete survey waves are connected as if the path between them is known.

```{r r24-bad}
library(ggplot2)

survey <- data.frame(wave = c("Wave 1", "Wave 2", "Wave 3", "Wave 4"), score = c(62, 66, 65, 70))

ggplot(survey, aes(wave, score, group = 1)) +
  geom_line() +
  geom_point(size = 2.5) +
  labs(title = "Survey score by wave", x = "Wave", y = "Score") +
  theme_minimal()
```

<span class="gv-example-label gv-example-label-good">Better chart</span> points show separate observations without implying continuous movement.

```{r r24-good}
library(ggplot2)

survey <- data.frame(wave = c("Wave 1", "Wave 2", "Wave 3", "Wave 4"), score = c(62, 66, 65, 70))

ggplot(survey, aes(wave, score)) +
  geom_point(size = 3, colour = "#4c78a8") +
  labs(title = "Survey score by wave", x = "Survey wave", y = "Score") +
  theme_minimal()
```

### Rule 25: Neutral framing

<span class="gv-example-label gv-example-label-bad">Needs improvement</span> loaded language tells the reader what to feel.

```{r r25-bad}
library(ggplot2)

costs <- data.frame(year = 2021:2025, cost = c(40, 42, 45, 48, 51))

ggplot(costs, aes(year, cost)) +
  geom_line() +
  geom_point() +
  labs(title = "Costs are exploding", x = "Year", y = "Cost") +
  theme_minimal()
```

<span class="gv-example-label gv-example-label-good">Better chart</span> neutral wording describes the observed change.

```{r r25-good}
library(ggplot2)

costs <- data.frame(year = 2021:2025, cost = c(40, 42, 45, 48, 51))

ggplot(costs, aes(year, cost)) +
  geom_line() +
  geom_point() +
  scale_x_continuous(breaks = costs$year) +
  labs(title = "Costs increased from 2021 to 2025", x = "Year", y = "Cost") +
  theme_minimal()
```
