summaryrefslogtreecommitdiffstats
path: root/sc/source/core/tool/chartpos.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'sc/source/core/tool/chartpos.cxx')
-rw-r--r--sc/source/core/tool/chartpos.cxx523
1 files changed, 523 insertions, 0 deletions
diff --git a/sc/source/core/tool/chartpos.cxx b/sc/source/core/tool/chartpos.cxx
new file mode 100644
index 000000000..4c93be995
--- /dev/null
+++ b/sc/source/core/tool/chartpos.cxx
@@ -0,0 +1,523 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <chartpos.hxx>
+#include <document.hxx>
+#include <osl/diagnose.h>
+#include <svl/numformat.hxx>
+
+#include <memory>
+
+namespace
+{
+ bool lcl_hasValueDataButNoDates( const ScDocument& rDocument, SCCOL nCol, SCROW nRow, SCTAB nTab )
+ {
+ bool bReturn = false;
+ if (rDocument.HasValueData( nCol, nRow, nTab ))
+ {
+ //treat dates like text #i25706#
+ sal_uInt32 nNumberFormat = rDocument.GetNumberFormat( ScAddress( nCol, nRow, nTab ) );
+ SvNumFormatType nType = rDocument.GetFormatTable()->GetType(nNumberFormat);
+ bool bIsDate(nType & SvNumFormatType::DATE);
+ bReturn = !bIsDate;
+ }
+ return bReturn;
+ }
+}
+
+ScChartPositioner::ScChartPositioner( ScDocument& rDoc, SCTAB nTab,
+ SCCOL nStartColP, SCROW nStartRowP, SCCOL nEndColP, SCROW nEndRowP) :
+ rDocument( rDoc ),
+ eGlue( ScChartGlue::NA ),
+ nStartCol(0),
+ nStartRow(0),
+ bColHeaders( false ),
+ bRowHeaders( false ),
+ bDummyUpperLeft( false )
+{
+ SetRangeList( ScRange( nStartColP, nStartRowP, nTab, nEndColP, nEndRowP, nTab ) );
+ CheckColRowHeaders();
+}
+
+ScChartPositioner::ScChartPositioner( ScDocument& rDoc, const ScRangeListRef& rRangeList ) :
+ aRangeListRef( rRangeList ),
+ rDocument( rDoc ),
+ eGlue( ScChartGlue::NA ),
+ nStartCol(0),
+ nStartRow(0),
+ bColHeaders( false ),
+ bRowHeaders( false ),
+ bDummyUpperLeft( false )
+{
+ if ( aRangeListRef.is() )
+ CheckColRowHeaders();
+}
+
+ScChartPositioner::ScChartPositioner( const ScChartPositioner& rPositioner ) :
+ aRangeListRef( rPositioner.aRangeListRef ),
+ rDocument(rPositioner.rDocument),
+ eGlue(rPositioner.eGlue),
+ nStartCol(rPositioner.nStartCol),
+ nStartRow(rPositioner.nStartRow),
+ bColHeaders(rPositioner.bColHeaders),
+ bRowHeaders(rPositioner.bRowHeaders),
+ bDummyUpperLeft( rPositioner.bDummyUpperLeft )
+{
+}
+
+ScChartPositioner::~ScChartPositioner()
+{
+}
+
+void ScChartPositioner::SetRangeList( const ScRange& rRange )
+{
+ aRangeListRef = new ScRangeList( rRange );
+ InvalidateGlue();
+}
+
+void ScChartPositioner::GlueState()
+{
+ if ( eGlue != ScChartGlue::NA )
+ return;
+ bDummyUpperLeft = false;
+ ScRange* pR;
+ if ( aRangeListRef->size() <= 1 )
+ {
+ if ( !aRangeListRef->empty() )
+ {
+ pR = &aRangeListRef->front();
+ if ( pR->aStart.Tab() == pR->aEnd.Tab() )
+ eGlue = ScChartGlue::NONE;
+ else
+ eGlue = ScChartGlue::Cols; // several tables column by column
+ nStartCol = pR->aStart.Col();
+ nStartRow = pR->aStart.Row();
+ }
+ else
+ {
+ InvalidateGlue();
+ nStartCol = 0;
+ nStartRow = 0;
+ }
+ return;
+ }
+
+ pR = &aRangeListRef->front();
+ nStartCol = pR->aStart.Col();
+ nStartRow = pR->aStart.Row();
+ SCCOL nMaxCols, nEndCol;
+ SCROW nMaxRows, nEndRow;
+ nMaxCols = nEndCol = 0;
+ nMaxRows = nEndRow = 0;
+
+ // <= so 1 extra pass after last item
+ for ( size_t i = 1, nRanges = aRangeListRef->size(); i <= nRanges; ++i )
+ { // detect spanning/surrounding area etc.
+ SCCOLROW nTmp, n1, n2;
+ if ( (n1 = pR->aStart.Col()) < nStartCol ) nStartCol = static_cast<SCCOL>(n1 );
+ if ( (n2 = pR->aEnd.Col() ) > nEndCol ) nEndCol = static_cast<SCCOL>(n2 );
+ if ( (nTmp = n2 - n1 + 1 ) > nMaxCols ) nMaxCols = static_cast<SCCOL>(nTmp);
+ if ( (n1 = pR->aStart.Row()) < nStartRow ) nStartRow = static_cast<SCROW>(n1 );
+ if ( (n2 = pR->aEnd.Row() ) > nEndRow ) nEndRow = static_cast<SCROW>(n2 );
+ if ( (nTmp = n2 - n1 + 1 ) > nMaxRows ) nMaxRows = static_cast<SCROW>(nTmp);
+
+ // in last pass; i = nRanges so don't use at()
+ if ( i < nRanges )
+ pR = &(*aRangeListRef)[i];
+ }
+ SCCOL nC = nEndCol - nStartCol + 1;
+ if ( nC == 1 )
+ {
+ eGlue = ScChartGlue::Rows;
+ return;
+ }
+ SCROW nR = nEndRow - nStartRow + 1;
+ if ( nR == 1 )
+ {
+ eGlue = ScChartGlue::Cols;
+ return;
+ }
+ sal_uLong nCR = static_cast<sal_uLong>(nC) * nR;
+
+ /*
+ TODO:
+ First do it simple without bit masking. A maximum of 8MB could be allocated
+ this way (256 Cols x 32000 Rows). That could be reduced to 2MB by
+ using 2 Bits per entry, but it is faster this way.
+ Another optimization would be to store only used rows/columns in the array, but
+ would mean another iteration of the RangeList indirect access to the array. */
+
+ enum class CellState : sal_uInt8 { Hole, Occupied, Free, Glue };
+ CellState* p;
+ std::unique_ptr<CellState[]> pA(new CellState[ nCR ]);
+ memset( pA.get(), 0, nCR * sizeof(CellState) );
+
+ SCCOL nCol, nCol1, nCol2;
+ SCROW nRow, nRow1, nRow2;
+ for ( size_t i = 0, nRanges = aRangeListRef->size(); i < nRanges; ++i )
+ { // mark selections as used in 2D
+ pR = &(*aRangeListRef)[i];
+ nCol1 = pR->aStart.Col() - nStartCol;
+ nCol2 = pR->aEnd.Col() - nStartCol;
+ nRow1 = pR->aStart.Row() - nStartRow;
+ nRow2 = pR->aEnd.Row() - nStartRow;
+ for ( nCol = nCol1; nCol <= nCol2; nCol++ )
+ {
+ p = pA.get() + static_cast<sal_uLong>(nCol) * nR + nRow1;
+ for ( nRow = nRow1; nRow <= nRow2; nRow++, p++ )
+ *p = CellState::Occupied;
+ }
+ }
+ bool bGlue = true;
+
+ bool bGlueCols = false;
+ for ( nCol = 0; bGlue && nCol < nC; nCol++ )
+ { // iterate columns and try to mark as unused
+ p = pA.get() + static_cast<sal_uLong>(nCol) * nR;
+ for ( nRow = 0; bGlue && nRow < nR; nRow++, p++ )
+ {
+ if ( *p == CellState::Occupied )
+ { // If there's one right in the middle, we can't combine.
+ // If it were at the edge, we could combine, if in this Column
+ // in every set line, one is set.
+ if ( nRow > 0 && nCol > 0 )
+ bGlue = false; // nCol==0 can be DummyUpperLeft
+ else
+ nRow = nR;
+ }
+ else
+ *p = CellState::Free;
+ }
+ if ( bGlue )
+ {
+ p = pA.get() + (((static_cast<sal_uLong>(nCol)+1) * nR) - 1);
+ if (*p == CellState::Free)
+ { // mark column as totally unused
+ *p = CellState::Glue;
+ bGlueCols = true; // one unused column at least
+ }
+ }
+ }
+
+ bool bGlueRows = false;
+ for ( nRow = 0; bGlue && nRow < nR; nRow++ )
+ { // iterate rows and try to mark as unused
+ p = pA.get() + nRow;
+ for ( nCol = 0; bGlue && nCol < nC; nCol++, p+=nR )
+ {
+ if ( *p == CellState::Occupied )
+ {
+ if ( nCol > 0 && nRow > 0 )
+ bGlue = false; // nRow==0 can be DummyUpperLeft
+ else
+ nCol = nC;
+ }
+ else
+ *p = CellState::Free;
+ }
+ if ( bGlue )
+ {
+ p = pA.get() + (((static_cast<sal_uLong>(nC)-1) * nR) + nRow);
+ if (*p == CellState::Free )
+ { // mark row as totally unused
+ *p = CellState::Glue;
+ bGlueRows = true; // one unused row at least
+ }
+ }
+ }
+
+ // If n=1: The upper left corner could be automagically pulled in for labeling
+ p = pA.get() + 1;
+ for ( sal_uLong n = 1; bGlue && n < nCR; n++, p++ )
+ { // An untouched field means we could neither reach it through rows nor columns,
+ // thus we can't combine anything
+ if ( *p == CellState::Hole )
+ bGlue = false;
+ }
+ if ( bGlue )
+ {
+ if ( bGlueCols && bGlueRows )
+ eGlue = ScChartGlue::Both;
+ else if ( bGlueRows )
+ eGlue = ScChartGlue::Rows;
+ else
+ eGlue = ScChartGlue::Cols;
+ if ( pA[0] != CellState::Occupied )
+ bDummyUpperLeft = true;
+ }
+ else
+ {
+ eGlue = ScChartGlue::NONE;
+ }
+}
+
+void ScChartPositioner::CheckColRowHeaders()
+{
+ SCCOL nCol1, nCol2, iCol;
+ SCROW nRow1, nRow2, iRow;
+ SCTAB nTab1, nTab2;
+
+ bool bColStrings = true;
+ bool bRowStrings = true;
+ GlueState();
+ if ( aRangeListRef->size() == 1 )
+ {
+ aRangeListRef->front().GetVars( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 );
+ if ( nCol1 > nCol2 || nRow1 > nRow2 )
+ bColStrings = bRowStrings = false;
+ else
+ {
+ for (iCol=nCol1; iCol<=nCol2 && bColStrings; iCol++)
+ {
+ if (lcl_hasValueDataButNoDates( rDocument, iCol, nRow1, nTab1 ))
+ bColStrings = false;
+ }
+ for (iRow=nRow1; iRow<=nRow2 && bRowStrings; iRow++)
+ {
+ if (lcl_hasValueDataButNoDates( rDocument, nCol1, iRow, nTab1 ))
+ bRowStrings = false;
+ }
+ }
+ }
+ else
+ {
+ bool bVert = (eGlue == ScChartGlue::NONE || eGlue == ScChartGlue::Rows);
+ for ( size_t i = 0, nRanges = aRangeListRef->size();
+ (i < nRanges) && (bColStrings || bRowStrings);
+ ++i
+ )
+ {
+ const ScRange & rR = (*aRangeListRef)[i];
+ rR.GetVars( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 );
+ bool bTopRow = (nRow1 == nStartRow);
+ if ( bRowStrings && (bVert || nCol1 == nStartCol) )
+ { // NONE or ROWS: RowStrings in every selection possible
+ // COLS or BOTH: only from first column
+ if ( nCol1 <= nCol2 )
+ for (iRow=nRow1; iRow<=nRow2 && bRowStrings; iRow++)
+ {
+ if (lcl_hasValueDataButNoDates( rDocument, nCol1, iRow, nTab1 ))
+ bRowStrings = false;
+ }
+ }
+ if ( bColStrings && bTopRow )
+ { // ColStrings only from first row
+ if ( nRow1 <= nRow2 )
+ for (iCol=nCol1; iCol<=nCol2 && bColStrings; iCol++)
+ {
+ if (lcl_hasValueDataButNoDates( rDocument, iCol, nRow1, nTab1 ))
+ bColStrings = false;
+ }
+ }
+ }
+ }
+ bColHeaders = bColStrings;
+ bRowHeaders = bRowStrings;
+}
+
+const ScChartPositionMap* ScChartPositioner::GetPositionMap()
+{
+ CreatePositionMap();
+ return pPositionMap.get();
+}
+
+void ScChartPositioner::CreatePositionMap()
+{
+ if ( eGlue == ScChartGlue::NA && pPositionMap )
+ {
+ pPositionMap.reset();
+ }
+
+ if ( pPositionMap )
+ return ;
+
+ SCSIZE nColAdd = bRowHeaders ? 1 : 0;
+ SCSIZE nRowAdd = bColHeaders ? 1 : 0;
+
+ SCCOL nCol, nCol1, nCol2;
+ SCROW nRow, nRow1, nRow2;
+ SCTAB nTab, nTab1, nTab2;
+
+ // real size (without hidden rows/columns)
+
+ SCSIZE nColCount = 0;
+ SCSIZE nRowCount = 0;
+
+ GlueState();
+
+ const bool bNoGlue = (eGlue == ScChartGlue::NONE);
+ ColumnMap aColMap;
+ SCROW nNoGlueRow = 0;
+ for ( size_t i = 0, nRanges = aRangeListRef->size(); i < nRanges; ++i )
+ {
+ const ScRange & rR = (*aRangeListRef)[i];
+ rR.GetVars( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 );
+ for ( nTab = nTab1; nTab <= nTab2; nTab++ )
+ {
+ // nTab in ColKey to allow to have the same col/row in another table
+ sal_uLong nInsCol = (static_cast<sal_uLong>(nTab) << 16) | (bNoGlue ? 0 :
+ static_cast<sal_uLong>(nCol1));
+ for ( nCol = nCol1; nCol <= nCol2; ++nCol, ++nInsCol )
+ {
+ RowMap* pCol = &aColMap[nInsCol];
+
+ // in other table a new ColKey already was created,
+ // the rows must be equal to be filled with Dummy
+ sal_uLong nInsRow = (bNoGlue ? nNoGlueRow : nRow1);
+ for ( nRow = nRow1; nRow <= nRow2; nRow++, nInsRow++ )
+ {
+ if ( pCol->find( nInsRow ) == pCol->end() )
+ {
+ pCol->emplace( nInsRow, std::make_unique<ScAddress>( nCol, nRow, nTab ) );
+ }
+ }
+ }
+ }
+ // For NoGlue: associated tables will be rendered as ColGlue
+ nNoGlueRow += nRow2 - nRow1 + 1;
+ }
+
+ // count of data
+ nColCount = static_cast< SCSIZE >( aColMap.size());
+ if ( !aColMap.empty() )
+ {
+ RowMap& rCol = aColMap.begin()->second;
+ if ( bDummyUpperLeft )
+ rCol[ 0 ] = nullptr; // Dummy for labeling
+ nRowCount = static_cast< SCSIZE >( rCol.size());
+ }
+ else
+ nRowCount = 0;
+ if ( nColCount > 0 )
+ nColCount -= nColAdd;
+ if ( nRowCount > 0 )
+ nRowCount -= nRowAdd;
+
+ if ( nColCount==0 || nRowCount==0 )
+ { // create an entry without data
+ RowMap& rCol = aColMap[0];
+ nColCount = 1;
+ rCol[ 0 ] = nullptr;
+ nRowCount = 1;
+ nColAdd = 0;
+ nRowAdd = 0;
+ }
+ else
+ {
+ if ( bNoGlue )
+ { // fill gaps with Dummies, first column is master
+ RowMap& rFirstCol = aColMap.begin()->second;
+
+ for ( const auto& it1 : rFirstCol )
+ {
+ sal_uLong nKey = it1.first;
+ for (ColumnMap::iterator it2 = ++aColMap.begin(); it2 != aColMap.end(); ++it2 )
+ it2->second.emplace( nKey, nullptr ); // no data
+ }
+ }
+ }
+
+ pPositionMap.reset( new ScChartPositionMap( static_cast<SCCOL>(nColCount), static_cast<SCROW>(nRowCount),
+ static_cast<SCCOL>(nColAdd), static_cast<SCROW>(nRowAdd), aColMap ) );
+}
+
+void ScChartPositioner::InvalidateGlue()
+{
+ eGlue = ScChartGlue::NA;
+ pPositionMap.reset();
+}
+
+ScChartPositionMap::ScChartPositionMap( SCCOL nChartCols, SCROW nChartRows,
+ SCCOL nColAdd, SCROW nRowAdd, ColumnMap& rCols ) :
+ ppData( new std::unique_ptr<ScAddress> [ nChartCols * nChartRows ] ),
+ ppColHeader( new std::unique_ptr<ScAddress> [ nChartCols ] ),
+ ppRowHeader( new std::unique_ptr<ScAddress> [ nChartRows ] ),
+ nCount( static_cast<sal_uLong>(nChartCols) * nChartRows ),
+ nColCount( nChartCols ),
+ nRowCount( nChartRows )
+{
+ OSL_ENSURE( nColCount && nRowCount, "ScChartPositionMap without dimension" );
+
+ ColumnMap::iterator pColIter = rCols.begin();
+ RowMap& rCol1 = pColIter->second;
+
+ // row header
+ auto pPos1Iter = rCol1.begin();
+ if ( nRowAdd )
+ ++pPos1Iter;
+ if ( nColAdd )
+ { // independent
+ SCROW nRow = 0;
+ for ( ; nRow < nRowCount && pPos1Iter != rCol1.end(); nRow++ )
+ {
+ ppRowHeader[ nRow ] = std::move(pPos1Iter->second);
+ ++pPos1Iter;
+ }
+ }
+ else
+ { // copy
+ SCROW nRow = 0;
+ for ( ; nRow < nRowCount && pPos1Iter != rCol1.end(); nRow++ )
+ {
+ if (pPos1Iter->second)
+ ppRowHeader[ nRow ].reset(new ScAddress( *pPos1Iter->second ));
+ ++pPos1Iter;
+ }
+ }
+ if ( nColAdd )
+ {
+ ++pColIter;
+ }
+
+ // data column by column and column-header
+ sal_uLong nIndex = 0;
+ for ( SCCOL nCol = 0; nCol < nColCount; nCol++ )
+ {
+ if ( pColIter != rCols.end() )
+ {
+ RowMap& rCol2 = pColIter->second;
+ RowMap::iterator pPosIter = rCol2.begin();
+ if ( pPosIter != rCol2.end() )
+ {
+ if ( nRowAdd )
+ {
+ ppColHeader[ nCol ] = std::move(pPosIter->second); // independent
+ ++pPosIter;
+ }
+ else if ( pPosIter->second )
+ ppColHeader[ nCol ].reset( new ScAddress( *pPosIter->second ) );
+ }
+
+ SCROW nRow = 0;
+ for ( ; nRow < nRowCount && pPosIter != rCol2.end(); nRow++, nIndex++ )
+ {
+ ppData[ nIndex ] = std::move(pPosIter->second);
+ ++pPosIter;
+ }
+
+ ++pColIter;
+ }
+ }
+}
+
+ScChartPositionMap::~ScChartPositionMap()
+{
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */