1 // This may look like C code, but it's really -*- C++ -*-
2 /*
3  * Copyright (C) 2008 Emweb bv, Herent, Belgium.
4  *
5  * See the LICENSE file for terms of use.
6  */
7 #ifndef CHART_WPIE_CHART_H_
8 #define CHART_WPIE_CHART_H_
9 
10 #include <Wt/Chart/WAbstractChart.h>
11 
12 #include <Wt/WRectF.h>
13 
14 namespace Wt {
15 
16   class WPainter;
17 
18   namespace Chart {
19 
20     class WChartPalette;
21 
22 /*! \brief Enumeration that specifies options for the labels.
23  *
24  * \sa WPieChart::setDisplayLabels(WFlags<LabelOption>)
25  *
26  * \ingroup charts
27  */
28 enum class LabelOption {
29   None           = 0x00, //!< Do not display labels (default).
30   Inside         = 0x01, //!< Display labels inside each segment.
31   Outside        = 0x02, //!< Display labels outside each segment.
32   TextLabel      = 0x10, //!< Display the label text
33   TextPercentage = 0x20  //!< Display the value (as percentage)
34 };
35 
W_DECLARE_OPERATORS_FOR_FLAGS(LabelOption)36 W_DECLARE_OPERATORS_FOR_FLAGS(LabelOption)
37 
38 /*! \class WPieChart Wt/Chart/WPieChart.h Wt/Chart/WPieChart.h
39  *  \brief A pie chart.
40  *
41  * A pie chart renders a single data series as segments of a circle, so that
42  * the area of each segment is proportional to the value in the data series.
43  *
44  * To use a pie chart, you need to set a model using setModel(), and use
45  * setLabelsColumn() and setDataColumn() to specify the model column that
46  * contains the category labels and data.
47  *
48  * The pie chart may be customized visually by enabling a 3D effect
49  * (setPerspectiveEnabled()), or by specifying the angle of the first
50  * segment. One or more segments may be exploded, which separates the
51  * segment from the rest of the pie chart, using setExplode().
52  *
53  * The segments may be labeled in various ways using
54  * setDisplayLabels().
55  *
56  * <h3>CSS</h3>
57  *
58  * Styling through CSS is not applicable.
59  *
60  * \image html ChartWPieChart-1.png "Example of a pie chart"
61  *
62  * \sa WCartesianChart
63  *
64  * \ingroup charts modelview
65  */
66 class WT_API WPieChart : public WAbstractChart
67 {
68 public:
69   /*! \brief Creates a new pie chart.
70    */
71   WPieChart();
72 
73   /*! \brief Sets the model column that holds the labels.
74    *
75    * The labels are used only when setDisplayLabels() is called with
76    * the \link Chart::LabelOption::TextLabel LabelOption::TextLabel\endlink option.
77    *
78    * The default value is -1 (not defined).
79    *
80    * \sa setModel(WAbstractItemModel *), setDisplayLabels(), setDataColumn(int)
81    */
82   void setLabelsColumn(int column);
83 
84   /*! \brief Returns the model column used for the labels.
85    *
86    * \sa setLabelsColumn(int)
87    */
88   int labelsColumn() const { return labelsColumn_; }
89 
90   /*! \brief Sets the model column that holds the data.
91    *
92    * \if cpp
93    * The data column should contain data that can be converted to
94    * a number, but should not necessarily be of a number type, see
95    * also asNumber(const boost::any&).
96    * \elseif java
97    * The data column should contain data that can be converted to
98    * a number, but should not necessarily be of a number type, see
99    * also {javadoclink StringUtils#asNumber(Object)}.
100    * \endif
101    *
102    * The default value is -1 (not defined).
103    *
104    * \sa setModel(WAbstractItemModel *), setLabelsColumn(int)
105    */
106   void setDataColumn(int modelColumn);
107 
108   /*! \brief Returns the model column used for the data.
109    *
110    * \sa setDataColumn(int)
111    */
112   int dataColumn() const { return dataColumn_; }
113 
114   /*! \brief Customizes the brush used for a pie segment.
115    *
116    * By default, the brush is taken from the palette(). You can use
117    * this method to override the palette's brush for a particular
118    * <i>modelRow</i>.
119    *
120    * \sa setPalette(WChartPalette *)
121    */
122   void setBrush(int modelRow, const WBrush& brush);
123 
124   /*! \brief Returns the brush used for a pie segment.
125    *
126    * \sa setBrush(int, const WBrush&)
127    */
128   WBrush brush(int modelRow) const;
129 
130   /*! \brief Sets the explosion factor for a pie segment.
131    *
132    * Separates the segment corresponding to model row <i>modelRow</i>
133    * from the rest of the pie. The <i>factor</i> is a positive number
134    * that represents the distance from the center as a fraction of the
135    * pie radius. Thus, 0 corresponds to no separation, and 0.1 to a
136    * 10% separation, and 1 to a separation where the segment tip is on
137    * the outer perimeter of the pie.
138    *
139    * The default value is 0.
140    */
141   void setExplode(int modelRow, double factor);
142 
143   /*! \brief Returns the explosion factor for a segment.
144    *
145    * \sa setExplode(int, double)
146    */
147   double explode(int modelRow) const;
148 
149   /*! \brief Enables a 3D perspective effect on the pie.
150    *
151    * A 3D perspective effect is added, which may be customized by
152    * specifying the simulated <i>height</i> of the pie. The height is
153    * defined as a fraction of the pie radius.
154    *
155    * The default value is false.
156    */
157   void setPerspectiveEnabled(bool enabled, double height = 1.0);
158 
159   /*! \brief Returns whether a 3D effect is enabled.
160    *
161    * \sa setPerspectiveEnabled(bool, double)
162    */
163   bool isPerspectiveEnabled() const { return height_ > 0.0; }
164 
165   /*! \brief Enables a shadow effect.
166    *
167    * A soft shadow effect is added.
168    *
169    * The default value is false.
170    */
171   void setShadowEnabled(bool enabled);
172 
173   /*! \brief Returns whether a shadow effect is enabled.
174    *
175    * \sa setShadowEnabled()
176    */
177   bool isShadowEnabled() const { return shadow_; }
178 
179   /*! \brief Sets the angle of the first segment.
180    *
181    * The default value is 45 degrees.
182    */
183   void setStartAngle(double degrees);
184 
185   /*! \brief Returns the angle of the first segment.
186    *
187    * \sa setStartAngle(double)
188    */
189   double startAngle() const { return startAngle_; }
190 
191   /*! \brief Sets the percentage value to avoid rendering of label texts.
192    *
193    * The default value is 0 percent.
194    */
195   void setAvoidLabelRendering(double percent);
196 
197   /*! \brief Returns the percentage to avoid label rendering.
198    *
199    * \sa setAvoidLabelRendering(double)
200    */
201   double avoidLabelRendering() const { return avoidLabelRendering_; }
202 
203   /*! \brief Configures if and how labels should be displayed
204    *
205    * The <i>options</i> must be the logical OR of a placement option
206    * (\link Chart::LabelOption::Inside LabelOption::Inside\endlink or \link
207    * Chart::LabelOption::Outside LabelOption::Outside\endlink) and \link
208    * Chart::LabelOption::TextLabel LabelOption::TextLabel\endlink and/or \link
209    * Chart::LabelOption::TextPercentage LabelOption::TextPercentage\endlink. If both
210    * LabelOption::TextLabel and LabelOption::TextPercentage are specified, then these are
211    * combined as "<label>: <percentage>".
212    *
213    * The default value is \link Chart::LabelOption::None LabelOption::None\endlink.
214    */
215   void setDisplayLabels(WFlags<LabelOption> options);
216 
217   /*! \brief Returns options set for displaying labels.
218    *
219    * \sa WPieChart::setDisplayLabels()
220    */
221   WFlags<LabelOption> displayLabels() const { return labelOptions_; }
222 
223   /*! \brief Sets the label format.
224    *
225    * Sets a format string which is used to format label (percentage)
226    * values.
227    *
228    * The format string must be a format string that is accepted by
229    * snprintf() and which formats one double.
230    * \if cpp
231    * If the format string is an empty string, then WLocale::toString() is used.
232    * \endif
233    *
234    * The default value is "%.3g%%".
235    *
236    * \sa labelFormat()
237    */
238   void setLabelFormat(const WString& format);
239 
240   /*! \brief Returns the label format string.
241    *
242    * \sa setLabelFormat()
243    */
244   WString labelFormat() const;
245 
246   /*! \brief Creates a widget which renders the a legend item.
247    *
248    * Depending on the passed LabelOption flags, the legend item widget,
249    * will contain a text (with or without the percentage) and/or a span with
250    * the segment's color.
251    */
252   std::unique_ptr<WWidget> createLegendItemWidget(int index,
253 						  WFlags<LabelOption> options);
254 
255   /*! \brief Adds a data point area (used for displaying e.g. tooltips).
256    *
257    * You may want to specialize this is if you wish to modify (or delete)
258    * the area.
259    *
260    * \note Currently, an area is only created if the ItemDataRole::ToolTip data at the
261    *       data point is not empty.
262    */
263   virtual void addDataPointArea(int row, int column,
264                                 std::unique_ptr<WAbstractArea> area) const;
265 
266   /**
267    * @brief createLabelWidget possition textWidget where the text would be
268    * rendered.
269    * Assuming that textWidget is added to a container with same dimensions as
270    * the WPieChart.
271    * This should be used in combinaltion with drawLabel().
272    *
273    * @return The new WContainerWidget that contains textWidget and can be placed
274    * on an other layer that has the same dimensions as the WPieChart.
275    * \sa drawLabel()
276    *
277    * \if cpp
278    * Usage example, PieChart with label links.
279    * \code
280    * class PChart : public Wt::Chart::WPieChart {
281    * private:
282    *   Wt::WContainerWidget *widgetsLayer_;
283    * public:
284    *   PChart(Wt::WContainerWidget *widgetsLayer) :
285    *     widgetsLayer_(widgetsLayer)
286    *   {
287    *     widgetsLayer_->resize(800, 300);
288    *     resize(800, 300);
289    *     widgetsLayer_->setPositionScheme(Wt::PositionScheme::Relative);
290    *   }
291    *   virtual void drawLabel(Wt::WPainter* painter, const Wt::WRectF& rect,
292    *                            Wt::WFlags<Wt::AlignmentFlag> alignmentFlags,
293    *                            const Wt::WString& text, int row) const
294    *   {
295    *     if (model()->link(row, dataColumn()) == 0) {
296    *       WPieChart::drawLabel(painter, rect, alignmentFlags, text, row);
297    *     } else {
298    *       auto a = std::make_unique<Wt::WAnchor>(
299    *                  *model()->link(row, dataColumn()), text);
300    *       widgetsLayer_->addWidget(createLabelWidget(std::move(a), painter, rect, alignmentFlags));
301    *     }
302    *   }
303    * };
304    * \endcode
305    * \endif
306    */
307   virtual std::unique_ptr<WContainerWidget> createLabelWidget(std::unique_ptr<WWidget> textWidget,
308       WPainter* painter, const WRectF& rect,
309       Wt::WFlags<AlignmentFlag> alignmentFlags) const;
310 
311   virtual void paint(WPainter& painter, const WRectF& rectangle = WRectF())
312     const override;
313 
314 protected:
315   void paintEvent(Wt::WPaintDevice *paintDevice) override;
316 
317   /**
318    * @brief drawLabel draw a label on the chart. Will be called by paint.
319    *
320    * You may want to specialize this if you wish to replace the label by
321    * a widget.
322    * \sa createLabelWidget() if you wish to replace the label by a Widget.
323    *
324    */
325   virtual void drawLabel(WPainter* painter, const WRectF& rect,
326                            WFlags<AlignmentFlag> alignmentFlags,
327                            const WString& text, int row) const;
328 
329 private:
330   int                  labelsColumn_;
331   int                  dataColumn_;
332   double               height_;
333   double               startAngle_;
334   double               avoidLabelRendering_;
335   WFlags<LabelOption>  labelOptions_;
336   bool                 shadow_;
337   WString              labelFormat_;
338 
339   struct PieData {
340     bool customBrush;
341     WBrush brush;
342     double explode;
343 
344     PieData();
345   };
346 
347   std::vector<PieData> pie_;
348 
349 protected:
350   virtual void modelChanged() override;
351   virtual void modelReset() override;
352 
353 private:
354   void drawPie(WPainter& painter, double cx, double cy, double r, double h,
355 	       double total) const;
356   void drawSlices(WPainter& painter, double cx, double cy, double r,
357 		  double total, bool ignoreBrush) const;
358   void drawSide(WPainter& painter, double pcx, double pcy, double r,
359 		double angle, double h) const;
360   void drawOuter(WPainter& painter, double pcx, double pcy, double r,
361 		 double a1, double a2, double h) const;
362 
363   void setShadow(WPainter& painter) const;
364 
365   int prevIndex(int i) const;
366   int nextIndex(int i) const;
367 
368   static WBrush darken(const WBrush& brush);
369 
370   WString labelText(int index, double v, double total,
371 		    WFlags<LabelOption> options) const;
372 };
373 
374   }
375 }
376 
377 #endif // CHART_WPIE_CHART_H_
378