Revision control
Copy as Markdown
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
// Qt-Security score:significant reason:default
#include <QtGraphs/qbarseries.h>
#include <QtGraphs/qbarset.h>
#include <QtQuick/private/qquickrectangle_p.h>
#include <QtQuick/private/qquicktaphandler_p.h>
#include <private/axisrenderer_p.h>
#include <private/barsrenderer_p.h>
#include <private/qbarseries_p.h>
#include <private/qgraphsview_p.h>
#include <utility>
#if QT_CONFIG(graphs_2d_high_performance_backend)
#include <QtCanvasPainter/QCanvasPainter>
#endif
#include <qtgraphs_tracepoints_p.h>
QT_BEGIN_NAMESPACE
Q_TRACE_PREFIX(qtgraphs,
"QT_BEGIN_NAMESPACE" \
"class BarsRenderer;" \
"QT_END_NAMESPACE"
)
Q_TRACE_POINT(qtgraphs, QGraphs2DBarsRendererUpdateVerticalBars_entry, int setCount, int valuesPerSet, int barSeriesCount);
Q_TRACE_POINT(qtgraphs, QGraphs2DBarsRendererUpdateVerticalBars_exit);
Q_TRACE_POINT(qtgraphs, QGraphs2DBarsRendererUpdateupdateHorizontalBars_entry, int setCount, int valuesPerSet, int barSeriesCount);
Q_TRACE_POINT(qtgraphs, QGraphs2DBarsRendererUpdateupdateHorizontalBars_exit);
Q_TRACE_POINT(qtgraphs, QGraphs2DBarsRendererUpdateValueLabels_entry);
Q_TRACE_POINT(qtgraphs, QGraphs2DBarsRendererUpdateValueLabels_exit);
Q_TRACE_POINT(qtgraphs, QGraphs2DBarsRendererUpdateComponents_entry);
Q_TRACE_POINT(qtgraphs, QGraphs2DBarsRendererUpdateComponents_exit);
static const char* TAG_BAR_COLOR = "barColor";
static const char* TAG_BAR_BORDER_COLOR = "barBorderColor";
static const char* TAG_BAR_BORDER_WIDTH = "barBorderWidth";
static const char* TAG_BAR_SELECTED = "barSelected";
static const char* TAG_BAR_VALUE = "barValue";
static const char* TAG_BAR_LABEL = "barLabel";
static const char* TAG_BAR_INDEX = "barIndex";
BarsRenderer::BarsRenderer(QGraphsView *graph, bool clipPlotArea)
: QQuickItem(graph)
, m_graph(graph)
{
setFlag(QQuickItem::ItemHasContents);
setClip(clipPlotArea);
m_tapHandler = new QQuickTapHandler(this);
connect(m_tapHandler, &QQuickTapHandler::singleTapped, this, &BarsRenderer::onSingleTapped);
connect(m_tapHandler, &QQuickTapHandler::doubleTapped, this, &BarsRenderer::onDoubleTapped);
connect(m_tapHandler, &QQuickTapHandler::pressedChanged, this, &BarsRenderer::onPressedChanged);
}
BarsRenderer::~BarsRenderer() {}
#if QT_CONFIG(graphs_2d_high_performance_backend)
void BarsRenderer::paintSnapshot(const PaintSnapshot &snapshot,
QCanvasPainter *p)
{
for (const auto &dataList : snapshot) {
for (const auto &d : dataList) {
p->setFillStyle(d.color);
p->setStrokeStyle(d.borderColor);
p->setLineWidth(d.borderWidth);
p->beginPath();
p->rect(d.rect);
p->fill();
p->stroke();
}
}
}
void BarsRenderer::synchronizeData()
{
m_paintSnapshot = m_seriesData;
}
BarsRenderer::PaintSnapshot BarsRenderer::paintSnapshot() const
{
return m_paintSnapshot;
}
#endif
// Returns color in this order:
// 1) QBarSet::color if that is defined (alpha > 0).
// 2) QBarSeries::seriesColors at index if that is defined.
// 3) QGraphsTheme::seriesColors at index.
// 4) Black if seriesColors is empty.
QColor BarsRenderer::getSetColor(QBarSeries *series, QBarSet *set, qsizetype barSeriesIndex)
{
const auto &seriesColors = !series->seriesColors().isEmpty()
? series->seriesColors() : m_graph->theme()->seriesColors();
if (seriesColors.isEmpty())
return QColorConstants::Black;
qsizetype index = m_colorIndex + barSeriesIndex;
index = index % seriesColors.size();
QColor color = set->color().alpha() != 0
? set->color()
: seriesColors.at(index);
return color;
}
QColor BarsRenderer::getSetSelectedColor(QBarSeries *series, QBarSet *set)
{
Q_UNUSED(series);
auto theme = m_graph->theme();
QColor color = set->selectedColor().alpha() != 0
? set->selectedColor()
: theme->singleHighlightColor();
return color;
}
QColor BarsRenderer::getSetBorderColor(QBarSeries *series, QBarSet *set, qsizetype barSeriesIndex)
{
const auto &borderColors = !series->borderColors().isEmpty()
? series->borderColors() : m_graph->theme()->borderColors();
if (borderColors.isEmpty())
return QColorConstants::Black;
qsizetype index = m_colorIndex + barSeriesIndex;
index = index % borderColors.size();
QColor color = set->borderColor().alpha() != 0
? set->borderColor()
: borderColors.at(index);
return color;
}
qreal BarsRenderer::getSetBorderWidth(QBarSeries *series, QBarSet *set)
{
Q_UNUSED(series);
auto theme = m_graph->theme();
qreal borderWidth = set->borderWidth();
if (qFuzzyCompare(borderWidth, qreal(-1.0)))
borderWidth = theme->borderWidth();
return borderWidth;
}
QString BarsRenderer::generateLabelText(QBarSeries *series, qreal value)
{
static const QString valueTag(QLatin1String("@value"));
QString valueString = QString::number(value, 'f', series->labelsPrecision());
QString valueLabel;
if (series->labelsFormat().isEmpty()) {
valueLabel = valueString;
} else {
valueLabel = series->labelsFormat();
valueLabel.replace(valueTag, valueString);
}
return valueLabel;
}
void BarsRenderer::positionLabelItem(QBarSeries *series, QQuickText *textItem, const BarSeriesData &d)
{
auto pos = series->labelsPosition();
const bool vertical = m_graph->orientation() == Qt::Orientation::Vertical;
const float w = textItem->contentWidth() + series->labelsMargin() * 2;
const float h = textItem->contentHeight() + series->labelsMargin() * 2;
textItem->setWidth(w);
textItem->setHeight(h);
textItem->setHAlign(QQuickText::HAlignment::AlignHCenter);
textItem->setVAlign(QQuickText::VAlignment::AlignVCenter);
if (pos == QBarSeries::LabelsPosition::Center) {
textItem->setX(d.rect.x() + d.rect.width() * 0.5 - w * 0.5);
textItem->setY(d.rect.y() + d.rect.height() * 0.5 - h * 0.5);
} else if (pos == QBarSeries::LabelsPosition::InsideEnd) {
if (vertical) {
textItem->setX(d.rect.x() + d.rect.width() * 0.5 - w * 0.5);
textItem->setY(d.rect.y());
} else {
textItem->setX(d.rect.x() + d.rect.width() - w);
textItem->setY(d.rect.y() + d.rect.height() * 0.5 - h * 0.5);
}
} else if (pos == QBarSeries::LabelsPosition::InsideBase) {
if (vertical) {
textItem->setX(d.rect.x() + d.rect.width() * 0.5 - w * 0.5);
textItem->setY(d.rect.y() + d.rect.height() - h);
} else {
textItem->setX(d.rect.x());
textItem->setY(d.rect.y() + d.rect.height() * 0.5 - h * 0.5);
}
} else {
// OutsideEnd
if (vertical) {
textItem->setX(d.rect.x() + d.rect.width() * 0.5 - w * 0.5);
textItem->setY(d.rect.y() - h);
} else {
textItem->setX(d.rect.x() + d.rect.width());
textItem->setY(d.rect.y() + d.rect.height() * 0.5 - h * 0.5);
}
}
textItem->update();
}
void BarsRenderer::updateComponents(QBarSeries *series)
{
int barIndex = 0;
auto &seriesData = m_seriesData[series];
auto &barItems = m_barItems[series];
Q_TRACE(QGraphs2DBarsRendererUpdateComponents_entry);
for (auto i = seriesData.cbegin(), end = seriesData.cend(); i != end; ++i) {
if (barItems.size() <= barIndex) {
QQuickItem *item = nullptr;
// Create more components as needed
if (series->barDelegate()) {
item = qobject_cast<QQuickItem *>(
series->barDelegate()->create(series->barDelegate()->creationContext()));
}
if (!item)
item = new QQuickRectangle();
item->setParent(this);
item->setParentItem(this);
barItems << item;
}
if (barItems.size() > barIndex) {
BarSeriesData d = *i;
if (series->barDelegate()) {
// Set custom bar components
auto &barItem = barItems[barIndex];
barItem->setX(d.rect.x());
barItem->setY(d.rect.y());
barItem->setZ(series->zValue());
barItem->setWidth(d.rect.width());
barItem->setHeight(d.rect.height());
barItem->setVisible(series->isVisible());
// Check for specific dynamic properties
if (barItem->property(TAG_BAR_COLOR).isValid())
barItem->setProperty(TAG_BAR_COLOR, d.color);
if (barItem->property(TAG_BAR_BORDER_COLOR).isValid())
barItem->setProperty(TAG_BAR_BORDER_COLOR, d.borderColor);
if (barItem->property(TAG_BAR_BORDER_WIDTH).isValid())
barItem->setProperty(TAG_BAR_BORDER_WIDTH, d.borderWidth);
if (barItem->property(TAG_BAR_SELECTED).isValid())
barItem->setProperty(TAG_BAR_SELECTED, d.isSelected);
if (barItem->property(TAG_BAR_VALUE).isValid())
barItem->setProperty(TAG_BAR_VALUE, d.value);
if (barItem->property(TAG_BAR_LABEL).isValid())
barItem->setProperty(TAG_BAR_LABEL, d.label);
if (barItem->property(TAG_BAR_INDEX).isValid())
barItem->setProperty(TAG_BAR_INDEX, barIndex);
} else {
if (!m_graph->useCanvasPainter()) {
// Set default rectangle bars
auto barItem = qobject_cast<QQuickRectangle *>(barItems[barIndex]);
if (barItem) {
barItem->setX(d.rect.x());
barItem->setY(d.rect.y());
barItem->setZ(series->zValue());
barItem->setWidth(d.rect.width());
barItem->setHeight(d.rect.height());
barItem->setVisible(series->isVisible());
barItem->setColor(d.color);
barItem->border()->setColor(d.borderColor);
barItem->border()->setWidth(d.borderWidth);
barItem->setRadius(4.0);
}
}
}
}
barIndex++;
}
Q_TRACE(QGraphs2DBarsRendererUpdateComponents_exit);
}
void BarsRenderer::updateValueLabels(QBarSeries *series)
{
if (!series->barDelegate() && series->isVisible() && series->labelsVisible()) {
// Update default value labels
int barIndex = 0;
auto &seriesData = m_seriesData[series];
auto &labelTextItems = m_labelTextItems[series];
Q_TRACE(QGraphs2DBarsRendererUpdateValueLabels_entry);
for (auto i = seriesData.cbegin(), end = seriesData.cend(); i != end; ++i) {
if (labelTextItems.size() <= barIndex) {
// Create more label items as needed
auto labelItem = new QQuickText(this);
labelTextItems << labelItem;
}
if (labelTextItems.size() > barIndex) {
// Set label item values
auto &textItem = labelTextItems[barIndex];
const auto d = *i;
if (qFuzzyIsNull(d.value)) {
textItem->setVisible(false);
} else {
textItem->setVisible(series->labelsVisible());
QString valueLabel = generateLabelText(series, d.value);
textItem->setText(valueLabel);
positionLabelItem(series, textItem, d);
QColor labelColor = d.labelColor;
if (labelColor.alpha() == 0) {
// TODO: Use graphs theme labels color.
labelColor = QColor(255, 255, 255);
}
textItem->setColor(labelColor);
textItem->setRotation(series->labelsAngle());
}
}
barIndex++;
}
Q_TRACE(QGraphs2DBarsRendererUpdateValueLabels_exit);
} else {
// Hide all possibly existing label items
auto &labelTextItems = m_labelTextItems[series];
for (auto textItem : labelTextItems)
textItem->setVisible(false);
}
}
void calculateCategoryTotalValues(QBarSeries *series, QList<float> &totalValues, qsizetype valuesPerSet)
{
totalValues.fill(0, valuesPerSet);
auto barsets = series->barSets();
for (auto s : std::as_const(barsets)) {
QVariantList v = s->values();
int setIndex = 0;
for (const auto &variantValue : std::as_const(v)) {
if (setIndex < totalValues.size())
totalValues[setIndex] += qAbs(variantValue.toReal());
setIndex++;
}
}
}
void BarsRenderer::updateVerticalBars(QBarSeries *series, qsizetype setCount,
qsizetype valuesPerSet, int barSeriesIndex,
int barSeriesCount)
{
bool stacked = series->barsType() == QBarSeries::BarsType::Stacked
|| series->barsType() == QBarSeries::BarsType::StackedPercent;
bool percent = series->barsType() == QBarSeries::BarsType::StackedPercent;
// Bars area width & height
float w = width();
float h = height();
// Max width of a bar if no separation between sets.
float maxBarWidth = w / (setCount * valuesPerSet * barSeriesCount) - m_barMargin;
if (stacked)
maxBarWidth = w / (valuesPerSet * barSeriesCount);
// Actual bar width.
float barWidth = maxBarWidth * series->barWidth();
// Helper to keep barsets centered when bar width is less than max width.
float barCentering = (maxBarWidth - barWidth) * setCount * 0.5;
if (stacked)
barCentering = (maxBarWidth - barWidth) * 0.5;
const float barSeriesOffset = ((float)barSeriesIndex / (valuesPerSet * barSeriesCount)) * w;
auto &seriesData = m_seriesData[series];
auto &rectNodesInputRects = m_rectNodesInputRects[series];
// Clear the selection rects
// These will be filled only if series is selectable
rectNodesInputRects.clear();
seriesData.clear();
float seriesPos = barSeriesOffset;
float posXInSet = 0;
QList<float> positiveYListInSet;
QList<float> negativeYListInSet;
if (stacked) {
positiveYListInSet.fill(0, valuesPerSet);
negativeYListInSet.fill(0, valuesPerSet);
}
QList<float> totalValuesListInSet;
if (percent)
calculateCategoryTotalValues(series, totalValuesListInSet, valuesPerSet);
int barIndexInSet = 0;
int barSetIndex = 0;
QList<QLegendData> legendDataList;
auto barsets = series->barSets();
Q_TRACE(QGraphs2DBarsRendererUpdateVerticalBars_entry, setCount, valuesPerSet, barSeriesCount);
for (auto s : std::as_const(barsets)) {
QVariantList v = s->values();
qsizetype valuesCount = v.size();
if (valuesCount == 0)
continue;
seriesPos = barSeriesOffset;
barIndexInSet = 0;
BarSelectionRect *barSelectionRect = nullptr;
if (series->isSelectable() || series->isHoverable()) {
rectNodesInputRects << BarSelectionRect();
barSelectionRect = &rectNodesInputRects.last();
barSelectionRect->barSet = s;
barSelectionRect->series = series;
}
auto &axisY = m_graph->m_axisRenderer->getAxisY(series);
QColor color = getSetColor(series, s, barSetIndex);
QColor borderColor = getSetBorderColor(series, s, barSetIndex);
qreal borderWidth = getSetBorderWidth(series, s);
// Update legendData
legendDataList.push_back({
color,
borderColor,
s->label()
});
// Apply series opacity
color.setAlpha(color.alpha() * series->opacity());
borderColor.setAlpha(borderColor.alpha() * series->opacity());
const auto selectedBars = s->selectedBars();
for (const auto &variantValue : std::as_const(v)) {
const float realValue = variantValue.toReal();
float value = realValue * series->valuesMultiplier();
if (percent) {
if (auto totalValue = totalValuesListInSet.at(barIndexInSet))
value *= (100.0 / totalValue);
}
const bool isSelected = selectedBars.contains(barIndexInSet);
double delta = axisY.maxValue - axisY.minValue;
double maxValues = delta > 0 ? 1.0 / delta : 100.0;
float barLength = qAbs(h * value * maxValues);
float barY = h - barLength;
float barX = seriesPos + posXInSet + barCentering;
float offset = h * (- axisY.minValue) * series->valuesMultiplier() * maxValues;
if (stacked) {
barX = seriesPos + barCentering;
if (value >= 0)
barY = h - barLength - offset - positiveYListInSet[barIndexInSet];
else
barY = h - offset + negativeYListInSet[barIndexInSet];
} else {
barY -= offset;
if (value < 0)
barY += barLength;
}
QRectF barRect(barX, barY, barWidth, barLength);
if (barSelectionRect)
barSelectionRect->rects << barRect;
// Collect the series data
BarSeriesData d;
d.rect = barRect;
d.color = isSelected ? getSetSelectedColor(series, s) : color;
d.borderColor = borderColor;
d.borderWidth = borderWidth;
d.isSelected = isSelected;
d.label = s->label();
d.labelColor = s->labelColor();
d.value = realValue;
seriesData << d;
if (stacked) {
if (value >= 0)
positiveYListInSet[barIndexInSet] += barLength;
else
negativeYListInSet[barIndexInSet] += barLength;
}
barIndexInSet++;
seriesPos = ((float)barIndexInSet / valuesPerSet) * w + barSeriesOffset;
}
posXInSet += barWidth + m_barMargin;
barSetIndex++;
}
Q_TRACE(QGraphs2DBarsRendererUpdateVerticalBars_exit);
series->d_func()->setLegendData(legendDataList);
}
void BarsRenderer::updateHorizontalBars(QBarSeries *series, qsizetype setCount,
qsizetype valuesPerSet, int barSeriesIndex,
int barSeriesCount)
{
bool stacked = series->barsType() == QBarSeries::BarsType::Stacked
|| series->barsType() == QBarSeries::BarsType::StackedPercent;
bool percent = series->barsType() == QBarSeries::BarsType::StackedPercent;
// Bars area width & height
float w = width();
float h = height();
// Max width of a bar if no separation between sets.
float maxBarWidth = h / (setCount * valuesPerSet * barSeriesCount) - m_barMargin;
if (stacked)
maxBarWidth = h / (valuesPerSet * barSeriesCount);
// Actual bar width.
float barWidth = maxBarWidth * series->barWidth();
// Helper to keep barsets centered when bar width is less than max width.
float barCentering = (maxBarWidth - barWidth) * setCount * 0.5;
if (stacked)
barCentering = (maxBarWidth - barWidth) * 0.5;
const float barSeriesOffset = ((float)barSeriesIndex / (valuesPerSet * barSeriesCount)) * h;
auto &seriesData = m_seriesData[series];
auto &rectNodesInputRects = m_rectNodesInputRects[series];
// Clear the selection rects
// These will be filled only if series is selectable
rectNodesInputRects.clear();
seriesData.clear();
float seriesPos = barSeriesOffset;
float posYInSet = 0;
QList<float> positiveXListInSet;
QList<float> negativeXListInSet;
if (stacked) {
positiveXListInSet.fill(0, valuesPerSet);
negativeXListInSet.fill(0, valuesPerSet);
}
QList<float> totalValuesListInSet;
if (percent)
calculateCategoryTotalValues(series, totalValuesListInSet, valuesPerSet);
int barIndexInSet = 0;
int barSetIndex = 0;
QList<QLegendData> legendDataList;
auto barsets = series->barSets();
Q_TRACE(QGraphs2DBarsRendererUpdateupdateHorizontalBars_entry, setCount, valuesPerSet, barSeriesCount);
for (auto s : std::as_const(barsets)) {
QVariantList v = s->values();
qsizetype valuesCount = v.size();
if (valuesCount == 0)
continue;
seriesPos = barSeriesOffset;
barIndexInSet = 0;
BarSelectionRect *barSelectionRect = nullptr;
if (series->isSelectable() || series->isHoverable()) {
rectNodesInputRects << BarSelectionRect();
barSelectionRect = &rectNodesInputRects.last();
barSelectionRect->barSet = s;
barSelectionRect->series = series;
}
auto &axisX = m_graph->m_axisRenderer->getAxisX(series);
QColor color = getSetColor(series, s, barSetIndex);
QColor borderColor = getSetBorderColor(series, s, barSetIndex);
qreal borderWidth = getSetBorderWidth(series, s);
// Update legendData
legendDataList.push_back({
color,
borderColor,
s->label()
});
// Apply series opacity
color.setAlpha(color.alpha() * series->opacity());
borderColor.setAlpha(borderColor.alpha() * series->opacity());
const auto selectedBars = s->selectedBars();
for (const auto &variantValue : std::as_const(v)) {
const float realValue = variantValue.toReal();
float value = realValue * series->valuesMultiplier();
if (percent) {
if (auto totalValue = totalValuesListInSet.at(barIndexInSet))
value *= (100.0 / totalValue);
}
const bool isSelected = selectedBars.contains(barIndexInSet);
double delta = axisX.maxValue - axisX.minValue;
double maxValues = delta > 0 ? 1.0 / delta : 100.0;
float barLength = qAbs(w * value * maxValues);
float barY = seriesPos + posYInSet + barCentering;
float barX = 0;
float offset = w * (- axisX.minValue) * series->valuesMultiplier() * maxValues;
if (stacked) {
barY = seriesPos + barCentering;
if (value >= 0)
barX = offset + positiveXListInSet[barIndexInSet];
else
barX = offset - barLength - negativeXListInSet[barIndexInSet];
} else {
barX += offset;
if (value < 0)
barX -= barLength;
}
QRectF barRect(barX, barY, barLength, barWidth);
if (barSelectionRect)
barSelectionRect->rects << barRect;
// Collect the series data
BarSeriesData d;
d.rect = barRect;
d.color = isSelected ? getSetSelectedColor(series, s) : color;
d.borderColor = borderColor;
d.borderWidth = borderWidth;
d.isSelected = isSelected;
d.label = s->label();
d.labelColor = s->labelColor();
d.value = realValue;
seriesData << d;
if (stacked) {
if (value >= 0)
positiveXListInSet[barIndexInSet] += barLength;
else
negativeXListInSet[barIndexInSet] += barLength;
}
barIndexInSet++;
seriesPos = ((float)barIndexInSet / valuesPerSet) * h + barSeriesOffset;
}
posYInSet += barWidth + m_barMargin;
barSetIndex++;
}
Q_TRACE(QGraphs2DBarsRendererUpdateVerticalBars_exit);
series->d_func()->setLegendData(legendDataList);
}
void BarsRenderer::handlePolish(QBarSeries *series, int barSeriesIndex, int barSeriesCount)
{
auto theme = m_graph->theme();
if (!theme) {
qCCritical(lcCritical2D, "Theme not found.");
return;
}
if (!m_graph->m_axisRenderer) {
qCCritical(lcCritical2D, "Axis renderer not found.");
return;
}
qsizetype setCount = series->barSets().size();
auto &seriesData = m_seriesData[series];
auto &barItems = m_barItems[series];
auto &rectNodesInputRects = m_rectNodesInputRects[series];
if (setCount == 0) {
for (int i = 0; i < barItems.size(); i++)
barItems[i]->deleteLater();
barItems.clear();
series->d_func()->clearLegendData();
rectNodesInputRects.clear();
seriesData.clear();
return;
}
if (m_colorIndex < 0)
m_colorIndex = m_graph->graphSeriesCount();
m_graph->setGraphSeriesCount(m_colorIndex + setCount);
if (series->barDelegateDirty() && !barItems.isEmpty()) {
// Bars delegate has changed, so remove the old items.
for (int i = 0; i < barItems.size(); i++)
barItems[i]->deleteLater();
barItems.clear();
series->setBarDelegateDirty(false);
}
// Get bars values
qsizetype valuesPerSet = series->barSets().constFirst()->values().size();
if (m_graph->orientation() == Qt::Orientation::Vertical)
updateVerticalBars(series, setCount, valuesPerSet, barSeriesIndex, barSeriesCount);
else
updateHorizontalBars(series, setCount, valuesPerSet, barSeriesIndex, barSeriesCount);
updateComponents(series);
updateValueLabels(series);
// Remove additional components
for (qsizetype i = barItems.size() - 1; i >= seriesData.size(); --i)
barItems[i]->deleteLater();
const auto range = barItems.size() - seriesData.size();
if (range > 0)
barItems.remove(seriesData.size(), range);
}
void BarsRenderer::updateSeries(QBarSeries *series)
{
Q_UNUSED(series);
}
void BarsRenderer::seriesAboutToBeRemoved(QAbstractSeries *series)
{
if (auto *barsSeries = qobject_cast<QBarSeries *>(series)) {
auto barItemsIter = m_barItems.find(barsSeries);
if (barItemsIter != m_barItems.end()) {
// Remove custom bar items
auto &barItems = *barItemsIter;
for (const auto &item : std::as_const(barItems))
item->deleteLater();
barItems.clear();
m_barItems.erase(barItemsIter);
}
auto textItemsIter = m_labelTextItems.find(barsSeries);
if (textItemsIter != m_labelTextItems.end()) {
// Remove bar label items
auto &labelTextItems = *textItemsIter;
for (const auto &item : std::as_const(labelTextItems))
item->deleteLater();
labelTextItems.clear();
m_labelTextItems.erase(textItemsIter);
}
if (barsSeries && barsSeries == m_currentHoverSeries)
m_currentHoverSeries = nullptr;
if (barsSeries && barsSeries == m_currentHoverBarSeries) {
m_currentHoverBarSeries = nullptr;
m_currentHoverBarSet = nullptr;
m_currentHoverBarIndex = -1;
}
}
}
void BarsRenderer::afterUpdate(QList<QAbstractSeries *> &cleanupSeries)
{
Q_UNUSED(cleanupSeries);
}
void BarsRenderer::afterPolish(QList<QAbstractSeries *> &cleanupSeries)
{
Q_UNUSED(cleanupSeries);
}
void BarsRenderer::handleBarSetsRemoved(const QList<QBarSet *> &sets)
{
if (m_currentHoverBarSet && sets.contains(m_currentHoverBarSet)) {
if (m_currentHoverBarSeries)
emit m_currentHoverBarSeries->hovered(false, m_currentHoverBarIndex, m_currentHoverBarSet);
m_currentHoverBarSeries = nullptr;
m_currentHoverBarSet = nullptr;
m_currentHoverBarIndex = -1;
}
}
bool BarsRenderer::handleHoverMove(QHoverEvent *event)
{
bool handled = false;
const QPointF &position = event->position();
bool hovering = false;
QBarSeries *hoveredSeries = nullptr;
QBarSet *hoveredBarSet = nullptr;
qsizetype hoveredBarIndex = -1;
for (auto &rectNodesInputRects : m_rectNodesInputRects) {
for (auto &barSelection : rectNodesInputRects) {
int indexInSet = 0;
for (auto &rect : barSelection.rects) {
if (rect.contains(event->position().toPoint())) {
const QString &name = barSelection.series->name();
const QPointF point(indexInSet, barSelection.barSet->at(indexInSet));
if (!m_currentHoverSeries) {
m_currentHoverSeries = barSelection.series;
barSelection.series->setHovered(true);
emit barSelection.series->hoverEnter(name, position, point);
}
emit barSelection.series->hover(name, position, point);
hovering = true;
handled = true;
hoveredSeries = barSelection.series;
hoveredBarSet = barSelection.barSet;
hoveredBarIndex = indexInSet;
}
indexInSet++;
}
}
}
if (hoveredBarSet != m_currentHoverBarSet || hoveredBarIndex != m_currentHoverBarIndex) {
if (m_currentHoverBarSet)
emit m_currentHoverBarSeries->hovered(false, m_currentHoverBarIndex, m_currentHoverBarSet);
if (hoveredBarSet)
emit hoveredSeries->hovered(true, hoveredBarIndex, hoveredBarSet);
m_currentHoverBarSeries = hoveredSeries;
m_currentHoverBarSet = hoveredBarSet;
m_currentHoverBarIndex = hoveredBarIndex;
handled = true;
}
if (!hovering && m_currentHoverSeries) {
m_currentHoverSeries->setHovered(false);
emit m_currentHoverSeries->hoverExit(m_currentHoverSeries->name(), position);
m_currentHoverSeries = nullptr;
handled = true;
}
return handled;
}
void BarsRenderer::onSingleTapped(QEventPoint eventPoint, Qt::MouseButton button)
{
Q_UNUSED(button)
for (auto &rectNodesInputRects : m_rectNodesInputRects) {
for (auto &barSelection : rectNodesInputRects) {
if (!barSelection.series->isSelectable())
continue;
qsizetype indexInSet = 0;
for (auto &rect : barSelection.rects) {
if (rect.contains(eventPoint.position())) {
emit barSelection.series->clicked(indexInSet, barSelection.barSet);
return;
}
indexInSet++;
}
}
}
}
void BarsRenderer::onDoubleTapped(QEventPoint eventPoint, Qt::MouseButton button)
{
Q_UNUSED(button)
for (auto &rectNodesInputRects : m_rectNodesInputRects) {
for (auto &barSelection : rectNodesInputRects) {
if (!barSelection.series->isSelectable())
continue;
qsizetype indexInSet = 0;
for (auto &rect : barSelection.rects) {
if (rect.contains(eventPoint.position())) {
emit barSelection.series->doubleClicked(indexInSet, barSelection.barSet);
return;
}
indexInSet++;
}
}
}
}
void BarsRenderer::onPressedChanged()
{
for (auto &rectNodesInputRects : m_rectNodesInputRects) {
for (auto &barSelection : rectNodesInputRects) {
if (!barSelection.series->isSelectable())
continue;
qsizetype indexInSet = 0;
for (auto &rect : barSelection.rects) {
if (rect.contains(m_tapHandler->point().position())) {
// TODO: Currently just toggling selection
if (m_tapHandler->isPressed()) {
QList<qsizetype> indexList = {indexInSet};
barSelection.barSet->toggleSelection(indexList);
emit barSelection.series->pressed(indexInSet, barSelection.barSet);
} else {
emit barSelection.series->released(indexInSet, barSelection.barSet);
}
}
indexInSet++;
}
}
}
}
QT_END_NAMESPACE
#include "moc_barsrenderer_p.cpp"