Source code of the Charts example

Browse below the source code for Wt's Charts example.

charts
class ChartConfig
ChartConfig.C
ChartConfig.h
class ChartsExample
class CsvUtil
class PanelList
ChartsApplication.C
category.csv
charts.css
charts.xml
timeseries.csv
/*
* Copyright (C) 2008 Emweb bvba, Kessel-Lo, Belgium.
*
* See the LICENSE file for terms of use.
*/

#include "ChartConfig.h"
#include "PanelList.h"

#include <iostream>
#include <boost/lexical_cast.hpp>

#include <Wt/WAbstractItemModel>
#include <Wt/WApplication>
#include <Wt/WCheckBox>
#include <Wt/WComboBox>
#include <Wt/WDoubleValidator>
#include <Wt/WEnvironment>
#include <Wt/WIntValidator>
#include <Wt/WLineEdit>
#include <Wt/WPanel>
#include <Wt/WPushButton>
#include <Wt/WStandardItemModel>
#include <Wt/WTable>
#include <Wt/WText>

#include <Wt/Chart/WCartesianChart>

using namespace Wt;
using namespace Wt::Chart;

namespace {
void addHeader(WTable *t, const char *value) {
t->elementAt(0, t->columnCount())->addWidget(new WText(value));
}

void addEntry(WAbstractItemModel *model, const char *value) {
model->insertRows(model->rowCount(), 1);
model->setData(model->rowCount()-1, 0, boost::any(std::string(value)));
}

bool getDouble(WLineEdit *edit, double& value) {
try {
value = boost::lexical_cast<double>(edit->text().toUTF8());
return true;
} catch (...) {
return false;
}
}

int seriesIndexOf(WCartesianChart* chart, int modelColumn) {
for (unsigned i = 0; i < chart->series().size(); ++i)
if (chart->series()[i].modelColumn() == modelColumn)
return i;

return -1;
}
}

ChartConfig::ChartConfig(WCartesianChart *chart, WContainerWidget *parent)
: WContainerWidget(parent),
chart_(chart),
fill_(MinimumValueFill)
{
chart_->setLegendStyle(chart_->legendFont(), WPen(black),
WBrush(WColor(0xFF, 0xFA, 0xE5)));
chart->initLayout();

PanelList *list = new PanelList(this);

WIntValidator *sizeValidator = new WIntValidator(200, 2000, this);
sizeValidator->setMandatory(true);

WDoubleValidator *anyNumberValidator = new WDoubleValidator(this);
anyNumberValidator->setMandatory(true);

WDoubleValidator *angleValidator = new WDoubleValidator(-90, 90, this);
angleValidator->setMandatory(true);

// ---- Chart properties ----

WStandardItemModel *orientation = new WStandardItemModel(0, 1, this);
addEntry(orientation, "Vertical");
addEntry(orientation, "Horizontal");

WStandardItemModel *legendLocation = new WStandardItemModel(0, 1, this);
addEntry(legendLocation, "Outside");
addEntry(legendLocation, "Inside");

WStandardItemModel *legendSide = new WStandardItemModel(0, 1, this);
addEntry(legendSide, "Top");
addEntry(legendSide, "Right");
addEntry(legendSide, "Bottom");
addEntry(legendSide, "Left");

WStandardItemModel *legendAlignment = new WStandardItemModel(0, 1, this);
addEntry(legendAlignment, "AlignLeft");
addEntry(legendAlignment, "AlignCenter");
addEntry(legendAlignment, "AlignRight");
addEntry(legendAlignment, "AlignTop");
addEntry(legendAlignment, "AlignMiddle");
addEntry(legendAlignment, "AlignBottom");

WTable *chartConfig = new WTable();
chartConfig->setMargin(WLength::Auto, Left | Right);

int row = 0;
chartConfig->elementAt(row, 0)->addWidget(new WText("Title:"));
titleEdit_ = new WLineEdit(chartConfig->elementAt(row, 1));
connectSignals(titleEdit_);
++row;

chartConfig->elementAt(row, 0)->addWidget(new WText("Width:"));
chartWidthEdit_ = new WLineEdit(chartConfig->elementAt(row, 1));
chartWidthEdit_
->setText(boost::lexical_cast<std::string>(chart_->width().value()));
chartWidthEdit_->setValidator(sizeValidator);
chartWidthEdit_->setMaxLength(4);
connectSignals(chartWidthEdit_);
++row;

chartConfig->elementAt(row, 0)->addWidget(new WText("Height:"));
chartHeightEdit_ = new WLineEdit(chartConfig->elementAt(row, 1));
chartHeightEdit_
->setText(boost::lexical_cast<std::string>(chart_->height().value()));
chartHeightEdit_->setValidator(sizeValidator);
chartHeightEdit_->setMaxLength(4);
connectSignals(chartHeightEdit_);
++row;

chartConfig->elementAt(row, 0)->addWidget(new WText("Orientation:"));
chartOrientationEdit_ = new WComboBox(chartConfig->elementAt(row, 1));
chartOrientationEdit_->setModel(orientation);
connectSignals(chartOrientationEdit_);
++row;

chartConfig->elementAt(row, 0)->addWidget(new WText("Legend location:"));
legendLocationEdit_ = new WComboBox(chartConfig->elementAt(row, 1));
legendLocationEdit_->setModel(legendLocation);
connectSignals(legendLocationEdit_);
++row;

chartConfig->elementAt(row, 0)->addWidget(new WText("Legend side:"));
legendSideEdit_ = new WComboBox(chartConfig->elementAt(row, 1));
legendSideEdit_->setModel(legendSide);
legendSideEdit_->setCurrentIndex(1);
connectSignals(legendSideEdit_);
++row;

chartConfig->elementAt(row, 0)->addWidget(new WText("Legend alignment:"));
legendAlignmentEdit_ = new WComboBox(chartConfig->elementAt(row, 1));
legendAlignmentEdit_->setModel(legendAlignment);
legendAlignmentEdit_->setCurrentIndex(4);
connectSignals(legendAlignmentEdit_);
++row;

for (int i = 0; i < chartConfig->rowCount(); ++i) {
chartConfig->elementAt(i, 0)->setStyleClass("tdhead");
chartConfig->elementAt(i, 1)->setStyleClass("tddata");
}

WPanel *p = list->addWidget("Chart properties", chartConfig);
p->setMargin(WLength::Auto, Left | Right);
p->resize(750, WLength::Auto);
p->setMargin(20, Top | Bottom);

if (chart_->isLegendEnabled())
chart_->setPlotAreaPadding(200, Right);

// ---- Series properties ----

WStandardItemModel *types = new WStandardItemModel(0, 1, this);
addEntry(types, "Points");
addEntry(types, "Line");
addEntry(types, "Curve");
addEntry(types, "Bar");
addEntry(types, "Line Area");
addEntry(types, "Curve Area");
addEntry(types, "Stacked Bar");
addEntry(types, "Stacked Line Area");
addEntry(types, "Stacked Curve Area");

WStandardItemModel *markers = new WStandardItemModel(0, 1, this);
addEntry(markers, "None");
addEntry(markers, "Square");
addEntry(markers, "Circle");
addEntry(markers, "Cross");
addEntry(markers, "X cross");
addEntry(markers, "Triangle");

WStandardItemModel *axes = new WStandardItemModel(0, 1, this);
addEntry(axes, "1st Y axis");
addEntry(axes, "2nd Y axis");

WStandardItemModel *labels = new WStandardItemModel(0, 1, this);
addEntry(labels, "None");
addEntry(labels, "X");
addEntry(labels, "Y");
addEntry(labels, "X: Y");

WTable *seriesConfig = new WTable();
seriesConfig->setMargin(WLength::Auto, Left | Right);

::addHeader(seriesConfig, "Name");
::addHeader(seriesConfig, "Enabled");
::addHeader(seriesConfig, "Type");
::addHeader(seriesConfig, "Marker");
::addHeader(seriesConfig, "Y axis");
::addHeader(seriesConfig, "Legend");
::addHeader(seriesConfig, "Shadow");
::addHeader(seriesConfig, "Value labels");

seriesConfig->rowAt(0)->setStyleClass("trhead");

for (int j = 1; j < chart->model()->columnCount(); ++j) {
SeriesControl sc;

new WText(asString(chart->model()->headerData(j)),
seriesConfig->elementAt(j, 0));

sc.enabledEdit = new WCheckBox(seriesConfig->elementAt(j, 1));
connectSignals(sc.enabledEdit);

sc.typeEdit = new WComboBox(seriesConfig->elementAt(j, 2));
sc.typeEdit->setModel(types);
connectSignals(sc.typeEdit);

sc.markerEdit = new WComboBox(seriesConfig->elementAt(j, 3));
sc.markerEdit->setModel(markers);
connectSignals(sc.markerEdit);

sc.axisEdit = new WComboBox(seriesConfig->elementAt(j, 4));
sc.axisEdit->setModel(axes);
connectSignals(sc.axisEdit);

sc.legendEdit = new WCheckBox(seriesConfig->elementAt(j, 5));
connectSignals(sc.legendEdit);

sc.shadowEdit = new WCheckBox(seriesConfig->elementAt(j, 6));
connectSignals(sc.shadowEdit);

sc.labelsEdit = new WComboBox(seriesConfig->elementAt(j, 7));
sc.labelsEdit->setModel(labels);
connectSignals(sc.labelsEdit);

int si = seriesIndexOf(chart, j);

if (si != -1) {
sc.enabledEdit->setChecked();
const WDataSeries& s = chart_->series(j);
switch (s.type()) {
case PointSeries:
sc.typeEdit->setCurrentIndex(0); break;
case LineSeries:
sc.typeEdit->setCurrentIndex(s.fillRange() != NoFill ?
(s.isStacked() ? 7 : 4) : 1); break;
case CurveSeries:
sc.typeEdit->setCurrentIndex(s.fillRange() != NoFill ?
(s.isStacked() ? 8 : 5) : 2); break;
case BarSeries:
sc.typeEdit->setCurrentIndex(s.isStacked() ? 6 : 3);
}

sc.markerEdit->setCurrentIndex((int)s.marker());
sc.legendEdit->setChecked(s.isLegendEnabled());
sc.shadowEdit->setChecked(s.shadow() != WShadow());
}

seriesControls_.push_back(sc);

seriesConfig->rowAt(j)->setStyleClass("trdata");
}

p = list->addWidget("Series properties", seriesConfig);
p->expand();
p->setMargin(WLength::Auto, Left | Right);
p->resize(750, WLength::Auto);
p->setMargin(20, Top | Bottom);

// ---- Axis properties ----

WStandardItemModel *yScales = new WStandardItemModel(0, 1, this);
addEntry(yScales, "Linear scale");
addEntry(yScales, "Log scale");

WStandardItemModel *xScales = new WStandardItemModel(0, 1, this);
addEntry(xScales, "Categories");
addEntry(xScales, "Linear scale");
addEntry(xScales, "Log scale");
addEntry(xScales, "Date scale");

WTable *axisConfig = new WTable();
axisConfig->setMargin(WLength::Auto, Left | Right);

::addHeader(axisConfig, "Axis");
::addHeader(axisConfig, "Visible");
::addHeader(axisConfig, "Scale");
::addHeader(axisConfig, "Automatic");
::addHeader(axisConfig, "Minimum");
::addHeader(axisConfig, "Maximum");
::addHeader(axisConfig, "Gridlines");
::addHeader(axisConfig, "Label angle");

axisConfig->rowAt(0)->setStyleClass("trhead");

for (int i = 0; i < 3; ++i) {
const char *axisName[] = { "X axis", "1st Y axis", "2nd Y axis" };
int j = i + 1;

const WAxis& axis = chart_->axis(static_cast<Axis>(i));
AxisControl sc;

new WText(WString(axisName[i], UTF8), axisConfig->elementAt(j, 0));

sc.visibleEdit = new WCheckBox(axisConfig->elementAt(j, 1));
sc.visibleEdit->setChecked(axis.isVisible());
connectSignals(sc.visibleEdit);

sc.scaleEdit = new WComboBox(axisConfig->elementAt(j, 2));
if (axis.scale() == CategoryScale)
sc.scaleEdit->addItem("Category scale");
else {
if (axis.id() == XAxis) {
sc.scaleEdit->setModel(xScales);
sc.scaleEdit->setCurrentIndex(axis.scale());
} else {
sc.scaleEdit->setModel(yScales);
sc.scaleEdit->setCurrentIndex(axis.scale() - 1);
}
}
connectSignals(sc.scaleEdit);

bool autoValues = axis.autoLimits() == (MinimumValue | MaximumValue);

sc.minimumEdit = new WLineEdit(axisConfig->elementAt(j, 4));
sc.minimumEdit->setText(boost::lexical_cast<std::string>(axis.minimum()));
sc.minimumEdit->setValidator(anyNumberValidator);
sc.minimumEdit->setEnabled(!autoValues);
connectSignals(sc.minimumEdit);

sc.maximumEdit = new WLineEdit(axisConfig->elementAt(j, 5));
sc.maximumEdit->setText(boost::lexical_cast<std::string>(axis.maximum()));
sc.maximumEdit->setValidator(anyNumberValidator);
sc.maximumEdit->setEnabled(!autoValues);
connectSignals(sc.maximumEdit);

sc.autoEdit = new WCheckBox(axisConfig->elementAt(j, 3));
sc.autoEdit->setChecked(autoValues);
connectSignals(sc.autoEdit);
sc.autoEdit->checked().connect(sc.maximumEdit, &WLineEdit::disable);
sc.autoEdit->unChecked().connect(sc.maximumEdit, &WLineEdit::enable);
sc.autoEdit->checked().connect(sc.minimumEdit, &WLineEdit::disable);
sc.autoEdit->unChecked().connect(sc.minimumEdit, &WLineEdit::enable);

sc.gridLinesEdit = new WCheckBox(axisConfig->elementAt(j, 6));
connectSignals(sc.gridLinesEdit);

sc.labelAngleEdit = new WLineEdit(axisConfig->elementAt(j, 7));
sc.labelAngleEdit->setText("0");
sc.labelAngleEdit->setValidator(angleValidator);
connectSignals(sc.labelAngleEdit);

axisConfig->rowAt(j)->setStyleClass("trdata");

axisControls_.push_back(sc);
}

p = list->addWidget("Axis properties", axisConfig);
p->setMargin(WLength::Auto, Left | Right);
p->resize(750, WLength::Auto);
p->setMargin(20, Top | Bottom);

/*
* If we do not have JavaScript, then add a button to reflect changes to
* the chart.
*/
if (!WApplication::instance()->environment().javaScript()) {
WPushButton *b = new WPushButton(this);
b->setText("Update chart");
b->setInline(false); // so we can add margin to center horizontally
b->setMargin(WLength::Auto, Left | Right);
b->clicked().connect(this, &ChartConfig::update);
}
}

void ChartConfig::setValueFill(FillRangeType fill)
{
fill_ = fill;
}

void ChartConfig::update()
{
bool haveLegend = false;
std::vector<WDataSeries> series;

for (int i = 1; i < chart_->model()->columnCount(); ++i) {
SeriesControl& sc = seriesControls_[i-1];

if (sc.enabledEdit->isChecked()) {
WDataSeries s(i);

switch (sc.typeEdit->currentIndex()) {
case 0:
s.setType(PointSeries);
if (sc.markerEdit->currentIndex() == 0)
sc.markerEdit->setCurrentIndex(1);
break;
case 1:
s.setType(LineSeries);
sc.markerEdit->setCurrentIndex(0);
break;
case 2:
s.setType(CurveSeries);
sc.markerEdit->setCurrentIndex(0);
break;
case 3:
s.setType(BarSeries);
sc.markerEdit->setCurrentIndex(0);
break;
case 4:
s.setType(LineSeries);
s.setFillRange(fill_);
sc.markerEdit->setCurrentIndex(0);
break;
case 5:
s.setType(CurveSeries);
s.setFillRange(fill_);
sc.markerEdit->setCurrentIndex(0);
break;
case 6:
s.setType(BarSeries);
s.setStacked(true);
sc.markerEdit->setCurrentIndex(0);
break;
case 7:
s.setType(LineSeries);
s.setFillRange(fill_);
s.setStacked(true);
sc.markerEdit->setCurrentIndex(0);
break;
case 8:
s.setType(CurveSeries);
s.setFillRange(fill_);
s.setStacked(true);
sc.markerEdit->setCurrentIndex(0);
}

s.setMarker(static_cast<MarkerType>(sc.markerEdit->currentIndex()));

if (sc.axisEdit->currentIndex() == 1) {
s.bindToAxis(Y2Axis);
}

if (sc.legendEdit->isChecked()) {
s.setLegendEnabled(true);
haveLegend = true;
} else
s.setLegendEnabled(false);

if (sc.shadowEdit->isChecked()) {
s.setShadow(WShadow(3, 3, WColor(0, 0, 0, 127), 3));
} else
s.setShadow(WShadow());

switch (sc.labelsEdit->currentIndex()) {
case 1:
s.setLabelsEnabled(XAxis);
break;
case 2:
s.setLabelsEnabled(YAxis);
break;
case 3:
s.setLabelsEnabled(XAxis);
s.setLabelsEnabled(YAxis);
break;
}

series.push_back(s);
}
}

chart_->setSeries(series);

for (int i = 0; i < 3; ++i) {
AxisControl& sc = axisControls_[i];
WAxis& axis = chart_->axis(static_cast<Axis>(i));

axis.setVisible(sc.visibleEdit->isChecked());

if (sc.scaleEdit->count() != 1) {
int k = sc.scaleEdit->currentIndex();
if (axis.id() != XAxis)
k += 1;
else {
if (k == 0)
chart_->setType(CategoryChart);
else
chart_->setType(ScatterPlot);
}

switch (k) {
case 1:
axis.setScale(LinearScale); break;
case 2:
axis.setScale(LogScale); break;
case 3:
axis.setScale(DateScale); break;
}
}

if (sc.autoEdit->isChecked())
axis.setAutoLimits(MinimumValue | MaximumValue);
else {
if (validate(sc.minimumEdit) && validate(sc.maximumEdit)) {
double min, max;
getDouble(sc.minimumEdit, min);
getDouble(sc.maximumEdit, max);

if (axis.scale() == LogScale)
if (min <= 0)
min = 0.0001;

axis.setRange(min, max);
}

}

if (validate(sc.labelAngleEdit)) {
double angle;
getDouble(sc.labelAngleEdit, angle);
axis.setLabelAngle(angle);
}

axis.setGridLinesEnabled(sc.gridLinesEdit->isChecked());
}

chart_->setTitle(titleEdit_->text());

if (validate(chartWidthEdit_) && validate(chartHeightEdit_)) {
double width, height;
getDouble(chartWidthEdit_, width);
getDouble(chartHeightEdit_, height);
chart_->resize(width, height);
}

switch (chartOrientationEdit_->currentIndex()) {
case 0:
chart_->setOrientation(Vertical); break;
case 1:
chart_->setOrientation(Horizontal); break;
}

chart_->setLegendEnabled(haveLegend);

if (haveLegend) {
LegendLocation location = LegendOutside;
Side side = Right;
AlignmentFlag alignment = AlignMiddle;

switch (legendLocationEdit_->currentIndex()) {
case 0: location = LegendOutside; break;
case 1: location = LegendInside; break;
}

switch (legendSideEdit_->currentIndex()) {
case 0: side = Top; break;
case 1: side = Right; break;
case 2: side = Bottom; break;
case 3: side = Left; break;
}

if (side == Left || side == Right) {
if (legendAlignmentEdit_->currentIndex() < 3)
legendAlignmentEdit_->setCurrentIndex(4);
} else {
if (legendAlignmentEdit_->currentIndex() >= 3)
legendAlignmentEdit_->setCurrentIndex(2);
}

switch (legendAlignmentEdit_->currentIndex()) {
case 0: alignment = AlignLeft; break;
case 1: alignment = AlignCenter; break;
case 2: alignment = AlignRight; break;
case 3: alignment = AlignTop; break;
case 4: alignment = AlignMiddle; break;
case 5: alignment = AlignBottom; break;
}

chart_->setLegendLocation(location, side, alignment);

chart_->setLegendColumns((side == Top || side == Bottom ) ? 2 : 1,
WLength(100));
}

for (unsigned i = 0; i < 4; ++i) {
Side sides[] = { Top, Right, Bottom, Left };

bool legendRoom =
haveLegend
&& chart_->legendLocation() == LegendOutside
&& chart_->legendSide() == sides[i];

int padding;

if (i % 2 == 0)
padding = legendRoom ? 80 : 40;
else
padding = legendRoom ? 200 : 80;

chart_->setPlotAreaPadding(padding, sides[i]);
}
}

bool ChartConfig::validate(WFormWidget *w)
{
bool valid = w->validate() == WValidator::Valid;

if (!WApplication::instance()->environment().javaScript()) {
w->setStyleClass(valid ? "" : "Wt-invalid");
w->setToolTip(valid ? "" : "Invalid value");
}

return valid;
}

void ChartConfig::connectSignals(WFormWidget *w)
{
w->changed().connect(this, &ChartConfig::update);
if (dynamic_cast<WLineEdit *>(w))
w->enterPressed().connect(this, &ChartConfig::update);
}