Graphics & Charts — generic and specialized painting infrastructure

Axis slider widget

If you have a chart with a long range of X values, and want to easily focus on a particular range, you can use a Chart::WAxisSliderWidget. You can change the size of the focused region by dragging the blue handles, and change the position by dragging the selected area. When using touch, the size of this area can also be changed using a pinch movement.

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>();

std::shared_ptr<WStandardItemModel> 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->addNew<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);
auto s_ = s.get();
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

// Add a WAxisSliderWidget for the chart using the data series for column 2
auto sliderWidget = container->addWidget(cpp14::make_unique<Chart::WAxisSliderWidget>(s_));
sliderWidget->resize(800, 80);
sliderWidget->setSelectionAreaPadding(40, Side::Left | Side::Right);
sliderWidget->setMargin(WLength::Auto, Side::Left | Side::Right); // Center horizontally

Using an alternative WDataSeries

If a series is assigned to a Chart::WAxisSliderWidget, but is hidden on the chart, then it will still be drawn on the WAxisSliderWidget. Combined with the zoomRangeChanged method of Chart::WAxis, this can be used to have a rough version of the data to present in the WAxisSliderWidget, and as the user zooms in, present a more detailed version of the data.

The example below shows a rough version of the data in the slider, and as you zoom in, a more fine version will be loaded on demand of the zoomed in area.

Example
source
#include <Wt/Chart/WAbstractChartModel.h>
#include <Wt/Chart/WAxisSliderWidget.h>
#include <Wt/Chart/WCartesianChart.h>
#include <Wt/Chart/WDataSeries.h>
#include <Wt/WApplication.h>
#include <Wt/WContainerWidget.h>
#include <Wt/WObject.h>
#include <Wt/WShadow.h>

#include <cmath>

#define M_PI 3.14159265358979323846

using namespace Wt;

class SinModel : public Chart::WAbstractChartModel {
public:
  SinModel(double minimum, double maximum, int rows)
    : Chart::WAbstractChartModel(),
      minimum_(minimum),
      maximum_(maximum),
      rows_(rows)
  { }

  virtual double data(int row, int column) const
  {
    double x = minimum_ + row * (maximum_ - minimum_) / (rowCount() - 1);
    if (column == 0) {
      return x;
    } else {
      return std::sin(x) + std::sin(x * 100.0) / 40.0;
    }
  }

  virtual int columnCount() const
  {
    return 2;
  }

  virtual int rowCount() const
  {
    return rows_;
  }

  double minimum() const { return minimum_; }
  double maximum() const { return maximum_; }

private:
  double minimum_;
  double maximum_;
  int rows_;
};


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

auto chart =
    container->addNew<Chart::WCartesianChart>();
chart->setBackground(WColor(220, 220, 220));
chart->setType(Chart::ChartType::Scatter);

/*
 * Create a rough model to use for the WAxisSliderWidget
 */
auto roughModel = std::make_shared<SinModel>(-M_PI, M_PI, 100);
auto roughSeries =
    cpp14::make_unique<Chart::WDataSeries>(1, Chart::SeriesType::Line);
auto roughSeries_ = roughSeries.get();
roughSeries_->setModel(roughModel);
roughSeries_->setXSeriesColumn(0);
// Add the rough series to the chart, but hide it!
roughSeries->setHidden(true);
chart->addSeries(std::move(roughSeries));

/*
 * Create a detailed model
 */
auto detailedModel = std::make_shared<SinModel>(-M_PI, M_PI, 10000);
auto seriesPtr =
    cpp14::make_unique<Chart::WDataSeries>(1, Chart::SeriesType::Line);
auto series = seriesPtr.get();
series->setModel(detailedModel);
series->setXSeriesColumn(0);
series->setShadow(WShadow(3, 3, WColor(0, 0, 0, 127), 3));
chart->addSeries(std::move(seriesPtr));


chart->axis(Chart::Axis::X).setMaximumZoomRange(M_PI);
chart->axis(Chart::Axis::X).setMinimumZoomRange(M_PI / 16.0);
chart->axis(Chart::Axis::X).setMinimum(-3.5);
chart->axis(Chart::Axis::X).setMaximum(3.5);

chart->axis(Chart::Axis::Y).setMinimumZoomRange(0.1);
chart->axis(Chart::Axis::Y).setMinimum(-1.5);
chart->axis(Chart::Axis::Y).setMaximum(1.5);

chart->resize(800, 400);

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

// Enable on-demand loading
chart->setOnDemandLoadingEnabled(true);

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

// Add a WAxisSliderWidget for the chart using the data series for column 2
auto sliderWidget =
    container->addNew<Chart::WAxisSliderWidget>(roughSeries_);
sliderWidget->resize(800, 80);
sliderWidget->setSelectionAreaPadding(40, Side::Left | Side::Right);
sliderWidget->setMargin(WLength::Auto, Side::Left | Side::Right); // Center horizontally