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