KChart

KChartWidget.cpp
1 /*
2  * SPDX-FileCopyrightText: 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved.
3  *
4  * This file is part of the KD Chart library.
5  *
6  * SPDX-License-Identifier: GPL-2.0-or-later
7  */
8 
9 #include <KChartWidget.h>
10 #include <KChartWidget_p.h>
11 
12 #include <KChartAbstractDiagram.h>
13 #include <KChartBarDiagram.h>
14 #include <KChartChart.h>
15 #include <KChartAbstractCoordinatePlane.h>
16 #include <KChartLineDiagram.h>
17 #include <KChartPlotter.h>
18 #include <KChartPieDiagram.h>
19 #include <KChartPolarDiagram.h>
20 #include <KChartRingDiagram.h>
21 #include <KChartLegend.h>
22 #include "KChartMath_p.h"
23 
24 #include <QDebug>
25 
26 #define d d_func()
27 
28 using namespace KChart;
29 
30 Widget::Private::Private( Widget * qq )
31  : q( qq ),
32  layout( q ),
33  m_model( q ),
34  m_chart( q ),
35  m_cartPlane( &m_chart ),
36  m_polPlane( &m_chart ),
37  usedDatasetWidth( 0 )
38 {
39  KDAB_SET_OBJECT_NAME( layout );
40  KDAB_SET_OBJECT_NAME( m_model );
41  KDAB_SET_OBJECT_NAME( m_chart );
42 
43  layout.addWidget( &m_chart );
44 }
45 
46 Widget::Private::~Private() {}
47 
48 
49 
50 Widget::Widget( QWidget* parent ) :
51  QWidget(parent), _d( new Private( this ) )
52 {
53  // as default we have a cartesian coordinate plane ...
54  // ... and a line diagram
55  setType( Line );
56 }
57 
59 {
60  delete _d; _d = nullptr;
61 }
62 
63 void Widget::init()
64 {
65 }
66 
67 void Widget::setDataset( int column, const QVector< qreal > & data, const QString& title )
68 {
69  if ( ! checkDatasetWidth( 1 ) )
70  return;
71 
72  QStandardItemModel & model = d->m_model;
73 
74  justifyModelSize( data.size(), column + 1 );
75 
76  for ( int i = 0; i < data.size(); ++i )
77  {
78  const QModelIndex index = model.index( i, column );
79  model.setData( index, QVariant( data[i] ), Qt::DisplayRole );
80  }
81  if ( ! title.isEmpty() )
82  model.setHeaderData( column, Qt::Horizontal, QVariant( title ) );
83 }
84 
85 void Widget::setDataset( int column, const QVector< QPair< qreal, qreal > > & data, const QString& title )
86 {
87  if ( ! checkDatasetWidth( 2 ))
88  return;
89 
90  QStandardItemModel & model = d->m_model;
91 
92  justifyModelSize( data.size(), (column + 1) * 2 );
93 
94  for ( int i = 0; i < data.size(); ++i )
95  {
96  QModelIndex index = model.index( i, column * 2 );
97  model.setData( index, QVariant( data[i].first ), Qt::DisplayRole );
98 
99  index = model.index( i, column * 2 + 1 );
100  model.setData( index, QVariant( data[i].second ), Qt::DisplayRole );
101  }
102  if ( ! title.isEmpty() ) {
103  model.setHeaderData( column, Qt::Horizontal, QVariant( title ) );
104  }
105 }
106 
107 void Widget::setDataCell( int row, int column, qreal data )
108 {
109  if ( ! checkDatasetWidth( 1 ) )
110  return;
111 
112  QStandardItemModel & model = d->m_model;
113 
114  justifyModelSize( row + 1, column + 1 );
115 
116  const QModelIndex index = model.index( row, column );
117  model.setData( index, QVariant( data ), Qt::DisplayRole );
118 }
119 
120 void Widget::setDataCell( int row, int column, QPair< qreal, qreal > data )
121 {
122  if ( ! checkDatasetWidth( 2 ))
123  return;
124 
125  QStandardItemModel & model = d->m_model;
126 
127  justifyModelSize( row + 1, (column + 1) * 2 );
128 
129  QModelIndex index = model.index( row, column * 2 );
130  model.setData( index, QVariant( data.first ), Qt::DisplayRole );
131 
132  index = model.index( row, column * 2 + 1 );
133  model.setData( index, QVariant( data.second ), Qt::DisplayRole );
134 }
135 
136 /*
137  * Resets all data.
138  */
140 {
141  d->m_model.clear();
142  d->usedDatasetWidth = 0;
143 }
144 
145 void Widget::setGlobalLeading( int left, int top, int right, int bottom )
146 {
147  d->m_chart.setGlobalLeading( left, top, right, bottom );
148 }
149 
150 void Widget::setGlobalLeadingLeft( int leading )
151 {
152  d->m_chart.setGlobalLeadingLeft( leading );
153 }
154 
156 {
157  return d->m_chart.globalLeadingLeft();
158 }
159 
160 void Widget::setGlobalLeadingTop( int leading )
161 {
162  d->m_chart.setGlobalLeadingTop( leading );
163 }
164 
166 {
167  return d->m_chart.globalLeadingTop();
168 }
169 
170 void Widget::setGlobalLeadingRight( int leading )
171 {
172  d->m_chart.setGlobalLeadingRight( leading );
173 }
174 
176 {
177  return d->m_chart.globalLeadingRight();
178 }
179 
181 {
182  d->m_chart.setGlobalLeadingBottom( leading );
183 }
184 
186 {
187  return d->m_chart.globalLeadingBottom();
188 }
189 
191 {
192  return d->m_chart.headerFooter();
193 }
194 
196 {
197  return d->m_chart.headerFooters();
198 }
199 
201  HeaderFooter::HeaderFooterType type,
202  Position position)
203 {
204  HeaderFooter* newHeader = new HeaderFooter( &d->m_chart );
205  newHeader->setType( type );
206  newHeader->setPosition( position );
207  newHeader->setText( text );
208  d->m_chart.addHeaderFooter( newHeader ); // we need this explicit call !
209 }
210 
212 {
213  header->setParent( &d->m_chart );
214  d->m_chart.addHeaderFooter( header ); // we need this explicit call !
215 }
216 
218 {
219  header->setParent( &d->m_chart );
220  d->m_chart.replaceHeaderFooter( header, oldHeader );
221 }
222 
224 {
225  d->m_chart.takeHeaderFooter( header );
226 }
227 
229 {
230  return d->m_chart.legend();
231 }
232 
234 {
235  return d->m_chart.legends();
236 }
237 
238 void Widget::addLegend( Position position )
239 {
240  Legend* legend = new Legend( diagram(), &d->m_chart );
241  legend->setPosition( position );
242  d->m_chart.addLegend( legend );
243 }
244 
246 {
247  legend->setDiagram( diagram() );
248  legend->setParent( &d->m_chart );
249  d->m_chart.addLegend( legend );
250 }
251 
252 void Widget::replaceLegend( Legend* legend, Legend* oldLegend )
253 {
254  legend->setDiagram( diagram() );
255  legend->setParent( &d->m_chart );
256  d->m_chart.replaceLegend( legend, oldLegend );
257 }
258 
259 void Widget::takeLegend( Legend* legend )
260 {
261  d->m_chart.takeLegend( legend );
262 }
263 
265 {
266  if ( coordinatePlane() == nullptr )
267  qDebug() << "diagram(): coordinatePlane() was NULL";
268 
269  return coordinatePlane()->diagram();
270 }
271 
273 {
274  return dynamic_cast<BarDiagram*>( diagram() );
275 }
277 {
278  return dynamic_cast<LineDiagram*>( diagram() );
279 }
281 {
282  return dynamic_cast<Plotter*>( diagram() );
283 }
285 {
286  return dynamic_cast<PieDiagram*>( diagram() );
287 }
289 {
290  return dynamic_cast<RingDiagram*>( diagram() );
291 }
293 {
294  return dynamic_cast<PolarDiagram*>( diagram() );
295 }
296 
298 {
299  return d->m_chart.coordinatePlane();
300 }
301 
302 static bool isCartesian( KChart::Widget::ChartType type )
303 {
304  return (type == KChart::Widget::Bar) || (type == KChart::Widget::Line);
305 }
306 
307 static bool isPolar( KChart::Widget::ChartType type )
308 {
309  return (type == KChart::Widget::Pie)
310  || (type == KChart::Widget::Ring)
311  || (type == KChart::Widget::Polar);
312 }
313 
314 void Widget::setType( ChartType chartType, SubType chartSubType )
315 {
316  AbstractDiagram* diag = nullptr;
317  const ChartType oldType = type();
318 
319  if ( chartType != oldType ) {
320  if ( chartType != NoType ) {
321  if ( isCartesian( chartType ) && ! isCartesian( oldType ) )
322  {
323  if ( coordinatePlane() == &d->m_polPlane ) {
324  d->m_chart.takeCoordinatePlane( &d->m_polPlane );
325  d->m_chart.addCoordinatePlane( &d->m_cartPlane );
326  } else {
327  d->m_chart.replaceCoordinatePlane( &d->m_cartPlane );
328  }
329  }
330  else if ( isPolar( chartType ) && ! isPolar( oldType ) )
331  {
332  if ( coordinatePlane() == &d->m_cartPlane ) {
333  d->m_chart.takeCoordinatePlane( &d->m_cartPlane );
334  d->m_chart.addCoordinatePlane( &d->m_polPlane );
335  } else {
336  d->m_chart.replaceCoordinatePlane( &d->m_polPlane );
337  }
338  }
339  }
340  switch ( chartType ) {
341  case Bar:
342  diag = new BarDiagram( &d->m_chart, &d->m_cartPlane );
343  break;
344  case Line:
345  diag = new LineDiagram( &d->m_chart, &d->m_cartPlane );
346  break;
347  case Plot:
348  diag = new Plotter( &d->m_chart, &d->m_cartPlane );
349  break;
350  case Pie:
351  diag = new PieDiagram( &d->m_chart, &d->m_polPlane );
352  break;
353  case Polar:
354  diag = new PolarDiagram( &d->m_chart, &d->m_polPlane );
355  break;
356  case Ring:
357  diag = new RingDiagram( &d->m_chart, &d->m_polPlane );
358  break;
359  case NoType:
360  break;
361  }
362  if ( diag != nullptr ) {
363  if ( isCartesian( oldType ) && isCartesian( chartType ) ) {
364  AbstractCartesianDiagram *oldDiag =
366  AbstractCartesianDiagram *newDiag =
368  const auto axes = oldDiag->axes();
369  for ( CartesianAxis* axis : axes ) {
370  oldDiag->takeAxis( axis );
371  newDiag->addAxis ( axis );
372  }
373  }
374 
375  const auto legends = d->m_chart.legends();
376  for ( Legend* l : legends ) {
377  l->setDiagram( diag );
378  }
379 
380  diag->setModel( &d->m_model );
381  coordinatePlane()->replaceDiagram( diag );
382 
383  //checkDatasetWidth( d->usedDatasetWidth );
384  }
385  //coordinatePlane()->setGridNeedsRecalculate();
386  }
387 
388  if ( chartType != NoType ) {
389  if ( chartType != oldType || chartSubType != subType() )
390  setSubType( chartSubType );
391  d->m_chart.resize( size() ); // triggering immediate update
392  }
393 }
394 
395 template< class DiagramType, class Subtype >
396 void setSubtype( AbstractDiagram *_dia, Subtype st)
397 {
398  if ( DiagramType *dia = qobject_cast< DiagramType * >( _dia ) ) {
399  dia->setType( st );
400  }
401 }
402 
404 {
405  // ### at least PieDiagram, PolarDiagram and RingDiagram are unhandled here
406 
407  AbstractDiagram *dia = diagram();
408  switch ( subType ) {
409  case Normal:
410  setSubtype< BarDiagram >( dia, BarDiagram::Normal );
411  setSubtype< LineDiagram >( dia, LineDiagram::Normal );
412  setSubtype< Plotter >( dia, Plotter::Normal );
413  break;
414  case Stacked:
415  setSubtype< BarDiagram >( dia, BarDiagram::Stacked );
416  setSubtype< LineDiagram >( dia, LineDiagram::Stacked );
417  // setSubtype< Plotter >( dia, Plotter::Stacked );
418  break;
419  case Percent:
420  setSubtype< BarDiagram >( dia, BarDiagram::Percent );
421  setSubtype< LineDiagram >( dia, LineDiagram::Percent );
422  setSubtype< Plotter >( dia, Plotter::Percent );
423  break;
424  case Rows:
425  setSubtype< BarDiagram >( dia, BarDiagram::Rows );
426  break;
427  default:
428  Q_ASSERT_X ( false, "Widget::setSubType", "Sub-type not supported!" );
429  break;
430  }
431 }
432 
433 Widget::ChartType Widget::type() const
434 {
435  // PENDING(christoph) save the type out-of-band:
436  AbstractDiagram * const dia = const_cast<Widget*>( this )->diagram();
437  if ( qobject_cast< BarDiagram* >( dia ) )
438  return Bar;
439  else if ( qobject_cast< LineDiagram* >( dia ) )
440  return Line;
441  else if ( qobject_cast< Plotter* >( dia ) )
442  return Plot;
443  else if ( qobject_cast< PieDiagram* >( dia ) )
444  return Pie;
445  else if ( qobject_cast< PolarDiagram* >( dia ) )
446  return Polar;
447  else if ( qobject_cast< RingDiagram* >( dia ) )
448  return Ring;
449  else
450  return NoType;
451 }
452 
454 {
455  // PENDING(christoph) save the type out-of-band:
456  Widget::SubType retVal = Normal;
457 
458  AbstractDiagram * const dia = const_cast<Widget*>( this )->diagram();
459  BarDiagram* barDia = qobject_cast< BarDiagram* >( dia );
460  LineDiagram* lineDia = qobject_cast< LineDiagram* >( dia );
461  Plotter* plotterDia = qobject_cast< Plotter* >( dia );
462 
463 //FIXME(khz): Add the impl for these chart types - or remove them from here:
464 // PieDiagram* pieDia = qobject_cast< PieDiagram* >( diagram() );
465 // PolarDiagram* polarDia = qobject_cast< PolarDiagram* >( diagram() );
466 // RingDiagram* ringDia = qobject_cast< RingDiagram* >( diagram() );
467 
468 #define TEST_SUB_TYPE(DIAGRAM, INTERNALSUBTYPE, SUBTYPE) \
469 { \
470  if ( DIAGRAM && DIAGRAM->type() == INTERNALSUBTYPE ) \
471  retVal = SUBTYPE; \
472 }
473  const Widget::ChartType mainType = type();
474  switch ( mainType )
475  {
476  case Bar:
477  TEST_SUB_TYPE( barDia, BarDiagram::Normal, Normal );
478  TEST_SUB_TYPE( barDia, BarDiagram::Stacked, Stacked );
479  TEST_SUB_TYPE( barDia, BarDiagram::Percent, Percent );
480  TEST_SUB_TYPE( barDia, BarDiagram::Rows, Rows );
481  break;
482  case Line:
483  TEST_SUB_TYPE( lineDia, LineDiagram::Normal, Normal );
484  TEST_SUB_TYPE( lineDia, LineDiagram::Stacked, Stacked );
485  TEST_SUB_TYPE( lineDia, LineDiagram::Percent, Percent );
486  break;
487  case Plot:
488  TEST_SUB_TYPE( plotterDia, Plotter::Normal, Normal );
489  TEST_SUB_TYPE( plotterDia, Plotter::Percent, Percent );
490  break;
491  case Pie:
492  // no impl. yet
493  break;
494  case Polar:
495  // no impl. yet
496  break;
497  case Ring:
498  // no impl. yet
499  break;
500  default:
501  Q_ASSERT_X ( false,
502  "Widget::subType", "Chart type not supported!" );
503  break;
504  }
505  return retVal;
506 }
507 
508 
509 bool Widget::checkDatasetWidth( int width )
510 {
511  if ( width == diagram()->datasetDimension() )
512  {
513  d->usedDatasetWidth = width;
514  return true;
515  }
516  qDebug() << "The current diagram type doesn't support this data dimension.";
517  return false;
518 /* if ( d->usedDatasetWidth == width || d->usedDatasetWidth == 0 ) {
519  d->usedDatasetWidth = width;
520  diagram()->setDatasetDimension( width );
521  return true;
522  }
523  qDebug() << "It's impossible to mix up the different setDataset() methods on the same widget.";
524  return false;*/
525 }
526 
527 void Widget::justifyModelSize( int rows, int columns )
528 {
529  QAbstractItemModel & model = d->m_model;
530  const int currentRows = model.rowCount();
531  const int currentCols = model.columnCount();
532 
533  if ( currentCols < columns )
534  if ( ! model.insertColumns( currentCols, columns - currentCols ))
535  qDebug() << "justifyModelSize: could not increase model size.";
536  if ( currentRows < rows )
537  if ( ! model.insertRows( currentRows, rows - currentRows ))
538  qDebug() << "justifyModelSize: could not increase model size.";
539 
540  Q_ASSERT( model.rowCount() >= rows );
541  Q_ASSERT( model.columnCount() >= columns );
542 }
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const const override
Class only listed here to document inheritance of some KChart classes.
virtual int rowCount(const QModelIndex &parent) const const =0
AbstractCoordinatePlane * coordinatePlane()
Returns a pointer to the current coordinate plane.
PieDiagram defines a common pie diagram.
void setDataset(int column, const QVector< qreal > &data, const QString &title=QString())
Sets the data in the given column using a QVector of qreal for the Y values.
int globalLeadingRight() const
Returns the right leading (border).
RingDiagram * ringDiagram()
If the current diagram is a RingDiagram, it is returned; otherwise 0 is returned. ...
void addHeaderFooter(const QString &text, HeaderFooter::HeaderFooterType type, Position position)
Adds a new header/footer with the given text to the position.
Legend defines the interface for the legend drawing class.
Definition: KChartLegend.h:41
AbstractDiagram defines the interface for diagram classes.
int globalLeadingTop() const
Returns the top leading (border).
Plotter defines a diagram type plotting two-dimensional data.
Definition: KChartPlotter.h:24
Plotter * plotter()
If the current diagram is a LineDiagram, it is returned; otherwise 0 is returned. ...
The KChart widget for usage without Interwiev.
Definition: KChartWidget.h:46
Defines a position, using compass terminology.
void setGlobalLeading(int left, int top, int right, int bottom)
Sets all global leadings (borders).
SubType subType() const
Returns the sub-type of the chart.
void setDataCell(int row, int column, qreal data)
Sets the Y value data for a given cell.
virtual bool insertRows(int row, int count, const QModelIndex &parent)
void setPosition(Position position)
Specify the position of a non-floating legend.
void setParent(QWidget *parent)
The class for cartesian axes.
virtual bool setData(const QModelIndex &index, const QVariant &value, int role) override
void setDiagram(KChart::AbstractDiagram *newDiagram)
A convenience method doing the same as replaceDiagram( newDiagram, 0 );.
AbstractDiagram * diagram()
Returns a pointer to the current diagram.
void setGlobalLeadingTop(int leading)
Sets the top leading (border).
LineDiagram * lineDiagram()
If the current diagram is a LineDiagram, it is returned; otherwise 0 is returned. ...
void addLegend(Position position)
Adds an empty legend on the given position.
~Widget() override
Destructor.
int width() const const
QSize size() const const
void setGlobalLeadingRight(int leading)
Sets the right leading (border).
Base class for diagrams based on a cartesian coordianate system.
DisplayRole
bool isEmpty() const const
Base class common for all coordinate planes, CartesianCoordinatePlane, PolarCoordinatePlane, TernaryCoordinatePlane.
BarDiagram * barDiagram()
If the current diagram is a BarDiagram, it is returned; otherwise 0 is returned.
RingDiagram defines a common ring diagram.
int globalLeadingBottom() const
Returns the bottom leading (border).
ChartType type() const
Returns the type of the chart.
HeaderFooter * firstHeaderFooter()
Returns the first of all headers.
SubType
Sub type values, matching the values defines for the respective Diagram classes.
Definition: KChartWidget.h:200
virtual KChart::CartesianAxisList axes() const
Legend * legend()
Returns the first of all legends.
int globalLeadingLeft() const
Returns the left leading (border).
void setSubType(KChart::Widget::SubType subType)
Sets the type of the chart without changing the main type.
virtual bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role) override
Widget(QWidget *parent=nullptr)
Standard Qt-style Constructor.
virtual void replaceDiagram(AbstractDiagram *diagram, AbstractDiagram *oldDiagram=nullptr)
Replaces the old diagram, or appends the diagram, it there is none yet.
void takeHeaderFooter(HeaderFooter *header)
Remove the header (or footer, resp.) from the widget, without deleting it.
virtual bool insertColumns(int column, int count, const QModelIndex &parent)
PolarDiagram * polarDiagram()
If the current diagram is a PolarDiagram, it is returned; otherwise 0 is returned.
void resetData()
Resets all data.
virtual void takeAxis(CartesianAxis *axis)
Removes the axis from the diagram, without deleting it.
BarDiagram defines a common bar diagram.
virtual int columnCount(const QModelIndex &parent) const const =0
A header or footer displaying text above or below charts.
void setType(KChart::Widget::ChartType chartType, KChart::Widget::SubType subType=Normal)
Sets the type of the chart.
void setGlobalLeadingBottom(int leading)
Sets the bottom leading (border).
void setModel(QAbstractItemModel *model) override
Associate a model with the diagram.
QList< Legend * > allLegends()
Returns a list with all legends.
Horizontal
LineDiagram defines a common line diagram.
PieDiagram * pieDiagram()
If the current diagram is a Plotter, it is returned; otherwise 0 is returned.
void replaceHeaderFooter(HeaderFooter *header, HeaderFooter *oldHeader=nullptr)
Replaces the old header (or footer, resp.), or appends the new header or footer, it there is none yet...
Global namespace.
void setGlobalLeadingLeft(int leading)
Sets the left leading (border).
T qobject_cast(QObject *object)
int size() const const
QList< HeaderFooter * > allHeadersFooters()
Returns a list with all headers.
PolarDiagram defines a common polar diagram.
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Thu Jan 27 2022 22:33:23 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.