diff options
author | Friedemann Kleint <Friedemann.Kleint@qt.io> | 2022-04-13 14:15:49 +0200 |
---|---|---|
committer | Qt Cherry-pick Bot <cherrypick_bot@qt-project.org> | 2022-04-20 20:27:46 +0000 |
commit | 01470f2516df95e03d8e5d5e9c804ddc1386e1d3 (patch) | |
tree | 377b9ad1032008850b0a7160f7954f927d3200d7 | |
parent | a9588af52cc64462e9ae8519e07771660498a955 (diff) |
Add a surface example using numpy arrays
Demonstrate how to use numpy arrays in conjunction with a
QAbstractTableModel to populate a surface graph.
Task-number: PYSIDE-1880
Change-Id: I4eeeea73c1ec1f63278d8d2cd6a740108a644d41
Reviewed-by: Christian Tismer <tismer@stackless.com>
(cherry picked from commit 7659017d479ce7ddd0d1a13f00a3569e7e33ec54)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
4 files changed, 568 insertions, 0 deletions
diff --git a/examples/datavisualization/surface_numpy/doc/surface_numpy.rst b/examples/datavisualization/surface_numpy/doc/surface_numpy.rst new file mode 100644 index 000000000..e08ca00a3 --- /dev/null +++ b/examples/datavisualization/surface_numpy/doc/surface_numpy.rst @@ -0,0 +1,24 @@ +Surface Example +=============== + +Using Q3DSurface in a widget application. + +The surface example shows how to make a simple 3D surface graph using +Q3DSurface and combining the use of widgets for adjusting several adjustable +qualities. This example demonstrates the following features: + +* How to set up a QItemModelSurfaceDataProxy with a QAbstractTableModel + backed by a numpy array. +* How to use QHeightMapSurfaceDataProxy for showing 3D height maps. +* Three different selection modes for studying the graph. +* Axis range usage for displaying selected portions of the graph. +* Changing theme. +* How to set a custom surface gradient. + +For instructions about how to interact with the graph, see `this page`_. + +.. image:: ../../surface/doc/surface_mountain.png + :width: 400 + :alt: Surface Example Screenshot + +.. _`this page`: https://doc.qt.io/qt-6/qtdatavisualization-interacting-with-data.html diff --git a/examples/datavisualization/surface_numpy/main.py b/examples/datavisualization/surface_numpy/main.py new file mode 100644 index 000000000..17e23a795 --- /dev/null +++ b/examples/datavisualization/surface_numpy/main.py @@ -0,0 +1,220 @@ +############################################################################# +## +## Copyright (C) 2022 The Qt Company Ltd. +## Contact: https://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +import sys + +from PySide6.QtCore import QSize, Qt +from PySide6.QtDataVisualization import Q3DSurface +from PySide6.QtGui import QBrush, QIcon, QLinearGradient, QPainter, QPixmap +from PySide6.QtWidgets import (QApplication, QComboBox, QGroupBox, QHBoxLayout, + QLabel, QMessageBox, QPushButton, QRadioButton, + QSizePolicy, QSlider, QVBoxLayout, QWidget) + +from surfacegraph import SurfaceGraph + +THEMES = ["Qt", "Primary Colors", "Digia", "Stone Moss", "Army Blue", "Retro", + "Ebony", "Isabelle"] + + +if __name__ == "__main__": + app = QApplication(sys.argv) + graph = Q3DSurface() + container = QWidget.createWindowContainer(graph) + + if not graph.hasContext(): + msgBox = QMessageBox() + msgBox.setText("Couldn't initialize the OpenGL context.") + msgBox.exec() + sys.exit(-1) + + screenSize = graph.screen().size() + container.setMinimumSize(QSize(screenSize.width() / 2, + screenSize.height() / 1.6)) + container.setMaximumSize(screenSize) + container.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + container.setFocusPolicy(Qt.StrongFocus) + + widget = QWidget() + hLayout = QHBoxLayout(widget) + vLayout = QVBoxLayout() + hLayout.addWidget(container, 1) + hLayout.addLayout(vLayout) + vLayout.setAlignment(Qt.AlignTop) + + widget.setWindowTitle("Surface example") + + modelGroupBox = QGroupBox("Model") + + sqrtSinModelRB = QRadioButton(widget) + sqrtSinModelRB.setText("Sqrt& Sin") + sqrtSinModelRB.setChecked(False) + + heightMapModelRB = QRadioButton(widget) + heightMapModelRB.setText("Height Map") + heightMapModelRB.setChecked(False) + + modelVBox = QVBoxLayout() + modelVBox.addWidget(sqrtSinModelRB) + modelVBox.addWidget(heightMapModelRB) + modelGroupBox.setLayout(modelVBox) + + selectionGroupBox = QGroupBox("Selection Mode") + + modeNoneRB = QRadioButton(widget) + modeNoneRB.setText("No selection") + modeNoneRB.setChecked(False) + + modeItemRB = QRadioButton(widget) + modeItemRB.setText("Item") + modeItemRB.setChecked(False) + + modeSliceRowRB = QRadioButton(widget) + modeSliceRowRB.setText("Row Slice") + modeSliceRowRB.setChecked(False) + + modeSliceColumnRB = QRadioButton(widget) + modeSliceColumnRB.setText("Column Slice") + modeSliceColumnRB.setChecked(False) + + selectionVBox = QVBoxLayout() + selectionVBox.addWidget(modeNoneRB) + selectionVBox.addWidget(modeItemRB) + selectionVBox.addWidget(modeSliceRowRB) + selectionVBox.addWidget(modeSliceColumnRB) + selectionGroupBox.setLayout(selectionVBox) + + axisMinSliderX = QSlider(Qt.Horizontal, widget) + axisMinSliderX.setMinimum(0) + axisMinSliderX.setTickInterval(1) + axisMinSliderX.setEnabled(True) + axisMaxSliderX = QSlider(Qt.Horizontal, widget) + axisMaxSliderX.setMinimum(1) + axisMaxSliderX.setTickInterval(1) + axisMaxSliderX.setEnabled(True) + axisMinSliderZ = QSlider(Qt.Horizontal, widget) + axisMinSliderZ.setMinimum(0) + axisMinSliderZ.setTickInterval(1) + axisMinSliderZ.setEnabled(True) + axisMaxSliderZ = QSlider(Qt.Horizontal, widget) + axisMaxSliderZ.setMinimum(1) + axisMaxSliderZ.setTickInterval(1) + axisMaxSliderZ.setEnabled(True) + + themeList = QComboBox(widget) + themeList.addItems(THEMES) + + colorGroupBox = QGroupBox("Custom gradient") + + grBtoY = QLinearGradient(0, 0, 1, 100) + grBtoY.setColorAt(1.0, Qt.black) + grBtoY.setColorAt(0.67, Qt.blue) + grBtoY.setColorAt(0.33, Qt.red) + grBtoY.setColorAt(0.0, Qt.yellow) + + pm = QPixmap(24, 100) + pmp = QPainter(pm) + pmp.setBrush(QBrush(grBtoY)) + pmp.setPen(Qt.NoPen) + pmp.drawRect(0, 0, 24, 100) + pmp.end() + + gradientBtoYPB = QPushButton(widget) + gradientBtoYPB.setIcon(QIcon(pm)) + gradientBtoYPB.setIconSize(QSize(24, 100)) + + grGtoR = QLinearGradient(0, 0, 1, 100) + grGtoR.setColorAt(1.0, Qt.darkGreen) + grGtoR.setColorAt(0.5, Qt.yellow) + grGtoR.setColorAt(0.2, Qt.red) + grGtoR.setColorAt(0.0, Qt.darkRed) + pmp.begin(pm) + pmp.setBrush(QBrush(grGtoR)) + pmp.drawRect(0, 0, 24, 100) + pmp.end() + + gradientGtoRPB = QPushButton(widget) + gradientGtoRPB.setIcon(QIcon(pm)) + gradientGtoRPB.setIconSize(QSize(24, 100)) + + colorHBox = QHBoxLayout() + colorHBox.addWidget(gradientBtoYPB) + colorHBox.addWidget(gradientGtoRPB) + colorGroupBox.setLayout(colorHBox) + + vLayout.addWidget(modelGroupBox) + vLayout.addWidget(selectionGroupBox) + vLayout.addWidget(QLabel("Column range")) + vLayout.addWidget(axisMinSliderX) + vLayout.addWidget(axisMaxSliderX) + vLayout.addWidget(QLabel("Row range")) + vLayout.addWidget(axisMinSliderZ) + vLayout.addWidget(axisMaxSliderZ) + vLayout.addWidget(QLabel("Theme")) + vLayout.addWidget(themeList) + vLayout.addWidget(colorGroupBox) + + widget.show() + + modifier = SurfaceGraph(graph) + + heightMapModelRB.toggled.connect(modifier.enableHeightMapModel) + sqrtSinModelRB.toggled.connect(modifier.enableSqrtSinModel) + modeNoneRB.toggled.connect(modifier.toggleModeNone) + modeItemRB.toggled.connect(modifier.toggleModeItem) + modeSliceRowRB.toggled.connect(modifier.toggleModeSliceRow) + modeSliceColumnRB.toggled.connect(modifier.toggleModeSliceColumn) + axisMinSliderX.valueChanged.connect(modifier.adjustXMin) + axisMaxSliderX.valueChanged.connect(modifier.adjustXMax) + axisMinSliderZ.valueChanged.connect(modifier.adjustZMin) + axisMaxSliderZ.valueChanged.connect(modifier.adjustZMax) + themeList.currentIndexChanged[int].connect(modifier.changeTheme) + gradientBtoYPB.pressed.connect(modifier.setBlackToYellowGradient) + gradientGtoRPB.pressed.connect(modifier.setGreenToRedGradient) + + modifier.setAxisMinSliderX(axisMinSliderX) + modifier.setAxisMaxSliderX(axisMaxSliderX) + modifier.setAxisMinSliderZ(axisMinSliderZ) + modifier.setAxisMaxSliderZ(axisMaxSliderZ) + + sqrtSinModelRB.setChecked(True) + modeItemRB.setChecked(True) + themeList.setCurrentIndex(2) + + sys.exit(app.exec()) diff --git a/examples/datavisualization/surface_numpy/surface_numpy.pyproject b/examples/datavisualization/surface_numpy/surface_numpy.pyproject new file mode 100644 index 000000000..598a6541f --- /dev/null +++ b/examples/datavisualization/surface_numpy/surface_numpy.pyproject @@ -0,0 +1,4 @@ +{ + "files": ["main.py", + "surfacegraph.py"] +} diff --git a/examples/datavisualization/surface_numpy/surfacegraph.py b/examples/datavisualization/surface_numpy/surfacegraph.py new file mode 100644 index 000000000..88ef7e36a --- /dev/null +++ b/examples/datavisualization/surface_numpy/surfacegraph.py @@ -0,0 +1,320 @@ +############################################################################# +## +## Copyright (C) 2022 The Qt Company Ltd. +## Contact: https://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +import numpy as np +import math +from pathlib import Path + +from PySide6.QtCore import (QAbstractTableModel, QByteArray, QModelIndex, + QObject, Qt, Slot) +from PySide6.QtDataVisualization import (Q3DTheme, QAbstract3DGraph, + QHeightMapSurfaceDataProxy, + QSurface3DSeries, + QItemModelSurfaceDataProxy, + QValue3DAxis) +from PySide6.QtGui import QImage, QLinearGradient +from PySide6.QtWidgets import QSlider + +sampleCountX = 50 +sampleCountZ = 50 +heightMapGridStepX = 6 +heightMapGridStepZ = 6 +sampleMin = -8.0 +sampleMax = 8.0 + + +X_ROLE = Qt.UserRole + 1 +Y_ROLE = Qt.UserRole + 2 +Z_ROLE = Qt.UserRole + 3 + + +class SqrtSinModel(QAbstractTableModel): + def __init__(self, parent=None): + super().__init__(parent) + + self._x = np.zeros(sampleCountX) + self._z = np.zeros(sampleCountZ) + self._data = np.zeros((sampleCountZ, sampleCountX)) + + stepX = (sampleMax - sampleMin) / float(sampleCountX - 1) + stepZ = (sampleMax - sampleMin) / float(sampleCountZ - 1) + + for i in range(sampleCountZ): + # Keep values within range bounds, since just adding step can cause + # minor drift due to the rounding errors. + z = min(sampleMax, (i * stepZ + sampleMin)) + self._z[i] = z + for j in range(sampleCountX): + x = min(sampleMax, (j * stepX + sampleMin)) + self._x[j] = x + R = math.sqrt(z * z + x * x) + 0.01 + y = (math.sin(R) / R + 0.24) * 1.61 + self._data[i, j] = y + + def roleNames(self): + result = super().roleNames() + result[X_ROLE] = QByteArray(b"x") + result[Y_ROLE] = QByteArray(b"y") + result[Z_ROLE] = QByteArray(b"z") + return result + + def rowCount(self, index=QModelIndex()): + return self._z.size + + def columnCount(self, index=QModelIndex()): + return self._x.size + + def data(self, index, role=Qt.DisplayRole): + row = index.row() + col = index.column() + if role == X_ROLE: + return float(self._x[col]) + if role == Y_ROLE: + return float(self._data[row][col]) + if role == Z_ROLE: + return float(self._z[row]) + return 0.0 + + +class SurfaceGraph(QObject): + def __init__(self, surface, parent=None): + super().__init__(parent) + + self.m_graph = surface + self.m_graph.setAxisX(QValue3DAxis()) + self.m_graph.setAxisY(QValue3DAxis()) + self.m_graph.setAxisZ(QValue3DAxis()) + + self.m_sqrtSinModel = SqrtSinModel(self) + self.m_sqrtSinProxy = QItemModelSurfaceDataProxy(self.m_sqrtSinModel, self) + self.m_sqrtSinProxy.setUseModelCategories(True) + self.m_sqrtSinProxy.setXPosRole("x") + self.m_sqrtSinProxy.setYPosRole("y") + self.m_sqrtSinProxy.setZPosRole("z") + + self.m_sqrtSinSeries = QSurface3DSeries(self.m_sqrtSinProxy) + + imageFile = Path(__file__).parent.parent / "surface" / "mountain.png" + heightMapImage = QImage(imageFile) + self.m_heightMapProxy = QHeightMapSurfaceDataProxy(heightMapImage) + self.m_heightMapSeries = QSurface3DSeries(self.m_heightMapProxy) + self.m_heightMapSeries.setItemLabelFormat("(@xLabel, @zLabel): @yLabel") + self.m_heightMapProxy.setValueRanges(34.0, 40.0, 18.0, 24.0) + + self.m_heightMapWidth = heightMapImage.width() + self.m_heightMapHeight = heightMapImage.height() + + self.m_axisMinSliderX = QSlider() + self.m_axisMaxSliderX = QSlider() + self.m_axisMinSliderZ = QSlider() + self.m_axisMaxSliderZ = QSlider() + self.m_rangeMinX = 0.0 + self.m_rangeMinZ = 0.0 + self.m_stepX = 0.0 + self.m_stepZ = 0.0 + + def enableSqrtSinModel(self, enable): + if enable: + self.m_sqrtSinSeries.setDrawMode(QSurface3DSeries.DrawSurfaceAndWireframe) + self.m_sqrtSinSeries.setFlatShadingEnabled(True) + + self.m_graph.axisX().setLabelFormat("%.2f") + self.m_graph.axisZ().setLabelFormat("%.2f") + self.m_graph.axisX().setRange(sampleMin, sampleMax) + self.m_graph.axisY().setRange(0.0, 2.0) + self.m_graph.axisZ().setRange(sampleMin, sampleMax) + self.m_graph.axisX().setLabelAutoRotation(30) + self.m_graph.axisY().setLabelAutoRotation(90) + self.m_graph.axisZ().setLabelAutoRotation(30) + + self.m_graph.removeSeries(self.m_heightMapSeries) + self.m_graph.addSeries(self.m_sqrtSinSeries) + + # Reset range sliders for Sqrt&Sin + self.m_rangeMinX = sampleMin + self.m_rangeMinZ = sampleMin + self.m_stepX = (sampleMax - sampleMin) / float(sampleCountX - 1) + self.m_stepZ = (sampleMax - sampleMin) / float(sampleCountZ - 1) + self.m_axisMinSliderX.setMaximum(sampleCountX - 2) + self.m_axisMinSliderX.setValue(0) + self.m_axisMaxSliderX.setMaximum(sampleCountX - 1) + self.m_axisMaxSliderX.setValue(sampleCountX - 1) + self.m_axisMinSliderZ.setMaximum(sampleCountZ - 2) + self.m_axisMinSliderZ.setValue(0) + self.m_axisMaxSliderZ.setMaximum(sampleCountZ - 1) + self.m_axisMaxSliderZ.setValue(sampleCountZ - 1) + + def enableHeightMapModel(self, enable): + if enable: + self.m_heightMapSeries.setDrawMode(QSurface3DSeries.DrawSurface) + self.m_heightMapSeries.setFlatShadingEnabled(False) + + self.m_graph.axisX().setLabelFormat("%.1f N") + self.m_graph.axisZ().setLabelFormat("%.1f E") + self.m_graph.axisX().setRange(34.0, 40.0) + self.m_graph.axisY().setAutoAdjustRange(True) + self.m_graph.axisZ().setRange(18.0, 24.0) + + self.m_graph.axisX().setTitle("Latitude") + self.m_graph.axisY().setTitle("Height") + self.m_graph.axisZ().setTitle("Longitude") + + self.m_graph.removeSeries(self.m_sqrtSinSeries) + self.m_graph.addSeries(self.m_heightMapSeries) + + # Reset range sliders for height map + mapGridCountX = self.m_heightMapWidth / heightMapGridStepX + mapGridCountZ = self.m_heightMapHeight / heightMapGridStepZ + self.m_rangeMinX = 34.0 + self.m_rangeMinZ = 18.0 + self.m_stepX = 6.0 / float(mapGridCountX - 1) + self.m_stepZ = 6.0 / float(mapGridCountZ - 1) + self.m_axisMinSliderX.setMaximum(mapGridCountX - 2) + self.m_axisMinSliderX.setValue(0) + self.m_axisMaxSliderX.setMaximum(mapGridCountX - 1) + self.m_axisMaxSliderX.setValue(mapGridCountX - 1) + self.m_axisMinSliderZ.setMaximum(mapGridCountZ - 2) + self.m_axisMinSliderZ.setValue(0) + self.m_axisMaxSliderZ.setMaximum(mapGridCountZ - 1) + self.m_axisMaxSliderZ.setValue(mapGridCountZ - 1) + + def adjustXMin(self, minimum): + minX = self.m_stepX * float(minimum) + self.m_rangeMinX + + maximum = self.m_axisMaxSliderX.value() + if minimum >= maximum: + maximum = minimum + 1 + self.m_axisMaxSliderX.setValue(maximum) + maxX = self.m_stepX * maximum + self.m_rangeMinX + + self.setAxisXRange(minX, maxX) + + def adjustXMax(self, maximum): + maxX = self.m_stepX * float(maximum) + self.m_rangeMinX + + minimum = self.m_axisMinSliderX.value() + if maximum <= minimum: + minimum = maximum - 1 + self.m_axisMinSliderX.setValue(minimum) + minX = self.m_stepX * minimum + self.m_rangeMinX + + self.setAxisXRange(minX, maxX) + + def adjustZMin(self, minimum): + minZ = self.m_stepZ * float(minimum) + self.m_rangeMinZ + + maximum = self.m_axisMaxSliderZ.value() + if minimum >= maximum: + maximum = minimum + 1 + self.m_axisMaxSliderZ.setValue(maximum) + maxZ = self.m_stepZ * maximum + self.m_rangeMinZ + + self.setAxisZRange(minZ, maxZ) + + def adjustZMax(self, maximum): + maxX = self.m_stepZ * float(maximum) + self.m_rangeMinZ + + minimum = self.m_axisMinSliderZ.value() + if maximum <= minimum: + minimum = maximum - 1 + self.m_axisMinSliderZ.setValue(minimum) + minX = self.m_stepZ * minimum + self.m_rangeMinZ + + self.setAxisZRange(minX, maxX) + + def setAxisXRange(self, minimum, maximum): + self.m_graph.axisX().setRange(minimum, maximum) + + def setAxisZRange(self, minimum, maximum): + self.m_graph.axisZ().setRange(minimum, maximum) + + @Slot() + def changeTheme(self, theme): + self.m_graph.activeTheme().setType(Q3DTheme.Theme(theme)) + + def setBlackToYellowGradient(self): + gr = QLinearGradient() + gr.setColorAt(0.0, Qt.black) + gr.setColorAt(0.33, Qt.blue) + gr.setColorAt(0.67, Qt.red) + gr.setColorAt(1.0, Qt.yellow) + + series = self.m_graph.seriesList()[0] + series.setBaseGradient(gr) + series.setColorStyle(Q3DTheme.ColorStyleRangeGradient) + + def setGreenToRedGradient(self): + gr = QLinearGradient() + gr.setColorAt(0.0, Qt.darkGreen) + gr.setColorAt(0.5, Qt.yellow) + gr.setColorAt(0.8, Qt.red) + gr.setColorAt(1.0, Qt.darkRed) + + series = self.m_graph.seriesList()[0] + series.setBaseGradient(gr) + series.setColorStyle(Q3DTheme.ColorStyleRangeGradient) + + def toggleModeNone(self): + self.m_graph.setSelectionMode(QAbstract3DGraph.SelectionNone) + + def toggleModeItem(self): + self.m_graph.setSelectionMode(QAbstract3DGraph.SelectionItem) + + def toggleModeSliceRow(self): + self.m_graph.setSelectionMode( + QAbstract3DGraph.SelectionItemAndRow | QAbstract3DGraph.SelectionSlice + ) + + def toggleModeSliceColumn(self): + self.m_graph.setSelectionMode( + QAbstract3DGraph.SelectionItemAndColumn | QAbstract3DGraph.SelectionSlice + ) + + def setAxisMinSliderX(self, slider): + self.m_axisMinSliderX = slider + + def setAxisMaxSliderX(self, slider): + self.m_axisMaxSliderX = slider + + def setAxisMinSliderZ(self, slider): + self.m_axisMinSliderZ = slider + + def setAxisMaxSliderZ(self, slider): + self.m_axisMaxSliderZ = slider |