blob: 6b3040262e18b67ca8815137ad9a7a34de468976 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Data Visualization module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 or (at your option) any later version
** approved by the KDE Free Qt Foundation. The licenses are as published by
** the Free Software Foundation and appearing in the file LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "bars3dcontroller_p.h"
#include "bars3drenderer_p.h"
#include "qvalue3daxis_p.h"
#include "qcategory3daxis_p.h"
#include "qbardataproxy_p.h"
#include "qbar3dseries_p.h"
#include "thememanager_p.h"
#include "q3dtheme_p.h"
#include <QtCore/QMutexLocker>
QT_BEGIN_NAMESPACE_DATAVISUALIZATION
Bars3DController::Bars3DController(QRect boundRect, Q3DScene *scene)
: Abstract3DController(boundRect, scene),
m_selectedBar(invalidSelectionPosition()),
m_selectedBarSeries(0),
m_primarySeries(0),
m_isMultiSeriesUniform(false),
m_isBarSpecRelative(true),
m_barThicknessRatio(1.0f),
m_barSpacing(QSizeF(1.0, 1.0)),
m_floorLevel(0.0f),
m_renderer(0)
{
// Setting a null axis creates a new default axis according to orientation and graph type.
// Note: these cannot be set in the Abstract3DController constructor, as they will call virtual
// functions implemented by subclasses.
setAxisX(0);
setAxisY(0);
setAxisZ(0);
}
Bars3DController::~Bars3DController()
{
}
void Bars3DController::initializeOpenGL()
{
QMutexLocker mutexLocker(&m_renderMutex);
// Initialization is called multiple times when Qt Quick components are used
if (isInitialized())
return;
m_renderer = new Bars3DRenderer(this);
setRenderer(m_renderer);
mutexLocker.unlock();
synchDataToRenderer();
emitNeedRender();
}
void Bars3DController::synchDataToRenderer()
{
QMutexLocker mutexLocker(&m_renderMutex);
if (!isInitialized())
return;
// Background change requires reloading the meshes in bar graphs, so dirty the series visuals
if (m_themeManager->activeTheme()->d_ptr->m_dirtyBits.backgroundEnabledDirty) {
m_isSeriesVisualsDirty = true;
foreach (QAbstract3DSeries *series, m_seriesList)
series->d_ptr->m_changeTracker.meshChanged = true;
}
// If y range or reverse changed, scene needs to be updated to update camera limits
bool needSceneUpdate = false;
if (Abstract3DController::m_changeTracker.axisYRangeChanged
|| Abstract3DController::m_changeTracker.axisYReversedChanged) {
needSceneUpdate = true;
}
// Floor level update requires data update, so do before abstract sync
if (m_changeTracker.floorLevelChanged) {
m_renderer->updateFloorLevel(m_floorLevel);
m_changeTracker.floorLevelChanged = false;
}
Abstract3DController::synchDataToRenderer();
// Notify changes to renderer
if (m_changeTracker.rowsChanged) {
m_renderer->updateRows(m_changedRows);
m_changeTracker.rowsChanged = false;
m_changedRows.clear();
}
if (m_changeTracker.itemChanged) {
m_renderer->updateItems(m_changedItems);
m_changeTracker.itemChanged = false;
m_changedItems.clear();
}
if (m_changeTracker.multiSeriesScalingChanged) {
m_renderer->updateMultiSeriesScaling(m_isMultiSeriesUniform);
m_changeTracker.multiSeriesScalingChanged = false;
}
if (m_changeTracker.barSpecsChanged) {
m_renderer->updateBarSpecs(m_barThicknessRatio, m_barSpacing, m_isBarSpecRelative);
m_changeTracker.barSpecsChanged = false;
}
// Needs to be done after data is set, as it needs to know the visual array.
if (m_changeTracker.selectedBarChanged) {
m_renderer->updateSelectedBar(m_selectedBar, m_selectedBarSeries);
m_changeTracker.selectedBarChanged = false;
}
// Since scene is updated before axis updates are handled, do another render pass to
// properly update controller side camera limits.
if (needSceneUpdate)
m_scene->d_ptr->markDirty();
}
void Bars3DController::handleArrayReset()
{
QBar3DSeries *series;
if (qobject_cast<QBarDataProxy *>(sender()))
series = static_cast<QBarDataProxy *>(sender())->series();
else
series = static_cast<QBar3DSeries *>(sender());
if (series->isVisible()) {
adjustAxisRanges();
m_isDataDirty = true;
series->d_ptr->markItemLabelDirty();
}
if (!m_changedSeriesList.contains(series))
m_changedSeriesList.append(series);
// Clear selection unless still valid
setSelectedBar(m_selectedBar, m_selectedBarSeries, false);
emitNeedRender();
}
void Bars3DController::handleRowsAdded(int startIndex, int count)
{
Q_UNUSED(startIndex)
Q_UNUSED(count)
QBar3DSeries *series = static_cast<QBarDataProxy *>(sender())->series();
if (series->isVisible()) {
adjustAxisRanges();
m_isDataDirty = true;
}
if (!m_changedSeriesList.contains(series))
m_changedSeriesList.append(series);
emitNeedRender();
}
void Bars3DController::handleRowsChanged(int startIndex, int count)
{
QBar3DSeries *series = static_cast<QBarDataProxy *>(sender())->series();
int oldChangeCount = m_changedRows.size();
if (!oldChangeCount)
m_changedRows.reserve(count);
for (int i = 0; i < count; i++) {
bool newItem = true;
int candidate = startIndex + i;
for (int j = 0; j < oldChangeCount; j++) {
const ChangeRow &oldChangeItem = m_changedRows.at(j);
if (oldChangeItem.row == candidate && series == oldChangeItem.series) {
newItem = false;
break;
}
}
if (newItem) {
ChangeRow newChangeItem = {series, candidate};
m_changedRows.append(newChangeItem);
if (series == m_selectedBarSeries && m_selectedBar.x() == candidate)
series->d_ptr->markItemLabelDirty();
}
}
if (count) {
m_changeTracker.rowsChanged = true;
if (series->isVisible())
adjustAxisRanges();
// Clear selection unless still valid (row length might have changed)
setSelectedBar(m_selectedBar, m_selectedBarSeries, false);
emitNeedRender();
}
}
void Bars3DController::handleRowsRemoved(int startIndex, int count)
{
Q_UNUSED(startIndex)
Q_UNUSED(count)
QBar3DSeries *series = static_cast<QBarDataProxy *>(sender())->series();
if (series == m_selectedBarSeries) {
// If rows removed from selected series before the selection, adjust the selection
int selectedRow = m_selectedBar.x();
if (startIndex <= selectedRow) {
if ((startIndex + count) > selectedRow)
selectedRow = -1; // Selected row removed
else
selectedRow -= count; // Move selected row down by amount of rows removed
setSelectedBar(QPoint(selectedRow, m_selectedBar.y()), m_selectedBarSeries, false);
}
}
if (series->isVisible()) {
adjustAxisRanges();
m_isDataDirty = true;
}
if (!m_changedSeriesList.contains(series))
m_changedSeriesList.append(series);
emitNeedRender();
}
void Bars3DController::handleRowsInserted(int startIndex, int count)
{
Q_UNUSED(startIndex)
Q_UNUSED(count)
QBar3DSeries *series = static_cast<QBarDataProxy *>(sender())->series();
if (series == m_selectedBarSeries) {
// If rows inserted to selected series before the selection, adjust the selection
int selectedRow = m_selectedBar.x();
if (startIndex <= selectedRow) {
selectedRow += count;
setSelectedBar(QPoint(selectedRow, m_selectedBar.y()), m_selectedBarSeries, false);
}
}
if (series->isVisible()) {
adjustAxisRanges();
m_isDataDirty = true;
}
if (!m_changedSeriesList.contains(series))
m_changedSeriesList.append(series);
emitNeedRender();
}
void Bars3DController::handleItemChanged(int rowIndex, int columnIndex)
{
QBar3DSeries *series = static_cast<QBarDataProxy *>(sender())->series();
bool newItem = true;
QPoint candidate(rowIndex, columnIndex);
foreach (ChangeItem item, m_changedItems) {
if (item.point == candidate && item.series == series) {
newItem = false;
break;
}
}
if (newItem) {
ChangeItem newItem = {series, candidate};
m_changedItems.append(newItem);
m_changeTracker.itemChanged = true;
if (series == m_selectedBarSeries && m_selectedBar == candidate)
series->d_ptr->markItemLabelDirty();
if (series->isVisible())
adjustAxisRanges();
emitNeedRender();
}
}
void Bars3DController::handleDataRowLabelsChanged()
{
if (m_axisZ) {
// Grab a sublist equal to data window (no need to have more labels in axis)
int min = int(m_axisZ->min());
int count = int(m_axisZ->max()) - min + 1;
QStringList subList;
if (m_primarySeries && m_primarySeries->dataProxy())
subList = m_primarySeries->dataProxy()->rowLabels().mid(min, count);
static_cast<QCategory3DAxis *>(m_axisZ)->dptr()->setDataLabels(subList);
}
}
void Bars3DController::handleDataColumnLabelsChanged()
{
if (m_axisX) {
// Grab a sublist equal to data window (no need to have more labels in axis)
int min = int(m_axisX->min());
int count = int(m_axisX->max()) - min + 1;
QStringList subList;
if (m_primarySeries && m_primarySeries->dataProxy()) {
subList = static_cast<QBarDataProxy *>(m_primarySeries->dataProxy())
->columnLabels().mid(min, count);
}
static_cast<QCategory3DAxis *>(m_axisX)->dptr()->setDataLabels(subList);
}
}
void Bars3DController::handleAxisAutoAdjustRangeChangedInOrientation(
QAbstract3DAxis::AxisOrientation orientation, bool autoAdjust)
{
Q_UNUSED(orientation)
Q_UNUSED(autoAdjust)
adjustAxisRanges();
}
void Bars3DController::handleSeriesVisibilityChangedBySender(QObject *sender)
{
Abstract3DController::handleSeriesVisibilityChangedBySender(sender);
// Visibility changes may require disabling slicing,
// so just reset selection to ensure everything is still valid.
setSelectedBar(m_selectedBar, m_selectedBarSeries, false);
}
void Bars3DController::handlePendingClick()
{
// This function is called while doing the sync, so it is okay to query from renderer
QPoint position = m_renderer->clickedPosition();
QBar3DSeries *series = static_cast<QBar3DSeries *>(m_renderer->clickedSeries());
setSelectedBar(position, series, true);
Abstract3DController::handlePendingClick();
m_renderer->resetClickedStatus();
}
QPoint Bars3DController::invalidSelectionPosition()
{
static QPoint invalidSelectionPos(-1, -1);
return invalidSelectionPos;
}
void Bars3DController::setAxisX(QAbstract3DAxis *axis)
{
Abstract3DController::setAxisX(axis);
handleDataColumnLabelsChanged();
}
void Bars3DController::setAxisZ(QAbstract3DAxis *axis)
{
Abstract3DController::setAxisZ(axis);
handleDataRowLabelsChanged();
}
void Bars3DController::setPrimarySeries(QBar3DSeries *series)
{
if (!series) {
if (m_seriesList.size())
series = static_cast<QBar3DSeries *>(m_seriesList.at(0));
} else if (!m_seriesList.contains(series)) {
// Add nonexistent series.
addSeries(series);
}
if (m_primarySeries != series) {
m_primarySeries = series;
handleDataRowLabelsChanged();
handleDataColumnLabelsChanged();
emit primarySeriesChanged(m_primarySeries);
}
}
QBar3DSeries *Bars3DController::primarySeries() const
{
return m_primarySeries;
}
void Bars3DController::addSeries(QAbstract3DSeries *series)
{
insertSeries(m_seriesList.size(), series);
}
void Bars3DController::removeSeries(QAbstract3DSeries *series)
{
bool wasVisible = (series && series->d_ptr->m_controller == this && series->isVisible());
Abstract3DController::removeSeries(series);
if (m_selectedBarSeries == series)
setSelectedBar(invalidSelectionPosition(), 0, false);
if (wasVisible)
adjustAxisRanges();
// If primary series is removed, reset it to default
if (series == m_primarySeries) {
if (m_seriesList.size())
m_primarySeries = static_cast<QBar3DSeries *>(m_seriesList.at(0));
else
m_primarySeries = 0;
handleDataRowLabelsChanged();
handleDataColumnLabelsChanged();
emit primarySeriesChanged(m_primarySeries);
}
}
void Bars3DController::insertSeries(int index, QAbstract3DSeries *series)
{
Q_ASSERT(series && series->type() == QAbstract3DSeries::SeriesTypeBar);
int oldSize = m_seriesList.size();
Abstract3DController::insertSeries(index, series);
if (oldSize != m_seriesList.size()) {
QBar3DSeries *barSeries = static_cast<QBar3DSeries *>(series);
if (!oldSize) {
m_primarySeries = barSeries;
handleDataRowLabelsChanged();
handleDataColumnLabelsChanged();
}
if (barSeries->selectedBar() != invalidSelectionPosition())
setSelectedBar(barSeries->selectedBar(), barSeries, false);
if (!oldSize)
emit primarySeriesChanged(m_primarySeries);
}
}
QList<QBar3DSeries *> Bars3DController::barSeriesList()
{
QList<QAbstract3DSeries *> abstractSeriesList = seriesList();
QList<QBar3DSeries *> barSeriesList;
foreach (QAbstract3DSeries *abstractSeries, abstractSeriesList) {
QBar3DSeries *barSeries = qobject_cast<QBar3DSeries *>(abstractSeries);
if (barSeries)
barSeriesList.append(barSeries);
}
return barSeriesList;
}
void Bars3DController::handleAxisRangeChangedBySender(QObject *sender)
{
// Data window changed
if (sender == m_axisX || sender == m_axisZ) {
if (sender == m_axisX)
handleDataColumnLabelsChanged();
if (sender == m_axisZ)
handleDataRowLabelsChanged();
}
Abstract3DController::handleAxisRangeChangedBySender(sender);
// Update selected bar - may be moved offscreen
setSelectedBar(m_selectedBar, m_selectedBarSeries, false);
}
void Bars3DController::setMultiSeriesScaling(bool uniform)
{
m_isMultiSeriesUniform = uniform;
m_changeTracker.multiSeriesScalingChanged = true;
emitNeedRender();
}
bool Bars3DController::multiSeriesScaling() const
{
return m_isMultiSeriesUniform;
}
void Bars3DController::setBarSpecs(GLfloat thicknessRatio, const QSizeF &spacing, bool relative)
{
m_barThicknessRatio = thicknessRatio;
m_barSpacing = spacing;
m_isBarSpecRelative = relative;
m_changeTracker.barSpecsChanged = true;
emitNeedRender();
}
GLfloat Bars3DController::barThickness()
{
return m_barThicknessRatio;
}
QSizeF Bars3DController::barSpacing()
{
return m_barSpacing;
}
bool Bars3DController::isBarSpecRelative()
{
return m_isBarSpecRelative;
}
void Bars3DController::setFloorLevel(float level)
{
m_floorLevel = level;
m_isDataDirty = true;
m_changeTracker.floorLevelChanged = true;
emitNeedRender();
}
float Bars3DController::floorLevel() const
{
return m_floorLevel;
}
void Bars3DController::setSelectionMode(QAbstract3DGraph::SelectionFlags mode)
{
if (mode.testFlag(QAbstract3DGraph::SelectionSlice)
&& (mode.testFlag(QAbstract3DGraph::SelectionRow)
== mode.testFlag(QAbstract3DGraph::SelectionColumn))) {
qWarning("Must specify one of either row or column selection mode in conjunction with slicing mode.");
} else {
QAbstract3DGraph::SelectionFlags oldMode = selectionMode();
Abstract3DController::setSelectionMode(mode);
if (mode != oldMode) {
// Refresh selection upon mode change to ensure slicing is correctly updated
// according to series the visibility.
setSelectedBar(m_selectedBar, m_selectedBarSeries, true);
// Special case: Always deactivate slicing when changing away from slice
// automanagement, as this can't be handled in setSelectedBar.
if (!mode.testFlag(QAbstract3DGraph::SelectionSlice)
&& oldMode.testFlag(QAbstract3DGraph::SelectionSlice)) {
scene()->setSlicingActive(false);
}
}
}
}
void Bars3DController::setSelectedBar(const QPoint &position, QBar3DSeries *series, bool enterSlice)
{
// If the selection targets non-existent bar, clear selection instead.
QPoint pos = position;
// Series may already have been removed, so check it before setting the selection.
if (!m_seriesList.contains(series))
series = 0;
adjustSelectionPosition(pos, series);
if (selectionMode().testFlag(QAbstract3DGraph::SelectionSlice)) {
// If the selected bar is outside data window, or there is no visible selected bar,
// disable slicing.
if (pos.x() < m_axisZ->min() || pos.x() > m_axisZ->max()
|| pos.y() < m_axisX->min() || pos.y() > m_axisX->max()
|| !series->isVisible()) {
scene()->setSlicingActive(false);
} else if (enterSlice) {
scene()->setSlicingActive(true);
}
emitNeedRender();
}
if (pos != m_selectedBar || series != m_selectedBarSeries) {
bool seriesChanged = (series != m_selectedBarSeries);
m_selectedBar = pos;
m_selectedBarSeries = series;
m_changeTracker.selectedBarChanged = true;
// Clear selection from other series and finally set new selection to the specified series
foreach (QAbstract3DSeries *otherSeries, m_seriesList) {
QBar3DSeries *barSeries = static_cast<QBar3DSeries *>(otherSeries);
if (barSeries != m_selectedBarSeries)
barSeries->dptr()->setSelectedBar(invalidSelectionPosition());
}
if (m_selectedBarSeries)
m_selectedBarSeries->dptr()->setSelectedBar(m_selectedBar);
if (seriesChanged)
emit selectedSeriesChanged(m_selectedBarSeries);
emitNeedRender();
}
}
void Bars3DController::clearSelection()
{
setSelectedBar(invalidSelectionPosition(), 0, false);
}
void Bars3DController::adjustAxisRanges()
{
QCategory3DAxis *categoryAxisZ = static_cast<QCategory3DAxis *>(m_axisZ);
QCategory3DAxis *categoryAxisX = static_cast<QCategory3DAxis *>(m_axisX);
QValue3DAxis *valueAxis = static_cast<QValue3DAxis *>(m_axisY);
bool adjustZ = (categoryAxisZ && categoryAxisZ->isAutoAdjustRange());
bool adjustX = (categoryAxisX && categoryAxisX->isAutoAdjustRange());
bool adjustY = (valueAxis && categoryAxisX && categoryAxisZ && valueAxis->isAutoAdjustRange());
if (adjustZ || adjustX || adjustY) {
int maxRowCount = 0;
int maxColumnCount = 0;
float minValue = 0.0f;
float maxValue = 0.0f;
// First figure out row and column counts
int seriesCount = m_seriesList.size();
if (adjustZ || adjustX) {
for (int series = 0; series < seriesCount; series++) {
const QBar3DSeries *barSeries =
static_cast<QBar3DSeries *>(m_seriesList.at(series));
if (barSeries->isVisible()) {
const QBarDataProxy *proxy = barSeries->dataProxy();
if (adjustZ && proxy) {
int rowCount = proxy->rowCount();
if (rowCount)
rowCount--;
maxRowCount = qMax(maxRowCount, rowCount);
}
if (adjustX && proxy) {
const QBarDataArray *array = proxy->array();
int columnCount = 0;
for (int i = 0; i < array->size(); i++) {
if (columnCount < array->at(i)->size())
columnCount = array->at(i)->size();
}
if (columnCount)
columnCount--;
maxColumnCount = qMax(maxColumnCount, columnCount);
}
}
}
// Call private implementations of setRange to avoid unsetting auto adjust flag
if (adjustZ)
categoryAxisZ->dptr()->setRange(0.0f, float(maxRowCount), true);
if (adjustX)
categoryAxisX->dptr()->setRange(0.0f, float(maxColumnCount), true);
}
// Now that we know the row and column ranges, figure out the value axis range
if (adjustY) {
for (int series = 0; series < seriesCount; series++) {
const QBar3DSeries *barSeries =
static_cast<QBar3DSeries *>(m_seriesList.at(series));
if (barSeries->isVisible()) {
const QBarDataProxy *proxy = barSeries->dataProxy();
if (adjustY && proxy) {
QPair<GLfloat, GLfloat> limits =
proxy->dptrc()->limitValues(categoryAxisZ->min(),
categoryAxisZ->max(),
categoryAxisX->min(),
categoryAxisX->max());
if (!series) {
// First series initializes the values
minValue = limits.first;
maxValue = limits.second;
} else {
minValue = qMin(minValue, limits.first);
maxValue = qMax(maxValue, limits.second);
}
}
}
}
if (maxValue < 0.0f)
maxValue = 0.0f;
if (minValue > 0.0f)
minValue = 0.0f;
if (minValue == 0.0f && maxValue == 0.0f) {
// Only zero value values in data set, set range to something.
minValue = 0.0f;
maxValue = 1.0f;
}
valueAxis->dptr()->setRange(minValue, maxValue, true);
}
}
}
// Invalidate selection position if outside data for the series
void Bars3DController::adjustSelectionPosition(QPoint &pos, const QBar3DSeries *series)
{
const QBarDataProxy *proxy = 0;
if (series)
proxy = series->dataProxy();
if (!proxy)
pos = invalidSelectionPosition();
if (pos != invalidSelectionPosition()) {
int maxRow = proxy->rowCount() - 1;
int maxCol = (pos.x() <= maxRow && pos.x() >= 0 && proxy->rowAt(pos.x()))
? proxy->rowAt(pos.x())->size() - 1 : -1;
if (pos.x() < 0 || pos.x() > maxRow || pos.y() < 0 || pos.y() > maxCol)
pos = invalidSelectionPosition();
}
}
QAbstract3DAxis *Bars3DController::createDefaultAxis(QAbstract3DAxis::AxisOrientation orientation)
{
QAbstract3DAxis *defaultAxis = 0;
if (orientation == QAbstract3DAxis::AxisOrientationY)
defaultAxis = createDefaultValueAxis();
else
defaultAxis = createDefaultCategoryAxis();
return defaultAxis;
}
QT_END_NAMESPACE_DATAVISUALIZATION