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