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