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 "graphs3d/utils/qgraphs3dlogging_p.h"
#include "q3dscene.h"
#include "qgraphsinputhandler_p.h"
#include "qquickgraphsscatter_p.h"
#include "qquickgraphstexturedata_p.h"
#include "qscatter3dseries.h"
#include "qscatter3dseries_p.h"
#include "qscatterdataproxy_p.h"
#include "qvalue3daxis_p.h"
#include "qgraphs3dlogging_p.h"
#include <QColor>
#include <QtQuick3D/private/qquick3dcustommaterial_p.h>
#include <QtQuick3D/private/qquick3ddirectionallight_p.h>
#include <QtQuick3D/private/qquick3dmodel_p.h>
#include <QtQuick3D/private/qquick3dperspectivecamera_p.h>
#include <QtQuick3D/private/qquick3dpointlight_p.h>
#include <QtQuick3D/private/qquick3dprincipledmaterial_p.h>
#include <QtQuick3D/private/qquick3drepeater_p.h>
#include <QtGui/qquaternion.h>
#include <qtgraphs_tracepoints_p.h>
QT_BEGIN_NAMESPACE
Q_TRACE_PREFIX(qtgraphs,
"QT_BEGIN_NAMESPACE" \
"class QQuickGraphsScatter;" \
"QT_END_NAMESPACE"
)
Q_TRACE_POINT(qtgraphs, QGraphs3DScatterPointDataUpdate_entry);
Q_TRACE_POINT(qtgraphs, QGraphs3DScatterPointDataUpdate_exit);
Q_TRACE_POINT(qtgraphs, QGraphs3DScatterPointSelectionUpdate_entry);
Q_TRACE_POINT(qtgraphs, QGraphs3DScatterPointSelectionUpdate_exit);
Q_TRACE_POINT(qtgraphs, QGraphs3DScatterDoPicking_entry);
Q_TRACE_POINT(qtgraphs, QGraphs3DScatterDoPicking_exit);
Q_TRACE_POINT(qtgraphs, QGraphs3DScatterDoRayPicking_entry);
Q_TRACE_POINT(qtgraphs, QGraphs3DScatterDoRayPicking_exit);
Q_TRACE_POINT(qtgraphs, QGraphs3DScatterLegacyModelsCountUpdate_entry);
Q_TRACE_POINT(qtgraphs, QGraphs3DScatterLegacyModelsCountUpdate_exit);
Q_TRACE_POINT(qtgraphs, QGraphs3DScatterLegacyGeneratePointsForScatterModel_entry, int itemCount);
Q_TRACE_POINT(qtgraphs, QGraphs3DScatterLegacyGeneratePointsForScatterModel_exit);
Q_TRACE_POINT(qtgraphs, QGraphs3DScatterLegacyModelsPositionUpdate_entry);
Q_TRACE_POINT(qtgraphs, QGraphs3DScatterLegacyModelsPositionUpdate_exit);
Q_TRACE_POINT(qtgraphs, QGraphs3DScatterDefaultModelsPositionUpdate_entry);
Q_TRACE_POINT(qtgraphs, QGraphs3DScatterDefaultModelsPositionUpdate_exit);
Q_TRACE_POINT(qtgraphs, QGraphs3DScatterDefaultGeneratePointsForScatterModel_entry);
Q_TRACE_POINT(qtgraphs, QGraphs3DScatterDefaultGeneratePointsForScatterModel_exit);
Q_TRACE_POINT(qtgraphs, QGraphs3DScatterSplineUpdate_entry);
Q_TRACE_POINT(qtgraphs, QGraphs3DScatterSplineUpdate_exit);
static const int insertRemoveRecordReserveSize = 31;
/*!
* \qmltype Scatter3D
* \inherits GraphsItem3D
* \inqmlmodule QtGraphs
* \ingroup graphs_qml_3D
* \brief 3D scatter graph.
*
* This type enables developers to render scatter graphs in 3D with Qt Quick.
*
* You will need to import Qt Graphs module to use this type:
*
* \snippet doc_src_qmlgraphs.cpp 0
*
* After that you can use Scatter3D in your qml files:
*
* \snippet doc_src_qmlgraphs.cpp 2
*
* See \l{Simple Scatter Graph} for more thorough usage example.
*
* \sa Scatter3DSeries, Spline3DSeries, ScatterDataProxy, Bars3D, Surface3D,
* {Qt Graphs C++ Classes for 3D}
*/
/*!
* \qmlproperty Value3DAxis Scatter3D::axisX
* The active x-axis.
*
* If an axis is not given, a temporary default axis with no labels and an
* automatically adjusting range is created.
* This temporary axis is destroyed if another axis is explicitly set to the
* same orientation.
*/
/*!
* \qmlproperty Value3DAxis Scatter3D::axisY
* The active y-axis.
*
* If an axis is not given, a temporary default axis with no labels and an
* automatically adjusting range is created.
* This temporary axis is destroyed if another axis is explicitly set to the
* same orientation.
*/
/*!
* \qmlproperty Value3DAxis Scatter3D::axisZ
* The active z-axis.
*
* If an axis is not given, a temporary default axis with no labels and an
* automatically adjusting range is created.
* This temporary axis is destroyed if another axis is explicitly set to the
* same orientation.
*/
/*!
* \qmlproperty Scatter3DSeries Scatter3D::selectedSeries
* \readonly
*
* The selected series or null.
*/
/*!
* \qmlproperty list<Scatter3DSeries> Scatter3D::seriesList
* \qmldefault
* This property holds the series of the graph.
* By default, this property contains an empty list.
* To set the series, either use the addSeries() method or define them as
* children of the graph.
*/
/*!
* \qmlmethod void Scatter3D::addSeries(Scatter3DSeries series)
* Adds the \a series to the graph. A graph can contain multiple series, but has
* only one set of axes. If the newly added series has specified a selected
* item, it will be highlighted and any existing selection will be cleared. Only
* one added series can have an active selection.
* \sa GraphsItem3D::hasSeries()
*/
/*!
* \qmlmethod void Scatter3D::removeSeries(Scatter3DSeries series)
* Remove the \a series from the graph.
* \sa GraphsItem3D::hasSeries()
*/
/*!
* \qmlsignal Scatter3D::axisXChanged(ValueAxis3D axis)
*
* This signal is emitted when axisX changes to \a axis.
*/
/*!
* \qmlsignal Scatter3D::axisYChanged(ValueAxis3D axis)
*
* This signal is emitted when axisY changes to \a axis.
*/
/*!
* \qmlsignal Scatter3D::axisZChanged(ValueAxis3D axis)
*
* This signal is emitted when axisZ changes to \a axis.
*/
/*!
* \qmlsignal Scatter3D::selectedSeriesChanged(Scatter3DSeries series)
*
* This signal is emitted when selectedSeries changes to \a series.
*/
QQuickGraphsScatter::QQuickGraphsScatter(QQuickItem *parent)
: QQuickGraphsItem(parent)
{
m_graphType = QAbstract3DSeries::SeriesType::Scatter;
setAxisX(0);
setAxisY(0);
setAxisZ(0);
setAcceptedMouseButtons(Qt::AllButtons);
setFlag(ItemHasContents);
clearSelection();
}
QQuickGraphsScatter::~QQuickGraphsScatter()
{
for (auto &graphModel : m_scatterGraphs) {
delete graphModel;
}
}
void QQuickGraphsScatter::setAxisX(QValue3DAxis *axis)
{
QQuickGraphsItem::setAxisX(axis);
}
QValue3DAxis *QQuickGraphsScatter::axisX() const
{
return static_cast<QValue3DAxis *>(QQuickGraphsItem::axisX());
}
void QQuickGraphsScatter::setAxisY(QValue3DAxis *axis)
{
QQuickGraphsItem::setAxisY(axis);
}
QValue3DAxis *QQuickGraphsScatter::axisY() const
{
return static_cast<QValue3DAxis *>(QQuickGraphsItem::axisY());
}
void QQuickGraphsScatter::setAxisZ(QValue3DAxis *axis)
{
QQuickGraphsItem::setAxisZ(axis);
}
QValue3DAxis *QQuickGraphsScatter::axisZ() const
{
return static_cast<QValue3DAxis *>(QQuickGraphsItem::axisZ());
}
void QQuickGraphsScatter::disconnectSeries(QScatter3DSeries *series)
{
QObject::disconnect(series, 0, this, 0);
}
void QQuickGraphsScatter::generatePointsForScatterModel(ScatterModel *graphModel)
{
QList<QQuick3DModel *> itemList;
if (optimizationHint() == QtGraphs3D::OptimizationHint::Legacy) {
qsizetype itemCount = graphModel->series->dataProxy()->itemCount();
Q_TRACE_SCOPE(QGraphs3DScatterLegacyGeneratePointsForScatterModel, static_cast<int>(itemCount));
if (graphModel->series->dataProxy()->itemCount() > 0)
itemList.resize(itemCount);
for (int i = 0; i < itemCount; i++) {
QQuick3DModel *item = createDataItem(graphModel->series);
item->setPickable(true);
item->setParent(graphModel->series);
itemList[i] = item;
}
graphModel->dataItems = itemList;
markDataDirty();
} else if (optimizationHint() == QtGraphs3D::OptimizationHint::Default) {
Q_TRACE_SCOPE(QGraphs3DScatterDefaultGeneratePointsForScatterModel);
graphModel->instancingRootItem = createDataItem(graphModel->series);
graphModel->instancingRootItem->setParent(graphModel->series);
graphModel->instancingRootItem->setInstancing(graphModel->instancing);
if (selectionMode() != QtGraphs3D::SelectionFlag::None) {
graphModel->selectionIndicator = createDataItem(graphModel->series);
graphModel->instancingRootItem->setPickable(true);
}
}
markSeriesVisualsDirty();
}
qsizetype QQuickGraphsScatter::getItemIndex(QQuick3DModel *item)
{
Q_UNUSED(item);
if (optimizationHint() == QtGraphs3D::OptimizationHint::Legacy)
return 0;
return -1;
}
void QQuickGraphsScatter::clearSelection()
{
clearSelectionModel();
}
void QQuickGraphsScatter::updateScatterGraphItemPositions(ScatterModel *graphModel)
{
float itemSize = graphModel->series->itemSize() / m_itemScaler;
QQuaternion meshRotation = graphModel->series->meshRotation();
QScatterDataProxy *dataProxy = graphModel->series->dataProxy();
QList<QQuick3DModel *> itemList = graphModel->dataItems;
QValue3DAxis *valueAxisX = graphModel->series->axisX();
QValue3DAxis *valueAxisY = graphModel->series->axisY();
QValue3DAxis *valueAxisZ = graphModel->series->axisZ();
if (!valueAxisX)
valueAxisX = static_cast<QValue3DAxis *>(axisX());
if (!valueAxisY)
valueAxisY = static_cast<QValue3DAxis *>(axisY());
if (!valueAxisZ)
valueAxisZ = static_cast<QValue3DAxis *>(axisZ());
bool xReversed = valueAxisX->reversed();
bool yReversed = valueAxisY->reversed();
bool zReversed = valueAxisZ->reversed();
if (itemSize == 0.0f)
itemSize = m_pointScale;
if (optimizationHint() == QtGraphs3D::OptimizationHint::Legacy) {
if (dataProxy->itemCount() != itemList.size()) {
qCWarning(lcProperties3D, "%ls Item count differs from itemList count",
qUtf16Printable(QString::fromUtf8(__func__)));
}
Q_TRACE_SCOPE(QGraphs3DScatterLegacyModelsPositionUpdate);
for (int i = 0; i < dataProxy->itemCount(); ++i) {
const QScatterDataItem item = dataProxy->itemAt(i);
QQuick3DModel *dataPoint = itemList.at(i);
QVector3D dotPos = item.position();
if (isDotPositionInAxisRange(dotPos, graphModel->series)) {
dataPoint->setVisible(true);
QQuaternion dotRot = item.rotation();
float dotPosX = xReversed ? 1.0f - valueAxisX->positionAt(dotPos.x())
: valueAxisX->positionAt(dotPos.x());
float dotPosY = yReversed ? 1.0f - valueAxisY->positionAt(dotPos.y())
: valueAxisY->positionAt(dotPos.y());
float dotPosZ = zReversed ? 1.0f - valueAxisZ->positionAt(dotPos.z())
: valueAxisZ->positionAt(dotPos.z());
float posX = dotPosX * scale().x() + translate().x();
float posY = dotPosY * scale().y() + translate().y();
float posZ = dotPosZ * scale().z() + translate().z();
dataPoint->setPosition(QVector3D(posX, posY, posZ));
QQuaternion totalRotation;
if (graphModel->series->mesh() != QAbstract3DSeries::Mesh::Point)
totalRotation = dotRot * meshRotation;
else
totalRotation = cameraTarget()->rotation();
dataPoint->setRotation(totalRotation);
dataPoint->setScale(QVector3D(itemSize, itemSize, itemSize)
* dataProxy->scaleAt(i));
} else {
dataPoint->setVisible(false);
}
}
} else if (optimizationHint() == QtGraphs3D::OptimizationHint::Default) {
qsizetype count = dataProxy->itemCount();
QList<DataItemHolder> positions;
Q_TRACE_SCOPE(QGraphs3DScatterDefaultModelsPositionUpdate);
for (int i = 0; i < count; i++) {
const QScatterDataItem &item = dataProxy->itemAt(i);
QVector3D dotPos = item.position();
if (isDotPositionInAxisRange(dotPos, graphModel->series)) {
float dotPosX = xReversed ? 1.0f - valueAxisX->positionAt(dotPos.x())
: valueAxisX->positionAt(dotPos.x());
float dotPosY = yReversed ? 1.0f - valueAxisY->positionAt(dotPos.y())
: valueAxisY->positionAt(dotPos.y());
float dotPosZ = zReversed ? 1.0f - valueAxisZ->positionAt(dotPos.z())
: valueAxisZ->positionAt(dotPos.z());
float posX = dotPosX * scale().x() + translate().x();
float posY = dotPosY * scale().y() + translate().y();
float posZ = dotPosZ * scale().z() + translate().z();
QQuaternion totalRotation;
if (graphModel->series->mesh() != QAbstract3DSeries::Mesh::Point)
totalRotation = item.rotation() * meshRotation;
else
totalRotation = cameraTarget()->rotation();
DataItemHolder dih;
if (isPolar()) {
float x;
float z;
calculatePolarXZ(axisX()->positionAt(dotPos.x()),
axisZ()->positionAt(dotPos.z()),
x,
z);
dih.position = {x, posY, z};
} else {
dih.position = {posX, posY, posZ};
}
dih.rotation = totalRotation;
dih.scale = QVector3D(itemSize, itemSize, itemSize) * dataProxy->scaleAt(i);
positions.push_back(dih);
} else {
DataItemHolder dih;
dih.hide = true;
positions.push_back(dih);
}
}
graphModel->instancing->setDataArray(positions);
if (selectedItemInSeries(graphModel->series)) {
if (isDotPositionInAxisRange(dataProxy->itemAt(m_selectedItem).position(), graphModel->series)) {
QQuaternion totalRotation;
if (graphModel->series->mesh() != QAbstract3DSeries::Mesh::Point) {
totalRotation = graphModel->instancing->dataArray().at(m_selectedItem).rotation
* meshRotation;
} else {
totalRotation = cameraTarget()->rotation();
}
graphModel->selectionIndicator->setRotation(totalRotation);
graphModel->instancing->hideDataItem(m_selectedItem);
} else {
clearSelectionModel();
}
}
}
}
void QQuickGraphsScatter::updateScatterGraphItemVisuals(ScatterModel *graphModel)
{
bool useGradient = graphModel->series->d_func()->isUsingGradient();
bool usePoint = graphModel->series->mesh() == QAbstract3DSeries::Mesh::Point;
qsizetype itemCount = graphModel->series->dataProxy()->itemCount();
if (useGradient) {
if (!graphModel->seriesTexture) {
graphModel->seriesTexture = createTexture();
graphModel->seriesTexture->setParent(graphModel->series);
}
QLinearGradient gradient = graphModel->series->baseGradient();
auto textureData = static_cast<QQuickGraphsTextureData *>(
graphModel->seriesTexture->textureData());
textureData->createGradient(gradient);
if (!graphModel->highlightTexture) {
graphModel->highlightTexture = createTexture();
graphModel->highlightTexture->setParent(graphModel->series);
}
QLinearGradient highlightGradient = graphModel->series->singleHighlightGradient();
auto highlightTextureData = static_cast<QQuickGraphsTextureData *>(
graphModel->highlightTexture->textureData());
highlightTextureData->createGradient(highlightGradient);
} else {
if (graphModel->seriesTexture) {
graphModel->seriesTexture->deleteLater();
graphModel->seriesTexture = nullptr;
}
if (graphModel->highlightTexture) {
graphModel->highlightTexture->deleteLater();
graphModel->highlightTexture = nullptr;
}
}
bool rangeGradient = (useGradient
&& graphModel->series->d_func()->m_colorStyle
== QGraphsTheme::ColorStyle::RangeGradient)
? true
: false;
const bool shaded = graphModel->series->lightingMode()
== QAbstract3DSeries::LightingMode::Shaded;
if (optimizationHint() == QtGraphs3D::OptimizationHint::Legacy) {
// Release resources that might not have been deleted even though deleteLater had been set
if (m_customView)
m_customView->window()->releaseResources();
else
window()->releaseResources();
if (itemCount != graphModel->dataItems.size())
qCWarning(lcGraphsScatter3D, "%ls Item count differs from itemList count",
qUtf16Printable(QString::fromUtf8(__func__)));
bool transparentTexture = false;
if (graphModel->seriesTexture) {
auto textureData = static_cast<QQuickGraphsTextureData *>(
graphModel->seriesTexture->textureData());
transparentTexture = textureData->hasTransparency();
}
const bool transparency = (graphModel->series->baseColor().alphaF() < 1.0)
|| transparentTexture;
updateMaterialReference(graphModel);
QQmlListReference baseRef(graphModel->baseRef, "materials");
auto baseMat = static_cast<QQuick3DCustomMaterial *>(baseRef.at(0));
for (const auto &obj : std::as_const(graphModel->dataItems)) {
QQmlListReference matRef(obj, "materials");
matRef.clear();
matRef.append(baseMat);
}
if (m_selectedItem != invalidSelectionIndex() && graphModel->series == selectedSeries()) {
QQmlListReference selRef(graphModel->selectionRef, "materials");
auto selMat = static_cast<QQuick3DCustomMaterial *>(selRef.at(0));
QQuick3DModel *selectedItem = graphModel->dataItems.at(m_selectedItem);
QQmlListReference matRef(selectedItem, "materials");
matRef.clear();
matRef.append(selMat);
}
updateItemMaterial(graphModel->baseRef,
useGradient,
rangeGradient,
usePoint,
QStringLiteral(":/materials/ScatterMaterial"));
updateItemMaterial(graphModel->selectionRef,
useGradient,
rangeGradient,
usePoint,
QStringLiteral(":/materials/ScatterMaterial"));
updateMaterialProperties(graphModel->baseRef,
graphModel->seriesTexture,
graphModel->series->baseColor(),
transparency,
shaded);
updateMaterialProperties(graphModel->selectionRef,
graphModel->highlightTexture,
graphModel->series->singleHighlightColor(),
shaded);
} else if (optimizationHint() == QtGraphs3D::OptimizationHint::Default) {
graphModel->instancingRootItem->setVisible(true);
graphModel->instancing->setRangeGradient(rangeGradient);
if (!rangeGradient) {
bool transparentTexture = false;
if (graphModel->seriesTexture) {
auto textureData = static_cast<QQuickGraphsTextureData *>(
graphModel->seriesTexture->textureData());
transparentTexture = textureData->hasTransparency();
}
const bool transparency = (graphModel->series->baseColor().alphaF() < 1.0)
|| transparentTexture;
if (transparency)
graphModel->instancing->setTransparency(true);
else
graphModel->instancing->setTransparency(false);
updateItemMaterial(graphModel->instancingRootItem,
useGradient,
rangeGradient,
usePoint,
QStringLiteral(":/materials/ScatterMaterialInstancing"));
updateMaterialProperties(graphModel->instancingRootItem,
graphModel->seriesTexture,
graphModel->series->baseColor(),
transparency,
shaded);
} else {
auto textureData = static_cast<QQuickGraphsTextureData *>(
graphModel->seriesTexture->textureData());
if (textureData->hasTransparency())
graphModel->instancing->setTransparency(true);
else
graphModel->instancing->setTransparency(false);
updateItemMaterial(graphModel->instancingRootItem,
useGradient,
rangeGradient,
usePoint,
QStringLiteral(":/materials/ScatterMaterialInstancing"));
updateInstancedMaterialProperties(graphModel,
false,
graphModel->seriesTexture,
graphModel->highlightTexture,
textureData->hasTransparency());
const float scaleY = scaleWithBackground().y();
float rangeGradientYScaler = m_rangeGradientYHelper / scaleY;
QList<float> customData;
customData.resize(itemCount);
QList<DataItemHolder> instancingData = graphModel->instancing->dataArray();
for (int i = 0; i < instancingData.size(); i++) {
auto dih = instancingData.at(i);
float value = (dih.position.y() + scaleY) * rangeGradientYScaler;
customData[i] = value;
}
graphModel->instancing->setCustomData(customData);
}
if (selectedItemInSeries(graphModel->series)) {
// Selection indicator
if (!rangeGradient) {
updateItemMaterial(graphModel->selectionIndicator,
useGradient,
rangeGradient,
usePoint,
QStringLiteral(":/materials/ScatterMaterial"));
updateMaterialProperties(graphModel->selectionIndicator,
graphModel->highlightTexture,
graphModel->series->singleHighlightColor(),
shaded);
graphModel->selectionIndicator->setCastsShadows(!usePoint);
} else {
// Rangegradient
updateItemMaterial(graphModel->selectionIndicator,
useGradient,
rangeGradient,
usePoint,
QStringLiteral(":/materials/ScatterMaterial"));
updateInstancedMaterialProperties(graphModel,
true,
nullptr,
graphModel->highlightTexture);
graphModel->selectionIndicator->setCastsShadows(!usePoint);
}
const DataItemHolder &dih = graphModel->instancing->dataArray().at(m_selectedItem);
graphModel->selectionIndicator->setPosition(dih.position);
graphModel->selectionIndicator->setRotation(dih.rotation);
graphModel->selectionIndicator->setScale(dih.scale);
graphModel->selectionIndicator->setVisible(true);
itemLabel()->setVisible(graphModel->series->isItemLabelVisible());
graphModel->instancing->hideDataItem(m_selectedItem);
updateItemLabel(graphModel->selectionIndicator->position());
graphModel->instancing->markDataDirty();
} else if ((m_selectedItem == -1 || m_selectedItemSeries != graphModel->series)
&& graphModel->selectionIndicator) {
graphModel->selectionIndicator->setVisible(false);
}
}
}
void QQuickGraphsScatter::updateMaterialReference(ScatterModel *model)
{
if (model->baseRef == nullptr) {
model->baseRef = createDataItem(model->series);
model->baseRef->setParent(model->series);
model->baseRef->setVisible(false);
}
if (model->selectionRef == nullptr) {
model->selectionRef = createDataItem(model->series);
model->selectionRef->setParent(model->series);
model->selectionRef->setVisible(false);
}
QQmlListReference baseRef(model->baseRef, "materials");
QQmlListReference selectionRef(model->selectionRef, "materials");
const QString &materialName = QStringLiteral(":/materials/ScatterMaterial");
if (!baseRef.size()) {
auto mat = createQmlCustomMaterial(materialName);
mat->setObjectName(materialName);
mat->setParent(model->baseRef);
baseRef.append(mat);
}
if (!selectionRef.size()) {
auto mat = createQmlCustomMaterial(materialName);
mat->setObjectName(materialName + QStringLiteral("_Selection"));
mat->setParent(model->selectionRef);
selectionRef.append(mat);
}
}
void QQuickGraphsScatter::updateItemMaterial(QQuick3DModel *item,
bool useGradient,
bool rangeGradient,
bool usePoint,
const QString &materialName)
{
QQmlListReference materialsRef(item, "materials");
bool needNewMat = false;
if (!materialsRef.size()) {
needNewMat = true;
} else if (materialsRef.at(0)->objectName().contains(QStringLiteral("Instancing"))
!= materialName.contains(QStringLiteral("Instancing"))) {
needNewMat = true;
}
if (needNewMat) {
materialsRef.clear();
auto newMaterial = createQmlCustomMaterial(materialName);
newMaterial->setObjectName(materialName);
newMaterial->setParent(item);
materialsRef.append(newMaterial);
}
auto material = qobject_cast<QQuick3DCustomMaterial *>(materialsRef.at(0));
if (!useGradient)
material->setProperty("colorStyle", 0);
else if (!rangeGradient)
material->setProperty("colorStyle", 1);
else
material->setProperty("colorStyle", 2);
material->setProperty("usePoint", usePoint);
material->setProperty("rootScale", rootNode()->scale().y());
}
void QQuickGraphsScatter::updateInstancedMaterialProperties(ScatterModel *graphModel,
const bool isHighlight,
QQuick3DTexture *seriesTexture,
QQuick3DTexture *highlightTexture,
const bool transparency)
{
QQuick3DModel *model = nullptr;
if (isHighlight)
model = graphModel->selectionIndicator;
else
model = graphModel->instancingRootItem;
QQmlListReference materialsRef(model, "materials");
auto customMaterial = static_cast<QQuick3DCustomMaterial *>(materialsRef.at(0));
customMaterial->setProperty("transparency", transparency);
customMaterial->setProperty("shaded",
graphModel->series->lightingMode()
== QAbstract3DSeries::LightingMode::Shaded);
QVariant textureInputAsVariant = customMaterial->property("custex");
QQuick3DShaderUtilsTextureInput *textureInput = textureInputAsVariant
.value<QQuick3DShaderUtilsTextureInput *>();
if (isHighlight) {
textureInput->setTexture(highlightTexture);
if (selectedItemInSeries(graphModel->series))
m_selectedGradientPos = graphModel->instancing->customData().at(m_selectedItem);
customMaterial->setProperty("gradientPos", m_selectedGradientPos);
} else {
textureInput->setTexture(seriesTexture);
}
}
void QQuickGraphsScatter::updateMaterialProperties(QQuick3DModel *item,
QQuick3DTexture *texture,
QColor color,
const bool transparency,
const bool shaded)
{
QQmlListReference materialsRef(item, "materials");
auto customMaterial = static_cast<QQuick3DCustomMaterial *>(materialsRef.at(0));
customMaterial->setProperty("transparency", transparency);
customMaterial->setProperty("rootScale", rootNode()->scale().y());
customMaterial->setProperty("shaded", shaded);
int style = customMaterial->property("colorStyle").value<int>();
if (style == 0) {
customMaterial->setProperty("uColor", color);
} else {
QVariant textureInputAsVariant = customMaterial->property("custex");
QQuick3DShaderUtilsTextureInput *textureInput
= textureInputAsVariant.value<QQuick3DShaderUtilsTextureInput *>();
textureInput->setTexture(texture);
const float scaleY = scaleWithBackground().y();
float rangeGradientYScaler = m_rangeGradientYHelper / scaleY;
float value = (item->y() + scaleY) * rangeGradientYScaler;
customMaterial->setProperty("gradientPos", value);
}
}
QQuick3DTexture *QQuickGraphsScatter::createTexture()
{
QQuick3DTexture *texture = new QQuick3DTexture();
texture->setParent(this);
texture->setRotationUV(-90.0f);
texture->setHorizontalTiling(QQuick3DTexture::ClampToEdge);
texture->setVerticalTiling(QQuick3DTexture::ClampToEdge);
QQuickGraphsTextureData *textureData = new QQuickGraphsTextureData();
textureData->setParent(texture);
textureData->setParentItem(texture);
texture->setTextureData(textureData);
return texture;
}
QQuick3DNode *QQuickGraphsScatter::createSeriesRoot()
{
auto model = new QQuick3DNode();
model->setParentItem(graphNode());
return model;
}
QQuick3DModel *QQuickGraphsScatter::createDataItem(QAbstract3DSeries *series)
{
auto model = new QQuick3DModel();
model->setParent(this);
model->setParentItem(graphNode());
QString fileName = getMeshFileName(series);
if (fileName.isEmpty())
fileName = series->userDefinedMesh();
model->setSource(QUrl(fileName));
return model;
}
void QQuickGraphsScatter::removeDataItems(ScatterModel *graphModel,
QtGraphs3D::OptimizationHint optimizationHint)
{
if (optimizationHint == QtGraphs3D::OptimizationHint::Default) {
delete graphModel->instancing;
graphModel->instancing = nullptr;
deleteDataItem(graphModel->instancingRootItem);
deleteDataItem(graphModel->selectionIndicator);
deleteDataItem(graphModel->baseRef);
deleteDataItem(graphModel->selectionRef);
deleteDataItem(graphModel->splineModel);
graphModel->instancingRootItem = nullptr;
graphModel->selectionIndicator = nullptr;
graphModel->baseRef = nullptr;
graphModel->selectionRef = nullptr;
graphModel->splineModel = nullptr;
} else {
QList<QQuick3DModel *> &items = graphModel->dataItems;
removeDataItems(items, items.count());
}
}
void QQuickGraphsScatter::removeDataItems(QList<QQuick3DModel *> &items, qsizetype count)
{
for (int i = 0; i < count; ++i) {
QQuick3DModel *item = items.takeLast();
QQmlListReference materialsRef(item, "materials");
if (materialsRef.size()) {
QObject *material = materialsRef.at(0);
delete material;
}
item->deleteLater();
}
}
QList<QScatter3DSeries *> QQuickGraphsScatter::scatterSeriesList()
{
QList<QScatter3DSeries *> scatterSeriesList;
for (QAbstract3DSeries *abstractSeries : std::as_const(m_seriesList)) {
QScatter3DSeries *scatterSeries = qobject_cast<QScatter3DSeries *>(abstractSeries);
if (scatterSeries)
scatterSeriesList.append(scatterSeries);
}
return scatterSeriesList;
}
void QQuickGraphsScatter::recreateDataItems()
{
if (!isComponentComplete())
return;
QList<QScatter3DSeries *> seriesList = scatterSeriesList();
for (auto series : std::as_const(seriesList)) {
for (const auto &model : std::as_const(m_scatterGraphs)) {
if (model->series == series)
removeDataItems(model, optimizationHint());
}
}
markDataDirty();
}
void QQuickGraphsScatter::recreateDataItems(const QList<ScatterModel *> &graphs)
{
if (!isComponentComplete())
return;
QList<QScatter3DSeries *> seriesList = scatterSeriesList();
for (auto series : std::as_const(seriesList)) {
for (const auto &model : graphs) {
if (model->series == series)
removeDataItems(model, optimizationHint());
}
}
markDataDirty();
}
void QQuickGraphsScatter::addPointsToScatterModel(ScatterModel *graphModel, qsizetype count)
{
for (int i = 0; i < count; i++) {
QQuick3DModel *item = createDataItem(graphModel->series);
item->setPickable(true);
item->setParent(graphModel->series);
graphModel->dataItems.push_back(item);
}
setSeriesVisualsDirty();
}
qsizetype QQuickGraphsScatter::sizeDifference(qsizetype size1, qsizetype size2)
{
return size2 - size1;
}
QVector3D QQuickGraphsScatter::selectedItemPosition()
{
QVector3D position;
if (optimizationHint() == QtGraphs3D::OptimizationHint::Legacy)
position = {0.0f, 0.0f, 0.0f};
else if (optimizationHint() == QtGraphs3D::OptimizationHint::Default)
position = {0.0f, 0.0f, 0.0f};
return position;
}
void QQuickGraphsScatter::fixMeshFileName(QString &fileName, QAbstract3DSeries *series)
{
auto meshType = series->mesh();
// Should it be smooth?
if (series->isMeshSmooth() && meshType != QAbstract3DSeries::Mesh::Point
&& meshType != QAbstract3DSeries::Mesh::UserDefined) {
fileName += QStringLiteral("Smooth");
}
// Should it be filled?
if (meshType != QAbstract3DSeries::Mesh::Sphere && meshType != QAbstract3DSeries::Mesh::Arrow
&& meshType != QAbstract3DSeries::Mesh::Minimal
&& meshType != QAbstract3DSeries::Mesh::Point
&& meshType != QAbstract3DSeries::Mesh::UserDefined) {
fileName.append(QStringLiteral("Full"));
}
}
QString QQuickGraphsScatter::getMeshFileName(QAbstract3DSeries *series)
{
QString fileName = {};
switch (series->mesh()) {
case QAbstract3DSeries::Mesh::Sphere:
fileName = QStringLiteral("defaultMeshes/sphereMesh");
break;
case QAbstract3DSeries::Mesh::Bar:
case QAbstract3DSeries::Mesh::Cube:
fileName = QStringLiteral("defaultMeshes/barMesh");
break;
case QAbstract3DSeries::Mesh::Pyramid:
fileName = QStringLiteral("defaultMeshes/pyramidMesh");
break;
case QAbstract3DSeries::Mesh::Cone:
fileName = QStringLiteral("defaultMeshes/coneMesh");
break;
case QAbstract3DSeries::Mesh::Cylinder:
fileName = QStringLiteral("defaultMeshes/cylinderMesh");
break;
case QAbstract3DSeries::Mesh::BevelBar:
case QAbstract3DSeries::Mesh::BevelCube:
fileName = QStringLiteral("defaultMeshes/bevelBarMesh");
break;
case QAbstract3DSeries::Mesh::Minimal:
fileName = QStringLiteral("defaultMeshes/minimalMesh");
break;
case QAbstract3DSeries::Mesh::Arrow:
fileName = QStringLiteral("defaultMeshes/arrowMesh");
break;
case QAbstract3DSeries::Mesh::Point:
fileName = shadowQuality() == QtGraphs3D::ShadowQuality::None
? QStringLiteral("defaultMeshes/planeMesh")
: QStringLiteral("defaultMeshes/octagonMesh");
break;
case QAbstract3DSeries::Mesh::UserDefined:
break;
default:
fileName = QStringLiteral("defaultMeshes/sphereMesh");
}
fixMeshFileName(fileName, series);
return fileName;
}
void QQuickGraphsScatter::deleteDataItem(QQuick3DModel *item)
{
if (item) {
QQmlListReference materialsRef(item, "materials");
if (materialsRef.size()) {
QObject *material = materialsRef.at(0);
delete material;
}
item->deleteLater();
item = nullptr;
}
}
void QQuickGraphsScatter::handleSeriesChanged(QList<QAbstract3DSeries *> changedSeries)
{
Q_UNUSED(changedSeries)
// TODO: generate items and remove old items
}
bool QQuickGraphsScatter::selectedItemInSeries(const QScatter3DSeries *series)
{
return (m_selectedItem != -1 && m_selectedItemSeries == series);
}
bool QQuickGraphsScatter::isDotPositionInAxisRange(QVector3D dotPos, QScatter3DSeries *series)
{
float cutoffMargin = QQuickGraphsItem::cutoffMargin();
QValue3DAxis *xAxis = series->axisX();
QValue3DAxis *yAxis = series->axisY();
QValue3DAxis *zAxis = series->axisZ();
if (!xAxis)
xAxis = static_cast<QValue3DAxis *>(axisX());
if (!yAxis)
yAxis = static_cast<QValue3DAxis *>(axisY());
if (!zAxis)
zAxis = static_cast<QValue3DAxis *>(axisZ());
float axisMinX = xAxis->min() - cutoffMargin;
float axisMinY = yAxis->min() - cutoffMargin;
float axisMinZ = zAxis->min() - cutoffMargin;
float axisMaxX = xAxis->max() + cutoffMargin;
float axisMaxY = yAxis->max() + cutoffMargin;
float axisMaxZ = zAxis->max() + cutoffMargin;
return ((dotPos.x() >= axisMinX && dotPos.x() <= axisMaxX)
&& (dotPos.y() >= axisMinY && dotPos.y() <= axisMaxY)
&& (dotPos.z() >= axisMinZ && dotPos.z() <= axisMaxZ));
}
QScatter3DSeries *QQuickGraphsScatter::selectedSeries() const
{
return m_selectedItemSeries;
}
void QQuickGraphsScatter::setSelectedItem(qsizetype index, QScatter3DSeries *series)
{
const QScatterDataProxy *proxy = 0;
// Series may already have been removed, so check it before setting the selection.
if (!m_seriesList.contains(series))
series = nullptr;
if (series)
proxy = series->dataProxy();
if (!proxy || index < 0 || index >= proxy->itemCount())
index = invalidSelectionIndex();
if (index != m_selectedItem || series != m_selectedItemSeries) {
bool seriesChanged = (series != m_selectedItemSeries);
// Clear hidden point from the previous selected series
if (seriesChanged) {
for (auto model : std::as_const(m_scatterGraphs)) {
if (model->series && model->instancing && model->series == m_selectedItemSeries)
model->instancing->unhidePreviousDataItem();
}
}
m_selectedItem = index;
m_selectedItemSeries = series;
m_changeTracker.selectedItemChanged = true;
// Clear selection from other series and finally set new selection to the
// specified series
for (QAbstract3DSeries *otherSeries : std::as_const(m_seriesList)) {
QScatter3DSeries *scatterSeries = static_cast<QScatter3DSeries *>(otherSeries);
if (scatterSeries != m_selectedItemSeries)
scatterSeries->d_func()->setSelectedItem(invalidSelectionIndex());
}
if (m_selectedItemSeries)
m_selectedItemSeries->d_func()->setSelectedItem(m_selectedItem);
if (seriesChanged)
emit selectedSeriesChanged(m_selectedItemSeries);
emitNeedRender();
}
if (index != invalidSelectionIndex())
itemLabel()->setVisible(series->isItemLabelVisible());
}
void QQuickGraphsScatter::setSelectionMode(QtGraphs3D::SelectionFlags mode)
{
// We only support single item selection mode and no selection mode
if (mode != QtGraphs3D::SelectionFlag::Item && mode != QtGraphs3D::SelectionFlag::None) {
qCWarning(lcProperties3D, "%s unsupported selection mode - only none and item selection modes "
"are supported", qUtf8Printable(QLatin1String(__FUNCTION__)));
return;
}
QQuickGraphsItem::setSelectionMode(mode);
}
void QQuickGraphsScatter::handleAxisAutoAdjustRangeChangedInOrientation(
QAbstract3DAxis::AxisOrientation orientation, bool autoAdjust)
{
Q_UNUSED(orientation);
Q_UNUSED(autoAdjust);
adjustAxisRanges();
}
void QQuickGraphsScatter::handleAxisRangeChangedBySender(QObject *sender)
{
QQuickGraphsItem::handleAxisRangeChangedBySender(sender);
m_isDataDirty = true;
// Update selected index - may be moved offscreen
setSelectedItem(m_selectedItem, m_selectedItemSeries);
}
void QQuickGraphsScatter::handleLightingModeChanged() {
auto series = static_cast<QScatter3DSeries *>(QObject::sender());
for (auto model : std::as_const(m_scatterGraphs)) {
if (model->series == series) {
updateScatterGraphItemVisuals(model);
break;
}
}
}
QQmlListProperty<QScatter3DSeries> QQuickGraphsScatter::seriesList()
{
return QQmlListProperty<QScatter3DSeries>(this,
this,
&QQuickGraphsScatter::appendSeriesFunc,
&QQuickGraphsScatter::countSeriesFunc,
&QQuickGraphsScatter::atSeriesFunc,
&QQuickGraphsScatter::clearSeriesFunc);
}
void QQuickGraphsScatter::appendSeriesFunc(QQmlListProperty<QScatter3DSeries> *list,
QScatter3DSeries *series)
{
reinterpret_cast<QQuickGraphsScatter *>(list->data)->addSeries(series);
}
qsizetype QQuickGraphsScatter::countSeriesFunc(QQmlListProperty<QScatter3DSeries> *list)
{
return reinterpret_cast<QQuickGraphsScatter *>(list->data)->scatterSeriesList().size();
}
QScatter3DSeries *QQuickGraphsScatter::atSeriesFunc(QQmlListProperty<QScatter3DSeries> *list,
qsizetype index)
{
return reinterpret_cast<QQuickGraphsScatter *>(list->data)->scatterSeriesList().at(index);
}
void QQuickGraphsScatter::clearSeriesFunc(QQmlListProperty<QScatter3DSeries> *list)
{
QQuickGraphsScatter *declScatter = reinterpret_cast<QQuickGraphsScatter *>(list->data);
QList<QScatter3DSeries *> realList = declScatter->scatterSeriesList();
qsizetype count = realList.size();
for (int i = 0; i < count; i++)
declScatter->removeSeries(realList.at(i));
}
void QQuickGraphsScatter::addSeries(QScatter3DSeries *series)
{
Q_ASSERT(series && series->type() == QAbstract3DSeries::SeriesType::Scatter);
QQuickGraphsItem::addSeriesInternal(series);
QScatter3DSeries *scatterSeries = static_cast<QScatter3DSeries *>(series);
if (scatterSeries->selectedItem() != invalidSelectionIndex())
setSelectedItem(scatterSeries->selectedItem(), scatterSeries);
auto graphModel = new ScatterModel;
graphModel->series = series;
graphModel->seriesTexture = nullptr;
graphModel->highlightTexture = nullptr;
m_scatterGraphs.push_back(graphModel);
connectSeries(series);
if (series->selectedItem() != invalidSelectionIndex())
setSelectedItem(series->selectedItem(), series);
if (scatterSeries->axisX())
handleMultiAxisChanged(scatterSeries->axisX());
if (scatterSeries->axisY())
handleMultiAxisChanged(scatterSeries->axisY());
if (scatterSeries->axisZ())
handleMultiAxisChanged(scatterSeries->axisZ());
}
void QQuickGraphsScatter::removeSeries(QScatter3DSeries *series)
{
bool wasVisible = (series && series->d_func()->m_graph == this && series->isVisible());
QQuickGraphsItem::removeSeriesInternal(series);
if (m_selectedItemSeries == series)
setSelectedItem(invalidSelectionIndex(), 0);
if (wasVisible)
adjustAxisRanges();
series->setParent(this); // Reparent as removing will leave series parentless
// Find scattergraph model
for (QList<ScatterModel *>::Iterator it = m_scatterGraphs.begin();
it != m_scatterGraphs.end();) {
if ((*it)->series == series) {
removeDataItems(*it, optimizationHint());
if ((*it)->seriesTexture)
delete (*it)->seriesTexture;
if ((*it)->highlightTexture)
delete (*it)->highlightTexture;
delete *it;
it = m_scatterGraphs.erase(it);
} else {
++it;
}
}
QObject::disconnect(series,
&QScatter3DSeries::axisXChanged,
this,
&QQuickGraphsScatter::handleMultiAxisChanged);
QObject::disconnect(series,
&QScatter3DSeries::axisYChanged,
this,
&QQuickGraphsScatter::handleMultiAxisChanged);
QObject::disconnect(series,
&QScatter3DSeries::axisZChanged,
this,
&QQuickGraphsScatter::handleMultiAxisChanged);
disconnectSeries(series);
}
void QQuickGraphsScatter::handleAxisXChanged(QAbstract3DAxis *axis)
{
emit axisXChanged(static_cast<QValue3DAxis *>(axis));
}
void QQuickGraphsScatter::handleAxisYChanged(QAbstract3DAxis *axis)
{
emit axisYChanged(static_cast<QValue3DAxis *>(axis));
}
void QQuickGraphsScatter::handleAxisZChanged(QAbstract3DAxis *axis)
{
emit axisZChanged(static_cast<QValue3DAxis *>(axis));
}
void QQuickGraphsScatter::handleMultiAxisChanged(QAbstract3DAxis *axis)
{
QQuickGraphsItem::handleMultiAxisChanged(axis);
}
QAbstract3DAxis *QQuickGraphsScatter::getSeriesMultiAxis(QAbstract3DSeries *series,
QAbstract3DAxis::AxisOrientation orientation)
{
QScatter3DSeries *scatterSeries = qobject_cast<QScatter3DSeries *>(series);
if (!scatterSeries)
return nullptr;
QAbstract3DAxis *axis = nullptr;
switch (orientation) {
case QAbstract3DAxis::AxisOrientation::X:
axis = scatterSeries->axisX();
break;
case QAbstract3DAxis::AxisOrientation::Y:
axis = scatterSeries->axisY();
break;
case QAbstract3DAxis::AxisOrientation::Z:
axis = scatterSeries->axisZ();
break;
case QAbstract3DAxis::AxisOrientation::None:
break;
}
if (axis)
axis->d_func()->setOrientation(orientation);
return axis;
}
void QQuickGraphsScatter::handleSeriesMeshChanged()
{
recreateDataItems();
}
void QQuickGraphsScatter::handleMeshSmoothChanged(bool enable)
{
Q_UNUSED(enable);
QScatter3DSeries *series = qobject_cast<QScatter3DSeries *>(sender());
for (auto &model : std::as_const(m_scatterGraphs)) {
if (model->series == series)
removeDataItems(model, optimizationHint());
}
markDataDirty();
}
void QQuickGraphsScatter::handleArrayReset()
{
QScatter3DSeries *series;
if (qobject_cast<QScatterDataProxy *>(sender()))
series = static_cast<QScatterDataProxy *>(sender())->series();
else
series = static_cast<QScatter3DSeries *>(sender());
if (series->isVisible()) {
adjustAxisRanges();
m_isDataDirty = true;
}
if (!m_changedSeriesList.contains(series))
m_changedSeriesList.append(series);
setSelectedItem(m_selectedItem, m_selectedItemSeries);
series->d_func()->markItemLabelDirty();
emitNeedRender();
}
void QQuickGraphsScatter::handleItemsAdded(qsizetype startIndex, qsizetype count)
{
Q_UNUSED(startIndex);
Q_UNUSED(count);
QScatter3DSeries *series = static_cast<QScatterDataProxy *>(sender())->series();
if (series->isVisible()) {
adjustAxisRanges();
m_isDataDirty = true;
}
if (!m_changedSeriesList.contains(series))
m_changedSeriesList.append(series);
emitNeedRender();
}
void QQuickGraphsScatter::handleItemsChanged(qsizetype startIndex, qsizetype count)
{
QScatter3DSeries *series = static_cast<QScatterDataProxy *>(sender())->series();
qsizetype oldChangeCount = m_changedItems.size();
if (!oldChangeCount)
m_changedItems.reserve(count);
for (int i = 0; i < count; i++) {
bool newItem = true;
qsizetype candidate = startIndex + i;
for (qsizetype j = 0; j < oldChangeCount; j++) {
const ChangeItem &oldChangeItem = m_changedItems.at(j);
if (oldChangeItem.index == candidate && series == oldChangeItem.series) {
newItem = false;
break;
}
}
if (newItem) {
ChangeItem newChangeItem = {series, candidate};
m_changedItems.append(newChangeItem);
if (series == m_selectedItemSeries && m_selectedItem == candidate)
series->d_func()->markItemLabelDirty();
}
}
if (count) {
m_changeTracker.itemChanged = true;
if (series->isVisible())
adjustAxisRanges();
emitNeedRender();
}
}
void QQuickGraphsScatter::handleItemsRemoved(qsizetype startIndex, qsizetype count)
{
Q_UNUSED(startIndex);
Q_UNUSED(count);
QScatter3DSeries *series = static_cast<QScatterDataProxy *>(sender())->series();
if (series == m_selectedItemSeries) {
// If items removed from selected series before the selection, adjust the selection
qsizetype selectedItem = m_selectedItem;
if (startIndex <= selectedItem) {
if ((startIndex + count) > selectedItem)
selectedItem = -1; // Selected item removed
else
selectedItem -= count; // Move selected item down by amount of item removed
setSelectedItem(selectedItem, m_selectedItemSeries);
}
}
if (series->isVisible()) {
adjustAxisRanges();
m_isDataDirty = true;
}
if (!m_changedSeriesList.contains(series))
m_changedSeriesList.append(series);
if (m_recordInsertsAndRemoves) {
InsertRemoveRecord record(false, startIndex, count, series);
m_insertRemoveRecords.append(record);
}
emitNeedRender();
}
void QQuickGraphsScatter::adjustAxisRanges()
{
QValue3DAxis *valueAxisX = static_cast<QValue3DAxis *>(m_axisX);
QValue3DAxis *valueAxisY = static_cast<QValue3DAxis *>(m_axisY);
QValue3DAxis *valueAxisZ = static_cast<QValue3DAxis *>(m_axisZ);
bool adjustX = (valueAxisX && valueAxisX->isAutoAdjustRange());
bool adjustY = (valueAxisY && valueAxisY->isAutoAdjustRange());
bool adjustZ = (valueAxisZ && valueAxisZ->isAutoAdjustRange());
if (adjustX || adjustY || adjustZ) {
float minValueX = 0.0f;
float maxValueX = 0.0f;
float minValueY = 0.0f;
float maxValueY = 0.0f;
float minValueZ = 0.0f;
float maxValueZ = 0.0f;
qsizetype seriesCount = m_seriesList.size();
for (int series = 0; series < seriesCount; series++) {
const QScatter3DSeries *scatterSeries = static_cast<QScatter3DSeries *>(
m_seriesList.at(series));
const QScatterDataProxy *proxy = scatterSeries->dataProxy();
if (scatterSeries->isVisible() && proxy) {
QVector3D minLimits;
QVector3D maxLimits;
proxy->d_func()->limitValues(minLimits,
maxLimits,
valueAxisX,
valueAxisY,
valueAxisZ);
if (adjustX) {
if (!series) {
// First series initializes the values
minValueX = minLimits.x();
maxValueX = maxLimits.x();
} else {
minValueX = qMin(minValueX, minLimits.x());
maxValueX = qMax(maxValueX, maxLimits.x());
}
}
if (adjustY) {
if (!series) {
// First series initializes the values
minValueY = minLimits.y();
maxValueY = maxLimits.y();
} else {
minValueY = qMin(minValueY, minLimits.y());
maxValueY = qMax(maxValueY, maxLimits.y());
}
}
if (adjustZ) {
if (!series) {
// First series initializes the values
minValueZ = minLimits.z();
maxValueZ = maxLimits.z();
} else {
minValueZ = qMin(minValueZ, minLimits.z());
maxValueZ = qMax(maxValueZ, maxLimits.z());
}
}
}
}
static const float adjustmentRatio = 20.0f;
static const float defaultAdjustment = 1.0f;
if (adjustX) {
// If all points at same coordinate, need to default to some valid range
float adjustment = 0.0f;
if (minValueX == maxValueX) {
if (adjustZ) {
// X and Z are linked to have similar unit size, so choose the valid
// range based on it
if (minValueZ == maxValueZ)
adjustment = defaultAdjustment;
else
adjustment = qAbs(maxValueZ - minValueZ) / adjustmentRatio;
} else {
if (valueAxisZ)
adjustment = qAbs(valueAxisZ->max() - valueAxisZ->min()) / adjustmentRatio;
else
adjustment = defaultAdjustment;
}
}
valueAxisX->d_func()->setRange(minValueX - adjustment, maxValueX + adjustment, true);
}
if (adjustY) {
// If all points at same coordinate, need to default to some valid range
// Y-axis unit is not dependent on other axes, so simply adjust +-1.0f
float adjustment = 0.0f;
if (minValueY == maxValueY)
adjustment = defaultAdjustment;
valueAxisY->d_func()->setRange(minValueY - adjustment, maxValueY + adjustment, true);
}
if (adjustZ) {
// If all points at same coordinate, need to default to some valid range
float adjustment = 0.0f;
if (minValueZ == maxValueZ) {
if (adjustX) {
// X and Z are linked to have similar unit size, so choose the valid
// range based on it
if (minValueX == maxValueX)
adjustment = defaultAdjustment;
else
adjustment = qAbs(maxValueX - minValueX) / adjustmentRatio;
} else {
if (valueAxisX)
adjustment = qAbs(valueAxisX->max() - valueAxisX->min()) / adjustmentRatio;
else
adjustment = defaultAdjustment;
}
}
valueAxisZ->d_func()->setRange(minValueZ - adjustment, maxValueZ + adjustment, true);
}
}
}
void QQuickGraphsScatter::handleItemsInserted(qsizetype startIndex, qsizetype count)
{
Q_UNUSED(startIndex);
Q_UNUSED(count);
QScatter3DSeries *series = static_cast<QScatterDataProxy *>(sender())->series();
if (series == m_selectedItemSeries) {
// If items inserted to selected series before the selection, adjust the selection
qsizetype selectedItem = m_selectedItem;
if (startIndex <= selectedItem) {
selectedItem += count;
setSelectedItem(selectedItem, m_selectedItemSeries);
}
}
if (series->isVisible()) {
adjustAxisRanges();
m_isDataDirty = true;
}
if (!m_changedSeriesList.contains(series))
m_changedSeriesList.append(series);
if (m_recordInsertsAndRemoves) {
InsertRemoveRecord record(true, startIndex, count, series);
m_insertRemoveRecords.append(record);
}
emitNeedRender();
}
bool QQuickGraphsScatter::doPicking(QPointF position)
{
if (!QQuickGraphsItem::doPicking(position))
return false;
if (selectionMode() == QtGraphs3D::SelectionFlag::Item) {
Q_TRACE_SCOPE(QGraphs3DScatterDoPicking);
QList<QQuick3DPickResult> results;
if (m_customView)
results = m_customView->pickAll(position.x(), position.y());
else
results = pickAll(position.x(), position.y());
if (!results.empty()) {
for (const auto &result : std::as_const(results)) {
if (const auto &hitItem = result.objectHit()) {
if (hitItem == backgroundBB() || hitItem == background()) {
m_clickedType = QtGraphs3D::ElementType::None;
clearSelectionModel();
continue;
}
if (m_clickedType != QtGraphs3D::ElementType::AxisXLabel
&& m_clickedType != QtGraphs3D::ElementType::AxisYLabel
&& m_clickedType != QtGraphs3D::ElementType::AxisZLabel) {
if (optimizationHint() == QtGraphs3D::OptimizationHint::Legacy) {
setSelected(hitItem);
handleSelectedElementChange(QtGraphs3D::ElementType::Series);
break;
} else if (optimizationHint() == QtGraphs3D::OptimizationHint::Default) {
setSelected(hitItem, result.instanceIndex());
handleSelectedElementChange(QtGraphs3D::ElementType::Series);
const auto& hitSeries = static_cast<QScatter3DSeries *>(hitItem->parent());
qCDebug(lcInput3D) << "pick results:"
<< "\n hit item position:" << position
<< "\n hit item instance index:" << result.instanceIndex()
<< "\n hit item values:" << hitSeries->dataProxy()->itemAt(result.instanceIndex()).position();
break;
}
} else {
clearSelectionModel();
}
}
}
} else {
clearSelectionModel();
handleSelectedElementChange(QtGraphs3D::ElementType::None);
}
}
return true;
}
bool QQuickGraphsScatter::doRayPicking(QVector3D origin, QVector3D direction)
{
if (!QQuickGraphsItem::doRayPicking(origin, direction))
return false;
if (selectionMode() == QtGraphs3D::SelectionFlag::Item) {
Q_TRACE_SCOPE(QGraphs3DScatterDoRayPicking);
QList<QQuick3DPickResult> results;
if (m_customView)
results = m_customView->rayPickAll(origin, direction);
else
results = rayPickAll(origin, direction);
if (!results.empty()) {
for (const auto &result : std::as_const(results)) {
if (const auto &hit = result.objectHit()) {
if (hit == backgroundBB() || hit == background()) {
clearSelectionModel();
continue;
}
if (optimizationHint() == QtGraphs3D::OptimizationHint::Legacy) {
setSelected(hit);
break;
} else if (optimizationHint() == QtGraphs3D::OptimizationHint::Default) {
setSelected(hit, result.instanceIndex());
const auto& hitSeries = static_cast<QScatter3DSeries *>(hit->parent());
qCDebug(lcInput3D) << "pick results:"
<< "\n hit item instance index:" << result.instanceIndex()
<< "\n hit item values:" << hitSeries->dataProxy()->itemAt(result.instanceIndex()).position();
break;
}
}
}
} else {
clearSelectionModel();
}
}
return true;
}
void QQuickGraphsScatter::updateShadowQuality(QtGraphs3D::ShadowQuality quality)
{
// Were shadows visible before?
bool prevShadowsVisible = light()->castsShadow();
QQuickGraphsItem::updateShadowQuality(quality);
setSeriesVisualsDirty();
if (prevShadowsVisible != light()->castsShadow()) {
// Need to change mesh for series using point type
QList<ScatterModel *> graphs;
for (const auto &graph : std::as_const(m_scatterGraphs)) {
if (graph->series->mesh() == QAbstract3DSeries::Mesh::Point)
graphs.append(graph);
}
recreateDataItems(graphs);
}
}
void QQuickGraphsScatter::updateLightStrength()
{
for (auto graphModel : std::as_const(m_scatterGraphs)) {
for (const auto &obj : std::as_const(graphModel->dataItems)) {
QQmlListReference materialsRef(obj, "materials");
auto material = qobject_cast<QQuick3DCustomMaterial *>(materialsRef.at(0));
material->setProperty("specularBrightness", lightStrength() * 0.05);
}
}
}
void QQuickGraphsScatter::startRecordingRemovesAndInserts()
{
m_recordInsertsAndRemoves = false;
if (m_scene->selectionQueryPosition() != m_scene->invalidSelectionPoint()) {
m_recordInsertsAndRemoves = true;
if (m_insertRemoveRecords.size()) {
m_insertRemoveRecords.clear();
// Reserve some space for remove/insert records to avoid unnecessary reallocations.
m_insertRemoveRecords.reserve(insertRemoveRecordReserveSize);
}
}
}
void QQuickGraphsScatter::componentComplete()
{
QQuickGraphsItem::componentComplete();
if (!isReady())
return;
QObject::connect(cameraTarget(),
&QQuick3DNode::rotationChanged,
this,
&QQuickGraphsScatter::cameraRotationChanged);
graphsInputHandler()->setGraphsItem(this);
qCDebug(lcGraphs3D) << "QQuickGraphsScatter::componentComplete";
}
void QQuickGraphsScatter::connectSeries(QScatter3DSeries *series)
{
QObject::connect(series,
&QScatter3DSeries::meshChanged,
this,
&QQuickGraphsScatter::handleSeriesMeshChanged);
QObject::connect(series,
&QScatter3DSeries::meshSmoothChanged,
this,
&QQuickGraphsScatter::handleMeshSmoothChanged);
QObject::connect(series,
&QScatter3DSeries::itemSizeChanged,
this,
&QQuickGraphsScatter::markDataDirty);
QObject::connect(series,
&QScatter3DSeries::axisXChanged,
this,
&QQuickGraphsScatter::handleMultiAxisChanged);
QObject::connect(series,
&QScatter3DSeries::axisYChanged,
this,
&QQuickGraphsScatter::handleMultiAxisChanged);
QObject::connect(series,
&QScatter3DSeries::axisZChanged,
this,
&QQuickGraphsScatter::handleMultiAxisChanged);
}
void QQuickGraphsScatter::calculateSceneScalingFactors()
{
float marginV = 0.0f;
float marginH = 0.0f;
if (margin() < 0.0f) {
if (m_maxItemSize > m_defaultMaxSize)
marginH = m_maxItemSize / m_itemScaler;
else
marginH = m_defaultMaxSize;
marginV = marginH;
} else {
marginH = margin();
marginV = margin();
}
if (isPolar()) {
float polarMargin = calculatePolarBackgroundMargin();
marginH = qMax(marginH, polarMargin);
}
float tHorizontalAspectRatio;
if (isPolar())
tHorizontalAspectRatio = 1.0f;
else
tHorizontalAspectRatio = horizontalAspectRatio();
QSizeF areaSize;
if (qFuzzyIsNull(tHorizontalAspectRatio)) {
areaSize.setHeight(axisZ()->max() - axisZ()->min());
areaSize.setWidth(axisX()->max() - axisX()->min());
} else {
areaSize.setHeight(1.0f);
areaSize.setWidth(tHorizontalAspectRatio);
}
float horizontalMaxDimension;
float scaleY = 0.0f;
if (aspectRatio() > 2.0f) {
horizontalMaxDimension = 2.0f;
scaleY = 2.0f / aspectRatio();
} else {
horizontalMaxDimension = aspectRatio();
scaleY = 1.0f;
}
if (isPolar())
m_polarRadius = horizontalMaxDimension;
float scaleFactor = qMax(areaSize.width(), areaSize.height());
float scaleX = horizontalMaxDimension * areaSize.width() / scaleFactor;
float scaleZ = horizontalMaxDimension * areaSize.height() / scaleFactor;
setBackgroundScaleMargin({marginH, marginV, marginH});
setScaleWithBackground({scaleX, scaleY, scaleZ});
setScale({scaleX * 2.0f, scaleY * 2.0f, scaleZ * -2.0f});
setTranslate({-scaleX, -scaleY, scaleZ});
}
float QQuickGraphsScatter::calculatePointScaleSize()
{
QList<QScatter3DSeries *> series = scatterSeriesList();
int totalDataSize = 0;
for (const auto &scatterSeries : std::as_const(series)) {
if (scatterSeries->isVisible())
totalDataSize += scatterSeries->dataArray().size();
}
return qBound(m_defaultMinSize, 2.0f / float(qSqrt(qreal(totalDataSize))), m_defaultMaxSize);
}
void QQuickGraphsScatter::updatePointScaleSize()
{
m_pointScale = calculatePointScaleSize();
}
void QQuickGraphsScatter::calculatePolarXZ(const float posX,
const float posZ,
float &x,
float &z) const
{
const qreal angle = posX * (M_PI * 2.0f);
const qreal radius = posZ;
x = static_cast<float>(radius * qSin(angle)) * m_polarRadius;
z = -static_cast<float>(radius * qCos(angle)) * m_polarRadius;
}
void QQuickGraphsScatter::updateSpline(ScatterModel *model)
{
if (auto series = qobject_cast<QSpline3DSeries *>(model->series)) {
Q_TRACE_SCOPE(QGraphs3DScatterSplineUpdate);
if (!series->isSplineVisible()) {
if (model->splineModel)
model->splineModel->setVisible(false);
return;
} else {
if (!model->splineModel)
createSplineModel(model);
QQmlListReference materialRef(model->splineModel, "materials");
QQuick3DCustomMaterial *material = qobject_cast<QQuick3DCustomMaterial *>(
materialRef.at(0));
QVariant splineInputAsVariant = material->property("controlPoints");
QQuick3DShaderUtilsTextureInput *splineInput
= splineInputAsVariant.value<QQuick3DShaderUtilsTextureInput *>();
QQuick3DTexture *splineTexture = splineInput->texture();
QQuick3DTextureData *splineData = splineTexture->textureData();
bool loop = series->isSplineLooping();
material->setProperty("tension", series->splineTension());
material->setProperty("knotting", series->splineKnotting());
material->setProperty("loop", loop);
material->setProperty("color", series->splineColor());
const QScatterDataArray &array = series->dataArray();
qsizetype pointCount = array.size();
if (isDataDirty() && array.size() != 0) {
QList<QVector4D> splinePoints;
QList<SplineVertex> vertices;
splinePoints.reserve(pointCount + 2);
splineData->setSize(QSize(pointCount + 2, 1));
QValue3DAxis *valueAxisX = series->axisX();
QValue3DAxis *valueAxisY = series->axisY();
QValue3DAxis *valueAxisZ = series->axisZ();
if (!valueAxisX)
valueAxisX = static_cast<QValue3DAxis *>(axisX());
if (!valueAxisY)
valueAxisY = static_cast<QValue3DAxis *>(axisY());
if (!valueAxisZ)
valueAxisZ = static_cast<QValue3DAxis *>(axisZ());
auto normalizedPos = [this, valueAxisX, valueAxisY, valueAxisZ](QVector3D pos) {
float posX = valueAxisX->positionAt(pos.x())
* scale().x()
+ translate().x();
float posY = valueAxisY->positionAt(pos.y())
* scale().y()
+ translate().y();
float posZ = valueAxisZ->positionAt(pos.z())
* scale().z()
+ translate().z();
return QVector3D(posX, posY, posZ);
};
QVector3D first = normalizedPos(array.at(0).position());
QVector3D second = normalizedPos(array.at(1).position());
QVector3D pStart = first + (first - second) * 0.1f;
QVector3D last = normalizedPos(array.at(pointCount - 1).position());
QVector3D secondLast = normalizedPos(array.at(pointCount - 2).position());
QVector3D pEnd = last + (last - secondLast) * 0.1f;
if (loop)
splinePoints.append(QVector4D(last, 1));
else
splinePoints.append(QVector4D(pStart, 1));
const qsizetype resolution = series->splineResolution();
vertices.reserve(resolution * pointCount);
for (int i = 0; i < pointCount; i++) {
splinePoints.push_back(QVector4D(normalizedPos(array.at(i).position()), 1));
for (int j = 0; j < resolution; j++) {
SplineVertex vertex;
vertex.position = QVector3D(float(j) / float(resolution), float(i), 0);
vertex.uv = QVector2D(float(j) / float(resolution - 1),
float(i) / float(pointCount + 2));
vertices.push_back(vertex);
}
}
if (loop)
splinePoints.append(QVector4D(first, 1));
else
splinePoints.append(QVector4D(pEnd, 1));
QByteArray pointData = QByteArray(reinterpret_cast<char *>(splinePoints.data()),
splinePoints.size() * sizeof(QVector4D));
splineData->setTextureData(pointData);
material->setProperty("points", splinePoints.size());
QQuick3DGeometry *splineGeometry = model->splineModel->geometry();
QByteArray vertexBuffer(reinterpret_cast<char *>(vertices.data()),
vertices.size() * sizeof(SplineVertex));
splineGeometry->setVertexData(vertexBuffer);
splineGeometry->update();
splineTexture->setTextureData(splineData);
splineInput->setTexture(splineTexture);
}
model->splineModel->setVisible(true);
}
}
}
void QQuickGraphsScatter::createSplineModel(ScatterModel *model)
{
QQuick3DModel *splineModel = new QQuick3DModel();
splineModel->setParent(model->series);
splineModel->setParentItem(graphNode());
splineModel->setObjectName(QStringLiteral("SplineModel"));
splineModel->setVisible(true);
splineModel->setPickable(false);
auto geometry = new QQuick3DGeometry();
geometry->setParent(splineModel);
geometry->setStride(sizeof(SplineVertex)); //pos + uv
geometry->setPrimitiveType(QQuick3DGeometry::PrimitiveType::LineStrip);
geometry->addAttribute(QQuick3DGeometry::Attribute::PositionSemantic,
0,
QQuick3DGeometry::Attribute::F32Type);
geometry->addAttribute(QQuick3DGeometry::Attribute::TexCoord0Semantic,
sizeof(QVector3D),
QQuick3DGeometry::Attribute::F32Type);
splineModel->setGeometry(geometry);
QQuick3DTexture *splineTex = new QQuick3DTexture();
splineTex->setHorizontalTiling(QQuick3DTexture::ClampToEdge);
splineTex->setVerticalTiling(QQuick3DTexture::ClampToEdge);
splineTex->setMinFilter(QQuick3DTexture::Nearest);
splineTex->setMagFilter(QQuick3DTexture::Nearest);
QQuick3DTextureData *splineData = new QQuick3DTextureData;
splineData->setSize(QSize(0, 1));
splineData->setFormat(QQuick3DTextureData::RGBA32F);
splineData->setParent(splineTex);
splineData->setParentItem(splineTex);
splineTex->setTextureData(splineData);
QQmlListReference materialRef(splineModel, "materials");
QQuick3DCustomMaterial *material = createQmlCustomMaterial(
QStringLiteral(":/materials/SplineMaterial"));
material->setParent(splineModel);
material->setParentItem(splineModel);
material->setObjectName("splineMaterial");
QVariant textureInputAsVariant = material->property("controlPoints");
QQuick3DShaderUtilsTextureInput *textureInput = textureInputAsVariant
.value<QQuick3DShaderUtilsTextureInput *>();
textureInput->setTexture(splineTex);
splineTex->setParent(material);
materialRef.append(material);
model->splineModel = splineModel;
if (auto series = qobject_cast<QSpline3DSeries *>(model->series)) {
connect(series,
&QSpline3DSeries::splineTensionChanged,
this,
&QQuickGraphsScatter::handleSplineChanged);
connect(series,
&QSpline3DSeries::splineKnottingChanged,
this,
&QQuickGraphsScatter::handleSplineChanged);
connect(series,
&QSpline3DSeries::splineLoopingChanged,
this,
&QQuickGraphsScatter::handleSplineChanged);
connect(series,
&QSpline3DSeries::splineColorChanged,
this,
&QQuickGraphsScatter::handleSplineChanged);
connect(series,
&QSpline3DSeries::splineResolutionChanged,
this,
&QQuickGraphsScatter::handleSplineChanged);
}
}
void QQuickGraphsScatter::handleSplineChanged()
{
m_isDataDirty = true;
}
QQuick3DModel *QQuickGraphsScatter::selected() const
{
return m_selected;
}
void QQuickGraphsScatter::setSelected(QQuick3DModel *newSelected)
{
if (newSelected != m_selected) {
m_previousSelected = m_selected;
m_selected = newSelected;
auto series = static_cast<QScatter3DSeries *>(m_selected->parent());
// Find scattermodel
ScatterModel *graphModel = nullptr;
for (const auto &model : std::as_const(m_scatterGraphs)) {
if (model->series == series) {
graphModel = model;
break;
}
}
if (graphModel) {
qsizetype index = graphModel->dataItems.indexOf(m_selected);
setSelectedItem(index, series);
setSeriesVisualsDirty();
setSelectedItemChanged(true);
}
}
}
void QQuickGraphsScatter::setSelected(QQuick3DModel *root, qsizetype index)
{
auto series = static_cast<QScatter3DSeries *>(root->parent());
if (index != m_selectedItem || series != m_selectedItemSeries) {
setSeriesVisualsDirty();
setSelectedItem(index, series);
setSelectedItemChanged(true);
}
}
void QQuickGraphsScatter::clearSelectionModel()
{
if (optimizationHint() == QtGraphs3D::OptimizationHint::Default)
clearAllSelectionInstanced();
setSelectedItem(invalidSelectionIndex(), nullptr);
if (itemLabel())
itemLabel()->setVisible(false);
setSeriesVisualsDirty();
m_selected = nullptr;
m_previousSelected = nullptr;
}
void QQuickGraphsScatter::clearAllSelectionInstanced()
{
for (const auto &graph : std::as_const(m_scatterGraphs)) {
if (graph->instancing)
graph->instancing->resetVisibilty();
}
}
void QQuickGraphsScatter::handleItemLabelVisibleChangedBySender(bool visible, QObject *sender)
{
auto series = qobject_cast<QScatter3DSeries *>(sender);
if (series && series == m_selectedItemSeries)
itemLabel()->setVisible(visible);
}
void QQuickGraphsScatter::optimizationChanged(QtGraphs3D::OptimizationHint toOptimization)
{
if (toOptimization == QtGraphs3D::OptimizationHint::Default) {
for (const auto &graph : std::as_const(m_scatterGraphs))
removeDataItems(graph, QtGraphs3D::OptimizationHint::Legacy);
} else {
for (const auto &graph : std::as_const(m_scatterGraphs))
removeDataItems(graph, QtGraphs3D::OptimizationHint::Default);
}
setSeriesVisualsDirty();
}
void QQuickGraphsScatter::updateGraph()
{
updatePointScaleSize();
if (m_optimizationChanged) {
optimizationChanged(optimizationHint());
m_optimizationChanged = false;
}
for (auto graphModel : std::as_const(m_scatterGraphs)) {
bool seriesVisible = graphModel->series->isVisible();
if (isDataDirty()) {
if (optimizationHint() == QtGraphs3D::OptimizationHint::Legacy && seriesVisible) {
if (graphModel->dataItems.count() != graphModel->series->dataProxy()->itemCount()) {
Q_TRACE(QGraphs3DScatterLegacyModelsCountUpdate_entry);
qsizetype sizeDiff = sizeDifference(graphModel->dataItems.count(),
graphModel->series->dataProxy()->itemCount());
if (sizeDiff > 0)
addPointsToScatterModel(graphModel, sizeDiff);
else
removeDataItems(graphModel->dataItems, qAbs(sizeDiff));
Q_TRACE(QGraphs3DScatterLegacyModelsCountUpdate_exit);
}
} else if (optimizationHint() == QtGraphs3D::OptimizationHint::Default
&& seriesVisible) {
if (graphModel->instancing == nullptr) {
graphModel->instancing = new ScatterInstancing;
graphModel->instancing->setParent(graphModel->series);
qCDebug(lcGraphs3D) << "scatter info: "
<< "\n series:" << graphModel->series
<< "\n dataArray size:" << graphModel->series->dataArray().size()
<< "\n basecolor:" << graphModel->series->baseColor()
<< "\n customData:" << graphModel->instancing->customData()
<< "\n mesh:" << graphModel->series->mesh();
}
if (graphModel->instancingRootItem == nullptr) {
graphModel->instancingRootItem = createDataItem(graphModel->series);
graphModel->instancingRootItem->setParent(graphModel->series);
graphModel->instancingRootItem->setInstancing(graphModel->instancing);
if (selectionMode() != QtGraphs3D::SelectionFlag::None) {
graphModel->instancingRootItem->setPickable(true);
graphModel->selectionIndicator = createDataItem(graphModel->series);
graphModel->selectionIndicator->setVisible(false);
}
}
}
}
if (seriesVisible && (isDataDirty() || isSeriesVisualsDirty())) {
updateScatterGraphItemPositions(graphModel);
updateSpline(graphModel);
}
if (seriesVisible
&& (isSeriesVisualsDirty()
|| (graphModel->instancing && graphModel->instancing->isDirty()))) {
updateScatterGraphItemVisuals(graphModel);
}
const bool validSelection = (m_selectedItemSeries == graphModel->series
&& m_selectedItem != invalidSelectionIndex())
&& selectedItemInRange(graphModel);
if (validSelection) {
QVector3D selectionPosition = {0.0f, 0.0f, 0.0f};
if (optimizationHint() == QtGraphs3D::OptimizationHint::Legacy) {
QQuick3DModel *selectedModel = graphModel->dataItems.at(m_selectedItem);
selectionPosition = selectedModel->position();
} else {
selectionPosition = graphModel->instancing->dataArray().at(m_selectedItem).position;
}
updateItemLabel(selectionPosition);
QString label = m_selectedItemSeries->itemLabel();
itemLabel()->setProperty("labelText", label);
if (!label.compare(QString(hiddenLabelTag)))
itemLabel()->setVisible(false);
}
}
if (m_selectedItem == invalidSelectionIndex())
itemLabel()->setVisible(false);
setItemSelected(m_selectedItem != invalidSelectionIndex());
}
void QQuickGraphsScatter::synchData()
{
qCDebug(lcGraphs3D, "%s start syncing", qUtf8Printable(QLatin1String(__FUNCTION__)));
if (!isReady())
return;
QList<QScatter3DSeries *> seriesList = scatterSeriesList();
Q_TRACE(QGraphs3DScatterPointDataUpdate_entry);
float maxItemSize = 0.0f;
for (const auto &series : std::as_const(seriesList)) {
if (series->isVisible()) {
float itemSize = series->itemSize();
if (itemSize > maxItemSize)
maxItemSize = itemSize;
}
}
m_maxItemSize = maxItemSize;
updatePointScaleSize();
Q_TRACE(QGraphs3DScatterPointDataUpdate_exit);
QQuickGraphsItem::synchData();
m_pointScale = calculatePointScaleSize();
Q_TRACE(QGraphs3DScatterPointSelectionUpdate_entry);
if (hasSelectedItemChanged()) {
if (m_selectedItem != invalidSelectionIndex()) {
QString itemLabelText = m_selectedItemSeries->itemLabel();
itemLabel()->setProperty("labelText", itemLabelText);
if (!itemLabelText.compare(QString(hiddenLabelTag)))
itemLabel()->setVisible(false);
}
setSelectedItemChanged(false);
}
Q_TRACE(QGraphs3DScatterPointSelectionUpdate_exit);
qCDebug(lcGraphs3D, "%s end syncing", qUtf8Printable(QLatin1String(__FUNCTION__)));
}
void QQuickGraphsScatter::cameraRotationChanged()
{
m_isDataDirty = true;
}
void QQuickGraphsScatter::handleOptimizationHintChange(QtGraphs3D::OptimizationHint hint)
{
Q_UNUSED(hint)
m_optimizationChanged = true;
}
void QQuickGraphsScatter::handleSeriesVisibilityChangedBySender(QObject *sender)
{
QQuickGraphsItem::handleSeriesVisibilityChangedBySender(sender);
auto series = static_cast<QScatter3DSeries *>(sender);
if (optimizationHint() == QtGraphs3D::OptimizationHint::Default) {
ScatterModel *graphModel = findGraphModel(series);
if (graphModel && graphModel->series == series) {
if (graphModel->instancingRootItem)
graphModel->instancingRootItem->setVisible(series->isVisible());
if (series == m_selectedItemSeries) {
itemLabel()->setVisible(series->isVisible());
graphModel->selectionIndicator->setVisible(series->isVisible());
}
}
}
}
bool QQuickGraphsScatter::selectedItemInRange(const ScatterModel *graphModel)
{
qsizetype itemCount;
if (optimizationHint() == QtGraphs3D::OptimizationHint::Default)
itemCount = graphModel->instancing->dataArray().count();
else
itemCount = graphModel->dataItems.count();
return m_selectedItem >= 0 && m_selectedItem < itemCount;
}
QQuickGraphsScatter::ScatterModel *QQuickGraphsScatter::findGraphModel(QScatter3DSeries *series)
{
for (const auto &graphModel : std::as_const(m_scatterGraphs)) {
if (graphModel->series == series)
return graphModel;
}
return nullptr;
}
QT_END_NAMESPACE
#include "moc_qquickgraphsscatter_p.cpp"