Contents Menu Expand Light mode Dark mode Auto light/dark, in light mode Auto light/dark, in dark mode Skip to content
Qt for Python
Logo
Qt for Python
  • 入门指南
  • 商业用途
  • 从源代码构建
  • 包详情
  • 模块 API
  • 工具
  • Tutorials
  • 示例
    • 扩展QML - 添加类型示例
    • 扩展QML(高级)- 生日派对基础项目
    • 扩展QML(高级)- 继承与强制转换
    • 扩展QML(高级)- 默认属性
    • 扩展QML(高级)- 分组属性
    • 扩展QML(高级)- 附加属性
    • 扩展QML(高级)- 属性值源
    • 扩展QML - 绑定示例
    • 扩展QML - 创建新类型
    • 扩展QML - 连接到C++方法和信号
    • 扩展QML - 添加属性绑定
    • 扩展QML - 使用自定义属性类型
    • 扩展QML - 使用列表属性类型
    • 扩展QML - 插件示例
    • QML中的QAbstractListModel
    • 扩展QML - 扩展对象示例
    • 扩展QML - 方法示例
    • 扩展QML - 对象和列表属性类型示例
    • examples/qml/signals/pytoqml1
    • examples/qml/signals/pytoqml2
    • examples/qml/signals/qmltopy1
    • examples/qml/signals/qmltopy2
    • examples/qml/signals/qmltopy3
    • examples/qml/signals/qmltopy4
    • 文本属性示例
    • 使用模型示例
    • 对象列表模型示例
    • OpenGL under QML Squircle
    • 场景图绘制项示例
    • QQuickRenderControl OpenGL 示例
    • 场景图 - 自定义几何
    • 字符串列表模型示例
    • Qt Quick 示例 - 窗口和屏幕
    • Qt Quick Controls 2 - 画廊
    • Qt Quick Controls - 联系人列表
    • Qt Quick Controls - 文件系统浏览器
    • Widgets Gallery Example
    • 通讯录示例
    • 通讯录示例
    • examples/widgets/graphicsview/anchorlayout
    • 动画瓷砖示例
    • 应用程序选择器示例
    • examples/widgets/mainwindows/application
    • 基本绘图示例
    • examples/widgets/itemviews/basicfiltermodel
    • 基本布局示例
    • 模糊选择器效果示例
    • 边框布局示例
    • Cannon 示例
    • 字符映射示例
    • Classwizard 示例
    • examples/widgets/graphicsview/collidingmice
    • 同心圆示例
    • examples/widgets/graphicsview/diagramscene
    • 数字时钟示例
    • 目录视图示例
    • Dock Widget 示例
    • examples/widgets/graphicsview/dragdroprobot
    • examples/widgets/draganddrop/draggabletext
    • Drop Site Example
    • 动态布局示例
    • Easing Example
    • 可编辑树模型示例
    • examples/widgets/graphicsview/elasticnodes
    • examples/widgets/dialogs/extension
    • examples/widgets/itemviews/fetchmore
    • 流式布局示例
    • GNU gettext 示例
    • examples/widgets/imageviewer
    • JSON 模型示例
    • 许可证向导示例
    • examples/widgets/effects/lighting
    • Qt Linguist 示例
    • examples/widgets/mainwindows/mdi
    • 模型视图教程示例
    • examples/widgets/richtext/orderform
    • Painter Example
    • 绘图示例
    • QRegularExpression 示例
    • Screenshot Example
    • 简单的RHI小部件示例
    • SpinBox 委托示例
    • examples/widgets/dialogs/standarddialogs
    • Star Delegate Example
    • 状态示例
    • Syntax Highlighter Example
    • examples/widgets/desktop/systray
    • 选项卡对话框示例
    • Tetrix
    • TextEdit 示例
    • TextObject 示例
    • examples/widgets/thread_signals
    • examples/widgets/dialogs/trivialwizard
    • 任务菜单扩展示例
    • examples/uitools/uiloader
    • MIME类型浏览器示例
    • 设置编辑器示例
    • examples/corelib/ipc/sharedmemory
    • Mandelbrot 线程示例
    • Async “Eratosthenes” 示例
    • Async “Minimal” Example
    • examples/network/blockingfortuneclient
    • 下载器示例
    • examples/network/fortuneclient
    • examples/network/fortuneserver
    • Google 建议示例
    • Loopback 示例
    • examples/network/threadedfortuneserver
    • SQL Books Example
    • examples/dbus/listnames
    • examples/dbus/pingpong
    • DOM 书签示例
    • 模拟时钟窗口示例
    • RHI 窗口示例
    • 上下文信息示例
    • 你好 GL2 示例
    • 纹理示例
    • Threaded QOpenGLWidget 示例
    • Sample Bindings Example
    • 使用CMake
    • 可脚本化应用程序示例
    • WigglyWidget 示例
    • 媒体播放器示例
    • RESTful API client
    • 文档查看器示例
    • OSM Buildings
    • Simple HTTP Server Example
    • Widget Graph Gallery
    • 简单条形图
    • HelloGraphs 示例
    • 最小曲面示例
    • Graph Gallery
    • 曲面图库
    • Bars 3D 示例
    • Surface 示例
    • Surface 示例
    • Surface 示例
    • 区域图示例
    • 音频示例
    • 柱状图示例
    • Callout Example
    • 图表主题示例
    • Donut Chart Breakdown Example
    • 动态样条示例
    • Legend Example
    • 折线和柱状图示例
    • 折线图示例
    • 对数轴示例
    • 内存使用示例
    • 模型数据示例
    • 嵌套甜甜圈示例
    • 百分比条形图示例
    • 饼图示例
    • Selected Point Configuration Example
    • Light Markers and Points Selection Example
    • QML 极坐标图示例
    • 温度记录示例
    • 缩放折线图示例
    • 音频输出示例
    • 音频源示例
    • 相机示例
    • Player Example
    • 屏幕捕获示例
    • Nano 浏览器示例
    • examples/webenginewidgets/markdowneditor
    • WebEngine 通知示例
    • Simple Browser
    • Qt Widgets Nano 浏览器示例
    • Ax Viewer 示例
    • 蓝牙扫描仪示例
    • 蓝牙低功耗心率游戏
    • 蓝牙低功耗心率服务器
    • Bluetooth Low Energy Scanner Example
    • Networkx 查看器示例
    • OpenCV 人脸检测示例
    • Pandas 简单示例
    • Scikit Image 示例
    • Matplotlib Widget 3D 示例
    • Matplotlib Widget 高斯示例
    • 地图查看器示例
    • Reddit 示例
    • PDF 查看器示例
    • PDF 查看器示例
    • 自定义几何示例
    • Qt Quick 3D 介绍示例
    • Procedural Texture Example
    • examples/remoteobjects/modelview
    • Spatial Audio Panning Example
    • Hello Speak
    • 简单的Qt 3D示例
    • CAN总线示例
    • Modbus Client 示例
    • 终端示例
    • 移动块示例
    • examples/statemachine/ping_pong
    • examples/statemachine/rogue
    • 交通灯示例
    • WebChannel 独立示例
    • Simple CoAP Client Example
    • Simple MQTT Client Example
    • Qt OPC UA 查看器示例
    • 财务经理示例 - 第1部分
    • 财务经理示例 - 第2部分
    • 财务经理示例 - 第3部分
    • Minibrowser 示例
  • 视频
  • 部署
  • 注意事项
  • 开发者笔记
  • 发布说明
  • 模块索引
Back to top

小部件图库¶

小部件图库展示了所有三种图表类型及其一些特殊功能。图表在应用程序中有自己的标签页。

Widget Screenshot

下载 这个 示例

# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations

"""PySide6 port of the Qt Graphs widgetgallery example from Qt v6.x"""

import sys

from PySide6.QtCore import QSize
from PySide6.QtWidgets import QApplication, QTabWidget

from bargraph import BarGraph
from scattergraph import ScatterGraph
from surfacegraph import SurfaceGraph


class MainWidget(QTabWidget):
    """Tab widget for creating own tabs for Q3DBars, Q3DScatter, and Q3DSurface"""

    def __init__(self, p=None):
        super().__init__(p)

        screen_size = self.screen().size()
        minimum_graph_size = QSize(screen_size.width() / 2, screen_size.height() / 1.75)

        # Create bar graph
        self._bars = BarGraph(minimum_graph_size, screen_size)
        # Create scatter graph
        self._scatter = ScatterGraph(minimum_graph_size, screen_size)
        # Create surface graph
        self._surface = SurfaceGraph(minimum_graph_size, screen_size)

        # Add bars widget
        self.addTab(self._bars.barsWidget(), "Bar Graph")
        # Add scatter widget
        self.addTab(self._scatter.scatterWidget(), "Scatter Graph")
        # Add surface widget
        self.addTab(self._surface.surfaceWidget(), "Surface Graph")


if __name__ == "__main__":
    app = QApplication(sys.argv)

    tabWidget = MainWidget()
    tabWidget.setWindowTitle("Widget Gallery")

    tabWidget.show()
    exit_code = app.exec()
    del tabWidget
    sys.exit(exit_code)
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations

from graphmodifier import GraphModifier

from PySide6.QtCore import QObject, Qt
from PySide6.QtGui import QFont
from PySide6.QtWidgets import (QButtonGroup, QCheckBox, QComboBox, QFontComboBox,
                               QLabel, QPushButton, QHBoxLayout, QSizePolicy,
                               QRadioButton, QSlider, QVBoxLayout, QWidget)
from PySide6.QtQuickWidgets import QQuickWidget
from PySide6.QtGraphs import QAbstract3DSeries, QtGraphs3D
from PySide6.QtGraphsWidgets import Q3DBarsWidgetItem


class BarGraph(QObject):

    def __init__(self, minimum_graph_size, maximum_graph_size):
        super().__init__()

        barsGraph = Q3DBarsWidgetItem()
        barsGraphWidget = QQuickWidget()
        barsGraph.setWidget(barsGraphWidget)
        self._barsWidget = QWidget()
        hLayout = QHBoxLayout(self._barsWidget)
        barsGraphWidget.setMinimumSize(minimum_graph_size)
        barsGraphWidget.setMaximumSize(maximum_graph_size)
        barsGraphWidget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        barsGraphWidget.setFocusPolicy(Qt.StrongFocus)
        hLayout.addWidget(barsGraphWidget, 1)

        vLayout = QVBoxLayout()
        hLayout.addLayout(vLayout)

        themeList = QComboBox(self._barsWidget)
        themeList.addItem("QtGreen")
        themeList.addItem("QtGreenNeon")
        themeList.addItem("MixSeries")
        themeList.addItem("OrangeSeries")
        themeList.addItem("YellowSeries")
        themeList.addItem("BlueSeries")
        themeList.addItem("PurpleSeries")
        themeList.addItem("GreySeries")
        themeList.setCurrentIndex(0)

        labelButton = QPushButton(self._barsWidget)
        labelButton.setText("Change label style")

        smoothCheckBox = QCheckBox(self._barsWidget)
        smoothCheckBox.setText("Smooth bars")
        smoothCheckBox.setChecked(False)

        barStyleList = QComboBox(self._barsWidget)
        barStyleList.addItem("Bar", QAbstract3DSeries.Mesh.Bar)
        barStyleList.addItem("Pyramid", QAbstract3DSeries.Mesh.Pyramid)
        barStyleList.addItem("Cone", QAbstract3DSeries.Mesh.Cone)
        barStyleList.addItem("Cylinder", QAbstract3DSeries.Mesh.Cylinder)
        barStyleList.addItem("Bevel bar", QAbstract3DSeries.Mesh.BevelBar)
        barStyleList.addItem("Sphere", QAbstract3DSeries.Mesh.Sphere)
        barStyleList.setCurrentIndex(4)

        cameraButton = QPushButton(self._barsWidget)
        cameraButton.setText("Change camera preset")

        zoomToSelectedButton = QPushButton(self._barsWidget)
        zoomToSelectedButton.setText("Zoom to selected bar")

        selectionModeList = QComboBox(self._barsWidget)
        selectionModeList.addItem("None", QtGraphs3D.SelectionFlag.None_)
        selectionModeList.addItem("Bar", QtGraphs3D.SelectionFlag.Item)
        selectionModeList.addItem("Row", QtGraphs3D.SelectionFlag.Row)
        sel = QtGraphs3D.SelectionFlag.ItemAndRow
        selectionModeList.addItem("Bar and Row", sel)
        selectionModeList.addItem("Column", QtGraphs3D.SelectionFlag.Column)
        sel = QtGraphs3D.SelectionFlag.ItemAndColumn
        selectionModeList.addItem("Bar and Column", sel)
        sel = QtGraphs3D.SelectionFlag.RowAndColumn
        selectionModeList.addItem("Row and Column", sel)
        sel = QtGraphs3D.SelectionFlag.RowAndColumn
        selectionModeList.addItem("Bar, Row and Column", sel)
        sel = QtGraphs3D.SelectionFlag.Slice | QtGraphs3D.SelectionFlag.Row
        selectionModeList.addItem("Slice into Row", sel)
        sel = QtGraphs3D.SelectionFlag.Slice | QtGraphs3D.SelectionFlag.ItemAndRow
        selectionModeList.addItem("Slice into Row and Item", sel)
        sel = QtGraphs3D.SelectionFlag.Slice | QtGraphs3D.SelectionFlag.Column
        selectionModeList.addItem("Slice into Column", sel)
        sel = (QtGraphs3D.SelectionFlag.Slice
               | QtGraphs3D.SelectionFlag.ItemAndColumn)
        selectionModeList.addItem("Slice into Column and Item", sel)
        sel = (QtGraphs3D.SelectionFlag.ItemRowAndColumn
               | QtGraphs3D.SelectionFlag.MultiSeries)
        selectionModeList.addItem("Multi: Bar, Row, Col", sel)
        sel = (QtGraphs3D.SelectionFlag.Slice
               | QtGraphs3D.SelectionFlag.ItemAndRow
               | QtGraphs3D.SelectionFlag.MultiSeries)
        selectionModeList.addItem("Multi, Slice: Row, Item", sel)
        sel = (QtGraphs3D.SelectionFlag.Slice
               | QtGraphs3D.SelectionFlag.ItemAndColumn
               | QtGraphs3D.SelectionFlag.MultiSeries)
        selectionModeList.addItem("Multi, Slice: Col, Item", sel)
        selectionModeList.setCurrentIndex(1)

        backgroundCheckBox = QCheckBox(self._barsWidget)
        backgroundCheckBox.setText("Show background")
        backgroundCheckBox.setChecked(False)

        gridCheckBox = QCheckBox(self._barsWidget)
        gridCheckBox.setText("Show grid")
        gridCheckBox.setChecked(True)

        seriesCheckBox = QCheckBox(self._barsWidget)
        seriesCheckBox.setText("Show second series")
        seriesCheckBox.setChecked(False)

        reverseValueAxisCheckBox = QCheckBox(self._barsWidget)
        reverseValueAxisCheckBox.setText("Reverse value axis")
        reverseValueAxisCheckBox.setChecked(False)

        rotationSliderX = QSlider(Qt.Orientation.Horizontal, self._barsWidget)
        rotationSliderX.setTickInterval(30)
        rotationSliderX.setTickPosition(QSlider.TicksBelow)
        rotationSliderX.setMinimum(-180)
        rotationSliderX.setValue(0)
        rotationSliderX.setMaximum(180)
        rotationSliderY = QSlider(Qt.Orientation.Horizontal, self._barsWidget)
        rotationSliderY.setTickInterval(15)
        rotationSliderY.setTickPosition(QSlider.TicksAbove)
        rotationSliderY.setMinimum(-90)
        rotationSliderY.setValue(0)
        rotationSliderY.setMaximum(90)

        fontSizeSlider = QSlider(Qt.Orientation.Horizontal, self._barsWidget)
        fontSizeSlider.setTickInterval(10)
        fontSizeSlider.setTickPosition(QSlider.TicksBelow)
        fontSizeSlider.setMinimum(1)
        fontSizeSlider.setValue(30)
        fontSizeSlider.setMaximum(100)

        fontList = QFontComboBox(self._barsWidget)
        fontList.setCurrentFont(QFont("Times New Roman"))

        shadowQuality = QComboBox(self._barsWidget)
        shadowQuality.addItem("None")
        shadowQuality.addItem("Low")
        shadowQuality.addItem("Medium")
        shadowQuality.addItem("High")
        shadowQuality.addItem("Low Soft")
        shadowQuality.addItem("Medium Soft")
        shadowQuality.addItem("High Soft")
        shadowQuality.setCurrentIndex(5)

        rangeList = QComboBox(self._barsWidget)
        rangeList.addItem("2015")
        rangeList.addItem("2016")
        rangeList.addItem("2017")
        rangeList.addItem("2018")
        rangeList.addItem("2019")
        rangeList.addItem("2020")
        rangeList.addItem("2021")
        rangeList.addItem("2022")
        rangeList.addItem("All")
        rangeList.setCurrentIndex(8)

        axisTitlesVisibleCB = QCheckBox(self._barsWidget)
        axisTitlesVisibleCB.setText("Axis titles visible")
        axisTitlesVisibleCB.setChecked(True)

        axisTitlesFixedCB = QCheckBox(self._barsWidget)
        axisTitlesFixedCB.setText("Axis titles fixed")
        axisTitlesFixedCB.setChecked(True)

        axisLabelRotationSlider = QSlider(Qt.Orientation.Horizontal, self._barsWidget)
        axisLabelRotationSlider.setTickInterval(10)
        axisLabelRotationSlider.setTickPosition(QSlider.TicksBelow)
        axisLabelRotationSlider.setMinimum(0)
        axisLabelRotationSlider.setValue(30)
        axisLabelRotationSlider.setMaximum(90)

        modeGroup = QButtonGroup(self._barsWidget)
        modeWeather = QRadioButton("Temperature Data", self._barsWidget)
        modeWeather.setChecked(True)
        modeCustomProxy = QRadioButton("Custom Proxy Data", self._barsWidget)
        modeGroup.addButton(modeWeather)
        modeGroup.addButton(modeCustomProxy)

        vLayout.addWidget(QLabel("Rotate horizontally"))
        vLayout.addWidget(rotationSliderX, 0, Qt.AlignTop)
        vLayout.addWidget(QLabel("Rotate vertically"))
        vLayout.addWidget(rotationSliderY, 0, Qt.AlignTop)
        vLayout.addWidget(labelButton, 0, Qt.AlignTop)
        vLayout.addWidget(cameraButton, 0, Qt.AlignTop)
        vLayout.addWidget(zoomToSelectedButton, 0, Qt.AlignTop)
        vLayout.addWidget(backgroundCheckBox)
        vLayout.addWidget(gridCheckBox)
        vLayout.addWidget(smoothCheckBox)
        vLayout.addWidget(seriesCheckBox)
        vLayout.addWidget(reverseValueAxisCheckBox)
        vLayout.addWidget(axisTitlesVisibleCB)
        vLayout.addWidget(axisTitlesFixedCB)
        vLayout.addWidget(QLabel("Show year"))
        vLayout.addWidget(rangeList)
        vLayout.addWidget(QLabel("Change bar style"))
        vLayout.addWidget(barStyleList)
        vLayout.addWidget(QLabel("Change selection mode"))
        vLayout.addWidget(selectionModeList)
        vLayout.addWidget(QLabel("Change theme"))
        vLayout.addWidget(themeList)
        vLayout.addWidget(QLabel("Adjust shadow quality"))
        vLayout.addWidget(shadowQuality)
        vLayout.addWidget(QLabel("Change font"))
        vLayout.addWidget(fontList)
        vLayout.addWidget(QLabel("Adjust font size"))
        vLayout.addWidget(fontSizeSlider)
        vLayout.addWidget(QLabel("Axis label rotation"))
        vLayout.addWidget(axisLabelRotationSlider, 0, Qt.AlignTop)
        vLayout.addWidget(modeWeather, 0, Qt.AlignTop)
        vLayout.addWidget(modeCustomProxy, 1, Qt.AlignTop)

        modifier = GraphModifier(barsGraph, self)
        modifier.changeTheme(themeList.currentIndex())

        rotationSliderX.valueChanged.connect(modifier.rotateX)
        rotationSliderY.valueChanged.connect(modifier.rotateY)

        labelButton.clicked.connect(modifier.changeLabelBackground)
        cameraButton.clicked.connect(modifier.changePresetCamera)
        zoomToSelectedButton.clicked.connect(modifier.zoomToSelectedBar)

        backgroundCheckBox.checkStateChanged.connect(modifier.setPlotAreaBackgroundVisible)
        gridCheckBox.checkStateChanged.connect(modifier.setGridVisible)
        smoothCheckBox.checkStateChanged.connect(modifier.setSmoothBars)
        seriesCheckBox.checkStateChanged.connect(modifier.setSeriesVisibility)
        reverseValueAxisCheckBox.checkStateChanged.connect(modifier.setReverseValueAxis)

        modifier.backgroundVisibleChanged.connect(backgroundCheckBox.setChecked)
        modifier.gridVisibleChanged.connect(gridCheckBox.setChecked)

        rangeList.currentIndexChanged.connect(modifier.changeRange)

        barStyleList.currentIndexChanged.connect(modifier.changeStyle)

        selectionModeList.currentIndexChanged.connect(modifier.changeSelectionMode)

        themeList.currentIndexChanged.connect(modifier.changeTheme)

        shadowQuality.currentIndexChanged.connect(modifier.changeShadowQuality)

        modifier.shadowQualityChanged.connect(shadowQuality.setCurrentIndex)
        barsGraph.shadowQualityChanged.connect(modifier.shadowQualityUpdatedByVisual)

        fontSizeSlider.valueChanged.connect(modifier.changeFontSize)
        fontList.currentFontChanged.connect(modifier.changeFont)

        modifier.fontSizeChanged.connect(fontSizeSlider.setValue)
        modifier.fontChanged.connect(fontList.setCurrentFont)

        axisTitlesVisibleCB.checkStateChanged.connect(modifier.setAxisTitleVisibility)
        axisTitlesFixedCB.checkStateChanged.connect(modifier.setAxisTitleFixed)
        axisLabelRotationSlider.valueChanged.connect(modifier.changeLabelRotation)

        modeWeather.toggled.connect(modifier.setDataModeToWeather)
        modeCustomProxy.toggled.connect(modifier.setDataModeToCustom)
        modeWeather.toggled.connect(seriesCheckBox.setEnabled)
        modeWeather.toggled.connect(rangeList.setEnabled)
        modeWeather.toggled.connect(axisTitlesVisibleCB.setEnabled)
        modeWeather.toggled.connect(axisTitlesFixedCB.setEnabled)
        modeWeather.toggled.connect(axisLabelRotationSlider.setEnabled)

    def barsWidget(self):
        return self._barsWidget
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations


from math import atan, degrees
import numpy as np

from PySide6.QtCore import QObject, QPropertyAnimation, Qt, Signal, Slot
from PySide6.QtGui import QFont, QVector3D
from PySide6.QtGraphs import (QAbstract3DSeries,
                              QBarDataItem, QBar3DSeries, QCategory3DAxis,
                              QValue3DAxis, QtGraphs3D, QGraphsTheme)

from rainfalldata import RainfallData

# Set up data
TEMP_OULU = np.array([
    [-7.4, -2.4, 0.0, 3.0, 8.2, 11.6, 14.7, 15.4, 11.4, 4.2, 2.1, -2.3],  # 2015
    [-13.4, -3.9, -1.8, 3.1, 10.6, 13.7, 17.8, 13.6, 10.7, 3.5, -3.1, -4.2],  # 2016
    [-5.7, -6.7, -3.0, -0.1, 4.7, 12.4, 16.1, 14.1, 9.4, 3.0, -0.3, -3.2],  # 2017
    [-6.4, -11.9, -7.4, 1.9, 11.4, 12.4, 21.5, 16.1, 11.0, 4.4, 2.1, -4.1],  # 2018
    [-11.7, -6.1, -2.4, 3.9, 7.2, 14.5, 15.6, 14.4, 8.5, 2.0, -3.0, -1.5],  # 2019
    [-2.1, -3.4, -1.8, 0.6, 7.0, 17.1, 15.6, 15.4, 11.1, 5.6, 1.9, -1.7],  # 2020
    [-9.6, -11.6, -3.2, 2.4, 7.8, 17.3, 19.4, 14.2, 8.0, 5.2, -2.2, -8.6],  # 2021
    [-7.3, -6.4, -1.8, 1.3, 8.1, 15.5, 17.6, 17.6, 9.1, 5.4, -1.5, -4.4]],  # 2022
    np.float64)


TEMP_HELSINKI = np.array([
    [-2.0, -0.1, 1.8, 5.1, 9.7, 13.7, 16.3, 17.3, 12.7, 5.4, 4.6, 2.1],  # 2015
    [-10.3, -0.6, 0.0, 4.9, 14.3, 15.7, 17.7, 16.0, 12.7, 4.6, -1.0, -0.9],  # 2016
    [-2.9, -3.3, 0.7, 2.3, 9.9, 13.8, 16.1, 15.9, 11.4, 5.0, 2.7, 0.7],  # 2017
    [-2.2, -8.4, -4.7, 5.0, 15.3, 15.8, 21.2, 18.2, 13.3, 6.7, 2.8, -2.0],  # 2018
    [-6.2, -0.5, -0.3, 6.8, 10.6, 17.9, 17.5, 16.8, 11.3, 5.2, 1.8, 1.4],  # 2019
    [1.9, 0.5, 1.7, 4.5, 9.5, 18.4, 16.5, 16.8, 13.0, 8.2, 4.4, 0.9],  # 2020
    [-4.7, -8.1, -0.9, 4.5, 10.4, 19.2, 20.9, 15.4, 9.5, 8.0, 1.5, -6.7],  # 2021
    [-3.3, -2.2, -0.2, 3.3, 9.6, 16.9, 18.1, 18.9, 9.2, 7.6, 2.3, -3.4]],  # 2022
    np.float64)


class GraphModifier(QObject):

    shadowQualityChanged = Signal(int)
    backgroundVisibleChanged = Signal(bool)
    gridVisibleChanged = Signal(bool)
    fontChanged = Signal(QFont)
    fontSizeChanged = Signal(int)

    def __init__(self, bargraph, parent):
        super().__init__(parent)
        self._graph = bargraph
        self._temperatureAxis = QValue3DAxis()
        self._yearAxis = QCategory3DAxis()
        self._monthAxis = QCategory3DAxis()
        self._primarySeries = QBar3DSeries()
        self._secondarySeries = QBar3DSeries()
        self._celsiusString = "°C"

        self._xRotation = float(0)
        self._yRotation = float(0)
        self._fontSize = 30
        self._segments = 4
        self._subSegments = 3
        self._minval = float(-20)
        self._maxval = float(20)
        self._barMesh = QAbstract3DSeries.Mesh.BevelBar
        self._smooth = False
        self._animationCameraX = QPropertyAnimation()
        self._animationCameraY = QPropertyAnimation()
        self._animationCameraZoom = QPropertyAnimation()
        self._animationCameraTarget = QPropertyAnimation()
        self._defaultAngleX = float(0)
        self._defaultAngleY = float(0)
        self._defaultZoom = float(0)
        self._defaultTarget = []
        self._customData = None

        self._graph.setShadowQuality(QtGraphs3D.ShadowQuality.SoftMedium)
        theme = self._graph.activeTheme()
        theme.setPlotAreaBackgroundVisible(False)
        theme.setLabelFont(QFont("Times New Roman", self._fontSize))
        theme.setLabelBackgroundVisible(True)
        self._graph.setMultiSeriesUniform(True)

        self._months = ["January", "February", "March", "April", "May", "June",
                        "July", "August", "September", "October", "November",
                        "December"]
        self._years = ["2015", "2016", "2017", "2018", "2019", "2020",
                       "2021", "2022"]

        self._temperatureAxis.setTitle("Average temperature")
        self._temperatureAxis.setSegmentCount(self._segments)
        self._temperatureAxis.setSubSegmentCount(self._subSegments)
        self._temperatureAxis.setRange(self._minval, self._maxval)
        self._temperatureAxis.setLabelFormat("%.1f " + self._celsiusString)
        self._temperatureAxis.setLabelAutoAngle(30.0)
        self._temperatureAxis.setTitleVisible(True)

        self._yearAxis.setTitle("Year")
        self._yearAxis.setLabelAutoAngle(30.0)
        self._yearAxis.setTitleVisible(True)
        self._monthAxis.setTitle("Month")
        self._monthAxis.setLabelAutoAngle(30.0)
        self._monthAxis.setTitleVisible(True)

        self._graph.setValueAxis(self._temperatureAxis)
        self._graph.setRowAxis(self._yearAxis)
        self._graph.setColumnAxis(self._monthAxis)

        format = "Oulu - @colLabel @rowLabel: @valueLabel"
        self._primarySeries.setItemLabelFormat(format)
        self._primarySeries.setMesh(QAbstract3DSeries.Mesh.BevelBar)
        self._primarySeries.setMeshSmooth(False)

        format = "Helsinki - @colLabel @rowLabel: @valueLabel"
        self._secondarySeries.setItemLabelFormat(format)
        self._secondarySeries.setMesh(QAbstract3DSeries.Mesh.BevelBar)
        self._secondarySeries.setMeshSmooth(False)
        self._secondarySeries.setVisible(False)

        self._graph.addSeries(self._primarySeries)
        self._graph.addSeries(self._secondarySeries)

        self.changePresetCamera()

        self.resetTemperatureData()

        # Set up property animations for zooming to the selected bar
        self._defaultAngleX = self._graph.cameraXRotation()
        self._defaultAngleY = self._graph.cameraYRotation()
        self._defaultZoom = self._graph.cameraZoomLevel()
        self._defaultTarget = self._graph.cameraTargetPosition()

        self._animationCameraX.setTargetObject(self._graph)
        self._animationCameraY.setTargetObject(self._graph)
        self._animationCameraZoom.setTargetObject(self._graph)
        self._animationCameraTarget.setTargetObject(self._graph)

        self._animationCameraX.setPropertyName(b"cameraXRotation")
        self._animationCameraY.setPropertyName(b"cameraYRotation")
        self._animationCameraZoom.setPropertyName(b"cameraZoomLevel")
        self._animationCameraTarget.setPropertyName(b"cameraTargetPosition")

        duration = 1700
        self._animationCameraX.setDuration(duration)
        self._animationCameraY.setDuration(duration)
        self._animationCameraZoom.setDuration(duration)
        self._animationCameraTarget.setDuration(duration)

        # The zoom always first zooms out above the graph and then zooms in
        zoomOutFraction = 0.3
        self._animationCameraX.setKeyValueAt(zoomOutFraction, 0.0)
        self._animationCameraY.setKeyValueAt(zoomOutFraction, 90.0)
        self._animationCameraZoom.setKeyValueAt(zoomOutFraction, 50.0)
        self._animationCameraTarget.setKeyValueAt(zoomOutFraction,
                                                  QVector3D(0, 0, 0))
        self._customData = RainfallData()

    def resetTemperatureData(self):
        # Create data arrays
        dataSet = []
        dataSet2 = []

        for year in range(0, len(self._years)):
            # Create a data row
            dataRow = []
            dataRow2 = []
            for month in range(0, len(self._months)):
                # Add data to the row
                item = QBarDataItem()
                item.setValue(TEMP_OULU[year][month])
                dataRow.append(item)
                item = QBarDataItem()
                item.setValue(TEMP_HELSINKI[year][month])
                dataRow2.append(item)

            # Add the row to the set
            dataSet.append(dataRow)
            dataSet2.append(dataRow2)

        # Add data to the data proxy (the data proxy assumes ownership of it)
        self._primarySeries.dataProxy().resetArray(dataSet, self._years, self._months)
        self._secondarySeries.dataProxy().resetArray(dataSet2, self._years, self._months)

    @Slot(int)
    def changeRange(self, range):
        if range >= len(self._years):
            self._yearAxis.setRange(0, len(self._years) - 1)
        else:
            self._yearAxis.setRange(range, range)

    @Slot(int)
    def changeStyle(self, style):
        comboBox = self.sender()
        if comboBox:
            self._barMesh = comboBox.itemData(style)
            self._primarySeries.setMesh(self._barMesh)
            self._secondarySeries.setMesh(self._barMesh)
            self._customData.customSeries().setMesh(self._barMesh)

    def changePresetCamera(self):
        self._animationCameraX.stop()
        self._animationCameraY.stop()
        self._animationCameraZoom.stop()
        self._animationCameraTarget.stop()

        # Restore camera target in case animation has changed it
        self._graph.setCameraTargetPosition(QVector3D(0.0, 0.0, 0.0))

        self._preset = QtGraphs3D.CameraPreset.Front.value

        self._graph.setCameraPreset(QtGraphs3D.CameraPreset(self._preset))

        self._preset += 1
        if self._preset > QtGraphs3D.CameraPreset.DirectlyBelow.value:
            self._preset = QtGraphs3D.CameraPreset.FrontLow.value

    @Slot(int)
    def changeTheme(self, theme):
        currentTheme = self._graph.activeTheme()
        currentTheme.setTheme(QGraphsTheme.Theme(theme))
        self.backgroundVisibleChanged.emit(currentTheme.isBackgroundVisible())
        self.gridVisibleChanged.emit(currentTheme.isGridVisible())
        self.fontChanged.emit(currentTheme.labelFont())
        self.fontSizeChanged.emit(currentTheme.labelFont().pointSize())

    def changeLabelBackground(self):
        theme = self._graph.activeTheme()
        theme.setLabelBackgroundVisible(not theme.isLabelBackgroundVisible())

    @Slot(int)
    def changeSelectionMode(self, selectionMode):
        comboBox = self.sender()
        if comboBox:
            flags = comboBox.itemData(selectionMode)
            self._graph.setSelectionMode(QtGraphs3D.SelectionFlags(flags))

    def changeFont(self, font):
        newFont = font
        self._graph.activeTheme().setLabelFont(newFont)

    def changeFontSize(self, fontsize):
        self._fontSize = fontsize
        font = self._graph.activeTheme().labelFont()
        font.setPointSize(self._fontSize)
        self._graph.activeTheme().setLabelFont(font)

    @Slot(QtGraphs3D.ShadowQuality)
    def shadowQualityUpdatedByVisual(self, sq):
        # Updates the UI component to show correct shadow quality
        self.shadowQualityChanged.emit(sq.value)

    @Slot(int)
    def changeLabelRotation(self, rotation):
        self._temperatureAxis.setLabelAutoAngle(float(rotation))
        self._monthAxis.setLabelAutoAngle(float(rotation))
        self._yearAxis.setLabelAutoAngle(float(rotation))

    @Slot(bool)
    def setAxisTitleVisibility(self, state):
        enabled = state == Qt.CheckState.Checked
        self._temperatureAxis.setTitleVisible(enabled)
        self._monthAxis.setTitleVisible(enabled)
        self._yearAxis.setTitleVisible(enabled)

    @Slot(bool)
    def setAxisTitleFixed(self, state):
        enabled = state == Qt.CheckState.Checked
        self._temperatureAxis.setTitleFixed(enabled)
        self._monthAxis.setTitleFixed(enabled)
        self._yearAxis.setTitleFixed(enabled)

    @Slot()
    def zoomToSelectedBar(self):
        self._animationCameraX.stop()
        self._animationCameraY.stop()
        self._animationCameraZoom.stop()
        self._animationCameraTarget.stop()

        currentX = self._graph.cameraXRotation()
        currentY = self._graph.cameraYRotation()
        currentZoom = self._graph.cameraZoomLevel()
        currentTarget = self._graph.cameraTargetPosition()

        self._animationCameraX.setStartValue(currentX)
        self._animationCameraY.setStartValue(currentY)
        self._animationCameraZoom.setStartValue(currentZoom)
        self._animationCameraTarget.setStartValue(currentTarget)

        selectedBar = (self._graph.selectedSeries().selectedBar()
                       if self._graph.selectedSeries()
                       else QBar3DSeries.invalidSelectionPosition())

        if selectedBar != QBar3DSeries.invalidSelectionPosition():
            # Normalize selected bar position within axis range to determine
            # target coordinates
            endTarget = QVector3D()
            xMin = self._graph.columnAxis().min()
            xRange = self._graph.columnAxis().max() - xMin
            zMin = self._graph.rowAxis().min()
            zRange = self._graph.rowAxis().max() - zMin
            endTarget.setX((selectedBar.y() - xMin) / xRange * 2.0 - 1.0)
            endTarget.setZ((selectedBar.x() - zMin) / zRange * 2.0 - 1.0)

            # Rotate the camera so that it always points approximately to the
            # graph center
            endAngleX = 90.0 - degrees(atan(float(endTarget.z() / endTarget.x())))
            if endTarget.x() > 0.0:
                endAngleX -= 180.0
            proxy = self._graph.selectedSeries().dataProxy()
            barValue = proxy.itemAt(selectedBar.x(), selectedBar.y()).value()
            endAngleY = 30.0 if barValue >= 0.0 else -30.0
            if self._graph.valueAxis().reversed():
                endAngleY *= -1.0

            self._animationCameraX.setEndValue(float(endAngleX))
            self._animationCameraY.setEndValue(endAngleY)
            self._animationCameraZoom.setEndValue(250)
            self._animationCameraTarget.setEndValue(endTarget)
        else:
            # No selected bar, so return to the default view
            self._animationCameraX.setEndValue(self._defaultAngleX)
            self._animationCameraY.setEndValue(self._defaultAngleY)
            self._animationCameraZoom.setEndValue(self._defaultZoom)
            self._animationCameraTarget.setEndValue(self._defaultTarget)

        self._animationCameraX.start()
        self._animationCameraY.start()
        self._animationCameraZoom.start()
        self._animationCameraTarget.start()

    @Slot(bool)
    def setDataModeToWeather(self, enabled):
        if enabled:
            self.changeDataMode(False)

    @Slot(bool)
    def setDataModeToCustom(self, enabled):
        if enabled:
            self.changeDataMode(True)

    def changeShadowQuality(self, quality):
        sq = QtGraphs3D.ShadowQuality(quality)
        self._graph.setShadowQuality(sq)
        self.shadowQualityChanged.emit(quality)

    def rotateX(self, rotation):
        self._xRotation = rotation
        self._graph.setCameraPosition(self._xRotation, self._yRotation)

    def rotateY(self, rotation):
        self._yRotation = rotation
        self._graph.setCameraPosition(self._xRotation, self._yRotation)

    def setPlotAreaBackgroundVisible(self, state):
        enabled = state == Qt.CheckState.Checked
        self._graph.activeTheme().setPlotAreaBackgroundVisible(enabled)

    def setGridVisible(self, state):
        self._graph.activeTheme().setGridVisible(state == Qt.CheckState.Checked)

    def setSmoothBars(self, state):
        self._smooth = state == Qt.CheckState.Checked
        self._primarySeries.setMeshSmooth(self._smooth)
        self._secondarySeries.setMeshSmooth(self._smooth)
        self._customData.customSeries().setMeshSmooth(self._smooth)

    def setSeriesVisibility(self, state):
        self._secondarySeries.setVisible(state == Qt.CheckState.Checked)

    def setReverseValueAxis(self, state):
        self._graph.valueAxis().setReversed(state == Qt.CheckState.Checked)

    def changeDataMode(self, customData):
        # Change between weather data and data from custom proxy
        if customData:
            self._graph.removeSeries(self._primarySeries)
            self._graph.removeSeries(self._secondarySeries)
            self._graph.addSeries(self._customData.customSeries())
            self._graph.setValueAxis(self._customData.valueAxis())
            self._graph.setRowAxis(self._customData.rowAxis())
            self._graph.setColumnAxis(self._customData.colAxis())
        else:
            self._graph.removeSeries(self._customData.customSeries())
            self._graph.addSeries(self._primarySeries)
            self._graph.addSeries(self._secondarySeries)
            self._graph.setValueAxis(self._temperatureAxis)
            self._graph.setRowAxis(self._yearAxis)
            self._graph.setColumnAxis(self._monthAxis)
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations

from PySide6.QtCore import QPoint, Qt, Slot
from PySide6.QtGui import QLinearGradient, QVector3D
from PySide6.QtGraphs import (QSurface3DSeries, QSurfaceDataItem,
                              QGraphsTheme)


DARK_RED_POS = 1.0
RED_POS = 0.8
YELLOW_POS = 0.6
GREEN_POS = 0.4
DARK_GREEN_POS = 0.2


class HighlightSeries(QSurface3DSeries):

    def __init__(self):
        super().__init__()
        self._width = 100
        self._height = 100
        self._srcWidth = 0
        self._srcHeight = 0
        self._position = {}
        self._topographicSeries = None
        self._minHeight = 0.0
        self.setDrawMode(QSurface3DSeries.DrawSurface)
        self.setShading(QSurface3DSeries.Shading.Flat)
        self.setVisible(False)

    def setTopographicSeries(self, series):
        self._topographicSeries = series
        array = self._topographicSeries.dataArray()
        self._srcWidth = len(array[0])
        self._srcHeight = len(array)
        self._topographicSeries.selectedPointChanged.connect(self.handlePositionChange)

    def setMinHeight(self, height):
        self. m_minHeight = height

    @Slot(QPoint)
    def handlePositionChange(self, position):
        self._position = position

        if position == self.invalidSelectionPosition():
            self.setVisible(False)
            return

        halfWidth = self._width / 2
        halfHeight = self._height / 2

        startX = position.y() - halfWidth
        if startX < 0:
            startX = 0
        endX = position.y() + halfWidth
        if endX > (self._srcWidth - 1):
            endX = self._srcWidth - 1
        startZ = position.x() - halfHeight
        if startZ < 0:
            startZ = 0
        endZ = position.x() + halfHeight
        if endZ > (self._srcHeight - 1):
            endZ = self._srcHeight - 1

        srcArray = self._topographicSeries.dataArray()

        dataArray = []
        for i in range(int(startZ), int(endZ)):
            newRow = []
            srcRow = srcArray[i]
            for j in range(startX, endX):
                pos = srcRow.at(j).position()
                pos.setY(pos.y() + 0.1)
                item = QSurfaceDataItem(QVector3D(pos))
                newRow.append(item)
            dataArray.append(newRow)
        self.dataProxy().resetArray(dataArray)
        self.setVisible(True)

    @Slot(float)
    def handleGradientChange(self, value):
        ratio = self._minHeight / value

        gr = QLinearGradient()
        gr.setColorAt(0.0, Qt.black)
        gr.setColorAt(DARK_GREEN_POS * ratio, Qt.darkGreen)
        gr.setColorAt(GREEN_POS * ratio, Qt.green)
        gr.setColorAt(YELLOW_POS * ratio, Qt.yellow)
        gr.setColorAt(RED_POS * ratio, Qt.red)
        gr.setColorAt(DARK_RED_POS * ratio, Qt.darkRed)

        self.setBaseGradient(gr)
        self.setColorStyle(QGraphsTheme.ColorStyle.RangeGradient)
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations

import sys

from pathlib import Path

from PySide6.QtCore import QFile, QIODevice, QObject
from PySide6.QtGraphs import (QBar3DSeries, QCategory3DAxis, QValue3DAxis)

from variantbardataproxy import VariantBarDataProxy
from variantbardatamapping import VariantBarDataMapping
from variantdataset import VariantDataSet


MONTHS = ["January", "February", "March", "April",
          "May", "June", "July", "August", "September", "October",
          "November", "December"]


class RainfallData(QObject):

    def __init__(self):
        super().__init__()
        self._columnCount = 0
        self._rowCount = 0
        self._years = []
        self._numericMonths = []
        self._proxy = VariantBarDataProxy()
        self._mapping = None
        self._dataSet = None
        self._series = QBar3DSeries()
        self._valueAxis = QValue3DAxis()
        self._rowAxis = QCategory3DAxis()
        self._colAxis = QCategory3DAxis()

        # In data file the months are in numeric format, so create custom list
        for i in range(1, 13):
            self._numericMonths.append(str(i))

        self._columnCount = len(self._numericMonths)

        self.updateYearsList(2010, 2022)

        # Create proxy and series
        self._proxy = VariantBarDataProxy()
        self._series = QBar3DSeries(self._proxy)

        self._series.setItemLabelFormat("%.1f mm")

        # Create the axes
        self._rowAxis = QCategory3DAxis(self)
        self._colAxis = QCategory3DAxis(self)
        self._valueAxis = QValue3DAxis(self)
        self._rowAxis.setAutoAdjustRange(True)
        self._colAxis.setAutoAdjustRange(True)
        self._valueAxis.setAutoAdjustRange(True)

        # Set axis labels and titles
        self._rowAxis.setTitle("Year")
        self._colAxis.setTitle("Month")
        self._valueAxis.setTitle("rainfall (mm)")
        self._valueAxis.setSegmentCount(5)
        self._rowAxis.setLabels(self._years)
        self._colAxis.setLabels(MONTHS)
        self._rowAxis.setTitleVisible(True)
        self._colAxis.setTitleVisible(True)
        self._valueAxis.setTitleVisible(True)

        self.addDataSet()

    def customSeries(self):
        return self._series

    def valueAxis(self):
        return self._valueAxis

    def rowAxis(self):
        return self._rowAxis

    def colAxis(self):
        return self._colAxis

    def updateYearsList(self, start, end):
        self._years.clear()
        for i in range(start, end + 1):
            self._years.append(str(i))
        self._rowCount = len(self._years)

    def addDataSet(self):
        # Create a new variant data set and data item list
        self._dataSet = VariantDataSet()
        itemList = []

        # Read data from a data file into the data item list
        file_path = Path(__file__).resolve().parent / "data" / "raindata.txt"
        dataFile = QFile(file_path)
        if dataFile.open(QIODevice.ReadOnly | QIODevice.Text):
            data = dataFile.readAll().data().decode("utf8")
            for line in data.split("\n"):
                if line and not line.startswith("#"):  # Ignore comments
                    tokens = line.split(",")
                    # Each line has three data items: Year, month, and
                    # rainfall value
                    if len(tokens) >= 3:
                        # Store year and month as strings, and rainfall value
                        # as double into a variant data item and add the item to
                        # the item list.
                        newItem = []
                        newItem.append(tokens[0].strip())
                        newItem.append(tokens[1].strip())
                        newItem.append(float(tokens[2].strip()))
                        itemList.append(newItem)
        else:
            print("Unable to open data file:", dataFile.fileName(),
                  file=sys.stderr)

        # Add items to the data set and set it to the proxy
        self._dataSet.addItems(itemList)
        self._proxy.setDataSet(self._dataSet)

        # Create new mapping for the data and set it to the proxy
        self._mapping = VariantBarDataMapping(0, 1, 2,
                                              self._years, self._numericMonths)
        self._proxy.setMapping(self._mapping)
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations

from enum import Enum
from math import sin, cos, degrees, sqrt

from PySide6.QtCore import QObject, Signal, Slot, Qt
from PySide6.QtGui import QVector2D, QVector3D
from PySide6.QtGraphs import (QAbstract3DSeries,
                              QScatterDataItem, QScatterDataProxy,
                              QScatter3DSeries, QtGraphs3D, QGraphsTheme)


NUMBER_OF_ITEMS = 10000
CURVE_DIVIDER = 7.5
LOWER_NUMBER_OF_ITEMS = 900
LOWER_CURVE_DIVIDER = 0.75


class InputState(Enum):
    StateNormal = 0
    StateDraggingX = 1
    StateDraggingZ = 2
    StateDraggingY = 3


class ScatterDataModifier(QObject):

    backgroundEnabledChanged = Signal(bool)
    gridVisibleChanged = Signal(bool)
    shadowQualityChanged = Signal(int)

    def __init__(self, scatter, parent):
        super().__init__(parent)

        self._graph = scatter

        self._style = QAbstract3DSeries.Mesh.Sphere
        self._smooth = True
        self._autoAdjust = True
        self._itemCount = LOWER_NUMBER_OF_ITEMS
        self._CURVE_DIVIDER = LOWER_CURVE_DIVIDER

        self._graph.activeTheme().setTheme(QGraphsTheme.Theme.MixSeries)
        self._graph.activeTheme().setColorScheme(QGraphsTheme.ColorScheme.Dark)
        self._graph.setShadowQuality(QtGraphs3D.ShadowQuality.SoftHigh)
        self._graph.setCameraPreset(QtGraphs3D.CameraPreset.Front)
        self._graph.setCameraZoomLevel(80.0)

        self._proxy = QScatterDataProxy()
        self._series = QScatter3DSeries(self._proxy)
        self._series.setItemLabelFormat("@xTitle: @xLabel @yTitle: @yLabel @zTitle: @zLabel")
        self._series.setMeshSmooth(self._smooth)
        self._graph.addSeries(self._series)
        self._preset = QtGraphs3D.CameraPreset.FrontLow.value

        self._state = InputState.StateNormal
        self._dragSpeedModifier = float(15)

        self._graph.selectedElementChanged.connect(self.handleElementSelected)
        self._graph.dragged.connect(self.handleAxisDragging)
        self._graph.setDragButton(Qt.LeftButton)

        self.addData()

    def addData(self):
        # Configure the axes according to the data
        self._graph.axisX().setTitle("X")
        self._graph.axisY().setTitle("Y")
        self._graph.axisZ().setTitle("Z")

        dataArray = []
        limit = int(sqrt(self._itemCount) / 2.0)
        for i in range(-limit, limit):
            for j in range(-limit, limit):
                x = float(i) + 0.5
                y = cos(degrees(float(i * j) / self._CURVE_DIVIDER))
                z = float(j) + 0.5
                dataArray.append(QScatterDataItem(QVector3D(x, y, z)))

        self._graph.seriesList()[0].dataProxy().resetArray(dataArray)

    @Slot(int)
    def changeStyle(self, style):
        comboBox = self.sender()
        if comboBox:
            self._style = comboBox.itemData(style)
            if self._graph.seriesList():
                self._graph.seriesList()[0].setMesh(self._style)

    @Slot(int)
    def setSmoothDots(self, smooth):
        self._smooth = smooth == Qt.CheckState.Checked
        series = self._graph.seriesList()[0]
        series.setMeshSmooth(self._smooth)

    @Slot(int)
    def changeTheme(self, theme):
        currentTheme = self._graph.activeTheme()
        currentTheme.setTheme(QGraphsTheme.Theme(theme))
        self.backgroundEnabledChanged.emit(currentTheme.isPlotAreaBackgroundVisible())
        self.gridVisibleChanged.emit(currentTheme.isGridVisible())

    @Slot()
    def changePresetCamera(self):
        self._graph.setCameraPreset(QtGraphs3D.CameraPreset(self._preset))

        self._preset += 1
        if self._preset > QtGraphs3D.CameraPreset.DirectlyBelow.value:
            self._preset = QtGraphs3D.CameraPreset.FrontLow.value

    @Slot(QtGraphs3D.ShadowQuality)
    def shadowQualityUpdatedByVisual(self, sq):
        self.shadowQualityChanged.emit(sq.value)

    @Slot(int)
    def changeShadowQuality(self, quality):
        sq = QtGraphs3D.ShadowQuality(quality)
        self._graph.setShadowQuality(sq)

    @Slot(int)
    def setPlotAreaBackgroundVisible(self, state):
        enabled = state == Qt.CheckState.Checked
        self._graph.activeTheme().setPlotAreaBackgroundVisible(enabled)

    @Slot(int)
    def setGridVisible(self, state):
        self._graph.activeTheme().setGridVisible(state == Qt.Checked.value)

    @Slot()
    def toggleItemCount(self):
        if self._itemCount == NUMBER_OF_ITEMS:
            self._itemCount = LOWER_NUMBER_OF_ITEMS
            self._CURVE_DIVIDER = LOWER_CURVE_DIVIDER
        else:
            self._itemCount = NUMBER_OF_ITEMS
            self._CURVE_DIVIDER = CURVE_DIVIDER

        self._graph.seriesList()[0].dataProxy().resetArray([])
        self.addData()

    @Slot()
    def toggleRanges(self):
        if not self._autoAdjust:
            self._graph.axisX().setAutoAdjustRange(True)
            self._graph.axisZ().setAutoAdjustRange(True)
            self._dragSpeedModifier = 1.5
            self._autoAdjust = True
        else:
            self._graph.axisX().setRange(-10.0, 10.0)
            self._graph.axisZ().setRange(-10.0, 10.0)
            self._dragSpeedModifier = float(15)
            self._autoAdjust = False

    @Slot(QtGraphs3D.ElementType)
    def handleElementSelected(self, type):
        if type == QtGraphs3D.ElementType.AxisXLabel:
            self._state = InputState.StateDraggingX
        elif type == QtGraphs3D.ElementType.AxisYLabel:
            self._state = InputState.StateDraggingY
        elif type == QtGraphs3D.ElementType.AxisZLabel:
            self._state = InputState.StateDraggingZ
        else:
            self._state = InputState.StateNormal

    @Slot(QVector2D)
    def handleAxisDragging(self, delta):
        distance = 0.0
        # Get scene orientation from active camera
        xRotation = self._graph.cameraXRotation()
        yRotation = self._graph.cameraYRotation()

        # Calculate directional drag multipliers based on rotation
        xMulX = cos(degrees(xRotation))
        xMulY = sin(degrees(xRotation))
        zMulX = sin(degrees(xRotation))
        zMulY = cos(degrees(xRotation))

        # Get the drag amount
        move = delta.toPoint()

        # Flip the effect of y movement if we're viewing from below
        yMove = -move.y() if yRotation < 0 else move.y()

        # Adjust axes
        if self._state == InputState.StateDraggingX:
            axis = self._graph.axisX()
            distance = (move.x() * xMulX - yMove * xMulY) / self._dragSpeedModifier
            axis.setRange(axis.min() - distance, axis.max() - distance)
        elif self._state == InputState.StateDraggingZ:
            axis = self._graph.axisZ()
            distance = (move.x() * zMulX + yMove * zMulY) / self._dragSpeedModifier
            axis.setRange(axis.min() + distance, axis.max() + distance)
        elif self._state == InputState.StateDraggingY:
            axis = self._graph.axisY()
            # No need to use adjusted y move here
            distance = move.y() / self._dragSpeedModifier
            axis.setRange(axis.min() + distance, axis.max() + distance)
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations

from PySide6.QtCore import QObject, QSize, Qt
from PySide6.QtWidgets import (QCheckBox, QComboBox, QCommandLinkButton,
                               QLabel, QHBoxLayout, QSizePolicy,
                               QVBoxLayout, QWidget, )
from PySide6.QtQuickWidgets import QQuickWidget
from PySide6.QtGraphs import QAbstract3DSeries
from PySide6.QtGraphsWidgets import Q3DScatterWidgetItem

from scatterdatamodifier import ScatterDataModifier


class ScatterGraph(QObject):

    def __init__(self, minimum_graph_size, maximum_graph_size):
        super().__init__()

        scatterGraph = Q3DScatterWidgetItem()
        scatterGraphWidget = QQuickWidget()
        scatterGraph.setWidget(scatterGraphWidget)
        self._scatterWidget = QWidget()
        hLayout = QHBoxLayout(self._scatterWidget)
        scatterGraphWidget.setMinimumSize(minimum_graph_size)
        scatterGraphWidget.setMaximumSize(maximum_graph_size)
        scatterGraphWidget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        scatterGraphWidget.setFocusPolicy(Qt.StrongFocus)
        hLayout.addWidget(scatterGraphWidget, 1)

        vLayout = QVBoxLayout()
        hLayout.addLayout(vLayout)

        cameraButton = QCommandLinkButton(self._scatterWidget)
        cameraButton.setText("Change camera preset")
        cameraButton.setDescription("Switch between a number of preset camera positions")
        cameraButton.setIconSize(QSize(0, 0))

        itemCountButton = QCommandLinkButton(self._scatterWidget)
        itemCountButton.setText("Toggle item count")
        itemCountButton.setDescription("Switch between 900 and 10000 data points")
        itemCountButton.setIconSize(QSize(0, 0))

        rangeButton = QCommandLinkButton(self._scatterWidget)
        rangeButton.setText("Toggle axis ranges")
        rangeButton.setDescription("Switch between automatic axis ranges and preset ranges")
        rangeButton.setIconSize(QSize(0, 0))

        backgroundCheckBox = QCheckBox(self._scatterWidget)
        backgroundCheckBox.setText("Show graph background")
        backgroundCheckBox.setChecked(True)

        gridCheckBox = QCheckBox(self._scatterWidget)
        gridCheckBox.setText("Show grid")
        gridCheckBox.setChecked(True)

        smoothCheckBox = QCheckBox(self._scatterWidget)
        smoothCheckBox.setText("Smooth dots")
        smoothCheckBox.setChecked(True)

        itemStyleList = QComboBox(self._scatterWidget)
        itemStyleList.addItem("Sphere", QAbstract3DSeries.Mesh.Sphere)
        itemStyleList.addItem("Cube", QAbstract3DSeries.Mesh.Cube)
        itemStyleList.addItem("Minimal", QAbstract3DSeries.Mesh.Minimal)
        itemStyleList.addItem("Point", QAbstract3DSeries.Mesh.Point)
        itemStyleList.setCurrentIndex(0)

        themeList = QComboBox(self._scatterWidget)
        themeList.addItem("Qt")
        themeList.addItem("Primary Colors")
        themeList.addItem("Digia")
        themeList.addItem("Stone Moss")
        themeList.addItem("Army Blue")
        themeList.addItem("Retro")
        themeList.addItem("Ebony")
        themeList.addItem("Isabelle")
        themeList.setCurrentIndex(3)

        shadowQuality = QComboBox(self._scatterWidget)
        shadowQuality.addItem("None")
        shadowQuality.addItem("Low")
        shadowQuality.addItem("Medium")
        shadowQuality.addItem("High")
        shadowQuality.addItem("Low Soft")
        shadowQuality.addItem("Medium Soft")
        shadowQuality.addItem("High Soft")
        shadowQuality.setCurrentIndex(6)

        vLayout.addWidget(cameraButton)
        vLayout.addWidget(itemCountButton)
        vLayout.addWidget(rangeButton)
        vLayout.addWidget(backgroundCheckBox)
        vLayout.addWidget(gridCheckBox)
        vLayout.addWidget(smoothCheckBox)
        vLayout.addWidget(QLabel("Change dot style"))
        vLayout.addWidget(itemStyleList)
        vLayout.addWidget(QLabel("Change theme"))
        vLayout.addWidget(themeList)
        vLayout.addWidget(QLabel("Adjust shadow quality"))
        vLayout.addWidget(shadowQuality, 1, Qt.AlignTop)

        modifier = ScatterDataModifier(scatterGraph, self)

        cameraButton.clicked.connect(modifier.changePresetCamera)
        itemCountButton.clicked.connect(modifier.toggleItemCount)
        rangeButton.clicked.connect(modifier.toggleRanges)

        backgroundCheckBox.checkStateChanged.connect(modifier.setPlotAreaBackgroundVisible)
        gridCheckBox.checkStateChanged.connect(modifier.setGridVisible)
        smoothCheckBox.checkStateChanged.connect(modifier.setSmoothDots)

        modifier.backgroundEnabledChanged.connect(backgroundCheckBox.setChecked)
        modifier.gridVisibleChanged.connect(gridCheckBox.setChecked)
        itemStyleList.currentIndexChanged.connect(modifier.changeStyle)

        themeList.currentIndexChanged.connect(modifier.changeTheme)

        shadowQuality.currentIndexChanged.connect(modifier.changeShadowQuality)

        modifier.shadowQualityChanged.connect(shadowQuality.setCurrentIndex)
        scatterGraph.shadowQualityChanged.connect(modifier.shadowQualityUpdatedByVisual)

    def scatterWidget(self):
        return self._scatterWidget
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations

from surfacegraphmodifier import SurfaceGraphModifier

from PySide6.QtCore import QObject, Qt
from PySide6.QtGui import QBrush, QIcon, QLinearGradient, QPainter, QPixmap
from PySide6.QtWidgets import (QGroupBox, QCheckBox, QLabel, QHBoxLayout,
                               QPushButton, QRadioButton, QSizePolicy, QSlider,
                               QVBoxLayout, QWidget)
from PySide6.QtQuickWidgets import QQuickWidget
from PySide6.QtGraphsWidgets import Q3DSurfaceWidgetItem


def gradientBtoYPB_Pixmap():
    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)
    with QPainter(pm) as pmp:
        pmp.setBrush(QBrush(grBtoY))
        pmp.setPen(Qt.NoPen)
        pmp.drawRect(0, 0, 24, 100)
    return pm


def gradientGtoRPB_Pixmap():
    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)
    pm = QPixmap(24, 100)
    with QPainter(pm) as pmp:
        pmp.setBrush(QBrush(grGtoR))
        pmp.setPen(Qt.NoPen)
        pmp.drawRect(0, 0, 24, 100)
    return pm


def highlightPixmap():
    HEIGHT = 400
    WIDTH = 110
    BORDER = 10
    gr = QLinearGradient(0, 0, 1, HEIGHT - 2 * BORDER)
    gr.setColorAt(1.0, Qt.black)
    gr.setColorAt(0.8, Qt.darkGreen)
    gr.setColorAt(0.6, Qt.green)
    gr.setColorAt(0.4, Qt.yellow)
    gr.setColorAt(0.2, Qt.red)
    gr.setColorAt(0.0, Qt.darkRed)
    pmHighlight = QPixmap(WIDTH, HEIGHT)
    pmHighlight.fill(Qt.transparent)
    with QPainter(pmHighlight) as pmpHighlight:
        pmpHighlight.setBrush(QBrush(gr))
        pmpHighlight.setPen(Qt.NoPen)
        pmpHighlight.drawRect(BORDER, BORDER, 35, HEIGHT - 2 * BORDER)
        pmpHighlight.setPen(Qt.black)
        step = (HEIGHT - 2 * BORDER) / 5
        for i in range(0, 6):
            yPos = i * step + BORDER
            pmpHighlight.drawLine(BORDER, yPos, 55, yPos)
            HEIGHT = 550 - (i * 110)
            pmpHighlight.drawText(60, yPos + 2, f"{HEIGHT} m")
    return pmHighlight


class SurfaceGraph(QObject):

    def __init__(self, minimum_graph_size, maximum_graph_size):
        super().__init__()

        surfaceGraphWidget = QQuickWidget()
        surfaceGraph = Q3DSurfaceWidgetItem()
        surfaceGraph.setWidget(surfaceGraphWidget)
        self._surfaceWidget = QWidget()
        hLayout = QHBoxLayout(self._surfaceWidget)
        surfaceGraphWidget.setMinimumSize(minimum_graph_size)
        surfaceGraphWidget.setMaximumSize(maximum_graph_size)
        surfaceGraphWidget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        surfaceGraphWidget.setFocusPolicy(Qt.StrongFocus)
        surfaceGraphWidget.setResizeMode(QQuickWidget.SizeRootObjectToView)
        hLayout.addWidget(surfaceGraphWidget, 1)
        vLayout = QVBoxLayout()
        hLayout.addLayout(vLayout)
        vLayout.setAlignment(Qt.AlignTop)
        # Create control widgets
        modelGroupBox = QGroupBox("Model")
        sqrtSinModelRB = QRadioButton(self._surfaceWidget)
        sqrtSinModelRB.setText("Sqrt and Sin")
        sqrtSinModelRB.setChecked(False)
        heightMapModelRB = QRadioButton(self._surfaceWidget)
        heightMapModelRB.setText("Multiseries\nHeight Map")
        heightMapModelRB.setChecked(False)
        texturedModelRB = QRadioButton(self._surfaceWidget)
        texturedModelRB.setText("Textured\nTopography")
        texturedModelRB.setChecked(False)
        modelVBox = QVBoxLayout()
        modelVBox.addWidget(sqrtSinModelRB)
        modelVBox.addWidget(heightMapModelRB)
        modelVBox.addWidget(texturedModelRB)
        modelGroupBox.setLayout(modelVBox)
        selectionGroupBox = QGroupBox("Graph Selection Mode")
        modeNoneRB = QRadioButton(self._surfaceWidget)
        modeNoneRB.setText("No selection")
        modeNoneRB.setChecked(False)
        modeItemRB = QRadioButton(self._surfaceWidget)
        modeItemRB.setText("Item")
        modeItemRB.setChecked(False)
        modeSliceRowRB = QRadioButton(self._surfaceWidget)
        modeSliceRowRB.setText("Row Slice")
        modeSliceRowRB.setChecked(False)
        modeSliceColumnRB = QRadioButton(self._surfaceWidget)
        modeSliceColumnRB.setText("Column Slice")
        modeSliceColumnRB.setChecked(False)
        selectionVBox = QVBoxLayout()
        selectionVBox.addWidget(modeNoneRB)
        selectionVBox.addWidget(modeItemRB)
        selectionVBox.addWidget(modeSliceRowRB)
        selectionVBox.addWidget(modeSliceColumnRB)
        selectionGroupBox.setLayout(selectionVBox)
        axisGroupBox = QGroupBox("Axis ranges")
        axisMinSliderX = QSlider(Qt.Orientation.Horizontal)
        axisMinSliderX.setMinimum(0)
        axisMinSliderX.setTickInterval(1)
        axisMinSliderX.setEnabled(True)
        axisMaxSliderX = QSlider(Qt.Orientation.Horizontal)
        axisMaxSliderX.setMinimum(1)
        axisMaxSliderX.setTickInterval(1)
        axisMaxSliderX.setEnabled(True)
        axisMinSliderZ = QSlider(Qt.Orientation.Horizontal)
        axisMinSliderZ.setMinimum(0)
        axisMinSliderZ.setTickInterval(1)
        axisMinSliderZ.setEnabled(True)
        axisMaxSliderZ = QSlider(Qt.Orientation.Horizontal)
        axisMaxSliderZ.setMinimum(1)
        axisMaxSliderZ.setTickInterval(1)
        axisMaxSliderZ.setEnabled(True)
        axisVBox = QVBoxLayout(axisGroupBox)
        axisVBox.addWidget(QLabel("Column range"))
        axisVBox.addWidget(axisMinSliderX)
        axisVBox.addWidget(axisMaxSliderX)
        axisVBox.addWidget(QLabel("Row range"))
        axisVBox.addWidget(axisMinSliderZ)
        axisVBox.addWidget(axisMaxSliderZ)
        # Mode-dependent controls
        # sqrt-sin
        colorGroupBox = QGroupBox("Custom gradient")

        pixmap = gradientBtoYPB_Pixmap()
        gradientBtoYPB = QPushButton(self._surfaceWidget)
        gradientBtoYPB.setIcon(QIcon(pixmap))
        gradientBtoYPB.setIconSize(pixmap.size())

        pixmap = gradientGtoRPB_Pixmap()
        gradientGtoRPB = QPushButton(self._surfaceWidget)
        gradientGtoRPB.setIcon(QIcon(pixmap))
        gradientGtoRPB.setIconSize(pixmap.size())

        colorHBox = QHBoxLayout(colorGroupBox)
        colorHBox.addWidget(gradientBtoYPB)
        colorHBox.addWidget(gradientGtoRPB)
        # Multiseries heightmap
        showGroupBox = QGroupBox("Show Object")
        showGroupBox.setVisible(False)
        checkboxShowOilRigOne = QCheckBox("Oil Rig 1")
        checkboxShowOilRigOne.setChecked(True)
        checkboxShowOilRigTwo = QCheckBox("Oil Rig 2")
        checkboxShowOilRigTwo.setChecked(True)
        checkboxShowRefinery = QCheckBox("Refinery")
        showVBox = QVBoxLayout()
        showVBox.addWidget(checkboxShowOilRigOne)
        showVBox.addWidget(checkboxShowOilRigTwo)
        showVBox.addWidget(checkboxShowRefinery)
        showGroupBox.setLayout(showVBox)
        visualsGroupBox = QGroupBox("Visuals")
        visualsGroupBox.setVisible(False)
        checkboxVisualsSeeThrough = QCheckBox("See-Through")
        checkboxHighlightOil = QCheckBox("Highlight Oil")
        checkboxShowShadows = QCheckBox("Shadows")
        checkboxShowShadows.setChecked(True)
        visualVBox = QVBoxLayout(visualsGroupBox)
        visualVBox.addWidget(checkboxVisualsSeeThrough)
        visualVBox.addWidget(checkboxHighlightOil)
        visualVBox.addWidget(checkboxShowShadows)
        labelSelection = QLabel("Selection:")
        labelSelection.setVisible(False)
        labelSelectedItem = QLabel("Nothing")
        labelSelectedItem.setVisible(False)
        # Textured topography heightmap
        enableTexture = QCheckBox("Surface texture")
        enableTexture.setVisible(False)

        label = QLabel(self._surfaceWidget)
        label.setPixmap(highlightPixmap())
        heightMapGroupBox = QGroupBox("Highlight color map")
        colorMapVBox = QVBoxLayout()
        colorMapVBox.addWidget(label)
        heightMapGroupBox.setLayout(colorMapVBox)
        heightMapGroupBox.setVisible(False)
        # Populate vertical layout
        # Common
        vLayout.addWidget(modelGroupBox)
        vLayout.addWidget(selectionGroupBox)
        vLayout.addWidget(axisGroupBox)
        # Sqrt Sin
        vLayout.addWidget(colorGroupBox)
        # Multiseries heightmap
        vLayout.addWidget(showGroupBox)
        vLayout.addWidget(visualsGroupBox)
        vLayout.addWidget(labelSelection)
        vLayout.addWidget(labelSelectedItem)
        # Textured topography
        vLayout.addWidget(heightMapGroupBox)
        vLayout.addWidget(enableTexture)
        # Create the controller
        modifier = SurfaceGraphModifier(surfaceGraph, labelSelectedItem, self)
        # Connect widget controls to controller
        heightMapModelRB.toggled.connect(modifier.enableHeightMapModel)
        sqrtSinModelRB.toggled.connect(modifier.enableSqrtSinModel)
        texturedModelRB.toggled.connect(modifier.enableTopographyModel)
        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)
        # Mode dependent connections
        gradientBtoYPB.pressed.connect(modifier.setBlackToYellowGradient)
        gradientGtoRPB.pressed.connect(modifier.setGreenToRedGradient)
        checkboxShowOilRigOne.toggled.connect(modifier.toggleItemOne)
        checkboxShowOilRigTwo.toggled.connect(modifier.toggleItemTwo)
        checkboxShowRefinery.toggled.connect(modifier.toggleItemThree)
        checkboxVisualsSeeThrough.toggled.connect(modifier.toggleSeeThrough)
        checkboxHighlightOil.toggled.connect(modifier.toggleOilHighlight)
        checkboxShowShadows.toggled.connect(modifier.toggleShadows)
        enableTexture.toggled.connect(modifier.toggleSurfaceTexture)
        # Connections to disable features depending on mode
        sqrtSinModelRB.toggled.connect(colorGroupBox.setVisible)
        heightMapModelRB.toggled.connect(showGroupBox.setVisible)
        heightMapModelRB.toggled.connect(visualsGroupBox.setVisible)
        heightMapModelRB.toggled.connect(labelSelection.setVisible)
        heightMapModelRB.toggled.connect(labelSelectedItem.setVisible)
        texturedModelRB.toggled.connect(enableTexture.setVisible)
        texturedModelRB.toggled.connect(heightMapGroupBox.setVisible)
        modifier.setAxisMinSliderX(axisMinSliderX)
        modifier.setAxisMaxSliderX(axisMaxSliderX)
        modifier.setAxisMinSliderZ(axisMinSliderZ)
        modifier.setAxisMaxSliderZ(axisMaxSliderZ)
        sqrtSinModelRB.setChecked(True)
        modeItemRB.setChecked(True)
        enableTexture.setChecked(True)

    def surfaceWidget(self):
        return self._surfaceWidget
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations

import os
from enum import Enum
from math import sqrt, sin, cos, degrees
from pathlib import Path

from PySide6.QtCore import QObject, QPropertyAnimation, Qt, Slot
from PySide6.QtGui import (QColor, QFont, QImage, QLinearGradient,
                           QQuaternion, QVector2D, QVector3D, QWheelEvent)
from PySide6.QtGraphs import (QCustom3DItem,
                              QCustom3DLabel, QHeightMapSurfaceDataProxy,
                              QValue3DAxis, QSurfaceDataItem,
                              QSurfaceDataProxy, QSurface3DSeries,
                              QtGraphs3D, QGraphsTheme)


from highlightseries import HighlightSeries
from topographicseries import TopographicSeries


class InputState(Enum):
    StateNormal = 0
    StateDraggingX = 1
    StateDraggingZ = 2
    StateDraggingY = 3


SAMPLE_COUNT_X = 150
SAMPLE_COUNT_Z = 150
HEIGHTMAP_GRID_STEP_X = 6
HEIGHTMAP_GRID_STEP_Z = 6
SAMPLE_MIN = -8.0
SAMPLE_MAX = 8.0
SPEED_MODIFIER = 20.0

AREA_WIDTH = 8000.0
AREA_HEIGHT = 8000.0
ASPECT_RATIO = 0.1389
MIN_RANGE = AREA_WIDTH * 0.49


class SurfaceGraphModifier(QObject):

    def __init__(self, surface, label, parent):
        super().__init__(parent)
        self._state = InputState.StateNormal
        self._data_path = Path(__file__).resolve().parent / "data"
        self._graph = surface
        self._textField = label
        self._sqrtSinProxy = None
        self._sqrtSinSeries = None
        self._heightMapProxyOne = None
        self._heightMapProxyTwo = None
        self._heightMapProxyThree = None
        self._heightMapSeriesOne = None
        self._heightMapSeriesTwo = None
        self._heightMapSeriesThree = None

        self._axisMinSliderX = None
        self._axisMaxSliderX = None
        self._axisMinSliderZ = None
        self._axisMaxSliderZ = None
        self._rangeMinX = 0.0
        self._rangeMinZ = 0.0
        self._stepX = 0.0
        self._stepZ = 0.0
        self._heightMapWidth = 0
        self._heightMapHeight = 0

        self._axisXMinValue = 0.0
        self._axisXMaxValue = 0.0
        self._axisXMinRange = 0.0
        self._axisZMinValue = 0.0
        self._axisZMaxValue = 0.0
        self._axisZMinRange = 0.0
        self._areaMinValue = 0.0
        self._areaMaxValue = 0.0

        self._selectionAnimation = None
        self._titleLabel = None
        self._previouslyAnimatedItem = None
        self._previousScaling = {}

        self._topography = None
        self._highlight = None
        self._highlightWidth = 0
        self._highlightHeight = 0

        self._graph.setCameraZoomLevel(85.0)
        self._graph.setCameraPreset(QtGraphs3D.CameraPreset.IsometricRight)
        theme = self._graph.activeTheme()
        theme.setTheme(QGraphsTheme.Theme.MixSeries)
        theme.setLabelBackgroundVisible(False)
        theme.setLabelBorderVisible(False)

        self._x_axis = QValue3DAxis()
        self._y_axis = QValue3DAxis()
        self._z_axis = QValue3DAxis()
        self._graph.setAxisX(self._x_axis)
        self._graph.setAxisY(self._y_axis)
        self._graph.setAxisZ(self._z_axis)

        #
        # Sqrt Sin
        #
        self._sqrtSinProxy = QSurfaceDataProxy()
        self._sqrtSinSeries = QSurface3DSeries(self._sqrtSinProxy)
        self.fillSqrtSinProxy()

        #
        # Multisurface heightmap
        #
        # Create the first surface layer
        heightMapImageOne = QImage(self._data_path / "layer_1.png")
        self._heightMapProxyOne = QHeightMapSurfaceDataProxy(heightMapImageOne)
        self._heightMapSeriesOne = QSurface3DSeries(self._heightMapProxyOne)
        self._heightMapSeriesOne.setItemLabelFormat("(@xLabel, @zLabel): @yLabel")
        self._heightMapProxyOne.setValueRanges(34.0, 40.0, 18.0, 24.0)

        # Create the other 2 surface layers
        heightMapImageTwo = QImage(self._data_path / "layer_2.png")
        self._heightMapProxyTwo = QHeightMapSurfaceDataProxy(heightMapImageTwo)
        self._heightMapSeriesTwo = QSurface3DSeries(self._heightMapProxyTwo)
        self._heightMapSeriesTwo.setItemLabelFormat("(@xLabel, @zLabel): @yLabel")
        self._heightMapProxyTwo.setValueRanges(34.0, 40.0, 18.0, 24.0)

        heightMapImageThree = QImage(self._data_path / "layer_3.png")
        self._heightMapProxyThree = QHeightMapSurfaceDataProxy(heightMapImageThree)
        self._heightMapSeriesThree = QSurface3DSeries(self._heightMapProxyThree)
        self._heightMapSeriesThree.setItemLabelFormat("(@xLabel, @zLabel): @yLabel")
        self._heightMapProxyThree.setValueRanges(34.0, 40.0, 18.0, 24.0)

        # The images are the same size, so it's enough to get the dimensions
        # from one
        self._heightMapWidth = heightMapImageOne.width()
        self._heightMapHeight = heightMapImageOne.height()

        # Set the gradients for multi-surface layers
        grOne = QLinearGradient()
        grOne.setColorAt(0.0, Qt.black)
        grOne.setColorAt(0.38, Qt.darkYellow)
        grOne.setColorAt(0.39, Qt.darkGreen)
        grOne.setColorAt(0.5, Qt.darkGray)
        grOne.setColorAt(1.0, Qt.gray)
        self._heightMapSeriesOne.setBaseGradient(grOne)
        self._heightMapSeriesOne.setColorStyle(QGraphsTheme.ColorStyle.RangeGradient)

        grTwo = QLinearGradient()
        grTwo.setColorAt(0.39, Qt.blue)
        grTwo.setColorAt(0.4, Qt.white)
        self._heightMapSeriesTwo.setBaseGradient(grTwo)
        self._heightMapSeriesTwo.setColorStyle(QGraphsTheme.ColorStyle.RangeGradient)

        grThree = QLinearGradient()
        grThree.setColorAt(0.0, Qt.white)
        grThree.setColorAt(0.05, Qt.black)
        self._heightMapSeriesThree.setBaseGradient(grThree)
        self._heightMapSeriesThree.setColorStyle(QGraphsTheme.ColorStyle.RangeGradient)

        # Custom items and label
        self._graph.selectedElementChanged.connect(self.handleElementSelected)

        self._selectionAnimation = QPropertyAnimation(self)
        self._selectionAnimation.setPropertyName(b"scaling")
        self._selectionAnimation.setDuration(500)
        self._selectionAnimation.setLoopCount(-1)

        titleFont = QFont("Century Gothic", 30)
        titleFont.setBold(True)
        self._titleLabel = QCustom3DLabel("Oil Rigs on Imaginary Sea", titleFont,
                                          QVector3D(0.0, 1.2, 0.0),
                                          QVector3D(1.0, 1.0, 0.0),
                                          QQuaternion())
        self._titleLabel.setPositionAbsolute(True)
        self._titleLabel.setFacingCamera(True)
        self._titleLabel.setBackgroundColor(QColor(0x66cdaa))
        self._graph.addCustomItem(self._titleLabel)
        self._titleLabel.setVisible(False)

        # Make two of the custom object visible
        self.toggleItemOne(True)
        self.toggleItemTwo(True)

        #
        # Topographic map
        #
        self._topography = TopographicSeries()
        file_name = os.fspath(self._data_path / "topography.png")
        self._topography.setTopographyFile(file_name, AREA_WIDTH, AREA_HEIGHT)
        self._topography.setItemLabelFormat("@yLabel m")

        self._highlight = HighlightSeries()
        self._highlight.setTopographicSeries(self._topography)
        self._highlight.setMinHeight(MIN_RANGE * ASPECT_RATIO)
        self._highlight.handleGradientChange(AREA_WIDTH * ASPECT_RATIO)
        self._graph.axisY().maxChanged.connect(self._highlight.handleGradientChange)

        self._graph.wheel.connect(self.onWheel)
        self._graph.dragged.connect(self.handleAxisDragging)

    def fillSqrtSinProxy(self):
        stepX = (SAMPLE_MAX - SAMPLE_MIN) / float(SAMPLE_COUNT_X - 1)
        stepZ = (SAMPLE_MAX - SAMPLE_MIN) / float(SAMPLE_COUNT_Z - 1)

        dataArray = []
        for i in range(0, SAMPLE_COUNT_Z):
            newRow = []
            # Keep values within range bounds, since just adding step can
            # cause minor drift due to the rounding errors.
            z = min(SAMPLE_MAX, (i * stepZ + SAMPLE_MIN))
            for j in range(0, SAMPLE_COUNT_X):
                x = min(SAMPLE_MAX, (j * stepX + SAMPLE_MIN))
                R = sqrt(z * z + x * x) + 0.01
                y = (sin(R) / R + 0.24) * 1.61
                item = QSurfaceDataItem(QVector3D(x, y, z))
                newRow.append(item)
            dataArray.append(newRow)
        self._sqrtSinProxy.resetArray(dataArray)

    @Slot(bool)
    def enableSqrtSinModel(self, enable):
        if enable:
            self._sqrtSinSeries.setDrawMode(QSurface3DSeries.DrawSurfaceAndWireframe)
            self._sqrtSinSeries.setShading(QSurface3DSeries.Shading.Flat)

            self._graph.axisX().setLabelFormat("%.2f")
            self._graph.axisZ().setLabelFormat("%.2f")
            self._graph.axisX().setRange(SAMPLE_MIN, SAMPLE_MAX)
            self._graph.axisY().setRange(0.0, 2.0)
            self._graph.axisZ().setRange(SAMPLE_MIN, SAMPLE_MAX)
            self._graph.axisX().setLabelAutoAngle(30.0)
            self._graph.axisY().setLabelAutoAngle(90.0)
            self._graph.axisZ().setLabelAutoAngle(30.0)

            self._graph.removeSeries(self._heightMapSeriesOne)
            self._graph.removeSeries(self._heightMapSeriesTwo)
            self._graph.removeSeries(self._heightMapSeriesThree)
            self._graph.removeSeries(self._topography)
            self._graph.removeSeries(self._highlight)

            self._graph.addSeries(self._sqrtSinSeries)

            self._titleLabel.setVisible(False)
            self._graph.axisX().setTitleVisible(False)
            self._graph.axisY().setTitleVisible(False)
            self._graph.axisZ().setTitleVisible(False)

            self._graph.axisX().setTitle("")
            self._graph.axisY().setTitle("")
            self._graph.axisZ().setTitle("")

            # Reset range sliders for Sqrt & Sin
            self._rangeMinX = SAMPLE_MIN
            self._rangeMinZ = SAMPLE_MIN
            self._stepX = (SAMPLE_MAX - SAMPLE_MIN) / float(SAMPLE_COUNT_X - 1)
            self._stepZ = (SAMPLE_MAX - SAMPLE_MIN) / float(SAMPLE_COUNT_Z - 1)
            self._axisMinSliderX.setMinimum(0)
            self._axisMinSliderX.setMaximum(SAMPLE_COUNT_X - 2)
            self._axisMinSliderX.setValue(0)
            self._axisMaxSliderX.setMinimum(1)
            self._axisMaxSliderX.setMaximum(SAMPLE_COUNT_X - 1)
            self._axisMaxSliderX.setValue(SAMPLE_COUNT_X - 1)
            self._axisMinSliderZ.setMinimum(0)
            self._axisMinSliderZ.setMaximum(SAMPLE_COUNT_Z - 2)
            self._axisMinSliderZ.setValue(0)
            self._axisMaxSliderZ.setMinimum(1)
            self._axisMaxSliderZ.setMaximum(SAMPLE_COUNT_Z - 1)
            self._axisMaxSliderZ.setValue(SAMPLE_COUNT_Z - 1)

            self._graph.setZoomEnabled(True)

    @Slot(bool)
    def enableHeightMapModel(self, enable):
        if enable:
            self._heightMapSeriesOne.setDrawMode(QSurface3DSeries.DrawSurface)
            self._heightMapSeriesOne.setShading(QSurface3DSeries.Shading.Flat)
            self._heightMapSeriesTwo.setDrawMode(QSurface3DSeries.DrawSurface)
            self._heightMapSeriesTwo.setShading(QSurface3DSeries.Shading.Flat)
            self._heightMapSeriesThree.setDrawMode(QSurface3DSeries.DrawSurface)
            self._heightMapSeriesThree.setShading(QSurface3DSeries.Shading.Flat)

            self._graph.axisX().setLabelFormat("%.1f N")
            self._graph.axisZ().setLabelFormat("%.1f E")
            self._graph.axisX().setRange(34.0, 40.0)
            self._graph.axisY().setAutoAdjustRange(True)
            self._graph.axisZ().setRange(18.0, 24.0)

            self._graph.axisX().setTitle("Latitude")
            self._graph.axisY().setTitle("Height")
            self._graph.axisZ().setTitle("Longitude")

            self._graph.removeSeries(self._sqrtSinSeries)
            self._graph.removeSeries(self._topography)
            self._graph.removeSeries(self._highlight)
            self._graph.addSeries(self._heightMapSeriesOne)
            self._graph.addSeries(self._heightMapSeriesTwo)
            self._graph.addSeries(self._heightMapSeriesThree)

            self._titleLabel.setVisible(True)
            self._graph.axisX().setTitleVisible(True)
            self._graph.axisY().setTitleVisible(True)
            self._graph.axisZ().setTitleVisible(True)

            # Reset range sliders for height map
            mapGridCountX = self._heightMapWidth / HEIGHTMAP_GRID_STEP_X
            mapGridCountZ = self._heightMapHeight / HEIGHTMAP_GRID_STEP_Z
            self._rangeMinX = 34.0
            self._rangeMinZ = 18.0
            self._stepX = 6.0 / float(mapGridCountX - 1)
            self._stepZ = 6.0 / float(mapGridCountZ - 1)
            self._axisMinSliderX.setMinimum(0)
            self._axisMinSliderX.setMaximum(mapGridCountX - 2)
            self._axisMinSliderX.setValue(0)
            self._axisMaxSliderX.setMinimum(1)
            self._axisMaxSliderX.setMaximum(mapGridCountX - 1)
            self._axisMaxSliderX.setValue(mapGridCountX - 1)
            self._axisMinSliderZ.setMinimum(0)
            self._axisMinSliderZ.setMaximum(mapGridCountZ - 2)
            self._axisMinSliderZ.setValue(0)
            self._axisMaxSliderZ.setMinimum(1)
            self._axisMaxSliderZ.setMaximum(mapGridCountZ - 1)
            self._axisMaxSliderZ.setValue(mapGridCountZ - 1)

            self._graph.wheel.disconnect(self.onWheel)
            self._graph.dragged.disconnect(self.handleAxisDragging)
            self._graph.setDefaultInputHandler()
            self._graph.setZoomEnabled(True)

    @Slot(bool)
    def enableTopographyModel(self, enable):
        if enable:
            self._graph.axisX().setLabelFormat("%i")
            self._graph.axisZ().setLabelFormat("%i")
            self._graph.axisX().setRange(0.0, AREA_WIDTH)
            self._graph.axisY().setRange(100.0, AREA_WIDTH * ASPECT_RATIO)
            self._graph.axisZ().setRange(0.0, AREA_HEIGHT)
            self._graph.axisX().setLabelAutoAngle(30.0)
            self._graph.axisY().setLabelAutoAngle(90.0)
            self._graph.axisZ().setLabelAutoAngle(30.0)

            self._graph.removeSeries(self._heightMapSeriesOne)
            self._graph.removeSeries(self._heightMapSeriesTwo)
            self._graph.removeSeries(self._heightMapSeriesThree)
            self._graph.addSeries(self._topography)
            self._graph.addSeries(self._highlight)

            self._titleLabel.setVisible(False)
            self._graph.axisX().setTitleVisible(False)
            self._graph.axisY().setTitleVisible(False)
            self._graph.axisZ().setTitleVisible(False)

            self._graph.axisX().setTitle("")
            self._graph.axisY().setTitle("")
            self._graph.axisZ().setTitle("")

            # Reset range sliders for topography map
            self._rangeMinX = 0.0
            self._rangeMinZ = 0.0
            self._stepX = 1.0
            self._stepZ = 1.0
            self._axisMinSliderX.setMinimum(0)
            self._axisMinSliderX.setMaximum(AREA_WIDTH - 200)
            self._axisMinSliderX.setValue(0)
            self._axisMaxSliderX.setMinimum(200)
            self._axisMaxSliderX.setMaximum(AREA_WIDTH)
            self._axisMaxSliderX.setValue(AREA_WIDTH)
            self._axisMinSliderZ.setMinimum(0)
            self._axisMinSliderZ.setMaximum(AREA_HEIGHT - 200)
            self._axisMinSliderZ.setValue(0)
            self._axisMaxSliderZ.setMinimum(200)
            self._axisMaxSliderZ.setMaximum(AREA_HEIGHT)
            self._axisMaxSliderZ.setValue(AREA_HEIGHT)

            self._areaMinValue = 0
            self._areaMaxValue = AREA_WIDTH
            self._axisXMinValue = self._areaMinValue
            self._axisXMaxValue = self._areaMaxValue
            self._axisZMinValue = self._areaMinValue
            self._axisZMaxValue = self._areaMaxValue
            self._axisXMinRange = MIN_RANGE
            self._axisZMinRange = MIN_RANGE

            self._graph.wheel.connect(self.onWheel)
            self._graph.dragged.connect(self.handleAxisDragging)
            self._graph.setZoomEnabled(False)

    def adjustXMin(self, min):
        minX = self._stepX * float(min) + self._rangeMinX

        max = self._axisMaxSliderX.value()
        if min >= max:
            max = min + 1
            self._axisMaxSliderX.setValue(max)

        maxX = self._stepX * max + self._rangeMinX

        self.setAxisXRange(minX, maxX)

    def adjustXMax(self, max):
        maxX = self._stepX * float(max) + self._rangeMinX

        min = self._axisMinSliderX.value()
        if max <= min:
            min = max - 1
            self._axisMinSliderX.setValue(min)

        minX = self._stepX * min + self._rangeMinX

        self.setAxisXRange(minX, maxX)

    def adjustZMin(self, min):
        minZ = self._stepZ * float(min) + self._rangeMinZ

        max = self._axisMaxSliderZ.value()
        if min >= max:
            max = min + 1
            self._axisMaxSliderZ.setValue(max)

        maxZ = self._stepZ * max + self._rangeMinZ

        self.setAxisZRange(minZ, maxZ)

    def adjustZMax(self, max):
        maxX = self._stepZ * float(max) + self._rangeMinZ

        min = self._axisMinSliderZ.value()
        if max <= min:
            min = max - 1
            self._axisMinSliderZ.setValue(min)

        minX = self._stepZ * min + self._rangeMinZ

        self.setAxisZRange(minX, maxX)

    def setAxisXRange(self, min, max):
        self._graph.axisX().setRange(min, max)

    def setAxisZRange(self, min, max):
        self._graph.axisZ().setRange(min, max)

    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)

        self._sqrtSinSeries.setBaseGradient(gr)
        self._sqrtSinSeries.setColorStyle(QGraphsTheme.ColorStyle.RangeGradient)

    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)

        self._sqrtSinSeries.setBaseGradient(gr)
        self._sqrtSinSeries.setColorStyle(QGraphsTheme.ColorStyle.RangeGradient)

    @Slot(bool)
    def toggleItemOne(self, show):
        positionOne = QVector3D(39.0, 77.0, 19.2)
        positionOnePipe = QVector3D(39.0, 45.0, 19.2)
        positionOneLabel = QVector3D(39.0, 107.0, 19.2)
        if show:
            color = QImage(2, 2, QImage.Format_RGB32)
            color.fill(Qt.red)
            file_name = os.fspath(self._data_path / "oilrig.mesh")
            item = QCustom3DItem(file_name, positionOne,
                                 QVector3D(0.025, 0.025, 0.025),
                                 QQuaternion.fromAxisAndAngle(0.0, 1.0, 0.0, 45.0),
                                 color)
            self._graph.addCustomItem(item)
            file_name = os.fspath(self._data_path / "pipe.mesh")
            item = QCustom3DItem(file_name, positionOnePipe,
                                 QVector3D(0.005, 0.5, 0.005), QQuaternion(),
                                 color)
            item.setShadowCasting(False)
            self._graph.addCustomItem(item)

            label = QCustom3DLabel()
            label.setText("Oil Rig One")
            label.setPosition(positionOneLabel)
            label.setScaling(QVector3D(1.0, 1.0, 1.0))
            self._graph.addCustomItem(label)
        else:
            self.resetSelection()
            self._graph.removeCustomItemAt(positionOne)
            self._graph.removeCustomItemAt(positionOnePipe)
            self._graph.removeCustomItemAt(positionOneLabel)

    @Slot(bool)
    def toggleItemTwo(self, show):
        positionTwo = QVector3D(34.5, 77.0, 23.4)
        positionTwoPipe = QVector3D(34.5, 45.0, 23.4)
        positionTwoLabel = QVector3D(34.5, 107.0, 23.4)
        if show:
            color = QImage(2, 2, QImage.Format_RGB32)
            color.fill(Qt.red)
            item = QCustom3DItem()
            file_name = os.fspath(self._data_path / "oilrig.mesh")
            item.setMeshFile(file_name)
            item.setPosition(positionTwo)
            item.setScaling(QVector3D(0.025, 0.025, 0.025))
            item.setRotation(QQuaternion.fromAxisAndAngle(0.0, 1.0, 0.0, 25.0))
            item.setTextureImage(color)
            self._graph.addCustomItem(item)
            file_name = os.fspath(self._data_path / "pipe.mesh")
            item = QCustom3DItem(file_name, positionTwoPipe,
                                 QVector3D(0.005, 0.5, 0.005), QQuaternion(),
                                 color)
            item.setShadowCasting(False)
            self._graph.addCustomItem(item)

            label = QCustom3DLabel()
            label.setText("Oil Rig Two")
            label.setPosition(positionTwoLabel)
            label.setScaling(QVector3D(1.0, 1.0, 1.0))
            self._graph.addCustomItem(label)
        else:
            self.resetSelection()
            self._graph.removeCustomItemAt(positionTwo)
            self._graph.removeCustomItemAt(positionTwoPipe)
            self._graph.removeCustomItemAt(positionTwoLabel)

    @Slot(bool)
    def toggleItemThree(self, show):
        positionThree = QVector3D(34.5, 86.0, 19.1)
        positionThreeLabel = QVector3D(34.5, 116.0, 19.1)
        if show:
            color = QImage(2, 2, QImage.Format_RGB32)
            color.fill(Qt.darkMagenta)
            item = QCustom3DItem()
            file_name = os.fspath(self._data_path / "refinery.mesh")
            item.setMeshFile(file_name)
            item.setPosition(positionThree)
            item.setScaling(QVector3D(0.04, 0.04, 0.04))
            item.setRotation(QQuaternion.fromAxisAndAngle(0.0, 1.0, 0.0, 75.0))
            item.setTextureImage(color)
            self._graph.addCustomItem(item)

            label = QCustom3DLabel()
            label.setText("Refinery")
            label.setPosition(positionThreeLabel)
            label.setScaling(QVector3D(1.0, 1.0, 1.0))
            self._graph.addCustomItem(label)
        else:
            self.resetSelection()
            self._graph.removeCustomItemAt(positionThree)
            self._graph.removeCustomItemAt(positionThreeLabel)

    @Slot(bool)
    def toggleSeeThrough(self, seethrough):
        s0 = self._graph.seriesList()[0]
        s1 = self._graph.seriesList()[1]
        if seethrough:
            s0.setDrawMode(QSurface3DSeries.DrawWireframe)
            s1.setDrawMode(QSurface3DSeries.DrawWireframe)
        else:
            s0.setDrawMode(QSurface3DSeries.DrawSurface)
            s1.setDrawMode(QSurface3DSeries.DrawSurface)

    @Slot(bool)
    def toggleOilHighlight(self, highlight):
        s2 = self._graph.seriesList()[2]
        if highlight:
            grThree = QLinearGradient()
            grThree.setColorAt(0.0, Qt.black)
            grThree.setColorAt(0.05, Qt.red)
            s2.setBaseGradient(grThree)
        else:
            grThree = QLinearGradient()
            grThree.setColorAt(0.0, Qt.white)
            grThree.setColorAt(0.05, Qt.black)
            s2.setBaseGradient(grThree)

    @Slot(bool)
    def toggleShadows(self, shadows):
        sq = (QtGraphs3D.ShadowQuality.Medium
              if shadows else QtGraphs3D.ShadowQuality.None_)
        self._graph.setShadowQuality(sq)

    @Slot(bool)
    def toggleSurfaceTexture(self, enable):
        if enable:
            file_name = os.fspath(self._data_path / "maptexture.jpg")
            self._topography.setTextureFile(file_name)
        else:
            self._topography.setTextureFile("")

    def handleElementSelected(self, type):
        self.resetSelection()
        if type == QtGraphs3D.ElementType.CustomItem:
            item = self._graph.selectedCustomItem()
            text = ""
            if isinstance(item, QCustom3DItem):
                text += "Custom label: "
            else:
                file = item.meshFile().split("/")[-1]
                text += f"{file}: "

            text += str(self._graph.selectedCustomItemIndex())
            self._textField.setText(text)
            self._previouslyAnimatedItem = item
            self._previousScaling = item.scaling()
            self._selectionAnimation.setTargetObject(item)
            self._selectionAnimation.setStartValue(item.scaling())
            self._selectionAnimation.setEndValue(item.scaling() * 1.5)
            self._selectionAnimation.start()
        elif type == QtGraphs3D.ElementType.Series:
            text = "Surface ("
            series = self._graph.selectedSeries()
            if series:
                point = series.selectedPoint()
                text += f"{point.x()}, {point.y()}"
            text += ")"
            self._textField.setText(text)
        elif (type.value > QtGraphs3D.ElementType.Series.value
              and type.value < QtGraphs3D.ElementType.CustomItem.value):
            index = self._graph.selectedLabelIndex()
            text = ""
            if type == QtGraphs3D.ElementType.AxisXLabel:
                text += "Axis X label: "
                self._state = InputState.StateDraggingX
            elif type == QtGraphs3D.ElementType.AxisYLabel:
                text += "Axis Y label: "
                self._state = InputState.StateDraggingY
            else:
                text += "Axis Z label: "
                self._state = InputState.StateDraggingZ
            text += str(index)
            self._textField.setText(text)
        else:
            self._textField.setText("Nothing")

    def resetSelection(self):
        self._selectionAnimation.stop()
        if self._previouslyAnimatedItem:
            self._previouslyAnimatedItem.setScaling(self._previousScaling)
        self._previouslyAnimatedItem = None

    def toggleModeNone(self):
        self._graph.setSelectionMode(QtGraphs3D.SelectionFlag.None_)

    def toggleModeItem(self):
        self._graph.setSelectionMode(QtGraphs3D.SelectionFlag.Item)

    def toggleModeSliceRow(self):
        sm = (QtGraphs3D.SelectionFlag.ItemAndRow
              | QtGraphs3D.SelectionFlag.Slice
              | QtGraphs3D.SelectionFlag.MultiSeries)
        self._graph.setSelectionMode(sm)

    def toggleModeSliceColumn(self):
        sm = (QtGraphs3D.SelectionFlag.ItemAndColumn
              | QtGraphs3D.SelectionFlag.Slice
              | QtGraphs3D.SelectionFlag.MultiSeries)
        self._graph.setSelectionMode(sm)

    def setAxisMinSliderX(self, slider):
        self._axisMinSliderX = slider

    def setAxisMaxSliderX(self, slider):
        self._axisMaxSliderX = slider

    def setAxisMinSliderZ(self, slider):
        self._axisMinSliderZ = slider

    def setAxisMaxSliderZ(self, slider):
        self._axisMaxSliderZ = slider

    def checkConstraints(self):
        if self._axisXMinValue < self._areaMinValue:
            self._axisXMinValue = self._areaMinValue
        if self._axisXMaxValue > self._areaMaxValue:
            self._axisXMaxValue = self._areaMaxValue
        # Don't allow too much zoom in
        range = self._axisXMaxValue - self._axisXMinValue
        if range < self._axisXMinRange:
            adjust = (self._axisXMinRange - range) / 2.0
            self._axisXMinValue -= adjust
            self._axisXMaxValue += adjust

        if self._axisZMinValue < self._areaMinValue:
            self._axisZMinValue = self._areaMinValue
        if self._axisZMaxValue > self._areaMaxValue:
            self._axisZMaxValue = self._areaMaxValue
        # Don't allow too much zoom in
        range = self._axisZMaxValue - self._axisZMinValue
        if range < self._axisZMinRange:
            adjust = (self._axisZMinRange - range) / 2.0
            self._axisZMinValue -= adjust
            self._axisZMaxValue += adjust

    @Slot(QVector2D)
    def handleAxisDragging(self, delta):

        distance = float(0)

        # Get scene orientation from active camera
        xRotation = self._graph.cameraXRotation()

        # Calculate directional drag multipliers based on rotation
        xMulX = cos(degrees(xRotation))
        xMulY = sin(degrees(xRotation))
        zMulX = sin(degrees(xRotation))
        zMulY = cos(degrees(xRotation))

        # Get the drag amount
        move = delta.toPoint()

        # Adjust axes
        if self._state == InputState.StateDraggingX:
            distance = (move.x() * xMulX - move.y() * xMulY) * SPEED_MODIFIER
            self._axisXMinValue -= distance
            self._axisXMaxValue -= distance
            if self._axisXMinValue < self._areaMinValue:
                dist = self._axisXMaxValue - self._axisXMinValue
                self._axisXMinValue = self._areaMinValue
                self._axisXMaxValue = self._axisXMinValue + dist

            if self._axisXMaxValue > self._areaMaxValue:
                dist = self._axisXMaxValue - self._axisXMinValue
                self._axisXMaxValue = self._areaMaxValue
                self._axisXMinValue = self._axisXMaxValue - dist

            self._graph.axisX().setRange(self._axisXMinValue, self._axisXMaxValue)
        elif self._state == InputState.StateDraggingZ:
            distance = (move.x() * zMulX + move.y() * zMulY) * SPEED_MODIFIER
            self._axisZMinValue += distance
            self._axisZMaxValue += distance
            if self._axisZMinValue < self._areaMinValue:
                dist = self._axisZMaxValue - self._axisZMinValue
                self._axisZMinValue = self._areaMinValue
                self._axisZMaxValue = self._axisZMinValue + dist

            if self._axisZMaxValue > self._areaMaxValue:
                dist = self._axisZMaxValue - self._axisZMinValue
                self._axisZMaxValue = self._areaMaxValue
                self._axisZMinValue = self._axisZMaxValue - dist

            self._graph.axisZ().setRange(self._axisZMinValue, self._axisZMaxValue)

    @Slot(QWheelEvent)
    def onWheel(self, event):
        delta = float(event.angleDelta().y())

        self._axisXMinValue += delta
        self._axisXMaxValue -= delta
        self._axisZMinValue += delta
        self._axisZMaxValue -= delta
        self.checkConstraints()

        y = (self._axisXMaxValue - self._axisXMinValue) * ASPECT_RATIO

        self._graph.axisX().setRange(self._axisXMinValue, self._axisXMaxValue)
        self._graph.axisY().setRange(100.0, y)
        self._graph.axisZ().setRange(self._axisZMinValue, self._axisZMaxValue)
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations

from PySide6.QtCore import Qt
from PySide6.QtGui import QImage, QVector3D
from PySide6.QtGraphs import (QSurface3DSeries, QSurfaceDataItem)


# Value used to encode height data as RGB value on PNG file
PACKING_FACTOR = 11983.0


class TopographicSeries(QSurface3DSeries):

    def __init__(self):
        super().__init__()
        self._sampleCountX = 0.0
        self._sampleCountZ = 0.0
        self.setDrawMode(QSurface3DSeries.DrawSurface)
        self.setShading(QSurface3DSeries.Shading.Flat)
        self.setBaseColor(Qt.white)

    def sampleCountX(self):
        return self._sampleCountX

    def sampleCountZ(self):
        return self._sampleCountZ

    def setTopographyFile(self, file, width, height):
        heightMapImage = QImage(file)
        bits = heightMapImage.bits()
        imageHeight = heightMapImage.height()
        imageWidth = heightMapImage.width()
        widthBits = imageWidth * 4
        stepX = width / float(imageWidth)
        stepZ = height / float(imageHeight)

        dataArray = []
        for i in range(0, imageHeight):
            p = i * widthBits
            z = height - float(i) * stepZ
            newRow = []
            for j in range(0, imageWidth):
                aa = bits[p + 0]
                rr = bits[p + 1]
                gg = bits[p + 2]
                color = (gg << 16) + (rr << 8) + aa
                y = float(color) / PACKING_FACTOR
                item = QSurfaceDataItem(QVector3D(float(j) * stepX, y, z))
                newRow.append(item)
                p += 4
            dataArray.append(newRow)

        self.dataProxy().resetArray(dataArray)

        self._sampleCountX = float(imageWidth)
        self._sampleCountZ = float(imageHeight)
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations

from PySide6.QtCore import QObject, Signal


class VariantBarDataMapping(QObject):

    rowIndexChanged = Signal()
    columnIndexChanged = Signal()
    valueIndexChanged = Signal()
    rowCategoriesChanged = Signal()
    columnCategoriesChanged = Signal()
    mappingChanged = Signal()

    def __init__(self, rowIndex, columnIndex, valueIndex,
                 rowCategories=[], columnCategories=[]):
        super().__init__(None)
        self._rowIndex = rowIndex
        self._columnIndex = columnIndex
        self._valueIndex = valueIndex
        self._rowCategories = rowCategories
        self._columnCategories = columnCategories

    def setRowIndex(self, index):
        self._rowIndex = index
        self.mappingChanged.emit()

    def rowIndex(self):
        return self._rowIndex

    def setColumnIndex(self, index):
        self._columnIndex = index
        self.mappingChanged.emit()

    def columnIndex(self):
        return self._columnIndex

    def setValueIndex(self, index):
        self._valueIndex = index
        self.mappingChanged.emit()

    def valueIndex(self):
        return self._valueIndex

    def setRowCategories(self, categories):
        self._rowCategories = categories
        self.mappingChanged.emit()

    def rowCategories(self):
        return self._rowCategories

    def setColumnCategories(self, categories):
        self._columnCategories = categories
        self.mappingChanged.emit()

    def columnCategories(self):
        return self._columnCategories

    def remap(self, rowIndex, columnIndex, valueIndex,
              rowCategories=[], columnCategories=[]):
        self._rowIndex = rowIndex
        self._columnIndex = columnIndex
        self._valueIndex = valueIndex
        self._rowCategories = rowCategories
        self._columnCategories = columnCategories
        self.mappingChanged.emit()
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations

from PySide6.QtCore import Slot
from PySide6.QtGraphs import QBarDataProxy, QBarDataItem


class VariantBarDataProxy(QBarDataProxy):

    def __init__(self):
        super().__init__()
        self._dataSet = None
        self._mapping = None

    def setDataSet(self, newSet):
        if self._dataSet:
            self._dataSet.itemsAdded.disconnect(self.handleItemsAdded)
            self._dataSet.dataCleared.disconnect(self.handleDataCleared)

        self._dataSet = newSet

        if self._dataSet:
            self._dataSet.itemsAdded.connect(self.handleItemsAdded)
            self._dataSet.dataCleared.connect(self.handleDataCleared)
        self.resolveDataSet()

    def dataSet(self):
        return self._dataSet.data()

    # Map key (row, column, value) to value index in data item (VariantItem).
    # Doesn't gain ownership of mapping, but does connect to it to listen for
    # mapping changes. Modifying mapping that is set to proxy will trigger
    # dataset re-resolving.
    def setMapping(self, mapping):
        if self._mapping:
            self._mapping.mappingChanged.disconnect(self.handleMappingChanged)

        self._mapping = mapping

        if self._mapping:
            self._mapping.mappingChanged.connect(self.handleMappingChanged)

        self.resolveDataSet()

    def mapping(self):
        return self._mapping.data()

    @Slot(int, int)
    def handleItemsAdded(self, index, count):
        # Resolve new items
        self.resolveDataSet()

    @Slot()
    def handleDataCleared(self):
        # Data cleared, reset array
        self.resetArray(None)

    @Slot()
    def handleMappingChanged(self):
        self.resolveDataSet()

    # Resolve entire dataset into QBarDataArray.
    def resolveDataSet(self):
        # If we have no data or mapping, or the categories are not defined,
        # simply clear the array
        if (not self._dataSet or not self._mapping
                or not self._mapping.rowCategories()
                or not self._mapping.columnCategories()):
            self.resetArray()
            return

        itemList = self._dataSet.itemList()

        rowIndex = self._mapping.rowIndex()
        columnIndex = self._mapping.columnIndex()
        valueIndex = self._mapping.valueIndex()
        rowList = self._mapping.rowCategories()
        columnList = self._mapping.columnCategories()

        # Sort values into rows and columns
        itemValueMap = {}
        for item in itemList:
            key = str(item[rowIndex])
            v = itemValueMap.get(key)
            if not v:
                v = {}
                itemValueMap[key] = v
            v[str(item[columnIndex])] = float(item[valueIndex])

        # Create a new data array in format the parent class understands
        newProxyArray = []
        for rowKey in rowList:
            newProxyRow = []
            for i in range(0, len(columnList)):
                item = QBarDataItem(itemValueMap[rowKey][columnList[i]])
                newProxyRow.append(item)
            newProxyArray.append(newProxyRow)

        # Finally, reset the data array in the parent class
        self.resetArray(newProxyArray)
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations

from PySide6.QtCore import QObject, Signal


class VariantDataSet(QObject):

    itemsAdded = Signal(int, int)
    dataCleared = Signal()

    def __init__(self):
        super().__init__()
        self._variantData = []

    def clear(self):
        for item in self._variantData:
            item.clear()
            del item

        self._variantData.clear()
        self.dataCleared.emit()

    def addItem(self, item):
        self._variantData.append(item)
        addIndex = len(self._variantData)

        self.itemsAdded.emit(addIndex, 1)
        return addIndex

    def addItems(self, itemList):
        newCount = len(itemList)
        addIndex = len(self._variantData)
        self._variantData.extend(itemList)
        self.itemsAdded.emit(addIndex, newCount)
        return addIndex

    def itemList(self):
        return self._variantData
License information regarding the data obtained from National Land Survey of
Finland http://www.maanmittauslaitos.fi/en
- topographic model from Elevation model 2 m (U4421B, U4421D, U4422A and
  U4422C) 08/2014
- map image extracted from Topographic map raster 1:50 000 (U442) 08/2014

National Land Survey open data licence - version 1.0 - 1 May 2012

1. General information

The National Land Survey of Finland (hereinafter the Licensor), as the holder
of the immaterial rights to the data, has granted on the terms mentioned below
the right to use a copy (hereinafter data or dataset(s)) of the data (or a part
of it).

The Licensee is a natural or legal person who makes use of the data covered by
this licence. The Licensee accepts the terms of this licence by receiving the
dataset(s) covered by the licence.

This Licence agreement does not create a co-operation or business relationship
between the Licensee and the Licensor.

2. Terms of the licence

2.1. Right of use

This licence grants a worldwide, free of charge and irrevocable parallel right
of use to open data. According to the terms of the licence, data received by
the Licensee can be freely:
 - copied, distributed and published,
 - modified and utilised commercially and non-commercially,
 - inserted into other products and
 - used as a part of a software application or service.

2.2. Duties and responsibilities of the Licensee

Through reasonable means suitable to the distribution medium or method which is
used in conjunction with a product containing data or a service utilising data
covered by this licence or while distributing data, the Licensee shall:
 - mention the name of the Licensor, the name of the dataset(s) and the time
   when the National Land Survey has delivered the dataset(s) (e.g.: contains
   data from the National Land Survey of Finland Topographic Database 06/2012)
 - provide a copy of this licence or a link to it, as well as
 - require third parties to provide the same information when granting rights
   to copies of dataset(s) or products and services containing such data and
 - remove the name of the Licensor from the product or service, if required to
   do so by the Licensor.

The terms of this licence do not allow the Licensee to state in conjunction
with the use of dataset(s) that the Licensor supports or recommends such use.

2.3. Duties and responsibilities of the Licensor

The Licensor shall ensure that
 - the Licensor has the right to grant rights to the dataset(s) in accordance
   with this licence.

The data has been licensed "as is" and the Licensor
 - shall not be held responsible for any errors or omissions in the data,
   disclaims any warranty for the validity or up to date status of the data and
   shall be free from liability for direct or consequential damages arising
   from the use of data provided by the Licensor,
 - and is not obligated to ensure the continuous availability of the data, nor
   to announce in advance the interruption or cessation of availability, and
   the Licensor shall be free from liability for direct or consequential
   damages arising from any such interruption or cessation.

3. Jurisdiction

Finnish law shall apply to this licence.

4. Changes to this licence

The Licensor may at any time change the terms of the licence or apply a
different licence to the data. The terms of this licence shall, however, still
apply to such data that has been received prior to the change of the terms of
the licence or the licence itself.
# Rainfall per month from 2010 to 2022 in Northern Finland (Oulu)
# Format: year, month, rainfall
2010,1, 0,
2010,2, 3.4,
2010,3, 52,
2010,4, 33.8,
2010,5, 45.6,
2010,6, 43.8,
2010,7, 104.6,
2010,8, 105.4,
2010,9, 107.2,
2010,10,38.6,
2010,11,17.8,
2010,12,0,
2011,1, 8.2,
2011,2, 1.6,
2011,3, 27.4,
2011,4, 15.8,
2011,5, 57.6,
2011,6, 85.2,
2011,7, 127,
2011,8, 72.2,
2011,9, 82.2,
2011,10,62.4,
2011,11,31.6,
2011,12,53.8,
2012,1, 0,
2012,2, 5,
2012,3, 32.4,
2012,4, 57.6,
2012,5, 71.4,
2012,6, 60.8,
2012,7, 109,
2012,8, 43.6,
2012,9, 79.4,
2012,10,117.2,
2012,11,59,
2012,12,0.2,
2013,1, 28,
2013,2, 19,
2013,3, 0,
2013,4, 37.6,
2013,5, 44.2,
2013,6, 104.8,
2013,7, 84.2,
2013,8, 57.2,
2013,9, 37.2,
2013,10,64.6,
2013,11,77.8,
2013,12,92.8,
2014,1, 23.8,
2014,2, 23.6,
2014,3, 15.4,
2014,4, 13.2,
2014,5, 36.4,
2014,6, 26.4,
2014,7, 95.8,
2014,8, 81.8,
2014,9, 13.8,
2014,10,94.6,
2014,11,44.6,
2014,12,31,
2015,1, 37.4,
2015,2, 21,
2015,3, 42,
2015,4, 8.8,
2015,5, 82.4,
2015,6, 150,
2015,7, 56.8,
2015,8, 67.2,
2015,9, 131.2,
2015,10,38.4,
2015,11,83.4,
2015,12,47.8,
2016,1, 12.4,
2016,2, 34.8,
2016,3, 29,
2016,4, 40.4,
2016,5, 32.4,
2016,6, 80.2,
2016,7, 102.6,
2016,8, 95.6,
2016,9, 40.2,
2016,10,7.8,
2016,11,39.6,
2016,12,8.8,
2017,1, 9.4,
2017,2, 6.6,
2017,3, 29,
2017,4, 46.2,
2017,5, 43.2,
2017,6, 25.2,
2017,7, 72.4,
2017,8, 58.8,
2017,9, 68.8,
2017,10,45.8,
2017,11,36.8,
2017,12,29.6,
2018,1, 19.8,
2018,2, 0.8,
2018,3, 4,
2018,4, 23.2,
2018,5, 13.2,
2018,6, 62.8,
2018,7, 33,
2018,8, 96.6,
2018,9, 72.6,
2018,10,48.8,
2018,11,31.8,
2018,12,12.8,
2019,1, 0.2,
2019,2, 24.8,
2019,3, 32,
2019,4, 8.8,
2019,5, 71.4,
2019,6, 65.8,
2019,7, 17.6,
2019,8, 90,
2019,9, 50,
2019,10,77,
2019,11,27,
2019,12,43.2,
2020,1, 28.8,
2020,2, 45,
2020,3, 18.6,
2020,4, 13,
2020,5, 30.8,
2020,6, 21.4,
2020,7, 163.6,
2020,8, 12,
2020,9, 102.4,
2020,10,133.2,
2020,11,69.8,
2020,12,40.6,
2021,1, 0.4,
2021,2, 21.6,
2021,3, 24,
2021,4, 51.4,
2021,5, 76.4,
2021,6, 29.2,
2021,7, 36.4,
2021,8, 116,
2021,9, 72.4,
2021,10,93.4,
2021,11,21,
2021,12,10.2,
2022,1, 8.6,
2022,2, 6.6,
2022,3, 5.2,
2022,4, 15.2,
2022,5, 37.6,
2022,6, 45,
2022,7, 67.4,
2022,8, 161.6,
2022,9, 22.8,
2022,10,75.2,
2022,11,21.8,
2022,12,0.2
Next
Simple Bar Graph
Previous
Simple HTTP Server Example