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