kchart

KDChartRingPainter.cpp
1/*
2 KDChart - a multi-platform charting engine
3 */
4
5/****************************************************************************
6 ** Copyright (C) 2001-2003 Klarälvdalens Datakonsult AB. All rights reserved.
7 **
8 ** This file is part of the KDChart library.
9 **
10 ** This file may be distributed and/or modified under the terms of the
11 ** GNU General Public License version 2 as published by the Free Software
12 ** Foundation and appearing in the file LICENSE.GPL included in the
13 ** packaging of this file.
14 **
15 ** Licensees holding valid commercial KDChart licenses may use this file in
16 ** accordance with the KDChart Commercial License Agreement provided with
17 ** the Software.
18 **
19 ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
20 ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
21 **
22 ** See http://www.klaralvdalens-datakonsult.se/?page=products for
23 ** information about KDChart Commercial License Agreements.
24 **
25 ** Contact info@klaralvdalens-datakonsult.se if any conditions of this
26 ** licensing are not clear to you.
27 **
28 **********************************************************************/
29#include "KDChartRingPainter.h"
30#include "KDChartParams.h"
31
32#include <tqpainter.h>
33#include <tqvaluestack.h>
34
35#include <stdlib.h>
36
37#define DEGTORAD(d) (d)*M_PI/180
38
46
53KDChartPainter( params )
54{
55 // This constructor intentionally left blank so far; we cannot setup the
56 // geometry yet since we do not know the size of the painter.
57}
58
59
64{
65 // intentionally left blank
66}
67
68
78void KDChartRingPainter::paintData( TQPainter* painter,
80 bool paint2nd,
81 KDChartDataRegionList* regions )
82{
83 uint chart = paint2nd ? 1 : 0;
84
85 TQRect ourClipRect( _dataRect );
86
87 const TQWMatrix & world = painter->worldMatrix();
88 ourClipRect = world.mapRect( ourClipRect );
89 ourClipRect.setTop(ourClipRect.top()-1);
90 ourClipRect.setLeft(ourClipRect.left()-1);
91 ourClipRect.setBottom(ourClipRect.bottom()+1);
92 ourClipRect.setRight(ourClipRect.right()+1);
93 painter->setClipRect( ourClipRect );
94
95 uint datasetStart, datasetEnd;
96 if ( params()->neverUsedSetChartSourceMode()
97 || !params()->findDataset( KDChartParams::DataEntry,
98 datasetStart,
99 datasetEnd,
100 chart ) ) {
101 uint maxRow, maxRowMinus1;
102 switch ( data->usedRows() ) {
103 case 0:
104 return ;
105 case 1:
106 maxRow = 0;
107 maxRowMinus1 = 0;
108 break;
109 default:
110 maxRow = data->usedRows() - 1;
111 maxRowMinus1 = maxRow - 1;
112 }
113 datasetStart = paint2nd ? maxRow
114 : 0;
115 datasetEnd = paint2nd ? maxRow
116 : ( ( KDChartParams::NoType
117 == params()->additionalChartType() )
118 ? maxRow
119 : maxRowMinus1 );
120 }
121 uint datasetNum = abs( (int)( datasetEnd - datasetStart ) + 1 );
122
123
124 // Number of values: If -1, use all values, otherwise use the
125 // specified number of values.
126 if ( params()->numValues() != -1 )
127 _numValues = params()->numValues();
128 else
129 _numValues = data->usedCols();
130
131 // compute position
132 _size = TQMIN( _dataRect.width(), _dataRect.height() ); // initial size
133 // if the rings explode, we need to give them additional space =>
134 // make the basic size smaller
135 if ( params()->explode() ) {
136 double doubleSize = ( double ) _size;
137 doubleSize /= ( 1.0 + params()->explodeFactor() * 2 );
138 _size = ( int ) doubleSize;
139 }
140
141 int x = ( _dataRect.width() == _size ) ? 0 : ( ( _dataRect.width() - _size ) / 2 );
142 int y = ( _dataRect.height() == _size ) ? 0 : ( ( _dataRect.height() - _size ) / 2 );
143 _position = TQRect( x, y, _size, _size );
144 _position.moveBy( _dataRect.left(), _dataRect.top() );
145
146 // We need the row sums anyway later, so we can just as well compute them
147 // here, because we need them in case of relative ring thicknesses.
148 TQMemArray<double> rowsums;
149 double totalSum = 0.0;
150 rowsums.resize( datasetEnd+1 ); // not datasetNum!
151 for( int d1 = (int)datasetStart; d1 <= (int)datasetEnd; d1++ ) {
152 rowsums[d1] = data->rowAbsSum( d1 );
153 totalSum += rowsums[d1];
154 }
155
156 TQMemArray<int> ringthicknesses;
157 ringthicknesses.resize( datasetEnd+1 ); // not datasetNum!
158
159 // constant ring thickness
160 int ringthickness = _size / ( datasetNum * 2 );
161 // Never let the ring thickness be more than 1/10 of the size to
162 // ensure "ringness"
163 if( ringthickness > ( _size/10 ) )
164 ringthickness = _size / 10;
165
166 for( int d2 = (int)datasetStart; d2 <= (int)datasetEnd; d2++ )
167 if( params()->relativeRingThickness() ) {
168 // 50% should be the same thickness as the one used when ring
169 // thickness is constant.
170 ringthicknesses[d2] = (uint)floor( (rowsums[d2] / totalSum) *
171 ( 2.0 * (double)ringthickness ) + 0.5 );
172 } else {
173 ringthicknesses[d2] = ringthickness;
174 }
175
176 int currentouterradius = _size/2;
177
178 // Loop through all the displayable datasets; each dataset is one ring
179 for( int dataset = (int)datasetStart; dataset <= (int)datasetEnd; dataset++ ) {
180 double sectorsPerValue = 5760.0 / rowsums[dataset]; // 5760 == 16*360, number of sections in TQt circle
181 //int sectorsPerValueI = static_cast<int>( sectorsPerValue );
182 double currentstartpos = (double)params()->ringStart() * 16.0;
183 // Loop through all the values; each value is one piece on the ring.
184 TQVariant vValY;
185 for( int value = 0; value < _numValues; value++ ) {
186 // is there anything at all at this value?
187 double cellValue = 0.0;
188 if( data->cellCoord( dataset, value, vValY, 1 ) &&
189 TQVariant::Double == vValY.type() ){
190 cellValue = fabs( vValY.toDouble() );
191 // Explosion: Only explode if explosion is turned on generally
192 // and we are on the first ring. Besides, if there is a list
193 // of explodeable values, the current value must be on this
194 // list.
195
196 TQValueList<int> explodeList = params()->explodeValues();
197 bool explode = params()->explode() && // explosion is on at all
198 ( dataset == (int)datasetStart ) && // outermost ring
199 ( ( explodeList.count() == 0 ) || // either nothing on explode list
200 ( explodeList.find( value ) != explodeList.end() ) ); // or pie is on it
201
202 drawOneSegment( painter,
203 currentouterradius,
204 currentouterradius-ringthicknesses[dataset],
205 currentstartpos,
206 sectorsPerValue * cellValue,
207 dataset, value, chart, explode, regions );
208 }
209 currentstartpos += sectorsPerValue * cellValue;
210 }
211 currentouterradius -= ringthicknesses[dataset];
212 }
213}
214
215
216
217void KDChartRingPainter::drawOneSegment( TQPainter* painter,
218 uint outerRadius,
219 uint innerRadius,
220 double startAngle,
221 double angles,
222 uint dataset,
223 uint value,
224 uint chart,
225 bool explode,
226 KDChartDataRegionList* regions )
227{
228 // special case for full circle
229 if( angles == 5760.0 )
230 startAngle = 0.0;
231
232 painter->setPen( TQPen( params()->outlineDataColor(),
233 params()->outlineDataLineWidth() ) );
234 painter->setBrush( params()->dataColor( value ) );
235
236 uint outerRadius2 = outerRadius * 2;
237 uint innerRadius2 = innerRadius * 2;
238
239 TQRect drawPosition = _position;
240 if ( explode ) {
241 // need to compute a new position for each pie
242 double explodeAngle = ( startAngle + angles / 2.0 ) / 16.0;
243 double explodeAngleRad = DEGTORAD( explodeAngle );
244 double cosAngle = cos( explodeAngleRad );
245 double sinAngle = -sin( explodeAngleRad );
246
247 // find the explode factor for this particular ring segment
248 double explodeFactor = 0.0;
249 TQMap<int,double> explodeFactors = params()->explodeFactors();
250 if( !explodeFactors.contains( value ) ) // not on factors list, use default
251 explodeFactor = params()->explodeFactor();
252 else // on factors list, use segment-specific value
253 explodeFactor = explodeFactors[value];
254
255 double explodeX = explodeFactor * _size * cosAngle;
256 double explodeY = explodeFactor * _size * sinAngle;
257 drawPosition.moveBy( static_cast<int>( explodeX ), static_cast<int>( explodeY ) );
258 }
259
260 TQRect outerRect( drawPosition.x() +
261 ( drawPosition.width() - outerRadius2 ) / 2,
262 drawPosition.y() +
263 ( drawPosition.height() - outerRadius2 ) / 2,
264 outerRadius2, outerRadius2 );
265 TQRect innerRect( drawPosition.x() +
266 ( drawPosition.width() - innerRadius2 ) / 2,
267 drawPosition.y() +
268 ( drawPosition.height() - innerRadius2 ) / 2,
269 innerRadius2, innerRadius2 );
270
271 // Start with getting the points for the inner arc.
272 TQPointArray innerArc;
273 makeArc( innerArc, innerRect, startAngle, angles );
274
275 // And the points for the outer arc
276 TQPointArray outerArc;
277 makeArc( outerArc, outerRect, startAngle, angles );
278
279 // Now copy the points from the outer arc in the reverse order onto the
280 // inner arc array and draw that.
281 uint innerArcPoints = innerArc.size();
282 uint outerArcPoints = outerArc.size();
283 innerArc.resize( innerArcPoints + outerArcPoints );
284 for ( int i = outerArcPoints - 1; i >= 0; i-- ) {
285 innerArc.setPoint( innerArcPoints+outerArcPoints-i-1,
286 outerArc.point( i ) );
287 }
288
289 painter->drawPolygon( innerArc );
290 if ( regions /* && ( innerArc.size() > 2 )*/ ) {
291 KDChartDataRegion* datReg = new KDChartDataRegion( dataset,
292 value,
293 chart,
294 innerArc );
295
296 const int aA = static_cast<int>( startAngle );
297 const int aM = static_cast<int>( startAngle + angles / 2.0 );
298 const int aZ = static_cast<int>( startAngle + angles );
299
300 datReg->points[ KDChartEnums::PosTopLeft ]
301 = pointOnCircle( outerRect, aZ );
302 datReg->points[ KDChartEnums::PosTopCenter ]
303 = pointOnCircle( outerRect, aM );
304 datReg->points[ KDChartEnums::PosTopRight ]
305 = pointOnCircle( outerRect, aA );
306
307 datReg->points[ KDChartEnums::PosBottomLeft ]
308 = pointOnCircle( innerRect, aZ );
309 datReg->points[ KDChartEnums::PosBottomCenter ]
310 = pointOnCircle( innerRect, aM );
311 datReg->points[ KDChartEnums::PosBottomRight ]
312 = pointOnCircle( innerRect, aA );
313
314 datReg->points[ KDChartEnums::PosCenterLeft ]
315 = TQPoint( ( datReg->points[ KDChartEnums::PosTopLeft ].x()
316 + datReg->points[ KDChartEnums::PosBottomLeft ].x() ) / 2,
317 ( datReg->points[ KDChartEnums::PosTopLeft ].y()
318 + datReg->points[ KDChartEnums::PosBottomLeft ].y() ) / 2 );
319 datReg->points[ KDChartEnums::PosCenter ]
320 = TQPoint( ( datReg->points[ KDChartEnums::PosTopCenter ].x()
321 + datReg->points[ KDChartEnums::PosBottomCenter ].x() ) / 2,
322 ( datReg->points[ KDChartEnums::PosTopCenter ].y()
323 + datReg->points[ KDChartEnums::PosBottomCenter ].y() ) / 2 );
324 datReg->points[ KDChartEnums::PosCenterRight ]
325 = TQPoint( ( datReg->points[ KDChartEnums::PosTopRight ].x()
326 + datReg->points[ KDChartEnums::PosBottomRight ].x() ) / 2,
327 ( datReg->points[ KDChartEnums::PosTopRight ].y()
328 + datReg->points[ KDChartEnums::PosBottomRight ].y() ) / 2 );
329
330 // test the 9 positions:
331 /*
332 painter->drawEllipse( datReg->points[ KDChartEnums::PosTopLeft ].x() - 2,
333 datReg->points[ KDChartEnums::PosTopLeft ].y() - 2, 5, 5);
334 painter->drawEllipse( datReg->points[ KDChartEnums::PosCenterLeft ].x() - 2,
335 datReg->points[ KDChartEnums::PosCenterLeft ].y() - 2, 5, 5);
336 painter->drawEllipse( datReg->points[ KDChartEnums::PosBottomLeft ].x() - 2,
337 datReg->points[ KDChartEnums::PosBottomLeft ].y() - 2, 5, 5);
338
339 tqDebug( "\ncenter: (%i, %i)",
340 datReg->points[ KDChartEnums::PosCenter ].x(),
341 datReg->points[ KDChartEnums::PosCenter ].y() );
342 painter->drawEllipse( datReg->points[ KDChartEnums::PosTopCenter ].x() - 2,
343 datReg->points[ KDChartEnums::PosTopCenter ].y() - 2, 5, 5);
344 painter->drawEllipse( datReg->points[ KDChartEnums::PosCenter ].x() - 2,
345 datReg->points[ KDChartEnums::PosCenter ].y() - 2, 5, 5);
346 painter->drawEllipse( datReg->points[ KDChartEnums::PosBottomCenter].x() - 2,
347 datReg->points[ KDChartEnums::PosBottomCenter].y() - 2, 5, 5);
348
349 painter->drawRect( datReg->points[ KDChartEnums::PosCenterRight ].x() - 2,
350 datReg->points[ KDChartEnums::PosCenterRight ].y() - 2, 5, 5);
351 //painter->drawRect( datReg->points[ KDChartEnums::PosTopRight ].x() - 2,
352 // datReg->points[ KDChartEnums::PosTopRight ].y() - 2, 5, 5);
353 painter->drawRect( datReg->points[ KDChartEnums::PosBottomRight ].x() - 2,
354 datReg->points[ KDChartEnums::PosBottomRight ].y() - 2, 5, 5);
355 */
356 datReg->startAngle = static_cast<int>( startAngle );
357 datReg->angleLen = static_cast<int>( angles );
358 regions->append( datReg );
359 }
360}
361
362
376TQString KDChartRingPainter::fallbackLegendText( uint dataset ) const
377{
378 return TQObject::tr( "Item " ) + TQString::number( dataset + 1 );
379}
380
381
392{
393 return data->usedCols();
394}
Header for all common chart parameters.
The collection class used by KD Chart to store data region information.
Bundles the display parameters of a chart.
ExplodeFactorsMap explodeFactors() const
Returns the list of explode factors.
double explodeFactor() const
Returns the default explode factor, i.e., the explode factor used for those segments which should be ...
virtual ~KDChartRingPainter()
Destructor.
virtual void paintData(TQPainter *painter, KDChartTableDataBase *data, bool paint2nd, KDChartDataRegionList *regions=0)
Paints the actual data area.
virtual TQString fallbackLegendText(uint dataset) const
This method is a specialization that returns a fallback legend text appropriate for rings where the f...
KDChartRingPainter(KDChartParams *params)
Constructor.
virtual uint numLegendFallbackTexts(KDChartTableDataBase *data) const
This methods returns the number of elements to be shown in the legend in case fallback texts are used...
KD Chart's build-in table data for an easy way of storing data values.
virtual uint usedCols() const
Returns the number of cols in the table that actually contain data.
virtual bool cellCoord(uint _row, uint _col, TQVariant &_value, int coordinate=1) const =0
Returns one of the coordinate data value(s) stored in a cell.
virtual uint usedRows() const
Returns the number of rows in the table that actually contain data.