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 bv, Herent, Belgium.
                      *
                      * See the LICENSE file for terms of use.
                      */

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

                      #include <iostream>

                      #include <Wt/WAbstractItemModel.h>
                      #include <Wt/WApplication.h>
                      #include <Wt/WCheckBox.h>
                      #include <Wt/WComboBox.h>
                      #include <Wt/WDoubleValidator.h>
                      #include <Wt/WDate.h>
                      #include <Wt/WEnvironment.h>
                      #include <Wt/WIntValidator.h>
                      #include <Wt/WLineEdit.h>
                      #include <Wt/WLocale.h>
                      #include <Wt/WPanel.h>
                      #include <Wt/WPushButton.h>
                      #include <Wt/WStandardItemModel.h>
                      #include <Wt/WTable.h>
                      #include <Wt/WText.h>
                      #include <Wt/WPainterPath.h>

                      #include <Wt/Chart/WCartesianChart.h>

                      using namespace Wt;
                      using namespace Wt::Chart;

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

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

                      void addEntry(const std::shared_ptr<WAbstractItemModel>& model, const WString &value) {
                      model->insertRows(model->rowCount(), 1);
                      model->setData(model->rowCount()-1, 0, cpp17::any(value));
                      }

                      bool getDouble(WLineEdit *edit, double& value) {
                      try {
                      value = WLocale::currentLocale().toDouble(edit->text());
                      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;
                      }

                      WString axisName(Axis axis, int axisId)
                      {
                      if (axis == Axis::X) {
                      return Wt::utf8("X Axis {1}").arg(axisId + 1);
                      } else {
                      return Wt::utf8("Y axis {1}").arg(axisId + 1);
                      }
                      }
                      }

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

                      PanelList *list = this->addWidget(std::make_unique<PanelList>());

                      std::shared_ptr<WIntValidator> sizeValidator
                      = std::make_shared<WIntValidator>(200,2000);
                      sizeValidator->setMandatory(true);

                      anyNumberValidator_ = std::make_shared<WDoubleValidator>();
                      anyNumberValidator_->setMandatory(true);

                      angleValidator_ = std::make_shared<WDoubleValidator>(-90, 90);
                      angleValidator_->setMandatory(true);

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

                      std::shared_ptr<WStandardItemModel> orientation
                      = std::make_shared<WStandardItemModel>(0,1);
                      addEntry(orientation, "Vertical");
                      addEntry(orientation, "Horizontal");

                      std::shared_ptr<WStandardItemModel> legendLocation
                      = std::make_shared<WStandardItemModel>(0,1);
                      addEntry(legendLocation, "Outside");
                      addEntry(legendLocation, "Inside");

                      std::shared_ptr<WStandardItemModel> legendSide
                      = std::make_shared<WStandardItemModel>(0,1);
                      addEntry(legendSide, "Top");
                      addEntry(legendSide, "Right");
                      addEntry(legendSide, "Bottom");
                      addEntry(legendSide, "Left");

                      std::shared_ptr<WStandardItemModel> legendAlignment
                      = std::make_shared<WStandardItemModel>(0,1);
                      addEntry(legendAlignment, "AlignLeft");
                      addEntry(legendAlignment, "AlignCenter");
                      addEntry(legendAlignment, "AlignRight");
                      addEntry(legendAlignment, "AlignTop");
                      addEntry(legendAlignment, "AlignMiddle");
                      addEntry(legendAlignment, "AlignBottom");

                      std::unique_ptr<WTable> chartConfig
                      = std::make_unique<WTable>();
                      chartConfig->setMargin(WLength::Auto, Side::Left | Side::Right);

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

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

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

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

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

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

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

                      chartConfig->elementAt(row, 0)->addWidget(std::make_unique<WText>("Border:"));
                      borderEdit_ = chartConfig->elementAt(row,1)->addWidget(std::make_unique<WCheckBox>());
                      borderEdit_->setChecked(false);
                      connectSignals(borderEdit_);
                      ++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", std::move(chartConfig));
                      p->setMargin(WLength::Auto, Side::Left | Side::Right);
                      p->resize(1160, WLength::Auto);
                      p->setMargin(20, Side::Top | Side::Bottom);

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

                      std::shared_ptr<WStandardItemModel> types
                      = std::make_shared<WStandardItemModel>(0,1);
                      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");

                      std::shared_ptr<WStandardItemModel> markers
                      = std::make_shared<WStandardItemModel>(0,1);
                      addEntry(markers, "None");
                      addEntry(markers, "Square");
                      addEntry(markers, "Circle");
                      addEntry(markers, "Cross");
                      addEntry(markers, "X cross");
                      addEntry(markers, "Triangle");
                      addEntry(markers, "Pipe");
                      addEntry(markers, "Star");
                      addEntry(markers, "Inverted triangle");
                      addEntry(markers, "Asterisk");
                      addEntry(markers, "Diamond");

                      xAxesModel_ = std::make_shared<WStandardItemModel>(0, 1);
                      addEntry(xAxesModel_, axisName(Axis::X, 0));

                      yAxesModel_ = std::make_shared<WStandardItemModel>(0, 1);
                      addEntry(yAxesModel_, axisName(Axis::Y, 0));
                      addEntry(yAxesModel_, axisName(Axis::Y, 1));

                      std::shared_ptr<WStandardItemModel> labels
                      = std::make_shared<WStandardItemModel>(0,1);
                      addEntry(labels, "None");
                      addEntry(labels, "X");
                      addEntry(labels, "Y");
                      addEntry(labels, "X: Y");

                      std::unique_ptr<WTable> seriesConfig
                      = std::make_unique<WTable>();
                      WTable *seriesConfigPtr = seriesConfig.get();
                      seriesConfig->setMargin(WLength::Auto, Side::Left | Side::Right);
                      ::addHeader(seriesConfigPtr, "Name");
                      ::addHeader(seriesConfigPtr, "Enabled");
                      ::addHeader(seriesConfigPtr, "Type");
                      ::addHeader(seriesConfigPtr, "Marker");
                      ::addHeader(seriesConfigPtr, "X axis");
                      ::addHeader(seriesConfigPtr, "Y axis");
                      ::addHeader(seriesConfigPtr, "Legend");
                      ::addHeader(seriesConfigPtr, "Shadow");
                      ::addHeader(seriesConfigPtr, "Value labels");

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

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

                      seriesConfig->elementAt(j,0)->addWidget(std::make_unique<WText>(chart->model()->headerData(j)));

                      sc.enabledEdit = seriesConfig->elementAt(j,1)->addWidget(std::make_unique<WCheckBox>());
                      connectSignals(sc.enabledEdit);

                      sc.typeEdit = seriesConfig->elementAt(j,2)->addWidget(std::make_unique<WComboBox>());
                      sc.typeEdit->setModel(types);
                      sc.typeEdit->setCurrentIndex(0);
                      connectSignals(sc.typeEdit);

                      sc.markerEdit = seriesConfig->elementAt(j,3)->addWidget(std::make_unique<WComboBox>());
                      sc.markerEdit->setModel(markers);
                      sc.markerEdit->setCurrentIndex(0);
                      connectSignals(sc.markerEdit);

                      sc.xAxisEdit = seriesConfig->elementAt(j, 4)->addNew<WComboBox>();
                      sc.xAxisEdit->setModel(xAxesModel_);
                      sc.xAxisEdit->setCurrentIndex(0);
                      connectSignals(sc.xAxisEdit);

                      sc.yAxisEdit = seriesConfig->elementAt(j, 5)->addNew<WComboBox>();
                      sc.yAxisEdit->setModel(yAxesModel_);
                      sc.yAxisEdit->setCurrentIndex(0);
                      connectSignals(sc.yAxisEdit);

                      sc.legendEdit = seriesConfig->elementAt(j, 6)->addWidget(std::make_unique<WCheckBox>());
                      connectSignals(sc.legendEdit);

                      sc.shadowEdit = seriesConfig->elementAt(j, 7)->addWidget(std::make_unique<WCheckBox>());
                      connectSignals(sc.shadowEdit);

                      sc.labelsEdit = seriesConfig->elementAt(j, 8)->addWidget(std::make_unique<WComboBox>());
                      sc.labelsEdit->setModel(labels);
                      sc.labelsEdit->setCurrentIndex(0);
                      connectSignals(sc.labelsEdit);

                      int si = seriesIndexOf(chart, j);

                      if (si != -1) {
                      sc.enabledEdit->setChecked();
                      const WDataSeries& s = chart_->series(j);
                      switch (s.type()) {
                      case SeriesType::Point:
                      sc.typeEdit->setCurrentIndex(0); break;
                      case SeriesType::Line:
                      sc.typeEdit->setCurrentIndex(s.fillRange() != FillRangeType::None ?
                      (s.isStacked() ? 7 : 4) : 1); break;
                      case SeriesType::Curve:
                      sc.typeEdit->setCurrentIndex(s.fillRange() != FillRangeType::None ?
                      (s.isStacked() ? 8 : 5) : 2); break;
                      case SeriesType::Bar:
                      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", std::move(seriesConfig));
                      p->expand();
                      p->setMargin(WLength::Auto, Side::Left | Side::Right);
                      p->resize(1160, WLength::Auto);
                      p->setMargin(20, Side::Top | Side::Bottom);

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

                      yScales_ = std::make_shared<WStandardItemModel>(0, 1);
                      addEntry(yScales_, "Linear scale");
                      addEntry(yScales_, "Log scale");

                      xScales_ = std::make_shared<WStandardItemModel>(0, 1);
                      addEntry(xScales_, "Categories");
                      addEntry(xScales_, "Linear scale");
                      addEntry(xScales_, "Log scale");
                      addEntry(xScales_, "Date scale");

                      auto axisConfig = std::make_unique<WContainerWidget>();
                      axisConfig_ = axisConfig->addNew<WTable>();
                      axisConfig_->setMargin(WLength::Auto, Side::Left | Side::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");
                      ::addHeader(axisConfig_, "Title");
                      ::addHeader(axisConfig_, "Title orientation");
                      ::addHeader(axisConfig_, "Tick direction");
                      ::addHeader(axisConfig_, "Location");

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

                      addAxis(Axis::X, 0);
                      addAxis(Axis::Y, 0);
                      addAxis(Axis::Y, 1);

                      WPushButton *addXAxisBtn =
                      axisConfig->addNew<WPushButton>(Wt::utf8("Add X axis"));
                      addXAxisBtn->clicked().connect(this, &ChartConfig::addXAxis);
                      WPushButton *clearXAxesBtn =
                      axisConfig->addNew<WPushButton>(Wt::utf8("Clear X axes"));
                      clearXAxesBtn->clicked().connect(this, &ChartConfig::clearXAxes);
                      WPushButton *addYAxisBtn =
                      axisConfig->addNew<WPushButton>(utf8("Add Y axis"));
                      addYAxisBtn->clicked().connect(this, &ChartConfig::addYAxis);
                      WPushButton *clearYAxesBtn =
                      axisConfig->addNew<WPushButton>(utf8("Clear Y axes"));
                      clearYAxesBtn->clicked().connect(this, &ChartConfig::clearYAxes);

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

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

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

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

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

                      if (sc.enabledEdit->isChecked()) {
                      std::unique_ptr<WDataSeries> s
                      = std::make_unique<WDataSeries>(i);

                      switch (sc.typeEdit->currentIndex()) {
                      case 0:
                      s->setType(SeriesType::Point);
                      if (sc.markerEdit->currentIndex() == 0)
                      sc.markerEdit->setCurrentIndex(1);
                      break;
                      case 1:
                      s->setType(SeriesType::Line);
                      break;
                      case 2:
                      s->setType(SeriesType::Curve);
                      break;
                      case 3:
                      s->setType(SeriesType::Bar);
                      break;
                      case 4:
                      s->setType(SeriesType::Line);
                      s->setFillRange(fill_);
                      break;
                      case 5:
                      s->setType(SeriesType::Curve);
                      s->setFillRange(fill_);
                      break;
                      case 6:
                      s->setType(SeriesType::Bar);
                      s->setStacked(true);
                      break;
                      case 7:
                      s->setType(SeriesType::Line);
                      s->setFillRange(fill_);
                      s->setStacked(true);
                      break;
                      case 8:
                      s->setType(SeriesType::Curve);
                      s->setFillRange(fill_);
                      s->setStacked(true);
                      }

                      //set WPainterPath to draw a pipe
                      if(sc.markerEdit->currentIndex() == static_cast<int>(MarkerType::Custom)){ //was customMarker before
                      WPainterPath pp = WPainterPath();
                      pp.moveTo(0, -6);
                      pp.lineTo(0, 6);
                      s->setCustomMarker(pp);
                      }

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

                      s->bindToXAxis(sc.xAxisEdit->currentIndex());
                      s->bindToYAxis(sc.yAxisEdit->currentIndex());

                      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(Axis::X);
                      break;
                      case 2:
                      s->setLabelsEnabled(Axis::Y);
                      break;
                      case 3:
                      s->setLabelsEnabled(Axis::X);
                      s->setLabelsEnabled(Axis::Y);
                      break;
                      }

                      series.push_back(std::move(s));
                      }
                      }

                      chart_->setSeries(std::move(series));

                      for (std::size_t i = 0; i < axisControls_.size(); ++i) {
                      AxisControl& sc = axisControls_[i];
                      WAxis& axis = static_cast<int>(i) < chart_->xAxisCount() ? chart_->xAxis(i) : chart_->yAxis(i - chart_->xAxisCount());

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

                      if (sc.scaleEdit->count() != 1) {
                      int k = sc.scaleEdit->currentIndex();
                      if (axis.id() != Axis::X)
                      k += 1;
                      else {
                      if (k == 0)
                      chart_->setType(ChartType::Category);
                      else
                      chart_->setType(ChartType::Scatter);
                      }

                      switch (k) {
                      case 1:
                      axis.setScale(AxisScale::Linear); break;
                      case 2:
                      axis.setScale(AxisScale::Log); break;
                      case 3:
                      axis.setScale(AxisScale::Date); break;
                      }
                      }

                      if (sc.autoEdit->isChecked())
                      axis.setAutoLimits(AxisValue::Minimum | AxisValue::Maximum);
                      else {
                      if (!(axis.autoLimits() & (AxisValue::Minimum | AxisValue::Maximum)).empty()) {
                      sc.minimumEdit->setText(WLocale::currentLocale()
                      .toString(axis.minimum()));
                      sc.maximumEdit->setText(WLocale::currentLocale()
                      .toString(axis.maximum()));
                      }
                      if (validate(sc.minimumEdit) && validate(sc.maximumEdit)) {
                      double min, max;
                      getDouble(sc.minimumEdit, min);
                      getDouble(sc.maximumEdit, max);

                      if (axis.scale() == AxisScale::Log)
                      if (min <= 0)
                      min = 0.0001;

                      if (axis.scale() == AxisScale::Date){
                      //the number of julian days until year 1986
                      WDate dMin = WDate(1900,1,1);
                      double gregDaysMin = (double)dMin.toJulianDay();
                      //the number of julian days until year 1988
                      WDate dMax = WDate(3000,1,1);
                      double gregDaysMax = (double)dMax.toJulianDay();

                      bool greg_year_validation =
                      (min > gregDaysMin &&
                      min < gregDaysMax &&
                      max > gregDaysMin &&
                      max < gregDaysMax);

                      if(!greg_year_validation){
                      min = gregDaysMin;
                      max = gregDaysMax;
                      }
                      }

                      axis.setRange(min, max);
                      }

                      }

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

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

                      axis.setTitle(sc.titleEdit->text());

                      axis.setTitleOrientation(sc.titleOrientationEdit->currentIndex() == 0 ? Orientation::Horizontal : Orientation::Vertical);

                      axis.setTickDirection(sc.tickDirectionEdit->currentIndex() == 0 ? TickDirection::Outwards : TickDirection::Inwards);

                      switch (sc.locationEdit->currentIndex()) {
                      case 0:
                      axis.setLocation(AxisValue::Minimum);
                      break;
                      case 1:
                      axis.setLocation(AxisValue::Maximum);
                      break;
                      case 2:
                      axis.setLocation(AxisValue::Zero);
                      break;
                      case 3:
                      axis.setLocation(AxisValue::Both);
                      break;
                      }
                      }

                      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(Orientation::Vertical); break;
                      case 1:
                      chart_->setOrientation(Orientation::Horizontal); break;
                      }

                      chart_->setLegendEnabled(haveLegend);

                      if (haveLegend) {
                      LegendLocation location = LegendLocation::Outside;
                      Side side = Side::Right;
                      AlignmentFlag alignment = AlignmentFlag::Middle;
                      switch (legendLocationEdit_->currentIndex()) {
                      case 0: location = LegendLocation::Outside; break;
                      case 1: location = LegendLocation::Inside; break;
                      }

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

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

                      switch (legendAlignmentEdit_->currentIndex()) {
                      case 0: alignment = AlignmentFlag::Left; break;
                      case 1: alignment = AlignmentFlag::Center; break;
                      case 2: alignment = AlignmentFlag::Right; break;
                      case 3: alignment = AlignmentFlag::Top; break;
                      case 4: alignment = AlignmentFlag::Middle; break;
                      case 5: alignment = AlignmentFlag::Bottom; break;
                      }

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

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

                      if (borderEdit_->isChecked()) {
                      chart_->setBorderPen(WPen());
                      } else {
                      chart_->setBorderPen(PenStyle::None);
                      }
                      }

                      bool ChartConfig::validate(WFormWidget *w)
                      {
                      bool valid = w->validate() == ValidationState::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);
                      }

                      void ChartConfig::addXAxis()
                      {
                      int xAxis = chart_->addXAxis(std::make_unique<WAxis>());
                      addAxis(Axis::X, xAxis);
                      addEntry(xAxesModel_, axisName(Axis::X, xAxis));
                      if (xAxis == 0)
                      update();
                      }

                      void ChartConfig::addYAxis()
                      {
                      int yAxis = chart_->addYAxis(std::make_unique<WAxis>());
                      addAxis(Axis::Y, yAxis);
                      addEntry(yAxesModel_, axisName(Axis::Y, yAxis));
                      if (yAxis == 0)
                      update();
                      }

                      void ChartConfig::addAxis(Axis ax, int axisId)
                      {
                      int j = ax == Axis::X ? 1 + axisId : 1 + chart_->xAxisCount() + axisId;

                      const WAxis& axis = ax == Axis::X ? chart_->xAxis(axisId) : chart_->yAxis(axisId);
                      AxisControl sc;

                      axisConfig_->insertRow(j);
                      axisConfig_->elementAt(j, 0)->addNew<WText>(axisName(axis.id(), axis.yAxisId()));

                      sc.visibleEdit = axisConfig_->elementAt(j, 1)->addNew<WCheckBox>();
                      sc.visibleEdit->setChecked(axis.isVisible());
                      connectSignals(sc.visibleEdit);

                      sc.scaleEdit = axisConfig_->elementAt(j, 2)->addNew<WComboBox>();
                      if (axis.scale() == AxisScale::Discrete)
                      sc.scaleEdit->addItem("Discrete scale");
                      else {
                      if (axis.id() == Axis::X) {
                      sc.scaleEdit->setModel(xScales_);
                      sc.scaleEdit->setCurrentIndex(static_cast<int>(axis.scale()));
                      } else {
                      sc.scaleEdit->setModel(yScales_);
                      sc.scaleEdit->setCurrentIndex(static_cast<int>(axis.scale()) - 1);
                      }
                      }
                      connectSignals(sc.scaleEdit);

                      bool autoValues = axis.autoLimits() == (AxisValue::Minimum | AxisValue::Maximum);

                      sc.minimumEdit = axisConfig_->elementAt(j, 4)->addNew<WLineEdit>();
                      sc.minimumEdit->setText(WLocale::currentLocale()
                      .toString(axis.minimum()));
                      sc.minimumEdit->setValidator(anyNumberValidator_);
                      sc.minimumEdit->setEnabled(!autoValues);
                      connectSignals(sc.minimumEdit);

                      sc.maximumEdit = axisConfig_->elementAt(j, 5)->addNew<WLineEdit>();
                      sc.maximumEdit->setText(WLocale::currentLocale()
                      .toString(axis.maximum()));
                      sc.maximumEdit->setValidator(anyNumberValidator_);
                      sc.maximumEdit->setEnabled(!autoValues);
                      connectSignals(sc.maximumEdit);

                      sc.autoEdit = axisConfig_->elementAt(j, 3)->addNew<WCheckBox>();
                      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 = axisConfig_->elementAt(j, 6)->addNew<WCheckBox>();
                      connectSignals(sc.gridLinesEdit);

                      sc.labelAngleEdit = axisConfig_->elementAt(j, 7)->addNew<WLineEdit>();
                      sc.labelAngleEdit->setText("0");
                      sc.labelAngleEdit->setValidator(angleValidator_);
                      connectSignals(sc.labelAngleEdit);

                      sc.titleEdit = axisConfig_->elementAt(j, 8)->addNew<WLineEdit>();
                      sc.titleEdit->setText("");
                      connectSignals(sc.titleEdit);

                      sc.titleOrientationEdit = axisConfig_->elementAt(j, 9)->addNew<WComboBox>();
                      sc.titleOrientationEdit->addItem("Horizontal");
                      sc.titleOrientationEdit->addItem("Vertical");
                      sc.titleOrientationEdit->setCurrentIndex(0);
                      connectSignals(sc.titleOrientationEdit);

                      sc.tickDirectionEdit = axisConfig_->elementAt(j, 10)->addNew<WComboBox>();
                      sc.tickDirectionEdit->addItem("Outwards");
                      sc.tickDirectionEdit->addItem("Inwards");
                      sc.tickDirectionEdit->setCurrentIndex(0);
                      connectSignals(sc.tickDirectionEdit);

                      sc.locationEdit = axisConfig_->elementAt(j, 11)->addNew<WComboBox>();
                      sc.locationEdit->addItem("Minimum value");
                      sc.locationEdit->addItem("Maximum value");
                      sc.locationEdit->addItem("Zero value");
                      sc.locationEdit->addItem("Both sides");
                      sc.locationEdit->setCurrentIndex(0);
                      if (axis.location() == AxisValue::Maximum) {
                      sc.locationEdit->setCurrentIndex(1);
                      } else if (axis.location() == AxisValue::Zero) {
                      sc.locationEdit->setCurrentIndex(2);
                      }
                      connectSignals(sc.locationEdit);

                      WPushButton *removeAxisButton =
                      axisConfig_->elementAt(j, 12)->addNew<WPushButton>(utf8("x"));
                      if (ax == Axis::X) {
                      removeAxisButton->clicked().connect(std::bind(&ChartConfig::removeXAxis, this, &axis));
                      } else {
                      removeAxisButton->clicked().connect(std::bind(&ChartConfig::removeYAxis, this, &axis));
                      }

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

                      axisControls_.insert(axisControls_.begin() + j - 1, sc);
                      }

                      void ChartConfig::removeXAxis(const WAxis *axis)
                      {
                      int xAxis = axis->xAxisId();
                      for (std::size_t i = 0; i < chart_->series().size(); ++i) {
                      if (chart_->series()[i]->xAxis() == xAxis)
                      chart_->series()[i]->bindToXAxis(-1);
                      }
                      chart_->removeXAxis(xAxis);
                      axisConfig_->removeRow(1 + xAxis);
                      xAxesModel_->removeRow(xAxis);
                      axisControls_.erase(axisControls_.begin() + xAxis);
                      update();
                      }

                      void ChartConfig::removeYAxis(const WAxis *axis)
                      {
                      int yAxis = axis->yAxisId();
                      for (std::size_t i = 0; i < chart_->series().size(); ++i) {
                      if (chart_->series()[i]->yAxis() == yAxis)
                      chart_->series()[i]->bindToYAxis(-1);
                      }
                      chart_->removeYAxis(yAxis);
                      axisConfig_->removeRow(1 + chart_->xAxisCount() + yAxis);
                      yAxesModel_->removeRow(yAxis);
                      axisControls_.erase(axisControls_.begin() + chart_->xAxisCount() + yAxis);
                      update();
                      }

                      void ChartConfig::clearXAxes()
                      {
                      if (chart_->xAxisCount() == 0)
                      return;

                      for (std::size_t i = 0; i < chart_->series().size(); ++i) {
                      chart_->series()[i]->bindToXAxis(-1);
                      }
                      const int xAxisCount = chart_->xAxisCount();
                      chart_->clearXAxes();
                      for (int i = 0; i < xAxisCount; ++i) {
                      axisConfig_->removeRow(1);
                      }
                      xAxesModel_->clear();
                      axisControls_.erase(axisControls_.begin(), axisControls_.begin() + xAxisCount);
                      }

                      void ChartConfig::clearYAxes()
                      {
                      if (chart_->yAxisCount() == 0)
                      return;

                      for (std::size_t i = 0; i < chart_->series().size(); ++i) {
                      chart_->series()[i]->bindToYAxis(-1);
                      }
                      const int yAxisCount = chart_->yAxisCount();
                      chart_->clearYAxes();
                      for (int i = 0; i < yAxisCount; ++i) {
                      axisConfig_->removeRow(axisConfig_->rowCount() - 1);
                      }
                      yAxesModel_->clear();
                      axisControls_.resize(chart_->xAxisCount());
                      }