Wt examples  3.2.2
ChartConfig.C
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2008 Emweb bvba, Kessel-Lo, Belgium.
3  *
4  * See the LICENSE file for terms of use.
5  */
6 
7 #include "ChartConfig.h"
8 #include "PanelList.h"
9 
10 #include <iostream>
11 #include <boost/lexical_cast.hpp>
12 
13 #include <Wt/WAbstractItemModel>
14 #include <Wt/WApplication>
15 #include <Wt/WCheckBox>
16 #include <Wt/WComboBox>
17 #include <Wt/WDoubleValidator>
18 #include <Wt/WEnvironment>
19 #include <Wt/WIntValidator>
20 #include <Wt/WLineEdit>
21 #include <Wt/WPanel>
22 #include <Wt/WPushButton>
23 #include <Wt/WStandardItemModel>
24 #include <Wt/WTable>
25 #include <Wt/WText>
26 #include <Wt/WPainterPath>
27 
28 #include <Wt/Chart/WCartesianChart>
29 
30 using namespace Wt;
31 using namespace Wt::Chart;
32 
33 namespace {
34  void addHeader(WTable *t, const char *value) {
35  t->elementAt(0, t->columnCount())->addWidget(new WText(value));
36  }
37 
38  void addEntry(WAbstractItemModel *model, const char *value) {
39  model->insertRows(model->rowCount(), 1);
40  model->setData(model->rowCount()-1, 0, boost::any(std::string(value)));
41  }
42 
43  bool getDouble(WLineEdit *edit, double& value) {
44  try {
45  value = boost::lexical_cast<double>(edit->text().toUTF8());
46  return true;
47  } catch (...) {
48  return false;
49  }
50  }
51 
52  int seriesIndexOf(WCartesianChart* chart, int modelColumn) {
53  for (unsigned i = 0; i < chart->series().size(); ++i)
54  if (chart->series()[i].modelColumn() == modelColumn)
55  return i;
56 
57  return -1;
58  }
59 }
60 
62  : WContainerWidget(parent),
63  chart_(chart),
64  fill_(MinimumValueFill)
65 {
67  WBrush(WColor(0xFF, 0xFA, 0xE5)));
68  chart->initLayout();
69 
70  PanelList *list = new PanelList(this);
71 
72  WIntValidator *sizeValidator = new WIntValidator(200, 2000, this);
73  sizeValidator->setMandatory(true);
74 
75  WDoubleValidator *anyNumberValidator = new WDoubleValidator(this);
76  anyNumberValidator->setMandatory(true);
77 
78  WDoubleValidator *angleValidator = new WDoubleValidator(-90, 90, this);
79  angleValidator->setMandatory(true);
80 
81  // ---- Chart properties ----
82 
83  WStandardItemModel *orientation = new WStandardItemModel(0, 1, this);
84  addEntry(orientation, "Vertical");
85  addEntry(orientation, "Horizontal");
86 
87  WStandardItemModel *legendLocation = new WStandardItemModel(0, 1, this);
88  addEntry(legendLocation, "Outside");
89  addEntry(legendLocation, "Inside");
90 
91  WStandardItemModel *legendSide = new WStandardItemModel(0, 1, this);
92  addEntry(legendSide, "Top");
93  addEntry(legendSide, "Right");
94  addEntry(legendSide, "Bottom");
95  addEntry(legendSide, "Left");
96 
97  WStandardItemModel *legendAlignment = new WStandardItemModel(0, 1, this);
98  addEntry(legendAlignment, "AlignLeft");
99  addEntry(legendAlignment, "AlignCenter");
100  addEntry(legendAlignment, "AlignRight");
101  addEntry(legendAlignment, "AlignTop");
102  addEntry(legendAlignment, "AlignMiddle");
103  addEntry(legendAlignment, "AlignBottom");
104 
105  WTable *chartConfig = new WTable();
106  chartConfig->setMargin(WLength::Auto, Left | Right);
107 
108  int row = 0;
109  chartConfig->elementAt(row, 0)->addWidget(new WText("Title:"));
110  titleEdit_ = new WLineEdit(chartConfig->elementAt(row, 1));
112  ++row;
113 
114  chartConfig->elementAt(row, 0)->addWidget(new WText("Width:"));
115  chartWidthEdit_ = new WLineEdit(chartConfig->elementAt(row, 1));
117  ->setText(boost::lexical_cast<std::string>(chart_->width().value()));
118  chartWidthEdit_->setValidator(sizeValidator);
121  ++row;
122 
123  chartConfig->elementAt(row, 0)->addWidget(new WText("Height:"));
124  chartHeightEdit_ = new WLineEdit(chartConfig->elementAt(row, 1));
126  ->setText(boost::lexical_cast<std::string>(chart_->height().value()));
127  chartHeightEdit_->setValidator(sizeValidator);
130  ++row;
131 
132  chartConfig->elementAt(row, 0)->addWidget(new WText("Orientation:"));
133  chartOrientationEdit_ = new WComboBox(chartConfig->elementAt(row, 1));
134  chartOrientationEdit_->setModel(orientation);
136  ++row;
137 
138  chartConfig->elementAt(row, 0)->addWidget(new WText("Legend location:"));
139  legendLocationEdit_ = new WComboBox(chartConfig->elementAt(row, 1));
140  legendLocationEdit_->setModel(legendLocation);
142  ++row;
143 
144  chartConfig->elementAt(row, 0)->addWidget(new WText("Legend side:"));
145  legendSideEdit_ = new WComboBox(chartConfig->elementAt(row, 1));
146  legendSideEdit_->setModel(legendSide);
149  ++row;
150 
151  chartConfig->elementAt(row, 0)->addWidget(new WText("Legend alignment:"));
152  legendAlignmentEdit_ = new WComboBox(chartConfig->elementAt(row, 1));
153  legendAlignmentEdit_->setModel(legendAlignment);
156  ++row;
157 
158  for (int i = 0; i < chartConfig->rowCount(); ++i) {
159  chartConfig->elementAt(i, 0)->setStyleClass("tdhead");
160  chartConfig->elementAt(i, 1)->setStyleClass("tddata");
161  }
162 
163  WPanel *p = list->addWidget("Chart properties", chartConfig);
164  p->setMargin(WLength::Auto, Left | Right);
165  p->resize(750, WLength::Auto);
166  p->setMargin(20, Top | Bottom);
167 
168  if (chart_->isLegendEnabled())
170 
171  // ---- Series properties ----
172 
173  WStandardItemModel *types = new WStandardItemModel(0, 1, this);
174  addEntry(types, "Points");
175  addEntry(types, "Line");
176  addEntry(types, "Curve");
177  addEntry(types, "Bar");
178  addEntry(types, "Line Area");
179  addEntry(types, "Curve Area");
180  addEntry(types, "Stacked Bar");
181  addEntry(types, "Stacked Line Area");
182  addEntry(types, "Stacked Curve Area");
183 
184  WStandardItemModel *markers = new WStandardItemModel(0, 1, this);
185  addEntry(markers, "None");
186  addEntry(markers, "Square");
187  addEntry(markers, "Circle");
188  addEntry(markers, "Cross");
189  addEntry(markers, "X cross");
190  addEntry(markers, "Triangle");
191  addEntry(markers, "Diamond");
192 
193  WStandardItemModel *axes = new WStandardItemModel(0, 1, this);
194  addEntry(axes, "1st Y axis");
195  addEntry(axes, "2nd Y axis");
196 
197  WStandardItemModel *labels = new WStandardItemModel(0, 1, this);
198  addEntry(labels, "None");
199  addEntry(labels, "X");
200  addEntry(labels, "Y");
201  addEntry(labels, "X: Y");
202 
203  WTable *seriesConfig = new WTable();
204  seriesConfig->setMargin(WLength::Auto, Left | Right);
205 
206  ::addHeader(seriesConfig, "Name");
207  ::addHeader(seriesConfig, "Enabled");
208  ::addHeader(seriesConfig, "Type");
209  ::addHeader(seriesConfig, "Marker");
210  ::addHeader(seriesConfig, "Y axis");
211  ::addHeader(seriesConfig, "Legend");
212  ::addHeader(seriesConfig, "Shadow");
213  ::addHeader(seriesConfig, "Value labels");
214 
215  seriesConfig->rowAt(0)->setStyleClass("trhead");
216 
217  for (int j = 1; j < chart->model()->columnCount(); ++j) {
218  SeriesControl sc;
219 
220  new WText(asString(chart->model()->headerData(j)),
221  seriesConfig->elementAt(j, 0));
222 
223  sc.enabledEdit = new WCheckBox(seriesConfig->elementAt(j, 1));
225 
226  sc.typeEdit = new WComboBox(seriesConfig->elementAt(j, 2));
227  sc.typeEdit->setModel(types);
229 
230  sc.markerEdit = new WComboBox(seriesConfig->elementAt(j, 3));
231  sc.markerEdit->setModel(markers);
233 
234  sc.axisEdit = new WComboBox(seriesConfig->elementAt(j, 4));
235  sc.axisEdit->setModel(axes);
237 
238  sc.legendEdit = new WCheckBox(seriesConfig->elementAt(j, 5));
240 
241  sc.shadowEdit = new WCheckBox(seriesConfig->elementAt(j, 6));
243 
244  sc.labelsEdit = new WComboBox(seriesConfig->elementAt(j, 7));
245  sc.labelsEdit->setModel(labels);
247 
248  int si = seriesIndexOf(chart, j);
249 
250  if (si != -1) {
251  sc.enabledEdit->setChecked();
252  const WDataSeries& s = chart_->series(j);
253  switch (s.type()) {
254  case PointSeries:
255  sc.typeEdit->setCurrentIndex(0); break;
256  case LineSeries:
258  (s.isStacked() ? 7 : 4) : 1); break;
259  case CurveSeries:
261  (s.isStacked() ? 8 : 5) : 2); break;
262  case BarSeries:
263  sc.typeEdit->setCurrentIndex(s.isStacked() ? 6 : 3);
264  }
265 
266  sc.markerEdit->setCurrentIndex((int)s.marker());
268  sc.shadowEdit->setChecked(s.shadow() != WShadow());
269  }
270 
271  seriesControls_.push_back(sc);
272 
273  seriesConfig->rowAt(j)->setStyleClass("trdata");
274  }
275 
276  p = list->addWidget("Series properties", seriesConfig);
277  p->expand();
278  p->setMargin(WLength::Auto, Left | Right);
279  p->resize(750, WLength::Auto);
280  p->setMargin(20, Top | Bottom);
281 
282  // ---- Axis properties ----
283 
284  WStandardItemModel *yScales = new WStandardItemModel(0, 1, this);
285  addEntry(yScales, "Linear scale");
286  addEntry(yScales, "Log scale");
287 
288  WStandardItemModel *xScales = new WStandardItemModel(0, 1, this);
289  addEntry(xScales, "Categories");
290  addEntry(xScales, "Linear scale");
291  addEntry(xScales, "Log scale");
292  addEntry(xScales, "Date scale");
293 
294  WTable *axisConfig = new WTable();
295  axisConfig->setMargin(WLength::Auto, Left | Right);
296 
297  ::addHeader(axisConfig, "Axis");
298  ::addHeader(axisConfig, "Visible");
299  ::addHeader(axisConfig, "Scale");
300  ::addHeader(axisConfig, "Automatic");
301  ::addHeader(axisConfig, "Minimum");
302  ::addHeader(axisConfig, "Maximum");
303  ::addHeader(axisConfig, "Gridlines");
304  ::addHeader(axisConfig, "Label angle");
305 
306  axisConfig->rowAt(0)->setStyleClass("trhead");
307 
308  for (int i = 0; i < 3; ++i) {
309  const char *axisName[] = { "X axis", "1st Y axis", "2nd Y axis" };
310  int j = i + 1;
311 
312  const WAxis& axis = chart_->axis(static_cast<Axis>(i));
313  AxisControl sc;
314 
315  new WText(WString(axisName[i], UTF8), axisConfig->elementAt(j, 0));
316 
317  sc.visibleEdit = new WCheckBox(axisConfig->elementAt(j, 1));
318  sc.visibleEdit->setChecked(axis.isVisible());
320 
321  sc.scaleEdit = new WComboBox(axisConfig->elementAt(j, 2));
322  if (axis.scale() == CategoryScale)
323  sc.scaleEdit->addItem("Category scale");
324  else {
325  if (axis.id() == XAxis) {
326  sc.scaleEdit->setModel(xScales);
327  sc.scaleEdit->setCurrentIndex(axis.scale());
328  } else {
329  sc.scaleEdit->setModel(yScales);
330  sc.scaleEdit->setCurrentIndex(axis.scale() - 1);
331  }
332  }
334 
335  bool autoValues = axis.autoLimits() == (MinimumValue | MaximumValue);
336 
337  sc.minimumEdit = new WLineEdit(axisConfig->elementAt(j, 4));
338  sc.minimumEdit->setText(boost::lexical_cast<std::string>(axis.minimum()));
339  sc.minimumEdit->setValidator(anyNumberValidator);
340  sc.minimumEdit->setEnabled(!autoValues);
342 
343  sc.maximumEdit = new WLineEdit(axisConfig->elementAt(j, 5));
344  sc.maximumEdit->setText(boost::lexical_cast<std::string>(axis.maximum()));
345  sc.maximumEdit->setValidator(anyNumberValidator);
346  sc.maximumEdit->setEnabled(!autoValues);
348 
349  sc.autoEdit = new WCheckBox(axisConfig->elementAt(j, 3));
350  sc.autoEdit->setChecked(autoValues);
352  sc.autoEdit->checked().connect(sc.maximumEdit, &WLineEdit::disable);
353  sc.autoEdit->unChecked().connect(sc.maximumEdit, &WLineEdit::enable);
354  sc.autoEdit->checked().connect(sc.minimumEdit, &WLineEdit::disable);
355  sc.autoEdit->unChecked().connect(sc.minimumEdit, &WLineEdit::enable);
356 
357  sc.gridLinesEdit = new WCheckBox(axisConfig->elementAt(j, 6));
359 
360  sc.labelAngleEdit = new WLineEdit(axisConfig->elementAt(j, 7));
361  sc.labelAngleEdit->setText("0");
362  sc.labelAngleEdit->setValidator(angleValidator);
364 
365  axisConfig->rowAt(j)->setStyleClass("trdata");
366 
367  axisControls_.push_back(sc);
368  }
369 
370  p = list->addWidget("Axis properties", axisConfig);
371  p->setMargin(WLength::Auto, Left | Right);
372  p->resize(750, WLength::Auto);
373  p->setMargin(20, Top | Bottom);
374 
375  /*
376  * If we do not have JavaScript, then add a button to reflect changes to
377  * the chart.
378  */
379  if (!WApplication::instance()->environment().javaScript()) {
380  WPushButton *b = new WPushButton(this);
381  b->setText("Update chart");
382  b->setInline(false); // so we can add margin to center horizontally
383  b->setMargin(WLength::Auto, Left | Right);
384  b->clicked().connect(this, &ChartConfig::update);
385  }
386 }
387 
389 {
390  fill_ = fill;
391 }
392 
394 {
395  bool haveLegend = false;
396  std::vector<WDataSeries> series;
397 
398  for (int i = 1; i < chart_->model()->columnCount(); ++i) {
399  SeriesControl& sc = seriesControls_[i-1];
400 
401  if (sc.enabledEdit->isChecked()) {
402  WDataSeries s(i);
403 
404  switch (sc.typeEdit->currentIndex()) {
405  case 0:
406  s.setType(PointSeries);
407  if (sc.markerEdit->currentIndex() == 0)
409  break;
410  case 1:
411  s.setType(LineSeries);
413  break;
414  case 2:
415  s.setType(CurveSeries);
417  break;
418  case 3:
419  s.setType(BarSeries);
421  break;
422  case 4:
423  s.setType(LineSeries);
424  s.setFillRange(fill_);
426  break;
427  case 5:
428  s.setType(CurveSeries);
429  s.setFillRange(fill_);
431  break;
432  case 6:
433  s.setType(BarSeries);
434  s.setStacked(true);
436  break;
437  case 7:
438  s.setType(LineSeries);
439  s.setFillRange(fill_);
440  s.setStacked(true);
442  break;
443  case 8:
444  s.setType(CurveSeries);
445  s.setFillRange(fill_);
446  s.setStacked(true);
448  }
449 
450  //set WPainterPath to draw a diamond
451  if(sc.markerEdit->currentIndex() == CustomMarker){
452  WPainterPath pp = WPainterPath();
453  pp.moveTo(-6, 0);
454  pp.lineTo(0, 6);
455  pp.lineTo(6, 0);
456  pp.lineTo(0, -6);
457  pp.lineTo(-6, 0);
458  s.setCustomMarker(pp);
459  }
460 
461  s.setMarker(static_cast<MarkerType>(sc.markerEdit->currentIndex()));
462 
463  if (sc.axisEdit->currentIndex() == 1) {
464  s.bindToAxis(Y2Axis);
465  }
466 
467  if (sc.legendEdit->isChecked()) {
468  s.setLegendEnabled(true);
469  haveLegend = true;
470  } else
471  s.setLegendEnabled(false);
472 
473  if (sc.shadowEdit->isChecked()) {
474  s.setShadow(WShadow(3, 3, WColor(0, 0, 0, 127), 3));
475  } else
476  s.setShadow(WShadow());
477 
478  switch (sc.labelsEdit->currentIndex()) {
479  case 1:
481  break;
482  case 2:
484  break;
485  case 3:
488  break;
489  }
490 
491  series.push_back(s);
492  }
493  }
494 
495  chart_->setSeries(series);
496 
497  for (int i = 0; i < 3; ++i) {
498  AxisControl& sc = axisControls_[i];
499  WAxis& axis = chart_->axis(static_cast<Axis>(i));
500 
501  axis.setVisible(sc.visibleEdit->isChecked());
502 
503  if (sc.scaleEdit->count() != 1) {
504  int k = sc.scaleEdit->currentIndex();
505  if (axis.id() != XAxis)
506  k += 1;
507  else {
508  if (k == 0)
510  else
512  }
513 
514  switch (k) {
515  case 1:
516  axis.setScale(LinearScale); break;
517  case 2:
518  axis.setScale(LogScale); break;
519  case 3:
520  axis.setScale(DateScale); break;
521  }
522  }
523 
524  if (sc.autoEdit->isChecked())
526  else {
527  if (validate(sc.minimumEdit) && validate(sc.maximumEdit)) {
528  double min, max;
529  getDouble(sc.minimumEdit, min);
530  getDouble(sc.maximumEdit, max);
531 
532  if (axis.scale() == LogScale)
533  if (min <= 0)
534  min = 0.0001;
535 
536  axis.setRange(min, max);
537  }
538 
539  }
540 
541  if (validate(sc.labelAngleEdit)) {
542  double angle;
543  getDouble(sc.labelAngleEdit, angle);
544  axis.setLabelAngle(angle);
545  }
546 
548  }
549 
551 
553  double width, height;
554  getDouble(chartWidthEdit_, width);
555  getDouble(chartHeightEdit_, height);
556  chart_->resize(width, height);
557  }
558 
559  switch (chartOrientationEdit_->currentIndex()) {
560  case 0:
561  chart_->setOrientation(Vertical); break;
562  case 1:
564  }
565 
566  chart_->setLegendEnabled(haveLegend);
567 
568  if (haveLegend) {
569  LegendLocation location = LegendOutside;
570  Side side = Right;
571  AlignmentFlag alignment = AlignMiddle;
572 
573  switch (legendLocationEdit_->currentIndex()) {
574  case 0: location = LegendOutside; break;
575  case 1: location = LegendInside; break;
576  }
577 
578  switch (legendSideEdit_->currentIndex()) {
579  case 0: side = Top; break;
580  case 1: side = Right; break;
581  case 2: side = Bottom; break;
582  case 3: side = Left; break;
583  }
584 
585  if (side == Left || side == Right) {
588  } else {
591  }
592 
593  switch (legendAlignmentEdit_->currentIndex()) {
594  case 0: alignment = AlignLeft; break;
595  case 1: alignment = AlignCenter; break;
596  case 2: alignment = AlignRight; break;
597  case 3: alignment = AlignTop; break;
598  case 4: alignment = AlignMiddle; break;
599  case 5: alignment = AlignBottom; break;
600  }
601 
602  chart_->setLegendLocation(location, side, alignment);
603 
604  chart_->setLegendColumns((side == Top || side == Bottom ) ? 2 : 1,
605  WLength(100));
606  }
607 
608  for (unsigned i = 0; i < 4; ++i) {
609  Side sides[] = { Top, Right, Bottom, Left };
610 
611  bool legendRoom =
612  haveLegend
614  && chart_->legendSide() == sides[i];
615 
616  int padding;
617 
618  if (i % 2 == 0)
619  padding = legendRoom ? 80 : 40;
620  else
621  padding = legendRoom ? 200 : 80;
622 
623  chart_->setPlotAreaPadding(padding, sides[i]);
624  }
625 }
626 
628 {
629  bool valid = w->validate() == WValidator::Valid;
630 
631  if (!WApplication::instance()->environment().javaScript()) {
632  w->setStyleClass(valid ? "" : "Wt-invalid");
633  w->setToolTip(valid ? "" : "Invalid value");
634  }
635 
636  return valid;
637 }
638 
640 {
641  w->changed().connect(this, &ChartConfig::update);
642  if (dynamic_cast<WLineEdit *>(w))
644 }

Generated on Tue Oct 30 2012 for the C++ Web Toolkit (Wt) by doxygen 1.8.1.2