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(100, Left | Right);
00114 p->setMargin(20, Top | Bottom);
00115
00116 if (chart_->isLegendEnabled())
00117 chart_->setPlotAreaPadding(200, Right);
00118
00119
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
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
00319
00320
00321 if (!WApplication::instance()->environment().javaScript()) {
00322 WPushButton *b = new WPushButton(this);
00323 b->setText("Update chart");
00324 b->setInline(false);
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 }