##// END OF EJS Templates
Fix clicked, released and doubleClicked signal points...
Titta Heikkala -
r2746:4909289ecbd7
parent child
Show More
@@ -1,283 +1,283
1 1 /****************************************************************************
2 2 **
3 3 ** Copyright (C) 2014 Digia Plc
4 4 ** All rights reserved.
5 5 ** For any questions to Digia, please use contact form at http://qt.io
6 6 **
7 7 ** This file is part of the Qt Charts module.
8 8 **
9 9 ** Licensees holding valid commercial license for Qt may use this file in
10 10 ** accordance with the Qt License Agreement provided with the Software
11 11 ** or, alternatively, in accordance with the terms contained in a written
12 12 ** agreement between you and Digia.
13 13 **
14 14 ** If you have questions regarding the use of this file, please use
15 15 ** contact form at http://qt.io
16 16 **
17 17 ****************************************************************************/
18 18
19 19 #include <private/areachartitem_p.h>
20 20 #include <QtCharts/QAreaSeries>
21 21 #include <private/qareaseries_p.h>
22 22 #include <QtCharts/QLineSeries>
23 23 #include <private/chartpresenter_p.h>
24 24 #include <private/abstractdomain_p.h>
25 25 #include <QtGui/QPainter>
26 26 #include <QtWidgets/QGraphicsSceneMouseEvent>
27 27 #include <QtCore/QDebug>
28 28
29 29
30 30 QT_CHARTS_BEGIN_NAMESPACE
31 31
32 32 AreaChartItem::AreaChartItem(QAreaSeries *areaSeries, QGraphicsItem* item)
33 33 : ChartItem(areaSeries->d_func(),item),
34 34 m_series(areaSeries),
35 35 m_upper(0),
36 36 m_lower(0),
37 37 m_pointsVisible(false),
38 38 m_pointLabelsVisible(false),
39 39 m_pointLabelsFormat(areaSeries->pointLabelsFormat()),
40 40 m_pointLabelsFont(areaSeries->pointLabelsFont()),
41 41 m_pointLabelsColor(areaSeries->pointLabelsColor()),
42 42 m_mousePressed(false)
43 43 {
44 44 setAcceptHoverEvents(true);
45 45 setFlag(QGraphicsItem::ItemIsSelectable, true);
46 46 setZValue(ChartPresenter::LineChartZValue);
47 47 if (m_series->upperSeries())
48 48 m_upper = new AreaBoundItem(this, m_series->upperSeries());
49 49 if (m_series->lowerSeries())
50 50 m_lower = new AreaBoundItem(this, m_series->lowerSeries());
51 51
52 52 QObject::connect(m_series->d_func(), SIGNAL(updated()), this, SLOT(handleUpdated()));
53 53 QObject::connect(m_series, SIGNAL(visibleChanged()), this, SLOT(handleUpdated()));
54 54 QObject::connect(m_series, SIGNAL(opacityChanged()), this, SLOT(handleUpdated()));
55 55 QObject::connect(this, SIGNAL(clicked(QPointF)), areaSeries, SIGNAL(clicked(QPointF)));
56 56 QObject::connect(this, SIGNAL(hovered(QPointF,bool)), areaSeries, SIGNAL(hovered(QPointF,bool)));
57 57 QObject::connect(this, SIGNAL(pressed(QPointF)), areaSeries, SIGNAL(pressed(QPointF)));
58 58 QObject::connect(this, SIGNAL(released(QPointF)), areaSeries, SIGNAL(released(QPointF)));
59 59 QObject::connect(this, SIGNAL(doubleClicked(QPointF)),
60 60 areaSeries, SIGNAL(doubleClicked(QPointF)));
61 61 QObject::connect(areaSeries, SIGNAL(pointLabelsFormatChanged(QString)),
62 62 this, SLOT(handleUpdated()));
63 63 QObject::connect(areaSeries, SIGNAL(pointLabelsVisibilityChanged(bool)),
64 64 this, SLOT(handleUpdated()));
65 65 QObject::connect(areaSeries, SIGNAL(pointLabelsFontChanged(QFont)),
66 66 this, SLOT(handleUpdated()));
67 67 QObject::connect(areaSeries, SIGNAL(pointLabelsColorChanged(QColor)),
68 68 this, SLOT(handleUpdated()));
69 69
70 70 handleUpdated();
71 71 }
72 72
73 73 AreaChartItem::~AreaChartItem()
74 74 {
75 75 delete m_upper;
76 76 delete m_lower;
77 77 }
78 78
79 79 void AreaChartItem::setPresenter(ChartPresenter *presenter)
80 80 {
81 81 if (m_upper)
82 82 m_upper->setPresenter(presenter);
83 83 if (m_lower) {
84 84 m_lower->setPresenter(presenter);
85 85 }
86 86 ChartItem::setPresenter(presenter);
87 87 }
88 88
89 89 QRectF AreaChartItem::boundingRect() const
90 90 {
91 91 return m_rect;
92 92 }
93 93
94 94 QPainterPath AreaChartItem::shape() const
95 95 {
96 96 return m_path;
97 97 }
98 98
99 99 void AreaChartItem::updatePath()
100 100 {
101 101 QPainterPath path;
102 102 QRectF rect(QPointF(0,0),domain()->size());
103 103
104 104 path = m_upper->path();
105 105
106 106 if (m_lower) {
107 107 // Note: Polarcharts always draw area correctly only when both series have equal width or are
108 108 // fully displayed. If one series is partally off-chart, the connecting line between
109 109 // the series does not attach to the end of the partially hidden series but to the point
110 110 // where it intersects the axis line. The problem is especially noticeable when one of the series
111 111 // is entirely off-chart, in which case the connecting line connects two ends of the
112 112 // visible series.
113 113 // This happens because we get the paths from linechart, which omits off-chart segments.
114 114 // To properly fix, linechart would need to provide true full path, in right, left, and the rest
115 115 // portions to enable proper clipping. However, combining those to single visually unified area
116 116 // would be a nightmare, since they would have to be painted separately.
117 117 path.connectPath(m_lower->path().toReversed());
118 118 } else {
119 119 QPointF first = path.pointAtPercent(0);
120 120 QPointF last = path.pointAtPercent(1);
121 121 if (presenter()->chartType() == QChart::ChartTypeCartesian) {
122 122 path.lineTo(last.x(), rect.bottom());
123 123 path.lineTo(first.x(), rect.bottom());
124 124 } else { // polar
125 125 path.lineTo(rect.center());
126 126 }
127 127 }
128 128 path.closeSubpath();
129 129
130 130 // Only zoom in if the bounding rect of the path fits inside int limits. QWidget::update() uses
131 131 // a region that has to be compatible with QRect.
132 132 if (path.boundingRect().height() <= INT_MAX
133 133 && path.boundingRect().width() <= INT_MAX) {
134 134 prepareGeometryChange();
135 135 m_path = path;
136 136 m_rect = path.boundingRect();
137 137 update();
138 138 }
139 139 }
140 140
141 141 void AreaChartItem::handleUpdated()
142 142 {
143 143 setVisible(m_series->isVisible());
144 144 m_pointsVisible = m_series->pointsVisible();
145 145 m_linePen = m_series->pen();
146 146 m_brush = m_series->brush();
147 147 m_pointPen = m_series->pen();
148 148 m_pointPen.setWidthF(2 * m_pointPen.width());
149 149 setOpacity(m_series->opacity());
150 150 m_pointLabelsFormat = m_series->pointLabelsFormat();
151 151 m_pointLabelsVisible = m_series->pointLabelsVisible();
152 152 m_pointLabelsFont = m_series->pointLabelsFont();
153 153 m_pointLabelsColor = m_series->pointLabelsColor();
154 154 update();
155 155 }
156 156
157 157 void AreaChartItem::handleDomainUpdated()
158 158 {
159 159 if (m_upper) {
160 160 AbstractDomain* d = m_upper->domain();
161 161 d->setSize(domain()->size());
162 162 d->setRange(domain()->minX(),domain()->maxX(),domain()->minY(),domain()->maxY());
163 163 m_upper->handleDomainUpdated();
164 164 }
165 165
166 166 if (m_lower) {
167 167 AbstractDomain* d = m_lower->domain();
168 168 d->setSize(domain()->size());
169 169 d->setRange(domain()->minX(),domain()->maxX(),domain()->minY(),domain()->maxY());
170 170 m_lower->handleDomainUpdated();
171 171 }
172 172 }
173 173
174 174 void AreaChartItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
175 175 {
176 176 Q_UNUSED(widget)
177 177 Q_UNUSED(option)
178 178 painter->save();
179 179 painter->setPen(m_linePen);
180 180 painter->setBrush(m_brush);
181 181 QRectF clipRect = QRectF(QPointF(0, 0), domain()->size());
182 182 if (presenter()->chartType() == QChart::ChartTypePolar)
183 183 painter->setClipRegion(QRegion(clipRect.toRect(), QRegion::Ellipse));
184 184 else
185 185 painter->setClipRect(clipRect);
186 186 painter->drawPath(m_path);
187 187 if (m_pointsVisible) {
188 188 painter->setPen(m_pointPen);
189 189 painter->drawPoints(m_upper->geometryPoints());
190 190 if (m_lower)
191 191 painter->drawPoints(m_lower->geometryPoints());
192 192 }
193 193
194 194 // Draw series point label
195 195 if (m_pointLabelsVisible) {
196 196 static const QString xPointTag(QLatin1String("@xPoint"));
197 197 static const QString yPointTag(QLatin1String("@yPoint"));
198 198 const int labelOffset = 2;
199 199
200 200 painter->setFont(m_pointLabelsFont);
201 201 painter->setPen(QPen(m_pointLabelsColor));
202 202 QFontMetrics fm(painter->font());
203 203
204 204 QString pointLabel = m_pointLabelsFormat;
205 205
206 206 if (m_series->upperSeries()) {
207 207 for (int i(0); i < m_series->upperSeries()->count(); i++) {
208 208 pointLabel.replace(xPointTag,
209 209 presenter()->numberToString(m_series->upperSeries()->at(i).x()));
210 210 pointLabel.replace(yPointTag,
211 211 presenter()->numberToString(m_series->upperSeries()->at(i).y()));
212 212
213 213 // Position text in relation to the point
214 214 int pointLabelWidth = fm.width(pointLabel);
215 215 QPointF position(m_upper->geometryPoints().at(i));
216 216 position.setX(position.x() - pointLabelWidth / 2);
217 217 position.setY(position.y() - m_series->upperSeries()->pen().width() / 2 - labelOffset);
218 218
219 219 painter->drawText(position, pointLabel);
220 220 }
221 221 }
222 222
223 223 if (m_series->lowerSeries()) {
224 224 for (int i(0); i < m_series->lowerSeries()->count(); i++) {
225 225 pointLabel.replace(xPointTag,
226 226 presenter()->numberToString(m_series->lowerSeries()->at(i).x()));
227 227 pointLabel.replace(yPointTag,
228 228 presenter()->numberToString(m_series->lowerSeries()->at(i).y()));
229 229
230 230 // Position text in relation to the point
231 231 int pointLabelWidth = fm.width(pointLabel);
232 232 QPointF position(m_lower->geometryPoints().at(i));
233 233 position.setX(position.x() - pointLabelWidth / 2);
234 234 position.setY(position.y() - m_series->lowerSeries()->pen().width() / 2 - labelOffset);
235 235
236 236 painter->drawText(position, pointLabel);
237 237 }
238 238 }
239 239 }
240 240
241 241 painter->restore();
242 242 }
243 243
244 244 void AreaChartItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
245 245 {
246 246 emit pressed(m_upper->domain()->calculateDomainPoint(event->pos()));
247 247 m_lastMousePos = event->pos();
248 248 m_mousePressed = true;
249 249 ChartItem::mousePressEvent(event);
250 250 }
251 251
252 252 void AreaChartItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
253 253 {
254 254 emit hovered(domain()->calculateDomainPoint(event->pos()), true);
255 255 event->accept();
256 256 // QGraphicsItem::hoverEnterEvent(event);
257 257 }
258 258
259 259 void AreaChartItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
260 260 {
261 261 emit hovered(domain()->calculateDomainPoint(event->pos()), false);
262 262 event->accept();
263 263 // QGraphicsItem::hoverEnterEvent(event);
264 264 }
265 265
266 266 void AreaChartItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
267 267 {
268 emit released(m_upper->domain()->calculateDomainPoint(event->pos()));
269 if (m_lastMousePos == event->pos() && m_mousePressed)
270 emit clicked(m_upper->domain()->calculateDomainPoint(event->pos()));
268 emit released(m_upper->domain()->calculateDomainPoint(m_lastMousePos));
269 if (m_mousePressed)
270 emit clicked(m_upper->domain()->calculateDomainPoint(m_lastMousePos));
271 271 m_mousePressed = false;
272 272 ChartItem::mouseReleaseEvent(event);
273 273 }
274 274
275 275 void AreaChartItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
276 276 {
277 emit doubleClicked(m_upper->domain()->calculateDomainPoint(event->pos()));
277 emit doubleClicked(m_upper->domain()->calculateDomainPoint(m_lastMousePos));
278 278 ChartItem::mouseDoubleClickEvent(event);
279 279 }
280 280
281 281 #include "moc_areachartitem_p.cpp"
282 282
283 283 QT_CHARTS_END_NAMESPACE
@@ -1,682 +1,695
1 1 /****************************************************************************
2 2 **
3 3 ** Copyright (C) 2014 Digia Plc
4 4 ** All rights reserved.
5 5 ** For any questions to Digia, please use contact form at http://qt.io
6 6 **
7 7 ** This file is part of the Qt Charts module.
8 8 **
9 9 ** Licensees holding valid commercial license for Qt may use this file in
10 10 ** accordance with the Qt License Agreement provided with the Software
11 11 ** or, alternatively, in accordance with the terms contained in a written
12 12 ** agreement between you and Digia.
13 13 **
14 14 ** If you have questions regarding the use of this file, please use
15 15 ** contact form at http://qt.io
16 16 **
17 17 ****************************************************************************/
18 18
19 19 #include <QtCharts/QAreaSeries>
20 20 #include <private/qareaseries_p.h>
21 21 #include <QtCharts/QLineSeries>
22 22 #include <private/areachartitem_p.h>
23 23 #include <private/abstractdomain_p.h>
24 24 #include <private/chartdataset_p.h>
25 25 #include <private/charttheme_p.h>
26 26 #include <QtCharts/QValueAxis>
27 27 #include <QtCharts/QAreaLegendMarker>
28 28 #include <private/qchart_p.h>
29 29
30 30 QT_CHARTS_BEGIN_NAMESPACE
31 31
32 32 /*!
33 33 \class QAreaSeries
34 34 \inmodule Qt Charts
35 35 \brief The QAreaSeries class is used for making area charts.
36 36
37 37 \mainclass
38 38
39 39 An area chart is used to show quantitative data. It is based on line chart, in the way that area between axis and the line
40 40 is emphasized with color. Since the area chart is based on line chart, QAreaSeries constructor needs QLineSeries instance,
41 41 which defines "upper" boundary of the area. "Lower" boundary is defined by default by axis X. Instead of axis X "lower" boundary can be specified by other line.
42 42 In that case QAreaSeries should be initiated with two QLineSeries instances. Please note terms "upper" and "lower" boundary can be misleading in cases
43 43 where "lower" boundary had bigger values than the "upper" one, however the main point that area between these two boundary lines will be filled.
44 44
45 45 See the \l {AreaChart Example} {area chart example} to learn how to create a simple area chart.
46 46 \image examples_areachart.png
47 47 */
48 48
49 49 /*!
50 50 \qmltype AreaSeries
51 51 \instantiates QAreaSeries
52 52 \inqmlmodule QtCharts
53 53
54 54 \inherits AbstractSeries
55 55
56 56 \brief The AreaSeries type is used for making area charts.
57 57
58 58 The following QML shows how to create a simple area chart:
59 59 \snippet qmlchart/qml/qmlchart/View4.qml 1
60 60 \beginfloatleft
61 61 \image examples_qmlchart4.png
62 62 \endfloat
63 63 \clearfloat
64 64 */
65 65
66 66 /*!
67 67 \property QAreaSeries::upperSeries
68 68 \brief The upper one of the two line series used to define area series boundaries.
69 69 */
70 70 /*!
71 71 \qmlproperty LineSeries AreaSeries::upperSeries
72 72 The upper one of the two line series used to define area series boundaries.
73 73 */
74 74
75 75 /*!
76 76 \property QAreaSeries::lowerSeries
77 77 The lower one of the two line series used to define are series boundaries. Note if
78 78 QAreaSeries was constructed without a\ lowerSeries this is null.
79 79 */
80 80 /*!
81 81 \qmlproperty LineSeries AreaSeries::lowerSeries
82 82 The lower one of the two line series used to define are series boundaries. Note if
83 83 AreaSeries was constructed without a\ lowerSeries this is null.
84 84 */
85 85
86 86 /*!
87 87 \property QAreaSeries::color
88 88 Fill (brush) color of the series. This is a convenience property for modifying the color of brush.
89 89 \sa QAreaSeries::brush()
90 90 */
91 91 /*!
92 92 \qmlproperty color AreaSeries::color
93 93 Fill (brush) color of the series.
94 94 */
95 95
96 96 /*!
97 97 \property QAreaSeries::borderColor
98 98 Line (pen) color of the series. This is a convenience property for modifying the color of pen.
99 99 \sa QAreaSeries::pen()
100 100 */
101 101 /*!
102 102 \qmlproperty color AreaSeries::borderColor
103 103 Line (pen) color of the series.
104 104 */
105 105
106 106 /*!
107 107 \qmlproperty real AreaSeries::borderWidth
108 108 The width of the border line. By default the width is 2.0.
109 109 */
110 110
111 111 /*!
112 112 \fn QPen QAreaSeries::pen() const
113 113 \brief Returns the pen used to draw line for this series.
114 114 \sa setPen()
115 115 */
116 116
117 117 /*!
118 118 \fn QPen QAreaSeries::brush() const
119 119 \brief Returns the brush used to draw line for this series.
120 120 \sa setBrush()
121 121 */
122 122
123 123 /*!
124 124 \qmlproperty QString AreaSeries::brushFilename
125 125 The name of the file used as a brush image for the series.
126 126 */
127 127
128 128 /*!
129 129 \fn void QAreaSeries::colorChanged(QColor color)
130 130 \brief Signal is emitted when the fill (brush) color has changed to \a color.
131 131 */
132 132 /*!
133 133 \qmlsignal AreaSeries::onColorChanged(color color)
134 134 Signal is emitted when the fill (brush) color has changed to \a color.
135 135 */
136 136
137 137 /*!
138 138 \fn void QAreaSeries::borderColorChanged(QColor color)
139 139 \brief Signal is emitted when the line (pen) color has changed to \a color.
140 140 */
141 141 /*!
142 142 \qmlsignal AreaSeries::onBorderColorChanged(color color)
143 143 Signal is emitted when the line (pen) color has changed to \a color.
144 144 */
145 145
146 146 /*!
147 147 \fn void QAreaSeries::clicked(const QPointF& point)
148 \brief Signal is emitted when user clicks the \a point on area chart.
148 \brief Signal is emitted when user clicks the \a point on area chart. The \a point is the point
149 where the press was triggered.
150 \sa pressed, released, doubleClicked
149 151 */
150 152 /*!
151 153 \qmlsignal AreaSeries::onClicked(QPointF point)
152 Signal is emitted when user clicks the \a point on area chart.
154 Signal is emitted when user clicks the \a point on area chart. The \a point is the point where
155 the press was triggered.
156 \sa onPressed, onReleased, onDoubleClicked
153 157 */
154 158
155 159 /*!
156 160 \fn void QAreaSeries::hovered(const QPointF &point, bool state)
157 161 This signal is emitted when user has hovered over or away from the series. \a point shows the origin (coordinate)
158 162 of the hover event. \a state is true when user has hovered over the series and false when hover has moved away from
159 163 the series.
160 164 */
161 165 /*!
162 166 \qmlsignal AreaSeries::onHovered(point point, bool state)
163 167 This signal is emitted when user has hovered over or away from the series. \a point shows the origin (coordinate)
164 168 of the hover event. \a state is true when user has hovered over the series and false when hover has moved away from
165 169 the series.
166 170 */
167 171
168 172 /*!
169 173 \fn void QAreaSeries::pressed(const QPointF& point)
170 174 \brief Signal is emitted when user presses the \a point on area chart.
175 \sa clicked, released, doubleClicked
171 176 */
172 177 /*!
173 178 \qmlsignal AreaSeries::onPressed(QPointF point)
174 179 Signal is emitted when user presses the \a point on area chart.
180 \sa onClicked, onReleased, onDoubleClicked
175 181 */
176 182
177 183 /*!
178 184 \fn void QAreaSeries::released(const QPointF& point)
179 \brief Signal is emitted when user releases the \a point on area chart.
185 \brief Signal is emitted when user releases a press that was triggered on a \a point on area
186 chart.
187 \sa pressed, clicked, doubleClicked
180 188 */
181 189 /*!
182 190 \qmlsignal AreaSeries::onReleased(QPointF point)
183 Signal is emitted when user releases the \a point on area chart.
191 Signal is emitted when user releases a press that was triggered on a \a point on area chart.
192 \sa onPressed, onClicked, onDoubleClicked
184 193 */
185 194
186 195 /*!
187 196 \fn void QAreaSeries::doubleClicked(const QPointF& point)
188 \brief Signal is emitted when user doubleclicks the \a point on area chart.
197 \brief Signal is emitted when user doubleclicks the \a point on area chart. The \a point is the
198 point where the first press was triggered.
199 \sa pressed, released, clicked
189 200 */
190 201 /*!
191 202 \qmlsignal AreaSeries::onDoubleClicked(QPointF point)
192 Signal is emitted when user doubleclicks the \a point on area chart.
203 Signal is emitted when user doubleclicks the \a point on area chart. The \a point is the point
204 where the first press was triggered.
205 \sa onPressed, onReleased, onClicked
193 206 */
194 207
195 208 /*!
196 209 \fn void QAreaSeries::selected()
197 210 The signal is emitted if the user selects/deselects the XY series. The logic for maintaining selections should be
198 211 implemented by the user of QAreaSeries API.
199 212 */
200 213 /*!
201 214 \qmlsignal AreaSeries::onSelected()
202 215 The signal is emitted if the user selects/deselects the XY series. The logic for maintaining selections should be
203 216 implemented by the user of AreaSeries API.
204 217 */
205 218
206 219 /*!
207 220 \fn void QAreaSeriesPrivate::updated()
208 221 \brief \internal
209 222 */
210 223
211 224 /*!
212 225 \property QAreaSeries::pointLabelsFormat
213 226 The \a format used for showing labels with series points.
214 227
215 228 QAreaSeries supports the following format tags:
216 229 \table
217 230 \row
218 231 \li @xPoint \li The x value of the data point
219 232 \row
220 233 \li @yPoint \li The y value of the data point
221 234 \endtable
222 235
223 236 For example, the following usage of the format tags would produce labels that have the data
224 237 point (x, y) shown inside brackets separated by a comma:
225 238 \code
226 239 series->setPointLabelsFormat("(@xPoint, @yPoint)");
227 240 \endcode
228 241
229 242 By default, the labels format is set to '@xPoint, @yPoint'. The labels are shown on the plot
230 243 area, labels on the edge of the plot area are cut. If the points are close to each other the
231 244 labels may overlap.
232 245
233 246 \sa QAreaSeries::pointLabelsVisible, QAreaSeries::pointLabelsFont, QAreaSeries::pointLabelsColor
234 247 */
235 248 /*!
236 249 \qmlproperty string AreaSeries::pointLabelsFormat
237 250 The \a format used for showing labels with series points.
238 251
239 252 \sa QAreaSeries::pointLabelsFormat, pointLabelsVisible, pointLabelsFont, pointLabelsColor
240 253 */
241 254 /*!
242 255 \fn void QAreaSeries::pointLabelsFormatChanged(const QString &format)
243 256 Signal is emitted when the \a format of data point labels is changed.
244 257 */
245 258 /*!
246 259 \qmlsignal AreaSeries::onPointLabelsFormatChanged(string format)
247 260 Signal is emitted when the \a format of data point labels is changed.
248 261 */
249 262
250 263 /*!
251 264 \property QAreaSeries::pointLabelsVisible
252 265 Defines the visibility for data point labels. False by default.
253 266
254 267 \sa QAreaSeries::pointLabelsFormat
255 268 */
256 269 /*!
257 270 \qmlproperty bool AreaSeries::pointLabelsVisible
258 271 Defines the visibility for data point labels.
259 272
260 273 \sa pointLabelsFormat
261 274 */
262 275 /*!
263 276 \fn void QAreaSeries::pointLabelsVisibilityChanged(bool visible)
264 277 The visibility of the data point labels is changed to \a visible.
265 278 */
266 279 /*!
267 280 \qmlsignal AreaSeries::onPointLabelsVisibilityChanged(bool visible)
268 281 The visibility of the data point labels is changed to \a visible.
269 282 */
270 283
271 284 /*!
272 285 \property QAreaSeries::pointLabelsFont
273 286 Defines the font used for data point labels.
274 287
275 288 \sa QAreaSeries::pointLabelsFormat
276 289 */
277 290 /*!
278 291 \qmlproperty font AreaSeries::pointLabelsFont
279 292 Defines the font used for data point labels.
280 293
281 294 \sa pointLabelsFormat
282 295 */
283 296 /*!
284 297 \fn void QAreaSeries::pointLabelsFontChanged(const QFont &font);
285 298 The font used for data point labels is changed to \a font.
286 299 */
287 300 /*!
288 301 \qmlsignal AreaSeries::onPointLabelsFontChanged(Font font)
289 302 The font used for data point labels is changed to \a font.
290 303 */
291 304
292 305 /*!
293 306 \property QAreaSeries::pointLabelsColor
294 307 Defines the color used for data point labels. By default, the color is the color of the brush
295 308 defined in theme for labels.
296 309
297 310 \sa QAreaSeries::pointLabelsFormat
298 311 */
299 312 /*!
300 313 \qmlproperty font AreaSeries::pointLabelsColor
301 314 Defines the color used for data point labels. By default, the color is the color of the brush
302 315 defined in theme for labels.
303 316
304 317 \sa pointLabelsFormat
305 318 */
306 319 /*!
307 320 \fn void QAreaSeries::pointLabelsColorChanged(const QColor &color);
308 321 The color used for data point labels is changed to \a color.
309 322 */
310 323 /*!
311 324 \qmlsignal AreaSeries::onPointLabelsColorChanged(Color color)
312 325 The color used for data point labels is changed to \a color.
313 326 */
314 327
315 328 /*!
316 329 Constructs area series object which is a child of \a upperSeries. Area will be spanned between \a
317 330 upperSeries line and \a lowerSeries line. If no \a lowerSeries is passed to constructor, area is specified by axis x (y=0) instead.
318 331 When series object is added to QChartView or QChart instance ownerships is transferred.
319 332 */
320 333 QAreaSeries::QAreaSeries(QLineSeries *upperSeries, QLineSeries *lowerSeries)
321 334 : QAbstractSeries(*new QAreaSeriesPrivate(upperSeries, lowerSeries, this), upperSeries)
322 335 {
323 336 }
324 337
325 338 /*!
326 339 Constructs area series object without upper or lower series with \a parent object.
327 340 */
328 341 QAreaSeries::QAreaSeries(QObject *parent)
329 342 : QAbstractSeries(*new QAreaSeriesPrivate(0, 0, this), parent)
330 343 {
331 344 }
332 345
333 346 /*!
334 347 Destroys the object.
335 348 */
336 349 QAreaSeries::~QAreaSeries()
337 350 {
338 351 Q_D(QAreaSeries);
339 352 if (d->m_chart)
340 353 d->m_chart->removeSeries(this);
341 354 }
342 355
343 356 /*!
344 357 Returns QAbstractSeries::SeriesTypeArea.
345 358 */
346 359 QAbstractSeries::SeriesType QAreaSeries::type() const
347 360 {
348 361 return QAbstractSeries::SeriesTypeArea;
349 362 }
350 363
351 364 /*!
352 365 Sets the \a series that is to be used as the area chart upper series.
353 366 */
354 367 void QAreaSeries::setUpperSeries(QLineSeries *series)
355 368 {
356 369 Q_D(QAreaSeries);
357 370 if (d->m_upperSeries != series)
358 371 d->m_upperSeries = series;
359 372 }
360 373
361 374 QLineSeries *QAreaSeries::upperSeries() const
362 375 {
363 376 Q_D(const QAreaSeries);
364 377 return d->m_upperSeries;
365 378 }
366 379
367 380 /*!
368 381 Sets the \a series that is to be used as the area chart lower series.
369 382 */
370 383 void QAreaSeries::setLowerSeries(QLineSeries *series)
371 384 {
372 385 Q_D(QAreaSeries);
373 386 d->m_lowerSeries = series;
374 387 }
375 388
376 389 QLineSeries *QAreaSeries::lowerSeries() const
377 390 {
378 391 Q_D(const QAreaSeries);
379 392 return d->m_lowerSeries;
380 393 }
381 394
382 395 /*!
383 396 Sets \a pen used for drawing area outline.
384 397 */
385 398 void QAreaSeries::setPen(const QPen &pen)
386 399 {
387 400 Q_D(QAreaSeries);
388 401 if (d->m_pen != pen) {
389 402 d->m_pen = pen;
390 403 emit d->updated();
391 404 }
392 405 }
393 406
394 407 QPen QAreaSeries::pen() const
395 408 {
396 409 Q_D(const QAreaSeries);
397 410 if (d->m_pen == QChartPrivate::defaultPen())
398 411 return QPen();
399 412 else
400 413 return d->m_pen;
401 414 }
402 415
403 416 /*!
404 417 Sets \a brush used for filling the area.
405 418 */
406 419 void QAreaSeries::setBrush(const QBrush &brush)
407 420 {
408 421 Q_D(QAreaSeries);
409 422 if (d->m_brush != brush) {
410 423 bool emitColorChanged = brush.color() != d->m_brush.color();
411 424 d->m_brush = brush;
412 425 emit d->updated();
413 426 if (emitColorChanged)
414 427 emit colorChanged(brush.color());
415 428 }
416 429 }
417 430
418 431 QBrush QAreaSeries::brush() const
419 432 {
420 433 Q_D(const QAreaSeries);
421 434 if (d->m_brush == QChartPrivate::defaultBrush())
422 435 return QBrush();
423 436 else
424 437 return d->m_brush;
425 438 }
426 439
427 440 void QAreaSeries::setColor(const QColor &color)
428 441 {
429 442 QBrush b = brush();
430 443 if (b == QBrush())
431 444 b.setStyle(Qt::SolidPattern);
432 445 b.setColor(color);
433 446 setBrush(b);
434 447 }
435 448
436 449 QColor QAreaSeries::color() const
437 450 {
438 451 return brush().color();
439 452 }
440 453
441 454 void QAreaSeries::setBorderColor(const QColor &color)
442 455 {
443 456 QPen p = pen();
444 457 if (p.color() != color) {
445 458 p.setColor(color);
446 459 setPen(p);
447 460 emit borderColorChanged(color);
448 461 }
449 462 }
450 463
451 464 QColor QAreaSeries::borderColor() const
452 465 {
453 466 return pen().color();
454 467 }
455 468
456 469 /*!
457 470 Sets if data points are \a visible and should be drawn on line.
458 471 */
459 472 void QAreaSeries::setPointsVisible(bool visible)
460 473 {
461 474 Q_D(QAreaSeries);
462 475 if (d->m_pointsVisible != visible) {
463 476 d->m_pointsVisible = visible;
464 477 emit d->updated();
465 478 }
466 479 }
467 480
468 481 /*!
469 482 Returns if the points are drawn for this series.
470 483 \sa setPointsVisible()
471 484 */
472 485 bool QAreaSeries::pointsVisible() const
473 486 {
474 487 Q_D(const QAreaSeries);
475 488 return d->m_pointsVisible;
476 489 }
477 490
478 491 void QAreaSeries::setPointLabelsFormat(const QString &format)
479 492 {
480 493 Q_D(QAreaSeries);
481 494 if (d->m_pointLabelsFormat != format) {
482 495 d->m_pointLabelsFormat = format;
483 496 emit pointLabelsFormatChanged(format);
484 497 }
485 498 }
486 499
487 500 QString QAreaSeries::pointLabelsFormat() const
488 501 {
489 502 Q_D(const QAreaSeries);
490 503 return d->m_pointLabelsFormat;
491 504 }
492 505
493 506 void QAreaSeries::setPointLabelsVisible(bool visible)
494 507 {
495 508 Q_D(QAreaSeries);
496 509 if (d->m_pointLabelsVisible != visible) {
497 510 d->m_pointLabelsVisible = visible;
498 511 emit pointLabelsVisibilityChanged(visible);
499 512 }
500 513 }
501 514
502 515 bool QAreaSeries::pointLabelsVisible() const
503 516 {
504 517 Q_D(const QAreaSeries);
505 518 return d->m_pointLabelsVisible;
506 519 }
507 520
508 521 void QAreaSeries::setPointLabelsFont(const QFont &font)
509 522 {
510 523 Q_D(QAreaSeries);
511 524 if (d->m_pointLabelsFont != font) {
512 525 d->m_pointLabelsFont = font;
513 526 emit pointLabelsFontChanged(font);
514 527 }
515 528 }
516 529
517 530 QFont QAreaSeries::pointLabelsFont() const
518 531 {
519 532 Q_D(const QAreaSeries);
520 533 return d->m_pointLabelsFont;
521 534 }
522 535
523 536 void QAreaSeries::setPointLabelsColor(const QColor &color)
524 537 {
525 538 Q_D(QAreaSeries);
526 539 if (d->m_pointLabelsColor != color) {
527 540 d->m_pointLabelsColor = color;
528 541 emit pointLabelsColorChanged(color);
529 542 }
530 543 }
531 544
532 545 QColor QAreaSeries::pointLabelsColor() const
533 546 {
534 547 Q_D(const QAreaSeries);
535 548 if (d->m_pointLabelsColor == QChartPrivate::defaultPen().color())
536 549 return QPen().color();
537 550 else
538 551 return d->m_pointLabelsColor;
539 552 }
540 553
541 554 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
542 555
543 556 QAreaSeriesPrivate::QAreaSeriesPrivate(QLineSeries *upperSeries, QLineSeries *lowerSeries, QAreaSeries *q)
544 557 : QAbstractSeriesPrivate(q),
545 558 m_brush(QChartPrivate::defaultBrush()),
546 559 m_pen(QChartPrivate::defaultPen()),
547 560 m_upperSeries(upperSeries),
548 561 m_lowerSeries(lowerSeries),
549 562 m_pointsVisible(false),
550 563 m_pointLabelsFormat(QLatin1String("@xPoint, @yPoint")),
551 564 m_pointLabelsVisible(false),
552 565 m_pointLabelsFont(QChartPrivate::defaultFont()),
553 566 m_pointLabelsColor(QChartPrivate::defaultPen().color())
554 567 {
555 568 }
556 569
557 570 void QAreaSeriesPrivate::initializeDomain()
558 571 {
559 572 Q_Q(QAreaSeries);
560 573
561 574 qreal minX(domain()->minX());
562 575 qreal minY(domain()->minY());
563 576 qreal maxX(domain()->maxX());
564 577 qreal maxY(domain()->maxY());
565 578
566 579 QLineSeries *upperSeries = q->upperSeries();
567 580 QLineSeries *lowerSeries = q->lowerSeries();
568 581
569 582 if (upperSeries) {
570 583 const QList<QPointF>& points = upperSeries->points();
571 584
572 585 for (int i = 0; i < points.count(); i++) {
573 586 qreal x = points[i].x();
574 587 qreal y = points[i].y();
575 588 minX = qMin(minX, x);
576 589 minY = qMin(minY, y);
577 590 maxX = qMax(maxX, x);
578 591 maxY = qMax(maxY, y);
579 592 }
580 593 }
581 594 if (lowerSeries) {
582 595
583 596 const QList<QPointF>& points = lowerSeries->points();
584 597
585 598 for (int i = 0; i < points.count(); i++) {
586 599 qreal x = points[i].x();
587 600 qreal y = points[i].y();
588 601 minX = qMin(minX, x);
589 602 minY = qMin(minY, y);
590 603 maxX = qMax(maxX, x);
591 604 maxY = qMax(maxY, y);
592 605 }
593 606 }
594 607
595 608 domain()->setRange(minX, maxX, minY, maxY);
596 609 }
597 610
598 611 void QAreaSeriesPrivate::initializeGraphics(QGraphicsItem* parent)
599 612 {
600 613 Q_Q(QAreaSeries);
601 614 AreaChartItem *area = new AreaChartItem(q,parent);
602 615 m_item.reset(area);
603 616 QAbstractSeriesPrivate::initializeGraphics(parent);
604 617 }
605 618 void QAreaSeriesPrivate::initializeAnimations(QChart::AnimationOptions options)
606 619 {
607 620 Q_Q(QAreaSeries);
608 621 AreaChartItem *area = static_cast<AreaChartItem *>(m_item.data());
609 622
610 623 if (q->upperSeries() && area->upperLineItem()->animation())
611 624 area->upperLineItem()->animation()->stopAndDestroyLater();
612 625 if (q->lowerSeries() && area->lowerLineItem()->animation())
613 626 area->lowerLineItem()->animation()->stopAndDestroyLater();
614 627
615 628 if (options.testFlag(QChart::SeriesAnimations)) {
616 629 area->upperLineItem()->setAnimation(new XYAnimation(area->upperLineItem()));
617 630 if (q->lowerSeries())
618 631 area->lowerLineItem()->setAnimation(new XYAnimation(area->lowerLineItem()));
619 632 } else {
620 633 if (q->upperSeries())
621 634 area->upperLineItem()->setAnimation(0);
622 635 if (q->lowerSeries())
623 636 area->lowerLineItem()->setAnimation(0);
624 637 }
625 638 QAbstractSeriesPrivate::initializeAnimations(options);
626 639 }
627 640
628 641 QList<QLegendMarker*> QAreaSeriesPrivate::createLegendMarkers(QLegend* legend)
629 642 {
630 643 Q_Q(QAreaSeries);
631 644 QList<QLegendMarker*> list;
632 645 return list << new QAreaLegendMarker(q,legend);
633 646 }
634 647
635 648
636 649 void QAreaSeriesPrivate::initializeAxes()
637 650 {
638 651
639 652 }
640 653
641 654 QAbstractAxis::AxisType QAreaSeriesPrivate::defaultAxisType(Qt::Orientation orientation) const
642 655 {
643 656 Q_UNUSED(orientation);
644 657 return QAbstractAxis::AxisTypeValue;
645 658 }
646 659
647 660 QAbstractAxis* QAreaSeriesPrivate::createDefaultAxis(Qt::Orientation orientation) const
648 661 {
649 662 Q_UNUSED(orientation);
650 663 return new QValueAxis;
651 664 }
652 665
653 666 void QAreaSeriesPrivate::initializeTheme(int index, ChartTheme* theme, bool forced)
654 667 {
655 668 Q_Q(QAreaSeries);
656 669
657 670 const QList<QGradient> gradients = theme->seriesGradients();
658 671 const QList<QColor> colors = theme->seriesColors();
659 672
660 673 if (forced || QChartPrivate::defaultPen() == m_pen) {
661 674 QPen pen;
662 675 pen.setColor(ChartThemeManager::colorAt(gradients.at(index % gradients.size()), 0.0));
663 676 pen.setWidthF(2);
664 677 q->setPen(pen);
665 678 }
666 679
667 680 if (forced || QChartPrivate::defaultBrush() == m_brush) {
668 681 QBrush brush(colors.at(index % colors.size()));
669 682 q->setBrush(brush);
670 683 }
671 684
672 685 if (forced || QChartPrivate::defaultPen().color() == m_pointLabelsColor) {
673 686 QColor color = theme->labelBrush().color();
674 687 q->setPointLabelsColor(color);
675 688 }
676 689 }
677 690
678 691
679 692 #include "moc_qareaseries.cpp"
680 693 #include "moc_qareaseries_p.cpp"
681 694
682 695 QT_CHARTS_END_NAMESPACE
@@ -1,93 +1,92
1 1 /****************************************************************************
2 2 **
3 3 ** Copyright (C) 2014 Digia Plc
4 4 ** All rights reserved.
5 5 ** For any questions to Digia, please use contact form at http://qt.io
6 6 **
7 7 ** This file is part of the Qt Charts module.
8 8 **
9 9 ** Licensees holding valid commercial license for Qt may use this file in
10 10 ** accordance with the Qt License Agreement provided with the Software
11 11 ** or, alternatively, in accordance with the terms contained in a written
12 12 ** agreement between you and Digia.
13 13 **
14 14 ** If you have questions regarding the use of this file, please use
15 15 ** contact form at http://qt.io
16 16 **
17 17 ****************************************************************************/
18 18
19 19 #include <private/bar_p.h>
20 20 #include <QtGui/QPainter>
21 21 #include <QtWidgets/QGraphicsSceneEvent>
22 22 #include <QtWidgets/QStyleOptionGraphicsItem>
23 23 #include <QtWidgets/QStyle>
24 24
25 25 QT_CHARTS_BEGIN_NAMESPACE
26 26
27 27 Bar::Bar(QBarSet *barset, int index, QGraphicsItem *parent) : QGraphicsRectItem(parent),
28 28 m_index(index),
29 29 m_barset(barset),
30 30 m_hovering(false),
31 31 m_mousePressed(false)
32 32 {
33 33 setAcceptedMouseButtons(Qt::LeftButton | Qt::RightButton);
34 34 setAcceptHoverEvents(true);
35 35 setFlag(QGraphicsItem::ItemIsSelectable);
36 36 }
37 37
38 38 Bar::~Bar()
39 39 {
40 40 // End hover event, if bar is deleted during it
41 41 if (m_hovering)
42 42 emit hovered(false, m_index, m_barset);
43 43 }
44 44
45 45 void Bar::mousePressEvent(QGraphicsSceneMouseEvent *event)
46 46 {
47 47 emit pressed(m_index, m_barset);
48 m_lastMousePos = event->pos();
49 48 m_mousePressed = true;
50 49 QGraphicsItem::mousePressEvent(event);
51 50 }
52 51
53 52 void Bar::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
54 53 {
55 54 Q_UNUSED(event)
56 55 m_hovering = true;
57 56 emit hovered(true, m_index, m_barset);
58 57
59 58 }
60 59
61 60 void Bar::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
62 61 {
63 62 Q_UNUSED(event)
64 63 m_hovering = false;
65 64 emit hovered(false, m_index, m_barset);
66 65 }
67 66
68 67 void Bar::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
69 68 {
70 69 emit released(m_index, m_barset);
71 if (m_lastMousePos == event->pos() && m_mousePressed)
70 if (m_mousePressed)
72 71 emit clicked(m_index, m_barset);
73 72 m_mousePressed = false;
74 73 QGraphicsItem::mouseReleaseEvent(event);
75 74 }
76 75
77 76 void Bar::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
78 77 {
79 78 emit doubleClicked(m_index, m_barset);
80 79 QGraphicsItem::mouseDoubleClickEvent(event);
81 80 }
82 81
83 82 void Bar::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
84 83 {
85 84 // Remove selection border around bar
86 85 QStyleOptionGraphicsItem barOption(*option);
87 86 barOption.state &= ~QStyle::State_Selected;
88 87 QGraphicsRectItem::paint(painter, &barOption, widget);
89 88 }
90 89
91 90 #include "moc_bar_p.cpp"
92 91
93 92 QT_CHARTS_END_NAMESPACE
@@ -1,73 +1,72
1 1 /****************************************************************************
2 2 **
3 3 ** Copyright (C) 2014 Digia Plc
4 4 ** All rights reserved.
5 5 ** For any questions to Digia, please use contact form at http://qt.io
6 6 **
7 7 ** This file is part of the Qt Charts module.
8 8 **
9 9 ** Licensees holding valid commercial license for Qt may use this file in
10 10 ** accordance with the Qt License Agreement provided with the Software
11 11 ** or, alternatively, in accordance with the terms contained in a written
12 12 ** agreement between you and Digia.
13 13 **
14 14 ** If you have questions regarding the use of this file, please use
15 15 ** contact form at http://qt.io
16 16 **
17 17 ****************************************************************************/
18 18
19 19 // W A R N I N G
20 20 // -------------
21 21 //
22 22 // This file is not part of the Qt Enterprise Chart API. It exists purely as an
23 23 // implementation detail. This header file may change from version to
24 24 // version without notice, or even be removed.
25 25 //
26 26 // We mean it.
27 27
28 28 #ifndef BAR_H
29 29 #define BAR_H
30 30
31 31 #include <QtCharts/QChartGlobal>
32 32 #include <QtWidgets/QGraphicsRectItem>
33 33
34 34 QT_CHARTS_BEGIN_NAMESPACE
35 35
36 36 class QBarSet;
37 37
38 38 // Single visual bar item of chart
39 39 class Bar : public QObject, public QGraphicsRectItem
40 40 {
41 41 Q_OBJECT
42 42 public:
43 43 Bar(QBarSet *barset, int index, QGraphicsItem *parent = 0);
44 44 ~Bar();
45 45
46 46 public:
47 47 void mousePressEvent(QGraphicsSceneMouseEvent *event);
48 48 void hoverEnterEvent(QGraphicsSceneHoverEvent *event);
49 49 void hoverLeaveEvent(QGraphicsSceneHoverEvent *event);
50 50 void mouseReleaseEvent(QGraphicsSceneMouseEvent *event);
51 51 void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event);
52 52
53 53 void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0);
54 54
55 55 Q_SIGNALS:
56 56 void clicked(int index, QBarSet *barset);
57 57 void hovered(bool status, int index, QBarSet *barset);
58 58 void pressed(int index, QBarSet *barset);
59 59 void released(int index, QBarSet *barset);
60 60 void doubleClicked(int index, QBarSet *barset);
61 61
62 62 private:
63 63 int m_index;
64 64 QBarSet *m_barset;
65 65 bool m_hovering;
66 66
67 QPointF m_lastMousePos;
68 67 bool m_mousePressed;
69 68 };
70 69
71 70 QT_CHARTS_END_NAMESPACE
72 71
73 72 #endif // BAR_H
@@ -1,211 +1,212
1 1 /****************************************************************************
2 2 **
3 3 ** Copyright (C) 2014 Digia Plc
4 4 ** All rights reserved.
5 5 ** For any questions to Digia, please use contact form at http://qt.io
6 6 **
7 7 ** This file is part of the Qt Charts module.
8 8 **
9 9 ** Licensees holding valid commercial license for Qt may use this file in
10 10 ** accordance with the Qt License Agreement provided with the Software
11 11 ** or, alternatively, in accordance with the terms contained in a written
12 12 ** agreement between you and Digia.
13 13 **
14 14 ** If you have questions regarding the use of this file, please use
15 15 ** contact form at http://qt.io
16 16 **
17 17 ****************************************************************************/
18 18
19 19 #include <private/boxwhiskers_p.h>
20 20 #include <QtGui/QPainter>
21 21 #include <QtWidgets/QWidget>
22 22 #include <QtWidgets/QGraphicsSceneMouseEvent>
23 23
24 24 QT_CHARTS_BEGIN_NAMESPACE
25 25
26 26 BoxWhiskers::BoxWhiskers(QBoxSet *set, AbstractDomain *domain, QGraphicsObject *parent) :
27 27 QGraphicsObject(parent),
28 28 m_boxSet(set),
29 29 m_domain(domain),
30 30 m_mousePressed(false)
31 31 {
32 32 setAcceptHoverEvents(true);
33 33 setAcceptedMouseButtons(Qt::MouseButtonMask);
34 34 setFlag(QGraphicsObject::ItemIsSelectable);
35 35 }
36 36
37 37 BoxWhiskers::~BoxWhiskers()
38 38 {
39 39 }
40 40
41 41 void BoxWhiskers::mousePressEvent(QGraphicsSceneMouseEvent *event)
42 42 {
43 Q_UNUSED(event)
43 44 emit pressed(m_boxSet);
44 m_lastMousePos = event->pos();
45 45 m_mousePressed = true;
46 46 }
47 47
48 48 void BoxWhiskers::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
49 49 {
50 50 Q_UNUSED(event)
51 51 emit hovered(true, m_boxSet);
52 52 }
53 53
54 54 void BoxWhiskers::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
55 55 {
56 56 Q_UNUSED(event)
57 57 emit hovered(false, m_boxSet);
58 58 }
59 59
60 60 void BoxWhiskers::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
61 61 {
62 Q_UNUSED(event)
62 63 emit released(m_boxSet);
63 if (m_lastMousePos == event->pos() && m_mousePressed)
64 if (m_mousePressed)
64 65 emit clicked(m_boxSet);
65 66 }
66 67
67 68 void BoxWhiskers::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
68 69 {
69 70 Q_UNUSED(event)
70 71 // For Box a press signal needs to be explicitly fired for mouseDoubleClickEvent
71 72 emit pressed(m_boxSet);
72 73 emit doubleClicked(m_boxSet);
73 74 }
74 75
75 76 void BoxWhiskers::setBrush(const QBrush &brush)
76 77 {
77 78 m_brush = brush;
78 79 m_outlinePen.setColor(m_brush.color());
79 80 update();
80 81 }
81 82
82 83 void BoxWhiskers::setPen(const QPen &pen)
83 84 {
84 85 qreal widthDiff = pen.widthF() - m_pen.widthF();
85 86 m_boundingRect.adjust(-widthDiff, -widthDiff, widthDiff, widthDiff);
86 87
87 88 m_pen = pen;
88 89 m_medianPen = pen;
89 90 m_medianPen.setCapStyle(Qt::FlatCap);
90 91 m_outlinePen = pen;
91 92 m_outlinePen.setStyle(Qt::SolidLine);
92 93 m_outlinePen.setColor(m_brush.color());
93 94
94 95 update();
95 96 }
96 97
97 98 void BoxWhiskers::setBoxWidth(const qreal width)
98 99 {
99 100 m_boxWidth = width;
100 101 }
101 102
102 103 void BoxWhiskers::setLayout(const BoxWhiskersData &data)
103 104 {
104 105 m_data = data;
105 106
106 107 updateGeometry(m_domain);
107 108 update();
108 109 }
109 110
110 111 QSizeF BoxWhiskers::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const
111 112 {
112 113 Q_UNUSED(which)
113 114 Q_UNUSED(constraint)
114 115
115 116 return QSizeF();
116 117 }
117 118
118 119 void BoxWhiskers::setGeometry(const QRectF &rect)
119 120 {
120 121 Q_UNUSED(rect)
121 122 }
122 123
123 124 QRectF BoxWhiskers::boundingRect() const
124 125 {
125 126 return m_boundingRect;
126 127 }
127 128
128 129 void BoxWhiskers::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
129 130 {
130 131 Q_UNUSED(option)
131 132 Q_UNUSED(widget)
132 133
133 134 painter->save();
134 135 painter->setBrush(m_brush);
135 136 painter->setClipRect(parentItem()->boundingRect());
136 137 painter->setPen(m_pen);
137 138 painter->drawPath(m_boxPath);
138 139 if (!m_boxOutlined)
139 140 painter->setPen(m_outlinePen);
140 141 painter->drawRect(m_middleBox);
141 142 painter->setPen(m_medianPen);
142 143 qreal halfLine = m_pen.widthF() / 2.0;
143 144 painter->drawLine(QLineF(m_geometryLeft - halfLine, m_geometryMedian,
144 145 m_geometryRight + halfLine, m_geometryMedian));
145 146 painter->restore();
146 147 }
147 148
148 149 void BoxWhiskers::updateGeometry(AbstractDomain *domain)
149 150 {
150 151 m_domain = domain;
151 152
152 153 prepareGeometryChange();
153 154
154 155 QPainterPath path;
155 156 m_boxPath = path;
156 157 m_boundingRect = m_boxPath.boundingRect();
157 158
158 159 qreal columnWidth = 1.0 / m_data.m_seriesCount;
159 160 qreal left = ((1.0 - m_boxWidth) / 2.0) * columnWidth + columnWidth * m_data.m_seriesIndex + m_data.m_index - 0.5;
160 161 qreal barWidth = m_boxWidth * columnWidth;
161 162
162 163 QPointF geometryPoint = m_domain->calculateGeometryPoint(QPointF(left, m_data.m_upperExtreme), m_validData);
163 164 if (!m_validData)
164 165 return;
165 166 m_geometryLeft = geometryPoint.x();
166 167 qreal geometryUpperExtreme = geometryPoint.y();
167 168 geometryPoint = m_domain->calculateGeometryPoint(QPointF(left + barWidth, m_data.m_upperQuartile), m_validData);
168 169 if (!m_validData)
169 170 return;
170 171 m_geometryRight = geometryPoint.x();
171 172 qreal geometryUpperQuartile = geometryPoint.y();
172 173 geometryPoint = m_domain->calculateGeometryPoint(QPointF(left, m_data.m_lowerQuartile), m_validData);
173 174 if (!m_validData)
174 175 return;
175 176 qreal geometryLowerQuartile = geometryPoint.y();
176 177 geometryPoint = m_domain->calculateGeometryPoint(QPointF(left, m_data.m_lowerExtreme), m_validData);
177 178 if (!m_validData)
178 179 return;
179 180 qreal geometryLowerExtreme = geometryPoint.y();
180 181 geometryPoint = m_domain->calculateGeometryPoint(QPointF(left, m_data.m_median), m_validData);
181 182 if (!m_validData)
182 183 return;
183 184 m_geometryMedian = geometryPoint.y();
184 185
185 186 // Upper whisker
186 187 path.moveTo(m_geometryLeft, geometryUpperExtreme);
187 188 path.lineTo(m_geometryRight, geometryUpperExtreme);
188 189 path.moveTo((m_geometryLeft + m_geometryRight) / 2.0, geometryUpperExtreme);
189 190 path.lineTo((m_geometryLeft + m_geometryRight) / 2.0, geometryUpperQuartile);
190 191
191 192 // Middle Box
192 193 m_middleBox.setCoords(m_geometryLeft, geometryUpperQuartile, m_geometryRight, geometryLowerQuartile);
193 194
194 195 // Lower whisker
195 196 path.moveTo(m_geometryLeft, geometryLowerExtreme);
196 197 path.lineTo(m_geometryRight, geometryLowerExtreme);
197 198 path.moveTo((m_geometryLeft + m_geometryRight) / 2.0, geometryLowerQuartile);
198 199 path.lineTo((m_geometryLeft + m_geometryRight) / 2.0, geometryLowerExtreme);
199 200
200 201 path.closeSubpath();
201 202
202 203 m_boxPath = path;
203 204 m_boundingRect = m_boxPath.boundingRect();
204 205
205 206 qreal extra = m_pen.widthF();
206 207 m_boundingRect.adjust(-extra, -extra, extra, extra);
207 208 }
208 209
209 210 #include "moc_boxwhiskers_p.cpp"
210 211
211 212 QT_CHARTS_END_NAMESPACE
@@ -1,108 +1,107
1 1 /****************************************************************************
2 2 **
3 3 ** Copyright (C) 2014 Digia Plc
4 4 ** All rights reserved.
5 5 ** For any questions to Digia, please use contact form at http://qt.io
6 6 **
7 7 ** This file is part of the Qt Charts module.
8 8 **
9 9 ** Licensees holding valid commercial license for Qt may use this file in
10 10 ** accordance with the Qt License Agreement provided with the Software
11 11 ** or, alternatively, in accordance with the terms contained in a written
12 12 ** agreement between you and Digia.
13 13 **
14 14 ** If you have questions regarding the use of this file, please use
15 15 ** contact form at http://qt.io
16 16 **
17 17 ****************************************************************************/
18 18
19 19 // W A R N I N G
20 20 // -------------
21 21 //
22 22 // This file is not part of the Qt Enterprise Chart API. It exists purely as an
23 23 // implementation detail. This header file may change from version to
24 24 // version without notice, or even be removed.
25 25 //
26 26 // We mean it.
27 27
28 28 #ifndef BOXWHISKERS_H
29 29 #define BOXWHISKERS_H
30 30
31 31 #include <private/boxwhiskersdata_p.h>
32 32 #include <QtCharts/QChartGlobal>
33 33 #include <private/abstractdomain_p.h>
34 34 #include <QtCharts/QBoxSet>
35 35 #include <QtWidgets/QGraphicsRectItem>
36 36 #include <QtWidgets/QGraphicsLineItem>
37 37 #include <QtWidgets/QGraphicsLayoutItem>
38 38 #include <QtGui/QPainterPath>
39 39
40 40 QT_CHARTS_BEGIN_NAMESPACE
41 41
42 42 class QBarSet;
43 43
44 44 class BoxWhiskers : public QGraphicsObject
45 45 {
46 46 Q_OBJECT
47 47
48 48 public:
49 49 BoxWhiskers(QBoxSet *set, AbstractDomain *domain, QGraphicsObject *parent);
50 50 ~BoxWhiskers();
51 51
52 52 void setBrush(const QBrush &brush);
53 53 void setPen(const QPen &pen);
54 54 void setLayout(const BoxWhiskersData &data);
55 55 void setBoxOutlined(const bool outlined) { m_boxOutlined = outlined; }
56 56 void setBoxWidth(const qreal width);
57 57
58 58 void mousePressEvent(QGraphicsSceneMouseEvent *event);
59 59 void hoverEnterEvent(QGraphicsSceneHoverEvent *event);
60 60 void hoverLeaveEvent(QGraphicsSceneHoverEvent *event);
61 61 void mouseReleaseEvent(QGraphicsSceneMouseEvent *event);
62 62 void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event);
63 63
64 64 QRectF boundingRect() const;
65 65 void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0);
66 66
67 67 void updateGeometry(AbstractDomain *domain);
68 68 protected:
69 69 QSizeF sizeHint(Qt::SizeHint which, const QSizeF &constraint) const;
70 70 void setGeometry(const QRectF &rect);
71 71
72 72 Q_SIGNALS:
73 73 void clicked(QBoxSet *boxset);
74 74 void hovered(bool status, QBoxSet *boxset);
75 75 void pressed(QBoxSet *boxset);
76 76 void released(QBoxSet *boxset);
77 77 void doubleClicked(QBoxSet *boxset);
78 78
79 79 private:
80 80 friend class BoxPlotChartItem;
81 81 friend class BoxPlotAnimation;
82 82
83 83 QBoxSet *m_boxSet;
84 84 AbstractDomain *m_domain;
85 85 QPainterPath m_boxPath;
86 86 QRectF m_boundingRect;
87 87 bool m_hovering;
88 88 bool m_validData;
89 89 QBrush m_brush;
90 90 QPen m_pen;
91 91 QPen m_medianPen;
92 92 QPen m_outlinePen;
93 93 bool m_boxOutlined;
94 94 qreal m_boxWidth;
95 95 BoxWhiskersData m_data;
96 96 QSizeF m_domainSize;
97 97 QRectF m_middleBox;
98 98 qreal m_geometryMedian;
99 99 qreal m_geometryLeft;
100 100 qreal m_geometryRight;
101 101
102 QPointF m_lastMousePos;
103 102 bool m_mousePressed;
104 103 };
105 104
106 105 QT_CHARTS_END_NAMESPACE
107 106
108 107 #endif // BOXWHISKERS_H
@@ -1,422 +1,422
1 1 /****************************************************************************
2 2 **
3 3 ** Copyright (C) 2014 Digia Plc
4 4 ** All rights reserved.
5 5 ** For any questions to Digia, please use contact form at http://qt.io
6 6 **
7 7 ** This file is part of the Qt Charts module.
8 8 **
9 9 ** Licensees holding valid commercial license for Qt may use this file in
10 10 ** accordance with the Qt License Agreement provided with the Software
11 11 ** or, alternatively, in accordance with the terms contained in a written
12 12 ** agreement between you and Digia.
13 13 **
14 14 ** If you have questions regarding the use of this file, please use
15 15 ** contact form at http://qt.io
16 16 **
17 17 ****************************************************************************/
18 18
19 19 #include <private/linechartitem_p.h>
20 20 #include <QtCharts/QLineSeries>
21 21 #include <private/qlineseries_p.h>
22 22 #include <private/chartpresenter_p.h>
23 23 #include <private/polardomain_p.h>
24 24 #include <private/chartthememanager_p.h>
25 25 #include <private/charttheme_p.h>
26 26 #include <QtGui/QPainter>
27 27 #include <QtWidgets/QGraphicsSceneMouseEvent>
28 28
29 29 QT_CHARTS_BEGIN_NAMESPACE
30 30
31 31 const qreal mouseEventMinWidth(12);
32 32
33 33 LineChartItem::LineChartItem(QLineSeries *series, QGraphicsItem *item)
34 34 : XYChart(series,item),
35 35 m_series(series),
36 36 m_pointsVisible(false),
37 37 m_chartType(QChart::ChartTypeUndefined),
38 38 m_pointLabelsVisible(false),
39 39 m_pointLabelsFormat(series->pointLabelsFormat()),
40 40 m_pointLabelsFont(series->pointLabelsFont()),
41 41 m_pointLabelsColor(series->pointLabelsColor()),
42 42 m_mousePressed(false)
43 43 {
44 44 setAcceptHoverEvents(true);
45 45 setFlag(QGraphicsItem::ItemIsSelectable);
46 46 setZValue(ChartPresenter::LineChartZValue);
47 47 QObject::connect(series->d_func(), SIGNAL(updated()), this, SLOT(handleUpdated()));
48 48 QObject::connect(series, SIGNAL(visibleChanged()), this, SLOT(handleUpdated()));
49 49 QObject::connect(series, SIGNAL(opacityChanged()), this, SLOT(handleUpdated()));
50 50 QObject::connect(series, SIGNAL(pointLabelsFormatChanged(QString)),
51 51 this, SLOT(handleUpdated()));
52 52 QObject::connect(series, SIGNAL(pointLabelsVisibilityChanged(bool)),
53 53 this, SLOT(handleUpdated()));
54 54 QObject::connect(series, SIGNAL(pointLabelsFontChanged(QFont)), this, SLOT(handleUpdated()));
55 55 QObject::connect(series, SIGNAL(pointLabelsColorChanged(QColor)), this, SLOT(handleUpdated()));
56 56 handleUpdated();
57 57 }
58 58
59 59 QRectF LineChartItem::boundingRect() const
60 60 {
61 61 return m_rect;
62 62 }
63 63
64 64 QPainterPath LineChartItem::shape() const
65 65 {
66 66 return m_shapePath;
67 67 }
68 68
69 69 void LineChartItem::updateGeometry()
70 70 {
71 71 m_points = geometryPoints();
72 72 const QVector<QPointF> &points = m_points;
73 73
74 74 if (points.size() == 0) {
75 75 prepareGeometryChange();
76 76 m_fullPath = QPainterPath();
77 77 m_linePath = QPainterPath();
78 78 m_rect = QRect();
79 79 return;
80 80 }
81 81
82 82 QPainterPath linePath;
83 83 QPainterPath fullPath;
84 84 // Use worst case scenario to determine required margin.
85 85 qreal margin = m_linePen.width() * 1.42;
86 86
87 87 // Area series use component line series that aren't necessarily added to the chart themselves,
88 88 // so check if chart type is forced before trying to obtain it from the chart.
89 89 QChart::ChartType chartType = m_chartType;
90 90 if (chartType == QChart::ChartTypeUndefined)
91 91 chartType = m_series->chart()->chartType();
92 92
93 93 // For polar charts, we need special handling for angular (horizontal)
94 94 // points that are off-grid.
95 95 if (chartType == QChart::ChartTypePolar) {
96 96 QPainterPath linePathLeft;
97 97 QPainterPath linePathRight;
98 98 QPainterPath *currentSegmentPath = 0;
99 99 QPainterPath *previousSegmentPath = 0;
100 100 qreal minX = domain()->minX();
101 101 qreal maxX = domain()->maxX();
102 102 qreal minY = domain()->minY();
103 103 QPointF currentSeriesPoint = m_series->at(0);
104 104 QPointF currentGeometryPoint = points.at(0);
105 105 QPointF previousGeometryPoint = points.at(0);
106 106 int size = m_linePen.width();
107 107 bool pointOffGrid = false;
108 108 bool previousPointWasOffGrid = (currentSeriesPoint.x() < minX || currentSeriesPoint.x() > maxX);
109 109
110 110 qreal domainRadius = domain()->size().height() / 2.0;
111 111 const QPointF centerPoint(domainRadius, domainRadius);
112 112
113 113 if (!previousPointWasOffGrid) {
114 114 fullPath.moveTo(points.at(0));
115 115 if (m_pointsVisible && currentSeriesPoint.y() >= minY) {
116 116 // Do not draw ellipses for points below minimum Y.
117 117 linePath.addEllipse(points.at(0), size, size);
118 118 fullPath.addEllipse(points.at(0), size, size);
119 119 linePath.moveTo(points.at(0));
120 120 fullPath.moveTo(points.at(0));
121 121 }
122 122 }
123 123
124 124 qreal leftMarginLine = centerPoint.x() - margin;
125 125 qreal rightMarginLine = centerPoint.x() + margin;
126 126 qreal horizontal = centerPoint.y();
127 127
128 128 // See ScatterChartItem::updateGeometry() for explanation why seriesLastIndex is needed
129 129 const int seriesLastIndex = m_series->count() - 1;
130 130
131 131 for (int i = 1; i < points.size(); i++) {
132 132 // Interpolating line fragments would be ugly when thick pen is used,
133 133 // so we work around it by utilizing three separate
134 134 // paths for line segments and clip those with custom regions at paint time.
135 135 // "Right" path contains segments that cross the axis line with visible point on the
136 136 // right side of the axis line, as well as segments that have one point within the margin
137 137 // on the right side of the axis line and another point on the right side of the chart.
138 138 // "Left" path contains points with similarly on the left side.
139 139 // "Full" path contains rest of the points.
140 140 // This doesn't yield perfect results always. E.g. when segment covers more than 90
141 141 // degrees and both of the points are within the margin, one in the top half and one in the
142 142 // bottom half of the chart, the bottom one gets clipped incorrectly.
143 143 // However, this should be rare occurrence in any sensible chart.
144 144 currentSeriesPoint = m_series->at(qMin(seriesLastIndex, i));
145 145 currentGeometryPoint = points.at(i);
146 146 pointOffGrid = (currentSeriesPoint.x() < minX || currentSeriesPoint.x() > maxX);
147 147
148 148 // Draw something unless both off-grid
149 149 if (!pointOffGrid || !previousPointWasOffGrid) {
150 150 QPointF intersectionPoint;
151 151 qreal y;
152 152 if (pointOffGrid != previousPointWasOffGrid) {
153 153 if (currentGeometryPoint.x() == previousGeometryPoint.x()) {
154 154 y = currentGeometryPoint.y() + (currentGeometryPoint.y() - previousGeometryPoint.y()) / 2.0;
155 155 } else {
156 156 qreal ratio = (centerPoint.x() - currentGeometryPoint.x()) / (currentGeometryPoint.x() - previousGeometryPoint.x());
157 157 y = currentGeometryPoint.y() + (currentGeometryPoint.y() - previousGeometryPoint.y()) * ratio;
158 158 }
159 159 intersectionPoint = QPointF(centerPoint.x(), y);
160 160 }
161 161
162 162 bool dummyOk; // We know points are ok, but this is needed
163 163 qreal currentAngle = static_cast<PolarDomain *>(domain())->toAngularCoordinate(currentSeriesPoint.x(), dummyOk);
164 164 qreal previousAngle = static_cast<PolarDomain *>(domain())->toAngularCoordinate(m_series->at(i - 1).x(), dummyOk);
165 165
166 166 if ((qAbs(currentAngle - previousAngle) > 180.0)) {
167 167 // If the angle between two points is over 180 degrees (half X range),
168 168 // any direct segment between them becomes meaningless.
169 169 // In this case two line segments are drawn instead, from previous
170 170 // point to the center and from center to current point.
171 171 if ((previousAngle < 0.0 || (previousAngle <= 180.0 && previousGeometryPoint.x() < rightMarginLine))
172 172 && previousGeometryPoint.y() < horizontal) {
173 173 currentSegmentPath = &linePathRight;
174 174 } else if ((previousAngle > 360.0 || (previousAngle > 180.0 && previousGeometryPoint.x() > leftMarginLine))
175 175 && previousGeometryPoint.y() < horizontal) {
176 176 currentSegmentPath = &linePathLeft;
177 177 } else if (previousAngle > 0.0 && previousAngle < 360.0) {
178 178 currentSegmentPath = &linePath;
179 179 } else {
180 180 currentSegmentPath = 0;
181 181 }
182 182
183 183 if (currentSegmentPath) {
184 184 if (previousSegmentPath != currentSegmentPath)
185 185 currentSegmentPath->moveTo(previousGeometryPoint);
186 186 if (previousPointWasOffGrid)
187 187 fullPath.moveTo(intersectionPoint);
188 188
189 189 currentSegmentPath->lineTo(centerPoint);
190 190 fullPath.lineTo(centerPoint);
191 191 }
192 192
193 193 previousSegmentPath = currentSegmentPath;
194 194
195 195 if ((currentAngle < 0.0 || (currentAngle <= 180.0 && currentGeometryPoint.x() < rightMarginLine))
196 196 && currentGeometryPoint.y() < horizontal) {
197 197 currentSegmentPath = &linePathRight;
198 198 } else if ((currentAngle > 360.0 || (currentAngle > 180.0 &&currentGeometryPoint.x() > leftMarginLine))
199 199 && currentGeometryPoint.y() < horizontal) {
200 200 currentSegmentPath = &linePathLeft;
201 201 } else if (currentAngle > 0.0 && currentAngle < 360.0) {
202 202 currentSegmentPath = &linePath;
203 203 } else {
204 204 currentSegmentPath = 0;
205 205 }
206 206
207 207 if (currentSegmentPath) {
208 208 if (previousSegmentPath != currentSegmentPath)
209 209 currentSegmentPath->moveTo(centerPoint);
210 210 if (!previousSegmentPath)
211 211 fullPath.moveTo(centerPoint);
212 212
213 213 currentSegmentPath->lineTo(currentGeometryPoint);
214 214 if (pointOffGrid)
215 215 fullPath.lineTo(intersectionPoint);
216 216 else
217 217 fullPath.lineTo(currentGeometryPoint);
218 218 }
219 219 } else {
220 220 if (previousAngle < 0.0 || currentAngle < 0.0
221 221 || ((previousAngle <= 180.0 && currentAngle <= 180.0)
222 222 && ((previousGeometryPoint.x() < rightMarginLine && previousGeometryPoint.y() < horizontal)
223 223 || (currentGeometryPoint.x() < rightMarginLine && currentGeometryPoint.y() < horizontal)))) {
224 224 currentSegmentPath = &linePathRight;
225 225 } else if (previousAngle > 360.0 || currentAngle > 360.0
226 226 || ((previousAngle > 180.0 && currentAngle > 180.0)
227 227 && ((previousGeometryPoint.x() > leftMarginLine && previousGeometryPoint.y() < horizontal)
228 228 || (currentGeometryPoint.x() > leftMarginLine && currentGeometryPoint.y() < horizontal)))) {
229 229 currentSegmentPath = &linePathLeft;
230 230 } else {
231 231 currentSegmentPath = &linePath;
232 232 }
233 233
234 234 if (currentSegmentPath != previousSegmentPath)
235 235 currentSegmentPath->moveTo(previousGeometryPoint);
236 236 if (previousPointWasOffGrid)
237 237 fullPath.moveTo(intersectionPoint);
238 238
239 239 if (pointOffGrid)
240 240 fullPath.lineTo(intersectionPoint);
241 241 else
242 242 fullPath.lineTo(currentGeometryPoint);
243 243 currentSegmentPath->lineTo(currentGeometryPoint);
244 244 }
245 245 } else {
246 246 currentSegmentPath = 0;
247 247 }
248 248
249 249 previousPointWasOffGrid = pointOffGrid;
250 250 if (m_pointsVisible && !pointOffGrid && currentSeriesPoint.y() >= minY) {
251 251 linePath.addEllipse(points.at(i), size, size);
252 252 fullPath.addEllipse(points.at(i), size, size);
253 253 linePath.moveTo(points.at(i));
254 254 fullPath.moveTo(points.at(i));
255 255 }
256 256 previousSegmentPath = currentSegmentPath;
257 257 previousGeometryPoint = currentGeometryPoint;
258 258 }
259 259 m_linePathPolarRight = linePathRight;
260 260 m_linePathPolarLeft = linePathLeft;
261 261 // Note: This construction of m_fullpath is not perfect. The partial segments that are
262 262 // outside left/right clip regions at axis boundary still generate hover/click events,
263 263 // because shape doesn't get clipped. It doesn't seem possible to do sensibly.
264 264 } else { // not polar
265 265 linePath.moveTo(points.at(0));
266 266 if (m_pointsVisible) {
267 267 int size = m_linePen.width();
268 268 linePath.addEllipse(points.at(0), size, size);
269 269 linePath.moveTo(points.at(0));
270 270 for (int i = 1; i < points.size(); i++) {
271 271 linePath.lineTo(points.at(i));
272 272 linePath.addEllipse(points.at(i), size, size);
273 273 linePath.moveTo(points.at(i));
274 274 }
275 275 } else {
276 276 for (int i = 1; i < points.size(); i++)
277 277 linePath.lineTo(points.at(i));
278 278 }
279 279 fullPath = linePath;
280 280 }
281 281
282 282 QPainterPathStroker stroker;
283 283 // QPainter::drawLine does not respect join styles, for example BevelJoin becomes MiterJoin.
284 284 // This is why we are prepared for the "worst case" scenario, i.e. use always MiterJoin and
285 285 // multiply line width with square root of two when defining shape and bounding rectangle.
286 286 stroker.setWidth(margin);
287 287 stroker.setJoinStyle(Qt::MiterJoin);
288 288 stroker.setCapStyle(Qt::SquareCap);
289 289 stroker.setMiterLimit(m_linePen.miterLimit());
290 290
291 291 QPainterPath checkShapePath = stroker.createStroke(fullPath);
292 292
293 293 // Only zoom in if the bounding rects of the paths fit inside int limits. QWidget::update() uses
294 294 // a region that has to be compatible with QRect.
295 295 if (checkShapePath.boundingRect().height() <= INT_MAX
296 296 && checkShapePath.boundingRect().width() <= INT_MAX
297 297 && linePath.boundingRect().height() <= INT_MAX
298 298 && linePath.boundingRect().width() <= INT_MAX
299 299 && fullPath.boundingRect().height() <= INT_MAX
300 300 && fullPath.boundingRect().width() <= INT_MAX) {
301 301 prepareGeometryChange();
302 302
303 303 m_linePath = linePath;
304 304 m_fullPath = fullPath;
305 305 m_shapePath = checkShapePath;
306 306
307 307 m_rect = m_shapePath.boundingRect();
308 308 } else {
309 309 update();
310 310 }
311 311 }
312 312
313 313 void LineChartItem::handleUpdated()
314 314 {
315 315 // If points visibility has changed, a geometry update is needed.
316 316 // Also, if pen changes when points are visible, geometry update is needed.
317 317 bool doGeometryUpdate =
318 318 (m_pointsVisible != m_series->pointsVisible())
319 319 || (m_series->pointsVisible() && (m_linePen != m_series->pen()));
320 320 setVisible(m_series->isVisible());
321 321 setOpacity(m_series->opacity());
322 322 m_pointsVisible = m_series->pointsVisible();
323 323 m_linePen = m_series->pen();
324 324 m_pointLabelsFormat = m_series->pointLabelsFormat();
325 325 m_pointLabelsVisible = m_series->pointLabelsVisible();
326 326 m_pointLabelsFont = m_series->pointLabelsFont();
327 327 m_pointLabelsColor = m_series->pointLabelsColor();
328 328 if (doGeometryUpdate)
329 329 updateGeometry();
330 330 update();
331 331 }
332 332
333 333 void LineChartItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
334 334 {
335 335 Q_UNUSED(widget)
336 336 Q_UNUSED(option)
337 337
338 338 QRectF clipRect = QRectF(QPointF(0, 0), domain()->size());
339 339
340 340 painter->save();
341 341 painter->setPen(m_linePen);
342 342 bool alwaysUsePath = false;
343 343
344 344 if (m_series->chart()->chartType() == QChart::ChartTypePolar) {
345 345 qreal halfWidth = domain()->size().width() / 2.0;
346 346 QRectF clipRectLeft = QRectF(0, 0, halfWidth, domain()->size().height());
347 347 QRectF clipRectRight = QRectF(halfWidth, 0, halfWidth, domain()->size().height());
348 348 QRegion fullPolarClipRegion(clipRect.toRect(), QRegion::Ellipse);
349 349 QRegion clipRegionLeft(fullPolarClipRegion.intersected(clipRectLeft.toRect()));
350 350 QRegion clipRegionRight(fullPolarClipRegion.intersected(clipRectRight.toRect()));
351 351 painter->setClipRegion(clipRegionLeft);
352 352 painter->drawPath(m_linePathPolarLeft);
353 353 painter->setClipRegion(clipRegionRight);
354 354 painter->drawPath(m_linePathPolarRight);
355 355 painter->setClipRegion(fullPolarClipRegion);
356 356 alwaysUsePath = true; // required for proper clipping
357 357 } else {
358 358 painter->setClipRect(clipRect);
359 359 }
360 360
361 361 if (m_pointsVisible) {
362 362 painter->setBrush(m_linePen.color());
363 363 painter->drawPath(m_linePath);
364 364 } else {
365 365 painter->setBrush(QBrush(Qt::NoBrush));
366 366 if (m_linePen.style() != Qt::SolidLine || alwaysUsePath) {
367 367 // If pen style is not solid line, always fall back to path painting
368 368 // to ensure proper continuity of the pattern
369 369 painter->drawPath(m_linePath);
370 370 } else {
371 371 for (int i(1); i < m_points.size(); i++)
372 372 painter->drawLine(m_points.at(i - 1), m_points.at(i));
373 373 }
374 374 }
375 375
376 376 if (m_pointLabelsVisible)
377 377 m_series->d_func()->drawSeriesPointLabels(painter, m_points, m_linePen.width() / 2);
378 378
379 379 painter->restore();
380 380
381 381 }
382 382
383 383 void LineChartItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
384 384 {
385 385 emit XYChart::pressed(domain()->calculateDomainPoint(event->pos()));
386 386 m_lastMousePos = event->pos();
387 387 m_mousePressed = true;
388 388 QGraphicsItem::mousePressEvent(event);
389 389 }
390 390
391 391 void LineChartItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
392 392 {
393 393 emit XYChart::hovered(domain()->calculateDomainPoint(event->pos()), true);
394 394 // event->accept();
395 395 QGraphicsItem::hoverEnterEvent(event);
396 396 }
397 397
398 398 void LineChartItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
399 399 {
400 400 emit XYChart::hovered(domain()->calculateDomainPoint(event->pos()), false);
401 401 // event->accept();
402 402 QGraphicsItem::hoverEnterEvent(event);
403 403 }
404 404
405 405 void LineChartItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
406 406 {
407 emit XYChart::released(domain()->calculateDomainPoint(event->pos()));
408 if (m_lastMousePos == event->pos() && m_mousePressed)
409 emit XYChart::clicked(domain()->calculateDomainPoint(event->pos()));
407 emit XYChart::released(domain()->calculateDomainPoint(m_lastMousePos));
408 if (m_mousePressed)
409 emit XYChart::clicked(domain()->calculateDomainPoint(m_lastMousePos));
410 410 m_mousePressed = false;
411 411 QGraphicsItem::mouseReleaseEvent(event);
412 412 }
413 413
414 414 void LineChartItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
415 415 {
416 emit XYChart::doubleClicked(domain()->calculateDomainPoint(event->pos()));
416 emit XYChart::doubleClicked(domain()->calculateDomainPoint(m_lastMousePos));
417 417 QGraphicsItem::mouseDoubleClickEvent(event);
418 418 }
419 419
420 420 #include "moc_linechartitem_p.cpp"
421 421
422 422 QT_CHARTS_END_NAMESPACE
@@ -1,335 +1,334
1 1 /****************************************************************************
2 2 **
3 3 ** Copyright (C) 2014 Digia Plc
4 4 ** All rights reserved.
5 5 ** For any questions to Digia, please use contact form at http://qt.io
6 6 **
7 7 ** This file is part of the Qt Charts module.
8 8 **
9 9 ** Licensees holding valid commercial license for Qt may use this file in
10 10 ** accordance with the Qt License Agreement provided with the Software
11 11 ** or, alternatively, in accordance with the terms contained in a written
12 12 ** agreement between you and Digia.
13 13 **
14 14 ** If you have questions regarding the use of this file, please use
15 15 ** contact form at http://qt.io
16 16 **
17 17 ****************************************************************************/
18 18
19 19 #include <private/piesliceitem_p.h>
20 20 #include <private/piechartitem_p.h>
21 21 #include <QtCharts/QPieSeries>
22 22 #include <QtCharts/QPieSlice>
23 23 #include <private/chartpresenter_p.h>
24 24 #include <QtGui/QPainter>
25 25 #include <QtCore/QtMath>
26 26 #include <QtWidgets/QGraphicsSceneEvent>
27 27 #include <QtCore/QTime>
28 28 #include <QtGui/QTextDocument>
29 29 #include <QtCore/QDebug>
30 30
31 31 QT_CHARTS_BEGIN_NAMESPACE
32 32
33 33 QPointF offset(qreal angle, qreal length)
34 34 {
35 35 qreal dx = qSin(angle * (M_PI / 180)) * length;
36 36 qreal dy = qCos(angle * (M_PI / 180)) * length;
37 37 return QPointF(dx, -dy);
38 38 }
39 39
40 40 PieSliceItem::PieSliceItem(QGraphicsItem *parent)
41 41 : QGraphicsObject(parent),
42 42 m_hovered(false),
43 43 m_mousePressed(false)
44 44 {
45 45 setAcceptHoverEvents(true);
46 46 setAcceptedMouseButtons(Qt::MouseButtonMask);
47 47 setZValue(ChartPresenter::PieSeriesZValue);
48 48 setFlag(QGraphicsItem::ItemIsSelectable);
49 49 m_labelItem = new QGraphicsTextItem(this);
50 50 m_labelItem->document()->setDocumentMargin(1.0);
51 51 }
52 52
53 53 PieSliceItem::~PieSliceItem()
54 54 {
55 55 // If user is hovering over the slice and it gets destroyed we do
56 56 // not get a hover leave event. So we must emit the signal here.
57 57 if (m_hovered)
58 58 emit hovered(false);
59 59 }
60 60
61 61 QRectF PieSliceItem::boundingRect() const
62 62 {
63 63 return m_boundingRect;
64 64 }
65 65
66 66 QPainterPath PieSliceItem::shape() const
67 67 {
68 68 // Don't include the label and label arm.
69 69 // This is used to detect a mouse clicks. We do not want clicks from label.
70 70 return m_slicePath;
71 71 }
72 72
73 73 void PieSliceItem::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
74 74 {
75 75 painter->save();
76 76 painter->setClipRect(parentItem()->boundingRect());
77 77 painter->setPen(m_data.m_slicePen);
78 78 painter->setBrush(m_data.m_sliceBrush);
79 79 painter->drawPath(m_slicePath);
80 80 painter->restore();
81 81
82 82 if (m_data.m_isLabelVisible) {
83 83 painter->save();
84 84
85 85 // Pen for label arm not defined in the QPieSeries api, let's use brush's color instead
86 86 painter->setBrush(m_data.m_labelBrush);
87 87
88 88 if (m_data.m_labelPosition == QPieSlice::LabelOutside) {
89 89 painter->setClipRect(parentItem()->boundingRect());
90 90 painter->strokePath(m_labelArmPath, m_data.m_labelBrush.color());
91 91 }
92 92
93 93 painter->restore();
94 94 }
95 95 }
96 96
97 97 void PieSliceItem::hoverEnterEvent(QGraphicsSceneHoverEvent * /*event*/)
98 98 {
99 99 m_hovered = true;
100 100 emit hovered(true);
101 101 }
102 102
103 103 void PieSliceItem::hoverLeaveEvent(QGraphicsSceneHoverEvent * /*event*/)
104 104 {
105 105 m_hovered = false;
106 106 emit hovered(false);
107 107 }
108 108
109 109 void PieSliceItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
110 110 {
111 111 emit pressed(event->buttons());
112 m_lastMousePos = event->pos();
113 112 m_mousePressed = true;
114 113 }
115 114
116 115 void PieSliceItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
117 116 {
118 117 emit released(event->buttons());
119 if (m_lastMousePos == event->pos() && m_mousePressed)
118 if (m_mousePressed)
120 119 emit clicked(event->buttons());
121 120 }
122 121
123 122 void PieSliceItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
124 123 {
125 124 // For Pie slice a press signal needs to be explicitly fired for mouseDoubleClickEvent
126 125 emit pressed(event->buttons());
127 126 emit doubleClicked(event->buttons());
128 127 }
129 128
130 129 void PieSliceItem::setLayout(const PieSliceData &sliceData)
131 130 {
132 131 m_data = sliceData;
133 132 updateGeometry();
134 133 update();
135 134 }
136 135
137 136 void PieSliceItem::updateGeometry()
138 137 {
139 138 if (m_data.m_radius <= 0)
140 139 return;
141 140
142 141 prepareGeometryChange();
143 142
144 143 // slice path
145 144 qreal centerAngle;
146 145 QPointF armStart;
147 146 m_slicePath = slicePath(m_data.m_center, m_data.m_radius, m_data.m_startAngle, m_data.m_angleSpan, &centerAngle, &armStart);
148 147
149 148 m_labelItem->setVisible(m_data.m_isLabelVisible);
150 149
151 150 if (m_data.m_isLabelVisible) {
152 151 // text rect
153 152 m_labelTextRect = ChartPresenter::textBoundingRect(m_data.m_labelFont,
154 153 m_data.m_labelText,
155 154 0);
156 155
157 156 QString label(m_data.m_labelText);
158 157 m_labelItem->setDefaultTextColor(m_data.m_labelBrush.color());
159 158 m_labelItem->setFont(m_data.m_labelFont);
160 159
161 160 // text position
162 161 if (m_data.m_labelPosition == QPieSlice::LabelOutside) {
163 162 setFlag(QGraphicsItem::ItemClipsChildrenToShape, false);
164 163
165 164 // label arm path
166 165 QPointF labelTextStart;
167 166 m_labelArmPath = labelArmPath(armStart, centerAngle,
168 167 m_data.m_radius * m_data.m_labelArmLengthFactor,
169 168 m_labelTextRect.width(), &labelTextStart);
170 169
171 170 m_labelTextRect.moveBottomLeft(labelTextStart);
172 171 if (m_labelTextRect.left() < 0)
173 172 m_labelTextRect.setLeft(0);
174 173 else if (m_labelTextRect.left() < parentItem()->boundingRect().left())
175 174 m_labelTextRect.setLeft(parentItem()->boundingRect().left());
176 175 if (m_labelTextRect.right() > parentItem()->boundingRect().right())
177 176 m_labelTextRect.setRight(parentItem()->boundingRect().right());
178 177
179 178 label = ChartPresenter::truncatedText(m_data.m_labelFont, m_data.m_labelText,
180 179 qreal(0.0), m_labelTextRect.width(),
181 180 m_labelTextRect.height(), m_labelTextRect);
182 181 m_labelArmPath = labelArmPath(armStart, centerAngle,
183 182 m_data.m_radius * m_data.m_labelArmLengthFactor,
184 183 m_labelTextRect.width(), &labelTextStart);
185 184 m_labelTextRect.moveBottomLeft(labelTextStart);
186 185
187 186 m_labelItem->setTextWidth(m_labelTextRect.width()
188 187 + m_labelItem->document()->documentMargin());
189 188 m_labelItem->setHtml(label);
190 189 m_labelItem->setRotation(0);
191 190 m_labelItem->setPos(m_labelTextRect.x(), m_labelTextRect.y() + 1.0);
192 191 } else {
193 192 // label inside
194 193 setFlag(QGraphicsItem::ItemClipsChildrenToShape);
195 194 m_labelItem->setTextWidth(m_labelTextRect.width()
196 195 + m_labelItem->document()->documentMargin());
197 196 m_labelItem->setHtml(label);
198 197
199 198 QPointF textCenter;
200 199 if (m_data.m_holeRadius > 0) {
201 200 textCenter = m_data.m_center + offset(centerAngle, m_data.m_holeRadius
202 201 + (m_data.m_radius
203 202 - m_data.m_holeRadius) / 2);
204 203 } else {
205 204 textCenter = m_data.m_center + offset(centerAngle, m_data.m_radius / 2);
206 205 }
207 206 m_labelItem->setPos(textCenter.x() - m_labelItem->boundingRect().width() / 2,
208 207 textCenter.y() - m_labelTextRect.height() / 2);
209 208
210 209 QPointF labelCenter = m_labelItem->boundingRect().center();
211 210 m_labelItem->setTransformOriginPoint(labelCenter);
212 211
213 212 if (m_data.m_labelPosition == QPieSlice::LabelInsideTangential) {
214 213 m_labelItem->setRotation(m_data.m_startAngle + m_data.m_angleSpan / 2);
215 214 } else if (m_data.m_labelPosition == QPieSlice::LabelInsideNormal) {
216 215 if (m_data.m_startAngle + m_data.m_angleSpan / 2 < 180)
217 216 m_labelItem->setRotation(m_data.m_startAngle + m_data.m_angleSpan / 2 - 90);
218 217 else
219 218 m_labelItem->setRotation(m_data.m_startAngle + m_data.m_angleSpan / 2 + 90);
220 219 } else {
221 220 m_labelItem->setRotation(0);
222 221 }
223 222 }
224 223 // Hide label if it's outside the bounding rect of parent item
225 224 QRectF labelRect(m_labelItem->boundingRect());
226 225 labelRect.moveTopLeft(m_labelItem->pos());
227 226 if ((parentItem()->boundingRect().left()
228 227 < (labelRect.left() + m_labelItem->document()->documentMargin() + 1.0))
229 228 && (parentItem()->boundingRect().right()
230 229 > (labelRect.right() - m_labelItem->document()->documentMargin() - 1.0))
231 230 && (parentItem()->boundingRect().top()
232 231 < (labelRect.top() + m_labelItem->document()->documentMargin() + 1.0))
233 232 && (parentItem()->boundingRect().bottom()
234 233 > (labelRect.bottom() - m_labelItem->document()->documentMargin() - 1.0)))
235 234 m_labelItem->show();
236 235 else
237 236 m_labelItem->hide();
238 237 }
239 238
240 239 // bounding rect
241 240 if (m_data.m_isLabelVisible)
242 241 m_boundingRect = m_slicePath.boundingRect().united(m_labelArmPath.boundingRect()).united(m_labelTextRect);
243 242 else
244 243 m_boundingRect = m_slicePath.boundingRect();
245 244
246 245 // Inflate bounding rect by 2/3 pen width to make sure it encompasses whole slice also for thick pens
247 246 // and miter joins.
248 247 int penWidth = (m_data.m_slicePen.width() * 2) / 3;
249 248 m_boundingRect = m_boundingRect.adjusted(-penWidth, -penWidth, penWidth, penWidth);
250 249 }
251 250
252 251 QPointF PieSliceItem::sliceCenter(QPointF point, qreal radius, QPieSlice *slice)
253 252 {
254 253 if (slice->isExploded()) {
255 254 qreal centerAngle = slice->startAngle() + (slice->angleSpan() / 2);
256 255 qreal len = radius * slice->explodeDistanceFactor();
257 256 point += offset(centerAngle, len);
258 257 }
259 258 return point;
260 259 }
261 260
262 261 QPainterPath PieSliceItem::slicePath(QPointF center, qreal radius, qreal startAngle, qreal angleSpan, qreal *centerAngle, QPointF *armStart)
263 262 {
264 263 // calculate center angle
265 264 *centerAngle = startAngle + (angleSpan / 2);
266 265
267 266 // calculate slice rectangle
268 267 QRectF rect(center.x() - radius, center.y() - radius, radius * 2, radius * 2);
269 268
270 269 // slice path
271 270 QPainterPath path;
272 271 if (m_data.m_holeRadius > 0) {
273 272 QRectF insideRect(center.x() - m_data.m_holeRadius, center.y() - m_data.m_holeRadius, m_data.m_holeRadius * 2, m_data.m_holeRadius * 2);
274 273 path.arcMoveTo(rect, -startAngle + 90);
275 274 path.arcTo(rect, -startAngle + 90, -angleSpan);
276 275 path.arcTo(insideRect, -startAngle + 90 - angleSpan, angleSpan);
277 276 path.closeSubpath();
278 277 } else {
279 278 path.moveTo(rect.center());
280 279 path.arcTo(rect, -startAngle + 90, -angleSpan);
281 280 path.closeSubpath();
282 281 }
283 282
284 283 // calculate label arm start point
285 284 *armStart = center;
286 285 *armStart += offset(*centerAngle, radius + PIESLICE_LABEL_GAP);
287 286
288 287 return path;
289 288 }
290 289
291 290 QPainterPath PieSliceItem::labelArmPath(QPointF start, qreal angle, qreal length, qreal textWidth, QPointF *textStart)
292 291 {
293 292 // Normalize the angle to 0-360 range
294 293 // NOTE: We are using int here on purpose. Depenging on platform and hardware
295 294 // qreal can be a double, float or something the user gives to the Qt configure
296 295 // (QT_COORD_TYPE). Compilers do not seem to support modulo for double or float
297 296 // but there are fmod() and fmodf() functions for that. So instead of some #ifdef
298 297 // that might break we just use int. Precision for this is just fine for our needs.
299 298 int normalized = angle * 10.0;
300 299 normalized = normalized % 3600;
301 300 if (normalized < 0)
302 301 normalized += 3600;
303 302 angle = (qreal) normalized / 10.0;
304 303
305 304 // prevent label arm pointing straight down because it will look bad
306 305 if (angle < 180 && angle > 170)
307 306 angle = 170;
308 307 if (angle > 180 && angle < 190)
309 308 angle = 190;
310 309
311 310 // line from slice to label
312 311 QPointF parm1 = start + offset(angle, length);
313 312
314 313 // line to underline the label
315 314 QPointF parm2 = parm1;
316 315 if (angle < 180) { // arm swings the other way on the left side
317 316 parm2 += QPointF(textWidth, 0);
318 317 *textStart = parm1;
319 318 } else {
320 319 parm2 += QPointF(-textWidth, 0);
321 320 *textStart = parm2;
322 321 }
323 322
324 323 QPainterPath path;
325 324 path.moveTo(start);
326 325 path.lineTo(parm1);
327 326 path.lineTo(parm2);
328 327
329 328 return path;
330 329 }
331 330
332 331 #include "moc_piesliceitem_p.cpp"
333 332
334 333 QT_CHARTS_END_NAMESPACE
335 334
@@ -1,97 +1,96
1 1 /****************************************************************************
2 2 **
3 3 ** Copyright (C) 2014 Digia Plc
4 4 ** All rights reserved.
5 5 ** For any questions to Digia, please use contact form at http://qt.io
6 6 **
7 7 ** This file is part of the Qt Charts module.
8 8 **
9 9 ** Licensees holding valid commercial license for Qt may use this file in
10 10 ** accordance with the Qt License Agreement provided with the Software
11 11 ** or, alternatively, in accordance with the terms contained in a written
12 12 ** agreement between you and Digia.
13 13 **
14 14 ** If you have questions regarding the use of this file, please use
15 15 ** contact form at http://qt.io
16 16 **
17 17 ****************************************************************************/
18 18
19 19 // W A R N I N G
20 20 // -------------
21 21 //
22 22 // This file is not part of the Qt Enterprise Chart API. It exists purely as an
23 23 // implementation detail. This header file may change from version to
24 24 // version without notice, or even be removed.
25 25 //
26 26 // We mean it.
27 27
28 28 #ifndef PIESLICEITEM_H
29 29 #define PIESLICEITEM_H
30 30
31 31 #include <QtCharts/QChartGlobal>
32 32 #include <private/charttheme_p.h>
33 33 #include <QtCharts/QPieSeries>
34 34 #include <private/pieslicedata_p.h>
35 35 #include <QtWidgets/QGraphicsItem>
36 36 #include <QtCore/QRectF>
37 37 #include <QtGui/QColor>
38 38 #include <QtGui/QPen>
39 39
40 40 #define PIESLICE_LABEL_GAP 5
41 41
42 42 QT_CHARTS_BEGIN_NAMESPACE
43 43 class PieChartItem;
44 44 class PieSliceLabel;
45 45 class QPieSlice;
46 46
47 47 class PieSliceItem : public QGraphicsObject
48 48 {
49 49 Q_OBJECT
50 50
51 51 public:
52 52 PieSliceItem(QGraphicsItem *parent = 0);
53 53 ~PieSliceItem();
54 54
55 55 // from QGraphicsItem
56 56 QRectF boundingRect() const;
57 57 QPainterPath shape() const;
58 58 void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
59 59 void hoverEnterEvent(QGraphicsSceneHoverEvent *event);
60 60 void hoverLeaveEvent(QGraphicsSceneHoverEvent *event);
61 61 void mousePressEvent(QGraphicsSceneMouseEvent *event);
62 62 void mouseReleaseEvent(QGraphicsSceneMouseEvent *event);
63 63 void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event);
64 64
65 65 void setLayout(const PieSliceData &sliceData);
66 66 static QPointF sliceCenter(QPointF point, qreal radius, QPieSlice *slice);
67 67
68 68 Q_SIGNALS:
69 69 void clicked(Qt::MouseButtons buttons);
70 70 void hovered(bool state);
71 71 void pressed(Qt::MouseButtons buttons);
72 72 void released(Qt::MouseButtons buttons);
73 73 void doubleClicked(Qt::MouseButtons buttons);
74 74
75 75 private:
76 76 void updateGeometry();
77 77 QPainterPath slicePath(QPointF center, qreal radius, qreal startAngle, qreal angleSpan, qreal *centerAngle, QPointF *armStart);
78 78 QPainterPath labelArmPath(QPointF start, qreal angle, qreal length, qreal textWidth, QPointF *textStart);
79 79
80 80 private:
81 81 PieSliceData m_data;
82 82 QRectF m_boundingRect;
83 83 QPainterPath m_slicePath;
84 84 QPainterPath m_labelArmPath;
85 85 QRectF m_labelTextRect;
86 86 bool m_hovered;
87 87 QGraphicsTextItem *m_labelItem;
88 88
89 QPointF m_lastMousePos;
90 89 bool m_mousePressed;
91 90
92 91 friend class PieSliceAnimation;
93 92 };
94 93
95 94 QT_CHARTS_END_NAMESPACE
96 95
97 96 #endif // PIESLICEITEM_H
@@ -1,1005 +1,1005
1 1 /****************************************************************************
2 2 **
3 3 ** Copyright (C) 2014 Digia Plc
4 4 ** All rights reserved.
5 5 ** For any questions to Digia, please use contact form at http://qt.io
6 6 **
7 7 ** This file is part of the Qt Charts module.
8 8 **
9 9 ** Licensees holding valid commercial license for Qt may use this file in
10 10 ** accordance with the Qt License Agreement provided with the Software
11 11 ** or, alternatively, in accordance with the terms contained in a written
12 12 ** agreement between you and Digia.
13 13 **
14 14 ** If you have questions regarding the use of this file, please use
15 15 ** contact form at http://qt.io
16 16 **
17 17 ****************************************************************************/
18 18
19 19 #include <QtCharts/QPieSeries>
20 20 #include <private/qpieseries_p.h>
21 21 #include <QtCharts/QPieSlice>
22 22 #include <private/qpieslice_p.h>
23 23 #include <private/pieslicedata_p.h>
24 24 #include <private/chartdataset_p.h>
25 25 #include <private/charttheme_p.h>
26 26 #include <QtCharts/QAbstractAxis>
27 27 #include <private/pieanimation_p.h>
28 28 #include <private/charthelpers_p.h>
29 29
30 30 #include <QtCharts/QPieLegendMarker>
31 31
32 32 QT_CHARTS_BEGIN_NAMESPACE
33 33
34 34 /*!
35 35 \class QPieSeries
36 36 \inmodule Qt Charts
37 37 \brief Pie series API for Qt Charts.
38 38
39 39 The pie series defines a pie chart which consists of pie slices which are defined as QPieSlice objects.
40 40 The slices can have any values as the QPieSeries will calculate its relative value to the sum of all slices.
41 41 The actual slice size is determined by that relative value.
42 42
43 43 Pie size and position on the chart is controlled by using relative values which range from 0.0 to 1.0.
44 44 These relate to the actual chart rectangle.
45 45
46 46 By default the pie is defined as a full pie but it can also be a partial pie.
47 47 This can be done by setting a starting angle and angle span to the series.
48 48 Full pie is 360 degrees where 0 is at 12 a'clock.
49 49
50 50 See the \l {PieChart Example} {pie chart example} or \l {DonutChart Example} {donut chart example} to learn how to use QPieSeries.
51 51 \table 100%
52 52 \row
53 53 \li \image examples_piechart.png
54 54 \li \image examples_donutchart.png
55 55 \endtable
56 56 */
57 57 /*!
58 58 \qmltype PieSeries
59 59 \instantiates QPieSeries
60 60 \inqmlmodule QtCharts
61 61
62 62 \inherits AbstractSeries
63 63
64 64 \brief The PieSeries type is used for making pie charts.
65 65
66 66 The following QML shows how to create a simple pie chart.
67 67
68 68 \snippet qmlchart/qml/qmlchart/View1.qml 1
69 69
70 70 \beginfloatleft
71 71 \image examples_qmlchart1.png
72 72 \endfloat
73 73 \clearfloat
74 74 */
75 75
76 76 /*!
77 77 \property QPieSeries::horizontalPosition
78 78 \brief Defines the horizontal position of the pie.
79 79
80 80 The value is a relative value to the chart rectangle where:
81 81
82 82 \list
83 83 \li 0.0 is the absolute left.
84 84 \li 1.0 is the absolute right.
85 85 \endlist
86 86 Default value is 0.5 (center).
87 87 \sa verticalPosition
88 88 */
89 89
90 90 /*!
91 91 \qmlproperty real PieSeries::horizontalPosition
92 92
93 93 Defines the horizontal position of the pie.
94 94
95 95 The value is a relative value to the chart rectangle where:
96 96
97 97 \list
98 98 \li 0.0 is the absolute left.
99 99 \li 1.0 is the absolute right.
100 100 \endlist
101 101 Default value is 0.5 (center).
102 102 \sa verticalPosition
103 103 */
104 104
105 105 /*!
106 106 \property QPieSeries::verticalPosition
107 107 \brief Defines the vertical position of the pie.
108 108
109 109 The value is a relative value to the chart rectangle where:
110 110
111 111 \list
112 112 \li 0.0 is the absolute top.
113 113 \li 1.0 is the absolute bottom.
114 114 \endlist
115 115 Default value is 0.5 (center).
116 116 \sa horizontalPosition
117 117 */
118 118
119 119 /*!
120 120 \qmlproperty real PieSeries::verticalPosition
121 121
122 122 Defines the vertical position of the pie.
123 123
124 124 The value is a relative value to the chart rectangle where:
125 125
126 126 \list
127 127 \li 0.0 is the absolute top.
128 128 \li 1.0 is the absolute bottom.
129 129 \endlist
130 130 Default value is 0.5 (center).
131 131 \sa horizontalPosition
132 132 */
133 133
134 134 /*!
135 135 \property QPieSeries::size
136 136 \brief Defines the pie size.
137 137
138 138 The value is a relative value to the chart rectangle where:
139 139
140 140 \list
141 141 \li 0.0 is the minimum size (pie not drawn).
142 142 \li 1.0 is the maximum size that can fit the chart.
143 143 \endlist
144 144
145 145 When setting this property the holeSize property is adjusted if necessary, to ensure that the hole size is not greater than the outer size.
146 146
147 147 Default value is 0.7.
148 148 */
149 149
150 150 /*!
151 151 \qmlproperty real PieSeries::size
152 152
153 153 Defines the pie size.
154 154
155 155 The value is a relative value to the chart rectangle where:
156 156
157 157 \list
158 158 \li 0.0 is the minimum size (pie not drawn).
159 159 \li 1.0 is the maximum size that can fit the chart.
160 160 \endlist
161 161
162 162 Default value is 0.7.
163 163 */
164 164
165 165 /*!
166 166 \property QPieSeries::holeSize
167 167 \brief Defines the donut hole size.
168 168
169 169 The value is a relative value to the chart rectangle where:
170 170
171 171 \list
172 172 \li 0.0 is the minimum size (full pie drawn, without any hole inside).
173 173 \li 1.0 is the maximum size that can fit the chart. (donut has no width)
174 174 \endlist
175 175
176 176 The value is never greater then size property.
177 177 Default value is 0.0.
178 178 */
179 179
180 180 /*!
181 181 \qmlproperty real PieSeries::holeSize
182 182
183 183 Defines the donut hole size.
184 184
185 185 The value is a relative value to the chart rectangle where:
186 186
187 187 \list
188 188 \li 0.0 is the minimum size (full pie drawn, without any hole inside).
189 189 \li 1.0 is the maximum size that can fit the chart. (donut has no width)
190 190 \endlist
191 191
192 192 When setting this property the size property is adjusted if necessary, to ensure that the inner size is not greater than the outer size.
193 193
194 194 Default value is 0.0.
195 195 */
196 196
197 197 /*!
198 198 \property QPieSeries::startAngle
199 199 \brief Defines the starting angle of the pie.
200 200
201 201 Full pie is 360 degrees where 0 degrees is at 12 a'clock.
202 202
203 203 Default is value is 0.
204 204 */
205 205
206 206 /*!
207 207 \qmlproperty real PieSeries::startAngle
208 208
209 209 Defines the starting angle of the pie.
210 210
211 211 Full pie is 360 degrees where 0 degrees is at 12 a'clock.
212 212
213 213 Default is value is 0.
214 214 */
215 215
216 216 /*!
217 217 \property QPieSeries::endAngle
218 218 \brief Defines the ending angle of the pie.
219 219
220 220 Full pie is 360 degrees where 0 degrees is at 12 a'clock.
221 221
222 222 Default is value is 360.
223 223 */
224 224
225 225 /*!
226 226 \qmlproperty real PieSeries::endAngle
227 227
228 228 Defines the ending angle of the pie.
229 229
230 230 Full pie is 360 degrees where 0 degrees is at 12 a'clock.
231 231
232 232 Default is value is 360.
233 233 */
234 234
235 235 /*!
236 236 \property QPieSeries::count
237 237
238 238 Number of slices in the series.
239 239 */
240 240
241 241 /*!
242 242 \qmlproperty int PieSeries::count
243 243
244 244 Number of slices in the series.
245 245 */
246 246
247 247 /*!
248 248 \fn void QPieSeries::countChanged()
249 249 Emitted when the slice count has changed.
250 250 \sa count
251 251 */
252 252 /*!
253 253 \qmlsignal PieSeries::onCountChanged()
254 254 Emitted when the slice count has changed.
255 255 */
256 256
257 257 /*!
258 258 \property QPieSeries::sum
259 259
260 260 Sum of all slices.
261 261
262 262 The series keeps track of the sum of all slices it holds.
263 263 */
264 264
265 265 /*!
266 266 \qmlproperty real PieSeries::sum
267 267
268 268 Sum of all slices.
269 269
270 270 The series keeps track of the sum of all slices it holds.
271 271 */
272 272
273 273 /*!
274 274 \fn void QPieSeries::sumChanged()
275 275 Emitted when the sum of all slices has changed.
276 276 \sa sum
277 277 */
278 278 /*!
279 279 \qmlsignal PieSeries::onSumChanged()
280 280 Emitted when the sum of all slices has changed. This may happen for example if you add or remove slices, or if you
281 281 change value of a slice.
282 282 */
283 283
284 284 /*!
285 285 \fn void QPieSeries::added(QList<QPieSlice*> slices)
286 286
287 287 This signal is emitted when \a slices have been added to the series.
288 288
289 289 \sa append(), insert()
290 290 */
291 291 /*!
292 292 \qmlsignal PieSeries::onAdded(PieSlice slice)
293 293 Emitted when \a slice has been added to the series.
294 294 */
295 295
296 296 /*!
297 297 \fn void QPieSeries::removed(QList<QPieSlice*> slices)
298 298 This signal is emitted when \a slices have been removed from the series.
299 299 \sa remove()
300 300 */
301 301 /*!
302 302 \qmlsignal PieSeries::onRemoved(PieSlice slice)
303 303 Emitted when \a slice has been removed from the series.
304 304 */
305 305
306 306 /*!
307 \fn void QPieSeries::clicked(QPieSlice* slice)
307 \fn void QPieSeries::clicked(QPieSlice *slice)
308 308 This signal is emitted when a \a slice has been clicked.
309 309 \sa QPieSlice::clicked()
310 310 */
311 311 /*!
312 312 \qmlsignal PieSeries::onClicked(PieSlice slice)
313 313 This signal is emitted when a \a slice has been clicked.
314 314 */
315 315
316 316 /*!
317 \fn void QPieSeries::pressed(QPieSlice* slice)
317 \fn void QPieSeries::pressed(QPieSlice *slice)
318 318 This signal is emitted when a \a slice has been pressed.
319 319 \sa QPieSlice::pressed()
320 320 */
321 321 /*!
322 322 \qmlsignal PieSeries::onPressed(PieSlice slice)
323 323 This signal is emitted when a \a slice has been pressed.
324 324 */
325 325
326 326 /*!
327 \fn void QPieSeries::released(QPieSlice* slice)
327 \fn void QPieSeries::released(QPieSlice *slice)
328 328 This signal is emitted when a \a slice has been released.
329 329 \sa QPieSlice::released()
330 330 */
331 331 /*!
332 332 \qmlsignal PieSeries::onReleased(PieSlice slice)
333 333 This signal is emitted when a \a slice has been released.
334 334 */
335 335
336 336 /*!
337 \fn void QPieSeries::doubleClicked(QPieSlice* slice)
337 \fn void QPieSeries::doubleClicked(QPieSlice *slice)
338 338 This signal is emitted when a \a slice has been doubleClicked.
339 339 \sa QPieSlice::doubleClicked()
340 340 */
341 341 /*!
342 342 \qmlsignal PieSeries::onDoubleClicked(PieSlice slice)
343 343 This signal is emitted when a \a slice has been doubleClicked.
344 344 */
345 345
346 346 /*!
347 347 \fn void QPieSeries::hovered(QPieSlice* slice, bool state)
348 348 This signal is emitted when user has hovered over or away from the \a slice.
349 349 \a state is true when user has hovered over the slice and false when hover has moved away from the slice.
350 350 \sa QPieSlice::hovered()
351 351 */
352 352 /*!
353 353 \qmlsignal PieSeries::onHovered(PieSlice slice, bool state)
354 354 This signal is emitted when user has hovered over or away from the \a slice. \a state is true when user has hovered
355 355 over the slice and false when hover has moved away from the slice.
356 356 */
357 357
358 358 /*!
359 359 \qmlmethod PieSlice PieSeries::at(int index)
360 360 Returns slice at \a index. Returns null if the index is not valid.
361 361 */
362 362
363 363 /*!
364 364 \qmlmethod PieSlice PieSeries::find(string label)
365 365 Returns the first slice with \a label. Returns null if the index is not valid.
366 366 */
367 367
368 368 /*!
369 369 \qmlmethod PieSlice PieSeries::append(string label, real value)
370 370 Adds a new slice with \a label and \a value to the pie.
371 371 */
372 372
373 373 /*!
374 374 \qmlmethod bool PieSeries::remove(PieSlice slice)
375 375 Removes the \a slice from the pie. Returns true if the removal was successful, false otherwise.
376 376 */
377 377
378 378 /*!
379 379 \qmlmethod PieSeries::clear()
380 380 Removes all slices from the pie.
381 381 */
382 382
383 383 /*!
384 384 Constructs a series object which is a child of \a parent.
385 385 */
386 386 QPieSeries::QPieSeries(QObject *parent)
387 387 : QAbstractSeries(*new QPieSeriesPrivate(this), parent)
388 388 {
389 389 Q_D(QPieSeries);
390 390 QObject::connect(this, SIGNAL(countChanged()), d, SIGNAL(countChanged()));
391 391 }
392 392
393 393 /*!
394 394 Destroys the series and its slices.
395 395 */
396 396 QPieSeries::~QPieSeries()
397 397 {
398 398 // NOTE: d_prt destroyed by QObject
399 399 clear();
400 400 }
401 401
402 402 /*!
403 403 Returns QAbstractSeries::SeriesTypePie.
404 404 */
405 405 QAbstractSeries::SeriesType QPieSeries::type() const
406 406 {
407 407 return QAbstractSeries::SeriesTypePie;
408 408 }
409 409
410 410 /*!
411 411 Appends a single \a slice to the series.
412 412 Slice ownership is passed to the series.
413 413
414 414 Returns true if append was succesfull.
415 415 */
416 416 bool QPieSeries::append(QPieSlice *slice)
417 417 {
418 418 return append(QList<QPieSlice *>() << slice);
419 419 }
420 420
421 421 /*!
422 422 Appends an array of \a slices to the series.
423 423 Slice ownership is passed to the series.
424 424
425 425 Returns true if append was successful.
426 426 */
427 427 bool QPieSeries::append(QList<QPieSlice *> slices)
428 428 {
429 429 Q_D(QPieSeries);
430 430
431 431 if (slices.count() == 0)
432 432 return false;
433 433
434 434 foreach (QPieSlice *s, slices) {
435 435 if (!s || d->m_slices.contains(s))
436 436 return false;
437 437 if (s->series()) // already added to some series
438 438 return false;
439 439 if (!isValidValue(s->value()))
440 440 return false;
441 441 }
442 442
443 443 foreach (QPieSlice *s, slices) {
444 444 s->setParent(this);
445 445 QPieSlicePrivate::fromSlice(s)->m_series = this;
446 446 d->m_slices << s;
447 447 }
448 448
449 449 d->updateDerivativeData();
450 450
451 451 foreach(QPieSlice * s, slices) {
452 452 connect(s, SIGNAL(valueChanged()), d, SLOT(sliceValueChanged()));
453 453 connect(s, SIGNAL(clicked()), d, SLOT(sliceClicked()));
454 454 connect(s, SIGNAL(hovered(bool)), d, SLOT(sliceHovered(bool)));
455 455 connect(s, SIGNAL(pressed()), d, SLOT(slicePressed()));
456 456 connect(s, SIGNAL(released()), d, SLOT(sliceReleased()));
457 457 connect(s, SIGNAL(doubleClicked()), d, SLOT(sliceDoubleClicked()));
458 458 }
459 459
460 460 emit added(slices);
461 461 emit countChanged();
462 462
463 463 return true;
464 464 }
465 465
466 466 /*!
467 467 Appends a single \a slice to the series and returns a reference to the series.
468 468 Slice ownership is passed to the series.
469 469 */
470 470 QPieSeries &QPieSeries::operator << (QPieSlice *slice)
471 471 {
472 472 append(slice);
473 473 return *this;
474 474 }
475 475
476 476
477 477 /*!
478 478 Appends a single slice to the series with give \a value and \a label.
479 479 Slice ownership is passed to the series.
480 480 Returns NULL if value is NaN, Inf or -Inf and no slice is added to the series.
481 481 */
482 482 QPieSlice *QPieSeries::append(QString label, qreal value)
483 483 {
484 484 if (isValidValue(value)) {
485 485 QPieSlice *slice = new QPieSlice(label, value);
486 486 append(slice);
487 487 return slice;
488 488 } else {
489 489 return 0;
490 490 }
491 491 }
492 492
493 493 /*!
494 494 Inserts a single \a slice to the series before the slice at \a index position.
495 495 Slice ownership is passed to the series.
496 496
497 497 Returns true if insert was successful.
498 498 */
499 499 bool QPieSeries::insert(int index, QPieSlice *slice)
500 500 {
501 501 Q_D(QPieSeries);
502 502
503 503 if (index < 0 || index > d->m_slices.count())
504 504 return false;
505 505
506 506 if (!slice || d->m_slices.contains(slice))
507 507 return false;
508 508
509 509 if (slice->series()) // already added to some series
510 510 return false;
511 511
512 512 if (!isValidValue(slice->value()))
513 513 return false;
514 514
515 515 slice->setParent(this);
516 516 QPieSlicePrivate::fromSlice(slice)->m_series = this;
517 517 d->m_slices.insert(index, slice);
518 518
519 519 d->updateDerivativeData();
520 520
521 521 connect(slice, SIGNAL(valueChanged()), d, SLOT(sliceValueChanged()));
522 522 connect(slice, SIGNAL(clicked()), d, SLOT(sliceClicked()));
523 523 connect(slice, SIGNAL(hovered(bool)), d, SLOT(sliceHovered(bool)));
524 524 connect(slice, SIGNAL(pressed()), d, SLOT(slicePressed()));
525 525 connect(slice, SIGNAL(released()), d, SLOT(sliceReleased()));
526 526 connect(slice, SIGNAL(doubleClicked()), d, SLOT(sliceDoubleClicked()));
527 527
528 528 emit added(QList<QPieSlice *>() << slice);
529 529 emit countChanged();
530 530
531 531 return true;
532 532 }
533 533
534 534 /*!
535 535 Removes a single \a slice from the series and deletes the slice.
536 536
537 537 Do not reference the pointer after this call.
538 538
539 539 Returns true if remove was successful.
540 540 */
541 541 bool QPieSeries::remove(QPieSlice *slice)
542 542 {
543 543 Q_D(QPieSeries);
544 544
545 545 if (!d->m_slices.removeOne(slice))
546 546 return false;
547 547
548 548 d->updateDerivativeData();
549 549
550 550 emit removed(QList<QPieSlice *>() << slice);
551 551 emit countChanged();
552 552
553 553 delete slice;
554 554 slice = 0;
555 555
556 556 return true;
557 557 }
558 558
559 559 /*!
560 560 Takes a single \a slice from the series. Does not destroy the slice object.
561 561
562 562 \note The series remains as the slice's parent object. You must set the
563 563 parent object to take full ownership.
564 564
565 565 Returns true if take was successful.
566 566 */
567 567 bool QPieSeries::take(QPieSlice *slice)
568 568 {
569 569 Q_D(QPieSeries);
570 570
571 571 if (!d->m_slices.removeOne(slice))
572 572 return false;
573 573
574 574 QPieSlicePrivate::fromSlice(slice)->m_series = 0;
575 575 slice->disconnect(d);
576 576
577 577 d->updateDerivativeData();
578 578
579 579 emit removed(QList<QPieSlice *>() << slice);
580 580 emit countChanged();
581 581
582 582 return true;
583 583 }
584 584
585 585 /*!
586 586 Clears all slices from the series.
587 587 */
588 588 void QPieSeries::clear()
589 589 {
590 590 Q_D(QPieSeries);
591 591 if (d->m_slices.count() == 0)
592 592 return;
593 593
594 594 QList<QPieSlice *> slices = d->m_slices;
595 595 foreach (QPieSlice *s, d->m_slices)
596 596 d->m_slices.removeOne(s);
597 597
598 598 d->updateDerivativeData();
599 599
600 600 emit removed(slices);
601 601 emit countChanged();
602 602
603 603 foreach (QPieSlice *s, slices)
604 604 delete s;
605 605 }
606 606
607 607 /*!
608 608 Returns a list of slices that belong to this series.
609 609 */
610 610 QList<QPieSlice *> QPieSeries::slices() const
611 611 {
612 612 Q_D(const QPieSeries);
613 613 return d->m_slices;
614 614 }
615 615
616 616 /*!
617 617 returns the number of the slices in this series.
618 618 */
619 619 int QPieSeries::count() const
620 620 {
621 621 Q_D(const QPieSeries);
622 622 return d->m_slices.count();
623 623 }
624 624
625 625 /*!
626 626 Returns true is the series is empty.
627 627 */
628 628 bool QPieSeries::isEmpty() const
629 629 {
630 630 Q_D(const QPieSeries);
631 631 return d->m_slices.isEmpty();
632 632 }
633 633
634 634 /*!
635 635 Returns the sum of all slice values in this series.
636 636
637 637 \sa QPieSlice::value(), QPieSlice::setValue(), QPieSlice::percentage()
638 638 */
639 639 qreal QPieSeries::sum() const
640 640 {
641 641 Q_D(const QPieSeries);
642 642 return d->m_sum;
643 643 }
644 644
645 645 void QPieSeries::setHoleSize(qreal holeSize)
646 646 {
647 647 Q_D(QPieSeries);
648 648 holeSize = qBound((qreal)0.0, holeSize, (qreal)1.0);
649 649 d->setSizes(holeSize, qMax(d->m_pieRelativeSize, holeSize));
650 650 }
651 651
652 652 qreal QPieSeries::holeSize() const
653 653 {
654 654 Q_D(const QPieSeries);
655 655 return d->m_holeRelativeSize;
656 656 }
657 657
658 658 void QPieSeries::setHorizontalPosition(qreal relativePosition)
659 659 {
660 660 Q_D(QPieSeries);
661 661
662 662 if (relativePosition < 0.0)
663 663 relativePosition = 0.0;
664 664 if (relativePosition > 1.0)
665 665 relativePosition = 1.0;
666 666
667 667 if (!qFuzzyCompare(d->m_pieRelativeHorPos, relativePosition)) {
668 668 d->m_pieRelativeHorPos = relativePosition;
669 669 emit d->horizontalPositionChanged();
670 670 }
671 671 }
672 672
673 673 qreal QPieSeries::horizontalPosition() const
674 674 {
675 675 Q_D(const QPieSeries);
676 676 return d->m_pieRelativeHorPos;
677 677 }
678 678
679 679 void QPieSeries::setVerticalPosition(qreal relativePosition)
680 680 {
681 681 Q_D(QPieSeries);
682 682
683 683 if (relativePosition < 0.0)
684 684 relativePosition = 0.0;
685 685 if (relativePosition > 1.0)
686 686 relativePosition = 1.0;
687 687
688 688 if (!qFuzzyCompare(d->m_pieRelativeVerPos, relativePosition)) {
689 689 d->m_pieRelativeVerPos = relativePosition;
690 690 emit d->verticalPositionChanged();
691 691 }
692 692 }
693 693
694 694 qreal QPieSeries::verticalPosition() const
695 695 {
696 696 Q_D(const QPieSeries);
697 697 return d->m_pieRelativeVerPos;
698 698 }
699 699
700 700 void QPieSeries::setPieSize(qreal relativeSize)
701 701 {
702 702 Q_D(QPieSeries);
703 703 relativeSize = qBound((qreal)0.0, relativeSize, (qreal)1.0);
704 704 d->setSizes(qMin(d->m_holeRelativeSize, relativeSize), relativeSize);
705 705
706 706 }
707 707
708 708 qreal QPieSeries::pieSize() const
709 709 {
710 710 Q_D(const QPieSeries);
711 711 return d->m_pieRelativeSize;
712 712 }
713 713
714 714
715 715 void QPieSeries::setPieStartAngle(qreal angle)
716 716 {
717 717 Q_D(QPieSeries);
718 718 if (qFuzzyCompare(d->m_pieStartAngle, angle))
719 719 return;
720 720 d->m_pieStartAngle = angle;
721 721 d->updateDerivativeData();
722 722 emit d->pieStartAngleChanged();
723 723 }
724 724
725 725 qreal QPieSeries::pieStartAngle() const
726 726 {
727 727 Q_D(const QPieSeries);
728 728 return d->m_pieStartAngle;
729 729 }
730 730
731 731 /*!
732 732 Sets the end angle of the pie.
733 733
734 734 Full pie is 360 degrees where 0 degrees is at 12 a'clock.
735 735
736 736 \a angle must be greater than start angle.
737 737
738 738 \sa pieEndAngle(), pieStartAngle(), setPieStartAngle()
739 739 */
740 740 void QPieSeries::setPieEndAngle(qreal angle)
741 741 {
742 742 Q_D(QPieSeries);
743 743 if (qFuzzyCompare(d->m_pieEndAngle, angle))
744 744 return;
745 745 d->m_pieEndAngle = angle;
746 746 d->updateDerivativeData();
747 747 emit d->pieEndAngleChanged();
748 748 }
749 749
750 750 /*!
751 751 Returns the end angle of the pie.
752 752
753 753 Full pie is 360 degrees where 0 degrees is at 12 a'clock.
754 754
755 755 \sa setPieEndAngle(), pieStartAngle(), setPieStartAngle()
756 756 */
757 757 qreal QPieSeries::pieEndAngle() const
758 758 {
759 759 Q_D(const QPieSeries);
760 760 return d->m_pieEndAngle;
761 761 }
762 762
763 763 /*!
764 764 Sets the all the slice labels \a visible or invisible.
765 765
766 766 Note that this affects only the current slices in the series.
767 767 If user adds a new slice the default label visibility is false.
768 768
769 769 \sa QPieSlice::isLabelVisible(), QPieSlice::setLabelVisible()
770 770 */
771 771 void QPieSeries::setLabelsVisible(bool visible)
772 772 {
773 773 Q_D(QPieSeries);
774 774 foreach (QPieSlice *s, d->m_slices)
775 775 s->setLabelVisible(visible);
776 776 }
777 777
778 778 /*!
779 779 Sets the all the slice labels \a position
780 780
781 781 Note that this affects only the current slices in the series.
782 782 If user adds a new slice the default label position is LabelOutside
783 783
784 784 \sa QPieSlice::labelPosition(), QPieSlice::setLabelPosition()
785 785 */
786 786 void QPieSeries::setLabelsPosition(QPieSlice::LabelPosition position)
787 787 {
788 788 Q_D(QPieSeries);
789 789 foreach (QPieSlice *s, d->m_slices)
790 790 s->setLabelPosition(position);
791 791 }
792 792
793 793 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
794 794
795 795
796 796 QPieSeriesPrivate::QPieSeriesPrivate(QPieSeries *parent) :
797 797 QAbstractSeriesPrivate(parent),
798 798 m_pieRelativeHorPos(0.5),
799 799 m_pieRelativeVerPos(0.5),
800 800 m_pieRelativeSize(0.7),
801 801 m_pieStartAngle(0),
802 802 m_pieEndAngle(360),
803 803 m_sum(0),
804 804 m_holeRelativeSize(0.0)
805 805 {
806 806 }
807 807
808 808 QPieSeriesPrivate::~QPieSeriesPrivate()
809 809 {
810 810 }
811 811
812 812 void QPieSeriesPrivate::updateDerivativeData()
813 813 {
814 814 // calculate sum of all slices
815 815 qreal sum = 0;
816 816 foreach (QPieSlice *s, m_slices)
817 817 sum += s->value();
818 818
819 819 if (!qFuzzyCompare(m_sum, sum)) {
820 820 m_sum = sum;
821 821 emit q_func()->sumChanged();
822 822 }
823 823
824 824 // nothing to show..
825 825 if (qFuzzyCompare(m_sum, 0))
826 826 return;
827 827
828 828 // update slice attributes
829 829 qreal sliceAngle = m_pieStartAngle;
830 830 qreal pieSpan = m_pieEndAngle - m_pieStartAngle;
831 831 QVector<QPieSlice *> changed;
832 832 foreach (QPieSlice *s, m_slices) {
833 833 QPieSlicePrivate *d = QPieSlicePrivate::fromSlice(s);
834 834 d->setPercentage(s->value() / m_sum);
835 835 d->setStartAngle(sliceAngle);
836 836 d->setAngleSpan(pieSpan * s->percentage());
837 837 sliceAngle += s->angleSpan();
838 838 }
839 839
840 840
841 841 emit calculatedDataChanged();
842 842 }
843 843
844 844 void QPieSeriesPrivate::setSizes(qreal innerSize, qreal outerSize)
845 845 {
846 846 bool changed = false;
847 847
848 848 if (!qFuzzyCompare(m_holeRelativeSize, innerSize)) {
849 849 m_holeRelativeSize = innerSize;
850 850 changed = true;
851 851 }
852 852
853 853 if (!qFuzzyCompare(m_pieRelativeSize, outerSize)) {
854 854 m_pieRelativeSize = outerSize;
855 855 changed = true;
856 856 }
857 857
858 858 if (changed)
859 859 emit pieSizeChanged();
860 860 }
861 861
862 862 QPieSeriesPrivate *QPieSeriesPrivate::fromSeries(QPieSeries *series)
863 863 {
864 864 return series->d_func();
865 865 }
866 866
867 867 void QPieSeriesPrivate::sliceValueChanged()
868 868 {
869 869 Q_ASSERT(m_slices.contains(qobject_cast<QPieSlice *>(sender())));
870 870 updateDerivativeData();
871 871 }
872 872
873 873 void QPieSeriesPrivate::sliceClicked()
874 874 {
875 875 QPieSlice *slice = qobject_cast<QPieSlice *>(sender());
876 876 Q_ASSERT(m_slices.contains(slice));
877 877 Q_Q(QPieSeries);
878 878 emit q->clicked(slice);
879 879 }
880 880
881 881 void QPieSeriesPrivate::sliceHovered(bool state)
882 882 {
883 883 QPieSlice *slice = qobject_cast<QPieSlice *>(sender());
884 884 if (!m_slices.isEmpty()) {
885 885 Q_ASSERT(m_slices.contains(slice));
886 886 Q_Q(QPieSeries);
887 887 emit q->hovered(slice, state);
888 888 }
889 889 }
890 890
891 891 void QPieSeriesPrivate::slicePressed()
892 892 {
893 893 QPieSlice *slice = qobject_cast<QPieSlice *>(sender());
894 894 Q_ASSERT(m_slices.contains(slice));
895 895 Q_Q(QPieSeries);
896 896 emit q->pressed(slice);
897 897 }
898 898
899 899 void QPieSeriesPrivate::sliceReleased()
900 900 {
901 901 QPieSlice *slice = qobject_cast<QPieSlice *>(sender());
902 902 Q_ASSERT(m_slices.contains(slice));
903 903 Q_Q(QPieSeries);
904 904 emit q->released(slice);
905 905 }
906 906
907 907 void QPieSeriesPrivate::sliceDoubleClicked()
908 908 {
909 909 QPieSlice *slice = qobject_cast<QPieSlice *>(sender());
910 910 Q_ASSERT(m_slices.contains(slice));
911 911 Q_Q(QPieSeries);
912 912 emit q->doubleClicked(slice);
913 913 }
914 914
915 915 void QPieSeriesPrivate::initializeDomain()
916 916 {
917 917 // does not apply to pie
918 918 }
919 919
920 920 void QPieSeriesPrivate::initializeGraphics(QGraphicsItem* parent)
921 921 {
922 922 Q_Q(QPieSeries);
923 923 PieChartItem *pie = new PieChartItem(q,parent);
924 924 m_item.reset(pie);
925 925 QAbstractSeriesPrivate::initializeGraphics(parent);
926 926 }
927 927
928 928 void QPieSeriesPrivate::initializeAnimations(QtCharts::QChart::AnimationOptions options)
929 929 {
930 930 PieChartItem *item = static_cast<PieChartItem *>(m_item.data());
931 931 Q_ASSERT(item);
932 932 if (item->animation())
933 933 item->animation()->stopAndDestroyLater();
934 934
935 935 if (options.testFlag(QChart::SeriesAnimations))
936 936 item->setAnimation(new PieAnimation(item));
937 937 else
938 938 item->setAnimation(0);
939 939 QAbstractSeriesPrivate::initializeAnimations(options);
940 940 }
941 941
942 942 QList<QLegendMarker*> QPieSeriesPrivate::createLegendMarkers(QLegend* legend)
943 943 {
944 944 Q_Q(QPieSeries);
945 945 QList<QLegendMarker*> markers;
946 946 foreach(QPieSlice* slice, q->slices()) {
947 947 QPieLegendMarker* marker = new QPieLegendMarker(q,slice,legend);
948 948 markers << marker;
949 949 }
950 950 return markers;
951 951 }
952 952
953 953 void QPieSeriesPrivate::initializeAxes()
954 954 {
955 955
956 956 }
957 957
958 958 QAbstractAxis::AxisType QPieSeriesPrivate::defaultAxisType(Qt::Orientation orientation) const
959 959 {
960 960 Q_UNUSED(orientation);
961 961 return QAbstractAxis::AxisTypeNoAxis;
962 962 }
963 963
964 964 QAbstractAxis* QPieSeriesPrivate::createDefaultAxis(Qt::Orientation orientation) const
965 965 {
966 966 Q_UNUSED(orientation);
967 967 return 0;
968 968 }
969 969
970 970 void QPieSeriesPrivate::initializeTheme(int index, ChartTheme* theme, bool forced)
971 971 {
972 972 //Q_Q(QPieSeries);
973 973 //const QList<QColor>& colors = theme->seriesColors();
974 974 const QList<QGradient>& gradients = theme->seriesGradients();
975 975
976 976 for (int i(0); i < m_slices.count(); i++) {
977 977
978 978 QColor penColor = ChartThemeManager::colorAt(gradients.at(index % gradients.size()), 0.0);
979 979
980 980 // Get color for a slice from a gradient linearly, beginning from the start of the gradient
981 981 qreal pos = (qreal)(i + 1) / (qreal) m_slices.count();
982 982 QColor brushColor = ChartThemeManager::colorAt(gradients.at(index % gradients.size()), pos);
983 983
984 984 QPieSlice *s = m_slices.at(i);
985 985 QPieSlicePrivate *d = QPieSlicePrivate::fromSlice(s);
986 986
987 987 if (forced || d->m_data.m_slicePen.isThemed())
988 988 d->setPen(penColor, true);
989 989
990 990 if (forced || d->m_data.m_sliceBrush.isThemed())
991 991 d->setBrush(brushColor, true);
992 992
993 993 if (forced || d->m_data.m_labelBrush.isThemed())
994 994 d->setLabelBrush(theme->labelBrush().color(), true);
995 995
996 996 if (forced || d->m_data.m_labelFont.isThemed())
997 997 d->setLabelFont(theme->labelFont(), true);
998 998 }
999 999 }
1000 1000
1001 1001
1002 1002 #include "moc_qpieseries.cpp"
1003 1003 #include "moc_qpieseries_p.cpp"
1004 1004
1005 1005 QT_CHARTS_END_NAMESPACE
@@ -1,194 +1,189
1 1 /****************************************************************************
2 2 **
3 3 ** Copyright (C) 2014 Digia Plc
4 4 ** All rights reserved.
5 5 ** For any questions to Digia, please use contact form at http://qt.io
6 6 **
7 7 ** This file is part of the Qt Charts module.
8 8 **
9 9 ** Licensees holding valid commercial license for Qt may use this file in
10 10 ** accordance with the Qt License Agreement provided with the Software
11 11 ** or, alternatively, in accordance with the terms contained in a written
12 12 ** agreement between you and Digia.
13 13 **
14 14 ** If you have questions regarding the use of this file, please use
15 15 ** contact form at http://qt.io
16 16 **
17 17 ****************************************************************************/
18 18
19 19 // W A R N I N G
20 20 // -------------
21 21 //
22 22 // This file is not part of the Qt Enterprise Chart API. It exists purely as an
23 23 // implementation detail. This header file may change from version to
24 24 // version without notice, or even be removed.
25 25 //
26 26 // We mean it.
27 27
28 28 #ifndef SCATTERCHARTITEM_H
29 29 #define SCATTERCHARTITEM_H
30 30
31 31 #include <QtCharts/QChartGlobal>
32 32 #include <private/xychart_p.h>
33 33 #include <QtWidgets/QGraphicsEllipseItem>
34 34 #include <QtGui/QPen>
35 35 #include <QtWidgets/QGraphicsSceneMouseEvent>
36 36
37 37 QT_CHARTS_BEGIN_NAMESPACE
38 38
39 39 class QScatterSeries;
40 40
41 41 class ScatterChartItem : public XYChart
42 42 {
43 43 Q_OBJECT
44 44 Q_INTERFACES(QGraphicsItem)
45 45 public:
46 46 explicit ScatterChartItem(QScatterSeries *series, QGraphicsItem *item = 0);
47 47
48 48 public:
49 49 //from QGraphicsItem
50 50 QRectF boundingRect() const;
51 51 void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
52 52
53 53 void setPen(const QPen &pen);
54 54 void setBrush(const QBrush &brush);
55 55
56 56 void markerSelected(QGraphicsItem *item);
57 57 void markerHovered(QGraphicsItem *item, bool state);
58 58 void markerPressed(QGraphicsItem *item);
59 59 void markerReleased(QGraphicsItem *item);
60 60 void markerDoubleClicked(QGraphicsItem *item);
61 61
62 void setLastMousePosition(const QPointF pos) {m_lastMousePos = pos;}
63 QPointF lastMousePosition() const {return m_lastMousePos;}
64 62 void setMousePressed(bool pressed = true) {m_mousePressed = pressed;}
65 63 bool mousePressed() {return m_mousePressed;}
66 64
67 65
68 66 public Q_SLOTS:
69 67 void handleUpdated();
70 68
71 69 private:
72 70 void createPoints(int count);
73 71 void deletePoints(int count);
74 72
75 73 protected:
76 74 void updateGeometry();
77 75
78 76 private:
79 77 QScatterSeries *m_series;
80 78 QGraphicsItemGroup m_items;
81 79 bool m_visible;
82 80 int m_shape;
83 81 int m_size;
84 82 QRectF m_rect;
85 83 QMap<QGraphicsItem *, QPointF> m_markerMap;
86 84
87 85 bool m_pointLabelsVisible;
88 86 QString m_pointLabelsFormat;
89 87 QFont m_pointLabelsFont;
90 88 QColor m_pointLabelsColor;
91 89
92 QPointF m_lastMousePos;
93 90 bool m_mousePressed;
94 91 };
95 92
96 93 class CircleMarker: public QGraphicsEllipseItem
97 94 {
98 95
99 96 public:
100 97 CircleMarker(qreal x, qreal y, qreal w, qreal h, ScatterChartItem *parent)
101 98 : QGraphicsEllipseItem(x, y, w, h, parent),
102 99 m_parent(parent)
103 100 {
104 101 setAcceptHoverEvents(true);
105 102 setFlag(QGraphicsItem::ItemIsSelectable);
106 103 }
107 104
108 105 protected:
109 106 void mousePressEvent(QGraphicsSceneMouseEvent *event)
110 107 {
111 108 QGraphicsEllipseItem::mousePressEvent(event);
112 109 m_parent->markerPressed(this);
113 m_parent->setLastMousePosition(event->pos());
114 110 m_parent->setMousePressed();
115 111 }
116 112 void hoverEnterEvent(QGraphicsSceneHoverEvent *event)
117 113 {
118 114 QGraphicsEllipseItem::hoverEnterEvent(event);
119 115 m_parent->markerHovered(this, true);
120 116 }
121 117 void hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
122 118 {
123 119 QGraphicsEllipseItem::hoverLeaveEvent(event);
124 120 m_parent->markerHovered(this, false);
125 121 }
126 122 void mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
127 123 {
128 124 QGraphicsEllipseItem::mouseReleaseEvent(event);
129 125 m_parent->markerReleased(this);
130 if (m_parent->lastMousePosition() == event->pos() && m_parent->mousePressed())
126 if (m_parent->mousePressed())
131 127 m_parent->markerSelected(this);
132 128 m_parent->setMousePressed(false);
133 129 }
134 130 void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
135 131 {
136 132 QGraphicsEllipseItem::mouseDoubleClickEvent(event);
137 133 m_parent->markerDoubleClicked(this);
138 134 }
139 135
140 136 private:
141 137 ScatterChartItem *m_parent;
142 138 };
143 139
144 140 class RectangleMarker: public QGraphicsRectItem
145 141 {
146 142
147 143 public:
148 144 RectangleMarker(qreal x, qreal y, qreal w, qreal h, ScatterChartItem *parent)
149 145 : QGraphicsRectItem(x, y, w, h, parent),
150 146 m_parent(parent)
151 147 {
152 148 setAcceptHoverEvents(true);
153 149 setFlag(QGraphicsItem::ItemIsSelectable);
154 150 }
155 151
156 152 protected:
157 153 void mousePressEvent(QGraphicsSceneMouseEvent *event)
158 154 {
159 155 QGraphicsRectItem::mousePressEvent(event);
160 156 m_parent->markerPressed(this);
161 m_parent->setLastMousePosition(event->pos());
162 157 m_parent->setMousePressed();
163 158 }
164 159 void hoverEnterEvent(QGraphicsSceneHoverEvent *event)
165 160 {
166 161 QGraphicsRectItem::hoverEnterEvent(event);
167 162 m_parent->markerHovered(this, true);
168 163 }
169 164 void hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
170 165 {
171 166 QGraphicsRectItem::hoverLeaveEvent(event);
172 167 m_parent->markerHovered(this, false);
173 168 }
174 169 void mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
175 170 {
176 171 QGraphicsRectItem::mouseReleaseEvent(event);
177 172 m_parent->markerReleased(this);
178 if (m_parent->lastMousePosition() == event->pos() && m_parent->mousePressed())
173 if (m_parent->mousePressed())
179 174 m_parent->markerSelected(this);
180 175 m_parent->setMousePressed(false);
181 176 }
182 177 void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
183 178 {
184 179 QGraphicsRectItem::mouseDoubleClickEvent(event);
185 180 m_parent->markerDoubleClicked(this);
186 181 }
187 182
188 183 private:
189 184 ScatterChartItem *m_parent;
190 185 };
191 186
192 187 QT_CHARTS_END_NAMESPACE
193 188
194 189 #endif // SCATTERPRESENTER_H
@@ -1,502 +1,502
1 1 /****************************************************************************
2 2 **
3 3 ** Copyright (C) 2014 Digia Plc
4 4 ** All rights reserved.
5 5 ** For any questions to Digia, please use contact form at http://qt.io
6 6 **
7 7 ** This file is part of the Qt Charts module.
8 8 **
9 9 ** Licensees holding valid commercial license for Qt may use this file in
10 10 ** accordance with the Qt License Agreement provided with the Software
11 11 ** or, alternatively, in accordance with the terms contained in a written
12 12 ** agreement between you and Digia.
13 13 **
14 14 ** If you have questions regarding the use of this file, please use
15 15 ** contact form at http://qt.io
16 16 **
17 17 ****************************************************************************/
18 18
19 19 #include <private/splinechartitem_p.h>
20 20 #include <private/qsplineseries_p.h>
21 21 #include <private/chartpresenter_p.h>
22 22 #include <private/splineanimation_p.h>
23 23 #include <private/polardomain_p.h>
24 24 #include <QtGui/QPainter>
25 25 #include <QtWidgets/QGraphicsSceneMouseEvent>
26 26
27 27 QT_CHARTS_BEGIN_NAMESPACE
28 28
29 29 SplineChartItem::SplineChartItem(QSplineSeries *series, QGraphicsItem *item)
30 30 : XYChart(series,item),
31 31 m_series(series),
32 32 m_pointsVisible(false),
33 33 m_animation(0),
34 34 m_pointLabelsVisible(false),
35 35 m_pointLabelsFormat(series->pointLabelsFormat()),
36 36 m_pointLabelsFont(series->pointLabelsFont()),
37 37 m_pointLabelsColor(series->pointLabelsColor()),
38 38 m_mousePressed(false)
39 39 {
40 40 setAcceptHoverEvents(true);
41 41 setFlag(QGraphicsItem::ItemIsSelectable);
42 42 setZValue(ChartPresenter::SplineChartZValue);
43 43 QObject::connect(m_series->d_func(), SIGNAL(updated()), this, SLOT(handleUpdated()));
44 44 QObject::connect(series, SIGNAL(visibleChanged()), this, SLOT(handleUpdated()));
45 45 QObject::connect(series, SIGNAL(opacityChanged()), this, SLOT(handleUpdated()));
46 46 QObject::connect(series, SIGNAL(pointLabelsFormatChanged(QString)),
47 47 this, SLOT(handleUpdated()));
48 48 QObject::connect(series, SIGNAL(pointLabelsVisibilityChanged(bool)),
49 49 this, SLOT(handleUpdated()));
50 50 QObject::connect(series, SIGNAL(pointLabelsFontChanged(QFont)), this, SLOT(handleUpdated()));
51 51 QObject::connect(series, SIGNAL(pointLabelsColorChanged(QColor)), this, SLOT(handleUpdated()));
52 52 handleUpdated();
53 53 }
54 54
55 55 QRectF SplineChartItem::boundingRect() const
56 56 {
57 57 return m_rect;
58 58 }
59 59
60 60 QPainterPath SplineChartItem::shape() const
61 61 {
62 62 return m_fullPath;
63 63 }
64 64
65 65 void SplineChartItem::setAnimation(SplineAnimation *animation)
66 66 {
67 67 m_animation = animation;
68 68 XYChart::setAnimation(animation);
69 69 }
70 70
71 71 ChartAnimation *SplineChartItem::animation() const
72 72 {
73 73 return m_animation;
74 74 }
75 75
76 76 void SplineChartItem::setControlGeometryPoints(QVector<QPointF>& points)
77 77 {
78 78 m_controlPoints = points;
79 79 }
80 80
81 81 QVector<QPointF> SplineChartItem::controlGeometryPoints() const
82 82 {
83 83 return m_controlPoints;
84 84 }
85 85
86 86 void SplineChartItem::updateChart(QVector<QPointF> &oldPoints, QVector<QPointF> &newPoints, int index)
87 87 {
88 88 QVector<QPointF> controlPoints;
89 89 if (newPoints.count() >= 2)
90 90 controlPoints = calculateControlPoints(newPoints);
91 91
92 92 if (m_animation)
93 93 m_animation->setup(oldPoints, newPoints, m_controlPoints, controlPoints, index);
94 94
95 95 m_points = newPoints;
96 96 m_controlPoints = controlPoints;
97 97 setDirty(false);
98 98
99 99 if (m_animation)
100 100 presenter()->startAnimation(m_animation);
101 101 else
102 102 updateGeometry();
103 103 }
104 104
105 105 void SplineChartItem::updateGeometry()
106 106 {
107 107 const QVector<QPointF> &points = m_points;
108 108 const QVector<QPointF> &controlPoints = m_controlPoints;
109 109
110 110 if ((points.size() < 2) || (controlPoints.size() < 2)) {
111 111 prepareGeometryChange();
112 112 m_path = QPainterPath();
113 113 m_rect = QRect();
114 114 return;
115 115 }
116 116
117 117 Q_ASSERT(points.count() * 2 - 2 == controlPoints.count());
118 118
119 119 QPainterPath splinePath;
120 120 QPainterPath fullPath;
121 121 // Use worst case scenario to determine required margin.
122 122 qreal margin = m_linePen.width() * 1.42;
123 123
124 124 if (m_series->chart()->chartType() == QChart::ChartTypePolar) {
125 125 QPainterPath splinePathLeft;
126 126 QPainterPath splinePathRight;
127 127 QPainterPath *currentSegmentPath = 0;
128 128 QPainterPath *previousSegmentPath = 0;
129 129 qreal minX = domain()->minX();
130 130 qreal maxX = domain()->maxX();
131 131 qreal minY = domain()->minY();
132 132 QPointF currentSeriesPoint = m_series->at(0);
133 133 QPointF currentGeometryPoint = points.at(0);
134 134 QPointF previousGeometryPoint = points.at(0);
135 135 bool pointOffGrid = false;
136 136 bool previousPointWasOffGrid = (currentSeriesPoint.x() < minX || currentSeriesPoint.x() > maxX);
137 137 m_visiblePoints.clear();
138 138 m_visiblePoints.reserve(points.size());
139 139
140 140 qreal domainRadius = domain()->size().height() / 2.0;
141 141 const QPointF centerPoint(domainRadius, domainRadius);
142 142
143 143 if (!previousPointWasOffGrid) {
144 144 fullPath.moveTo(points.at(0));
145 145 // Do not draw points for points below minimum Y.
146 146 if (m_pointsVisible && currentSeriesPoint.y() >= minY)
147 147 m_visiblePoints.append(currentGeometryPoint);
148 148 }
149 149
150 150 qreal leftMarginLine = centerPoint.x() - margin;
151 151 qreal rightMarginLine = centerPoint.x() + margin;
152 152 qreal horizontal = centerPoint.y();
153 153
154 154 // See ScatterChartItem::updateGeometry() for explanation why seriesLastIndex is needed
155 155 const int seriesLastIndex = m_series->count() - 1;
156 156
157 157 for (int i = 1; i < points.size(); i++) {
158 158 // Interpolating spline fragments accurately is not trivial, and would anyway be ugly
159 159 // when thick pen is used, so we work around it by utilizing three separate
160 160 // paths for spline segments and clip those with custom regions at paint time.
161 161 // "Right" path contains segments that cross the axis line with visible point on the
162 162 // right side of the axis line, as well as segments that have one point within the margin
163 163 // on the right side of the axis line and another point on the right side of the chart.
164 164 // "Left" path contains points with similarly on the left side.
165 165 // "Full" path contains rest of the points.
166 166 // This doesn't yield perfect results always. E.g. when segment covers more than 90
167 167 // degrees and both of the points are within the margin, one in the top half and one in the
168 168 // bottom half of the chart, the bottom one gets clipped incorrectly.
169 169 // However, this should be rare occurrence in any sensible chart.
170 170 currentSeriesPoint = m_series->at(qMin(seriesLastIndex, i));
171 171 currentGeometryPoint = points.at(i);
172 172 pointOffGrid = (currentSeriesPoint.x() < minX || currentSeriesPoint.x() > maxX);
173 173
174 174 // Draw something unless both off-grid
175 175 if (!pointOffGrid || !previousPointWasOffGrid) {
176 176 bool dummyOk; // We know points are ok, but this is needed
177 177 qreal currentAngle = static_cast<PolarDomain *>(domain())->toAngularCoordinate(currentSeriesPoint.x(), dummyOk);
178 178 qreal previousAngle = static_cast<PolarDomain *>(domain())->toAngularCoordinate(m_series->at(i - 1).x(), dummyOk);
179 179
180 180 if ((qAbs(currentAngle - previousAngle) > 180.0)) {
181 181 // If the angle between two points is over 180 degrees (half X range),
182 182 // any direct segment between them becomes meaningless.
183 183 // In this case two line segments are drawn instead, from previous
184 184 // point to the center and from center to current point.
185 185 if ((previousAngle < 0.0 || (previousAngle <= 180.0 && previousGeometryPoint.x() < rightMarginLine))
186 186 && previousGeometryPoint.y() < horizontal) {
187 187 currentSegmentPath = &splinePathRight;
188 188 } else if ((previousAngle > 360.0 || (previousAngle > 180.0 && previousGeometryPoint.x() > leftMarginLine))
189 189 && previousGeometryPoint.y() < horizontal) {
190 190 currentSegmentPath = &splinePathLeft;
191 191 } else if (previousAngle > 0.0 && previousAngle < 360.0) {
192 192 currentSegmentPath = &splinePath;
193 193 } else {
194 194 currentSegmentPath = 0;
195 195 }
196 196
197 197 if (currentSegmentPath) {
198 198 if (previousSegmentPath != currentSegmentPath)
199 199 currentSegmentPath->moveTo(previousGeometryPoint);
200 200 if (!previousSegmentPath)
201 201 fullPath.moveTo(previousGeometryPoint);
202 202
203 203 currentSegmentPath->lineTo(centerPoint);
204 204 fullPath.lineTo(centerPoint);
205 205 }
206 206
207 207 previousSegmentPath = currentSegmentPath;
208 208
209 209 if ((currentAngle < 0.0 || (currentAngle <= 180.0 && currentGeometryPoint.x() < rightMarginLine))
210 210 && currentGeometryPoint.y() < horizontal) {
211 211 currentSegmentPath = &splinePathRight;
212 212 } else if ((currentAngle > 360.0 || (currentAngle > 180.0 &&currentGeometryPoint.x() > leftMarginLine))
213 213 && currentGeometryPoint.y() < horizontal) {
214 214 currentSegmentPath = &splinePathLeft;
215 215 } else if (currentAngle > 0.0 && currentAngle < 360.0) {
216 216 currentSegmentPath = &splinePath;
217 217 } else {
218 218 currentSegmentPath = 0;
219 219 }
220 220
221 221 if (currentSegmentPath) {
222 222 if (previousSegmentPath != currentSegmentPath)
223 223 currentSegmentPath->moveTo(centerPoint);
224 224 if (!previousSegmentPath)
225 225 fullPath.moveTo(centerPoint);
226 226
227 227 currentSegmentPath->lineTo(currentGeometryPoint);
228 228 fullPath.lineTo(currentGeometryPoint);
229 229 }
230 230 } else {
231 231 QPointF cp1 = controlPoints[2 * (i - 1)];
232 232 QPointF cp2 = controlPoints[(2 * i) - 1];
233 233
234 234 if (previousAngle < 0.0 || currentAngle < 0.0
235 235 || ((previousAngle <= 180.0 && currentAngle <= 180.0)
236 236 && ((previousGeometryPoint.x() < rightMarginLine && previousGeometryPoint.y() < horizontal)
237 237 || (currentGeometryPoint.x() < rightMarginLine && currentGeometryPoint.y() < horizontal)))) {
238 238 currentSegmentPath = &splinePathRight;
239 239 } else if (previousAngle > 360.0 || currentAngle > 360.0
240 240 || ((previousAngle > 180.0 && currentAngle > 180.0)
241 241 && ((previousGeometryPoint.x() > leftMarginLine && previousGeometryPoint.y() < horizontal)
242 242 || (currentGeometryPoint.x() > leftMarginLine && currentGeometryPoint.y() < horizontal)))) {
243 243 currentSegmentPath = &splinePathLeft;
244 244 } else {
245 245 currentSegmentPath = &splinePath;
246 246 }
247 247
248 248 if (currentSegmentPath != previousSegmentPath)
249 249 currentSegmentPath->moveTo(previousGeometryPoint);
250 250 if (!previousSegmentPath)
251 251 fullPath.moveTo(previousGeometryPoint);
252 252
253 253 fullPath.cubicTo(cp1, cp2, currentGeometryPoint);
254 254 currentSegmentPath->cubicTo(cp1, cp2, currentGeometryPoint);
255 255 }
256 256 } else {
257 257 currentSegmentPath = 0;
258 258 }
259 259
260 260 previousPointWasOffGrid = pointOffGrid;
261 261 if (!pointOffGrid && m_pointsVisible && currentSeriesPoint.y() >= minY)
262 262 m_visiblePoints.append(currentGeometryPoint);
263 263 previousSegmentPath = currentSegmentPath;
264 264 previousGeometryPoint = currentGeometryPoint;
265 265 }
266 266
267 267 m_pathPolarRight = splinePathRight;
268 268 m_pathPolarLeft = splinePathLeft;
269 269 // Note: This construction of m_fullpath is not perfect. The partial segments that are
270 270 // outside left/right clip regions at axis boundary still generate hover/click events,
271 271 // because shape doesn't get clipped. It doesn't seem possible to do sensibly.
272 272 } else { // not polar
273 273 splinePath.moveTo(points.at(0));
274 274 for (int i = 0; i < points.size() - 1; i++) {
275 275 const QPointF &point = points.at(i + 1);
276 276 splinePath.cubicTo(controlPoints[2 * i], controlPoints[2 * i + 1], point);
277 277 }
278 278 fullPath = splinePath;
279 279 }
280 280
281 281 QPainterPathStroker stroker;
282 282 // The full path is comprised of three separate paths.
283 283 // This is why we are prepared for the "worst case" scenario, i.e. use always MiterJoin and
284 284 // multiply line width with square root of two when defining shape and bounding rectangle.
285 285 stroker.setWidth(margin);
286 286 stroker.setJoinStyle(Qt::MiterJoin);
287 287 stroker.setCapStyle(Qt::SquareCap);
288 288 stroker.setMiterLimit(m_linePen.miterLimit());
289 289
290 290 // Only zoom in if the bounding rects of the path fit inside int limits. QWidget::update() uses
291 291 // a region that has to be compatible with QRect.
292 292 QPainterPath checkShapePath = stroker.createStroke(fullPath);
293 293 if (checkShapePath.boundingRect().height() <= INT_MAX
294 294 && checkShapePath.boundingRect().width() <= INT_MAX
295 295 && splinePath.boundingRect().height() <= INT_MAX
296 296 && splinePath.boundingRect().width() <= INT_MAX) {
297 297 m_path = splinePath;
298 298
299 299 prepareGeometryChange();
300 300
301 301 m_fullPath = checkShapePath;
302 302 m_rect = m_fullPath.boundingRect();
303 303 }
304 304 }
305 305
306 306 /*!
307 307 Calculates control points which are needed by QPainterPath.cubicTo function to draw the cubic Bezier cureve between two points.
308 308 */
309 309 QVector<QPointF> SplineChartItem::calculateControlPoints(const QVector<QPointF> &points)
310 310 {
311 311 QVector<QPointF> controlPoints;
312 312 controlPoints.resize(points.count() * 2 - 2);
313 313
314 314 int n = points.count() - 1;
315 315
316 316 if (n == 1) {
317 317 //for n==1
318 318 controlPoints[0].setX((2 * points[0].x() + points[1].x()) / 3);
319 319 controlPoints[0].setY((2 * points[0].y() + points[1].y()) / 3);
320 320 controlPoints[1].setX(2 * controlPoints[0].x() - points[0].x());
321 321 controlPoints[1].setY(2 * controlPoints[0].y() - points[0].y());
322 322 return controlPoints;
323 323 }
324 324
325 325 // Calculate first Bezier control points
326 326 // Set of equations for P0 to Pn points.
327 327 //
328 328 // | 2 1 0 0 ... 0 0 0 ... 0 0 0 | | P1_1 | | P0 + 2 * P1 |
329 329 // | 1 4 1 0 ... 0 0 0 ... 0 0 0 | | P1_2 | | 4 * P1 + 2 * P2 |
330 330 // | 0 1 4 1 ... 0 0 0 ... 0 0 0 | | P1_3 | | 4 * P2 + 2 * P3 |
331 331 // | . . . . . . . . . . . . | | ... | | ... |
332 332 // | 0 0 0 0 ... 1 4 1 ... 0 0 0 | * | P1_i | = | 4 * P(i-1) + 2 * Pi |
333 333 // | . . . . . . . . . . . . | | ... | | ... |
334 334 // | 0 0 0 0 0 0 0 0 ... 1 4 1 | | P1_(n-1)| | 4 * P(n-2) + 2 * P(n-1) |
335 335 // | 0 0 0 0 0 0 0 0 ... 0 2 7 | | P1_n | | 8 * P(n-1) + Pn |
336 336 //
337 337 QVector<qreal> vector;
338 338 vector.resize(n);
339 339
340 340 vector[0] = points[0].x() + 2 * points[1].x();
341 341
342 342
343 343 for (int i = 1; i < n - 1; ++i)
344 344 vector[i] = 4 * points[i].x() + 2 * points[i + 1].x();
345 345
346 346 vector[n - 1] = (8 * points[n - 1].x() + points[n].x()) / 2.0;
347 347
348 348 QVector<qreal> xControl = firstControlPoints(vector);
349 349
350 350 vector[0] = points[0].y() + 2 * points[1].y();
351 351
352 352 for (int i = 1; i < n - 1; ++i)
353 353 vector[i] = 4 * points[i].y() + 2 * points[i + 1].y();
354 354
355 355 vector[n - 1] = (8 * points[n - 1].y() + points[n].y()) / 2.0;
356 356
357 357 QVector<qreal> yControl = firstControlPoints(vector);
358 358
359 359 for (int i = 0, j = 0; i < n; ++i, ++j) {
360 360
361 361 controlPoints[j].setX(xControl[i]);
362 362 controlPoints[j].setY(yControl[i]);
363 363
364 364 j++;
365 365
366 366 if (i < n - 1) {
367 367 controlPoints[j].setX(2 * points[i + 1].x() - xControl[i + 1]);
368 368 controlPoints[j].setY(2 * points[i + 1].y() - yControl[i + 1]);
369 369 } else {
370 370 controlPoints[j].setX((points[n].x() + xControl[n - 1]) / 2);
371 371 controlPoints[j].setY((points[n].y() + yControl[n - 1]) / 2);
372 372 }
373 373 }
374 374 return controlPoints;
375 375 }
376 376
377 377 QVector<qreal> SplineChartItem::firstControlPoints(const QVector<qreal>& vector)
378 378 {
379 379 QVector<qreal> result;
380 380
381 381 int count = vector.count();
382 382 result.resize(count);
383 383 result[0] = vector[0] / 2.0;
384 384
385 385 QVector<qreal> temp;
386 386 temp.resize(count);
387 387 temp[0] = 0;
388 388
389 389 qreal b = 2.0;
390 390
391 391 for (int i = 1; i < count; i++) {
392 392 temp[i] = 1 / b;
393 393 b = (i < count - 1 ? 4.0 : 3.5) - temp[i];
394 394 result[i] = (vector[i] - result[i - 1]) / b;
395 395 }
396 396
397 397 for (int i = 1; i < count; i++)
398 398 result[count - i - 1] -= temp[count - i] * result[count - i];
399 399
400 400 return result;
401 401 }
402 402
403 403 //handlers
404 404
405 405 void SplineChartItem::handleUpdated()
406 406 {
407 407 setVisible(m_series->isVisible());
408 408 setOpacity(m_series->opacity());
409 409 m_pointsVisible = m_series->pointsVisible();
410 410 m_linePen = m_series->pen();
411 411 m_pointPen = m_series->pen();
412 412 m_pointPen.setWidthF(2 * m_pointPen.width());
413 413 m_pointLabelsFormat = m_series->pointLabelsFormat();
414 414 m_pointLabelsVisible = m_series->pointLabelsVisible();
415 415 m_pointLabelsFont = m_series->pointLabelsFont();
416 416 m_pointLabelsColor = m_series->pointLabelsColor();
417 417 update();
418 418 }
419 419
420 420 //painter
421 421
422 422 void SplineChartItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
423 423 {
424 424 Q_UNUSED(widget)
425 425 Q_UNUSED(option)
426 426
427 427 QRectF clipRect = QRectF(QPointF(0, 0), domain()->size());
428 428
429 429 painter->save();
430 430 painter->setPen(m_linePen);
431 431 painter->setBrush(Qt::NoBrush);
432 432
433 433 if (m_series->chart()->chartType() == QChart::ChartTypePolar) {
434 434 qreal halfWidth = domain()->size().width() / 2.0;
435 435 QRectF clipRectLeft = QRectF(0, 0, halfWidth, domain()->size().height());
436 436 QRectF clipRectRight = QRectF(halfWidth, 0, halfWidth, domain()->size().height());
437 437 QRegion fullPolarClipRegion(clipRect.toRect(), QRegion::Ellipse);
438 438 QRegion clipRegionLeft(fullPolarClipRegion.intersected(clipRectLeft.toRect()));
439 439 QRegion clipRegionRight(fullPolarClipRegion.intersected(clipRectRight.toRect()));
440 440 painter->setClipRegion(clipRegionLeft);
441 441 painter->drawPath(m_pathPolarLeft);
442 442 painter->setClipRegion(clipRegionRight);
443 443 painter->drawPath(m_pathPolarRight);
444 444 painter->setClipRegion(fullPolarClipRegion);
445 445 } else {
446 446 painter->setClipRect(clipRect);
447 447 }
448 448
449 449 painter->drawPath(m_path);
450 450
451 451 if (m_pointsVisible) {
452 452 painter->setPen(m_pointPen);
453 453 if (m_series->chart()->chartType() == QChart::ChartTypePolar)
454 454 painter->drawPoints(m_visiblePoints);
455 455 else
456 456 painter->drawPoints(geometryPoints());
457 457 }
458 458
459 459 if (m_pointLabelsVisible)
460 460 m_series->d_func()->drawSeriesPointLabels(painter, m_points, m_linePen.width() / 2);
461 461
462 462 painter->restore();
463 463 }
464 464
465 465 void SplineChartItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
466 466 {
467 467 emit XYChart::pressed(domain()->calculateDomainPoint(event->pos()));
468 468 m_lastMousePos = event->pos();
469 469 m_mousePressed = true;
470 470 QGraphicsItem::mousePressEvent(event);
471 471 }
472 472
473 473 void SplineChartItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
474 474 {
475 475 emit XYChart::hovered(domain()->calculateDomainPoint(event->pos()), true);
476 476 QGraphicsItem::hoverEnterEvent(event);
477 477 }
478 478
479 479 void SplineChartItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
480 480 {
481 481 emit XYChart::hovered(domain()->calculateDomainPoint(event->pos()), false);
482 482 QGraphicsItem::hoverLeaveEvent(event);
483 483 }
484 484
485 485 void SplineChartItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
486 486 {
487 emit XYChart::released(domain()->calculateDomainPoint(event->pos()));
488 if (m_lastMousePos == event->pos() && m_mousePressed)
489 emit XYChart::clicked(domain()->calculateDomainPoint(event->pos()));
487 emit XYChart::released(domain()->calculateDomainPoint(m_lastMousePos));
488 if (m_mousePressed)
489 emit XYChart::clicked(domain()->calculateDomainPoint(m_lastMousePos));
490 490 m_mousePressed = false;
491 491 QGraphicsItem::mouseReleaseEvent(event);
492 492 }
493 493
494 494 void SplineChartItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
495 495 {
496 emit XYChart::doubleClicked(domain()->calculateDomainPoint(event->pos()));
496 emit XYChart::doubleClicked(domain()->calculateDomainPoint(m_lastMousePos));
497 497 QGraphicsItem::mouseDoubleClickEvent(event);
498 498 }
499 499
500 500 #include "moc_splinechartitem_p.cpp"
501 501
502 502 QT_CHARTS_END_NAMESPACE
@@ -1,848 +1,861
1 1 /****************************************************************************
2 2 **
3 3 ** Copyright (C) 2014 Digia Plc
4 4 ** All rights reserved.
5 5 ** For any questions to Digia, please use contact form at http://qt.io
6 6 **
7 7 ** This file is part of the Qt Charts module.
8 8 **
9 9 ** Licensees holding valid commercial license for Qt may use this file in
10 10 ** accordance with the Qt License Agreement provided with the Software
11 11 ** or, alternatively, in accordance with the terms contained in a written
12 12 ** agreement between you and Digia.
13 13 **
14 14 ** If you have questions regarding the use of this file, please use
15 15 ** contact form at http://qt.io
16 16 **
17 17 ****************************************************************************/
18 18
19 19 #include <QtCharts/QXYSeries>
20 20 #include <private/qxyseries_p.h>
21 21 #include <private/abstractdomain_p.h>
22 22 #include <QtCharts/QValueAxis>
23 23 #include <private/xychart_p.h>
24 24 #include <QtCharts/QXYLegendMarker>
25 25 #include <private/charthelpers_p.h>
26 26 #include <private/qchart_p.h>
27 27 #include <QtGui/QPainter>
28 28
29 29 QT_CHARTS_BEGIN_NAMESPACE
30 30
31 31 /*!
32 32 \class QXYSeries
33 33 \inmodule Qt Charts
34 34 \brief The QXYSeries class is a base class for line, spline and scatter series.
35 35 */
36 36 /*!
37 37 \qmltype XYSeries
38 38 \instantiates QXYSeries
39 39 \inqmlmodule QtCharts
40 40
41 41 \inherits AbstractSeries
42 42
43 43 \brief The XYSeries type is a base type for line, spline and scatter series.
44 44
45 45 The XYSeries class is a base class for line, spline and scatter series.
46 46 The class cannot be instantiated directly.
47 47 */
48 48
49 49 /*!
50 50 \qmlproperty AbstractAxis XYSeries::axisX
51 51 The x axis used for the series. If you leave both axisX and axisXTop undefined, a ValueAxis is created for
52 52 the series.
53 53 \sa axisXTop
54 54 */
55 55
56 56 /*!
57 57 \qmlproperty AbstractAxis XYSeries::axisY
58 58 The y axis used for the series. If you leave both axisY and axisYRight undefined, a ValueAxis is created for
59 59 the series.
60 60 \sa axisYRight
61 61 */
62 62
63 63 /*!
64 64 \qmlproperty AbstractAxis XYSeries::axisXTop
65 65 The x axis used for the series, drawn on top of the chart view. Note that you can only provide either axisX or
66 66 axisXTop, but not both.
67 67 \sa axisX
68 68 */
69 69
70 70 /*!
71 71 \qmlproperty AbstractAxis XYSeries::axisYRight
72 72 The y axis used for the series, drawn to the right on the chart view. Note that you can only provide either axisY
73 73 or axisYRight, but not both.
74 74 \sa axisY
75 75 */
76 76
77 77 /*!
78 78 \qmlproperty AbstractAxis XYSeries::axisAngular
79 79 The angular axis used for the series, drawn around the polar chart view.
80 80 \sa axisX
81 81 */
82 82
83 83 /*!
84 84 \qmlproperty AbstractAxis XYSeries::axisRadial
85 85 The radial axis used for the series, drawn inside the polar chart view.
86 86 \sa axisY
87 87 */
88 88
89 89 /*!
90 90 \property QXYSeries::pointsVisible
91 91 Controls if the data points are visible and should be drawn.
92 92 */
93 93 /*!
94 94 \qmlproperty bool XYSeries::pointsVisible
95 95 Controls if the data points are visible and should be drawn.
96 96 */
97 97
98 98 /*!
99 99 \fn QPen QXYSeries::pen() const
100 100 \brief Returns pen used to draw points for series.
101 101 \sa setPen()
102 102 */
103 103
104 104 /*!
105 105 \fn QBrush QXYSeries::brush() const
106 106 \brief Returns brush used to draw points for series.
107 107 \sa setBrush()
108 108 */
109 109
110 110 /*!
111 111 \property QXYSeries::color
112 112 The color of the series. This is line (pen) color in case of QLineSeries or QSplineSeries and
113 113 fill (brush) color in case of QScatterSeries or QAreaSeries.
114 114 \sa QXYSeries::pen(), QXYSeries::brush()
115 115 */
116 116 /*!
117 117 \qmlproperty color XYSeries::color
118 118 The color of the series. This is line (pen) color in case of LineSeries or SplineSeries and
119 119 fill (brush) color in case of ScatterSeries or AreaSeries.
120 120 */
121 121
122 122 /*!
123 123 \property QXYSeries::pointLabelsFormat
124 124 The \a format used for showing labels with series points.
125 125
126 126 QXYSeries supports the following format tags:
127 127 \table
128 128 \row
129 129 \li @xPoint \li The x value of the data point
130 130 \row
131 131 \li @yPoint \li The y value of the data point
132 132 \endtable
133 133
134 134 For example, the following usage of the format tags would produce labels that have the data
135 135 point (x, y) shown inside brackets separated by a comma:
136 136 \code
137 137 series->setPointLabelsFormat("(@xPoint, @yPoint)");
138 138 \endcode
139 139
140 140 By default, the labels format is set to '@xPoint, @yPoint'. The labels are shown on the plot
141 141 area, labels on the edge of the plot area are cut. If the points are close to each other the
142 142 labels may overlap.
143 143
144 144 \sa QXYSeries::pointLabelsVisible, QXYSeries::pointLabelsFont, QXYSeries::pointLabelsColor
145 145 */
146 146 /*!
147 147 \qmlproperty string XYSeries::pointLabelsFormat
148 148 The \a format used for showing labels with series points.
149 149
150 150 \sa QXYSeries::pointLabelsFormat, pointLabelsVisible, pointLabelsFont, pointLabelsColor
151 151 */
152 152 /*!
153 153 \fn void QXYSeries::pointLabelsFormatChanged(const QString &format)
154 154 Signal is emitted when the \a format of data point labels is changed.
155 155 */
156 156 /*!
157 157 \qmlsignal XYSeries::onPointLabelsFormatChanged(string format)
158 158 Signal is emitted when the \a format of data point labels is changed.
159 159 */
160 160
161 161 /*!
162 162 \property QXYSeries::pointLabelsVisible
163 163 Defines the visibility for data point labels. False by default.
164 164
165 165 \sa QXYSeries::pointLabelsFormat
166 166 */
167 167 /*!
168 168 \qmlproperty bool XYSeries::pointLabelsVisible
169 169 Defines the visibility for data point labels.
170 170
171 171 \sa pointLabelsFormat
172 172 */
173 173 /*!
174 174 \fn void QXYSeries::pointLabelsVisibilityChanged(bool visible)
175 175 The visibility of the data point labels is changed to \a visible.
176 176 */
177 177 /*!
178 178 \qmlsignal XYSeries::onPointLabelsVisibilityChanged(bool visible)
179 179 The visibility of the data point labels is changed to \a visible.
180 180 */
181 181
182 182 /*!
183 183 \property QXYSeries::pointLabelsFont
184 184 Defines the font used for data point labels.
185 185
186 186 \sa QXYSeries::pointLabelsFormat
187 187 */
188 188 /*!
189 189 \qmlproperty font XYSeries::pointLabelsFont
190 190 Defines the font used for data point labels.
191 191
192 192 \sa pointLabelsFormat
193 193 */
194 194 /*!
195 195 \fn void QXYSeries::pointLabelsFontChanged(const QFont &font);
196 196 The font used for data point labels is changed to \a font.
197 197 */
198 198 /*!
199 199 \qmlsignal XYSeries::onPointLabelsFontChanged(Font font)
200 200 The font used for data point labels is changed to \a font.
201 201 */
202 202
203 203 /*!
204 204 \property QXYSeries::pointLabelsColor
205 205 Defines the color used for data point labels. By default, the color is the color of the brush
206 206 defined in theme for labels.
207 207
208 208 \sa QXYSeries::pointLabelsFormat
209 209 */
210 210 /*!
211 211 \qmlproperty font XYSeries::pointLabelsColor
212 212 Defines the color used for data point labels. By default, the color is the color of the brush
213 213 defined in theme for labels.
214 214
215 215 \sa pointLabelsFormat
216 216 */
217 217 /*!
218 218 \fn void QXYSeries::pointLabelsColorChanged(const QColor &color);
219 219 The color used for data point labels is changed to \a color.
220 220 */
221 221 /*!
222 222 \qmlsignal XYSeries::onPointLabelsColorChanged(Color color)
223 223 The color used for data point labels is changed to \a color.
224 224 */
225 225
226 226 /*!
227 227 \fn void QXYSeries::clicked(const QPointF& point)
228 \brief Signal is emitted when user clicks the \a point on chart.
228 \brief Signal is emitted when user clicks the \a point on chart. The \a point is the point
229 where the press was triggered.
230 \sa pressed, released, doubleClicked
229 231 */
230 232 /*!
231 233 \qmlsignal XYSeries::onClicked(QPointF point)
232 Signal is emitted when user clicks the \a point on chart. For example:
234 Signal is emitted when user clicks the \a point on chart. The \a point is the point where the
235 press was triggered. For example:
233 236 \code
234 237 LineSeries {
235 238 XYPoint { x: 0; y: 0 }
236 239 XYPoint { x: 1.1; y: 2.1 }
237 240 onClicked: console.log("onClicked: " + point.x + ", " + point.y);
238 241 }
239 242 \endcode
243 \sa onPressed, onReleased, onDoubleClicked
240 244 */
241 245
242 246 /*!
243 247 \fn void QXYSeries::hovered(const QPointF &point, bool state)
244 248 This signal is emitted when user has hovered over or away from the series. \a point shows the origin (coordinate)
245 249 of the hover event. \a state is true when user has hovered over the series and false when hover has moved away from
246 250 the series.
247 251 */
248 252 /*!
249 253 \qmlsignal XYSeries::onHovered(point point, bool state)
250 254 This signal is emitted when user has hovered over or away from the series. \a point shows the origin (coordinate)
251 255 of the hover event. \a state is true when user has hovered over the series and false when hover has moved away from
252 256 the series.
253 257 */
254 258
255 259 /*!
256 260 \fn void QXYSeries::pressed(const QPointF& point)
257 261 \brief Signal is emitted when user presses the \a point on chart.
262 \sa clicked, released, doubleClicked
258 263 */
259 264 /*!
260 265 \qmlsignal XYSeries::onPressed(QPointF point)
261 266 Signal is emitted when user presses the \a point on chart. For example:
262 267 \code
263 268 LineSeries {
264 269 XYPoint { x: 0; y: 0 }
265 270 XYPoint { x: 1.1; y: 2.1 }
266 271 onPressed: console.log("onPressed: " + point.x + ", " + point.y);
267 272 }
268 273 \endcode
274 \sa onClicked, onReleased, onDoubleClicked
269 275 */
270 276
271 277 /*!
272 278 \fn void QXYSeries::released(const QPointF& point)
273 \brief Signal is emitted when user releases the \a point on chart.
279 \brief Signal is emitted when user releases a press that was triggered on a \a point on chart.
280 \sa pressed, clicked, doubleClicked
274 281 */
275 282 /*!
276 283 \qmlsignal XYSeries::onReleased(QPointF point)
277 Signal is emitted when user releases the \a point on chart. For example:
284 Signal is emitted when user releases a press that was triggered on a \a point on chart.
285 For example:
278 286 \code
279 287 LineSeries {
280 288 XYPoint { x: 0; y: 0 }
281 289 XYPoint { x: 1.1; y: 2.1 }
282 290 onReleased: console.log("onReleased: " + point.x + ", " + point.y);
283 291 }
284 292 \endcode
293 \sa onPressed, onClicked, onDoubleClicked
285 294 */
286 295
287 296 /*!
288 297 \fn void QXYSeries::doubleClicked(const QPointF& point)
289 \brief Signal is emitted when user doubleclicks the \a point on chart.
298 \brief Signal is emitted when user doubleclicks the \a point on chart. The \a point is the
299 point where the first press was triggered.
300 \sa pressed, released, clicked
290 301 */
291 302 /*!
292 303 \qmlsignal XYSeries::onDoubleClicked(QPointF point)
293 Signal is emitted when user doubleclicks the \a point on chart. For example:
304 Signal is emitted when user doubleclicks the \a point on chart. The \a point is the point where
305 the first press was triggered. For example:
294 306 \code
295 307 LineSeries {
296 308 XYPoint { x: 0; y: 0 }
297 309 XYPoint { x: 1.1; y: 2.1 }
298 310 onDoubleClicked: console.log("onDoubleClicked: " + point.x + ", " + point.y);
299 311 }
300 312 \endcode
313 \sa onPressed, onReleased, onClicked
301 314 */
302 315
303 316 /*!
304 317 \fn void QXYSeries::pointReplaced(int index)
305 318 Signal is emitted when a point has been replaced at \a index.
306 319 \sa replace()
307 320 */
308 321 /*!
309 322 \qmlsignal XYSeries::onPointReplaced(int index)
310 323 Signal is emitted when a point has been replaced at \a index.
311 324 */
312 325
313 326 /*!
314 327 \fn void QXYSeries::pointsReplaced()
315 328 Signal is emitted when all points have been replaced with other points.
316 329 \sa replace()
317 330 */
318 331 /*!
319 332 \qmlsignal XYSeries::onPointsReplaced()
320 333 */
321 334
322 335 /*!
323 336 \fn void QXYSeries::pointAdded(int index)
324 337 Signal is emitted when a point has been added at \a index.
325 338 \sa append(), insert()
326 339 */
327 340 /*!
328 341 \qmlsignal XYSeries::onPointAdded(int index)
329 342 Signal is emitted when a point has been added at \a index.
330 343 */
331 344
332 345 /*!
333 346 \fn void QXYSeries::pointRemoved(int index)
334 347 Signal is emitted when a point has been removed from \a index.
335 348 \sa remove()
336 349 */
337 350
338 351 /*!
339 352 \qmlsignal XYSeries::onPointRemoved(int index)
340 353 Signal is emitted when a point has been removed from \a index.
341 354 */
342 355
343 356 /*!
344 357 \fn void QXYSeries::colorChanged(QColor color)
345 358 \brief Signal is emitted when the line (pen) color has changed to \a color.
346 359 */
347 360 /*!
348 361 \qmlsignal XYSeries::onColorChanged(color color)
349 362 Signal is emitted when the line (pen) color has changed to \a color.
350 363 */
351 364
352 365 /*!
353 366 \fn void QXYSeriesPrivate::updated()
354 367 \brief \internal
355 368 */
356 369
357 370 /*!
358 371 \qmlmethod XYSeries::append(real x, real y)
359 372 Append point (\a x, \a y) to the series
360 373 */
361 374
362 375 /*!
363 376 \qmlmethod XYSeries::replace(real oldX, real oldY, real newX, real newY)
364 377 Replaces point (\a oldX, \a oldY) with point (\a newX, \a newY). Does nothing, if point (oldX, oldY) does not
365 378 exist.
366 379 */
367 380
368 381 /*!
369 382 \qmlmethod XYSeries::remove(real x, real y)
370 383 Removes point (\a x, \a y) from the series. Does nothing, if point (x, y) does not exist.
371 384 */
372 385
373 386 /*!
374 387 \qmlmethod XYSeries::insert(int index, real x, real y)
375 388 Inserts point (\a x, \a y) to the \a index. If index is 0 or smaller than 0 the point is prepended to the list of
376 389 points. If index is the same as or bigger than count, the point is appended to the list of points.
377 390 */
378 391
379 392 /*!
380 393 \qmlmethod QPointF XYSeries::at(int index)
381 394 Returns point at \a index. Returns (0, 0) if the index is not valid.
382 395 */
383 396
384 397 /*!
385 398 \internal
386 399
387 400 Constructs empty series object which is a child of \a parent.
388 401 When series object is added to QChart instance ownerships is transferred.
389 402 */
390 403 QXYSeries::QXYSeries(QXYSeriesPrivate &d, QObject *parent)
391 404 : QAbstractSeries(d, parent)
392 405 {
393 406 }
394 407
395 408 /*!
396 409 Destroys the object. Series added to QChart instances are owned by those,
397 410 and are destroyed when QChart instances are destroyed.
398 411 */
399 412 QXYSeries::~QXYSeries()
400 413 {
401 414 }
402 415
403 416 /*!
404 417 Adds data point (\a x, \a y) to the series.
405 418 */
406 419 void QXYSeries::append(qreal x, qreal y)
407 420 {
408 421 append(QPointF(x, y));
409 422 }
410 423
411 424 /*!
412 425 This is an overloaded function.
413 426 Adds data \a point to the series.
414 427 */
415 428 void QXYSeries::append(const QPointF &point)
416 429 {
417 430 Q_D(QXYSeries);
418 431
419 432 if (isValidValue(point)) {
420 433 d->m_points << point;
421 434 emit pointAdded(d->m_points.count() - 1);
422 435 }
423 436 }
424 437
425 438 /*!
426 439 This is an overloaded function.
427 440 Adds list of data \a points to the series.
428 441 */
429 442 void QXYSeries::append(const QList<QPointF> &points)
430 443 {
431 444 foreach (const QPointF &point , points)
432 445 append(point);
433 446 }
434 447
435 448 /*!
436 449 Replaces data point (\a oldX, \a oldY) with data point (\a newX, \a newY).
437 450 \sa QXYSeries::pointReplaced()
438 451 */
439 452 void QXYSeries::replace(qreal oldX, qreal oldY, qreal newX, qreal newY)
440 453 {
441 454 replace(QPointF(oldX, oldY), QPointF(newX, newY));
442 455 }
443 456
444 457 /*!
445 458 Replaces \a oldPoint with \a newPoint.
446 459 \sa QXYSeries::pointReplaced()
447 460 */
448 461 void QXYSeries::replace(const QPointF &oldPoint, const QPointF &newPoint)
449 462 {
450 463 Q_D(QXYSeries);
451 464 int index = d->m_points.indexOf(oldPoint);
452 465 if (index == -1)
453 466 return;
454 467 replace(index, newPoint);
455 468 }
456 469
457 470 /*!
458 471 Replaces the point at \a index with data point (\a newX, \a newY).
459 472 \sa QXYSeries::pointReplaced()
460 473 */
461 474 void QXYSeries::replace(int index, qreal newX, qreal newY)
462 475 {
463 476 replace(index, QPointF(newX, newY));
464 477 }
465 478
466 479 /*!
467 480 Replaces the point at \a index with \a newPoint.
468 481 \sa QXYSeries::pointReplaced()
469 482 */
470 483 void QXYSeries::replace(int index, const QPointF &newPoint)
471 484 {
472 485 Q_D(QXYSeries);
473 486 if (isValidValue(newPoint)) {
474 487 d->m_points[index] = newPoint;
475 488 emit pointReplaced(index);
476 489 }
477 490 }
478 491
479 492 /*!
480 493 Replaces the current points with \a points.
481 494 \note This is much faster than replacing data points one by one,
482 495 or first clearing all data, and then appending the new data. Emits QXYSeries::pointsReplaced()
483 496 when the points have been replaced.
484 497 \sa QXYSeries::pointsReplaced()
485 498 */
486 499 void QXYSeries::replace(QList<QPointF> points)
487 500 {
488 501 Q_D(QXYSeries);
489 502 d->m_points = points.toVector();
490 503 emit pointsReplaced();
491 504 }
492 505
493 506 /*!
494 507 Removes the point (\a x, \a y) from the series.
495 508 */
496 509 void QXYSeries::remove(qreal x, qreal y)
497 510 {
498 511 remove(QPointF(x, y));
499 512 }
500 513
501 514 /*!
502 515 Removes the \a point from the series.
503 516 */
504 517 void QXYSeries::remove(const QPointF &point)
505 518 {
506 519 Q_D(QXYSeries);
507 520 int index = d->m_points.indexOf(point);
508 521 if (index == -1)
509 522 return;
510 523 remove(index);
511 524 }
512 525
513 526 /*!
514 527 Removes the point at \a index from the series.
515 528 */
516 529 void QXYSeries::remove(int index)
517 530 {
518 531 Q_D(QXYSeries);
519 532 d->m_points.remove(index);
520 533 emit pointRemoved(index);
521 534 }
522 535
523 536 /*!
524 537 Inserts a \a point in the series at \a index position.
525 538 */
526 539 void QXYSeries::insert(int index, const QPointF &point)
527 540 {
528 541 Q_D(QXYSeries);
529 542 if (isValidValue(point)) {
530 543 d->m_points.insert(index, point);
531 544 emit pointAdded(index);
532 545 }
533 546 }
534 547
535 548 /*!
536 549 Removes all points from the series.
537 550 */
538 551 void QXYSeries::clear()
539 552 {
540 553 Q_D(QXYSeries);
541 554 for (int i = d->m_points.size() - 1; i >= 0; i--)
542 555 remove(d->m_points.at(i));
543 556 }
544 557
545 558 /*!
546 559 Returns list of points in the series.
547 560 */
548 561 QList<QPointF> QXYSeries::points() const
549 562 {
550 563 Q_D(const QXYSeries);
551 564 return d->m_points.toList();
552 565 }
553 566
554 567 /*!
555 568 Returns point at \a index in internal points vector.
556 569 */
557 570 const QPointF &QXYSeries::at(int index) const
558 571 {
559 572 Q_D(const QXYSeries);
560 573 return d->m_points.at(index);
561 574 }
562 575
563 576 /*!
564 577 Returns number of data points within series.
565 578 */
566 579 int QXYSeries::count() const
567 580 {
568 581 Q_D(const QXYSeries);
569 582 return d->m_points.count();
570 583 }
571 584
572 585
573 586 /*!
574 587 Sets \a pen used for drawing points on the chart. If the pen is not defined, the
575 588 pen from chart theme is used.
576 589 \sa QChart::setTheme()
577 590 */
578 591 void QXYSeries::setPen(const QPen &pen)
579 592 {
580 593 Q_D(QXYSeries);
581 594 if (d->m_pen != pen) {
582 595 bool emitColorChanged = d->m_pen.color() != pen.color();
583 596 d->m_pen = pen;
584 597 emit d->updated();
585 598 if (emitColorChanged)
586 599 emit colorChanged(pen.color());
587 600 }
588 601 }
589 602
590 603 QPen QXYSeries::pen() const
591 604 {
592 605 Q_D(const QXYSeries);
593 606 if (d->m_pen == QChartPrivate::defaultPen())
594 607 return QPen();
595 608 else
596 609 return d->m_pen;
597 610 }
598 611
599 612 /*!
600 613 Sets \a brush used for drawing points on the chart. If the brush is not defined, brush
601 614 from chart theme setting is used.
602 615 \sa QChart::setTheme()
603 616 */
604 617 void QXYSeries::setBrush(const QBrush &brush)
605 618 {
606 619 Q_D(QXYSeries);
607 620 if (d->m_brush != brush) {
608 621 d->m_brush = brush;
609 622 emit d->updated();
610 623 }
611 624 }
612 625
613 626 QBrush QXYSeries::brush() const
614 627 {
615 628 Q_D(const QXYSeries);
616 629 if (d->m_brush == QChartPrivate::defaultBrush())
617 630 return QBrush();
618 631 else
619 632 return d->m_brush;
620 633 }
621 634
622 635 void QXYSeries::setColor(const QColor &color)
623 636 {
624 637 QPen p = pen();
625 638 if (p.color() != color) {
626 639 p.setColor(color);
627 640 setPen(p);
628 641 }
629 642 }
630 643
631 644 QColor QXYSeries::color() const
632 645 {
633 646 return pen().color();
634 647 }
635 648
636 649 void QXYSeries::setPointsVisible(bool visible)
637 650 {
638 651 Q_D(QXYSeries);
639 652 if (d->m_pointsVisible != visible) {
640 653 d->m_pointsVisible = visible;
641 654 emit d->updated();
642 655 }
643 656 }
644 657
645 658 bool QXYSeries::pointsVisible() const
646 659 {
647 660 Q_D(const QXYSeries);
648 661 return d->m_pointsVisible;
649 662 }
650 663
651 664 void QXYSeries::setPointLabelsFormat(const QString &format)
652 665 {
653 666 Q_D(QXYSeries);
654 667 if (d->m_pointLabelsFormat != format) {
655 668 d->m_pointLabelsFormat = format;
656 669 emit pointLabelsFormatChanged(format);
657 670 }
658 671 }
659 672
660 673 QString QXYSeries::pointLabelsFormat() const
661 674 {
662 675 Q_D(const QXYSeries);
663 676 return d->m_pointLabelsFormat;
664 677 }
665 678
666 679 void QXYSeries::setPointLabelsVisible(bool visible)
667 680 {
668 681 Q_D(QXYSeries);
669 682 if (d->m_pointLabelsVisible != visible) {
670 683 d->m_pointLabelsVisible = visible;
671 684 emit pointLabelsVisibilityChanged(visible);
672 685 }
673 686 }
674 687
675 688 bool QXYSeries::pointLabelsVisible() const
676 689 {
677 690 Q_D(const QXYSeries);
678 691 return d->m_pointLabelsVisible;
679 692 }
680 693
681 694 void QXYSeries::setPointLabelsFont(const QFont &font)
682 695 {
683 696 Q_D(QXYSeries);
684 697 if (d->m_pointLabelsFont != font) {
685 698 d->m_pointLabelsFont = font;
686 699 emit pointLabelsFontChanged(font);
687 700 }
688 701 }
689 702
690 703 QFont QXYSeries::pointLabelsFont() const
691 704 {
692 705 Q_D(const QXYSeries);
693 706 return d->m_pointLabelsFont;
694 707 }
695 708
696 709 void QXYSeries::setPointLabelsColor(const QColor &color)
697 710 {
698 711 Q_D(QXYSeries);
699 712 if (d->m_pointLabelsColor != color) {
700 713 d->m_pointLabelsColor = color;
701 714 emit pointLabelsColorChanged(color);
702 715 }
703 716 }
704 717
705 718 QColor QXYSeries::pointLabelsColor() const
706 719 {
707 720 Q_D(const QXYSeries);
708 721 if (d->m_pointLabelsColor == QChartPrivate::defaultPen().color())
709 722 return QPen().color();
710 723 else
711 724 return d->m_pointLabelsColor;
712 725 }
713 726
714 727 /*!
715 728 Stream operator for adding a data \a point to the series.
716 729 \sa append()
717 730 */
718 731 QXYSeries &QXYSeries::operator<< (const QPointF &point)
719 732 {
720 733 append(point);
721 734 return *this;
722 735 }
723 736
724 737
725 738 /*!
726 739 Stream operator for adding a list of \a points to the series.
727 740 \sa append()
728 741 */
729 742
730 743 QXYSeries &QXYSeries::operator<< (const QList<QPointF>& points)
731 744 {
732 745 append(points);
733 746 return *this;
734 747 }
735 748
736 749 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
737 750
738 751
739 752 QXYSeriesPrivate::QXYSeriesPrivate(QXYSeries *q)
740 753 : QAbstractSeriesPrivate(q),
741 754 m_pen(QChartPrivate::defaultPen()),
742 755 m_brush(QChartPrivate::defaultBrush()),
743 756 m_pointsVisible(false),
744 757 m_pointLabelsFormat(QLatin1String("@xPoint, @yPoint")),
745 758 m_pointLabelsVisible(false),
746 759 m_pointLabelsFont(QChartPrivate::defaultFont()),
747 760 m_pointLabelsColor(QChartPrivate::defaultPen().color())
748 761 {
749 762 }
750 763
751 764 void QXYSeriesPrivate::initializeDomain()
752 765 {
753 766 qreal minX(0);
754 767 qreal minY(0);
755 768 qreal maxX(1);
756 769 qreal maxY(1);
757 770
758 771 Q_Q(QXYSeries);
759 772
760 773 const QList<QPointF>& points = q->points();
761 774
762 775 if (!points.isEmpty()) {
763 776 minX = points[0].x();
764 777 minY = points[0].y();
765 778 maxX = minX;
766 779 maxY = minY;
767 780
768 781 for (int i = 0; i < points.count(); i++) {
769 782 qreal x = points[i].x();
770 783 qreal y = points[i].y();
771 784 minX = qMin(minX, x);
772 785 minY = qMin(minY, y);
773 786 maxX = qMax(maxX, x);
774 787 maxY = qMax(maxY, y);
775 788 }
776 789 }
777 790
778 791 domain()->setRange(minX, maxX, minY, maxY);
779 792 }
780 793
781 794 QList<QLegendMarker*> QXYSeriesPrivate::createLegendMarkers(QLegend* legend)
782 795 {
783 796 Q_Q(QXYSeries);
784 797 QList<QLegendMarker*> list;
785 798 return list << new QXYLegendMarker(q,legend);
786 799 }
787 800
788 801 void QXYSeriesPrivate::initializeAxes()
789 802 {
790 803
791 804 }
792 805
793 806 QAbstractAxis::AxisType QXYSeriesPrivate::defaultAxisType(Qt::Orientation orientation) const
794 807 {
795 808 Q_UNUSED(orientation);
796 809 return QAbstractAxis::AxisTypeValue;
797 810 }
798 811
799 812 QAbstractAxis* QXYSeriesPrivate::createDefaultAxis(Qt::Orientation orientation) const
800 813 {
801 814 Q_UNUSED(orientation);
802 815 return new QValueAxis;
803 816 }
804 817
805 818 void QXYSeriesPrivate::initializeAnimations(QtCharts::QChart::AnimationOptions options)
806 819 {
807 820 XYChart *item = static_cast<XYChart *>(m_item.data());
808 821 Q_ASSERT(item);
809 822 if (item->animation())
810 823 item->animation()->stopAndDestroyLater();
811 824
812 825 if (options.testFlag(QChart::SeriesAnimations))
813 826 item->setAnimation(new XYAnimation(item));
814 827 else
815 828 item->setAnimation(0);
816 829 QAbstractSeriesPrivate::initializeAnimations(options);
817 830 }
818 831
819 832 void QXYSeriesPrivate::drawSeriesPointLabels(QPainter *painter, const QVector<QPointF> &points,
820 833 const int offset)
821 834 {
822 835 static const QString xPointTag(QLatin1String("@xPoint"));
823 836 static const QString yPointTag(QLatin1String("@yPoint"));
824 837 const int labelOffset = offset + 2;
825 838
826 839 painter->setFont(m_pointLabelsFont);
827 840 painter->setPen(QPen(m_pointLabelsColor));
828 841 QFontMetrics fm(painter->font());
829 842
830 843 for (int i(0); i < m_points.size(); i++) {
831 844 QString pointLabel = m_pointLabelsFormat;
832 845 pointLabel.replace(xPointTag, presenter()->numberToString(m_points.at(i).x()));
833 846 pointLabel.replace(yPointTag, presenter()->numberToString(m_points.at(i).y()));
834 847
835 848 // Position text in relation to the point
836 849 int pointLabelWidth = fm.width(pointLabel);
837 850 QPointF position(points.at(i));
838 851 position.setX(position.x() - pointLabelWidth / 2);
839 852 position.setY(position.y() - labelOffset);
840 853
841 854 painter->drawText(position, pointLabel);
842 855 }
843 856 }
844 857
845 858 #include "moc_qxyseries.cpp"
846 859 #include "moc_qxyseries_p.cpp"
847 860
848 861 QT_CHARTS_END_NAMESPACE
@@ -1,202 +1,204
1 1 /****************************************************************************
2 2 **
3 3 ** Copyright (C) 2014 Digia Plc
4 4 ** All rights reserved.
5 5 ** For any questions to Digia, please use contact form at http://qt.io
6 6 **
7 7 ** This file is part of the Qt Charts module.
8 8 **
9 9 ** Licensees holding valid commercial license for Qt may use this file in
10 10 ** accordance with the Qt License Agreement provided with the Software
11 11 ** or, alternatively, in accordance with the terms contained in a written
12 12 ** agreement between you and Digia.
13 13 **
14 14 ** If you have questions regarding the use of this file, please use
15 15 ** contact form at http://qt.io
16 16 **
17 17 ****************************************************************************/
18 18
19 19 #include "../qxyseries/tst_qxyseries.h"
20 20 #include <QtCharts/QLineSeries>
21 21
22 22
23 23 Q_DECLARE_METATYPE(QList<QPointF>)
24 24
25 25 class tst_QLineSeries : public tst_QXYSeries
26 26 {
27 27 Q_OBJECT
28 28
29 29 public slots:
30 30 void initTestCase();
31 31 void cleanupTestCase();
32 32 void init();
33 33 void cleanup();
34 34 private slots:
35 35 void qlineseries_data();
36 36 void qlineseries();
37 37 void pressedSignal();
38 38 void releasedSignal();
39 39 void doubleClickedSignal();
40 40 protected:
41 41 void pointsVisible_data();
42 42 };
43 43
44 44 void tst_QLineSeries::initTestCase()
45 45 {
46 46 }
47 47
48 48 void tst_QLineSeries::cleanupTestCase()
49 49 {
50 50 QTest::qWait(1); // Allow final deleteLaters to run
51 51 }
52 52
53 53 void tst_QLineSeries::init()
54 54 {
55 55 tst_QXYSeries::init();
56 56 m_series = new QLineSeries();
57 57 }
58 58
59 59 void tst_QLineSeries::cleanup()
60 60 {
61 61 delete m_series;
62 62 m_series=0;
63 63 tst_QXYSeries::cleanup();
64 64 }
65 65
66 66 void tst_QLineSeries::qlineseries_data()
67 67 {
68 68
69 69 }
70 70
71 71 void tst_QLineSeries::qlineseries()
72 72 {
73 73 QLineSeries series;
74 74
75 75 QCOMPARE(series.count(),0);
76 76 QCOMPARE(series.brush(), QBrush());
77 77 QCOMPARE(series.points(), QList<QPointF>());
78 78 QCOMPARE(series.pen(), QPen());
79 79 QCOMPARE(series.pointsVisible(), false);
80 80 QCOMPARE(series.pointLabelsVisible(), false);
81 81 QCOMPARE(series.pointLabelsFormat(), QLatin1String("@xPoint, @yPoint"));
82 82
83 83 series.append(QList<QPointF>());
84 84 series.append(0.0,0.0);
85 85 series.append(QPointF());
86 86
87 87 series.remove(0.0,0.0);
88 88 series.remove(QPointF());
89 89 series.clear();
90 90
91 91 series.replace(QPointF(),QPointF());
92 92 series.replace(0,0,0,0);
93 93 series.setBrush(QBrush());
94 94
95 95 series.setPen(QPen());
96 96 series.setPointsVisible(false);
97 97
98 98 series.setPointLabelsVisible(false);
99 99 series.setPointLabelsFormat(QString());
100 100
101 101 m_chart->addSeries(&series);
102 102 m_view->show();
103 103 QTest::qWaitForWindowShown(m_view);
104 104 }
105 105
106 106 void tst_QLineSeries::pressedSignal()
107 107 {
108 108 SKIP_IF_CANNOT_TEST_MOUSE_EVENTS();
109 109
110 110 QPointF linePoint(4, 12);
111 111 QLineSeries *lineSeries = new QLineSeries();
112 112 lineSeries->append(QPointF(2, 1));
113 113 lineSeries->append(linePoint);
114 114 lineSeries->append(QPointF(6, 12));
115 115
116 116 QChartView view;
117 117 view.chart()->legend()->setVisible(false);
118 118 view.chart()->addSeries(lineSeries);
119 119 view.show();
120 120 QTest::qWaitForWindowShown(&view);
121 121
122 122 QSignalSpy seriesSpy(lineSeries, SIGNAL(pressed(QPointF)));
123 123
124 124 QPointF checkPoint = view.chart()->mapToPosition(linePoint);
125 125 QTest::mouseClick(view.viewport(), Qt::LeftButton, 0, checkPoint.toPoint());
126 126 QCoreApplication::processEvents(QEventLoop::AllEvents, 1000);
127 127
128 128 QCOMPARE(seriesSpy.count(), 1);
129 129 QList<QVariant> seriesSpyArg = seriesSpy.takeFirst();
130 130 // checkPoint is QPointF and for the mouseClick it it's changed to QPoint
131 131 // this causes small distinction in decimals so we round it before comparing
132 132 QPointF signalPoint = qvariant_cast<QPointF>(seriesSpyArg.at(0));
133 133 QCOMPARE(qRound(signalPoint.x()), qRound(linePoint.x()));
134 134 QCOMPARE(qRound(signalPoint.y()), qRound(linePoint.y()));
135 135 }
136 136
137 137 void tst_QLineSeries::releasedSignal()
138 138 {
139 139 SKIP_IF_CANNOT_TEST_MOUSE_EVENTS();
140 140
141 141 QPointF linePoint(4, 12);
142 142 QLineSeries *lineSeries = new QLineSeries();
143 143 lineSeries->append(QPointF(2, 20));
144 144 lineSeries->append(linePoint);
145 145 lineSeries->append(QPointF(6, 12));
146 146
147 147 QChartView view;
148 148 view.chart()->legend()->setVisible(false);
149 149 view.chart()->addSeries(lineSeries);
150 150 view.show();
151 151 QTest::qWaitForWindowShown(&view);
152 152
153 153 QSignalSpy seriesSpy(lineSeries, SIGNAL(released(QPointF)));
154 154
155 155 QPointF checkPoint = view.chart()->mapToPosition(linePoint);
156 156 QTest::mouseClick(view.viewport(), Qt::LeftButton, 0, checkPoint.toPoint());
157 157 QCoreApplication::processEvents(QEventLoop::AllEvents, 1000);
158 158
159 159 QCOMPARE(seriesSpy.count(), 1);
160 160 QList<QVariant> seriesSpyArg = seriesSpy.takeFirst();
161 161 // checkPoint is QPointF and for the mouseClick it it's changed to QPoint
162 162 // this causes small distinction in decimals so we round it before comparing
163 163 QPointF signalPoint = qvariant_cast<QPointF>(seriesSpyArg.at(0));
164 164 QCOMPARE(qRound(signalPoint.x()), qRound(linePoint.x()));
165 165 QCOMPARE(qRound(signalPoint.y()), qRound(linePoint.y()));
166 166 }
167 167
168 168 void tst_QLineSeries::doubleClickedSignal()
169 169 {
170 170 SKIP_IF_CANNOT_TEST_MOUSE_EVENTS();
171 171
172 172 QPointF linePoint(4, 12);
173 173 QLineSeries *lineSeries = new QLineSeries();
174 174 lineSeries->append(QPointF(2, 20));
175 175 lineSeries->append(linePoint);
176 176 lineSeries->append(QPointF(6, 12));
177 177
178 178 QChartView view;
179 179 view.chart()->legend()->setVisible(false);
180 180 view.chart()->addSeries(lineSeries);
181 181 view.show();
182 182 QTest::qWaitForWindowShown(&view);
183 183
184 184 QSignalSpy seriesSpy(lineSeries, SIGNAL(doubleClicked(QPointF)));
185 185
186 186 QPointF checkPoint = view.chart()->mapToPosition(linePoint);
187 // mouseClick needed first to save the position
188 QTest::mouseClick(view.viewport(), Qt::LeftButton, 0, checkPoint.toPoint());
187 189 QTest::mouseDClick(view.viewport(), Qt::LeftButton, 0, checkPoint.toPoint());
188 190 QCoreApplication::processEvents(QEventLoop::AllEvents, 1000);
189 191
190 192 QCOMPARE(seriesSpy.count(), 1);
191 193 QList<QVariant> seriesSpyArg = seriesSpy.takeFirst();
192 194 // checkPoint is QPointF and for the mouseClick it it's changed to QPoint
193 195 // this causes small distinction in decimals so we round it before comparing
194 196 QPointF signalPoint = qvariant_cast<QPointF>(seriesSpyArg.at(0));
195 197 QCOMPARE(qRound(signalPoint.x()), qRound(linePoint.x()));
196 198 QCOMPARE(qRound(signalPoint.y()), qRound(linePoint.y()));
197 199 }
198 200
199 201 QTEST_MAIN(tst_QLineSeries)
200 202
201 203 #include "tst_qlineseries.moc"
202 204
@@ -1,194 +1,196
1 1 /****************************************************************************
2 2 **
3 3 ** Copyright (C) 2014 Digia Plc
4 4 ** All rights reserved.
5 5 ** For any questions to Digia, please use contact form at http://qt.io
6 6 **
7 7 ** This file is part of the Qt Charts module.
8 8 **
9 9 ** Licensees holding valid commercial license for Qt may use this file in
10 10 ** accordance with the Qt License Agreement provided with the Software
11 11 ** or, alternatively, in accordance with the terms contained in a written
12 12 ** agreement between you and Digia.
13 13 **
14 14 ** If you have questions regarding the use of this file, please use
15 15 ** contact form at http://qt.io
16 16 **
17 17 ****************************************************************************/
18 18
19 19 #include "../qxyseries/tst_qxyseries.h"
20 20 #include <QtCharts/QSplineSeries>
21 21
22 22 Q_DECLARE_METATYPE(QList<QPointF>)
23 23
24 24 class tst_QSplineSeries : public tst_QXYSeries
25 25 {
26 26 Q_OBJECT
27 27
28 28 public slots:
29 29 void initTestCase();
30 30 void cleanupTestCase();
31 31 void init();
32 32 void cleanup();
33 33 private slots:
34 34 void qsplineseries_data();
35 35 void qsplineseries();
36 36 void pressedSignal();
37 37 void releasedSignal();
38 38 void doubleClickedSignal();
39 39 protected:
40 40 void pointsVisible_data();
41 41 };
42 42
43 43 void tst_QSplineSeries::initTestCase()
44 44 {
45 45 }
46 46
47 47 void tst_QSplineSeries::cleanupTestCase()
48 48 {
49 49 QTest::qWait(1); // Allow final deleteLaters to run
50 50 }
51 51
52 52 void tst_QSplineSeries::init()
53 53 {
54 54 tst_QXYSeries::init();
55 55 m_series = new QSplineSeries();
56 56 }
57 57
58 58 void tst_QSplineSeries::cleanup()
59 59 {
60 60 delete m_series;
61 61 tst_QXYSeries::cleanup();
62 62 }
63 63
64 64 void tst_QSplineSeries::qsplineseries_data()
65 65 {
66 66
67 67 }
68 68
69 69 void tst_QSplineSeries::qsplineseries()
70 70 {
71 71 QSplineSeries series;
72 72
73 73 QCOMPARE(series.count(),0);
74 74 QCOMPARE(series.brush(), QBrush());
75 75 QCOMPARE(series.points(), QList<QPointF>());
76 76 QCOMPARE(series.pen(), QPen());
77 77 QCOMPARE(series.pointsVisible(), false);
78 78
79 79 series.append(QList<QPointF>());
80 80 series.append(0.0,0.0);
81 81 series.append(QPointF());
82 82
83 83 series.remove(0.0,0.0);
84 84 series.remove(QPointF());
85 85 series.clear();
86 86
87 87 series.replace(QPointF(),QPointF());
88 88 series.replace(0,0,0,0);
89 89 series.setBrush(QBrush());
90 90
91 91 series.setPen(QPen());
92 92 series.setPointsVisible(false);
93 93
94 94 m_chart->addSeries(&series);
95 95 m_view->show();
96 96 QTest::qWaitForWindowShown(m_view);
97 97 }
98 98
99 99 void tst_QSplineSeries::pressedSignal()
100 100 {
101 101 SKIP_IF_CANNOT_TEST_MOUSE_EVENTS();
102 102
103 103 QPointF splinePoint(4, 12);
104 104 QSplineSeries *splineSeries = new QSplineSeries();
105 105 splineSeries->append(QPointF(2, 1));
106 106 splineSeries->append(splinePoint);
107 107 splineSeries->append(QPointF(6, 12));
108 108
109 109 QChartView view;
110 110 view.chart()->legend()->setVisible(false);
111 111 view.chart()->addSeries(splineSeries);
112 112 view.show();
113 113 QTest::qWaitForWindowShown(&view);
114 114
115 115 QSignalSpy seriesSpy(splineSeries, SIGNAL(pressed(QPointF)));
116 116
117 117 QPointF checkPoint = view.chart()->mapToPosition(splinePoint);
118 118 QTest::mouseClick(view.viewport(), Qt::LeftButton, 0, checkPoint.toPoint());
119 119 QCoreApplication::processEvents(QEventLoop::AllEvents, 1000);
120 120
121 121 QCOMPARE(seriesSpy.count(), 1);
122 122 QList<QVariant> seriesSpyArg = seriesSpy.takeFirst();
123 123 // checkPoint is QPointF and for the mouseClick it it's changed to QPoint
124 124 // this causes small distinction in decimals so we round it before comparing
125 125 QPointF signalPoint = qvariant_cast<QPointF>(seriesSpyArg.at(0));
126 126 QCOMPARE(qRound(signalPoint.x()), qRound(splinePoint.x()));
127 127 QCOMPARE(qRound(signalPoint.y()), qRound(splinePoint.y()));
128 128 }
129 129
130 130 void tst_QSplineSeries::releasedSignal()
131 131 {
132 132 SKIP_IF_CANNOT_TEST_MOUSE_EVENTS();
133 133
134 134 QPointF splinePoint(4, 12);
135 135 QSplineSeries *splineSeries = new QSplineSeries();
136 136 splineSeries->append(QPointF(2, 20));
137 137 splineSeries->append(splinePoint);
138 138 splineSeries->append(QPointF(6, 12));
139 139
140 140 QChartView view;
141 141 view.chart()->legend()->setVisible(false);
142 142 view.chart()->addSeries(splineSeries);
143 143 view.show();
144 144 QTest::qWaitForWindowShown(&view);
145 145
146 146 QSignalSpy seriesSpy(splineSeries, SIGNAL(released(QPointF)));
147 147
148 148 QPointF checkPoint = view.chart()->mapToPosition(splinePoint);
149 149 QTest::mouseClick(view.viewport(), Qt::LeftButton, 0, checkPoint.toPoint());
150 150 QCoreApplication::processEvents(QEventLoop::AllEvents, 1000);
151 151
152 152 QCOMPARE(seriesSpy.count(), 1);
153 153 QList<QVariant> seriesSpyArg = seriesSpy.takeFirst();
154 154 // checkPoint is QPointF and for the mouseClick it it's changed to QPoint
155 155 // this causes small distinction in decimals so we round it before comparing
156 156 QPointF signalPoint = qvariant_cast<QPointF>(seriesSpyArg.at(0));
157 157 QCOMPARE(qRound(signalPoint.x()), qRound(splinePoint.x()));
158 158 QCOMPARE(qRound(signalPoint.y()), qRound(splinePoint.y()));
159 159 }
160 160
161 161 void tst_QSplineSeries::doubleClickedSignal()
162 162 {
163 163 SKIP_IF_CANNOT_TEST_MOUSE_EVENTS();
164 164
165 165 QPointF splinePoint(4, 12);
166 166 QSplineSeries *splineSeries = new QSplineSeries();
167 167 splineSeries->append(QPointF(2, 20));
168 168 splineSeries->append(splinePoint);
169 169 splineSeries->append(QPointF(6, 12));
170 170
171 171 QChartView view;
172 172 view.chart()->legend()->setVisible(false);
173 173 view.chart()->addSeries(splineSeries);
174 174 view.show();
175 175 QTest::qWaitForWindowShown(&view);
176 176
177 177 QSignalSpy seriesSpy(splineSeries, SIGNAL(doubleClicked(QPointF)));
178 178
179 179 QPointF checkPoint = view.chart()->mapToPosition(splinePoint);
180 // mouseClick needed first to save the position
181 QTest::mouseClick(view.viewport(), Qt::LeftButton, 0, checkPoint.toPoint());
180 182 QTest::mouseDClick(view.viewport(), Qt::LeftButton, 0, checkPoint.toPoint());
181 183 QCoreApplication::processEvents(QEventLoop::AllEvents, 1000);
182 184
183 185 QCOMPARE(seriesSpy.count(), 1);
184 186 QList<QVariant> seriesSpyArg = seriesSpy.takeFirst();
185 187 // checkPoint is QPointF and for the mouseClick it it's changed to QPoint
186 188 // this causes small distinction in decimals so we round it before comparing
187 189 QPointF signalPoint = qvariant_cast<QPointF>(seriesSpyArg.at(0));
188 190 QCOMPARE(qRound(signalPoint.x()), qRound(splinePoint.x()));
189 191 QCOMPARE(qRound(signalPoint.y()), qRound(splinePoint.y()));
190 192 }
191 193 QTEST_MAIN(tst_QSplineSeries)
192 194
193 195 #include "tst_qsplineseries.moc"
194 196
@@ -1,79 +1,80
1 1 /****************************************************************************
2 2 **
3 3 ** Copyright (C) 2014 Digia Plc
4 4 ** All rights reserved.
5 5 ** For any questions to Digia, please use contact form at http://qt.io
6 6 **
7 7 ** This file is part of the Qt Charts module.
8 8 **
9 9 ** Licensees holding valid commercial license for Qt may use this file in
10 10 ** accordance with the Qt License Agreement provided with the Software
11 11 ** or, alternatively, in accordance with the terms contained in a written
12 12 ** agreement between you and Digia.
13 13 **
14 14 ** If you have questions regarding the use of this file, please use
15 15 ** contact form at http://qt.io
16 16 **
17 17 ****************************************************************************/
18 18
19 19 import QtQuick 2.0
20 20 import QtCharts 2.0
21 21
22 22 ChartView {
23 23 title: "scatter series"
24 24 property variant series: scatterSeries
25 25 animationOptions: ChartView.SeriesAnimations
26 26
27 27 ScatterSeries {
28 28 id: scatterSeries
29 29 name: "scatter 1"
30 30 XYPoint { x: 1.5; y: 1.5 }
31 31 XYPoint { x: 1.5; y: 1.6 }
32 32 XYPoint { x: 1.57; y: 1.55 }
33 33 XYPoint { x: 1.8; y: 1.8 }
34 34 XYPoint { x: 1.9; y: 1.6 }
35 35 XYPoint { x: 2.1; y: 1.3 }
36 36 XYPoint { x: 2.5; y: 2.1 }
37 37
38 38 pointLabelsFormat: "@xPoint, @yPoint";
39 39
40 40 onNameChanged: console.log("scatterSeries.onNameChanged: " + name);
41 41 onVisibleChanged: console.log("scatterSeries.onVisibleChanged: " + visible);
42 42 onOpacityChanged: console.log(name + ".onOpacityChanged: " + opacity);
43 43 onClicked: console.log(name + ".onClicked: " + point.x + ", " + point.y);
44 44 onHovered: console.log(name + ".onHovered: " + point.x + ", " + point.y);
45 45 onPointReplaced: console.log("scatterSeries.onPointReplaced: " + index);
46 46 onPointRemoved: console.log("scatterSeries.onPointRemoved: " + index);
47 47 onPointAdded: console.log("scatterSeries.onPointAdded: " + series.at(index).x + ", " + series.at(index).y);
48 48 onColorChanged: console.log("scatterSeries.onColorChanged: " + color);
49 49 onBorderColorChanged: console.log("scatterSeries.onBorderColorChanged: " + borderColor);
50 50 onBorderWidthChanged: console.log("scatterSeries.onBorderChanged: " + borderWidth);
51 51 onCountChanged: console.log("scatterSeries.onCountChanged: " + count);
52 onPointLabelsVisibilityChanged: console.log("lineSeries.onPointLabelsVisibilityChanged: "
52 onPointLabelsVisibilityChanged: console.log("scatterSeries.onPointLabelsVisibilityChanged: "
53 53 + visible);
54 onPointLabelsFormatChanged: console.log("lineSeries.onPointLabelsFormatChanged: "
54 onPointLabelsFormatChanged: console.log("scatterSeries.onPointLabelsFormatChanged: "
55 55 + format);
56 onPointLabelsFontChanged: console.log("lineSeries.onPointLabelsFontChanged: "
56 onPointLabelsFontChanged: console.log("scatterSeries.onPointLabelsFontChanged: "
57 57 + font.family);
58 onPointLabelsColorChanged: console.log("lineSeries.onPointLabelsColorChanged: "
58 onPointLabelsColorChanged: console.log("scatterSeries.onPointLabelsColorChanged: "
59 59 + color);
60 60 onPressed: console.log(name + ".onPressed: " + point.x + ", " + point.y);
61 61 onReleased: console.log(name + ".onReleased: " + point.x + ", " + point.y);
62 62 onDoubleClicked: console.log(name + ".onDoubleClicked: " + point.x + ", " + point.y);
63 63 }
64 64
65 65 ScatterSeries {
66 66 name: "scatter2"
67 67 XYPoint { x: 2.0; y: 2.0 }
68 68 XYPoint { x: 2.0; y: 2.1 }
69 69 XYPoint { x: 2.07; y: 2.05 }
70 70 XYPoint { x: 2.2; y: 2.9 }
71 71 XYPoint { x: 2.4; y: 2.7 }
72 72 XYPoint { x: 2.67; y: 2.65 }
73 73 onClicked: console.log(name + ".onClicked: " + point.x + ", " + point.y);
74 74 onHovered: console.log(name + ".onHovered: " + point.x + ", " + point.y);
75 75 onPressed: console.log(name + ".onPressed: " + point.x + ", " + point.y);
76 76 onReleased: console.log(name + ".onReleased: " + point.x + ", " + point.y);
77 77 onDoubleClicked: console.log(name + ".onDoubleClicked: " + point.x + ", " + point.y);
78 78 }
79
79 80 }
General Comments 0
You need to be logged in to leave comments. Login now