x_topics <- c(
"Environmental\nresponsibility\n[account of interest]",
"Premium quality\nperception",
"Price expectation\n(eco = premium)",
"Social identity\n& values alignment",
"Health &\nnaturalness",
"Certification\ntrust",
"Demand effects\n(context signals\n'eco is expected')",
"Guilt\nreduction"
)
y_topics <- c(
"Personal\nenvironmental values",
"Perceived product\nquality",
"Price sensitivity\n& budget",
"Social identity",
"Health concern",
"Brand trust",
"Social desirability\n& demand compliance",
"Hedonic taste\nexpectations",
"Habitual spending\npatterns"
)
# First 7 x-topics are shared with first 7 y-topics; last in each column are unique
nx <- length(x_topics)
ny <- length(y_topics)
plot_x <- data.frame(
topic = x_topics,
xpos = 0.15,
ypos = seq(nx, 1, by = -1L),
shared = c(rep(TRUE, 7L), FALSE),
stringsAsFactors = FALSE
)
plot_y <- data.frame(
topic = y_topics,
xpos = 0.85,
ypos = seq(nx, nx / ny, length.out = ny),
shared = c(rep(TRUE, 7L), FALSE, FALSE),
stringsAsFactors = FALSE
)
conn_df <- data.frame(
x1 = 0.265, x2 = 0.735,
y1 = plot_x$ypos[plot_x$shared],
y2 = plot_y$ypos[plot_y$shared],
is_demand = c(rep(FALSE, 6L), TRUE) # flag the demand-effect link
)
ggplot() +
# Non-demand links — standard blue
geom_segment(data = conn_df[!conn_df$is_demand, ],
aes(x = x1, xend = x2, y = y1, yend = y2),
color = "#4a90d9", linewidth = 1.4, alpha = 0.55) +
# Demand-effect link — highlighted red-orange
geom_segment(data = conn_df[conn_df$is_demand, ],
aes(x = x1, xend = x2, y = y1, yend = y2),
color = "#d7301f", linewidth = 2.0, alpha = 0.80,
arrow = arrow(ends = "both", length = unit(0.08, "inches"),
type = "open")) +
geom_label(data = plot_x,
aes(x = xpos, y = ypos, label = topic, fill = shared),
size = 2.7, fontface = "bold", lineheight = 0.88,
label.padding = unit(0.38, "lines"),
label.r = unit(0.15, "lines"), hjust = 0.5) +
geom_label(data = plot_y,
aes(x = xpos, y = ypos, label = topic, fill = shared),
size = 2.7, fontface = "bold", lineheight = 0.88,
label.padding = unit(0.38, "lines"),
label.r = unit(0.15, "lines"), hjust = 0.5) +
# Column header boxes
annotate("rect", xmin = 0.02, xmax = 0.28, ymin = 8.55, ymax = 9.20,
fill = "#1e3a5f", color = NA) +
annotate("text", x = 0.15, y = 8.88,
label = "What's in X?\n(Eco-label Manipulation)",
color = "white", fontface = "bold", size = 3.5, lineheight = 0.9) +
annotate("rect", xmin = 0.72, xmax = 0.98, ymin = 8.55, ymax = 9.20,
fill = "#2d6a4f", color = NA) +
annotate("text", x = 0.85, y = 8.88,
label = "What's in Y?\n(Willingness to Pay)",
color = "white", fontface = "bold", size = 3.5, lineheight = 0.9) +
# Demand-effect annotation
annotate("label", x = 0.50, y = plot_x$ypos[7] + 0.30,
label = "Demand effect confounder",
fill = "#fff0ed", color = "#d7301f", fontface = "bold",
size = 2.6, label.padding = unit(0.25, "lines")) +
scale_fill_manual(
values = c("TRUE" = "#d6ecff", "FALSE" = "#ffe0b2"),
labels = c("Shared construct (confound)", "Unique to this variable"),
name = NULL
) +
scale_x_continuous(limits = c(0, 1)) +
scale_y_continuous(limits = c(0.4, 9.5)) +
labs(
title = "Construct Overlap Between Treatment (X) and Outcome (Y)",
subtitle = paste0(
"Blue lines = shared constructs (confounds) \u00b7 Red line = demand effect \u00b7 Orange boxes = unique to one side\n",
"The account of interest is ONE pathway. Every other shared link is a confounder the researcher must account for."
)
) +
theme_void(base_size = 12) +
theme(
plot.title = element_text(face = "bold", hjust = 0.5, size = 14,
margin = margin(b = 6)),
plot.subtitle = element_text(hjust = 0.5, color = "#555555", size = 10,
lineheight = 1.3),
legend.position = "bottom",
plot.margin = margin(15, 15, 15, 15)
)