The Foundation
The Foundation is your basic scatterplot, no fluff, no frills. It may seem
plain to some, but in certain cases it's exactly what you need. A million different
colors with things swooshing around the screen can be overwhelming and make
your visualization hard to look at; it can even distract a viewer from the message
you're trying to communicate through your data. Every so often, you'll want to
come back to The Foundation for quick and easy scatterplots.
Here are it's main features:
- It's fully static (no animations or transitions included).
- Circles represent each data point relative to the x- and y-axis.
- The axes have ticks (little lines with numerical "checkpoints") and labels.
- The SVG that it's positioned within has a special
viewBox
attribute—this
is a must for making your website responsive so that it looks good on all screen sizes!
According to the width and height that you give it, viewBox
will always
preserve the SVG's aspect ratio and resize it depending on the container it's in.
Here are the basic steps that I took to create it:
- Hold the data in a simple
emp::vector<emp::vector<double>>
(an Empirical vector type).
- Add an
svg
to my page with .Append("svg");
(which will
hold all of the D3 objects we draw).
- Create an
x_scale
and a y_scale
. A scale's domain is
the lower and upper limit of the data that will be graphed on it, and its range
is the lower and upper limit of the space it will cover—in other words,
the pixels it will be drawn on. The x_scale
's range starts at
60px instead of 0 to leave room for the left_axis
's ticks and label.
- Create two axes, one on the bottom and one on the left, shifting them down and
to the right respectively so that their origins meet up at (0, 0). Here I'm using
the Axis constructor that takes
shift_x
and shift_y
arguments to position them correctly.
- Bind the data to an empty selection called
circles
.
- For each piece of data that doesn't already have a corresponding element on the DOM,
append a circle to the svg. Then, set the circle's x-coordinate to the first number in
each data pair, and set its y-coordinate to the second number (both scaled to have the
correct position relative to the axes).
And finally, here's the code for
The Foundation:
#include "../third-party/Empirical/source/web/d3/d3_init.h"
#include "../third-party/Empirical/source/web/d3/selection.h"
#include "../third-party/Empirical/source/web/d3/scales.h"
#include "../third-party/Empirical/source/web/d3/axis.h"
void MakeTheFoundation() {
// store data in vector of doubles
emp::vector<emp::vector<double>> example_data = {
{1,0}, {5,1}, {8,0.5}, {14,2.5}, {15,1.6}, {19,2.3}, {28,2.5}, {28,3.4}, {33,3.2},
{35,2.2}, {43,4.8}, {48,2.6}, {58,3.4}, {63,5.2}, {67,3.9}, {64,5.7}, {75,4},
{34,3.3}, {40,4.1}, {52,3.3}, {52,4.7}, {55,4.5}, {67,4.9}, {66,6.5}, {75,5.6},
{79,5.4}, {83,5.8}, {93,6.4}, {100,7}, {97,7}, {98,6.2}, {99,6.9}, {100,6.8}
};
std::string x_label = "days in quarantine";
std::string y_label = "days per week wearing sweatpants";
double svg_width = 700;
double svg_height = 500;
double graph_width = 600;
double graph_height = 400;
// set padding for axes
double padding_left = 60;
double padding_top = (svg_height - graph_height)/2;
// set up div and responsive svg
D3::Selection viz_div = D3::Select("#foundation-viz");
D3::Selection viz_svg = viz_div.Append("svg").SetAttr("id", "scatterplot_svg")
.SetAttr("viewBox", "0 0 " +std::to_string(svg_width)+ " " +std::to_string(svg_height));
// set up scales
D3::LinearScale x_scale = D3::LinearScale();
x_scale.SetDomain(0, 100).SetRange(padding_left, graph_width+padding_left);
D3::LinearScale y_scale = D3::LinearScale();
y_scale.SetDomain(0, 7).SetRange(graph_height+padding_top, padding_top);
// set up axes with shifts according to scale range
D3::Axis<D3::LinearScale> bottom_axis = D3::Axis<D3::LinearScale>(0, graph_height+padding_top, "bottom", x_label).SetScale(x_scale).Draw(viz_svg);
D3::Axis<D3::LinearScale> left_axis = D3::Axis<D3::LinearScale>(padding_left, 0, "left", y_label).SetScale(y_scale).Draw(viz_svg);
// set up circle data points
D3::Selection data_points = viz_svg.SelectAll("circle");
// bind data and assign a circle to each data point that doesn't have a DOM element
// (use lambdas for ApplyScale so that it's in scope)
D3::Selection circles = viz_svg.SelectAll("circle")
.Data(example_data)
.EnterAppend("circle")
.SetAttr("cx", [&example_data, &x_scale](int d, int i, int j) { return x_scale.ApplyScale<double, double>(example_data.at(i).at(0)); })
.SetAttr("cy", [&example_data, &y_scale](int d, int i, int j) { return y_scale.ApplyScale<double, double>(example_data.at(i).at(1)); })
.SetAttr("r", 3);
}
int main() {
D3::InitializeEmpD3();
MakeTheFoundation();
return 0;
}