Graphics & Charts — generic and specialized painting infrastructure

Scatter Plot

The Charting library

See Category chart for an introduction to the charting library.

What is a scatter plot?

A scatter plot is very much like a category chart, but uses numerical data on the X axis. By default, these numerical data are mapped linearly on the X axis, but may also be log transformed (This can also be configured for the Y axes). In addition, there is special support for displaying date series, by means of smart heuristics for chosing the labels on the X axis.

In a ScatterPlot, the X series data are interpreted as numbers on a linear scale. The scale for the X axis defaults to a LinearScale, but this may be changed to a DateScale when the X series contains dates (of type WDate) to create a time series chart, or to a LogScale. A ScatterPlot supports the same types of data series as a CategoryChart, but does not support stacking. In a scatter plot, the X series do not need to be ordered in increasing values, and it may be set differently for each dataseries using WDataSeries::setXSeriesColumn(int modelColumn).

Scatter plot of a time series

The table below shows an extract from historical financial market data. The scatter plot shows the second and the third column as line series.

Example
Date
AMSTEOE
DAXINDX
FTSE100
HNGKNGI
JAPDOWA
SNGALLS
SPCOMP
1986-01-06
275.8
1425.6
1424.1
1796.6
13053.8
233.6
210.7
1986-01-07
275.4
1428.5
1415.2
1815.5
12991.2
237.4
213.8
1986-01-08
278.8
1474.2
1404.2
1826.8
13056.4
241.0
208.0
1986-01-09
272.3
1461.2
1379.6
1798.5
13034.2
239.7
206.1
1986-01-10
272.9
1449.0
1394.5
1807.9
12998.2
238.9
206.0
1 of 100
source
#include <Wt/Chart/WCartesianChart.h>
#include <Wt/Chart/WDataSeries.h>
#include <Wt/WAbstractItemModel.h>
#include <Wt/WAbstractItemView.h>
#include <Wt/WApplication.h>
#include <Wt/WContainerWidget.h>
#include <Wt/WDate.h>
#include <Wt/WEnvironment.h>
#include <Wt/WPaintedWidget.h>
#include <Wt/WItemDelegate.h>
#include <Wt/WShadow.h>
#include <Wt/WStandardItemModel.h>
#include <Wt/WTableView.h>

#include "../treeview-dragdrop/CsvUtil.h"

auto container = cpp14::make_unique<WContainerWidget>();

auto model
    = csvToModel(WApplication::appRoot() + "timeseries.csv");

if (!model)
    return std::move(container);

/*
 * Parses the first column as dates, to be able to use a date scale
 */
for (int row = 0; row < model->rowCount(); ++row) {
    WString s = asString(model->data(row, 0));
    WDate date = WDate::fromString(s, "dd/MM/yy");
    model->setData(row, 0, date);
  }

// Renders the data in a table.
WTableView *table =
    container->addWidget(cpp14::make_unique<WTableView>());
table->setModel(model);
table->setSortingEnabled(false);
table->setColumnResizeEnabled(true);
table->setAlternatingRowColors(true);
table->setColumnAlignment(0, AlignmentFlag::Center);
table->setHeaderAlignment(0, AlignmentFlag::Center);
table->setRowHeight(28);
table->setHeaderHeight(28);
table->setColumnWidth(0, 80);
for (int column = 1; column < model->columnCount(); ++column)
    table->setColumnWidth(column, 90);
table->resize(783, 200);

/*
 * Use a delegate for the numeric data which rounds values sensibly.
 */
auto delegate = std::make_shared<WItemDelegate>();
delegate->setTextFormat("%.1f");
table->setItemDelegate(delegate);
table->setItemDelegateForColumn(0, std::make_shared<WItemDelegate>());

/*
 * Creates the scatter plot.
 */
Chart::WCartesianChart *chart =
    container->addWidget(cpp14::make_unique<Chart::WCartesianChart>());
chart->setBackground(WColor(220, 220, 220));
chart->setModel(model);
chart->setXSeriesColumn(0);
chart->setLegendEnabled(true);
chart->setType(Chart::ChartType::Scatter);
chart->axis(Chart::Axis::X).setScale(Chart::AxisScale::Date);

/*
 * Provide ample space for the title, the X and Y axis and the legend.
 */
chart->setPlotAreaPadding(40, Side::Left | Side::Top | Side::Bottom);
chart->setPlotAreaPadding(120, Side::Right);

/*
 * Add the second and the third column as line series.
 */
for (int i = 2; i < 4; ++i) {
    auto s = cpp14::make_unique<Chart::WDataSeries>(i, Chart::SeriesType::Line);
    s->setShadow(WShadow(3, 3, WColor(0, 0, 0, 127), 3));
    chart->addSeries(std::move(s));
}

chart->resize(800, 400);
chart->setMargin(WLength::Auto, Side::Left | Side::Right);

Top

Scatter plot of a function

Below we plot a single sine curve. We use 'curve' data series, which creates a smooth spline curve that interpolates the data points. As is typical when showing mathematical functions, we let the axes cross each other at the origin (0, 0).

Example
source
#include <Wt/Chart/WCartesianChart.h>
#include <Wt/WContainerWidget.h>
#include <Wt/WStandardItemModel.h>
#include <Wt/WTimer.h>

#include <cmath>

using namespace Wt;

auto container = cpp14::make_unique<WContainerWidget>();

auto model =
    std::make_shared<WStandardItemModel>(40, 2);
model->setHeaderData(0, WString("X"));
model->setHeaderData(1, WString("Y = sin(X)"));

for (unsigned i = 0; i < 40; ++i) {
    double x = (static_cast<double>(i) - 20) / 4;

    model->setData(i, 0, x);
    model->setData(i, 1, std::sin(x));
}

/*
 * Create the scatter plot.
 */
Chart::WCartesianChart *chart =
    container->addWidget(cpp14::make_unique<Chart::WCartesianChart>());
chart->setModel(model);        // Set the model.
chart->setXSeriesColumn(0);    // Set the column that holds the X data.
chart->setLegendEnabled(true); // Enable the legend.

chart->setType(Chart::ChartType::Scatter);   // Set type to ScatterPlot.

// Typically, for mathematical functions, you want the axes to cross
// at the 0 mark:
chart->axis(Chart::Axis::X).setLocation(Chart::AxisValue::Zero);
chart->axis(Chart::Axis::Y).setLocation(Chart::AxisValue::Zero);

// Provide space for the X and Y axis and title.
chart->setPlotAreaPadding(120, Side::Right);
chart->setPlotAreaPadding(40, Side::Top | Side::Bottom);

// Add the curves
auto s = cpp14::make_unique<Chart::WDataSeries>(1, Chart::SeriesType::Curve);
s->setShadow(WShadow(3, 3, WColor(0, 0, 0, 127), 3));
chart->addSeries(std::move(s));

chart->resize(800, 300); // WPaintedWidget must be given explicit size.

chart->setMargin(10, Side::Top | Side::Bottom);            // Add margin vertically
chart->setMargin(WLength::Auto, Side::Left | Side::Right); // Center horizontally

Remark

Missing data in a model series Y values is interpreted as a break. For curve-like series, this breaks the curve (or line).

Top

Interactive features

WCartesianChart supports some forms of interaction that do not require a server roundtrip. You can zoom in on the chart below using ctrl+scroll, or with a pinch movement, and pan it with the scrollwheel, click and drag, or touch and drag.

Example
source
#include <Wt/Chart/WAxisSliderWidget.h>
#include <Wt/Chart/WCartesianChart.h>
#include <Wt/Chart/WDataSeries.h>
#include <Wt/WAbstractItemModel.h>
#include <Wt/WAbstractItemView.h>
#include <Wt/WApplication.h>
#include <Wt/WContainerWidget.h>
#include <Wt/WDate.h>
#include <Wt/WEnvironment.h>
#include <Wt/WPaintedWidget.h>
#include <Wt/WItemDelegate.h>
#include <Wt/WShadow.h>
#include <Wt/WStandardItemModel.h>

#include "../treeview-dragdrop/CsvUtil.h"

auto container = cpp14::make_unique<WContainerWidget>();

auto model
    = csvToModel(WApplication::appRoot() + "timeseries.csv");

if (!model)
  return std::move(container);

/*
 * Parses the first column as dates, to be able to use a date scale
 */
for (int row = 0; row < model->rowCount(); ++row) {
  WString s = asString(model->data(row, 0));
  WDate date = WDate::fromString(s, "dd/MM/yy");
  model->setData(row, 0, date);
}

/*
 * Creates the scatter plot.
 */
Chart::WCartesianChart *chart =
    container->addWidget(cpp14::make_unique<Chart::WCartesianChart>());
chart->setBackground(WColor(220, 220, 220));
chart->setModel(model);
chart->setXSeriesColumn(0);
chart->setType(Chart::ChartType::Scatter);
chart->axis(Chart::Axis::X).setScale(Chart::AxisScale::Date);
double min = asNumber(model->data(0, 0));
double max = asNumber(model->data(model->rowCount() - 1, 0));
// Set maximum X zoom level to 16x zoom
chart->axis(Chart::Axis::X).setMinimumZoomRange((max - min) / 16.0);

/*
 * Add the second and the third column as line series.
 */
{
  auto s =
      cpp14::make_unique<Chart::WDataSeries>(2, Chart::SeriesType::Line);
  s->setShadow(WShadow(3, 3, WColor(0, 0, 0, 127), 3));
  chart->addSeries(std::move(s));
}
{
  auto s =
      cpp14::make_unique<Chart::WDataSeries>(3, Chart::SeriesType::Line);
  s->setShadow(WShadow(3, 3, WColor(0, 0, 0, 127), 3));
  chart->addSeries(std::move(s));
}

chart->resize(800, 400);

// Enable pan and zoom
chart->setPanEnabled(true);
chart->setZoomEnabled(true);

chart->setMargin(WLength::Auto, Side::Left | Side::Right); // Center horizontally

Top