61
61
bool firstColumnIsLabel;
62
62
Qt::Orientation dataDirection;
63
63
int dataDimensions;
64
QMap<int, int> dataMap;
65
QString categoryDataRegion;
67
QVector< CellRegion > dataSetRegions;
66
69
QList<DataSet*> dataSets;
67
70
QList<DataSet*> removedDataSets;
69
72
QVector<QRect> selection;
74
bool automaticDataSetCreation;
73
77
ChartProxyModel::Private::Private()
75
79
firstRowIsLabel = false;
76
80
firstColumnIsLabel = false;
77
81
dataDimensions = 1;
82
automaticDataSetCreation = true;
79
dataDirection = Qt::Vertical; // Apparently the default is columns.
84
// Determines what orientation the data points in a data series
85
// have when multiple data sets are created from one source
86
// region. For example, vertical means that each column in the source
87
// region is assigned to one data series.
88
// Default to Qt::Vertical, as that's what OOo does also.
89
dataDirection = Qt::Vertical;
82
92
ChartProxyModel::Private::~Private()
113
void ChartProxyModel::setAutomaticDataSetCreation( bool enable )
115
d->automaticDataSetCreation = enable;
118
bool ChartProxyModel::automaticDataSetCreation() const
120
return d->automaticDataSetCreation;
123
#if QT_VERSION < 0x040600
124
void ChartProxyModel::beginResetModel()
128
void ChartProxyModel::endResetModel()
103
134
void ChartProxyModel::rebuildDataMap()
136
if ( !d->automaticDataSetCreation )
139
invalidateDataSets();
140
d->dataSets = createDataSetsFromRegion( d->removedDataSets );
143
QList<DataSet*> ChartProxyModel::createDataSetsFromRegion( QList<DataSet*> dataSetsToRecycle )
145
QList<DataSet*> createdDataSets;
105
146
QVector<QRect> dataRegions;
107
148
if ( d->selection.isEmpty() ) {
155
196
QMapIterator<int, QVector<QRect> > j( sortedRows );
157
CellRegion categoryDataRegion;
159
if ( d->firstRowIsLabel && j.hasNext() ) {
199
if ( ! categoryDataRegion().isEmpty() ) {
200
category = CellRegion( categoryDataRegion() );
201
} else if ( d->firstRowIsLabel && j.hasNext() ) {
162
categoryDataRegion = CellRegion( j.value() );
204
category = CellRegion( j.value() );
163
205
if ( d->firstColumnIsLabel )
164
categoryDataRegion.subtract( categoryDataRegion.pointAtIndex( 0 ) );
206
category.subtract( category.pointAtIndex( 0 ) );
167
209
while ( j.hasNext() ) {
170
212
DataSet *dataSet;
171
if ( createdDataSetCount >= d->dataSets.size() ) {
172
if ( !d->removedDataSets.isEmpty() )
173
dataSet = d->removedDataSets.takeLast();
175
dataSet = new DataSet( this );
177
d->dataSets.append( dataSet );
213
if ( !dataSetsToRecycle.isEmpty() )
214
dataSet = dataSetsToRecycle.takeLast();
180
dataSet = d->dataSets[createdDataSetCount];
216
dataSet = new DataSet( this );
217
createdDataSets.append( dataSet );
181
219
dataSet->blockSignals( true );
183
221
dataSet->setNumber( createdDataSetCount );
184
dataSet->setColor( defaultDataSetColor( createdDataSetCount ) );
186
CellRegion yDataRegion( j.value() );
222
//dataSet->setColor( defaultDataSetColor( createdDataSetCount ) );
187
224
CellRegion labelDataRegion;
226
CellRegion xDataRegion;
227
// In case of > 1 data dimensions, x data appears before y data
228
if ( d->dataDimensions > 1 )
229
xDataRegion = CellRegion( j.value() );
189
231
//qDebug() << "Creating data set with region" << j.value();
190
232
if ( d->firstColumnIsLabel ) {
191
QPoint labelDataPoint = yDataRegion.pointAtIndex( 0 );
233
CellRegion tmpRegion = CellRegion( j.value() );
234
QPoint labelDataPoint = tmpRegion.pointAtIndex( 0 );
192
235
labelDataRegion = CellRegion( labelDataPoint );
194
yDataRegion.subtract( labelDataPoint );
238
if ( d->dataDimensions > 1 && j.hasNext() )
241
CellRegion yDataRegion( j.value() );
243
if ( d->firstColumnIsLabel ) {
244
xDataRegion.subtract( xDataRegion.pointAtIndex( 0 ) );
245
yDataRegion.subtract( yDataRegion.pointAtIndex( 0 ) );
248
dataSet->setXDataRegion( xDataRegion );
197
249
dataSet->setYDataRegion( yDataRegion );
198
dataSet->setCategoryDataRegion( categoryDataRegion );
250
dataSet->setCategoryDataRegion( category );
199
251
dataSet->setLabelDataRegion( labelDataRegion );
200
252
createdDataSetCount++;
201
253
dataSet->blockSignals( false );
245
297
QMapIterator<int, QVector<QRect> > j( sortedColumns );
247
CellRegion categoryDataRegion;
249
if ( d->firstColumnIsLabel && j.hasNext() ) {
300
if ( ! categoryDataRegion().isEmpty() ) {
301
category = CellRegion( categoryDataRegion() );
302
} else if ( d->firstColumnIsLabel && j.hasNext() ) {
252
categoryDataRegion = CellRegion( j.value() );
305
category = CellRegion( j.value() );
253
306
if ( d->firstRowIsLabel )
254
categoryDataRegion.subtract( categoryDataRegion.pointAtIndex( 0 ) );
307
category.subtract( category.pointAtIndex( 0 ) );
257
310
while ( j.hasNext() ) {
260
313
DataSet *dataSet;
261
if ( createdDataSetCount >= d->dataSets.size() ) {
262
if ( !d->removedDataSets.isEmpty() )
263
dataSet = d->removedDataSets.takeLast();
265
dataSet = new DataSet( this );
266
d->dataSets.append( dataSet );
314
if ( !dataSetsToRecycle.isEmpty() )
315
dataSet = dataSetsToRecycle.takeLast();
269
dataSet = d->dataSets[createdDataSetCount];
317
dataSet = new DataSet( this );
318
createdDataSets.append( dataSet );
270
320
dataSet->blockSignals( true );
272
322
dataSet->setNumber( createdDataSetCount );
273
dataSet->setColor( defaultDataSetColor( createdDataSetCount ) );
275
CellRegion yDataRegion( j.value() );
323
//dataSet->setColor( defaultDataSetColor( createdDataSetCount ) );
276
325
CellRegion labelDataRegion;
278
//qDebug() << "Creating data set with region " << j.value();
327
CellRegion xDataRegion;
328
// In case of > 1 data dimensions, x data appears before y data
329
if ( d->dataDimensions > 1 )
330
xDataRegion = CellRegion( j.value() );
332
//qDebug() << "Creating data set with region" << j.value();
279
333
if ( d->firstRowIsLabel ) {
280
QPoint labelDataPoint = yDataRegion.pointAtIndex( 0 );
334
CellRegion tmpRegion = CellRegion( j.value() );
335
QPoint labelDataPoint = tmpRegion.pointAtIndex( 0 );
281
336
labelDataRegion = CellRegion( labelDataPoint );
283
yDataRegion.subtract( labelDataPoint );
339
if ( d->dataDimensions > 1 && j.hasNext() )
342
CellRegion yDataRegion( j.value() );
344
if ( d->firstRowIsLabel ) {
345
xDataRegion.subtract( xDataRegion.pointAtIndex( 0 ) );
346
yDataRegion.subtract( yDataRegion.pointAtIndex( 0 ) );
349
dataSet->setXDataRegion( xDataRegion );
286
350
dataSet->setYDataRegion( yDataRegion );
287
351
dataSet->setLabelDataRegion( labelDataRegion );
288
dataSet->setCategoryDataRegion( categoryDataRegion );
352
dataSet->setCategoryDataRegion( category );
289
353
createdDataSetCount++;
290
354
dataSet->blockSignals( false );
294
while ( d->dataSets.size() > createdDataSetCount ) {
295
DataSet *dataSet = d->dataSets.takeLast();
297
// TODO: Restore attached axis when dataset is re-inserted?
298
if ( dataSet->attachedAxis() )
299
dataSet->attachedAxis()->detachDataSet( dataSet, true );
301
d->removedDataSets.append( dataSet );
358
return createdDataSets;
308
364
if ( this->sourceModel() == sourceModel )
311
369
if ( this->sourceModel() ) {
370
disconnect( this->sourceModel(), SIGNAL( modelReset() ),
371
this, SLOT( slotModelReset() ) );
312
372
disconnect( this->sourceModel(), SIGNAL( dataChanged( const QModelIndex&, const QModelIndex& ) ),
313
373
this, SLOT( dataChanged( const QModelIndex&, const QModelIndex& ) ) );
314
374
disconnect( this->sourceModel(), SIGNAL( rowsInserted( const QModelIndex&, int, int ) ),
324
384
if ( sourceModel ) {
385
connect( sourceModel, SIGNAL( modelReset() ),
386
this, SLOT( slotModelReset() ) );
325
387
connect( sourceModel, SIGNAL( dataChanged( const QModelIndex&, const QModelIndex& ) ),
326
388
this, SLOT( dataChanged( const QModelIndex&, const QModelIndex& ) ) );
327
389
connect( sourceModel, SIGNAL( rowsInserted( const QModelIndex&, int, int ) ),
337
399
QAbstractProxyModel::setSourceModel( sourceModel );
339
401
rebuildDataMap();
341
// Update the entire data set
345
void ChartProxyModel::setSourceModel( QAbstractItemModel *sourceModel,
405
void ChartProxyModel::setSourceModel( QAbstractItemModel *model,
346
406
const QVector<QRect> &selection )
348
// FIXME: What if we already have a source model? Don't we have
349
// to disconnect that one before connecting the new one?
351
connect( sourceModel, SIGNAL( dataChanged( const QModelIndex&, const QModelIndex& ) ),
352
this, SLOT( dataChanged( const QModelIndex&, const QModelIndex& ) ) );
354
408
d->selection = selection;
356
QAbstractProxyModel::setSourceModel( sourceModel );
360
// Update the entire data set
409
setSourceModel( model );
364
412
void ChartProxyModel::setSelection( const QVector<QRect> &selection )
370
418
void ChartProxyModel::saveOdf( KoShapeSavingContext &context ) const
372
KoXmlWriter &bodyWriter = context.xmlWriter();
373
KoGenStyles &mainStyles = context.mainStyles();
375
foreach ( DataSet *dataSet, d->dataSets ) {
376
bodyWriter.startElement( "chart:series" );
378
KoGenStyle style( KoGenStyle::StyleGraphicAuto, "chart" );
380
if ( dataSet->chartType() != LastChartType )
381
style.addProperty( "chart:family", ODF_CHARTTYPES[ dataSet->chartType() ] );
383
KoOdfGraphicStyles::saveOdfFillStyle( style, mainStyles, dataSet->brush() );
384
KoOdfGraphicStyles::saveOdfStrokeStyle( style, mainStyles, dataSet->pen() );
386
// TODO: Save external data sources also
387
const QString prefix( "local-table." );
390
bodyWriter.addAttribute( "chart:values-cell-range-address", prefix + dataSet->yDataRegionString() );
391
bodyWriter.addAttribute( "chart:label-cell-address", prefix + dataSet->labelDataRegionString() );
393
const QString styleName = mainStyles.lookup( style, "ch", KoGenStyles::ForceNumbering );
394
bodyWriter.addAttribute( "chart:style-name", styleName );
396
bodyWriter.endElement(); // chart:series
420
foreach ( DataSet *dataSet, d->dataSets )
421
dataSet->saveOdf( context );
400
424
// This loads the properties of the datasets (chart:series).
402
426
bool ChartProxyModel::loadOdf( const KoXmlElement &element,
403
427
KoShapeLoadingContext &context )
405
430
KoStyleStack &styleStack = context.odfLoadingContext().styleStack();
406
431
styleStack.save();
433
invalidateDataSets();
435
QList<DataSet*> createdDataSets;
436
int loadedDataSetCount = 0;
438
// A cell range for all data is optional.
439
// If it is specified, use createDataSetsFromRegion() to automatically
440
// turn this data region into consecutive data series.
441
// If cell ranges are in addition specified for one or more of these
442
// data series, they'll be overwritten by these values.
443
if ( element.hasAttributeNS( KoXmlNS::table, "cell-range-address" ) )
445
QString cellRangeAddress = element.attributeNS( KoXmlNS::table, "cell-range-address" );
446
setSelection( CellRegion::stringToRegion( cellRangeAddress ) );
447
createdDataSets = createDataSetsFromRegion( d->removedDataSets );
411
451
forEachElement ( n, element ) {
415
455
if ( n.localName() == "series" ) {
416
DataSet *dataSet = new DataSet( this );
417
dataSet->setNumber( d->dataSets.size() );
457
if ( loadedDataSetCount < createdDataSets.size() ) {
458
dataSet = createdDataSets[loadedDataSetCount];
460
dataSet = new DataSet( this );
461
dataSet->setNumber( d->dataSets.size() );
418
463
d->dataSets.append( dataSet );
420
if ( n.hasAttributeNS( KoXmlNS::chart, "style-name" ) ) {
421
//qDebug() << "HAS style-name:" << n.attributeNS( KoXmlNS::chart, "style-name" );
423
context.odfLoadingContext().fillStyleStack( n, KoXmlNS::chart, "style-name", "chart" );
425
//styleStack.setTypeProperties( "chart" );
427
// FIXME: Load Pie explode factors
428
//if ( styleStack.hasProperty( KoXmlNS::chart, "pie-offset" ) )
429
// setPieExplodeFactor( dataSet, styleStack.property( KoXmlNS::chart, "pie-offset" ).toInt() );
431
styleStack.setTypeProperties( "graphic" );
433
if ( styleStack.hasProperty( KoXmlNS::draw, "stroke" ) ) {
434
qDebug() << "HAS stroke";
435
QString stroke = styleStack.property( KoXmlNS::draw, "stroke" );
436
if( stroke == "solid" || stroke == "dash" ) {
437
QPen pen = KoOdfGraphicStyles::loadOdfStrokeStyle( styleStack, stroke, context.odfLoadingContext().stylesReader() );
438
dataSet->setPen( pen );
442
if ( styleStack.hasProperty( KoXmlNS::draw, "fill" ) ) {
443
//qDebug() << "HAS fill";
444
QString fill = styleStack.property( KoXmlNS::draw, "fill" );
446
if ( fill == "solid" || fill == "hatch" ) {
447
brush = KoOdfGraphicStyles::loadOdfFillStyle( styleStack, fill, context.odfLoadingContext().stylesReader() );
448
} else if ( fill == "gradient" ) {
449
brush = KoOdfGraphicStyles::loadOdfGradientStyle( styleStack, context.odfLoadingContext().stylesReader(), QSizeF( 5.0, 60.0 ) );
450
} else if ( fill == "bitmap" )
451
brush = KoOdfGraphicStyles::loadOdfPatternStyle( styleStack, context.odfLoadingContext(), QSizeF( 5.0, 60.0 ) );
452
dataSet->setBrush( brush );
454
dataSet->setColor( defaultDataSetColor( dataSet->number() ) );
458
if ( n.hasAttributeNS( KoXmlNS::chart, "values-cell-range-address" ) ) {
459
const QString region = n.attributeNS( KoXmlNS::chart, "values-cell-range-address", QString() );
460
dataSet->setYDataRegionString( region );
462
if ( n.hasAttributeNS( KoXmlNS::chart, "label-cell-address" ) ) {
463
const QString region = n.attributeNS( KoXmlNS::chart, "label-cell-address", QString() );
464
dataSet->setLabelDataRegionString( region );
468
forEachElement ( m, n ) {
469
if ( m.namespaceURI() != KoXmlNS::chart )
471
// FIXME: Load data points
464
dataSet->loadOdf( n, context );
466
loadedDataSetCount++;
474
468
qWarning() << "ChartProxyModel::loadOdf(): Unknown tag name \"" << n.localName() << "\"";
481
475
styleStack.restore();
513
507
QRect dataChangedRect = QRect( topLeftPoint,
514
508
QSize( bottomRightPoint.x() - topLeftPoint.x() + 1,
515
509
bottomRightPoint.y() - topLeftPoint.y() + 1 ) );
517
511
foreach ( DataSet *dataSet, d->dataSets ) {
518
bool intersects = false;
520
foreach ( const QRect &rect, dataSet->yDataRegion().rects() ) {
521
if ( rect.intersects( dataChangedRect ) ) {
522
changedRect |= rect.intersected( dataChangedRect );
530
dataSet->yDataChanged( changedRect );
512
if ( dataSet->xDataRegion().intersects( dataChangedRect ) )
513
dataSet->xDataChanged( dataSet->xDataRegion().intersected( dataChangedRect ).boundingRect() );
515
if ( dataSet->yDataRegion().intersects( dataChangedRect ) )
516
dataSet->yDataChanged( dataSet->yDataRegion().intersected( dataChangedRect ).boundingRect() );
518
if ( dataSet->categoryDataRegion().intersects( dataChangedRect ) )
519
dataSet->categoryDataChanged( dataSet->categoryDataRegion().intersected( dataChangedRect ).boundingRect() );
521
if ( dataSet->labelDataRegion().intersects( dataChangedRect ) )
522
dataSet->labelDataChanged( dataSet->labelDataRegion().intersected( dataChangedRect ).boundingRect() );
524
if ( dataSet->customDataRegion().intersects( dataChangedRect ) )
525
dataSet->customDataChanged( dataSet->customDataRegion().intersected( dataChangedRect ).boundingRect() );
534
528
emit dataChanged();
618
612
QModelIndex ChartProxyModel::mapFromSource( const QModelIndex &sourceIndex ) const
623
if ( d->dataDirection == Qt::Horizontal ) {
624
row = sourceIndex.row();
625
column = sourceIndex.column();
627
if ( d->firstRowIsLabel )
629
if ( d->firstColumnIsLabel )
632
// Find the first occurrence of row in the map
633
for ( int i = 0; i < d->dataMap.size(); i++ ) {
634
if ( d->dataMap[i] == row ) {
641
// d->dataDirection == Qt::Vertical here
643
row = sourceIndex.column();
644
column = sourceIndex.row();
646
if ( d->firstRowIsLabel )
648
if ( d->firstColumnIsLabel )
651
// Find the first occurrence of column in the map
652
for ( int i = 0; i < d->dataMap.size(); i++ ) {
653
if ( d->dataMap[i] == column ) {
660
return sourceModel()->index( row, column );
614
Q_UNUSED( sourceIndex );
615
return QModelIndex();
663
618
QModelIndex ChartProxyModel::mapToSource( const QModelIndex &proxyIndex ) const
668
if ( d->dataDirection == Qt::Horizontal ) {
669
row = d->dataMap[ proxyIndex.row() ];
670
column = proxyIndex.column();
673
row = proxyIndex.column();
674
column = d->dataMap[ proxyIndex.row() ];
677
if ( d->firstRowIsLabel )
679
if ( d->firstColumnIsLabel )
682
return sourceModel()->index( row, column );
620
Q_UNUSED( proxyIndex );
621
return QModelIndex();
803
745
return d->dataDirection;
748
void ChartProxyModel::invalidateDataSets()
750
foreach ( DataSet *dataSet, d->dataSets )
752
if ( dataSet->attachedAxis() ) {
753
// Remove data sets 'silently'. Once the last data set
754
// has been detached from an axis, the axis will delete
755
// all models and diagrams associated with it, thus we
756
// do not need to propagate these events to any models.
757
dataSet->attachedAxis()->detachDataSet( dataSet, true );
761
d->removedDataSets = d->dataSets;
806
765
void ChartProxyModel::setDataDirection( Qt::Orientation orientation )
808
767
if ( d->dataDirection == orientation )
811
771
d->dataDirection = orientation;
813
773
if ( !sourceModel() )
816
776
rebuildDataMap();
820
780
void ChartProxyModel::setDataDimensions( int dimensions )