| exporter | importer | billions |
|---|---|---|
| China | United States | 500 |
| Mexico | United States | 455 |
| Canada | United States | 420 |
| United States | Mexico | 320 |
| United States | Canada | 355 |
21 Edge
Which things in your table are connected to which? edge draws one stroke per row of the table, between the two things named in that row. Every other mark that draws a stroke is placed by the axes. A line and a path join points at their x and y values, and an interval spans two values on one axis. An edge is different: each row gives it only two names, and no column says where a name is placed on the page. The picture an edge layer makes is the network diagram.
So an edge takes no x and no y, and no other mark refuses both. An edge’s endpoints come from a layout, a transform that reads the two columns of names and places each distinct name once. Write the from column first and the to column second, so the sentence states the direction of each relation. If you swap the two columns, the picture does not change. It changes only when you add arrowheads: style(arrow = "end") points each edge from the first column to the second (What you can set).
21.1 The network diagram
A network diagram needs a table with one row per relation. In trade_partners, each row names an exporter and an importer, and gives the value of the goods in billions of dollars, rounded:
Name the table, then write three things: the mark, the layout that derives it, and the space network():
data(trade_partners) + edge * layout(exporter, importer) + network()data(trade_partners) + edge * layout(col.exporter, col.importer) + network()data(trade_partners) + edge * layout(:exporter, :importer) + network()plot(data(trade_partners),
layer(edge, layout(col.exporter, col.importer)), network())“Given trade partners: edges derived by layout from exporter to importer, in the network.”
The 15 countries become 15 positions on the panel, and the 22 rows become 22 strokes. Five pairs of countries trade in both directions, and each pair’s two strokes lie on one line. So you can count only 17 lines, and those five look slightly darker than the rest. No country is labeled: the layout placed the countries, but no layer draws their names.
An edge draws only inside network(). A layout computes its positions from the graph. Here a graph is not a chart: it is a set of things and the relations between them. Each thing is a node, and each row is one relation between two nodes. A node is one name, however many rows it appears in: Germany is one node in seven rows. A layout chooses positions that keep the picture readable, so the distance between two nodes is not a quantity. An axis would put numbers on that distance, so gog refuses every other space.
A layout is not for edges alone. It gives every node one position, and it adds a column, name, that holds each node’s name. Two other marks read a layout and draw at those same positions:
Mark derived by layout |
Shape |
|---|---|
point * layout |
one dot per node |
text * layout + label(name) |
each node’s name |
edge * layout |
one stroke per relation, the plot above |
The next section gives each stroke a value from its row, and adds the first of those three: a dot at each node. The Network chapter draws all three in one plot.
21.2 Color, opacity and dash, one per row
In the first plot every stroke is drawn the same way, so a relation worth 500 billion dollars looks like one worth 50 billion. The table holds that value, and a stroke can carry it as opacity. Each edge is drawn from one row, so it can take its color, its opacity and its dash from that row’s values. opacity takes a continuous column such as billions. A faint stroke’s ends are hard to find. So a second layer, point * layout(exporter, importer), reads the same layout and draws a dot at each node:
data(trade_partners) +
edge * layout(exporter, importer) + opacity(billions) +
point * layout(exporter, importer) +
network() +
title("More trade, darker stroke")(data(trade_partners) +
edge * layout(col.exporter, col.importer) + opacity(col.billions) +
point * layout(col.exporter, col.importer) +
network() +
title("More trade, darker stroke"))data(trade_partners) + edge * layout(:exporter, :importer) +
opacity(:billions) + point * layout(:exporter, :importer) + network() +
title("More trade, darker stroke")plot(data(trade_partners),
layer(edge, layout(col.exporter, col.importer)), opacity(col.billions),
layer(point, layout(col.exporter, col.importer)), network(),
title("More trade, darker stroke"))“Given trade partners: edges derived by layout from exporter to importer, opacity by billions, and also points derived by layout from exporter to importer, in the network.”
The three darkest lines meet at one node. The table shows which: China, Mexico and Canada each export more than 400 billion dollars of goods to the United States. On line, opacity is only a setting, because a line joins many rows into one stroke. An edge draws one stroke per row, so each row gives its stroke an opacity of its own.
Which strokes share an exporter? color needs a categorical column such as exporter, and each stroke takes its exporter’s color. So the five strokes with Germany as exporter share one color:
data(trade_partners) +
edge * layout(exporter, importer) + color(exporter) +
point * layout(exporter, importer) +
network() +
title("Each stroke, colored by its exporter")(data(trade_partners) +
edge * layout(col.exporter, col.importer) + color(col.exporter) +
point * layout(col.exporter, col.importer) +
network() +
title("Each stroke, colored by its exporter"))data(trade_partners) + edge * layout(:exporter, :importer) +
color(:exporter) + point * layout(:exporter, :importer) + network() +
title("Each stroke, colored by its exporter")plot(data(trade_partners),
layer(edge, layout(col.exporter, col.importer)), color(col.exporter),
layer(point, layout(col.exporter, col.importer)), network(),
title("Each stroke, colored by its exporter"))“Given trade partners: edges derived by layout from exporter to importer, colored by exporter, and also points derived by layout from exporter to importer, in the network.”
Mexico, Canada and the Netherlands are in the legend, but no stroke shows their color. Each trades both ways with one partner, and the partner’s stroke is drawn on top of theirs.
The dash can come from a column too. pattern(exporter) gives each exporter a dash, the way color(exporter) gave each a color above. There are only five dashes: solid, dashed, dotted, dotdash and longdash. When a column has more than five categories, some of them share a dash, and gog prints a message that says how many:
data(trade_partners) +
edge * layout(exporter, importer) + pattern(exporter) +
point * layout(exporter, importer) +
network() +
title("Each stroke, dashed by its exporter")(data(trade_partners) +
edge * layout(col.exporter, col.importer) + pattern(col.exporter) +
point * layout(col.exporter, col.importer) +
network() +
title("Each stroke, dashed by its exporter"))data(trade_partners) + edge * layout(:exporter, :importer) +
pattern(:exporter) + point * layout(:exporter, :importer) + network() +
title("Each stroke, dashed by its exporter")plot(data(trade_partners),
layer(edge, layout(col.exporter, col.importer)), pattern(col.exporter),
layer(point, layout(col.exporter, col.importer)), network(),
title("Each stroke, dashed by its exporter"))gog: `pattern(exporter)` has 12 categories and `edge` draws 5 dashes, so the set starts over: 7 categories share a dash with an earlier one. Use `color(exporter)`, whose default palette has twenty, or give each category its own panel with `facet(exporter)`.
“Given trade partners: edges derived by layout from exporter to importer, patterned by exporter, and also points derived by layout from exporter to importer, in the network.”
The legend lists the exporters in the order the table first names them, and the dashes start again after the fifth. So Germany, sixth, has China’s solid stroke, and so does India, eleventh. Where two countries trade both ways, their two strokes lie on one line, and the lower dash shows through the gaps in the upper one. color(exporter) gave each of the twelve its own color.
21.3 What you can set
Some choices come from no column: how thick every stroke is, or one dash for all of them. You set those in the sentence, with style(), and each mark takes its own settings. These are an edge’s, with the values each accepts:
| Setting | Value |
|---|---|
style(color = ) |
any CSS color name or hex |
style(opacity = ) |
0 to 1 |
style(size = ) |
pixels |
style(pattern = ) |
solid, dashed, dotted, dotdash, longdash |
style(arrow = ) |
end, start, both |
style(size = ) sets the stroke width for the whole layer. style(color = ), style(opacity = ) and style(pattern = ) give every stroke one value, when no column is mapped to that channel. The settings table above lists every dash that style(pattern = ) accepts.
style(arrow = ) draws an arrowhead on each stroke. With "end", it is at the node in the second column of layout(), the to column. With "start", it is at the node in the first column, and with "both", at both nodes. Each arrowhead ends outside the dot that a point * layout layer draws at the node, so the dot does not cover it:
data(trade_partners) +
edge * layout(exporter, importer) + style(arrow = "end") +
point * layout(exporter, importer) +
network() +
title("Each stroke points at its importer")(data(trade_partners) +
edge * layout(col.exporter, col.importer) + style(arrow = "end") +
point * layout(col.exporter, col.importer) +
network() +
title("Each stroke points at its importer"))data(trade_partners) + edge * layout(:exporter, :importer) +
style(arrow = "end") + point * layout(:exporter, :importer) +
network() + title("Each stroke points at its importer")plot(data(trade_partners),
layer(edge, layout(col.exporter, col.importer)),
style({ arrow: "end" }),
layer(point, layout(col.exporter, col.importer)), network(),
title("Each stroke points at its importer"))“Given trade partners: edges derived by layout from exporter to importer, with arrow end, and also points derived by layout from exporter to importer, in the network.”
The grid of every mark and every setting shows which other marks share this list. What a mark maps rather than sets is its row on the companion grid.
21.4 What it refuses
The other marks take their positions from x and y, so binding them to an edge is the first thing most readers try. An edge with no layout has no endpoints to draw, and x and y cannot supply them. gog refuses the sentence, because no layer gives network() a layout to draw:
data(trade_partners) + edge + x(billions) + y(exporter) + network()data(trade_partners) + edge + x(col.billions) + y(col.exporter) + network()data(trade_partners) + edge + x(:billions) + y(:exporter) + network()plot(data(trade_partners), edge, x(col.billions), y(col.exporter),
network())Error:
! gog: `network()` draws what a `layout` places, and nothing here carries one. Compose the syllable: `edge * layout(from, to) + network()`, with `point * layout(...)` for the nodes and `text * layout(...) + label(name)` for their names.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.
A layout is read only inside network(), so the sentence has to name that space:
data(trade_partners) + edge * layout(exporter, importer)data(trade_partners) + edge * layout(col.exporter, col.importer)data(trade_partners) + edge * layout(:exporter, :importer)plot(data(trade_partners),
layer(edge, layout(col.exporter, col.importer)))Error:
! gog: a `layout`'s positions are the graph's, not the data's — an axis under them would number something no reader can mean. Draw it in the network: `+ network()` flat, or `+ network(turn = 30, tilt = 25)` for the cube.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.
The Network chapter adds a label beside each dot and sizes each dot by its number of relations. It also draws the same graph in a cube, and lists the other refusals.
21.5 What comes next
An edge never has an axis, because a layout places both of its endpoints. Most marks are placed by an axis instead, and Scales is where a value becomes a position on an axis.