Lecture 6: Aesthetic Mappings

Today’s Visualization

Figure 1: The 1 dataset 100 visualizations project

Minard’s March on Moscow - Sean Sudol

Minard’s March on Moscow - Garima Goyal

Minard’s March on Moscow - Paul Ayamah

Minard’s March on Moscow - Giacomo Radaelli

Minard’s March on Moscow - Jordan Matuszewski

Minard’s March on Moscow - Mahfal Naleemul Rahuman

Minard’s March on Moscow - Neh Majmudar

Minard’s March on Moscow - Joshua Rollins

Minard’s March on Moscow - Ryan Mc Neil

Minard’s March on Moscow - GiBeom Park

Aesthetic Attributes

Aesthetics in the Grammar of Graphics

Wilkinson has an extensive discussion, partially summarized by:

Some translations of Grammar of Graphics aesthetics

ggplot2

Aesthetics by geometry:

Geometry Position Texture/Shape Color Other
point, jitter x, y size, shape color, fill, alpha
line, rug x, y size, linetype color, alpha group
path, step x, y size, linetype color, alpha group, arrow
hline yintercept size, linetype color, alpha
vline xintercept size, linetype color, alpha
abline slope, intercept size, linetype color, alpha
segment x, xend, y, yend size, linetype color, alpha
bar, histogram, area x, y size, linetype color, fill, alpha
ribbon x, ymin, ymax size, linetype color, fill, alpha
polygon x, y size, linetype color, fill, alpha group
linerange, errorbar x, ymin, ymax size, linetype color, alpha
pointrange, crossbar x, y, ymin, ymax size, linetype color, fill, alpha

Also useful:

  • patchwork - arithmetic for combining ggplot plots
  • gganimate - grammar of animated graphics
  • ggstatsplots - specifically statistical testing reported in graphics
  • ggrepel - non-overlapping text labels
  • ggraph, ggnetwork, geomnet - graph/network layouts
  • ggforce - additional functionality; shapes, bezier curves and splines, voronoi and delaunay, convex hull annotations, LaTeX labelling, parallel sets/sankey diagrams
  • ggdist - visualize distributions and uncertainty
  • geomtextpath - curved text, directly labeled lines
  • ggalt - additional functionality; horizon charts, splines, ASH, byte number formatting, some plotly integration
  • GGally - numerous functions for arranging collections of ggplot graphs
  • ggbeeswarm - beeswarm plots (scatter+dodge+violin plot)
  • ggTimeSeries - several useful timeseries plots
  • ggwordcloud - word clouds
  • ggpp - add plots as insets

Some translations of Grammar of Graphics aesthetics

Altair Encoding channels

Position:

  • X, Y, X2, Y2
  • Longitude, Latitude
  • Longitude2, Latitude2
  • xError, yError
  • xError2, yError2
  • Theta, Theta2

Mark Property:

  • Angle, Radius
  • Color, Fill, Opacity, FillOpacity
  • Shape
  • Size
  • Stroke, StrokeDash, StrokeOpacity, StrokeWidth

Text:

  • Text, Key, Tooltip

Interaction:

  • Href

Group, z-Order:

  • Detail
  • Order

Faceting:

  • Row, Column, Facet

Reconstructing a Wilkinson plot

First some data mangling…

Code
library(tidyverse)
sexbible = read_csv("L6-figures/sexbiblecounts.csv", 
                    col_types = cols(
                      bible=col_factor(ordered=TRUE), 
                      partners=col_factor(ordered=TRUE),
                      count=col_integer()
                    )) %>%
  mutate(bible=fct_relevel(bible, sort)) %>%
  mutate(partners=partners %>% 
           fct_relevel(sort) %>% 
           fct_relevel(c("5-10","11-20","21-100"), after=Inf))
Code
import altair, pandas
sexbibleURL = "L6-figures/sexbiblecounts.csv"
bibledtype = pandas.api.types.CategoricalDtype(categories=[
  '(1) WORD OF GOD', 
  '(2) INSPIRED WORD', 
  '(3) BOOK OF FABLES', 
  '(4) OTHER'], ordered=True)
partnersdtype = pandas.api.types.CategoricalDtype(categories=['0', '1', '2', '3', '4', '5-10', '11-20', '21-100'], ordered=True)
sexbible = pandas.read_csv(sexbibleURL)
sexbible["bible"] = sexbible["bible"].astype(bibledtype)
sexbible["partners"] = sexbible["partners"].astype(partnersdtype)

Reconstructing a Wilkinson plot - ggplot2

Code
ggplot(sexbible) +
  geom_point(aes(x=partners, y=bible, size=count), shape=15) +
  xlab("Number of sex partners in last year") +
  ylab("Feelings about Bible") +
  theme(axis.text.x = element_text(angle=30))

Reconstructing a Wilkinson plot - Altair

Code
altair.Chart(sexbible).mark_point(shape="square", fill="DarkBlue", stroke="DarkBlue").encode(
  x=altair.X("partners:O",
    scale=altair.Scale(domain=sexbible["partners"].cat.categories.to_list()),
    axis=altair.Axis(title="Number of sex partners in last year")),
  y=altair.Y("bible:O",
    scale=altair.Scale(domain=sexbible["bible"].cat.categories.to_list(),
    reverse=True),
    axis=altair.Axis(title="Feelings about Bible")),
  size=altair.Size("count:Q"),
  tooltip="count:Q").properties(
    width=600, height=300
)

Reconstructing a Wilkinson plot - ggplot2

Code
ggplot(sexbible) +
  geom_point(aes(x=partners, y=bible, color=count), shape=15, size=10) +
  xlab("Number of sex partners in last year") +
  ylab("Feelings about Bible") +
  scale_color_gradient(low="grey80", high="grey20") +
  theme(axis.text.x = element_text(angle=30))

Reconstructing a Wilkinson plot - Altair

Code
altair.Chart(sexbible).mark_point(
  shape="square", size=2500).encode(
  x=altair.X("partners:O",
    scale=altair.Scale(domain=sexbible["partners"].cat.categories.to_list()),
    axis=altair.Axis(title="Number of sex partners in last year")),
  y=altair.Y("bible:O",
    scale=altair.Scale(domain=sexbible["bible"].cat.categories.to_list(),
    reverse=True),
    axis=altair.Axis(title="Feelings about Bible")),
  color=altair.Color("count:Q", 
    scale=altair.Scale(scheme="warmgreys")),
  fill=altair.Color("count:Q", 
    scale=altair.Scale(scheme="warmgreys")),
  tooltip="count:Q").properties(
    width=600, height=300
)

Reconstructing a Wilkinson plot - ggplot2

Code
ggplot(sexbible) +
  geom_point(aes(x=partners, y=bible, color=count), shape=15, size=10) +
  xlab("Number of sex partners in last year") +
  ylab("Feelings about Bible") +
  scale_color_gradientn(colors=c("darkblue", "cyan", "green", "orange")) +
  theme(axis.text.x = element_text(angle=30))

Reconstructing a Wilkinson plot - Altair

Code
altair.Chart(sexbible).mark_point(
  shape="square", size=2500).encode(
  x=altair.X("partners:O",
    scale=altair.Scale(domain=sexbible["partners"].cat.categories.to_list()),
    axis=altair.Axis(title="Number of sex partners in last year")),
  y=altair.Y("bible:O",
    scale=altair.Scale(domain=sexbible["bible"].cat.categories.to_list(),
    reverse=True),
    axis=altair.Axis(title="Feelings about Bible")),
  color=altair.Color("count:Q",
    scale=altair.Scale(domain=[0,400], range=["darkblue", "cyan", "green", "orange", "red"])),
  fill=altair.Color("count:Q",
    scale=altair.Scale(domain=[0,400], range=["darkblue", "cyan", "green", "orange", "red"])),
  tooltip="count:Q").properties(
    width=600, height=300
)

Redesigning a Wilkinson Plot

Now it’s your turn:

Homework: Use either the raw data or the summarized data to display your own version of this plot.

Wilkinson’s examples can be seen in Figures 8.25, 10.36, 10.51.

Together with your submitted redesign, you also need to submit a Design document, in which you state:

  1. What tasks you envision your design with enable.
  2. What affordances (definition to come later in this lecture) you have deliberately built.
  3. What concrete design choices you have made, and why you have made those specific design choices.

You are encouraged to refer to the information you have on effective visual channels, on Stevens work, Cleveland’s or Munzner’s hierarchies.

Affordances and Signifiers

Affordance

James J Gibson, in The Senses Considered as Perceptual Systems (1966), and in The Ecological Approach to Visual Perception (1979):

The affordances of the environment are what it offers the animal, what it provides or furnishes, either for good or ill. The verb to afford is found in the dictionary, the noun affordance is not. I have made it up. I mean by it something that refers to both the environment and the animal in a way that no existing term does. It implies the complementarity of the animal and the environment.

Developed further by Donald Norman in The Design of Everyday Things (1988), who shifts the definition to action possibilities that are readily perceivable by an actor.

As such, affordances are culturally dependent, often learned, and often key to interaction design.

Hidden and False Affordances

William Gaver further subdivides affordances into categories:

False Affordance
An apparent affordance that does not have any real function, such as placebo buttons (door close button in elevator, road crossing button for pedestrians - when these buttons have no effect on the doors or traffic lights themselves)
Hidden Affordance
A possibility for action not perceived by the actor, such as how the shoe trick to opening a wine bottle means that a shoe affords opening wine bottle, but this would not be apparent from looking at a shoe.
Perceptible Affordance
A possibility for action described by available information so that an actor perceived the possibility and can act on the affordance.

Signifiers

In his revision to The Design of Everyday Things, Norman introduced the notion of signifier. In digital UI design (especially smartphone design), affordances had been picked up by the design community in ways not originally intended - to mean the means through which a designer communicates possible actions.

Examples of signifiers include many tutorial annotations in software, especially in games.

Some early categories of computer games were very focused on affordances and signifiers:

  • Point and Click games (ie Lucasarts; Police Quest, Space Quest, Leisure Suit Larry, Grim Fandango, Monkey Island, Maniac Mansion, Day of the Tentacle, Sam&Max, etc.)
  • Text games (ie Infocom; Zork, The Hitchhiker’s Guide to the Galaxy, Leather Goddesses of Phobos, )

Rules of Thumb

Munzner’s Rules of Thumb

  1. No Unjustified 3D
  2. No Unjustified 2D
  3. Eyes Beat Memory
  4. Resolution over Immersion
  5. Overview First, Zoom and Filter, Detail on Demand
  6. Responsiveness Is Required
  7. Get It Right in Black and White
  8. Function First, Form Next

No Unjustified 3D

Stevens power law has exponent 1.0 for planar position (left/right; up/down), but 0.67 for depth perception.

We are significantly less accurate at perceiving 3 dimensions. We see the world, not as much in 3 dimensions, as in \(2+\epsilon\) dimensions.

Depth cues are communicated by occlusion, foreshortening, cast shadows, stereoscopy, atmospheric perspective.

These techniques all remove information or interfere with additional visual channels.

That said, 3D is very powerful when the task is directly related to perceiving 3-dimensional shapes.

No Unjustified 3D - what instead?

Powerful replacement design is to introduce several linked views of the same data. This is how many CAD and 3D design tools work.

Code
from palmerpenguins import load_penguins
import pandas

penguins = load_penguins()
Code
from matplotlib import pyplot
fig = pyplot.figure()
ax = fig.add_subplot(projection="3d")
speciesmapping = {
  "Adelie": 0,
  "Gentoo": 1,
  "Chinstrap": 2
}
ax.scatter("bill_length_mm", "bill_depth_mm", "flipper_length_mm", data=penguins.query("sex == 'male'"), marker="$\u2642$", c=[speciesmapping[p] for p in penguins.query("sex == 'male'")["species"]])
ax.scatter("bill_length_mm", "bill_depth_mm", "flipper_length_mm", data=penguins.query("sex == 'female'"), marker="$\u2640$", c=[speciesmapping[p] for p in penguins.query("sex == 'female'")["species"]])
ax.set_xlabel("Bill Length (mm)")
ax.set_ylabel("Bill Depth (mm)")
ax.set_zlabel("Flipper Length (mm)")
ax.set_title("Penguin data set; color indicates species")
ax.legend()
pyplot.show()

Matplotlib 3d scatter plot.

No Unjustified 3D - what instead?

Powerful replacement design is to introduce several linked views of the same data. This is how many CAD and 3D design tools work.

Code
penguins["symbol"] = [{"male": "♂", "female": "♀"}.get(s, " ") for s in penguins["sex"]]

bill_length_ax = altair.Axis(title="Bill Length (mm)")
bill_depth_ax = altair.Axis(title="Bill Depth (mm)")
flipper_length_ax = altair.Axis(title="Flipper Length (mm)")

single_nearest = altair.selection_single(on="mouseover", nearest=True, empty="none")

bill_bill = altair.Chart(penguins).mark_text().encode(
  x=altair.X("bill_length_mm:Q", axis=bill_length_ax, scale=altair.Scale(zero=False)),
  y=altair.Y("bill_depth_mm:Q", axis=bill_depth_ax, scale=altair.Scale(zero=False)),
  color=altair.Color("species:N"),
  size=altair.condition(single_nearest.ref(), altair.value(25), altair.value(15)),
  text="symbol"
).add_selection(single_nearest).interactive()

bill_flipper = altair.Chart(penguins).mark_text().encode(
  x=altair.X("bill_length_mm:Q", axis=bill_length_ax, scale=altair.Scale(zero=False)),
  y=altair.Y("flipper_length_mm:Q", axis=flipper_length_ax, scale=altair.Scale(zero=False)),
  color=altair.Color("species:N"),
  size=altair.condition(single_nearest, altair.value(25), altair.value(15)),
  text="symbol"
).add_selection(single_nearest).interactive()

flipper_bill = altair.Chart(penguins).mark_text().encode(
  x=altair.X("flipper_length_mm:Q", axis=flipper_length_ax, scale=altair.Scale(zero=False)),
  y=altair.Y("bill_depth_mm:Q", axis=bill_depth_ax, scale=altair.Scale(zero=False)),
  color=altair.Color("species:N"),
  size=altair.condition(single_nearest, altair.value(25), altair.value(15)),
  text="symbol"
).add_selection(single_nearest).interactive()

bill_bill & bill_flipper | flipper_bill

No Unjustified 2D

Is the 2D layout really better than showing the data in a list?

1D layouts minimize space, and are very efficient at lookup tasks.

This may even remain true when the data has spatial or network roots. See, for instance, the shop directories that usually accompany mall floor plans.

Eyes Beat Memory

Eyes Beat Memory

Eyes Beat Memory

Complications are due to the limited size of working memory and human attention.

The key to cutting through these complications:

Simultaneous, or close, displays of information under the user’s control. Scrobbling, or repeated stepping between frames are useful ways to make changes pop out.

Small Multiples also builds on simultaneous display.

Resolution over Immersion

Virtual Reality, stereoscopic viewers, projection rooms, projection cubes, …

There are a whole bunch of ways people have tried to produce immersive experiences in visualizations.

They tend to

  1. Require specialized equipment
  2. Lag behind in display technology

So even if you can use the specialized equipments and platforms, you end up with a lower resolution display.

So immersion is a very expensive design feature. It might be useful, but make sure you know why you are paying the price.

Overview First, Zoom and Filter, Detail on Demand

Remember when I tried to find a design guideline and couldn’t remember what it was? This is it.

The guideline is most applicable to medium sized data sets:

Very Small Data Sets
Show in a list
Small-Medium Data Sets
  • Overview First
  • Zoom and Filter (to allow exploration)
  • Detail on Demand (to allow inspection)
Large Data Sets
  • Search
  • Show Context
  • Expand on Demand
Very Large Data Sets
…be Google…

Responsiveness Is Required

Time Scales of human-computer interactions:

<0.1 seconds
First Impressions of visual appeal.
Users feel like their actions is directly causing something to happen.
<1 seconds
Users feel like the computer is causing the result to appear. Train of thought uninterrupted.
10 seconds
At this threshold, most attention spans are maxed out. The flow of a user’s process gets interrupted.

Responsiveness Is Required

Already for the span of 1-10 seconds, you may want visual indicators of progress, and for any process taking longer than 10 seconds you need to assume that the user may task-switch - which means you need to indicate progress, indicate successful/unsuccessful finish state, and make it easy to re-establish their activities.

Some methods include:

  1. Clearly indicate progress completed and time or steps remaining
  2. Contextualize success-dialogs with additional details
  3. Enable user-generated notes and comments
  4. Provide access to historical content (without re-computing)
  5. Allow processes to run in the background

Responsiveness Is Required

Even with a responsive system, interaction is cognitively expensive.

In the extreme, interactive systems break down to requiring a user to exhaustively search the visualization for the information they need - at which point the system could have been more automated.

Get It Right in Black and White

Both for handling color blindness issues, and for different presentation media, Get It Right in Black And White as a design slogan and design guideline advocates literally rendering in black/white (and grayscales) using a printer or image processing to check legibility.

This guideline suggests that luminance be preferred as a primary color based visual channel over hue and saturation that are better used to convey secondary information.

Good simulator for color blindness on a system-wide level: Color Oracle

Good simulator on a browser level: Let’s get color blind Chrome, Firefox

Code
library(tidyverse)
library(rwantshue)
iwh = iwanthue()
theme_set(theme_void())
data = tibble(x=c(0,runif(10) %>% sort, 1) %>% diff) %>% 
  mutate(id=row_number() %>% as.character %>% fct)
ggplot(data, aes(1, x, fill=id)) +
  geom_col(position="fill") +
  xlim(0,2) + 
  coord_polar(theta="y") +
  theme(legend.position="none") +
  labs(title="Hue")
ggplot(data, aes(1, x, fill=id)) +
  geom_col(position="fill") +
  xlim(0,2) + 
  coord_polar(theta="y") +
  scale_fill_brewer(type="qual", palette="Set3") +
  theme(legend.position="none") +
  labs(title="Brewer Set3")
ggplot(data, aes(1, x, fill=id)) +
  geom_col(position="fill") +
  xlim(0,2) + 
  coord_polar(theta="y") +
  #scale_fill_viridis_d(option="D") +
  scale_fill_manual(values=viridisLite::viridis(n=nrow(data)) %>% sample) +
  theme(legend.position="none") +
  labs(title="Shuffled Viridis")
ggplot(data, aes(1, x, fill=id)) +
  geom_col(position="fill") +
  xlim(0,2) + 
  coord_polar(theta="y") +
  scale_fill_manual(values=iwh$palette(n=nrow(data))) +
  theme(legend.position="none") +
  labs(title="I Want Hue")

Function First, Form Next

Munzner makes the case that:

  • An effective but ugly design can be improved, possibly by collaboration with graphic designers
  • A beautiful but ineffective design is likely so fundamentally flawed it has to be redesigned from the ground up to work.
  • You should care about both aspects - both efficient communication and beautiful design.