Create offscreen slice view of given index and grab it to image

Slice view creation of given index of row or column
The requested slice view is rendered outisde of window and grabbed to
QQuickGrabbedResult.
QML side provides to save it to a specified path.
grabsliceview manual test for feature test

Task-number: QTBUG-105607
Change-Id: Ia8019f99134abbb328a027b656ba85552ffcaf92
Reviewed-by: Kwanghyo Park <kwanghyo.park@qt.io>
This commit is contained in:
Kwanghyo Park 2024-12-20 14:48:47 +02:00
parent 426fa3fc01
commit 609e036600
40 changed files with 2018 additions and 85 deletions

View File

@ -16,6 +16,7 @@
#include <QtQuick3D/private/qquick3dcustommaterial_p.h>
#include <QtQuick3D/private/qquick3dprincipledmaterial_p.h>
#include <QtQuick3D/private/qquick3drepeater_p.h>
#include <QtQuick/qquickitemgrabresult.h>
/*!
* \qmltype Bars3D
@ -176,6 +177,16 @@
* \sa GraphsItem3D::hasSeries()
*/
/*!
* \qmlmethod void Bars3D::renderSliceToImage(int requestedIndex, QtGraphs3D::SliceType sliceType, QUrl filePath)
* \since 6.10
*
* Exports a 2d slice from series at \a requestedIndex and saves the result to an image
* at a specified \a filePath.
* The exported slice includes bars of row or column, which is defined by
* \a sliceType.
*/
/*!
* \qmlsignal Bars3D::multiSeriesUniformChanged(bool uniform)
*
@ -705,6 +716,200 @@ float QQuickGraphsBars::floorLevel() const
return m_floorLevel;
}
QQuick3DViewport *QQuickGraphsBars::createOffscreenSliceView(int requestedIndex,
QtGraphs3D::SliceType sliceType)
{
QQuick3DViewport *sliceView = QQuickGraphsItem::createOffscreenSliceView(sliceType);
bool isRow = (selectionMode().testFlag(QtGraphs3D::SelectionFlag::Row)
|| sliceType == QtGraphs3D::SliceType::SliceRow);
bool isColumn = (selectionMode().testFlag(QtGraphs3D::SelectionFlag::Column)
|| sliceType == QtGraphs3D::SliceType::SliceColumn);
QList<QBar3DSeries *> barSeriesList = this->barSeriesList();
for (const auto &barSeries : std::as_const(barSeriesList)) {
qsizetype newRowSize = qMin(barSeries->dataProxy()->rowCount() - m_minRow, m_newRows);
qsizetype newColSize = 0;
if (newRowSize) {
const QBarDataRow *dataRow = &barSeries->dataProxy()->rowAt(m_minRow);
if (dataRow) {
qsizetype dataColIndex = m_minCol;
newColSize = qMin(dataRow->size() - dataColIndex, m_newCols);
}
}
if (!barSeries->isVisible())
continue;
if (requestedIndex < 0 || requestedIndex >= newRowSize || requestedIndex >= newColSize) {
qWarning("The index is out of range. The render stops.");
sliceView->setVisible(false);
sliceView->deleteLater();
return nullptr;
}
qsizetype slicedBarListSize = -1;
if (isRow)
slicedBarListSize = newColSize;
else if (isColumn)
slicedBarListSize = newRowSize;
if (slicedBarListSize < 0)
return nullptr;
QList<BarModel *> barList = *m_barModelsMap.value(barSeries);
bool useGradient = barSeries->d_func()->isUsingGradient();
bool rangeGradient =
(useGradient
&& barSeries->d_func()->m_colorStyle == QGraphsTheme::ColorStyle::RangeGradient);
QQuick3DModel *model = nullptr;
QList<BarItemHolder *> barItemHolderList;
QList<BarItemHolder *> barItemList;
QList<BarItemHolder *> selectedItems;
if (optimizationHint() == QtGraphs3D::OptimizationHint::Default) {
model = new QQuick3DModel();
model->setParent(sliceView->scene());
model->setParentItem(sliceView->scene());
model->setObjectName(QStringLiteral("BarModel"));
QString fileName = getMeshFileName();
if (fileName.isEmpty())
fileName = barSeries->userDefinedMesh();
model->setSource(QUrl(fileName));
auto barInstancing = new BarInstancing;
barInstancing->setParent(barSeries);
model->setInstancing(barInstancing);
BarModel *barListItem = barList.at(0);
updateItemMaterial(model, useGradient, rangeGradient,
QStringLiteral(":/materials/BarsMaterialInstancing"));
updateMaterialProperties(model, false, false, barListItem->texture,
barSeries->baseColor());
barItemList = barListItem->instancing->dataArray();
for (const auto bih : std::as_const(barItemList)) {
if (!((isRow && bih->coord.x() == requestedIndex)
|| (isColumn && bih->coord.y() == requestedIndex)))
continue;
BarItemHolder *selectedBih = new BarItemHolder();
selectedBih->selectedBar = false;
selectedBih->coord = bih->coord;
selectedBih->rotation = bih->rotation;
selectedBih->heightValue = bih->heightValue;
selectedBih->position = bih->position;
selectedBih->scale = bih->scale;
selectedItems.push_back(selectedBih);
}
if (selectedItems.size() == 0)
continue;
}
qsizetype index = 0;
for (int ind = 0; ind < slicedBarListSize; ++ind) {
if (isRow)
index = (requestedIndex * barSeries->dataProxy()->colCount()) + ind;
else
index = requestedIndex + (ind * barSeries->dataProxy()->colCount());
if (optimizationHint() == QtGraphs3D::OptimizationHint::Legacy) {
if (index > barList.size())
return nullptr;
model = createDataItem(sliceView->scene(), barSeries);
BarModel *barModel = barList.at(index);
if (isRow) {
model->setPosition(QVector3D(barModel->model->x(), barModel->model->y(), 0.0f));
} else {
model->setX(barModel->model->z() - (barList.at(0)->visualIndex * .1f));
model->setY(barModel->model->y());
model->setZ(0.0f);
}
model->setScale(barModel->model->scale());
updateItemMaterial(model, useGradient, rangeGradient,
QStringLiteral(":/materials/BarsMaterial"));
updateMaterialProperties(model, false, false, barList.at(index)->texture,
barSeries->baseColor());
} else if (optimizationHint() == QtGraphs3D::OptimizationHint::Default) {
BarModel *barModel = barList.at(0);
BarItemHolder *itemHolder = new BarItemHolder();
itemHolder->selectedBar = false;
itemHolder->color = barSeries->baseColor();
itemHolder->coord = barModel->coord;
itemHolder->rotation = selectedItems.at(ind)->rotation;
itemHolder->heightValue = barModel->heightValue;
itemHolder->position = selectedItems.at(ind)->position;
itemHolder->scale = selectedItems.at(ind)->scale;
if (isRow) {
itemHolder->position.setZ(.0f);
} else {
itemHolder->position.setX(itemHolder->position.z()
- (barModel->visualIndex * .1f));
itemHolder->position.setZ(.0f);
}
barItemHolderList.push_back(itemHolder);
}
}
if (optimizationHint() == QtGraphs3D::OptimizationHint::Default) {
BarInstancing *instancing = static_cast<BarInstancing *>(model->instancing());
instancing->setDataArray(barItemHolderList);
}
}
return sliceView;
}
QSharedPointer<QQuickItemGrabResult> QQuickGraphsBars::renderSliceToImage(
int requestedIndex, QtGraphs3D::SliceType sliceType)
{
QQuick3DViewport *sliceView = createOffscreenSliceView(requestedIndex, sliceType);
if (!sliceView)
return QSharedPointer<QQuickItemGrabResult>();
QSharedPointer<QQuickItemGrabResult> grabbed = sliceView->grabToImage();
connect(grabbed.data(), &QQuickItemGrabResult::ready, this, [sliceView]() {
sliceView->setVisible(false);
sliceView->deleteLater();
});
return grabbed;
}
void QQuickGraphsBars::renderSliceToImage(int requestedIndex, QtGraphs3D::SliceType sliceType,
const QUrl &filePath)
{
QQuick3DViewport *sliceView = createOffscreenSliceView(requestedIndex, sliceType);
if (!sliceView)
return;
if (filePath.isEmpty()) {
qWarning("Save path is not defined.");
sliceView->setVisible(false);
sliceView->deleteLater();
return;
}
QSharedPointer<QQuickItemGrabResult> grabbed = sliceView->grabToImage();
connect(grabbed.data(), &QQuickItemGrabResult::ready, this, [grabbed, sliceView, filePath]() {
if (!grabbed.data()->saveToFile(filePath))
qWarning("Saving requested slice view to image failed");
sliceView->setVisible(false);
sliceView->deleteLater();
});
return;
}
void QQuickGraphsBars::componentComplete()
{
QQuickGraphsItem::componentComplete();
@ -2238,7 +2443,7 @@ void QQuickGraphsBars::updateSelectedBar()
}
}
QQuickGraphsItem::SelectionType QQuickGraphsBars::isSelected(int row, int bar, QBar3DSeries *series)
QQuickGraphsBars::SelectionType QQuickGraphsBars::isSelected(int row, int bar, QBar3DSeries *series)
{
QQuickGraphsBars::SelectionType isSelectedType = QQuickGraphsBars::SelectionNone;
if ((selectionMode().testFlag(QtGraphs3D::SelectionFlag::MultiSeries) && m_selectedBarSeries)
@ -2313,6 +2518,7 @@ void QQuickGraphsBars::createSliceView()
{
setSliceOrthoProjection(false);
QQuickGraphsItem::createSliceView();
QList<QBar3DSeries *> barSeries = barSeriesList();
for (const auto &barSeries : std::as_const(barSeries)) {
QList<BarModel *> &slicedBarList = m_slicedBarModels[barSeries];

View File

@ -149,6 +149,12 @@ public:
bool isDataDirty() const { return m_isDataDirty; }
void setDataDirty(bool dirty) { m_isDataDirty = dirty; }
QSharedPointer<QQuickItemGrabResult> renderSliceToImage(int requestedIndex,
QtGraphs3D::SliceType sliceType);
Q_REVISION(6, 10)
Q_INVOKABLE void renderSliceToImage(int requestedIndex, QtGraphs3D::SliceType sliceType,
const QUrl &filePath);
protected:
void componentComplete() override;
void synchData() override;
@ -172,6 +178,9 @@ protected:
QAbstract3DAxis *createDefaultAxis(QAbstract3DAxis::AxisOrientation orientation) override;
void updateSliceItemLabel(const QString &label, QVector3D position) override;
QQuick3DViewport* createOffscreenSliceView(int requestedIndex,
QtGraphs3D::SliceType sliceType);
public Q_SLOTS:
void handleAxisXChanged(QAbstract3DAxis *axis) override;
void handleAxisYChanged(QAbstract3DAxis *axis) override;
@ -205,6 +214,13 @@ Q_SIGNALS:
void floorLevelChanged(float level);
private:
enum SelectionType {
SelectionNone = 0,
SelectionItem,
SelectionRow,
SelectionColumn,
};
Bars3DChangeBitField m_changeTracker;
QList<ChangeItem> m_changedItems;
QList<ChangeRow> m_changedRows;
@ -320,7 +336,7 @@ private:
void deleteBarItemHolders(BarInstancing *instancing);
QQuick3DTexture *createTexture();
void updateSelectedBar();
QQuickGraphsItem::SelectionType isSelected(int row, int bar, QBar3DSeries *series);
SelectionType isSelected(int row, int bar, QBar3DSeries *series);
void resetClickedStatus();
void removeSlicedBarModels();
void createBarItemHolders(QBar3DSeries *series, QList<BarModel *> barList, bool slice);

View File

@ -2876,7 +2876,7 @@ void QQuickGraphsItem::synchData()
changeLabelTextColor(m_repeaterX, labelTextColor);
m_titleLabelX->setProperty("labelTextColor", labelTextColor);
if (m_sliceView && isSliceEnabled()) {
if (m_selectionMode == SelectionRow)
if (m_selectionMode == QtGraphs3D::SelectionFlag::Row)
changeLabelTextColor(m_sliceHorizontalLabelRepeater, labelTextColor);
m_sliceHorizontalTitleLabel->setProperty("labelTextColor", labelTextColor);
}
@ -2899,7 +2899,7 @@ void QQuickGraphsItem::synchData()
changeLabelTextColor(m_repeaterZ, labelTextColor);
m_titleLabelZ->setProperty("labelTextColor", labelTextColor);
if (m_sliceView && isSliceEnabled()) {
if (m_selectionMode == SelectionColumn)
if (m_selectionMode == QtGraphs3D::SelectionFlag::Column)
changeLabelTextColor(m_sliceHorizontalLabelRepeater, labelTextColor);
m_sliceHorizontalTitleLabel->setProperty("labelTextColor", labelTextColor);
}
@ -5027,9 +5027,9 @@ void QQuickGraphsItem::handleLabelCountChanged(QQuick3DRepeater *repeater, QColo
theme()->isLabelBackgroundVisible());
changeLabelBorderVisible(m_sliceHorizontalLabelRepeater, theme()->isLabelBorderVisible());
changeLabelBorderVisible(m_sliceVerticalLabelRepeater, theme()->isLabelBorderVisible());
if (m_selectionMode == SelectionRow)
if (m_selectionMode == QtGraphs3D::SelectionFlag::Row)
changeLabelTextColor(m_sliceHorizontalLabelRepeater, theme()->axisX().labelTextColor());
else if (m_selectionMode == SelectionColumn)
else if (m_selectionMode == QtGraphs3D::SelectionFlag::Column)
changeLabelTextColor(m_sliceHorizontalLabelRepeater, theme()->axisZ().labelTextColor());
changeLabelTextColor(m_sliceVerticalLabelRepeater, theme()->axisY().labelTextColor());
changeLabelFont(m_sliceHorizontalLabelRepeater, theme()->labelFont());
@ -6352,7 +6352,7 @@ void QQuickGraphsItem::createSliceView()
auto scene = m_sliceView->scene();
createSliceCamera();
createSliceCamera(m_sliceView);
// auto gridDelegate = createRepeaterDelegateComponent(QStringLiteral(":/axis/GridLine"));
m_labelDelegate.reset(new QQmlComponent(qmlEngine(this), QStringLiteral(":/axis/AxisLabel")));
@ -6390,40 +6390,102 @@ void QQuickGraphsItem::createSliceView()
m_sliceItemLabel->setVisible(false);
}
void QQuickGraphsItem::createSliceCamera()
QQuick3DViewport *QQuickGraphsItem::createOffscreenSliceView(QtGraphs3D::SliceType sliceType)
{
auto sliceView = new QQuick3DViewport();
sliceView->setParent(this);
sliceView->setParentItem(this);
sliceView->setWidth(parentItem()->width() * .5);
sliceView->setHeight(parentItem()->height() * .5);
sliceView->setX(sliceView->width() * -1);
sliceView->environment()->setBackgroundMode(
QQuick3DSceneEnvironment::QQuick3DEnvironmentBackgroundTypes::Color);
sliceView->environment()->setClearColor(environment()->clearColor());
sliceView->setRenderMode(renderMode());
auto scene = sliceView->scene();
createSliceCamera(sliceView);
std::unique_ptr<QQmlComponent> labelDelegate;
labelDelegate.reset(new QQmlComponent(qmlEngine(this), QStringLiteral(":/axis/AxisLabel")));
auto sliceGridGeometryModel = new QQuick3DModel(scene);
auto sliceGridGeometry = new QQuick3DGeometry(sliceGridGeometryModel);
sliceGridGeometry->setStride(sizeof(QVector3D));
sliceGridGeometry->setPrimitiveType(QQuick3DGeometry::PrimitiveType::Lines);
sliceGridGeometry->addAttribute(QQuick3DGeometry::Attribute::PositionSemantic,
0,
QQuick3DGeometry::Attribute::F32Type);
sliceGridGeometryModel->setGeometry(sliceGridGeometry);
QQmlListReference gridMaterialRef(sliceGridGeometryModel, "materials");
auto gridMaterial = new QQuick3DPrincipledMaterial(sliceGridGeometryModel);
gridMaterial->setLighting(QQuick3DPrincipledMaterial::Lighting::NoLighting);
gridMaterial->setCullMode(QQuick3DMaterial::CullMode::BackFaceCulling);
gridMaterial->setBaseColor(Qt::red);
gridMaterialRef.append(gridMaterial);
updateSliceGrid(sliceGridGeometryModel, sliceType);
auto sliceHorizontalLabelRepeater = createRepeater(scene);
sliceHorizontalLabelRepeater->setDelegate(labelDelegate.get());
auto sliceVerticalLabelRepeater = createRepeater(scene);
sliceVerticalLabelRepeater->setDelegate(labelDelegate.get());
auto sliceHorizontalTitleLabel = createTitleLabel(scene);
sliceHorizontalTitleLabel->setVisible(true);
auto sliceVerticalTitleLabel = createTitleLabel(scene);
sliceVerticalTitleLabel->setVisible(true);
auto sliceItemLabel = createTitleLabel(scene);
sliceItemLabel->setVisible(false);
updateSliceLabels(sliceHorizontalLabelRepeater, sliceVerticalLabelRepeater,
sliceHorizontalTitleLabel, sliceVerticalTitleLabel, sliceItemLabel,
sliceType);
return sliceView;
}
void QQuickGraphsItem::createSliceCamera(QQuick3DViewport *sliceView)
{
if (isSliceOrthoProjection()) {
auto camera = new QQuick3DOrthographicCamera(sliceView()->scene());
auto camera = new QQuick3DOrthographicCamera(sliceView->scene());
camera->setPosition(QVector3D(.0f, .0f, 20.0f));
const float scale = qMin(sliceView()->width(), sliceView()->height());
const float scale = qMin(sliceView->width(), sliceView->height());
const float magnificationScaleFactor = 2 * window()->devicePixelRatio()
* .08f; // this controls the size of the slice view
const float magnification = scale * magnificationScaleFactor;
camera->setHorizontalMagnification(magnification);
camera->setVerticalMagnification(magnification);
sliceView()->setCamera(camera);
sliceView->setCamera(camera);
auto light = new QQuick3DDirectionalLight(sliceView()->scene());
auto light = new QQuick3DDirectionalLight(sliceView->scene());
light->setParent(camera);
light->setParentItem(camera);
} else {
auto camera = new QQuick3DPerspectiveCamera(sliceView()->scene());
auto camera = new QQuick3DPerspectiveCamera(sliceView->scene());
camera->setFieldOfViewOrientation(
QQuick3DPerspectiveCamera::FieldOfViewOrientation::Vertical);
camera->setClipNear(5.f);
camera->setClipFar(15.f);
camera->setFieldOfView(35.f);
camera->setPosition(QVector3D(.0f, .0f, 10.f));
sliceView()->setCamera(camera);
sliceView->setCamera(camera);
auto light = new QQuick3DDirectionalLight(sliceView()->scene());
auto light = new QQuick3DDirectionalLight(sliceView->scene());
light->setParent(camera);
light->setParentItem(camera);
light->setAmbientColor(QColor::fromRgbF(1.f, 1.f, 1.f));
}
}
void QQuickGraphsItem::updateSliceGrid()
void QQuickGraphsItem::updateSliceGrid(QQuick3DModel *sliceGridGeometryModel,
QtGraphs3D::SliceType sliceType)
{
QAbstract3DAxis *horizontalAxis = nullptr;
QAbstract3DAxis *verticalAxis = axisY();
@ -6433,12 +6495,17 @@ void QQuickGraphsItem::updateSliceGrid()
float horizontalScale = 0.0f;
if (selectionMode().testFlag(QtGraphs3D::SelectionFlag::Row)) {
bool isRow = (selectionMode().testFlag(QtGraphs3D::SelectionFlag::Row)
|| sliceType == QtGraphs3D::SliceType::SliceRow);
bool isColumn = (selectionMode().testFlag(QtGraphs3D::SelectionFlag::Column)
|| sliceType == QtGraphs3D::SliceType::SliceColumn);
if (isRow) {
horizontalAxis = axisX();
horizontalScale = backgroundScale.x();
scale = m_scaleWithBackground.x();
translate = m_scaleWithBackground.x();
} else if (selectionMode().testFlag(QtGraphs3D::SelectionFlag::Column)) {
} else if (isColumn) {
horizontalAxis = axisZ();
horizontalScale = backgroundScale.z();
scale = m_scaleWithBackground.z();
@ -6516,16 +6583,26 @@ void QQuickGraphsItem::updateSliceGrid()
}
}
auto geometry = m_sliceGridGeometryModel->geometry();
QQuick3DModel *sliceModel = nullptr;
if (sliceGridGeometryModel)
sliceModel = sliceGridGeometryModel;
else
sliceModel = m_sliceGridGeometryModel;
QQuick3DGeometry *geometry = sliceModel->geometry();
geometry->setVertexData(vertices);
geometry->update();
QQmlListReference materialRef(m_sliceGridGeometryModel, "materials");
QQmlListReference materialRef(sliceModel, "materials");
auto material = static_cast<QQuick3DPrincipledMaterial *>(materialRef.at(0));
material->setBaseColor(theme()->grid().mainColor());
}
void QQuickGraphsItem::updateSliceLabels()
void QQuickGraphsItem::updateSliceLabels(QQuick3DRepeater *horizontalLabel,
QQuick3DRepeater *verticalLabel,
QQuick3DNode *horizontalTitle,
QQuick3DNode *verticalTitle,
QQuick3DNode *itemLabel,
QtGraphs3D::SliceType sliceType)
{
QAbstract3DAxis *horizontalAxis = nullptr;
QAbstract3DAxis *verticalAxis = axisY();
@ -6534,12 +6611,29 @@ void QQuickGraphsItem::updateSliceLabels()
float translate;
QColor horizontalLabelTextColor;
if (selectionMode().testFlag(QtGraphs3D::SelectionFlag::Row)) {
QQuick3DRepeater *sliceHorizontalLabelRepeater = nullptr;
if (horizontalLabel)
sliceHorizontalLabelRepeater = horizontalLabel;
else
sliceHorizontalLabelRepeater = m_sliceHorizontalLabelRepeater;
QQuick3DRepeater *sliceVerticalLabelRepeater = nullptr;
if (verticalLabel)
sliceVerticalLabelRepeater = verticalLabel;
else
sliceVerticalLabelRepeater = m_sliceVerticalLabelRepeater;
bool isRow = (selectionMode().testFlag(QtGraphs3D::SelectionFlag::Row)
|| sliceType == QtGraphs3D::SliceType::SliceRow);
bool isColumn = (selectionMode().testFlag(QtGraphs3D::SelectionFlag::Column)
|| sliceType == QtGraphs3D::SliceType::SliceColumn);
if (isRow) {
horizontalAxis = axisX();
scale = backgroundScale.x() - m_backgroundScaleMargin.x();
translate = backgroundScale.x() - m_backgroundScaleMargin.x();
horizontalLabelTextColor = theme()->axisX().labelTextColor();
} else if (selectionMode().testFlag(QtGraphs3D::SelectionFlag::Column)) {
} else if (isColumn) {
horizontalAxis = axisZ();
scale = backgroundScale.z() - m_backgroundScaleMargin.z();
translate = backgroundScale.z() - m_backgroundScaleMargin.z();
@ -6553,20 +6647,20 @@ void QQuickGraphsItem::updateSliceLabels()
if (horizontalAxis->type() == QAbstract3DAxis::AxisType::Value) {
QValue3DAxis *valueAxis = static_cast<QValue3DAxis *>(horizontalAxis);
m_sliceHorizontalLabelRepeater->model().clear();
m_sliceHorizontalLabelRepeater->setModel(valueAxis->labels().size());
sliceHorizontalLabelRepeater->model().clear();
sliceHorizontalLabelRepeater->setModel(valueAxis->labels().size());
} else if (horizontalAxis->type() == QAbstract3DAxis::AxisType::Category) {
m_sliceHorizontalLabelRepeater->model().clear();
m_sliceHorizontalLabelRepeater->setModel(horizontalAxis->labels().size());
sliceHorizontalLabelRepeater->model().clear();
sliceHorizontalLabelRepeater->setModel(horizontalAxis->labels().size());
}
if (verticalAxis->type() == QAbstract3DAxis::AxisType::Value) {
QValue3DAxis *valueAxis = static_cast<QValue3DAxis *>(verticalAxis);
m_sliceVerticalLabelRepeater->model().clear();
m_sliceVerticalLabelRepeater->setModel(valueAxis->labels().size());
sliceVerticalLabelRepeater->model().clear();
sliceVerticalLabelRepeater->setModel(valueAxis->labels().size());
} else if (horizontalAxis->type() == QAbstract3DAxis::AxisType::Category) {
m_sliceVerticalLabelRepeater->model().clear();
m_sliceVerticalLabelRepeater->setModel(verticalAxis->labels().size());
sliceVerticalLabelRepeater->model().clear();
sliceVerticalLabelRepeater->setModel(verticalAxis->labels().size());
}
float textPadding = 12.0f;
@ -6591,8 +6685,8 @@ void QQuickGraphsItem::updateSliceLabels()
QColor backgroundColor = theme()->labelBackgroundColor();
if (horizontalAxis->type() == QAbstract3DAxis::AxisType::Value) {
for (int i = 0; i < m_sliceHorizontalLabelRepeater->count(); i++) {
auto obj = static_cast<QQuick3DNode *>(m_sliceHorizontalLabelRepeater->objectAt(i));
for (int i = 0; i < sliceHorizontalLabelRepeater->count(); i++) {
auto obj = static_cast<QQuick3DNode *>(sliceHorizontalLabelRepeater->objectAt(i));
// It is important to use the position of vertical grids so that they can be in the same
// position when col/row ranges are updated.
float linePosX = static_cast<QValue3DAxis *>(horizontalAxis)->gridPositionAt(i) * scale
@ -6615,13 +6709,13 @@ void QQuickGraphsItem::updateSliceLabels()
obj->setVisible(false);
}
} else if (horizontalAxis->type() == QAbstract3DAxis::AxisType::Category) {
for (int i = 0; i < m_sliceHorizontalLabelRepeater->count(); i++) {
for (int i = 0; i < sliceHorizontalLabelRepeater->count(); i++) {
labelTrans = calculateCategoryLabelPosition(horizontalAxis, labelTrans, i);
labelTrans.setY(-yPos /*- (adjustment / 2.f)*/);
if (selectionMode().testFlag(QtGraphs3D::SelectionFlag::Column))
if (isColumn)
labelTrans.setX(labelTrans.z());
labelTrans.setZ(1.0f); // Bring the labels on top of bars and grid
auto obj = static_cast<QQuick3DNode *>(m_sliceHorizontalLabelRepeater->objectAt(i));
auto obj = static_cast<QQuick3DNode *>(sliceHorizontalLabelRepeater->objectAt(i));
obj->setScale(fontScaled);
obj->setPosition(labelTrans);
obj->setProperty("labelText", labels[i]);
@ -6645,17 +6739,17 @@ void QQuickGraphsItem::updateSliceLabels()
fontScaled.setX(scaleFactor * fontRatio);
adjustment = labelsMaxWidth * scaleFactor;
float xPos = 0.0f;
if (selectionMode().testFlag(QtGraphs3D::SelectionFlag::Row))
if (isRow)
xPos = backgroundScale.x() + (adjustment * 1.5f);
else if (selectionMode().testFlag(QtGraphs3D::SelectionFlag::Column))
else if (isColumn)
xPos = backgroundScale.z() + (adjustment * 1.5f);
labelTrans = QVector3D(xPos, 0.0f, 0.0f);
QColor verticalLabelTextColor = theme()->axisY().labelTextColor();
if (verticalAxis->type() == QAbstract3DAxis::AxisType::Value) {
auto valueAxis = static_cast<QValue3DAxis *>(verticalAxis);
for (int i = 0; i < m_sliceVerticalLabelRepeater->count(); i++) {
auto obj = static_cast<QQuick3DNode *>(m_sliceVerticalLabelRepeater->objectAt(i));
for (int i = 0; i < sliceVerticalLabelRepeater->count(); i++) {
auto obj = static_cast<QQuick3DNode *>(sliceVerticalLabelRepeater->objectAt(i));
labelTrans.setY(valueAxis->labelPositionAt(i) * scale * 2.0f - translate);
obj->setScale(fontScaled);
obj->setPosition(labelTrans);
@ -6671,9 +6765,9 @@ void QQuickGraphsItem::updateSliceLabels()
obj->setVisible(false);
}
} else if (verticalAxis->type() == QAbstract3DAxis::AxisType::Category) {
for (int i = 0; i < m_sliceVerticalLabelRepeater->count(); i++) {
for (int i = 0; i < sliceVerticalLabelRepeater->count(); i++) {
labelTrans = calculateCategoryLabelPosition(verticalAxis, labelTrans, i);
auto obj = static_cast<QQuick3DNode *>(m_sliceVerticalLabelRepeater->objectAt(i));
auto obj = static_cast<QQuick3DNode *>(sliceVerticalLabelRepeater->objectAt(i));
obj->setScale(fontScaled);
obj->setPosition(labelTrans);
obj->setProperty("labelText", labels[i]);
@ -6692,26 +6786,31 @@ void QQuickGraphsItem::updateSliceLabels()
QVector3D vTitleScale = fontScaled;
vTitleScale.setX(fontScaled.y() * labelWidth / labelHeight);
adjustment = labelHeight * scaleFactor;
if (selectionMode().testFlag(QtGraphs3D::SelectionFlag::Row))
if (isRow)
xPos = backgroundScale.x() + adjustment;
else if (selectionMode().testFlag(QtGraphs3D::SelectionFlag::Column))
else if (isColumn)
xPos = backgroundScale.z() + adjustment;
labelTrans = QVector3D(-(xPos + adjustment), 0.0f, 0.0f);
QQuick3DNode *sliceVerticalTitleLabel = nullptr;
if (verticalTitle)
sliceVerticalTitleLabel = verticalTitle;
else
sliceVerticalTitleLabel = m_sliceVerticalTitleLabel;
if (!verticalAxis->title().isEmpty()) {
m_sliceVerticalTitleLabel->setScale(vTitleScale);
m_sliceVerticalTitleLabel->setPosition(labelTrans);
m_sliceVerticalTitleLabel->setProperty("labelWidth", labelWidth);
m_sliceVerticalTitleLabel->setProperty("labelHeight", labelHeight);
m_sliceVerticalTitleLabel->setProperty("labelText", verticalAxis->title());
m_sliceVerticalTitleLabel->setProperty("labelFont", font);
m_sliceVerticalTitleLabel->setProperty("borderVisible", borderVisible);
m_sliceVerticalTitleLabel->setProperty("labelTextColor", verticalLabelTextColor);
m_sliceVerticalTitleLabel->setProperty("backgroundVisible", backgroundVisible);
m_sliceVerticalTitleLabel->setProperty("backgroundColor", backgroundColor);
m_sliceVerticalTitleLabel->setEulerRotation(QVector3D(.0f, .0f, 90.0f));
sliceVerticalTitleLabel->setScale(vTitleScale);
sliceVerticalTitleLabel->setPosition(labelTrans);
sliceVerticalTitleLabel->setProperty("labelWidth", labelWidth);
sliceVerticalTitleLabel->setProperty("labelHeight", labelHeight);
sliceVerticalTitleLabel->setProperty("labelText", verticalAxis->title());
sliceVerticalTitleLabel->setProperty("labelFont", font);
sliceVerticalTitleLabel->setProperty("borderVisible", borderVisible);
sliceVerticalTitleLabel->setProperty("labelTextColor", verticalLabelTextColor);
sliceVerticalTitleLabel->setProperty("backgroundVisible", backgroundVisible);
sliceVerticalTitleLabel->setProperty("backgroundColor", backgroundColor);
sliceVerticalTitleLabel->setEulerRotation(QVector3D(.0f, .0f, 90.0f));
} else {
m_sliceVerticalTitleLabel->setVisible(false);
sliceVerticalTitleLabel->setVisible(false);
}
labelHeight = fm.height() + textPadding;
@ -6722,26 +6821,36 @@ void QQuickGraphsItem::updateSliceLabels()
yPos = backgroundScale.y() * 1.5f + (adjustment * 6.f);
labelTrans = QVector3D(0.0f, -yPos, 0.0f);
QQuick3DNode *sliceHorizontalTitleLabel = nullptr;
if (horizontalTitle)
sliceHorizontalTitleLabel = horizontalTitle;
else
sliceHorizontalTitleLabel = m_sliceHorizontalTitleLabel;
if (!horizontalAxis->title().isEmpty()) {
m_sliceHorizontalTitleLabel->setScale(hTitleScale);
m_sliceHorizontalTitleLabel->setPosition(labelTrans);
m_sliceHorizontalTitleLabel->setProperty("labelWidth", labelWidth);
m_sliceHorizontalTitleLabel->setProperty("labelHeight", labelHeight);
m_sliceHorizontalTitleLabel->setProperty("labelText", horizontalAxis->title());
m_sliceHorizontalTitleLabel->setProperty("labelFont", font);
m_sliceHorizontalTitleLabel->setProperty("borderVisible", borderVisible);
m_sliceHorizontalTitleLabel->setProperty("labelTextColor", horizontalLabelTextColor);
m_sliceHorizontalTitleLabel->setProperty("backgroundVisible", backgroundVisible);
m_sliceHorizontalTitleLabel->setProperty("backgroundColor", backgroundColor);
sliceHorizontalTitleLabel->setScale(hTitleScale);
sliceHorizontalTitleLabel->setPosition(labelTrans);
sliceHorizontalTitleLabel->setProperty("labelWidth", labelWidth);
sliceHorizontalTitleLabel->setProperty("labelHeight", labelHeight);
sliceHorizontalTitleLabel->setProperty("labelText", horizontalAxis->title());
sliceHorizontalTitleLabel->setProperty("labelFont", font);
sliceHorizontalTitleLabel->setProperty("borderVisible", borderVisible);
sliceHorizontalTitleLabel->setProperty("labelTextColor", horizontalLabelTextColor);
sliceHorizontalTitleLabel->setProperty("backgroundVisible", backgroundVisible);
sliceHorizontalTitleLabel->setProperty("backgroundColor", backgroundColor);
} else {
m_sliceHorizontalTitleLabel->setVisible(false);
sliceHorizontalTitleLabel->setVisible(false);
}
m_sliceItemLabel->setProperty("labelFont", font);
m_sliceItemLabel->setProperty("borderVisible", borderVisible);
m_sliceItemLabel->setProperty("labelTextColor", theme()->labelTextColor());
m_sliceItemLabel->setProperty("backgroundVisible", backgroundVisible);
m_sliceItemLabel->setProperty("backgroundColor", backgroundColor);
QQuick3DNode *sliceItemLabel = nullptr;
if (itemLabel)
sliceItemLabel = itemLabel;
else
sliceItemLabel = m_sliceItemLabel;
sliceItemLabel->setProperty("labelFont", font);
sliceItemLabel->setProperty("borderVisible", borderVisible);
sliceItemLabel->setProperty("labelTextColor", theme()->labelTextColor());
sliceItemLabel->setProperty("backgroundVisible", backgroundVisible);
sliceItemLabel->setProperty("backgroundColor", backgroundColor);
}
void QQuickGraphsItem::setUpCamera()

View File

@ -279,13 +279,6 @@ public:
m_graphPositionQueryPending = pending;
}
enum SelectionType {
SelectionNone = 0,
SelectionItem,
SelectionRow,
SelectionColumn,
};
virtual void addSeriesInternal(QAbstract3DSeries *series);
void insertSeries(qsizetype index, QAbstract3DSeries *series);
virtual void removeSeriesInternal(QAbstract3DSeries *series);
@ -665,6 +658,7 @@ protected:
};
virtual void createSliceView();
QQuick3DViewport *createOffscreenSliceView(QtGraphs3D::SliceType sliceType);
void handleQueryPositionChanged(QPoint position);
@ -715,8 +709,14 @@ protected:
void updateGrid();
void updateGridLineType();
void updateLabels();
void updateSliceGrid();
void updateSliceLabels();
void updateSliceGrid(QQuick3DModel *sliceGrid = nullptr,
QtGraphs3D::SliceType selectedFlag = QtGraphs3D::SliceType::SliceNone);
void updateSliceLabels(QQuick3DRepeater *horizontalLabel = nullptr,
QQuick3DRepeater *verticalLabel = nullptr,
QQuick3DNode *horizontalTitle = nullptr,
QQuick3DNode *verticalTitle = nullptr,
QQuick3DNode *itemLabel = nullptr,
QtGraphs3D::SliceType selectedFlag = QtGraphs3D::SliceType::SliceNone);
void updateBackgroundColor();
void setItemSelected(bool selected);
virtual void updateShadowQuality(QtGraphs3D::ShadowQuality quality);
@ -748,7 +748,7 @@ protected:
void setSliceEnabled(bool enabled) { m_sliceEnabled = enabled; }
bool isSliceActivatedChanged() const { return m_sliceActivatedChanged; }
virtual void toggleSliceGraph();
void createSliceCamera();
void createSliceCamera(QQuick3DViewport *sliceView);
bool isSliceOrthoProjection() const { return m_sliceUseOrthoProjection; }
void setSliceOrthoProjection(bool enable) { m_sliceUseOrthoProjection = enable; }

View File

@ -18,6 +18,8 @@
#include <QtQuick3D/private/qquick3ddefaultmaterial_p.h>
#include <QtQuick3D/private/qquick3dprincipledmaterial_p.h>
#include <QtQuick/qquickitemgrabresult.h>
QT_BEGIN_NAMESPACE
/*!
@ -118,6 +120,23 @@ QT_BEGIN_NAMESPACE
* \sa GraphsItem3D::hasSeries()
*/
/*!
* \qmlmethod void Surface3D::removeSeries(Surface3DSeries series)
* Removes the \a series from the graph.
* \sa GraphsItem3D::hasSeries()
*/
/*!
* \qmlmethod void Surface3D::renderSliceToImage(int index, int requestedIndex, QtGraphs3D::SliceType sliceType, QUrl filePath)
* \since 6.10
*
* Exports a 2d slice from series at \a index and saves the result to an image
* at a specified \a filePath.
* To export all series, set \a index to -1.
* The exported slice includes lines of row or column, which is defined by
* \a sliceType at a given \a requestedIndex.
*/
/*!
* \qmlsignal Surface3D::axisXChanged(ValueAxis3D axis)
*
@ -2613,6 +2632,220 @@ void QQuickGraphsSurface::createSliceView()
}
}
QQuick3DViewport *QQuickGraphsSurface::createOffscreenSliceView(int index, int requestedIndex,
QtGraphs3D::SliceType sliceType)
{
QQuick3DViewport *sliceView = QQuickGraphsItem::createOffscreenSliceView(sliceType);
bool isRow = (selectionMode().testFlag(QtGraphs3D::SelectionFlag::Row)
|| sliceType == QtGraphs3D::SliceType::SliceRow);
bool isColumn = (selectionMode().testFlag(QtGraphs3D::SelectionFlag::Column)
|| sliceType == QtGraphs3D::SliceType::SliceColumn);
int modelIndex = 0;
for (const auto &model : std::as_const(m_model)) {
if (index > 0 && modelIndex++ != index)
continue;
QRect sampleSpace = model->sampleSpace;
int rowStart = sampleSpace.top();
int columnStart = sampleSpace.left();
int rowEnd = sampleSpace.bottom() + 1;
int columnEnd = sampleSpace.right() + 1;
int rowCount = sampleSpace.height();
int columnCount = sampleSpace.width();
QVector<SurfaceVertex> selectedSeries;
int indexCount = 0;
const QSurfaceDataArray &array = model->series->dataArray();
const qsizetype maxRow = array.size() - 1;
const qsizetype maxColumn = array.at(0).size() - 1;
const bool ascendingX = array.at(0).at(0).x() < array.at(0).at(maxColumn).x();
const bool ascendingZ = array.at(0).at(0).z() < array.at(maxRow).at(0).z();
if (requestedIndex < 0 || requestedIndex >= maxRow || requestedIndex >= maxColumn) {
qWarning("The index is out of range. The render stops.");
sliceView->setVisible(false);
sliceView->deleteLater();
return nullptr;
}
if (isRow && requestedIndex != -1) {
selectedSeries.reserve(columnCount * 2);
QVector<SurfaceVertex> list;
QSurfaceDataRow row = array.at(requestedIndex);
for (int i = columnStart; i < columnEnd; i++) {
int index = ascendingX ? i : columnEnd - i + columnStart - 1;
QVector3D pos = getNormalizedVertex(row.at(index), false, false);
SurfaceVertex vertex;
vertex.position = pos;
vertex.position.setY(vertex.position.y() - .025f);
vertex.position.setZ(.0f);
selectedSeries.append(vertex);
vertex.position.setY(vertex.position.y() + .05f);
list.append(vertex);
}
selectedSeries.append(list);
indexCount = columnCount - 1;
}
if (isColumn && requestedIndex != -1) {
selectedSeries.reserve(rowCount * 2);
QVector<SurfaceVertex> list;
for (int i = rowStart; i < rowEnd; i++) {
int index = ascendingZ ? i : rowEnd - i + rowStart - 1;
QVector3D pos =
getNormalizedVertex(array.at(index).at(requestedIndex), false, false);
SurfaceVertex vertex;
vertex.position = pos;
vertex.position.setX(-vertex.position.z());
vertex.position.setY(vertex.position.y() - .025f);
vertex.position.setZ(0);
selectedSeries.append(vertex);
vertex.position.setY(vertex.position.y() + .05f);
list.append(vertex);
}
selectedSeries.append(list);
indexCount = rowCount - 1;
QQmlListReference materialRef(model->sliceModel, "materials");
auto material = materialRef.at(0);
material->setProperty("isColumn", true);
}
QVector<quint32> indices;
indices.reserve(indexCount * 6);
for (int i = 0; i < indexCount; i++) {
indices.push_back(i + 1);
indices.push_back(i + indexCount + 1);
indices.push_back(i);
indices.push_back(i + indexCount + 2);
indices.push_back(i + indexCount + 1);
indices.push_back(i + 1);
}
auto surfaceModel = new QQuick3DModel();
surfaceModel->setParent(sliceView->scene());
surfaceModel->setParentItem(sliceView->scene());
auto geometry = new QQuick3DGeometry();
geometry->setParent(surfaceModel);
geometry->setParentItem(surfaceModel);
geometry->setStride(sizeof(SurfaceVertex));
geometry->setPrimitiveType(QQuick3DGeometry::PrimitiveType::Triangles);
geometry->addAttribute(QQuick3DGeometry::Attribute::PositionSemantic, 0,
QQuick3DGeometry::Attribute::F32Type);
geometry->addAttribute(QQuick3DGeometry::Attribute::TexCoord0Semantic, sizeof(QVector3D),
QQuick3DGeometry::Attribute::F32Type);
geometry->addAttribute(QQuick3DGeometry::Attribute::IndexSemantic, 0,
QQuick3DGeometry::Attribute::U32Type);
QByteArray vertexBuffer(reinterpret_cast<char *>(selectedSeries.data()),
selectedSeries.size() * sizeof(SurfaceVertex));
geometry->setVertexData(vertexBuffer);
QByteArray indexBuffer(reinterpret_cast<char *>(indices.data()),
indices.size() * sizeof(quint32));
geometry->setIndexData(indexBuffer);
surfaceModel->setGeometry(geometry);
QQmlListReference materialRef(surfaceModel, "materials");
auto material = createQmlCustomMaterial(QStringLiteral(":/materials/SurfaceSliceMaterial"));
material->setCullMode(QQuick3DMaterial::NoCulling);
QVariant textureInputAsVariant = material->property("custex");
QQuick3DShaderUtilsTextureInput *textureInput =
textureInputAsVariant.value<QQuick3DShaderUtilsTextureInput *>();
QQuick3DTexture *texture = model->texture;
textureInput->setTexture(texture);
materialRef.append(material);
if (model->series->drawMode().testFlag(QSurface3DSeries::DrawSurface))
surfaceModel->setLocalOpacity(1.f);
else
surfaceModel->setLocalOpacity(.0f);
if (model->series->drawMode().testFlag(QSurface3DSeries::DrawWireframe)) {
QVector<quint32> gridIndices;
gridIndices.reserve(indexCount * 4);
for (int i = 0; i < indexCount; i++) {
gridIndices.push_back(i);
gridIndices.push_back(i + indexCount + 1);
gridIndices.push_back(i);
gridIndices.push_back(i + 1);
}
QQuick3DModel *gridModel = new QQuick3DModel();
gridModel->setParent(sliceView->scene());
gridModel->setParentItem(sliceView->scene());
gridModel->setDepthBias(1.0f);
QQuick3DGeometry *gridGeometry = new QQuick3DGeometry();
gridGeometry->setParent(gridModel);
gridGeometry->setStride(sizeof(SurfaceVertex));
gridGeometry->setPrimitiveType(QQuick3DGeometry::PrimitiveType::Lines);
gridGeometry->addAttribute(QQuick3DGeometry::Attribute::PositionSemantic, 0,
QQuick3DGeometry::Attribute::F32Type);
gridGeometry->addAttribute(QQuick3DGeometry::Attribute::IndexSemantic, 0,
QQuick3DGeometry::Attribute::U32Type);
QByteArray gridIndexBuffer(reinterpret_cast<char *>(gridIndices.data()),
gridIndices.size() * sizeof(quint32));
gridGeometry->setVertexData(vertexBuffer);
gridGeometry->setIndexData(gridIndexBuffer);
gridGeometry->update();
gridModel->setGeometry(gridGeometry);
QQmlListReference gridMaterialRef(gridModel, "materials");
QQuick3DPrincipledMaterial *gridMaterial = new QQuick3DPrincipledMaterial();
gridMaterial->setParent(gridModel);
gridMaterial->setLighting(QQuick3DPrincipledMaterial::NoLighting);
gridMaterial->setParent(gridModel);
QColor gridColor = model->series->wireframeColor();
gridMaterial->setBaseColor(gridColor);
gridMaterialRef.append(gridMaterial);
}
}
return sliceView;
}
QSharedPointer<QQuickItemGrabResult> QQuickGraphsSurface::renderSliceToImage(
int index, int requestedIndex, QtGraphs3D::SliceType sliceType)
{
QQuick3DViewport *sliceView = createOffscreenSliceView(index, requestedIndex, sliceType);
if (!sliceView) {
return QSharedPointer<QQuickItemGrabResult>();
}
QSharedPointer<QQuickItemGrabResult> grabbed = sliceView->grabToImage();
connect(grabbed.data(), &QQuickItemGrabResult::ready, this, [sliceView]() {
sliceView->setVisible(false);
sliceView->deleteLater();
});
return grabbed;
}
void QQuickGraphsSurface::renderSliceToImage(int index, int requestedIndex,
QtGraphs3D::SliceType sliceType, const QUrl &filePath)
{
QQuick3DViewport *sliceView = createOffscreenSliceView(index, requestedIndex, sliceType);
if (!sliceView)
return;
if (filePath.isEmpty()) {
qWarning("Save path is not defined.");
sliceView->setVisible(false);
sliceView->deleteLater();
return;
}
QSharedPointer<QQuickItemGrabResult> grabbed = sliceView->grabToImage();
connect(grabbed.data(), &QQuickItemGrabResult::ready, this, [grabbed, sliceView, filePath]() {
if (!grabbed.data()->saveToFile(filePath))
qWarning("Saving requested slice view to image failed");
sliceView->setVisible(false);
sliceView->deleteLater();
});
}
void QQuickGraphsSurface::updateSliceItemLabel(const QString &label, QVector3D position)
{
QQuickGraphsItem::updateSliceItemLabel(label, position);
@ -2657,7 +2890,9 @@ void QQuickGraphsSurface::updateSelectionMode(QtGraphs3D::SelectionFlags mode)
void QQuickGraphsSurface::addSliceModel(SurfaceModel *model)
{
QQuick3DViewport *sliceParent = sliceView();
QQuick3DViewport *sliceParent = nullptr;
sliceParent = sliceView();
auto surfaceModel = new QQuick3DModel();
surfaceModel->setParent(sliceParent->scene());

View File

@ -144,6 +144,11 @@ public:
void handleSeriesVisibilityChangedBySender(QObject *sender) override;
void adjustAxisRanges() override;
QSharedPointer<QQuickItemGrabResult> renderSliceToImage(int index, int requestedIndex, QtGraphs3D::SliceType sliceType);
Q_REVISION(6, 10)
Q_INVOKABLE void renderSliceToImage(int index, int requestedIndex,
QtGraphs3D::SliceType sliceType, const QUrl &filePath);
protected:
void componentComplete() override;
void synchData() override;
@ -160,6 +165,9 @@ protected:
void updateSliceItemLabel(const QString &label, QVector3D position) override;
void updateSelectionMode(QtGraphs3D::SelectionFlags mode) override;
QQuick3DViewport *createOffscreenSliceView(int index, int requestedIndex,
QtGraphs3D::SliceType sliceType);
public Q_SLOTS:
void handleAxisXChanged(QAbstract3DAxis *axis) override;
void handleAxisYChanged(QAbstract3DAxis *axis) override;

View File

@ -51,6 +51,20 @@ QT_BEGIN_NAMESPACE
Multi-series selection is not supported for Q3DScatterWidgetItem.
*/
/*!
\enum QtGraphs3D::SliceType
\since 6.10
Type of slice to grab to an image.
\value SliceNone
Slice type is not defined.
\value SliceRow
Slice for rows.
\value SliceColumn
Slice for columns.
*/
/*!
\enum QtGraphs3D::ShadowQuality

View File

@ -28,6 +28,13 @@ enum class SelectionFlag {
Q_FLAG_NS(SelectionFlag)
Q_DECLARE_FLAGS(SelectionFlags, SelectionFlag)
enum class SliceType {
SliceNone,
SliceRow,
SliceColumn,
};
Q_ENUM_NS(SliceType)
enum class ShadowQuality {
None,
Low,

View File

@ -449,4 +449,22 @@ const QQuickGraphsBars *Q3DBarsWidgetItem::graphBars() const
return static_cast<const QQuickGraphsBars *>(d->m_graphsItem.get());
}
/*!
* Exports the requested slice view to an image.
* The exported slice is bars of row or column, which is defined by \a sliceType,
* at a given \a requestedIndex.
* Returns a shared pointer to grab result which can be used to access the
* exported image when it's ready. Image is rendered with the current
* antialiasing settings.
*
* \sa QQuickItem::grabToImage()
*
* \since 6.10
*/
QSharedPointer<QQuickItemGrabResult>
Q3DBarsWidgetItem::renderSliceToImage(int requestedIndex, QtGraphs3D::SliceType sliceType)
{
return graphBars()->renderSliceToImage(requestedIndex, sliceType);
}
QT_END_NAMESPACE

View File

@ -74,6 +74,10 @@ public:
void setFloorLevel(float level);
float floorLevel() const;
Q_REVISION(6, 10)
QSharedPointer<QQuickItemGrabResult> renderSliceToImage(int requestedIndex,
QtGraphs3D::SliceType sliceType);
protected:
bool event(QEvent *event) override;

View File

@ -4,6 +4,7 @@
#include <QtGraphsWidgets/q3dsurfacewidgetitem.h>
#include <private/q3dsurfacewidgetitem_p.h>
#include <private/qquickgraphssurface_p.h>
#include "q3dsurfacewidgetitem.h"
QT_BEGIN_NAMESPACE
@ -284,6 +285,27 @@ void Q3DSurfaceWidgetItem::releaseAxis(QValue3DAxis *axis)
return graphSurface()->releaseAxis(axis);
}
/*!
* Exports the requested slice view to an image.
* The sliced result is the series of \a index. To export all series, set
* \a index to -1.
* The exported slice is a line of row or column, which is defined by \a sliceType,
* at a given \a requestedIndex.
* Returns a shared pointer to grab result which can be used to access the
* exported image when it's ready. Image is rendered with the current
* antialiasing settings.
*
* \sa QQuickItem::grabToImage()
*
* \since 6.10
*/
QSharedPointer<QQuickItemGrabResult>
Q3DSurfaceWidgetItem::renderSliceToImage(int index, int requestedIndex,
QtGraphs3D::SliceType sliceType)
{
return graphSurface()->renderSliceToImage(index, requestedIndex, sliceType);
}
/*!
* Returns the list of all added axes.
*

View File

@ -46,6 +46,10 @@ public:
void setFlipHorizontalGrid(bool flip);
bool flipHorizontalGrid() const;
Q_REVISION(6, 10)
QSharedPointer<QQuickItemGrabResult> renderSliceToImage(int index, int requestedIndex,
QtGraphs3D::SliceType sliceType);
protected:
bool event(QEvent *event) override;

View File

@ -39,6 +39,7 @@ if(QT_FEATURE_graphs_3d)
endif()
if(QT_FEATURE_graphs_3d_bars3d AND QT_FEATURE_graphs_3d_surface3d)
add_subdirectory(qmltheme)
add_subdirectory(qmlrenderslicetoimage)
endif()
if(QT_FEATURE_graphs_3d_bars3d AND QT_FEATURE_graphs_3d_scatter3d AND QT_FEATURE_graphs_3d_surface3d)
add_subdirectory(qmlmultitest)
@ -65,6 +66,7 @@ if(QT_FEATURE_graphs_3d)
if(QT_FEATURE_graphs_3d_bars3d AND QT_FEATURE_graphs_3d_surface3d)
add_subdirectory(itemmodel)
add_subdirectory(itemmodeltest)
add_subdirectory(renderslicetoimage)
endif()
if(QT_FEATURE_graphs_3d_bars3d AND QT_FEATURE_graphs_3d_scatter3d AND QT_FEATURE_graphs_3d_surface3d)
add_subdirectory(multigraphs)

View File

@ -11,7 +11,8 @@ qtHaveModule(quick) {
qmlcustominput \
qmllegend \
qmlsurfacelayers \
qmltestbed
qmltestbed \
qmlrenderslicetoimage
}
!android:!ios:!winrt {
@ -24,7 +25,8 @@ qtHaveModule(quick) {
volumetrictest \
rotations \
custominput \
itemmodel
itemmodel \
renderslicetoimage
# For testing code snippets of minimal applications
SUBDIRS += minimalbars \

View File

@ -0,0 +1,45 @@
# Copyright (C) 2025 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
cmake_minimum_required(VERSION 3.16)
if (NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT)
project(tst_qmlrenderslicetoimage LANGUAGES C CXX ASM)
find_package(Qt6BuildInternals COMPONENTS STANDALONE_TEST)
endif()
qt_internal_add_manual_test(tst_qmlrenderslicetoimage
GUI
SOURCES
main.cpp
)
target_link_libraries(tst_qmlrenderslicetoimage PUBLIC
Qt::Gui
Qt::Graphs
)
set(qmlrenderslicetoimage_resource_files
"qml/qmlrenderslicetoimage/main.qml"
"qml/qmlrenderslicetoimage/SurfaceGraph.qml"
"qml/qmlrenderslicetoimage/BarGraph.qml"
)
qt_internal_add_resource(tst_qmlrenderslicetoimage "qmlgrabsliceview"
PREFIX
"/"
FILES
${qmlrenderslicetoimage_resource_files}
)
set(qmlrenderslicetoimage_image_resource_files
"layer_1.png"
"layer_2.png"
"layer_3.png"
)
qt6_add_resources(tst_qmlrenderslicetoimage "qmlsurfacelayers1"
PREFIX
"/heightmaps"
FILES
${qmlrenderslicetoimage_image_resource_files}
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -0,0 +1,33 @@
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <QtGui/QGuiApplication>
#include <QtCore/QDir>
#include <QtQuick/QQuickView>
#include <QtQml/QQmlEngine>
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQuickView viewer;
// The following are needed to make examples run without having to install the module
// in desktop environments.
#ifdef Q_OS_WIN
QString extraImportPath(QStringLiteral("%1/../../../%2"));
#else
QString extraImportPath(QStringLiteral("%1/../../%2"));
#endif
viewer.engine()->addImportPath(extraImportPath.arg(QGuiApplication::applicationDirPath(),
QString::fromLatin1("qml")));
QObject::connect(viewer.engine(), &QQmlEngine::quit, &viewer, &QWindow::close);
viewer.setTitle(QStringLiteral("QML Grab Slice View Test"));
viewer.setSource(QUrl("qrc:/qml/qmlrenderslicetoimage/main.qml"));
viewer.setResizeMode(QQuickView::SizeRootObjectToView);
viewer.show();
return app.exec();
}

View File

@ -0,0 +1,236 @@
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
import QtQuick
import QtGraphs
Item {
property var graph: barGraph
Bars3D {
id: barGraph
anchors.fill: parent
shadowQuality: Graphs3D.ShadowQuality.SoftHigh
selectionMode: Graphs3D.SelectionFlag.Row | Graphs3D.SelectionFlag.Slice
theme: GraphsTheme {
colorScheme: GraphsTheme.ColorScheme.Dark
labelBorderVisible: true
labelFont.pointSize: 35
labelBackgroundVisible: true
colorStyle: GraphsTheme.ColorStyle.RangeGradient
singleHighlightGradient: customGradient
Gradient {
id: customGradient
GradientStop { position: 1.0; color: "#FFFF00" }
GradientStop { position: 0.0; color: "#808000" }
}
}
barThickness: 0.7
barSpacing: Qt.size(0.5, 0.5)
barSpacingRelative: false
cameraPreset: Graphs3D.CameraPreset.IsometricLeftHigh
columnAxis: graphAxes.column
rowAxis: graphAxes.row
valueAxis: graphAxes.value
Bar3DSeries {
id: secondarySeries
visible: true
itemLabelFormat: "Expenses, @colLabel, @rowLabel: -@valueLabel"
baseGradient: secondaryGradient
ItemModelBarDataProxy {
id: secondaryProxy
itemModel: graphData.model
rowRole: "timestamp"
columnRole: "timestamp"
valueRole: "expenses"
rowRolePattern: /^(\d\d\d\d).*$/
columnRolePattern: /^.*-(\d\d)$/
valueRolePattern: /-/
rowRoleReplace: "\\1"
columnRoleReplace: "\\1"
multiMatchBehavior: ItemModelBarDataProxy.MultiMatchBehavior.Cumulative
}
Gradient {
id: secondaryGradient
GradientStop { position: 1.0; color: "#FF0000" }
GradientStop { position: 0.0; color: "#600000" }
}
}
Bar3DSeries {
id: barSeries
itemLabelFormat: "Income, @colLabel, @rowLabel: @valueLabel"
baseGradient: barGradient
ItemModelBarDataProxy {
id: modelProxy
itemModel: graphData.model
rowRole: "timestamp"
columnRole: "timestamp"
valueRole: "income"
rowRolePattern: /^(\d\d\d\d).*$/
columnRolePattern: /^.*-(\d\d)$/
rowRoleReplace: "\\1"
columnRoleReplace: "\\1"
multiMatchBehavior: ItemModelBarDataProxy.MultiMatchBehavior.Cumulative
}
Gradient {
id: barGradient
GradientStop { position: 1.0; color: "#00FF00" }
GradientStop { position: 0.0; color: "#006000" }
}
}
}
Item {
id: graphAxes
property alias column: columnAxis
property alias row: rowAxis
property alias value: valueAxis
property alias total: totalAxis
// Custom labels for columns, since the data contains abbreviated month names.
Category3DAxis {
id: columnAxis
labels: ["January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"]
labelAutoAngle: 30
}
Category3DAxis {
id: totalAxis
labels: ["Yearly total"]
labelAutoAngle: 30
}
Category3DAxis {
// For row labels we can use row labels from data proxy, no labels defined for rows.
id: rowAxis
labelAutoAngle: 30
}
Value3DAxis {
id: valueAxis
min: 0
max: 35
labelFormat: "%.2f M\u20AC"
title: "Monthly income"
labelAutoAngle: 90
}
}
Item {
id: graphData
property alias model: dataModel
property var modelAsJsArray: {
var arr = [];
for (var i = 0; i < dataModel.count; i++) {
var row = dataModel.get(i);
arr.push({
timestamp: row.timestamp,
expenses: row.expenses,
income: row.income
});
}
return arr;
}
ListModel {
id: dataModel
ListElement{ timestamp: "2016-01"; expenses: "-4"; income: "5" }
ListElement{ timestamp: "2016-02"; expenses: "-5"; income: "6" }
ListElement{ timestamp: "2016-03"; expenses: "-7"; income: "4" }
ListElement{ timestamp: "2016-04"; expenses: "-3"; income: "2" }
ListElement{ timestamp: "2016-05"; expenses: "-4"; income: "1" }
ListElement{ timestamp: "2016-06"; expenses: "-2"; income: "2" }
ListElement{ timestamp: "2016-07"; expenses: "-1"; income: "3" }
ListElement{ timestamp: "2016-08"; expenses: "-5"; income: "1" }
ListElement{ timestamp: "2016-09"; expenses: "-2"; income: "3" }
ListElement{ timestamp: "2016-10"; expenses: "-5"; income: "2" }
ListElement{ timestamp: "2016-11"; expenses: "-8"; income: "5" }
ListElement{ timestamp: "2016-12"; expenses: "-3"; income: "3" }
ListElement{ timestamp: "2017-01"; expenses: "-3"; income: "1" }
ListElement{ timestamp: "2017-02"; expenses: "-4"; income: "2" }
ListElement{ timestamp: "2017-03"; expenses: "-12"; income: "4" }
ListElement{ timestamp: "2017-04"; expenses: "-13"; income: "6" }
ListElement{ timestamp: "2017-05"; expenses: "-14"; income: "11" }
ListElement{ timestamp: "2017-06"; expenses: "-7"; income: "7" }
ListElement{ timestamp: "2017-07"; expenses: "-6"; income: "4" }
ListElement{ timestamp: "2017-08"; expenses: "-4"; income: "15" }
ListElement{ timestamp: "2017-09"; expenses: "-2"; income: "18" }
ListElement{ timestamp: "2017-10"; expenses: "-29"; income: "25" }
ListElement{ timestamp: "2017-11"; expenses: "-23"; income: "29" }
ListElement{ timestamp: "2017-12"; expenses: "-5"; income: "9" }
ListElement{ timestamp: "2018-01"; expenses: "-3"; income: "8" }
ListElement{ timestamp: "2018-02"; expenses: "-8"; income: "14" }
ListElement{ timestamp: "2018-03"; expenses: "-10"; income: "20" }
ListElement{ timestamp: "2018-04"; expenses: "-12"; income: "24" }
ListElement{ timestamp: "2018-05"; expenses: "-10"; income: "19" }
ListElement{ timestamp: "2018-06"; expenses: "-5"; income: "8" }
ListElement{ timestamp: "2018-07"; expenses: "-1"; income: "4" }
ListElement{ timestamp: "2018-08"; expenses: "-7"; income: "12" }
ListElement{ timestamp: "2018-09"; expenses: "-4"; income: "16" }
ListElement{ timestamp: "2018-10"; expenses: "-22"; income: "33" }
ListElement{ timestamp: "2018-11"; expenses: "-16"; income: "25" }
ListElement{ timestamp: "2018-12"; expenses: "-2"; income: "7" }
ListElement{ timestamp: "2019-01"; expenses: "-4"; income: "5" }
ListElement{ timestamp: "2019-02"; expenses: "-4"; income: "7" }
ListElement{ timestamp: "2019-03"; expenses: "-11"; income: "14" }
ListElement{ timestamp: "2019-04"; expenses: "-16"; income: "22" }
ListElement{ timestamp: "2019-05"; expenses: "-3"; income: "5" }
ListElement{ timestamp: "2019-06"; expenses: "-4"; income: "8" }
ListElement{ timestamp: "2019-07"; expenses: "-7"; income: "9" }
ListElement{ timestamp: "2019-08"; expenses: "-9"; income: "13" }
ListElement{ timestamp: "2019-09"; expenses: "-1"; income: "6" }
ListElement{ timestamp: "2019-10"; expenses: "-14"; income: "25" }
ListElement{ timestamp: "2019-11"; expenses: "-19"; income: "29" }
ListElement{ timestamp: "2019-12"; expenses: "-5"; income: "7" }
ListElement{ timestamp: "2020-01"; expenses: "-14"; income: "22" }
ListElement{ timestamp: "2020-02"; expenses: "-5"; income: "7" }
ListElement{ timestamp: "2020-03"; expenses: "-1"; income: "9" }
ListElement{ timestamp: "2020-04"; expenses: "-1"; income: "12" }
ListElement{ timestamp: "2020-05"; expenses: "-5"; income: "9" }
ListElement{ timestamp: "2020-06"; expenses: "-5"; income: "8" }
ListElement{ timestamp: "2020-07"; expenses: "-3"; income: "7" }
ListElement{ timestamp: "2020-08"; expenses: "-1"; income: "5" }
ListElement{ timestamp: "2020-09"; expenses: "-2"; income: "4" }
ListElement{ timestamp: "2020-10"; expenses: "-10"; income: "13" }
ListElement{ timestamp: "2020-11"; expenses: "-12"; income: "17" }
ListElement{ timestamp: "2020-12"; expenses: "-6"; income: "9" }
ListElement{ timestamp: "2021-01"; expenses: "-2"; income: "6" }
ListElement{ timestamp: "2021-02"; expenses: "-4"; income: "8" }
ListElement{ timestamp: "2021-03"; expenses: "-7"; income: "12" }
ListElement{ timestamp: "2021-04"; expenses: "-9"; income: "15" }
ListElement{ timestamp: "2021-05"; expenses: "-7"; income: "19" }
ListElement{ timestamp: "2021-06"; expenses: "-9"; income: "18" }
ListElement{ timestamp: "2021-07"; expenses: "-13"; income: "17" }
ListElement{ timestamp: "2021-08"; expenses: "-5"; income: "9" }
ListElement{ timestamp: "2021-09"; expenses: "-3"; income: "8" }
ListElement{ timestamp: "2021-10"; expenses: "-13"; income: "15" }
ListElement{ timestamp: "2021-11"; expenses: "-8"; income: "17" }
ListElement{ timestamp: "2021-12"; expenses: "-7"; income: "10" }
ListElement{ timestamp: "2022-01"; expenses: "-12"; income: "16" }
ListElement{ timestamp: "2022-02"; expenses: "-24"; income: "28" }
ListElement{ timestamp: "2022-03"; expenses: "-27"; income: "22" }
ListElement{ timestamp: "2022-04"; expenses: "-29"; income: "25" }
ListElement{ timestamp: "2022-05"; expenses: "-27"; income: "29" }
ListElement{ timestamp: "2022-06"; expenses: "-19"; income: "18" }
ListElement{ timestamp: "2022-07"; expenses: "-13"; income: "17" }
ListElement{ timestamp: "2022-08"; expenses: "-15"; income: "19" }
ListElement{ timestamp: "2022-09"; expenses: "-3"; income: "8" }
ListElement{ timestamp: "2022-10"; expenses: "-3"; income: "6" }
ListElement{ timestamp: "2022-11"; expenses: "-4"; income: "8" }
ListElement{ timestamp: "2022-12"; expenses: "-5"; income: "9" }
}
}
}

View File

@ -0,0 +1,92 @@
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
import QtQuick
import QtGraphs
Item {
property real fontSize: 12
property real windowRatio: 4
property real maxAxisSegmentCount: 20
property var graph: surfaceLayers
id: surfaceView
//! [0]
Gradient {
id: layerOneGradient
GradientStop { position: 0.0; color: "black" }
GradientStop { position: 0.31; color: "tan" }
GradientStop { position: 0.32; color: "green" }
GradientStop { position: 0.40; color: "darkslategray" }
GradientStop { position: 1.0; color: "white" }
}
Gradient {
id: layerTwoGradient
GradientStop { position: 0.315; color: "blue" }
GradientStop { position: 0.33; color: "white" }
}
Gradient {
id: layerThreeGradient
GradientStop { position: 0.0; color: "red" }
GradientStop { position: 0.15; color: "black" }
}
//! [0]
Surface3D {
id: surfaceLayers
width: surfaceView.width
height: surfaceView.height
theme: GraphsTheme {
theme: GraphsTheme.Theme.QtGreen
labelFont.pointSize: 35
colorStyle: GraphsTheme.ColorStyle.RangeGradient
}
shadowQuality: Graphs3D.ShadowQuality.None
selectionMode: Graphs3D.SelectionFlag.Row | Graphs3D.SelectionFlag.Slice | Graphs3D.SelectionFlag.MultiSeries
cameraPreset: Graphs3D.CameraPreset.IsometricLeft
axisY.min: 20
axisY.max: 200
axisX.segmentCount: 5
axisX.subSegmentCount: 2
axisX.labelFormat: "%i"
axisZ.segmentCount: 5
axisZ.subSegmentCount: 2
axisZ.labelFormat: "%i"
axisY.segmentCount: 5
axisY.subSegmentCount: 2
axisY.labelFormat: "%i"
Surface3DSeries {
id: layerOneSeries
baseGradient: layerOneGradient
HeightMapSurfaceDataProxy {
heightMapFile: ":/heightmaps/layer_1.png"
}
shading: Surface3DSeries.Shading.Smooth
drawMode: Surface3DSeries.DrawSurface
}
Surface3DSeries {
id: layerTwoSeries
baseGradient: layerTwoGradient
HeightMapSurfaceDataProxy {
heightMapFile: ":/heightmaps/layer_2.png"
}
shading: Surface3DSeries.Shading.Smooth
drawMode: Surface3DSeries.DrawSurface
}
Surface3DSeries {
id: layerThreeSeries
baseGradient: layerThreeGradient
HeightMapSurfaceDataProxy {
heightMapFile: ":/heightmaps/layer_3.png"
}
shading: Surface3DSeries.Shading.Smooth
drawMode: Surface3DSeries.DrawSurface
}
}
}

View File

@ -0,0 +1,113 @@
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
import QtCore
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import QtQuick.Dialogs
import QtGraphs
import "."
Item {
id: mainview
width: 1920
height: 1080
ColumnLayout {
id: controls
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
property url selectedFile: fileDialog.selectedFile
Button {
text: "Set Save Path"
onClicked: {
fileDialog.open()
}
}
Text {
text: "Path to save :"
}
Text {
text: controls.selectedFile.toString()
}
RadioButton {
id: rowRadio
text: "Row"
checked: true
}
RadioButton {
text: "Column"
}
Text {
text: "Selected " + (rowRadio.checked ? "Row" : "Column")
}
TextField {
id: textField
validator: RegularExpressionValidator{
regularExpression: /\d+/
}
}
Button {
text: "Slice To Image"
onClicked: {
var rowCol = (rowRadio.checked ? Graphs3D.SelectionFlag.Row : Graphs3D.SelectionFlag.Column)
var index = textField.text
if (tabBar.currentIndex === 0)
surfaceGraph.graph.renderSliceToImage(-1, index, rowCol, controls.selectedFile);
else
barGraph.graph.renderSliceToImage(index, rowCol, controls.selectedFile);
}
}
}
FileDialog {
id: fileDialog
currentFolder: StandardPaths.standardLocations(StandardPaths.PicturesLocation)[0]
fileMode: FileDialog.SaveFile
selectedFile: controls.selectedFile
onAccepted: {
controls.selectedFile = selectedFile
}
}
Item {
anchors.left: parent.left
anchors.right: controls.left
height: parent.height
TabBar {
id: tabBar
width: parent.width
TabButton {
text: "Surface"
}
TabButton {
text: "Bar"
}
}
StackLayout {
width: parent.width
currentIndex: tabBar.currentIndex
anchors.top: tabBar.bottom
anchors.bottom: parent.bottom
SurfaceGraph {
id: surfaceGraph
}
BarGraph {
id: barGraph
}
}
}
}

View File

@ -0,0 +1,9 @@
!include( ../tests.pri ) {
error( "Couldn't find the tests.pri file!" )
}
SOURCES += main.cpp
RESOURCES += qmlrenderslicetoimage.qrc
OTHER_FILES += qml/qmlrenderslicetoimage/*

View File

@ -0,0 +1,12 @@
<RCC>
<qresource prefix="/">
<file>qml/qmlgrabsliceview/main.qml</file>
<file>qml/qmlgrabsliceview/SurfaceGraph.qml</file>
<file>qml/qmlgrabsliceview/BarGraph.qml</file>
</qresource>
<qresource prefix="/heightmaps">
<file>layer_1.png</file>
<file>layer_2.png</file>
<file>layer_3.png</file>
</qresource>
</RCC>

View File

@ -0,0 +1,41 @@
# Copyright (C) 2025 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
cmake_minimum_required(VERSION 3.16)
if (NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT)
project(tst_renderslicetoimage LANGUAGES C CXX ASM)
find_package(Qt6BuildInternals COMPONENTS STANDALONE_TEST)
endif()
set(CMAKE_INCLUDE_CURRENT_DIR ON)
qt_internal_add_manual_test(tst_renderslicetoimage
GUI
SOURCES
bargraph.cpp bargraph.h
bargraphmodifier.cpp bargraphmodifier.h
bargraphwidget.cpp bargraphwidget.h
main.cpp
surfacegraph.cpp surfacegraph.h
surfacegraphmodifier.cpp surfacegraphmodifier.h
surfacegraphwidget.cpp surfacegraphwidget.h
)
target_link_libraries(tst_renderslicetoimage PUBLIC
Qt::Core
Qt::Gui
Qt::Widgets
Qt::Graphs
Qt::GraphsWidgets
)
set(renderslicetoimage_resource_files
)
qt6_add_resources(tst_renderslicetoimage "renderslicetoimage"
PREFIX
"/"
FILES
${renderslicetoimage_resource_files}
)

View File

@ -0,0 +1,88 @@
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "bargraph.h"
#include "bargraphmodifier.h"
#include "bargraphwidget.h"
#include <QtCore/qregularexpression.h>
#include <QtGui/qvalidator.h>
#include <QtQuick/qquickitemgrabresult.h>
#include <QtWidgets/qboxlayout.h>
#include <QtWidgets/qlabel.h>
#include <QtWidgets/qlineedit.h>
#include <QtWidgets/qpushbutton.h>
#include <QtWidgets/qradiobutton.h>
using namespace Qt::StringLiterals;
BarGraph::BarGraph(QWidget *parent)
{
m_barWidget = new QWidget(parent);
initialize();
}
void BarGraph::initialize()
{
m_barGraphWidget = new BarGraphWidget();
m_barGraphWidget->initialize();
QSize screenSize = m_barGraphWidget->screen()->size();
m_barGraphWidget->setMinimumSize(QSize(screenSize.width() / 2, screenSize.height() / 1.75));
m_barGraphWidget->setMaximumSize(screenSize);
m_barGraphWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
m_barGraphWidget->setFocusPolicy(Qt::StrongFocus);
QHBoxLayout *hLayout = new QHBoxLayout;
hLayout->addWidget(m_barGraphWidget, 1);
m_barWidget->setLayout(hLayout);
QVBoxLayout *vLayout = new QVBoxLayout;
hLayout->addLayout(vLayout);
vLayout->setAlignment(Qt::AlignCenter);
m_rowRadioButton = new QRadioButton(m_barWidget);
m_rowRadioButton->setText(u"Row"_s);
m_rowRadioButton->setChecked(true);
QRadioButton *columnRadioButton = new QRadioButton(m_barWidget);
columnRadioButton->setText(u"Column"_s);
columnRadioButton->setChecked(false);
m_lineSelectText = new QLineEdit(m_barWidget);
QRegularExpression re("\\d+");
QRegularExpressionValidator *reValidator = new QRegularExpressionValidator(re, m_barWidget);
m_lineSelectText->setValidator(reValidator);
QPushButton *sliceToImageButton = new QPushButton(m_barWidget);
sliceToImageButton->setText(u"Slice To Image"_s);
m_sliceResultLabel = new QLabel(m_barWidget);
vLayout->addWidget(m_rowRadioButton);
vLayout->addWidget(columnRadioButton);
vLayout->addWidget(m_lineSelectText);
vLayout->addWidget(sliceToImageButton);
vLayout->addWidget(m_sliceResultLabel);
m_barGraphWidget->raise();
m_modifier = new BarGraphModifier(m_barGraphWidget->barGraph(), this);
QObject::connect(sliceToImageButton,
&QPushButton::clicked,
this,
&BarGraph::renderSliceToImage);
}
void BarGraph::renderSliceToImage()
{
int index = m_lineSelectText->text().isEmpty() ? -1 : m_lineSelectText->text().toInt();
QtGraphs3D::SliceType sliceType = QtGraphs3D::SliceType::SliceRow;
if (!m_rowRadioButton->isChecked())
sliceType = QtGraphs3D::SliceType::SliceColumn;
m_grab = m_modifier->renderSliceToImage(sliceType, index);
connect(m_grab.data(), &QQuickItemGrabResult::ready, this, [&]() {
m_sliceResultLabel->setPixmap(QPixmap::fromImage(m_grab.data()->image()));
});
}

View File

@ -0,0 +1,38 @@
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#ifndef BARGRAPH_H
#define BARGRAPH_H
#include <QtCore/qobject.h>
#include <QtQuick/qquickitemgrabresult.h>
class BarGraphModifier;
class BarGraphWidget;
class QLineEdit;
class QLabel;
class QRadioButton;
class BarGraph : public QObject
{
Q_OBJECT
public:
BarGraph(QWidget *parent = nullptr);
void initialize();
QWidget *barWidget() { return m_barWidget; }
private:
void renderSliceToImage();
BarGraphModifier *m_modifier = nullptr;
BarGraphWidget *m_barGraphWidget = nullptr;
QWidget *m_barWidget = nullptr;
QRadioButton *m_rowRadioButton = nullptr;
QLineEdit *m_lineSelectText = nullptr;
QLabel *m_sliceResultLabel = nullptr;
QSharedPointer<QQuickItemGrabResult> m_grab;
};
#endif

View File

@ -0,0 +1,147 @@
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "bargraphmodifier.h"
#include <QtCore/qmath.h>
#include <QtGraphs/q3dscene.h>
#include <QtGraphs/qgraphstheme.h>
#include <QtGraphs/qbar3dseries.h>
#include <QtGraphs/qbardataproxy.h>
#include <QtGraphs/qcategory3daxis.h>
#include <QtGraphs/qvalue3daxis.h>
#include <QtWidgets/qcombobox.h>
using namespace Qt::StringLiterals;
// TODO: Many of the values do not affect custom proxy series now - should be fixed
//! [set up the graph]
BarGraphModifier::BarGraphModifier(Q3DBarsWidgetItem *bargraph, QObject *parent)
: QObject(parent)
, m_graph(bargraph)
, m_temperatureAxis(new QValue3DAxis)
, m_yearAxis(new QCategory3DAxis)
, m_monthAxis(new QCategory3DAxis)
, m_primarySeries(new QBar3DSeries)
, m_secondarySeries(new QBar3DSeries)
, m_celsiusString(u"°C"_s)
{
m_graph->setShadowQuality(QtGraphs3D::ShadowQuality::SoftMedium);
m_graph->setMultiSeriesUniform(true);
m_graph->activeTheme()->setPlotAreaBackgroundVisible(false);
m_graph->activeTheme()->setLabelFont(QFont("Times New Roman", m_fontSize));
m_graph->activeTheme()->setLabelBackgroundVisible(true);
m_graph->setSelectionMode(QtGraphs3D::SelectionFlag::Column | QtGraphs3D::SelectionFlag::Slice);
m_months = {"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December"};
m_years = {"2015", "2016", "2017", "2018", "2019", "2020", "2021", "2022"};
m_temperatureAxis->setTitle("Average temperature");
m_temperatureAxis->setSegmentCount(m_segments);
m_temperatureAxis->setSubSegmentCount(m_subSegments);
m_temperatureAxis->setRange(m_minval, m_maxval);
m_temperatureAxis->setLabelFormat(u"%.1f "_s + m_celsiusString);
m_temperatureAxis->setLabelAutoAngle(30.0f);
m_temperatureAxis->setTitleVisible(true);
m_yearAxis->setTitle("Year");
m_yearAxis->setLabelAutoAngle(30.0f);
m_yearAxis->setTitleVisible(true);
m_monthAxis->setTitle("Month");
m_monthAxis->setLabelAutoAngle(30.0f);
m_monthAxis->setTitleVisible(true);
m_graph->setValueAxis(m_temperatureAxis);
m_graph->setRowAxis(m_yearAxis);
m_graph->setColumnAxis(m_monthAxis);
m_primarySeries->setItemLabelFormat(u"Oulu - @colLabel @rowLabel: @valueLabel"_s);
m_primarySeries->setMesh(QAbstract3DSeries::Mesh::BevelBar);
m_primarySeries->setMeshSmooth(false);
m_secondarySeries->setItemLabelFormat(u"Helsinki - @colLabel @rowLabel: @valueLabel"_s);
m_secondarySeries->setMesh(QAbstract3DSeries::Mesh::BevelBar);
m_secondarySeries->setMeshSmooth(false);
m_secondarySeries->setVisible(false);
m_graph->addSeries(m_primarySeries);
m_graph->addSeries(m_secondarySeries);
changePresetCamera();
resetTemperatureData();
}
void BarGraphModifier::resetTemperatureData()
{
static const float tempOulu[8][12] = {
{-7.4f, -2.4f, 0.0f, 3.0f, 8.2f, 11.6f, 14.7f, 15.4f, 11.4f, 4.2f, 2.1f, -2.3f}, // 2015
{-13.4f, -3.9f, -1.8f, 3.1f, 10.6f, 13.7f, 17.8f, 13.6f, 10.7f, 3.5f, -3.1f, -4.2f}, // 2016
{-5.7f, -6.7f, -3.0f, -0.1f, 4.7f, 12.4f, 16.1f, 14.1f, 9.4f, 3.0f, -0.3f, -3.2f}, // 2017
{-6.4f, -11.9f, -7.4f, 1.9f, 11.4f, 12.4f, 21.5f, 16.1f, 11.0f, 4.4f, 2.1f, -4.1f}, // 2018
{-11.7f, -6.1f, -2.4f, 3.9f, 7.2f, 14.5f, 15.6f, 14.4f, 8.5f, 2.0f, -3.0f, -1.5f}, // 2019
{-2.1f, -3.4f, -1.8f, 0.6f, 7.0f, 17.1f, 15.6f, 15.4f, 11.1f, 5.6f, 1.9f, -1.7f}, // 2020
{-9.6f, -11.6f, -3.2f, 2.4f, 7.8f, 17.3f, 19.4f, 14.2f, 8.0f, 5.2f, -2.2f, -8.6f}, // 2021
{-7.3f, -6.4f, -1.8f, 1.3f, 8.1f, 15.5f, 17.6f, 17.6f, 9.1f, 5.4f, -1.5f, -4.4f} // 2022
};
static const float tempHelsinki[8][12] = {
{-2.0f, -0.1f, 1.8f, 5.1f, 9.7f, 13.7f, 16.3f, 17.3f, 12.7f, 5.4f, 4.6f, 2.1f}, // 2015
{-10.3f, -0.6f, 0.0f, 4.9f, 14.3f, 15.7f, 17.7f, 16.0f, 12.7f, 4.6f, -1.0f, -0.9f}, // 2016
{-2.9f, -3.3f, 0.7f, 2.3f, 9.9f, 13.8f, 16.1f, 15.9f, 11.4f, 5.0f, 2.7f, 0.7f}, // 2017
{-2.2f, -8.4f, -4.7f, 5.0f, 15.3f, 15.8f, 21.2f, 18.2f, 13.3f, 6.7f, 2.8f, -2.0f}, // 2018
{-6.2f, -0.5f, -0.3f, 6.8f, 10.6f, 17.9f, 17.5f, 16.8f, 11.3f, 5.2f, 1.8f, 1.4f}, // 2019
{1.9f, 0.5f, 1.7f, 4.5f, 9.5f, 18.4f, 16.5f, 16.8f, 13.0f, 8.2f, 4.4f, 0.9f}, // 2020
{-4.7f, -8.1f, -0.9f, 4.5f, 10.4f, 19.2f, 20.9f, 15.4f, 9.5f, 8.0f, 1.5f, -6.7f}, // 2021
{-3.3f, -2.2f, -0.2f, 3.3f, 9.6f, 16.9f, 18.1f, 18.9f, 9.2f, 7.6f, 2.3f, -3.4f} // 2022
};
QBarDataArray dataSet;
QBarDataArray dataSet2;
dataSet.reserve(m_years.size());
for (qsizetype year = 0; year < m_years.size(); ++year) {
QBarDataRow dataRow(m_months.size());
QBarDataRow dataRow2(m_months.size());
for (qsizetype month = 0; month < m_months.size(); ++month) {
dataRow[month].setValue(tempOulu[year][month]);
dataRow2[month].setValue(tempHelsinki[year][month]);
}
dataSet.append(dataRow);
dataSet2.append(dataRow2);
}
m_primarySeries->dataProxy()->resetArray(dataSet, m_years, m_months);
m_secondarySeries->dataProxy()->resetArray(dataSet2, m_years, m_months);
}
void BarGraphModifier::changePresetCamera()
{
m_graph->setCameraTargetPosition(QVector3D(0.0f, 0.0f, 0.0f));
static int preset = int(QtGraphs3D::CameraPreset::Front);
m_graph->setCameraPreset((QtGraphs3D::CameraPreset) preset);
if (++preset > int(QtGraphs3D::CameraPreset::DirectlyBelow))
preset = int(QtGraphs3D::CameraPreset::FrontLow);
}
QSharedPointer<QQuickItemGrabResult>
BarGraphModifier::renderSliceToImage(QtGraphs3D::SliceType sliceType, int requestedIndex)
{
return m_graph->renderSliceToImage(requestedIndex, sliceType);
}

View File

@ -0,0 +1,45 @@
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#ifndef BARGRAPHMODIFIER_H
#define BARGRAPHMODIFIER_H
#include <QtCore/qpropertyanimation.h>
#include <QtGraphs/qabstract3dseries.h>
#include <QtGraphs/qbardataproxy.h>
#include <QtGraphsWidgets/q3dbarswidgetitem.h>
class RainfallData;
class BarGraphModifier : public QObject
{
Q_OBJECT
public:
explicit BarGraphModifier(Q3DBarsWidgetItem *bargraph, QObject *parent);
virtual ~BarGraphModifier() = default;
void resetTemperatureData();
void changePresetCamera();
QSharedPointer<QQuickItemGrabResult> renderSliceToImage(QtGraphs3D::SliceType sliceType,
int requestedIndex);
private:
Q3DBarsWidgetItem *m_graph = nullptr;
int m_fontSize = 30;
int m_segments = 4;
int m_subSegments = 3;
float m_minval = -20.f;
float m_maxval = 20.f;
QStringList m_months = {};
QStringList m_years = {};
QValue3DAxis *m_temperatureAxis = nullptr;
QCategory3DAxis *m_yearAxis = nullptr;
QCategory3DAxis *m_monthAxis = nullptr;
QBar3DSeries *m_primarySeries = nullptr;
QBar3DSeries *m_secondarySeries = nullptr;
QAbstract3DSeries::Mesh m_barMesh = QAbstract3DSeries::Mesh::BevelBar;
const QString m_celsiusString;
};
#endif

View File

@ -0,0 +1,21 @@
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "bargraphwidget.h"
#include <QtGraphsWidgets/q3dbarswidgetitem.h>
BarGraphWidget::BarGraphWidget() {}
BarGraphWidget::~BarGraphWidget() {}
void BarGraphWidget::initialize()
{
m_barGraph = new Q3DBarsWidgetItem();
m_barGraph->setWidget(this);
}
Q3DBarsWidgetItem *BarGraphWidget::barGraph() const
{
return m_barGraph;
}

View File

@ -0,0 +1,25 @@
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#ifndef BARGRAPHWIDGET_H
#define BARGRAPHWIDGET_H
#include <QtQuickWidgets/qquickwidget.h>
class Q3DBarsWidgetItem;
class BarGraphWidget : public QQuickWidget
{
Q_OBJECT
public:
BarGraphWidget();
~BarGraphWidget() override;
void initialize();
Q3DBarsWidgetItem *barGraph() const;
private:
Q3DBarsWidgetItem *m_barGraph = nullptr;
};
#endif // BARGRAPHWIDGET_H

View File

@ -0,0 +1,43 @@
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "bargraph.h"
#include "surfacegraph.h"
#include <QtWidgets/qapplication.h>
#include <QtWidgets/qboxlayout.h>
#include <QtWidgets/qstackedwidget.h>
#include <QtWidgets/qcombobox.h>
using namespace Qt::StringLiterals;
int main(int argc, char **argv)
{
QApplication app(argc, argv);
QWidget *widget = new QWidget;
QStackedWidget *stackedWidget = new QStackedWidget;
widget->setWindowTitle(u"Render slice to image example"_s);
SurfaceGraph surface;
stackedWidget->addWidget(surface.surfaceWidget());
BarGraph bar;
stackedWidget->addWidget(bar.barWidget());
QComboBox *comboBox = new QComboBox;
comboBox->addItem(u"Surface"_s);
comboBox->addItem(u"Bar"_s);
QVBoxLayout *layout = new QVBoxLayout;
layout->addWidget(comboBox, 1);
layout->addWidget(stackedWidget);
QObject::connect(comboBox, &QComboBox::activated, stackedWidget, &QStackedWidget::setCurrentIndex);
widget->setLayout(layout);
widget->show();
int retVal = app.exec();
return retVal;
}

View File

@ -0,0 +1,10 @@
android|ios|winrt {
error( "This example is not supported for android, ios, or winrt." )
}
SOURCES += main.cpp surfacegraph.cpp surfacegraphmodifier.cpp surfacegraphwidget.cpp
HEADERS += surfacegraph.h surfacegraphmodifier.h surfacegraphwidget.h
QT += widgets graphs
RESOURCES += renderslicetoimage.qrc

View File

@ -0,0 +1,4 @@
<RCC>
<qresource prefix="/">
</qresource>
</RCC>

View File

@ -0,0 +1,87 @@
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "surfacegraph.h"
#include "surfacegraphmodifier.h"
#include "surfacegraphwidget.h"
#include <QtCore/qregularexpression.h>
#include <QtGui/qvalidator.h>
#include <QtWidgets/qboxlayout.h>
#include <QtWidgets/qlabel.h>
#include <QtWidgets/qlineedit.h>
#include <QtWidgets/qpushbutton.h>
#include <QtWidgets/qradiobutton.h>
using namespace Qt::StringLiterals;
SurfaceGraph::SurfaceGraph(QWidget *parent)
{
m_surfaceWidget = new QWidget(parent);
initialize();
}
void SurfaceGraph::initialize()
{
m_surfaceGraphWidget = new SurfaceGraphWidget();
m_surfaceGraphWidget->initialize();
QSize screenSize = m_surfaceGraphWidget->screen()->size();
m_surfaceGraphWidget->setMinimumSize(QSize(screenSize.width() / 2, screenSize.height() / 1.75));
m_surfaceGraphWidget->setMaximumSize(screenSize);
m_surfaceGraphWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
m_surfaceGraphWidget->setFocusPolicy(Qt::StrongFocus);
QHBoxLayout *hLayout = new QHBoxLayout;
hLayout->addWidget(m_surfaceGraphWidget, 1);
m_surfaceWidget->setLayout(hLayout);
QVBoxLayout *vLayout = new QVBoxLayout;
hLayout->addLayout(vLayout);
vLayout->setAlignment(Qt::AlignCenter);
m_rowRadioButton = new QRadioButton(m_surfaceWidget);
m_rowRadioButton->setText(u"Row"_s);
m_rowRadioButton->setChecked(true);
QRadioButton *columnRadioButton = new QRadioButton(m_surfaceWidget);
columnRadioButton->setText(u"Column"_s);
columnRadioButton->setChecked(false);
m_lineSelectText = new QLineEdit(m_surfaceWidget);
QRegularExpression re("\\d+");
QRegularExpressionValidator *reValidator = new QRegularExpressionValidator(re, m_surfaceWidget);
m_lineSelectText->setValidator(reValidator);
QPushButton *sliceToImageButton = new QPushButton(m_surfaceWidget);
sliceToImageButton->setText(u"Slice To Image"_s);
m_sliceResultLabel = new QLabel(m_surfaceWidget);
vLayout->addWidget(m_rowRadioButton);
vLayout->addWidget(columnRadioButton);
vLayout->addWidget(m_lineSelectText);
vLayout->addWidget(sliceToImageButton);
vLayout->addWidget(m_sliceResultLabel);
m_surfaceGraphWidget->raise();
m_modifier = new SurfaceGraphModifier(m_surfaceGraphWidget->surfaceGraph(), this);
QObject::connect(sliceToImageButton,
&QPushButton::clicked,
this,
&SurfaceGraph::renderSliceToImage);
}
void SurfaceGraph::renderSliceToImage()
{
int index = m_lineSelectText->text().isEmpty() ? -1 : m_lineSelectText->text().toInt();
QtGraphs3D::SliceType sliceType = QtGraphs3D::SliceType::SliceRow;
if (!m_rowRadioButton->isChecked())
sliceType = QtGraphs3D::SliceType::SliceColumn;
m_grab = m_modifier->renderSliceToImage(sliceType, index);
connect(m_grab.data(), &QQuickItemGrabResult::ready, this, [&]() {
m_sliceResultLabel->setPixmap(QPixmap::fromImage(m_grab.data()->image()));
});
}

View File

@ -0,0 +1,38 @@
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#ifndef SURFACEGRAPH_H
#define SURFACEGRAPH_H
#include <QtCore/qobject.h>
#include <QtQuick/qquickitemgrabresult.h>
class QLineEdit;
class QLabel;
class QRadioButton;
class SurfaceGraphModifier;
class SurfaceGraphWidget;
class SurfaceGraph : public QObject
{
Q_OBJECT
public:
SurfaceGraph(QWidget *parent = nullptr);
void initialize();
QWidget *surfaceWidget() { return m_surfaceWidget; }
private:
void renderSliceToImage();
SurfaceGraphModifier *m_modifier = nullptr;
SurfaceGraphWidget *m_surfaceGraphWidget = nullptr;
QWidget *m_surfaceWidget = nullptr;
QRadioButton *m_rowRadioButton = nullptr;
QLineEdit *m_lineSelectText = nullptr;
QLabel *m_sliceResultLabel = nullptr;
QSharedPointer<QQuickItemGrabResult> m_grab;
};
#endif

View File

@ -0,0 +1,79 @@
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "surfacegraphmodifier.h"
#include <QtCore/qmath.h>
#include <QtGraphs/qgraphstheme.h>
#include <QtGraphs/qsurface3dseries.h>
#include <QtGraphs/qsurfacedataproxy.h>
#include <QtGraphsWidgets/q3dsurfacewidgetitem.h>
#include <QtGraphs/qvalue3daxis.h>
#include <QtGui/qimage.h>
SurfaceGraphModifier::SurfaceGraphModifier(Q3DSurfaceWidgetItem *surface, QObject *parent)
: QObject(parent), m_graph(surface)
{
m_graph->setCameraZoomLevel(85.f);
m_graph->setCameraPreset(QtGraphs3D::CameraPreset::IsometricRight);
m_graph->activeTheme()->setTheme(QGraphsTheme::Theme::MixSeries);
m_graph->activeTheme()->setLabelBackgroundVisible(false);
m_graph->activeTheme()->setLabelBorderVisible(false);
m_graph->setAxisX(new QValue3DAxis);
m_graph->setAxisY(new QValue3DAxis);
m_graph->setAxisZ(new QValue3DAxis);
m_sqrtSinProxy = new QSurfaceDataProxy();
m_sqrtSinSeries = new QSurface3DSeries(m_sqrtSinProxy);
fillSqrtSinProxy();
m_sqrtSinSeries->setDrawMode(QSurface3DSeries::DrawSurfaceAndWireframe);
m_sqrtSinSeries->setShading(QSurface3DSeries::Shading::Flat);
m_graph->axisX()->setLabelFormat("%.2f");
m_graph->axisZ()->setLabelFormat("%.2f");
m_graph->axisX()->setRange(-8.f, 8.f);
m_graph->axisY()->setRange(0.f, 2.f);
m_graph->axisZ()->setRange(-8.f, 8.f);
m_graph->axisX()->setLabelAutoAngle(30.f);
m_graph->axisY()->setLabelAutoAngle(90.f);
m_graph->axisZ()->setLabelAutoAngle(30.f);
m_graph->addSeries(m_sqrtSinSeries);
m_graph->setDefaultInputHandler();
m_graph->setZoomEnabled(true);
m_graph->setSelectionMode(QtGraphs3D::SelectionFlag::Row | QtGraphs3D::SelectionFlag::Slice);
}
QSharedPointer<QQuickItemGrabResult>
SurfaceGraphModifier::renderSliceToImage(QtGraphs3D::SliceType sliceType, int requestedIndex)
{
return m_graph->renderSliceToImage(-1, requestedIndex, sliceType);
}
SurfaceGraphModifier::~SurfaceGraphModifier() {}
void SurfaceGraphModifier::fillSqrtSinProxy()
{
float stepX = (8.f - -8.f) / float(150 - 1);
float stepZ = (8.f - -8.f) / float(150 - 1);
QSurfaceDataArray dataArray;
dataArray.reserve(150);
for (int i = 0; i < 150; ++i) {
QSurfaceDataRow newRow;
newRow.reserve(150);
float z = qMin(8.f, (i * stepZ + -8.f));
for (int j = 0; j < 150; ++j) {
float x = qMin(8.f, (j * stepX + -8.f));
float R = qSqrt(z * z + x * x) + 0.01f;
float y = (qSin(R) / R + 0.24f) * 1.61f;
newRow.append(QSurfaceDataItem(x, y, z));
}
dataArray.append(newRow);
}
m_sqrtSinProxy->resetArray(dataArray);
}

View File

@ -0,0 +1,34 @@
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#ifndef SURFACEGRAPHMODIFIER_H
#define SURFACEGRAPHMODIFIER_H
#include <QtCore/qobject.h>
#include <QtGraphsWidgets/q3dsurfacewidgetitem.h>
class QSurfaceDataProxy;
class QSurface3DSeries;
class QQuickItemGrabResult;
class SurfaceGraphModifier : public QObject
{
Q_OBJECT
public:
explicit SurfaceGraphModifier(Q3DSurfaceWidgetItem *surface, QObject *parent);
~SurfaceGraphModifier();
QSharedPointer<QQuickItemGrabResult> renderSliceToImage(QtGraphs3D::SliceType sliceType,
int requestedIndex);
private:
void fillSqrtSinProxy();
private:
Q3DSurfaceWidgetItem *m_graph = nullptr;
QSurfaceDataProxy *m_sqrtSinProxy = nullptr;
QSurface3DSeries *m_sqrtSinSeries = nullptr;
};
#endif // SURFACEGRAPHMODIFIER_H

View File

@ -0,0 +1,21 @@
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "surfacegraphwidget.h"
#include <QtGraphsWidgets/q3dsurfacewidgetitem.h>
SurfaceGraphWidget::SurfaceGraphWidget() {}
SurfaceGraphWidget::~SurfaceGraphWidget() {}
void SurfaceGraphWidget::initialize()
{
m_surfaceGraph = new Q3DSurfaceWidgetItem();
m_surfaceGraph->setWidget(this);
}
Q3DSurfaceWidgetItem *SurfaceGraphWidget::surfaceGraph() const
{
return m_surfaceGraph;
}

View File

@ -0,0 +1,25 @@
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#ifndef SURFACEGRAPHWIDGET_H
#define SURFACEGRAPHWIDGET_H
#include <QtQuickWidgets/qquickwidget.h>
class Q3DSurfaceWidgetItem;
class SurfaceGraphWidget : public QQuickWidget
{
Q_OBJECT
public:
SurfaceGraphWidget();
~SurfaceGraphWidget() override;
void initialize();
Q3DSurfaceWidgetItem *surfaceGraph() const;
private:
Q3DSurfaceWidgetItem *m_surfaceGraph = nullptr;
};
#endif // SURFACEGRAPHWIDGET_H