12 #include <Wt/WAbstractItemModel.h> 13 #include <Wt/WApplication.h> 14 #include <Wt/WCheckBox.h> 15 #include <Wt/WComboBox.h> 16 #include <Wt/WDoubleValidator.h> 18 #include <Wt/WEnvironment.h> 19 #include <Wt/WIntValidator.h> 20 #include <Wt/WLineEdit.h> 21 #include <Wt/WLocale.h> 22 #include <Wt/WPanel.h> 23 #include <Wt/WPushButton.h> 24 #include <Wt/WStandardItemModel.h> 25 #include <Wt/WTable.h> 27 #include <Wt/WPainterPath.h> 29 #include <Wt/Chart/WCartesianChart.h> 35 void addHeader(WTable *t,
const char *value) {
36 t->elementAt(0, t->columnCount())->addWidget(cpp14::make_unique<WText>(value));
39 void addEntry(std::shared_ptr<WAbstractItemModel> model,
const char *value) {
40 model->insertRows(model->rowCount(), 1);
41 model->setData(model->rowCount()-1, 0, cpp17::any(std::string(value)));
44 bool getDouble(WLineEdit *edit,
double& value) {
46 value = WLocale::currentLocale().toDouble(edit->text());
53 int seriesIndexOf(WCartesianChart* chart,
int modelColumn) {
54 for (
unsigned i = 0; i < chart->series().size(); ++i)
55 if (chart->series()[i]->modelColumn() == modelColumn)
65 fill_(FillRangeType::MinimumValue)
67 chart_->setLegendStyle(
chart_->legendFont(), WPen(WColor(
"black")),
68 WBrush(WColor(0xFF, 0xFA, 0xE5)));
70 PanelList *list = this->addWidget(cpp14::make_unique<PanelList>());
72 std::shared_ptr<WIntValidator> sizeValidator
73 = std::make_shared<WIntValidator>(200,2000);
74 sizeValidator->setMandatory(
true);
76 std::shared_ptr<WDoubleValidator> anyNumberValidator
77 = std::make_shared<WDoubleValidator>();
78 anyNumberValidator->setMandatory(
true);
80 std::shared_ptr<WDoubleValidator> angleValidator
81 = std::make_shared<WDoubleValidator>(-90,90);
82 angleValidator->setMandatory(
true);
86 std::shared_ptr<WStandardItemModel> orientation
87 = std::make_shared<WStandardItemModel>(0,1);
88 addEntry(orientation,
"Vertical");
89 addEntry(orientation,
"Horizontal");
91 std::shared_ptr<WStandardItemModel> legendLocation
92 = std::make_shared<WStandardItemModel>(0,1);
93 addEntry(legendLocation,
"Outside");
94 addEntry(legendLocation,
"Inside");
96 std::shared_ptr<WStandardItemModel> legendSide
97 = std::make_shared<WStandardItemModel>(0,1);
98 addEntry(legendSide,
"Top");
99 addEntry(legendSide,
"Right");
100 addEntry(legendSide,
"Bottom");
101 addEntry(legendSide,
"Left");
103 std::shared_ptr<WStandardItemModel> legendAlignment
104 = std::make_shared<WStandardItemModel>(0,1);
105 addEntry(legendAlignment,
"AlignLeft");
106 addEntry(legendAlignment,
"AlignCenter");
107 addEntry(legendAlignment,
"AlignRight");
108 addEntry(legendAlignment,
"AlignTop");
109 addEntry(legendAlignment,
"AlignMiddle");
110 addEntry(legendAlignment,
"AlignBottom");
112 std::unique_ptr<WTable> chartConfig
113 = cpp14::make_unique<WTable>();
114 chartConfig->setMargin(WLength::Auto, Side::Left | Side::Right);
117 chartConfig->elementAt(row, 0)->addWidget(cpp14::make_unique<WText>(
"Title:"));
118 titleEdit_ = chartConfig->elementAt(row,1)->addWidget(cpp14::make_unique<WLineEdit>());
122 chartConfig->elementAt(row, 0)->addWidget(cpp14::make_unique<WText>(
"Width:"));
123 chartWidthEdit_ = chartConfig->elementAt(row,1)->addWidget(cpp14::make_unique<WLineEdit>());
125 ->setText(WLocale::currentLocale().toString(
chart_->width().value()));
131 chartConfig->elementAt(row, 0)->addWidget(cpp14::make_unique<WText>(
"Height:"));
132 chartHeightEdit_ = chartConfig->elementAt(row,1)->addWidget(cpp14::make_unique<WLineEdit>());
134 ->setText(WLocale::currentLocale().toString(
chart_->height().value()));
140 chartConfig->elementAt(row, 0)->addWidget(cpp14::make_unique<WText>(
"Orientation:"));
147 chartConfig->elementAt(row, 0)->addWidget(cpp14::make_unique<WText>(
"Legend location:"));
148 legendLocationEdit_ = chartConfig->elementAt(row,1)->addWidget(cpp14::make_unique<WComboBox>());
154 chartConfig->elementAt(row, 0)->addWidget(cpp14::make_unique<WText>(
"Legend side:"));
155 legendSideEdit_ = chartConfig->elementAt(row,1)->addWidget(cpp14::make_unique<WComboBox>());
161 chartConfig->elementAt(row, 0)->addWidget(cpp14::make_unique<WText>(
"Legend alignment:"));
162 legendAlignmentEdit_ = chartConfig->elementAt(row,1)->addWidget(cpp14::make_unique<WComboBox>());
168 chartConfig->elementAt(row, 0)->addWidget(cpp14::make_unique<WText>(
"Border:"));
169 borderEdit_ = chartConfig->elementAt(row,1)->addWidget(cpp14::make_unique<WCheckBox>());
174 for (
int i = 0; i < chartConfig->rowCount(); ++i) {
175 chartConfig->elementAt(i, 0)->setStyleClass(
"tdhead");
176 chartConfig->elementAt(i, 1)->setStyleClass(
"tddata");
179 WPanel *p = list->
addWidget(
"Chart properties", std::move(chartConfig));
180 p->setMargin(WLength::Auto, Side::Left | Side::Right);
181 p->resize(1080, WLength::Auto);
182 p->setMargin(20, Side::Top | Side::Bottom);
186 std::shared_ptr<WStandardItemModel> types
187 = std::make_shared<WStandardItemModel>(0,1);
188 addEntry(types,
"Points");
189 addEntry(types,
"Line");
190 addEntry(types,
"Curve");
191 addEntry(types,
"Bar");
192 addEntry(types,
"Line Area");
193 addEntry(types,
"Curve Area");
194 addEntry(types,
"Stacked Bar");
195 addEntry(types,
"Stacked Line Area");
196 addEntry(types,
"Stacked Curve Area");
198 std::shared_ptr<WStandardItemModel> markers
199 = std::make_shared<WStandardItemModel>(0,1);
200 addEntry(markers,
"None");
201 addEntry(markers,
"Square");
202 addEntry(markers,
"Circle");
203 addEntry(markers,
"Cross");
204 addEntry(markers,
"X cross");
205 addEntry(markers,
"Triangle");
206 addEntry(markers,
"Pipe");
207 addEntry(markers,
"Star");
208 addEntry(markers,
"Inverted triangle");
209 addEntry(markers,
"Asterisk");
210 addEntry(markers,
"Diamond");
212 std::shared_ptr<WStandardItemModel> axes
213 = std::make_shared<WStandardItemModel>(0,1);
214 addEntry(axes,
"1st Y axis");
215 addEntry(axes,
"2nd Y axis");
217 std::shared_ptr<WStandardItemModel> labels
218 = std::make_shared<WStandardItemModel>(0,1);
219 addEntry(labels,
"None");
220 addEntry(labels,
"X");
221 addEntry(labels,
"Y");
222 addEntry(labels,
"X: Y");
224 std::unique_ptr<WTable> seriesConfig
225 = cpp14::make_unique<WTable>();
226 WTable *seriesConfigPtr = seriesConfig.get();
227 seriesConfig->setMargin(WLength::Auto, Side::Left | Side::Right);
228 ::addHeader(seriesConfigPtr,
"Name");
229 ::addHeader(seriesConfigPtr,
"Enabled");
230 ::addHeader(seriesConfigPtr,
"Type");
231 ::addHeader(seriesConfigPtr,
"Marker");
232 ::addHeader(seriesConfigPtr,
"Y axis");
233 ::addHeader(seriesConfigPtr,
"Legend");
234 ::addHeader(seriesConfigPtr,
"Shadow");
235 ::addHeader(seriesConfigPtr,
"Value labels");
237 seriesConfig->rowAt(0)->setStyleClass(
"trhead");
239 for (
int j = 1; j < chart->model()->columnCount(); ++j) {
242 seriesConfig->elementAt(j,0)->addWidget(cpp14::make_unique<WText>(chart->model()->headerData(j)));
244 sc.
enabledEdit = seriesConfig->elementAt(j,1)->addWidget(cpp14::make_unique<WCheckBox>());
247 sc.
typeEdit = seriesConfig->elementAt(j,2)->addWidget(cpp14::make_unique<WComboBox>());
252 sc.
markerEdit = seriesConfig->elementAt(j,3)->addWidget(cpp14::make_unique<WComboBox>());
257 sc.
axisEdit = seriesConfig->elementAt(j,4)->addWidget(cpp14::make_unique<WComboBox>());
262 sc.
legendEdit = seriesConfig->elementAt(j, 5)->addWidget(cpp14::make_unique<WCheckBox>());
265 sc.
shadowEdit = seriesConfig->elementAt(j,6)->addWidget(cpp14::make_unique<WCheckBox>());
268 sc.
labelsEdit = seriesConfig->elementAt(j,7)->addWidget(cpp14::make_unique<WComboBox>());
273 int si = seriesIndexOf(chart, j);
277 const WDataSeries& s =
chart_->series(j);
279 case SeriesType::Point:
280 sc.
typeEdit->setCurrentIndex(0);
break;
281 case SeriesType::Line:
282 sc.
typeEdit->setCurrentIndex(s.fillRange() != FillRangeType::None ?
283 (s.isStacked() ? 7 : 4) : 1);
break;
284 case SeriesType::Curve:
285 sc.
typeEdit->setCurrentIndex(s.fillRange() != FillRangeType::None ?
286 (s.isStacked() ? 8 : 5) : 2);
break;
287 case SeriesType::Bar:
288 sc.
typeEdit->setCurrentIndex(s.isStacked() ? 6 : 3);
291 sc.
markerEdit->setCurrentIndex((
int)s.marker());
292 sc.
legendEdit->setChecked(s.isLegendEnabled());
293 sc.
shadowEdit->setChecked(s.shadow() != WShadow());
298 seriesConfig->rowAt(j)->setStyleClass(
"trdata");
301 p = list->
addWidget(
"Series properties", std::move(seriesConfig));
303 p->setMargin(WLength::Auto, Side::Left | Side::Right);
304 p->resize(1080, WLength::Auto);
305 p->setMargin(20, Side::Top | Side::Bottom);
309 std::shared_ptr<WStandardItemModel> yScales
310 = std::make_shared<WStandardItemModel>(0,1);
311 addEntry(yScales,
"Linear scale");
312 addEntry(yScales,
"Log scale");
314 std::shared_ptr<WStandardItemModel> xScales
315 = std::make_shared<WStandardItemModel>(0,1);
316 addEntry(xScales,
"Categories");
317 addEntry(xScales,
"Linear scale");
318 addEntry(xScales,
"Log scale");
319 addEntry(xScales,
"Date scale");
321 std::unique_ptr<WTable> axisConfig
322 = cpp14::make_unique<WTable>();
323 axisConfig->setMargin(WLength::Auto, Side::Left | Side::Right);
324 WTable *axisConfigPtr = axisConfig.get();
326 ::addHeader(axisConfigPtr,
"Axis");
327 ::addHeader(axisConfigPtr,
"Visible");
328 ::addHeader(axisConfigPtr,
"Scale");
329 ::addHeader(axisConfigPtr,
"Automatic");
330 ::addHeader(axisConfigPtr,
"Minimum");
331 ::addHeader(axisConfigPtr,
"Maximum");
332 ::addHeader(axisConfigPtr,
"Gridlines");
333 ::addHeader(axisConfigPtr,
"Label angle");
334 ::addHeader(axisConfigPtr,
"Title");
335 ::addHeader(axisConfigPtr,
"Title orientation");
336 ::addHeader(axisConfigPtr,
"Tick direction");
337 ::addHeader(axisConfigPtr,
"Location");
339 axisConfig->rowAt(0)->setStyleClass(
"trhead");
341 for (
int i = 0; i < 3; ++i) {
342 const char *axisName[] = {
"X axis",
"1st Y axis",
"2nd Y axis" };
345 const WAxis& axis =
chart_->axis(static_cast<Axis>(i));
348 axisConfig->elementAt(j,0)->addWidget(cpp14::make_unique<WText>(WString(axisName[i], CharEncoding::UTF8)));
350 sc.
visibleEdit = axisConfig->elementAt(j,1)->addWidget(cpp14::make_unique<WCheckBox>());
354 sc.
scaleEdit = axisConfig->elementAt(j,2)->addWidget(cpp14::make_unique<WComboBox>());
355 if (axis.scale() == AxisScale::Discrete)
358 if (axis.id() == Axis::X) {
361 sc.
scaleEdit->setCurrentIndex(static_cast<int>(axis.scale()));
364 sc.
scaleEdit->setCurrentIndex(static_cast<int>(axis.scale()) - 1);
369 bool autoValues = axis.autoLimits() == (AxisValue::Minimum | AxisValue::Maximum);
371 sc.
minimumEdit = axisConfig->elementAt(j,4)->addWidget(cpp14::make_unique<WLineEdit>());
373 .toString(axis.minimum()));
378 sc.
maximumEdit = axisConfig->elementAt(j,5)->addWidget(cpp14::make_unique<WLineEdit>());
380 .toString(axis.maximum()));
385 sc.
autoEdit = axisConfig->elementAt(j,3)->addWidget(cpp14::make_unique<WCheckBox>());
386 sc.
autoEdit->setChecked(autoValues);
393 sc.
gridLinesEdit = axisConfig->elementAt(j,6)->addWidget(cpp14::make_unique<WCheckBox>());
396 sc.
labelAngleEdit = axisConfig->elementAt(j,7)->addWidget(cpp14::make_unique<WLineEdit>());
401 sc.
titleEdit = axisConfig->elementAt(j,8)->addWidget(cpp14::make_unique<WLineEdit>());
405 sc.
titleOrientationEdit = axisConfig->elementAt(j,9)->addWidget(cpp14::make_unique<WComboBox>());
411 sc.
tickDirectionEdit = axisConfig->elementAt(j,10)->addWidget(cpp14::make_unique<WComboBox>());
417 sc.
locationEdit = axisConfig->elementAt(j,11)->addWidget(cpp14::make_unique<WComboBox>());
423 if (axis.location() == AxisValue::Zero) {
428 axisConfig->rowAt(j)->setStyleClass(
"trdata");
433 p = list->
addWidget(
"Axis properties", std::move(axisConfig));
434 p->setMargin(WLength::Auto, Side::Left | Side::Right);
435 p->resize(1080, WLength::Auto);
436 p->setMargin(20, Side::Top | Side::Bottom);
442 if (!WApplication::instance()->environment().javaScript()) {
443 auto *b = this->addWidget(cpp14::make_unique<WPushButton>());
444 b->setText(
"Update chart");
446 b->setMargin(WLength::Auto, Side::Left | Side::Right);
458 bool haveLegend =
false;
459 std::vector<std::unique_ptr<WDataSeries>> series;
461 for (
int i = 1; i <
chart_->model()->columnCount(); ++i) {
465 std::unique_ptr<WDataSeries> s
466 = cpp14::make_unique<WDataSeries>(i);
468 switch (sc.
typeEdit->currentIndex()) {
470 s->setType(SeriesType::Point);
475 s->setType(SeriesType::Line);
478 s->setType(SeriesType::Curve);
481 s->setType(SeriesType::Bar);
484 s->setType(SeriesType::Line);
485 s->setFillRange(
fill_);
488 s->setType(SeriesType::Curve);
489 s->setFillRange(
fill_);
492 s->setType(SeriesType::Bar);
496 s->setType(SeriesType::Line);
497 s->setFillRange(
fill_);
501 s->setType(SeriesType::Curve);
502 s->setFillRange(
fill_);
507 if(sc.
markerEdit->currentIndex() ==
static_cast<int>(MarkerType::Custom)){
508 WPainterPath pp = WPainterPath();
511 s->setCustomMarker(pp);
514 s->setMarker(static_cast<MarkerType>(sc.
markerEdit->currentIndex()));
516 if (sc.
axisEdit->currentIndex() == 1) {
517 s->bindToAxis(Axis::Y2);
521 s->setLegendEnabled(
true);
524 s->setLegendEnabled(
false);
527 s->setShadow(WShadow(3, 3, WColor(0, 0, 0, 127), 3));
529 s->setShadow(WShadow());
533 s->setLabelsEnabled(Axis::X);
536 s->setLabelsEnabled(Axis::Y);
539 s->setLabelsEnabled(Axis::X);
540 s->setLabelsEnabled(Axis::Y);
544 series.push_back(std::move(s));
548 chart_->setSeries(std::move(series));
550 for (
int i = 0; i < 3; ++i) {
552 WAxis& axis =
chart_->axis(static_cast<Axis>(i));
558 if (axis.id() != Axis::X)
562 chart_->setType(ChartType::Category);
564 chart_->setType(ChartType::Scatter);
569 axis.setScale(AxisScale::Linear);
break;
571 axis.setScale(AxisScale::Log);
break;
573 axis.setScale(AxisScale::Date);
break;
578 axis.setAutoLimits(AxisValue::Minimum | AxisValue::Maximum);
580 if (!(axis.autoLimits() & (AxisValue::Minimum | AxisValue::Maximum)).empty()) {
582 .toString(axis.minimum()));
584 .toString(axis.maximum()));
591 if (axis.scale() == AxisScale::Log)
595 if (axis.scale() == AxisScale::Date){
597 WDate dMin = WDate(1900,1,1);
598 double gregDaysMin = (double)dMin.toJulianDay();
600 WDate dMax = WDate(3000,1,1);
601 double gregDaysMax = (double)dMax.toJulianDay();
603 bool greg_year_validation =
604 (min > gregDaysMin &&
609 if(!greg_year_validation){
615 axis.setRange(min, max);
623 axis.setLabelAngle(angle);
630 axis.setTitleOrientation(sc.
titleOrientationEdit->currentIndex() == 0 ? Orientation::Horizontal : Orientation::Vertical);
632 axis.setTickDirection(sc.
tickDirectionEdit->currentIndex() == 0 ? TickDirection::Outwards : TickDirection::Inwards);
636 axis.setLocation(AxisValue::Minimum);
639 axis.setLocation(AxisValue::Maximum);
642 axis.setLocation(AxisValue::Zero);
645 axis.setLocation(AxisValue::Both);
653 double width, height;
656 chart_->resize(width, height);
661 chart_->setOrientation(Orientation::Vertical);
break;
663 chart_->setOrientation(Orientation::Horizontal);
break;
666 chart_->setLegendEnabled(haveLegend);
669 LegendLocation location = LegendLocation::Outside;
670 Side side = Side::Right;
671 AlignmentFlag alignment = AlignmentFlag::Middle;
673 case 0: location = LegendLocation::Outside;
break;
674 case 1: location = LegendLocation::Inside;
break;
678 case 0: side = Side::Top;
break;
679 case 1: side = Side::Right;
break;
680 case 2: side = Side::Bottom;
break;
681 case 3: side = Side::Left;
break;
684 if (side == Side::Left || side == Side::Right) {
693 case 0: alignment = AlignmentFlag::Left;
break;
694 case 1: alignment = AlignmentFlag::Center;
break;
695 case 2: alignment = AlignmentFlag::Right;
break;
696 case 3: alignment = AlignmentFlag::Top;
break;
697 case 4: alignment = AlignmentFlag::Middle;
break;
698 case 5: alignment = AlignmentFlag::Bottom;
break;
701 chart_->setLegendLocation(location, side, alignment);
703 chart_->setLegendColumns((side == Side::Top || side == Side::Bottom ) ? 2 : 1,
708 chart_->setBorderPen(WPen());
710 chart_->setBorderPen(PenStyle::None);
716 bool valid = w->validate() == ValidationState::Valid;
718 if (!WApplication::instance()->environment().javaScript()) {
719 w->setStyleClass(valid ?
"" :
"Wt-invalid");
720 w->setToolTip(valid ?
"" :
"Invalid value");
729 if (dynamic_cast<WLineEdit *>(w))
ChartConfig(Wt::Chart::WCartesianChart *chart)
Constructor.
Wt::WCheckBox * borderEdit_
Wt::Chart::FillRangeType fill_
Wt::WComboBox * scaleEdit
Wt::WLineEdit * titleEdit_
Wt::Chart::WCartesianChart * chart_
void connectSignals(Wt::WFormWidget *w)
Wt::WCheckBox * enabledEdit
Wt::WComboBox * locationEdit
Struct that holds the controls for one series.
Wt::WLineEdit * titleEdit
Wt::WCheckBox * visibleEdit
Wt::WComboBox * markerEdit
Wt::WLineEdit * chartWidthEdit_
Struct that holds the controls for one axis.
Wt::WPanel * addWidget(const Wt::WString &text, std::unique_ptr< Wt::WWidget > w)
Wt::WComboBox * chartOrientationEdit_
Wt::WComboBox * legendLocationEdit_
std::vector< SeriesControl > seriesControls_
Controls for series.
Wt::WLineEdit * minimumEdit
Wt::WComboBox * legendSideEdit_
std::vector< AxisControl > axisControls_
Controls for axes.
Wt::WCheckBox * shadowEdit
Wt::WComboBox * labelsEdit
void setValueFill(Wt::Chart::FillRangeType fill)
Wt::WComboBox * tickDirectionEdit
Wt::WLineEdit * labelAngleEdit
Wt::WCheckBox * legendEdit
Wt::WCheckBox * gridLinesEdit
Wt::WComboBox * titleOrientationEdit
Wt::WComboBox * legendAlignmentEdit_
Wt::WLineEdit * maximumEdit
static bool validate(Wt::WFormWidget *w)
Wt::WLineEdit * chartHeightEdit_