829 lines
29 KiB
C++
829 lines
29 KiB
C++
/****************************************************************************
|
|
** Copyright (C) 2001-2011 Klaralvdalens Datakonsult AB. All rights reserved.
|
|
**
|
|
** This file is part of the KD Chart library.
|
|
**
|
|
** Licensees holding valid commercial KD Chart licenses may use this file in
|
|
** accordance with the KD Chart Commercial License Agreement provided with
|
|
** the Software.
|
|
**
|
|
**
|
|
** This file may be distributed and/or modified under the terms of the
|
|
** GNU General Public License version 2 and version 3 as published by the
|
|
** Free Software Foundation and appearing in the file LICENSE.GPL.txt included.
|
|
**
|
|
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
|
|
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
|
|
**
|
|
** Contact info@kdab.com if any conditions of this licensing are not
|
|
** clear to you.
|
|
**
|
|
**********************************************************************/
|
|
|
|
#include "KDChartCartesianDiagramDataCompressor_p.h"
|
|
|
|
#include <QtDebug>
|
|
#include <QAbstractItemModel>
|
|
|
|
#include "KDChartAbstractCartesianDiagram.h"
|
|
|
|
#include <KDABLibFakes>
|
|
|
|
using namespace KDChart;
|
|
using namespace std;
|
|
|
|
CartesianDiagramDataCompressor::CartesianDiagramDataCompressor( QObject* parent )
|
|
: QObject( parent )
|
|
, m_mode( Precise )
|
|
, m_xResolution( 0 )
|
|
, m_yResolution( 0 )
|
|
, m_sampleStep( 0 )
|
|
, m_datasetDimension( 1 )
|
|
{
|
|
calculateSampleStepWidth();
|
|
}
|
|
|
|
QModelIndexList CartesianDiagramDataCompressor::indexesAt( const CachePosition& position ) const
|
|
{
|
|
if ( isValidCachePosition( position ) ) {
|
|
CachePosition posPrev( position );
|
|
if( m_datasetDimension == 2 ){
|
|
if(posPrev.second)
|
|
--posPrev.second;
|
|
}else{
|
|
if(posPrev.first)
|
|
--posPrev.first;
|
|
}
|
|
const QModelIndexList indPrev = mapToModel( posPrev );
|
|
const QModelIndexList indCur = mapToModel( position );
|
|
|
|
QModelIndexList indexes;
|
|
if( m_datasetDimension == 2 )
|
|
{
|
|
const int iStart = (indPrev.empty() || indPrev==indCur) ? indCur.first().column()
|
|
: indPrev.first().column() + 1;
|
|
const int iEnd = indCur.last().column();
|
|
for( int i=iStart; i<=iEnd; ++i){
|
|
indexes << m_model->index( position.first, i, m_rootIndex );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const int iStart = (indPrev.empty() || indPrev==indCur) ? indCur.first().row()
|
|
: indPrev.first().row() + 1;
|
|
const int iEnd = (indCur.isEmpty()) ? iStart : indCur.first().row();
|
|
//qDebug()<<iStart<<iEnd << iEnd-iStart;
|
|
for( int i=iStart; i<=iEnd; ++i){
|
|
indexes << m_model->index( i, position.second, m_rootIndex );
|
|
}
|
|
}
|
|
return indexes;
|
|
} else {
|
|
return QModelIndexList();
|
|
}
|
|
}
|
|
|
|
|
|
CartesianDiagramDataCompressor::DataValueAttributesList CartesianDiagramDataCompressor::aggregatedAttrs(
|
|
AbstractDiagram * diagram,
|
|
const QModelIndex & index,
|
|
const CachePosition& position ) const
|
|
{
|
|
// return cached attrs, if any
|
|
DataValueAttributesCache::const_iterator i = m_dataValueAttributesCache.constFind(position);
|
|
if( i != m_dataValueAttributesCache.constEnd() )
|
|
return i.value();
|
|
// retrieve attrs from all cells between the prev. cell and the current one
|
|
CartesianDiagramDataCompressor::DataValueAttributesList allAttrs;
|
|
const QModelIndexList indexes( indexesAt( position ) );
|
|
KDAB_FOREACH( QModelIndex idx, indexes ) {
|
|
DataValueAttributes attrs( diagram->dataValueAttributes( idx ) );
|
|
if( attrs.isVisible() ){
|
|
// make sure no duplicate attrs are stored
|
|
bool isDuplicate = false;
|
|
CartesianDiagramDataCompressor::DataValueAttributesList::const_iterator i = allAttrs.constBegin();
|
|
while (i != allAttrs.constEnd()) {
|
|
if( i.value() == attrs ){
|
|
isDuplicate = true;
|
|
break;
|
|
}
|
|
++i;
|
|
}
|
|
if( !isDuplicate ){
|
|
//qDebug()<<idx.row();
|
|
allAttrs[idx] = attrs;
|
|
}
|
|
}
|
|
}
|
|
// if none of the attrs had the visible flag set
|
|
// we just take the one set for the index to not return an empty list
|
|
if( allAttrs.empty() ){
|
|
allAttrs[index] = diagram->dataValueAttributes( index );
|
|
}
|
|
// cache the attrs
|
|
m_dataValueAttributesCache[position] = allAttrs;
|
|
return allAttrs;
|
|
}
|
|
|
|
|
|
void CartesianDiagramDataCompressor::slotRowsAboutToBeInserted( const QModelIndex& parent, int start, int end )
|
|
{
|
|
if ( parent != m_rootIndex )
|
|
return;
|
|
Q_ASSERT( start <= end );
|
|
|
|
CachePosition startPos = mapToCache( start, 0 );
|
|
CachePosition endPos = mapToCache( end, 0 );
|
|
|
|
static const CachePosition NullPosition( -1, -1 );
|
|
if( startPos == NullPosition )
|
|
{
|
|
rebuildCache();
|
|
startPos = mapToCache( start, 0 );
|
|
endPos = mapToCache( end, 0 );
|
|
// The start position still isn't valid,
|
|
// means that no resolution was set yet or we're about to add the first rows
|
|
if( startPos == NullPosition ) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
start = startPos.first;
|
|
end = endPos.first;
|
|
|
|
for( int i = 0; i < m_data.size(); ++i )
|
|
{
|
|
Q_ASSERT( start >= 0 && start <= m_data[ i ].size() );
|
|
m_data[ i ].insert( start, end - start + 1, DataPoint() );
|
|
}
|
|
}
|
|
|
|
void CartesianDiagramDataCompressor::slotRowsInserted( const QModelIndex& parent, int start, int end )
|
|
{
|
|
if ( parent != m_rootIndex )
|
|
return;
|
|
Q_ASSERT( start <= end );
|
|
|
|
CachePosition startPos = mapToCache( start, 0 );
|
|
CachePosition endPos = mapToCache( end, 0 );
|
|
|
|
static const CachePosition NullPosition( -1, -1 );
|
|
if( startPos == NullPosition )
|
|
{
|
|
// Rebuild the cache at this point if we have added the first rows
|
|
rebuildCache();
|
|
startPos = mapToCache( start, 0 );
|
|
endPos = mapToCache( end, 0 );
|
|
// The start position still isn't valid,
|
|
// means that no resolution was set yet
|
|
if( startPos == NullPosition ) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
start = startPos.first;
|
|
end = endPos.first;
|
|
|
|
for( int i = 0; i < m_data.size(); ++i )
|
|
{
|
|
for( int j = start; j < m_data[i].size(); ++j ) {
|
|
retrieveModelData( CachePosition( j, i ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
void CartesianDiagramDataCompressor::slotColumnsAboutToBeInserted( const QModelIndex& parent, int start, int end )
|
|
{
|
|
if ( parent != m_rootIndex )
|
|
return;
|
|
Q_ASSERT( start <= end );
|
|
|
|
CachePosition startPos = mapToCache( 0, start );
|
|
CachePosition endPos = mapToCache( 0, end );
|
|
|
|
static const CachePosition NullPosition( -1, -1 );
|
|
if( startPos == NullPosition )
|
|
{
|
|
rebuildCache();
|
|
startPos = mapToCache( 0, start );
|
|
endPos = mapToCache( 0, end );
|
|
// The start position still isn't valid,
|
|
// means that no resolution was set yet or we're about to add the first columns
|
|
if( startPos == NullPosition ) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
start = startPos.second;
|
|
end = endPos.second;
|
|
|
|
const int rowCount = qMin( m_model ? m_model->rowCount( m_rootIndex ) : 0, m_xResolution );
|
|
Q_ASSERT( start >= 0 && start <= m_data.size() );
|
|
m_data.insert( start, end - start + 1, QVector< DataPoint >( rowCount ) );
|
|
}
|
|
|
|
void CartesianDiagramDataCompressor::slotColumnsInserted( const QModelIndex& parent, int start, int end )
|
|
{
|
|
if ( parent != m_rootIndex )
|
|
return;
|
|
Q_ASSERT( start <= end );
|
|
|
|
CachePosition startPos = mapToCache( 0, start );
|
|
CachePosition endPos = mapToCache( 0, end );
|
|
|
|
static const CachePosition NullPosition( -1, -1 );
|
|
if( startPos == NullPosition )
|
|
{
|
|
// Rebuild the cache at this point if we have added the first columns
|
|
rebuildCache();
|
|
startPos = mapToCache( 0, start );
|
|
endPos = mapToCache( 0, end );
|
|
// The start position still isn't valid,
|
|
// means that no resolution was set yet
|
|
if( startPos == NullPosition ) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
start = startPos.second;
|
|
end = endPos.second;
|
|
|
|
for( int i = start; i < m_data.size(); ++i )
|
|
{
|
|
for(int j = 0; j < m_data[i].size(); ++j ) {
|
|
retrieveModelData( CachePosition( j, i ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
void CartesianDiagramDataCompressor::slotRowsAboutToBeRemoved( const QModelIndex& parent, int start, int end )
|
|
{
|
|
if ( parent != m_rootIndex )
|
|
return;
|
|
Q_ASSERT( start <= end );
|
|
|
|
CachePosition startPos = mapToCache( start, 0 );
|
|
CachePosition endPos = mapToCache( end, 0 );
|
|
|
|
static const CachePosition NullPosition( -1, -1 );
|
|
if( startPos == NullPosition )
|
|
{
|
|
rebuildCache();
|
|
startPos = mapToCache( start, 0 );
|
|
endPos = mapToCache( end, 0 );
|
|
// The start position still isn't valid,
|
|
// probably means that no resolution was set yet
|
|
if( startPos == NullPosition ) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
start = startPos.first;
|
|
end = endPos.first;
|
|
|
|
for( int i = 0; i < m_data.size(); ++i )
|
|
{
|
|
m_data[ i ].remove( start, end - start + 1 );
|
|
}
|
|
}
|
|
|
|
void CartesianDiagramDataCompressor::slotRowsRemoved( const QModelIndex& parent, int start, int end )
|
|
{
|
|
if ( parent != m_rootIndex )
|
|
return;
|
|
Q_ASSERT( start <= end );
|
|
|
|
CachePosition startPos = mapToCache( start, 0 );
|
|
CachePosition endPos = mapToCache( end, 0 );
|
|
|
|
start = startPos.first;
|
|
end = endPos.first;
|
|
|
|
static const CachePosition NullPosition( -1, -1 );
|
|
if( startPos == NullPosition )
|
|
{
|
|
// Since we should already have rebuilt the cache, it won't help to rebuild it again.
|
|
// Do not Q_ASSERT() though, since the resolution might simply not be set or we might now have 0 rows
|
|
return;
|
|
}
|
|
|
|
for( int i = 0; i < m_data.size(); ++i ) {
|
|
for(int j = start; j < m_data[i].size(); ++j ) {
|
|
retrieveModelData( CachePosition( j, i ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
void CartesianDiagramDataCompressor::slotColumnsAboutToBeRemoved( const QModelIndex& parent, int start, int end )
|
|
{
|
|
if ( parent != m_rootIndex )
|
|
return;
|
|
Q_ASSERT( start <= end );
|
|
|
|
CachePosition startPos = mapToCache( 0, start );
|
|
CachePosition endPos = mapToCache( 0, end );
|
|
|
|
static const CachePosition NullPosition( -1, -1 );
|
|
if( startPos == NullPosition )
|
|
{
|
|
rebuildCache();
|
|
startPos = mapToCache( 0, start );
|
|
endPos = mapToCache( 0, end );
|
|
// The start position still isn't valid,
|
|
// probably means that no resolution was set yet
|
|
if( startPos == NullPosition ) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
start = startPos.second;
|
|
end = endPos.second;
|
|
|
|
m_data.remove( start, end - start + 1 );
|
|
}
|
|
|
|
void CartesianDiagramDataCompressor::slotColumnsRemoved( const QModelIndex& parent, int start, int end )
|
|
{
|
|
if ( parent != m_rootIndex )
|
|
return;
|
|
Q_ASSERT( start <= end );
|
|
|
|
const CachePosition startPos = mapToCache( 0, start );
|
|
const CachePosition endPos = mapToCache( 0, end );
|
|
|
|
start = startPos.second;
|
|
end = endPos.second;
|
|
|
|
static const CachePosition NullPosition( -1, -1 );
|
|
if( startPos == NullPosition )
|
|
{
|
|
// Since we should already have rebuilt the cache, it won't help to rebuild it again.
|
|
// Do not Q_ASSERT() though, since the resolution might simply not be set or we might now have 0 columns
|
|
return;
|
|
}
|
|
|
|
for( int i = start; i < m_data.size(); ++i ) {
|
|
for( int j = 0; j < m_data[i].size(); ++j ) {
|
|
retrieveModelData( CachePosition( j, i ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
void CartesianDiagramDataCompressor::slotModelHeaderDataChanged( Qt::Orientation orientation, int first, int last )
|
|
{
|
|
if( orientation != Qt::Vertical )
|
|
return;
|
|
|
|
const QModelIndex firstRow = m_model->index( 0, first, m_rootIndex );
|
|
const QModelIndex lastRow = m_model->index( m_model->rowCount( m_rootIndex ) - 1, last, m_rootIndex );
|
|
|
|
slotModelDataChanged( firstRow, lastRow );
|
|
}
|
|
|
|
void CartesianDiagramDataCompressor::slotModelDataChanged(
|
|
const QModelIndex& topLeftIndex,
|
|
const QModelIndex& bottomRightIndex )
|
|
{
|
|
if ( topLeftIndex.parent() != m_rootIndex )
|
|
return;
|
|
Q_ASSERT( topLeftIndex.parent() == bottomRightIndex.parent() );
|
|
Q_ASSERT( topLeftIndex.row() <= bottomRightIndex.row() );
|
|
Q_ASSERT( topLeftIndex.column() <= bottomRightIndex.column() );
|
|
CachePosition topleft = mapToCache( topLeftIndex );
|
|
CachePosition bottomright = mapToCache( bottomRightIndex );
|
|
for ( int row = topleft.first; row <= bottomright.first; ++row )
|
|
for ( int column = topleft.second; column <= bottomright.second; ++column )
|
|
invalidate( CachePosition( row, column ) );
|
|
}
|
|
|
|
void CartesianDiagramDataCompressor::slotModelLayoutChanged()
|
|
{
|
|
rebuildCache();
|
|
calculateSampleStepWidth();
|
|
}
|
|
|
|
void CartesianDiagramDataCompressor::slotDiagramLayoutChanged( AbstractDiagram* diagramBase )
|
|
{
|
|
AbstractCartesianDiagram* diagram = qobject_cast< AbstractCartesianDiagram* >( diagramBase );
|
|
Q_ASSERT( diagram );
|
|
if ( diagram->datasetDimension() != m_datasetDimension ) {
|
|
setDatasetDimension( diagram->datasetDimension() );
|
|
}
|
|
}
|
|
|
|
int CartesianDiagramDataCompressor::modelDataColumns() const
|
|
{
|
|
Q_ASSERT( m_datasetDimension != 0 );
|
|
// only operational if there is a model and a resolution
|
|
if ( m_model ) {
|
|
const int columns = m_model->columnCount( m_rootIndex ) / m_datasetDimension;
|
|
|
|
if( columns != m_data.size() )
|
|
{
|
|
rebuildCache();
|
|
}
|
|
|
|
Q_ASSERT( columns == m_data.size() );
|
|
return columns;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
int CartesianDiagramDataCompressor::modelDataRows() const
|
|
{
|
|
// only operational if there is a model, columns, and a resolution
|
|
if ( m_model && m_model->columnCount( m_rootIndex ) > 0 && m_xResolution > 0 ) {
|
|
return m_data.isEmpty() ? 0 : m_data.first().size();
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
void CartesianDiagramDataCompressor::setModel( QAbstractItemModel* model )
|
|
{
|
|
if ( m_model != 0 && m_model != model ) {
|
|
disconnect( m_model, SIGNAL( headerDataChanged( Qt::Orientation, int, int ) ),
|
|
this, SLOT( slotModelHeaderDataChanged( Qt::Orientation, int, int ) ) );
|
|
disconnect( m_model, SIGNAL( dataChanged( QModelIndex, QModelIndex ) ),
|
|
this, SLOT( slotModelDataChanged( QModelIndex, QModelIndex ) ) );
|
|
disconnect( m_model, SIGNAL( layoutChanged() ),
|
|
this, SLOT( slotModelLayoutChanged() ) );
|
|
disconnect( m_model, SIGNAL( rowsAboutToBeInserted( QModelIndex, int, int ) ),
|
|
this, SLOT( slotRowsAboutToBeInserted( QModelIndex, int, int ) ) );
|
|
disconnect( m_model, SIGNAL( rowsInserted( QModelIndex, int, int ) ),
|
|
this, SLOT( slotRowsInserted( QModelIndex, int, int ) ) );
|
|
disconnect( m_model, SIGNAL( rowsAboutToBeRemoved( QModelIndex, int, int ) ),
|
|
this, SLOT( slotRowsAboutToBeRemoved( QModelIndex, int, int ) ) );
|
|
disconnect( m_model, SIGNAL( rowsRemoved( QModelIndex, int, int ) ),
|
|
this, SLOT( slotRowsRemoved( QModelIndex, int, int ) ) );
|
|
disconnect( m_model, SIGNAL( columnsAboutToBeInserted( QModelIndex, int, int ) ),
|
|
this, SLOT( slotColumnsAboutToBeInserted( QModelIndex, int, int ) ) );
|
|
disconnect( m_model, SIGNAL( columnsInserted( QModelIndex, int, int ) ),
|
|
this, SLOT( slotColumnsInserted( QModelIndex, int, int ) ) );
|
|
disconnect( m_model, SIGNAL( columnsRemoved( QModelIndex, int, int ) ),
|
|
this, SLOT( slotColumnsRemoved( QModelIndex, int, int ) ) );
|
|
disconnect( m_model, SIGNAL( columnsAboutToBeRemoved( QModelIndex, int, int ) ),
|
|
this, SLOT( slotColumnsAboutToBeRemoved( QModelIndex, int, int ) ) );
|
|
disconnect( m_model, SIGNAL( modelReset() ),
|
|
this, SLOT( rebuildCache() ) );
|
|
m_model = 0;
|
|
}
|
|
|
|
m_modelCache.setModel( model );
|
|
|
|
if ( model != 0 ) {
|
|
m_model = model;
|
|
connect( m_model, SIGNAL( headerDataChanged( Qt::Orientation, int, int ) ),
|
|
SLOT( slotModelHeaderDataChanged( Qt::Orientation, int, int ) ) );
|
|
connect( m_model, SIGNAL( dataChanged( QModelIndex, QModelIndex ) ),
|
|
SLOT( slotModelDataChanged( QModelIndex, QModelIndex ) ) );
|
|
connect( m_model, SIGNAL( layoutChanged() ),
|
|
SLOT( slotModelLayoutChanged() ) );
|
|
connect( m_model, SIGNAL( rowsAboutToBeInserted( QModelIndex, int, int ) ),
|
|
SLOT( slotRowsAboutToBeInserted( QModelIndex, int, int ) ) );
|
|
connect( m_model, SIGNAL( rowsInserted( QModelIndex, int, int ) ),
|
|
SLOT( slotRowsInserted( QModelIndex, int, int ) ) );
|
|
connect( m_model, SIGNAL( rowsAboutToBeRemoved( QModelIndex, int, int ) ),
|
|
SLOT( slotRowsAboutToBeRemoved( QModelIndex, int, int ) ) );
|
|
connect( m_model, SIGNAL( rowsRemoved( QModelIndex, int, int ) ),
|
|
SLOT( slotRowsRemoved( QModelIndex, int, int ) ) );
|
|
connect( m_model, SIGNAL( columnsAboutToBeInserted( QModelIndex, int, int ) ),
|
|
SLOT( slotColumnsAboutToBeInserted( QModelIndex, int, int ) ) );
|
|
connect( m_model, SIGNAL( columnsInserted( QModelIndex, int, int ) ),
|
|
SLOT( slotColumnsInserted( QModelIndex, int, int ) ) );
|
|
connect( m_model, SIGNAL( columnsRemoved( QModelIndex, int, int ) ),
|
|
SLOT( slotColumnsRemoved( QModelIndex, int, int ) ) );
|
|
connect( m_model, SIGNAL( columnsAboutToBeRemoved( QModelIndex, int, int ) ),
|
|
SLOT( slotColumnsAboutToBeRemoved( QModelIndex, int, int ) ) );
|
|
connect( m_model, SIGNAL( modelReset() ),
|
|
this, SLOT( rebuildCache() ) );
|
|
}
|
|
rebuildCache();
|
|
calculateSampleStepWidth();
|
|
}
|
|
|
|
void CartesianDiagramDataCompressor::setRootIndex( const QModelIndex& root )
|
|
{
|
|
if ( m_rootIndex != root ) {
|
|
Q_ASSERT( root.model() == m_model || !root.isValid() );
|
|
m_rootIndex = root;
|
|
m_modelCache.setRootIndex( root );
|
|
rebuildCache();
|
|
calculateSampleStepWidth();
|
|
}
|
|
}
|
|
void CartesianDiagramDataCompressor::setResolution( int x, int y )
|
|
{
|
|
const int oldX = m_xResolution;
|
|
const int oldY = m_yResolution;
|
|
|
|
if( m_datasetDimension != 1 )
|
|
{
|
|
// just ignore the resolution in that case
|
|
m_xResolution = m_model == 0 ? 0 : m_model->rowCount( m_rootIndex );
|
|
m_yResolution = qMax( 0, y );
|
|
}
|
|
else if ( x != m_xResolution || y != m_yResolution ) {
|
|
m_xResolution = qMax( 0, x );
|
|
m_yResolution = qMax( 0, y );
|
|
rebuildCache();
|
|
calculateSampleStepWidth();
|
|
}
|
|
|
|
if( oldX != m_xResolution || ( oldY != m_yResolution && m_datasetDimension == 1 ) )
|
|
{
|
|
rebuildCache();
|
|
calculateSampleStepWidth();
|
|
}
|
|
}
|
|
|
|
void CartesianDiagramDataCompressor::clearCache()
|
|
{
|
|
for ( int column = 0; column < m_data.size(); ++column )
|
|
m_data[column].fill( DataPoint() );
|
|
}
|
|
|
|
void CartesianDiagramDataCompressor::rebuildCache() const
|
|
{
|
|
Q_ASSERT( m_datasetDimension != 0 );
|
|
|
|
m_data.clear();
|
|
const int columnCount = m_model ? m_model->columnCount( m_rootIndex ) / m_datasetDimension : 0;
|
|
const int rowCount = qMin( m_model ? m_model->rowCount( m_rootIndex ) : 0, m_xResolution );
|
|
m_data.resize( columnCount );
|
|
for ( int i = 0; i < columnCount; ++i ) {
|
|
m_data[i].resize( rowCount );
|
|
}
|
|
// also empty the attrs cache
|
|
m_dataValueAttributesCache.clear();
|
|
}
|
|
|
|
const CartesianDiagramDataCompressor::DataPoint& CartesianDiagramDataCompressor::data( const CachePosition& position ) const
|
|
{
|
|
static DataPoint NullDataPoint;
|
|
if ( ! isValidCachePosition( position ) ) return NullDataPoint;
|
|
if ( ! isCached( position ) ) retrieveModelData( position );
|
|
return m_data[ position.second ][ position.first ];
|
|
}
|
|
|
|
QPair< QPointF, QPointF > CartesianDiagramDataCompressor::dataBoundaries() const
|
|
{
|
|
const int colCount = modelDataColumns();
|
|
double xMin = std::numeric_limits< double >::quiet_NaN();
|
|
double xMax = std::numeric_limits< double >::quiet_NaN();
|
|
double yMin = std::numeric_limits< double >::quiet_NaN();
|
|
double yMax = std::numeric_limits< double >::quiet_NaN();
|
|
|
|
for( int column = 0; column < colCount; ++column )
|
|
{
|
|
const DataPointVector& data = m_data[ column ];
|
|
int row = 0;
|
|
for( DataPointVector::const_iterator it = data.begin(); it != data.end(); ++it, ++row )
|
|
{
|
|
const DataPoint& p = *it;
|
|
if( !p.index.isValid() )
|
|
retrieveModelData( CachePosition( row, column ) );
|
|
|
|
const double valueX = ISNAN( p.key ) ? 0.0 : p.key;
|
|
const double valueY = ISNAN( p.value ) ? 0.0 : p.value;
|
|
if( ISNAN( xMin ) )
|
|
{
|
|
xMin = valueX;
|
|
xMax = valueX;
|
|
yMin = valueY;
|
|
yMax = valueY;
|
|
}
|
|
else
|
|
{
|
|
xMin = qMin( xMin, valueX );
|
|
xMax = qMax( xMax, valueX );
|
|
yMin = qMin( yMin, valueY );
|
|
yMax = qMax( yMax, valueY );
|
|
}
|
|
}
|
|
}
|
|
|
|
// NOTE: calculateDataBoundaries must return the *real* data boundaries!
|
|
// i.e. we may NOT fake yMin to be qMin( 0.0, yMin )
|
|
// (khz, 2008-01-24)
|
|
const QPointF bottomLeft( QPointF( xMin, yMin ) );
|
|
const QPointF topRight( QPointF( xMax, yMax ) );
|
|
return QPair< QPointF, QPointF >( bottomLeft, topRight );
|
|
}
|
|
|
|
void CartesianDiagramDataCompressor::retrieveModelData( const CachePosition& position ) const
|
|
{
|
|
Q_ASSERT( isValidCachePosition( position ) );
|
|
DataPoint result;
|
|
|
|
switch(m_mode ) {
|
|
case Precise:
|
|
{
|
|
bool forceHidden = false;
|
|
result.hidden = true;
|
|
const QModelIndexList indexes = mapToModel( position );
|
|
if( m_datasetDimension != 1 )
|
|
{
|
|
Q_ASSERT( indexes.count() == 2 );
|
|
|
|
// try the ColumnDataRole approach first
|
|
const int xColumn = indexes.first().column();
|
|
const int yColumn = indexes.last().column();
|
|
const QVariantList xValues = m_model->headerData( xColumn, Qt::Horizontal, ColumnDataRole ).toList();
|
|
const QVariantList yValues = m_model->headerData( yColumn, Qt::Horizontal, ColumnDataRole ).toList();
|
|
|
|
if( !xValues.isEmpty() && !yValues.isEmpty() )
|
|
{
|
|
Q_ASSERT( xValues.count() == yValues.count() );
|
|
int row = 0;
|
|
QVariantList::const_iterator itX = xValues.begin();
|
|
QVariantList::const_iterator itY = yValues.begin();
|
|
for( ; itX != xValues.end(); ++itX, ++itY, ++row )
|
|
{
|
|
DataPoint result;
|
|
result.index = m_model->index( row, xColumn, m_rootIndex );
|
|
if( !itX->isNull() )
|
|
{
|
|
result.key = itX->toDouble();
|
|
result.value = itY->toDouble();
|
|
}
|
|
m_data[ position.second ][ row ] = result;
|
|
}
|
|
return;
|
|
}
|
|
|
|
const QModelIndex& xIndex = indexes.first();
|
|
const QModelIndex& yIndex = indexes.last();
|
|
const double xData = m_modelCache.data( xIndex );
|
|
const double yData = m_modelCache.data( yIndex );
|
|
result.index = xIndex;
|
|
result.key = xData;
|
|
result.value = yData;
|
|
}
|
|
else
|
|
{
|
|
if ( ! indexes.isEmpty() ) {
|
|
result.value = std::numeric_limits< double >::quiet_NaN();
|
|
result.key = 0.0;
|
|
Q_FOREACH( const QModelIndex& index, indexes ) {
|
|
const double value = m_modelCache.data( index );
|
|
if( !ISNAN( value ) )
|
|
{
|
|
result.value = ISNAN( result.value ) ? value : result.value + value;
|
|
}
|
|
result.key += index.row();
|
|
}
|
|
result.index = indexes.at( 0 );
|
|
result.key /= indexes.size();
|
|
result.value /= indexes.size();
|
|
}
|
|
}
|
|
if( !forceHidden )
|
|
{
|
|
Q_FOREACH( const QModelIndex& index, indexes )
|
|
{
|
|
// the point is visible if any of the points at this pixel position is visible
|
|
if ( qVariantValue<bool>( m_model->data( index, DataHiddenRole ) ) == false ) {
|
|
result.hidden = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case SamplingSeven:
|
|
default:
|
|
{
|
|
}
|
|
break;
|
|
};
|
|
|
|
m_data[position.second][position.first] = result;
|
|
Q_ASSERT( isCached( position ) );
|
|
}
|
|
|
|
CartesianDiagramDataCompressor::CachePosition CartesianDiagramDataCompressor::mapToCache(
|
|
const QModelIndex& index ) const
|
|
{
|
|
Q_ASSERT( m_datasetDimension != 0 );
|
|
|
|
static const CachePosition NullPosition( -1, -1 );
|
|
if ( ! index.isValid() ) return NullPosition;
|
|
return mapToCache( index.row(), index.column() );
|
|
}
|
|
|
|
CartesianDiagramDataCompressor::CachePosition CartesianDiagramDataCompressor::mapToCache(
|
|
int row, int column ) const
|
|
{
|
|
Q_ASSERT( m_datasetDimension != 0 );
|
|
|
|
if ( m_data.size() == 0 || m_data[0].size() == 0 ) return mapToCache( QModelIndex() );
|
|
// assumption: indexes per column == 1
|
|
if ( indexesPerPixel() == 0 ) return mapToCache( QModelIndex() );
|
|
return CachePosition( static_cast< int >( ( row ) / indexesPerPixel() ), column / m_datasetDimension );
|
|
}
|
|
|
|
QModelIndexList CartesianDiagramDataCompressor::mapToModel( const CachePosition& position ) const
|
|
{
|
|
if ( isValidCachePosition( position ) ) {
|
|
QModelIndexList indexes;
|
|
if( m_datasetDimension == 2 )
|
|
{
|
|
indexes << m_model->index( position.first, position.second * 2, m_rootIndex );
|
|
indexes << m_model->index( position.first, position.second * 2 + 1, m_rootIndex );
|
|
}
|
|
else
|
|
{
|
|
// assumption: indexes per column == 1
|
|
const qreal ipp = indexesPerPixel();
|
|
for ( int i = 0; i < ipp; ++i ) {
|
|
const QModelIndex index = m_model->index( qRound( position.first * ipp ) + i, position.second, m_rootIndex );
|
|
if( index.isValid() )
|
|
indexes << index;
|
|
}
|
|
}
|
|
return indexes;
|
|
} else {
|
|
return QModelIndexList();
|
|
}
|
|
}
|
|
|
|
qreal CartesianDiagramDataCompressor::indexesPerPixel() const
|
|
{
|
|
if ( m_data.size() == 0 ) return 0;
|
|
if ( m_data[0].size() == 0 ) return 0;
|
|
if ( ! m_model ) return 0;
|
|
return static_cast< qreal >( m_model->rowCount( m_rootIndex ) ) / static_cast< qreal >( m_data[0].size() );
|
|
}
|
|
|
|
bool CartesianDiagramDataCompressor::isValidCachePosition( const CachePosition& position ) const
|
|
{
|
|
if ( ! m_model ) return false;
|
|
if ( m_data.size() == 0 || m_data[0].size() == 0 ) return false;
|
|
if ( position.second < 0 || position.second >= m_data.size() ) return false;
|
|
if ( position.first < 0 || position.first >= m_data[position.second].size() ) return false;
|
|
return true;
|
|
}
|
|
|
|
void CartesianDiagramDataCompressor::invalidate( const CachePosition& position )
|
|
{
|
|
if ( isValidCachePosition( position ) ) {
|
|
m_data[position.second][position.first] = DataPoint();
|
|
// Also invalidate the data value attributes at "position".
|
|
// Otherwise the user overwrites the attributes without us noticing
|
|
// it because we keep reading what's in the cache.
|
|
m_dataValueAttributesCache.remove( position );
|
|
}
|
|
}
|
|
|
|
bool CartesianDiagramDataCompressor::isCached( const CachePosition& position ) const
|
|
{
|
|
Q_ASSERT( isValidCachePosition( position ) );
|
|
const DataPoint& p = m_data[position.second][position.first];
|
|
return p.index.isValid();
|
|
}
|
|
|
|
void CartesianDiagramDataCompressor::calculateSampleStepWidth()
|
|
{
|
|
if ( m_mode == Precise ) {
|
|
m_sampleStep = 1;
|
|
return;
|
|
}
|
|
|
|
static unsigned int SomePrimes[] = {
|
|
2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47,
|
|
53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101,
|
|
151, 211, 313, 401, 503, 607, 701, 811, 911, 1009,
|
|
10037, 12911, 16001, 20011, 50021,
|
|
100003, 137867, 199999, 500009, 707753, 1000003, 0
|
|
}; // ... after that, having a model at all becomes impractical
|
|
|
|
// we want at least 17 samples per data point, using a prime step width
|
|
const double WantedSamples = 17;
|
|
if ( WantedSamples > indexesPerPixel() ) {
|
|
m_sampleStep = 1;
|
|
} else {
|
|
int i;
|
|
for ( i = 0; SomePrimes[i] != 0; ++i ) {
|
|
if ( WantedSamples * SomePrimes[i+1] > indexesPerPixel() ) {
|
|
break;
|
|
}
|
|
}
|
|
m_sampleStep = SomePrimes[i];
|
|
if ( SomePrimes[i] == 0 ) {
|
|
m_sampleStep = SomePrimes[i-1];
|
|
} else {
|
|
m_sampleStep = SomePrimes[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
void CartesianDiagramDataCompressor::setDatasetDimension( int dimension )
|
|
{
|
|
if ( dimension != m_datasetDimension ) {
|
|
m_datasetDimension = dimension;
|
|
rebuildCache();
|
|
calculateSampleStepWidth();
|
|
}
|
|
}
|