/home/koen/project/wt/cvs/wt/examples/charts/ChartConfig.C

Go to the documentation of this file.
00001 /*
00002  * Copyright (C) 2008 Emweb bvba, Kessel-Lo, Belgium.
00003  *
00004  * See the LICENSE file for terms of use.
00005  */
00006 
00007 #include "ChartConfig.h"
00008 #include "PanelList.h"
00009 
00010 #include <iostream>
00011 #include <boost/lexical_cast.hpp>
00012 
00013 #include <Wt/WAbstractItemModel>
00014 #include <Wt/WApplication>
00015 #include <Wt/WCheckBox>
00016 #include <Wt/WComboBox>
00017 #include <Wt/WDoubleValidator>
00018 #include <Wt/WEnvironment>
00019 #include <Wt/WIntValidator>
00020 #include <Wt/WLineEdit>
00021 #include <Wt/WPanel>
00022 #include <Wt/WPushButton>
00023 #include <Wt/WStandardItemModel>
00024 #include <Wt/WTable>
00025 #include <Wt/WText>
00026 
00027 #include <Wt/Chart/WCartesianChart>
00028 
00029 using namespace Wt;
00030 using namespace Wt::Chart;
00031 
00032 namespace {
00033   void addHeader(WTable *t, const char *value) {
00034     t->elementAt(0, t->numColumns())->addWidget(new WText(value));    
00035   }
00036 
00037   void addEntry(WAbstractItemModel *model, const char *value) {
00038     model->insertRows(model->rowCount(), 1);
00039     model->setData(model->rowCount()-1, 0, boost::any(std::string(value)));
00040   }
00041 
00042   bool getDouble(WLineEdit *edit, double& value) {
00043     try {
00044       value = boost::lexical_cast<double>(edit->text().toUTF8());
00045       return true;
00046     } catch (...) {
00047       return false;
00048     }
00049   }
00050 }
00051 
00052 ChartConfig::ChartConfig(WCartesianChart *chart, WContainerWidget *parent)
00053   : WContainerWidget(parent),
00054     chart_(chart),
00055     fill_(MinimumValueFill)
00056 {
00057   PanelList *list = new PanelList(this);
00058 
00059   WIntValidator *sizeValidator = new WIntValidator(200, 2000, this);
00060   sizeValidator->setMandatory(true);
00061 
00062   WDoubleValidator *anyNumberValidator = new WDoubleValidator(this);
00063   anyNumberValidator->setMandatory(true);
00064 
00065   WDoubleValidator *angleValidator = new WDoubleValidator(-90, 90, this);
00066   angleValidator->setMandatory(true);
00067 
00068   // ---- Chart properties ----
00069 
00070   WStandardItemModel *orientation = new WStandardItemModel(0, 1, this);
00071   addEntry(orientation, "Vertical");
00072   addEntry(orientation, "Horizontal");
00073 
00074   WTable *chartConfig = new WTable();
00075   chartConfig->setMargin(WLength(), Left | Right);
00076 
00077   int row = 0;
00078   chartConfig->elementAt(row, 0)->addWidget(new WText("Title:"));
00079   titleEdit_ = new WLineEdit(chartConfig->elementAt(row, 1));
00080   connectSignals(titleEdit_);
00081   ++row;
00082 
00083   chartConfig->elementAt(row, 0)->addWidget(new WText("Width:"));
00084   chartWidthEdit_ = new WLineEdit(chartConfig->elementAt(row, 1));
00085   chartWidthEdit_
00086     ->setText(boost::lexical_cast<std::string>(chart_->width().value()));
00087   chartWidthEdit_->setValidator(sizeValidator);
00088   chartWidthEdit_->setMaxLength(4);
00089   connectSignals(chartWidthEdit_);
00090   ++row;
00091 
00092   chartConfig->elementAt(row, 0)->addWidget(new WText("Height:"));
00093   chartHeightEdit_ = new WLineEdit(chartConfig->elementAt(row, 1));
00094   chartHeightEdit_
00095     ->setText(boost::lexical_cast<std::string>(chart_->height().value()));
00096   chartHeightEdit_->setValidator(sizeValidator);
00097   chartHeightEdit_->setMaxLength(4);
00098   connectSignals(chartHeightEdit_);
00099   ++row;
00100 
00101   chartConfig->elementAt(row, 0)->addWidget(new WText("Orientation:"));
00102   chartOrientationEdit_ = new WComboBox(chartConfig->elementAt(row, 1));
00103   chartOrientationEdit_->setModel(orientation);
00104   connectSignals(chartOrientationEdit_);
00105   ++row;
00106 
00107   for (int i = 0; i < chartConfig->numRows(); ++i) {
00108     chartConfig->elementAt(i, 0)->setStyleClass("tdhead");
00109     chartConfig->elementAt(i, 1)->setStyleClass("tddata");
00110   }
00111 
00112   WPanel *p = list->addWidget("Chart properties", chartConfig);
00113   p->setMargin(100, Left | Right);
00114   p->setMargin(20, Top | Bottom);
00115 
00116   if (chart_->isLegendEnabled())
00117     chart_->setPlotAreaPadding(200, Right);
00118 
00119   // ---- Series properties ----
00120 
00121   WStandardItemModel *types = new WStandardItemModel(0, 1, this);
00122   addEntry(types, "Points");
00123   addEntry(types, "Line");
00124   addEntry(types, "Curve");
00125   addEntry(types, "Bar");
00126   addEntry(types, "Line Area");
00127   addEntry(types, "Curve Area");
00128   addEntry(types, "Stacked Bar");
00129   addEntry(types, "Stacked Line Area");
00130   addEntry(types, "Stacked Curve Area");
00131 
00132   WStandardItemModel *markers = new WStandardItemModel(0, 1, this);
00133   addEntry(markers, "None");
00134   addEntry(markers, "Square");
00135   addEntry(markers, "Circle");
00136   addEntry(markers, "Cross");
00137   addEntry(markers, "X cross");
00138   addEntry(markers, "Triangle");
00139 
00140   WStandardItemModel *axes = new WStandardItemModel(0, 1, this);
00141   addEntry(axes, "1st Y axis");
00142   addEntry(axes, "2nd Y axis");
00143 
00144   WStandardItemModel *labels = new WStandardItemModel(0, 1, this);
00145   addEntry(labels, "None");
00146   addEntry(labels, "X");
00147   addEntry(labels, "Y");
00148   addEntry(labels, "X: Y");
00149 
00150   WTable *seriesConfig = new WTable();
00151   seriesConfig->setMargin(WLength(), Left | Right);
00152 
00153   ::addHeader(seriesConfig, "Name");
00154   ::addHeader(seriesConfig, "Enabled");
00155   ::addHeader(seriesConfig, "Type");
00156   ::addHeader(seriesConfig, "Marker");
00157   ::addHeader(seriesConfig, "Y axis");
00158   ::addHeader(seriesConfig, "Legend");
00159   ::addHeader(seriesConfig, "Value labels");
00160 
00161   seriesConfig->rowAt(0)->setStyleClass("trhead");
00162 
00163   for (int j = 1; j < chart->model()->columnCount(); ++j) {
00164     SeriesControl sc;
00165 
00166     new WText(asString(chart->model()->headerData(j)),
00167               seriesConfig->elementAt(j, 0));
00168 
00169     sc.enabledEdit = new WCheckBox(seriesConfig->elementAt(j, 1));
00170     connectSignals(sc.enabledEdit);
00171 
00172     sc.typeEdit = new WComboBox(seriesConfig->elementAt(j, 2));
00173     sc.typeEdit->setModel(types);
00174     connectSignals(sc.typeEdit);
00175 
00176     sc.markerEdit = new WComboBox(seriesConfig->elementAt(j, 3));
00177     sc.markerEdit->setModel(markers);
00178     connectSignals(sc.markerEdit);
00179 
00180     sc.axisEdit = new WComboBox(seriesConfig->elementAt(j, 4));
00181     sc.axisEdit->setModel(axes);
00182     connectSignals(sc.axisEdit);
00183 
00184     sc.legendEdit = new WCheckBox(seriesConfig->elementAt(j, 5));
00185     connectSignals(sc.legendEdit);
00186 
00187     sc.labelsEdit = new WComboBox(seriesConfig->elementAt(j, 6));
00188     sc.labelsEdit->setModel(labels);
00189     connectSignals(sc.labelsEdit);
00190 
00191     int si = chart->seriesIndexOf(j);
00192 
00193     if (si != -1) {
00194       sc.enabledEdit->setChecked();
00195       const WDataSeries& s = chart_->series(j);
00196       switch (s.type()) {
00197       case PointSeries:
00198         sc.typeEdit->setCurrentIndex(0); break;
00199       case LineSeries:
00200         sc.typeEdit->setCurrentIndex(s.fillRange() != NoFill ?
00201                                      (s.isStacked() ? 7 : 4) : 1); break;
00202       case CurveSeries:
00203         sc.typeEdit->setCurrentIndex(s.fillRange() != NoFill ?
00204                                      (s.isStacked() ? 8 : 5) : 2); break;
00205       case BarSeries:
00206         sc.typeEdit->setCurrentIndex(s.isStacked() ? 6 : 3);
00207       }
00208 
00209       sc.markerEdit->setCurrentIndex((int)s.marker());
00210       sc.legendEdit->setChecked(s.isLegendEnabled());
00211     }
00212 
00213     seriesControls_.push_back(sc);
00214 
00215     seriesConfig->rowAt(j)->setStyleClass("trdata");
00216   }
00217 
00218   p = list->addWidget("Series properties", seriesConfig);
00219   p->expand();
00220   p->setMargin(100, Left | Right);
00221   p->setMargin(20, Top | Bottom);
00222 
00223   // ---- Axis properties ----
00224 
00225   WStandardItemModel *yScales = new WStandardItemModel(0, 1, this);
00226   addEntry(yScales, "Linear scale");
00227   addEntry(yScales, "Log scale");
00228 
00229   WStandardItemModel *xScales = new WStandardItemModel(0, 1, this);
00230   addEntry(xScales, "Categories");
00231   addEntry(xScales, "Linear scale");
00232   addEntry(xScales, "Log scale");
00233   addEntry(xScales, "Date scale");
00234 
00235   WTable *axisConfig = new WTable();
00236   axisConfig->setMargin(WLength(), Left | Right);
00237 
00238   ::addHeader(axisConfig, "Axis");
00239   ::addHeader(axisConfig, "Visible");
00240   ::addHeader(axisConfig, "Scale");
00241   ::addHeader(axisConfig, "Automatic");
00242   ::addHeader(axisConfig, "Minimum");
00243   ::addHeader(axisConfig, "Maximum");
00244   ::addHeader(axisConfig, "Gridlines");
00245   ::addHeader(axisConfig, "Label angle");
00246 
00247   axisConfig->rowAt(0)->setStyleClass("trhead");
00248 
00249   for (int i = 0; i < 3; ++i) {
00250     const char *axisName[] = { "X axis", "1st Y axis", "2nd Y axis" };
00251     int j = i + 1;
00252 
00253     const WAxis& axis = chart_->axis(static_cast<Axis>(i));
00254     AxisControl sc;
00255 
00256     new WText(WString(axisName[i], UTF8), axisConfig->elementAt(j, 0));
00257 
00258     sc.visibleEdit = new WCheckBox(axisConfig->elementAt(j, 1));
00259     sc.visibleEdit->setChecked(axis.isVisible());
00260     connectSignals(sc.visibleEdit);
00261 
00262     sc.scaleEdit = new WComboBox(axisConfig->elementAt(j, 2));
00263     if (axis.scale() == CategoryScale)
00264       sc.scaleEdit->addItem("Category scale");
00265     else {
00266       if (axis.id() == XAxis) {
00267         sc.scaleEdit->setModel(xScales);
00268         sc.scaleEdit->setCurrentIndex(axis.scale());
00269       } else {
00270         sc.scaleEdit->setModel(yScales);
00271         sc.scaleEdit->setCurrentIndex(axis.scale() - 1);
00272       }
00273     }
00274     connectSignals(sc.scaleEdit);
00275 
00276     bool autoValues
00277       =    axis.minimum() == WAxis::AUTO_MINIMUM
00278         && axis.maximum() == WAxis::AUTO_MAXIMUM;
00279 
00280     sc.minimumEdit = new WLineEdit(axisConfig->elementAt(j, 4));
00281     sc.minimumEdit->setText(boost::lexical_cast<std::string>(axis.minimum()));
00282     sc.minimumEdit->setValidator(anyNumberValidator);
00283     sc.minimumEdit->setEnabled(!autoValues);
00284     connectSignals(sc.minimumEdit);
00285 
00286     sc.maximumEdit = new WLineEdit(axisConfig->elementAt(j, 5));
00287     sc.maximumEdit->setText(boost::lexical_cast<std::string>(axis.maximum()));
00288     sc.maximumEdit->setValidator(anyNumberValidator);
00289     sc.maximumEdit->setEnabled(!autoValues);
00290     connectSignals(sc.maximumEdit);
00291 
00292     sc.autoEdit = new WCheckBox(axisConfig->elementAt(j, 3));
00293     sc.autoEdit->setChecked(autoValues);
00294     connectSignals(sc.autoEdit);
00295     sc.autoEdit->checked.connect(SLOT(sc.maximumEdit, WLineEdit::disable));
00296     sc.autoEdit->unChecked.connect(SLOT(sc.maximumEdit, WLineEdit::enable));
00297     sc.autoEdit->checked.connect(SLOT(sc.minimumEdit, WLineEdit::disable));
00298     sc.autoEdit->unChecked.connect(SLOT(sc.minimumEdit, WLineEdit::enable));
00299 
00300     sc.gridLinesEdit = new WCheckBox(axisConfig->elementAt(j, 6));
00301     connectSignals(sc.gridLinesEdit);
00302 
00303     sc.labelAngleEdit = new WLineEdit(axisConfig->elementAt(j, 7));
00304     sc.labelAngleEdit->setText("0");
00305     sc.labelAngleEdit->setValidator(angleValidator);
00306     connectSignals(sc.labelAngleEdit);
00307 
00308     axisConfig->rowAt(j)->setStyleClass("trdata");
00309 
00310     axisControls_.push_back(sc);
00311   }
00312 
00313   p = list->addWidget("Axis properties", axisConfig);
00314   p->setMargin(100, Left | Right);
00315   p->setMargin(20, Top | Bottom);
00316 
00317   /*
00318    * If we do not have JavaScript, then add a button to reflect changes to
00319    * the chart.
00320    */
00321   if (!WApplication::instance()->environment().javaScript()) {
00322     WPushButton *b = new WPushButton(this);
00323     b->setText("Update chart");
00324     b->setInline(false); // so we can add margin to center horizontally
00325     b->setMargin(WLength(), Left | Right);
00326     b->clicked.connect(SLOT(this, ChartConfig::update));
00327   }
00328 }
00329 
00330 void ChartConfig::setValueFill(FillRangeType fill)
00331 {
00332   fill_ = fill;
00333 }
00334 
00335 void ChartConfig::update()
00336 {
00337   bool haveLegend = false;
00338   std::vector<WDataSeries> series;
00339 
00340   for (int i = 1; i < chart_->model()->columnCount(); ++i) {
00341     SeriesControl& sc = seriesControls_[i-1];
00342 
00343     if (sc.enabledEdit->isChecked()) {
00344       WDataSeries s(i);
00345 
00346       switch (sc.typeEdit->currentIndex()) {
00347       case 0:
00348         s.setType(PointSeries);
00349         if (sc.markerEdit->currentIndex() == 0)
00350           sc.markerEdit->setCurrentIndex(1);
00351         break;
00352       case 1:
00353         s.setType(LineSeries);
00354         break;
00355       case 2:
00356         s.setType(CurveSeries);
00357         break;
00358       case 3:
00359         s.setType(BarSeries);
00360         break;
00361       case 4:
00362         s.setType(LineSeries);
00363         s.setFillRange(fill_);
00364         break;
00365       case 5:
00366         s.setType(CurveSeries);
00367         s.setFillRange(fill_);
00368         break;
00369       case 6:
00370         s.setType(BarSeries);
00371         s.setStacked(true);
00372         break;
00373       case 7:
00374         s.setType(LineSeries);
00375         s.setFillRange(fill_);
00376         s.setStacked(true);
00377         break;
00378       case 8:
00379         s.setType(CurveSeries);
00380         s.setFillRange(fill_);
00381         s.setStacked(true);     
00382       }
00383 
00384       s.setMarker(static_cast<MarkerType>(sc.markerEdit->currentIndex()));
00385 
00386       if (sc.axisEdit->currentIndex() == 1) {
00387         s.bindToAxis(Y2Axis);
00388       }
00389 
00390       if (sc.legendEdit->isChecked()) {
00391         s.setLegendEnabled(true);
00392         haveLegend = true;
00393       } else
00394         s.setLegendEnabled(false);
00395 
00396       switch (sc.labelsEdit->currentIndex()) {
00397       case 1:
00398         s.setLabelsEnabled(XAxis);
00399         break;
00400       case 2:
00401         s.setLabelsEnabled(YAxis);
00402         break;
00403       case 3:
00404         s.setLabelsEnabled(XAxis);      
00405         s.setLabelsEnabled(YAxis);
00406         break;
00407       }
00408 
00409       series.push_back(s);
00410     }
00411   }
00412 
00413   chart_->setSeries(series);
00414 
00415   for (int i = 0; i < 3; ++i) {
00416     AxisControl& sc = axisControls_[i];
00417     WAxis& axis = chart_->axis(static_cast<Axis>(i));
00418 
00419     axis.setVisible(sc.visibleEdit->isChecked());
00420 
00421     if (sc.scaleEdit->count() != 1) {
00422       int k = sc.scaleEdit->currentIndex();
00423       if (axis.id() != XAxis)
00424         k += 1;
00425       else {
00426         if (k == 0)
00427           chart_->setType(CategoryChart);
00428         else
00429           chart_->setType(ScatterPlot);
00430       }
00431 
00432       switch (k) {
00433       case 1:
00434         axis.setScale(LinearScale); break;
00435       case 2:
00436         axis.setScale(LogScale); break;
00437       case 3:
00438         axis.setScale(DateScale); break;
00439       }
00440     }
00441 
00442     if (sc.autoEdit->isChecked())
00443       axis.setRange(WAxis::AUTO_MINIMUM, WAxis::AUTO_MAXIMUM);
00444     else {
00445       if (validate(sc.minimumEdit) && validate(sc.maximumEdit)) {
00446         double min, max;
00447         getDouble(sc.minimumEdit, min);
00448         getDouble(sc.maximumEdit, max);
00449 
00450         if (axis.scale() == LogScale)
00451           if (min <= 0)
00452             min = 0.0001;
00453 
00454         max = std::max(min, max);
00455 
00456         axis.setRange(min, max);
00457       }
00458 
00459     }
00460 
00461 
00462     if (validate(sc.labelAngleEdit)) {
00463       double angle;
00464       getDouble(sc.labelAngleEdit, angle);
00465       axis.setLabelAngle(angle);
00466     }
00467 
00468     axis.setGridLinesEnabled(sc.gridLinesEdit->isChecked());
00469   }
00470 
00471   chart_->setTitle(titleEdit_->text());
00472 
00473   if (validate(chartWidthEdit_) && validate(chartHeightEdit_)) {
00474     double width, height;
00475     getDouble(chartWidthEdit_, width);
00476     getDouble(chartHeightEdit_, height);
00477     chart_->resize(width, height);
00478   }
00479 
00480   switch (chartOrientationEdit_->currentIndex()) {
00481   case 0:
00482     chart_->setOrientation(Vertical); break;
00483   case 1:
00484     chart_->setOrientation(Horizontal); break;
00485   }
00486 
00487   chart_->setLegendEnabled(haveLegend);
00488   chart_->setPlotAreaPadding(haveLegend ? 200 : 40, Right);
00489 }
00490 
00491 bool ChartConfig::validate(WFormWidget *w)
00492 {
00493   bool valid = w->validate() == WValidator::Valid;
00494 
00495   if (!WApplication::instance()->environment().javaScript()) {
00496     w->setStyleClass(valid ? "" : "Wt-invalid");
00497     w->setToolTip(valid ? "" : "Invalid value");
00498   }
00499 
00500   return valid;
00501 }
00502 
00503 void ChartConfig::connectSignals(WFormWidget *w)
00504 {
00505   w->changed.connect(SLOT(this, ChartConfig::update));
00506   if (dynamic_cast<WLineEdit *>(w))
00507     w->enterPressed.connect(SLOT(this, ChartConfig::update));
00508 }

Generated on Fri Jul 25 17:05:59 2008 for Wt by doxygen 1.5.3