Line charts · data effects

Goals vs expected goals, three ways

A season of goals against expected goals on lineChart, on a line drawn with D3, and on a Vega-Lite line. One Play button, one scrubber and the motion switch drive the first four. Each carries one decoration, only("goals", parallel(threshold({ compareTo: "xg" }), travel("soccer-ball", { ride: "on" }))), with no change to effect code.

Through matchweek 38

Goals vs expected goals, season to date

The library's own lineChart. The goals line turns red wherever the team has scored fewer goals than its chances were worth, and the ball rides its leading edge as the season plays. Hover anywhere, or Tab in and use the arrow keys, to read both lines and the gap week by week. The toggles curve its lines with update({ curve }), and hover the nearest point instead of the week with update({ hover }), here and on the chart with missing weeks.

Line Hover

The same story on a D3 line

Drawn the way D3 code would draw it, with d3.line(). expressive() describes each path from its points, samples it with samplesAlong, and adds the same decoration; watch follows each frame the page draws. Tab reaches the lines, and hovering one dims the other.

Goals scoredExpected goals (xG)Goals below xG

With missing weeks

The same season as a weekly data feed recorded it, on lineChart. The feed missed matchweeks 9 and 10, and has no xG for matchweeks 4 and 5, so those values are null: a gap in the line, not a zero. Nothing turns red where either line has no value, and as the season plays, the ball waits at matchweek 8, then jumps to 11. A missing week keeps its place: hover it, or reach it with the arrow keys, and it says what's missing.

The same gaps on a D3 line

D3 leaves a missing week out with line().defined(), which breaks the path there. expressive() describes each path by the weeks it has and where they break, with describe.line({ breaks }), so the same decoration leaves the same gaps.

Goals scoredExpected goals (xG)Goals below xG

And on a Vega-Lite line

Drawn by Vega as SVG. fromVegaView() reads its line mark from the View's scenegraph and adds the same decoration. It shows the whole season, since Vega redraws its marks on every data change. It follows the first chart's Straight or Curved toggle: Vega draws it again with interpolate "linear" or "monotone", from a signal, and fromVegaView() reads the curve from the path Vega draws, with no refresh().

Goals scoredExpected goals (xG)Goals below xG

Design notes

  • The ball is drawn at 11 px, smaller than the icon's own 17 px, so it doesn't hide the last weeks.
  • xG is a dashed grey line rather than a second colour: a colour such as orange sits too close to the red below xG, and the dash tells the lines apart whatever the viewer's colour vision. The legends draw each entry as a short line, so xG's shows its dash.
  • The goals line's end dot and hover dot keep their series colour, since threshold draws nothing on a dot. The tooltip's "vs expected" line and the table's Difference column still say when goals are below xG.
  • "Goals below xG" comes after the series in the legend, since a page's entries follow the chart's own.
  • The table's headers are the series' names, "Goals scored" and "Expected goals (xG)", and its values share the chart's one format, so an xG of 20.0 reads "20".
  • A missing week is null, never 0: a zero would pull the season-to-date line down to the axis and back. The names, the tooltip and the table say "missing", since "no goals" reads as none scored.
  • Matchweeks 3 to 6 have goals below xG, but no red: with no xG for weeks 4 and 5, there's nothing to compare, so threshold colours nothing rather than guess.
  • Curved is curve: "monotone", d3's curveMonotoneX. It goes through every week's value, and between two weeks it stays between their values, so it never overshoots: the line never shows a total the season didn't have. Basis, cardinal and natural curves overshoot or cut past the values, and a step's corners would jolt the ball.
  • On a curve, the red still starts and ends where the drawn lines cross, since the chart samples each bend. The Vega-Lite line is sampled at its weeks, so its red can start a few px from the crossing between them.
  • Nearest point hovers one point, not a week, with no crosshair. Its line, the red and the ball stay at full strength, and the other line, its points and its decorations dim. Text doesn't dim: the end labels, the axes and the tooltip keep their contrast. The tooltip and each point's name give its one value.
  • The toggles drive both lineCharts. With missing weeks, each run between gaps curves on its own, and with Nearest point, Left and Right pass over a missing week to the line's next point, and Up and Down do nothing where the other line is missing.

Things to try