00001
00002
00003
00004
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
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(WLength(), Left | Right);
00114 p->resize(750, WLength());
00115 p->setMargin(20, Top | Bottom);
00116
00117 if (chart_->isLegendEnabled())
00118 chart_->setPlotAreaPadding(200, Right);
00119
00120
00121
00122 WStandardItemModel *types = new WStandardItemModel(0, 1, this);
00123 addEntry(types, "Points");
00124 addEntry(types, "Line");
00125 addEntry(types, "Curve");
00126 addEntry(types, "Bar");
00127 addEntry(types, "Line Area");
00128 addEntry(types, "Curve Area");
00129 addEntry(types, "Stacked Bar");
00130 addEntry(types, "Stacked Line Area");
00131 addEntry(types, "Stacked Curve Area");
00132
00133 WStandardItemModel *markers = new WStandardItemModel(0, 1, this);
00134 addEntry(markers, "None");
00135 addEntry(markers, "Square");
00136 addEntry(markers, "Circle");
00137 addEntry(markers, "Cross");
00138 addEntry(markers, "X cross");
00139 addEntry(markers, "Triangle");
00140
00141 WStandardItemModel *axes = new WStandardItemModel(0, 1, this);
00142 addEntry(axes, "1st Y axis");
00143 addEntry(axes, "2nd Y axis");
00144
00145 WStandardItemModel *labels = new WStandardItemModel(0, 1, this);
00146 addEntry(labels, "None");
00147 addEntry(labels, "X");
00148 addEntry(labels, "Y");
00149 addEntry(labels, "X: Y");
00150
00151 WTable *seriesConfig = new WTable();
00152 seriesConfig->setMargin(WLength(), Left | Right);
00153
00154 ::addHeader(seriesConfig, "Name");
00155 ::addHeader(seriesConfig, "Enabled");
00156 ::addHeader(seriesConfig, "Type");
00157 ::addHeader(seriesConfig, "Marker");
00158 ::addHeader(seriesConfig, "Y axis");
00159 ::addHeader(seriesConfig, "Legend");
00160 ::addHeader(seriesConfig, "Value labels");
00161
00162 seriesConfig->rowAt(0)->setStyleClass("trhead");
00163
00164 for (int j = 1; j < chart->model()->columnCount(); ++j) {
00165 SeriesControl sc;
00166
00167 new WText(asString(chart->model()->headerData(j)),
00168 seriesConfig->elementAt(j, 0));
00169
00170 sc.enabledEdit = new WCheckBox(seriesConfig->elementAt(j, 1));
00171 connectSignals(sc.enabledEdit);
00172
00173 sc.typeEdit = new WComboBox(seriesConfig->elementAt(j, 2));
00174 sc.typeEdit->setModel(types);
00175 connectSignals(sc.typeEdit);
00176
00177 sc.markerEdit = new WComboBox(seriesConfig->elementAt(j, 3));
00178 sc.markerEdit->setModel(markers);
00179 connectSignals(sc.markerEdit);
00180
00181 sc.axisEdit = new WComboBox(seriesConfig->elementAt(j, 4));
00182 sc.axisEdit->setModel(axes);
00183 connectSignals(sc.axisEdit);
00184
00185 sc.legendEdit = new WCheckBox(seriesConfig->elementAt(j, 5));
00186 connectSignals(sc.legendEdit);
00187
00188 sc.labelsEdit = new WComboBox(seriesConfig->elementAt(j, 6));
00189 sc.labelsEdit->setModel(labels);
00190 connectSignals(sc.labelsEdit);
00191
00192 int si = chart->seriesIndexOf(j);
00193
00194 if (si != -1) {
00195 sc.enabledEdit->setChecked();
00196 const WDataSeries& s = chart_->series(j);
00197 switch (s.type()) {
00198 case PointSeries:
00199 sc.typeEdit->setCurrentIndex(0); break;
00200 case LineSeries:
00201 sc.typeEdit->setCurrentIndex(s.fillRange() != NoFill ?
00202 (s.isStacked() ? 7 : 4) : 1); break;
00203 case CurveSeries:
00204 sc.typeEdit->setCurrentIndex(s.fillRange() != NoFill ?
00205 (s.isStacked() ? 8 : 5) : 2); break;
00206 case BarSeries:
00207 sc.typeEdit->setCurrentIndex(s.isStacked() ? 6 : 3);
00208 }
00209
00210 sc.markerEdit->setCurrentIndex((int)s.marker());
00211 sc.legendEdit->setChecked(s.isLegendEnabled());
00212 }
00213
00214 seriesControls_.push_back(sc);
00215
00216 seriesConfig->rowAt(j)->setStyleClass("trdata");
00217 }
00218
00219 p = list->addWidget("Series properties", seriesConfig);
00220 p->expand();
00221 p->setMargin(WLength(), Left | Right);
00222 p->resize(750, WLength());
00223 p->setMargin(20, Top | Bottom);
00224
00225
00226
00227 WStandardItemModel *yScales = new WStandardItemModel(0, 1, this);
00228 addEntry(yScales, "Linear scale");
00229 addEntry(yScales, "Log scale");
00230
00231 WStandardItemModel *xScales = new WStandardItemModel(0, 1, this);
00232 addEntry(xScales, "Categories");
00233 addEntry(xScales, "Linear scale");
00234 addEntry(xScales, "Log scale");
00235 addEntry(xScales, "Date scale");
00236
00237 WTable *axisConfig = new WTable();
00238 axisConfig->setMargin(WLength(), Left | Right);
00239
00240 ::addHeader(axisConfig, "Axis");
00241 ::addHeader(axisConfig, "Visible");
00242 ::addHeader(axisConfig, "Scale");
00243 ::addHeader(axisConfig, "Automatic");
00244 ::addHeader(axisConfig, "Minimum");
00245 ::addHeader(axisConfig, "Maximum");
00246 ::addHeader(axisConfig, "Gridlines");
00247 ::addHeader(axisConfig, "Label angle");
00248
00249 axisConfig->rowAt(0)->setStyleClass("trhead");
00250
00251 for (int i = 0; i < 3; ++i) {
00252 const char *axisName[] = { "X axis", "1st Y axis", "2nd Y axis" };
00253 int j = i + 1;
00254
00255 const WAxis& axis = chart_->axis(static_cast<Axis>(i));
00256 AxisControl sc;
00257
00258 new WText(WString(axisName[i], UTF8), axisConfig->elementAt(j, 0));
00259
00260 sc.visibleEdit = new WCheckBox(axisConfig->elementAt(j, 1));
00261 sc.visibleEdit->setChecked(axis.isVisible());
00262 connectSignals(sc.visibleEdit);
00263
00264 sc.scaleEdit = new WComboBox(axisConfig->elementAt(j, 2));
00265 if (axis.scale() == CategoryScale)
00266 sc.scaleEdit->addItem("Category scale");
00267 else {
00268 if (axis.id() == XAxis) {
00269 sc.scaleEdit->setModel(xScales);
00270 sc.scaleEdit->setCurrentIndex(axis.scale());
00271 } else {
00272 sc.scaleEdit->setModel(yScales);
00273 sc.scaleEdit->setCurrentIndex(axis.scale() - 1);
00274 }
00275 }
00276 connectSignals(sc.scaleEdit);
00277
00278 bool autoValues
00279 = axis.minimum() == WAxis::AUTO_MINIMUM
00280 && axis.maximum() == WAxis::AUTO_MAXIMUM;
00281
00282 sc.minimumEdit = new WLineEdit(axisConfig->elementAt(j, 4));
00283 sc.minimumEdit->setText(boost::lexical_cast<std::string>(axis.minimum()));
00284 sc.minimumEdit->setValidator(anyNumberValidator);
00285 sc.minimumEdit->setEnabled(!autoValues);
00286 connectSignals(sc.minimumEdit);
00287
00288 sc.maximumEdit = new WLineEdit(axisConfig->elementAt(j, 5));
00289 sc.maximumEdit->setText(boost::lexical_cast<std::string>(axis.maximum()));
00290 sc.maximumEdit->setValidator(anyNumberValidator);
00291 sc.maximumEdit->setEnabled(!autoValues);
00292 connectSignals(sc.maximumEdit);
00293
00294 sc.autoEdit = new WCheckBox(axisConfig->elementAt(j, 3));
00295 sc.autoEdit->setChecked(autoValues);
00296 connectSignals(sc.autoEdit);
00297 sc.autoEdit->checked.connect(SLOT(sc.maximumEdit, WLineEdit::disable));
00298 sc.autoEdit->unChecked.connect(SLOT(sc.maximumEdit, WLineEdit::enable));
00299 sc.autoEdit->checked.connect(SLOT(sc.minimumEdit, WLineEdit::disable));
00300 sc.autoEdit->unChecked.connect(SLOT(sc.minimumEdit, WLineEdit::enable));
00301
00302 sc.gridLinesEdit = new WCheckBox(axisConfig->elementAt(j, 6));
00303 connectSignals(sc.gridLinesEdit);
00304
00305 sc.labelAngleEdit = new WLineEdit(axisConfig->elementAt(j, 7));
00306 sc.labelAngleEdit->setText("0");
00307 sc.labelAngleEdit->setValidator(angleValidator);
00308 connectSignals(sc.labelAngleEdit);
00309
00310 axisConfig->rowAt(j)->setStyleClass("trdata");
00311
00312 axisControls_.push_back(sc);
00313 }
00314
00315 p = list->addWidget("Axis properties", axisConfig);
00316 p->setMargin(WLength(), Left | Right);
00317 p->resize(750, WLength());
00318 p->setMargin(20, Top | Bottom);
00319
00320
00321
00322
00323
00324 if (!WApplication::instance()->environment().javaScript()) {
00325 WPushButton *b = new WPushButton(this);
00326 b->setText("Update chart");
00327 b->setInline(false);
00328 b->setMargin(WLength(), Left | Right);
00329 b->clicked.connect(SLOT(this, ChartConfig::update));
00330 }
00331 }
00332
00333 void ChartConfig::setValueFill(FillRangeType fill)
00334 {
00335 fill_ = fill;
00336 }
00337
00338 void ChartConfig::update()
00339 {
00340 bool haveLegend = false;
00341 std::vector<WDataSeries> series;
00342
00343 for (int i = 1; i < chart_->model()->columnCount(); ++i) {
00344 SeriesControl& sc = seriesControls_[i-1];
00345
00346 if (sc.enabledEdit->isChecked()) {
00347 WDataSeries s(i);
00348
00349 switch (sc.typeEdit->currentIndex()) {
00350 case 0:
00351 s.setType(PointSeries);
00352 if (sc.markerEdit->currentIndex() == 0)
00353 sc.markerEdit->setCurrentIndex(1);
00354 break;
00355 case 1:
00356 s.setType(LineSeries);
00357 break;
00358 case 2:
00359 s.setType(CurveSeries);
00360 break;
00361 case 3:
00362 s.setType(BarSeries);
00363 break;
00364 case 4:
00365 s.setType(LineSeries);
00366 s.setFillRange(fill_);
00367 break;
00368 case 5:
00369 s.setType(CurveSeries);
00370 s.setFillRange(fill_);
00371 break;
00372 case 6:
00373 s.setType(BarSeries);
00374 s.setStacked(true);
00375 break;
00376 case 7:
00377 s.setType(LineSeries);
00378 s.setFillRange(fill_);
00379 s.setStacked(true);
00380 break;
00381 case 8:
00382 s.setType(CurveSeries);
00383 s.setFillRange(fill_);
00384 s.setStacked(true);
00385 }
00386
00387 s.setMarker(static_cast<MarkerType>(sc.markerEdit->currentIndex()));
00388
00389 if (sc.axisEdit->currentIndex() == 1) {
00390 s.bindToAxis(Y2Axis);
00391 }
00392
00393 if (sc.legendEdit->isChecked()) {
00394 s.setLegendEnabled(true);
00395 haveLegend = true;
00396 } else
00397 s.setLegendEnabled(false);
00398
00399 switch (sc.labelsEdit->currentIndex()) {
00400 case 1:
00401 s.setLabelsEnabled(XAxis);
00402 break;
00403 case 2:
00404 s.setLabelsEnabled(YAxis);
00405 break;
00406 case 3:
00407 s.setLabelsEnabled(XAxis);
00408 s.setLabelsEnabled(YAxis);
00409 break;
00410 }
00411
00412 series.push_back(s);
00413 }
00414 }
00415
00416 chart_->setSeries(series);
00417
00418 for (int i = 0; i < 3; ++i) {
00419 AxisControl& sc = axisControls_[i];
00420 WAxis& axis = chart_->axis(static_cast<Axis>(i));
00421
00422 axis.setVisible(sc.visibleEdit->isChecked());
00423
00424 if (sc.scaleEdit->count() != 1) {
00425 int k = sc.scaleEdit->currentIndex();
00426 if (axis.id() != XAxis)
00427 k += 1;
00428 else {
00429 if (k == 0)
00430 chart_->setType(CategoryChart);
00431 else
00432 chart_->setType(ScatterPlot);
00433 }
00434
00435 switch (k) {
00436 case 1:
00437 axis.setScale(LinearScale); break;
00438 case 2:
00439 axis.setScale(LogScale); break;
00440 case 3:
00441 axis.setScale(DateScale); break;
00442 }
00443 }
00444
00445 if (sc.autoEdit->isChecked())
00446 axis.setRange(WAxis::AUTO_MINIMUM, WAxis::AUTO_MAXIMUM);
00447 else {
00448 if (validate(sc.minimumEdit) && validate(sc.maximumEdit)) {
00449 double min, max;
00450 getDouble(sc.minimumEdit, min);
00451 getDouble(sc.maximumEdit, max);
00452
00453 if (axis.scale() == LogScale)
00454 if (min <= 0)
00455 min = 0.0001;
00456
00457 max = std::max(min, max);
00458
00459 axis.setRange(min, max);
00460 }
00461
00462 }
00463
00464
00465 if (validate(sc.labelAngleEdit)) {
00466 double angle;
00467 getDouble(sc.labelAngleEdit, angle);
00468 axis.setLabelAngle(angle);
00469 }
00470
00471 axis.setGridLinesEnabled(sc.gridLinesEdit->isChecked());
00472 }
00473
00474 chart_->setTitle(titleEdit_->text());
00475
00476 if (validate(chartWidthEdit_) && validate(chartHeightEdit_)) {
00477 double width, height;
00478 getDouble(chartWidthEdit_, width);
00479 getDouble(chartHeightEdit_, height);
00480 chart_->resize(width, height);
00481 }
00482
00483 switch (chartOrientationEdit_->currentIndex()) {
00484 case 0:
00485 chart_->setOrientation(Vertical); break;
00486 case 1:
00487 chart_->setOrientation(Horizontal); break;
00488 }
00489
00490 chart_->setLegendEnabled(haveLegend);
00491 chart_->setPlotAreaPadding(haveLegend ? 200 : 40, Right);
00492 }
00493
00494 bool ChartConfig::validate(WFormWidget *w)
00495 {
00496 bool valid = w->validate() == WValidator::Valid;
00497
00498 if (!WApplication::instance()->environment().javaScript()) {
00499 w->setStyleClass(valid ? "" : "Wt-invalid");
00500 w->setToolTip(valid ? "" : "Invalid value");
00501 }
00502
00503 return valid;
00504 }
00505
00506 void ChartConfig::connectSignals(WFormWidget *w)
00507 {
00508 w->changed.connect(SLOT(this, ChartConfig::update));
00509 if (dynamic_cast<WLineEdit *>(w))
00510 w->enterPressed.connect(SLOT(this, ChartConfig::update));
00511 }