1 /*
2  * Copyright (C) 2008 Emweb bv, Herent, Belgium.
3  *
4  * See the LICENSE file for terms of use.
5  */
6 
7 #include <cmath>
8 #include <cstdio>
9 
10 #include "Wt/Chart/WAbstractChartModel.h"
11 #include "Wt/Chart/WPieChart.h"
12 #include "Wt/Chart/WStandardPalette.h"
13 
14 #include "Wt/WContainerWidget.h"
15 #include "Wt/WCssDecorationStyle.h"
16 #include "Wt/WText.h"
17 #include "Wt/WPainter.h"
18 #include "Wt/WPolygonArea.h"
19 #include "Wt/WApplication.h"
20 #include "Wt/WEnvironment.h"
21 #include "Wt/WException.h"
22 #include "Wt/WModelIndex.h"
23 
24 #include "WebUtils.h"
25 
26 #ifndef M_PI
27 #define M_PI 3.14159265358979323846
28 #endif
29 
30 namespace Wt {
31   namespace Chart {
32 
PieData()33 WPieChart::PieData::PieData()
34   : customBrush(false),
35     explode(0)
36 { }
37 
WPieChart()38 WPieChart::WPieChart()
39   : labelsColumn_(-1),
40     dataColumn_(-1),
41     height_(0.0),
42     startAngle_(45),
43     avoidLabelRendering_(0.0),
44     labelOptions_(None),
45     shadow_(false),
46     labelFormat_(WString::fromUTF8("%.3g%%"))
47 {
48   setPalette
49     (std::make_shared<WStandardPalette>(PaletteFlavour::Neutral));
50   setPlotAreaPadding(5);
51 }
52 
setLabelsColumn(int modelColumn)53 void WPieChart::setLabelsColumn(int modelColumn)
54 {
55   if (labelsColumn_ != modelColumn) {
56     labelsColumn_ = modelColumn;
57     update();
58   }
59 }
60 
setLabelFormat(const WString & format)61 void WPieChart::setLabelFormat(const WString& format)
62 {
63   labelFormat_ = format;
64   update();
65 }
66 
labelFormat()67 WString WPieChart::labelFormat() const
68 {
69   return labelFormat_;
70 }
71 
setDataColumn(int modelColumn)72 void WPieChart::setDataColumn(int modelColumn)
73 {
74   if (dataColumn_ != modelColumn) {
75     dataColumn_ = modelColumn;
76     update();
77   }
78 }
79 
setBrush(int modelRow,const WBrush & brush)80 void WPieChart::setBrush(int modelRow, const WBrush& brush)
81 {
82   pie_[modelRow].customBrush = true;
83   pie_[modelRow].brush = brush;
84   update();
85 }
86 
brush(int modelRow)87 WBrush WPieChart::brush(int modelRow) const
88 {
89   if (pie_[modelRow].customBrush)
90     return pie_[modelRow].brush;
91   else
92     return palette()->brush(modelRow);
93 }
94 
setExplode(int modelRow,double factor)95 void WPieChart::setExplode(int modelRow, double factor)
96 {
97   pie_[modelRow].explode = factor;
98   update();
99 }
100 
explode(int modelRow)101 double WPieChart::explode(int modelRow) const
102 {
103   return pie_[modelRow].explode;
104 }
105 
setPerspectiveEnabled(bool enabled,double height)106 void WPieChart::setPerspectiveEnabled(bool enabled, double height)
107 {
108   if ((!enabled && height_ != 0.0) || height_ != height) {
109     height_ = enabled ? height : 0.0;
110     update();
111   }
112 }
113 
setShadowEnabled(bool enabled)114 void WPieChart::setShadowEnabled(bool enabled)
115 {
116   if (shadow_ != enabled) {
117     shadow_ = enabled;
118     update();
119   }
120 }
121 
setStartAngle(double startAngle)122 void WPieChart::setStartAngle(double startAngle)
123 {
124   if (startAngle_ != startAngle) {
125     startAngle_ = startAngle;
126     update();
127   }
128 }
129 
setAvoidLabelRendering(double avoidLabelRendering)130 void WPieChart::setAvoidLabelRendering(double avoidLabelRendering)
131 {
132   if (avoidLabelRendering_ != avoidLabelRendering) {
133     avoidLabelRendering_ = avoidLabelRendering;
134     update();
135   }
136 }
137 
setDisplayLabels(WFlags<LabelOption> options)138 void WPieChart::setDisplayLabels(WFlags<LabelOption> options)
139 {
140   labelOptions_ = options;
141 
142   update();
143 }
144 
145 std::unique_ptr<WWidget>
createLegendItemWidget(int index,WFlags<LabelOption> options)146 WPieChart::createLegendItemWidget(int index, WFlags<LabelOption> options)
147 {
148   std::unique_ptr<WContainerWidget> legendItem(new WContainerWidget());
149   legendItem->setPadding(4);
150 
151   auto colorText = legendItem->addWidget(std::make_unique<WText>());
152   colorText->setPadding(10, WFlags<Side>(Side::Left) | Side::Right);
153   colorText->decorationStyle().setBackgroundColor(brush(index).color());
154 
155   if (WApplication::instance()->environment().agentIsIE())
156     colorText->setAttributeValue("style", "zoom: 1;");
157 
158   double total = 0;
159 
160   if (dataColumn_ != -1)
161     for (int i = 0; i < model()->rowCount(); ++i) {
162       double v = model()->data(i, dataColumn_);
163       if (!Utils::isNaN(v))
164 	total += v;
165     }
166 
167   double value = model()->data(index, dataColumn_);
168   if (!Utils::isNaN(value)) {
169     WString label = labelText(index, value, total, options);
170     if (!label.empty()) {
171       std::unique_ptr<WText> l(new WText(label));
172       l->setPadding(5, Side::Left);
173       l->setToolTip(model()->toolTip(index, dataColumn_));
174       legendItem->addWidget(std::move(l));
175     }
176   }
177 
178   return std::move(legendItem);
179 }
180 
paint(WPainter & painter,const WRectF & rectangle)181 void WPieChart::paint(WPainter& painter, const WRectF& rectangle) const
182 {
183   double total = 0;
184 
185   if (dataColumn_ != -1)
186     for (int i = 0; i < model()->rowCount(); ++i) {
187       double v = model()->data(i, dataColumn_);
188       if (!Utils::isNaN(v))
189 	total += v;
190     }
191 
192   if (!painter.isActive())
193     throw WException("WPieChart::paint(): painter is not active.");
194 
195   WRectF rect = rectangle;
196 
197   if (rect.isNull() || rect.isEmpty())
198     rect = painter.window();
199 
200   rect.setX(rect.x() + plotAreaPadding(Side::Left));
201   rect.setY(rect.y() + plotAreaPadding(Side::Top));
202   rect.setWidth(rect.width() - plotAreaPadding(Side::Left) - plotAreaPadding(Side::Right));
203   rect.setHeight(rect.height() - plotAreaPadding(Side::Top)
204 		 - plotAreaPadding(Side::Bottom));
205 
206   double side = std::min(rect.width(), rect.height());
207 
208   painter.save();
209   painter.translate(rect.left() + (rect.width() - side)/2,
210 		    rect.top() + (rect.height() - side)/2);
211 
212   if (!title().empty())
213     painter.translate(0, 15);
214 
215   double cx = std::floor(side/2) + 0.5;
216   double cy = cx;
217   double r = (int)(side/2 + 0.5);
218   double h = height_ * r;
219 
220   painter.save();
221   if (h > 0.0) {
222     painter.translate(0, r/2 - h/4);
223     painter.scale(1, 0.5);
224   }
225 
226   drawPie(painter, cx, cy, r, h, total);
227   painter.restore();
228 
229   painter.translate(0, -h/4);
230   if (!labelOptions_.empty()) {
231     if (total != 0) {
232       double currentAngle = startAngle_;
233 
234       for (int i = 0; i < model()->rowCount(); ++i) {
235 	double v = model()->data(i, dataColumn_);
236 	if (Utils::isNaN(v))
237 	  continue;
238 
239 	double spanAngle = -v / total * 360;
240 	double midAngle = currentAngle + spanAngle / 2.0;
241 	double endAngle = currentAngle + spanAngle;
242 	if (endAngle < 0)
243 	  endAngle += 360;
244 	if (midAngle < 0)
245 	  midAngle += 360;
246 
247 	double width = 200;
248 	double height = 30;
249 	double left;
250 	double top;
251 
252 	double f;
253 	if (labelOptions_.test(LabelOption::Outside))
254 	  f = pie_[i].explode + 1.1;
255 	else
256 	  f = pie_[i].explode + 0.7;
257 
258 	double px = cx + f * r * std::cos(-midAngle / 180.0 * M_PI);
259 	double py = cy + f * r * std::sin(-midAngle / 180.0 * M_PI)
260 	  * (h > 0 ? 0.5 : 1);
261 
262 	WFlags<AlignmentFlag> alignment;
263 
264 	WColor c = painter.pen().color();
265 	if (labelOptions_.test(LabelOption::Outside)) {
266 	  if (midAngle < 90) {
267 	    left = px;
268 	    top = py - height;
269 	    alignment = WFlags<AlignmentFlag>(AlignmentFlag::Left) | AlignmentFlag::Bottom;
270 	  } else if (midAngle < 180) {
271 	    left = px - width;
272 	    top = py - height;
273 	    alignment = WFlags<AlignmentFlag>(AlignmentFlag::Right) | AlignmentFlag::Bottom;
274 	  } else if (midAngle < 270) {
275 	    left = px - width;
276 	    top = py + h/2;
277 	    alignment = WFlags<AlignmentFlag>(AlignmentFlag::Right) | AlignmentFlag::Top;
278 	  } else {
279 	    left = px;
280 	    top = py + h/2;
281 	    alignment = WFlags<AlignmentFlag>(AlignmentFlag::Left) | AlignmentFlag::Top;
282 	  }
283 	} else {
284 	  left = px - width/2;
285 	  top = py - height/2;
286 	  alignment = WFlags<AlignmentFlag>(AlignmentFlag::Center) | AlignmentFlag::Middle;
287 	  c = palette()->fontColor(i);
288 	}
289 
290   if ((v / total * 100) >= avoidLabelRendering_) {
291     painter.setPen(WPen(c));
292     drawLabel(&painter, WRectF(left, top, width, height),
293              alignment, labelText(i, v, total, labelOptions_), i);
294 	}
295 
296 	currentAngle = endAngle;
297       }
298     }
299   }
300 
301   if (!title().empty()) {
302     WFont oldFont = painter.font();
303     painter.setFont(titleFont());
304     painter.drawText(cx - 50, cy - r, 100, 50,
305 		     WFlags<AlignmentFlag>(AlignmentFlag::Center) | AlignmentFlag::Top, title());
306     painter.setFont(oldFont);
307   }
308 
309   painter.restore();
310 }
311 
createLabelWidget(std::unique_ptr<WWidget> textWidget,WPainter * painter,const WRectF & rect,Wt::WFlags<Wt::AlignmentFlag> alignmentFlags)312 std::unique_ptr<WContainerWidget> WPieChart::createLabelWidget(std::unique_ptr<WWidget> textWidget,
313     WPainter* painter, const WRectF& rect,
314     Wt::WFlags<Wt::AlignmentFlag> alignmentFlags) const
315 {
316   AlignmentFlag verticalAlign = alignmentFlags & AlignVerticalMask;
317   AlignmentFlag horizontalAlign = alignmentFlags & AlignHorizontalMask;
318 
319   // style parent container
320   auto c = std::make_unique<WContainerWidget>();
321   WWidget *tw = textWidget.get();
322   c->addWidget(std::move(textWidget));
323   c->setPositionScheme(PositionScheme::Absolute);
324   c->setAttributeValue("style", "display: flex;");
325 
326   const WRectF& normRect = rect.normalized();
327   Wt::WTransform t = painter->worldTransform();
328   Wt::WPointF p = t.map(Wt::WPointF(normRect.left(), normRect.top()));
329 
330   c->setWidth(WLength(normRect.width() * t.m11()));
331   c->setHeight(WLength(normRect.height() * t.m22()));
332   c->decorationStyle().setFont(painter->font());
333 
334   std::stringstream containerStyle;
335   containerStyle << "top:" << p.y() << "px; left:" << p.x() << "px; "
336     << "display: flex;";
337 
338   switch (horizontalAlign) {
339   case AlignmentFlag::Left: containerStyle << " justify-content: flex-start;"; break;
340   case AlignmentFlag::Right: containerStyle << " justify-content: flex-end;"; break;
341   case AlignmentFlag::Center: containerStyle << " justify-content: center;"; break;
342   default: break;
343   }
344 
345   c->setAttributeValue("style", containerStyle.str());
346 
347   // style inner text.
348 
349   std::stringstream innerStyle;
350 
351   switch (verticalAlign) {
352   case AlignmentFlag::Top: innerStyle << "align-self: flex-start;"; break;
353   case AlignmentFlag::Bottom: innerStyle << "align-self: flex-end;"; break;
354   case AlignmentFlag::Middle: innerStyle << " align-self: center;"; break;
355   default: break;
356   }
357 
358   tw->setAttributeValue("style", innerStyle.str());
359 
360   return c;
361 }
362 
drawLabel(WPainter * painter,const WRectF & rect,WFlags<AlignmentFlag> alignmentFlags,const WString & text,int row)363 void WPieChart::drawLabel(WPainter* painter, const WRectF& rect,
364                          WFlags<AlignmentFlag> alignmentFlags,
365                          const WString& text, int row) const
366 {
367   painter->drawText(rect, alignmentFlags, text);
368 }
369 
labelText(int index,double v,double total,WFlags<LabelOption> options)370 WString WPieChart::labelText(int index, double v, double total,
371 			     WFlags<LabelOption> options) const
372 {
373   WString text;
374 
375   if (options.test(LabelOption::TextLabel))
376     if (labelsColumn_ != -1)
377       text += model()->displayData(index, labelsColumn_);
378 
379   if (options.test(LabelOption::TextPercentage)) {
380     std::string label;
381     double u = v / total * 100;
382 
383     std::string format = labelFormat().toUTF8();
384     if (format.empty())
385       label = WLocale::currentLocale().toString(u).toUTF8() + "%";
386     else {
387 #ifndef WT_TARGET_JAVA
388       char buf[30];
389 #else
390       char *buf = 0;
391 #endif
392 
393 #ifdef WT_TARGET_JAVA
394       buf =
395 #endif // WT_TARGET_JAVA
396 	std::sprintf(buf, format.c_str(), u);
397       label = buf;
398     }
399 
400     if (!text.empty())
401       text += ": ";
402     text += WString::fromUTF8(label);
403   }
404 
405   return text;
406 }
407 
setShadow(WPainter & painter)408 void WPieChart::setShadow(WPainter& painter) const
409 {
410   painter.setShadow(WShadow(5, 15, WColor(0, 0, 0, 20), 40));
411 }
412 
drawPie(WPainter & painter,double cx,double cy,double r,double h,double total)413 void WPieChart::drawPie(WPainter& painter, double cx, double cy,
414 			double r, double h, double total) const
415 {
416   /*
417    * Draw sides where applicable
418    */
419   if (h > 0) {
420     if (total == 0) {
421       if (shadow_)
422 	setShadow(painter);
423       drawOuter(painter, cx, cy, r, 0, -180, h);
424       if (shadow_)
425 	painter.setShadow(WShadow());
426     } else {
427       if (shadow_) {
428 	setShadow(painter);
429 	painter.setBrush(WBrush(StandardColor::Black));
430 	drawSlices(painter, cx, cy + h, r, total, true);
431 	painter.setShadow(WShadow());
432       }
433 
434       /*
435        * Pre-processing: determine start and mid angles of each pie,
436        * and get the index of the one that contains 90 degrees (which is
437        * the one at the back
438        */
439 #ifndef WT_TARGET_JAVA
440       std::vector<double> startAngles(model()->rowCount());
441       std::vector<double> midAngles(model()->rowCount());
442 #else
443       std::vector<double> startAngles, midAngles;
444       startAngles.insert(startAngles.end(), model()->rowCount(), 0.0);
445       midAngles.insert(midAngles.end(), model()->rowCount(), 0.0);
446 #endif // WT_TARGET_JAVA
447 
448       int index90 = 0;
449 
450       double currentAngle = startAngle_;
451       for (int i = 0; i < model()->rowCount(); ++i) {
452 	startAngles[i] = currentAngle;
453 
454 	double v = model()->data(i, dataColumn_);
455 	if (Utils::isNaN(v))
456 	  continue;
457 
458 	double spanAngle = -v / total * 360;
459 	midAngles[i] = currentAngle + spanAngle / 2.0;
460 
461 	double endAngle = currentAngle + spanAngle;
462 
463 	double to90 = currentAngle - 90;
464 	if (to90 < 0)
465 	  to90 += 360;
466 
467 	if (spanAngle <= -to90)
468 	  index90 = i;
469 
470 	if (endAngle < 0)
471 	  endAngle += 360;
472 
473 	currentAngle = endAngle;
474       }
475 
476       /*
477        * Draw clock wise side
478        */
479       for (int j = 0; j < model()->rowCount(); ++j) {
480 	int i = (index90 + j) % model()->rowCount();
481 
482 	double v = model()->data(i, dataColumn_);
483 	if (Utils::isNaN(v))
484 	  continue;
485 
486 	double midAngle = midAngles[i];
487 	double endAngle = startAngles[(i + 1) % model()->rowCount()];
488 
489 	int n = nextIndex(i);
490 
491 	bool visible = (endAngle <= 90) || (endAngle >= 270);
492 
493 	bool drawS2 = visible
494 	  && ((pie_[i].explode > 0.0) || (pie_[n].explode > 0.0));
495 
496 	if (drawS2) {
497 	  double pcx = cx + r * pie_[i].explode * std::cos(-midAngle / 180.0 * M_PI);
498 	  double pcy = cy + r * pie_[i].explode * std::sin(-midAngle / 180.0 * M_PI);
499 
500 	  painter.setBrush(darken(brush(i)));
501 
502 	  drawSide(painter, pcx, pcy, r, endAngle, h);
503 	}
504 
505 	if (!visible)
506 	  break;
507       }
508 
509       /*
510        * Draw counter-clock wise side
511        */
512       for (int j = model()->rowCount(); j > 0; --j) {
513 	int i = (index90 + j) % model()->rowCount();
514 
515 	double v = model()->data(i, dataColumn_);
516 	if (Utils::isNaN(v))
517 	  continue;
518 
519 	double startAngle = startAngles[i];
520 	double midAngle = midAngles[i];
521 
522 	int p = prevIndex(i);
523 
524 	bool visible = (startAngle >= 90) && (startAngle <= 270);
525 
526 	bool drawS1 = visible
527 	  && ((pie_[i].explode > 0.0) || (pie_[p].explode > 0.0));
528 
529 	if (drawS1) {
530 	  double pcx = cx + r * pie_[i].explode * std::cos(-midAngle / 180.0 * M_PI);
531 	  double pcy = cy + r * pie_[i].explode * std::sin(-midAngle / 180.0 * M_PI);
532 
533 	  painter.setBrush(darken(brush(i)));
534 	  drawSide(painter, pcx, pcy, r, startAngle, h);
535 	}
536 
537 	if (!visible)
538 	  break;
539       }
540 
541       /*
542        * LabelOption::Outside
543        */
544       for (int j = 0; j < model()->rowCount(); ++j) {
545 	int i = (index90 + j) % model()->rowCount();
546 
547 	double v = model()->data(i, dataColumn_);
548 	if (Utils::isNaN(v))
549 	  continue;
550 
551 	double startAngle = startAngles[i];
552 	double midAngle = midAngles[i];
553 	double endAngle = startAngles[(i + 1) % model()->rowCount()];
554 
555 	double spanAngle = endAngle - startAngle;
556 
557 	if (spanAngle > 0)
558 	  spanAngle -= 360;
559 
560 	bool drawBorder = startAngle > 180 || endAngle > 180
561 	  || spanAngle < -180 || model()->rowCount() == 1;
562 
563 	if (drawBorder) {
564 	  painter.setBrush(darken(brush(i)));
565 
566 	  double pcx = cx + r * pie_[i].explode * std::cos(-midAngle / 180.0 * M_PI);
567 	  double pcy = cy + r * pie_[i].explode * std::sin(-midAngle / 180.0 * M_PI);
568 
569 	  double a1 = (startAngle < 180 ? 360 : startAngle);
570 	  double a2 = (endAngle < 180 ? 180 : endAngle);
571 
572 	  drawOuter(painter, pcx, pcy, r, a1, a2, h);
573 	}
574       }
575     }
576   }
577 
578   /*
579    * Draw top
580    */
581   if (total == 0)
582     painter.drawArc(cx - r, cy - r, r*2, r*2, 0, 16*360);
583   else
584     drawSlices(painter, cx, cy, r, total, false);
585 }
586 
drawSlices(WPainter & painter,double cx,double cy,double r,double total,bool shadow)587 void WPieChart::drawSlices(WPainter& painter,
588 			   double cx, double cy, double r, double total,
589 			   bool shadow) const
590 {
591   double currentAngle = startAngle_;
592 
593   for (int i = 0; i < model()->rowCount(); ++i) {
594     double v = model()->data(i, dataColumn_);
595     if (Utils::isNaN(v))
596       continue;
597 
598     double spanAngle = -v / total * 360;
599     double midAngle = currentAngle + spanAngle / 2.0;
600 
601     double pcx = cx + r * pie_[i].explode * std::cos(-midAngle / 180.0 * M_PI);
602     double pcy = cy + r * pie_[i].explode * std::sin(-midAngle / 180.0 * M_PI);
603 
604     if (!shadow)
605       painter.setBrush(brush(i));
606 
607     if (v/total != 1.0)
608       painter.drawPie(pcx - r, pcy - r, r*2, r*2,
609 		      static_cast<int>(currentAngle * 16),
610 		      static_cast<int>(spanAngle * 16));
611     else
612       painter.drawEllipse(pcx - r, pcy - r, r*2, r*2);
613 
614     /*
615      * See if we need to add an interactive area
616      */
617     if (!shadow) {
618       WString toolTip = model()->toolTip(i, dataColumn_);
619       WLink *link = model()->link(i, dataColumn_);
620       if (!toolTip.empty() || link) {
621         const int SEGMENT_ANGLE = 20;
622 
623 	std::unique_ptr<WPolygonArea> area(new WPolygonArea());
624 	WTransform t = painter.worldTransform();
625 
626 	area->addPoint(t.map(WPointF(pcx, pcy)));
627 
628 	double sa = std::fabs(spanAngle);
629 
630 	for (double d = 0; d < sa; d += SEGMENT_ANGLE) {
631 	  double a;
632 	  if (spanAngle < 0)
633 	    a = currentAngle - d;
634 	  else
635 	    a = currentAngle + d;
636 	  area->addPoint(t.map(WPointF(pcx + r * std::cos(-a / 180.0 * M_PI),
637 				       pcy + r * std::sin(-a / 180.0 * M_PI))));
638 	}
639 
640 	double a = currentAngle + spanAngle;
641 	area->addPoint(t.map(WPointF(pcx + r * std::cos(-a / 180.0 * M_PI),
642 				     pcy + r * std::sin(-a / 180.0 * M_PI))));
643 
644         area->setToolTip(toolTip);
645         if (link)
646           area->setLink(*link);
647 
648 	addDataPointArea(i, dataColumn_, std::move(area));
649       }
650     }
651 
652     double endAngle = currentAngle + spanAngle;
653     if (endAngle < 0)
654       endAngle += 360;
655 
656     currentAngle = endAngle;
657   }
658 }
659 
addDataPointArea(int row,int column,std::unique_ptr<WAbstractArea> area)660 void WPieChart::addDataPointArea(int row, int column,
661 				 std::unique_ptr<WAbstractArea> area) const
662 {
663   (const_cast<WPieChart *>(this))->addArea(std::move(area));
664 }
665 
darken(const WBrush & brush)666 WBrush WPieChart::darken(const WBrush& brush)
667 {
668   WBrush result = brush;
669   WColor c = result.color();
670 
671   c.setRgb(c.red() * 3/4, c.green() * 3/4, c.blue() * 3/4, c.alpha());
672 
673   result.setColor(c);
674 
675   return result;
676 }
677 
drawSide(WPainter & painter,double pcx,double pcy,double r,double angle,double h)678 void WPieChart::drawSide(WPainter& painter, double pcx, double pcy, double r,
679 			 double angle, double h) const
680 {
681   WPainterPath path;
682   path.arcMoveTo(pcx - r, pcy - r, 2 * r, 2 * r, angle);
683   path.lineTo(path.currentPosition().x(), path.currentPosition().y() + h);
684   path.lineTo(pcx, pcy + h);
685   path.lineTo(pcx, pcy);
686   path.closeSubPath();
687 
688   painter.drawPath(path);
689 }
690 
drawOuter(WPainter & painter,double pcx,double pcy,double r,double a1,double a2,double h)691 void WPieChart::drawOuter(WPainter& painter, double pcx, double pcy, double r,
692 			  double a1, double a2, double h) const
693 {
694   WPainterPath path;
695   path.arcMoveTo(pcx - r, pcy - r, 2 * r, 2 * r, a1);
696   path.lineTo(path.currentPosition().x(), path.currentPosition().y() + h);
697   path.arcTo(pcx, pcy + h, r, a1, a2 - a1);
698   path.arcTo(pcx, pcy, r, a2, a1 - a2);
699   path.closeSubPath();
700 
701   painter.drawPath(path);
702 }
703 
paintEvent(WPaintDevice * paintDevice)704 void WPieChart::paintEvent(WPaintDevice *paintDevice)
705 {
706   const std::vector<WAbstractArea *> allAreas = areas();
707   for (auto area : allAreas) {
708     removeArea(area);
709   }
710 
711   WPainter painter(paintDevice);
712   painter.setRenderHint(RenderHint::Antialiasing, true);
713   paint(painter);
714 }
715 
nextIndex(int i)716 int WPieChart::nextIndex(int i) const
717 {
718   int r = model()->rowCount();
719   for (int n = (i + 1) % r; n != i; n = (n + 1) % r) {
720     double v = model()->data(n, dataColumn_);
721     if (!Utils::isNaN(v))
722       return n;
723   }
724 
725   return i;
726 }
727 
prevIndex(int i)728 int WPieChart::prevIndex(int i) const
729 {
730   int r = model()->rowCount();
731   for (int p = i - 1; p != i; --p) {
732     if (p < 0)
733       p += r;
734     double v = model()->data(p, dataColumn_);
735     if (!Utils::isNaN(v))
736       return p;
737   }
738 
739   return i;
740 }
741 
modelReset()742 void WPieChart::modelReset()
743 {
744   if (model()->rowCount() != (int)pie_.size())
745     modelChanged();
746   else
747     update();
748 }
749 
modelChanged()750 void WPieChart::modelChanged()
751 {
752   pie_.clear();
753   pie_.insert(pie_.begin(), model()->rowCount(), PieData());
754 
755   update();
756 }
757 
758   }
759 }
760