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

Generated on Wed Jun 13 2012 for the C++ Web Toolkit (Wt) by doxygen 1.8.1