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(
const 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 void addEntry(
const std::shared_ptr<WAbstractItemModel>& model,
const WString &value) {
45 model->insertRows(model->rowCount(), 1);
46 model->setData(model->rowCount()-1, 0, cpp17::any(value));
49 bool getDouble(WLineEdit *edit,
double& value) {
51 value = WLocale::currentLocale().toDouble(edit->text());
58 int seriesIndexOf(WCartesianChart* chart,
int modelColumn) {
59 for (
unsigned i = 0; i < chart->series().size(); ++i)
60 if (chart->series()[i]->modelColumn() == modelColumn)
66 WString axisName(Axis axis,
int yAxis)
69 return Wt::utf8(
"X Axis");
71 return Wt::utf8(
"Y axis {1}").arg(yAxis + 1);
79 fill_(FillRangeType::MinimumValue)
81 chart_->setLegendStyle(
chart_->legendFont(), WPen(WColor(
"black")),
82 WBrush(WColor(0xFF, 0xFA, 0xE5)));
84 PanelList *list = this->addWidget(cpp14::make_unique<PanelList>());
86 std::shared_ptr<WIntValidator> sizeValidator
87 = std::make_shared<WIntValidator>(200,2000);
88 sizeValidator->setMandatory(
true);
98 std::shared_ptr<WStandardItemModel> orientation
99 = std::make_shared<WStandardItemModel>(0,1);
100 addEntry(orientation,
"Vertical");
101 addEntry(orientation,
"Horizontal");
103 std::shared_ptr<WStandardItemModel> legendLocation
104 = std::make_shared<WStandardItemModel>(0,1);
105 addEntry(legendLocation,
"Outside");
106 addEntry(legendLocation,
"Inside");
108 std::shared_ptr<WStandardItemModel> legendSide
109 = std::make_shared<WStandardItemModel>(0,1);
110 addEntry(legendSide,
"Top");
111 addEntry(legendSide,
"Right");
112 addEntry(legendSide,
"Bottom");
113 addEntry(legendSide,
"Left");
115 std::shared_ptr<WStandardItemModel> legendAlignment
116 = std::make_shared<WStandardItemModel>(0,1);
117 addEntry(legendAlignment,
"AlignLeft");
118 addEntry(legendAlignment,
"AlignCenter");
119 addEntry(legendAlignment,
"AlignRight");
120 addEntry(legendAlignment,
"AlignTop");
121 addEntry(legendAlignment,
"AlignMiddle");
122 addEntry(legendAlignment,
"AlignBottom");
124 std::unique_ptr<WTable> chartConfig
125 = cpp14::make_unique<WTable>();
126 chartConfig->setMargin(WLength::Auto, Side::Left | Side::Right);
129 chartConfig->elementAt(row, 0)->addWidget(cpp14::make_unique<WText>(
"Title:"));
130 titleEdit_ = chartConfig->elementAt(row,1)->addWidget(cpp14::make_unique<WLineEdit>());
134 chartConfig->elementAt(row, 0)->addWidget(cpp14::make_unique<WText>(
"Width:"));
135 chartWidthEdit_ = chartConfig->elementAt(row,1)->addWidget(cpp14::make_unique<WLineEdit>());
137 ->setText(WLocale::currentLocale().toString(
chart_->width().value()));
143 chartConfig->elementAt(row, 0)->addWidget(cpp14::make_unique<WText>(
"Height:"));
144 chartHeightEdit_ = chartConfig->elementAt(row,1)->addWidget(cpp14::make_unique<WLineEdit>());
146 ->setText(WLocale::currentLocale().toString(
chart_->height().value()));
152 chartConfig->elementAt(row, 0)->addWidget(cpp14::make_unique<WText>(
"Orientation:"));
159 chartConfig->elementAt(row, 0)->addWidget(cpp14::make_unique<WText>(
"Legend location:"));
160 legendLocationEdit_ = chartConfig->elementAt(row,1)->addWidget(cpp14::make_unique<WComboBox>());
166 chartConfig->elementAt(row, 0)->addWidget(cpp14::make_unique<WText>(
"Legend side:"));
167 legendSideEdit_ = chartConfig->elementAt(row,1)->addWidget(cpp14::make_unique<WComboBox>());
173 chartConfig->elementAt(row, 0)->addWidget(cpp14::make_unique<WText>(
"Legend alignment:"));
174 legendAlignmentEdit_ = chartConfig->elementAt(row,1)->addWidget(cpp14::make_unique<WComboBox>());
180 chartConfig->elementAt(row, 0)->addWidget(cpp14::make_unique<WText>(
"Border:"));
181 borderEdit_ = chartConfig->elementAt(row,1)->addWidget(cpp14::make_unique<WCheckBox>());
186 for (
int i = 0; i < chartConfig->rowCount(); ++i) {
187 chartConfig->elementAt(i, 0)->setStyleClass(
"tdhead");
188 chartConfig->elementAt(i, 1)->setStyleClass(
"tddata");
191 WPanel *p = list->
addWidget(
"Chart properties", std::move(chartConfig));
192 p->setMargin(WLength::Auto, Side::Left | Side::Right);
193 p->resize(1160, WLength::Auto);
194 p->setMargin(20, Side::Top | Side::Bottom);
198 std::shared_ptr<WStandardItemModel> types
199 = std::make_shared<WStandardItemModel>(0,1);
200 addEntry(types,
"Points");
201 addEntry(types,
"Line");
202 addEntry(types,
"Curve");
203 addEntry(types,
"Bar");
204 addEntry(types,
"Line Area");
205 addEntry(types,
"Curve Area");
206 addEntry(types,
"Stacked Bar");
207 addEntry(types,
"Stacked Line Area");
208 addEntry(types,
"Stacked Curve Area");
210 std::shared_ptr<WStandardItemModel> markers
211 = std::make_shared<WStandardItemModel>(0,1);
212 addEntry(markers,
"None");
213 addEntry(markers,
"Square");
214 addEntry(markers,
"Circle");
215 addEntry(markers,
"Cross");
216 addEntry(markers,
"X cross");
217 addEntry(markers,
"Triangle");
218 addEntry(markers,
"Pipe");
219 addEntry(markers,
"Star");
220 addEntry(markers,
"Inverted triangle");
221 addEntry(markers,
"Asterisk");
222 addEntry(markers,
"Diamond");
224 yAxesModel_ = std::make_shared<WStandardItemModel>(0, 1);
228 std::shared_ptr<WStandardItemModel> labels
229 = std::make_shared<WStandardItemModel>(0,1);
230 addEntry(labels,
"None");
231 addEntry(labels,
"X");
232 addEntry(labels,
"Y");
233 addEntry(labels,
"X: Y");
235 std::unique_ptr<WTable> seriesConfig
236 = cpp14::make_unique<WTable>();
237 WTable *seriesConfigPtr = seriesConfig.get();
238 seriesConfig->setMargin(WLength::Auto, Side::Left | Side::Right);
239 ::addHeader(seriesConfigPtr,
"Name");
240 ::addHeader(seriesConfigPtr,
"Enabled");
241 ::addHeader(seriesConfigPtr,
"Type");
242 ::addHeader(seriesConfigPtr,
"Marker");
243 ::addHeader(seriesConfigPtr,
"Y axis");
244 ::addHeader(seriesConfigPtr,
"Legend");
245 ::addHeader(seriesConfigPtr,
"Shadow");
246 ::addHeader(seriesConfigPtr,
"Value labels");
248 seriesConfig->rowAt(0)->setStyleClass(
"trhead");
250 for (
int j = 1; j < chart->model()->columnCount(); ++j) {
253 seriesConfig->elementAt(j,0)->addWidget(cpp14::make_unique<WText>(chart->model()->headerData(j)));
255 sc.
enabledEdit = seriesConfig->elementAt(j,1)->addWidget(cpp14::make_unique<WCheckBox>());
258 sc.
typeEdit = seriesConfig->elementAt(j,2)->addWidget(cpp14::make_unique<WComboBox>());
263 sc.
markerEdit = seriesConfig->elementAt(j,3)->addWidget(cpp14::make_unique<WComboBox>());
268 sc.
axisEdit = seriesConfig->elementAt(j,4)->addNew<WComboBox>();
273 sc.
legendEdit = seriesConfig->elementAt(j, 5)->addWidget(cpp14::make_unique<WCheckBox>());
276 sc.
shadowEdit = seriesConfig->elementAt(j,6)->addWidget(cpp14::make_unique<WCheckBox>());
279 sc.
labelsEdit = seriesConfig->elementAt(j,7)->addWidget(cpp14::make_unique<WComboBox>());
284 int si = seriesIndexOf(chart, j);
288 const WDataSeries& s =
chart_->series(j);
290 case SeriesType::Point:
291 sc.
typeEdit->setCurrentIndex(0);
break;
292 case SeriesType::Line:
293 sc.
typeEdit->setCurrentIndex(s.fillRange() != FillRangeType::None ?
294 (s.isStacked() ? 7 : 4) : 1);
break;
295 case SeriesType::Curve:
296 sc.
typeEdit->setCurrentIndex(s.fillRange() != FillRangeType::None ?
297 (s.isStacked() ? 8 : 5) : 2);
break;
298 case SeriesType::Bar:
299 sc.
typeEdit->setCurrentIndex(s.isStacked() ? 6 : 3);
302 sc.
markerEdit->setCurrentIndex((
int)s.marker());
303 sc.
legendEdit->setChecked(s.isLegendEnabled());
304 sc.
shadowEdit->setChecked(s.shadow() != WShadow());
309 seriesConfig->rowAt(j)->setStyleClass(
"trdata");
312 p = list->
addWidget(
"Series properties", std::move(seriesConfig));
314 p->setMargin(WLength::Auto, Side::Left | Side::Right);
315 p->resize(1160, WLength::Auto);
316 p->setMargin(20, Side::Top | Side::Bottom);
320 yScales_ = std::make_shared<WStandardItemModel>(0, 1);
324 xScales_ = std::make_shared<WStandardItemModel>(0, 1);
330 auto axisConfig = cpp14::make_unique<WContainerWidget>();
332 axisConfig_->setMargin(WLength::Auto, Side::Left | Side::Right);
353 WPushButton *addAxisBtn =
354 axisConfig->addNew<WPushButton>(utf8(
"Add Y axis"));
356 WPushButton *clearAxesBtn =
357 axisConfig->addNew<WPushButton>(utf8(
"Clear Y axes"));
360 p = list->
addWidget(
"Axis properties", std::move(axisConfig));
361 p->setMargin(WLength::Auto, Side::Left | Side::Right);
362 p->resize(1160, WLength::Auto);
363 p->setMargin(20, Side::Top | Side::Bottom);
369 if (!WApplication::instance()->environment().javaScript()) {
370 auto *b = this->addWidget(cpp14::make_unique<WPushButton>());
371 b->setText(
"Update chart");
373 b->setMargin(WLength::Auto, Side::Left | Side::Right);
385 bool haveLegend =
false;
386 std::vector<std::unique_ptr<WDataSeries>> series;
388 for (
int i = 1; i <
chart_->model()->columnCount(); ++i) {
392 std::unique_ptr<WDataSeries> s
393 = cpp14::make_unique<WDataSeries>(i);
395 switch (sc.
typeEdit->currentIndex()) {
397 s->setType(SeriesType::Point);
402 s->setType(SeriesType::Line);
405 s->setType(SeriesType::Curve);
408 s->setType(SeriesType::Bar);
411 s->setType(SeriesType::Line);
412 s->setFillRange(
fill_);
415 s->setType(SeriesType::Curve);
416 s->setFillRange(
fill_);
419 s->setType(SeriesType::Bar);
423 s->setType(SeriesType::Line);
424 s->setFillRange(
fill_);
428 s->setType(SeriesType::Curve);
429 s->setFillRange(
fill_);
434 if(sc.
markerEdit->currentIndex() == static_cast<int>(MarkerType::Custom)){
435 WPainterPath pp = WPainterPath();
438 s->setCustomMarker(pp);
441 s->setMarker(static_cast<MarkerType>(sc.
markerEdit->currentIndex()));
443 s->bindToYAxis(sc.
axisEdit->currentIndex());
446 s->setLegendEnabled(
true);
449 s->setLegendEnabled(
false);
452 s->setShadow(WShadow(3, 3, WColor(0, 0, 0, 127), 3));
454 s->setShadow(WShadow());
458 s->setLabelsEnabled(Axis::X);
461 s->setLabelsEnabled(Axis::Y);
464 s->setLabelsEnabled(Axis::X);
465 s->setLabelsEnabled(Axis::Y);
469 series.push_back(std::move(s));
473 chart_->setSeries(std::move(series));
477 WAxis& axis = i == 0 ?
chart_->axis(Axis::X) :
chart_->yAxis(i - 1);
483 if (axis.id() != Axis::X)
487 chart_->setType(ChartType::Category);
489 chart_->setType(ChartType::Scatter);
494 axis.setScale(AxisScale::Linear);
break;
496 axis.setScale(AxisScale::Log);
break;
498 axis.setScale(AxisScale::Date);
break;
503 axis.setAutoLimits(AxisValue::Minimum | AxisValue::Maximum);
505 if (!(axis.autoLimits() & (AxisValue::Minimum | AxisValue::Maximum)).empty()) {
507 .toString(axis.minimum()));
509 .toString(axis.maximum()));
516 if (axis.scale() == AxisScale::Log)
520 if (axis.scale() == AxisScale::Date){
522 WDate dMin = WDate(1900,1,1);
523 double gregDaysMin = (double)dMin.toJulianDay();
525 WDate dMax = WDate(3000,1,1);
526 double gregDaysMax = (double)dMax.toJulianDay();
528 bool greg_year_validation =
529 (min > gregDaysMin &&
534 if(!greg_year_validation){
540 axis.setRange(min, max);
548 axis.setLabelAngle(angle);
555 axis.setTitleOrientation(sc.
titleOrientationEdit->currentIndex() == 0 ? Orientation::Horizontal : Orientation::Vertical);
557 axis.setTickDirection(sc.
tickDirectionEdit->currentIndex() == 0 ? TickDirection::Outwards : TickDirection::Inwards);
561 axis.setLocation(AxisValue::Minimum);
564 axis.setLocation(AxisValue::Maximum);
567 axis.setLocation(AxisValue::Zero);
570 axis.setLocation(AxisValue::Both);
578 double width, height;
581 chart_->resize(width, height);
586 chart_->setOrientation(Orientation::Vertical);
break;
588 chart_->setOrientation(Orientation::Horizontal);
break;
591 chart_->setLegendEnabled(haveLegend);
594 LegendLocation location = LegendLocation::Outside;
595 Side side = Side::Right;
596 AlignmentFlag alignment = AlignmentFlag::Middle;
598 case 0: location = LegendLocation::Outside;
break;
599 case 1: location = LegendLocation::Inside;
break;
603 case 0: side = Side::Top;
break;
604 case 1: side = Side::Right;
break;
605 case 2: side = Side::Bottom;
break;
606 case 3: side = Side::Left;
break;
609 if (side == Side::Left || side == Side::Right) {
618 case 0: alignment = AlignmentFlag::Left;
break;
619 case 1: alignment = AlignmentFlag::Center;
break;
620 case 2: alignment = AlignmentFlag::Right;
break;
621 case 3: alignment = AlignmentFlag::Top;
break;
622 case 4: alignment = AlignmentFlag::Middle;
break;
623 case 5: alignment = AlignmentFlag::Bottom;
break;
626 chart_->setLegendLocation(location, side, alignment);
628 chart_->setLegendColumns((side == Side::Top || side == Side::Bottom ) ? 2 : 1,
633 chart_->setBorderPen(WPen());
635 chart_->setBorderPen(PenStyle::None);
641 bool valid = w->validate() == ValidationState::Valid;
643 if (!WApplication::instance()->environment().javaScript()) {
644 w->setStyleClass(valid ?
"" :
"Wt-invalid");
645 w->setToolTip(valid ?
"" :
"Invalid value");
654 if (dynamic_cast<WLineEdit *>(w))
660 int yAxis =
chart_->addYAxis(cpp14::make_unique<WAxis>());
669 int j = ax == Axis::X ? 1 : yAxis + 2;
671 const WAxis& axis = ax == Axis::X ?
chart_->axis(Axis::X) :
chart_->yAxis(yAxis);
674 axisConfig_->elementAt(j, 0)->addNew<WText>(axisName(axis.id(), axis.yAxisId()));
681 if (axis.scale() == AxisScale::Discrete)
684 if (axis.id() == Axis::X) {
686 sc.
scaleEdit->setCurrentIndex(static_cast<int>(axis.scale()));
689 sc.
scaleEdit->setCurrentIndex(static_cast<int>(axis.scale()) - 1);
694 bool autoValues = axis.autoLimits() == (AxisValue::Minimum | AxisValue::Maximum);
698 .toString(axis.minimum()));
705 .toString(axis.maximum()));
711 sc.
autoEdit->setChecked(autoValues);
748 if (axis.location() == AxisValue::Maximum) {
750 }
else if (axis.location() == AxisValue::Zero) {
756 WPushButton *removeAxisButton =
757 axisConfig_->elementAt(j, 12)->addNew<WPushButton>(utf8(
"x"));
768 int yAxis = axis->yAxisId();
769 for (std::size_t i = 0; i <
chart_->series().size(); ++i) {
770 if (
chart_->series()[i]->yAxis() == yAxis)
771 chart_->series()[i]->bindToYAxis(-1);
773 chart_->removeYAxis(yAxis);
782 if (
chart_->yAxisCount() == 0)
785 for (std::size_t i = 0; i <
chart_->series().size(); ++i) {
786 chart_->series()[i]->bindToYAxis(-1);
ChartConfig(Wt::Chart::WCartesianChart *chart)
Constructor.
Wt::WCheckBox * borderEdit_
std::shared_ptr< Wt::WStandardItemModel > xScales_
std::shared_ptr< Wt::WValidator > angleValidator_
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
void removeYAxis(const Wt::Chart::WAxis *axis)
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
std::shared_ptr< Wt::WStandardItemModel > yScales_
Wt::WCheckBox * gridLinesEdit
std::shared_ptr< Wt::WValidator > anyNumberValidator_
Wt::WComboBox * titleOrientationEdit
Wt::WComboBox * legendAlignmentEdit_
Wt::WLineEdit * maximumEdit
void addAxis(Wt::Chart::Axis axis, int yAxis)
std::shared_ptr< Wt::WStandardItemModel > yAxesModel_
static bool validate(Wt::WFormWidget *w)
Wt::WLineEdit * chartHeightEdit_