summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJukka Jokiniva <jukka.jokiniva@qt.io>2023-03-02 15:45:23 +0200
committerJukka Jokiniva <jukka.jokiniva@qt.io>2023-03-02 15:45:23 +0200
commit8d18b561830efc3d4be91a447b8f87a333ca1650 (patch)
tree3cad3e568a1bbae7805e7a40ce5583ba73eb8fb1
Initial commit
-rw-r--r--.cmake.conf3
-rw-r--r--.gitattributes5
-rw-r--r--.gitignore254
-rw-r--r--.tag1
-rw-r--r--CMakeLists.txt28
-rw-r--r--LICENSES/BSD-3-Clause.txt9
-rw-r--r--LICENSES/GFDL-1.3-no-invariants-only.txt451
-rw-r--r--LICENSES/GPL-3.0-only.txt674
-rw-r--r--LICENSES/LicenseRef-Qt-Commercial.txt8
-rw-r--r--LICENSES/Qt-GPL-exception-1.0.txt22
-rw-r--r--README90
-rw-r--r--coin/module_config.yaml23
-rw-r--r--conanfile.py34
-rw-r--r--dependencies.yaml10
-rw-r--r--examples/CMakeLists.txt8
-rw-r--r--examples/examples.pro2
-rw-r--r--examples/graphs/CMakeLists.txt13
-rw-r--r--examples/graphs/examples.pri14
-rw-r--r--examples/graphs/graphgallery/CMakeLists.txt75
-rw-r--r--examples/graphs/graphgallery/axesinputhandler.cpp117
-rw-r--r--examples/graphs/graphgallery/axesinputhandler.h54
-rw-r--r--examples/graphs/graphgallery/bargraph.cpp325
-rw-r--r--examples/graphs/graphgallery/bargraph.h25
-rw-r--r--examples/graphs/graphgallery/custominputhandler.cpp162
-rw-r--r--examples/graphs/graphgallery/custominputhandler.h76
-rw-r--r--examples/graphs/graphgallery/data/layer_1.pngbin0 -> 34540 bytes
-rw-r--r--examples/graphs/graphgallery/data/layer_2.pngbin0 -> 10553 bytes
-rw-r--r--examples/graphs/graphgallery/data/layer_3.pngbin0 -> 7119 bytes
-rw-r--r--examples/graphs/graphgallery/data/license.txt77
-rw-r--r--examples/graphs/graphgallery/data/maptexture.jpgbin0 -> 352922 bytes
-rw-r--r--examples/graphs/graphgallery/data/oilrig.obj2322
-rw-r--r--examples/graphs/graphgallery/data/pipe.obj330
-rw-r--r--examples/graphs/graphgallery/data/raindata.txt158
-rw-r--r--examples/graphs/graphgallery/data/refinery.obj2330
-rw-r--r--examples/graphs/graphgallery/data/topography.pngbin0 -> 395504 bytes
-rw-r--r--examples/graphs/graphgallery/doc/images/graphgallery-example.pngbin0 -> 375425 bytes
-rw-r--r--examples/graphs/graphgallery/doc/src/graphgallery.qdoc706
-rw-r--r--examples/graphs/graphgallery/graphgallery.pro49
-rw-r--r--examples/graphs/graphgallery/graphgallery.qrc13
-rw-r--r--examples/graphs/graphgallery/graphmodifier.cpp443
-rw-r--r--examples/graphs/graphgallery/graphmodifier.h87
-rw-r--r--examples/graphs/graphgallery/highlightseries.cpp99
-rw-r--r--examples/graphs/graphgallery/highlightseries.h35
-rw-r--r--examples/graphs/graphgallery/main.cpp40
-rw-r--r--examples/graphs/graphgallery/rainfalldata.cpp118
-rw-r--r--examples/graphs/graphgallery/rainfalldata.h43
-rw-r--r--examples/graphs/graphgallery/scatterdatamodifier.cpp201
-rw-r--r--examples/graphs/graphgallery/scatterdatamodifier.h50
-rw-r--r--examples/graphs/graphgallery/scattergraph.cpp139
-rw-r--r--examples/graphs/graphgallery/scattergraph.h25
-rw-r--r--examples/graphs/graphgallery/surfacegraph.cpp326
-rw-r--r--examples/graphs/graphgallery/surfacegraph.h25
-rw-r--r--examples/graphs/graphgallery/surfacegraphmodifier.cpp668
-rw-r--r--examples/graphs/graphgallery/surfacegraphmodifier.h110
-rw-r--r--examples/graphs/graphgallery/topographicseries.cpp54
-rw-r--r--examples/graphs/graphgallery/topographicseries.h26
-rw-r--r--examples/graphs/graphgallery/variantbardatamapping.cpp109
-rw-r--r--examples/graphs/graphgallery/variantbardatamapping.h70
-rw-r--r--examples/graphs/graphgallery/variantbardataproxy.cpp122
-rw-r--r--examples/graphs/graphgallery/variantbardataproxy.h49
-rw-r--r--examples/graphs/graphgallery/variantdataset.cpp45
-rw-r--r--examples/graphs/graphgallery/variantdataset.h41
-rw-r--r--examples/graphs/graphs.pro12
-rw-r--r--examples/graphs/qmlaxishandling/CMakeLists.txt60
-rw-r--r--examples/graphs/qmlaxishandling/customformatter.cpp138
-rw-r--r--examples/graphs/qmlaxishandling/customformatter.h55
-rw-r--r--examples/graphs/qmlaxishandling/doc/images/qmlaxishandling-example.pngbin0 -> 132862 bytes
-rw-r--r--examples/graphs/qmlaxishandling/doc/src/qmlaxishandling.qdoc189
-rw-r--r--examples/graphs/qmlaxishandling/main.cpp42
-rw-r--r--examples/graphs/qmlaxishandling/qml/qmlaxishandling/AxisDragging.qml298
-rw-r--r--examples/graphs/qmlaxishandling/qml/qmlaxishandling/AxisFormatting.qml156
-rw-r--r--examples/graphs/qmlaxishandling/qml/qmlaxishandling/Data.qml35
-rw-r--r--examples/graphs/qmlaxishandling/qml/qmlaxishandling/cube.obj415
-rw-r--r--examples/graphs/qmlaxishandling/qml/qmlaxishandling/cubetexture.pngbin0 -> 3386 bytes
-rw-r--r--examples/graphs/qmlaxishandling/qml/qmlaxishandling/main.qml47
-rw-r--r--examples/graphs/qmlaxishandling/qmlaxishandling.pro16
-rw-r--r--examples/graphs/qmlaxishandling/qmlaxishandling.qrc12
-rw-r--r--examples/graphs/qmlaxishandling/qmldir2
-rw-r--r--examples/graphs/qmlbars/CMakeLists.txt58
-rw-r--r--examples/graphs/qmlbars/doc/images/qmlbars-example.pngbin0 -> 186928 bytes
-rw-r--r--examples/graphs/qmlbars/doc/src/qmlbars.qdoc111
-rw-r--r--examples/graphs/qmlbars/main.cpp32
-rw-r--r--examples/graphs/qmlbars/qml/qmlbars/Axes.qml41
-rw-r--r--examples/graphs/qmlbars/qml/qmlbars/Data.qml118
-rw-r--r--examples/graphs/qmlbars/qml/qmlbars/main.qml482
-rw-r--r--examples/graphs/qmlbars/qmlbars.pro11
-rw-r--r--examples/graphs/qmlbars/qmlbars.qrc7
-rw-r--r--examples/graphs/qmlscatter/CMakeLists.txt53
-rw-r--r--examples/graphs/qmlscatter/doc/images/qmlscatter-example.pngbin0 -> 107603 bytes
-rw-r--r--examples/graphs/qmlscatter/doc/src/qmlscatter.qdoc164
-rw-r--r--examples/graphs/qmlscatter/main.cpp41
-rw-r--r--examples/graphs/qmlscatter/qml/qmlscatter/Data.qml1084
-rw-r--r--examples/graphs/qmlscatter/qml/qmlscatter/main.qml228
-rw-r--r--examples/graphs/qmlscatter/qmlscatter.pro11
-rw-r--r--examples/graphs/qmlscatter/qmlscatter.qrc6
-rw-r--r--examples/graphs/qmlsurfacegallery/CMakeLists.txt60
-rw-r--r--examples/graphs/qmlsurfacegallery/datasource.cpp129
-rw-r--r--examples/graphs/qmlsurfacegallery/datasource.h38
-rw-r--r--examples/graphs/qmlsurfacegallery/doc/images/qmlsurfacegallery-example.pngbin0 -> 172206 bytes
-rw-r--r--examples/graphs/qmlsurfacegallery/doc/src/qmlsurfacegallery.qdoc239
-rw-r--r--examples/graphs/qmlsurfacegallery/main.cpp49
-rw-r--r--examples/graphs/qmlsurfacegallery/qml/qmlsurfacegallery/SpectrogramData.qml1545
-rw-r--r--examples/graphs/qmlsurfacegallery/qml/qmlsurfacegallery/SurfaceHeightMap.qml227
-rw-r--r--examples/graphs/qmlsurfacegallery/qml/qmlsurfacegallery/SurfaceOscilloscope.qml430
-rw-r--r--examples/graphs/qmlsurfacegallery/qml/qmlsurfacegallery/SurfaceSpectrogram.qml272
-rw-r--r--examples/graphs/qmlsurfacegallery/qml/qmlsurfacegallery/heightmap.pngbin0 -> 638731 bytes
-rw-r--r--examples/graphs/qmlsurfacegallery/qml/qmlsurfacegallery/heightmap.readme25
-rw-r--r--examples/graphs/qmlsurfacegallery/qml/qmlsurfacegallery/main.qml57
-rw-r--r--examples/graphs/qmlsurfacegallery/qmlsurfacegallery.pro17
-rw-r--r--examples/graphs/qmlsurfacegallery/qmlsurfacegallery.qrc10
-rw-r--r--examples/graphs/volumetric/CMakeLists.txt56
-rw-r--r--examples/graphs/volumetric/doc/images/volumetric-example.pngbin0 -> 169556 bytes
-rw-r--r--examples/graphs/volumetric/doc/src/volumetric.qdoc116
-rw-r--r--examples/graphs/volumetric/layer_ground.pngbin0 -> 62477 bytes
-rw-r--r--examples/graphs/volumetric/layer_magma.pngbin0 -> 16550 bytes
-rw-r--r--examples/graphs/volumetric/layer_water.pngbin0 -> 10181 bytes
-rw-r--r--examples/graphs/volumetric/main.cpp232
-rw-r--r--examples/graphs/volumetric/volumetric.cpp703
-rw-r--r--examples/graphs/volumetric/volumetric.h95
-rw-r--r--examples/graphs/volumetric/volumetric.pro17
-rw-r--r--examples/graphs/volumetric/volumetric.qrc7
-rw-r--r--src/CMakeLists.txt4
-rw-r--r--src/graphs/CMakeLists.txt642
-rw-r--r--src/graphs/axis/qabstract3daxis.cpp549
-rw-r--r--src/graphs/axis/qabstract3daxis.h107
-rw-r--r--src/graphs/axis/qabstract3daxis_p.h67
-rw-r--r--src/graphs/axis/qcategory3daxis.cpp145
-rw-r--r--src/graphs/axis/qcategory3daxis.h35
-rw-r--r--src/graphs/axis/qcategory3daxis_p.h46
-rw-r--r--src/graphs/axis/qlogvalue3daxisformatter.cpp427
-rw-r--r--src/graphs/axis/qlogvalue3daxisformatter.h57
-rw-r--r--src/graphs/axis/qlogvalue3daxisformatter_p.h56
-rw-r--r--src/graphs/axis/qvalue3daxis.cpp386
-rw-r--r--src/graphs/axis/qvalue3daxis.h75
-rw-r--r--src/graphs/axis/qvalue3daxis_p.h62
-rw-r--r--src/graphs/axis/qvalue3daxisformatter.cpp443
-rw-r--r--src/graphs/axis/qvalue3daxisformatter.h75
-rw-r--r--src/graphs/axis/qvalue3daxisformatter_p.h84
-rw-r--r--src/graphs/configure.cmake7
-rw-r--r--src/graphs/data/abstractitemmodelhandler.cpp214
-rw-r--r--src/graphs/data/abstractitemmodelhandler_p.h70
-rw-r--r--src/graphs/data/baritemmodelhandler.cpp276
-rw-r--r--src/graphs/data/baritemmodelhandler_p.h51
-rw-r--r--src/graphs/data/qabstract3dseries.cpp879
-rw-r--r--src/graphs/data/qabstract3dseries.h147
-rw-r--r--src/graphs/data/qabstract3dseries_p.h149
-rw-r--r--src/graphs/data/qabstractdataproxy.cpp100
-rw-r--r--src/graphs/data/qabstractdataproxy.h48
-rw-r--r--src/graphs/data/qabstractdataproxy_p.h45
-rw-r--r--src/graphs/data/qbar3dseries.cpp453
-rw-r--r--src/graphs/data/qbar3dseries.h60
-rw-r--r--src/graphs/data/qbar3dseries_p.h54
-rw-r--r--src/graphs/data/qbardataitem.cpp116
-rw-r--r--src/graphs/data/qbardataitem.h41
-rw-r--r--src/graphs/data/qbardataitem_p.h31
-rw-r--r--src/graphs/data/qbardataproxy.cpp786
-rw-r--r--src/graphs/data/qbardataproxy.h102
-rw-r--r--src/graphs/data/qbardataproxy_p.h61
-rw-r--r--src/graphs/data/qcustom3ditem.cpp510
-rw-r--r--src/graphs/data/qcustom3ditem.h92
-rw-r--r--src/graphs/data/qcustom3ditem_p.h85
-rw-r--r--src/graphs/data/qcustom3dlabel.cpp357
-rw-r--r--src/graphs/data/qcustom3dlabel.h76
-rw-r--r--src/graphs/data/qcustom3dlabel_p.h58
-rw-r--r--src/graphs/data/qcustom3dvolume.cpp1313
-rw-r--r--src/graphs/data/qcustom3dvolume.h131
-rw-r--r--src/graphs/data/qcustom3dvolume_p.h93
-rw-r--r--src/graphs/data/qheightmapsurfacedataproxy.cpp745
-rw-r--r--src/graphs/data/qheightmapsurfacedataproxy.h80
-rw-r--r--src/graphs/data/qheightmapsurfacedataproxy_p.h60
-rw-r--r--src/graphs/data/qitemmodelbardataproxy.cpp934
-rw-r--r--src/graphs/data/qitemmodelbardataproxy.h153
-rw-r--r--src/graphs/data/qitemmodelbardataproxy_p.h69
-rw-r--r--src/graphs/data/qitemmodelscatterdataproxy.cpp622
-rw-r--r--src/graphs/data/qitemmodelscatterdataproxy.h105
-rw-r--r--src/graphs/data/qitemmodelscatterdataproxy_p.h58
-rw-r--r--src/graphs/data/qitemmodelsurfacedataproxy.cpp1099
-rw-r--r--src/graphs/data/qitemmodelsurfacedataproxy.h170
-rw-r--r--src/graphs/data/qitemmodelsurfacedataproxy_p.h72
-rw-r--r--src/graphs/data/qscatter3dseries.cpp369
-rw-r--r--src/graphs/data/qscatter3dseries.h55
-rw-r--r--src/graphs/data/qscatter3dseries_p.h47
-rw-r--r--src/graphs/data/qscatterdataitem.cpp152
-rw-r--r--src/graphs/data/qscatterdataitem.h48
-rw-r--r--src/graphs/data/qscatterdataitem_p.h31
-rw-r--r--src/graphs/data/qscatterdataproxy.cpp426
-rw-r--r--src/graphs/data/qscatterdataproxy.h72
-rw-r--r--src/graphs/data/qscatterdataproxy_p.h54
-rw-r--r--src/graphs/data/qsurface3dseries.cpp560
-rw-r--r--src/graphs/data/qsurface3dseries.h87
-rw-r--r--src/graphs/data/qsurface3dseries_p.h55
-rw-r--r--src/graphs/data/qsurfacedataitem.cpp126
-rw-r--r--src/graphs/data/qsurfacedataitem.h44
-rw-r--r--src/graphs/data/qsurfacedataitem_p.h31
-rw-r--r--src/graphs/data/qsurfacedataproxy.cpp641
-rw-r--r--src/graphs/data/qsurfacedataproxy.h80
-rw-r--r--src/graphs/data/qsurfacedataproxy_p.h59
-rw-r--r--src/graphs/data/scatteritemmodelhandler.cpp218
-rw-r--r--src/graphs/data/scatteritemmodelhandler_p.h63
-rw-r--r--src/graphs/data/surfaceitemmodelhandler.cpp310
-rw-r--r--src/graphs/data/surfaceitemmodelhandler_p.h54
-rw-r--r--src/graphs/doc/images/q3dbars-minimal.pngbin0 -> 14701 bytes
-rw-r--r--src/graphs/doc/images/q3dscatter-minimal.pngbin0 -> 16467 bytes
-rw-r--r--src/graphs/doc/images/q3dsurface-minimal.pngbin0 -> 20622 bytes
-rw-r--r--src/graphs/doc/qtgraphs.qdocconf51
-rw-r--r--src/graphs/doc/snippets/doc_src_q3dbars_construction.cpp33
-rw-r--r--src/graphs/doc/snippets/doc_src_q3dscatter_construction.cpp29
-rw-r--r--src/graphs/doc/snippets/doc_src_q3dsurface_construction.cpp39
-rw-r--r--src/graphs/doc/snippets/doc_src_q3dtheme.cpp96
-rw-r--r--src/graphs/doc/snippets/doc_src_qmlgraphs.cpp154
-rw-r--r--src/graphs/doc/snippets/doc_src_qtgraphs.cpp113
-rw-r--r--src/graphs/doc/snippets/doc_src_qtgraphs.pro6
-rw-r--r--src/graphs/doc/src/qtgraphs-index.qdoc61
-rw-r--r--src/graphs/doc/src/qtgraphs-overview.qdoc224
-rw-r--r--src/graphs/doc/src/qtgraphs-qml-abstractdeclarative.qdoc402
-rw-r--r--src/graphs/doc/src/qtgraphs-qml-bars3d.qdoc160
-rw-r--r--src/graphs/doc/src/qtgraphs-qml-color.qdoc19
-rw-r--r--src/graphs/doc/src/qtgraphs-qml-colorgradient.qdoc64
-rw-r--r--src/graphs/doc/src/qtgraphs-qml-scatter3d.qdoc82
-rw-r--r--src/graphs/doc/src/qtgraphs-qml-surface3d.qdoc92
-rw-r--r--src/graphs/doc/src/qtgraphs.qdoc250
-rw-r--r--src/graphs/engine/abstract3dcontroller.cpp1290
-rw-r--r--src/graphs/engine/abstract3dcontroller_p.h413
-rw-r--r--src/graphs/engine/axishelper.cpp14
-rw-r--r--src/graphs/engine/axishelper_p.h83
-rw-r--r--src/graphs/engine/bars3dcontroller.cpp629
-rw-r--r--src/graphs/engine/bars3dcontroller_p.h161
-rw-r--r--src/graphs/engine/meshes/arrowFlat.meshbin0 -> 27736 bytes
-rw-r--r--src/graphs/engine/meshes/arrowFlat.obj403
-rw-r--r--src/graphs/engine/meshes/arrowSmooth.meshbin0 -> 15584 bytes
-rw-r--r--src/graphs/engine/meshes/arrowSmooth.obj422
-rw-r--r--src/graphs/engine/meshes/background.meshbin0 -> 1112 bytes
-rw-r--r--src/graphs/engine/meshes/background.obj32
-rw-r--r--src/graphs/engine/meshes/backgroundNoFloor.meshbin0 -> 600 bytes
-rw-r--r--src/graphs/engine/meshes/backgroundNoFloor.obj22
-rw-r--r--src/graphs/engine/meshes/barFilledFlat.meshbin0 -> 6584 bytes
-rw-r--r--src/graphs/engine/meshes/barFilledFlat.obj242
-rw-r--r--src/graphs/engine/meshes/barFilledSmooth.meshbin0 -> 3640 bytes
-rw-r--r--src/graphs/engine/meshes/barFilledSmooth.obj236
-rw-r--r--src/graphs/engine/meshes/barFlat.meshbin0 -> 6080 bytes
-rw-r--r--src/graphs/engine/meshes/barFlat.obj219
-rw-r--r--src/graphs/engine/meshes/barSmooth.meshbin0 -> 3136 bytes
-rw-r--r--src/graphs/engine/meshes/barSmooth.obj214
-rw-r--r--src/graphs/engine/meshes/coneFilledFlat.meshbin0 -> 5304 bytes
-rw-r--r--src/graphs/engine/meshes/coneFilledFlat.obj128
-rw-r--r--src/graphs/engine/meshes/coneFilledSmooth.meshbin0 -> 3344 bytes
-rw-r--r--src/graphs/engine/meshes/coneFilledSmooth.obj128
-rw-r--r--src/graphs/engine/meshes/coneFlat.meshbin0 -> 3968 bytes
-rw-r--r--src/graphs/engine/meshes/coneFlat.obj89
-rw-r--r--src/graphs/engine/meshes/coneSmooth.meshbin0 -> 2008 bytes
-rw-r--r--src/graphs/engine/meshes/coneSmooth.obj90
-rw-r--r--src/graphs/engine/meshes/cubeFilledFlat.meshbin0 -> 1856 bytes
-rw-r--r--src/graphs/engine/meshes/cubeFilledFlat.obj54
-rw-r--r--src/graphs/engine/meshes/cubeFilledSmooth.meshbin0 -> 1856 bytes
-rw-r--r--src/graphs/engine/meshes/cubeFilledSmooth.obj56
-rw-r--r--src/graphs/engine/meshes/cubeFlat.meshbin0 -> 1608 bytes
-rw-r--r--src/graphs/engine/meshes/cubeFlat.obj47
-rw-r--r--src/graphs/engine/meshes/cubeSmooth.meshbin0 -> 1608 bytes
-rw-r--r--src/graphs/engine/meshes/cubeSmooth.obj50
-rw-r--r--src/graphs/engine/meshes/cylinderFilledFlat.meshbin0 -> 12608 bytes
-rw-r--r--src/graphs/engine/meshes/cylinderFilledFlat.obj361
-rw-r--r--src/graphs/engine/meshes/cylinderFilledSmooth.meshbin0 -> 9584 bytes
-rw-r--r--src/graphs/engine/meshes/cylinderFilledSmooth.obj391
-rw-r--r--src/graphs/engine/meshes/cylinderFlat.meshbin0 -> 10456 bytes
-rw-r--r--src/graphs/engine/meshes/cylinderFlat.obj299
-rw-r--r--src/graphs/engine/meshes/cylinderSmooth.meshbin0 -> 7320 bytes
-rw-r--r--src/graphs/engine/meshes/cylinderSmooth.obj330
-rw-r--r--src/graphs/engine/meshes/minimalFlat.meshbin0 -> 1088 bytes
-rw-r--r--src/graphs/engine/meshes/minimalFlat.obj27
-rw-r--r--src/graphs/engine/meshes/minimalSmooth.meshbin0 -> 1088 bytes
-rw-r--r--src/graphs/engine/meshes/minimalSmooth.obj27
-rw-r--r--src/graphs/engine/meshes/plane.meshbin0 -> 616 bytes
-rw-r--r--src/graphs/engine/meshes/plane.obj15
-rw-r--r--src/graphs/engine/meshes/pyramidFilledFlat.meshbin0 -> 1336 bytes
-rw-r--r--src/graphs/engine/meshes/pyramidFilledFlat.obj36
-rw-r--r--src/graphs/engine/meshes/pyramidFilledSmooth.meshbin0 -> 1336 bytes
-rw-r--r--src/graphs/engine/meshes/pyramidFilledSmooth.obj36
-rw-r--r--src/graphs/engine/meshes/pyramidFlat.meshbin0 -> 1328 bytes
-rw-r--r--src/graphs/engine/meshes/pyramidFlat.obj22
-rw-r--r--src/graphs/engine/meshes/pyramidSmooth.meshbin0 -> 696 bytes
-rw-r--r--src/graphs/engine/meshes/pyramidSmooth.obj23
-rw-r--r--src/graphs/engine/meshes/sphere.meshbin0 -> 62352 bytes
-rw-r--r--src/graphs/engine/meshes/sphere.obj1301
-rw-r--r--src/graphs/engine/meshes/sphereSmooth.meshbin0 -> 23432 bytes
-rw-r--r--src/graphs/engine/meshes/sphereSmooth.obj1232
-rw-r--r--src/graphs/engine/q3dbars.cpp460
-rw-r--r--src/graphs/engine/q3dbars.h96
-rw-r--r--src/graphs/engine/q3dcamera.cpp911
-rw-r--r--src/graphs/engine/q3dcamera.h117
-rw-r--r--src/graphs/engine/q3dcamera_p.h99
-rw-r--r--src/graphs/engine/q3dlight.cpp95
-rw-r--r--src/graphs/engine/q3dlight.h39
-rw-r--r--src/graphs/engine/q3dlight_p.h45
-rw-r--r--src/graphs/engine/q3dobject.cpp130
-rw-r--r--src/graphs/engine/q3dobject.h52
-rw-r--r--src/graphs/engine/q3dobject_p.h38
-rw-r--r--src/graphs/engine/q3dscatter.cpp267
-rw-r--r--src/graphs/engine/q3dscatter.h57
-rw-r--r--src/graphs/engine/q3dscene.cpp807
-rw-r--r--src/graphs/engine/q3dscene.h97
-rw-r--r--src/graphs/engine/q3dscene_p.h112
-rw-r--r--src/graphs/engine/q3dsurface.cpp312
-rw-r--r--src/graphs/engine/q3dsurface.h62
-rw-r--r--src/graphs/engine/qabstract3dgraph.cpp634
-rw-r--r--src/graphs/engine/qabstract3dgraph.h108
-rw-r--r--src/graphs/engine/scatter3dcontroller.cpp394
-rw-r--r--src/graphs/engine/scatter3dcontroller_p.h132
-rw-r--r--src/graphs/engine/scatterinstancing.cpp74
-rw-r--r--src/graphs/engine/scatterinstancing_p.h55
-rw-r--r--src/graphs/engine/shaders/3dsliceframes.frag15
-rw-r--r--src/graphs/engine/shaders/colorOnY.frag39
-rw-r--r--src/graphs/engine/shaders/colorOnY_ES2.frag40
-rw-r--r--src/graphs/engine/shaders/default.frag38
-rw-r--r--src/graphs/engine/shaders/default.vert28
-rw-r--r--src/graphs/engine/shaders/defaultNoMatrices.vert26
-rw-r--r--src/graphs/engine/shaders/default_ES2.frag39
-rw-r--r--src/graphs/engine/shaders/depth.frag3
-rw-r--r--src/graphs/engine/shaders/depth.vert8
-rw-r--r--src/graphs/engine/shaders/label.frag8
-rw-r--r--src/graphs/engine/shaders/label.vert11
-rw-r--r--src/graphs/engine/shaders/plainColor.frag6
-rw-r--r--src/graphs/engine/shaders/plainColor.vert7
-rw-r--r--src/graphs/engine/shaders/point_ES2.vert8
-rw-r--r--src/graphs/engine/shaders/point_ES2_UV.vert12
-rw-r--r--src/graphs/engine/shaders/position.vert10
-rw-r--r--src/graphs/engine/shaders/positionmap.frag11
-rw-r--r--src/graphs/engine/shaders/shadow.frag67
-rw-r--r--src/graphs/engine/shaders/shadow.vert37
-rw-r--r--src/graphs/engine/shaders/shadowNoMatrices.vert36
-rw-r--r--src/graphs/engine/shaders/shadowNoTex.frag66
-rw-r--r--src/graphs/engine/shaders/shadowNoTexColorOnY.frag70
-rw-r--r--src/graphs/engine/shaders/surface.frag39
-rw-r--r--src/graphs/engine/shaders/surfaceFlat.frag41
-rw-r--r--src/graphs/engine/shaders/surfaceFlat.vert32
-rw-r--r--src/graphs/engine/shaders/surfaceShadowFlat.frag74
-rw-r--r--src/graphs/engine/shaders/surfaceShadowFlat.vert39
-rw-r--r--src/graphs/engine/shaders/surfaceShadowNoTex.frag72
-rw-r--r--src/graphs/engine/shaders/surfaceTexturedFlat.frag38
-rw-r--r--src/graphs/engine/shaders/surfaceTexturedShadow.frag67
-rw-r--r--src/graphs/engine/shaders/surfaceTexturedShadowFlat.frag72
-rw-r--r--src/graphs/engine/shaders/surface_ES2.frag42
-rw-r--r--src/graphs/engine/shaders/texture.frag37
-rw-r--r--src/graphs/engine/shaders/texture.vert28
-rw-r--r--src/graphs/engine/shaders/texture3d.frag155
-rw-r--r--src/graphs/engine/shaders/texture3d.vert36
-rw-r--r--src/graphs/engine/shaders/texture3dlowdef.frag109
-rw-r--r--src/graphs/engine/shaders/texture3dslice.frag155
-rw-r--r--src/graphs/engine/shaders/texture_ES2.frag40
-rw-r--r--src/graphs/engine/surface3dcontroller.cpp511
-rw-r--r--src/graphs/engine/surface3dcontroller_p.h134
-rw-r--r--src/graphs/engine/surfaceselectioninstancing.cpp47
-rw-r--r--src/graphs/engine/surfaceselectioninstancing_p.h50
-rw-r--r--src/graphs/global/graphsglobal_p.h62
-rw-r--r--src/graphs/global/qgraphsglobal.h24
-rw-r--r--src/graphs/input/q3dinputhandler.cpp403
-rw-r--r--src/graphs/input/q3dinputhandler.h56
-rw-r--r--src/graphs/input/q3dinputhandler_p.h56
-rw-r--r--src/graphs/input/qabstract3dinputhandler.cpp234
-rw-r--r--src/graphs/input/qabstract3dinputhandler.h80
-rw-r--r--src/graphs/input/qabstract3dinputhandler_p.h56
-rw-r--r--src/graphs/input/qtouch3dinputhandler.cpp253
-rw-r--r--src/graphs/input/qtouch3dinputhandler.h32
-rw-r--r--src/graphs/input/qtouch3dinputhandler_p.h50
-rw-r--r--src/graphs/qml/colorgradient.cpp62
-rw-r--r--src/graphs/qml/colorgradient_p.h79
-rw-r--r--src/graphs/qml/declarativecolor.cpp26
-rw-r--r--src/graphs/qml/declarativecolor_p.h46
-rw-r--r--src/graphs/qml/declarativescene.cpp34
-rw-r--r--src/graphs/qml/declarativescene_p.h50
-rw-r--r--src/graphs/qml/declarativeseries.cpp435
-rw-r--r--src/graphs/qml/declarativeseries_p.h204
-rw-r--r--src/graphs/qml/declarativetheme.cpp386
-rw-r--r--src/graphs/qml/declarativetheme_p.h117
-rw-r--r--src/graphs/qml/designer/Bars3DSpecifics.qml387
-rw-r--r--src/graphs/qml/designer/Scatter3DSpecifics.qml183
-rw-r--r--src/graphs/qml/designer/Surface3DSpecifics.qml324
-rw-r--r--src/graphs/qml/designer/default/Bars3D.qml23
-rw-r--r--src/graphs/qml/designer/default/Scatter3D.qml23
-rw-r--r--src/graphs/qml/designer/default/Surface3D.qml24
-rw-r--r--src/graphs/qml/designer/images/bars3d-icon.pngbin0 -> 1352 bytes
-rw-r--r--src/graphs/qml/designer/images/bars3d-icon16.pngbin0 -> 1232 bytes
-rw-r--r--src/graphs/qml/designer/images/scatter3d-icon.pngbin0 -> 1271 bytes
-rw-r--r--src/graphs/qml/designer/images/scatter3d-icon16.pngbin0 -> 1146 bytes
-rw-r--r--src/graphs/qml/designer/images/surface3d-icon.pngbin0 -> 1413 bytes
-rw-r--r--src/graphs/qml/designer/images/surface3d-icon16.pngbin0 -> 1231 bytes
-rw-r--r--src/graphs/qml/designer/qtgraphs.metainfo44
-rw-r--r--src/graphs/qml/foreigntypes_p.h122
-rw-r--r--src/graphs/qml/qquickgraphsbars.cpp1331
-rw-r--r--src/graphs/qml/qquickgraphsbars_p.h248
-rw-r--r--src/graphs/qml/qquickgraphsitem.cpp2689
-rw-r--r--src/graphs/qml/qquickgraphsitem_p.h471
-rw-r--r--src/graphs/qml/qquickgraphsscatter.cpp987
-rw-r--r--src/graphs/qml/qquickgraphsscatter_p.h192
-rw-r--r--src/graphs/qml/qquickgraphssurface.cpp1345
-rw-r--r--src/graphs/qml/qquickgraphssurface_p.h142
-rw-r--r--src/graphs/qml/quickgraphstexturedata.cpp56
-rw-r--r--src/graphs/qml/quickgraphstexturedata_p.h39
-rw-r--r--src/graphs/qml/resources/AxisLabel.qml42
-rw-r--r--src/graphs/qml/resources/DatapointCube.qml15
-rw-r--r--src/graphs/qml/resources/DatapointSphere.qml15
-rw-r--r--src/graphs/qml/resources/GridLine.qml18
-rw-r--r--src/graphs/qml/resources/ItemLabel.qml42
-rw-r--r--src/graphs/qml/resources/RangeGradientMaterial.qml14
-rw-r--r--src/graphs/qml/resources/RangeGradientMaterialInstancing.qml13
-rw-r--r--src/graphs/qml/resources/SurfaceShadowNoTex.qml12
-rw-r--r--src/graphs/theme/q3dtheme.cpp1178
-rw-r--r--src/graphs/theme/q3dtheme.h176
-rw-r--r--src/graphs/theme/q3dtheme_p.h134
-rw-r--r--src/graphs/theme/thememanager.cpp579
-rw-r--r--src/graphs/theme/thememanager_p.h71
-rw-r--r--src/graphs/utils/qutils.h89
-rw-r--r--src/graphs/utils/utils.cpp366
-rw-r--r--src/graphs/utils/utils_p.h66
-rw-r--r--sync.profile15
-rw-r--r--tests/CMakeLists.txt6
-rw-r--r--tests/auto/CMakeLists.txt9
-rw-r--r--tests/auto/cpptest/CMakeLists.txt29
-rw-r--r--tests/auto/cpptest/common/cpptestutil.h24
-rw-r--r--tests/auto/cpptest/q3daxis-category/CMakeLists.txt10
-rw-r--r--tests/auto/cpptest/q3daxis-category/tst_axis.cpp116
-rw-r--r--tests/auto/cpptest/q3daxis-logvalue/CMakeLists.txt10
-rw-r--r--tests/auto/cpptest/q3daxis-logvalue/tst_axis.cpp85
-rw-r--r--tests/auto/cpptest/q3daxis-value/CMakeLists.txt10
-rw-r--r--tests/auto/cpptest/q3daxis-value/tst_axis.cpp141
-rw-r--r--tests/auto/cpptest/q3dbars-modelproxy/CMakeLists.txt14
-rw-r--r--tests/auto/cpptest/q3dbars-modelproxy/tst_proxy.cpp258
-rw-r--r--tests/auto/cpptest/q3dbars-proxy/CMakeLists.txt10
-rw-r--r--tests/auto/cpptest/q3dbars-proxy/tst_proxy.cpp80
-rw-r--r--tests/auto/cpptest/q3dbars-series/CMakeLists.txt10
-rw-r--r--tests/auto/cpptest/q3dbars-series/tst_series.cpp165
-rw-r--r--tests/auto/cpptest/q3dbars/CMakeLists.txt13
-rw-r--r--tests/auto/cpptest/q3dbars/tst_bars.cpp409
-rw-r--r--tests/auto/cpptest/q3dcustom-label/CMakeLists.txt13
-rw-r--r--tests/auto/cpptest/q3dcustom-label/tst_custom.cpp145
-rw-r--r--tests/auto/cpptest/q3dcustom-volume/CMakeLists.txt10
-rw-r--r--tests/auto/cpptest/q3dcustom-volume/tst_custom.cpp187
-rw-r--r--tests/auto/cpptest/q3dcustom/CMakeLists.txt10
-rw-r--r--tests/auto/cpptest/q3dcustom/tst_custom.cpp111
-rw-r--r--tests/auto/cpptest/q3dinput-touch/CMakeLists.txt10
-rw-r--r--tests/auto/cpptest/q3dinput-touch/tst_input.cpp89
-rw-r--r--tests/auto/cpptest/q3dinput/CMakeLists.txt10
-rw-r--r--tests/auto/cpptest/q3dinput/tst_input.cpp92
-rw-r--r--tests/auto/cpptest/q3dscatter-modelproxy/CMakeLists.txt11
-rw-r--r--tests/auto/cpptest/q3dscatter-modelproxy/tst_proxy.cpp180
-rw-r--r--tests/auto/cpptest/q3dscatter-proxy/CMakeLists.txt10
-rw-r--r--tests/auto/cpptest/q3dscatter-proxy/tst_proxy.cpp74
-rw-r--r--tests/auto/cpptest/q3dscatter-series/CMakeLists.txt10
-rw-r--r--tests/auto/cpptest/q3dscatter-series/tst_series.cpp93
-rw-r--r--tests/auto/cpptest/q3dscatter/CMakeLists.txt13
-rw-r--r--tests/auto/cpptest/q3dscatter/tst_scatter.cpp241
-rw-r--r--tests/auto/cpptest/q3dscene-camera/CMakeLists.txt10
-rw-r--r--tests/auto/cpptest/q3dscene-camera/tst_camera.cpp162
-rw-r--r--tests/auto/cpptest/q3dscene-light/CMakeLists.txt10
-rw-r--r--tests/auto/cpptest/q3dscene-light/tst_light.cpp76
-rw-r--r--tests/auto/cpptest/q3dscene/CMakeLists.txt13
-rw-r--r--tests/auto/cpptest/q3dscene/tst_scene.cpp162
-rw-r--r--tests/auto/cpptest/q3dsurface-heightproxy/CMakeLists.txt21
-rw-r--r--tests/auto/cpptest/q3dsurface-heightproxy/customtexture.jpgbin0 -> 516 bytes
-rw-r--r--tests/auto/cpptest/q3dsurface-heightproxy/tst_proxy.cpp143
-rw-r--r--tests/auto/cpptest/q3dsurface-modelproxy-nan/CMakeLists.txt14
-rw-r--r--tests/auto/cpptest/q3dsurface-modelproxy-nan/tst_proxy.cpp266
-rw-r--r--tests/auto/cpptest/q3dsurface-modelproxy/CMakeLists.txt14
-rw-r--r--tests/auto/cpptest/q3dsurface-modelproxy/tst_proxy.cpp274
-rw-r--r--tests/auto/cpptest/q3dsurface-proxy/CMakeLists.txt10
-rw-r--r--tests/auto/cpptest/q3dsurface-proxy/tst_proxy.cpp91
-rw-r--r--tests/auto/cpptest/q3dsurface-series/CMakeLists.txt10
-rw-r--r--tests/auto/cpptest/q3dsurface-series/tst_series.cpp105
-rw-r--r--tests/auto/cpptest/q3dsurface/CMakeLists.txt13
-rw-r--r--tests/auto/cpptest/q3dsurface/tst_surface.cpp251
-rw-r--r--tests/auto/cpptest/q3dtheme/CMakeLists.txt10
-rw-r--r--tests/auto/cpptest/q3dtheme/tst_theme.cpp199
-rw-r--r--tests/auto/qmltest/CMakeLists.txt28
-rw-r--r--tests/auto/qmltest/axis3d/tst_category.qml120
-rw-r--r--tests/auto/qmltest/axis3d/tst_logvalue.qml76
-rw-r--r--tests/auto/qmltest/axis3d/tst_value.qml139
-rw-r--r--tests/auto/qmltest/bars3d/tst_bars.qml179
-rw-r--r--tests/auto/qmltest/bars3d/tst_barseries.qml223
-rw-r--r--tests/auto/qmltest/bars3d/tst_basic.qml284
-rw-r--r--tests/auto/qmltest/bars3d/tst_proxy.qml239
-rw-r--r--tests/auto/qmltest/custom3d/tst_customitem.qml91
-rw-r--r--tests/auto/qmltest/custom3d/tst_customlabel.qml119
-rw-r--r--tests/auto/qmltest/custom3d/tst_customvolume.qml167
-rw-r--r--tests/auto/qmltest/customitem.obj54
-rw-r--r--tests/auto/qmltest/customtexture.jpgbin0 -> 516 bytes
-rw-r--r--tests/auto/qmltest/input3d/tst_input.qml68
-rw-r--r--tests/auto/qmltest/input3d/tst_touch.qml68
-rw-r--r--tests/auto/qmltest/scatter3d/tst_basic.qml237
-rw-r--r--tests/auto/qmltest/scatter3d/tst_proxy.qml119
-rw-r--r--tests/auto/qmltest/scatter3d/tst_scatter.qml54
-rw-r--r--tests/auto/qmltest/scatter3d/tst_scatterseries.qml218
-rw-r--r--tests/auto/qmltest/scene3d/tst_camera.qml120
-rw-r--r--tests/auto/qmltest/scene3d/tst_light.qml53
-rw-r--r--tests/auto/qmltest/scene3d/tst_scene.qml145
-rw-r--r--tests/auto/qmltest/surface3d/tst_basic.qml245
-rw-r--r--tests/auto/qmltest/surface3d/tst_heightproxy.qml101
-rw-r--r--tests/auto/qmltest/surface3d/tst_proxy.qml249
-rw-r--r--tests/auto/qmltest/surface3d/tst_surface.qml53
-rw-r--r--tests/auto/qmltest/surface3d/tst_surfaceseries.qml219
-rw-r--r--tests/auto/qmltest/theme3d/tst_colorgradient.qml74
-rw-r--r--tests/auto/qmltest/theme3d/tst_theme.qml251
-rw-r--r--tests/auto/qmltest/theme3d/tst_themecolor.qml51
-rw-r--r--tests/auto/qmltest/tst_qmltest.cpp33
-rw-r--r--tests/manual/CMakeLists.txt30
-rw-r--r--tests/manual/barstest/CMakeLists.txt31
-rw-r--r--tests/manual/barstest/barstest.pro10
-rw-r--r--tests/manual/barstest/barstest.qrc6
-rw-r--r--tests/manual/barstest/buttonwrapper.cpp14
-rw-r--r--tests/manual/barstest/buttonwrapper.h22
-rw-r--r--tests/manual/barstest/chart.cpp1786
-rw-r--r--tests/manual/barstest/chart.h174
-rw-r--r--tests/manual/barstest/custominputhandler.cpp39
-rw-r--r--tests/manual/barstest/custominputhandler.h19
-rw-r--r--tests/manual/barstest/main.cpp674
-rw-r--r--tests/manual/barstest/shuttle.obj6349
-rw-r--r--tests/manual/barstest/shuttle.pngbin0 -> 1361 bytes
-rw-r--r--tests/manual/barstest/sliderwrapper.cpp13
-rw-r--r--tests/manual/barstest/sliderwrapper.h23
-rw-r--r--tests/manual/custominput/CMakeLists.txt36
-rw-r--r--tests/manual/custominput/custominput.pro20
-rw-r--r--tests/manual/custominput/custominput.qrc5
-rw-r--r--tests/manual/custominput/custominputhandler.cpp39
-rw-r--r--tests/manual/custominput/custominputhandler.h19
-rw-r--r--tests/manual/custominput/data/data.txt1060
-rw-r--r--tests/manual/custominput/main.cpp77
-rw-r--r--tests/manual/custominput/scatterdatamodifier.cpp158
-rw-r--r--tests/manual/custominput/scatterdatamodifier.h43
-rw-r--r--tests/manual/directional/CMakeLists.txt16
-rw-r--r--tests/manual/directional/directional.pro8
-rw-r--r--tests/manual/directional/main.cpp149
-rw-r--r--tests/manual/directional/scatterdatamodifier.cpp199
-rw-r--r--tests/manual/directional/scatterdatamodifier.h52
-rw-r--r--tests/manual/galaxy/CMakeLists.txt14
-rw-r--r--tests/manual/galaxy/cumulativedistributor.cpp129
-rw-r--r--tests/manual/galaxy/cumulativedistributor.h61
-rw-r--r--tests/manual/galaxy/galaxy.pro24
-rw-r--r--tests/manual/galaxy/galaxydata.cpp463
-rw-r--r--tests/manual/galaxy/galaxydata.h99
-rw-r--r--tests/manual/galaxy/main.cpp145
-rw-r--r--tests/manual/galaxy/star.cpp34
-rw-r--r--tests/manual/galaxy/star.h28
-rw-r--r--tests/manual/itemmodel/CMakeLists.txt17
-rw-r--r--tests/manual/itemmodel/itemmodel.pro15
-rw-r--r--tests/manual/itemmodel/main.cpp279
-rw-r--r--tests/manual/itemmodeltest/CMakeLists.txt13
-rw-r--r--tests/manual/itemmodeltest/itemmodeltest.pro7
-rw-r--r--tests/manual/itemmodeltest/main.cpp300
-rw-r--r--tests/manual/manual.pro32
-rw-r--r--tests/manual/minimalbars/CMakeLists.txt12
-rw-r--r--tests/manual/minimalbars/minimalbars.pro10
-rw-r--r--tests/manual/minimalscatter/CMakeLists.txt12
-rw-r--r--tests/manual/minimalscatter/minimalscatter.pro10
-rw-r--r--tests/manual/minimalsurface/CMakeLists.txt12
-rw-r--r--tests/manual/minimalsurface/minimalsurface.pro10
-rw-r--r--tests/manual/multigraphs/CMakeLists.txt27
-rw-r--r--tests/manual/multigraphs/australia.pngbin0 -> 414991 bytes
-rw-r--r--tests/manual/multigraphs/data.cpp309
-rw-r--r--tests/manual/multigraphs/data.h81
-rw-r--r--tests/manual/multigraphs/main.cpp142
-rw-r--r--tests/manual/multigraphs/multigraphs.pro12
-rw-r--r--tests/manual/multigraphs/multigraphs.qrc5
-rw-r--r--tests/manual/qmlbarsrowcolors/CMakeLists.txt31
-rw-r--r--tests/manual/qmlbarsrowcolors/main.cpp32
-rw-r--r--tests/manual/qmlbarsrowcolors/qml/qmlbarsrowcolors/Axes.qml41
-rw-r--r--tests/manual/qmlbarsrowcolors/qml/qmlbarsrowcolors/Data.qml118
-rw-r--r--tests/manual/qmlbarsrowcolors/qml/qmlbarsrowcolors/main.qml392
-rw-r--r--tests/manual/qmlcustominput/CMakeLists.txt30
-rw-r--r--tests/manual/qmlcustominput/main.cpp34
-rw-r--r--tests/manual/qmlcustominput/qml/qmlcustominput/Data.qml1080
-rw-r--r--tests/manual/qmlcustominput/qml/qmlcustominput/main.qml218
-rw-r--r--tests/manual/qmlcustominput/qmlcustominput.pro12
-rw-r--r--tests/manual/qmlcustominput/qmlcustominput.qrc6
-rw-r--r--tests/manual/qmldynamicdata/CMakeLists.txt23
-rw-r--r--tests/manual/qmldynamicdata/main.cpp33
-rw-r--r--tests/manual/qmldynamicdata/qml/qmldynamicdata/main.qml246
-rw-r--r--tests/manual/qmldynamicdata/qmldynamicdata.pro9
-rw-r--r--tests/manual/qmldynamicdata/qmldynamicdata.qrc5
-rw-r--r--tests/manual/qmlgradient/CMakeLists.txt28
-rw-r--r--tests/manual/qmlgradient/crater.pngbin0 -> 28296 bytes
-rw-r--r--tests/manual/qmlgradient/main.cpp36
-rw-r--r--tests/manual/qmlgradient/qml.qrc5
-rw-r--r--tests/manual/qmlgradient/qml/qmlgradient/main.qml191
-rw-r--r--tests/manual/qmlheightmap/CMakeLists.txt41
-rw-r--r--tests/manual/qmlheightmap/gradientGRAY16.pngbin0 -> 12392 bytes
-rw-r--r--tests/manual/qmlheightmap/gradientGRAY8.pngbin0 -> 26548 bytes
-rw-r--r--tests/manual/qmlheightmap/gradientRGB16.pngbin0 -> 12516 bytes
-rw-r--r--tests/manual/qmlheightmap/gradientRGB8.pngbin0 -> 26548 bytes
-rw-r--r--tests/manual/qmlheightmap/main.cpp36
-rw-r--r--tests/manual/qmlheightmap/qml.qrc5
-rw-r--r--tests/manual/qmlheightmap/qml/qmlheightmap/main.qml181
-rw-r--r--tests/manual/qmllegend/CMakeLists.txt31
-rw-r--r--tests/manual/qmllegend/main.cpp34
-rw-r--r--tests/manual/qmllegend/qml/qmllegend/Data.qml63
-rw-r--r--tests/manual/qmllegend/qml/qmllegend/LegendItem.qml109
-rw-r--r--tests/manual/qmllegend/qml/qmllegend/main.qml215
-rw-r--r--tests/manual/qmllegend/qmllegend.pro13
-rw-r--r--tests/manual/qmllegend/qmllegend.qrc7
-rw-r--r--tests/manual/qmlmultitest/CMakeLists.txt24
-rw-r--r--tests/manual/qmlmultitest/main.cpp32
-rw-r--r--tests/manual/qmlmultitest/qml/qmlmultitest/Data.qml54
-rw-r--r--tests/manual/qmlmultitest/qml/qmlmultitest/main.qml237
-rw-r--r--tests/manual/qmlmultitest/qmlmultitest.pro12
-rw-r--r--tests/manual/qmlmultitest/qmlmultitest.qrc6
-rw-r--r--tests/manual/qmlperf/CMakeLists.txt26
-rw-r--r--tests/manual/qmlperf/datagenerator.cpp68
-rw-r--r--tests/manual/qmlperf/datagenerator.h27
-rw-r--r--tests/manual/qmlperf/main.cpp38
-rw-r--r--tests/manual/qmlperf/qml/qmlperf/main.qml176
-rw-r--r--tests/manual/qmlperf/qmlperf.pro16
-rw-r--r--tests/manual/qmlperf/qmlperf.qrc5
-rw-r--r--tests/manual/qmlsurfacelayers/CMakeLists.txt41
-rw-r--r--tests/manual/qmlsurfacelayers/layer_1.pngbin0 -> 34540 bytes
-rw-r--r--tests/manual/qmlsurfacelayers/layer_2.pngbin0 -> 10563 bytes
-rw-r--r--tests/manual/qmlsurfacelayers/layer_3.pngbin0 -> 13022 bytes
-rw-r--r--tests/manual/qmlsurfacelayers/main.cpp38
-rw-r--r--tests/manual/qmlsurfacelayers/qml/qmlsurfacelayers/main.qml283
-rw-r--r--tests/manual/qmlsurfacelayers/qmlsurfacelayers.pro12
-rw-r--r--tests/manual/qmlsurfacelayers/qmlsurfacelayers.qrc10
-rw-r--r--tests/manual/qmlvolume/CMakeLists.txt26
-rw-r--r--tests/manual/qmlvolume/datasource.cpp73
-rw-r--r--tests/manual/qmlvolume/datasource.h21
-rw-r--r--tests/manual/qmlvolume/main.cpp41
-rw-r--r--tests/manual/qmlvolume/qml/qmlvolume/main.qml140
-rw-r--r--tests/manual/qmlvolume/qmlvolume.pro16
-rw-r--r--tests/manual/qmlvolume/qmlvolume.qrc5
-rw-r--r--tests/manual/rotations/CMakeLists.txt30
-rw-r--r--tests/manual/rotations/main.cpp81
-rw-r--r--tests/manual/rotations/mesh/largesphere.obj1938
-rw-r--r--tests/manual/rotations/mesh/narrowarrow.obj413
-rw-r--r--tests/manual/rotations/rotations.pro17
-rw-r--r--tests/manual/rotations/rotations.qrc6
-rw-r--r--tests/manual/rotations/scatterdatamodifier.cpp165
-rw-r--r--tests/manual/rotations/scatterdatamodifier.h39
-rw-r--r--tests/manual/scattertest/CMakeLists.txt16
-rw-r--r--tests/manual/scattertest/main.cpp479
-rw-r--r--tests/manual/scattertest/scatterchart.cpp1154
-rw-r--r--tests/manual/scattertest/scatterchart.h112
-rw-r--r--tests/manual/scattertest/scattertest.pro8
-rw-r--r--tests/manual/surfacetest/CMakeLists.txt36
-rw-r--r--tests/manual/surfacetest/Heightmap.pngbin0 -> 71764 bytes
-rw-r--r--tests/manual/surfacetest/buttonwrapper.cpp14
-rw-r--r--tests/manual/surfacetest/buttonwrapper.h22
-rw-r--r--tests/manual/surfacetest/checkboxwrapper.cpp14
-rw-r--r--tests/manual/surfacetest/checkboxwrapper.h23
-rw-r--r--tests/manual/surfacetest/graphmodifier.cpp1664
-rw-r--r--tests/manual/surfacetest/graphmodifier.h184
-rw-r--r--tests/manual/surfacetest/main.cpp748
-rw-r--r--tests/manual/surfacetest/mapimage.pngbin0 -> 159540 bytes
-rw-r--r--tests/manual/surfacetest/surfacetest.pro14
-rw-r--r--tests/manual/surfacetest/surfacetest.qrc6
-rw-r--r--tests/manual/tests.pri14
-rw-r--r--tests/manual/volumetrictest/CMakeLists.txt33
-rw-r--r--tests/manual/volumetrictest/cubeFilledFlat.obj54
-rw-r--r--tests/manual/volumetrictest/logo.pngbin0 -> 2205 bytes
-rw-r--r--tests/manual/volumetrictest/logo_no_padding.pngbin0 -> 2278 bytes
-rw-r--r--tests/manual/volumetrictest/main.cpp154
-rw-r--r--tests/manual/volumetrictest/volumetrictest.cpp699
-rw-r--r--tests/manual/volumetrictest/volumetrictest.h62
-rw-r--r--tests/manual/volumetrictest/volumetrictest.pro14
-rw-r--r--tests/manual/volumetrictest/volumetrictest.qrc7
-rw-r--r--tools/blender/arrow.blendbin0 -> 497968 bytes
-rw-r--r--tools/blender/backgroud.blendbin0 -> 463436 bytes
-rw-r--r--tools/blender/backgroudNegatives.blendbin0 -> 462544 bytes
-rw-r--r--tools/blender/backgroudNegativesWall.blendbin0 -> 465188 bytes
-rw-r--r--tools/blender/cone.blendbin0 -> 450400 bytes
-rw-r--r--tools/blender/cone_filled.blendbin0 -> 450476 bytes
-rw-r--r--tools/blender/cube.blendbin0 -> 448248 bytes
-rw-r--r--tools/blender/cube_filled.blendbin0 -> 448340 bytes
-rw-r--r--tools/blender/cylinder.blendbin0 -> 453976 bytes
-rw-r--r--tools/blender/cylinder_filled.blendbin0 -> 454612 bytes
-rw-r--r--tools/blender/narrowArrow.blendbin0 -> 489112 bytes
-rw-r--r--tools/blender/oilrefinery.blendbin0 -> 567896 bytes
-rw-r--r--tools/blender/oilrig.blendbin0 -> 553204 bytes
-rw-r--r--tools/blender/plane.blendbin0 -> 447624 bytes
-rw-r--r--tools/blender/pyramid.blendbin0 -> 447952 bytes
-rw-r--r--tools/blender/pyramid_filled.blendbin0 -> 448100 bytes
-rw-r--r--tools/blender/scatterdot.blendbin0 -> 456132 bytes
-rw-r--r--tools/blender/smoothcube.blendbin0 -> 452616 bytes
-rw-r--r--tools/blender/smoothcube_filled.blendbin0 -> 452884 bytes
-rw-r--r--tools/blender/sphere.blendbin0 -> 493380 bytes
679 files changed, 104832 insertions, 0 deletions
diff --git a/.cmake.conf b/.cmake.conf
new file mode 100644
index 0000000..2792a7d
--- /dev/null
+++ b/.cmake.conf
@@ -0,0 +1,3 @@
+set(QT_REPO_MODULE_VERSION "6.6.0")
+set(QT_REPO_MODULE_PRERELEASE_VERSION_SEGMENT "alpha1")
+set(QT_EXTRA_INTERNAL_TARGET_DEFINES "QT_NO_AS_CONST=1")
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..e4b9352
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,5 @@
+.tag export-subst
+.gitignore export-ignore
+.gitattributes export-ignore
+.commit-template export-ignore
+tools export-ignore
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..019d9df
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,254 @@
+# This file is used to ignore files which are generated in the Qt build system
+# ----------------------------------------------------------------------------
+
+!examples/*/*/*[.]*
+!examples/*/*/README
+examples/*/*/*[.]app
+!examples/declarative/*
+!examples/tutorials/*
+!examples/tutorials/*/*
+!examples/ja_JP/*/*
+!demos/spectrum/*
+demos/spectrum/bin
+!demos/*/*[.]*
+demos/*/*[.]app
+!demos/declarative/*
+config.tests/*/*/*
+!config.tests/*/*/*[.]*
+config.tests/*/*/*[.]app
+
+callgrind.out.*
+pcviewer.cfg
+*~
+*.a
+*.la
+*.core
+*.moc
+*.o
+*.orig
+*.swp
+*.rej
+*.so
+*.pbxuser
+*.mode1
+*.mode1v3
+*_pch.h.cpp
+*_resource.rc
+.#*
+*.*#
+core
+.qmake.cache
+.qmake.vars
+*.prl
+tags
+.DS_Store
+*.debug
+Makefile*
+!qmake/Makefile.win32*
+!qmake/Makefile.unix
+*.prl
+*.app
+*.pro.user*
+*.qmlproject.user*
+*.gcov
+bin/Qt*.dll
+bin/assistant*
+bin/designer*
+bin/dumpcpp*
+bin/idc*
+bin/linguist*
+bin/lrelease*
+bin/lupdate*
+bin/lconvert*
+bin/moc*
+bin/makeqpf*
+bin/pixeltool*
+bin/qmake*
+bin/qdoc3*
+bin/qt3to4*
+bin/qtdemo*
+bin/qttracereplay*
+bin/rcc*
+bin/uic*
+bin/patternist*
+bin/phonon*
+bin/qcollectiongenerator*
+bin/qdbus*
+bin/qhelpconverter*
+bin/qhelpgenerator*
+bin/qtconfig*
+bin/xmlpatterns*
+bin/cetest*
+bin/collectiongenerator
+bin/helpconverter
+bin/helpgenerator
+bin/kmap2qmap*
+bin/qlalr*
+bin/qmlconv*
+bin/qmldebugger*
+bin/qml*
+bin/qttracereplay*
+configure.cache
+config.status
+mkspecs/default
+mkspecs/qconfig.pri
+moc_*.cpp
+qmake/qmake.exe
+qmake/Makefile.bak
+src/corelib/global/qconfig.cpp
+src/corelib/global/qconfig.h
+src/corelib/global/qconfig.h.qmake
+src/tools/uic/qclass_lib_map.h
+ui_*.h
+tests/auto/qprocess/test*/*.exe
+tests/auto/qtcpsocket/stressTest/*.exe
+tests/auto/qprocess/fileWriterProcess/*.exe
+tests/auto/qmake/testdata/quotedfilenames/*.exe
+tests/auto/compilerwarnings/*.exe
+tests/auto/qmake/testdata/quotedfilenames/test.cpp
+tests/auto/qprocess/fileWriterProcess.txt
+.com.apple.timemachine.supported
+tests/auto/qlibrary/libmylib.so*
+tests/auto/qresourceengine/runtime_resource.rcc
+tools/qtestlib/chart/chart*
+tools/qtestlib/updater/updater*
+tools/activeqt/testcon/testcon.tlb
+translations/*.qm
+translations/*_untranslated.ts
+qrc_*.cpp
+*.qmlc
+
+# Test generated files
+QObject.log
+tst_*
+!tst_*.*
+tst_*.log
+tst_*.debug
+tst_*~
+tst_*.qmlc
+
+# xemacs temporary files
+*.flc
+
+# Vim temporary files
+.*.swp
+
+# Visual Studio generated files
+*.ib_pdb_index
+*.idb
+*.ilk
+*.pdb
+*.sln
+*.suo
+*.vcproj
+*vcproj.*.*.user
+*.ncb
+*.vcxproj
+*.vcxproj.filters
+*.vcxproj.user
+
+# MinGW generated files
+*.Debug
+*.Release
+
+# WebKit temp files
+src/3rdparty/webkit/WebCore/mocinclude.tmp
+src/3rdparty/webkit/includes.txt
+src/3rdparty/webkit/includes2.txt
+
+# Symlinks generated by configure
+tools/qvfb/qvfbhdr.h
+tools/qvfb/qlock_p.h
+tools/qvfb/qlock.cpp
+tools/qvfb/qwssignalhandler.cpp
+tools/qvfb/qwssignalhandler_p.h
+.DS_Store
+.pch
+.rcc
+*.app
+config.status
+config.tests/unix/cups/cups
+config.tests/unix/getaddrinfo/getaddrinfo
+config.tests/unix/getifaddrs/getifaddrs
+config.tests/unix/iconv/iconv
+config.tests/unix/ipv6/ipv6
+config.tests/unix/ipv6ifname/ipv6ifname
+config.tests/unix/largefile/largefile
+config.tests/unix/nis/nis
+config.tests/unix/odbc/odbc
+config.tests/unix/openssl/openssl
+config.tests/unix/stl/stl
+config.tests/unix/zlib/zlib
+config.tests/unix/3dnow/3dnow
+config.tests/unix/mmx/mmx
+config.tests/unix/sse/sse
+config.tests/unix/sse2/sse2
+
+
+
+# Directories to ignore
+# ---------------------
+
+debug
+examples/tools/plugandpaint/plugins
+include/*
+include/*/*
+lib/*
+!lib/fonts
+!lib/README
+release
+tmp
+doc-build
+doc/html/*
+tools/qdoc3/doc/html/*
+doc/qch
+doc-build
+.rcc
+.pch
+.metadata
+build/*
+bin/*
+coverage/*
+build-*/*
+
+# runonphone crash dumps
+d_exc_*.txt
+d_exc_*.stk
+
+# Generated by abldfast.bat from devtools.
+.abldsteps.*
+
+# Carbide project files
+# ---------------------
+.project
+.cproject
+.make.cache
+*.d
+
+# OSX build files
+*.xcodeproj
+Info.plist
+
+qtc-debugging-helper
+qtc-qmldump
+qtc-qmldbg
+
+.pc/
+
+# INTEGRITY generated files
+*.gpj
+*.int
+*.ael
+*.dla
+*.dnm
+*.dep
+*.map
+work
+
+.obj/
+
+mkspecs/
+examples/spectrum/*.dll*
+examples/spectrum/*.exe*
+examples/spectrum/*.lib
+examples/spectrum/*.exp \ No newline at end of file
diff --git a/.tag b/.tag
new file mode 100644
index 0000000..6828f88
--- /dev/null
+++ b/.tag
@@ -0,0 +1 @@
+$Format:%H$
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..18f285a
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,28 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+cmake_minimum_required(VERSION 3.16)
+
+include(.cmake.conf)
+project(QtGraphs
+ VERSION "${QT_REPO_MODULE_VERSION}"
+ DESCRIPTION "Qt Graphs Libraries"
+ HOMEPAGE_URL "https://qt.io/"
+ LANGUAGES CXX C
+)
+
+find_package(Qt6 ${PROJECT_VERSION} CONFIG REQUIRED COMPONENTS BuildInternals)
+find_package(Qt6 ${PROJECT_VERSION} CONFIG OPTIONAL_COMPONENTS Core OpenGL Quick Qml Gui Widgets QuickTest QuickWidgets Test Quick3D)
+
+macro(assertTargets)
+ foreach(qtTarget IN ITEMS ${ARGN})
+ if(NOT TARGET Qt::${qtTarget})
+ message(NOTICE "Skipping the build as the condition \"TARGET Qt::${qtTarget}\" is not met.")
+ return()
+ endif()
+ endforeach()
+endmacro()
+
+assertTargets(Gui Widgets OpenGL Quick Qml QuickTest QuickWidgets Test Quick3D)
+
+qt_build_repo()
diff --git a/LICENSES/BSD-3-Clause.txt b/LICENSES/BSD-3-Clause.txt
new file mode 100644
index 0000000..b91bbd8
--- /dev/null
+++ b/LICENSES/BSD-3-Clause.txt
@@ -0,0 +1,9 @@
+Copyright (c) <year> <owner>.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+ 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/LICENSES/GFDL-1.3-no-invariants-only.txt b/LICENSES/GFDL-1.3-no-invariants-only.txt
new file mode 100644
index 0000000..857214d
--- /dev/null
+++ b/LICENSES/GFDL-1.3-no-invariants-only.txt
@@ -0,0 +1,451 @@
+
+ GNU Free Documentation License
+ Version 1.3, 3 November 2008
+
+
+ Copyright (C) 2000, 2001, 2002, 2007, 2008 Free Software Foundation, Inc.
+ <https://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+0. PREAMBLE
+
+The purpose of this License is to make a manual, textbook, or other
+functional and useful document "free" in the sense of freedom: to
+assure everyone the effective freedom to copy and redistribute it,
+with or without modifying it, either commercially or noncommercially.
+Secondarily, this License preserves for the author and publisher a way
+to get credit for their work, while not being considered responsible
+for modifications made by others.
+
+This License is a kind of "copyleft", which means that derivative
+works of the document must themselves be free in the same sense. It
+complements the GNU General Public License, which is a copyleft
+license designed for free software.
+
+We have designed this License in order to use it for manuals for free
+software, because free software needs free documentation: a free
+program should come with manuals providing the same freedoms that the
+software does. But this License is not limited to software manuals;
+it can be used for any textual work, regardless of subject matter or
+whether it is published as a printed book. We recommend this License
+principally for works whose purpose is instruction or reference.
+
+
+1. APPLICABILITY AND DEFINITIONS
+
+This License applies to any manual or other work, in any medium, that
+contains a notice placed by the copyright holder saying it can be
+distributed under the terms of this License. Such a notice grants a
+world-wide, royalty-free license, unlimited in duration, to use that
+work under the conditions stated herein. The "Document", below,
+refers to any such manual or work. Any member of the public is a
+licensee, and is addressed as "you". You accept the license if you
+copy, modify or distribute the work in a way requiring permission
+under copyright law.
+
+A "Modified Version" of the Document means any work containing the
+Document or a portion of it, either copied verbatim, or with
+modifications and/or translated into another language.
+
+A "Secondary Section" is a named appendix or a front-matter section of
+the Document that deals exclusively with the relationship of the
+publishers or authors of the Document to the Document's overall
+subject (or to related matters) and contains nothing that could fall
+directly within that overall subject. (Thus, if the Document is in
+part a textbook of mathematics, a Secondary Section may not explain
+any mathematics.) The relationship could be a matter of historical
+connection with the subject or with related matters, or of legal,
+commercial, philosophical, ethical or political position regarding
+them.
+
+The "Invariant Sections" are certain Secondary Sections whose titles
+are designated, as being those of Invariant Sections, in the notice
+that says that the Document is released under this License. If a
+section does not fit the above definition of Secondary then it is not
+allowed to be designated as Invariant. The Document may contain zero
+Invariant Sections. If the Document does not identify any Invariant
+Sections then there are none.
+
+The "Cover Texts" are certain short passages of text that are listed,
+as Front-Cover Texts or Back-Cover Texts, in the notice that says that
+the Document is released under this License. A Front-Cover Text may
+be at most 5 words, and a Back-Cover Text may be at most 25 words.
+
+A "Transparent" copy of the Document means a machine-readable copy,
+represented in a format whose specification is available to the
+general public, that is suitable for revising the document
+straightforwardly with generic text editors or (for images composed of
+pixels) generic paint programs or (for drawings) some widely available
+drawing editor, and that is suitable for input to text formatters or
+for automatic translation to a variety of formats suitable for input
+to text formatters. A copy made in an otherwise Transparent file
+format whose markup, or absence of markup, has been arranged to thwart
+or discourage subsequent modification by readers is not Transparent.
+An image format is not Transparent if used for any substantial amount
+of text. A copy that is not "Transparent" is called "Opaque".
+
+Examples of suitable formats for Transparent copies include plain
+ASCII without markup, Texinfo input format, LaTeX input format, SGML
+or XML using a publicly available DTD, and standard-conforming simple
+HTML, PostScript or PDF designed for human modification. Examples of
+transparent image formats include PNG, XCF and JPG. Opaque formats
+include proprietary formats that can be read and edited only by
+proprietary word processors, SGML or XML for which the DTD and/or
+processing tools are not generally available, and the
+machine-generated HTML, PostScript or PDF produced by some word
+processors for output purposes only.
+
+The "Title Page" means, for a printed book, the title page itself,
+plus such following pages as are needed to hold, legibly, the material
+this License requires to appear in the title page. For works in
+formats which do not have any title page as such, "Title Page" means
+the text near the most prominent appearance of the work's title,
+preceding the beginning of the body of the text.
+
+The "publisher" means any person or entity that distributes copies of
+the Document to the public.
+
+A section "Entitled XYZ" means a named subunit of the Document whose
+title either is precisely XYZ or contains XYZ in parentheses following
+text that translates XYZ in another language. (Here XYZ stands for a
+specific section name mentioned below, such as "Acknowledgements",
+"Dedications", "Endorsements", or "History".) To "Preserve the Title"
+of such a section when you modify the Document means that it remains a
+section "Entitled XYZ" according to this definition.
+
+The Document may include Warranty Disclaimers next to the notice which
+states that this License applies to the Document. These Warranty
+Disclaimers are considered to be included by reference in this
+License, but only as regards disclaiming warranties: any other
+implication that these Warranty Disclaimers may have is void and has
+no effect on the meaning of this License.
+
+2. VERBATIM COPYING
+
+You may copy and distribute the Document in any medium, either
+commercially or noncommercially, provided that this License, the
+copyright notices, and the license notice saying this License applies
+to the Document are reproduced in all copies, and that you add no
+other conditions whatsoever to those of this License. You may not use
+technical measures to obstruct or control the reading or further
+copying of the copies you make or distribute. However, you may accept
+compensation in exchange for copies. If you distribute a large enough
+number of copies you must also follow the conditions in section 3.
+
+You may also lend copies, under the same conditions stated above, and
+you may publicly display copies.
+
+
+3. COPYING IN QUANTITY
+
+If you publish printed copies (or copies in media that commonly have
+printed covers) of the Document, numbering more than 100, and the
+Document's license notice requires Cover Texts, you must enclose the
+copies in covers that carry, clearly and legibly, all these Cover
+Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on
+the back cover. Both covers must also clearly and legibly identify
+you as the publisher of these copies. The front cover must present
+the full title with all words of the title equally prominent and
+visible. You may add other material on the covers in addition.
+Copying with changes limited to the covers, as long as they preserve
+the title of the Document and satisfy these conditions, can be treated
+as verbatim copying in other respects.
+
+If the required texts for either cover are too voluminous to fit
+legibly, you should put the first ones listed (as many as fit
+reasonably) on the actual cover, and continue the rest onto adjacent
+pages.
+
+If you publish or distribute Opaque copies of the Document numbering
+more than 100, you must either include a machine-readable Transparent
+copy along with each Opaque copy, or state in or with each Opaque copy
+a computer-network location from which the general network-using
+public has access to download using public-standard network protocols
+a complete Transparent copy of the Document, free of added material.
+If you use the latter option, you must take reasonably prudent steps,
+when you begin distribution of Opaque copies in quantity, to ensure
+that this Transparent copy will remain thus accessible at the stated
+location until at least one year after the last time you distribute an
+Opaque copy (directly or through your agents or retailers) of that
+edition to the public.
+
+It is requested, but not required, that you contact the authors of the
+Document well before redistributing any large number of copies, to
+give them a chance to provide you with an updated version of the
+Document.
+
+
+4. MODIFICATIONS
+
+You may copy and distribute a Modified Version of the Document under
+the conditions of sections 2 and 3 above, provided that you release
+the Modified Version under precisely this License, with the Modified
+Version filling the role of the Document, thus licensing distribution
+and modification of the Modified Version to whoever possesses a copy
+of it. In addition, you must do these things in the Modified Version:
+
+A. Use in the Title Page (and on the covers, if any) a title distinct
+ from that of the Document, and from those of previous versions
+ (which should, if there were any, be listed in the History section
+ of the Document). You may use the same title as a previous version
+ if the original publisher of that version gives permission.
+B. List on the Title Page, as authors, one or more persons or entities
+ responsible for authorship of the modifications in the Modified
+ Version, together with at least five of the principal authors of the
+ Document (all of its principal authors, if it has fewer than five),
+ unless they release you from this requirement.
+C. State on the Title page the name of the publisher of the
+ Modified Version, as the publisher.
+D. Preserve all the copyright notices of the Document.
+E. Add an appropriate copyright notice for your modifications
+ adjacent to the other copyright notices.
+F. Include, immediately after the copyright notices, a license notice
+ giving the public permission to use the Modified Version under the
+ terms of this License, in the form shown in the Addendum below.
+G. Preserve in that license notice the full lists of Invariant Sections
+ and required Cover Texts given in the Document's license notice.
+H. Include an unaltered copy of this License.
+I. Preserve the section Entitled "History", Preserve its Title, and add
+ to it an item stating at least the title, year, new authors, and
+ publisher of the Modified Version as given on the Title Page. If
+ there is no section Entitled "History" in the Document, create one
+ stating the title, year, authors, and publisher of the Document as
+ given on its Title Page, then add an item describing the Modified
+ Version as stated in the previous sentence.
+J. Preserve the network location, if any, given in the Document for
+ public access to a Transparent copy of the Document, and likewise
+ the network locations given in the Document for previous versions
+ it was based on. These may be placed in the "History" section.
+ You may omit a network location for a work that was published at
+ least four years before the Document itself, or if the original
+ publisher of the version it refers to gives permission.
+K. For any section Entitled "Acknowledgements" or "Dedications",
+ Preserve the Title of the section, and preserve in the section all
+ the substance and tone of each of the contributor acknowledgements
+ and/or dedications given therein.
+L. Preserve all the Invariant Sections of the Document,
+ unaltered in their text and in their titles. Section numbers
+ or the equivalent are not considered part of the section titles.
+M. Delete any section Entitled "Endorsements". Such a section
+ may not be included in the Modified Version.
+N. Do not retitle any existing section to be Entitled "Endorsements"
+ or to conflict in title with any Invariant Section.
+O. Preserve any Warranty Disclaimers.
+
+If the Modified Version includes new front-matter sections or
+appendices that qualify as Secondary Sections and contain no material
+copied from the Document, you may at your option designate some or all
+of these sections as invariant. To do this, add their titles to the
+list of Invariant Sections in the Modified Version's license notice.
+These titles must be distinct from any other section titles.
+
+You may add a section Entitled "Endorsements", provided it contains
+nothing but endorsements of your Modified Version by various
+parties--for example, statements of peer review or that the text has
+been approved by an organization as the authoritative definition of a
+standard.
+
+You may add a passage of up to five words as a Front-Cover Text, and a
+passage of up to 25 words as a Back-Cover Text, to the end of the list
+of Cover Texts in the Modified Version. Only one passage of
+Front-Cover Text and one of Back-Cover Text may be added by (or
+through arrangements made by) any one entity. If the Document already
+includes a cover text for the same cover, previously added by you or
+by arrangement made by the same entity you are acting on behalf of,
+you may not add another; but you may replace the old one, on explicit
+permission from the previous publisher that added the old one.
+
+The author(s) and publisher(s) of the Document do not by this License
+give permission to use their names for publicity for or to assert or
+imply endorsement of any Modified Version.
+
+
+5. COMBINING DOCUMENTS
+
+You may combine the Document with other documents released under this
+License, under the terms defined in section 4 above for modified
+versions, provided that you include in the combination all of the
+Invariant Sections of all of the original documents, unmodified, and
+list them all as Invariant Sections of your combined work in its
+license notice, and that you preserve all their Warranty Disclaimers.
+
+The combined work need only contain one copy of this License, and
+multiple identical Invariant Sections may be replaced with a single
+copy. If there are multiple Invariant Sections with the same name but
+different contents, make the title of each such section unique by
+adding at the end of it, in parentheses, the name of the original
+author or publisher of that section if known, or else a unique number.
+Make the same adjustment to the section titles in the list of
+Invariant Sections in the license notice of the combined work.
+
+In the combination, you must combine any sections Entitled "History"
+in the various original documents, forming one section Entitled
+"History"; likewise combine any sections Entitled "Acknowledgements",
+and any sections Entitled "Dedications". You must delete all sections
+Entitled "Endorsements".
+
+
+6. COLLECTIONS OF DOCUMENTS
+
+You may make a collection consisting of the Document and other
+documents released under this License, and replace the individual
+copies of this License in the various documents with a single copy
+that is included in the collection, provided that you follow the rules
+of this License for verbatim copying of each of the documents in all
+other respects.
+
+You may extract a single document from such a collection, and
+distribute it individually under this License, provided you insert a
+copy of this License into the extracted document, and follow this
+License in all other respects regarding verbatim copying of that
+document.
+
+
+7. AGGREGATION WITH INDEPENDENT WORKS
+
+A compilation of the Document or its derivatives with other separate
+and independent documents or works, in or on a volume of a storage or
+distribution medium, is called an "aggregate" if the copyright
+resulting from the compilation is not used to limit the legal rights
+of the compilation's users beyond what the individual works permit.
+When the Document is included in an aggregate, this License does not
+apply to the other works in the aggregate which are not themselves
+derivative works of the Document.
+
+If the Cover Text requirement of section 3 is applicable to these
+copies of the Document, then if the Document is less than one half of
+the entire aggregate, the Document's Cover Texts may be placed on
+covers that bracket the Document within the aggregate, or the
+electronic equivalent of covers if the Document is in electronic form.
+Otherwise they must appear on printed covers that bracket the whole
+aggregate.
+
+
+8. TRANSLATION
+
+Translation is considered a kind of modification, so you may
+distribute translations of the Document under the terms of section 4.
+Replacing Invariant Sections with translations requires special
+permission from their copyright holders, but you may include
+translations of some or all Invariant Sections in addition to the
+original versions of these Invariant Sections. You may include a
+translation of this License, and all the license notices in the
+Document, and any Warranty Disclaimers, provided that you also include
+the original English version of this License and the original versions
+of those notices and disclaimers. In case of a disagreement between
+the translation and the original version of this License or a notice
+or disclaimer, the original version will prevail.
+
+If a section in the Document is Entitled "Acknowledgements",
+"Dedications", or "History", the requirement (section 4) to Preserve
+its Title (section 1) will typically require changing the actual
+title.
+
+
+9. TERMINATION
+
+You may not copy, modify, sublicense, or distribute the Document
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense, or distribute it is void, and
+will automatically terminate your rights under this License.
+
+However, if you cease all violation of this License, then your license
+from a particular copyright holder is reinstated (a) provisionally,
+unless and until the copyright holder explicitly and finally
+terminates your license, and (b) permanently, if the copyright holder
+fails to notify you of the violation by some reasonable means prior to
+60 days after the cessation.
+
+Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, receipt of a copy of some or all of the same material does
+not give you any rights to use it.
+
+
+10. FUTURE REVISIONS OF THIS LICENSE
+
+The Free Software Foundation may publish new, revised versions of the
+GNU Free Documentation License from time to time. Such new versions
+will be similar in spirit to the present version, but may differ in
+detail to address new problems or concerns. See
+https://www.gnu.org/licenses/.
+
+Each version of the License is given a distinguishing version number.
+If the Document specifies that a particular numbered version of this
+License "or any later version" applies to it, you have the option of
+following the terms and conditions either of that specified version or
+of any later version that has been published (not as a draft) by the
+Free Software Foundation. If the Document does not specify a version
+number of this License, you may choose any version ever published (not
+as a draft) by the Free Software Foundation. If the Document
+specifies that a proxy can decide which future versions of this
+License can be used, that proxy's public statement of acceptance of a
+version permanently authorizes you to choose that version for the
+Document.
+
+11. RELICENSING
+
+"Massive Multiauthor Collaboration Site" (or "MMC Site") means any
+World Wide Web server that publishes copyrightable works and also
+provides prominent facilities for anybody to edit those works. A
+public wiki that anybody can edit is an example of such a server. A
+"Massive Multiauthor Collaboration" (or "MMC") contained in the site
+means any set of copyrightable works thus published on the MMC site.
+
+"CC-BY-SA" means the Creative Commons Attribution-Share Alike 3.0
+license published by Creative Commons Corporation, a not-for-profit
+corporation with a principal place of business in San Francisco,
+California, as well as future copyleft versions of that license
+published by that same organization.
+
+"Incorporate" means to publish or republish a Document, in whole or in
+part, as part of another Document.
+
+An MMC is "eligible for relicensing" if it is licensed under this
+License, and if all works that were first published under this License
+somewhere other than this MMC, and subsequently incorporated in whole or
+in part into the MMC, (1) had no cover texts or invariant sections, and
+(2) were thus incorporated prior to November 1, 2008.
+
+The operator of an MMC Site may republish an MMC contained in the site
+under CC-BY-SA on the same site at any time before August 1, 2009,
+provided the MMC is eligible for relicensing.
+
+
+ADDENDUM: How to use this License for your documents
+
+To use this License in a document you have written, include a copy of
+the License in the document and put the following copyright and
+license notices just after the title page:
+
+ Copyright (c) YEAR YOUR NAME.
+ Permission is granted to copy, distribute and/or modify this document
+ under the terms of the GNU Free Documentation License, Version 1.3
+ or any later version published by the Free Software Foundation;
+ with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.
+ A copy of the license is included in the section entitled "GNU
+ Free Documentation License".
+
+If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts,
+replace the "with...Texts." line with this:
+
+ with the Invariant Sections being LIST THEIR TITLES, with the
+ Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST.
+
+If you have Invariant Sections without Cover Texts, or some other
+combination of the three, merge those two alternatives to suit the
+situation.
+
+If your document contains nontrivial examples of program code, we
+recommend releasing these examples in parallel under your choice of
+free software license, such as the GNU General Public License,
+to permit their use in free software.
diff --git a/LICENSES/GPL-3.0-only.txt b/LICENSES/GPL-3.0-only.txt
new file mode 100644
index 0000000..94a9ed0
--- /dev/null
+++ b/LICENSES/GPL-3.0-only.txt
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ <program> Copyright (C) <year> <name of author>
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/LICENSES/LicenseRef-Qt-Commercial.txt b/LICENSES/LicenseRef-Qt-Commercial.txt
new file mode 100644
index 0000000..825b1f3
--- /dev/null
+++ b/LICENSES/LicenseRef-Qt-Commercial.txt
@@ -0,0 +1,8 @@
+Licensees holding valid commercial Qt licenses may use this software in
+accordance with the the terms contained in a written agreement between
+you and The Qt Company. Alternatively, the terms and conditions that were
+accepted by the licensee when buying and/or downloading the
+software do apply.
+
+For the latest licensing terms and conditions, see https://www.qt.io/terms-conditions.
+For further information use the contact form at https://www.qt.io/contact-us.
diff --git a/LICENSES/Qt-GPL-exception-1.0.txt b/LICENSES/Qt-GPL-exception-1.0.txt
new file mode 100644
index 0000000..d0322bf
--- /dev/null
+++ b/LICENSES/Qt-GPL-exception-1.0.txt
@@ -0,0 +1,22 @@
+The Qt Company GPL Exception 1.0
+
+Exception 1:
+
+As a special exception you may create a larger work which contains the
+output of this application and distribute that work under terms of your
+choice, so long as the work is not otherwise derived from or based on
+this application and so long as the work does not in itself generate
+output that contains the output from this application in its original
+or modified form.
+
+Exception 2:
+
+As a special exception, you have permission to combine this application
+with Plugins licensed under the terms of your choice, to produce an
+executable, and to copy and distribute the resulting executable under
+the terms of your choice. However, the executable must be accompanied
+by a prominent notice offering all users of the executable the entire
+source code to this application, excluding the source code of the
+independent modules, but including any changes you have made to this
+application, under the terms of this license.
+
diff --git a/README b/README
new file mode 100644
index 0000000..ae0ec17
--- /dev/null
+++ b/README
@@ -0,0 +1,90 @@
+---------------------------
+Qt Data Visualization 5.9.0
+---------------------------
+
+Qt Data Visualization module provides multiple graph types to visualize data in 3D space
+both with C++ and Qt Quick 2.
+
+System Requirements
+===================
+
+- Qt 5.2.1 or newer
+- OpenGL 2.1 or newer (recommended) or OpenGL ES2 (reduced feature set)
+- Manipulating Qt Data Visualization graphs with QML Designer requires
+ Qt Creator 3.3 or newer
+
+Building
+========
+Configure the project with qmake:
+ qmake
+
+After running qmake, build the project with make:
+ (Linux) make
+ (Windows with MinGw) mingw32-make
+ (Windows with Visual Studio) nmake
+ (OS X) make
+
+The above generates the default makefiles for your configuration, which is typically
+the release build if you are using precompiled binary Qt distribution. To build both
+debug and release, or one specifically, use one of the following qmake lines instead.
+
+For debug builds:
+ qmake CONFIG+=debug
+ make
+ or
+ qmake CONFIG+=debug_and_release
+ make debug
+
+For release builds:
+ qmake CONFIG+=release
+ make
+ or
+ qmake CONFIG+=debug_and_release
+ make release
+
+For both builds (Windows/OS X only):
+ qmake CONFIG+="debug_and_release build_all"
+ make
+
+After building, install the module to your Qt directory:
+ make install
+
+If you want to uninstall the module:
+ make uninstall
+
+Building as a statically linked library
+=======================================
+
+The same as above applies, you will just have to add static to the CONFIG:
+ qmake CONFIG+=static
+
+Documentation
+=============
+
+The documentation can be generated with:
+ make docs
+
+The documentation is generated into the doc folder under the build folder.
+Both Qt Assistant (qtdatavisualization.qch) and in HTML format
+(qtdatavisualization subfolder) documentation is generated.
+
+Please refer to the generated documentation for more information:
+ doc/qtdatavisualization/qtdatavisualization-index.html
+
+Known Issues
+============
+
+- Some platforms like Android and WinRT cannot handle multiple native windows properly,
+ so only the Qt Quick 2 versions of graphs are available in practice for those platforms.
+- Shadows are not supported with OpenGL ES2 (including Angle builds in Windows).
+- Anti-aliasing doesn't work with OpenGL ES2 (including Angle builds in Windows).
+- QCustom3DVolume items are not supported with OpenGL ES2 (including Angle builds in Windows).
+- Surfaces with non-straight rows and columns do not always render properly.
+- Widget based examples layout incorrectly in iOS.
+- Reparenting a graph to an item in another QQuickWindow is not supported.
+- Android builds of QML applications importing QtDataVisualization also require
+ "QT += datavisualization" in the pro file. This is because Qt Data Visualization QML plugin has
+ a dependency to Qt Data Visualization C++ library, which Qt Creator doesn't automatically add
+ to the deployment package.
+- Only OpenGL ES2 emulation is available for software renderer (that is, when using
+ QCoreApplication::setAttribute(Qt::AA_UseSoftwareOpenGL))
diff --git a/coin/module_config.yaml b/coin/module_config.yaml
new file mode 100644
index 0000000..58c91b7
--- /dev/null
+++ b/coin/module_config.yaml
@@ -0,0 +1,23 @@
+version: 2
+accept_configuration:
+ condition: property
+ property: features
+ not_contains_value: Disable
+
+instructions:
+ Build:
+ - !include "{{qt/qtbase}}/coin_module_build_template_v2.yaml"
+
+ Test:
+ - type: Group
+ instructions:
+ - !include "{{qt/qtbase}}/coin_module_test_template_v3.yaml"
+ disable_if:
+ condition: or
+ conditions:
+ - condition: property
+ property: configureArgs
+ contains_value: "-DFEATURE_widgets=OFF"
+ - condition: property
+ property: configureArgs
+ contains_value: "-no-widgets"
diff --git a/conanfile.py b/conanfile.py
new file mode 100644
index 0000000..b47d771
--- /dev/null
+++ b/conanfile.py
@@ -0,0 +1,34 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+from conans import ConanFile
+import re
+from pathlib import Path
+
+
+def _parse_qt_version_by_key(key: str) -> str:
+ with open(Path(__file__).parent.resolve() / ".cmake.conf") as f:
+ m = re.search(fr'{key} .*"(.*)"', f.read())
+ return m.group(1) if m else ""
+
+
+def _get_qt_minor_version() -> str:
+ return ".".join(_parse_qt_version_by_key("QT_REPO_MODULE_VERSION").split(".")[:2])
+
+
+class QtGraphs(ConanFile):
+ name = "qtdatavis3d"
+ license = "GPL-3.0+, Commercial Qt License Agreement"
+ author = "The Qt Company <https://www.qt.io/contact-us>"
+ url = "https://code.qt.io/cgit/qt/qtdatavis3d.git"
+ description = (
+ "Qt Data Visualization provides UI Components for displaying 3D graphs, driven by static or "
+ "dynamic data models."
+ )
+ topics = "qt", "qt6", "data visualization", "qtquick"
+ settings = "os", "compiler", "arch", "build_type"
+ # for referencing the version number and prerelease tag and dependencies info
+ exports = ".cmake.conf", "dependencies.yaml"
+ exports_sources = "*", "!conan*.*"
+ python_requires = f"qt-conan-common/{_get_qt_minor_version()}@qt/everywhere"
+ python_requires_extend = "qt-conan-common.QtLeafModule"
diff --git a/dependencies.yaml b/dependencies.yaml
new file mode 100644
index 0000000..ddd976a
--- /dev/null
+++ b/dependencies.yaml
@@ -0,0 +1,10 @@
+dependencies:
+ ../qtbase:
+ ref: ca772a8ffa2fdde4238ff0af325997dd78dbfe83
+ required: true
+ ../qtdeclarative:
+ ref: 7fdac218ad12ad21d1a8bd7276aae8e8f3b51c01
+ required: true
+ ../qtquick3d:
+ ref: d4a89896527208707d342293cc863846b66189b9
+ required: true
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
new file mode 100644
index 0000000..9e947c0
--- /dev/null
+++ b/examples/CMakeLists.txt
@@ -0,0 +1,8 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+qt_examples_build_begin(EXTERNAL_BUILD)
+
+add_subdirectory(graphs)
+
+qt_examples_build_end()
diff --git a/examples/examples.pro b/examples/examples.pro
new file mode 100644
index 0000000..46149f3
--- /dev/null
+++ b/examples/examples.pro
@@ -0,0 +1,2 @@
+TEMPLATE = subdirs
+SUBDIRS += graphs
diff --git a/examples/graphs/CMakeLists.txt b/examples/graphs/CMakeLists.txt
new file mode 100644
index 0000000..a4dea32
--- /dev/null
+++ b/examples/graphs/CMakeLists.txt
@@ -0,0 +1,13 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+if(TARGET Qt::Quick)
+ qt_internal_add_example(qmlaxishandling)
+ qt_internal_add_example(qmlbars)
+ qt_internal_add_example(qmlscatter)
+ qt_internal_add_example(qmlsurfacegallery)
+endif()
+if(NOT ANDROID AND NOT IOS AND NOT WINRT)
+ qt_internal_add_example(graphgallery)
+ qt_internal_add_example(volumetric)
+endif()
diff --git a/examples/graphs/examples.pri b/examples/graphs/examples.pri
new file mode 100644
index 0000000..28203e7
--- /dev/null
+++ b/examples/graphs/examples.pri
@@ -0,0 +1,14 @@
+INCLUDEPATH += ../../../include
+
+LIBS += -L$$OUT_PWD/../../../lib
+
+TEMPLATE = app
+
+QT += graphs
+
+contains(TARGET, qml.*) {
+ QT += qml quick
+}
+
+target.path = $$[QT_INSTALL_EXAMPLES]/graphs/$$TARGET
+INSTALLS += target
diff --git a/examples/graphs/graphgallery/CMakeLists.txt b/examples/graphs/graphgallery/CMakeLists.txt
new file mode 100644
index 0000000..d511c86
--- /dev/null
+++ b/examples/graphs/graphgallery/CMakeLists.txt
@@ -0,0 +1,75 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+cmake_minimum_required(VERSION 3.16)
+project(graphgallery LANGUAGES CXX)
+
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+set(CMAKE_AUTOMOC ON)
+set(CMAKE_AUTORCC ON)
+set(CMAKE_AUTOUIC ON)
+
+if(NOT DEFINED INSTALL_EXAMPLESDIR)
+ set(INSTALL_EXAMPLESDIR "examples")
+endif()
+
+set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}")
+
+find_package(Qt6 COMPONENTS Core)
+find_package(Qt6 COMPONENTS Gui)
+find_package(Qt6 COMPONENTS Widgets)
+find_package(Qt6 COMPONENTS Graphs)
+
+qt_add_executable(graphgallery
+ main.cpp
+ bargraph.cpp bargraph.h
+ graphmodifier.cpp graphmodifier.h
+ rainfalldata.cpp rainfalldata.h
+ variantbardatamapping.cpp variantbardatamapping.h
+ variantbardataproxy.cpp variantbardataproxy.h
+ variantdataset.cpp variantdataset.h
+ scattergraph.cpp scattergraph.h
+ scatterdatamodifier.cpp scatterdatamodifier.h
+ axesinputhandler.cpp axesinputhandler.h
+ surfacegraph.cpp surfacegraph.h
+ surfacegraphmodifier.cpp surfacegraphmodifier.h
+ custominputhandler.cpp custominputhandler.h
+ highlightseries.cpp highlightseries.h
+ topographicseries.cpp topographicseries.h
+)
+set_target_properties(graphgallery PROPERTIES
+ WIN32_EXECUTABLE TRUE
+ MACOSX_BUNDLE TRUE
+)
+target_link_libraries(graphgallery PUBLIC
+ Qt::Core
+ Qt::Gui
+ Qt::Widgets
+ Qt::Graphs
+)
+
+set(graphgallery_resource_files
+ "data/raindata.txt"
+ "data/layer_1.png"
+ "data/layer_2.png"
+ "data/layer_3.png"
+ "data/oilrig.obj"
+ "data/pipe.obj"
+ "data/refinery.obj"
+ "data/maptexture.jpg"
+ "data/topography.png"
+)
+
+qt6_add_resources(graphgallery "graphgallery"
+ PREFIX
+ "/"
+ FILES
+ ${graphgallery_resource_files}
+)
+
+install(TARGETS graphgallery
+ RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
+ BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
+ LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
+)
diff --git a/examples/graphs/graphgallery/axesinputhandler.cpp b/examples/graphs/graphgallery/axesinputhandler.cpp
new file mode 100644
index 0000000..7c5ef9a
--- /dev/null
+++ b/examples/graphs/graphgallery/axesinputhandler.cpp
@@ -0,0 +1,117 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "axesinputhandler.h"
+#include <QtCore/qmath.h>
+
+AxesInputHandler::AxesInputHandler(QAbstract3DGraph *graph, QObject *parent) :
+ Q3DInputHandler(parent)
+{
+ //! [3]
+ // Connect to the item selection signal from graph
+ // TODO: API missing (QTBUG-111611)
+// connect(graph, &QAbstract3DGraph::selectedElementChanged, this,
+// &AxesInputHandler::handleElementSelected);
+ //! [3]
+}
+
+//! [0]
+void AxesInputHandler::mousePressEvent(QMouseEvent *event, const QPoint &mousePos)
+{
+ Q3DInputHandler::mousePressEvent(event, mousePos);
+ if (Qt::LeftButton == event->button())
+ m_mousePressed = true;
+}
+//! [0]
+
+//! [2]
+void AxesInputHandler::mouseMoveEvent(QMouseEvent *event, const QPoint &mousePos)
+{
+ //! [5]
+ // Check if we're trying to drag axis label
+ if (m_mousePressed && m_state != StateNormal) {
+ //! [5]
+ setPreviousInputPos(inputPosition());
+ setInputPosition(mousePos);
+ handleAxisDragging();
+ } else {
+ Q3DInputHandler::mouseMoveEvent(event, mousePos);
+ }
+}
+//! [2]
+
+//! [1]
+void AxesInputHandler::mouseReleaseEvent(QMouseEvent *event, const QPoint &mousePos)
+{
+ Q3DInputHandler::mouseReleaseEvent(event, mousePos);
+ m_mousePressed = false;
+ m_state = StateNormal;
+}
+//! [1]
+
+void AxesInputHandler::handleElementSelected(QAbstract3DGraph::ElementType type)
+{
+ //! [4]
+ switch (type) {
+ case QAbstract3DGraph::ElementAxisXLabel:
+ m_state = StateDraggingX;
+ break;
+ case QAbstract3DGraph::ElementAxisYLabel:
+ m_state = StateDraggingY;
+ break;
+ case QAbstract3DGraph::ElementAxisZLabel:
+ m_state = StateDraggingZ;
+ break;
+ default:
+ m_state = StateNormal;
+ break;
+ }
+ //! [4]
+}
+
+void AxesInputHandler::handleAxisDragging()
+{
+ float distance = 0.0f;
+
+ //! [6]
+ // Get scene orientation from active camera
+ float xRotation = scene()->activeCamera()->xRotation();
+ float yRotation = scene()->activeCamera()->yRotation();
+ //! [6]
+
+ //! [7]
+ // Calculate directional drag multipliers based on rotation
+ float xMulX = qCos(qDegreesToRadians(xRotation));
+ float xMulY = qSin(qDegreesToRadians(xRotation));
+ float zMulX = qSin(qDegreesToRadians(xRotation));
+ float zMulY = qCos(qDegreesToRadians(xRotation));
+ //! [7]
+
+ //! [8]
+ // Get the drag amount
+ QPoint move = inputPosition() - previousInputPos();
+
+ // Flip the effect of y movement if we're viewing from below
+ float yMove = (yRotation < 0) ? -move.y() : move.y();
+ //! [8]
+
+ //! [9]
+ // Adjust axes
+ switch (m_state) {
+ case StateDraggingX:
+ distance = (move.x() * xMulX - yMove * xMulY) / m_speedModifier;
+ m_axisX->setRange(m_axisX->min() - distance, m_axisX->max() - distance);
+ break;
+ case StateDraggingZ:
+ distance = (move.x() * zMulX + yMove * zMulY) / m_speedModifier;
+ m_axisZ->setRange(m_axisZ->min() + distance, m_axisZ->max() + distance);
+ break;
+ case StateDraggingY:
+ distance = move.y() / m_speedModifier; // No need to use adjusted y move here
+ m_axisY->setRange(m_axisY->min() + distance, m_axisY->max() + distance);
+ break;
+ default:
+ break;
+ }
+ //! [9]
+}
diff --git a/examples/graphs/graphgallery/axesinputhandler.h b/examples/graphs/graphgallery/axesinputhandler.h
new file mode 100644
index 0000000..5af989a
--- /dev/null
+++ b/examples/graphs/graphgallery/axesinputhandler.h
@@ -0,0 +1,54 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef AXESINPUTHANDLER_H
+#define AXESINPUTHANDLER_H
+
+#include <QtGraphs/q3dinputhandler.h>
+#include <QtGraphs/qabstract3dgraph.h>
+#include <QtGraphs/qvalue3daxis.h>
+
+//! [0]
+class AxesInputHandler : public Q3DInputHandler
+//! [0]
+{
+ Q_OBJECT
+
+ enum InputState {
+ StateNormal = 0,
+ StateDraggingX,
+ StateDraggingZ,
+ StateDraggingY
+ };
+
+public:
+ explicit AxesInputHandler(QAbstract3DGraph *graph, QObject *parent = 0);
+
+ inline void setAxes(QValue3DAxis *axisX, QValue3DAxis *axisZ, QValue3DAxis *axisY) {
+ m_axisX = axisX;
+ m_axisZ = axisZ;
+ m_axisY = axisY;
+ }
+
+ //! [1]
+ inline void setDragSpeedModifier(float modifier) { m_speedModifier = modifier; }
+ //! [1]
+
+ virtual void mousePressEvent(QMouseEvent *event, const QPoint &mousePos);
+ virtual void mouseMoveEvent(QMouseEvent *event, const QPoint &mousePos);
+ virtual void mouseReleaseEvent(QMouseEvent *event, const QPoint &mousePos);
+
+private:
+ void handleElementSelected(QAbstract3DGraph::ElementType type);
+ void handleAxisDragging();
+
+private:
+ bool m_mousePressed = false;
+ InputState m_state = StateNormal;
+ QValue3DAxis *m_axisX = nullptr;
+ QValue3DAxis *m_axisZ = nullptr;
+ QValue3DAxis *m_axisY = nullptr;
+ float m_speedModifier = 15.f;
+};
+
+#endif
diff --git a/examples/graphs/graphgallery/bargraph.cpp b/examples/graphs/graphgallery/bargraph.cpp
new file mode 100644
index 0000000..431a21e
--- /dev/null
+++ b/examples/graphs/graphgallery/bargraph.cpp
@@ -0,0 +1,325 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "bargraph.h"
+#include "graphmodifier.h"
+
+#include <QtWidgets/qboxlayout.h>
+#include <QtWidgets/qpushbutton.h>
+#include <QtWidgets/qcheckbox.h>
+#include <QtWidgets/qslider.h>
+#include <QtWidgets/qfontcombobox.h>
+#include <QtWidgets/qlabel.h>
+#include <QtWidgets/qradiobutton.h>
+#include <QtWidgets/qbuttongroup.h>
+#include <QtGui/qfontdatabase.h>
+
+using namespace Qt::StringLiterals;
+
+BarGraph::BarGraph()
+{
+ //! [0]
+ m_barsGraph = new Q3DBars();
+ //! [0]
+ initialize();
+}
+
+BarGraph::~BarGraph() = default;
+
+void BarGraph::initialize()
+{
+ //! [1]
+ m_barsWidget = new QWidget;
+ QHBoxLayout *hLayout = new QHBoxLayout(m_barsWidget);
+ QSize screenSize = m_barsGraph->screen()->size();
+ m_barsGraph->setMinimumSize(QSize(screenSize.width() / 2, screenSize.height() / 1.75));
+ m_barsGraph->setMaximumSize(screenSize);
+ m_barsGraph->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+ m_barsGraph->setFocusPolicy(Qt::StrongFocus);
+ m_barsGraph->setResizeMode(QQuickWidget::SizeRootObjectToView);
+ hLayout->addWidget(m_barsGraph, 1);
+
+ QVBoxLayout *vLayout = new QVBoxLayout();
+ hLayout->addLayout(vLayout);
+ //! [1]
+
+ QComboBox *themeList = new QComboBox(m_barsWidget);
+ themeList->addItem(u"Qt"_s);
+ themeList->addItem(u"Primary Colors"_s);
+ themeList->addItem(u"Digia"_s);
+ themeList->addItem(u"Stone Moss"_s);
+ themeList->addItem(u"Army Blue"_s);
+ themeList->addItem(u"Retro"_s);
+ themeList->addItem(u"Ebony"_s);
+ themeList->addItem(u"Isabelle"_s);
+ themeList->setCurrentIndex(0);
+
+ QPushButton *labelButton = new QPushButton(m_barsWidget);
+ labelButton->setText(u"Change label style"_s);
+
+ QCheckBox *smoothCheckBox = new QCheckBox(m_barsWidget);
+ smoothCheckBox->setText(u"Smooth bars"_s);
+ smoothCheckBox->setChecked(false);
+
+ QComboBox *barStyleList = new QComboBox(m_barsWidget);
+ barStyleList->addItem(u"Bar"_s, QAbstract3DSeries::MeshBar);
+ barStyleList->addItem(u"Pyramid"_s, QAbstract3DSeries::MeshPyramid);
+ barStyleList->addItem(u"Cone"_s, QAbstract3DSeries::MeshCone);
+ barStyleList->addItem(u"Cylinder"_s, QAbstract3DSeries::MeshCylinder);
+ barStyleList->addItem(u"Bevel bar"_s, QAbstract3DSeries::MeshBevelBar);
+ barStyleList->addItem(u"Sphere"_s, QAbstract3DSeries::MeshSphere);
+ barStyleList->setCurrentIndex(4);
+
+ QPushButton *cameraButton = new QPushButton(m_barsWidget);
+ cameraButton->setText(u"Change camera preset"_s);
+
+ QPushButton *zoomToSelectedButton = new QPushButton(m_barsWidget);
+ zoomToSelectedButton->setText(u"Zoom to selected bar"_s);
+
+ QComboBox *selectionModeList = new QComboBox(m_barsWidget);
+ selectionModeList->addItem(u"None"_s,
+ int(QAbstract3DGraph::SelectionNone));
+ selectionModeList->addItem(u"Bar"_s,
+ int(QAbstract3DGraph::SelectionItem));
+ selectionModeList->addItem(u"Row"_s,
+ int(QAbstract3DGraph::SelectionRow));
+ selectionModeList->addItem(u"Bar and Row"_s,
+ int(QAbstract3DGraph::SelectionItemAndRow));
+ selectionModeList->addItem(u"Column"_s,
+ int(QAbstract3DGraph::SelectionColumn));
+ selectionModeList->addItem(u"Bar and Column"_s,
+ int(QAbstract3DGraph::SelectionItemAndColumn));
+ selectionModeList->addItem(u"Row and Column"_s,
+ int(QAbstract3DGraph::SelectionRowAndColumn));
+ selectionModeList->addItem(u"Bar, Row and Column"_s,
+ int(QAbstract3DGraph::SelectionItemRowAndColumn));
+ selectionModeList->addItem(u"Slice into Row"_s,
+ int(QAbstract3DGraph::SelectionSlice | QAbstract3DGraph::SelectionRow));
+ selectionModeList->addItem(u"Slice into Row and Item"_s,
+ int(QAbstract3DGraph::SelectionSlice | QAbstract3DGraph::SelectionItemAndRow));
+ selectionModeList->addItem(u"Slice into Column"_s,
+ int(QAbstract3DGraph::SelectionSlice | QAbstract3DGraph::SelectionColumn));
+ selectionModeList->addItem(u"Slice into Column and Item"_s,
+ int(QAbstract3DGraph::SelectionSlice | QAbstract3DGraph::SelectionItemAndColumn));
+ selectionModeList->addItem(u"Multi: Bar, Row, Col"_s,
+ int(QAbstract3DGraph::SelectionItemRowAndColumn
+ | QAbstract3DGraph::SelectionMultiSeries));
+ selectionModeList->addItem(u"Multi, Slice: Row, Item"_s,
+ int(QAbstract3DGraph::SelectionSlice | QAbstract3DGraph::SelectionItemAndRow
+ | QAbstract3DGraph::SelectionMultiSeries));
+ selectionModeList->addItem(u"Multi, Slice: Col, Item"_s,
+ int(QAbstract3DGraph::SelectionSlice | QAbstract3DGraph::SelectionItemAndColumn
+ | QAbstract3DGraph::SelectionMultiSeries));
+ selectionModeList->setCurrentIndex(1);
+
+ QCheckBox *backgroundCheckBox = new QCheckBox(m_barsWidget);
+ backgroundCheckBox->setText(u"Show background"_s);
+ backgroundCheckBox->setChecked(false);
+
+ QCheckBox *gridCheckBox = new QCheckBox(m_barsWidget);
+ gridCheckBox->setText(u"Show grid"_s);
+ gridCheckBox->setChecked(true);
+
+ QCheckBox *seriesCheckBox = new QCheckBox(m_barsWidget);
+ seriesCheckBox->setText(u"Show second series"_s);
+ seriesCheckBox->setChecked(false);
+
+ QCheckBox *reverseValueAxisCheckBox = new QCheckBox(m_barsWidget);
+ reverseValueAxisCheckBox->setText(u"Reverse value axis"_s);
+ reverseValueAxisCheckBox->setChecked(false);
+
+ QCheckBox *reflectionCheckBox = new QCheckBox(m_barsWidget);
+ reflectionCheckBox->setText(u"Show reflections"_s);
+ reflectionCheckBox->setChecked(false);
+
+ //! [3]
+ QSlider *rotationSliderX = new QSlider(Qt::Horizontal, m_barsWidget);
+ rotationSliderX->setTickInterval(30);
+ rotationSliderX->setTickPosition(QSlider::TicksBelow);
+ rotationSliderX->setMinimum(-180);
+ rotationSliderX->setValue(0);
+ rotationSliderX->setMaximum(180);
+ //! [3]
+ QSlider *rotationSliderY = new QSlider(Qt::Horizontal, m_barsWidget);
+ rotationSliderY->setTickInterval(15);
+ rotationSliderY->setTickPosition(QSlider::TicksAbove);
+ rotationSliderY->setMinimum(-90);
+ rotationSliderY->setValue(0);
+ rotationSliderY->setMaximum(90);
+
+ QSlider *fontSizeSlider = new QSlider(Qt::Horizontal, m_barsWidget);
+ fontSizeSlider->setTickInterval(10);
+ fontSizeSlider->setTickPosition(QSlider::TicksBelow);
+ fontSizeSlider->setMinimum(1);
+ fontSizeSlider->setValue(30);
+ fontSizeSlider->setMaximum(100);
+
+ QFontComboBox *fontList = new QFontComboBox(m_barsWidget);
+ fontList->setCurrentFont(QFont("Times New Roman"));
+
+ QComboBox *shadowQuality = new QComboBox(m_barsWidget);
+ shadowQuality->addItem(u"None"_s);
+ shadowQuality->addItem(u"Low"_s);
+ shadowQuality->addItem(u"Medium"_s);
+ shadowQuality->addItem(u"High"_s);
+ shadowQuality->addItem(u"Low Soft"_s);
+ shadowQuality->addItem(u"Medium Soft"_s);
+ shadowQuality->addItem(u"High Soft"_s);
+ shadowQuality->setCurrentIndex(5);
+
+ QComboBox *rangeList = new QComboBox(m_barsWidget);
+ rangeList->addItem(u"2015"_s);
+ rangeList->addItem(u"2016"_s);
+ rangeList->addItem(u"2017"_s);
+ rangeList->addItem(u"2018"_s);
+ rangeList->addItem(u"2019"_s);
+ rangeList->addItem(u"2020"_s);
+ rangeList->addItem(u"2021"_s);
+ rangeList->addItem(u"2022"_s);
+ rangeList->addItem(u"All"_s);
+ rangeList->setCurrentIndex(8);
+
+ QCheckBox *axisTitlesVisibleCB = new QCheckBox(m_barsWidget);
+ axisTitlesVisibleCB->setText(u"Axis titles visible"_s);
+ axisTitlesVisibleCB->setChecked(true);
+
+ QCheckBox *axisTitlesFixedCB = new QCheckBox(m_barsWidget);
+ axisTitlesFixedCB->setText(u"Axis titles fixed"_s);
+ axisTitlesFixedCB->setChecked(true);
+
+ QSlider *axisLabelRotationSlider = new QSlider(Qt::Horizontal, m_barsWidget);
+ axisLabelRotationSlider->setTickInterval(10);
+ axisLabelRotationSlider->setTickPosition(QSlider::TicksBelow);
+ axisLabelRotationSlider->setMinimum(0);
+ axisLabelRotationSlider->setValue(30);
+ axisLabelRotationSlider->setMaximum(90);
+
+ QButtonGroup *modeGroup = new QButtonGroup(m_barsWidget);
+ QRadioButton *modeWeather = new QRadioButton(u"Temperature Data"_s, m_barsWidget);
+ modeWeather->setChecked(true);
+ QRadioButton *modeCustomProxy = new QRadioButton(u"Custom Proxy Data"_s, m_barsWidget);
+ modeGroup->addButton(modeWeather);
+ modeGroup->addButton(modeCustomProxy);
+
+ //! [4]
+ vLayout->addWidget(new QLabel(u"Rotate horizontally"_s));
+ vLayout->addWidget(rotationSliderX, 0, Qt::AlignTop);
+ //! [4]
+ vLayout->addWidget(new QLabel(u"Rotate vertically"_s));
+ 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(reflectionCheckBox);
+ vLayout->addWidget(seriesCheckBox);
+ vLayout->addWidget(reverseValueAxisCheckBox);
+ vLayout->addWidget(axisTitlesVisibleCB);
+ vLayout->addWidget(axisTitlesFixedCB);
+ vLayout->addWidget(new QLabel(u"Show year"_s));
+ vLayout->addWidget(rangeList);
+ vLayout->addWidget(new QLabel(u"Change bar style"_s));
+ vLayout->addWidget(barStyleList);
+ vLayout->addWidget(new QLabel(u"Change selection mode"_s));
+ vLayout->addWidget(selectionModeList);
+ vLayout->addWidget(new QLabel(u"Change theme"_s));
+ vLayout->addWidget(themeList);
+ vLayout->addWidget(new QLabel(u"Adjust shadow quality"_s));
+ vLayout->addWidget(shadowQuality);
+ vLayout->addWidget(new QLabel(u"Change font"_s));
+ vLayout->addWidget(fontList);
+ vLayout->addWidget(new QLabel(u"Adjust font size"_s));
+ vLayout->addWidget(fontSizeSlider);
+ vLayout->addWidget(new QLabel(u"Axis label rotation"_s));
+ vLayout->addWidget(axisLabelRotationSlider, 0, Qt::AlignTop);
+ vLayout->addWidget(modeWeather, 0, Qt::AlignTop);
+ vLayout->addWidget(modeCustomProxy, 1, Qt::AlignTop);
+
+ //! [2]
+ GraphModifier *modifier = new GraphModifier(m_barsGraph);
+ //! [2]
+
+ //! [5]
+ QObject::connect(rotationSliderX, &QSlider::valueChanged, modifier, &GraphModifier::rotateX);
+ //! [5]
+ QObject::connect(rotationSliderY, &QSlider::valueChanged, modifier, &GraphModifier::rotateY);
+
+ QObject::connect(labelButton, &QPushButton::clicked, modifier,
+ &GraphModifier::changeLabelBackground);
+ QObject::connect(cameraButton, &QPushButton::clicked, modifier,
+ &GraphModifier::changePresetCamera);
+ QObject::connect(zoomToSelectedButton, &QPushButton::clicked, modifier,
+ &GraphModifier::zoomToSelectedBar);
+
+ QObject::connect(backgroundCheckBox, &QCheckBox::stateChanged, modifier,
+ &GraphModifier::setBackgroundEnabled);
+ QObject::connect(gridCheckBox, &QCheckBox::stateChanged, modifier,
+ &GraphModifier::setGridEnabled);
+ QObject::connect(smoothCheckBox, &QCheckBox::stateChanged, modifier,
+ &GraphModifier::setSmoothBars);
+ QObject::connect(seriesCheckBox, &QCheckBox::stateChanged, modifier,
+ &GraphModifier::setSeriesVisibility);
+ QObject::connect(reverseValueAxisCheckBox, &QCheckBox::stateChanged, modifier,
+ &GraphModifier::setReverseValueAxis);
+ QObject::connect(reflectionCheckBox, &QCheckBox::stateChanged, modifier,
+ &GraphModifier::setReflection);
+
+ QObject::connect(modifier, &GraphModifier::backgroundEnabledChanged,
+ backgroundCheckBox, &QCheckBox::setChecked);
+ QObject::connect(modifier, &GraphModifier::gridEnabledChanged,
+ gridCheckBox, &QCheckBox::setChecked);
+
+ QObject::connect(rangeList, &QComboBox::currentIndexChanged, modifier,
+ &GraphModifier::changeRange);
+
+ QObject::connect(barStyleList, &QComboBox::currentIndexChanged, modifier,
+ &GraphModifier::changeStyle);
+
+ QObject::connect(selectionModeList, &QComboBox::currentIndexChanged, modifier,
+ &GraphModifier::changeSelectionMode);
+
+ QObject::connect(themeList, &QComboBox::currentIndexChanged, modifier,
+ &GraphModifier::changeTheme);
+
+ QObject::connect(shadowQuality, &QComboBox::currentIndexChanged, modifier,
+ &GraphModifier::changeShadowQuality);
+
+ QObject::connect(modifier, &GraphModifier::shadowQualityChanged, shadowQuality,
+ &QComboBox::setCurrentIndex);
+ QObject::connect(m_barsGraph, &Q3DBars::shadowQualityChanged, modifier,
+ &GraphModifier::shadowQualityUpdatedByVisual);
+
+ QObject::connect(fontSizeSlider, &QSlider::valueChanged, modifier,
+ &GraphModifier::changeFontSize);
+ QObject::connect(fontList, &QFontComboBox::currentFontChanged, modifier,
+ &GraphModifier::changeFont);
+
+ QObject::connect(modifier, &GraphModifier::fontSizeChanged, fontSizeSlider,
+ &QSlider::setValue);
+ QObject::connect(modifier, &GraphModifier::fontChanged, fontList,
+ &QFontComboBox::setCurrentFont);
+
+ QObject::connect(axisTitlesVisibleCB, &QCheckBox::stateChanged, modifier,
+ &GraphModifier::setAxisTitleVisibility);
+ QObject::connect(axisTitlesFixedCB, &QCheckBox::stateChanged, modifier,
+ &GraphModifier::setAxisTitleFixed);
+ QObject::connect(axisLabelRotationSlider, &QSlider::valueChanged, modifier,
+ &GraphModifier::changeLabelRotation);
+
+ QObject::connect(modeWeather, &QRadioButton::toggled, modifier,
+ &GraphModifier::setDataModeToWeather);
+ QObject::connect(modeCustomProxy, &QRadioButton::toggled, modifier,
+ &GraphModifier::setDataModeToCustom);
+ QObject::connect(modeWeather, &QRadioButton::toggled, seriesCheckBox,
+ &QCheckBox::setEnabled);
+ QObject::connect(modeWeather, &QRadioButton::toggled, rangeList,
+ &QComboBox::setEnabled);
+ QObject::connect(modeWeather, &QRadioButton::toggled, axisTitlesVisibleCB,
+ &QCheckBox::setEnabled);
+ QObject::connect(modeWeather, &QRadioButton::toggled, axisTitlesFixedCB,
+ &QCheckBox::setEnabled);
+ QObject::connect(modeWeather, &QRadioButton::toggled, axisLabelRotationSlider,
+ &QSlider::setEnabled);
+}
diff --git a/examples/graphs/graphgallery/bargraph.h b/examples/graphs/graphgallery/bargraph.h
new file mode 100644
index 0000000..3373080
--- /dev/null
+++ b/examples/graphs/graphgallery/bargraph.h
@@ -0,0 +1,25 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef BARGRAPH_H
+#define BARGRAPH_H
+
+#include <QtGraphs/q3dbars.h>
+#include <QtCore/qobject.h>
+
+class BarGraph : public QObject
+{
+ Q_OBJECT
+public:
+ BarGraph();
+ ~BarGraph();
+
+ void initialize();
+ QWidget *barsWidget() { return m_barsWidget; }
+
+private:
+ Q3DBars *m_barsGraph = nullptr;
+ QWidget *m_barsWidget = nullptr;
+};
+
+#endif
diff --git a/examples/graphs/graphgallery/custominputhandler.cpp b/examples/graphs/graphgallery/custominputhandler.cpp
new file mode 100644
index 0000000..3124c57
--- /dev/null
+++ b/examples/graphs/graphgallery/custominputhandler.cpp
@@ -0,0 +1,162 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "custominputhandler.h"
+
+#include <QtGraphs/q3dcamera.h>
+#include <QtCore/qmath.h>
+
+CustomInputHandler::CustomInputHandler(QAbstract3DGraph *graph, QObject *parent) :
+ Q3DInputHandler(parent)
+{
+ // Connect to the item selection signal from graph
+ // TODO: API missing (QTBUG-111611)
+// connect(graph, &QAbstract3DGraph::selectedElementChanged, this,
+// &CustomInputHandler::handleElementSelected);
+}
+
+void CustomInputHandler::mousePressEvent(QMouseEvent *event, const QPoint &mousePos)
+{
+ if (Qt::LeftButton == event->button()) {
+ m_highlight->setVisible(false);
+ m_mousePressed = true;
+ }
+ Q3DInputHandler::mousePressEvent(event, mousePos);
+}
+
+//! [1]
+void CustomInputHandler::wheelEvent(QWheelEvent *event)
+{
+ float delta = float(event->angleDelta().y());
+
+ m_axisXMinValue += delta;
+ m_axisXMaxValue -= delta;
+ m_axisZMinValue += delta;
+ m_axisZMaxValue -= delta;
+ checkConstraints();
+
+ float y = (m_axisXMaxValue - m_axisXMinValue) * m_aspectRatio;
+
+ m_axisX->setRange(m_axisXMinValue, m_axisXMaxValue);
+ m_axisY->setRange(100.f, y);
+ m_axisZ->setRange(m_axisZMinValue, m_axisZMaxValue);
+}
+//! [1]
+
+void CustomInputHandler::mouseMoveEvent(QMouseEvent *event, const QPoint &mousePos)
+{
+ // Check if we're trying to drag axis label
+ if (m_mousePressed && m_state != StateNormal) {
+ setPreviousInputPos(inputPosition());
+ setInputPosition(mousePos);
+ handleAxisDragging();
+ } else {
+ Q3DInputHandler::mouseMoveEvent(event, mousePos);
+ }
+}
+
+void CustomInputHandler::mouseReleaseEvent(QMouseEvent *event, const QPoint &mousePos)
+{
+ Q3DInputHandler::mouseReleaseEvent(event, mousePos);
+ m_mousePressed = false;
+ m_state = StateNormal;
+}
+
+void CustomInputHandler::handleElementSelected(QAbstract3DGraph::ElementType type)
+{
+ switch (type) {
+ case QAbstract3DGraph::ElementAxisXLabel:
+ m_state = StateDraggingX;
+ break;
+ case QAbstract3DGraph::ElementAxisZLabel:
+ m_state = StateDraggingZ;
+ break;
+ default:
+ m_state = StateNormal;
+ break;
+ }
+}
+
+void CustomInputHandler::handleAxisDragging()
+{
+ float distance = 0.f;
+
+ // Get scene orientation from active camera
+ float xRotation = scene()->activeCamera()->xRotation();
+
+ // Calculate directional drag multipliers based on rotation
+ float xMulX = qCos(qDegreesToRadians(xRotation));
+ float xMulY = qSin(qDegreesToRadians(xRotation));
+ float zMulX = qSin(qDegreesToRadians(xRotation));
+ float zMulY = qCos(qDegreesToRadians(xRotation));
+
+ // Get the drag amount
+ QPoint move = inputPosition() - previousInputPos();
+
+ // Adjust axes
+ switch (m_state) {
+ //! [0]
+ case StateDraggingX:
+ distance = (move.x() * xMulX - move.y() * xMulY) * m_speedModifier;
+ m_axisXMinValue -= distance;
+ m_axisXMaxValue -= distance;
+ if (m_axisXMinValue < m_areaMinValue) {
+ float dist = m_axisXMaxValue - m_axisXMinValue;
+ m_axisXMinValue = m_areaMinValue;
+ m_axisXMaxValue = m_axisXMinValue + dist;
+ }
+ if (m_axisXMaxValue > m_areaMaxValue) {
+ float dist = m_axisXMaxValue - m_axisXMinValue;
+ m_axisXMaxValue = m_areaMaxValue;
+ m_axisXMinValue = m_axisXMaxValue - dist;
+ }
+ m_axisX->setRange(m_axisXMinValue, m_axisXMaxValue);
+ break;
+ //! [0]
+ case StateDraggingZ:
+ distance = (move.x() * zMulX + move.y() * zMulY) * m_speedModifier;
+ m_axisZMinValue += distance;
+ m_axisZMaxValue += distance;
+ if (m_axisZMinValue < m_areaMinValue) {
+ float dist = m_axisZMaxValue - m_axisZMinValue;
+ m_axisZMinValue = m_areaMinValue;
+ m_axisZMaxValue = m_axisZMinValue + dist;
+ }
+ if (m_axisZMaxValue > m_areaMaxValue) {
+ float dist = m_axisZMaxValue - m_axisZMinValue;
+ m_axisZMaxValue = m_areaMaxValue;
+ m_axisZMinValue = m_axisZMaxValue - dist;
+ }
+ m_axisZ->setRange(m_axisZMinValue, m_axisZMaxValue);
+ break;
+ default:
+ break;
+ }
+}
+
+void CustomInputHandler::checkConstraints()
+{
+ //! [2]
+ if (m_axisXMinValue < m_areaMinValue)
+ m_axisXMinValue = m_areaMinValue;
+ if (m_axisXMaxValue > m_areaMaxValue)
+ m_axisXMaxValue = m_areaMaxValue;
+ // Don't allow too much zoom in
+ if ((m_axisXMaxValue - m_axisXMinValue) < m_axisXMinRange) {
+ float adjust = (m_axisXMinRange - (m_axisXMaxValue - m_axisXMinValue)) / 2.f;
+ m_axisXMinValue -= adjust;
+ m_axisXMaxValue += adjust;
+ }
+ //! [2]
+
+ if (m_axisZMinValue < m_areaMinValue)
+ m_axisZMinValue = m_areaMinValue;
+ if (m_axisZMaxValue > m_areaMaxValue)
+ m_axisZMaxValue = m_areaMaxValue;
+ // Don't allow too much zoom in
+ if ((m_axisZMaxValue - m_axisZMinValue) < m_axisZMinRange) {
+ float adjust = (m_axisZMinRange - (m_axisZMaxValue - m_axisZMinValue)) / 2.f;
+ m_axisZMinValue -= adjust;
+ m_axisZMaxValue += adjust;
+ }
+}
diff --git a/examples/graphs/graphgallery/custominputhandler.h b/examples/graphs/graphgallery/custominputhandler.h
new file mode 100644
index 0000000..f868da7
--- /dev/null
+++ b/examples/graphs/graphgallery/custominputhandler.h
@@ -0,0 +1,76 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef CUSTOMINPUTHANDLER_H
+#define CUSTOMINPUTHANDLER_H
+
+#include "highlightseries.h"
+
+#include <QtGraphs/q3dinputhandler.h>
+#include <QtGraphs/qabstract3dgraph.h>
+#include <QtGraphs/qvalue3daxis.h>
+
+class CustomInputHandler : public Q3DInputHandler
+{
+ Q_OBJECT
+
+ enum InputState {
+ StateNormal = 0,
+ StateDraggingX,
+ StateDraggingZ,
+ StateDraggingY
+ };
+
+public:
+ explicit CustomInputHandler(QAbstract3DGraph *graph, QObject *parent = 0);
+
+ inline void setLimits(float min, float max, float minRange) {
+ m_areaMinValue = min;
+ m_areaMaxValue = max;
+ m_axisXMinValue = m_areaMinValue;
+ m_axisXMaxValue = m_areaMaxValue;
+ m_axisZMinValue = m_areaMinValue;
+ m_axisZMaxValue = m_areaMaxValue;
+ m_axisXMinRange = minRange;
+ m_axisZMinRange = minRange;
+ }
+ inline void setAxes(QValue3DAxis *axisX, QValue3DAxis *axisY, QValue3DAxis *axisZ) {
+ m_axisX = axisX;
+ m_axisY = axisY;
+ m_axisZ = axisZ;
+ }
+ inline void setAspectRatio(float ratio) { m_aspectRatio = ratio; }
+ inline void setHighlightSeries(HighlightSeries *series) { m_highlight = series; }
+ inline void setDragSpeedModifier(float modifier) { m_speedModifier = modifier; }
+
+protected:
+ virtual void mousePressEvent(QMouseEvent *event, const QPoint &mousePos);
+ virtual void mouseMoveEvent(QMouseEvent *event, const QPoint &mousePos);
+ virtual void mouseReleaseEvent(QMouseEvent *event, const QPoint &mousePos);
+ virtual void wheelEvent(QWheelEvent *event);
+
+private:
+ void handleElementSelected(QAbstract3DGraph::ElementType type);
+ void handleAxisDragging();
+ void checkConstraints();
+
+private:
+ HighlightSeries *m_highlight = nullptr;
+ bool m_mousePressed = false;
+ InputState m_state = StateNormal;
+ QValue3DAxis *m_axisX = nullptr;
+ QValue3DAxis *m_axisY = nullptr;
+ QValue3DAxis *m_axisZ = nullptr;
+ float m_speedModifier = 20.f;
+ float m_aspectRatio = 0.f;
+ float m_axisXMinValue = 0.f;
+ float m_axisXMaxValue = 0.f;
+ float m_axisXMinRange = 0.f;
+ float m_axisZMinValue = 0.f;
+ float m_axisZMaxValue = 0.f;
+ float m_axisZMinRange = 0.f;
+ float m_areaMinValue = 0.f;
+ float m_areaMaxValue = 0.f;
+};
+
+#endif
diff --git a/examples/graphs/graphgallery/data/layer_1.png b/examples/graphs/graphgallery/data/layer_1.png
new file mode 100644
index 0000000..9138c71
--- /dev/null
+++ b/examples/graphs/graphgallery/data/layer_1.png
Binary files differ
diff --git a/examples/graphs/graphgallery/data/layer_2.png b/examples/graphs/graphgallery/data/layer_2.png
new file mode 100644
index 0000000..61631ae
--- /dev/null
+++ b/examples/graphs/graphgallery/data/layer_2.png
Binary files differ
diff --git a/examples/graphs/graphgallery/data/layer_3.png b/examples/graphs/graphgallery/data/layer_3.png
new file mode 100644
index 0000000..066ffbe
--- /dev/null
+++ b/examples/graphs/graphgallery/data/layer_3.png
Binary files differ
diff --git a/examples/graphs/graphgallery/data/license.txt b/examples/graphs/graphgallery/data/license.txt
new file mode 100644
index 0000000..749daf3
--- /dev/null
+++ b/examples/graphs/graphgallery/data/license.txt
@@ -0,0 +1,77 @@
+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.
diff --git a/examples/graphs/graphgallery/data/maptexture.jpg b/examples/graphs/graphgallery/data/maptexture.jpg
new file mode 100644
index 0000000..ae5d66e
--- /dev/null
+++ b/examples/graphs/graphgallery/data/maptexture.jpg
Binary files differ
diff --git a/examples/graphs/graphgallery/data/oilrig.obj b/examples/graphs/graphgallery/data/oilrig.obj
new file mode 100644
index 0000000..c3b6ea5
--- /dev/null
+++ b/examples/graphs/graphgallery/data/oilrig.obj
@@ -0,0 +1,2322 @@
+# Blender v2.66 (sub 0) OBJ File: 'oilrig.blend'
+# www.blender.org
+v 0.057462 2.272318 -1.170324
+v 0.057461 8.181165 -0.128434
+v 0.055540 2.268930 -1.151111
+v 0.055539 8.177776 -0.109221
+v 0.049849 2.265673 -1.132637
+v 0.049849 8.174520 -0.090747
+v 0.040608 2.262671 -1.115611
+v 0.040608 8.171517 -0.073721
+v 0.028172 2.260039 -1.100687
+v 0.028172 8.168886 -0.058798
+v 0.013019 2.257880 -1.088440
+v 0.013018 8.166726 -0.046550
+v -0.004270 2.256275 -1.079339
+v -0.004271 8.165121 -0.037450
+v -0.023029 2.255287 -1.073735
+v -0.023030 8.164133 -0.031846
+v -0.042539 2.254953 -1.071843
+v -0.042539 8.163799 -0.029953
+v -0.062048 2.255287 -1.073735
+v -0.062048 8.164133 -0.031846
+v -0.080807 2.256275 -1.079339
+v -0.080808 8.165121 -0.037450
+v -0.098096 2.257880 -1.088440
+v -0.098096 8.166726 -0.046550
+v -0.113249 2.260039 -1.100687
+v -0.113250 8.168886 -0.058798
+v -0.125685 2.262671 -1.115611
+v -0.125686 8.171517 -0.073721
+v -0.134926 2.265673 -1.132637
+v -0.134927 8.174520 -0.090747
+v -0.140617 2.268930 -1.151111
+v -0.140618 8.177776 -0.109222
+v -0.142538 2.272318 -1.170324
+v -0.142539 8.181165 -0.128434
+v -0.140617 2.275706 -1.189536
+v -0.140618 8.184552 -0.147647
+v -0.134926 2.278963 -1.208011
+v -0.134927 8.187810 -0.166121
+v -0.125685 2.281965 -1.225037
+v -0.125686 8.190812 -0.183147
+v -0.113249 2.284597 -1.239960
+v -0.113250 8.193443 -0.198071
+v -0.098095 2.286757 -1.252208
+v -0.098096 8.195602 -0.210318
+v -0.080807 2.288361 -1.261308
+v -0.080807 8.197207 -0.219419
+v -0.062047 2.289349 -1.266912
+v -0.062048 8.198195 -0.225023
+v -0.042538 2.289683 -1.268804
+v -0.042539 8.198529 -0.226915
+v -0.023029 2.289349 -1.266912
+v -0.023030 8.198195 -0.225023
+v -0.004270 2.288361 -1.261308
+v -0.004271 8.197207 -0.219418
+v 0.013019 2.286757 -1.252207
+v 0.013018 8.195602 -0.210318
+v 0.028172 2.284597 -1.239960
+v 0.028172 8.193443 -0.198070
+v 0.040609 2.281965 -1.225036
+v 0.040608 8.190812 -0.183147
+v 0.049850 2.278963 -1.208010
+v 0.049849 8.187810 -0.166121
+v 0.055540 2.275706 -1.189536
+v 0.055539 8.184552 -0.147646
+vt 0.000000 0.000000
+vt 1.000000 0.000000
+vt 1.000000 1.000000
+vt 0.000000 1.000000
+vt 0.961940 0.308658
+vt 1.000000 0.500000
+vt 0.990393 0.402455
+vt 0.500000 1.000000
+vt 0.597545 0.990393
+vt 0.402456 0.990393
+vt 0.990393 0.597545
+vt 0.915735 0.222215
+vt 0.961940 0.691342
+vt 0.853553 0.146447
+vt 0.915735 0.777785
+vt 0.777785 0.084265
+vt 0.853553 0.853553
+vt 0.691342 0.038060
+vt 0.777785 0.915735
+vt 0.597545 0.009607
+vt 0.691342 0.961940
+vt 0.000000 0.500000
+vt 0.038060 0.308659
+vt 0.009607 0.402455
+vt 0.038060 0.691342
+vt 0.009607 0.597546
+vt 0.500000 0.000000
+vt 0.402455 0.009607
+vt 0.308658 0.038060
+vt 0.222215 0.084265
+vt 0.146446 0.146447
+vt 0.308659 0.961940
+vt 0.222215 0.915735
+vt 0.084265 0.222215
+vt 0.146447 0.853554
+vt 0.084266 0.777786
+vn 0.995185 -0.017020 0.096528
+vn 0.956940 -0.050408 0.285877
+vn 0.881921 -0.081857 0.464235
+vn 0.773009 -0.110162 0.624758
+vn 0.634397 -0.134231 0.761264
+vn 0.471397 -0.153144 0.868523
+vn 0.290285 -0.166171 0.942402
+vn 0.098018 -0.172812 0.980066
+vn -0.098022 -0.172812 0.980065
+vn -0.290285 -0.166171 0.942402
+vn -0.471392 -0.153145 0.868526
+vn -0.634399 -0.134231 0.761262
+vn -0.773009 -0.110162 0.624757
+vn -0.881923 -0.081857 0.464231
+vn -0.956941 -0.050407 0.285873
+vn -0.995185 -0.017021 0.096528
+vn -0.995185 0.017021 -0.096529
+vn -0.956940 0.050407 -0.285875
+vn -0.881920 0.081858 -0.464238
+vn -0.773015 0.110160 -0.624751
+vn -0.634391 0.134232 -0.761268
+vn -0.471394 0.153144 -0.868524
+vn -0.290288 0.166171 -0.942401
+vn -0.098006 0.172812 -0.980067
+vn 0.098019 0.172812 -0.980065
+vn 0.290285 0.166171 -0.942402
+vn 0.471401 0.153144 -0.868521
+vn 0.634393 0.134232 -0.761267
+vn 0.773011 0.110161 -0.624755
+vn 0.881922 0.081857 -0.464233
+vn -0.000000 0.984796 0.173713
+vn 0.995185 0.017020 -0.096527
+vn 0.956941 0.050407 -0.285872
+vn -0.000011 -0.984808 -0.173648
+vn 0.995185 -0.017020 0.096529
+vn 0.956940 -0.050407 0.285875
+vn 0.881920 -0.081858 0.464237
+vn 0.773011 -0.110161 0.624755
+vn 0.634396 -0.134231 0.761265
+vn 0.471389 -0.153145 0.868527
+vn 0.290287 -0.166171 0.942402
+vn 0.098019 -0.172812 0.980065
+vn -0.098021 -0.172812 0.980065
+vn -0.290282 -0.166171 0.942403
+vn -0.471402 -0.153144 0.868520
+vn -0.634395 -0.134232 0.761265
+vn -0.773010 -0.110161 0.624756
+vn -0.881922 -0.081857 0.464234
+vn -0.956940 -0.050408 0.285875
+vn -0.995185 -0.017020 0.096527
+vn -0.956940 0.050408 -0.285876
+vn -0.881921 0.081857 -0.464236
+vn -0.773009 0.110162 -0.624757
+vn -0.634391 0.134232 -0.761269
+vn -0.471398 0.153144 -0.868522
+vn -0.290282 0.166171 -0.942403
+vn -0.098016 0.172812 -0.980066
+vn 0.098018 0.172812 -0.980065
+vn 0.290287 0.166171 -0.942402
+vn 0.471399 0.153144 -0.868522
+vn 0.773014 0.110161 -0.624751
+vn 0.881921 0.081857 -0.464236
+vn -0.000000 0.984812 0.173622
+vn -0.000000 0.984812 0.173623
+vn -0.000000 0.984801 0.173685
+vn -0.000000 0.984814 0.173615
+vn -0.000000 0.984810 0.173634
+vn -0.000000 0.984808 0.173649
+vn -0.000000 0.984806 0.173660
+vn -0.000043 0.984788 0.173763
+vn -0.000000 0.984797 0.173710
+vn -0.000000 0.984805 0.173661
+vn -0.000000 0.984810 0.173635
+vn -0.000000 0.984810 0.173637
+vn -0.000000 0.984802 0.173683
+vn -0.000000 0.984814 0.173611
+vn -0.000000 0.984800 0.173689
+vn -0.000000 0.984800 0.173690
+vn -0.000000 0.984801 0.173686
+vn 0.000005 0.984810 0.173637
+vn 0.956941 0.050407 -0.285873
+vn -0.000000 -0.984818 -0.173587
+vn -0.000007 -0.984807 -0.173654
+vn -0.000000 -0.984808 -0.173648
+vn -0.000027 -0.984801 -0.173685
+vn -0.000004 -0.984807 -0.173652
+vn -0.000010 -0.984800 -0.173693
+vn -0.000020 -0.984817 -0.173596
+vn -0.000013 -0.984810 -0.173638
+vn -0.000001 -0.984807 -0.173650
+vn -0.000005 -0.984808 -0.173646
+vn -0.000002 -0.984808 -0.173648
+vn 0.000002 -0.984808 -0.173649
+vn 0.000001 -0.984808 -0.173649
+vn -0.000011 -0.984809 -0.173642
+vn -0.000004 -0.984808 -0.173646
+vn -0.000001 -0.984808 -0.173648
+vn -0.000002 -0.984808 -0.173649
+vn -0.000006 -0.984808 -0.173649
+vn 0.000004 -0.984808 -0.173648
+vn 0.000000 -0.984808 -0.173649
+vn -0.000004 -0.984807 -0.173650
+vn 0.000005 -0.984808 -0.173646
+vn 0.000003 -0.984808 -0.173647
+vn 0.000008 -0.984805 -0.173663
+s off
+f 1/1/1 2/2/1 4/3/1
+f 3/1/2 4/2/2 6/3/2
+f 5/1/3 6/2/3 8/3/3
+f 7/1/4 8/2/4 10/3/4
+f 9/1/5 10/2/5 12/3/5
+f 11/1/6 12/2/6 14/3/6
+f 13/1/7 14/2/7 16/3/7
+f 15/1/8 16/2/8 18/3/8
+f 17/1/9 18/2/9 19/4/9
+f 19/1/10 20/2/10 21/4/10
+f 21/1/11 22/2/11 23/4/11
+f 23/1/12 24/2/12 25/4/12
+f 25/1/13 26/2/13 27/4/13
+f 27/1/14 28/2/14 29/4/14
+f 29/1/15 30/2/15 31/4/15
+f 31/1/16 32/2/16 33/4/16
+f 33/1/17 34/2/17 35/4/17
+f 35/1/18 36/2/18 37/4/18
+f 37/1/19 38/2/19 39/4/19
+f 39/1/20 40/2/20 41/4/20
+f 41/1/21 42/2/21 43/4/21
+f 43/1/22 44/2/22 45/4/22
+f 45/1/23 46/2/23 47/4/23
+f 47/1/24 48/2/24 49/4/24
+f 49/1/25 50/2/25 52/3/25
+f 51/1/26 52/2/26 54/3/26
+f 53/1/27 54/2/27 56/3/27
+f 55/1/28 56/2/28 58/3/28
+f 57/1/29 58/2/29 60/3/29
+f 59/1/30 60/2/30 62/3/30
+f 48/5/31 52/6/31 50/7/31
+f 63/1/32 64/2/32 2/3/32
+f 61/1/33 62/2/33 64/3/33
+f 1/8/34 3/9/34 63/10/34
+f 3/4/35 1/1/35 4/3/35
+f 5/4/36 3/1/36 6/3/36
+f 7/4/37 5/1/37 8/3/37
+f 9/4/38 7/1/38 10/3/38
+f 11/4/39 9/1/39 12/3/39
+f 13/4/40 11/1/40 14/3/40
+f 15/4/41 13/1/41 16/3/41
+f 17/4/42 15/1/42 18/3/42
+f 18/2/43 20/3/43 19/4/43
+f 20/2/44 22/3/44 21/4/44
+f 22/2/45 24/3/45 23/4/45
+f 24/2/46 26/3/46 25/4/46
+f 26/2/47 28/3/47 27/4/47
+f 28/2/48 30/3/48 29/4/48
+f 30/2/49 32/3/49 31/4/49
+f 32/2/50 34/3/50 33/4/50
+f 34/2/17 36/3/17 35/4/17
+f 36/2/51 38/3/51 37/4/51
+f 38/2/52 40/3/52 39/4/52
+f 40/2/53 42/3/53 41/4/53
+f 42/2/54 44/3/54 43/4/54
+f 44/2/55 46/3/55 45/4/55
+f 46/2/56 48/3/56 47/4/56
+f 48/2/57 50/3/57 49/4/57
+f 51/4/58 49/1/58 52/3/58
+f 53/4/59 51/1/59 54/3/59
+f 55/4/60 53/1/60 56/3/60
+f 57/4/28 55/1/28 58/3/28
+f 59/4/61 57/1/61 60/3/61
+f 61/4/62 59/1/62 62/3/62
+f 48/5/63 54/11/63 52/6/63
+f 46/12/64 54/11/64 48/5/64
+f 46/12/65 56/13/65 54/11/65
+f 44/14/65 56/13/65 46/12/65
+f 44/14/66 58/15/66 56/13/66
+f 42/16/66 58/15/66 44/14/66
+f 42/16/67 60/17/67 58/15/67
+f 40/18/67 60/17/67 42/16/67
+f 40/18/68 62/19/68 60/17/68
+f 38/20/68 62/19/68 40/18/68
+f 38/20/69 64/21/69 62/19/69
+f 20/22/70 24/23/70 22/24/70
+f 16/25/71 20/22/71 18/26/71
+f 16/25/72 24/23/72 20/22/72
+f 36/27/69 64/21/69 38/20/69
+f 34/28/73 64/21/73 36/27/73
+f 2/9/74 64/21/74 34/28/74
+f 4/8/75 2/9/75 34/28/75
+f 4/8/75 34/28/75 32/29/75
+f 6/10/76 4/8/76 32/29/76
+f 6/10/76 32/29/76 30/30/76
+f 6/10/68 30/30/68 28/31/68
+f 8/32/68 6/10/68 28/31/68
+f 10/33/73 8/32/73 28/31/73
+f 10/33/67 28/31/67 26/34/67
+f 12/35/77 10/33/77 26/34/77
+f 12/35/78 26/34/78 24/23/78
+f 14/36/79 12/35/79 24/23/79
+f 16/25/80 14/36/80 24/23/80
+f 1/4/32 63/1/32 2/3/32
+f 63/4/81 61/1/81 64/3/81
+f 19/7/82 15/11/82 17/6/82
+f 41/31/83 37/29/83 39/30/83
+f 3/9/84 5/21/84 63/10/84
+f 45/23/85 41/31/85 43/34/85
+f 45/23/86 37/29/86 41/31/86
+f 49/22/87 45/23/87 47/24/87
+f 49/22/84 37/29/84 45/23/84
+f 49/22/84 35/28/84 37/29/84
+f 49/22/84 33/27/84 35/28/84
+f 49/22/84 31/20/84 33/27/84
+f 55/36/88 51/26/88 53/25/88
+f 59/33/89 55/36/89 57/35/89
+f 61/32/90 55/36/90 59/33/90
+f 63/10/91 55/36/91 61/32/91
+f 63/10/92 5/21/92 55/36/92
+f 5/21/93 7/19/93 55/36/93
+f 7/19/94 9/17/94 55/36/94
+f 29/18/95 25/14/95 27/16/95
+f 31/20/96 25/14/96 29/18/96
+f 49/22/97 25/14/97 31/20/97
+f 49/22/98 23/12/98 25/14/98
+f 9/17/93 11/15/93 55/36/93
+f 11/15/99 13/13/99 55/36/99
+f 13/13/100 15/11/100 55/36/100
+f 15/11/101 19/7/101 55/36/101
+f 19/7/102 21/5/102 55/36/102
+f 21/5/103 23/12/103 55/36/103
+f 23/12/104 49/22/104 55/36/104
+f 49/22/105 51/26/105 55/36/105
+v 0.053672 2.252534 1.125439
+v 0.053673 8.161380 0.083549
+v 0.051751 2.255921 1.144652
+v 0.051751 8.164768 0.102762
+v 0.046060 2.259179 1.163126
+v 0.046061 8.168025 0.121236
+v 0.036819 2.262181 1.180152
+v 0.036820 8.171027 0.138262
+v 0.024383 2.264812 1.195075
+v 0.024384 8.173658 0.153186
+v 0.009229 2.266972 1.207323
+v 0.009230 8.175818 0.165433
+v -0.008059 2.268577 1.216423
+v -0.008059 8.177423 0.174534
+v -0.026819 2.269565 1.222028
+v -0.026818 8.178411 0.180138
+v -0.046328 2.269898 1.223920
+v -0.046327 8.178745 0.182030
+v -0.065837 2.269565 1.222027
+v -0.065836 8.178411 0.180138
+v -0.084596 2.268577 1.216423
+v -0.084595 8.177423 0.174534
+v -0.101885 2.266972 1.207323
+v -0.101884 8.175818 0.165433
+v -0.117038 2.264812 1.195075
+v -0.117038 8.173658 0.153186
+v -0.129475 2.262181 1.180152
+v -0.129474 8.171027 0.138262
+v -0.138716 2.259179 1.163126
+v -0.138715 8.168025 0.121236
+v -0.144406 2.255921 1.144651
+v -0.144406 8.164768 0.102762
+v -0.146328 2.252534 1.125439
+v -0.146327 8.161380 0.083549
+v -0.144406 2.249146 1.106226
+v -0.144406 8.157992 0.064337
+v -0.138716 2.245888 1.087752
+v -0.138715 8.154735 0.045862
+v -0.129475 2.242886 1.070726
+v -0.129474 8.151732 0.028836
+v -0.117038 2.240255 1.055802
+v -0.117038 8.149101 0.013913
+v -0.101885 2.238095 1.043555
+v -0.101884 8.146942 0.001666
+v -0.084596 2.236491 1.034454
+v -0.084595 8.145337 -0.007435
+v -0.065837 2.235502 1.028850
+v -0.065836 8.144349 -0.013039
+v -0.046328 2.235169 1.026958
+v -0.046327 8.144015 -0.014931
+v -0.026819 2.235502 1.028850
+v -0.026818 8.144349 -0.013039
+v -0.008059 2.236491 1.034455
+v -0.008059 8.145337 -0.007435
+v 0.009229 2.238095 1.043555
+v 0.009230 8.146942 0.001666
+v 0.024383 2.240255 1.055803
+v 0.024384 8.149101 0.013913
+v 0.036819 2.242886 1.070726
+v 0.036820 8.151732 0.028837
+v 0.046060 2.245888 1.087752
+v 0.046061 8.154735 0.045863
+v 0.051751 2.249146 1.106226
+v 0.051752 8.157992 0.064337
+vt 0.000000 0.000000
+vt 1.000000 0.000000
+vt 1.000000 1.000000
+vt 0.000000 1.000000
+vt 0.500000 1.000000
+vt 0.597545 0.990393
+vt 0.402456 0.990393
+vt 0.691342 0.961940
+vt 0.777785 0.915735
+vt 0.308659 0.961940
+vt 0.222215 0.915735
+vt 0.853553 0.853553
+vt 0.915735 0.777785
+vt 0.961940 0.691342
+vt 0.990393 0.597545
+vt 0.990393 0.402455
+vt 1.000000 0.500000
+vt 0.853553 0.146447
+vt 0.961940 0.308658
+vt 0.915735 0.222215
+vt 0.691342 0.038060
+vt 0.777785 0.084265
+vt 0.597545 0.009607
+vt 0.500000 0.000000
+vt 0.146447 0.853554
+vt 0.084266 0.777786
+vt 0.038060 0.691342
+vt 0.308658 0.038060
+vt 0.402455 0.009607
+vt 0.146446 0.146447
+vt 0.222215 0.084265
+vt 0.084265 0.222215
+vt 0.038060 0.308659
+vt 0.009607 0.402455
+vt 0.009607 0.597546
+vt 0.000000 0.500000
+vn 0.995185 0.017020 0.096528
+vn 0.956940 0.050408 0.285877
+vn 0.881921 0.081857 0.464236
+vn 0.773010 0.110161 0.624756
+vn 0.634396 0.134232 0.761265
+vn 0.471397 0.153144 0.868523
+vn 0.290283 0.166171 0.942403
+vn 0.098018 0.172812 0.980066
+vn -0.098018 0.172812 0.980066
+vn -0.290285 0.166171 0.942402
+vn -0.471398 0.153144 0.868522
+vn -0.634392 0.134232 0.761268
+vn -0.773007 0.110162 0.624759
+vn -0.881923 0.081857 0.464231
+vn -0.956941 0.050407 0.285874
+vn -0.995185 0.017021 0.096528
+vn -0.995185 -0.017021 -0.096529
+vn -0.956940 -0.050408 -0.285877
+vn -0.881921 -0.081857 -0.464236
+vn -0.773008 -0.110162 -0.624758
+vn -0.634391 -0.134232 -0.761269
+vn -0.471388 -0.153145 -0.868528
+vn -0.290288 -0.166171 -0.942401
+vn -0.098006 -0.172812 -0.980067
+vn 0.098021 -0.172812 -0.980065
+vn 0.290285 -0.166171 -0.942402
+vn 0.471402 -0.153144 -0.868520
+vn 0.634392 -0.134232 -0.761267
+vn 0.773010 -0.110161 -0.624755
+vn 0.881922 -0.081857 -0.464233
+vn -0.000356 0.984796 -0.173717
+vn 0.995185 -0.017020 -0.096527
+vn 0.956941 -0.050407 -0.285872
+vn -0.000005 -0.984808 0.173646
+vn 0.995185 0.017020 0.096529
+vn 0.956940 0.050407 0.285875
+vn 0.881920 0.081858 0.464237
+vn 0.773011 0.110161 0.624755
+vn 0.634396 0.134231 0.761264
+vn 0.471389 0.153145 0.868527
+vn 0.290292 0.166171 0.942400
+vn 0.098013 0.172812 0.980066
+vn -0.098019 0.172812 0.980065
+vn -0.290283 0.166171 0.942403
+vn -0.471400 0.153144 0.868521
+vn -0.634396 0.134232 0.761264
+vn -0.773010 0.110161 0.624756
+vn -0.881922 0.081857 0.464234
+vn -0.956940 0.050408 0.285876
+vn -0.995185 0.017020 0.096526
+vn -0.995185 -0.017021 -0.096530
+vn -0.956940 -0.050407 -0.285875
+vn -0.773009 -0.110161 -0.624757
+vn -0.634390 -0.134232 -0.761269
+vn -0.471399 -0.153144 -0.868522
+vn -0.290282 -0.166171 -0.942403
+vn -0.098018 -0.172812 -0.980066
+vn 0.098018 -0.172812 -0.980065
+vn 0.290287 -0.166171 -0.942402
+vn 0.471399 -0.153144 -0.868522
+vn 0.634393 -0.134232 -0.761267
+vn 0.773014 -0.110161 -0.624751
+vn 0.881921 -0.081857 -0.464236
+vn -0.000114 0.984812 -0.173626
+vn -0.000002 0.984808 -0.173648
+vn -0.000054 0.984802 -0.173678
+vn -0.000008 0.984808 -0.173648
+vn -0.000004 0.984808 -0.173647
+vn -0.000107 0.984836 -0.173487
+vn -0.000015 0.984812 -0.173622
+vn -0.000013 0.984820 -0.173580
+vn -0.000001 0.984809 -0.173639
+vn -0.000044 0.984787 -0.173766
+vn -0.000047 0.984804 -0.173672
+vn -0.000051 0.984803 -0.173676
+vn -0.000050 0.984803 -0.173675
+vn -0.000006 0.984799 -0.173697
+vn -0.000037 0.984791 -0.173743
+vn -0.000244 0.984806 -0.173659
+vn -0.000025 0.984806 -0.173659
+vn -0.000168 0.984823 -0.173560
+vn -0.000013 0.984812 -0.173624
+vn -0.000088 0.984819 -0.173584
+vn -0.000036 0.984813 -0.173618
+vn 0.000016 0.984808 -0.173645
+vn 0.000005 0.984808 -0.173650
+vn 0.000006 0.984807 -0.173650
+vn 0.000005 0.984808 -0.173649
+vn 0.000002 0.984808 -0.173648
+vn 0.000003 0.984807 -0.173650
+vn 0.000005 0.984804 -0.173667
+vn 0.956941 -0.050407 -0.285873
+vn -0.000005 -0.984808 0.173649
+vn -0.000001 -0.984818 0.173592
+vn -0.000000 -0.984808 0.173648
+vn -0.000006 -0.984808 0.173646
+vn -0.000001 -0.984808 0.173648
+vn -0.000002 -0.984808 0.173648
+vn -0.000001 -0.984806 0.173661
+vn -0.000000 -0.984805 0.173663
+vn -0.000000 -0.984810 0.173635
+vn -0.000000 -0.984810 0.173634
+vn -0.000000 -0.984807 0.173653
+vn -0.000000 -0.984807 0.173650
+vn -0.000000 -0.984808 0.173647
+vn 0.000000 -0.984807 0.173651
+vn -0.000000 -0.984807 0.173649
+vn -0.000000 -0.984810 0.173637
+vn 0.000000 -0.984806 0.173659
+vn -0.000001 -0.984805 0.173664
+s off
+f 65/37/106 66/38/106 68/39/106
+f 67/37/107 68/38/107 70/39/107
+f 69/37/108 70/38/108 72/39/108
+f 71/37/109 72/38/109 74/39/109
+f 73/37/110 74/38/110 76/39/110
+f 75/37/111 76/38/111 78/39/111
+f 77/37/112 78/38/112 80/39/112
+f 79/37/113 80/38/113 82/39/113
+f 81/37/114 82/38/114 83/40/114
+f 83/37/115 84/38/115 85/40/115
+f 85/37/116 86/38/116 87/40/116
+f 87/37/117 88/38/117 89/40/117
+f 89/37/118 90/38/118 91/40/118
+f 91/37/119 92/38/119 93/40/119
+f 93/37/120 94/38/120 95/40/120
+f 95/37/121 96/38/121 97/40/121
+f 97/37/122 98/38/122 99/40/122
+f 99/37/123 100/38/123 101/40/123
+f 101/37/124 102/38/124 103/40/124
+f 103/37/125 104/38/125 105/40/125
+f 105/37/126 106/38/126 107/40/126
+f 107/37/127 108/38/127 109/40/127
+f 109/37/128 110/38/128 111/40/128
+f 111/37/129 112/38/129 113/40/129
+f 113/37/130 114/38/130 116/39/130
+f 115/37/131 116/38/131 118/39/131
+f 117/37/132 118/38/132 120/39/132
+f 119/37/133 120/38/133 122/39/133
+f 121/37/134 122/38/134 124/39/134
+f 123/37/135 124/38/135 126/39/135
+f 68/41/136 66/42/136 70/43/136
+f 127/37/137 128/38/137 66/39/137
+f 125/37/138 126/38/138 128/39/138
+f 65/41/139 67/42/139 69/44/139
+f 67/40/140 65/37/140 68/39/140
+f 69/40/141 67/37/141 70/39/141
+f 71/40/142 69/37/142 72/39/142
+f 73/40/143 71/37/143 74/39/143
+f 75/40/144 73/37/144 76/39/144
+f 77/40/145 75/37/145 78/39/145
+f 79/40/146 77/37/146 80/39/146
+f 81/40/147 79/37/147 82/39/147
+f 82/38/148 84/39/148 83/40/148
+f 84/38/149 86/39/149 85/40/149
+f 86/38/150 88/39/150 87/40/150
+f 88/38/151 90/39/151 89/40/151
+f 90/38/152 92/39/152 91/40/152
+f 92/38/153 94/39/153 93/40/153
+f 94/38/154 96/39/154 95/40/154
+f 96/38/155 98/39/155 97/40/155
+f 98/38/156 100/39/156 99/40/156
+f 100/38/157 102/39/157 101/40/157
+f 102/38/124 104/39/124 103/40/124
+f 104/38/158 106/39/158 105/40/158
+f 106/38/159 108/39/159 107/40/159
+f 108/38/160 110/39/160 109/40/160
+f 110/38/161 112/39/161 111/40/161
+f 112/38/162 114/39/162 113/40/162
+f 115/40/163 113/37/163 116/39/163
+f 117/40/164 115/37/164 118/39/164
+f 119/40/165 117/37/165 120/39/165
+f 121/40/166 119/37/166 122/39/166
+f 123/40/167 121/37/167 124/39/167
+f 125/40/168 123/37/168 126/39/168
+f 66/42/169 128/44/169 126/45/169
+f 70/43/170 66/42/170 126/45/170
+f 72/46/171 70/43/171 74/47/171
+f 70/43/172 126/45/172 74/47/172
+f 126/45/173 124/48/173 74/47/173
+f 122/49/174 120/50/174 118/51/174
+f 124/48/175 122/49/175 118/51/175
+f 114/52/176 118/51/176 116/53/176
+f 124/48/177 118/51/177 114/52/177
+f 108/54/178 112/55/178 110/56/178
+f 104/57/179 108/54/179 106/58/179
+f 102/59/180 108/54/180 104/57/180
+f 100/60/181 108/54/181 102/59/181
+f 76/61/182 74/47/182 78/62/182
+f 78/62/183 74/47/183 80/63/183
+f 96/64/184 100/60/184 98/65/184
+f 96/64/185 108/54/185 100/60/185
+f 92/66/186 96/64/186 94/67/186
+f 90/68/187 96/64/187 92/66/187
+f 88/69/188 96/64/188 90/68/188
+f 86/70/189 96/64/189 88/69/189
+f 82/71/176 86/70/176 84/72/176
+f 74/47/190 124/48/190 80/63/190
+f 124/48/191 114/52/191 80/63/191
+f 114/52/192 112/55/192 80/63/192
+f 112/55/193 108/54/193 80/63/193
+f 108/54/194 96/64/194 80/63/194
+f 96/64/195 86/70/195 80/63/195
+f 86/70/196 82/71/196 80/63/196
+f 65/40/137 127/37/137 66/39/137
+f 127/40/197 125/37/197 128/39/197
+f 127/43/198 65/41/198 125/46/198
+f 83/52/199 79/51/199 81/53/199
+f 97/60/200 93/57/200 95/59/200
+f 99/65/200 93/57/200 97/60/200
+f 103/67/201 99/65/201 101/64/201
+f 103/67/202 93/57/202 99/65/202
+f 105/66/203 93/57/203 103/67/203
+f 115/71/199 111/70/199 113/72/199
+f 115/71/204 109/69/204 111/70/204
+f 117/63/205 109/69/205 115/71/205
+f 117/63/206 107/68/206 109/69/206
+f 119/62/207 107/68/207 117/63/207
+f 119/62/208 105/66/208 107/68/208
+f 121/61/208 105/66/208 119/62/208
+f 123/47/200 105/66/200 121/61/200
+f 125/46/209 105/66/209 123/47/209
+f 125/46/200 65/41/200 105/66/200
+f 105/66/200 65/41/200 93/57/200
+f 65/41/210 69/44/210 93/57/210
+f 69/44/209 71/45/209 93/57/209
+f 93/57/200 71/45/200 91/58/200
+f 91/58/211 71/45/211 89/54/211
+f 71/45/212 73/48/212 89/54/212
+f 73/48/211 75/49/211 89/54/211
+f 89/54/208 75/49/208 87/56/208
+f 75/49/213 77/50/213 87/56/213
+f 87/56/207 77/50/207 85/55/207
+f 77/50/214 79/51/214 85/55/214
+f 79/51/215 83/52/215 85/55/215
+v 1.116865 2.257815 -0.125221
+v 0.074976 8.166661 -0.125221
+v 1.136078 2.261203 -0.123300
+v 0.094188 8.170050 -0.123300
+v 1.154552 2.264460 -0.117609
+v 0.112663 8.173306 -0.117609
+v 1.171578 2.267462 -0.108368
+v 0.129689 8.176309 -0.108368
+v 1.186502 2.270094 -0.095932
+v 0.144612 8.178940 -0.095932
+v 1.198749 2.272254 -0.080778
+v 0.156859 8.181100 -0.080778
+v 1.207850 2.273858 -0.063490
+v 0.165960 8.182705 -0.063490
+v 1.213454 2.274846 -0.044730
+v 0.171564 8.183693 -0.044730
+v 1.215346 2.275180 -0.025221
+v 0.173456 8.184027 -0.025221
+v 1.213454 2.274846 -0.005712
+v 0.171564 8.183693 -0.005712
+v 1.207850 2.273858 0.013047
+v 0.165960 8.182705 0.013047
+v 1.198749 2.272254 0.030336
+v 0.156859 8.181100 0.030336
+v 1.186502 2.270094 0.045489
+v 0.144612 8.178940 0.045489
+v 1.171578 2.267462 0.057926
+v 0.129689 8.176309 0.057926
+v 1.154552 2.264460 0.067167
+v 0.112663 8.173306 0.067167
+v 1.136078 2.261203 0.072857
+v 0.094188 8.170050 0.072857
+v 1.116865 2.257815 0.074779
+v 0.074976 8.166661 0.074779
+v 1.097653 2.254427 0.072857
+v 0.055763 8.163274 0.072857
+v 1.079178 2.251170 0.067167
+v 0.037289 8.160016 0.067167
+v 1.062152 2.248168 0.057926
+v 0.020263 8.157014 0.057926
+v 1.047229 2.245536 0.045489
+v 0.005339 8.154383 0.045489
+v 1.034981 2.243377 0.030336
+v -0.006908 8.152224 0.030336
+v 1.025881 2.241772 0.013047
+v -0.016009 8.150619 0.013047
+v 1.020277 2.240784 -0.005712
+v -0.021613 8.149631 -0.005712
+v 1.018384 2.240450 -0.025221
+v -0.023505 8.149297 -0.025221
+v 1.020277 2.240784 -0.044730
+v -0.021613 8.149631 -0.044730
+v 1.025881 2.241772 -0.063490
+v -0.016009 8.150619 -0.063490
+v 1.034981 2.243377 -0.080778
+v -0.006908 8.152224 -0.080778
+v 1.047229 2.245536 -0.095932
+v 0.005339 8.154383 -0.095932
+v 1.062152 2.248168 -0.108368
+v 0.020263 8.157014 -0.108368
+v 1.079178 2.251170 -0.117609
+v 0.037289 8.160016 -0.117609
+v 1.097653 2.254427 -0.123300
+v 0.055763 8.163274 -0.123300
+vt 0.000000 0.000000
+vt 1.000000 0.000000
+vt 1.000000 1.000000
+vt 0.000000 1.000000
+vt 0.500000 1.000000
+vt 0.597545 0.990393
+vt 0.402456 0.990393
+vt 0.691342 0.961940
+vt 0.777785 0.915735
+vt 0.853553 0.853553
+vt 0.915735 0.777785
+vt 0.961940 0.691342
+vt 0.990393 0.597545
+vt 1.000000 0.500000
+vt 0.990393 0.402455
+vt 0.961940 0.308658
+vt 0.915735 0.222215
+vt 0.853553 0.146447
+vt 0.777785 0.084265
+vt 0.691342 0.038060
+vt 0.597545 0.009607
+vt 0.500000 0.000000
+vt 0.000000 0.500000
+vt 0.038060 0.308659
+vt 0.009607 0.402455
+vt 0.146447 0.853554
+vt 0.038060 0.691342
+vt 0.084266 0.777786
+vt 0.308659 0.961940
+vt 0.222215 0.915735
+vt 0.402455 0.009607
+vt 0.308658 0.038060
+vt 0.222215 0.084265
+vt 0.146446 0.146447
+vt 0.009607 0.597546
+vt 0.084265 0.222215
+vn 0.096528 0.017020 -0.995185
+vn 0.285876 0.050408 -0.956940
+vn 0.464235 0.081857 -0.881921
+vn 0.624757 0.110162 -0.773009
+vn 0.761264 0.134232 -0.634396
+vn 0.868521 0.153144 -0.471400
+vn 0.942403 0.166171 -0.290283
+vn 0.980066 0.172812 -0.098018
+vn 0.980066 0.172812 0.098018
+vn 0.942403 0.166171 0.290283
+vn 0.868521 0.153144 0.471399
+vn 0.761264 0.134232 0.634396
+vn 0.624757 0.110162 0.773009
+vn 0.464235 0.081857 0.881921
+vn 0.285876 0.050408 0.956940
+vn 0.096527 0.017020 0.995185
+vn -0.096528 -0.017021 0.995185
+vn -0.285875 -0.050407 0.956940
+vn -0.464235 -0.081857 0.881921
+vn -0.624754 -0.110161 0.773012
+vn -0.761268 -0.134232 0.634391
+vn -0.868524 -0.153144 0.471394
+vn -0.942401 -0.166171 0.290288
+vn -0.980066 -0.172812 0.098010
+vn -0.980065 -0.172812 -0.098018
+vn -0.942402 -0.166171 -0.290285
+vn -0.868521 -0.153144 -0.471401
+vn -0.761266 -0.134232 -0.634394
+vn -0.624751 -0.110161 -0.773014
+vn -0.464236 -0.081857 -0.881921
+vn -0.173717 0.984796 0.000356
+vn -0.096527 -0.017020 -0.995185
+vn -0.285873 -0.050407 -0.956941
+vn 0.173648 -0.984808 -0.000011
+vn 0.285874 0.050407 -0.956940
+vn 0.464237 0.081858 -0.881920
+vn 0.624752 0.110161 -0.773013
+vn 0.761267 0.134232 -0.634393
+vn 0.868524 0.153144 -0.471394
+vn 0.942402 0.166171 -0.290287
+vn 0.980065 0.172812 -0.098019
+vn 0.980065 0.172812 0.098019
+vn 0.942402 0.166171 0.290287
+vn 0.868524 0.153144 0.471394
+vn 0.761267 0.134232 0.634393
+vn 0.624752 0.110161 0.773013
+vn 0.464237 0.081858 0.881920
+vn 0.285874 0.050407 0.956940
+vn -0.096529 -0.017021 0.995185
+vn -0.285874 -0.050407 0.956940
+vn -0.624758 -0.110162 0.773008
+vn -0.761268 -0.134232 0.634392
+vn -0.868523 -0.153144 0.471397
+vn -0.942402 -0.166171 0.290284
+vn -0.980066 -0.172812 0.098018
+vn -0.980065 -0.172812 -0.098021
+vn -0.868521 -0.153144 -0.471400
+vn -0.761267 -0.134232 -0.634392
+vn -0.624756 -0.110161 -0.773010
+vn -0.464234 -0.081857 -0.881922
+vn -0.173640 0.984809 -0.000036
+vn -0.173648 0.984808 0.000040
+vn -0.173648 0.984808 0.000002
+vn -0.173650 0.984807 -0.000020
+vn -0.173652 0.984807 -0.000031
+vn -0.173635 0.984810 0.000027
+vn -0.173649 0.984808 -0.000009
+vn -0.173641 0.984809 0.000007
+vn -0.173650 0.984807 -0.000006
+vn -0.173640 0.984809 0.000006
+vn -0.173660 0.984806 -0.000014
+vn -0.173675 0.984803 0.000050
+vn -0.173675 0.984803 0.000049
+vn -0.173767 0.984787 -0.000044
+vn -0.173767 0.984787 0.000044
+vn -0.173642 0.984809 0.000000
+vn -0.173677 0.984803 0.000052
+vn -0.173717 0.984796 -0.000357
+vn -0.173646 0.984808 0.000005
+vn -0.173645 0.984808 0.000007
+vn -0.173703 0.984798 -0.000011
+vn -0.173684 0.984802 -0.000003
+vn -0.173667 0.984805 0.000002
+vn -0.173671 0.984804 -0.000015
+vn -0.173656 0.984806 -0.000026
+vn -0.173637 0.984810 0.000008
+vn -0.173643 0.984809 0.000005
+vn -0.096528 -0.017020 -0.995185
+vn -0.285872 -0.050407 -0.956941
+vn 0.173648 -0.984808 -0.000000
+vn 0.173647 -0.984808 0.000006
+vn 0.173647 -0.984808 0.000005
+vn 0.173652 -0.984807 -0.000009
+vn 0.173645 -0.984808 0.000005
+vn 0.173653 -0.984807 -0.000007
+vn 0.173643 -0.984809 0.000006
+vn 0.173652 -0.984807 -0.000003
+vn 0.173643 -0.984809 0.000004
+vn 0.173651 -0.984807 -0.000002
+vn 0.173650 -0.984807 -0.000001
+vn 0.173647 -0.984808 0.000000
+vn 0.173649 -0.984808 0.000000
+vn 0.173652 -0.984807 0.000001
+vn 0.173642 -0.984809 -0.000003
+vn 0.173653 -0.984807 0.000003
+vn 0.173642 -0.984809 -0.000004
+vn 0.173655 -0.984806 0.000007
+vn 0.173641 -0.984809 -0.000008
+vn 0.173658 -0.984806 0.000013
+vn 0.173645 -0.984808 -0.000007
+vn 0.173654 -0.984807 0.000007
+vn 0.173646 -0.984808 -0.000004
+s off
+f 129/73/216 130/74/216 132/75/216
+f 131/73/217 132/74/217 134/75/217
+f 133/73/218 134/74/218 136/75/218
+f 135/73/219 136/74/219 138/75/219
+f 137/73/220 138/74/220 140/75/220
+f 139/73/221 140/74/221 142/75/221
+f 141/73/222 142/74/222 144/75/222
+f 143/73/223 144/74/223 146/75/223
+f 145/73/224 146/74/224 148/75/224
+f 147/73/225 148/74/225 150/75/225
+f 149/73/226 150/74/226 152/75/226
+f 151/73/227 152/74/227 154/75/227
+f 153/73/228 154/74/228 156/75/228
+f 155/73/229 156/74/229 158/75/229
+f 157/73/230 158/74/230 160/75/230
+f 159/73/231 160/74/231 162/75/231
+f 161/73/232 162/74/232 163/76/232
+f 163/73/233 164/74/233 165/76/233
+f 165/73/234 166/74/234 167/76/234
+f 167/73/235 168/74/235 169/76/235
+f 169/73/236 170/74/236 171/76/236
+f 171/73/237 172/74/237 173/76/237
+f 173/73/238 174/74/238 175/76/238
+f 175/73/239 176/74/239 177/76/239
+f 177/73/240 178/74/240 179/76/240
+f 179/73/241 180/74/241 181/76/241
+f 181/73/242 182/74/242 183/76/242
+f 183/73/243 184/74/243 185/76/243
+f 185/73/244 186/74/244 187/76/244
+f 187/73/245 188/74/245 189/76/245
+f 132/77/246 130/78/246 134/79/246
+f 191/73/247 192/74/247 129/76/247
+f 189/73/248 190/74/248 191/76/248
+f 129/77/249 131/78/249 191/79/249
+f 131/76/216 129/73/216 132/75/216
+f 133/76/250 131/73/250 134/75/250
+f 135/76/251 133/73/251 136/75/251
+f 137/76/252 135/73/252 138/75/252
+f 139/76/253 137/73/253 140/75/253
+f 141/76/254 139/73/254 142/75/254
+f 143/76/255 141/73/255 144/75/255
+f 145/76/256 143/73/256 146/75/256
+f 147/76/257 145/73/257 148/75/257
+f 149/76/258 147/73/258 150/75/258
+f 151/76/259 149/73/259 152/75/259
+f 153/76/260 151/73/260 154/75/260
+f 155/76/261 153/73/261 156/75/261
+f 157/76/262 155/73/262 158/75/262
+f 159/76/263 157/73/263 160/75/263
+f 161/76/231 159/73/231 162/75/231
+f 162/74/264 164/75/264 163/76/264
+f 164/74/265 166/75/265 165/76/265
+f 166/74/234 168/75/234 167/76/234
+f 168/74/266 170/75/266 169/76/266
+f 170/74/267 172/75/267 171/76/267
+f 172/74/268 174/75/268 173/76/268
+f 174/74/269 176/75/269 175/76/269
+f 176/74/270 178/75/270 177/76/270
+f 178/74/271 180/75/271 179/76/271
+f 180/74/241 182/75/241 181/76/241
+f 182/74/272 184/75/272 183/76/272
+f 184/74/273 186/75/273 185/76/273
+f 186/74/274 188/75/274 187/76/274
+f 188/74/275 190/75/275 189/76/275
+f 130/78/276 192/80/276 134/79/276
+f 192/80/277 190/81/277 134/79/277
+f 190/81/278 188/82/278 134/79/278
+f 188/82/279 186/83/279 134/79/279
+f 186/83/280 184/84/280 134/79/280
+f 184/84/281 182/85/281 134/79/281
+f 182/85/282 180/86/282 134/79/282
+f 180/86/283 178/87/283 134/79/283
+f 178/87/284 176/88/284 134/79/284
+f 176/88/285 174/89/285 134/79/285
+f 174/89/286 172/90/286 134/79/286
+f 170/91/287 168/92/287 166/93/287
+f 170/91/288 166/93/288 164/94/288
+f 148/95/289 152/96/289 150/97/289
+f 140/98/290 144/99/290 142/100/290
+f 172/90/287 170/91/287 164/94/287
+f 134/79/291 172/90/291 164/94/291
+f 136/101/292 134/79/292 138/102/292
+f 134/79/291 164/94/291 138/102/291
+f 162/103/293 160/104/293 158/105/293
+f 162/103/294 158/105/294 156/106/294
+f 146/107/295 152/96/295 148/95/295
+f 146/107/296 154/108/296 152/96/296
+f 144/99/297 154/108/297 146/107/297
+f 140/98/298 154/108/298 144/99/298
+f 138/102/299 164/94/299 140/98/299
+f 162/103/300 156/106/300 154/108/300
+f 164/94/301 162/103/301 140/98/301
+f 162/103/302 154/108/302 140/98/302
+f 192/74/303 130/75/303 129/76/303
+f 190/74/304 192/75/304 191/76/304
+f 131/78/305 133/80/305 191/79/305
+f 133/80/306 135/81/306 191/79/306
+f 135/81/305 137/82/305 191/79/305
+f 137/82/307 139/83/307 191/79/307
+f 139/83/308 141/84/308 191/79/308
+f 141/84/309 143/85/309 191/79/309
+f 143/85/310 145/86/310 191/79/310
+f 145/86/311 147/87/311 191/79/311
+f 147/87/312 149/88/312 191/79/312
+f 149/88/313 151/89/313 191/79/313
+f 151/89/314 153/90/314 191/79/314
+f 153/90/305 155/91/305 191/79/305
+f 155/91/315 157/92/315 191/79/315
+f 157/92/305 159/93/305 191/79/305
+f 159/93/316 161/94/316 191/79/316
+f 161/94/305 163/103/305 191/79/305
+f 163/103/305 165/104/305 191/79/305
+f 165/104/317 167/105/317 191/79/317
+f 167/105/317 169/106/317 191/79/317
+f 169/106/318 171/108/318 191/79/318
+f 171/108/319 173/96/319 191/79/319
+f 173/96/320 175/97/320 191/79/320
+f 175/97/321 177/95/321 191/79/321
+f 177/95/322 179/107/322 191/79/322
+f 179/107/323 181/99/323 191/79/323
+f 181/99/324 183/100/324 191/79/324
+f 183/100/325 185/98/325 191/79/325
+f 185/98/326 187/102/326 189/101/326
+f 191/79/327 185/98/327 189/101/327
+v -1.178897 2.277600 -0.129009
+v -0.137008 8.186446 -0.129009
+v -1.159685 2.274212 -0.127088
+v -0.117795 8.183058 -0.127088
+v -1.141210 2.270954 -0.121397
+v -0.099321 8.179801 -0.121397
+v -1.124184 2.267952 -0.112156
+v -0.082295 8.176799 -0.112156
+v -1.109261 2.265321 -0.099720
+v -0.067371 8.174168 -0.099720
+v -1.097013 2.263161 -0.084566
+v -0.055124 8.172008 -0.084566
+v -1.087913 2.261557 -0.067277
+v -0.046023 8.170403 -0.067277
+v -1.082309 2.260568 -0.048518
+v -0.040419 8.169415 -0.048518
+v -1.080417 2.260235 -0.029009
+v -0.038527 8.169081 -0.029009
+v -1.082309 2.260568 -0.009500
+v -0.040419 8.169415 -0.009500
+v -1.087913 2.261557 0.009259
+v -0.046023 8.170403 0.009259
+v -1.097013 2.263161 0.026548
+v -0.055124 8.172008 0.026548
+v -1.109261 2.265321 0.041702
+v -0.067371 8.174168 0.041702
+v -1.124184 2.267952 0.054138
+v -0.082295 8.176799 0.054138
+v -1.141210 2.270954 0.063379
+v -0.099321 8.179801 0.063379
+v -1.159685 2.274212 0.069069
+v -0.117795 8.183058 0.069069
+v -1.178897 2.277600 0.070991
+v -0.137008 8.186446 0.070991
+v -1.198110 2.280987 0.069069
+v -0.156220 8.189834 0.069069
+v -1.216584 2.284245 0.063379
+v -0.174695 8.193091 0.063379
+v -1.233610 2.287247 0.054138
+v -0.191721 8.196094 0.054138
+v -1.248534 2.289878 0.041702
+v -0.206644 8.198725 0.041702
+v -1.260781 2.292038 0.026548
+v -0.218892 8.200884 0.026548
+v -1.269882 2.293643 0.009259
+v -0.227992 8.202489 0.009259
+v -1.275486 2.294631 -0.009500
+v -0.233596 8.203477 -0.009500
+v -1.277378 2.294964 -0.029009
+v -0.235489 8.203811 -0.029009
+v -1.275486 2.294631 -0.048518
+v -0.233596 8.203477 -0.048518
+v -1.269882 2.293643 -0.067278
+v -0.227992 8.202489 -0.067278
+v -1.260781 2.292038 -0.084566
+v -0.218892 8.200884 -0.084566
+v -1.248534 2.289878 -0.099720
+v -0.206644 8.198725 -0.099720
+v -1.233610 2.287247 -0.112156
+v -0.191721 8.196094 -0.112156
+v -1.216584 2.284245 -0.121397
+v -0.174695 8.193091 -0.121397
+v -1.198110 2.280987 -0.127088
+v -0.156220 8.189834 -0.127088
+vt 0.000000 0.000000
+vt 1.000000 0.000000
+vt 1.000000 1.000000
+vt 0.000000 1.000000
+vt 0.500000 1.000000
+vt 0.597545 0.990393
+vt 0.402456 0.990393
+vt 0.691342 0.961940
+vt 0.777785 0.915735
+vt 0.853553 0.853553
+vt 0.915735 0.777785
+vt 0.961940 0.691342
+vt 0.990393 0.597545
+vt 1.000000 0.500000
+vt 0.990393 0.402455
+vt 0.961940 0.308658
+vt 0.915735 0.222215
+vt 0.853553 0.146447
+vt 0.777785 0.084265
+vt 0.691342 0.038060
+vt 0.597545 0.009607
+vt 0.500000 0.000000
+vt 0.402455 0.009607
+vt 0.308658 0.038060
+vt 0.222215 0.084265
+vt 0.146446 0.146447
+vt 0.084265 0.222215
+vt 0.038060 0.308659
+vt 0.009607 0.402455
+vt 0.000000 0.500000
+vt 0.009607 0.597546
+vt 0.038060 0.691342
+vt 0.084266 0.777786
+vt 0.146447 0.853554
+vt 0.222215 0.915735
+vt 0.308659 0.961940
+vn 0.096528 -0.017020 -0.995185
+vn 0.285876 -0.050408 -0.956940
+vn 0.464236 -0.081857 -0.881921
+vn 0.624756 -0.110161 -0.773010
+vn 0.761265 -0.134232 -0.634395
+vn 0.868521 -0.153144 -0.471400
+vn 0.942403 -0.166171 -0.290282
+vn 0.980065 -0.172812 -0.098021
+vn 0.980066 -0.172812 0.098018
+vn 0.942402 -0.166171 0.290285
+vn 0.868522 -0.153144 0.471399
+vn 0.761265 -0.134232 0.634395
+vn 0.624756 -0.110161 0.773010
+vn 0.464235 -0.081857 0.881922
+vn 0.285876 -0.050408 0.956940
+vn 0.096527 -0.017020 0.995185
+vn -0.096528 0.017020 0.995185
+vn -0.285876 0.050408 0.956940
+vn -0.464234 0.081857 0.881922
+vn -0.624758 0.110162 0.773008
+vn -0.761264 0.134231 0.634396
+vn -0.868524 0.153144 0.471394
+vn -0.942401 0.166171 0.290288
+vn -0.980066 0.172812 0.098014
+vn -0.980066 0.172812 -0.098014
+vn -0.942403 0.166171 -0.290281
+vn -0.868522 0.153144 -0.471398
+vn -0.761268 0.134232 -0.634392
+vn -0.624751 0.110161 -0.773014
+vn -0.464236 0.081857 -0.881921
+vn 0.173717 0.984795 -0.000357
+vn -0.096527 0.017020 -0.995185
+vn -0.285874 0.050407 -0.956940
+vn -0.173648 -0.984808 0.000011
+vn 0.285874 -0.050407 -0.956940
+vn 0.464237 -0.081858 -0.881920
+vn 0.624755 -0.110161 -0.773011
+vn 0.761264 -0.134231 -0.634396
+vn 0.868524 -0.153144 -0.471394
+vn 0.942402 -0.166171 -0.290287
+vn 0.980066 -0.172812 -0.098013
+vn 0.980066 -0.172812 0.098013
+vn 0.942402 -0.166171 0.290287
+vn 0.868524 -0.153144 0.471394
+vn 0.761264 -0.134231 0.634396
+vn 0.624755 -0.110161 0.773011
+vn 0.464237 -0.081858 0.881920
+vn 0.285874 -0.050407 0.956940
+vn -0.096529 0.017021 0.995185
+vn -0.285874 0.050407 0.956940
+vn -0.464235 0.081857 0.881921
+vn -0.624756 0.110161 0.773010
+vn -0.761269 0.134232 0.634391
+vn -0.868522 0.153144 0.471398
+vn -0.942403 0.166171 0.290282
+vn -0.980066 0.172812 0.098018
+vn -0.980065 0.172812 -0.098019
+vn -0.942402 0.166171 -0.290285
+vn -0.868521 0.153144 -0.471400
+vn -0.761267 0.134232 -0.634392
+vn -0.624756 0.110161 -0.773010
+vn -0.464234 0.081857 -0.881922
+vn 0.173640 0.984809 0.000036
+vn 0.173648 0.984808 -0.000041
+vn 0.173648 0.984808 -0.000002
+vn 0.173650 0.984807 0.000019
+vn 0.173652 0.984807 0.000031
+vn 0.173635 0.984810 -0.000027
+vn 0.173649 0.984808 0.000009
+vn 0.173641 0.984809 -0.000007
+vn 0.173650 0.984807 0.000006
+vn 0.173641 0.984809 -0.000005
+vn 0.173660 0.984806 0.000014
+vn 0.173629 0.984811 -0.000011
+vn 0.173638 0.984810 -0.000005
+vn 0.173649 0.984808 0.000001
+vn 0.173659 0.984806 0.000005
+vn 0.173636 0.984810 -0.000002
+vn 0.173683 0.984802 0.000007
+vn 0.173611 0.984814 0.000000
+vn 0.173649 0.984808 0.000000
+vn 0.173635 0.984810 0.000001
+vn 0.173681 0.984802 -0.000008
+vn 0.173674 0.984803 -0.000006
+vn 0.173644 0.984808 0.000007
+vn 0.173666 0.984805 -0.000005
+vn 0.173677 0.984803 -0.000016
+vn 0.173633 0.984810 0.000028
+vn 0.173518 0.984831 0.000143
+vn 0.173638 0.984810 0.000022
+vn -0.096528 0.017020 -0.995185
+vn -0.285872 0.050407 -0.956941
+vn -0.173648 -0.984808 0.000000
+vn -0.173647 -0.984808 -0.000006
+vn -0.173648 -0.984808 -0.000002
+vn -0.173647 -0.984808 -0.000003
+vn -0.173652 -0.984807 0.000009
+vn -0.173645 -0.984808 -0.000005
+vn -0.173652 -0.984807 0.000005
+vn -0.173644 -0.984809 -0.000005
+vn -0.173652 -0.984807 0.000003
+vn -0.173643 -0.984809 -0.000004
+vn -0.173650 -0.984807 0.000001
+vn -0.173680 -0.984802 0.000024
+vn -0.173592 -0.984818 0.000023
+vn -0.173693 -0.984800 0.000010
+vn -0.173644 -0.984808 -0.000000
+vn -0.173638 -0.984810 0.000013
+vn -0.173653 -0.984807 -0.000003
+vn -0.173644 -0.984808 0.000011
+vn -0.173646 -0.984808 0.000007
+vn -0.173645 -0.984808 -0.000007
+vn -0.173651 -0.984807 0.000003
+vn -0.173649 -0.984808 0.000000
+vn -0.173648 -0.984808 0.000001
+vn -0.173647 -0.984808 0.000002
+vn -0.173640 -0.984809 -0.000005
+vn -0.173647 -0.984808 -0.000001
+s off
+f 193/109/328 194/110/328 196/111/328
+f 195/109/329 196/110/329 198/111/329
+f 197/109/330 198/110/330 200/111/330
+f 199/109/331 200/110/331 202/111/331
+f 201/109/332 202/110/332 204/111/332
+f 203/109/333 204/110/333 206/111/333
+f 205/109/334 206/110/334 208/111/334
+f 207/109/335 208/110/335 210/111/335
+f 209/109/336 210/110/336 212/111/336
+f 211/109/337 212/110/337 214/111/337
+f 213/109/338 214/110/338 216/111/338
+f 215/109/339 216/110/339 218/111/339
+f 217/109/340 218/110/340 220/111/340
+f 219/109/341 220/110/341 222/111/341
+f 221/109/342 222/110/342 224/111/342
+f 223/109/343 224/110/343 226/111/343
+f 225/109/344 226/110/344 227/112/344
+f 227/109/345 228/110/345 229/112/345
+f 229/109/346 230/110/346 231/112/346
+f 231/109/347 232/110/347 233/112/347
+f 233/109/348 234/110/348 235/112/348
+f 235/109/349 236/110/349 237/112/349
+f 237/109/350 238/110/350 239/112/350
+f 239/109/351 240/110/351 241/112/351
+f 241/109/352 242/110/352 243/112/352
+f 243/109/353 244/110/353 245/112/353
+f 245/109/354 246/110/354 247/112/354
+f 247/109/355 248/110/355 249/112/355
+f 249/109/356 250/110/356 251/112/356
+f 251/109/357 252/110/357 253/112/357
+f 196/113/358 194/114/358 198/115/358
+f 255/109/359 256/110/359 193/112/359
+f 253/109/360 254/110/360 255/112/360
+f 193/113/361 195/114/361 255/115/361
+f 195/112/328 193/109/328 196/111/328
+f 197/112/362 195/109/362 198/111/362
+f 199/112/363 197/109/363 200/111/363
+f 201/112/364 199/109/364 202/111/364
+f 203/112/365 201/109/365 204/111/365
+f 205/112/366 203/109/366 206/111/366
+f 207/112/367 205/109/367 208/111/367
+f 209/112/368 207/109/368 210/111/368
+f 211/112/369 209/109/369 212/111/369
+f 213/112/370 211/109/370 214/111/370
+f 215/112/371 213/109/371 216/111/371
+f 217/112/372 215/109/372 218/111/372
+f 219/112/373 217/109/373 220/111/373
+f 221/112/374 219/109/374 222/111/374
+f 223/112/375 221/109/375 224/111/375
+f 225/112/343 223/109/343 226/111/343
+f 226/110/376 228/111/376 227/112/376
+f 228/110/377 230/111/377 229/112/377
+f 230/110/378 232/111/378 231/112/378
+f 232/110/379 234/111/379 233/112/379
+f 234/110/380 236/111/380 235/112/380
+f 236/110/381 238/111/381 237/112/381
+f 238/110/382 240/111/382 239/112/382
+f 240/110/383 242/111/383 241/112/383
+f 242/110/384 244/111/384 243/112/384
+f 244/110/385 246/111/385 245/112/385
+f 246/110/386 248/111/386 247/112/386
+f 248/110/387 250/111/387 249/112/387
+f 250/110/388 252/111/388 251/112/388
+f 252/110/389 254/111/389 253/112/389
+f 194/114/390 256/116/390 198/115/390
+f 256/116/391 254/117/391 198/115/391
+f 254/117/392 252/118/392 198/115/392
+f 252/118/393 250/119/393 198/115/393
+f 250/119/394 248/120/394 198/115/394
+f 248/120/395 246/121/395 198/115/395
+f 246/121/396 244/122/396 198/115/396
+f 244/122/397 242/123/397 198/115/397
+f 242/123/398 240/124/398 198/115/398
+f 240/124/399 238/125/399 198/115/399
+f 238/125/400 236/126/400 198/115/400
+f 236/126/401 234/127/401 198/115/401
+f 234/127/402 232/128/402 198/115/402
+f 232/128/403 230/129/403 198/115/403
+f 230/129/404 228/130/404 198/115/404
+f 228/130/405 226/131/405 198/115/405
+f 226/131/406 224/132/406 198/115/406
+f 224/132/407 222/133/407 198/115/407
+f 222/133/408 220/134/408 198/115/408
+f 220/134/409 218/135/409 198/115/409
+f 218/135/410 216/136/410 198/115/410
+f 216/136/411 214/137/411 198/115/411
+f 214/137/412 212/138/412 198/115/412
+f 212/138/413 210/139/413 198/115/413
+f 210/139/398 208/140/398 198/115/398
+f 208/140/414 206/141/414 198/115/414
+f 206/141/415 204/142/415 198/115/415
+f 204/142/416 202/143/416 200/144/416
+f 198/115/417 204/142/417 200/144/417
+f 256/110/418 194/111/418 193/112/418
+f 254/110/419 256/111/419 255/112/419
+f 195/114/420 197/116/420 255/115/420
+f 197/116/421 199/117/421 255/115/421
+f 199/117/422 201/118/422 255/115/422
+f 201/118/423 203/119/423 255/115/423
+f 203/119/424 205/120/424 255/115/424
+f 205/120/425 207/121/425 255/115/425
+f 207/121/426 209/122/426 255/115/426
+f 209/122/427 211/123/427 255/115/427
+f 211/123/428 213/124/428 255/115/428
+f 213/124/429 215/125/429 255/115/429
+f 215/125/430 217/126/430 255/115/430
+f 237/136/431 233/134/431 235/135/431
+f 247/141/432 243/139/432 245/140/432
+f 241/138/433 237/136/433 239/137/433
+f 241/138/434 233/134/434 237/136/434
+f 251/143/435 247/141/435 249/142/435
+f 251/143/436 243/139/436 247/141/436
+f 219/127/437 221/128/437 223/129/437
+f 219/127/438 223/129/438 225/130/438
+f 233/134/439 229/132/439 231/133/439
+f 241/138/440 229/132/440 233/134/440
+f 255/115/437 251/143/437 253/144/437
+f 255/115/441 217/126/441 251/143/441
+f 251/143/441 217/126/441 243/139/441
+f 219/127/442 225/130/442 227/131/442
+f 217/126/443 219/127/443 227/131/443
+f 243/139/420 217/126/420 227/131/420
+f 241/138/444 243/139/444 229/132/444
+f 243/139/445 227/131/445 229/132/445
+v -3.858562 2.027707 3.871576
+v -3.858562 2.027707 -3.907549
+v 3.920563 2.027707 -3.907549
+v 3.920563 2.027707 3.871576
+v -3.858562 2.306528 3.871576
+v -3.858562 2.306528 -3.907549
+v 3.920563 2.306528 -3.907549
+v 3.920563 2.306528 3.871576
+vt 0.000000 0.000000
+vt 1.000000 0.000000
+vt 0.000000 1.000000
+vt 1.000000 1.000000
+vn -1.000000 0.000000 0.000000
+vn 0.000000 0.000000 -1.000000
+vn 1.000000 -0.000000 0.000000
+vn 0.000000 0.000000 1.000000
+vn 0.000000 -1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+s off
+f 261/145/446 262/146/446 257/147/446
+f 262/145/447 263/146/447 258/147/447
+f 263/145/448 264/146/448 260/148/448
+f 264/145/449 261/146/449 257/148/449
+f 257/145/450 258/146/450 259/148/450
+f 264/145/451 263/146/451 262/148/451
+f 262/146/446 258/148/446 257/147/446
+f 263/146/447 259/148/447 258/147/447
+f 259/147/448 263/145/448 260/148/448
+f 260/147/449 264/145/449 257/148/449
+f 260/147/450 257/145/450 259/148/450
+f 261/147/451 264/145/451 262/148/451
+v 2.043798 0.024218 -3.001008
+v 2.043798 2.024218 -3.001008
+v 2.238889 0.024218 -2.981793
+v 2.238889 2.024218 -2.981793
+v 2.426482 0.024218 -2.924888
+v 2.426482 2.024218 -2.924888
+v 2.599369 0.024218 -2.832478
+v 2.599369 2.024218 -2.832478
+v 2.750905 0.024218 -2.708115
+v 2.750905 2.024218 -2.708115
+v 2.875268 0.024218 -2.556578
+v 2.875268 2.024218 -2.556578
+v 2.967678 0.024218 -2.383692
+v 2.967678 2.024218 -2.383692
+v 3.024584 0.024218 -2.196099
+v 3.024584 2.024218 -2.196099
+v 3.043798 0.024218 -2.001008
+v 3.043798 2.024218 -2.001008
+v 3.024584 0.024218 -1.805918
+v 3.024584 2.024218 -1.805918
+v 2.967678 0.024218 -1.618325
+v 2.967678 2.024218 -1.618325
+v 2.875268 0.024218 -1.445438
+v 2.875268 2.024218 -1.445438
+v 2.750905 0.024218 -1.293901
+v 2.750905 2.024218 -1.293901
+v 2.599369 0.024218 -1.169539
+v 2.599369 2.024218 -1.169539
+v 2.426482 0.024218 -1.077129
+v 2.426482 2.024218 -1.077129
+v 2.238889 0.024218 -1.020223
+v 2.238889 2.024218 -1.020223
+v 2.043798 0.024218 -1.001008
+v 2.043798 2.024218 -1.001008
+v 1.848708 0.024218 -1.020223
+v 1.848708 2.024218 -1.020223
+v 1.661115 0.024218 -1.077129
+v 1.661115 2.024218 -1.077129
+v 1.488228 0.024218 -1.169539
+v 1.488228 2.024218 -1.169539
+v 1.336691 0.024218 -1.293902
+v 1.336691 2.024218 -1.293902
+v 1.212328 0.024218 -1.445439
+v 1.212328 2.024218 -1.445439
+v 1.119919 0.024218 -1.618326
+v 1.119919 2.024218 -1.618326
+v 1.063013 0.024218 -1.805919
+v 1.063013 2.024218 -1.805919
+v 1.043798 0.024218 -2.001009
+v 1.043798 2.024218 -2.001009
+v 1.063013 0.024218 -2.196100
+v 1.063013 2.024218 -2.196100
+v 1.119919 0.024218 -2.383693
+v 1.119919 2.024218 -2.383693
+v 1.212330 0.024218 -2.556580
+v 1.212330 2.024218 -2.556580
+v 1.336693 0.024218 -2.708116
+v 1.336693 2.024218 -2.708116
+v 1.488229 0.024218 -2.832479
+v 1.488229 2.024218 -2.832479
+v 1.661116 0.024218 -2.924888
+v 1.661116 2.024218 -2.924888
+v 1.848710 0.024218 -2.981794
+v 1.848710 2.024218 -2.981794
+vt 0.000000 0.000000
+vt 1.000000 0.000000
+vt 1.000000 1.000000
+vt 0.000000 1.000000
+vt 0.500000 1.000000
+vt 0.597545 0.990393
+vt 0.402456 0.990393
+vt 0.691342 0.961940
+vt 0.777785 0.915735
+vt 0.853553 0.853553
+vt 0.915735 0.777785
+vt 0.961940 0.691342
+vt 0.990393 0.597545
+vt 1.000000 0.500000
+vt 0.990393 0.402455
+vt 0.961940 0.308658
+vt 0.915735 0.222215
+vt 0.853553 0.146447
+vt 0.777785 0.084265
+vt 0.691342 0.038060
+vt 0.597545 0.009607
+vt 0.500000 0.000000
+vt 0.402455 0.009607
+vt 0.308658 0.038060
+vt 0.222215 0.084265
+vt 0.146446 0.146447
+vt 0.084265 0.222215
+vt 0.038060 0.308659
+vt 0.009607 0.402455
+vt 0.000000 0.500000
+vt 0.009607 0.597546
+vt 0.038060 0.691342
+vt 0.084266 0.777786
+vt 0.146447 0.853554
+vt 0.222215 0.915735
+vt 0.308659 0.961940
+vn 0.098018 0.000000 -0.995185
+vn 0.290285 0.000000 -0.956940
+vn 0.471397 0.000000 -0.881921
+vn 0.634393 0.000000 -0.773011
+vn 0.773011 0.000000 -0.634393
+vn 0.881921 0.000000 -0.471398
+vn 0.956940 0.000000 -0.290285
+vn 0.995185 0.000000 -0.098017
+vn 0.995185 0.000000 0.098017
+vn 0.956940 0.000000 0.290285
+vn 0.881921 0.000000 0.471397
+vn 0.773011 0.000000 0.634393
+vn 0.634393 0.000000 0.773011
+vn 0.471397 0.000000 0.881921
+vn 0.290285 0.000000 0.956940
+vn 0.098017 0.000000 0.995185
+vn -0.098018 0.000000 0.995185
+vn -0.290285 0.000000 0.956940
+vn -0.471397 0.000000 0.881921
+vn -0.634394 0.000000 0.773010
+vn -0.773011 0.000000 0.634393
+vn -0.881922 0.000000 0.471396
+vn -0.956941 0.000000 0.290283
+vn -0.995185 0.000000 0.098017
+vn -0.995185 -0.000000 -0.098018
+vn -0.956940 -0.000000 -0.290286
+vn -0.881921 -0.000000 -0.471398
+vn -0.773010 -0.000000 -0.634394
+vn -0.634392 -0.000000 -0.773012
+vn -0.471396 -0.000000 -0.881922
+vn -0.098016 -0.000000 -0.995185
+vn -0.290283 -0.000000 -0.956941
+s off
+f 265/149/452 266/150/452 268/151/452
+f 267/149/453 268/150/453 270/151/453
+f 269/149/454 270/150/454 272/151/454
+f 271/149/455 272/150/455 274/151/455
+f 273/149/456 274/150/456 276/151/456
+f 275/149/457 276/150/457 278/151/457
+f 277/149/458 278/150/458 280/151/458
+f 279/149/459 280/150/459 282/151/459
+f 281/149/460 282/150/460 284/151/460
+f 283/149/461 284/150/461 286/151/461
+f 285/149/462 286/150/462 288/151/462
+f 287/149/463 288/150/463 290/151/463
+f 289/149/464 290/150/464 292/151/464
+f 291/149/465 292/150/465 294/151/465
+f 293/149/466 294/150/466 296/151/466
+f 295/149/467 296/150/467 298/151/467
+f 297/149/468 298/150/468 299/152/468
+f 299/149/469 300/150/469 301/152/469
+f 301/149/470 302/150/470 303/152/470
+f 303/149/471 304/150/471 305/152/471
+f 305/149/472 306/150/472 307/152/472
+f 307/149/473 308/150/473 309/152/473
+f 309/149/474 310/150/474 311/152/474
+f 311/149/475 312/150/475 313/152/475
+f 313/149/476 314/150/476 315/152/476
+f 315/149/477 316/150/477 317/152/477
+f 317/149/478 318/150/478 319/152/478
+f 319/149/479 320/150/479 321/152/479
+f 321/149/480 322/150/480 323/152/480
+f 323/149/481 324/150/481 325/152/481
+f 268/153/451 266/154/451 270/155/451
+f 327/149/482 328/150/482 265/152/482
+f 325/149/483 326/150/483 327/152/483
+f 265/153/450 267/154/450 327/155/450
+f 267/152/452 265/149/452 268/151/452
+f 269/152/453 267/149/453 270/151/453
+f 271/152/454 269/149/454 272/151/454
+f 273/152/455 271/149/455 274/151/455
+f 275/152/456 273/149/456 276/151/456
+f 277/152/457 275/149/457 278/151/457
+f 279/152/458 277/149/458 280/151/458
+f 281/152/459 279/149/459 282/151/459
+f 283/152/460 281/149/460 284/151/460
+f 285/152/461 283/149/461 286/151/461
+f 287/152/462 285/149/462 288/151/462
+f 289/152/463 287/149/463 290/151/463
+f 291/152/464 289/149/464 292/151/464
+f 293/152/465 291/149/465 294/151/465
+f 295/152/466 293/149/466 296/151/466
+f 297/152/467 295/149/467 298/151/467
+f 298/150/468 300/151/468 299/152/468
+f 300/150/469 302/151/469 301/152/469
+f 302/150/470 304/151/470 303/152/470
+f 304/150/471 306/151/471 305/152/471
+f 306/150/472 308/151/472 307/152/472
+f 308/150/473 310/151/473 309/152/473
+f 310/150/474 312/151/474 311/152/474
+f 312/150/475 314/151/475 313/152/475
+f 314/150/476 316/151/476 315/152/476
+f 316/150/477 318/151/477 317/152/477
+f 318/150/478 320/151/478 319/152/478
+f 320/150/479 322/151/479 321/152/479
+f 322/150/480 324/151/480 323/152/480
+f 324/150/481 326/151/481 325/152/481
+f 266/154/451 328/156/451 270/155/451
+f 328/156/451 326/157/451 270/155/451
+f 326/157/451 324/158/451 270/155/451
+f 324/158/451 322/159/451 270/155/451
+f 322/159/451 320/160/451 270/155/451
+f 320/160/451 318/161/451 270/155/451
+f 318/161/451 316/162/451 270/155/451
+f 316/162/451 314/163/451 270/155/451
+f 314/163/451 312/164/451 270/155/451
+f 312/164/451 310/165/451 270/155/451
+f 310/165/451 308/166/451 270/155/451
+f 308/166/451 306/167/451 270/155/451
+f 306/167/451 304/168/451 270/155/451
+f 304/168/451 302/169/451 270/155/451
+f 302/169/451 300/170/451 270/155/451
+f 300/170/451 298/171/451 270/155/451
+f 298/171/451 296/172/451 270/155/451
+f 296/172/451 294/173/451 270/155/451
+f 294/173/451 292/174/451 270/155/451
+f 292/174/451 290/175/451 270/155/451
+f 290/175/451 288/176/451 270/155/451
+f 288/176/451 286/177/451 270/155/451
+f 286/177/451 284/178/451 270/155/451
+f 284/178/451 282/179/451 270/155/451
+f 282/179/451 280/180/451 270/155/451
+f 280/180/451 278/181/451 270/155/451
+f 278/181/451 276/182/451 270/155/451
+f 276/182/451 274/183/451 270/155/451
+f 274/183/451 272/184/451 270/155/451
+f 328/150/482 266/151/482 265/152/482
+f 326/150/483 328/151/483 327/152/483
+f 267/154/450 269/156/450 327/155/450
+f 269/156/450 271/157/450 327/155/450
+f 271/157/450 273/158/450 327/155/450
+f 273/158/450 275/159/450 327/155/450
+f 275/159/450 277/160/450 327/155/450
+f 277/160/450 279/161/450 327/155/450
+f 279/161/450 281/162/450 327/155/450
+f 281/162/450 283/163/450 327/155/450
+f 283/163/450 285/164/450 327/155/450
+f 285/164/450 287/165/450 327/155/450
+f 287/165/450 289/166/450 327/155/450
+f 289/166/450 291/167/450 327/155/450
+f 291/167/450 293/168/450 327/155/450
+f 293/168/450 295/169/450 327/155/450
+f 295/169/450 297/170/450 327/155/450
+f 297/170/450 299/171/450 327/155/450
+f 299/171/450 301/172/450 327/155/450
+f 301/172/450 303/173/450 327/155/450
+f 303/173/450 305/174/450 327/155/450
+f 305/174/450 307/175/450 327/155/450
+f 307/175/450 309/176/450 327/155/450
+f 309/176/450 311/177/450 327/155/450
+f 311/177/450 313/178/450 327/155/450
+f 313/178/450 315/179/450 327/155/450
+f 315/179/450 317/180/450 327/155/450
+f 317/180/450 319/181/450 327/155/450
+f 319/181/450 321/182/450 327/155/450
+f 321/182/450 323/183/450 325/184/450
+f 327/155/450 321/182/450 325/184/450
+v -2.014818 0.007922 0.998641
+v -2.014818 2.007922 0.998641
+v -1.819728 0.007922 1.017856
+v -1.819728 2.007922 1.017856
+v -1.632135 0.007922 1.074762
+v -1.632135 2.007922 1.074762
+v -1.459248 0.007922 1.167172
+v -1.459248 2.007922 1.167172
+v -1.307712 0.007922 1.291534
+v -1.307712 2.007922 1.291534
+v -1.183349 0.007922 1.443071
+v -1.183349 2.007922 1.443071
+v -1.090939 0.007922 1.615958
+v -1.090939 2.007922 1.615958
+v -1.034033 0.007922 1.803551
+v -1.034033 2.007922 1.803551
+v -1.014818 0.007922 1.998641
+v -1.014818 2.007922 1.998641
+v -1.034033 0.007922 2.193732
+v -1.034033 2.007922 2.193732
+v -1.090939 0.007922 2.381325
+v -1.090939 2.007922 2.381325
+v -1.183349 0.007922 2.554211
+v -1.183349 2.007922 2.554211
+v -1.307712 0.007922 2.705748
+v -1.307712 2.007922 2.705748
+v -1.459248 0.007922 2.830111
+v -1.459248 2.007922 2.830111
+v -1.632135 0.007922 2.922521
+v -1.632135 2.007922 2.922521
+v -1.819728 0.007922 2.979427
+v -1.819728 2.007922 2.979427
+v -2.014819 0.007922 2.998641
+v -2.014819 2.007922 2.998641
+v -2.209909 0.007922 2.979426
+v -2.209909 2.007922 2.979426
+v -2.397502 0.007922 2.922521
+v -2.397502 2.007922 2.922521
+v -2.570389 0.007922 2.830111
+v -2.570389 2.007922 2.830111
+v -2.721926 0.007922 2.705748
+v -2.721926 2.007922 2.705748
+v -2.846288 0.007922 2.554211
+v -2.846288 2.007922 2.554211
+v -2.938698 0.007922 2.381324
+v -2.938698 2.007922 2.381324
+v -2.995604 0.007922 2.193731
+v -2.995604 2.007922 2.193731
+v -3.014818 0.007922 1.998640
+v -3.014818 2.007922 1.998640
+v -2.995604 0.007922 1.803550
+v -2.995604 2.007922 1.803550
+v -2.938698 0.007922 1.615957
+v -2.938698 2.007922 1.615957
+v -2.846287 0.007922 1.443070
+v -2.846287 2.007922 1.443070
+v -2.721924 0.007922 1.291534
+v -2.721924 2.007922 1.291534
+v -2.570388 0.007922 1.167171
+v -2.570388 2.007922 1.167171
+v -2.397501 0.007922 1.074761
+v -2.397501 2.007922 1.074761
+v -2.209907 0.007922 1.017856
+v -2.209907 2.007922 1.017856
+vt 0.000000 0.000000
+vt 1.000000 0.000000
+vt 1.000000 1.000000
+vt 0.000000 1.000000
+vt 0.500000 1.000000
+vt 0.597545 0.990393
+vt 0.402456 0.990393
+vt 0.691342 0.961940
+vt 0.777785 0.915735
+vt 0.853553 0.853553
+vt 0.915735 0.777785
+vt 0.961940 0.691342
+vt 0.990393 0.597545
+vt 1.000000 0.500000
+vt 0.990393 0.402455
+vt 0.961940 0.308658
+vt 0.915735 0.222215
+vt 0.853553 0.146447
+vt 0.777785 0.084265
+vt 0.691342 0.038060
+vt 0.597545 0.009607
+vt 0.500000 0.000000
+vt 0.402455 0.009607
+vt 0.308658 0.038060
+vt 0.222215 0.084265
+vt 0.146446 0.146447
+vt 0.084265 0.222215
+vt 0.038060 0.308659
+vt 0.009607 0.402455
+vt 0.000000 0.500000
+vt 0.009607 0.597546
+vt 0.038060 0.691342
+vt 0.084266 0.777786
+vt 0.146447 0.853554
+vt 0.222215 0.915735
+vt 0.308659 0.961940
+vn 0.098017 0.000000 -0.995185
+vn 0.881921 0.000000 -0.471397
+vn 0.634393 0.000000 0.773010
+vn 0.471396 0.000000 0.881922
+vn -0.881920 -0.000000 -0.471398
+vn -0.634393 -0.000000 -0.773011
+vn -0.471395 -0.000000 -0.881922
+s off
+f 329/185/484 330/186/484 332/187/484
+f 331/185/453 332/186/453 334/187/453
+f 333/185/454 334/186/454 336/187/454
+f 335/185/455 336/186/455 338/187/455
+f 337/185/456 338/186/456 340/187/456
+f 339/185/485 340/186/485 342/187/485
+f 341/185/458 342/186/458 344/187/458
+f 343/185/459 344/186/459 346/187/459
+f 345/185/460 346/186/460 348/187/460
+f 347/185/461 348/186/461 350/187/461
+f 349/185/462 350/186/462 352/187/462
+f 351/185/463 352/186/463 354/187/463
+f 353/185/486 354/186/486 356/187/486
+f 355/185/487 356/186/487 358/187/487
+f 357/185/466 358/186/466 360/187/466
+f 359/185/467 360/186/467 362/187/467
+f 361/185/468 362/186/468 363/188/468
+f 363/185/469 364/186/469 365/188/469
+f 365/185/470 366/186/470 367/188/470
+f 367/185/471 368/186/471 369/188/471
+f 369/185/472 370/186/472 371/188/472
+f 371/185/473 372/186/473 373/188/473
+f 373/185/474 374/186/474 375/188/474
+f 375/185/475 376/186/475 377/188/475
+f 377/185/476 378/186/476 379/188/476
+f 379/185/477 380/186/477 381/188/477
+f 381/185/488 382/186/488 383/188/488
+f 383/185/479 384/186/479 385/188/479
+f 385/185/489 386/186/489 387/188/489
+f 387/185/490 388/186/490 389/188/490
+f 332/189/451 330/190/451 334/191/451
+f 391/185/482 392/186/482 329/188/482
+f 389/185/483 390/186/483 391/188/483
+f 329/189/450 331/190/450 391/191/450
+f 331/188/484 329/185/484 332/187/484
+f 333/188/453 331/185/453 334/187/453
+f 335/188/454 333/185/454 336/187/454
+f 337/188/455 335/185/455 338/187/455
+f 339/188/456 337/185/456 340/187/456
+f 341/188/485 339/185/485 342/187/485
+f 343/188/458 341/185/458 344/187/458
+f 345/188/459 343/185/459 346/187/459
+f 347/188/460 345/185/460 348/187/460
+f 349/188/461 347/185/461 350/187/461
+f 351/188/462 349/185/462 352/187/462
+f 353/188/463 351/185/463 354/187/463
+f 355/188/486 353/185/486 356/187/486
+f 357/188/487 355/185/487 358/187/487
+f 359/188/466 357/185/466 360/187/466
+f 361/188/467 359/185/467 362/187/467
+f 362/186/468 364/187/468 363/188/468
+f 364/186/469 366/187/469 365/188/469
+f 366/186/470 368/187/470 367/188/470
+f 368/186/471 370/187/471 369/188/471
+f 370/186/472 372/187/472 371/188/472
+f 372/186/473 374/187/473 373/188/473
+f 374/186/474 376/187/474 375/188/474
+f 376/186/475 378/187/475 377/188/475
+f 378/186/476 380/187/476 379/188/476
+f 380/186/477 382/187/477 381/188/477
+f 382/186/488 384/187/488 383/188/488
+f 384/186/479 386/187/479 385/188/479
+f 386/186/489 388/187/489 387/188/489
+f 388/186/490 390/187/490 389/188/490
+f 330/190/451 392/192/451 334/191/451
+f 392/192/451 390/193/451 334/191/451
+f 390/193/451 388/194/451 334/191/451
+f 388/194/451 386/195/451 334/191/451
+f 386/195/451 384/196/451 334/191/451
+f 384/196/451 382/197/451 334/191/451
+f 382/197/451 380/198/451 334/191/451
+f 380/198/451 378/199/451 334/191/451
+f 378/199/451 376/200/451 334/191/451
+f 376/200/451 374/201/451 334/191/451
+f 374/201/451 372/202/451 334/191/451
+f 372/202/451 370/203/451 334/191/451
+f 370/203/451 368/204/451 334/191/451
+f 368/204/451 366/205/451 334/191/451
+f 366/205/451 364/206/451 334/191/451
+f 364/206/451 362/207/451 334/191/451
+f 362/207/451 360/208/451 334/191/451
+f 360/208/451 358/209/451 334/191/451
+f 358/209/451 356/210/451 334/191/451
+f 356/210/451 354/211/451 334/191/451
+f 354/211/451 352/212/451 334/191/451
+f 352/212/451 350/213/451 334/191/451
+f 350/213/451 348/214/451 334/191/451
+f 348/214/451 346/215/451 334/191/451
+f 346/215/451 344/216/451 334/191/451
+f 344/216/451 342/217/451 334/191/451
+f 342/217/451 340/218/451 334/191/451
+f 340/218/451 338/219/451 334/191/451
+f 338/219/451 336/220/451 334/191/451
+f 392/186/482 330/187/482 329/188/482
+f 390/186/483 392/187/483 391/188/483
+f 331/190/450 333/192/450 391/191/450
+f 333/192/450 335/193/450 391/191/450
+f 335/193/450 337/194/450 391/191/450
+f 337/194/450 339/195/450 391/191/450
+f 339/195/450 341/196/450 391/191/450
+f 341/196/450 343/197/450 391/191/450
+f 343/197/450 345/198/450 391/191/450
+f 345/198/450 347/199/450 391/191/450
+f 347/199/450 349/200/450 391/191/450
+f 349/200/450 351/201/450 391/191/450
+f 351/201/450 353/202/450 391/191/450
+f 353/202/450 355/203/450 391/191/450
+f 355/203/450 357/204/450 391/191/450
+f 357/204/450 359/205/450 391/191/450
+f 359/205/450 361/206/450 391/191/450
+f 361/206/450 363/207/450 391/191/450
+f 363/207/450 365/208/450 391/191/450
+f 365/208/450 367/209/450 391/191/450
+f 367/209/450 369/210/450 391/191/450
+f 369/210/450 371/211/450 391/191/450
+f 371/211/450 373/212/450 391/191/450
+f 373/212/450 375/213/450 391/191/450
+f 375/213/450 377/214/450 391/191/450
+f 377/214/450 379/215/450 391/191/450
+f 379/215/450 381/216/450 391/191/450
+f 381/216/450 383/217/450 391/191/450
+f 383/217/450 385/218/450 391/191/450
+f 385/218/450 387/219/450 391/191/450
+f 387/219/450 389/220/450 391/191/450
+v -2.001621 -0.021814 -3.021079
+v -2.001621 1.978186 -3.021079
+v -1.806530 -0.021814 -3.001864
+v -1.806530 1.978186 -3.001864
+v -1.618937 -0.021814 -2.944958
+v -1.618937 1.978186 -2.944958
+v -1.446051 -0.021814 -2.852548
+v -1.446051 1.978186 -2.852548
+v -1.294514 -0.021814 -2.728185
+v -1.294514 1.978186 -2.728185
+v -1.170151 -0.021814 -2.576649
+v -1.170151 1.978186 -2.576649
+v -1.077741 -0.021814 -2.403762
+v -1.077741 1.978186 -2.403762
+v -1.020836 -0.021814 -2.216169
+v -1.020836 1.978186 -2.216169
+v -1.001621 -0.021814 -2.021079
+v -1.001621 1.978186 -2.021079
+v -1.020835 -0.021814 -1.825988
+v -1.020835 1.978186 -1.825988
+v -1.077741 -0.021814 -1.638395
+v -1.077741 1.978186 -1.638395
+v -1.170151 -0.021814 -1.465508
+v -1.170151 1.978186 -1.465508
+v -1.294514 -0.021814 -1.313972
+v -1.294514 1.978186 -1.313972
+v -1.446051 -0.021814 -1.189609
+v -1.446051 1.978186 -1.189609
+v -1.618937 -0.021814 -1.097199
+v -1.618937 1.978186 -1.097199
+v -1.806531 -0.021814 -1.040293
+v -1.806531 1.978186 -1.040293
+v -2.001621 -0.021814 -1.021079
+v -2.001621 1.978186 -1.021079
+v -2.196712 -0.021814 -1.040293
+v -2.196712 1.978186 -1.040293
+v -2.384305 -0.021814 -1.097199
+v -2.384305 1.978186 -1.097199
+v -2.557191 -0.021814 -1.189609
+v -2.557191 1.978186 -1.189609
+v -2.708728 -0.021814 -1.313972
+v -2.708728 1.978186 -1.313972
+v -2.833091 -0.021814 -1.465509
+v -2.833091 1.978186 -1.465509
+v -2.925501 -0.021814 -1.638396
+v -2.925501 1.978186 -1.638396
+v -2.982406 -0.021814 -1.825989
+v -2.982406 1.978186 -1.825989
+v -3.001621 -0.021814 -2.021080
+v -3.001621 1.978186 -2.021080
+v -2.982406 -0.021814 -2.216170
+v -2.982406 1.978186 -2.216170
+v -2.925500 -0.021814 -2.403763
+v -2.925500 1.978186 -2.403763
+v -2.833090 -0.021814 -2.576650
+v -2.833090 1.978186 -2.576650
+v -2.708727 -0.021814 -2.728186
+v -2.708727 1.978186 -2.728186
+v -2.557190 -0.021814 -2.852549
+v -2.557190 1.978186 -2.852549
+v -2.384303 -0.021814 -2.944959
+v -2.384303 1.978186 -2.944959
+v -2.196710 -0.021814 -3.001864
+v -2.196710 1.978186 -3.001864
+vt 0.000000 0.000000
+vt 1.000000 0.000000
+vt 1.000000 1.000000
+vt 0.000000 1.000000
+vt 0.500000 1.000000
+vt 0.597545 0.990393
+vt 0.402456 0.990393
+vt 0.691342 0.961940
+vt 0.777785 0.915735
+vt 0.853553 0.853553
+vt 0.915735 0.777785
+vt 0.961940 0.691342
+vt 0.990393 0.597545
+vt 1.000000 0.500000
+vt 0.990393 0.402455
+vt 0.961940 0.308658
+vt 0.915735 0.222215
+vt 0.853553 0.146447
+vt 0.777785 0.084265
+vt 0.691342 0.038060
+vt 0.597545 0.009607
+vt 0.500000 0.000000
+vt 0.402455 0.009607
+vt 0.308658 0.038060
+vt 0.222215 0.084265
+vt 0.146446 0.146447
+vt 0.084265 0.222215
+vt 0.038060 0.308659
+vt 0.009607 0.402455
+vt 0.000000 0.500000
+vt 0.009607 0.597546
+vt 0.038060 0.691342
+vt 0.084266 0.777786
+vt 0.146447 0.853554
+vt 0.222215 0.915735
+vt 0.308659 0.961940
+vn 0.471398 0.000000 -0.881921
+vn -0.634393 0.000000 0.773010
+vn -0.881921 -0.000000 -0.471397
+vn -0.773009 -0.000000 -0.634395
+vn -0.634393 -0.000000 -0.773010
+s off
+f 393/221/484 394/222/484 396/223/484
+f 395/221/453 396/222/453 398/223/453
+f 397/221/491 398/222/491 400/223/491
+f 399/221/455 400/222/455 402/223/455
+f 401/221/456 402/222/456 404/223/456
+f 403/221/485 404/222/485 406/223/485
+f 405/221/458 406/222/458 408/223/458
+f 407/221/459 408/222/459 410/223/459
+f 409/221/460 410/222/460 412/223/460
+f 411/221/461 412/222/461 414/223/461
+f 413/221/462 414/222/462 416/223/462
+f 415/221/463 416/222/463 418/223/463
+f 417/221/464 418/222/464 420/223/464
+f 419/221/465 420/222/465 422/223/465
+f 421/221/466 422/222/466 424/223/466
+f 423/221/467 424/222/467 426/223/467
+f 425/221/468 426/222/468 427/224/468
+f 427/221/469 428/222/469 429/224/469
+f 429/221/470 430/222/470 431/224/470
+f 431/221/492 432/222/492 433/224/492
+f 433/221/472 434/222/472 435/224/472
+f 435/221/473 436/222/473 437/224/473
+f 437/221/474 438/222/474 439/224/474
+f 439/221/475 440/222/475 441/224/475
+f 441/221/476 442/222/476 443/224/476
+f 443/221/477 444/222/477 445/224/477
+f 445/221/493 446/222/493 447/224/493
+f 447/221/494 448/222/494 449/224/494
+f 449/221/495 450/222/495 451/224/495
+f 451/221/490 452/222/490 453/224/490
+f 396/225/451 394/226/451 398/227/451
+f 455/221/482 456/222/482 393/224/482
+f 453/221/483 454/222/483 455/224/483
+f 393/225/450 395/226/450 455/227/450
+f 395/224/484 393/221/484 396/223/484
+f 397/224/453 395/221/453 398/223/453
+f 399/224/491 397/221/491 400/223/491
+f 401/224/455 399/221/455 402/223/455
+f 403/224/456 401/221/456 404/223/456
+f 405/224/485 403/221/485 406/223/485
+f 407/224/458 405/221/458 408/223/458
+f 409/224/459 407/221/459 410/223/459
+f 411/224/460 409/221/460 412/223/460
+f 413/224/461 411/221/461 414/223/461
+f 415/224/462 413/221/462 416/223/462
+f 417/224/463 415/221/463 418/223/463
+f 419/224/464 417/221/464 420/223/464
+f 421/224/465 419/221/465 422/223/465
+f 423/224/466 421/221/466 424/223/466
+f 425/224/467 423/221/467 426/223/467
+f 426/222/468 428/223/468 427/224/468
+f 428/222/469 430/223/469 429/224/469
+f 430/222/470 432/223/470 431/224/470
+f 432/222/492 434/223/492 433/224/492
+f 434/222/472 436/223/472 435/224/472
+f 436/222/473 438/223/473 437/224/473
+f 438/222/474 440/223/474 439/224/474
+f 440/222/475 442/223/475 441/224/475
+f 442/222/476 444/223/476 443/224/476
+f 444/222/477 446/223/477 445/224/477
+f 446/222/493 448/223/493 447/224/493
+f 448/222/494 450/223/494 449/224/494
+f 450/222/495 452/223/495 451/224/495
+f 452/222/490 454/223/490 453/224/490
+f 394/226/451 456/228/451 398/227/451
+f 456/228/451 454/229/451 398/227/451
+f 454/229/451 452/230/451 398/227/451
+f 452/230/451 450/231/451 398/227/451
+f 450/231/451 448/232/451 398/227/451
+f 448/232/451 446/233/451 398/227/451
+f 446/233/451 444/234/451 398/227/451
+f 444/234/451 442/235/451 398/227/451
+f 442/235/451 440/236/451 398/227/451
+f 440/236/451 438/237/451 398/227/451
+f 438/237/451 436/238/451 398/227/451
+f 436/238/451 434/239/451 398/227/451
+f 434/239/451 432/240/451 398/227/451
+f 432/240/451 430/241/451 398/227/451
+f 430/241/451 428/242/451 398/227/451
+f 428/242/451 426/243/451 398/227/451
+f 426/243/451 424/244/451 398/227/451
+f 424/244/451 422/245/451 398/227/451
+f 422/245/451 420/246/451 398/227/451
+f 420/246/451 418/247/451 398/227/451
+f 418/247/451 416/248/451 398/227/451
+f 416/248/451 414/249/451 398/227/451
+f 414/249/451 412/250/451 398/227/451
+f 412/250/451 410/251/451 398/227/451
+f 410/251/451 408/252/451 398/227/451
+f 408/252/451 406/253/451 398/227/451
+f 406/253/451 404/254/451 398/227/451
+f 404/254/451 402/255/451 398/227/451
+f 402/255/451 400/256/451 398/227/451
+f 456/222/482 394/223/482 393/224/482
+f 454/222/483 456/223/483 455/224/483
+f 395/226/450 397/228/450 455/227/450
+f 397/228/450 399/229/450 455/227/450
+f 399/229/450 401/230/450 455/227/450
+f 401/230/450 403/231/450 455/227/450
+f 403/231/450 405/232/450 455/227/450
+f 405/232/450 407/233/450 455/227/450
+f 407/233/450 409/234/450 455/227/450
+f 409/234/450 411/235/450 455/227/450
+f 411/235/450 413/236/450 455/227/450
+f 413/236/450 415/237/450 455/227/450
+f 415/237/450 417/238/450 455/227/450
+f 417/238/450 419/239/450 455/227/450
+f 419/239/450 421/240/450 455/227/450
+f 421/240/450 423/241/450 455/227/450
+f 423/241/450 425/242/450 455/227/450
+f 425/242/450 427/243/450 455/227/450
+f 427/243/450 429/244/450 455/227/450
+f 429/244/450 431/245/450 455/227/450
+f 431/245/450 433/246/450 455/227/450
+f 433/246/450 435/247/450 455/227/450
+f 435/247/450 437/248/450 455/227/450
+f 437/248/450 439/249/450 455/227/450
+f 439/249/450 441/250/450 455/227/450
+f 441/250/450 443/251/450 455/227/450
+f 443/251/450 445/252/450 455/227/450
+f 445/252/450 447/253/450 455/227/450
+f 447/253/450 449/254/450 455/227/450
+f 449/254/450 451/255/450 455/227/450
+f 451/255/450 453/256/450 455/227/450
+v 2.021592 0.003623 1.016610
+v 2.021592 2.003623 1.016610
+v 2.216682 0.003623 1.035825
+v 2.216682 2.003623 1.035825
+v 2.404276 0.003623 1.092731
+v 2.404276 2.003623 1.092731
+v 2.577162 0.003623 1.185141
+v 2.577162 2.003623 1.185141
+v 2.728699 0.003623 1.309503
+v 2.728699 2.003623 1.309503
+v 2.853062 0.003623 1.461040
+v 2.853062 2.003623 1.461040
+v 2.945472 0.003623 1.633927
+v 2.945472 2.003623 1.633927
+v 3.002378 0.003623 1.821520
+v 3.002378 2.003623 1.821520
+v 3.021592 0.003623 2.016610
+v 3.021592 2.003623 2.016610
+v 3.002378 0.003623 2.211700
+v 3.002378 2.003623 2.211700
+v 2.945472 0.003623 2.399293
+v 2.945472 2.003623 2.399293
+v 2.853062 0.003623 2.572180
+v 2.853062 2.003623 2.572180
+v 2.728699 0.003623 2.723717
+v 2.728699 2.003623 2.723717
+v 2.577162 0.003623 2.848080
+v 2.577162 2.003623 2.848080
+v 2.404275 0.003623 2.940490
+v 2.404275 2.003623 2.940490
+v 2.216682 0.003623 2.997396
+v 2.216682 2.003623 2.997396
+v 2.021592 0.003623 3.016610
+v 2.021592 2.003623 3.016610
+v 1.826501 0.003623 2.997395
+v 1.826501 2.003623 2.997395
+v 1.638908 0.003623 2.940490
+v 1.638908 2.003623 2.940490
+v 1.466021 0.003623 2.848079
+v 1.466021 2.003623 2.848079
+v 1.314485 0.003623 2.723716
+v 1.314485 2.003623 2.723716
+v 1.190122 0.003623 2.572180
+v 1.190122 2.003623 2.572180
+v 1.097712 0.003623 2.399293
+v 1.097712 2.003623 2.399293
+v 1.040807 0.003623 2.211699
+v 1.040807 2.003623 2.211699
+v 1.021592 0.003623 2.016609
+v 1.021592 2.003623 2.016609
+v 1.040807 0.003623 1.821519
+v 1.040807 2.003623 1.821519
+v 1.097713 0.003623 1.633926
+v 1.097713 2.003623 1.633926
+v 1.190123 0.003623 1.461039
+v 1.190123 2.003623 1.461039
+v 1.314486 0.003623 1.309502
+v 1.314486 2.003623 1.309502
+v 1.466023 0.003623 1.185140
+v 1.466023 2.003623 1.185140
+v 1.638910 0.003623 1.092730
+v 1.638910 2.003623 1.092730
+v 1.826503 0.003623 1.035825
+v 1.826503 2.003623 1.035825
+vt 0.000000 0.000000
+vt 1.000000 0.000000
+vt 1.000000 1.000000
+vt 0.000000 1.000000
+vt 0.500000 1.000000
+vt 0.597545 0.990393
+vt 0.402456 0.990393
+vt 0.691342 0.961940
+vt 0.777785 0.915735
+vt 0.853553 0.853553
+vt 0.915735 0.777785
+vt 0.961940 0.691342
+vt 0.990393 0.597545
+vt 1.000000 0.500000
+vt 0.990393 0.402455
+vt 0.961940 0.308658
+vt 0.915735 0.222215
+vt 0.853553 0.146447
+vt 0.777785 0.084265
+vt 0.691342 0.038060
+vt 0.597545 0.009607
+vt 0.500000 0.000000
+vt 0.402455 0.009607
+vt 0.308658 0.038060
+vt 0.222215 0.084265
+vt 0.146446 0.146447
+vt 0.084265 0.222215
+vt 0.038060 0.308659
+vt 0.009607 0.402455
+vt 0.000000 0.500000
+vt 0.009607 0.597546
+vt 0.038060 0.691342
+vt 0.084266 0.777786
+vt 0.146447 0.853554
+vt 0.222215 0.915735
+vt 0.308659 0.961940
+s off
+f 457/257/484 458/258/484 460/259/484
+f 459/257/453 460/258/453 462/259/453
+f 461/257/454 462/258/454 464/259/454
+f 463/257/455 464/258/455 466/259/455
+f 465/257/456 466/258/456 468/259/456
+f 467/257/457 468/258/457 470/259/457
+f 469/257/458 470/258/458 472/259/458
+f 471/257/459 472/258/459 474/259/459
+f 473/257/460 474/258/460 476/259/460
+f 475/257/461 476/258/461 478/259/461
+f 477/257/462 478/258/462 480/259/462
+f 479/257/463 480/258/463 482/259/463
+f 481/257/464 482/258/464 484/259/464
+f 483/257/465 484/258/465 486/259/465
+f 485/257/466 486/258/466 488/259/466
+f 487/257/467 488/258/467 490/259/467
+f 489/257/468 490/258/468 491/260/468
+f 491/257/469 492/258/469 493/260/469
+f 493/257/470 494/258/470 495/260/470
+f 495/257/471 496/258/471 497/260/471
+f 497/257/472 498/258/472 499/260/472
+f 499/257/473 500/258/473 501/260/473
+f 501/257/474 502/258/474 503/260/474
+f 503/257/475 504/258/475 505/260/475
+f 505/257/476 506/258/476 507/260/476
+f 507/257/477 508/258/477 509/260/477
+f 509/257/478 510/258/478 511/260/478
+f 511/257/494 512/258/494 513/260/494
+f 513/257/489 514/258/489 515/260/489
+f 515/257/490 516/258/490 517/260/490
+f 460/261/451 458/262/451 462/263/451
+f 519/257/482 520/258/482 457/260/482
+f 517/257/483 518/258/483 519/260/483
+f 457/261/450 459/262/450 519/263/450
+f 459/260/484 457/257/484 460/259/484
+f 461/260/453 459/257/453 462/259/453
+f 463/260/454 461/257/454 464/259/454
+f 465/260/455 463/257/455 466/259/455
+f 467/260/456 465/257/456 468/259/456
+f 469/260/457 467/257/457 470/259/457
+f 471/260/458 469/257/458 472/259/458
+f 473/260/459 471/257/459 474/259/459
+f 475/260/460 473/257/460 476/259/460
+f 477/260/461 475/257/461 478/259/461
+f 479/260/462 477/257/462 480/259/462
+f 481/260/463 479/257/463 482/259/463
+f 483/260/464 481/257/464 484/259/464
+f 485/260/465 483/257/465 486/259/465
+f 487/260/466 485/257/466 488/259/466
+f 489/260/467 487/257/467 490/259/467
+f 490/258/468 492/259/468 491/260/468
+f 492/258/469 494/259/469 493/260/469
+f 494/258/470 496/259/470 495/260/470
+f 496/258/471 498/259/471 497/260/471
+f 498/258/472 500/259/472 499/260/472
+f 500/258/473 502/259/473 501/260/473
+f 502/258/474 504/259/474 503/260/474
+f 504/258/475 506/259/475 505/260/475
+f 506/258/476 508/259/476 507/260/476
+f 508/258/477 510/259/477 509/260/477
+f 510/258/478 512/259/478 511/260/478
+f 512/258/494 514/259/494 513/260/494
+f 514/258/489 516/259/489 515/260/489
+f 516/258/490 518/259/490 517/260/490
+f 458/262/451 520/264/451 462/263/451
+f 520/264/451 518/265/451 462/263/451
+f 518/265/451 516/266/451 462/263/451
+f 516/266/451 514/267/451 462/263/451
+f 514/267/451 512/268/451 462/263/451
+f 512/268/451 510/269/451 462/263/451
+f 510/269/451 508/270/451 462/263/451
+f 508/270/451 506/271/451 462/263/451
+f 506/271/451 504/272/451 462/263/451
+f 504/272/451 502/273/451 462/263/451
+f 502/273/451 500/274/451 462/263/451
+f 500/274/451 498/275/451 462/263/451
+f 498/275/451 496/276/451 462/263/451
+f 496/276/451 494/277/451 462/263/451
+f 494/277/451 492/278/451 462/263/451
+f 492/278/451 490/279/451 462/263/451
+f 490/279/451 488/280/451 462/263/451
+f 488/280/451 486/281/451 462/263/451
+f 486/281/451 484/282/451 462/263/451
+f 484/282/451 482/283/451 462/263/451
+f 482/283/451 480/284/451 462/263/451
+f 480/284/451 478/285/451 462/263/451
+f 478/285/451 476/286/451 462/263/451
+f 476/286/451 474/287/451 462/263/451
+f 474/287/451 472/288/451 462/263/451
+f 472/288/451 470/289/451 462/263/451
+f 470/289/451 468/290/451 462/263/451
+f 468/290/451 466/291/451 462/263/451
+f 466/291/451 464/292/451 462/263/451
+f 520/258/482 458/259/482 457/260/482
+f 518/258/483 520/259/483 519/260/483
+f 459/262/450 461/264/450 519/263/450
+f 461/264/450 463/265/450 519/263/450
+f 463/265/450 465/266/450 519/263/450
+f 465/266/450 467/267/450 519/263/450
+f 467/267/450 469/268/450 519/263/450
+f 469/268/450 471/269/450 519/263/450
+f 471/269/450 473/270/450 519/263/450
+f 473/270/450 475/271/450 519/263/450
+f 475/271/450 477/272/450 519/263/450
+f 477/272/450 479/273/450 519/263/450
+f 479/273/450 481/274/450 519/263/450
+f 481/274/450 483/275/450 519/263/450
+f 483/275/450 485/276/450 519/263/450
+f 485/276/450 487/277/450 519/263/450
+f 487/277/450 489/278/450 519/263/450
+f 489/278/450 491/279/450 519/263/450
+f 491/279/450 493/280/450 519/263/450
+f 493/280/450 495/281/450 519/263/450
+f 495/281/450 497/282/450 519/263/450
+f 497/282/450 499/283/450 519/263/450
+f 499/283/450 501/284/450 519/263/450
+f 501/284/450 503/285/450 519/263/450
+f 503/285/450 505/286/450 519/263/450
+f 505/286/450 507/287/450 519/263/450
+f 507/287/450 509/288/450 519/263/450
+f 509/288/450 511/289/450 519/263/450
+f 511/289/450 513/290/450 519/263/450
+f 513/290/450 515/291/450 519/263/450
+f 515/291/450 517/292/450 519/263/450
diff --git a/examples/graphs/graphgallery/data/pipe.obj b/examples/graphs/graphgallery/data/pipe.obj
new file mode 100644
index 0000000..6ccbb28
--- /dev/null
+++ b/examples/graphs/graphgallery/data/pipe.obj
@@ -0,0 +1,330 @@
+# Blender v2.66 (sub 0) OBJ File: 'cylinder.blend'
+# www.blender.org
+o Cylinder
+v 0.000000 -1.000000 -1.000000
+v 0.000000 1.000000 -1.000000
+v 0.195090 -1.000000 -0.980785
+v 0.195090 1.000000 -0.980785
+v 0.382683 -1.000000 -0.923880
+v 0.382683 1.000000 -0.923880
+v 0.555570 -1.000000 -0.831470
+v 0.555570 1.000000 -0.831470
+v 0.707107 -1.000000 -0.707107
+v 0.707107 1.000000 -0.707107
+v 0.831470 -1.000000 -0.555570
+v 0.831470 1.000000 -0.555570
+v 0.923880 -1.000000 -0.382683
+v 0.923880 1.000000 -0.382683
+v 0.980785 -1.000000 -0.195090
+v 0.980785 1.000000 -0.195090
+v 1.000000 -1.000000 -0.000000
+v 1.000000 1.000000 -0.000000
+v 0.980785 -1.000000 0.195090
+v 0.980785 1.000000 0.195090
+v 0.923880 -1.000000 0.382683
+v 0.923880 1.000000 0.382683
+v 0.831470 -1.000000 0.555570
+v 0.831470 1.000000 0.555570
+v 0.707107 -1.000000 0.707107
+v 0.707107 1.000000 0.707107
+v 0.555570 -1.000000 0.831470
+v 0.555570 1.000000 0.831470
+v 0.382683 -1.000000 0.923880
+v 0.382683 1.000000 0.923880
+v 0.195090 -1.000000 0.980785
+v 0.195090 1.000000 0.980785
+v -0.000000 -1.000000 1.000000
+v -0.000000 1.000000 1.000000
+v -0.195091 -1.000000 0.980785
+v -0.195091 1.000000 0.980785
+v -0.382684 -1.000000 0.923879
+v -0.382684 1.000000 0.923879
+v -0.555571 -1.000000 0.831469
+v -0.555571 1.000000 0.831469
+v -0.707107 -1.000000 0.707106
+v -0.707107 1.000000 0.707106
+v -0.831470 -1.000000 0.555570
+v -0.831470 1.000000 0.555570
+v -0.923880 -1.000000 0.382683
+v -0.923880 1.000000 0.382683
+v -0.980785 -1.000000 0.195089
+v -0.980785 1.000000 0.195089
+v -1.000000 -1.000000 -0.000001
+v -1.000000 1.000000 -0.000001
+v -0.980785 -1.000000 -0.195091
+v -0.980785 1.000000 -0.195091
+v -0.923879 -1.000000 -0.382684
+v -0.923879 1.000000 -0.382684
+v -0.831469 -1.000000 -0.555571
+v -0.831469 1.000000 -0.555571
+v -0.707106 -1.000000 -0.707108
+v -0.707106 1.000000 -0.707108
+v -0.555569 -1.000000 -0.831470
+v -0.555569 1.000000 -0.831470
+v -0.382682 -1.000000 -0.923880
+v -0.382682 1.000000 -0.923880
+v -0.195089 -1.000000 -0.980786
+v -0.195089 1.000000 -0.980786
+vt 0.289718 0.879351
+vt 0.288367 0.438844
+vt 0.330714 0.438714
+vt 0.332066 0.879221
+vt 0.370605 0.438592
+vt 0.371956 0.879099
+vt 0.406505 0.438482
+vt 0.407857 0.878988
+vt 0.437036 0.438388
+vt 0.778904 0.000000
+vt 0.780256 0.440507
+vt 0.749725 0.440601
+vt 0.748373 0.000094
+vt 0.713824 0.440711
+vt 0.712473 0.000204
+vt 0.673934 0.440833
+vt 0.672582 0.000326
+vt 0.631586 0.440963
+vt 0.630235 0.000456
+vt 0.588409 0.441095
+vt 0.587057 0.000588
+vt 0.546061 0.441225
+vt 0.544710 0.000718
+vt 0.506171 0.441348
+vt 0.504819 0.000841
+vt 0.470270 0.441458
+vt 0.468919 0.000951
+vt 0.439739 0.441552
+vt 0.720545 0.882916
+vt 0.719194 0.442409
+vt 0.755094 0.442299
+vt 0.756446 0.882806
+vt 0.794985 0.442176
+vt 0.796336 0.882683
+vt 0.837333 0.442046
+vt 0.838684 0.882553
+vt 0.881861 0.882421
+vt 0.880510 0.441914
+vt 0.924209 0.882291
+vt 0.922857 0.441784
+vt 0.964099 0.882168
+vt 0.962748 0.441662
+vt 1.000000 0.882058
+vt 0.717842 0.441552
+vt 0.719194 0.882058
+vt 0.681942 0.441662
+vt 0.683293 0.882169
+vt 0.642051 0.441784
+vt 0.643403 0.882291
+vt 0.599704 0.441914
+vt 0.601055 0.882421
+vt 0.556526 0.442046
+vt 0.557878 0.882553
+vt 0.514179 0.442176
+vt 0.515530 0.882683
+vt 0.474288 0.442299
+vt 0.475640 0.882806
+vt 0.438388 0.442409
+vt 0.097872 0.879939
+vt 0.096520 0.439433
+vt 0.128403 0.879846
+vt 0.127051 0.439339
+vt 0.164303 0.879735
+vt 0.162952 0.439229
+vt 0.204194 0.879613
+vt 0.000000 0.197605
+vt 0.008423 0.155257
+vt 0.000000 0.240783
+vt 0.246541 0.879483
+vt 0.245190 0.438976
+vt 0.202842 0.439106
+vt 0.438388 0.878895
+vt 0.438388 0.001045
+vt 0.998649 0.441552
+vt 0.439739 0.882916
+vt 0.024947 0.115367
+vt 0.048935 0.079466
+vt 0.079466 0.048935
+vt 0.115366 0.024947
+vt 0.155257 0.008424
+vt 0.197605 0.000000
+vt 0.240782 0.000000
+vt 0.283130 0.008423
+vt 0.323021 0.024947
+vt 0.358922 0.048935
+vt 0.389453 0.079466
+vt 0.413441 0.115367
+vt 0.429964 0.155257
+vt 0.438388 0.197605
+vt 0.438388 0.240783
+vt 0.429964 0.283130
+vt 0.413441 0.323021
+vt 0.389453 0.358922
+vt 0.358922 0.389453
+vt 0.323021 0.413441
+vt 0.283130 0.429964
+vt 0.240783 0.438388
+vt 0.197605 0.438388
+vt 0.155257 0.429964
+vt 0.115367 0.413441
+vt 0.079466 0.389453
+vt 0.048935 0.358922
+vt 0.024947 0.323021
+vt 0.008423 0.283130
+vn 0.000000 0.000000 -1.000000
+vn 0.000000 0.685690 -0.727866
+vn 0.142003 0.685690 -0.713889
+vn 0.195074 0.000000 -0.980773
+vn 0.278542 0.685690 -0.672475
+vn 0.382672 0.000000 -0.923856
+vn 0.404370 0.685690 -0.605213
+vn 0.555559 0.000000 -0.831446
+vn 0.514664 0.685690 -0.514664
+vn 0.707083 0.000000 -0.707083
+vn 0.605213 0.685690 -0.404370
+vn 0.831446 0.000000 -0.555559
+vn 0.672475 0.685690 -0.278542
+vn 0.923856 0.000000 -0.382672
+vn 0.713889 0.685690 -0.142003
+vn 0.980773 0.000000 -0.195074
+vn 0.727866 0.685690 0.000000
+vn 1.000000 0.000000 0.000000
+vn 0.713889 0.685690 0.142003
+vn 0.980773 0.000000 0.195074
+vn 0.672475 0.685690 0.278542
+vn 0.923856 0.000000 0.382672
+vn 0.605213 0.685690 0.404370
+vn 0.831446 0.000000 0.555559
+vn 0.514664 0.685690 0.514664
+vn 0.707083 0.000000 0.707083
+vn 0.404370 0.685690 0.605213
+vn 0.555559 0.000000 0.831446
+vn 0.278542 0.685690 0.672475
+vn 0.382672 0.000000 0.923856
+vn 0.142003 0.685690 0.713889
+vn 0.195074 0.000000 0.980773
+vn 0.000000 0.685690 0.727866
+vn 0.000000 0.000000 0.999969
+vn -0.195074 0.000000 0.980773
+vn -0.142003 0.685690 0.713889
+vn -0.382672 0.000000 0.923856
+vn -0.278542 0.685690 0.672475
+vn -0.555559 0.000000 0.831446
+vn -0.404370 0.685690 0.605213
+vn -0.707083 0.000000 0.707083
+vn -0.514664 0.685690 0.514664
+vn -0.831446 0.000000 0.555559
+vn -0.605213 0.685690 0.404370
+vn -0.923856 0.000000 0.382672
+vn -0.672475 0.685690 0.278542
+vn -0.980773 0.000000 0.195074
+vn -0.713889 0.685690 0.142003
+vn -1.000000 0.000000 0.000000
+vn -0.727866 0.685690 0.000000
+vn -0.980773 0.000000 -0.195074
+vn -0.713889 0.685690 -0.142003
+vn -0.923856 0.000000 -0.382672
+vn -0.672475 0.685690 -0.278542
+vn -0.831446 0.000000 -0.555559
+vn -0.605213 0.685690 -0.404370
+vn -0.707083 0.000000 -0.707083
+vn -0.514664 0.685690 -0.514695
+vn -0.555559 0.000000 -0.831446
+vn -0.404370 0.685690 -0.605213
+vn -0.382672 0.000000 -0.923856
+vn -0.195074 0.000000 -0.980773
+vn -0.142003 0.685690 -0.713889
+vn -0.278542 0.685690 -0.672475
+s 1
+f 1/1/1 2/2/2 4/3/3
+f 3/4/4 4/3/3 6/5/5
+f 5/6/6 6/5/5 8/7/7
+f 7/8/8 8/7/7 10/9/9
+f 9/10/10 10/11/9 12/12/11
+f 11/13/12 12/12/11 14/14/13
+f 13/15/14 14/14/13 16/16/15
+f 15/17/16 16/16/15 18/18/17
+f 17/19/18 18/18/17 20/20/19
+f 19/21/20 20/20/19 22/22/21
+f 21/23/22 22/22/21 24/24/23
+f 23/25/24 24/24/23 26/26/25
+f 25/27/26 26/26/25 28/28/27
+f 27/29/28 28/30/27 30/31/29
+f 29/32/30 30/31/29 32/33/31
+f 31/34/32 32/33/31 34/35/33
+f 33/36/34 34/35/33 35/37/35
+f 35/37/35 36/38/36 37/39/37
+f 37/39/37 38/40/38 39/41/39
+f 39/41/39 40/42/40 41/43/41
+f 41/44/41 42/45/42 43/46/43
+f 43/46/43 44/47/44 45/48/45
+f 45/48/45 46/49/46 47/50/47
+f 47/50/47 48/51/48 49/52/49
+f 49/52/49 50/53/50 51/54/51
+f 51/54/51 52/55/52 53/56/53
+f 53/56/53 54/57/54 55/58/55
+f 55/59/55 56/60/56 57/61/57
+f 57/61/57 58/62/58 59/63/59
+f 59/63/59 60/64/60 61/65/61
+f 4/66/3 2/67/2 6/68/5
+f 63/69/62 64/70/63 1/1/1
+f 61/65/61 62/71/64 63/69/62
+f 3/4/4 1/1/1 4/3/3
+f 5/6/6 3/4/4 6/5/5
+f 7/8/8 5/6/6 8/7/7
+f 9/72/10 7/8/8 10/9/9
+f 11/13/12 9/10/10 12/12/11
+f 13/15/14 11/13/12 14/14/13
+f 15/17/16 13/15/14 16/16/15
+f 17/19/18 15/17/16 18/18/17
+f 19/21/20 17/19/18 20/20/19
+f 21/23/22 19/21/20 22/22/21
+f 23/25/24 21/23/22 24/24/23
+f 25/27/26 23/25/24 26/26/25
+f 27/73/28 25/27/26 28/28/27
+f 29/32/30 27/29/28 30/31/29
+f 31/34/32 29/32/30 32/33/31
+f 33/36/34 31/34/32 34/35/33
+f 34/35/33 36/38/36 35/37/35
+f 36/38/36 38/40/38 37/39/37
+f 38/40/38 40/42/40 39/41/39
+f 40/42/40 42/74/42 41/43/41
+f 42/45/42 44/47/44 43/46/43
+f 44/47/44 46/49/46 45/48/45
+f 46/49/46 48/51/48 47/50/47
+f 48/51/48 50/53/50 49/52/49
+f 50/53/50 52/55/52 51/54/51
+f 52/55/52 54/57/54 53/56/53
+f 54/57/54 56/75/56 55/58/55
+f 56/60/56 58/62/58 57/61/57
+f 58/62/58 60/64/60 59/63/59
+f 60/64/60 62/71/64 61/65/61
+f 2/67/2 64/76/63 6/68/5
+f 64/76/63 62/77/64 6/68/5
+f 62/77/64 60/78/60 6/68/5
+f 60/78/60 58/79/58 6/68/5
+f 58/79/58 56/80/56 6/68/5
+f 56/80/56 54/81/54 6/68/5
+f 54/81/54 52/82/52 6/68/5
+f 52/82/52 50/83/50 6/68/5
+f 50/83/50 48/84/48 6/68/5
+f 48/84/48 46/85/46 6/68/5
+f 46/85/46 44/86/44 6/68/5
+f 44/86/44 42/87/42 6/68/5
+f 42/87/42 40/88/40 6/68/5
+f 40/88/40 38/89/38 6/68/5
+f 38/89/38 36/90/36 6/68/5
+f 36/90/36 34/91/33 6/68/5
+f 34/91/33 32/92/31 6/68/5
+f 32/92/31 30/93/29 6/68/5
+f 30/93/29 28/94/27 6/68/5
+f 28/94/27 26/95/25 6/68/5
+f 26/95/25 24/96/23 6/68/5
+f 24/96/23 22/97/21 6/68/5
+f 22/97/21 20/98/19 6/68/5
+f 20/98/19 18/99/17 6/68/5
+f 18/99/17 16/100/15 6/68/5
+f 16/100/15 14/101/13 6/68/5
+f 14/101/13 12/102/11 6/68/5
+f 12/102/11 10/103/9 8/104/7
+f 6/68/5 12/102/11 8/104/7
+f 64/70/63 2/2/2 1/1/1
+f 62/71/64 64/70/63 63/69/62
diff --git a/examples/graphs/graphgallery/data/raindata.txt b/examples/graphs/graphgallery/data/raindata.txt
new file mode 100644
index 0000000..d955892
--- /dev/null
+++ b/examples/graphs/graphgallery/data/raindata.txt
@@ -0,0 +1,158 @@
+# 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
diff --git a/examples/graphs/graphgallery/data/refinery.obj b/examples/graphs/graphgallery/data/refinery.obj
new file mode 100644
index 0000000..ed90c36
--- /dev/null
+++ b/examples/graphs/graphgallery/data/refinery.obj
@@ -0,0 +1,2330 @@
+# Blender v2.66 (sub 0) OBJ File: 'oilrefinery.blend'
+# www.blender.org
+v -2.719012 -0.196783 4.805554
+v -2.719012 -0.196783 -4.824533
+v 2.730989 -0.196783 -4.824533
+v 2.730989 -0.196783 4.805554
+v -2.719012 0.012961 4.805554
+v -2.719012 0.012961 -4.824533
+v 2.730989 0.012961 -4.824533
+v 2.730989 0.012961 4.805554
+vt 0.000000 0.000000
+vt 1.000000 0.000000
+vt 0.000000 1.000000
+vt 1.000000 1.000000
+vn -0.577349 0.577349 0.577349
+vn -0.577349 0.577349 -0.577349
+vn -0.577349 -0.577349 0.577349
+vn 0.577349 0.577349 -0.577349
+vn 0.577349 -0.577349 -0.577349
+vn 0.577349 0.577349 0.577349
+vn 0.577349 -0.577349 0.577349
+vn -0.577349 -0.577349 -0.577349
+s 1
+f 5/1/1 6/2/2 1/3/3
+f 6/1/2 7/2/4 3/4/5
+f 7/1/4 8/2/6 4/4/7
+f 8/1/6 5/2/1 1/4/3
+f 1/1/3 2/2/8 3/4/5
+f 8/1/6 7/2/4 6/4/2
+f 6/2/2 2/4/8 1/3/3
+f 2/3/8 6/1/2 3/4/5
+f 3/3/5 7/1/4 4/4/7
+f 4/3/7 8/1/6 1/4/3
+f 4/3/7 1/1/3 3/4/5
+f 5/3/1 8/1/6 6/4/2
+v -1.384247 1.252743 0.422195
+v -1.384247 2.069450 0.422195
+v -1.303725 1.252743 0.430041
+v -1.303725 2.069450 0.430041
+v -1.226298 1.252743 0.453279
+v -1.226298 2.069450 0.453279
+v -1.154941 1.252743 0.491015
+v -1.154941 2.069450 0.491015
+v -1.092396 1.252743 0.541799
+v -1.092396 2.069450 0.541799
+v -1.041066 1.252743 0.603679
+v -1.041066 2.069450 0.603679
+v -1.002925 1.252743 0.674278
+v -1.002925 2.069450 0.674278
+v -0.979437 1.252743 0.750883
+v -0.979437 2.069450 0.750883
+v -0.971507 1.252743 0.830548
+v -0.971507 2.069450 0.830548
+v -0.979437 1.252743 0.910214
+v -0.979437 2.069450 0.910214
+v -1.002925 1.252743 0.986818
+v -1.002925 2.069450 0.986818
+v -1.041066 1.252743 1.057417
+v -1.041066 2.069450 1.057417
+v -1.092396 1.252743 1.119298
+v -1.092396 2.069450 1.119298
+v -1.154941 1.252743 1.170082
+v -1.154941 2.069450 1.170082
+v -1.226298 1.252743 1.207818
+v -1.226298 2.069450 1.207818
+v -1.303726 1.252743 1.231055
+v -1.303726 2.069450 1.231055
+v -1.384247 1.252743 1.238902
+v -1.384247 2.069450 1.238902
+v -1.464769 1.252743 1.231055
+v -1.464769 2.069450 1.231055
+v -1.542196 1.252743 1.207818
+v -1.542196 2.069450 1.207818
+v -1.613554 1.252743 1.170082
+v -1.613554 2.069450 1.170082
+v -1.676099 1.252743 1.119298
+v -1.676099 2.069450 1.119298
+v -1.727429 1.252743 1.057417
+v -1.727429 2.069450 1.057417
+v -1.765570 1.252743 0.986818
+v -1.765570 2.069450 0.986818
+v -1.789057 1.252743 0.910214
+v -1.789057 2.069450 0.910214
+v -1.796988 1.252743 0.830548
+v -1.796988 2.069450 0.830548
+v -1.789057 1.252743 0.750882
+v -1.789057 2.069450 0.750882
+v -1.765570 1.252743 0.674278
+v -1.765570 2.069450 0.674278
+v -1.727428 1.252743 0.603679
+v -1.727428 2.069450 0.603679
+v -1.676098 1.252743 0.541798
+v -1.676098 2.069450 0.541798
+v -1.613553 1.252743 0.491015
+v -1.613553 2.069450 0.491015
+v -1.542196 1.252743 0.453279
+v -1.542196 2.069450 0.453279
+v -1.464768 1.252743 0.430041
+v -1.464768 2.069450 0.430041
+vt 0.000000 0.000000
+vt 1.000000 0.000000
+vt 1.000000 1.000000
+vt 0.000000 1.000000
+vt 0.500000 1.000000
+vt 0.597545 0.990393
+vt 0.402456 0.990393
+vt 0.691342 0.961940
+vt 0.777785 0.915735
+vt 0.853553 0.853553
+vt 0.915735 0.777785
+vt 0.961940 0.691342
+vt 0.990393 0.597545
+vt 1.000000 0.500000
+vt 0.990393 0.402455
+vt 0.961940 0.308658
+vt 0.915735 0.222215
+vt 0.853553 0.146447
+vt 0.777785 0.084265
+vt 0.691342 0.038060
+vt 0.597545 0.009607
+vt 0.500000 0.000000
+vt 0.402455 0.009607
+vt 0.308658 0.038060
+vt 0.222215 0.084265
+vt 0.146446 0.146447
+vt 0.084265 0.222215
+vt 0.038060 0.308659
+vt 0.009607 0.402455
+vt 0.000000 0.500000
+vt 0.009607 0.597546
+vt 0.038060 0.691342
+vt 0.084266 0.777786
+vt 0.146447 0.853554
+vt 0.222215 0.915735
+vt 0.308659 0.961940
+vn 0.096985 0.000000 -0.995286
+vn 0.287455 0.000000 -0.957794
+vn 0.467486 0.000000 -0.884001
+vn 0.630338 0.000000 -0.776321
+vn 0.769672 0.000000 -0.638440
+vn 0.879812 0.000000 -0.475321
+vn 0.956070 0.000000 -0.293137
+vn 0.995081 0.000000 -0.099061
+vn 0.995081 0.000000 0.099061
+vn 0.956070 0.000000 0.293137
+vn 0.879813 0.000000 0.475321
+vn 0.769672 0.000000 0.638440
+vn 0.630338 0.000000 0.776321
+vn 0.467486 0.000000 0.884001
+vn 0.287455 0.000000 0.957794
+vn 0.096986 0.000000 0.995286
+vn -0.096986 0.000000 0.995286
+vn -0.287456 0.000000 0.957794
+vn -0.467486 0.000000 0.884001
+vn -0.630338 0.000000 0.776321
+vn -0.769673 0.000000 0.638438
+vn -0.879812 0.000000 0.475322
+vn -0.956071 0.000000 0.293135
+vn -0.995081 0.000000 0.099059
+vn -0.995081 -0.000000 -0.099061
+vn -0.956070 -0.000000 -0.293138
+vn -0.879812 -0.000000 -0.475322
+vn -0.769671 -0.000000 -0.638441
+vn -0.630337 -0.000000 -0.776322
+vn -0.467484 -0.000000 -0.884001
+vn -0.000000 1.000000 0.000000
+vn -0.096985 -0.000000 -0.995286
+vn -0.287454 -0.000000 -0.957794
+vn 0.000000 -1.000000 -0.000000
+s off
+f 9/5/9 10/6/9 12/7/9
+f 11/5/10 12/6/10 14/7/10
+f 13/5/11 14/6/11 16/7/11
+f 15/5/12 16/6/12 18/7/12
+f 17/5/13 18/6/13 20/7/13
+f 19/5/14 20/6/14 22/7/14
+f 21/5/15 22/6/15 24/7/15
+f 23/5/16 24/6/16 26/7/16
+f 25/5/17 26/6/17 28/7/17
+f 27/5/18 28/6/18 30/7/18
+f 29/5/19 30/6/19 32/7/19
+f 31/5/20 32/6/20 34/7/20
+f 33/5/21 34/6/21 36/7/21
+f 35/5/22 36/6/22 38/7/22
+f 37/5/23 38/6/23 40/7/23
+f 39/5/24 40/6/24 42/7/24
+f 41/5/25 42/6/25 43/8/25
+f 43/5/26 44/6/26 45/8/26
+f 45/5/27 46/6/27 47/8/27
+f 47/5/28 48/6/28 49/8/28
+f 49/5/29 50/6/29 51/8/29
+f 51/5/30 52/6/30 53/8/30
+f 53/5/31 54/6/31 55/8/31
+f 55/5/32 56/6/32 57/8/32
+f 57/5/33 58/6/33 59/8/33
+f 59/5/34 60/6/34 61/8/34
+f 61/5/35 62/6/35 63/8/35
+f 63/5/36 64/6/36 65/8/36
+f 65/5/37 66/6/37 67/8/37
+f 67/5/38 68/6/38 69/8/38
+f 12/9/39 10/10/39 14/11/39
+f 71/5/40 72/6/40 9/8/40
+f 69/5/41 70/6/41 71/8/41
+f 9/9/42 11/10/42 71/11/42
+f 11/8/9 9/5/9 12/7/9
+f 13/8/10 11/5/10 14/7/10
+f 15/8/11 13/5/11 16/7/11
+f 17/8/12 15/5/12 18/7/12
+f 19/8/13 17/5/13 20/7/13
+f 21/8/14 19/5/14 22/7/14
+f 23/8/15 21/5/15 24/7/15
+f 25/8/16 23/5/16 26/7/16
+f 27/8/17 25/5/17 28/7/17
+f 29/8/18 27/5/18 30/7/18
+f 31/8/19 29/5/19 32/7/19
+f 33/8/20 31/5/20 34/7/20
+f 35/8/21 33/5/21 36/7/21
+f 37/8/22 35/5/22 38/7/22
+f 39/8/23 37/5/23 40/7/23
+f 41/8/24 39/5/24 42/7/24
+f 42/6/25 44/7/25 43/8/25
+f 44/6/26 46/7/26 45/8/26
+f 46/6/27 48/7/27 47/8/27
+f 48/6/28 50/7/28 49/8/28
+f 50/6/29 52/7/29 51/8/29
+f 52/6/30 54/7/30 53/8/30
+f 54/6/31 56/7/31 55/8/31
+f 56/6/32 58/7/32 57/8/32
+f 58/6/33 60/7/33 59/8/33
+f 60/6/34 62/7/34 61/8/34
+f 62/6/35 64/7/35 63/8/35
+f 64/6/36 66/7/36 65/8/36
+f 66/6/37 68/7/37 67/8/37
+f 68/6/38 70/7/38 69/8/38
+f 10/10/39 72/12/39 14/11/39
+f 72/12/39 70/13/39 14/11/39
+f 70/13/39 68/14/39 14/11/39
+f 68/14/39 66/15/39 14/11/39
+f 66/15/39 64/16/39 14/11/39
+f 64/16/39 62/17/39 14/11/39
+f 62/17/39 60/18/39 14/11/39
+f 60/18/39 58/19/39 14/11/39
+f 58/19/39 56/20/39 14/11/39
+f 56/20/39 54/21/39 14/11/39
+f 54/21/39 52/22/39 14/11/39
+f 52/22/39 50/23/39 14/11/39
+f 50/23/39 48/24/39 14/11/39
+f 48/24/39 46/25/39 14/11/39
+f 46/25/39 44/26/39 14/11/39
+f 44/26/39 42/27/39 14/11/39
+f 42/27/39 40/28/39 14/11/39
+f 40/28/39 38/29/39 14/11/39
+f 38/29/39 36/30/39 14/11/39
+f 36/30/39 34/31/39 14/11/39
+f 34/31/39 32/32/39 14/11/39
+f 32/32/39 30/33/39 14/11/39
+f 30/33/39 28/34/39 14/11/39
+f 28/34/39 26/35/39 14/11/39
+f 26/35/39 24/36/39 14/11/39
+f 24/36/39 22/37/39 14/11/39
+f 22/37/39 20/38/39 14/11/39
+f 20/38/39 18/39/39 16/40/39
+f 14/11/39 20/38/39 16/40/39
+f 72/6/40 10/7/40 9/8/40
+f 70/6/41 72/7/41 71/8/41
+f 11/10/42 13/12/42 71/11/42
+f 13/12/42 15/13/42 71/11/42
+f 15/13/42 17/14/42 71/11/42
+f 17/14/42 19/15/42 71/11/42
+f 19/15/42 21/16/42 71/11/42
+f 21/16/42 23/17/42 71/11/42
+f 23/17/42 25/18/42 71/11/42
+f 25/18/42 27/19/42 71/11/42
+f 27/19/42 29/20/42 71/11/42
+f 29/20/42 31/21/42 71/11/42
+f 31/21/42 33/22/42 71/11/42
+f 33/22/42 35/23/42 71/11/42
+f 35/23/42 37/24/42 71/11/42
+f 37/24/42 39/25/42 71/11/42
+f 39/25/42 41/26/42 71/11/42
+f 41/26/42 43/27/42 71/11/42
+f 43/27/42 45/28/42 71/11/42
+f 45/28/42 47/29/42 71/11/42
+f 47/29/42 49/30/42 71/11/42
+f 49/30/42 51/31/42 71/11/42
+f 51/31/42 53/32/42 71/11/42
+f 53/32/42 55/33/42 71/11/42
+f 55/33/42 57/34/42 71/11/42
+f 57/34/42 59/35/42 71/11/42
+f 59/35/42 61/36/42 71/11/42
+f 61/36/42 63/37/42 71/11/42
+f 63/37/42 65/38/42 71/11/42
+f 65/38/42 67/39/42 71/11/42
+f 67/39/42 69/40/42 71/11/42
+v 1.365790 1.252743 0.402799
+v 1.365790 2.069450 0.402799
+v 1.446312 1.252743 0.410646
+v 1.446312 2.069450 0.410646
+v 1.523739 1.252743 0.433883
+v 1.523739 2.069450 0.433883
+v 1.595097 1.252743 0.471619
+v 1.595097 2.069450 0.471619
+v 1.657642 1.252743 0.522403
+v 1.657642 2.069450 0.522403
+v 1.708972 1.252743 0.584284
+v 1.708972 2.069450 0.584284
+v 1.747113 1.252743 0.654883
+v 1.747113 2.069450 0.654883
+v 1.770600 1.252743 0.731487
+v 1.770600 2.069450 0.731487
+v 1.778531 1.252743 0.811153
+v 1.778531 2.069450 0.811153
+v 1.770600 1.252743 0.890818
+v 1.770600 2.069450 0.890818
+v 1.747113 1.252743 0.967423
+v 1.747113 2.069450 0.967423
+v 1.708972 1.252743 1.038022
+v 1.708972 2.069450 1.038022
+v 1.657642 1.252743 1.099902
+v 1.657642 2.069450 1.099902
+v 1.595097 1.252743 1.150686
+v 1.595097 2.069450 1.150686
+v 1.523739 1.252743 1.188422
+v 1.523739 2.069450 1.188422
+v 1.446312 1.252743 1.211660
+v 1.446312 2.069450 1.211660
+v 1.365790 1.252743 1.219506
+v 1.365790 2.069450 1.219506
+v 1.285269 1.252743 1.211660
+v 1.285269 2.069450 1.211660
+v 1.207841 1.252743 1.188422
+v 1.207841 2.069450 1.188422
+v 1.136484 1.252743 1.150686
+v 1.136484 2.069450 1.150686
+v 1.073939 1.252743 1.099902
+v 1.073939 2.069450 1.099902
+v 1.022609 1.252743 1.038021
+v 1.022609 2.069450 1.038021
+v 0.984468 1.252743 0.967422
+v 0.984468 2.069450 0.967422
+v 0.960981 1.252743 0.890818
+v 0.960981 2.069450 0.890818
+v 0.953050 1.252743 0.811152
+v 0.953050 2.069450 0.811152
+v 0.960981 1.252743 0.731486
+v 0.960981 2.069450 0.731486
+v 0.984468 1.252743 0.654882
+v 0.984468 2.069450 0.654882
+v 1.022609 1.252743 0.584283
+v 1.022609 2.069450 0.584283
+v 1.073939 1.252743 0.522403
+v 1.073939 2.069450 0.522403
+v 1.136485 1.252743 0.471619
+v 1.136485 2.069450 0.471619
+v 1.207842 1.252743 0.433883
+v 1.207842 2.069450 0.433883
+v 1.285269 1.252743 0.410646
+v 1.285269 2.069450 0.410646
+vt 0.000000 0.000000
+vt 1.000000 0.000000
+vt 1.000000 1.000000
+vt 0.000000 1.000000
+vt 0.500000 1.000000
+vt 0.597545 0.990393
+vt 0.402456 0.990393
+vt 0.691342 0.961940
+vt 0.777785 0.915735
+vt 0.853553 0.853553
+vt 0.915735 0.777785
+vt 0.961940 0.691342
+vt 0.990393 0.597545
+vt 1.000000 0.500000
+vt 0.990393 0.402455
+vt 0.961940 0.308658
+vt 0.915735 0.222215
+vt 0.853553 0.146447
+vt 0.777785 0.084265
+vt 0.691342 0.038060
+vt 0.597545 0.009607
+vt 0.500000 0.000000
+vt 0.402455 0.009607
+vt 0.308658 0.038060
+vt 0.222215 0.084265
+vt 0.146446 0.146447
+vt 0.084265 0.222215
+vt 0.038060 0.308659
+vt 0.009607 0.402455
+vt 0.000000 0.500000
+vt 0.009607 0.597546
+vt 0.038060 0.691342
+vt 0.084266 0.777786
+vt 0.146447 0.853554
+vt 0.222215 0.915735
+vt 0.308659 0.961940
+vn -0.879812 0.000000 0.475321
+vn -0.956071 0.000000 0.293136
+vn -0.956070 -0.000000 -0.293137
+s off
+f 73/41/9 74/42/9 76/43/9
+f 75/41/10 76/42/10 78/43/10
+f 77/41/11 78/42/11 80/43/11
+f 79/41/12 80/42/12 82/43/12
+f 81/41/13 82/42/13 84/43/13
+f 83/41/14 84/42/14 86/43/14
+f 85/41/15 86/42/15 88/43/15
+f 87/41/16 88/42/16 90/43/16
+f 89/41/17 90/42/17 92/43/17
+f 91/41/18 92/42/18 94/43/18
+f 93/41/19 94/42/19 96/43/19
+f 95/41/20 96/42/20 98/43/20
+f 97/41/21 98/42/21 100/43/21
+f 99/41/22 100/42/22 102/43/22
+f 101/41/23 102/42/23 104/43/23
+f 103/41/24 104/42/24 106/43/24
+f 105/41/25 106/42/25 107/44/25
+f 107/41/26 108/42/26 109/44/26
+f 109/41/27 110/42/27 111/44/27
+f 111/41/28 112/42/28 113/44/28
+f 113/41/29 114/42/29 115/44/29
+f 115/41/43 116/42/43 117/44/43
+f 117/41/44 118/42/44 119/44/44
+f 119/41/32 120/42/32 121/44/32
+f 121/41/33 122/42/33 123/44/33
+f 123/41/45 124/42/45 125/44/45
+f 125/41/35 126/42/35 127/44/35
+f 127/41/36 128/42/36 129/44/36
+f 129/41/37 130/42/37 131/44/37
+f 131/41/38 132/42/38 133/44/38
+f 76/45/39 74/46/39 78/47/39
+f 135/41/40 136/42/40 73/44/40
+f 133/41/41 134/42/41 135/44/41
+f 73/45/42 75/46/42 135/47/42
+f 75/44/9 73/41/9 76/43/9
+f 77/44/10 75/41/10 78/43/10
+f 79/44/11 77/41/11 80/43/11
+f 81/44/12 79/41/12 82/43/12
+f 83/44/13 81/41/13 84/43/13
+f 85/44/14 83/41/14 86/43/14
+f 87/44/15 85/41/15 88/43/15
+f 89/44/16 87/41/16 90/43/16
+f 91/44/17 89/41/17 92/43/17
+f 93/44/18 91/41/18 94/43/18
+f 95/44/19 93/41/19 96/43/19
+f 97/44/20 95/41/20 98/43/20
+f 99/44/21 97/41/21 100/43/21
+f 101/44/22 99/41/22 102/43/22
+f 103/44/23 101/41/23 104/43/23
+f 105/44/24 103/41/24 106/43/24
+f 106/42/25 108/43/25 107/44/25
+f 108/42/26 110/43/26 109/44/26
+f 110/42/27 112/43/27 111/44/27
+f 112/42/28 114/43/28 113/44/28
+f 114/42/29 116/43/29 115/44/29
+f 116/42/43 118/43/43 117/44/43
+f 118/42/44 120/43/44 119/44/44
+f 120/42/32 122/43/32 121/44/32
+f 122/42/33 124/43/33 123/44/33
+f 124/42/45 126/43/45 125/44/45
+f 126/42/35 128/43/35 127/44/35
+f 128/42/36 130/43/36 129/44/36
+f 130/42/37 132/43/37 131/44/37
+f 132/42/38 134/43/38 133/44/38
+f 74/46/39 136/48/39 78/47/39
+f 136/48/39 134/49/39 78/47/39
+f 134/49/39 132/50/39 78/47/39
+f 132/50/39 130/51/39 78/47/39
+f 130/51/39 128/52/39 78/47/39
+f 128/52/39 126/53/39 78/47/39
+f 126/53/39 124/54/39 78/47/39
+f 124/54/39 122/55/39 78/47/39
+f 122/55/39 120/56/39 78/47/39
+f 120/56/39 118/57/39 78/47/39
+f 118/57/39 116/58/39 78/47/39
+f 116/58/39 114/59/39 78/47/39
+f 114/59/39 112/60/39 78/47/39
+f 112/60/39 110/61/39 78/47/39
+f 110/61/39 108/62/39 78/47/39
+f 108/62/39 106/63/39 78/47/39
+f 106/63/39 104/64/39 78/47/39
+f 104/64/39 102/65/39 78/47/39
+f 102/65/39 100/66/39 78/47/39
+f 100/66/39 98/67/39 78/47/39
+f 98/67/39 96/68/39 78/47/39
+f 96/68/39 94/69/39 78/47/39
+f 94/69/39 92/70/39 78/47/39
+f 92/70/39 90/71/39 78/47/39
+f 90/71/39 88/72/39 78/47/39
+f 88/72/39 86/73/39 78/47/39
+f 86/73/39 84/74/39 78/47/39
+f 84/74/39 82/75/39 80/76/39
+f 78/47/39 84/74/39 80/76/39
+f 136/42/40 74/43/40 73/44/40
+f 134/42/41 136/43/41 135/44/41
+f 75/46/42 77/48/42 135/47/42
+f 77/48/42 79/49/42 135/47/42
+f 79/49/42 81/50/42 135/47/42
+f 81/50/42 83/51/42 135/47/42
+f 83/51/42 85/52/42 135/47/42
+f 85/52/42 87/53/42 135/47/42
+f 87/53/42 89/54/42 135/47/42
+f 89/54/42 91/55/42 135/47/42
+f 91/55/42 93/56/42 135/47/42
+f 93/56/42 95/57/42 135/47/42
+f 95/57/42 97/58/42 135/47/42
+f 97/58/42 99/59/42 135/47/42
+f 99/59/42 101/60/42 135/47/42
+f 101/60/42 103/61/42 135/47/42
+f 103/61/42 105/62/42 135/47/42
+f 105/62/42 107/63/42 135/47/42
+f 107/63/42 109/64/42 135/47/42
+f 109/64/42 111/65/42 135/47/42
+f 111/65/42 113/66/42 135/47/42
+f 113/66/42 115/67/42 135/47/42
+f 115/67/42 117/68/42 135/47/42
+f 117/68/42 119/69/42 135/47/42
+f 119/69/42 121/70/42 135/47/42
+f 121/70/42 123/71/42 135/47/42
+f 123/71/42 125/72/42 135/47/42
+f 125/72/42 127/73/42 135/47/42
+f 127/73/42 129/74/42 135/47/42
+f 129/74/42 131/75/42 135/47/42
+f 131/75/42 133/76/42 135/47/42
+v -2.345663 0.025178 -0.194338
+v -2.345663 0.025178 -0.594338
+v -1.345663 0.025178 -0.594338
+v -1.345663 0.025178 -0.194338
+v -2.345663 1.525178 -0.194338
+v -2.345663 1.525178 -0.594338
+v -1.345663 1.525178 -0.594338
+v -1.345663 1.525178 -0.194338
+vt 0.000000 0.000000
+vt 1.000000 0.000000
+vt 0.000000 1.000000
+vt 1.000000 1.000000
+vn -1.000000 0.000000 0.000000
+vn 0.000000 0.000000 -1.000000
+vn 1.000000 -0.000000 0.000000
+vn 0.000000 0.000000 1.000000
+s off
+f 141/77/46 142/78/46 137/79/46
+f 142/77/47 143/78/47 138/79/47
+f 143/77/48 144/78/48 140/80/48
+f 144/77/49 141/78/49 137/80/49
+f 137/77/42 138/78/42 139/80/42
+f 144/77/39 143/78/39 142/80/39
+f 142/78/46 138/80/46 137/79/46
+f 143/78/47 139/80/47 138/79/47
+f 139/79/48 143/77/48 140/80/48
+f 140/79/49 144/77/49 137/80/49
+f 140/79/42 137/77/42 139/80/42
+f 141/79/39 144/77/39 142/80/39
+v 1.364119 0.019809 -0.205019
+v 1.364119 0.019809 -0.605019
+v 2.364120 0.019809 -0.605019
+v 2.364120 0.019809 -0.205019
+v 1.364119 1.419809 -0.205019
+v 1.364119 1.419809 -0.605019
+v 2.364120 1.419809 -0.605019
+v 2.364120 1.419809 -0.205019
+vt 0.000000 0.000000
+vt 1.000000 0.000000
+vt 0.000000 1.000000
+vt 1.000000 1.000000
+s off
+f 149/81/46 150/82/46 145/83/46
+f 150/81/47 151/82/47 146/83/47
+f 151/81/48 152/82/48 148/84/48
+f 152/81/49 149/82/49 145/84/49
+f 145/81/42 146/82/42 147/84/42
+f 152/81/39 151/82/39 150/84/39
+f 150/82/46 146/84/46 145/83/46
+f 151/82/47 147/84/47 146/83/47
+f 147/83/48 151/81/48 148/84/48
+f 148/83/49 152/81/49 145/84/49
+f 148/83/42 145/81/42 147/84/42
+f 149/83/39 152/81/39 150/84/39
+v -1.384247 0.015116 3.993316
+v -1.384247 0.015113 -0.006684
+v -1.189157 0.034331 3.993316
+v -1.189157 0.034328 -0.006684
+v -1.001564 0.091236 3.993316
+v -1.001564 0.091234 -0.006684
+v -0.828677 0.183646 3.993316
+v -0.828677 0.183644 -0.006684
+v -0.677140 0.308009 3.993316
+v -0.677140 0.308007 -0.006684
+v -0.552777 0.459546 3.993316
+v -0.552777 0.459543 -0.006684
+v -0.460368 0.632432 3.993316
+v -0.460368 0.632430 -0.006684
+v -0.403462 0.820025 3.993316
+v -0.403462 0.820023 -0.006684
+v -0.384247 1.015116 3.993316
+v -0.384247 1.015113 -0.006684
+v -0.403462 1.210206 3.993316
+v -0.403462 1.210203 -0.006685
+v -0.460368 1.397799 3.993315
+v -0.460368 1.397797 -0.006685
+v -0.552777 1.570686 3.993315
+v -0.552777 1.570683 -0.006685
+v -0.677140 1.722223 3.993315
+v -0.677140 1.722220 -0.006685
+v -0.828677 1.846586 3.993315
+v -0.828677 1.846583 -0.006685
+v -1.001564 1.938995 3.993315
+v -1.001564 1.938993 -0.006685
+v -1.189157 1.995901 3.993315
+v -1.189157 1.995899 -0.006685
+v -1.384247 2.015116 3.993315
+v -1.384247 2.015113 -0.006685
+v -1.579338 1.995901 3.993315
+v -1.579338 1.995898 -0.006685
+v -1.766931 1.938995 3.993315
+v -1.766931 1.938993 -0.006685
+v -1.939818 1.846585 3.993315
+v -1.939818 1.846583 -0.006685
+v -2.091354 1.722222 3.993315
+v -2.091354 1.722220 -0.006685
+v -2.215717 1.570685 3.993315
+v -2.215717 1.570683 -0.006685
+v -2.308127 1.397799 3.993315
+v -2.308127 1.397796 -0.006685
+v -2.365032 1.210205 3.993316
+v -2.365032 1.210203 -0.006685
+v -2.384247 1.015115 3.993316
+v -2.384247 1.015112 -0.006684
+v -2.365032 0.820024 3.993316
+v -2.365032 0.820022 -0.006684
+v -2.308126 0.632431 3.993316
+v -2.308126 0.632429 -0.006684
+v -2.215716 0.459545 3.993316
+v -2.215716 0.459542 -0.006684
+v -2.091353 0.308008 3.993316
+v -2.091353 0.308006 -0.006684
+v -1.939816 0.183645 3.993316
+v -1.939816 0.183643 -0.006684
+v -1.766929 0.091236 3.993316
+v -1.766929 0.091233 -0.006684
+v -1.579336 0.034330 3.993316
+v -1.579336 0.034328 -0.006684
+v -1.384247 0.619839 -0.156215
+v -1.307133 0.627434 -0.156215
+v -1.232982 0.649928 -0.156215
+v -1.164645 0.686455 -0.156215
+v -1.104746 0.735612 -0.156215
+v -1.055589 0.795511 -0.156215
+v -1.019062 0.863848 -0.156216
+v -0.996568 0.937999 -0.156216
+v -0.988973 1.015113 -0.156216
+v -0.996568 1.092227 -0.156216
+v -1.019062 1.166378 -0.156216
+v -1.055589 1.234715 -0.156216
+v -1.104746 1.294614 -0.156216
+v -1.164645 1.343771 -0.156216
+v -1.232982 1.380298 -0.156216
+v -1.307133 1.402792 -0.156216
+v -1.384247 1.410387 -0.156216
+v -1.461361 1.402792 -0.156216
+v -1.535512 1.380298 -0.156216
+v -1.603849 1.343771 -0.156216
+v -1.663748 1.294613 -0.156216
+v -1.712905 1.234715 -0.156216
+v -1.749432 1.166377 -0.156216
+v -1.771926 1.092227 -0.156216
+v -1.779521 1.015113 -0.156216
+v -1.771925 0.937999 -0.156216
+v -1.749432 0.863848 -0.156216
+v -1.712905 0.795510 -0.156215
+v -1.663747 0.735612 -0.156215
+v -1.603849 0.686455 -0.156215
+v -1.535511 0.649928 -0.156215
+v -1.461360 0.627434 -0.156215
+v -1.384247 0.605419 4.159918
+v -1.304319 0.613291 4.159918
+v -1.227463 0.636605 4.159918
+v -1.156632 0.674465 4.159918
+v -1.094548 0.725416 4.159918
+v -1.043596 0.787500 4.159918
+v -1.005736 0.858332 4.159918
+v -0.982422 0.935188 4.159918
+v -0.974550 1.015116 4.159918
+v -0.982422 1.095044 4.159918
+v -1.005736 1.171900 4.159918
+v -1.043596 1.242731 4.159918
+v -1.094548 1.304815 4.159918
+v -1.156632 1.355766 4.159918
+v -1.227463 1.393626 4.159918
+v -1.304319 1.416940 4.159918
+v -1.384247 1.424813 4.159918
+v -1.464175 1.416940 4.159918
+v -1.541031 1.393626 4.159918
+v -1.611863 1.355766 4.159918
+v -1.673947 1.304815 4.159918
+v -1.724898 1.242731 4.159918
+v -1.762758 1.171900 4.159918
+v -1.786072 1.095043 4.159918
+v -1.793944 1.015115 4.159918
+v -1.786072 0.935187 4.159918
+v -1.762757 0.858331 4.159918
+v -1.724897 0.787500 4.159918
+v -1.673946 0.725416 4.159918
+v -1.611862 0.674465 4.159918
+v -1.541031 0.636605 4.159918
+v -1.464174 0.613291 4.159918
+v 0.006492 3.927105 -3.525056
+v 0.006492 4.374522 -3.525055
+v 0.050135 3.927105 -3.520757
+v 0.050135 4.374522 -3.520757
+v 0.092102 3.927104 -3.508027
+v 0.092102 4.374522 -3.508027
+v 0.130778 3.927104 -3.487354
+v 0.130778 4.374522 -3.487354
+v 0.164678 3.927104 -3.459533
+v 0.164678 4.374522 -3.459533
+v 0.192499 3.927104 -3.425632
+v 0.192499 4.374522 -3.425633
+v 0.213172 3.927105 -3.386956
+v 0.213172 4.374522 -3.386957
+v 0.225902 3.927105 -3.344990
+v 0.225902 4.374522 -3.344990
+v 0.230201 3.927105 -3.301347
+v 0.230201 4.374522 -3.301347
+v 0.225902 3.927105 -3.257704
+v 0.225902 4.374522 -3.257703
+v 0.213172 3.927105 -3.215737
+v 0.213172 4.374522 -3.215738
+v 0.192499 3.927105 -3.177061
+v 0.192499 4.374522 -3.177062
+v 0.164678 3.927105 -3.143161
+v 0.164678 4.374522 -3.143161
+v 0.130778 3.927105 -3.115340
+v 0.130778 4.374522 -3.115340
+v 0.092102 3.927105 -3.094666
+v 0.092102 4.374522 -3.094667
+v 0.050135 3.927105 -3.081937
+v 0.050135 4.374522 -3.081936
+v 0.006492 3.927105 -3.077638
+v 0.006492 4.374522 -3.077638
+v -0.037151 3.927105 -3.081937
+v -0.037151 4.374522 -3.081936
+v -0.079118 3.927105 -3.094666
+v -0.079118 4.374522 -3.094667
+v -0.117794 3.927105 -3.115340
+v -0.117794 4.374522 -3.115340
+v -0.151694 3.927105 -3.143161
+v -0.151694 4.374522 -3.143161
+v -0.179515 3.927105 -3.177061
+v -0.179515 4.374522 -3.177062
+v -0.200188 3.927105 -3.215737
+v -0.200188 4.374522 -3.215738
+v -0.212918 3.927105 -3.257704
+v -0.212918 4.374522 -3.257704
+v -0.217217 3.927105 -3.301347
+v -0.217217 4.374522 -3.301347
+v -0.212918 3.927105 -3.344991
+v -0.212918 4.374522 -3.344990
+v -0.200188 3.927105 -3.386957
+v -0.200188 4.374522 -3.386957
+v -0.179515 3.927104 -3.425633
+v -0.179515 4.374522 -3.425633
+v -0.151694 3.927104 -3.459533
+v -0.151694 4.374522 -3.459533
+v -0.117794 3.927104 -3.487354
+v -0.117794 4.374522 -3.487354
+v -0.079117 3.927104 -3.508027
+v -0.079117 4.374522 -3.508027
+v -0.037151 3.927105 -3.520757
+v -0.037151 4.374522 -3.520757
+vt 0.000000 0.000000
+vt 1.000000 0.000000
+vt 1.000000 1.000000
+vt 0.000000 1.000000
+vt 0.853553 0.853553
+vt 0.691342 0.961940
+vt 0.777785 0.915735
+vt 0.500000 1.000000
+vt 0.597545 0.990393
+vt 0.402456 0.990393
+vt 0.915735 0.777785
+vt 0.961940 0.691342
+vt 0.500000 0.000000
+vt 0.691342 0.038060
+vt 0.597545 0.009607
+vt 0.777785 0.084265
+vt 0.853553 0.146447
+vt 0.084265 0.222215
+vt 0.222215 0.084265
+vt 0.146446 0.146447
+vt 0.038060 0.308659
+vt 0.222215 0.915735
+vt 0.308659 0.961940
+vt 0.146447 0.853554
+vt 0.990393 0.597545
+vt 0.084266 0.777786
+vt 0.038060 0.691342
+vt 1.000000 0.500000
+vt 0.990393 0.402455
+vt 0.961940 0.308658
+vt 0.009607 0.597546
+vt 0.000000 0.500000
+vt 0.915735 0.222215
+vt 0.009607 0.402455
+vt 0.402455 0.009607
+vt 0.308658 0.038060
+vn 0.098018 -0.995185 0.000001
+vn 0.290285 -0.956940 0.000001
+vn 0.471397 -0.881921 0.000001
+vn 0.634393 -0.773010 0.000000
+vn 0.773010 -0.634393 0.000000
+vn 0.881921 -0.471397 0.000000
+vn 0.956940 -0.290285 0.000000
+vn 0.995185 -0.098017 0.000000
+vn 0.995185 0.098017 -0.000000
+vn 0.956940 0.290285 -0.000000
+vn 0.881922 0.471396 -0.000000
+vn 0.773010 0.634393 -0.000000
+vn 0.634393 0.773011 -0.000000
+vn 0.471397 0.881921 -0.000001
+vn 0.290284 0.956941 -0.000001
+vn 0.098017 0.995185 -0.000001
+vn -0.098017 0.995185 -0.000001
+vn -0.290285 0.956940 -0.000001
+vn -0.471397 0.881921 -0.000001
+vn -0.634394 0.773010 -0.000000
+vn -0.773011 0.634393 -0.000000
+vn -0.881922 0.471396 -0.000000
+vn -0.956941 0.290283 -0.000000
+vn -0.995185 0.098017 -0.000000
+vn -0.995185 -0.098018 0.000000
+vn -0.956940 -0.290286 0.000000
+vn -0.881920 -0.471398 0.000000
+vn -0.773010 -0.634394 0.000000
+vn -0.634393 -0.773011 0.000000
+vn -0.471395 -0.881922 0.000001
+vn -0.239975 -0.023636 -0.970491
+vn -0.098017 -0.995185 0.000001
+vn -0.290283 -0.956941 0.000001
+vn -0.210907 0.173086 0.962060
+vn 0.152975 0.186400 -0.970492
+vn -0.023635 -0.239975 -0.970491
+vn -0.152975 0.186400 -0.970492
+vn 0.239975 -0.023636 -0.970491
+vn -0.212662 -0.113672 -0.970491
+vn 0.069999 0.230752 -0.970492
+vn 0.113670 -0.212663 -0.970491
+vn -0.212662 0.113670 -0.970492
+vn 0.230752 0.069998 -0.970492
+vn -0.152975 -0.186401 -0.970491
+vn -0.023636 0.239974 -0.970492
+vn 0.186400 -0.152976 -0.970491
+vn -0.239974 0.023634 -0.970491
+vn 0.186401 0.152974 -0.970491
+vn -0.069998 -0.230753 -0.970491
+vn -0.113670 0.212662 -0.970492
+vn 0.230753 -0.069999 -0.970491
+vn -0.230752 -0.069999 -0.970491
+vn 0.113670 0.212662 -0.970491
+vn 0.069998 -0.230753 -0.970491
+vn -0.186401 0.152974 -0.970491
+vn 0.239975 0.023634 -0.970492
+vn -0.186400 -0.152976 -0.970491
+vn 0.023635 0.239974 -0.970492
+vn 0.152975 -0.186401 -0.970491
+vn -0.230752 0.069998 -0.970492
+vn 0.023636 -0.239975 -0.970491
+vn 0.212662 0.113670 -0.970492
+vn -0.113670 -0.212664 -0.970491
+vn -0.069999 0.230751 -0.970492
+vn 0.212663 -0.113671 -0.970491
+vn 0.271524 0.026743 0.962060
+vn -0.210906 -0.173085 0.962060
+vn 0.026743 0.271524 0.962060
+vn 0.173086 -0.210906 0.962060
+vn -0.261089 0.079201 0.962060
+vn 0.026743 -0.271523 0.962060
+vn 0.240621 0.128616 0.962060
+vn -0.128614 -0.240621 0.962060
+vn -0.079201 0.261090 0.962060
+vn 0.240621 -0.128614 0.962060
+vn -0.271524 -0.026742 0.962060
+vn 0.173087 0.210907 0.962060
+vn -0.026742 -0.271523 0.962060
+vn -0.173087 0.210907 0.962060
+vn 0.271524 -0.026742 0.962060
+vn -0.240621 -0.128615 0.962060
+vn 0.079201 0.261090 0.962060
+vn 0.128614 -0.240621 0.962060
+vn -0.240621 0.128616 0.962060
+vn 0.261089 0.079202 0.962060
+vn -0.173086 -0.210906 0.962060
+vn -0.026743 0.271524 0.962060
+vn 0.210907 -0.173085 0.962060
+vn -0.271524 0.026743 0.962060
+vn 0.210907 0.173087 0.962060
+vn -0.079201 -0.261088 0.962060
+vn -0.128615 0.240622 0.962060
+vn 0.261089 -0.079200 0.962060
+vn -0.261089 -0.079201 0.962060
+vn 0.128615 0.240622 0.962060
+vn 0.079201 -0.261088 0.962060
+vn 0.098012 0.000001 -0.995185
+vn 0.290289 -0.000001 -0.956939
+vn 0.471395 0.000001 -0.881922
+vn 0.634385 0.000001 -0.773018
+vn 0.773012 -0.000001 -0.634391
+vn 0.881922 -0.000001 -0.471395
+vn 0.956942 -0.000000 -0.290279
+vn 0.995184 -0.000000 -0.098022
+vn 0.995184 0.000000 0.098020
+vn 0.956940 -0.000000 0.290285
+vn 0.881922 0.000001 0.471395
+vn 0.773012 0.000001 0.634391
+vn 0.634398 0.000001 0.773007
+vn 0.471395 -0.000001 0.881922
+vn 0.290289 0.000001 0.956939
+vn 0.098010 -0.000001 0.995185
+vn -0.098033 0.000001 0.995183
+vn -0.290269 -0.000001 0.956945
+vn -0.471412 0.000001 0.881913
+vn -0.634385 -0.000001 0.773018
+vn -0.773012 0.000001 0.634391
+vn -0.881922 0.000001 0.471395
+vn -0.956942 0.000000 0.290279
+vn -0.995184 0.000000 0.098022
+vn -0.995184 -0.000000 -0.098020
+vn -0.956939 0.000000 -0.290289
+vn -0.881922 0.000001 -0.471395
+vn -0.773003 0.000001 -0.634402
+vn -0.634398 -0.000001 -0.773007
+vn -0.471395 0.000001 -0.881922
+vn -0.098032 -0.000001 -0.995183
+vn -0.290292 0.000001 -0.956938
+vn 0.098017 -0.995185 0.000001
+vn 0.881921 -0.471396 0.000000
+vn 0.881921 0.471397 -0.000000
+vn 0.634394 0.773010 -0.000001
+vn 0.471396 0.881922 -0.000001
+vn 0.290285 0.956940 -0.000001
+vn -0.098018 0.995185 -0.000001
+vn -0.881920 -0.471399 0.000000
+vn -0.239974 -0.023636 -0.970491
+vn -0.210907 0.173087 0.962060
+vn 0.000000 -0.000003 -1.000000
+vn 0.152975 0.186400 -0.970491
+vn 0.239975 -0.023635 -0.970491
+vn -0.212662 -0.113674 -0.970491
+vn 0.069998 0.230752 -0.970492
+vn 0.113671 -0.212663 -0.970491
+vn -0.212664 0.113668 -0.970491
+vn 0.230752 0.069998 -0.970491
+vn -0.023635 0.239974 -0.970492
+vn 0.186401 -0.152975 -0.970491
+vn -0.239974 0.023635 -0.970492
+vn 0.186400 0.152975 -0.970491
+vn 0.230753 -0.069998 -0.970491
+vn -0.230753 -0.069998 -0.970491
+vn -0.186400 0.152974 -0.970492
+vn 0.239974 0.023635 -0.970492
+vn -0.186400 -0.152975 -0.970491
+vn 0.023635 -0.239975 -0.970491
+vn 0.212664 0.113668 -0.970491
+vn -0.113670 -0.212663 -0.970491
+vn -0.069998 0.230752 -0.970492
+vn 0.212662 -0.113674 -0.970491
+vn -0.210905 -0.173086 0.962060
+vn -0.261089 0.079200 0.962060
+vn 0.026743 -0.271523 0.962061
+vn 0.240622 0.128615 0.962060
+vn 0.240621 -0.128615 0.962060
+vn -0.271523 -0.026743 0.962060
+vn 0.271523 -0.026743 0.962060
+vn 0.079200 0.261090 0.962060
+vn 0.128615 -0.240620 0.962060
+vn -0.240622 0.128615 0.962060
+vn 0.261089 0.079200 0.962060
+vn -0.173085 -0.210906 0.962060
+vn 0.210906 -0.173086 0.962060
+vn -0.271524 0.026742 0.962060
+vn -0.079200 -0.261088 0.962061
+vn -0.261089 -0.079200 0.962060
+vn 0.079200 -0.261088 0.962060
+vn 0.098033 -0.000001 -0.995183
+vn 0.290269 0.000001 -0.956945
+vn 0.634398 -0.000001 -0.773007
+vn 0.995184 -0.000000 0.098022
+vn 0.956942 0.000000 0.290279
+vn 0.634385 -0.000001 0.773018
+vn 0.471412 0.000001 0.881913
+vn 0.290269 -0.000001 0.956945
+vn 0.098032 0.000001 0.995183
+vn -0.098012 -0.000001 0.995185
+vn -0.290289 0.000001 0.956939
+vn -0.471395 -0.000001 0.881922
+vn -0.634398 0.000001 0.773007
+vn -0.995184 0.000000 -0.098022
+vn -0.773012 -0.000001 -0.634391
+vn -0.634385 0.000001 -0.773018
+vn -0.098010 0.000001 -0.995185
+vn -0.290272 -0.000001 -0.956944
+vn 0.000000 -1.000000 -0.000019
+vn -0.000002 -1.000000 0.000007
+vn 0.000001 -1.000000 -0.000000
+vn -0.000027 -1.000000 -0.000008
+vn -0.000009 -1.000000 -0.000001
+vn -0.000005 -1.000000 0.000000
+vn -0.000003 -1.000000 0.000001
+vn -0.000002 -1.000000 0.000001
+vn -0.000001 -1.000000 0.000001
+vn 0.000000 -1.000000 0.000001
+s off
+f 153/85/50 154/86/50 156/87/50
+f 155/85/51 156/86/51 158/87/51
+f 157/85/52 158/86/52 160/87/52
+f 159/85/53 160/86/53 162/87/53
+f 161/85/54 162/86/54 164/87/54
+f 163/85/55 164/86/55 166/87/55
+f 165/85/56 166/86/56 168/87/56
+f 167/85/57 168/86/57 170/87/57
+f 169/85/58 170/86/58 172/87/58
+f 171/85/59 172/86/59 174/87/59
+f 173/85/60 174/86/60 176/87/60
+f 175/85/61 176/86/61 178/87/61
+f 177/85/62 178/86/62 180/87/62
+f 179/85/63 180/86/63 182/87/63
+f 181/85/64 182/86/64 184/87/64
+f 183/85/65 184/86/65 186/87/65
+f 185/85/66 186/86/66 187/88/66
+f 187/85/67 188/86/67 189/88/67
+f 189/85/68 190/86/68 191/88/68
+f 191/85/69 192/86/69 193/88/69
+f 193/85/70 194/86/70 195/88/70
+f 195/85/71 196/86/71 197/88/71
+f 197/85/72 198/86/72 199/88/72
+f 199/85/73 200/86/73 201/88/73
+f 201/85/74 202/86/74 203/88/74
+f 203/85/75 204/86/75 205/88/75
+f 205/85/76 206/86/76 207/88/76
+f 207/85/77 208/86/77 209/88/77
+f 209/85/78 210/86/78 211/88/78
+f 211/85/79 212/86/79 213/88/79
+f 204/85/80 202/86/80 242/88/80
+f 215/85/81 216/86/81 153/88/81
+f 213/85/82 214/86/82 215/88/82
+f 193/85/83 195/86/83 269/88/83
+f 246/89/47 248/90/47 247/91/47
+f 180/85/84 178/86/84 229/87/84
+f 154/85/85 216/86/85 217/88/85
+f 194/85/86 192/86/86 237/88/86
+f 170/85/87 168/86/87 224/87/87
+f 208/85/88 206/86/88 244/88/88
+f 184/85/89 182/86/89 231/87/89
+f 160/85/90 158/86/90 219/87/90
+f 198/85/91 196/86/91 239/88/91
+f 174/85/92 172/86/92 226/87/92
+f 212/85/93 210/86/93 246/88/93
+f 188/85/94 186/86/94 234/88/94
+f 164/85/95 162/86/95 221/87/95
+f 202/85/96 200/86/96 241/88/96
+f 178/85/97 176/86/97 228/87/97
+f 216/85/98 214/86/98 248/88/98
+f 192/85/99 190/86/99 236/88/99
+f 168/85/100 166/86/100 223/87/100
+f 206/85/101 204/86/101 243/88/101
+f 182/85/102 180/86/102 230/87/102
+f 158/85/103 156/86/103 218/87/103
+f 196/85/104 194/86/104 238/88/104
+f 172/85/105 170/86/105 225/87/105
+f 210/85/106 208/86/106 245/88/106
+f 186/85/107 184/86/107 232/87/107
+f 162/85/108 160/86/108 220/87/108
+f 200/85/109 198/86/109 240/88/109
+f 156/85/110 154/86/110 217/87/110
+f 176/85/111 174/86/111 227/87/111
+f 214/85/112 212/86/112 247/88/112
+f 190/85/113 188/86/113 235/88/113
+f 166/85/114 164/86/114 222/87/114
+f 249/92/49 250/93/49 280/94/49
+f 169/85/115 171/86/115 258/87/115
+f 207/85/116 209/86/116 276/88/116
+f 183/85/117 185/86/117 265/87/117
+f 159/85/118 161/86/118 253/87/118
+f 197/85/119 199/86/119 271/88/119
+f 153/85/120 155/86/120 250/87/120
+f 173/85/121 175/86/121 260/87/121
+f 211/85/122 213/86/122 278/88/122
+f 187/85/123 189/86/123 266/88/123
+f 163/85/124 165/86/124 255/87/124
+f 201/85/125 203/86/125 273/88/125
+f 177/85/126 179/86/126 262/87/126
+f 215/85/127 153/86/127 280/88/127
+f 191/85/128 193/86/128 268/88/128
+f 167/85/129 169/86/129 257/87/129
+f 205/85/130 207/86/130 275/88/130
+f 181/85/131 183/86/131 264/87/131
+f 157/85/132 159/86/132 252/87/132
+f 195/85/133 197/86/133 270/88/133
+f 171/85/134 173/86/134 259/87/134
+f 209/85/135 211/86/135 277/88/135
+f 185/85/136 187/86/136 265/88/136
+f 161/85/137 163/86/137 254/87/137
+f 199/85/138 201/86/138 272/88/138
+f 175/85/139 177/86/139 261/87/139
+f 213/85/140 215/86/140 279/88/140
+f 189/85/141 191/86/141 267/88/141
+f 165/85/142 167/86/142 256/87/142
+f 203/85/143 205/86/143 274/88/143
+f 179/85/144 181/86/144 263/87/144
+f 155/85/145 157/86/145 251/87/145
+f 281/85/146 282/86/146 284/87/146
+f 283/85/147 284/86/147 286/87/147
+f 285/85/148 286/86/148 288/87/148
+f 287/85/149 288/86/149 290/87/149
+f 289/85/150 290/86/150 292/87/150
+f 291/85/151 292/86/151 294/87/151
+f 293/85/152 294/86/152 296/87/152
+f 295/85/153 296/86/153 298/87/153
+f 297/85/154 298/86/154 300/87/154
+f 299/85/155 300/86/155 302/87/155
+f 301/85/156 302/86/156 304/87/156
+f 303/85/157 304/86/157 306/87/157
+f 305/85/158 306/86/158 308/87/158
+f 307/85/159 308/86/159 310/87/159
+f 309/85/160 310/86/160 312/87/160
+f 311/85/161 312/86/161 314/87/161
+f 313/85/162 314/86/162 315/88/162
+f 315/85/163 316/86/163 317/88/163
+f 317/85/164 318/86/164 319/88/164
+f 319/85/165 320/86/165 321/88/165
+f 321/85/166 322/86/166 323/88/166
+f 323/85/167 324/86/167 325/88/167
+f 325/85/168 326/86/168 327/88/168
+f 327/85/169 328/86/169 329/88/169
+f 329/85/170 330/86/170 331/88/170
+f 331/85/171 332/86/171 333/88/171
+f 333/85/172 334/86/172 335/88/172
+f 335/85/173 336/86/173 337/88/173
+f 337/85/174 338/86/174 339/88/174
+f 339/85/175 340/86/175 341/88/175
+f 284/92/39 282/93/39 286/94/39
+f 343/85/176 344/86/176 281/88/176
+f 341/85/177 342/86/177 344/87/177
+f 281/92/42 283/93/42 343/94/42
+f 155/88/178 153/85/178 156/87/178
+f 157/88/51 155/85/51 158/87/51
+f 159/88/52 157/85/52 160/87/52
+f 161/88/53 159/85/53 162/87/53
+f 163/88/54 161/85/54 164/87/54
+f 165/88/179 163/85/179 166/87/179
+f 167/88/56 165/85/56 168/87/56
+f 169/88/57 167/85/57 170/87/57
+f 171/88/58 169/85/58 172/87/58
+f 173/88/59 171/85/59 174/87/59
+f 175/88/180 173/85/180 176/87/180
+f 177/88/61 175/85/61 178/87/61
+f 179/88/181 177/85/181 180/87/181
+f 181/88/182 179/85/182 182/87/182
+f 183/88/183 181/85/183 184/87/183
+f 185/88/65 183/85/65 186/87/65
+f 186/86/184 188/87/184 187/88/184
+f 188/86/67 190/87/67 189/88/67
+f 190/86/68 192/87/68 191/88/68
+f 192/86/69 194/87/69 193/88/69
+f 194/86/70 196/87/70 195/88/70
+f 196/86/71 198/87/71 197/88/71
+f 198/86/72 200/87/72 199/88/72
+f 200/86/73 202/87/73 201/88/73
+f 202/86/74 204/87/74 203/88/74
+f 204/86/75 206/87/75 205/88/75
+f 206/86/185 208/87/185 207/88/185
+f 208/86/77 210/87/77 209/88/77
+f 210/86/78 212/87/78 211/88/78
+f 212/86/79 214/87/79 213/88/79
+f 202/86/186 241/87/186 242/88/186
+f 216/86/81 154/87/81 153/88/81
+f 214/86/82 216/87/82 215/88/82
+f 195/86/187 270/87/187 269/88/187
+f 245/95/47 248/90/47 246/89/47
+f 244/96/47 248/90/47 245/95/47
+f 234/97/47 236/98/47 235/99/47
+f 234/97/47 237/100/47 236/98/47
+f 234/97/47 238/101/47 237/100/47
+f 229/102/47 231/103/47 230/104/47
+f 228/105/47 231/103/47 229/102/47
+f 219/94/47 221/106/47 220/107/47
+f 219/94/47 222/108/47 221/106/47
+f 218/92/47 217/93/47 219/94/47
+f 217/93/47 248/90/47 219/94/47
+f 248/90/47 244/96/47 219/94/47
+f 219/94/47 244/96/47 222/108/47
+f 244/96/188 243/109/188 222/108/188
+f 222/108/188 243/109/188 223/110/188
+f 223/110/47 243/109/47 224/111/47
+f 243/109/47 242/112/47 224/111/47
+f 242/112/47 241/113/47 224/111/47
+f 241/113/47 240/114/47 224/111/47
+f 224/111/47 240/114/47 225/115/47
+f 225/115/47 240/114/47 226/116/47
+f 240/114/47 239/117/47 226/116/47
+f 226/116/47 239/117/47 227/118/47
+f 239/117/188 238/101/188 227/118/188
+f 227/118/188 238/101/188 228/105/188
+f 228/105/47 238/101/47 231/103/47
+f 238/101/47 234/97/47 231/103/47
+f 234/97/47 233/119/47 232/120/47
+f 231/103/47 234/97/47 232/120/47
+f 230/88/189 180/85/189 229/87/189
+f 216/86/85 248/87/85 217/88/85
+f 192/86/86 236/87/86 237/88/86
+f 225/88/190 170/85/190 224/87/190
+f 206/86/191 243/87/191 244/88/191
+f 232/88/192 184/85/192 231/87/192
+f 220/88/193 160/85/193 219/87/193
+f 196/86/194 238/87/194 239/88/194
+f 227/88/195 174/85/195 226/87/195
+f 210/86/93 245/87/93 246/88/93
+f 186/86/196 233/87/196 234/88/196
+f 222/88/197 164/85/197 221/87/197
+f 200/86/198 240/87/198 241/88/198
+f 229/88/199 178/85/199 228/87/199
+f 214/86/98 247/87/98 248/88/98
+f 190/86/99 235/87/99 236/88/99
+f 224/88/200 168/85/200 223/87/200
+f 204/86/201 242/87/201 243/88/201
+f 231/88/102 182/85/102 230/87/102
+f 219/88/103 158/85/103 218/87/103
+f 194/86/202 237/87/202 238/88/202
+f 226/88/203 172/85/203 225/87/203
+f 208/86/204 244/87/204 245/88/204
+f 233/88/107 186/85/107 232/87/107
+f 221/88/108 162/85/108 220/87/108
+f 198/86/109 239/87/109 240/88/109
+f 218/88/205 156/85/205 217/87/205
+f 228/88/206 176/85/206 227/87/206
+f 212/86/207 246/87/207 247/88/207
+f 188/86/208 234/87/208 235/88/208
+f 223/88/209 166/85/209 222/87/209
+f 250/93/49 251/90/49 280/94/49
+f 251/90/49 252/91/49 280/94/49
+f 252/91/49 253/89/49 280/94/49
+f 253/89/49 254/95/49 280/94/49
+f 254/95/49 255/96/49 280/94/49
+f 255/96/49 256/109/49 280/94/49
+f 256/109/49 257/112/49 280/94/49
+f 257/112/49 258/113/49 280/94/49
+f 258/113/49 259/114/49 280/94/49
+f 259/114/49 260/117/49 280/94/49
+f 260/117/49 261/101/49 280/94/49
+f 261/101/49 262/100/49 280/94/49
+f 262/100/49 263/98/49 280/94/49
+f 263/98/49 264/99/49 280/94/49
+f 264/99/49 265/97/49 280/94/49
+f 265/97/49 266/119/49 280/94/49
+f 266/119/49 267/120/49 280/94/49
+f 267/120/49 268/103/49 280/94/49
+f 268/103/49 269/104/49 280/94/49
+f 269/104/49 270/102/49 280/94/49
+f 270/102/49 271/105/49 280/94/49
+f 271/105/49 272/118/49 280/94/49
+f 272/118/49 273/116/49 280/94/49
+f 273/116/49 274/115/49 280/94/49
+f 274/115/49 275/111/49 280/94/49
+f 275/111/49 276/110/49 280/94/49
+f 276/110/49 277/108/49 280/94/49
+f 277/108/49 278/106/49 279/107/49
+f 280/94/49 277/108/49 279/107/49
+f 257/88/115 169/85/115 258/87/115
+f 209/86/210 277/87/210 276/88/210
+f 264/88/117 183/85/117 265/87/117
+f 252/88/118 159/85/118 253/87/118
+f 199/86/211 272/87/211 271/88/211
+f 249/88/212 153/85/212 250/87/212
+f 259/88/213 173/85/213 260/87/213
+f 213/86/122 279/87/122 278/88/122
+f 189/86/123 267/87/123 266/88/123
+f 254/88/214 163/85/214 255/87/214
+f 203/86/215 274/87/215 273/88/215
+f 261/88/126 177/85/126 262/87/126
+f 153/86/127 249/87/127 280/88/127
+f 193/86/128 269/87/128 268/88/128
+f 256/88/216 167/85/216 257/87/216
+f 207/86/130 276/87/130 275/88/130
+f 263/88/217 181/85/217 264/87/217
+f 251/88/218 157/85/218 252/87/218
+f 197/86/219 271/87/219 270/88/219
+f 258/88/220 171/85/220 259/87/220
+f 211/86/221 278/87/221 277/88/221
+f 187/86/136 266/87/136 265/88/136
+f 253/88/222 161/85/222 254/87/222
+f 201/86/223 273/87/223 272/88/223
+f 260/88/139 175/85/139 261/87/139
+f 215/86/224 280/87/224 279/88/224
+f 191/86/141 268/87/141 267/88/141
+f 255/88/142 165/85/142 256/87/142
+f 205/86/225 275/87/225 274/88/225
+f 262/88/144 179/85/144 263/87/144
+f 250/88/226 155/85/226 251/87/226
+f 283/88/227 281/85/227 284/87/227
+f 285/88/228 283/85/228 286/87/228
+f 287/88/148 285/85/148 288/87/148
+f 289/88/229 287/85/229 290/87/229
+f 291/88/150 289/85/150 292/87/150
+f 293/88/151 291/85/151 294/87/151
+f 295/88/152 293/85/152 296/87/152
+f 297/88/153 295/85/153 298/87/153
+f 299/88/230 297/85/230 300/87/230
+f 301/88/231 299/85/231 302/87/231
+f 303/88/156 301/85/156 304/87/156
+f 305/88/157 303/85/157 306/87/157
+f 307/88/232 305/85/232 308/87/232
+f 309/88/233 307/85/233 310/87/233
+f 311/88/234 309/85/234 312/87/234
+f 313/88/235 311/85/235 314/87/235
+f 314/86/236 316/87/236 315/88/236
+f 316/86/237 318/87/237 317/88/237
+f 318/86/238 320/87/238 319/88/238
+f 320/86/239 322/87/239 321/88/239
+f 322/86/166 324/87/166 323/88/166
+f 324/86/167 326/87/167 325/88/167
+f 326/86/168 328/87/168 327/88/168
+f 328/86/169 330/87/169 329/88/169
+f 330/86/240 332/87/240 331/88/240
+f 332/86/171 334/87/171 333/88/171
+f 334/86/172 336/87/172 335/88/172
+f 336/86/241 338/87/241 337/88/241
+f 338/86/242 340/87/242 339/88/242
+f 340/86/175 342/87/175 341/88/175
+f 282/93/39 344/90/39 286/94/39
+f 344/90/39 342/91/39 286/94/39
+f 342/91/39 340/89/39 286/94/39
+f 340/89/39 338/95/39 286/94/39
+f 338/95/39 336/96/39 286/94/39
+f 336/96/39 334/109/39 286/94/39
+f 334/109/39 332/112/39 286/94/39
+f 332/112/39 330/113/39 286/94/39
+f 330/113/39 328/114/39 286/94/39
+f 328/114/39 326/117/39 286/94/39
+f 326/117/39 324/101/39 286/94/39
+f 324/101/39 322/100/39 286/94/39
+f 322/100/39 320/98/39 286/94/39
+f 320/98/39 318/99/39 286/94/39
+f 318/99/39 316/97/39 286/94/39
+f 316/97/39 314/119/39 286/94/39
+f 314/119/39 312/120/39 286/94/39
+f 312/120/39 310/103/39 286/94/39
+f 310/103/39 308/104/39 286/94/39
+f 308/104/39 306/102/39 286/94/39
+f 306/102/39 304/105/39 286/94/39
+f 304/105/39 302/118/39 286/94/39
+f 302/118/39 300/116/39 286/94/39
+f 300/116/39 298/115/39 286/94/39
+f 298/115/39 296/111/39 286/94/39
+f 296/111/39 294/110/39 286/94/39
+f 294/110/39 292/108/39 286/94/39
+f 292/108/39 290/106/39 286/94/39
+f 290/106/39 288/107/39 286/94/39
+f 344/86/243 282/87/243 281/88/243
+f 343/88/244 341/85/244 344/87/244
+f 283/93/245 285/90/245 343/94/245
+f 343/94/245 285/90/245 341/107/245
+f 285/90/42 287/91/42 341/107/42
+f 287/91/42 289/89/42 341/107/42
+f 289/89/42 291/95/42 341/107/42
+f 291/95/246 293/96/246 341/107/246
+f 293/96/247 295/109/247 341/107/247
+f 335/110/248 331/115/248 333/111/248
+f 295/109/247 297/112/247 341/107/247
+f 297/112/247 299/113/247 341/107/247
+f 335/110/249 329/116/249 331/115/249
+f 299/113/247 301/114/247 341/107/247
+f 335/110/250 327/118/250 329/116/250
+f 301/114/247 303/117/247 341/107/247
+f 335/110/251 325/105/251 327/118/251
+f 303/117/42 305/101/42 341/107/42
+f 335/110/252 323/102/252 325/105/252
+f 305/101/42 307/100/42 341/107/42
+f 335/110/253 321/104/253 323/102/253
+f 307/100/42 309/98/42 341/107/42
+f 335/110/253 319/103/253 321/104/253
+f 309/98/254 311/99/254 341/107/254
+f 335/110/254 317/120/254 319/103/254
+f 311/99/254 313/97/254 341/107/254
+f 315/119/254 317/120/254 335/110/254
+f 313/97/254 315/119/254 341/107/254
+f 315/119/254 335/110/254 341/107/254
+f 335/110/42 337/108/42 339/106/42
+f 341/107/42 335/110/42 339/106/42
+v 1.365790 0.015116 4.001092
+v 1.365790 0.015113 0.001092
+v 1.560881 0.034331 4.001092
+v 1.560881 0.034328 0.001092
+v 1.748474 0.091236 4.001092
+v 1.748474 0.091234 0.001092
+v 1.921361 0.183646 4.001092
+v 1.921361 0.183644 0.001092
+v 2.072897 0.308009 4.001092
+v 2.072897 0.308007 0.001092
+v 2.197260 0.459546 4.001092
+v 2.197260 0.459543 0.001092
+v 2.289670 0.632432 4.001092
+v 2.289670 0.632430 0.001092
+v 2.346576 0.820025 4.001092
+v 2.346576 0.820023 0.001092
+v 2.365790 1.015116 4.001092
+v 2.365790 1.015113 0.001092
+v 2.346576 1.210206 4.001091
+v 2.346576 1.210203 0.001091
+v 2.289670 1.397799 4.001091
+v 2.289670 1.397797 0.001091
+v 2.197260 1.570686 4.001091
+v 2.197260 1.570683 0.001091
+v 2.072897 1.722223 4.001091
+v 2.072897 1.722220 0.001091
+v 1.921361 1.846586 4.001091
+v 1.921361 1.846583 0.001091
+v 1.748474 1.938995 4.001091
+v 1.748474 1.938993 0.001091
+v 1.560881 1.995901 4.001091
+v 1.560881 1.995899 0.001091
+v 1.365790 2.015116 4.001091
+v 1.365790 2.015113 0.001091
+v 1.170700 1.995901 4.001091
+v 1.170700 1.995898 0.001091
+v 0.983107 1.938995 4.001091
+v 0.983107 1.938993 0.001091
+v 0.810220 1.846585 4.001091
+v 0.810220 1.846583 0.001091
+v 0.658683 1.722222 4.001091
+v 0.658683 1.722220 0.001091
+v 0.534320 1.570685 4.001091
+v 0.534320 1.570683 0.001091
+v 0.441911 1.397799 4.001091
+v 0.441911 1.397796 0.001091
+v 0.385005 1.210205 4.001091
+v 0.385005 1.210203 0.001091
+v 0.365790 1.015115 4.001092
+v 0.365790 1.015112 0.001092
+v 0.385005 0.820024 4.001092
+v 0.385005 0.820022 0.001092
+v 0.441911 0.632431 4.001092
+v 0.441911 0.632429 0.001092
+v 0.534322 0.459545 4.001092
+v 0.534322 0.459542 0.001092
+v 0.658685 0.308008 4.001092
+v 0.658685 0.308006 0.001092
+v 0.810221 0.183645 4.001092
+v 0.810221 0.183643 0.001092
+v 0.983108 0.091236 4.001092
+v 0.983108 0.091233 0.001092
+v 1.170702 0.034330 4.001092
+v 1.170702 0.034328 0.001092
+v 1.365791 0.649119 -0.167860
+v 1.437192 0.656151 -0.167860
+v 1.505850 0.676978 -0.167860
+v 1.569126 0.710800 -0.167860
+v 1.624587 0.756316 -0.167860
+v 1.670104 0.811778 -0.167860
+v 1.703925 0.875053 -0.167860
+v 1.724752 0.943711 -0.167860
+v 1.731785 1.015113 -0.167860
+v 1.724752 1.086515 -0.167860
+v 1.703925 1.155173 -0.167860
+v 1.670104 1.218448 -0.167861
+v 1.624587 1.273910 -0.167861
+v 1.569126 1.319426 -0.167861
+v 1.505850 1.353247 -0.167861
+v 1.437192 1.374075 -0.167861
+v 1.365790 1.381107 -0.167861
+v 1.294389 1.374075 -0.167861
+v 1.225731 1.353247 -0.167861
+v 1.162455 1.319426 -0.167861
+v 1.106993 1.273910 -0.167861
+v 1.061477 1.218448 -0.167861
+v 1.027656 1.155173 -0.167860
+v 1.006829 1.086515 -0.167860
+v 0.999797 1.015113 -0.167860
+v 1.006829 0.943711 -0.167860
+v 1.027656 0.875053 -0.167860
+v 1.061478 0.811777 -0.167860
+v 1.106994 0.756316 -0.167860
+v 1.162456 0.710800 -0.167860
+v 1.225731 0.676978 -0.167860
+v 1.294389 0.656151 -0.167860
+v 1.365791 0.605024 4.157856
+v 1.445795 0.612904 4.157856
+v 1.522726 0.636241 4.157856
+v 1.593625 0.674137 4.157856
+v 1.655769 0.725137 4.157856
+v 1.706769 0.787281 4.157856
+v 1.744666 0.858181 4.157855
+v 1.768002 0.935111 4.157855
+v 1.775882 1.015116 4.157855
+v 1.768002 1.095121 4.157855
+v 1.744666 1.172051 4.157855
+v 1.706769 1.242950 4.157855
+v 1.655769 1.305094 4.157855
+v 1.593625 1.356094 4.157855
+v 1.522726 1.393991 4.157855
+v 1.445795 1.417328 4.157855
+v 1.365790 1.425207 4.157855
+v 1.285786 1.417327 4.157855
+v 1.208855 1.393991 4.157855
+v 1.137956 1.356094 4.157855
+v 1.075812 1.305094 4.157855
+v 1.024812 1.242950 4.157855
+v 0.986915 1.172051 4.157855
+v 0.963579 1.095120 4.157855
+v 0.955699 1.015115 4.157855
+v 0.963579 0.935110 4.157855
+v 0.986916 0.858180 4.157855
+v 1.024812 0.787281 4.157856
+v 1.075812 0.725137 4.157856
+v 1.137956 0.674137 4.157856
+v 1.208856 0.636240 4.157856
+v 1.285786 0.612904 4.157856
+vt 0.000000 0.000000
+vt 0.450363 0.000088
+vt 0.519265 0.025135
+vt 0.412277 0.001462
+vt 0.592585 0.072108
+vt 0.660664 0.134431
+vt 0.714751 0.214466
+vt 0.755741 0.304410
+vt 0.778531 0.401983
+vt 0.781786 0.502742
+vt 0.147188 0.142771
+vt 0.050179 0.306373
+vt 0.093406 0.218681
+vt 0.765337 0.602148
+vt 0.019781 0.402090
+vt 0.729413 0.696327
+vt 0.000089 0.500905
+vt 0.680707 0.782826
+vt 0.000088 0.601389
+vt 0.627360 0.858268
+vt 0.022810 0.698277
+vt 0.566443 0.918999
+vt 0.063602 0.787572
+vt 0.501474 0.962949
+vt 0.119962 0.865210
+vt 0.436606 0.989270
+vt 0.188187 0.927714
+vt 0.377255 0.998855
+vt 0.263175 0.972402
+vt 0.336538 0.999912
+vt 0.208578 0.081678
+vt 0.280064 0.037078
+vt 0.344761 0.010798
+vn 0.634394 -0.773010 0.000000
+vn 0.773010 -0.634394 0.000000
+vn 0.773010 0.634394 -0.000000
+vn 0.634394 0.773010 -0.000000
+vn -0.956941 0.290284 -0.000000
+vn -0.995185 0.098016 -0.000000
+vn -0.881921 -0.471398 0.000000
+vn -0.634392 -0.773011 0.000000
+vn -0.075085 -0.247523 -0.965968
+vn -0.199427 0.163668 0.966148
+vn -0.121931 0.228117 -0.965969
+vn 0.247522 -0.075086 -0.965968
+vn -0.247522 -0.075086 -0.965968
+vn 0.121931 0.228117 -0.965969
+vn 0.075085 -0.247523 -0.965968
+vn -0.199948 0.164091 -0.965968
+vn 0.257414 0.025352 -0.965968
+vn -0.199947 -0.164093 -0.965968
+vn 0.025353 0.257414 -0.965969
+vn 0.164092 -0.199947 -0.965968
+vn -0.247522 0.075085 -0.965969
+vn 0.025353 -0.257415 -0.965968
+vn 0.228118 0.121931 -0.965969
+vn -0.121931 -0.228119 -0.965968
+vn -0.075086 0.247521 -0.965969
+vn 0.228118 -0.121932 -0.965968
+vn -0.257414 -0.025354 -0.965968
+vn 0.164092 0.199946 -0.965969
+vn -0.025353 -0.257415 -0.965968
+vn -0.164092 0.199946 -0.965969
+vn 0.257415 -0.025354 -0.965968
+vn -0.228117 -0.121932 -0.965968
+vn 0.075086 0.247521 -0.965969
+vn 0.121931 -0.228119 -0.965968
+vn -0.228117 0.121931 -0.965968
+vn 0.247522 0.075085 -0.965969
+vn -0.164092 -0.199947 -0.965968
+vn -0.025353 0.257414 -0.965969
+vn 0.199947 -0.164093 -0.965968
+vn -0.257414 0.025352 -0.965968
+vn 0.199947 0.164091 -0.965968
+vn 0.256746 0.025290 0.966148
+vn -0.199428 -0.163666 0.966148
+vn 0.025287 0.256747 0.966148
+vn 0.163666 -0.199428 0.966148
+vn -0.246880 0.074890 0.966148
+vn 0.025287 -0.256746 0.966148
+vn 0.227526 0.121615 0.966148
+vn -0.121615 -0.227526 0.966148
+vn -0.074890 0.246880 0.966148
+vn 0.227525 -0.121615 0.966148
+vn -0.256745 -0.025287 0.966148
+vn 0.163667 0.199428 0.966148
+vn -0.025287 -0.256746 0.966148
+vn -0.163667 0.199428 0.966148
+vn 0.256746 -0.025287 0.966148
+vn -0.227525 -0.121615 0.966148
+vn 0.074890 0.246880 0.966148
+vn 0.121615 -0.227526 0.966148
+vn -0.227526 0.121615 0.966148
+vn 0.246880 0.074890 0.966148
+vn -0.163666 -0.199428 0.966148
+vn -0.025287 0.256747 0.966148
+vn 0.199428 -0.163666 0.966148
+vn -0.256746 0.025289 0.966148
+vn 0.199427 0.163668 0.966148
+vn -0.074892 -0.246879 0.966148
+vn -0.121615 0.227526 0.966148
+vn 0.246879 -0.074890 0.966148
+vn -0.246879 -0.074890 0.966148
+vn 0.121615 0.227526 0.966148
+vn 0.074892 -0.246879 0.966148
+vn -0.199428 0.163666 0.966148
+vn 0.000000 -0.000004 -1.000000
+vn 0.247522 -0.075085 -0.965968
+vn -0.199947 0.164092 -0.965969
+vn 0.257414 0.025353 -0.965969
+vn -0.199947 -0.164092 -0.965968
+vn 0.228120 0.121929 -0.965968
+vn -0.121931 -0.228118 -0.965968
+vn -0.075085 0.247521 -0.965969
+vn 0.228117 -0.121935 -0.965968
+vn -0.257414 -0.025353 -0.965968
+vn 0.164092 0.199947 -0.965969
+vn 0.257415 -0.025353 -0.965968
+vn -0.228117 -0.121935 -0.965968
+vn 0.075085 0.247521 -0.965969
+vn 0.121932 -0.228118 -0.965968
+vn -0.228120 0.121928 -0.965968
+vn 0.199947 -0.164092 -0.965968
+vn -0.257414 0.025353 -0.965969
+vn 0.199946 0.164092 -0.965969
+vn -0.000004 0.000009 1.000000
+vn 0.000001 -0.000000 1.000000
+vn -0.000029 -0.000009 1.000000
+vn -0.000010 -0.000001 1.000000
+vn -0.000005 0.000001 1.000000
+vn -0.000003 0.000001 1.000000
+vn -0.000002 0.000001 1.000000
+vn -0.000001 0.000001 1.000000
+vn 0.000000 0.000001 1.000000
+vn 0.256746 0.025287 0.966148
+vn -0.074891 0.246880 0.966148
+vn 0.227529 -0.121610 0.966148
+vn -0.256746 -0.025287 0.966148
+vn -0.227529 -0.121610 0.966148
+vn -0.256746 0.025287 0.966148
+vn 0.199428 0.163666 0.966148
+vn -0.074889 -0.246879 0.966148
+vn 0.074890 -0.246879 0.966148
+s off
+f 345/121/50 346/121/50 348/121/50
+f 347/121/51 348/121/51 350/121/51
+f 349/121/52 350/121/52 352/121/52
+f 351/121/255 352/121/255 354/121/255
+f 353/121/256 354/121/256 356/121/256
+f 355/121/55 356/121/55 358/121/55
+f 357/121/56 358/121/56 360/121/56
+f 359/121/57 360/121/57 362/121/57
+f 361/121/58 362/121/58 364/121/58
+f 363/121/59 364/121/59 366/121/59
+f 365/121/60 366/121/60 368/121/60
+f 367/121/257 368/121/257 370/121/257
+f 369/121/258 370/121/258 372/121/258
+f 371/121/63 372/121/63 374/121/63
+f 373/121/64 374/121/64 376/121/64
+f 375/121/65 376/121/65 378/121/65
+f 377/121/66 378/121/66 379/121/66
+f 379/121/67 380/121/67 381/121/67
+f 381/121/68 382/121/68 383/121/68
+f 383/121/69 384/121/69 385/121/69
+f 385/121/70 386/121/70 387/121/70
+f 387/121/71 388/121/71 389/121/71
+f 389/121/259 390/121/259 391/121/259
+f 391/121/260 392/121/260 393/121/260
+f 393/121/74 394/121/74 395/121/74
+f 395/121/75 396/121/75 397/121/75
+f 397/121/261 398/121/261 399/121/261
+f 399/121/77 400/121/77 401/121/77
+f 401/121/262 402/121/262 403/121/262
+f 403/121/79 404/121/79 405/121/79
+f 408/121/263 406/121/263 440/121/263
+f 407/121/81 408/121/81 345/121/81
+f 405/121/82 406/121/82 407/121/82
+f 385/121/264 387/121/264 461/121/264
+f 437/121/47 439/121/47 438/121/47
+f 384/121/265 382/121/265 428/121/265
+f 360/121/266 358/121/266 415/121/266
+f 398/121/267 396/121/267 435/121/267
+f 374/121/268 372/121/268 422/121/268
+f 350/121/269 348/121/269 410/121/269
+f 388/121/270 386/121/270 430/121/270
+f 364/121/271 362/121/271 417/121/271
+f 402/121/272 400/121/272 437/121/272
+f 378/121/273 376/121/273 424/121/273
+f 354/121/274 352/121/274 412/121/274
+f 392/121/275 390/121/275 432/121/275
+f 348/121/276 346/121/276 409/121/276
+f 368/121/277 366/121/277 419/121/277
+f 406/121/278 404/121/278 439/121/278
+f 382/121/279 380/121/279 427/121/279
+f 358/121/280 356/121/280 414/121/280
+f 396/121/281 394/121/281 434/121/281
+f 372/121/282 370/121/282 421/121/282
+f 346/121/283 408/121/283 409/121/283
+f 386/121/284 384/121/284 429/121/284
+f 362/121/285 360/121/285 416/121/285
+f 400/121/286 398/121/286 436/121/286
+f 376/121/287 374/121/287 423/121/287
+f 352/121/288 350/121/288 411/121/288
+f 390/121/289 388/121/289 431/121/289
+f 366/121/290 364/121/290 418/121/290
+f 404/121/291 402/121/291 438/121/291
+f 380/121/292 378/121/292 426/121/292
+f 356/121/293 354/121/293 413/121/293
+f 394/121/294 392/121/294 433/121/294
+f 370/121/295 368/121/295 420/121/295
+f 441/122/49 442/123/49 472/124/49
+f 361/121/296 363/121/296 450/121/296
+f 399/121/297 401/121/297 468/121/297
+f 375/121/298 377/121/298 457/121/298
+f 351/121/299 353/121/299 445/121/299
+f 389/121/300 391/121/300 463/121/300
+f 345/121/301 347/121/301 442/121/301
+f 365/121/302 367/121/302 452/121/302
+f 403/121/303 405/121/303 470/121/303
+f 379/121/304 381/121/304 458/121/304
+f 355/121/305 357/121/305 447/121/305
+f 393/121/306 395/121/306 465/121/306
+f 369/121/307 371/121/307 454/121/307
+f 407/121/308 345/121/308 472/121/308
+f 383/121/309 385/121/309 460/121/309
+f 359/121/310 361/121/310 449/121/310
+f 397/121/311 399/121/311 467/121/311
+f 373/121/312 375/121/312 456/121/312
+f 349/121/313 351/121/313 444/121/313
+f 387/121/314 389/121/314 462/121/314
+f 363/121/315 365/121/315 451/121/315
+f 401/121/316 403/121/316 469/121/316
+f 377/121/317 379/121/317 457/121/317
+f 353/121/318 355/121/318 446/121/318
+f 391/121/319 393/121/319 464/121/319
+f 367/121/320 369/121/320 453/121/320
+f 405/121/321 407/121/321 471/121/321
+f 381/121/322 383/121/322 459/121/322
+f 357/121/323 359/121/323 448/121/323
+f 395/121/324 397/121/324 466/121/324
+f 371/121/325 373/121/325 455/121/325
+f 347/121/326 349/121/326 443/121/326
+f 347/121/178 345/121/178 348/121/178
+f 349/121/51 347/121/51 350/121/51
+f 351/121/52 349/121/52 352/121/52
+f 353/121/255 351/121/255 354/121/255
+f 355/121/256 353/121/256 356/121/256
+f 357/121/179 355/121/179 358/121/179
+f 359/121/56 357/121/56 360/121/56
+f 361/121/57 359/121/57 362/121/57
+f 363/121/58 361/121/58 364/121/58
+f 365/121/59 363/121/59 366/121/59
+f 367/121/60 365/121/60 368/121/60
+f 369/121/257 367/121/257 370/121/257
+f 371/121/181 369/121/181 372/121/181
+f 373/121/182 371/121/182 374/121/182
+f 375/121/183 373/121/183 376/121/183
+f 377/121/65 375/121/65 378/121/65
+f 378/121/184 380/121/184 379/121/184
+f 380/121/67 382/121/67 381/121/67
+f 382/121/68 384/121/68 383/121/68
+f 384/121/69 386/121/69 385/121/69
+f 386/121/70 388/121/70 387/121/70
+f 388/121/71 390/121/71 389/121/71
+f 390/121/259 392/121/259 391/121/259
+f 392/121/260 394/121/260 393/121/260
+f 394/121/74 396/121/74 395/121/74
+f 396/121/75 398/121/75 397/121/75
+f 398/121/261 400/121/261 399/121/261
+f 400/121/77 402/121/77 401/121/77
+f 402/121/262 404/121/262 403/121/262
+f 404/121/79 406/121/79 405/121/79
+f 406/121/263 439/121/263 440/121/263
+f 408/121/81 346/121/81 345/121/81
+f 406/121/82 408/121/82 407/121/82
+f 387/121/327 462/121/327 461/121/327
+f 436/121/47 439/121/47 437/121/47
+f 427/121/47 429/121/47 428/121/47
+f 427/121/47 430/121/47 429/121/47
+f 421/121/47 423/121/47 422/121/47
+f 420/121/47 423/121/47 421/121/47
+f 411/121/47 413/121/47 412/121/47
+f 411/121/47 414/121/47 413/121/47
+f 410/121/47 409/121/47 411/121/47
+f 409/121/47 440/121/47 411/121/47
+f 440/121/47 439/121/47 411/121/47
+f 439/121/47 436/121/47 411/121/47
+f 411/121/47 436/121/47 414/121/47
+f 436/121/328 435/121/328 414/121/328
+f 414/121/328 435/121/328 415/121/328
+f 435/121/47 434/121/47 415/121/47
+f 434/121/47 433/121/47 415/121/47
+f 433/121/47 432/121/47 415/121/47
+f 432/121/47 431/121/47 415/121/47
+f 415/121/47 431/121/47 416/121/47
+f 416/121/47 431/121/47 417/121/47
+f 417/121/47 431/121/47 418/121/47
+f 418/121/47 431/121/47 419/121/47
+f 431/121/328 430/121/328 419/121/328
+f 419/121/328 430/121/328 420/121/328
+f 430/121/47 427/121/47 420/121/47
+f 420/121/47 427/121/47 423/121/47
+f 427/121/47 426/121/47 423/121/47
+f 426/121/47 425/121/47 424/121/47
+f 423/121/47 426/121/47 424/121/47
+f 382/121/265 427/121/265 428/121/265
+f 416/121/329 360/121/329 415/121/329
+f 396/121/267 434/121/267 435/121/267
+f 423/121/268 374/121/268 422/121/268
+f 411/121/269 350/121/269 410/121/269
+f 386/121/330 429/121/330 430/121/330
+f 418/121/331 364/121/331 417/121/331
+f 400/121/332 436/121/332 437/121/332
+f 425/121/273 378/121/273 424/121/273
+f 413/121/274 354/121/274 412/121/274
+f 390/121/275 431/121/275 432/121/275
+f 410/121/276 348/121/276 409/121/276
+f 420/121/333 368/121/333 419/121/333
+f 404/121/334 438/121/334 439/121/334
+f 380/121/335 426/121/335 427/121/335
+f 415/121/336 358/121/336 414/121/336
+f 394/121/337 433/121/337 434/121/337
+f 422/121/338 372/121/338 421/121/338
+f 408/121/283 440/121/283 409/121/283
+f 384/121/284 428/121/284 429/121/284
+f 417/121/339 362/121/339 416/121/339
+f 398/121/340 435/121/340 436/121/340
+f 424/121/341 376/121/341 423/121/341
+f 412/121/342 352/121/342 411/121/342
+f 388/121/343 430/121/343 431/121/343
+f 419/121/290 366/121/290 418/121/290
+f 402/121/291 437/121/291 438/121/291
+f 378/121/292 425/121/292 426/121/292
+f 414/121/344 356/121/344 413/121/344
+f 392/121/345 432/121/345 433/121/345
+f 421/121/346 370/121/346 420/121/346
+f 442/123/49 443/125/49 472/124/49
+f 443/125/49 444/126/49 472/124/49
+f 444/126/49 445/127/49 472/124/49
+f 445/127/49 446/128/49 472/124/49
+f 446/128/347 447/129/347 472/124/347
+f 447/129/348 448/130/348 472/124/348
+f 468/131/349 466/132/349 467/133/349
+f 448/130/348 449/134/348 472/124/348
+f 468/131/350 465/135/350 466/132/350
+f 449/134/348 450/136/348 472/124/348
+f 468/131/351 464/137/351 465/135/351
+f 450/136/348 451/138/348 472/124/348
+f 468/131/352 463/139/352 464/137/352
+f 451/138/348 452/140/348 472/124/348
+f 468/131/353 462/141/353 463/139/353
+f 452/140/348 453/142/348 472/124/348
+f 468/131/354 461/143/354 462/141/354
+f 453/142/49 454/144/49 472/124/49
+f 468/131/354 460/145/354 461/143/354
+f 454/144/355 455/146/355 472/124/355
+f 468/131/354 459/147/354 460/145/354
+f 455/146/355 456/148/355 472/124/355
+f 468/131/355 458/149/355 459/147/355
+f 456/148/355 457/150/355 472/124/355
+f 457/150/355 458/149/355 472/124/355
+f 458/149/355 468/131/355 472/124/355
+f 468/131/49 469/151/49 472/124/49
+f 469/151/49 470/152/49 471/153/49
+f 472/124/49 469/151/49 471/153/49
+f 449/121/356 361/121/356 450/121/356
+f 401/121/297 469/121/297 468/121/297
+f 456/121/298 375/121/298 457/121/298
+f 444/121/299 351/121/299 445/121/299
+f 391/121/300 464/121/300 463/121/300
+f 441/121/301 345/121/301 442/121/301
+f 451/121/302 365/121/302 452/121/302
+f 405/121/303 471/121/303 470/121/303
+f 381/121/357 459/121/357 458/121/357
+f 446/121/358 355/121/358 447/121/358
+f 395/121/359 466/121/359 465/121/359
+f 453/121/307 369/121/307 454/121/307
+f 345/121/308 441/121/308 472/121/308
+f 385/121/309 461/121/309 460/121/309
+f 448/121/310 359/121/310 449/121/310
+f 399/121/360 468/121/360 467/121/360
+f 455/121/312 373/121/312 456/121/312
+f 443/121/313 349/121/313 444/121/313
+f 389/121/314 463/121/314 462/121/314
+f 450/121/315 363/121/315 451/121/315
+f 403/121/316 470/121/316 469/121/316
+f 379/121/317 458/121/317 457/121/317
+f 445/121/318 353/121/318 446/121/318
+f 393/121/361 465/121/361 464/121/361
+f 452/121/362 367/121/362 453/121/362
+f 407/121/363 472/121/363 471/121/363
+f 383/121/322 460/121/322 459/121/322
+f 447/121/323 357/121/323 448/121/323
+f 397/121/324 467/121/324 466/121/324
+f 454/121/325 371/121/325 455/121/325
+f 442/121/364 347/121/364 443/121/364
+v 0.000000 0.010026 -4.301087
+v 0.000000 4.010026 -4.301087
+v 0.195090 0.010026 -4.281873
+v 0.195090 4.010026 -4.281873
+v 0.382683 0.010026 -4.224967
+v 0.382683 4.010026 -4.224967
+v 0.555570 0.010026 -4.132557
+v 0.555570 4.010026 -4.132557
+v 0.707107 0.010026 -4.008194
+v 0.707107 4.010026 -4.008194
+v 0.831470 0.010026 -3.856658
+v 0.831470 4.010026 -3.856658
+v 0.923880 0.010026 -3.683771
+v 0.923880 4.010026 -3.683771
+v 0.980785 0.010026 -3.496178
+v 0.980785 4.010026 -3.496178
+v 1.000000 0.010026 -3.301088
+v 1.000000 4.010026 -3.301088
+v 0.980785 0.010026 -3.105997
+v 0.980785 4.010026 -3.105997
+v 0.923880 0.010026 -2.918404
+v 0.923880 4.010026 -2.918404
+v 0.831470 0.010026 -2.745517
+v 0.831470 4.010026 -2.745517
+v 0.707107 0.010026 -2.593981
+v 0.707107 4.010026 -2.593981
+v 0.555570 0.010026 -2.469618
+v 0.555570 4.010026 -2.469618
+v 0.382683 0.010026 -2.377208
+v 0.382683 4.010026 -2.377208
+v 0.195090 0.010026 -2.320302
+v 0.195090 4.010026 -2.320302
+v -0.000000 0.010026 -2.301088
+v -0.000000 4.010026 -2.301088
+v -0.195091 0.010026 -2.320302
+v -0.195091 4.010026 -2.320302
+v -0.382684 0.010026 -2.377208
+v -0.382684 4.010026 -2.377208
+v -0.555571 0.010026 -2.469618
+v -0.555571 4.010026 -2.469618
+v -0.707107 0.010026 -2.593981
+v -0.707107 4.010026 -2.593981
+v -0.831470 0.010026 -2.745518
+v -0.831470 4.010026 -2.745518
+v -0.923880 0.010026 -2.918405
+v -0.923880 4.010026 -2.918405
+v -0.980785 0.010026 -3.105998
+v -0.980785 4.010026 -3.105998
+v -1.000000 0.010026 -3.301089
+v -1.000000 4.010026 -3.301089
+v -0.980785 0.010026 -3.496179
+v -0.980785 4.010026 -3.496179
+v -0.923879 0.010026 -3.683772
+v -0.923879 4.010026 -3.683772
+v -0.831469 0.010026 -3.856659
+v -0.831469 4.010026 -3.856659
+v -0.707106 0.010026 -4.008195
+v -0.707106 4.010026 -4.008195
+v -0.555569 0.010026 -4.132558
+v -0.555569 4.010026 -4.132558
+v -0.382682 0.010026 -4.224968
+v -0.382682 4.010026 -4.224968
+v -0.195089 0.010026 -4.281873
+v -0.195089 4.010026 -4.281873
+v 0.000000 4.270857 -3.619825
+v 0.062183 4.270857 -3.613700
+v 0.121975 4.270857 -3.595562
+v 0.177081 4.270857 -3.566108
+v 0.225381 4.270857 -3.526469
+v 0.265020 4.270857 -3.478168
+v 0.294475 4.270857 -3.423063
+v 0.312612 4.270857 -3.363270
+v 0.318737 4.270857 -3.301088
+v 0.312613 4.270857 -3.238905
+v 0.294475 4.270857 -3.179112
+v 0.265020 4.270857 -3.124007
+v 0.225381 4.270857 -3.075707
+v 0.177081 4.270857 -3.036068
+v 0.121975 4.270857 -3.006613
+v 0.062182 4.270857 -2.988475
+v -0.000000 4.270857 -2.982351
+v -0.062183 4.270857 -2.988475
+v -0.121975 4.270857 -3.006613
+v -0.177081 4.270857 -3.036068
+v -0.225381 4.270857 -3.075707
+v -0.265020 4.270857 -3.124007
+v -0.294475 4.270857 -3.179113
+v -0.312613 4.270857 -3.238906
+v -0.318737 4.270857 -3.301088
+v -0.312612 4.270857 -3.363271
+v -0.294474 4.270857 -3.423064
+v -0.265020 4.270857 -3.478169
+v -0.225381 4.270857 -3.526469
+v -0.177080 4.270857 -3.566108
+v -0.121975 4.270857 -3.595562
+v -0.062182 4.270857 -3.613700
+vt 0.000000 0.000000
+vt 1.000000 0.000000
+vt 1.000000 1.000000
+vt 0.000000 1.000000
+vt 0.500000 1.000000
+vt 0.597545 0.990393
+vt 0.402456 0.990393
+vt 0.691342 0.961940
+vt 0.777785 0.915735
+vt 0.853553 0.853553
+vt 0.915735 0.777785
+vt 0.961940 0.691342
+vt 0.990393 0.597545
+vt 1.000000 0.500000
+vt 0.990393 0.402455
+vt 0.961940 0.308658
+vt 0.915735 0.222215
+vt 0.853553 0.146447
+vt 0.777785 0.084265
+vt 0.691342 0.038060
+vt 0.597545 0.009607
+vt 0.500000 0.000000
+vt 0.402455 0.009607
+vt 0.308658 0.038060
+vt 0.222215 0.084265
+vt 0.146446 0.146447
+vt 0.084265 0.222215
+vt 0.038060 0.308659
+vt 0.009607 0.402455
+vt 0.000000 0.500000
+vt 0.009607 0.597546
+vt 0.038060 0.691342
+vt 0.084266 0.777786
+vt 0.146447 0.853554
+vt 0.222215 0.915735
+vt 0.308659 0.961940
+vn 0.098017 0.000000 -0.995185
+vn 0.290285 0.000000 -0.956940
+vn 0.471396 0.000000 -0.881922
+vn 0.634394 0.000000 -0.773010
+vn 0.773011 0.000000 -0.634393
+vn 0.881921 0.000000 -0.471397
+vn 0.956940 0.000000 -0.290285
+vn 0.995185 0.000000 -0.098017
+vn 0.995185 0.000000 0.098017
+vn 0.881921 0.000000 0.471397
+vn 0.773011 0.000000 0.634393
+vn 0.634394 0.000000 0.773010
+vn 0.471396 0.000000 0.881921
+vn 0.290285 0.000000 0.956940
+vn 0.098017 0.000000 0.995185
+vn -0.098018 0.000000 0.995185
+vn -0.290285 0.000000 0.956940
+vn -0.471397 0.000000 0.881921
+vn -0.634394 0.000000 0.773010
+vn -0.773011 0.000000 0.634393
+vn -0.881922 0.000000 0.471396
+vn -0.956941 0.000000 0.290284
+vn -0.995185 0.000000 0.098016
+vn -0.995185 -0.000000 -0.098018
+vn -0.956940 -0.000000 -0.290286
+vn -0.881921 -0.000000 -0.471398
+vn -0.773010 -0.000000 -0.634394
+vn -0.634392 -0.000000 -0.773012
+vn -0.471397 -0.000000 -0.881921
+vn -0.357332 0.933314 -0.035195
+vn -0.098015 -0.000000 -0.995185
+vn -0.290282 -0.000000 -0.956941
+vn 0.227786 0.933314 0.277558
+vn -0.035193 0.933314 -0.357332
+vn -0.227786 0.933314 0.277558
+vn 0.357332 0.933314 -0.035194
+vn -0.316663 0.933314 -0.169261
+vn 0.104230 0.933314 0.343600
+vn 0.169260 0.933314 -0.316664
+vn -0.316664 0.933314 0.169260
+vn 0.343600 0.933314 0.104230
+vn -0.227785 0.933314 -0.277559
+vn -0.035194 0.933314 0.357332
+vn 0.277558 0.933314 -0.227786
+vn -0.357332 0.933314 0.035194
+vn 0.277558 0.933314 0.227786
+vn -0.104229 0.933314 -0.343600
+vn -0.169260 0.933314 0.316663
+vn 0.343600 0.933314 -0.104230
+vn -0.343600 0.933314 -0.104230
+vn 0.169260 0.933314 0.316664
+vn 0.104230 0.933314 -0.343600
+vn -0.277558 0.933314 0.227786
+vn 0.357332 0.933314 0.035194
+vn -0.277558 0.933314 -0.227786
+vn 0.035194 0.933314 0.357332
+vn 0.227786 0.933314 -0.277558
+vn -0.343600 0.933314 0.104230
+vn 0.035194 0.933314 -0.357332
+vn 0.316664 0.933314 0.169260
+vn -0.169260 0.933314 -0.316664
+vn -0.104230 0.933314 0.343600
+vn 0.316664 0.933314 -0.169260
+vn -0.357332 0.933314 -0.035194
+vn -0.035195 0.933314 -0.357332
+vn -0.316664 0.933314 -0.169260
+vn 0.169261 0.933314 -0.316663
+vn -0.227786 0.933314 -0.277558
+vn -0.357332 0.933314 0.035193
+vn -0.104230 0.933314 -0.343600
+vn -0.169261 0.933314 0.316663
+vn 0.035195 0.933314 -0.357332
+vn -0.169259 0.933314 -0.316664
+s off
+f 473/154/365 474/155/365 476/156/365
+f 475/154/366 476/155/366 478/156/366
+f 477/154/367 478/155/367 480/156/367
+f 479/154/368 480/155/368 482/156/368
+f 481/154/369 482/155/369 484/156/369
+f 483/154/370 484/155/370 486/156/370
+f 485/154/371 486/155/371 488/156/371
+f 487/154/372 488/155/372 490/156/372
+f 489/154/373 490/155/373 492/156/373
+f 491/154/155 492/155/155 494/156/155
+f 493/154/374 494/155/374 496/156/374
+f 495/154/375 496/155/375 498/156/375
+f 497/154/376 498/155/376 500/156/376
+f 499/154/377 500/155/377 502/156/377
+f 501/154/378 502/155/378 504/156/378
+f 503/154/379 504/155/379 506/156/379
+f 505/154/380 506/155/380 507/157/380
+f 507/154/381 508/155/381 509/157/381
+f 509/154/382 510/155/382 511/157/382
+f 511/154/383 512/155/383 513/157/383
+f 513/154/384 514/155/384 515/157/384
+f 515/154/385 516/155/385 517/157/385
+f 517/154/386 518/155/386 519/157/386
+f 519/154/387 520/155/387 521/157/387
+f 521/154/388 522/155/388 523/157/388
+f 523/154/389 524/155/389 525/157/389
+f 525/154/390 526/155/390 527/157/390
+f 527/154/391 528/155/391 529/157/391
+f 529/154/392 530/155/392 531/157/392
+f 531/154/393 532/155/393 533/157/393
+f 524/154/394 522/155/394 562/157/394
+f 535/154/395 536/155/395 473/157/395
+f 533/154/396 534/155/396 535/157/396
+f 473/158/42 475/159/42 535/160/42
+f 538/158/39 537/159/39 539/160/39
+f 500/154/397 498/155/397 549/156/397
+f 474/154/398 536/155/398 537/157/398
+f 514/154/399 512/155/399 557/157/399
+f 490/154/400 488/155/400 544/156/400
+f 528/154/401 526/155/401 564/157/401
+f 504/154/402 502/155/402 551/156/402
+f 480/154/403 478/155/403 539/156/403
+f 518/154/404 516/155/404 559/157/404
+f 494/154/405 492/155/405 546/156/405
+f 532/154/406 530/155/406 566/157/406
+f 508/154/407 506/155/407 554/157/407
+f 484/154/408 482/155/408 541/156/408
+f 522/154/409 520/155/409 561/157/409
+f 498/154/410 496/155/410 548/156/410
+f 536/154/411 534/155/411 568/157/411
+f 512/154/412 510/155/412 556/157/412
+f 488/154/413 486/155/413 543/156/413
+f 526/154/414 524/155/414 563/157/414
+f 502/154/415 500/155/415 550/156/415
+f 478/154/416 476/155/416 538/156/416
+f 516/154/417 514/155/417 558/157/417
+f 492/154/418 490/155/418 545/156/418
+f 530/154/419 528/155/419 565/157/419
+f 506/154/420 504/155/420 552/156/420
+f 482/154/421 480/155/421 540/156/421
+f 520/154/422 518/155/422 560/157/422
+f 476/154/423 474/155/423 537/156/423
+f 496/154/424 494/155/424 547/156/424
+f 534/154/425 532/155/425 567/157/425
+f 510/154/426 508/155/426 555/157/426
+f 486/154/427 484/155/427 542/156/427
+f 475/157/365 473/154/365 476/156/365
+f 477/157/366 475/154/366 478/156/366
+f 479/157/367 477/154/367 480/156/367
+f 481/157/368 479/154/368 482/156/368
+f 483/157/369 481/154/369 484/156/369
+f 485/157/370 483/154/370 486/156/370
+f 487/157/371 485/154/371 488/156/371
+f 489/157/372 487/154/372 490/156/372
+f 491/157/373 489/154/373 492/156/373
+f 493/157/155 491/154/155 494/156/155
+f 495/157/374 493/154/374 496/156/374
+f 497/157/375 495/154/375 498/156/375
+f 499/157/376 497/154/376 500/156/376
+f 501/157/377 499/154/377 502/156/377
+f 503/157/378 501/154/378 504/156/378
+f 505/157/379 503/154/379 506/156/379
+f 506/155/380 508/156/380 507/157/380
+f 508/155/381 510/156/381 509/157/381
+f 510/155/382 512/156/382 511/157/382
+f 512/155/383 514/156/383 513/157/383
+f 514/155/384 516/156/384 515/157/384
+f 516/155/385 518/156/385 517/157/385
+f 518/155/386 520/156/386 519/157/386
+f 520/155/387 522/156/387 521/157/387
+f 522/155/388 524/156/388 523/157/388
+f 524/155/389 526/156/389 525/157/389
+f 526/155/390 528/156/390 527/157/390
+f 528/155/391 530/156/391 529/157/391
+f 530/155/392 532/156/392 531/157/392
+f 532/155/393 534/156/393 533/157/393
+f 522/155/428 561/156/428 562/157/428
+f 536/155/395 474/156/395 473/157/395
+f 534/155/396 536/156/396 535/157/396
+f 475/159/42 477/161/42 535/160/42
+f 477/161/42 479/162/42 535/160/42
+f 479/162/42 481/163/42 535/160/42
+f 481/163/42 483/164/42 535/160/42
+f 483/164/42 485/165/42 535/160/42
+f 485/165/42 487/166/42 535/160/42
+f 487/166/42 489/167/42 535/160/42
+f 489/167/42 491/168/42 535/160/42
+f 491/168/42 493/169/42 535/160/42
+f 493/169/42 495/170/42 535/160/42
+f 495/170/42 497/171/42 535/160/42
+f 497/171/42 499/172/42 535/160/42
+f 499/172/42 501/173/42 535/160/42
+f 501/173/42 503/174/42 535/160/42
+f 503/174/42 505/175/42 535/160/42
+f 505/175/42 507/176/42 535/160/42
+f 507/176/42 509/177/42 535/160/42
+f 509/177/42 511/178/42 535/160/42
+f 511/178/42 513/179/42 535/160/42
+f 513/179/42 515/180/42 535/160/42
+f 515/180/42 517/181/42 535/160/42
+f 517/181/42 519/182/42 535/160/42
+f 519/182/42 521/183/42 535/160/42
+f 521/183/42 523/184/42 535/160/42
+f 523/184/42 525/185/42 535/160/42
+f 525/185/42 527/186/42 535/160/42
+f 527/186/42 529/187/42 535/160/42
+f 529/187/42 531/188/42 533/189/42
+f 535/160/42 529/187/42 533/189/42
+f 537/159/39 568/161/39 539/160/39
+f 568/161/39 567/162/39 539/160/39
+f 567/162/39 566/163/39 539/160/39
+f 566/163/39 565/164/39 539/160/39
+f 565/164/39 564/165/39 539/160/39
+f 564/165/39 563/166/39 539/160/39
+f 563/166/39 562/167/39 539/160/39
+f 562/167/39 561/168/39 539/160/39
+f 561/168/39 560/169/39 539/160/39
+f 560/169/39 559/170/39 539/160/39
+f 559/170/39 558/171/39 539/160/39
+f 558/171/39 557/172/39 539/160/39
+f 557/172/39 556/173/39 539/160/39
+f 556/173/39 555/174/39 539/160/39
+f 555/174/39 554/175/39 539/160/39
+f 554/175/39 553/176/39 539/160/39
+f 553/176/39 552/177/39 539/160/39
+f 552/177/39 551/178/39 539/160/39
+f 551/178/39 550/179/39 539/160/39
+f 550/179/39 549/180/39 539/160/39
+f 549/180/39 548/181/39 539/160/39
+f 548/181/39 547/182/39 539/160/39
+f 547/182/39 546/183/39 539/160/39
+f 546/183/39 545/184/39 539/160/39
+f 545/184/39 544/185/39 539/160/39
+f 544/185/39 543/186/39 539/160/39
+f 543/186/39 542/187/39 539/160/39
+f 542/187/39 541/188/39 539/160/39
+f 541/188/39 540/189/39 539/160/39
+f 550/157/397 500/154/397 549/156/397
+f 536/155/429 568/156/429 537/157/429
+f 512/155/399 556/156/399 557/157/399
+f 545/157/400 490/154/400 544/156/400
+f 526/155/430 563/156/430 564/157/430
+f 552/157/402 504/154/402 551/156/402
+f 540/157/431 480/154/431 539/156/431
+f 516/155/404 558/156/404 559/157/404
+f 547/157/405 494/154/405 546/156/405
+f 530/155/432 565/156/432 566/157/432
+f 506/155/407 553/156/407 554/157/407
+f 542/157/408 484/154/408 541/156/408
+f 520/155/433 560/156/433 561/157/433
+f 549/157/410 498/154/410 548/156/410
+f 534/155/434 567/156/434 568/157/434
+f 510/155/435 555/156/435 556/157/435
+f 544/157/413 488/154/413 543/156/413
+f 524/155/414 562/156/414 563/157/414
+f 551/157/415 502/154/415 550/156/415
+f 539/157/416 478/154/416 538/156/416
+f 514/155/417 557/156/417 558/157/417
+f 546/157/418 492/154/418 545/156/418
+f 528/155/419 564/156/419 565/157/419
+f 553/157/420 506/154/420 552/156/420
+f 541/157/421 482/154/421 540/156/421
+f 518/155/422 559/156/422 560/157/422
+f 538/157/436 476/154/436 537/156/436
+f 548/157/424 496/154/424 547/156/424
+f 532/155/437 566/156/437 567/157/437
+f 508/155/426 554/156/426 555/157/426
+f 543/157/427 486/154/427 542/156/427
+v 0.609274 0.018251 -1.901238
+v 0.609274 0.018251 -0.682690
+v -0.609274 0.018251 -0.682690
+v -0.609274 0.018251 -1.901238
+v 0.609274 1.236799 -1.901237
+v 0.609273 1.236799 -0.682690
+v -0.609274 1.236799 -0.682690
+v -0.609274 1.236799 -1.901238
+vt 0.000000 0.000000
+vt 1.000000 0.000000
+vt 1.000000 1.000000
+vt 0.000000 1.000000
+vn 1.000000 -0.000000 0.000001
+s off
+f 569/190/42 570/191/42 571/192/42
+f 573/190/39 576/191/39 575/192/39
+f 569/190/438 573/191/438 574/192/438
+f 570/190/49 574/191/49 571/193/49
+f 571/190/46 575/191/46 572/193/46
+f 573/190/47 569/191/47 572/192/47
+f 572/193/42 569/190/42 571/192/42
+f 574/193/39 573/190/39 575/192/39
+f 570/193/48 569/190/48 574/192/48
+f 574/191/49 575/192/49 571/193/49
+f 575/191/46 576/192/46 572/193/46
+f 576/193/47 573/190/47 572/192/47
diff --git a/examples/graphs/graphgallery/data/topography.png b/examples/graphs/graphgallery/data/topography.png
new file mode 100644
index 0000000..9349cdb
--- /dev/null
+++ b/examples/graphs/graphgallery/data/topography.png
Binary files differ
diff --git a/examples/graphs/graphgallery/doc/images/graphgallery-example.png b/examples/graphs/graphgallery/doc/images/graphgallery-example.png
new file mode 100644
index 0000000..f3320c8
--- /dev/null
+++ b/examples/graphs/graphgallery/doc/images/graphgallery-example.png
Binary files differ
diff --git a/examples/graphs/graphgallery/doc/src/graphgallery.qdoc b/examples/graphs/graphgallery/doc/src/graphgallery.qdoc
new file mode 100644
index 0000000..7b59190
--- /dev/null
+++ b/examples/graphs/graphgallery/doc/src/graphgallery.qdoc
@@ -0,0 +1,706 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ \example graphgallery
+ \meta tags {Graphs, Q3DBars, Bar Graph, Custom Proxy, Q3DScatter, Scatter Graph, Custom Input Handler, Q3DSurface, Surface Graph, QCustom3DItem, Textured Surface}
+ \meta category {Graphics}
+ \title Graph Gallery
+ \ingroup qtdatavisualization_examples
+ \brief Gallery of Bar, Scatter, and Surface graphs.
+
+ \e {Graph Gallery} demonstrates all three graph types and some of their special features.
+ The graphs have their own tabs in the application.
+
+ \image graphgallery-example.png
+
+ \include examples-run.qdocinc
+
+ \section1 Bar Graph
+
+ In the \uicontrol {Bar Graph} tab, create a 3D bar graph using Q3DBars and combine the use of
+ widgets to adjust various bar graph qualities. The example shows how to:
+
+ \list
+ \li Create an application with Q3DBars and some widgets
+ \li Use QBar3DSeries and QBarDataProxy to set data to the graph
+ \li Adjust some graph and series properties using widget controls
+ \li Select a row or a column by clicking an axis label
+ \li Create a custom proxy to use with Q3DBars
+ \endlist
+
+ For information about interacting with the graph, see
+ \l{Qt Data Visualization Interacting with Data}{this page}.
+
+ \section2 Creating the Application
+
+ First, in \c{bargraph.cpp}, instantiate Q3DBars:
+
+ \snippet graphgallery/bargraph.cpp 0
+
+ Then, create the widget, and horizontal and vertical layouts.
+
+ The graph is embedded in a window container using
+ QWidget::createWindowContainer(). This is required because all data
+ visualization graph classes (Q3DBars, Q3DScatter, Q3DSurface) inherit
+ QWindow. This is the only way to use a class inheriting QWindow as a widget.
+
+ Add the graph and the vertical layout to the
+ horizontal one:
+
+ \snippet graphgallery/bargraph.cpp 1
+
+ Next, create another class to handle the data addition and other interaction with the
+ graph:
+
+ \snippet graphgallery/bargraph.cpp 2
+
+ \section2 Setting up the Bar Graph
+
+ Set up the graph in the constructor of the \c GraphModifier class:
+
+ \snippet graphgallery/graphmodifier.cpp 0
+
+ First, create the axes and the series into member variables to support changing them easily:
+
+ \snippet graphgallery/graphmodifier.cpp 1
+
+ Then, set some visual qualities for the graph:
+
+ \snippet graphgallery/graphmodifier.cpp 2
+
+ Set up the axes and make them the active axes of the graph:
+
+ \snippet graphgallery/graphmodifier.cpp 3
+
+ Give axis labels a small autorotation angle with setLabelAutoRotation() to make them orient
+ slightly toward the camera. This improves axis label readability at extreme camera angles.
+
+ Next, initialize the visual properties of the series. Note that the second series is initially
+ not visible:
+
+ \snippet graphgallery/graphmodifier.cpp 4
+
+ Add the series to the graph:
+
+ \snippet graphgallery/graphmodifier.cpp 5
+
+ Finally, set the camera angle by calling the same method the camera angle change button
+ in the UI uses to cycle through various camera angles:
+
+ \snippet graphgallery/graphmodifier.cpp 6
+
+ The camera is controlled via the scene object of the graph:
+
+ \snippet graphgallery/graphmodifier.cpp 7
+
+ For more information about using scene and cameras, see Q3DScene and Q3DCamera.
+
+ \section2 Adding Data to the Graph
+
+ At the end of the constructor, call a method that sets up the data:
+
+ \snippet graphgallery/graphmodifier.cpp 8
+
+ This method adds data to the proxies of the two series:
+
+ \snippet graphgallery/graphmodifier.cpp 9a
+ \dots 0
+ \snippet graphgallery/graphmodifier.cpp 9b
+
+ \section2 Using Widgets to Control the Graph
+
+ Continue by adding some widgets in \c{bargraph.cpp}. Add a slider:
+
+ \snippet graphgallery/bargraph.cpp 3
+
+ Use the slider to rotate the graph instead of just using a mouse or touch. Add it to the
+ vertical layout:
+
+ \snippet graphgallery/bargraph.cpp 4
+
+ Then, connect it to a method in \c GraphModifier:
+
+ \snippet graphgallery/bargraph.cpp 5
+
+ Create a slot in \c GraphModifier for the signal connection. The camera is controlled via the
+ scene object. This time, specify the actual camera position along the orbit around the center
+ point, instead of specifying a preset camera angle:
+
+ \snippet graphgallery/graphmodifier.cpp 10
+
+ You can now use the slider to rotate the graph.
+
+ Add more widgets to the vertical layout to control:
+
+ \list
+ \li Graph rotation
+ \li Label style
+ \li Camera preset
+ \li Background visibility
+ \li Grid visibility
+ \li Bar shading smoothness
+ \li Visibility of the second bar series
+ \li Value axis direction
+ \li Axis title visibility and rotation
+ \li Data range to be shown
+ \li Bar style
+ \li Selection mode
+ \li Theme
+ \li Shadow quality
+ \li Font
+ \li Font size
+ \li Axis label rotation
+ \li Data mode
+ \endlist
+
+ Some widget controls are intentionally disabled when in the \uicontrol {Custom Proxy Data}
+ data mode.
+
+ \section2 Selecting a Row or Column by Clicking an Axis Label
+
+ Selection by axis label is default functionality for bar graphs. As an example, you can select
+ rows by clicking an axis label in the following way:
+
+ \list 1
+ \li Change selection mode to \c SelectionRow
+ \li Click a year label
+ \li The row with the clicked year is selected
+ \endlist
+
+ The same method works with \c SelectionSlice and \c SelectionItem flags, as long as
+ either \c SelectionRow or \c SelectionColumn is set as well.
+
+ \section2 Zooming to Selection
+
+ As an example of adjusting the camera target, implement an animation of zooming to
+ selection via a button press. Animation initializations are done in the constructor:
+
+ \snippet graphgallery/graphmodifier.cpp 11
+
+ Function \c{GraphModifier::zoomToSelectedBar()} contains the zooming functionality.
+ QPropertyAnimation \c m_animationCameraTarget targets Q3DCamera::target property,
+ which takes a value normalized to the range (-1, 1).
+
+ Figure out where the selected bar is relative to axes, and use that as the end value for
+ \c{m_animationCameraTarget}:
+
+ \snippet graphgallery/graphmodifier.cpp 12
+ \dots 0
+ \snippet graphgallery/graphmodifier.cpp 13
+
+ Then, rotate the camera so that it always points approximately to the center of
+ the graph at the end of the animation:
+
+ \snippet graphgallery/graphmodifier.cpp 14
+
+ \section2 Custom Proxy for Data
+
+ By toggling \uicontrol {Custom Proxy Data} data mode on, a custom dataset and the corresponding
+ proxy are taken into use.
+
+ Define a simple flexible data set, \c{VariantDataSet}, where each data item is
+ a variant list. Each item can have multiple values, identified by their index in
+ the list. In this case, the data set is storing monthly rainfall data, where the value in
+ index zero is the year, the value in index one is the month, and the value in index two is
+ the amount of rainfall in that month.
+
+ The custom proxy is similar to itemmodel-based proxies provided by Qt Data Visualization, and
+ it requires mapping to interpret the data.
+
+ \section3 VariantDataSet
+
+ Define the data items as QVariantList objects. Add functionality for clearing the data set and
+ querying for a reference to the data contained in the set. Also, add signals to be emitted when
+ data is added or the set is cleared:
+
+ \snippet graphgallery/variantdataset.h 0
+ \dots 0
+ \codeline
+ \snippet graphgallery/variantdataset.h 1
+
+ \section3 VariantBarDataProxy
+
+ Subclass \c VariantBarDataProxy from QBarDataProxy and provide a simple API of getters and
+ setters for the data set and the mapping:
+
+ \snippet graphgallery/variantbardataproxy.h 0
+ \dots 0
+ \codeline
+ \snippet graphgallery/variantbardataproxy.h 1
+
+ The proxy listens for the changes in the data set and the mapping, and resolves the data set
+ if any changes are detected. This is not a particularly efficient implementation, as any change
+ will cause re-resolving of the entire data set, but that is not an issue for this example.
+
+ In \c resolveDataSet() method, sort the variant data values into rows and columns based on the
+ mapping. This is very similar to how QItemModelBarDataProxy handles mapping, except you use
+ list indexes instead of item model roles here. Once the values are sorted, generate
+ \c QBarDataArray out of them, and call the \c resetArray() method in the parent class:
+
+ \snippet graphgallery/variantbardataproxy.cpp 0
+
+ \section3 VariantBarDataMapping
+
+ Store the mapping information between \c VariantDataSet data item indexes and rows, columns,
+ and values of \c QBarDataArray in \c VariantBarDataMapping. It contains the lists of rows and
+ columns to be included in the resolved data:
+
+ \snippet graphgallery/variantbardatamapping.h 0
+ \dots 0
+ \codeline
+ \snippet graphgallery/variantbardatamapping.h 1
+ \dots 0
+ \codeline
+ \snippet graphgallery/variantbardatamapping.h 2
+ \dots 0
+ \codeline
+ \snippet graphgallery/variantbardatamapping.h 3
+
+ The primary way to use a \c VariantBarDataMapping object is to give the mappings in the
+ constructor, though you can use the \c remap() method to set them later, either individually or
+ all together. Emit a signal if mapping changes. The outcome is a simplified version of the
+ mapping functionality of QItemModelBarDataProxy, adapted to work with variant lists instead of
+ item models.
+
+ \section3 RainfallData
+
+ Handle the setup of QBar3DSeries with the custom proxy in the \c RainfallData class:
+
+ \snippet graphgallery/rainfalldata.cpp 0
+
+ Populate the variant data set in the \c addDataSet() method:
+
+ \snippet graphgallery/rainfalldata.cpp 1
+ \dots
+
+ Add the data set to the custom proxy and set the mapping:
+
+ \snippet graphgallery/rainfalldata.cpp 2
+
+ Finally, add a function for getting the created series for displaying:
+
+ \snippet graphgallery/rainfalldata.h 0
+
+ \section1 Scatter Graph
+
+ In the \uicontrol {Scatter Graph} tab, create a 3D scatter graph using Q3DScatter.
+ The example shows how to:
+
+ \list
+ \li Set up Q3DScatter graph
+ \li Use QScatterDataProxy to set data to the graph
+ \li Create a custom input handler by extending Q3DInputHandler
+ \endlist
+
+ For basic application creation, see \l {Bar Graph}.
+
+ \section2 Setting up the Scatter Graph
+
+ First, set up some visual qualities for the graph in the constructor of the
+ \c ScatterDataModifier:
+
+ \snippet graphgallery/scatterdatamodifier.cpp 0
+
+ None of these are mandatory, but are used to override graph defaults. You can try how it looks
+ with the preset defaults by commenting out the block above.
+
+ Next, create a QScatterDataProxy and the associated QScatter3DSeries. Set a custom label format
+ and mesh smoothing for the series and add it to the graph:
+
+ \snippet graphgallery/scatterdatamodifier.cpp 1
+
+ \section2 Adding Scatter Data
+
+ The last thing to do in the \c ScatterDataModifier constructor is to add data to the graph:
+
+ \snippet graphgallery/scatterdatamodifier.cpp 2
+
+ The actual data addition is done in \c addData() method. First, configure the axes:
+
+ \snippet graphgallery/scatterdatamodifier.cpp 3
+
+ You could do this also in the constructor of \c {ScatterDataModifier}. Doing it here
+ keeps the constructor simpler and the axes' configuration near the data.
+
+ Next, create a data array and populate it:
+
+ \snippet graphgallery/scatterdatamodifier.cpp 4
+ \dots
+ \snippet graphgallery/scatterdatamodifier.cpp 5
+
+ Finally, tell the proxy to start using the data we gave it:
+
+ \snippet graphgallery/scatterdatamodifier.cpp 6
+
+ Now, the graph has the data and is ready for use. For information about adding widgets
+ to control the graph, see \l {Using Widgets to Control the Graph}.
+
+ \section2 Replacing Default Input Handling
+
+ Initialize \c m_inputHandler in the constructor with a pointer to the scatter graph instance:
+
+ \snippet graphgallery/scatterdatamodifier.cpp 7
+
+ Replace the default input handling mechanism by setting the active input handler of
+ Q3DScatter to \c {AxesInputHandler}, which implements the custom behavior:
+
+ \snippet graphgallery/scatterdatamodifier.cpp 8
+
+ The input handler needs access to the axes of the graph, so pass them to it:
+
+ \snippet graphgallery/scatterdatamodifier.cpp 9
+
+ \section2 Extending Mouse Event Handling
+
+ First, inherit the custom input handler from Q3DInputHandler instead of QAbstract3DInputHandler
+ to keep all the functionality of the default input handling, and to add the custom
+ functionality on top of it:
+
+ \snippet graphgallery/axesinputhandler.h 0
+
+ Start extending the default functionality by re-implementing some of the mouse events.
+ First, extend \c {mousePressEvent}. Add a \c{m_mousePressed} flag for the left mouse button
+ to it, and keep the rest of the default functionality:
+
+ \snippet graphgallery/axesinputhandler.cpp 0
+
+ Next, modify \c mouseReleaseEvent to clear the flag, and reset the internal state:
+
+ \snippet graphgallery/axesinputhandler.cpp 1
+
+ Then, modify \c {mouseMoveEvent}. Check if \c m_mousePressed flag is \c {true} and
+ the internal state is something other than \c StateNormal. If so, set the input positions
+ for mouse movement distance calculations, and call the axis dragging function (see
+ \l {Implementing Axis Dragging} for details):
+
+ \snippet graphgallery/axesinputhandler.cpp 2
+
+ \section2 Implementing Axis Dragging
+
+ First, start listening to the selection signal from the graph. Do that in the
+ constructor, and connect it to the \c handleElementSelected method:
+
+ \snippet graphgallery/axesinputhandler.cpp 3
+
+ In \c {handleElementSelected}, check the type of the selection, and set the internal state
+ based on it:
+
+ \snippet graphgallery/axesinputhandler.cpp 4
+
+ The actual dragging logic is implemented in the \c handleAxisDragging method, which is called
+ from \c {mouseMoveEvent}, if the required conditions are met:
+
+ \snippet graphgallery/axesinputhandler.cpp 5
+
+ In \c {handleAxisDragging}, first get the scene orientation from the active camera:
+
+ \snippet graphgallery/axesinputhandler.cpp 6
+
+ Then, calculate the modifiers for mouse movement direction based on the orientation:
+
+ \snippet graphgallery/axesinputhandler.cpp 7
+
+ After that, calculate the mouse movement, and modify it based on the y rotation of the
+ camera:
+
+ \snippet graphgallery/axesinputhandler.cpp 8
+
+ Then, apply the moved distance to the correct axis:
+
+ \snippet graphgallery/axesinputhandler.cpp 9
+
+ Finally, add a function for setting the dragging speed:
+
+ \snippet graphgallery/axesinputhandler.h 1
+
+ This is needed, as the mouse movement distance is absolute in screen coordinates, and you
+ need to adjust it to the axis range. The larger the value, the slower the dragging will be.
+ Note that in this example, the scene zoom level is not taken into account when determining the
+ drag speed, so you'll notice changes in the range adjustment as you change the zoom level.
+
+ You could also adjust the modifier automatically based on the axis range and camera zoom level.
+
+ \section1 Surface Graph
+
+ In the \uicontrol {Surface Graph} tab, create a 3D surface graph using Q3DSurface.
+ The example shows how to:
+
+ \list
+ \li Set up a basic QSurfaceDataProxy and set data for it.
+ \li Use QHeightMapSurfaceDataProxy for showing 3D height maps.
+ \li Use topographic data to create 3D height maps.
+ \li Use three different selection modes for studying the graph.
+ \li Use axis ranges to display selected portions of the graph.
+ \li Set a custom surface gradient.
+ \li Add custom items and labels with QCustom3DItem and QCustom3DLabel.
+ \li Use custom input handler to enable zooming and panning.
+ \li Highlight an area of the surface.
+ \endlist
+
+ For basic application creation, see \l {Bar Graph}.
+
+ \section2 Simple Surface with Generated Data
+
+ First, instantiate a new QSurfaceDataProxy and attach it to a new QSurface3DSeries:
+
+ \snippet graphgallery/surfacegraphmodifier.cpp 0
+
+ Then, fill the proxy with a simple square root and sine wave data. Create a new
+ \c QSurfaceDataArray instance, and add \c QSurfaceDataRow elements to it.
+ Set the created \c QSurfaceDataArray as the data array for the QSurfaceDataProxy by calling
+ \c{resetArray()}.
+
+ \snippet graphgallery/surfacegraphmodifier.cpp 1
+
+ \section2 Multiseries Height Map Data
+
+ Create the height map by instantiating a QHeightMapSurfaceDataProxy with a QImage containing
+ the height data. Use QHeightMapSurfaceDataProxy::setValueRanges() to define the
+ value range of the map. In the example, the map is from an imaginary position of
+ 34.0\unicode 0x00B0 N - 40.0\unicode 0x00B0 N and 18.0\unicode 0x00B0 E - 24.0\unicode 0x00B0 E.
+ These values are used to position the map on the axes.
+
+ \snippet graphgallery/surfacegraphmodifier.cpp 2
+
+ Add the other surface layers the same way, by creating a proxy and a series for them using
+ height map images.
+
+ \section2 Topographic Map Data
+
+ The topographic data is obtained from the National Land Survey of Finland. It provides a product
+ called \c{Elevation Model 2 m}, which is suitable for this example.
+ The topography data is from Levi fell. The accuracy of the data is well beyond the need, and
+ therefore it is compressed and encoded into a PNG file. The height value of the original
+ ASCII data is encoded into RGB format using a multiplier, which you will see later in
+ a code extract. The multiplier is calculated by dividing the largest 24-bit value with the
+ highest point in Finland.
+
+ QHeightMapSurfaceDataProxy converts only one-byte values. To utilize the higher accuracy of
+ the data from the National Land Survey of Finland, read the data from the PNG file and decode
+ it into QSurface3DSeries.
+
+ First, define the encoding multiplier:
+
+ \snippet graphgallery/topographicseries.cpp 0
+
+ Then, perform the actual decoding:
+
+ \snippet graphgallery/topographicseries.cpp 1
+
+ Now, the data is usable by the proxy.
+
+ \section2 Selecting the Data Set
+
+ To demonstrate different proxies, \uicontrol {Surface Graph} has three radio buttons to
+ switch between the series.
+
+ With \uicontrol {Sqrt & Sin}, the simple generated series is activated. First, set
+ the decorative features, such as enabling the grid for the surface, and selecting the flat
+ shading mode. Next, define the axis label format and value ranges. Set automatic label rotation
+ to improve label readability at low camera angles. Finally, make sure the correct series is
+ added to the graph and the others are not:
+
+ \snippet graphgallery/surfacegraphmodifier.cpp 3
+
+ With \uicontrol {Multiseries Height Map}, the height map series are activated and others
+ disabled. Auto-adjusting Y-axis range works well for the height map surface, so ensure it is
+ set.
+
+ \snippet graphgallery/surfacegraphmodifier.cpp 4
+
+ With \uicontrol {Textured Topography}, the topographic series is activated and others disabled.
+ Activate a custom input handler for this series, to be able to highlight areas on it:
+
+ \snippet graphgallery/surfacegraphmodifier.cpp 5
+
+ See \l {Use Custom Input Handler to Enable Zooming and Panning} for information about the
+ custom input handler for this data set.
+
+ \section2 Selection Modes
+
+ The three selection modes supported by Q3DSurface can be used with radio buttons.
+ To activate the selected mode or to clear it, add the following inline methods:
+
+ \snippet graphgallery/surfacegraphmodifier.h 0
+
+ Add \c{QAbstract3DGraph::SelectionSlice} and \c{QAbstract3DGraph::SelectionMultiSeries} flags
+ for the row and column selection modes to support doing a slice selection to all visible series
+ in the graph simultaneously.
+
+ \section2 Axis Ranges for Studying the Graph
+
+ The example has four slider controls for adjusting the min and max values for X and Z
+ axes. When selecting the proxy, these sliders are adjusted to match the axis ranges of the
+ current data set:
+
+ \snippet graphgallery/surfacegraphmodifier.cpp 6
+
+ Add support for setting the X range from the widget controls to the graph:
+
+ \snippet graphgallery/surfacegraphmodifier.cpp 7
+
+ Add the support for Z range the same way.
+
+ \section2 Custom Surface Gradients
+
+ With the \uicontrol {Sqrt & Sin} data set, custom surface gradients can be taken into use
+ with two push buttons. Define the gradient with QLinearGradient, where the desired colors are
+ set. Also, change the color style to Q3DTheme::ColorStyleRangeGradient to use the gradient.
+
+ \snippet graphgallery/surfacegraphmodifier.cpp 8
+
+ \section2 Adding Custom Meshes to the Application
+
+ Add the mesh files to \c{CMakeLists.txt} for cmake build:
+
+ \badcode
+ set(graphgallery_resource_files
+ ...
+ "data/oilrig.obj"
+ "data/pipe.obj"
+ "data/refinery.obj"
+ ...
+ )
+
+ qt6_add_resources(graphgallery "graphgallery"
+ PREFIX
+ "/"
+ FILES
+ ${graphgallery_resource_files}
+ )
+ \endcode
+
+ Also, add them in the qrc resource file for use with qmake:
+
+ \badcode
+ <RCC>
+ <qresource prefix="/">
+ ...
+ <file>data/refinery.obj</file>
+ <file>data/oilrig.obj</file>
+ <file>data/pipe.obj</file>
+ ...
+ </qresource>
+ </RCC>
+ \endcode
+
+ \section2 Adding Custom Item to a Graph
+
+ With the \uicontrol {Multiseries Height Map} data set, custom items are inserted into the
+ graph and can be toggled on or off using checkboxes. Other visual qualities can also be
+ controlled with another set of checkboxes, including see-through for the two top layers, and
+ a highlight for the bottom layer.
+
+ Begin by creating a small QImage. Fill it with a single color to use as the color for the
+ custom object:
+
+ \snippet graphgallery/surfacegraphmodifier.cpp 9
+
+ Then, specify the position of the item in a variable. The position can then be used for
+ removing the correct item from the graph:
+
+ \snippet graphgallery/surfacegraphmodifier.cpp 10
+
+ Then, create a new QCustom3DItem with all the parameters:
+
+ \snippet graphgallery/surfacegraphmodifier.cpp 11
+
+ Finally, add the item to the graph:
+
+ \snippet graphgallery/surfacegraphmodifier.cpp 12
+
+ \section2 Adding Custom Label to a Graph
+
+ Adding a custom label is very similar to adding a custom item. For the label, a custom mesh is
+ not needed, but just a QCustom3DLabel instance:
+
+ \snippet graphgallery/surfacegraphmodifier.cpp 13
+
+ \section2 Removing Custom Item from a Graph
+
+ To remove a specific item from the graph, call \c removeCustomItemAt() with the position of
+ the item:
+
+ \snippet graphgallery/surfacegraphmodifier.cpp 14
+
+ \note Removing a custom item from the graph also deletes the object. If you want to preserve
+ the item, use the \c releaseCustomItem() method instead.
+
+ \section2 Texture to a Surface Series
+
+ With the \uicontrol {Textured Topography} data set, create a map texture to be used with the
+ topographic height map.
+
+ Set an image to be used as the texture on a surface with QSurface3DSeries::setTextureFile().
+ Add a check box to control if the texture is set or not, and a handler to react to the checkbox
+ state:
+
+ \snippet graphgallery/surfacegraphmodifier.cpp 15
+
+ The image in this example is read from a JPG file. Setting an empty file with the method clears
+ the texture, and the surface uses the gradients or colors from the theme.
+
+ \section2 Use Custom Input Handler to Enable Zooming and Panning
+
+ With the \uicontrol {Textured Topography} data set, create a custom input handler to
+ highlight the selection on the graph and allow panning the graph.
+
+ The panning implementation is similar to the one shown in \l{Implementing Axis Dragging}.
+ The difference is that, in this example, you follow only the X and Z axes and don't allow
+ dragging the surface outside the graph. To limit the dragging, follow the limits of the axes
+ and do nothing if going outside the graph:
+
+ \snippet graphgallery/custominputhandler.cpp 0
+
+ For zooming, catch the \c wheelEvent and adjust the X and Y axis ranges according to the delta
+ value on QWheelEvent. Adjust the Y axis so that the aspect ratio between the Y axis and the XZ
+ plane stays the same. This prevents getting a graph in which the height is exaggerated:
+
+ \snippet graphgallery/custominputhandler.cpp 1
+
+ Next, add some limits to the zoom level, so that it won't get too near to or far from the
+ surface. For instance, if the value for the X axis gets below the allowed limit, i.e. zooming
+ gets too far, the value is set to the minimum allowed value. If the range is going to below
+ the range minimum, both ends of the axis are adjusted so that the range stays at the limit:
+
+ \snippet graphgallery/custominputhandler.cpp 2
+
+ \section2 Highlight an Area of the Surface
+
+ To implement a highlight to be displayed on the surface, create a copy of the series and add
+ some offset to the y value. In this example, the class \c HighlightSeries implements the
+ creation of the copy in its \c handlePositionChange method.
+
+ First, give \c HighlightSeries the pointer to the original series, and then start listening to
+ the QSurface3DSeries::selectedPointChanged signal:
+
+ \snippet graphgallery/highlightseries.cpp 0
+
+ When the signal triggers, check that the position is valid. Then, calculate the ranges
+ for the copied area, and check that they stay within the bounds. Finally, fill the data array
+ of the highlight series with the range from the data array of the topography series:
+
+ \snippet graphgallery/highlightseries.cpp 1
+
+ \section2 A Gradient to the Highlight Series
+
+ Since the \c HighlightSeries is QSurface3DSeries, all the decoration methods a series can
+ have are available. In this example, add a gradient to emphasize the elevation. Because the
+ suitable gradient style depends on the range of the Y axis and we change the range when
+ zooming, the gradient color positions need to be adjusted as the range changes. Do this by
+ defining proportional values for the gradient color positions:
+
+ \snippet graphgallery/highlightseries.cpp 2
+
+ The gradient modification is done in the \c handleGradientChange method, so connect it to
+ react to changes on the Y axis:
+
+ \snippet graphgallery/surfacegraphmodifier.cpp 16
+
+ When a change in the Y axis max value happens, calculate the new gradient color positions:
+
+ \snippet graphgallery/highlightseries.cpp 3
+
+ \section1 Example Contents
+*/
diff --git a/examples/graphs/graphgallery/graphgallery.pro b/examples/graphs/graphgallery/graphgallery.pro
new file mode 100644
index 0000000..c9bd96f
--- /dev/null
+++ b/examples/graphs/graphgallery/graphgallery.pro
@@ -0,0 +1,49 @@
+android|ios|winrt {
+ error( "This example is not supported for android, ios, or winrt." )
+}
+
+!include( ../examples.pri ) {
+ error( "Couldn't find the examples.pri file!" )
+}
+
+QT += widgets
+requires(qtConfig(fontcombobox))
+requires(qtConfig(combobox))
+
+SOURCES += main.cpp \
+ bargraph.cpp \
+ graphmodifier.cpp \
+ rainfalldata.cpp \
+ variantdataset.cpp \
+ variantbardataproxy.cpp \
+ variantbardatamapping.cpp \
+ scattergraph.cpp \
+ scatterdatamodifier.cpp \
+ axesinputhandler.cpp \
+ surfacegraph.cpp \
+ surfacegraphmodifier.cpp \
+ custominputhandler.cpp \
+ highlightseries.cpp \
+ topographicseries.cpp
+
+HEADERS += \
+ bargraph.h \
+ graphmodifier.h \
+ rainfalldata.h \
+ variantdataset.h \
+ variantbardataproxy.h \
+ variantbardatamapping.h \
+ scattergraph.h \
+ scatterdatamodifier.h \
+ axesinputhandler.h \
+ surfacegraph.h \
+ surfacegraphmodifier.h \
+ custominputhandler.h \
+ highlightseries.h \
+ topographicseries.h
+
+RESOURCES += graphgallery.qrc
+
+OTHER_FILES += doc/src/* \
+ doc/images/* \
+ data/*
diff --git a/examples/graphs/graphgallery/graphgallery.qrc b/examples/graphs/graphgallery/graphgallery.qrc
new file mode 100644
index 0000000..6df5703
--- /dev/null
+++ b/examples/graphs/graphgallery/graphgallery.qrc
@@ -0,0 +1,13 @@
+<RCC>
+ <qresource prefix="/">
+ <file>data/raindata.txt</file>
+ <file>data/layer_1.png</file>
+ <file>data/layer_2.png</file>
+ <file>data/layer_3.png</file>
+ <file>data/refinery.obj</file>
+ <file>data/oilrig.obj</file>
+ <file>data/pipe.obj</file>
+ <file>data/maptexture.jpg</file>
+ <file>data/topography.png</file>
+ </qresource>
+</RCC>
diff --git a/examples/graphs/graphgallery/graphmodifier.cpp b/examples/graphs/graphgallery/graphmodifier.cpp
new file mode 100644
index 0000000..a6883cf
--- /dev/null
+++ b/examples/graphs/graphgallery/graphmodifier.cpp
@@ -0,0 +1,443 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "graphmodifier.h"
+#include "rainfalldata.h"
+
+#include <QtGraphs/qcategory3daxis.h>
+#include <QtGraphs/qvalue3daxis.h>
+#include <QtGraphs/qbardataproxy.h>
+#include <QtGraphs/q3dscene.h>
+#include <QtGraphs/q3dcamera.h>
+#include <QtGraphs/qbar3dseries.h>
+#include <QtGraphs/q3dtheme.h>
+#include <QtCore/qmath.h>
+#include <QtWidgets/qcombobox.h>
+
+using namespace Qt::StringLiterals;
+
+// TODO: Many of the values do not affect custom proxy series now - should be fixed
+
+//! [0]
+GraphModifier::GraphModifier(Q3DBars *bargraph) :
+ m_graph(bargraph),
+ //! [0]
+ //! [1]
+ m_temperatureAxis(new QValue3DAxis),
+ m_yearAxis(new QCategory3DAxis),
+ m_monthAxis(new QCategory3DAxis),
+ m_primarySeries(new QBar3DSeries),
+ m_secondarySeries(new QBar3DSeries),
+ m_celsiusString(u"°C"_s)
+ //! [1]
+{
+ //! [2]
+ m_graph->setShadowQuality(QAbstract3DGraph::ShadowQualitySoftMedium);
+ m_graph->activeTheme()->setBackgroundEnabled(false);
+ m_graph->activeTheme()->setFont(QFont("Times New Roman", m_fontSize));
+ m_graph->activeTheme()->setLabelBackgroundEnabled(true);
+ m_graph->setMultiSeriesUniform(true);
+ //! [2]
+
+ m_months = {"January", "February", "March", "April", "May", "June" , "July",
+ "August", "September", "October" , "November", "December"};
+ m_years = {"2015", "2016", "2017", "2018", "2019", "2020", "2021", "2022"};
+
+ //! [3]
+ m_temperatureAxis->setTitle("Average temperature");
+ m_temperatureAxis->setSegmentCount(m_segments);
+ m_temperatureAxis->setSubSegmentCount(m_subSegments);
+ m_temperatureAxis->setRange(m_minval, m_maxval);
+ m_temperatureAxis->setLabelFormat(u"%.1f "_s + m_celsiusString);
+ m_temperatureAxis->setLabelAutoRotation(30.0f);
+ m_temperatureAxis->setTitleVisible(true);
+
+ m_yearAxis->setTitle("Year");
+ m_yearAxis->setLabelAutoRotation(30.0f);
+ m_yearAxis->setTitleVisible(true);
+ m_monthAxis->setTitle("Month");
+ m_monthAxis->setLabelAutoRotation(30.0f);
+ m_monthAxis->setTitleVisible(true);
+
+ m_graph->setValueAxis(m_temperatureAxis);
+ m_graph->setRowAxis(m_yearAxis);
+ m_graph->setColumnAxis(m_monthAxis);
+ //! [3]
+
+ //! [4]
+ m_primarySeries->setItemLabelFormat(u"Oulu - @colLabel @rowLabel: @valueLabel"_s);
+ m_primarySeries->setMesh(QAbstract3DSeries::MeshBevelBar);
+ m_primarySeries->setMeshSmooth(false);
+
+ m_secondarySeries->setItemLabelFormat(u"Helsinki - @colLabel @rowLabel: @valueLabel"_s);
+ m_secondarySeries->setMesh(QAbstract3DSeries::MeshBevelBar);
+ m_secondarySeries->setMeshSmooth(false);
+ m_secondarySeries->setVisible(false);
+ //! [4]
+
+ //! [5]
+ m_graph->addSeries(m_primarySeries);
+ m_graph->addSeries(m_secondarySeries);
+ //! [5]
+
+ //! [6]
+ changePresetCamera();
+ //! [6]
+
+ //! [8]
+ resetTemperatureData();
+ //! [8]
+
+ // Set up property animations for zooming to the selected bar
+ //! [11]
+ Q3DCamera *camera = m_graph->scene()->activeCamera();
+ m_defaultAngleX = camera->xRotation();
+ m_defaultAngleY = camera->yRotation();
+ m_defaultZoom = camera->zoomLevel();
+ m_defaultTarget = camera->target();
+
+ m_animationCameraX.setTargetObject(camera);
+ m_animationCameraY.setTargetObject(camera);
+ m_animationCameraZoom.setTargetObject(camera);
+ m_animationCameraTarget.setTargetObject(camera);
+
+ m_animationCameraX.setPropertyName("xRotation");
+ m_animationCameraY.setPropertyName("yRotation");
+ m_animationCameraZoom.setPropertyName("zoomLevel");
+ m_animationCameraTarget.setPropertyName("target");
+
+ int duration = 1700;
+ m_animationCameraX.setDuration(duration);
+ m_animationCameraY.setDuration(duration);
+ m_animationCameraZoom.setDuration(duration);
+ m_animationCameraTarget.setDuration(duration);
+
+ // The zoom always first zooms out above the graph and then zooms in
+ qreal zoomOutFraction = 0.3;
+ m_animationCameraX.setKeyValueAt(zoomOutFraction, QVariant::fromValue(0.0f));
+ m_animationCameraY.setKeyValueAt(zoomOutFraction, QVariant::fromValue(90.0f));
+ m_animationCameraZoom.setKeyValueAt(zoomOutFraction, QVariant::fromValue(50.0f));
+ m_animationCameraTarget.setKeyValueAt(zoomOutFraction,
+ QVariant::fromValue(QVector3D(0.0f, 0.0f, 0.0f)));
+ //! [11]
+
+ m_customData = new RainfallData();
+}
+
+GraphModifier::~GraphModifier()
+{
+ delete m_customData;
+ delete m_graph;
+}
+
+void GraphModifier::resetTemperatureData()
+{
+ //! [9a]
+ // Set up data
+ static const float tempOulu[8][12] = {
+ {-7.4f, -2.4f, 0.0f, 3.0f, 8.2f, 11.6f, 14.7f, 15.4f, 11.4f, 4.2f, 2.1f, -2.3f}, // 2015
+ {-13.4f, -3.9f, -1.8f, 3.1f, 10.6f, 13.7f, 17.8f, 13.6f, 10.7f, 3.5f, -3.1f, -4.2f}, // 2016
+ //! [9a]
+ {-5.7f, -6.7f, -3.0f, -0.1f, 4.7f, 12.4f, 16.1f, 14.1f, 9.4f, 3.0f, -0.3f, -3.2f}, // 2017
+ {-6.4f, -11.9f, -7.4f, 1.9f, 11.4f, 12.4f, 21.5f, 16.1f, 11.0f, 4.4f, 2.1f, -4.1f}, // 2018
+ {-11.7f, -6.1f, -2.4f, 3.9f, 7.2f, 14.5f, 15.6f, 14.4f, 8.5f, 2.0f, -3.0f, -1.5f}, // 2019
+ {-2.1f, -3.4f, -1.8f, 0.6f, 7.0f, 17.1f, 15.6f, 15.4f, 11.1f, 5.6f, 1.9f, -1.7f}, // 2020
+ {-9.6f, -11.6f, -3.2f, 2.4f, 7.8f, 17.3f, 19.4f, 14.2f, 8.0f, 5.2f, -2.2f, -8.6f}, // 2021
+ {-7.3f, -6.4f, -1.8f, 1.3f, 8.1f, 15.5f, 17.6f, 17.6f, 9.1f, 5.4f, -1.5f, -4.4f} // 2022
+ };
+
+ static const float tempHelsinki[8][12] = {
+ {-2.0f, -0.1f, 1.8f, 5.1f, 9.7f, 13.7f, 16.3f, 17.3f, 12.7f, 5.4f, 4.6f, 2.1f}, // 2015
+ {-10.3f, -0.6f, 0.0f, 4.9f, 14.3f, 15.7f, 17.7f, 16.0f, 12.7f, 4.6f, -1.0f, -0.9f}, // 2016
+ {-2.9f, -3.3f, 0.7f, 2.3f, 9.9f, 13.8f, 16.1f, 15.9f, 11.4f, 5.0f, 2.7f, 0.7f}, // 2017
+ {-2.2f, -8.4f, -4.7f, 5.0f, 15.3f, 15.8f, 21.2f, 18.2f, 13.3f, 6.7f, 2.8f, -2.0f}, // 2018
+ {-6.2f, -0.5f, -0.3f, 6.8f, 10.6f, 17.9f, 17.5f, 16.8f, 11.3f, 5.2f, 1.8f, 1.4f}, // 2019
+ {1.9f, 0.5f, 1.7f, 4.5f, 9.5f, 18.4f, 16.5f, 16.8f, 13.0f, 8.2f, 4.4f, 0.9f}, // 2020
+ {-4.7f, -8.1f, -0.9f, 4.5f, 10.4f, 19.2f, 20.9f, 15.4f, 9.5f, 8.0f, 1.5f, -6.7f}, // 2021
+ {-3.3f, -2.2f, -0.2f, 3.3f, 9.6f, 16.9f, 18.1f, 18.9f, 9.2f, 7.6f, 2.3f, -3.4f} // 2022
+ };
+
+ // Create data arrays
+ //! [9b]
+ QBarDataArray *dataSet = new QBarDataArray;
+ QBarDataArray *dataSet2 = new QBarDataArray;
+ QBarDataRow *dataRow = nullptr;
+ QBarDataRow *dataRow2= nullptr;
+
+ dataSet->reserve(m_years.size());
+ for (qsizetype year = 0; year < m_years.size(); ++year) {
+ // Create a data row
+ dataRow = new QBarDataRow(m_months.size());
+ dataRow2 = new QBarDataRow(m_months.size());
+ for (qsizetype month = 0; month < m_months.size(); ++month) {
+ // Add data to the row
+ (*dataRow)[month].setValue(tempOulu[year][month]);
+ (*dataRow2)[month].setValue(tempHelsinki[year][month]);
+ }
+ // 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)
+ m_primarySeries->dataProxy()->resetArray(dataSet, m_years, m_months);
+ m_secondarySeries->dataProxy()->resetArray(dataSet2, m_years, m_months);
+ //! [9b]
+}
+
+void GraphModifier::changeRange(int range)
+{
+ if (range >= m_years.count())
+ m_yearAxis->setRange(0, m_years.count() - 1);
+ else
+ m_yearAxis->setRange(range, range);
+}
+
+void GraphModifier::changeStyle(int style)
+{
+ QComboBox *comboBox = qobject_cast<QComboBox *>(sender());
+ if (comboBox) {
+ m_barMesh = comboBox->itemData(style).value<QAbstract3DSeries::Mesh>();
+ m_primarySeries->setMesh(m_barMesh);
+ m_secondarySeries->setMesh(m_barMesh);
+ m_customData->customSeries()->setMesh(m_barMesh);
+ }
+}
+
+void GraphModifier::changePresetCamera()
+{
+ m_animationCameraX.stop();
+ m_animationCameraY.stop();
+ m_animationCameraZoom.stop();
+ m_animationCameraTarget.stop();
+
+ // Restore camera target in case animation has changed it
+ m_graph->scene()->activeCamera()->setTarget(QVector3D(0.0f, 0.0f, 0.0f));
+
+ //! [7]
+ static int preset = Q3DCamera::CameraPresetFront;
+
+ m_graph->scene()->activeCamera()->setCameraPreset((Q3DCamera::CameraPreset)preset);
+
+ if (++preset > Q3DCamera::CameraPresetDirectlyBelow)
+ preset = Q3DCamera::CameraPresetFrontLow;
+ //! [7]
+}
+
+void GraphModifier::changeTheme(int theme)
+{
+ Q3DTheme *currentTheme = m_graph->activeTheme();
+ currentTheme->setType(Q3DTheme::Theme(theme));
+ emit backgroundEnabledChanged(currentTheme->isBackgroundEnabled());
+ emit gridEnabledChanged(currentTheme->isGridEnabled());
+ emit fontChanged(currentTheme->font());
+ emit fontSizeChanged(currentTheme->font().pointSize());
+}
+
+void GraphModifier::changeLabelBackground()
+{
+ m_graph->activeTheme()->setLabelBackgroundEnabled(!m_graph->activeTheme()->isLabelBackgroundEnabled());
+}
+
+void GraphModifier::changeSelectionMode(int selectionMode)
+{
+ QComboBox *comboBox = qobject_cast<QComboBox *>(sender());
+ if (comboBox) {
+ int flags = comboBox->itemData(selectionMode).toInt();
+ m_graph->setSelectionMode(QAbstract3DGraph::SelectionFlags(flags));
+ }
+}
+
+void GraphModifier::changeFont(const QFont &font)
+{
+ QFont newFont = font;
+ m_graph->activeTheme()->setFont(newFont);
+}
+
+void GraphModifier::changeFontSize(int fontsize)
+{
+ m_fontSize = fontsize;
+ QFont font = m_graph->activeTheme()->font();
+ font.setPointSize(m_fontSize);
+ m_graph->activeTheme()->setFont(font);
+}
+
+void GraphModifier::shadowQualityUpdatedByVisual(QAbstract3DGraph::ShadowQuality sq)
+{
+ int quality = int(sq);
+ // Updates the UI component to show correct shadow quality
+ emit shadowQualityChanged(quality);
+}
+
+void GraphModifier::changeLabelRotation(int rotation)
+{
+ m_temperatureAxis->setLabelAutoRotation(float(rotation));
+ m_monthAxis->setLabelAutoRotation(float(rotation));
+ m_yearAxis->setLabelAutoRotation(float(rotation));
+}
+
+void GraphModifier::setAxisTitleVisibility(bool enabled)
+{
+ m_temperatureAxis->setTitleVisible(enabled);
+ m_monthAxis->setTitleVisible(enabled);
+ m_yearAxis->setTitleVisible(enabled);
+}
+
+void GraphModifier::setAxisTitleFixed(bool enabled)
+{
+ m_temperatureAxis->setTitleFixed(enabled);
+ m_monthAxis->setTitleFixed(enabled);
+ m_yearAxis->setTitleFixed(enabled);
+}
+
+void GraphModifier::zoomToSelectedBar()
+{
+ m_animationCameraX.stop();
+ m_animationCameraY.stop();
+ m_animationCameraZoom.stop();
+ m_animationCameraTarget.stop();
+
+ Q3DCamera *camera = m_graph->scene()->activeCamera();
+ float currentX = camera->xRotation();
+ float currentY = camera->yRotation();
+ float currentZoom = camera->zoomLevel();
+ QVector3D currentTarget = camera->target();
+
+ m_animationCameraX.setStartValue(QVariant::fromValue(currentX));
+ m_animationCameraY.setStartValue(QVariant::fromValue(currentY));
+ m_animationCameraZoom.setStartValue(QVariant::fromValue(currentZoom));
+ m_animationCameraTarget.setStartValue(QVariant::fromValue(currentTarget));
+
+ QPoint selectedBar = m_graph->selectedSeries()
+ ? m_graph->selectedSeries()->selectedBar()
+ : QBar3DSeries::invalidSelectionPosition();
+
+ if (selectedBar != QBar3DSeries::invalidSelectionPosition()) {
+ // Normalize selected bar position within axis range to determine target coordinates
+ //! [12]
+ QVector3D endTarget;
+ float xMin = m_graph->columnAxis()->min();
+ float xRange = m_graph->columnAxis()->max() - xMin;
+ float zMin = m_graph->rowAxis()->min();
+ float zRange = m_graph->rowAxis()->max() - zMin;
+ endTarget.setX((selectedBar.y() - xMin) / xRange * 2.0f - 1.0f);
+ endTarget.setZ((selectedBar.x() - zMin) / zRange * 2.0f - 1.0f);
+ //! [12]
+
+ // Rotate the camera so that it always points approximately to the graph center
+ //! [14]
+ qreal endAngleX = 90.0 - qRadiansToDegrees(qAtan(qreal(endTarget.z() / endTarget.x())));
+ if (endTarget.x() > 0.0f)
+ endAngleX -= 180.0f;
+ float barValue = m_graph->selectedSeries()->dataProxy()->itemAt(selectedBar.x(),
+ selectedBar.y())->value();
+ float endAngleY = barValue >= 0.0f ? 30.0f : -30.0f;
+ if (m_graph->valueAxis()->reversed())
+ endAngleY *= -1.0f;
+ //! [14]
+
+ m_animationCameraX.setEndValue(QVariant::fromValue(float(endAngleX)));
+ m_animationCameraY.setEndValue(QVariant::fromValue(endAngleY));
+ m_animationCameraZoom.setEndValue(QVariant::fromValue(250));
+ //! [13]
+ m_animationCameraTarget.setEndValue(QVariant::fromValue(endTarget));
+ //! [13]
+ } else {
+ // No selected bar, so return to the default view
+ m_animationCameraX.setEndValue(QVariant::fromValue(m_defaultAngleX));
+ m_animationCameraY.setEndValue(QVariant::fromValue(m_defaultAngleY));
+ m_animationCameraZoom.setEndValue(QVariant::fromValue(m_defaultZoom));
+ m_animationCameraTarget.setEndValue(QVariant::fromValue(m_defaultTarget));
+ }
+
+ m_animationCameraX.start();
+ m_animationCameraY.start();
+ m_animationCameraZoom.start();
+ m_animationCameraTarget.start();
+}
+
+void GraphModifier::setDataModeToWeather(bool enabled)
+{
+ if (enabled)
+ changeDataMode(false);
+}
+
+void GraphModifier::setDataModeToCustom(bool enabled)
+{
+ if (enabled)
+ changeDataMode(true);
+}
+
+void GraphModifier::changeShadowQuality(int quality)
+{
+ QAbstract3DGraph::ShadowQuality sq = QAbstract3DGraph::ShadowQuality(quality);
+ m_graph->setShadowQuality(sq);
+ emit shadowQualityChanged(quality);
+}
+
+//! [10]
+void GraphModifier::rotateX(int rotation)
+{
+ m_xRotation = rotation;
+ m_graph->scene()->activeCamera()->setCameraPosition(m_xRotation, m_yRotation);
+}
+//! [10]
+
+void GraphModifier::rotateY(int rotation)
+{
+ m_yRotation = rotation;
+ m_graph->scene()->activeCamera()->setCameraPosition(m_xRotation, m_yRotation);
+}
+
+void GraphModifier::setBackgroundEnabled(int enabled)
+{
+ m_graph->activeTheme()->setBackgroundEnabled(bool(enabled));
+}
+
+void GraphModifier::setGridEnabled(int enabled)
+{
+ m_graph->activeTheme()->setGridEnabled(bool(enabled));
+}
+
+void GraphModifier::setSmoothBars(int smooth)
+{
+ m_smooth = bool(smooth);
+ m_primarySeries->setMeshSmooth(m_smooth);
+ m_secondarySeries->setMeshSmooth(m_smooth);
+ m_customData->customSeries()->setMeshSmooth(m_smooth);
+}
+
+void GraphModifier::setSeriesVisibility(int enabled)
+{
+ m_secondarySeries->setVisible(bool(enabled));
+}
+
+void GraphModifier::setReverseValueAxis(int enabled)
+{
+ m_graph->valueAxis()->setReversed(enabled);
+}
+
+void GraphModifier::setReflection(bool enabled)
+{
+ m_graph->setReflection(enabled);
+}
+
+void GraphModifier::changeDataMode(bool customData)
+{
+ // Change between weather data and data from custom proxy
+ if (customData) {
+ m_graph->removeSeries(m_primarySeries);
+ m_graph->removeSeries(m_secondarySeries);
+ m_graph->addSeries(m_customData->customSeries());
+ m_graph->setValueAxis(m_customData->valueAxis());
+ m_graph->setRowAxis(m_customData->rowAxis());
+ m_graph->setColumnAxis(m_customData->colAxis());
+ } else {
+ m_graph->removeSeries(m_customData->customSeries());
+ m_graph->addSeries(m_primarySeries);
+ m_graph->addSeries(m_secondarySeries);
+ m_graph->setValueAxis(m_temperatureAxis);
+ m_graph->setRowAxis(m_yearAxis);
+ m_graph->setColumnAxis(m_monthAxis);
+ }
+}
diff --git a/examples/graphs/graphgallery/graphmodifier.h b/examples/graphs/graphgallery/graphmodifier.h
new file mode 100644
index 0000000..0501af6
--- /dev/null
+++ b/examples/graphs/graphgallery/graphmodifier.h
@@ -0,0 +1,87 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef GRAPHMODIFIER_H
+#define GRAPHMODIFIER_H
+
+#include <QtGraphs/q3dbars.h>
+#include <QtGraphs/qbardataproxy.h>
+#include <QtGraphs/qabstract3dseries.h>
+#include <QtCore/qpropertyanimation.h>
+
+class RainfallData;
+
+class GraphModifier : public QObject
+{
+ Q_OBJECT
+public:
+ explicit GraphModifier(Q3DBars *bargraph);
+ ~GraphModifier();
+
+ void resetTemperatureData();
+ void changePresetCamera();
+ void changeLabelBackground();
+ void changeFont(const QFont &font);
+ void changeFontSize(int fontsize);
+ void rotateX(int rotation);
+ void rotateY(int rotation);
+ void setBackgroundEnabled(int enabled);
+ void setGridEnabled(int enabled);
+ void setSmoothBars(int smooth);
+ void setSeriesVisibility(int enabled);
+ void setReverseValueAxis(int enabled);
+ void setReflection(bool enabled);
+ void changeDataMode(bool customData);
+
+public Q_SLOTS:
+ void changeRange(int range);
+ void changeStyle(int style);
+ void changeSelectionMode(int selectionMode);
+ void changeTheme(int theme);
+ void changeShadowQuality(int quality);
+ void shadowQualityUpdatedByVisual(QAbstract3DGraph::ShadowQuality shadowQuality);
+ void changeLabelRotation(int rotation);
+ void setAxisTitleVisibility(bool enabled);
+ void setAxisTitleFixed(bool enabled);
+ void zoomToSelectedBar();
+ void setDataModeToWeather(bool enabled);
+ void setDataModeToCustom(bool enabled);
+
+Q_SIGNALS:
+ void shadowQualityChanged(int quality);
+ void backgroundEnabledChanged(bool enabled);
+ void gridEnabledChanged(bool enabled);
+ void fontChanged(const QFont &font);
+ void fontSizeChanged(int size);
+
+private:
+ Q3DBars *m_graph = nullptr;
+ float m_xRotation = 0.f;
+ float m_yRotation = 0.f;
+ int m_fontSize = 30;
+ int m_segments = 4;
+ int m_subSegments = 3;
+ float m_minval = -20.f;
+ float m_maxval = 20.f;
+ QStringList m_months = {};
+ QStringList m_years = {};
+ QValue3DAxis *m_temperatureAxis = nullptr;
+ QCategory3DAxis *m_yearAxis = nullptr;
+ QCategory3DAxis *m_monthAxis = nullptr;
+ QBar3DSeries *m_primarySeries = nullptr;
+ QBar3DSeries *m_secondarySeries = nullptr;
+ QAbstract3DSeries::Mesh m_barMesh = QAbstract3DSeries::MeshBevelBar;
+ bool m_smooth = false;
+ QPropertyAnimation m_animationCameraX = {};
+ QPropertyAnimation m_animationCameraY = {};
+ QPropertyAnimation m_animationCameraZoom = {};
+ QPropertyAnimation m_animationCameraTarget = {};
+ float m_defaultAngleX = 0.f;
+ float m_defaultAngleY = 0.f;
+ float m_defaultZoom = 0.f;
+ QVector3D m_defaultTarget = {};
+ const QString m_celsiusString;
+ RainfallData *m_customData = nullptr;
+};
+
+#endif
diff --git a/examples/graphs/graphgallery/highlightseries.cpp b/examples/graphs/graphgallery/highlightseries.cpp
new file mode 100644
index 0000000..c2aab2d
--- /dev/null
+++ b/examples/graphs/graphgallery/highlightseries.cpp
@@ -0,0 +1,99 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "highlightseries.h"
+
+//! [2]
+const float darkRedPos = 1.f;
+const float redPos = 0.8f;
+const float yellowPos = 0.6f;
+const float greenPos = 0.4f;
+const float darkGreenPos = 0.2f;
+//! [2]
+
+HighlightSeries::HighlightSeries()
+{
+ setDrawMode(QSurface3DSeries::DrawSurface);
+ setFlatShadingEnabled(true);
+ setVisible(false);
+}
+
+HighlightSeries::~HighlightSeries()
+{
+}
+
+//! [0]
+void HighlightSeries::setTopographicSeries(TopographicSeries *series)
+{
+ m_topographicSeries = series;
+ m_srcWidth = m_topographicSeries->dataProxy()->array()->at(0)->size();
+ m_srcHeight = m_topographicSeries->dataProxy()->array()->size();
+
+ QObject::connect(m_topographicSeries, &QSurface3DSeries::selectedPointChanged,
+ this, &HighlightSeries::handlePositionChange);
+}
+//! [0]
+
+//! [1]
+void HighlightSeries::handlePositionChange(const QPoint &position)
+{
+ m_position = position;
+
+ if (position == invalidSelectionPosition()) {
+ setVisible(false);
+ return;
+ }
+
+ int halfWidth = m_width / 2;
+ int halfHeight = m_height / 2;
+
+ int startX = position.y() - halfWidth;
+ if (startX < 0 )
+ startX = 0;
+ int endX = position.y() + halfWidth;
+ if (endX > (m_srcWidth - 1))
+ endX = m_srcWidth - 1;
+ int startZ = position.x() - halfHeight;
+ if (startZ < 0 )
+ startZ = 0;
+ int endZ = position.x() + halfHeight;
+ if (endZ > (m_srcHeight - 1))
+ endZ = m_srcHeight - 1;
+
+ QSurfaceDataProxy *srcProxy = m_topographicSeries->dataProxy();
+ const QSurfaceDataArray &srcArray = *srcProxy->array();
+
+ QSurfaceDataArray *dataArray = new QSurfaceDataArray;
+ dataArray->reserve(endZ - startZ);
+ for (int i = startZ; i < endZ; i++) {
+ QSurfaceDataRow *newRow = new QSurfaceDataRow(endX - startX);
+ QSurfaceDataRow *srcRow = srcArray.at(i);
+ for (int j = startX, p = 0; j < endX; j++, p++) {
+ QVector3D pos = srcRow->at(j).position();
+ (*newRow)[p].setPosition(QVector3D(pos.x(), pos.y() + 0.1f, pos.z()));
+ }
+ *dataArray << newRow;
+ }
+
+ dataProxy()->resetArray(dataArray);
+ setVisible(true);
+}
+//! [1]
+
+//! [3]
+void HighlightSeries::handleGradientChange(float value)
+{
+ float ratio = m_minHeight / value;
+
+ QLinearGradient gr;
+ gr.setColorAt(0.f, Qt::black);
+ gr.setColorAt(darkGreenPos * ratio, Qt::darkGreen);
+ gr.setColorAt(greenPos * ratio, Qt::green);
+ gr.setColorAt(yellowPos * ratio, Qt::yellow);
+ gr.setColorAt(redPos * ratio, Qt::red);
+ gr.setColorAt(darkRedPos * ratio, Qt::darkRed);
+
+ setBaseGradient(gr);
+ setColorStyle(Q3DTheme::ColorStyleRangeGradient);
+}
+//! [3]
diff --git a/examples/graphs/graphgallery/highlightseries.h b/examples/graphs/graphgallery/highlightseries.h
new file mode 100644
index 0000000..4d168ca
--- /dev/null
+++ b/examples/graphs/graphgallery/highlightseries.h
@@ -0,0 +1,35 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef HIGHLIGHTSERIES_H
+#define HIGHLIGHTSERIES_H
+
+#include <QtGraphs/qsurface3dseries.h>
+
+#include "topographicseries.h"
+
+class HighlightSeries : public QSurface3DSeries
+{
+ Q_OBJECT
+public:
+ explicit HighlightSeries();
+ ~HighlightSeries();
+
+ void setTopographicSeries(TopographicSeries *series);
+ inline void setMinHeight(float height) { m_minHeight = height; }
+
+public Q_SLOTS:
+ void handlePositionChange(const QPoint &position);
+ void handleGradientChange(float value);
+
+private:
+ int m_width = 100;
+ int m_height = 100;
+ int m_srcWidth = 0;
+ int m_srcHeight = 0;
+ QPoint m_position = {};
+ TopographicSeries *m_topographicSeries = nullptr;
+ float m_minHeight = 0.f;
+};
+
+#endif // HIGHLIGHTSERIES_H
diff --git a/examples/graphs/graphgallery/main.cpp b/examples/graphs/graphgallery/main.cpp
new file mode 100644
index 0000000..6a100d3
--- /dev/null
+++ b/examples/graphs/graphgallery/main.cpp
@@ -0,0 +1,40 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "bargraph.h"
+#include "scattergraph.h"
+#include "surfacegraph.h"
+
+#include <QtWidgets/qapplication.h>
+#include <QtWidgets/qwidget.h>
+#include <QtWidgets/qtabwidget.h>
+
+using namespace Qt::StringLiterals;
+
+int main(int argc, char **argv)
+{
+ QApplication app(argc, argv);
+
+ // Create bar graph
+ BarGraph bars;
+
+ // Create scatter graph
+ ScatterGraph scatter;
+
+ // Create surface graph
+ SurfaceGraph surface;
+
+ // Create a tab widget for creating own tabs for Q3DBars, Q3DScatter, and Q3DSurface
+ QTabWidget tabWidget;
+ tabWidget.setWindowTitle(u"Graph Gallery"_s);
+
+ // Add bars widget
+ tabWidget.addTab(bars.barsWidget(), u"Bar Graph"_s);
+ // Add scatter widget
+ tabWidget.addTab(scatter.scatterWidget(), u"Scatter Graph"_s);
+ // Add surface widget
+ tabWidget.addTab(surface.surfaceWidget(), u"Surface Graph"_s);
+
+ tabWidget.show();
+ return app.exec();
+}
diff --git a/examples/graphs/graphgallery/rainfalldata.cpp b/examples/graphs/graphgallery/rainfalldata.cpp
new file mode 100644
index 0000000..17c6b0b
--- /dev/null
+++ b/examples/graphs/graphgallery/rainfalldata.cpp
@@ -0,0 +1,118 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "rainfalldata.h"
+#include <QtGraphs/q3dscene.h>
+#include <QtGraphs/q3dcamera.h>
+#include <QtGraphs/qbar3dseries.h>
+#include <QtGraphs/q3dtheme.h>
+#include <QtCore/qtextstream.h>
+#include <QtCore/qfile.h>
+
+using namespace Qt::StringLiterals;
+
+RainfallData::RainfallData()
+{
+ // In data file the months are in numeric format, so create custom list
+ for (int i = 1; i <= 12; i++)
+ m_numericMonths << QString::number(i);
+
+ m_columnCount = m_numericMonths.size();
+
+ updateYearsList(2010, 2022);
+
+ // Create proxy and series
+ //! [0]
+ m_proxy = new VariantBarDataProxy;
+ m_series = new QBar3DSeries(m_proxy);
+ //! [0]
+
+ m_series->setItemLabelFormat(u"%.1f mm"_s);
+
+ // Create the axes
+ m_rowAxis = new QCategory3DAxis(this);
+ m_colAxis = new QCategory3DAxis(this);
+ m_valueAxis = new QValue3DAxis(this);
+ m_rowAxis->setAutoAdjustRange(true);
+ m_colAxis->setAutoAdjustRange(true);
+ m_valueAxis->setAutoAdjustRange(true);
+
+ // Set axis labels and titles
+ QStringList months{"January", "February", "March", "April",
+ "May", "June", "July", "August", "September", "October",
+ "November","December"};
+ m_rowAxis->setTitle("Year");
+ m_colAxis->setTitle("Month");
+ m_valueAxis->setTitle("rainfall (mm)");
+ m_valueAxis->setSegmentCount(5);
+ m_rowAxis->setLabels(m_years);
+ m_colAxis->setLabels(months);
+ m_rowAxis->setTitleVisible(true);
+ m_colAxis->setTitleVisible(true);
+ m_valueAxis->setTitleVisible(true);
+
+ addDataSet();
+}
+
+RainfallData::~RainfallData()
+{
+ delete m_mapping;
+ delete m_dataSet;
+ delete m_proxy;
+}
+
+void RainfallData::updateYearsList(int start, int end)
+{
+ m_years.clear();
+
+ for (int i = start; i <= end; i++)
+ m_years << QString::number(i);
+
+ m_rowCount = m_years.size();
+}
+
+//! [1]
+void RainfallData::addDataSet()
+{
+ // Create a new variant data set and data item list
+ m_dataSet = new VariantDataSet;
+ VariantDataItemList *itemList = new VariantDataItemList;
+
+ // Read data from a data file into the data item list
+ QTextStream stream;
+ QFile dataFile(":/data/raindata.txt");
+ if (dataFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
+ stream.setDevice(&dataFile);
+ while (!stream.atEnd()) {
+ QString line = stream.readLine();
+ if (line.startsWith('#')) // Ignore comments
+ continue;
+ QStringList strList = line.split(',', Qt::SkipEmptyParts);
+ // Each line has three data items: Year, month, and rainfall value
+ if (strList.size() < 3) {
+ qWarning() << "Invalid row read from data:" << line;
+ continue;
+ }
+ // Store year and month as strings, and rainfall value as double
+ // into a variant data item and add the item to the item list.
+ VariantDataItem *newItem = new VariantDataItem;
+ for (int i = 0; i < 2; i++)
+ newItem->append(strList.at(i).trimmed());
+ newItem->append(strList.at(2).trimmed().toDouble());
+ itemList->append(newItem);
+ }
+ } else {
+ qWarning() << "Unable to open data file:" << dataFile.fileName();
+ }
+ //! [1]
+
+ //! [2]
+ // Add items to the data set and set it to the proxy
+ m_dataSet->addItems(itemList);
+ m_proxy->setDataSet(m_dataSet);
+
+ // Create new mapping for the data and set it to the proxy
+ m_mapping = new VariantBarDataMapping(0, 1, 2, m_years, m_numericMonths);
+ m_proxy->setMapping(m_mapping);
+ //! [2]
+}
diff --git a/examples/graphs/graphgallery/rainfalldata.h b/examples/graphs/graphgallery/rainfalldata.h
new file mode 100644
index 0000000..5fb50ed
--- /dev/null
+++ b/examples/graphs/graphgallery/rainfalldata.h
@@ -0,0 +1,43 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef RAINFALLDATA_H
+#define RAINFALLDATA_H
+
+#include "variantbardataproxy.h"
+#include <QtGraphs/qcategory3daxis.h>
+#include <QtGraphs/qvalue3daxis.h>
+
+class RainfallData : public QObject
+{
+ Q_OBJECT
+public:
+ explicit RainfallData();
+ ~RainfallData();
+
+ void addDataSet();
+
+ //! [0]
+ QBar3DSeries *customSeries() { return m_series; }
+ //! [0]
+
+ QValue3DAxis *valueAxis() { return m_valueAxis; }
+ QCategory3DAxis *rowAxis() { return m_rowAxis; }
+ QCategory3DAxis *colAxis() { return m_colAxis; }
+
+private:
+ void updateYearsList(int start, int end);
+ int m_columnCount;
+ int m_rowCount;
+ QStringList m_years;
+ QStringList m_numericMonths;
+ VariantBarDataProxy *m_proxy;
+ VariantBarDataMapping *m_mapping;
+ VariantDataSet *m_dataSet;
+ QBar3DSeries *m_series;
+ QValue3DAxis *m_valueAxis;
+ QCategory3DAxis *m_rowAxis;
+ QCategory3DAxis *m_colAxis;
+};
+
+#endif
diff --git a/examples/graphs/graphgallery/scatterdatamodifier.cpp b/examples/graphs/graphgallery/scatterdatamodifier.cpp
new file mode 100644
index 0000000..52117e3
--- /dev/null
+++ b/examples/graphs/graphgallery/scatterdatamodifier.cpp
@@ -0,0 +1,201 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "scatterdatamodifier.h"
+#include <QtGraphs/qscatterdataproxy.h>
+#include <QtGraphs/qvalue3daxis.h>
+#include <QtGraphs/q3dscene.h>
+#include <QtGraphs/q3dcamera.h>
+#include <QtGraphs/qscatter3dseries.h>
+#include <QtGraphs/q3dtheme.h>
+#include <QtCore/qmath.h>
+#include <QtCore/qrandom.h>
+#include <QtWidgets/qcombobox.h>
+
+using namespace Qt::StringLiterals;
+
+//#define RANDOM_SCATTER // Uncomment this to switch to random scatter
+
+const int numberOfItems = 10000;
+const float curveDivider = 7.5f;
+const int lowerNumberOfItems = 900;
+const float lowerCurveDivider = 0.75f;
+
+ScatterDataModifier::ScatterDataModifier(Q3DScatter *scatter)
+ : m_graph(scatter),
+ m_itemCount(lowerNumberOfItems),
+ m_curveDivider(lowerCurveDivider),
+ //! [7]
+ m_inputHandler(new AxesInputHandler(scatter))
+ //! [7]
+{
+ //! [0]
+ m_graph->activeTheme()->setType(Q3DTheme::ThemeStoneMoss);
+ m_graph->setShadowQuality(QAbstract3DGraph::ShadowQualitySoftHigh);
+ m_graph->scene()->activeCamera()->setCameraPreset(Q3DCamera::CameraPresetFront);
+ m_graph->scene()->activeCamera()->setZoomLevel(80.f);
+ //! [0]
+
+ //! [1]
+ QScatterDataProxy *proxy = new QScatterDataProxy;
+ QScatter3DSeries *series = new QScatter3DSeries(proxy);
+ series->setItemLabelFormat(u"@xTitle: @xLabel @yTitle: @yLabel @zTitle: @zLabel"_s);
+ series->setMeshSmooth(m_smooth);
+ m_graph->addSeries(series);
+ //! [1]
+
+ //! [8]
+ // Give ownership of the handler to the graph and make it the active handler
+ // TODO: API missing (QTBUG-111611)
+// m_graph->setActiveInputHandler(m_inputHandler);
+ //! [8]
+
+ //! [9]
+ // Give our axes to the input handler
+ m_inputHandler->setAxes(m_graph->axisX(), m_graph->axisZ(), m_graph->axisY());
+ //! [9]
+
+ //! [2]
+ addData();
+ //! [2]
+}
+
+ScatterDataModifier::~ScatterDataModifier()
+{
+ delete m_graph;
+}
+
+void ScatterDataModifier::addData()
+{
+ // Configure the axes according to the data
+ //! [3]
+ m_graph->axisX()->setTitle("X");
+ m_graph->axisY()->setTitle("Y");
+ m_graph->axisZ()->setTitle("Z");
+ //! [3]
+
+ //! [4]
+ QScatterDataArray *dataArray = new QScatterDataArray;
+ dataArray->resize(m_itemCount);
+ QScatterDataItem *ptrToDataArray = &dataArray->first();
+ //! [4]
+
+#ifdef RANDOM_SCATTER
+ for (int i = 0; i < m_itemCount; i++) {
+ ptrToDataArray->setPosition(randVector());
+ ptrToDataArray++;
+ }
+#else
+ //! [5]
+ float limit = qSqrt(m_itemCount) / 2.0f;
+ for (int i = -limit; i < limit; i++) {
+ for (int j = -limit; j < limit; j++) {
+ ptrToDataArray->setPosition(QVector3D(float(i) + 0.5f,
+ qCos(qDegreesToRadians(float(i * j) / m_curveDivider)),
+ float(j) + 0.5f));
+ ptrToDataArray++;
+ }
+ }
+ //! [5]
+#endif
+
+ //! [6]
+ m_graph->seriesList().at(0)->dataProxy()->resetArray(dataArray);
+ //! [6]
+}
+
+void ScatterDataModifier::changeStyle(int style)
+{
+ QComboBox *comboBox = qobject_cast<QComboBox *>(sender());
+ if (comboBox) {
+ m_style = comboBox->itemData(style).value<QAbstract3DSeries::Mesh>();
+ if (!m_graph->seriesList().isEmpty())
+ m_graph->seriesList().at(0)->setMesh(m_style);
+ }
+}
+
+void ScatterDataModifier::setSmoothDots(int smooth)
+{
+ m_smooth = bool(smooth);
+ QScatter3DSeries *series = m_graph->seriesList().at(0);
+ series->setMeshSmooth(m_smooth);
+}
+
+void ScatterDataModifier::changeTheme(int theme)
+{
+ Q3DTheme *currentTheme = m_graph->activeTheme();
+ currentTheme->setType(Q3DTheme::Theme(theme));
+ emit backgroundEnabledChanged(currentTheme->isBackgroundEnabled());
+ emit gridEnabledChanged(currentTheme->isGridEnabled());
+}
+
+void ScatterDataModifier::changePresetCamera()
+{
+ static int preset = Q3DCamera::CameraPresetFrontLow;
+
+ m_graph->scene()->activeCamera()->setCameraPreset((Q3DCamera::CameraPreset)preset);
+
+ if (++preset > Q3DCamera::CameraPresetDirectlyBelow)
+ preset = Q3DCamera::CameraPresetFrontLow;
+}
+
+void ScatterDataModifier::shadowQualityUpdatedByVisual(QAbstract3DGraph::ShadowQuality sq)
+{
+ int quality = int(sq);
+ emit shadowQualityChanged(quality); // connected to a checkbox in scattergraph.cpp
+}
+
+void ScatterDataModifier::changeShadowQuality(int quality)
+{
+ QAbstract3DGraph::ShadowQuality sq = QAbstract3DGraph::ShadowQuality(quality);
+ m_graph->setShadowQuality(sq);
+}
+
+void ScatterDataModifier::setBackgroundEnabled(int enabled)
+{
+ m_graph->activeTheme()->setBackgroundEnabled((bool)enabled);
+}
+
+void ScatterDataModifier::setGridEnabled(int enabled)
+{
+ m_graph->activeTheme()->setGridEnabled((bool)enabled);
+}
+
+void ScatterDataModifier::toggleItemCount()
+{
+ if (m_itemCount == numberOfItems) {
+ m_itemCount = lowerNumberOfItems;
+ m_curveDivider = lowerCurveDivider;
+ } else {
+ m_itemCount = numberOfItems;
+ m_curveDivider = curveDivider;
+ }
+ m_graph->seriesList().at(0)->dataProxy()->resetArray(0);
+ addData();
+}
+
+void ScatterDataModifier::toggleRanges()
+{
+ if (!m_autoAdjust) {
+ m_graph->axisX()->setAutoAdjustRange(true);
+ m_graph->axisZ()->setAutoAdjustRange(true);
+ m_inputHandler->setDragSpeedModifier(1.5f);
+ m_autoAdjust = true;
+ } else {
+ m_graph->axisX()->setRange(-10.0f, 10.0f);
+ m_graph->axisZ()->setRange(-10.0f, 10.0f);
+ m_inputHandler->setDragSpeedModifier(15.0f);
+ m_autoAdjust = false;
+ }
+}
+
+QVector3D ScatterDataModifier::randVector()
+{
+ return QVector3D(
+ (float)(QRandomGenerator::global()->bounded(100)) / 2.0f -
+ (float)(QRandomGenerator::global()->bounded(100)) / 2.0f,
+ (float)(QRandomGenerator::global()->bounded(100)) / 100.0f -
+ (float)(QRandomGenerator::global()->bounded(100)) / 100.0f,
+ (float)(QRandomGenerator::global()->bounded(100)) / 2.0f -
+ (float)(QRandomGenerator::global()->bounded(100)) / 2.0f);
+}
diff --git a/examples/graphs/graphgallery/scatterdatamodifier.h b/examples/graphs/graphgallery/scatterdatamodifier.h
new file mode 100644
index 0000000..12ccc9f
--- /dev/null
+++ b/examples/graphs/graphgallery/scatterdatamodifier.h
@@ -0,0 +1,50 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef SCATTERDATAMODIFIER_H
+#define SCATTERDATAMODIFIER_H
+
+#include "axesinputhandler.h"
+
+#include <QtGraphs/q3dscatter.h>
+#include <QtGraphs/qabstract3dseries.h>
+
+class ScatterDataModifier : public QObject
+{
+ Q_OBJECT
+public:
+ explicit ScatterDataModifier(Q3DScatter *scatter);
+ ~ScatterDataModifier();
+
+ void addData();
+
+public Q_SLOTS:
+ void setBackgroundEnabled(int enabled);
+ void setGridEnabled(int enabled);
+ void setSmoothDots(int smooth);
+ void changePresetCamera();
+ void toggleItemCount();
+ void toggleRanges();
+ void changeStyle(int style);
+ void changeTheme(int theme);
+ void changeShadowQuality(int quality);
+ void shadowQualityUpdatedByVisual(QAbstract3DGraph::ShadowQuality shadowQuality);
+
+Q_SIGNALS:
+ void backgroundEnabledChanged(bool enabled);
+ void gridEnabledChanged(bool enabled);
+ void shadowQualityChanged(int quality);
+
+private:
+ QVector3D randVector();
+ Q3DScatter *m_graph = nullptr;
+ QAbstract3DSeries::Mesh m_style = QAbstract3DSeries::MeshSphere;
+ bool m_smooth = true;
+ int m_itemCount;
+ float m_curveDivider;
+
+ AxesInputHandler *m_inputHandler;
+ bool m_autoAdjust = true;
+};
+
+#endif
diff --git a/examples/graphs/graphgallery/scattergraph.cpp b/examples/graphs/graphgallery/scattergraph.cpp
new file mode 100644
index 0000000..11dbef4
--- /dev/null
+++ b/examples/graphs/graphgallery/scattergraph.cpp
@@ -0,0 +1,139 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "scattergraph.h"
+#include "scatterdatamodifier.h"
+
+#include <QtWidgets/qboxlayout.h>
+#include <QtWidgets/qcheckbox.h>
+#include <QtWidgets/qcombobox.h>
+#include <QtWidgets/qlabel.h>
+#include <QtWidgets/qcommandlinkbutton.h>
+
+using namespace Qt::StringLiterals;
+
+ScatterGraph::ScatterGraph()
+{
+ m_scatterGraph = new Q3DScatter();
+ initialize();
+}
+
+ScatterGraph::~ScatterGraph() = default;
+
+void ScatterGraph::initialize()
+{
+ m_scatterWidget = new QWidget;
+ QHBoxLayout *hLayout = new QHBoxLayout(m_scatterWidget);
+ QSize screenSize = m_scatterGraph->screen()->size();
+ m_scatterGraph->setMinimumSize(QSize(screenSize.width() / 2, screenSize.height() / 1.75));
+ m_scatterGraph->setMaximumSize(screenSize);
+ m_scatterGraph->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+ m_scatterGraph->setFocusPolicy(Qt::StrongFocus);
+ m_scatterGraph->setResizeMode(QQuickWidget::SizeRootObjectToView);
+ hLayout->addWidget(m_scatterGraph, 1);
+
+ QVBoxLayout *vLayout = new QVBoxLayout();
+ hLayout->addLayout(vLayout);
+
+ QCommandLinkButton *cameraButton = new QCommandLinkButton(m_scatterWidget);
+ cameraButton->setText(u"Change camera preset"_s);
+ cameraButton->setDescription(u"Switch between a number of preset camera positions"_s);
+ cameraButton->setIconSize(QSize(0, 0));
+
+ QCommandLinkButton *itemCountButton = new QCommandLinkButton(m_scatterWidget);
+ itemCountButton->setText(u"Toggle item count"_s);
+ itemCountButton->setDescription(u"Switch between 900 and 10000 data points"_s);
+ itemCountButton->setIconSize(QSize(0, 0));
+
+ QCommandLinkButton *rangeButton = new QCommandLinkButton(m_scatterWidget);
+ rangeButton->setText(u"Toggle axis ranges"_s);
+ rangeButton->setDescription(u"Switch between automatic axis ranges and preset ranges"_s);
+ rangeButton->setIconSize(QSize(0, 0));
+
+ QCheckBox *backgroundCheckBox = new QCheckBox(m_scatterWidget);
+ backgroundCheckBox->setText(u"Show background"_s);
+ backgroundCheckBox->setChecked(true);
+
+ QCheckBox *gridCheckBox = new QCheckBox(m_scatterWidget);
+ gridCheckBox->setText(u"Show grid"_s);
+ gridCheckBox->setChecked(true);
+
+ QCheckBox *smoothCheckBox = new QCheckBox(m_scatterWidget);
+ smoothCheckBox->setText(u"Smooth dots"_s);
+ smoothCheckBox->setChecked(true);
+
+ QComboBox *itemStyleList = new QComboBox(m_scatterWidget);
+ itemStyleList->addItem(u"Sphere"_s, QAbstract3DSeries::MeshSphere);
+ itemStyleList->addItem(u"Cube"_s, QAbstract3DSeries::MeshCube);
+ itemStyleList->addItem(u"Minimal"_s, QAbstract3DSeries::MeshMinimal);
+ itemStyleList->addItem(u"Point"_s, QAbstract3DSeries::MeshPoint);
+ itemStyleList->setCurrentIndex(0);
+
+ QComboBox *themeList = new QComboBox(m_scatterWidget);
+ themeList->addItem(u"Qt"_s);
+ themeList->addItem(u"Primary Colors"_s);
+ themeList->addItem(u"Digia"_s);
+ themeList->addItem(u"Stone Moss"_s);
+ themeList->addItem(u"Army Blue"_s);
+ themeList->addItem(u"Retro"_s);
+ themeList->addItem(u"Ebony"_s);
+ themeList->addItem(u"Isabelle"_s);
+ themeList->setCurrentIndex(3);
+
+ QComboBox *shadowQuality = new QComboBox(m_scatterWidget);
+ shadowQuality->addItem(u"None"_s);
+ shadowQuality->addItem(u"Low"_s);
+ shadowQuality->addItem(u"Medium"_s);
+ shadowQuality->addItem(u"High"_s);
+ shadowQuality->addItem(u"Low Soft"_s);
+ shadowQuality->addItem(u"Medium Soft"_s);
+ shadowQuality->addItem(u"High Soft"_s);
+ shadowQuality->setCurrentIndex(6);
+
+ vLayout->addWidget(cameraButton);
+ vLayout->addWidget(itemCountButton);
+ vLayout->addWidget(rangeButton);
+ vLayout->addWidget(backgroundCheckBox);
+ vLayout->addWidget(gridCheckBox);
+ vLayout->addWidget(smoothCheckBox);
+ vLayout->addWidget(new QLabel(u"Change dot style"_s));
+ vLayout->addWidget(itemStyleList);
+ vLayout->addWidget(new QLabel(u"Change theme"_s));
+ vLayout->addWidget(themeList);
+ vLayout->addWidget(new QLabel(u"Adjust shadow quality"_s));
+ vLayout->addWidget(shadowQuality, 1, Qt::AlignTop);
+
+ ScatterDataModifier *modifier = new ScatterDataModifier(m_scatterGraph);
+
+ QObject::connect(cameraButton, &QCommandLinkButton::clicked, modifier,
+ &ScatterDataModifier::changePresetCamera);
+ QObject::connect(itemCountButton, &QCommandLinkButton::clicked, modifier,
+ &ScatterDataModifier::toggleItemCount);
+ QObject::connect(rangeButton, &QCommandLinkButton::clicked, modifier,
+ &ScatterDataModifier::toggleRanges);
+
+ QObject::connect(backgroundCheckBox, &QCheckBox::stateChanged, modifier,
+ &ScatterDataModifier::setBackgroundEnabled);
+ QObject::connect(gridCheckBox, &QCheckBox::stateChanged, modifier,
+ &ScatterDataModifier::setGridEnabled);
+ QObject::connect(smoothCheckBox, &QCheckBox::stateChanged, modifier,
+ &ScatterDataModifier::setSmoothDots);
+
+ QObject::connect(modifier, &ScatterDataModifier::backgroundEnabledChanged,
+ backgroundCheckBox, &QCheckBox::setChecked);
+ QObject::connect(modifier, &ScatterDataModifier::gridEnabledChanged,
+ gridCheckBox, &QCheckBox::setChecked);
+ QObject::connect(itemStyleList, &QComboBox::currentIndexChanged, modifier,
+ &ScatterDataModifier::changeStyle);
+
+ QObject::connect(themeList, &QComboBox::currentIndexChanged, modifier,
+ &ScatterDataModifier::changeTheme);
+
+ QObject::connect(shadowQuality, &QComboBox::currentIndexChanged, modifier,
+ &ScatterDataModifier::changeShadowQuality);
+
+ QObject::connect(modifier, &ScatterDataModifier::shadowQualityChanged, shadowQuality,
+ &QComboBox::setCurrentIndex);
+ QObject::connect(m_scatterGraph, &Q3DScatter::shadowQualityChanged, modifier,
+ &ScatterDataModifier::shadowQualityUpdatedByVisual);
+}
diff --git a/examples/graphs/graphgallery/scattergraph.h b/examples/graphs/graphgallery/scattergraph.h
new file mode 100644
index 0000000..c79edcd
--- /dev/null
+++ b/examples/graphs/graphgallery/scattergraph.h
@@ -0,0 +1,25 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef SCATTERGRAPH_H
+#define SCATTERGRAPH_H
+
+#include <QtCore/qobject.h>
+#include <QtGraphs/q3dscatter.h>
+
+class ScatterGraph : public QObject
+{
+ Q_OBJECT
+public:
+ ScatterGraph();
+ ~ScatterGraph();
+
+ void initialize();
+ QWidget *scatterWidget() { return m_scatterWidget; }
+
+private:
+ Q3DScatter *m_scatterGraph = nullptr;
+ QWidget *m_scatterWidget = nullptr;
+};
+
+#endif
diff --git a/examples/graphs/graphgallery/surfacegraph.cpp b/examples/graphs/graphgallery/surfacegraph.cpp
new file mode 100644
index 0000000..3107297
--- /dev/null
+++ b/examples/graphs/graphgallery/surfacegraph.cpp
@@ -0,0 +1,326 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "surfacegraph.h"
+#include "surfacegraphmodifier.h"
+
+#include <QtWidgets/qboxlayout.h>
+#include <QtWidgets/qcheckbox.h>
+#include <QtWidgets/qcombobox.h>
+#include <QtWidgets/qradiobutton.h>
+#include <QtWidgets/qgroupbox.h>
+#include <QtWidgets/qlabel.h>
+#include <QtWidgets/qcommandlinkbutton.h>
+#include <QtGui/qpainter.h>
+
+using namespace Qt::StringLiterals;
+
+SurfaceGraph::SurfaceGraph()
+{
+ m_surfaceGraph = new Q3DSurface();
+ initialize();
+}
+
+SurfaceGraph::~SurfaceGraph() = default;
+
+void SurfaceGraph::initialize()
+{
+ m_surfaceWidget = new QWidget;
+ QHBoxLayout *hLayout = new QHBoxLayout(m_surfaceWidget);
+ QSize screenSize = m_surfaceGraph->screen()->size();
+ m_surfaceGraph->setMinimumSize(QSize(screenSize.width() / 2, screenSize.height() / 1.75));
+ m_surfaceGraph->setMaximumSize(screenSize);
+ m_surfaceGraph->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+ m_surfaceGraph->setFocusPolicy(Qt::StrongFocus);
+ m_surfaceGraph->setResizeMode(QQuickWidget::SizeRootObjectToView);
+ hLayout->addWidget(m_surfaceGraph, 1);
+
+ QVBoxLayout *vLayout = new QVBoxLayout();
+ hLayout->addLayout(vLayout);
+ vLayout->setAlignment(Qt::AlignTop);
+
+ // Create control widgets
+ QGroupBox *modelGroupBox = new QGroupBox(u"Model"_s);
+
+ QRadioButton *sqrtSinModelRB = new QRadioButton(m_surfaceWidget);
+ sqrtSinModelRB->setText(u"Sqrt && Sin"_s);
+ sqrtSinModelRB->setChecked(false);
+
+ QRadioButton *heightMapModelRB = new QRadioButton(m_surfaceWidget);
+ heightMapModelRB->setText(u"Multiseries\nHeight Map"_s);
+ heightMapModelRB->setChecked(false);
+
+ QRadioButton *texturedModelRB = new QRadioButton(m_surfaceWidget);
+ texturedModelRB->setText(u"Textured\nTopography"_s);
+ texturedModelRB->setChecked(false);
+
+ QVBoxLayout *modelVBox = new QVBoxLayout;
+ modelVBox->addWidget(sqrtSinModelRB);
+ modelVBox->addWidget(heightMapModelRB);
+ modelVBox->addWidget(texturedModelRB);
+ modelGroupBox->setLayout(modelVBox);
+
+ QGroupBox *selectionGroupBox = new QGroupBox(u"Graph Selection Mode"_s);
+
+ QRadioButton *modeNoneRB = new QRadioButton(m_surfaceWidget);
+ modeNoneRB->setText(u"No selection"_s);
+ modeNoneRB->setChecked(false);
+
+ QRadioButton *modeItemRB = new QRadioButton(m_surfaceWidget);
+ modeItemRB->setText(u"Item"_s);
+ modeItemRB->setChecked(false);
+
+ QRadioButton *modeSliceRowRB = new QRadioButton(m_surfaceWidget);
+ modeSliceRowRB->setText(u"Row Slice"_s);
+ modeSliceRowRB->setChecked(false);
+
+ QRadioButton *modeSliceColumnRB = new QRadioButton(m_surfaceWidget);
+ modeSliceColumnRB->setText(u"Column Slice"_s);
+ modeSliceColumnRB->setChecked(false);
+
+ QVBoxLayout *selectionVBox = new QVBoxLayout;
+ selectionVBox->addWidget(modeNoneRB);
+ selectionVBox->addWidget(modeItemRB);
+ selectionVBox->addWidget(modeSliceRowRB);
+ selectionVBox->addWidget(modeSliceColumnRB);
+ selectionGroupBox->setLayout(selectionVBox);
+
+ QGroupBox *axisGroupBox = new QGroupBox(u"Axis ranges"_s);
+
+ QSlider *axisMinSliderX = new QSlider(Qt::Horizontal);
+ axisMinSliderX->setMinimum(0);
+ axisMinSliderX->setTickInterval(1);
+ axisMinSliderX->setEnabled(true);
+ QSlider *axisMaxSliderX = new QSlider(Qt::Horizontal);
+ axisMaxSliderX->setMinimum(1);
+ axisMaxSliderX->setTickInterval(1);
+ axisMaxSliderX->setEnabled(true);
+ QSlider *axisMinSliderZ = new QSlider(Qt::Horizontal);
+ axisMinSliderZ->setMinimum(0);
+ axisMinSliderZ->setTickInterval(1);
+ axisMinSliderZ->setEnabled(true);
+ QSlider *axisMaxSliderZ = new QSlider(Qt::Horizontal);
+ axisMaxSliderZ->setMinimum(1);
+ axisMaxSliderZ->setTickInterval(1);
+ axisMaxSliderZ->setEnabled(true);
+
+ QVBoxLayout *axisVBox = new QVBoxLayout;
+ axisVBox->addWidget(new QLabel(u"Column range"_s));
+ axisVBox->addWidget(axisMinSliderX);
+ axisVBox->addWidget(axisMaxSliderX);
+ axisVBox->addWidget(new QLabel(u"Row range"_s));
+ axisVBox->addWidget(axisMinSliderZ);
+ axisVBox->addWidget(axisMaxSliderZ);
+ axisGroupBox->setLayout(axisVBox);
+
+ // Mode-dependent controls
+ // sqrt-sin
+ QGroupBox *colorGroupBox = new QGroupBox(u"Custom gradient"_s);
+
+ QLinearGradient grBtoY(0, 0, 1, 100);
+ grBtoY.setColorAt(1.f, Qt::black);
+ grBtoY.setColorAt(0.67f, Qt::blue);
+ grBtoY.setColorAt(0.33f, Qt::red);
+ grBtoY.setColorAt(0.f, Qt::yellow);
+ QPixmap pm(24, 100);
+ QPainter pmp(&pm);
+ pmp.setBrush(QBrush(grBtoY));
+ pmp.setPen(Qt::NoPen);
+ pmp.drawRect(0, 0, 24, 100);
+ QPushButton *gradientBtoYPB = new QPushButton(m_surfaceWidget);
+ gradientBtoYPB->setIcon(QIcon(pm));
+ gradientBtoYPB->setIconSize(QSize(24, 100));
+
+ QLinearGradient grGtoR(0, 0, 1, 100);
+ grGtoR.setColorAt(1.f, Qt::darkGreen);
+ grGtoR.setColorAt(0.5f, Qt::yellow);
+ grGtoR.setColorAt(0.2f, Qt::red);
+ grGtoR.setColorAt(0.f, Qt::darkRed);
+ pmp.setBrush(QBrush(grGtoR));
+ pmp.drawRect(0, 0, 24, 100);
+ QPushButton *gradientGtoRPB = new QPushButton(m_surfaceWidget);
+ gradientGtoRPB->setIcon(QIcon(pm));
+ gradientGtoRPB->setIconSize(QSize(24, 100));
+
+ QHBoxLayout *colorHBox = new QHBoxLayout;
+ colorHBox->addWidget(gradientBtoYPB);
+ colorHBox->addWidget(gradientGtoRPB);
+ colorGroupBox->setLayout(colorHBox);
+
+ // Multiseries heightmap
+ QGroupBox *showGroupBox = new QGroupBox(u"Show Object"_s);
+ showGroupBox->setVisible(false);
+
+ QCheckBox *checkboxShowOilRigOne = new QCheckBox(u"Oil Rig 1"_s);
+ checkboxShowOilRigOne->setChecked(true);
+
+ QCheckBox *checkboxShowOilRigTwo = new QCheckBox(u"Oil Rig 2"_s);
+ checkboxShowOilRigTwo->setChecked(true);
+
+ QCheckBox *checkboxShowRefinery = new QCheckBox(u"Refinery"_s);
+
+ QVBoxLayout *showVBox = new QVBoxLayout;
+ showVBox->addWidget(checkboxShowOilRigOne);
+ showVBox->addWidget(checkboxShowOilRigTwo);
+ showVBox->addWidget(checkboxShowRefinery);
+ showGroupBox->setLayout(showVBox);
+
+ QGroupBox *visualsGroupBox = new QGroupBox(u"Visuals"_s);
+ visualsGroupBox->setVisible(false);
+
+ QCheckBox *checkboxVisualsSeeThrough = new QCheckBox(u"See-Through"_s);
+
+ QCheckBox *checkboxHighlightOil = new QCheckBox(u"Highlight Oil"_s);
+
+ QCheckBox *checkboxShowShadows = new QCheckBox(u"Shadows"_s);
+ checkboxShowShadows->setChecked(true);
+
+ QVBoxLayout *visualVBox = new QVBoxLayout;
+ visualVBox->addWidget(checkboxVisualsSeeThrough);
+ visualVBox->addWidget(checkboxHighlightOil);
+ visualVBox->addWidget(checkboxShowShadows);
+ visualsGroupBox->setLayout(visualVBox);
+
+ QLabel *labelSelection = new QLabel(u"Selection:"_s);
+ labelSelection->setVisible(false);
+
+ QLabel *labelSelectedItem = new QLabel(u"Nothing"_s);
+ labelSelectedItem->setVisible(false);
+
+ // Textured topography heightmap
+ QCheckBox *enableTexture = new QCheckBox(u"Surface texture"_s);
+ enableTexture->setVisible(false);
+
+ int height = 400;
+ int width = 110;
+ int border = 10;
+ QLinearGradient gr(0, 0, 1, height - 2 * border);
+ gr.setColorAt(1.f, Qt::black);
+ gr.setColorAt(0.8f, Qt::darkGreen);
+ gr.setColorAt(0.6f, Qt::green);
+ gr.setColorAt(0.4f, Qt::yellow);
+ gr.setColorAt(0.2f, Qt::red);
+ gr.setColorAt(0.f, Qt::darkRed);
+
+ QPixmap pmHighlight(width, height);
+ pmHighlight.fill(Qt::transparent);
+ QPainter pmpHighlight(&pmHighlight);
+ pmpHighlight.setBrush(QBrush(gr));
+ pmpHighlight.setPen(Qt::NoPen);
+ pmpHighlight.drawRect(border, border, 35, height - 2 * border);
+ pmpHighlight.setPen(Qt::black);
+ int step = (height - 2 * border) / 5;
+ for (int i = 0; i < 6; i++) {
+ int yPos = i * step + border;
+ pmpHighlight.drawLine(border, yPos, 55, yPos);
+ const int height = 550 - (i * 110);
+ pmpHighlight.drawText(60, yPos + 2, QString::number(height) + u" m"_s);
+ }
+
+ QLabel *label = new QLabel(m_surfaceWidget);
+ label->setPixmap(pmHighlight);
+
+ QGroupBox *heightMapGroupBox = new QGroupBox(u"Highlight color map"_s);
+ QVBoxLayout *colorMapVBox = new 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
+ SurfaceGraphModifier *modifier = new SurfaceGraphModifier(m_surfaceGraph, labelSelectedItem);
+
+ // Connect widget controls to controller
+ QObject::connect(heightMapModelRB, &QRadioButton::toggled,
+ modifier, &SurfaceGraphModifier::enableHeightMapModel);
+ QObject::connect(sqrtSinModelRB, &QRadioButton::toggled,
+ modifier, &SurfaceGraphModifier::enableSqrtSinModel);
+ QObject::connect(texturedModelRB, &QRadioButton::toggled,
+ modifier, &SurfaceGraphModifier::enableTopographyModel);
+
+ QObject::connect(modeNoneRB, &QRadioButton::toggled,
+ modifier, &SurfaceGraphModifier::toggleModeNone);
+ QObject::connect(modeItemRB, &QRadioButton::toggled,
+ modifier, &SurfaceGraphModifier::toggleModeItem);
+ QObject::connect(modeSliceRowRB, &QRadioButton::toggled,
+ modifier, &SurfaceGraphModifier::toggleModeSliceRow);
+ QObject::connect(modeSliceColumnRB, &QRadioButton::toggled,
+ modifier, &SurfaceGraphModifier::toggleModeSliceColumn);
+
+ QObject::connect(axisMinSliderX, &QSlider::valueChanged,
+ modifier, &SurfaceGraphModifier::adjustXMin);
+ QObject::connect(axisMaxSliderX, &QSlider::valueChanged,
+ modifier, &SurfaceGraphModifier::adjustXMax);
+ QObject::connect(axisMinSliderZ, &QSlider::valueChanged,
+ modifier, &SurfaceGraphModifier::adjustZMin);
+ QObject::connect(axisMaxSliderZ, &QSlider::valueChanged,
+ modifier, &SurfaceGraphModifier::adjustZMax);
+
+ // Mode dependent connections
+ QObject::connect(gradientBtoYPB, &QPushButton::pressed,
+ modifier, &SurfaceGraphModifier::setBlackToYellowGradient);
+ QObject::connect(gradientGtoRPB, &QPushButton::pressed,
+ modifier, &SurfaceGraphModifier::setGreenToRedGradient);
+
+ QObject::connect(checkboxShowOilRigOne, &QCheckBox::stateChanged,
+ modifier, &SurfaceGraphModifier::toggleItemOne);
+ QObject::connect(checkboxShowOilRigTwo, &QCheckBox::stateChanged,
+ modifier, &SurfaceGraphModifier::toggleItemTwo);
+ QObject::connect(checkboxShowRefinery, &QCheckBox::stateChanged,
+ modifier, &SurfaceGraphModifier::toggleItemThree);
+
+ QObject::connect(checkboxVisualsSeeThrough, &QCheckBox::stateChanged,
+ modifier, &SurfaceGraphModifier::toggleSeeThrough);
+ QObject::connect(checkboxHighlightOil, &QCheckBox::stateChanged,
+ modifier, &SurfaceGraphModifier::toggleOilHighlight);
+ QObject::connect(checkboxShowShadows, &QCheckBox::stateChanged,
+ modifier, &SurfaceGraphModifier::toggleShadows);
+
+ QObject::connect(enableTexture, &QCheckBox::stateChanged,
+ modifier, &SurfaceGraphModifier::toggleSurfaceTexture);
+
+ // Connections to disable features depending on mode
+ QObject::connect(sqrtSinModelRB, &QRadioButton::toggled,
+ colorGroupBox, &QGroupBox::setVisible);
+
+ QObject::connect(heightMapModelRB, &QRadioButton::toggled,
+ showGroupBox, &QGroupBox::setVisible);
+ QObject::connect(heightMapModelRB, &QRadioButton::toggled,
+ visualsGroupBox, &QGroupBox::setVisible);
+ QObject::connect(heightMapModelRB, &QRadioButton::toggled,
+ labelSelection, &QLabel::setVisible);
+ QObject::connect(heightMapModelRB, &QRadioButton::toggled,
+ labelSelectedItem, &QLabel::setVisible);
+
+ QObject::connect(texturedModelRB, &QRadioButton::toggled,
+ enableTexture, &QLabel::setVisible);
+ QObject::connect(texturedModelRB, &QRadioButton::toggled,
+ heightMapGroupBox, &QGroupBox::setVisible);
+
+ modifier->setAxisMinSliderX(axisMinSliderX);
+ modifier->setAxisMaxSliderX(axisMaxSliderX);
+ modifier->setAxisMinSliderZ(axisMinSliderZ);
+ modifier->setAxisMaxSliderZ(axisMaxSliderZ);
+
+ sqrtSinModelRB->setChecked(true);
+ modeItemRB->setChecked(true);
+ enableTexture->setChecked(true);
+}
diff --git a/examples/graphs/graphgallery/surfacegraph.h b/examples/graphs/graphgallery/surfacegraph.h
new file mode 100644
index 0000000..79e2333
--- /dev/null
+++ b/examples/graphs/graphgallery/surfacegraph.h
@@ -0,0 +1,25 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef SURFACEGRAPH_H
+#define SURFACEGRAPH_H
+
+#include <QtCore/qobject.h>
+#include <QtGraphs/q3dsurface.h>
+
+class SurfaceGraph : public QObject
+{
+ Q_OBJECT
+public:
+ SurfaceGraph();
+ ~SurfaceGraph();
+
+ void initialize();
+ QWidget *surfaceWidget() { return m_surfaceWidget; }
+
+private:
+ Q3DSurface *m_surfaceGraph = nullptr;
+ QWidget *m_surfaceWidget = nullptr;
+};
+
+#endif
diff --git a/examples/graphs/graphgallery/surfacegraphmodifier.cpp b/examples/graphs/graphgallery/surfacegraphmodifier.cpp
new file mode 100644
index 0000000..4425295
--- /dev/null
+++ b/examples/graphs/graphgallery/surfacegraphmodifier.cpp
@@ -0,0 +1,668 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "surfacegraphmodifier.h"
+#include "highlightseries.h"
+#include "topographicseries.h"
+#include "custominputhandler.h"
+
+#include <QtGraphs/qvalue3daxis.h>
+#include <QtGraphs/q3dtheme.h>
+#include <QtGui/qimage.h>
+#include <QtCore/qmath.h>
+
+using namespace Qt::StringLiterals;
+
+const int sampleCountX = 150;
+const int sampleCountZ = 150;
+const int heightMapGridStepX = 6;
+const int heightMapGridStepZ = 6;
+const float sampleMin = -8.f;
+const float sampleMax = 8.f;
+
+const float areaWidth = 8000.f;
+const float areaHeight = 8000.f;
+const float aspectRatio = 0.1389f;
+const float minRange = areaWidth * 0.49f;
+
+SurfaceGraphModifier::SurfaceGraphModifier(Q3DSurface *surface, QLabel *label)
+ : m_graph(surface),
+ m_textField(label)
+{
+ m_graph->scene()->activeCamera()->setZoomLevel(85.f);
+ m_graph->scene()->activeCamera()->setCameraPreset(Q3DCamera::CameraPresetIsometricRight);
+ m_graph->activeTheme()->setType(Q3DTheme::ThemeRetro);
+
+ m_graph->setAxisX(new QValue3DAxis);
+ m_graph->setAxisY(new QValue3DAxis);
+ m_graph->setAxisZ(new QValue3DAxis);
+
+ //
+ // Sqrt Sin
+ //
+ //! [0]
+ m_sqrtSinProxy = new QSurfaceDataProxy();
+ m_sqrtSinSeries = new QSurface3DSeries(m_sqrtSinProxy);
+ //! [0]
+ fillSqrtSinProxy();
+
+ //
+ // Multisurface heightmap
+ //
+ //! [2]
+ // Create the first surface layer
+ QImage heightMapImageOne(":/data/layer_1.png");
+ m_heightMapProxyOne = new QHeightMapSurfaceDataProxy(heightMapImageOne);
+ m_heightMapSeriesOne = new QSurface3DSeries(m_heightMapProxyOne);
+ m_heightMapSeriesOne->setItemLabelFormat(u"(@xLabel, @zLabel): @yLabel"_s);
+ m_heightMapProxyOne->setValueRanges(34.f, 40.f, 18.f, 24.f);
+ //! [2]
+
+ // Create the other 2 surface layers
+ QImage heightMapImageTwo(":/data/layer_2.png");
+ m_heightMapProxyTwo = new QHeightMapSurfaceDataProxy(heightMapImageTwo);
+ m_heightMapSeriesTwo = new QSurface3DSeries(m_heightMapProxyTwo);
+ m_heightMapSeriesTwo->setItemLabelFormat(u"(@xLabel, @zLabel): @yLabel"_s);
+ m_heightMapProxyTwo->setValueRanges(34.f, 40.f, 18.f, 24.f);
+
+ QImage heightMapImageThree(":/data/layer_3.png");
+ m_heightMapProxyThree = new QHeightMapSurfaceDataProxy(heightMapImageThree);
+ m_heightMapSeriesThree = new QSurface3DSeries(m_heightMapProxyThree);
+ m_heightMapSeriesThree->setItemLabelFormat(u"(@xLabel, @zLabel): @yLabel"_s);
+ m_heightMapProxyThree->setValueRanges(34.f, 40.f, 18.f, 24.f);
+
+ // The images are the same size, so it's enough to get the dimensions from one
+ m_heightMapWidth = heightMapImageOne.width();
+ m_heightMapHeight = heightMapImageOne.height();
+
+ // Set the gradients for multi-surface layers
+ QLinearGradient grOne;
+ grOne.setColorAt(0.f, Qt::black);
+ grOne.setColorAt(0.38f, Qt::darkYellow);
+ grOne.setColorAt(0.39f, Qt::darkGreen);
+ grOne.setColorAt(0.5f, Qt::darkGray);
+ grOne.setColorAt(1.f, Qt::gray);
+ m_heightMapSeriesOne->setBaseGradient(grOne);
+ m_heightMapSeriesOne->setColorStyle(Q3DTheme::ColorStyleRangeGradient);
+
+ QLinearGradient grTwo;
+ grTwo.setColorAt(0.39f, Qt::blue);
+ grTwo.setColorAt(0.4f, Qt::white);
+ m_heightMapSeriesTwo->setBaseGradient(grTwo);
+ m_heightMapSeriesTwo->setColorStyle(Q3DTheme::ColorStyleRangeGradient);
+
+ QLinearGradient grThree;
+ grThree.setColorAt(0.f, Qt::white);
+ grThree.setColorAt(0.05f, Qt::black);
+ m_heightMapSeriesThree->setBaseGradient(grThree);
+ m_heightMapSeriesThree->setColorStyle(Q3DTheme::ColorStyleRangeGradient);
+
+ // Custom items and label
+ // TODO: API missing (QTBUG-111611)
+// connect(m_graph, &QAbstract3DGraph::selectedElementChanged,
+// this, &SurfaceGraphModifier::handleElementSelected);
+
+ m_selectionAnimation = new QPropertyAnimation(this);
+ m_selectionAnimation->setPropertyName("scaling");
+ m_selectionAnimation->setDuration(500);
+ m_selectionAnimation->setLoopCount(-1);
+
+ QFont titleFont = QFont("Century Gothic", 30);
+ titleFont.setBold(true);
+ m_titleLabel = new QCustom3DLabel("Oil Rigs on Imaginary Sea", titleFont,
+ QVector3D(0.f, 1.2f, 0.f),
+ QVector3D(1.f, 1.f, 0.f),
+ QQuaternion());
+ m_titleLabel->setPositionAbsolute(true);
+ m_titleLabel->setFacingCamera(true);
+ m_titleLabel->setBackgroundColor(QColor(0x66cdaa));
+ // TODO: API missing (QTBUG-111611)
+// m_graph->addCustomItem(m_titleLabel);
+ m_titleLabel->setVisible(false);
+
+ // Make two of the custom object visible
+ toggleItemOne(true);
+ toggleItemTwo(true);
+
+ //
+ // Topographic map
+ //
+ m_topography = new TopographicSeries();
+ m_topography->setTopographyFile(":/data/topography.png", areaWidth, areaHeight);
+ m_topography->setItemLabelFormat(u"@yLabel m"_s);
+
+ m_highlight = new HighlightSeries();
+ m_highlight->setTopographicSeries(m_topography);
+ m_highlight->setMinHeight(minRange * aspectRatio);
+ m_highlight->handleGradientChange(areaWidth * aspectRatio);
+ //! [16]
+ QObject::connect(m_graph->axisY(), &QValue3DAxis::maxChanged,
+ m_highlight, &HighlightSeries::handleGradientChange);
+ //! [16]
+
+ m_customInputHandler = new CustomInputHandler(m_graph);
+ m_customInputHandler->setHighlightSeries(m_highlight);
+ m_customInputHandler->setAxes(m_graph->axisX(), m_graph->axisY(), m_graph->axisZ());
+ m_customInputHandler->setLimits(0.f, areaWidth, minRange);
+ m_customInputHandler->setAspectRatio(aspectRatio);
+}
+
+SurfaceGraphModifier::~SurfaceGraphModifier()
+{
+ delete m_graph;
+}
+
+void SurfaceGraphModifier::fillSqrtSinProxy()
+{
+ float stepX = (sampleMax - sampleMin) / float(sampleCountX - 1);
+ float stepZ = (sampleMax - sampleMin) / float(sampleCountZ - 1);
+
+ //! [1]
+ QSurfaceDataArray *dataArray = new QSurfaceDataArray;
+ dataArray->reserve(sampleCountZ);
+ for (int i = 0 ; i < sampleCountZ ; i++) {
+ QSurfaceDataRow *newRow = new QSurfaceDataRow(sampleCountX);
+ // Keep values within range bounds, since just adding step can cause minor drift due
+ // to the rounding errors.
+ float z = qMin(sampleMax, (i * stepZ + sampleMin));
+ int index = 0;
+ for (int j = 0; j < sampleCountX; j++) {
+ float x = qMin(sampleMax, (j * stepX + sampleMin));
+ float R = qSqrt(z * z + x * x) + 0.01f;
+ float y = (qSin(R) / R + 0.24f) * 1.61f;
+ (*newRow)[index++].setPosition(QVector3D(x, y, z));
+ }
+ *dataArray << newRow;
+ }
+
+ m_sqrtSinProxy->resetArray(dataArray);
+ //! [1]
+}
+
+void SurfaceGraphModifier::enableSqrtSinModel(bool enable)
+{
+ if (enable) {
+ //! [3]
+ m_sqrtSinSeries->setDrawMode(QSurface3DSeries::DrawSurfaceAndWireframe);
+ m_sqrtSinSeries->setFlatShadingEnabled(true);
+
+ m_graph->axisX()->setLabelFormat("%.2f");
+ m_graph->axisZ()->setLabelFormat("%.2f");
+ m_graph->axisX()->setRange(sampleMin, sampleMax);
+ m_graph->axisY()->setRange(0.f, 2.f);
+ m_graph->axisZ()->setRange(sampleMin, sampleMax);
+ m_graph->axisX()->setLabelAutoRotation(30.f);
+ m_graph->axisY()->setLabelAutoRotation(90.f);
+ m_graph->axisZ()->setLabelAutoRotation(30.f);
+
+ m_graph->removeSeries(m_heightMapSeriesOne);
+ m_graph->removeSeries(m_heightMapSeriesTwo);
+ m_graph->removeSeries(m_heightMapSeriesThree);
+ m_graph->removeSeries(m_topography);
+ m_graph->removeSeries(m_highlight);
+
+ m_graph->addSeries(m_sqrtSinSeries);
+ //! [3]
+
+ m_titleLabel->setVisible(false);
+ m_graph->axisX()->setTitleVisible(false);
+ m_graph->axisY()->setTitleVisible(false);
+ m_graph->axisZ()->setTitleVisible(false);
+
+ m_graph->axisX()->setTitle({});
+ m_graph->axisY()->setTitle({});
+ m_graph->axisZ()->setTitle({});
+
+ // TODO: API missing (QTBUG-111611)
+// m_graph->setActiveInputHandler(m_defaultInputHandler);
+
+ //! [6]
+ // Reset range sliders for Sqrt & Sin
+ m_rangeMinX = sampleMin;
+ m_rangeMinZ = sampleMin;
+ m_stepX = (sampleMax - sampleMin) / float(sampleCountX - 1);
+ m_stepZ = (sampleMax - sampleMin) / float(sampleCountZ - 1);
+ m_axisMinSliderX->setMinimum(0);
+ m_axisMinSliderX->setMaximum(sampleCountX - 2);
+ m_axisMinSliderX->setValue(0);
+ m_axisMaxSliderX->setMinimum(1);
+ m_axisMaxSliderX->setMaximum(sampleCountX - 1);
+ m_axisMaxSliderX->setValue(sampleCountX - 1);
+ m_axisMinSliderZ->setMinimum(0);
+ m_axisMinSliderZ->setMaximum(sampleCountZ - 2);
+ m_axisMinSliderZ->setValue(0);
+ m_axisMaxSliderZ->setMinimum(1);
+ m_axisMaxSliderZ->setMaximum(sampleCountZ - 1);
+ m_axisMaxSliderZ->setValue(sampleCountZ - 1);
+ //! [6]
+ }
+}
+
+void SurfaceGraphModifier::enableHeightMapModel(bool enable)
+{
+ if (enable) {
+ m_heightMapSeriesOne->setDrawMode(QSurface3DSeries::DrawSurface);
+ m_heightMapSeriesOne->setFlatShadingEnabled(false);
+ m_heightMapSeriesTwo->setDrawMode(QSurface3DSeries::DrawSurface);
+ m_heightMapSeriesTwo->setFlatShadingEnabled(false);
+ m_heightMapSeriesThree->setDrawMode(QSurface3DSeries::DrawSurface);
+ m_heightMapSeriesThree->setFlatShadingEnabled(false);
+
+ m_graph->axisX()->setLabelFormat("%.1f N");
+ m_graph->axisZ()->setLabelFormat("%.1f E");
+ m_graph->axisX()->setRange(34.f, 40.f);
+ //! [4]
+ m_graph->axisY()->setAutoAdjustRange(true);
+ //! [4]
+ m_graph->axisZ()->setRange(18.f, 24.f);
+
+ m_graph->axisX()->setTitle(u"Latitude"_s);
+ m_graph->axisY()->setTitle(u"Height"_s);
+ m_graph->axisZ()->setTitle(u"Longitude"_s);
+
+ m_graph->removeSeries(m_sqrtSinSeries);
+ m_graph->removeSeries(m_topography);
+ m_graph->removeSeries(m_highlight);
+ m_graph->addSeries(m_heightMapSeriesOne);
+ m_graph->addSeries(m_heightMapSeriesTwo);
+ m_graph->addSeries(m_heightMapSeriesThree);
+
+ // TODO: API missing (QTBUG-111611)
+// m_graph->setActiveInputHandler(m_defaultInputHandler);
+
+ m_titleLabel->setVisible(true);
+ m_graph->axisX()->setTitleVisible(true);
+ m_graph->axisY()->setTitleVisible(true);
+ m_graph->axisZ()->setTitleVisible(true);
+
+ // Reset range sliders for height map
+ int mapGridCountX = m_heightMapWidth / heightMapGridStepX;
+ int mapGridCountZ = m_heightMapHeight / heightMapGridStepZ;
+ m_rangeMinX = 34.f;
+ m_rangeMinZ = 18.f;
+ m_stepX = 6.f / float(mapGridCountX - 1);
+ m_stepZ = 6.f / float(mapGridCountZ - 1);
+ m_axisMinSliderX->setMinimum(0);
+ m_axisMinSliderX->setMaximum(mapGridCountX - 2);
+ m_axisMinSliderX->setValue(0);
+ m_axisMaxSliderX->setMinimum(1);
+ m_axisMaxSliderX->setMaximum(mapGridCountX - 1);
+ m_axisMaxSliderX->setValue(mapGridCountX - 1);
+ m_axisMinSliderZ->setMinimum(0);
+ m_axisMinSliderZ->setMaximum(mapGridCountZ - 2);
+ m_axisMinSliderZ->setValue(0);
+ m_axisMaxSliderZ->setMinimum(1);
+ m_axisMaxSliderZ->setMaximum(mapGridCountZ - 1);
+ m_axisMaxSliderZ->setValue(mapGridCountZ - 1);
+ }
+}
+
+void SurfaceGraphModifier::enableTopographyModel(bool enable)
+{
+ if (enable) {
+ m_graph->axisX()->setLabelFormat("%i");
+ m_graph->axisZ()->setLabelFormat("%i");
+ m_graph->axisX()->setRange(0.f, areaWidth);
+ m_graph->axisY()->setRange(100.f, areaWidth * aspectRatio);
+ m_graph->axisZ()->setRange(0.f, areaHeight);
+ m_graph->axisX()->setLabelAutoRotation(30.f);
+ m_graph->axisY()->setLabelAutoRotation(90.f);
+ m_graph->axisZ()->setLabelAutoRotation(30.f);
+
+ m_graph->removeSeries(m_heightMapSeriesOne);
+ m_graph->removeSeries(m_heightMapSeriesTwo);
+ m_graph->removeSeries(m_heightMapSeriesThree);
+ m_graph->addSeries(m_topography);
+ m_graph->addSeries(m_highlight);
+
+ m_titleLabel->setVisible(false);
+ m_graph->axisX()->setTitleVisible(false);
+ m_graph->axisY()->setTitleVisible(false);
+ m_graph->axisZ()->setTitleVisible(false);
+
+ m_graph->axisX()->setTitle({});
+ m_graph->axisY()->setTitle({});
+ m_graph->axisZ()->setTitle({});
+
+ //! [5]
+ // TODO: API missing (QTBUG-111611)
+// m_graph->setActiveInputHandler(m_customInputHandler);
+ //! [5]
+
+ // Reset range sliders for topography map
+ m_rangeMinX = 0.f;
+ m_rangeMinZ = 0.f;
+ m_stepX = 1.f;
+ m_stepZ = 1.f;
+ m_axisMinSliderX->setMinimum(0);
+ m_axisMinSliderX->setMaximum(areaWidth - 200);
+ m_axisMinSliderX->setValue(0);
+ m_axisMaxSliderX->setMinimum(200);
+ m_axisMaxSliderX->setMaximum(areaWidth);
+ m_axisMaxSliderX->setValue(areaWidth);
+ m_axisMinSliderZ->setMinimum(0);
+ m_axisMinSliderZ->setMaximum(areaHeight - 200);
+ m_axisMinSliderZ->setValue(0);
+ m_axisMaxSliderZ->setMinimum(200);
+ m_axisMaxSliderZ->setMaximum(areaHeight);
+ m_axisMaxSliderZ->setValue(areaHeight);
+ }
+}
+
+void SurfaceGraphModifier::adjustXMin(int min)
+{
+ float minX = m_stepX * float(min) + m_rangeMinX;
+
+ int max = m_axisMaxSliderX->value();
+ if (min >= max) {
+ max = min + 1;
+ m_axisMaxSliderX->setValue(max);
+ }
+ float maxX = m_stepX * max + m_rangeMinX;
+
+ setAxisXRange(minX, maxX);
+}
+
+void SurfaceGraphModifier::adjustXMax(int max)
+{
+ float maxX = m_stepX * float(max) + m_rangeMinX;
+
+ int min = m_axisMinSliderX->value();
+ if (max <= min) {
+ min = max - 1;
+ m_axisMinSliderX->setValue(min);
+ }
+ float minX = m_stepX * min + m_rangeMinX;
+
+ setAxisXRange(minX, maxX);
+}
+
+void SurfaceGraphModifier::adjustZMin(int min)
+{
+ float minZ = m_stepZ * float(min) + m_rangeMinZ;
+
+ int max = m_axisMaxSliderZ->value();
+ if (min >= max) {
+ max = min + 1;
+ m_axisMaxSliderZ->setValue(max);
+ }
+ float maxZ = m_stepZ * max + m_rangeMinZ;
+
+ setAxisZRange(minZ, maxZ);
+}
+
+void SurfaceGraphModifier::adjustZMax(int max)
+{
+ float maxX = m_stepZ * float(max) + m_rangeMinZ;
+
+ int min = m_axisMinSliderZ->value();
+ if (max <= min) {
+ min = max - 1;
+ m_axisMinSliderZ->setValue(min);
+ }
+ float minX = m_stepZ * min + m_rangeMinZ;
+
+ setAxisZRange(minX, maxX);
+}
+
+//! [7]
+void SurfaceGraphModifier::setAxisXRange(float min, float max)
+{
+ m_graph->axisX()->setRange(min, max);
+}
+//! [7]
+
+void SurfaceGraphModifier::setAxisZRange(float min, float max)
+{
+ m_graph->axisZ()->setRange(min, max);
+}
+
+void SurfaceGraphModifier::setBlackToYellowGradient()
+{
+ //! [8]
+ QLinearGradient gr;
+ gr.setColorAt(0.f, Qt::black);
+ gr.setColorAt(0.33f, Qt::blue);
+ gr.setColorAt(0.67f, Qt::red);
+ gr.setColorAt(1.f, Qt::yellow);
+
+ m_sqrtSinSeries->setBaseGradient(gr);
+ m_sqrtSinSeries->setColorStyle(Q3DTheme::ColorStyleRangeGradient);
+ //! [8]
+}
+
+void SurfaceGraphModifier::setGreenToRedGradient()
+{
+ QLinearGradient gr;
+ gr.setColorAt(0.f, Qt::darkGreen);
+ gr.setColorAt(0.5f, Qt::yellow);
+ gr.setColorAt(0.8f, Qt::red);
+ gr.setColorAt(1.f, Qt::darkRed);
+
+ m_sqrtSinSeries->setBaseGradient(gr);
+ m_sqrtSinSeries->setColorStyle(Q3DTheme::ColorStyleRangeGradient);
+}
+
+void SurfaceGraphModifier::toggleItemOne(bool show)
+{
+ //! [10]
+ QVector3D positionOne = QVector3D(39.f, 77.f, 19.2f);
+ //! [10]
+ QVector3D positionOnePipe = QVector3D(39.f, 45.f, 19.2f);
+ QVector3D positionOneLabel = QVector3D(39.f, 107.f, 19.2f);
+ if (show) {
+ //! [9]
+ QImage color = QImage(2, 2, QImage::Format_RGB32);
+ color.fill(Qt::red);
+ //! [9]
+ //! [11]
+ QCustom3DItem *item = new QCustom3DItem(":/data/oilrig.obj", positionOne,
+ QVector3D(0.025f, 0.025f, 0.025f),
+ QQuaternion::fromAxisAndAngle(0.f, 1.f, 0.f, 45.f),
+ color);
+ //! [11]
+ //! [12]
+ // TODO: API missing (QTBUG-111611)
+// m_graph->addCustomItem(item);
+ //! [12]
+ item = new QCustom3DItem(":/data/pipe.obj", positionOnePipe,
+ QVector3D(0.005f, 0.5f, 0.005f),
+ QQuaternion(),
+ color);
+ item->setShadowCasting(false);
+ // TODO: API missing (QTBUG-111611)
+// m_graph->addCustomItem(item);
+
+ //! [13]
+ QCustom3DLabel *label = new QCustom3DLabel();
+ label->setText("Oil Rig One");
+ label->setPosition(positionOneLabel);
+ label->setScaling(QVector3D(1.f, 1.f, 1.f));
+ // TODO: API missing (QTBUG-111611)
+// m_graph->addCustomItem(label);
+ //! [13]
+ } else {
+ resetSelection();
+ //! [14]
+ // TODO: API missing (QTBUG-111611)
+// m_graph->removeCustomItemAt(positionOne);
+ //! [14]
+ // TODO: API missing (QTBUG-111611)
+// m_graph->removeCustomItemAt(positionOnePipe);
+// m_graph->removeCustomItemAt(positionOneLabel);
+ }
+}
+
+void SurfaceGraphModifier::toggleItemTwo(bool show)
+{
+ QVector3D positionTwo = QVector3D(34.5f, 77.f, 23.4f);
+ QVector3D positionTwoPipe = QVector3D(34.5f, 45.f, 23.4f);
+ QVector3D positionTwoLabel = QVector3D(34.5f, 107.f, 23.4f);
+ if (show) {
+ QImage color = QImage(2, 2, QImage::Format_RGB32);
+ color.fill(Qt::red);
+ QCustom3DItem *item = new QCustom3DItem();
+ item->setMeshFile(":/data/oilrig.obj");
+ item->setPosition(positionTwo);
+ item->setScaling(QVector3D(0.025f, 0.025f, 0.025f));
+ item->setRotation(QQuaternion::fromAxisAndAngle(0.f, 1.f, 0.f, 25.f));
+ item->setTextureImage(color);
+ // TODO: API missing (QTBUG-111611)
+// m_graph->addCustomItem(item);
+ item = new QCustom3DItem(":/data/pipe.obj", positionTwoPipe,
+ QVector3D(0.005f, 0.5f, 0.005f),
+ QQuaternion(),
+ color);
+ item->setShadowCasting(false);
+ // TODO: API missing (QTBUG-111611)
+// m_graph->addCustomItem(item);
+
+ QCustom3DLabel *label = new QCustom3DLabel();
+ label->setText("Oil Rig Two");
+ label->setPosition(positionTwoLabel);
+ label->setScaling(QVector3D(1.f, 1.f, 1.f));
+ // TODO: API missing (QTBUG-111611)
+// m_graph->addCustomItem(label);
+ } else {
+ resetSelection();
+ // TODO: API missing (QTBUG-111611)
+// m_graph->removeCustomItemAt(positionTwo);
+// m_graph->removeCustomItemAt(positionTwoPipe);
+// m_graph->removeCustomItemAt(positionTwoLabel);
+ }
+}
+
+void SurfaceGraphModifier::toggleItemThree(bool show)
+{
+ QVector3D positionThree = QVector3D(34.5f, 86.f, 19.1f);
+ QVector3D positionThreeLabel = QVector3D(34.5f, 116.f, 19.1f);
+ if (show) {
+ QImage color = QImage(2, 2, QImage::Format_RGB32);
+ color.fill(Qt::darkMagenta);
+ QCustom3DItem *item = new QCustom3DItem();
+ item->setMeshFile(":/data/refinery.obj");
+ item->setPosition(positionThree);
+ item->setScaling(QVector3D(0.04f, 0.04f, 0.04f));
+ item->setRotation(QQuaternion::fromAxisAndAngle(0.f, 1.f, 0.f, 75.f));
+ item->setTextureImage(color);
+ // TODO: API missing (QTBUG-111611)
+// m_graph->addCustomItem(item);
+
+ QCustom3DLabel *label = new QCustom3DLabel();
+ label->setText("Refinery");
+ label->setPosition(positionThreeLabel);
+ label->setScaling(QVector3D(1.f, 1.f, 1.f));
+ // TODO: API missing (QTBUG-111611)
+// m_graph->addCustomItem(label);
+ } else {
+ resetSelection();
+ // TODO: API missing (QTBUG-111611)
+// m_graph->removeCustomItemAt(positionThree);
+// m_graph->removeCustomItemAt(positionThreeLabel);
+ }
+}
+
+void SurfaceGraphModifier::toggleSeeThrough(bool seethrough)
+{
+ if (seethrough) {
+ m_graph->seriesList().at(0)->setDrawMode(QSurface3DSeries::DrawWireframe);
+ m_graph->seriesList().at(1)->setDrawMode(QSurface3DSeries::DrawWireframe);
+ } else {
+ m_graph->seriesList().at(0)->setDrawMode(QSurface3DSeries::DrawSurface);
+ m_graph->seriesList().at(1)->setDrawMode(QSurface3DSeries::DrawSurface);
+ }
+}
+
+void SurfaceGraphModifier::toggleOilHighlight(bool highlight)
+{
+ if (highlight) {
+ QLinearGradient grThree;
+ grThree.setColorAt(0.0, Qt::black);
+ grThree.setColorAt(0.05, Qt::red);
+ m_graph->seriesList().at(2)->setBaseGradient(grThree);
+ } else {
+ QLinearGradient grThree;
+ grThree.setColorAt(0.0, Qt::white);
+ grThree.setColorAt(0.05, Qt::black);
+ m_graph->seriesList().at(2)->setBaseGradient(grThree);
+ }
+}
+
+void SurfaceGraphModifier::toggleShadows(bool shadows)
+{
+ if (shadows)
+ m_graph->setShadowQuality(QAbstract3DGraph::ShadowQualityMedium);
+ else
+ m_graph->setShadowQuality(QAbstract3DGraph::ShadowQualityNone);
+}
+
+//! [15]
+void SurfaceGraphModifier::toggleSurfaceTexture(bool enable)
+{
+ if (enable)
+ m_topography->setTextureFile(":/data/maptexture.jpg");
+ else
+ m_topography->setTextureFile("");
+}
+//! [15]
+
+void SurfaceGraphModifier::handleElementSelected(QAbstract3DGraph::ElementType type)
+{
+ resetSelection();
+ if (type == QAbstract3DGraph::ElementCustomItem) {
+ QCustom3DItem *item;// = m_graph->selectedCustomItem(); // TODO: API missing (QTBUG-111611)
+ QString text;
+ if (qobject_cast<QCustom3DLabel *>(item) != 0) {
+ text.append("Custom label: ");
+ } else {
+ QStringList split = item->meshFile().split("/");
+ text.append(split.last());
+ text.append(": ");
+ }
+ int index;// = m_graph->selectedCustomItemIndex(); // TODO: API missing (QTBUG-111611)
+ text.append(QString::number(index));
+ m_textField->setText(text);
+ m_previouslyAnimatedItem = item;
+ m_previousScaling = item->scaling();
+ m_selectionAnimation->setTargetObject(item);
+ m_selectionAnimation->setStartValue(item->scaling());
+ m_selectionAnimation->setEndValue(item->scaling() * 1.5f);
+ m_selectionAnimation->start();
+ } else if (type == QAbstract3DGraph::ElementSeries) {
+ QString text = "Surface (";
+ QSurface3DSeries *series = m_graph->selectedSeries();
+ if (series) {
+ QPoint point = series->selectedPoint();
+ QString posStr;
+ posStr.setNum(point.x());
+ text.append(posStr);
+ text.append(", ");
+ posStr.setNum(point.y());
+ text.append(posStr);
+ }
+ text.append(")");
+ m_textField->setText(text);
+ } else if (type > QAbstract3DGraph::ElementSeries
+ && type < QAbstract3DGraph::ElementCustomItem) {
+ int index;// = m_graph->selectedLabelIndex(); // TODO: API missing (QTBUG-111611)
+ QString text;
+ if (type == QAbstract3DGraph::ElementAxisXLabel)
+ text.append("Axis X label: ");
+ else if (type == QAbstract3DGraph::ElementAxisYLabel)
+ text.append("Axis Y label: ");
+ else
+ text.append("Axis Z label: ");
+ text.append(QString::number(index));
+ m_textField->setText(text);
+ } else {
+ m_textField->setText("Nothing");
+ }
+}
+
+void SurfaceGraphModifier::resetSelection()
+{
+ m_selectionAnimation->stop();
+ if (m_previouslyAnimatedItem)
+ m_previouslyAnimatedItem->setScaling(m_previousScaling);
+ m_previouslyAnimatedItem = nullptr;
+}
diff --git a/examples/graphs/graphgallery/surfacegraphmodifier.h b/examples/graphs/graphgallery/surfacegraphmodifier.h
new file mode 100644
index 0000000..d05e5fd
--- /dev/null
+++ b/examples/graphs/graphgallery/surfacegraphmodifier.h
@@ -0,0 +1,110 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef SURFACEGRAPHMODIFIER_H
+#define SURFACEGRAPHMODIFIER_H
+
+#include <QtGraphs/q3dsurface.h>
+#include <QtGraphs/qsurfacedataproxy.h>
+#include <QtGraphs/qheightmapsurfacedataproxy.h>
+#include <QtGraphs/qsurface3dseries.h>
+#include <QtGraphs/qcustom3ditem.h>
+#include <QtGraphs/qcustom3dlabel.h>
+#include <QtGraphs/q3dinputhandler.h>
+#include <QtWidgets/qslider.h>
+#include <QtWidgets/qlabel.h>
+#include <QtCore/qpropertyanimation.h>
+
+class TopographicSeries;
+class HighlightSeries;
+class CustomInputHandler;
+
+class SurfaceGraphModifier : public QObject
+{
+ Q_OBJECT
+public:
+ explicit SurfaceGraphModifier(Q3DSurface *surface, QLabel *label);
+ ~SurfaceGraphModifier();
+
+ //! [0]
+ void toggleModeNone() { m_graph->setSelectionMode(QAbstract3DGraph::SelectionNone); }
+ void toggleModeItem() { m_graph->setSelectionMode(QAbstract3DGraph::SelectionItem); }
+ void toggleModeSliceRow() { m_graph->setSelectionMode(QAbstract3DGraph::SelectionItemAndRow
+ | QAbstract3DGraph::SelectionSlice
+ | QAbstract3DGraph::SelectionMultiSeries); }
+ void toggleModeSliceColumn() { m_graph->setSelectionMode(QAbstract3DGraph::SelectionItemAndColumn
+ | QAbstract3DGraph::SelectionSlice
+ | QAbstract3DGraph::SelectionMultiSeries); }
+ //! [0]
+
+ void setBlackToYellowGradient();
+ void setGreenToRedGradient();
+
+ void setAxisMinSliderX(QSlider *slider) { m_axisMinSliderX = slider; }
+ void setAxisMaxSliderX(QSlider *slider) { m_axisMaxSliderX = slider; }
+ void setAxisMinSliderZ(QSlider *slider) { m_axisMinSliderZ = slider; }
+ void setAxisMaxSliderZ(QSlider *slider) { m_axisMaxSliderZ = slider; }
+
+ void adjustXMin(int min);
+ void adjustXMax(int max);
+ void adjustZMin(int min);
+ void adjustZMax(int max);
+
+public Q_SLOTS:
+ void enableSqrtSinModel(bool enable);
+ void enableHeightMapModel(bool enable);
+ void enableTopographyModel(bool enable);
+
+ void toggleItemOne(bool show);
+ void toggleItemTwo(bool show);
+ void toggleItemThree(bool show);
+ void toggleSeeThrough(bool seethrough);
+ void toggleOilHighlight(bool highlight);
+ void toggleShadows(bool shadows);
+ void toggleSurfaceTexture(bool enable);
+
+private:
+ void setAxisXRange(float min, float max);
+ void setAxisZRange(float min, float max);
+ void fillSqrtSinProxy();
+ void handleElementSelected(QAbstract3DGraph::ElementType type);
+ void resetSelection();
+
+private:
+ Q3DSurface *m_graph = nullptr;
+ QSurfaceDataProxy *m_sqrtSinProxy = nullptr;
+ QSurface3DSeries *m_sqrtSinSeries = nullptr;
+ QHeightMapSurfaceDataProxy *m_heightMapProxyOne = nullptr;
+ QHeightMapSurfaceDataProxy *m_heightMapProxyTwo = nullptr;
+ QHeightMapSurfaceDataProxy *m_heightMapProxyThree = nullptr;
+ QSurface3DSeries *m_heightMapSeriesOne = nullptr;
+ QSurface3DSeries *m_heightMapSeriesTwo = nullptr;
+ QSurface3DSeries *m_heightMapSeriesThree = nullptr;
+
+ QSlider *m_axisMinSliderX = nullptr;
+ QSlider *m_axisMaxSliderX = nullptr;
+ QSlider *m_axisMinSliderZ = nullptr;
+ QSlider *m_axisMaxSliderZ = nullptr;
+ float m_rangeMinX = 0.f;
+ float m_rangeMinZ = 0.f;
+ float m_stepX = 0.f;
+ float m_stepZ = 0.f;
+ int m_heightMapWidth = 0;
+ int m_heightMapHeight = 0;
+
+ QLabel *m_textField = nullptr;
+ QPropertyAnimation *m_selectionAnimation = nullptr;
+ QCustom3DLabel *m_titleLabel = nullptr;
+ QCustom3DItem *m_previouslyAnimatedItem = nullptr;
+ QVector3D m_previousScaling = {};
+
+ TopographicSeries *m_topography = nullptr;
+ HighlightSeries *m_highlight = nullptr;
+ int m_highlightWidth = 0;
+ int m_highlightHeight = 0;
+
+ CustomInputHandler *m_customInputHandler = nullptr;
+ Q3DInputHandler *m_defaultInputHandler = new Q3DInputHandler();
+};
+
+#endif // SURFACEGRAPHMODIFIER_H
diff --git a/examples/graphs/graphgallery/topographicseries.cpp b/examples/graphs/graphgallery/topographicseries.cpp
new file mode 100644
index 0000000..ea0be77
--- /dev/null
+++ b/examples/graphs/graphgallery/topographicseries.cpp
@@ -0,0 +1,54 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "topographicseries.h"
+
+//! [0]
+// Value used to encode height data as RGB value on PNG file
+const float packingFactor = 11983.f;
+//! [0]
+
+TopographicSeries::TopographicSeries()
+{
+ setDrawMode(QSurface3DSeries::DrawSurface);
+ setFlatShadingEnabled(true);
+ setBaseColor(Qt::white);
+}
+
+TopographicSeries::~TopographicSeries() = default;
+
+void TopographicSeries::setTopographyFile(const QString file, float width, float height)
+{
+ //! [1]
+ QImage heightMapImage(file);
+ uchar *bits = heightMapImage.bits();
+ int imageHeight = heightMapImage.height();
+ int imageWidth = heightMapImage.width();
+ int widthBits = imageWidth * 4;
+ float stepX = width / float(imageWidth);
+ float stepZ = height / float(imageHeight);
+
+ QSurfaceDataArray *dataArray = new QSurfaceDataArray;
+ dataArray->reserve(imageHeight);
+ for (int i = 0; i < imageHeight; i++) {
+ int p = i * widthBits;
+ float z = height - float(i) * stepZ;
+ QSurfaceDataRow *newRow = new QSurfaceDataRow(imageWidth);
+ for (int j = 0; j < imageWidth; j++) {
+ uchar aa = bits[p + 0];
+ uchar rr = bits[p + 1];
+ uchar gg = bits[p + 2];
+ uint color = uint((gg << 16) + (rr << 8) + aa);
+ float y = float(color) / packingFactor;
+ (*newRow)[j].setPosition(QVector3D(float(j) * stepX, y, z));
+ p = p + 4;
+ }
+ *dataArray << newRow;
+ }
+
+ dataProxy()->resetArray(dataArray);
+ //! [1]
+
+ m_sampleCountX = float(imageWidth);
+ m_sampleCountZ = float(imageHeight);
+}
diff --git a/examples/graphs/graphgallery/topographicseries.h b/examples/graphs/graphgallery/topographicseries.h
new file mode 100644
index 0000000..c805fb3
--- /dev/null
+++ b/examples/graphs/graphgallery/topographicseries.h
@@ -0,0 +1,26 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef TOPOGRAPHICSERIES_H
+#define TOPOGRAPHICSERIES_H
+
+#include <QtGraphs/qsurface3dseries.h>
+
+class TopographicSeries : public QSurface3DSeries
+{
+ Q_OBJECT
+public:
+ TopographicSeries();
+ ~TopographicSeries();
+
+ void setTopographyFile(const QString file, float width, float height);
+
+ float sampleCountX() { return m_sampleCountX; }
+ float sampleCountZ() { return m_sampleCountZ; }
+
+private:
+ float m_sampleCountX = 0.f;
+ float m_sampleCountZ = 0.f;
+};
+
+#endif // TOPOGRAPHICSERIES_H
diff --git a/examples/graphs/graphgallery/variantbardatamapping.cpp b/examples/graphs/graphgallery/variantbardatamapping.cpp
new file mode 100644
index 0000000..acac28c
--- /dev/null
+++ b/examples/graphs/graphgallery/variantbardatamapping.cpp
@@ -0,0 +1,109 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "variantbardatamapping.h"
+
+VariantBarDataMapping::VariantBarDataMapping() = default;
+
+VariantBarDataMapping::VariantBarDataMapping(const VariantBarDataMapping &other) :
+ QObject(nullptr),
+ m_rowIndex(other.m_rowIndex),
+ m_columnIndex(other.m_columnIndex),
+ m_valueIndex(other.m_valueIndex),
+ m_rowCategories(other.m_rowCategories),
+ m_columnCategories(other.m_columnCategories)
+{
+}
+
+VariantBarDataMapping::VariantBarDataMapping(int rowIndex, int columnIndex, int valueIndex,
+ const QStringList &rowCategories,
+ const QStringList &columnCategories)
+ : QObject(nullptr)
+{
+ m_rowIndex = rowIndex;
+ m_columnIndex = columnIndex;
+ m_valueIndex = valueIndex;
+ m_rowCategories = rowCategories;
+ m_columnCategories = columnCategories;
+}
+
+VariantBarDataMapping::~VariantBarDataMapping() = default;
+
+VariantBarDataMapping &VariantBarDataMapping::operator=(const VariantBarDataMapping &other)
+{
+ if (this != &other) {
+ m_rowIndex = other.m_rowIndex;
+ m_columnIndex = other.m_columnIndex;
+ m_valueIndex = other.m_valueIndex;
+ m_rowCategories = other.m_rowCategories;
+ m_columnCategories = other.m_columnCategories;
+ }
+ return *this;
+}
+
+void VariantBarDataMapping::setRowIndex(int index)
+{
+ m_rowIndex = index;
+ emit mappingChanged();
+}
+
+int VariantBarDataMapping::rowIndex() const
+{
+ return m_rowIndex;
+}
+
+void VariantBarDataMapping::setColumnIndex(int index)
+{
+ m_columnIndex = index;
+ emit mappingChanged();
+}
+
+int VariantBarDataMapping::columnIndex() const
+{
+ return m_columnIndex;
+}
+
+void VariantBarDataMapping::setValueIndex(int index)
+{
+ m_valueIndex = index;
+ emit mappingChanged();
+}
+
+int VariantBarDataMapping::valueIndex() const
+{
+ return m_valueIndex;
+}
+
+void VariantBarDataMapping::setRowCategories(const QStringList &categories)
+{
+ m_rowCategories = categories;
+ emit mappingChanged();
+}
+
+const QStringList &VariantBarDataMapping::rowCategories() const
+{
+ return m_rowCategories;
+}
+
+void VariantBarDataMapping::setColumnCategories(const QStringList &categories)
+{
+ m_columnCategories = categories;
+ emit mappingChanged();
+}
+
+const QStringList &VariantBarDataMapping::columnCategories() const
+{
+ return m_columnCategories;
+}
+
+void VariantBarDataMapping::remap(int rowIndex, int columnIndex, int valueIndex,
+ const QStringList &rowCategories,
+ const QStringList &columnCategories)
+{
+ m_rowIndex = rowIndex;
+ m_columnIndex = columnIndex;
+ m_valueIndex = valueIndex;
+ m_rowCategories = rowCategories;
+ m_columnCategories = columnCategories;
+ emit mappingChanged();
+}
diff --git a/examples/graphs/graphgallery/variantbardatamapping.h b/examples/graphs/graphgallery/variantbardatamapping.h
new file mode 100644
index 0000000..bd4749a
--- /dev/null
+++ b/examples/graphs/graphgallery/variantbardatamapping.h
@@ -0,0 +1,70 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef VARIANTBARDATAMAPPING_H
+#define VARIANTBARDATAMAPPING_H
+
+#include <QtCore/qobject.h>
+#include <QtCore/qstringlist.h>
+
+class VariantBarDataMapping : public QObject
+{
+ Q_OBJECT
+ //! [0]
+ Q_PROPERTY(int rowIndex READ rowIndex WRITE setRowIndex NOTIFY rowIndexChanged)
+ Q_PROPERTY(int columnIndex READ columnIndex WRITE setColumnIndex NOTIFY columnIndexChanged)
+ Q_PROPERTY(int valueIndex READ valueIndex WRITE setValueIndex NOTIFY valueIndexChanged)
+ Q_PROPERTY(QStringList rowCategories READ rowCategories WRITE setRowCategories NOTIFY rowCategoriesChanged)
+ Q_PROPERTY(QStringList columnCategories READ columnCategories WRITE setColumnCategories NOTIFY columnCategoriesChanged)
+ //! [0]
+public:
+ explicit VariantBarDataMapping();
+ VariantBarDataMapping(const VariantBarDataMapping &other);
+ //! [1]
+ VariantBarDataMapping(int rowIndex, int columnIndex, int valueIndex,
+ const QStringList &rowCategories,
+ const QStringList &columnCategories);
+ //! [1]
+ virtual ~VariantBarDataMapping();
+
+ VariantBarDataMapping &operator=(const VariantBarDataMapping &other);
+
+ void setRowIndex(int index);
+ int rowIndex() const;
+ void setColumnIndex(int index);
+ int columnIndex() const;
+ void setValueIndex(int index);
+ int valueIndex() const;
+
+ void setRowCategories(const QStringList &categories);
+ const QStringList &rowCategories() const;
+ void setColumnCategories(const QStringList &categories);
+ const QStringList &columnCategories() const;
+
+ //! [2]
+ void remap(int rowIndex, int columnIndex, int valueIndex,
+ const QStringList &rowCategories,
+ const QStringList &columnCategories);
+ //! [2]
+Q_SIGNALS:
+ void rowIndexChanged();
+ void columnIndexChanged();
+ void valueIndexChanged();
+ void rowCategoriesChanged();
+ void columnCategoriesChanged();
+ //! [3]
+ void mappingChanged();
+ //! [3]
+
+private:
+ // Indexes of the mapped items in the VariantDataItem
+ int m_rowIndex = 0;
+ int m_columnIndex = 1;
+ int m_valueIndex = 2;
+
+ // For row/column items, sort items into these categories. Other categories are ignored.
+ QStringList m_rowCategories = {};
+ QStringList m_columnCategories = {};
+};
+
+#endif
diff --git a/examples/graphs/graphgallery/variantbardataproxy.cpp b/examples/graphs/graphgallery/variantbardataproxy.cpp
new file mode 100644
index 0000000..3bc3ec1
--- /dev/null
+++ b/examples/graphs/graphgallery/variantbardataproxy.cpp
@@ -0,0 +1,122 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "variantbardataproxy.h"
+
+VariantBarDataProxy::VariantBarDataProxy() = default;
+
+VariantBarDataProxy::VariantBarDataProxy(VariantDataSet *newSet,
+ VariantBarDataMapping *mapping) :
+ QBarDataProxy()
+{
+ setDataSet(newSet);
+ setMapping(mapping);
+}
+
+VariantBarDataProxy::~VariantBarDataProxy()
+{
+ delete m_dataSet;
+}
+
+void VariantBarDataProxy::setDataSet(VariantDataSet *newSet)
+{
+ if (!m_dataSet.isNull())
+ QObject::disconnect(m_dataSet.data(), nullptr, this, nullptr);
+
+ m_dataSet = newSet;
+
+ if (!m_dataSet.isNull()) {
+ QObject::connect(m_dataSet.data(), &VariantDataSet::itemsAdded, this,
+ &VariantBarDataProxy::handleItemsAdded);
+ QObject::connect(m_dataSet.data(), &VariantDataSet::dataCleared, this,
+ &VariantBarDataProxy::handleDataCleared);
+ }
+ resolveDataSet();
+}
+
+VariantDataSet *VariantBarDataProxy::dataSet()
+{
+ return m_dataSet.data();
+}
+
+void VariantBarDataProxy::setMapping(VariantBarDataMapping *mapping)
+{
+ if (!m_mapping.isNull()) {
+ QObject::disconnect(m_mapping.data(), &VariantBarDataMapping::mappingChanged, this,
+ &VariantBarDataProxy::handleMappingChanged);
+ }
+
+ m_mapping = mapping;
+
+ if (!m_mapping.isNull()) {
+ QObject::connect(m_mapping.data(), &VariantBarDataMapping::mappingChanged, this,
+ &VariantBarDataProxy::handleMappingChanged);
+ }
+
+ resolveDataSet();
+}
+
+VariantBarDataMapping *VariantBarDataProxy::mapping()
+{
+ return m_mapping.data();
+}
+
+void VariantBarDataProxy::handleItemsAdded(int index, int count)
+{
+ Q_UNUSED(index);
+ Q_UNUSED(count);
+
+ // Resolve new items
+ resolveDataSet();
+}
+
+void VariantBarDataProxy::handleDataCleared()
+{
+ // Data cleared, reset array
+ resetArray(nullptr);
+}
+
+void VariantBarDataProxy::handleMappingChanged()
+{
+ resolveDataSet();
+}
+
+// Resolve entire dataset into QBarDataArray.
+//! [0]
+void VariantBarDataProxy::resolveDataSet()
+{
+ // If we have no data or mapping, or the categories are not defined, simply clear the array
+ if (m_dataSet.isNull() || m_mapping.isNull() || !m_mapping->rowCategories().size()
+ || !m_mapping->columnCategories().size()) {
+ resetArray(nullptr);
+ return;
+ }
+ const VariantDataItemList &itemList = m_dataSet->itemList();
+
+ int rowIndex = m_mapping->rowIndex();
+ int columnIndex = m_mapping->columnIndex();
+ int valueIndex = m_mapping->valueIndex();
+ const QStringList &rowList = m_mapping->rowCategories();
+ const QStringList &columnList = m_mapping->columnCategories();
+
+ // Sort values into rows and columns
+ using ColumnValueMap = QHash<QString, float>;
+ QHash <QString, ColumnValueMap> itemValueMap;
+ for (const VariantDataItem *item : itemList) {
+ itemValueMap[item->at(rowIndex).toString()][item->at(columnIndex).toString()]
+ = item->at(valueIndex).toReal();
+ }
+
+ // Create a new data array in format the parent class understands
+ QBarDataArray *newProxyArray = new QBarDataArray;
+ for (const QString &rowKey : rowList) {
+ QBarDataRow *newProxyRow = new QBarDataRow(columnList.size());
+ for (qsizetype i = 0; i < columnList.size(); ++i)
+ (*newProxyRow)[i].setValue(itemValueMap[rowKey][columnList.at(i)]);
+ newProxyArray->append(newProxyRow);
+ }
+
+ // Finally, reset the data array in the parent class
+ resetArray(newProxyArray);
+}
+//! [0]
diff --git a/examples/graphs/graphgallery/variantbardataproxy.h b/examples/graphs/graphgallery/variantbardataproxy.h
new file mode 100644
index 0000000..c22fcdc
--- /dev/null
+++ b/examples/graphs/graphgallery/variantbardataproxy.h
@@ -0,0 +1,49 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef VARIANTBARDATAPROXY_H
+#define VARIANTBARDATAPROXY_H
+
+#include "variantdataset.h"
+#include "variantbardatamapping.h"
+#include <QtGraphs/qbardataproxy.h>
+#include <QtCore/qpointer.h>
+
+//! [0]
+class VariantBarDataProxy : public QBarDataProxy
+//! [0]
+{
+ Q_OBJECT
+
+public:
+ explicit VariantBarDataProxy();
+ explicit VariantBarDataProxy(VariantDataSet *newSet, VariantBarDataMapping *mapping);
+ virtual ~VariantBarDataProxy();
+
+ //! [1]
+ // Doesn't gain ownership of the dataset, but does connect to it to listen for data changes.
+ void setDataSet(VariantDataSet *newSet);
+ VariantDataSet *dataSet();
+
+ // 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.
+ void setMapping(VariantBarDataMapping *mapping);
+ VariantBarDataMapping *mapping();
+ //! [1]
+
+public Q_SLOTS:
+ void handleItemsAdded(int index, int count);
+ void handleDataCleared();
+ void handleMappingChanged();
+
+private:
+ void resolveDataSet();
+
+ QPointer<VariantDataSet> m_dataSet;
+ QPointer<VariantBarDataMapping> m_mapping;
+
+ Q_DISABLE_COPY(VariantBarDataProxy)
+};
+
+#endif
diff --git a/examples/graphs/graphgallery/variantdataset.cpp b/examples/graphs/graphgallery/variantdataset.cpp
new file mode 100644
index 0000000..c0bfa9d
--- /dev/null
+++ b/examples/graphs/graphgallery/variantdataset.cpp
@@ -0,0 +1,45 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "variantdataset.h"
+
+VariantDataSet::VariantDataSet() = default;
+
+VariantDataSet::~VariantDataSet()
+{
+ clear();
+}
+
+void VariantDataSet::clear()
+{
+ for (VariantDataItem *item : m_variantData) {
+ item->clear();
+ delete item;
+ }
+ m_variantData.clear();
+ emit dataCleared();
+}
+
+int VariantDataSet::addItem(VariantDataItem *item)
+{
+ m_variantData.append(item);
+ int addIndex = m_variantData.size();
+
+ emit itemsAdded(addIndex, 1);
+ return addIndex;
+}
+
+int VariantDataSet::addItems(VariantDataItemList *itemList)
+{
+ int newCount = itemList->size();
+ int addIndex = m_variantData.size();
+ m_variantData.append(*itemList);
+ delete itemList;
+ emit itemsAdded(addIndex, newCount);
+ return addIndex;
+}
+
+const VariantDataItemList &VariantDataSet::itemList() const
+{
+ return m_variantData;
+}
diff --git a/examples/graphs/graphgallery/variantdataset.h b/examples/graphs/graphgallery/variantdataset.h
new file mode 100644
index 0000000..219e58c
--- /dev/null
+++ b/examples/graphs/graphgallery/variantdataset.h
@@ -0,0 +1,41 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef VARIANTDATASET_H
+#define VARIANTDATASET_H
+
+#include <QtCore/qvariantlist.h>
+
+//! [0]
+using VariantDataItem = QVariantList;
+using VariantDataItemList = QList<VariantDataItem *>;
+//! [0]
+
+class VariantDataSet : public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit VariantDataSet();
+ ~VariantDataSet();
+
+ //! [1]
+ void clear();
+
+ int addItem(VariantDataItem *item);
+ int addItems(VariantDataItemList *itemList);
+
+ const VariantDataItemList &itemList() const;
+
+Q_SIGNALS:
+ void itemsAdded(int index, int count);
+ void dataCleared();
+ //! [1]
+
+private:
+ VariantDataItemList m_variantData;
+
+ Q_DISABLE_COPY(VariantDataSet)
+};
+
+#endif
diff --git a/examples/graphs/graphs.pro b/examples/graphs/graphs.pro
new file mode 100644
index 0000000..bfe0d27
--- /dev/null
+++ b/examples/graphs/graphs.pro
@@ -0,0 +1,12 @@
+TEMPLATE = subdirs
+qtHaveModule(quick) {
+ SUBDIRS += qmlbars \
+ qmlscatter \
+ qmlaxishandling \
+ qmlsurfacegallery
+}
+
+!android:!ios:!winrt {
+# SUBDIRS += graphgallery \
+# volumetric
+}
diff --git a/examples/graphs/qmlaxishandling/CMakeLists.txt b/examples/graphs/qmlaxishandling/CMakeLists.txt
new file mode 100644
index 0000000..09c10f8
--- /dev/null
+++ b/examples/graphs/qmlaxishandling/CMakeLists.txt
@@ -0,0 +1,60 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+cmake_minimum_required(VERSION 3.16)
+project(qmlaxishandling LANGUAGES CXX)
+
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+set(CMAKE_AUTOMOC ON)
+set(CMAKE_AUTORCC ON)
+set(CMAKE_AUTOUIC ON)
+
+if(NOT DEFINED INSTALL_EXAMPLESDIR)
+ set(INSTALL_EXAMPLESDIR "examples")
+endif()
+
+set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}")
+
+find_package(Qt6 COMPONENTS Core)
+find_package(Qt6 COMPONENTS Gui)
+find_package(Qt6 COMPONENTS Qml)
+find_package(Qt6 COMPONENTS Quick)
+find_package(Qt6 COMPONENTS Graphs)
+
+qt_add_executable(qmlaxishandling
+ main.cpp
+)
+set_target_properties(qmlaxishandling PROPERTIES
+ WIN32_EXECUTABLE TRUE
+ MACOSX_BUNDLE TRUE
+)
+target_link_libraries(qmlaxishandling PUBLIC
+ Qt::Core
+ Qt::Gui
+ Qt::Qml
+ Qt::Quick
+ Qt::Graphs
+)
+
+qt6_add_qml_module(qmlaxishandling
+ URI AxisHandling
+ VERSION 1.0
+ NO_RESOURCE_TARGET_PATH
+ SOURCES
+ customformatter.cpp customformatter.h
+ QML_FILES
+ qml/qmlaxishandling/main.qml
+ qml/qmlaxishandling/Data.qml
+ qml/qmlaxishandling/AxisDragging.qml
+ qml/qmlaxishandling/AxisFormatting.qml
+ RESOURCES
+ qml/qmlaxishandling/cube.obj
+ qml/qmlaxishandling/cubetexture.png
+)
+
+install(TARGETS qmlaxishandling
+ RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
+ BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
+ LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
+)
diff --git a/examples/graphs/qmlaxishandling/customformatter.cpp b/examples/graphs/qmlaxishandling/customformatter.cpp
new file mode 100644
index 0000000..798802e
--- /dev/null
+++ b/examples/graphs/qmlaxishandling/customformatter.cpp
@@ -0,0 +1,138 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "customformatter.h"
+#include <QtGraphs/qvalue3daxis.h>
+#include <QtQml/qqmlextensionplugin.h>
+
+static const qreal oneDayMs = 60.0 * 60.0 * 24.0 * 1000.0;
+
+CustomFormatter::CustomFormatter(QObject *parent) :
+ QValue3DAxisFormatter(parent)
+{
+ qRegisterMetaType<QValue3DAxisFormatter *>();
+}
+
+CustomFormatter::~CustomFormatter()
+{
+}
+
+//! [1]
+QValue3DAxisFormatter *CustomFormatter::createNewInstance() const
+{
+ return new CustomFormatter();
+}
+
+void CustomFormatter::populateCopy(QValue3DAxisFormatter &copy) const
+{
+ QValue3DAxisFormatter::populateCopy(copy);
+
+ CustomFormatter *customFormatter = static_cast<CustomFormatter *>(&copy);
+ customFormatter->m_originDate = m_originDate;
+ customFormatter->m_selectionFormat = m_selectionFormat;
+}
+//! [1]
+
+//! [2]
+void CustomFormatter::recalculate()
+{
+ // We want our axis to always have gridlines at date breaks
+
+ // Convert range into QDateTimes
+ QDateTime minTime = valueToDateTime(qreal(axis()->min()));
+ QDateTime maxTime = valueToDateTime(qreal(axis()->max()));
+
+ // Find out the grid counts
+ QTime midnight(0, 0);
+ QDateTime minFullDate(minTime.date(), midnight);
+ int gridCount = 0;
+ if (minFullDate != minTime)
+ minFullDate = minFullDate.addDays(1);
+ QDateTime maxFullDate(maxTime.date(), midnight);
+
+ gridCount += minFullDate.daysTo(maxFullDate) + 1;
+ int subGridCount = axis()->subSegmentCount() - 1;
+
+ // Reserve space for position arrays and label strings
+ gridPositions().resize(gridCount);
+ subGridPositions().resize((gridCount + 1) * subGridCount);
+ labelPositions().resize(gridCount);
+ labelStrings().reserve(gridCount);
+
+ // Calculate positions and format labels
+ qint64 startMs = minTime.toMSecsSinceEpoch();
+ qint64 endMs = maxTime.toMSecsSinceEpoch();
+ qreal dateNormalizer = endMs - startMs;
+ qreal firstLineOffset = (minFullDate.toMSecsSinceEpoch() - startMs) / dateNormalizer;
+ qreal segmentStep = oneDayMs / dateNormalizer;
+ qreal subSegmentStep = 0;
+ if (subGridCount > 0)
+ subSegmentStep = segmentStep / qreal(subGridCount + 1);
+
+ for (int i = 0; i < gridCount; i++) {
+ qreal gridValue = firstLineOffset + (segmentStep * qreal(i));
+ gridPositions()[i] = float(gridValue);
+ labelPositions()[i] = float(gridValue);
+ labelStrings() << minFullDate.addDays(i).toString(axis()->labelFormat());
+ }
+
+ for (int i = 0; i <= gridCount; i++) {
+ if (subGridPositions().size()) {
+ for (int j = 0; j < subGridCount; j++) {
+ float position;
+ if (i)
+ position = gridPositions().at(i - 1) + subSegmentStep * (j + 1);
+ else
+ position = gridPositions().at(0) - segmentStep + subSegmentStep * (j + 1);
+ if (position > 1.0f || position < 0.0f)
+ position = gridPositions().at(0);
+ subGridPositions()[i * subGridCount + j] = position;
+ }
+ }
+ }
+}
+//! [2]
+
+//! [3]
+QString CustomFormatter::stringForValue(qreal value, const QString &format) const
+{
+ Q_UNUSED(format);
+
+ return valueToDateTime(value).toString(m_selectionFormat);
+}
+//! [3]
+
+QDate CustomFormatter::originDate() const
+{
+ return m_originDate;
+}
+
+QString CustomFormatter::selectionFormat() const
+{
+ return m_selectionFormat;
+}
+
+void CustomFormatter::setOriginDate(const QDate &date)
+{
+ if (m_originDate != date) {
+ m_originDate = date;
+ markDirty(true);
+ emit originDateChanged(date);
+ }
+}
+
+void CustomFormatter::setSelectionFormat(const QString &format)
+{
+ if (m_selectionFormat != format) {
+ m_selectionFormat = format;
+ markDirty(true); // Necessary to regenerate already visible selection label
+ emit selectionFormatChanged(format);
+ }
+}
+
+//! [0]
+QDateTime CustomFormatter::valueToDateTime(qreal value) const
+{
+ return m_originDate.startOfDay().addMSecs(qint64(oneDayMs * value));
+}
+//! [0]
diff --git a/examples/graphs/qmlaxishandling/customformatter.h b/examples/graphs/qmlaxishandling/customformatter.h
new file mode 100644
index 0000000..0728df8
--- /dev/null
+++ b/examples/graphs/qmlaxishandling/customformatter.h
@@ -0,0 +1,55 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef CUSTOMFORMATTER_H
+#define CUSTOMFORMATTER_H
+
+#include <QtGraphs/qvalue3daxisformatter.h>
+#include <QtCore/qdatetime.h>
+#include <QtQml/qqmlregistration.h>
+
+//! [2]
+class CustomFormatter : public QValue3DAxisFormatter
+{
+ //! [2]
+ Q_OBJECT
+ QML_ELEMENT
+
+ //! [1]
+ Q_PROPERTY(QDate originDate READ originDate WRITE setOriginDate NOTIFY originDateChanged)
+ //! [1]
+ //! [3]
+ Q_PROPERTY(QString selectionFormat READ selectionFormat WRITE setSelectionFormat NOTIFY selectionFormatChanged)
+ //! [3]
+public:
+ explicit CustomFormatter(QObject *parent = 0);
+ virtual ~CustomFormatter();
+
+ //! [0]
+ virtual QValue3DAxisFormatter *createNewInstance() const;
+ virtual void populateCopy(QValue3DAxisFormatter &copy) const;
+ virtual void recalculate();
+ virtual QString stringForValue(qreal value, const QString &format) const;
+ //! [0]
+
+ QDate originDate() const;
+ QString selectionFormat() const;
+
+public Q_SLOTS:
+ void setOriginDate(const QDate &date);
+ void setSelectionFormat(const QString &format);
+
+Q_SIGNALS:
+ void originDateChanged(const QDate &date);
+ void selectionFormatChanged(const QString &format);
+
+private:
+ Q_DISABLE_COPY(CustomFormatter)
+
+ QDateTime valueToDateTime(qreal value) const;
+
+ QDate m_originDate;
+ QString m_selectionFormat;
+};
+
+#endif
diff --git a/examples/graphs/qmlaxishandling/doc/images/qmlaxishandling-example.png b/examples/graphs/qmlaxishandling/doc/images/qmlaxishandling-example.png
new file mode 100644
index 0000000..21e9551
--- /dev/null
+++ b/examples/graphs/qmlaxishandling/doc/images/qmlaxishandling-example.png
Binary files differ
diff --git a/examples/graphs/qmlaxishandling/doc/src/qmlaxishandling.qdoc b/examples/graphs/qmlaxishandling/doc/src/qmlaxishandling.qdoc
new file mode 100644
index 0000000..e31ebcc
--- /dev/null
+++ b/examples/graphs/qmlaxishandling/doc/src/qmlaxishandling.qdoc
@@ -0,0 +1,189 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ \example qmlaxishandling
+ \meta tags {Graphs, Scatter3D, Custom Input Handler, Dynamic Data, Custom Axis Formatter, Scatter Graph}
+ \meta category {Graphics}
+ \title Axis Handling
+ \ingroup qtdatavisualization_qmlexamples
+ \brief Implementing axis dragging with a custom input handler in QML, and creating a custom axis formatter.
+ \since QtGraphs 6.5
+
+ \e {Axis Handling} demonstrates two different custom features with axes. The features have their
+ own tabs in the application.
+
+ The following sections concentrate on those features only and skip explaining the basic
+ functionality - for more detailed QML example documentation, see \l{Simple Scatter Graph}.
+
+ \image qmlaxishandling-example.png
+
+ \include examples-run.qdocinc
+
+ \section1 Axis Dragging
+
+ In the \uicontrol {Axis Dragging} tab, implement a custom input handler in QML that enables you
+ to drag axis labels to change axis ranges. Further, use orthographic projection and dynamically
+ update the properties of a custom item.
+
+ \section2 Overriding Default Input Handling
+
+ To deactivate the default input handling mechanism, set the active input handler of Scatter3D
+ graph to \c{null}:
+
+ \snippet qmlaxishandling/qml/qmlaxishandling/AxisDragging.qml 0
+ \dots
+
+ Then, add a MouseArea and set it to fill the parent, which is the same \c Item our
+ \c scatterGraph is contained in. Also, set it to accept only left mouse button presses,
+ as in this example the other buttons are not needed:
+
+ \snippet qmlaxishandling/qml/qmlaxishandling/AxisDragging.qml 1
+ \dots
+
+ Then, listen to mouse presses, and when caught, send a selection query to the graph:
+
+ \snippet qmlaxishandling/qml/qmlaxishandling/AxisDragging.qml 2
+
+ The \c{onPositionChanged} signal handler catches the current mouse position that will be
+ needed for move distance calculation:
+
+ \snippet qmlaxishandling/qml/qmlaxishandling/AxisDragging.qml 3
+ \dots
+
+ At the end of \c{onPositionChanged}, save the previous mouse position for move distance
+ calculation that will be introduced later:
+
+ \dots 0
+ \snippet qmlaxishandling/qml/qmlaxishandling/AxisDragging.qml 4
+
+ \section2 Translating Mouse Movement to Axis Range Change
+
+ In \c {scatterGraph}, listen to \c {onSelectedElementChanged}. The signal
+ is emitted after the selection query has been made in the \c{onPressed} of the \c{inputArea}.
+ Set the element type into a property you defined (\c{property int selectedAxisLabel: -1}) in the
+ main component, since it is of a type you are interested in:
+
+ \snippet qmlaxishandling/qml/qmlaxishandling/AxisDragging.qml 5
+
+ Then, back in the \c onPositionChanged of \c{inputArea}, check if a mouse button is pressed
+ and if you have a current axis label selection. If the conditions are met, call the
+ function that does the conversion from mouse movement to axis range update:
+
+ \dots 0
+ \snippet qmlaxishandling/qml/qmlaxishandling/AxisDragging.qml 6
+ \dots 0
+
+ The conversion is easy in this case, as the camera rotation is fixed. You can use some
+ precalculated values, calculate mouse move distance, and apply the values to the
+ selected axis range:
+
+ \snippet qmlaxishandling/qml/qmlaxishandling/AxisDragging.qml 7
+
+ For a more sophisticated conversion from mouse movement to axis range update, see
+ \l{Graph Gallery}.
+
+ \section2 Other Features
+
+ The example also demonstrates how to use orthographic projection and how to update properties
+ of a custom item on the fly.
+
+ Orthographic projection is very simple. You'll just need to change the \c orthoProjection
+ property of \c{scatterGraph}. The example has a button for toggling it on and off:
+
+ \snippet qmlaxishandling/qml/qmlaxishandling/AxisDragging.qml 8
+
+ For custom items, add one to the \c customItemList of \c{scatterGraph}:
+
+ \snippet qmlaxishandling/qml/qmlaxishandling/AxisDragging.qml 9
+
+ You implement a timer to add, remove, and rotate all the items in the graph, and use the same
+ timer for rotating the custom item:
+
+ \snippet qmlaxishandling/qml/qmlaxishandling/AxisDragging.qml 10
+ \dots
+
+ \section1 Axis Formatters
+
+ In the \uicontrol {Axis Formatter} tab, create a custom axis formatter. It also illustrates how
+ to use predefined axis formatters.
+
+ \section2 Custom Axis Formatter
+
+ Customizing axis formatters requires subclassing the QValue3DAxisFormatter, which cannot be
+ done in QML code alone. In this example, the axis interprets the float values as
+ a timestamp and shows the date in the axis labels. To achieve this, introduce a new class
+ called \c CustomFormatter, which subclasses the QValue3DAxisFormatter:
+
+ \snippet qmlaxishandling/customformatter.h 2
+ \dots 0
+
+ Since float values of a QScatter3DSeries cannot be directly cast into QDateTime values due to
+ difference in data width, some sort of mapping between the two is needed. To do the mapping,
+ specify an origin date for the formatter and interpret the float values from the
+ QScatter3DSeries as date offsets to that origin value. The origin date is given as
+ a property:
+
+ \snippet qmlaxishandling/customformatter.h 1
+
+ For the mapping from value to QDateTime, use the \c valueToDateTime() method:
+
+ \snippet qmlaxishandling/customformatter.cpp 0
+
+ To function as an axis formatter, \c CustomFormatter needs to reimplement some virtual
+ methods:
+
+ \snippet qmlaxishandling/customformatter.h 0
+
+ The first two are simple, just create a new instance of \c CustomFormatter and copy the
+ necessary data over to it. Use these two methods to create and update a cache of formatter for
+ rendering purposes. Remember to call the superclass implementation of \c populateCopy():
+
+ \snippet qmlaxishandling/customformatter.cpp 1
+
+ \c CustomFormatter does the bulk of its work in the \c recalculate() method, where
+ our formatter calculates the grid, subgrid, and label positions, as well as formats the label
+ strings.
+ In the custom formatter, ignore the segment count of the axis and draw a grid line always at
+ midnight. Subsegment count and label positioning is handled normally:
+
+ \snippet qmlaxishandling/customformatter.cpp 2
+
+ The axis labels are formatted to show only the date. However, to increase the resolution of the
+ timestamp of the selection label, specify another property for the custom formatter to allow
+ the user to customize it:
+
+ \snippet qmlaxishandling/customformatter.h 3
+
+ This selection format property is used in the reimplemented \c stringToValue method, where
+ the submitted format is ignored and the custom selection format substituted for it:
+
+ \snippet qmlaxishandling/customformatter.cpp 3
+
+ To expose our new custom formatter to the QML, declare it and make it a QML module.
+ For information about how to do this, see \l{Surface Graph Gallery}.
+
+ \section2 QML
+
+ In the QML code, define a different axis for each dimension:
+
+ \snippet qmlaxishandling/qml/qmlaxishandling/AxisFormatting.qml 3
+
+ The Z-axis is just a regular ValueAxis3D:
+
+ \snippet qmlaxishandling/qml/qmlaxishandling/AxisFormatting.qml 0
+
+ For the Y-axis, define a logarithmic axis. To make ValueAxis3D show a logarithmic scale,
+ specify LogValueAxis3DFormatter for \c formatter property of the axis:
+
+ \snippet qmlaxishandling/qml/qmlaxishandling/AxisFormatting.qml 2
+
+ And finally, for the X-axis use the new \c CustomFormatter:
+
+ \snippet qmlaxishandling/qml/qmlaxishandling/AxisFormatting.qml 1
+
+ The rest of the application consists of fairly self-explanatory logic for modifying the axes and
+ showing the graph.
+
+ \section1 Example Contents
+*/
diff --git a/examples/graphs/qmlaxishandling/main.cpp b/examples/graphs/qmlaxishandling/main.cpp
new file mode 100644
index 0000000..7ecfe99
--- /dev/null
+++ b/examples/graphs/qmlaxishandling/main.cpp
@@ -0,0 +1,42 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtGui/qguiapplication.h>
+#include <QtQuick/qquickview.h>
+#include <QtQml/qqmlengine.h>
+#include <QtQml>
+
+#ifdef QMAKE_BUILD
+#include "customformatter.h"
+Q_DECLARE_METATYPE(CustomFormatter *)
+#endif
+
+int main(int argc, char *argv[])
+{
+ QGuiApplication app(argc, argv);
+
+#ifdef QMAKE_BUILD
+ qmlRegisterType<CustomFormatter>("AxisHandling", 1, 0, "CustomFormatter");
+#endif
+
+ QQuickView viewer;
+
+ // The following are needed to make examples run without having to install the module
+ // in desktop environments.
+#ifdef Q_OS_WIN
+ QString extraImportPath(QStringLiteral("%1/../../../../%2"));
+#else
+ QString extraImportPath(QStringLiteral("%1/../../../%2"));
+#endif
+ viewer.engine()->addImportPath(extraImportPath.arg(QGuiApplication::applicationDirPath(),
+ QString::fromLatin1("qml")));
+ QObject::connect(viewer.engine(), &QQmlEngine::quit, &viewer, &QWindow::close);
+
+ viewer.setTitle(QStringLiteral("Axis Handling"));
+
+ viewer.setSource(QUrl("qrc:/qml/qmlaxishandling/main.qml"));
+ viewer.setResizeMode(QQuickView::SizeRootObjectToView);
+ viewer.show();
+
+ return app.exec();
+}
diff --git a/examples/graphs/qmlaxishandling/qml/qmlaxishandling/AxisDragging.qml b/examples/graphs/qmlaxishandling/qml/qmlaxishandling/AxisDragging.qml
new file mode 100644
index 0000000..360aad6
--- /dev/null
+++ b/examples/graphs/qmlaxishandling/qml/qmlaxishandling/AxisDragging.qml
@@ -0,0 +1,298 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+import QtQuick.Controls
+import QtGraphs
+
+Item {
+ id: axisDragView
+
+ property int selectedAxisLabel: -1
+ property real dragSpeedModifier: 100.0
+ property int currentMouseX: -1
+ property int currentMouseY: -1
+ property int previousMouseX: -1
+ property int previousMouseY: -1
+
+ required property bool portraitMode
+
+ ListModel {
+ id: graphModel
+ ListElement{ xPos: 0.0; yPos: 0.0; zPos: 0.0; rotation: "@0,0,0,0" }
+ ListElement{ xPos: 1.0; yPos: 1.0; zPos: 1.0; rotation: "@45,1,1,1" }
+ }
+
+ Timer {
+ id: dataTimer
+ interval: 1
+ running: true
+ repeat: true
+ property bool isIncreasing: true
+ property real rotationAngle: 0
+
+ function generateQuaternion() {
+ return "@" + Math.random() * 360 + "," + Math.random() + ","
+ + Math.random() + "," + Math.random();
+ }
+
+ function appendRow() {
+ graphModel.append({"xPos": Math.random(),
+ "yPos": Math.random(),
+ "zPos": Math.random(),
+ "rotation": generateQuaternion()
+ });
+ }
+
+ //! [10]
+ onTriggered: {
+ rotationAngle = rotationAngle + 1;
+ qtCube.setRotationAxisAndAngle(Qt.vector3d(1, 0, 1), rotationAngle);
+ //! [10]
+ scatterSeries.setMeshAxisAndAngle(Qt.vector3d(1, 1, 1), rotationAngle);
+ if (isIncreasing) {
+ for (var i = 0; i < 10; i++)
+ appendRow();
+ if (graphModel.count > 2002) {
+ scatterGraph.theme = isabelleTheme;
+ isIncreasing = false;
+ }
+ } else {
+ graphModel.remove(2, 10);
+ if (graphModel.count === 2) {
+ scatterGraph.theme = dynamicColorTheme;
+ isIncreasing = true;
+ }
+ }
+ }
+ }
+
+ ThemeColor {
+ id: dynamicColor
+ ColorAnimation on color {
+ from: "red"
+ to: "yellow"
+ duration: 2000
+ loops: Animation.Infinite
+ }
+ }
+
+ Theme3D {
+ id: dynamicColorTheme
+ type: Theme3D.ThemeEbony
+ baseColors: [dynamicColor]
+ font.pointSize: 50
+ labelBorderEnabled: true
+ labelBackgroundColor: "gold"
+ labelTextColor: "black"
+ }
+
+ Theme3D {
+ id: isabelleTheme
+ type: Theme3D.ThemeIsabelle
+ font.pointSize: 50
+ labelBorderEnabled: true
+ labelBackgroundColor: "gold"
+ labelTextColor: "black"
+ }
+
+ //! [0]
+ Scatter3D {
+ id: scatterGraph
+ inputHandler: null
+ //! [0]
+ anchors.fill: parent
+ theme: dynamicColorTheme
+ shadowQuality: AbstractGraph3D.ShadowQualityMedium
+ scene.activeCamera.yRotation: 45.0
+ scene.activeCamera.xRotation: 45.0
+ scene.activeCamera.zoomLevel: 75.0
+
+ Scatter3DSeries {
+ id: scatterSeries
+ itemLabelFormat: "X:@xLabel Y:@yLabel Z:@zLabel"
+ mesh: Abstract3DSeries.MeshCube
+
+ ItemModelScatterDataProxy {
+ itemModel: graphModel
+ xPosRole: "xPos"
+ yPosRole: "yPos"
+ zPosRole: "zPos"
+ rotationRole: "rotation"
+ }
+ }
+ //! [9]
+ customItemList: [
+ Custom3DItem {
+ id: qtCube
+ meshFile: ":/qml/qmlaxishandling/cube.obj"
+ textureFile: ":/qml/qmlaxishandling/cubetexture.png"
+ position: Qt.vector3d(0.65, 0.35, 0.65)
+ scaling: Qt.vector3d(0.3, 0.3, 0.3)
+ }
+ ]
+ //! [9]
+ //! [5]
+ onSelectedElementChanged: {
+ if (selectedElement >= AbstractGraph3D.ElementAxisXLabel
+ && selectedElement <= AbstractGraph3D.ElementAxisZLabel) {
+ selectedAxisLabel = selectedElement;
+ } else {
+ selectedAxisLabel = -1;
+ }
+ }
+ //! [5]
+ }
+
+ //! [1]
+ MouseArea {
+ anchors.fill: parent
+ hoverEnabled: true
+ acceptedButtons: Qt.LeftButton
+ //! [1]
+
+ //! [3]
+ onPositionChanged: (mouse)=> {
+ currentMouseX = mouse.x;
+ currentMouseY = mouse.y;
+ //! [3]
+ //! [6]
+ if (pressed && selectedAxisLabel != -1)
+ axisDragView.dragAxis();
+ //! [6]
+ //! [4]
+ previousMouseX = currentMouseX;
+ previousMouseY = currentMouseY;
+ }
+ //! [4]
+
+ //! [2]
+ onPressed: (mouse)=> {
+ scatterGraph.scene.selectionQueryPosition = Qt.point(mouse.x, mouse.y);
+ }
+ //! [2]
+
+ onReleased: {
+ // We need to clear mouse positions and selected axis, because touch devices cannot
+ // track position all the time
+ selectedAxisLabel = -1;
+ currentMouseX = -1;
+ currentMouseY = -1;
+ previousMouseX = -1;
+ previousMouseY = -1;
+ }
+ }
+
+ //! [7]
+ function dragAxis() {
+ // Do nothing if previous mouse position is uninitialized
+ if (previousMouseX === -1)
+ return;
+
+ // Directional drag multipliers based on rotation. Camera is locked to 45 degrees, so we
+ // can use one precalculated value instead of calculating xx, xy, zx and zy individually
+ var cameraMultiplier = 0.70710678;
+
+ // Calculate the mouse move amount
+ var moveX = currentMouseX - previousMouseX;
+ var moveY = currentMouseY - previousMouseY;
+
+ // Adjust axes
+ switch (selectedAxisLabel) {
+ case AbstractGraph3D.ElementAxisXLabel:
+ var distance = ((moveX - moveY) * cameraMultiplier) / dragSpeedModifier;
+ // Check if we need to change min or max first to avoid invalid ranges
+ if (distance > 0) {
+ scatterGraph.axisX.min -= distance;
+ scatterGraph.axisX.max -= distance;
+ } else {
+ scatterGraph.axisX.max -= distance;
+ scatterGraph.axisX.min -= distance;
+ }
+ break;
+ case AbstractGraph3D.ElementAxisYLabel:
+ distance = moveY / dragSpeedModifier;
+ // Check if we need to change min or max first to avoid invalid ranges
+ if (distance > 0) {
+ scatterGraph.axisY.max += distance;
+ scatterGraph.axisY.min += distance;
+ } else {
+ scatterGraph.axisY.min += distance;
+ scatterGraph.axisY.max += distance;
+ }
+ break;
+ case AbstractGraph3D.ElementAxisZLabel:
+ distance = ((moveX + moveY) * cameraMultiplier) / dragSpeedModifier;
+ // Check if we need to change min or max first to avoid invalid ranges
+ if (distance > 0) {
+ scatterGraph.axisZ.max += distance;
+ scatterGraph.axisZ.min += distance;
+ } else {
+ scatterGraph.axisZ.min += distance;
+ scatterGraph.axisZ.max += distance;
+ }
+ break;
+ }
+ }
+ //! [7]
+
+ Button {
+ id: rangeToggle
+ // We're adding 3 buttons and want to divide them equally, if not in portrait mode
+ width: axisDragView.portraitMode ? parent.width : parent.width / 3
+ text: "Use Preset Range"
+ anchors.left: parent.left
+ anchors.top: parent.top
+ property bool autoRange: true
+ onClicked: {
+ if (autoRange) {
+ text = "Use Automatic Range";
+ scatterGraph.axisX.min = 0.3;
+ scatterGraph.axisX.max = 0.7;
+ scatterGraph.axisY.min = 0.3;
+ scatterGraph.axisY.max = 0.7;
+ scatterGraph.axisZ.min = 0.3;
+ scatterGraph.axisZ.max = 0.7;
+ autoRange = false;
+ dragSpeedModifier = 200.0;
+ } else {
+ text = "Use Preset Range";
+ autoRange = true;
+ dragSpeedModifier = 100.0;
+ }
+ scatterGraph.axisX.autoAdjustRange = autoRange;
+ scatterGraph.axisY.autoAdjustRange = autoRange;
+ scatterGraph.axisZ.autoAdjustRange = autoRange;
+ }
+ }
+
+ //! [8]
+ Button {
+ id: orthoToggle
+ width: axisDragView.portraitMode ? parent.width : parent.width / 3
+ text: "Display Orthographic"
+ anchors.left: axisDragView.portraitMode ? parent.left : rangeToggle.right
+ anchors.top: axisDragView.portraitMode ? rangeToggle.bottom : parent.top
+ onClicked: {
+ if (scatterGraph.orthoProjection) {
+ text = "Display Orthographic";
+ scatterGraph.orthoProjection = false;
+ // Orthographic projection disables shadows, so we need to switch them back on
+ scatterGraph.shadowQuality = AbstractGraph3D.ShadowQualityMedium
+ } else {
+ text = "Display Perspective";
+ scatterGraph.orthoProjection = true;
+ }
+ }
+ }
+ //! [8]
+
+ Button {
+ id: exitButton
+ width: axisDragView.portraitMode ? parent.width : parent.width / 3
+ text: "Quit"
+ anchors.left: axisDragView.portraitMode ? parent.left : orthoToggle.right
+ anchors.top: axisDragView.portraitMode ? orthoToggle.bottom : parent.top
+ onClicked: Qt.quit();
+ }
+}
diff --git a/examples/graphs/qmlaxishandling/qml/qmlaxishandling/AxisFormatting.qml b/examples/graphs/qmlaxishandling/qml/qmlaxishandling/AxisFormatting.qml
new file mode 100644
index 0000000..0a3846e
--- /dev/null
+++ b/examples/graphs/qmlaxishandling/qml/qmlaxishandling/AxisFormatting.qml
@@ -0,0 +1,156 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+import QtQuick.Controls
+import QtGraphs
+import AxisHandling
+
+Item {
+ id: axisFormattingView
+
+ required property bool portraitMode
+
+ Data {
+ id: seriesData
+ }
+
+ Theme3D {
+ id: themePrimaryColors
+ type: Theme3D.ThemePrimaryColors
+ font.family: "Lucida Handwriting"
+ font.pointSize: 40
+ }
+
+ //! [1]
+ ValueAxis3D {
+ id: dateAxis
+ formatter: CustomFormatter {
+ originDate: "2023-01-01"
+ selectionFormat: "yyyy-MM-dd HH:mm:ss"
+ }
+ subSegmentCount: 2
+ labelFormat: "yyyy-MM-dd"
+ min: 0
+ max: 14
+ }
+ //! [1]
+
+ //! [2]
+ ValueAxis3D {
+ id: logAxis
+ formatter: LogValueAxis3DFormatter {
+ id: logAxisFormatter
+ base: 10
+ autoSubGrid: true
+ showEdgeLabels: true
+ }
+ labelFormat: "%.2f"
+ }
+ //! [2]
+
+ ValueAxis3D {
+ id: linearAxis
+ labelFormat: "%.2f"
+ min: 0
+ max: 500
+ }
+
+ //! [0]
+ ValueAxis3D {
+ id: valueAxis
+ segmentCount: 5
+ subSegmentCount: 2
+ labelFormat: "%.2f"
+ min: 0
+ max: 10
+ }
+ //! [0]
+
+ Scatter3D {
+ id: scatterGraph
+ anchors.top: exitButton.bottom
+ anchors.bottom: parent.bottom
+ width: parent.width
+ theme: themePrimaryColors
+ shadowQuality: AbstractGraph3D.ShadowQualitySoftMedium
+ scene.activeCamera.cameraPreset: Camera3D.CameraPresetIsometricRight
+ //! [3]
+ axisZ: valueAxis
+ axisY: logAxis
+ axisX: dateAxis
+ //! [3]
+
+ Scatter3DSeries {
+ id: scatterSeries
+ itemLabelFormat: "@xLabel - (@yLabel, @zLabel)"
+ meshSmooth: true
+ ItemModelScatterDataProxy {
+ itemModel: seriesData.model
+ xPosRole: "xPos"
+ yPosRole: "yPos"
+ zPosRole: "zPos"
+ }
+ }
+ }
+
+ Button {
+ id: yAxisBaseChange
+ width: axisFormattingView.portraitMode ? parent.width : parent.width / 3
+ anchors.left: parent.left
+ anchors.top: parent.top
+ state: "enabled"
+ onClicked: {
+ if (logAxisFormatter.base === 10)
+ logAxisFormatter.base = 0;
+ else if (logAxisFormatter.base === 2)
+ logAxisFormatter.base = 10;
+ else
+ logAxisFormatter.base = 2;
+ }
+ states: [
+ State {
+ name: "enabled"
+ PropertyChanges {
+ target: yAxisBaseChange
+ text: "Y-axis log base: " + logAxisFormatter.base
+ enabled: true
+ }
+ },
+ State {
+ name: "disabled"
+ PropertyChanges {
+ target: yAxisBaseChange
+ text: "Y-axis linear"
+ enabled: false
+ }
+ }
+ ]
+ }
+
+ Button {
+ id: yAxisToggle
+ width: axisFormattingView.portraitMode ? parent.width : parent.width / 3
+ anchors.left: axisFormattingView.portraitMode ? parent.left : yAxisBaseChange.right
+ anchors.top: axisFormattingView.portraitMode ? yAxisBaseChange.bottom : parent.top
+ text: "Toggle Y-axis"
+ onClicked: {
+ if (scatterGraph.axisY == linearAxis) {
+ scatterGraph.axisY = logAxis;
+ yAxisBaseChange.state = "enabled";
+ } else {
+ scatterGraph.axisY = linearAxis;
+ yAxisBaseChange.state = "disabled";
+ }
+ }
+ }
+
+ Button {
+ id: exitButton
+ width: axisFormattingView.portraitMode ? parent.width : parent.width / 3
+ anchors.left: axisFormattingView.portraitMode ? parent.left : yAxisToggle.right
+ anchors.top: axisFormattingView.portraitMode ? yAxisToggle.bottom : parent.top
+ text: "Quit"
+ onClicked: Qt.quit();
+ }
+}
diff --git a/examples/graphs/qmlaxishandling/qml/qmlaxishandling/Data.qml b/examples/graphs/qmlaxishandling/qml/qmlaxishandling/Data.qml
new file mode 100644
index 0000000..5db8e85
--- /dev/null
+++ b/examples/graphs/qmlaxishandling/qml/qmlaxishandling/Data.qml
@@ -0,0 +1,35 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+
+Item {
+ property alias model: dataModel
+
+ ListModel {
+ id: dataModel
+ ListElement{ xPos: 2.456103; yPos: 1.0; zPos: 5.0 }
+ ListElement{ xPos: 5.687549; yPos: 3.0; zPos: 2.5 }
+ ListElement{ xPos: 2.357458; yPos: 4.1; zPos: 1.0 }
+ ListElement{ xPos: 4.567458; yPos: 4.75; zPos: 3.9 }
+ ListElement{ xPos: 6.885439; yPos: 4.9; zPos: 7.2 }
+ ListElement{ xPos: 2.366769; yPos: 13.42; zPos: 3.5 }
+ ListElement{ xPos: 7.546457; yPos: 233.1; zPos: 6.9 }
+ ListElement{ xPos: 2.475867; yPos: 32.91; zPos: 4.1 }
+ ListElement{ xPos: 8.456546; yPos: 153.68; zPos: 9.52 }
+ ListElement{ xPos: 3.456348; yPos: 52.96; zPos: 1.6 }
+ ListElement{ xPos: 1.536446; yPos: 32.4; zPos: 2.92 }
+ ListElement{ xPos: 8.456666; yPos: 114.74; zPos: 8.18 }
+ ListElement{ xPos: 5.468486; yPos: 83.1; zPos: 3.8 }
+ ListElement{ xPos: 6.546586 ; yPos: 63.66; zPos: 3.58 }
+ ListElement{ xPos: 8.567516 ; yPos: 1.82; zPos: 4.64 }
+ ListElement{ xPos: 7.678984 ; yPos: 213.18; zPos: 7.22 }
+ ListElement{ xPos: 7.457569 ; yPos: 63.06; zPos: 4.3 }
+ ListElement{ xPos: 8.456755 ; yPos: 122.64; zPos: 6.44 }
+ ListElement{ xPos: 6.234536 ; yPos: 63.96; zPos: 4.38 }
+ ListElement{ xPos: 9.456718 ; yPos: 243.32; zPos: 4.04 }
+ ListElement{ xPos: 10.789889 ; yPos: 43.4; zPos: 2.78 }
+ ListElement{ xPos: 11.346554 ; yPos: 345.12; zPos: 3.1 }
+ ListElement{ xPos: 12.023454 ; yPos: 500.0; zPos: 3.68 }
+ }
+}
diff --git a/examples/graphs/qmlaxishandling/qml/qmlaxishandling/cube.obj b/examples/graphs/qmlaxishandling/qml/qmlaxishandling/cube.obj
new file mode 100644
index 0000000..0197618
--- /dev/null
+++ b/examples/graphs/qmlaxishandling/qml/qmlaxishandling/cube.obj
@@ -0,0 +1,415 @@
+# Blender v2.66 (sub 0) OBJ File: 'beveled_cube.blend'
+# www.blender.org
+v -1.000000 -0.878027 0.878027
+v -0.978771 -0.929277 0.878027
+v -0.950975 -0.932562 0.932562
+v -0.978771 -0.878027 0.929277
+v -0.932562 -0.950975 0.932562
+v -0.929277 -0.978771 0.878027
+v -0.878027 -1.000000 0.878027
+v -0.878027 -0.978771 0.929277
+v -0.932562 -0.932562 0.950975
+v -0.878027 -0.929277 0.978771
+v -0.878027 -0.878027 1.000000
+v -0.929277 -0.878027 0.978771
+v -1.000000 -0.878027 -0.878027
+v -0.978771 -0.878027 -0.929277
+v -0.950975 -0.932562 -0.932562
+v -0.978771 -0.929277 -0.878027
+v -0.932562 -0.932562 -0.950975
+v -0.929277 -0.878027 -0.978771
+v -0.878027 -0.878027 -1.000000
+v -0.878027 -0.929277 -0.978771
+v -0.932562 -0.950975 -0.932562
+v -0.878027 -0.978771 -0.929277
+v -0.878027 -1.000000 -0.878027
+v -0.929277 -0.978771 -0.878027
+v 0.878027 -0.878027 -1.000000
+v 0.929277 -0.878027 -0.978771
+v 0.932562 -0.932562 -0.950975
+v 0.878027 -0.929277 -0.978771
+v 0.950975 -0.932562 -0.932562
+v 0.978771 -0.878027 -0.929277
+v 1.000000 -0.878027 -0.878027
+v 0.978771 -0.929277 -0.878027
+v 0.932562 -0.950975 -0.932562
+v 0.929277 -0.978771 -0.878027
+v 0.878027 -1.000000 -0.878027
+v 0.878027 -0.978771 -0.929277
+v 1.000000 -0.878027 0.878027
+v 0.978771 -0.878027 0.929277
+v 0.950975 -0.932562 0.932562
+v 0.978771 -0.929277 0.878027
+v 0.932562 -0.932562 0.950975
+v 0.929277 -0.878027 0.978771
+v 0.878027 -0.878027 1.000000
+v 0.878027 -0.929277 0.978771
+v 0.932562 -0.950975 0.932562
+v 0.878027 -0.978771 0.929277
+v 0.878027 -1.000000 0.878027
+v 0.929277 -0.978771 0.878027
+v -0.878027 0.878027 1.000000
+v -0.878027 0.929277 0.978771
+v -0.932562 0.932562 0.950975
+v -0.929277 0.878027 0.978771
+v -0.932562 0.950975 0.932562
+v -0.878027 0.978771 0.929277
+v -0.878027 1.000000 0.878027
+v -0.929277 0.978771 0.878027
+v -0.950975 0.932562 0.932562
+v -0.978771 0.929277 0.878027
+v -1.000000 0.878027 0.878027
+v -0.978771 0.878027 0.929277
+v -1.000000 0.878027 -0.878027
+v -0.978771 0.929277 -0.878027
+v -0.950975 0.932562 -0.932562
+v -0.978771 0.878027 -0.929277
+v -0.932562 0.950975 -0.932562
+v -0.929277 0.978771 -0.878027
+v -0.878027 1.000000 -0.878027
+v -0.878027 0.978771 -0.929277
+v -0.932562 0.932562 -0.950975
+v -0.878027 0.929277 -0.978771
+v -0.878027 0.878027 -1.000000
+v -0.929277 0.878027 -0.978771
+v 0.878027 0.878027 -1.000000
+v 0.878027 0.929277 -0.978771
+v 0.932562 0.932562 -0.950975
+v 0.929277 0.878027 -0.978771
+v 0.932562 0.950975 -0.932562
+v 0.878027 0.978771 -0.929277
+v 0.878027 1.000000 -0.878027
+v 0.929277 0.978771 -0.878027
+v 0.950975 0.932562 -0.932562
+v 0.978771 0.929277 -0.878027
+v 1.000000 0.878027 -0.878027
+v 0.978771 0.878027 -0.929277
+v 1.000000 0.878027 0.878027
+v 0.978771 0.929277 0.878027
+v 0.950975 0.932562 0.932562
+v 0.978771 0.878027 0.929277
+v 0.932562 0.950975 0.932562
+v 0.929277 0.978771 0.878027
+v 0.878027 1.000000 0.878027
+v 0.878027 0.978771 0.929277
+v 0.932562 0.932562 0.950975
+v 0.878027 0.929277 0.978771
+v 0.878027 0.878027 1.000000
+v 0.929277 0.878027 0.978771
+vt 0.024513 0.966281
+vt 0.033719 0.975487
+vt 0.033719 0.966281
+vt 0.964639 0.060986
+vt 0.964639 0.939014
+vt 0.989386 0.939014
+vt 0.939014 0.964639
+vt 0.060986 0.964639
+vt 0.939014 0.989386
+vt 0.060986 0.060986
+vt 0.060986 0.939014
+vt 0.939014 0.939014
+vt 0.035361 0.060986
+vt 0.035361 0.939014
+vt 0.010614 0.060986
+vt 0.010614 0.939014
+vt 0.989386 0.060986
+vt 0.939014 0.035361
+vt 0.060986 0.035361
+vt 0.060986 0.010614
+vt 0.060986 0.989386
+vt 0.939014 0.060986
+vt 0.966281 0.975487
+vt 0.966281 0.966281
+vt 0.975487 0.966281
+vt 0.975487 0.033719
+vt 0.966281 0.033719
+vt 0.966281 0.024513
+vt 0.033719 0.024513
+vt 0.033719 0.033719
+vt 0.024513 0.033719
+vt 0.939014 0.010614
+vn -0.713187 -0.495651 -0.495651
+vn -0.495651 -0.495651 -0.713187
+vn -0.495651 -0.713187 -0.495651
+vn 0.539384 -0.823450 0.175909
+vn 0.539384 -0.823450 -0.175909
+vn 0.823450 -0.539384 -0.175909
+vn -0.713187 0.495651 -0.495651
+vn -0.495651 0.713187 -0.495651
+vn -0.495651 0.495651 -0.713187
+vn 0.175909 -0.823450 -0.539384
+vn -0.185644 -0.825892 -0.532365
+vn 0.175909 -0.539384 -0.823450
+vn -0.193426 -0.961852 0.193426
+vn -0.193426 -0.961852 -0.193426
+vn 0.187689 -0.964110 -0.187689
+vn -0.532365 -0.185644 0.825892
+vn -0.532365 0.185644 0.825892
+vn -0.825892 -0.185644 0.532365
+vn -0.532365 0.185644 -0.825892
+vn -0.532365 -0.185644 -0.825892
+vn -0.825892 0.185644 -0.532365
+vn 0.823450 0.175909 -0.539384
+vn 0.823450 -0.175909 -0.539384
+vn 0.539384 -0.175909 -0.823450
+vn 0.539384 0.175909 0.823450
+vn 0.539384 -0.175909 0.823450
+vn 0.823450 -0.175909 0.539384
+vn 0.175909 0.823450 0.539384
+vn -0.185644 0.825892 0.532365
+vn -0.185644 0.532365 0.825892
+vn -0.185644 0.825892 -0.532365
+vn 0.175909 0.823450 -0.539384
+vn -0.185644 0.532365 -0.825892
+vn 0.539384 0.823450 -0.175909
+vn 0.539384 0.823450 0.175909
+vn 0.823450 0.539384 0.175909
+vn 0.187689 0.964110 0.187689
+vn 0.187689 0.964110 -0.187689
+vn -0.193426 0.961852 -0.193426
+vn -0.193426 0.193426 -0.961852
+vn 0.187689 0.187689 -0.964110
+vn -0.193426 -0.193426 -0.961852
+vn -0.961852 0.193426 0.193426
+vn -0.961852 0.193426 -0.193426
+vn -0.961852 -0.193426 0.193426
+vn -0.532365 -0.825892 -0.185644
+vn -0.532365 -0.825892 0.185644
+vn -0.825892 -0.532365 -0.185644
+vn 0.498856 0.498856 -0.708701
+vn 0.498856 0.708701 -0.498856
+vn 0.708701 0.498856 -0.498856
+vn 0.964110 0.187689 -0.187689
+vn 0.964110 0.187689 0.187689
+vn 0.964110 -0.187689 0.187689
+vn 0.498856 -0.498856 -0.708701
+vn 0.708701 -0.498856 -0.498856
+vn 0.498856 -0.708701 -0.498856
+vn 0.708701 0.498856 0.498856
+vn 0.498856 0.708701 0.498856
+vn 0.498856 0.498856 0.708701
+vn -0.495651 0.495651 0.713187
+vn -0.495651 0.713187 0.495651
+vn -0.713187 0.495651 0.495651
+vn 0.708701 -0.498856 0.498856
+vn 0.498856 -0.498856 0.708701
+vn 0.498856 -0.708701 0.498856
+vn -0.532365 0.825892 0.185644
+vn -0.532365 0.825892 -0.185644
+vn -0.825892 0.532365 0.185644
+vn 0.187689 0.187689 0.964110
+vn -0.193426 0.193426 0.961852
+vn -0.193426 -0.193426 0.961852
+vn -0.185644 -0.825892 0.532365
+vn 0.175909 -0.823450 0.539384
+vn 0.175909 -0.539384 0.823450
+vn -0.825892 -0.532365 0.185644
+vn -0.713187 -0.495651 0.495651
+vn -0.495651 -0.713187 0.495651
+vn -0.495651 -0.495651 0.713187
+vn -0.185644 -0.532365 0.825892
+vn -0.961852 -0.193426 -0.193426
+vn -0.825892 -0.185644 -0.532365
+vn 0.187689 -0.187689 -0.964110
+vn 0.823450 -0.539384 0.175909
+vn -0.193426 0.961852 0.193426
+vn -0.825892 0.532365 -0.185644
+vn 0.175909 0.539384 -0.823450
+vn 0.539384 0.175909 -0.823450
+vn 0.823450 0.539384 -0.175909
+vn 0.823450 0.175909 0.539384
+vn 0.175909 0.539384 0.823450
+vn -0.185644 -0.532365 -0.825892
+vn -0.825892 0.185644 0.532365
+vn 0.964110 -0.187689 -0.187689
+vn 0.187689 -0.187689 0.964110
+vn 0.187689 -0.964110 0.187689
+s 1
+f 15/1/1 17/2/2 21/3/3
+f 48/4/4 34/5/5 32/6/6
+f 63/1/7 65/3/8 69/2/9
+f 36/7/10 22/8/11 28/9/12
+f 7/10/13 23/11/14 35/12/15
+f 12/13/16 52/14/17 4/15/18
+f 72/14/19 18/13/20 64/16/21
+f 84/6/22 30/17/23 26/4/24
+f 96/5/25 42/4/26 38/17/27
+f 92/18/28 54/19/29 50/20/30
+f 68/8/31 78/7/32 70/21/33
+f 80/5/34 90/4/35 86/17/36
+f 91/22/37 79/12/38 67/11/39
+f 71/11/40 73/12/41 19/10/42
+f 59/11/43 61/12/44 1/10/45
+f 24/14/46 6/13/47 16/16/48
+f 75/23/49 77/24/50 81/25/51
+f 83/12/52 85/11/53 37/10/54
+f 27/23/55 29/25/56 33/24/57
+f 87/26/58 89/27/59 93/28/60
+f 51/29/61 53/30/62 57/31/63
+f 39/26/64 41/28/65 45/27/66
+f 56/13/67 66/14/68 58/15/69
+f 95/12/70 49/11/71 11/10/72
+f 8/19/73 46/18/74 44/32/75
+f 1/10/45 2/19/76 3/30/77
+f 5/30/78 6/13/47 7/10/13
+f 9/30/79 10/19/80 11/10/72
+f 13/22/81 14/4/82 15/27/1
+f 17/30/2 18/13/20 19/10/42
+f 21/3/3 22/8/11 23/11/14
+f 25/22/83 26/4/24 28/18/12
+f 29/27/56 30/4/23 32/18/6
+f 33/24/57 34/5/5 36/7/10
+f 37/10/54 38/13/27 40/19/84
+f 41/27/65 42/4/26 44/18/75
+f 45/27/66 46/18/74 48/4/4
+f 49/11/71 50/8/30 51/3/61
+f 53/30/62 54/19/29 55/10/85
+f 57/3/63 58/8/69 59/11/43
+f 61/12/44 62/7/86 63/24/7
+f 65/3/8 66/14/68 67/11/39
+f 69/3/9 70/8/33 71/11/40
+f 73/12/41 74/7/87 76/5/88
+f 77/24/50 78/7/32 80/5/34
+f 81/24/51 82/7/89 84/5/22
+f 85/11/53 86/8/36 88/14/90
+f 89/27/59 90/4/35 92/18/28
+f 93/24/60 94/7/91 96/5/25
+f 2/15/76 6/13/47 3/31/77
+f 8/19/73 10/20/80 5/30/78
+f 12/13/16 4/15/18 9/30/79
+f 14/15/82 18/13/20 15/31/1
+f 20/21/92 22/8/11 17/2/2
+f 24/14/46 16/16/48 21/3/3
+f 26/4/24 30/17/23 29/26/56
+f 32/6/6 34/5/5 33/24/57
+f 36/7/10 28/9/12 27/23/55
+f 38/17/27 42/4/26 41/27/65
+f 44/32/75 46/18/74 45/27/66
+f 48/4/4 40/17/84 39/26/64
+f 50/20/30 54/19/29 51/29/61
+f 56/13/67 58/15/69 53/30/62
+f 60/16/93 52/14/17 57/1/63
+f 62/16/86 66/14/68 63/1/7
+f 68/8/31 70/21/33 65/3/8
+f 72/14/19 64/16/21 69/3/9
+f 74/9/87 78/7/32 77/24/50
+f 80/5/34 82/6/89 81/25/51
+f 84/6/22 76/5/88 75/24/49
+f 86/17/36 90/4/35 89/27/59
+f 92/18/28 94/32/91 93/28/60
+f 96/5/25 88/6/90 87/25/58
+f 55/10/85 67/11/39 56/13/67
+f 61/12/44 59/11/43 62/7/86
+f 71/11/40 19/10/42 72/14/19
+f 13/22/81 61/12/44 14/4/82
+f 23/11/14 7/10/13 24/14/46
+f 1/10/45 13/22/81 2/19/76
+f 11/10/72 49/11/71 12/13/16
+f 59/11/43 1/10/45 60/14/93
+f 67/11/39 79/12/38 68/8/31
+f 73/12/41 71/11/40 74/7/87
+f 83/12/52 31/22/94 30/4/23
+f 25/22/83 73/12/41 76/5/88
+f 35/12/15 23/11/14 36/7/10
+f 19/10/42 25/22/83 20/19/92
+f 79/12/38 91/22/37 90/4/35
+f 85/11/53 83/12/52 82/7/89
+f 95/12/70 43/22/95 42/4/26
+f 37/10/54 85/11/53 88/14/90
+f 47/22/96 35/12/15 34/5/5
+f 31/22/94 37/10/54 40/19/84
+f 91/22/37 55/10/85 54/19/29
+f 49/11/71 95/12/70 94/7/91
+f 7/10/13 47/22/96 46/18/74
+f 43/22/95 11/10/72 10/19/80
+f 3/31/77 5/30/78 9/29/79
+f 40/17/84 48/4/4 32/6/6
+f 22/8/11 20/21/92 28/9/12
+f 47/22/96 7/10/13 35/12/15
+f 52/14/17 60/16/93 4/15/18
+f 18/13/20 14/15/82 64/16/21
+f 76/5/88 84/6/22 26/4/24
+f 88/6/90 96/5/25 38/17/27
+f 94/32/91 92/18/28 50/20/30
+f 78/7/32 74/9/87 70/21/33
+f 82/6/89 80/5/34 86/17/36
+f 55/10/85 91/22/37 67/11/39
+f 73/12/41 25/22/83 19/10/42
+f 61/12/44 13/22/81 1/10/45
+f 6/13/47 2/15/76 16/16/48
+f 31/22/94 83/12/52 37/10/54
+f 66/14/68 62/16/86 58/15/69
+f 43/22/95 95/12/70 11/10/72
+f 10/20/80 8/19/73 44/32/75
+f 4/13/18 1/10/45 3/30/77
+f 8/19/73 5/30/78 7/10/13
+f 12/13/16 9/30/79 11/10/72
+f 16/18/48 13/22/81 15/27/1
+f 20/19/92 17/30/2 19/10/42
+f 24/14/46 21/3/3 23/11/14
+f 26/4/24 27/27/55 28/18/12
+f 30/4/23 31/22/94 32/18/6
+f 34/5/5 35/12/15 36/7/10
+f 38/13/27 39/30/64 40/19/84
+f 42/4/26 43/22/95 44/18/75
+f 46/18/74 47/22/96 48/4/4
+f 52/14/17 49/11/71 51/3/61
+f 56/13/67 53/30/62 55/10/85
+f 60/14/93 57/3/63 59/11/43
+f 64/5/21 61/12/44 63/24/7
+f 68/8/31 65/3/8 67/11/39
+f 72/14/19 69/3/9 71/11/40
+f 74/7/87 75/24/49 76/5/88
+f 78/7/32 79/12/38 80/5/34
+f 82/7/89 83/12/52 84/5/22
+f 86/8/36 87/3/58 88/14/90
+f 90/4/35 91/22/37 92/18/28
+f 94/7/91 95/12/70 96/5/25
+f 6/13/47 5/30/78 3/31/77
+f 10/20/80 9/29/79 5/30/78
+f 4/15/18 3/31/77 9/30/79
+f 18/13/20 17/30/2 15/31/1
+f 22/8/11 21/3/3 17/2/2
+f 16/16/48 15/1/1 21/3/3
+f 27/27/55 26/4/24 29/26/56
+f 29/25/56 32/6/6 33/24/57
+f 33/24/57 36/7/10 27/23/55
+f 39/26/64 38/17/27 41/27/65
+f 41/28/65 44/32/75 45/27/66
+f 45/27/66 48/4/4 39/26/64
+f 54/19/29 53/30/62 51/29/61
+f 58/15/69 57/31/63 53/30/62
+f 52/14/17 51/3/61 57/1/63
+f 66/14/68 65/3/8 63/1/7
+f 70/21/33 69/2/9 65/3/8
+f 64/16/21 63/1/7 69/3/9
+f 75/23/49 74/9/87 77/24/50
+f 77/24/50 80/5/34 81/25/51
+f 81/25/51 84/6/22 75/24/49
+f 87/26/58 86/17/36 89/27/59
+f 89/27/59 92/18/28 93/28/60
+f 93/24/60 96/5/25 87/25/58
+f 67/11/39 66/14/68 56/13/67
+f 59/11/43 58/8/69 62/7/86
+f 19/10/42 18/13/20 72/14/19
+f 61/12/44 64/5/21 14/4/82
+f 7/10/13 6/13/47 24/14/46
+f 13/22/81 16/18/48 2/19/76
+f 49/11/71 52/14/17 12/13/16
+f 1/10/45 4/13/18 60/14/93
+f 79/12/38 78/7/32 68/8/31
+f 71/11/40 70/8/33 74/7/87
+f 84/5/22 83/12/52 30/4/23
+f 26/4/24 25/22/83 76/5/88
+f 23/11/14 22/8/11 36/7/10
+f 25/22/83 28/18/12 20/19/92
+f 80/5/34 79/12/38 90/4/35
+f 86/8/36 85/11/53 82/7/89
+f 96/5/25 95/12/70 42/4/26
+f 38/13/27 37/10/54 88/14/90
+f 48/4/4 47/22/96 34/5/5
+f 32/18/6 31/22/94 40/19/84
+f 92/18/28 91/22/37 54/19/29
+f 50/8/30 49/11/71 94/7/91
+f 8/19/73 7/10/13 46/18/74
+f 44/18/75 43/22/95 10/19/80
diff --git a/examples/graphs/qmlaxishandling/qml/qmlaxishandling/cubetexture.png b/examples/graphs/qmlaxishandling/qml/qmlaxishandling/cubetexture.png
new file mode 100644
index 0000000..6369363
--- /dev/null
+++ b/examples/graphs/qmlaxishandling/qml/qmlaxishandling/cubetexture.png
Binary files differ
diff --git a/examples/graphs/qmlaxishandling/qml/qmlaxishandling/main.qml b/examples/graphs/qmlaxishandling/qml/qmlaxishandling/main.qml
new file mode 100644
index 0000000..5231235
--- /dev/null
+++ b/examples/graphs/qmlaxishandling/qml/qmlaxishandling/main.qml
@@ -0,0 +1,47 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+Item {
+ id: mainView
+ width: 1280
+ height: 1024
+ visible: true
+
+ property bool portraitMode: width < height
+
+ TabBar {
+ id: tabBar
+ width: parent.width
+
+ TabButton {
+ text: "Axis Dragging"
+ }
+
+ TabButton {
+ text: "Axis Formatting"
+ }
+ }
+
+ StackLayout {
+ anchors.top: tabBar.bottom
+ anchors.bottom: parent.bottom
+ width: parent.width
+ currentIndex: tabBar.currentIndex
+
+ AxisDragging {
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ portraitMode: mainView.portraitMode
+ }
+
+ AxisFormatting {
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ portraitMode: mainView.portraitMode
+ }
+ }
+}
diff --git a/examples/graphs/qmlaxishandling/qmlaxishandling.pro b/examples/graphs/qmlaxishandling/qmlaxishandling.pro
new file mode 100644
index 0000000..36b0dee
--- /dev/null
+++ b/examples/graphs/qmlaxishandling/qmlaxishandling.pro
@@ -0,0 +1,16 @@
+!include( ../examples.pri ) {
+ error( "Couldn't find the examples.pri file!" )
+}
+
+DEFINES += QMAKE_BUILD
+
+SOURCES += main.cpp \
+ customformatter.cpp
+
+HEADERS += customformatter.h
+
+RESOURCES += qmlaxishandling.qrc
+
+OTHER_FILES += doc/src/* \
+ doc/images/* \
+ qml/qmlaxishandling/*
diff --git a/examples/graphs/qmlaxishandling/qmlaxishandling.qrc b/examples/graphs/qmlaxishandling/qmlaxishandling.qrc
new file mode 100644
index 0000000..136734f
--- /dev/null
+++ b/examples/graphs/qmlaxishandling/qmlaxishandling.qrc
@@ -0,0 +1,12 @@
+<RCC>
+ <qresource prefix="/">
+ <file>qml/qmlaxishandling/AxisDragging.qml</file>
+ <file>qml/qmlaxishandling/AxisFormatting.qml</file>
+ <file>qml/qmlaxishandling/cube.obj</file>
+ <file>qml/qmlaxishandling/cubetexture.png</file>
+ <file>qml/qmlaxishandling/Data.qml</file>
+ <file>qml/qmlaxishandling/main.qml</file>
+ </qresource>
+ <qresource prefix="/mesh"/>
+ <qresource prefix="/texture"/>
+</RCC>
diff --git a/examples/graphs/qmlaxishandling/qmldir b/examples/graphs/qmlaxishandling/qmldir
new file mode 100644
index 0000000..f6da01e
--- /dev/null
+++ b/examples/graphs/qmlaxishandling/qmldir
@@ -0,0 +1,2 @@
+module AxisHandling
+plugin customformatterplugin
diff --git a/examples/graphs/qmlbars/CMakeLists.txt b/examples/graphs/qmlbars/CMakeLists.txt
new file mode 100644
index 0000000..b42de2e
--- /dev/null
+++ b/examples/graphs/qmlbars/CMakeLists.txt
@@ -0,0 +1,58 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+cmake_minimum_required(VERSION 3.16)
+project(qmlbars LANGUAGES CXX)
+
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+set(CMAKE_AUTOMOC ON)
+set(CMAKE_AUTORCC ON)
+set(CMAKE_AUTOUIC ON)
+
+if(NOT DEFINED INSTALL_EXAMPLESDIR)
+ set(INSTALL_EXAMPLESDIR "examples")
+endif()
+
+set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}")
+
+find_package(Qt6 COMPONENTS Core)
+find_package(Qt6 COMPONENTS Gui)
+find_package(Qt6 COMPONENTS Qml)
+find_package(Qt6 COMPONENTS Quick)
+find_package(Qt6 COMPONENTS Graphs)
+
+qt_add_executable(qmlbars
+ main.cpp
+)
+set_target_properties(qmlbars PROPERTIES
+ WIN32_EXECUTABLE TRUE
+ MACOSX_BUNDLE TRUE
+)
+target_compile_definitions(qmlbars PUBLIC
+ QT_DISABLE_DEPRECATED_UP_TO=0x050F00
+)
+
+target_link_libraries(qmlbars PUBLIC
+ Qt::Core
+ Qt::Gui
+ Qt::Qml
+ Qt::Quick
+ Qt::Graphs
+)
+
+qt6_add_qml_module(qmlbars
+ URI Bars
+ VERSION 1.0
+ NO_RESOURCE_TARGET_PATH
+ QML_FILES
+ qml/qmlbars/Axes.qml
+ qml/qmlbars/Data.qml
+ qml/qmlbars/main.qml
+)
+
+install(TARGETS qmlbars
+ RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
+ BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
+ LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
+)
diff --git a/examples/graphs/qmlbars/doc/images/qmlbars-example.png b/examples/graphs/qmlbars/doc/images/qmlbars-example.png
new file mode 100644
index 0000000..803ff62
--- /dev/null
+++ b/examples/graphs/qmlbars/doc/images/qmlbars-example.png
Binary files differ
diff --git a/examples/graphs/qmlbars/doc/src/qmlbars.qdoc b/examples/graphs/qmlbars/doc/src/qmlbars.qdoc
new file mode 100644
index 0000000..f53c9bb
--- /dev/null
+++ b/examples/graphs/qmlbars/doc/src/qmlbars.qdoc
@@ -0,0 +1,111 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ \example qmlbars
+ \meta tags {Graphs, Barsr3D, Multiple Series}
+ \meta category {Graphics}
+ \title Simple Bar Graph
+ \ingroup qtdatavisualization_qmlexamples
+ \brief Using Bars3D in a QML application.
+
+ \e {Simple Bar Graph} shows how to make a simple 3D bar graph using Bars3D and QML.
+
+ \image qmlbars-example.png
+
+ The following sections describe how to switch series and display more than one series
+ at a time. For more information about basic QML application functionality, see
+ \l{Simple Scatter Graph}.
+
+ \include examples-run.qdocinc
+
+ \section1 Data
+
+ The example data set is the monthly income and expenses of a fictional company over several years.
+ The data is defined in a list model in \c Data.qml like this:
+
+ \snippet qmlbars/qml/qmlbars/Data.qml 0
+ \dots
+
+ Each data item has three roles: timestamp, income, and expenses. The timestamp value is in
+ format: \c{<four digit year>-<two digit month>}. Usually, you would map years and months to rows
+ and columns of a bar chart, but you can only show either income or expenses as the value.
+
+ Now, add the data to the Bars3D graph. Create two Bar3DSeries inside it, starting with a series
+ for the income:
+
+ \snippet qmlbars/qml/qmlbars/main.qml 3
+ \dots
+
+ The data is attached to the \c itemModel property of the ItemModelBarDataProxy inside the
+ series. For \c{valueRole}, specify the \c income field, as it contains the value you
+ want. Getting the years and months is a bit more complicated, since they are both found
+ in the same field. To extract those values, specify the \c timestamp field for both
+ \c rowRole and \c columnRole, and additionally specify a search pattern and a replace rule
+ for those roles to extract the correct portion of the field contents for each role.
+ The search pattern is a normal JavaScript regular expression and the replace rule specifies
+ what the field content that matches the regular expression is replaced with.
+ In this case, replace the entire field content with just the year or the month,
+ which is the first captured substring for both rows and columns.
+ For more information about the replace functionality with regular expression, see
+ QString::replace(const QRegExp &rx, const QString &after) function documentation.
+
+ The \c multiMatchBehavior property specifies what to do in case multiple item model items match
+ the same row/column combination. In this case, add their values together.
+ This property has no effect when showing values for each month, as there are no
+ duplicate months in our item model, but it becomes relevant later when you want to show
+ the yearly totals.
+
+ Then, add another series for the expenses:
+
+ \snippet qmlbars/qml/qmlbars/main.qml 4
+ \dots
+
+ The model contains expenses as negative values, but you want to show them as positive bars, so
+ that they can be easily compared to income bars. Use the \c valueRolePattern to remove the minus
+ sign to achieve this. No replacement string needs to be specified as the default replacement
+ is an empty string.
+
+ Use the \c visible property of the series to hide the second series for now.
+
+ \section1 Custom Axis Labels
+
+ \c Axes.qml redefines the category labels for the column axis because the data contains numbers
+ for months, which would clutter the labels:
+
+ \snippet qmlbars/qml/qmlbars/Axes.qml 0
+
+ To make axis labels more readable at low camera angles, set automatic axis label rotation.
+
+ \section1 Switching Series
+
+ In \c main.qml, set up the graph and various UI elements. There are three interesting
+ code blocks to highlight here. The first one shows how to change the visualized data between
+ income, expenses, and both, by simply changing the visibility of the two series:
+
+ \snippet qmlbars/qml/qmlbars/main.qml 0
+
+ The axis label format and item selection label formats are tweaked to get the negative sign
+ showing properly for expenses, which were actually resolved as positive values.
+
+ The second interesting block is where the visualized data is changed by adjusting the proxy
+ properties:
+
+ \snippet qmlbars/qml/qmlbars/main.qml 1
+
+ To show the yearly totals, combine the twelve months of each year into a single bar.
+ This is achieved by specifying a \c columnRolePattern that matches all model items. That way,
+ the data proxy will only have a single column. The cumulative \c multiMatchBehavior
+ specified earlier for the proxy becomes relevant now, causing the values of all twelve months
+ of each year to be added up into a single bar.
+
+ To show just a subset of years, set \c autoRowCategories to false on the ItemModelBarDataProxy
+ item and define the row categories explicitly. This way, only the items in the specified row
+ categories are visualized.
+
+ The third interesting block shows how to get the row and column index of an item if you know
+ the row and column values by using ItemModelBarDataProxy methods \c rowCategoryIndex()
+ and \c columnCategoryIndex():
+
+ \snippet qmlbars/qml/qmlbars/main.qml 2
+*/
diff --git a/examples/graphs/qmlbars/main.cpp b/examples/graphs/qmlbars/main.cpp
new file mode 100644
index 0000000..58dc01a
--- /dev/null
+++ b/examples/graphs/qmlbars/main.cpp
@@ -0,0 +1,32 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtGui/qguiapplication.h>
+#include <QtQuick/qquickview.h>
+#include <QtQml/qqmlengine.h>
+
+int main(int argc, char *argv[])
+{
+ QGuiApplication app(argc, argv);
+
+ QQuickView viewer;
+
+ // The following are needed to make examples run without having to install the module
+ // in desktop environments.
+#ifdef Q_OS_WIN
+ QString extraImportPath(QStringLiteral("%1/../../../../%2"));
+#else
+ QString extraImportPath(QStringLiteral("%1/../../../%2"));
+#endif
+ viewer.engine()->addImportPath(extraImportPath.arg(QGuiApplication::applicationDirPath(),
+ QString::fromLatin1("qml")));
+
+ viewer.setTitle(QStringLiteral("Monthly income / expenses"));
+
+ viewer.setSource(QUrl("qrc:/qml/qmlbars/main.qml"));
+ viewer.setResizeMode(QQuickView::SizeRootObjectToView);
+ viewer.setColor("black");
+ viewer.show();
+
+ return app.exec();
+}
diff --git a/examples/graphs/qmlbars/qml/qmlbars/Axes.qml b/examples/graphs/qmlbars/qml/qmlbars/Axes.qml
new file mode 100644
index 0000000..86562ae
--- /dev/null
+++ b/examples/graphs/qmlbars/qml/qmlbars/Axes.qml
@@ -0,0 +1,41 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+import QtGraphs
+
+Item {
+ property alias column: columnAxis
+ property alias row: rowAxis
+ property alias value: valueAxis
+ property alias total: totalAxis
+
+ // Custom labels for columns, since the data contains abbreviated month names.
+ //! [0]
+ CategoryAxis3D {
+ id: columnAxis
+ labels: ["January", "February", "March", "April", "May", "June",
+ "July", "August", "September", "October", "November", "December"]
+ labelAutoRotation: 30
+ }
+ //! [0]
+ CategoryAxis3D {
+ id: totalAxis
+ labels: ["Yearly total"]
+ labelAutoRotation: 30
+ }
+ CategoryAxis3D {
+ // For row labels we can use row labels from data proxy, no labels defined for rows.
+ id: rowAxis
+ labelAutoRotation: 30
+ }
+
+ ValueAxis3D {
+ id: valueAxis
+ min: 0
+ max: 35
+ labelFormat: "%.2f M\u20AC"
+ title: "Monthly income"
+ labelAutoRotation: 90
+ }
+}
diff --git a/examples/graphs/qmlbars/qml/qmlbars/Data.qml b/examples/graphs/qmlbars/qml/qmlbars/Data.qml
new file mode 100644
index 0000000..88c7934
--- /dev/null
+++ b/examples/graphs/qmlbars/qml/qmlbars/Data.qml
@@ -0,0 +1,118 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+import QtQml.Models
+
+Item {
+ property alias model: dataModel
+
+ property var modelAsJsArray: {
+ var arr = [];
+ for (var i = 0; i < dataModel.count; i++) {
+ var row = dataModel.get(i);
+ arr.push({
+ timestamp: row.timestamp,
+ expenses: row.expenses,
+ income: row.income
+ });
+ }
+ return arr;
+ }
+
+ //! [0]
+ ListModel {
+ id: dataModel
+ ListElement{ timestamp: "2016-01"; expenses: "-4"; income: "5" }
+ ListElement{ timestamp: "2016-02"; expenses: "-5"; income: "6" }
+ ListElement{ timestamp: "2016-03"; expenses: "-7"; income: "4" }
+ //! [0]
+ ListElement{ timestamp: "2016-04"; expenses: "-3"; income: "2" }
+ ListElement{ timestamp: "2016-05"; expenses: "-4"; income: "1" }
+ ListElement{ timestamp: "2016-06"; expenses: "-2"; income: "2" }
+ ListElement{ timestamp: "2016-07"; expenses: "-1"; income: "3" }
+ ListElement{ timestamp: "2016-08"; expenses: "-5"; income: "1" }
+ ListElement{ timestamp: "2016-09"; expenses: "-2"; income: "3" }
+ ListElement{ timestamp: "2016-10"; expenses: "-5"; income: "2" }
+ ListElement{ timestamp: "2016-11"; expenses: "-8"; income: "5" }
+ ListElement{ timestamp: "2016-12"; expenses: "-3"; income: "3" }
+
+ ListElement{ timestamp: "2017-01"; expenses: "-3"; income: "1" }
+ ListElement{ timestamp: "2017-02"; expenses: "-4"; income: "2" }
+ ListElement{ timestamp: "2017-03"; expenses: "-12"; income: "4" }
+ ListElement{ timestamp: "2017-04"; expenses: "-13"; income: "6" }
+ ListElement{ timestamp: "2017-05"; expenses: "-14"; income: "11" }
+ ListElement{ timestamp: "2017-06"; expenses: "-7"; income: "7" }
+ ListElement{ timestamp: "2017-07"; expenses: "-6"; income: "4" }
+ ListElement{ timestamp: "2017-08"; expenses: "-4"; income: "15" }
+ ListElement{ timestamp: "2017-09"; expenses: "-2"; income: "18" }
+ ListElement{ timestamp: "2017-10"; expenses: "-29"; income: "25" }
+ ListElement{ timestamp: "2017-11"; expenses: "-23"; income: "29" }
+ ListElement{ timestamp: "2017-12"; expenses: "-5"; income: "9" }
+
+ ListElement{ timestamp: "2018-01"; expenses: "-3"; income: "8" }
+ ListElement{ timestamp: "2018-02"; expenses: "-8"; income: "14" }
+ ListElement{ timestamp: "2018-03"; expenses: "-10"; income: "20" }
+ ListElement{ timestamp: "2018-04"; expenses: "-12"; income: "24" }
+ ListElement{ timestamp: "2018-05"; expenses: "-10"; income: "19" }
+ ListElement{ timestamp: "2018-06"; expenses: "-5"; income: "8" }
+ ListElement{ timestamp: "2018-07"; expenses: "-1"; income: "4" }
+ ListElement{ timestamp: "2018-08"; expenses: "-7"; income: "12" }
+ ListElement{ timestamp: "2018-09"; expenses: "-4"; income: "16" }
+ ListElement{ timestamp: "2018-10"; expenses: "-22"; income: "33" }
+ ListElement{ timestamp: "2018-11"; expenses: "-16"; income: "25" }
+ ListElement{ timestamp: "2018-12"; expenses: "-2"; income: "7" }
+
+ ListElement{ timestamp: "2019-01"; expenses: "-4"; income: "5" }
+ ListElement{ timestamp: "2019-02"; expenses: "-4"; income: "7" }
+ ListElement{ timestamp: "2019-03"; expenses: "-11"; income: "14" }
+ ListElement{ timestamp: "2019-04"; expenses: "-16"; income: "22" }
+ ListElement{ timestamp: "2019-05"; expenses: "-3"; income: "5" }
+ ListElement{ timestamp: "2019-06"; expenses: "-4"; income: "8" }
+ ListElement{ timestamp: "2019-07"; expenses: "-7"; income: "9" }
+ ListElement{ timestamp: "2019-08"; expenses: "-9"; income: "13" }
+ ListElement{ timestamp: "2019-09"; expenses: "-1"; income: "6" }
+ ListElement{ timestamp: "2019-10"; expenses: "-14"; income: "25" }
+ ListElement{ timestamp: "2019-11"; expenses: "-19"; income: "29" }
+ ListElement{ timestamp: "2019-12"; expenses: "-5"; income: "7" }
+
+ ListElement{ timestamp: "2020-01"; expenses: "-14"; income: "22" }
+ ListElement{ timestamp: "2020-02"; expenses: "-5"; income: "7" }
+ ListElement{ timestamp: "2020-03"; expenses: "-1"; income: "9" }
+ ListElement{ timestamp: "2020-04"; expenses: "-1"; income: "12" }
+ ListElement{ timestamp: "2020-05"; expenses: "-5"; income: "9" }
+ ListElement{ timestamp: "2020-06"; expenses: "-5"; income: "8" }
+ ListElement{ timestamp: "2020-07"; expenses: "-3"; income: "7" }
+ ListElement{ timestamp: "2020-08"; expenses: "-1"; income: "5" }
+ ListElement{ timestamp: "2020-09"; expenses: "-2"; income: "4" }
+ ListElement{ timestamp: "2020-10"; expenses: "-10"; income: "13" }
+ ListElement{ timestamp: "2020-11"; expenses: "-12"; income: "17" }
+ ListElement{ timestamp: "2020-12"; expenses: "-6"; income: "9" }
+
+ ListElement{ timestamp: "2021-01"; expenses: "-2"; income: "6" }
+ ListElement{ timestamp: "2021-02"; expenses: "-4"; income: "8" }
+ ListElement{ timestamp: "2021-03"; expenses: "-7"; income: "12" }
+ ListElement{ timestamp: "2021-04"; expenses: "-9"; income: "15" }
+ ListElement{ timestamp: "2021-05"; expenses: "-7"; income: "19" }
+ ListElement{ timestamp: "2021-06"; expenses: "-9"; income: "18" }
+ ListElement{ timestamp: "2021-07"; expenses: "-13"; income: "17" }
+ ListElement{ timestamp: "2021-08"; expenses: "-5"; income: "9" }
+ ListElement{ timestamp: "2021-09"; expenses: "-3"; income: "8" }
+ ListElement{ timestamp: "2021-10"; expenses: "-13"; income: "15" }
+ ListElement{ timestamp: "2021-11"; expenses: "-8"; income: "17" }
+ ListElement{ timestamp: "2021-12"; expenses: "-7"; income: "10" }
+
+ ListElement{ timestamp: "2022-01"; expenses: "-12"; income: "16" }
+ ListElement{ timestamp: "2022-02"; expenses: "-24"; income: "28" }
+ ListElement{ timestamp: "2022-03"; expenses: "-27"; income: "22" }
+ ListElement{ timestamp: "2022-04"; expenses: "-29"; income: "25" }
+ ListElement{ timestamp: "2022-05"; expenses: "-27"; income: "29" }
+ ListElement{ timestamp: "2022-06"; expenses: "-19"; income: "18" }
+ ListElement{ timestamp: "2022-07"; expenses: "-13"; income: "17" }
+ ListElement{ timestamp: "2022-08"; expenses: "-15"; income: "19" }
+ ListElement{ timestamp: "2022-09"; expenses: "-3"; income: "8" }
+ ListElement{ timestamp: "2022-10"; expenses: "-3"; income: "6" }
+ ListElement{ timestamp: "2022-11"; expenses: "-4"; income: "8" }
+ ListElement{ timestamp: "2022-12"; expenses: "-5"; income: "9" }
+ }
+}
diff --git a/examples/graphs/qmlbars/qml/qmlbars/main.qml b/examples/graphs/qmlbars/qml/qmlbars/main.qml
new file mode 100644
index 0000000..e4daede
--- /dev/null
+++ b/examples/graphs/qmlbars/qml/qmlbars/main.qml
@@ -0,0 +1,482 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import QtGraphs
+import Qt.labs.qmlmodels
+
+pragma ComponentBehavior: Bound
+
+Item {
+ id: mainview
+ width: 1280
+ height: 1024
+
+ property int buttonLayoutHeight: 180
+ property int currentRow
+ state: Screen.width < Screen.height ? "portrait" : "landscape"
+
+ Data {
+ id: graphData
+ }
+
+ Axes {
+ id: graphAxes
+ }
+
+ property Bar3DSeries selectedSeries
+ selectedSeries: barSeries
+
+ function handleSelectionChange(series, position) {
+ if (position !== series.invalidSelectionPosition)
+ selectedSeries = series;
+
+ // Set tableView current row to selected bar
+ var rowRole = series.dataProxy.rowLabels[position.x];
+ var colRole;
+ if (barGraph.columnAxis == graphAxes.total)
+ colRole = "01";
+ else
+ colRole = series.dataProxy.columnLabels[position.y];
+ var checkTimestamp = rowRole + "-" + colRole;
+
+ if (currentRow === -1 || checkTimestamp !== graphData.model.get(currentRow).timestamp) {
+ var totalRows = tableView.rows;
+ for (var i = 0; i < totalRows; i++) {
+ var modelTimestamp = graphData.model.get(i).timestamp;
+ if (modelTimestamp === checkTimestamp) {
+ currentRow = i;
+ break;
+ }
+ }
+ }
+ }
+
+ Item {
+ id: dataView
+ anchors.right: mainview.right
+ anchors.bottom: mainview.bottom
+
+ Bars3D {
+ id: barGraph
+ anchors.fill: parent
+ shadowQuality: AbstractGraph3D.ShadowQualitySoftHigh
+ selectionMode: AbstractGraph3D.SelectionItem
+ theme: Theme3D {
+ type: Theme3D.ThemeEbony
+ labelBorderEnabled: true
+ font.pointSize: 35
+ labelBackgroundEnabled: true
+ colorStyle: Theme3D.ColorStyleObjectGradient//Theme3D.ColorStyleRangeGradient // TODO: QTBUG-99822
+ singleHighlightGradient: customGradient
+
+ ColorGradient {
+ id: customGradient
+ ColorGradientStop { position: 1.0; color: "#FFFF00" }
+ ColorGradientStop { position: 0.0; color: "#808000" }
+ }
+ }
+ barThickness: 0.7
+ barSpacing: Qt.size(0.5, 0.5)
+ barSpacingRelative: false
+ scene.activeCamera.cameraPreset: Camera3D.CameraPresetIsometricLeftHigh
+ columnAxis: graphAxes.column
+ rowAxis: graphAxes.row
+ valueAxis: graphAxes.value
+
+ //! [4]
+ Bar3DSeries {
+ id: secondarySeries
+ visible: false
+ itemLabelFormat: "Expenses, @colLabel, @rowLabel: -@valueLabel"
+ baseGradient: secondaryGradient
+
+ ItemModelBarDataProxy {
+ id: secondaryProxy
+ itemModel: graphData.model
+ rowRole: "timestamp"
+ columnRole: "timestamp"
+ valueRole: "expenses"
+ rowRolePattern: /^(\d\d\d\d).*$/
+ columnRolePattern: /^.*-(\d\d)$/
+ valueRolePattern: /-/
+ rowRoleReplace: "\\1"
+ columnRoleReplace: "\\1"
+ multiMatchBehavior: ItemModelBarDataProxy.MMBCumulative
+ }
+ //! [4]
+
+ ColorGradient {
+ id: secondaryGradient
+ ColorGradientStop { position: 1.0; color: "#FF0000" }
+ ColorGradientStop { position: 0.0; color: "#600000" }
+ }
+
+ onSelectedBarChanged: (position) => mainview.handleSelectionChange(secondarySeries,
+ position);
+ }
+
+ //! [3]
+ Bar3DSeries {
+ id: barSeries
+ itemLabelFormat: "Income, @colLabel, @rowLabel: @valueLabel"
+ baseGradient: barGradient
+
+ ItemModelBarDataProxy {
+ id: modelProxy
+ itemModel: graphData.model
+ rowRole: "timestamp"
+ columnRole: "timestamp"
+ valueRole: "income"
+ rowRolePattern: /^(\d\d\d\d).*$/
+ columnRolePattern: /^.*-(\d\d)$/
+ rowRoleReplace: "\\1"
+ columnRoleReplace: "\\1"
+ multiMatchBehavior: ItemModelBarDataProxy.MMBCumulative
+ }
+ //! [3]
+
+ ColorGradient {
+ id: barGradient
+ ColorGradientStop { position: 1.0; color: "#00FF00" }
+ ColorGradientStop { position: 0.0; color: "#006000" }
+ }
+
+ onSelectedBarChanged: (position) => mainview.handleSelectionChange(barSeries,
+ position);
+ }
+ }
+ }
+
+ ColumnLayout {
+ id: tableViewLayout
+
+ anchors.top: parent.top
+ anchors.left: parent.left
+
+ HorizontalHeaderView {
+ id: headerView
+ readonly property var columnNames: ["Month", "Expenses", "Income"]
+
+ syncView: tableView
+ Layout.fillWidth: true
+ delegate: Text {
+ required property int index
+ padding: 3
+ text: headerView.columnNames[index]
+ color: barGraph.theme.labelTextColor
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ elide: Text.ElideRight
+ }
+ }
+
+ TableView {
+ id: tableView
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+
+ reuseItems: false
+ clip: true
+
+ model: TableModel {
+ id: tableModel
+ TableModelColumn { display: "timestamp" }
+ TableModelColumn { display: "expenses" }
+ TableModelColumn { display: "income" }
+
+ rows: graphData.modelAsJsArray
+ }
+
+ delegate: Rectangle {
+ id: delegateRoot
+ required property int row
+ required property int column
+ required property string display
+ implicitHeight: 30
+ implicitWidth: column === 0 ? tableView.width / 2 : tableView.width / 4
+ color: row === mainview.currentRow ? barGraph.theme.gridLineColor
+ : barGraph.theme.windowColor
+ border.color: row === mainview.currentRow ? barGraph.theme.labelTextColor
+ : barGraph.theme.gridLineColor
+ border.width: 1
+ MouseArea {
+ anchors.fill: parent
+ onClicked: mainview.currentRow = delegateRoot.row;
+ }
+
+ Text {
+ id: delegateText
+ anchors.verticalCenter: parent.verticalCenter
+ width: parent.width
+ anchors.leftMargin: 4
+ anchors.left: parent.left
+ anchors.right: parent.right
+ text: formattedText
+ property string formattedText: {
+ if (delegateRoot.column === 0) {
+ if (delegateRoot.display !== "") {
+ var pattern = /(\d\d\d\d)-(\d\d)/;
+ var matches = pattern.exec(delegateRoot.display);
+ var colIndex = parseInt(matches[2], 10) - 1;
+ return matches[1] + " - " + graphAxes.column.labels[colIndex];
+ }
+ } else {
+ return delegateRoot.display;
+ }
+ }
+ color: barGraph.theme.labelTextColor
+ horizontalAlignment: delegateRoot.column === 0 ? Text.AlignLeft
+ : Text.AlignHCenter
+ elide: Text.ElideRight
+ }
+ }
+ }
+ }
+
+ //! [2]
+ onCurrentRowChanged: {
+ var timestamp = graphData.model.get(mainview.currentRow).timestamp;
+ var pattern = /(\d\d\d\d)-(\d\d)/;
+ var matches = pattern.exec(timestamp);
+ var rowIndex = modelProxy.rowCategoryIndex(matches[1]);
+ var colIndex;
+ if (barGraph.columnAxis == graphAxes.total)
+ colIndex = 0 ;// Just one column when showing yearly totals
+ else
+ colIndex = modelProxy.columnCategoryIndex(matches[2]);
+ if (selectedSeries.visible)
+ mainview.selectedSeries.selectedBar = Qt.point(rowIndex, colIndex);
+ else if (barSeries.visible)
+ barSeries.selectedBar = Qt.point(rowIndex, colIndex);
+ else
+ secondarySeries.selectedBar = Qt.point(rowIndex, colIndex);
+ }
+ //! [2]
+
+ ColumnLayout {
+ id: controlLayout
+ spacing: 0
+
+ Button {
+ id: changeDataButton
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ text: "Show 2020 - 2022"
+ clip: true
+ //! [1]
+ onClicked: {
+ if (text === "Show yearly totals") {
+ modelProxy.autoRowCategories = true;
+ secondaryProxy.autoRowCategories = true;
+ modelProxy.columnRolePattern = /^.*$/;
+ secondaryProxy.columnRolePattern = /^.*$/;
+ graphAxes.value.autoAdjustRange = true;
+ barGraph.columnAxis = graphAxes.total;
+ text = "Show all years";
+ } else if (text === "Show all years") {
+ modelProxy.autoRowCategories = true;
+ secondaryProxy.autoRowCategories = true;
+ modelProxy.columnRolePattern = /^.*-(\d\d)$/;
+ secondaryProxy.columnRolePattern = /^.*-(\d\d)$/;
+ graphAxes.value.min = 0;
+ graphAxes.value.max = 35;
+ barGraph.columnAxis = graphAxes.column;
+ text = "Show 2020 - 2022";
+ } else { // text === "Show 2020 - 2022"
+ // Explicitly defining row categories, since we do not want to show data for
+ // all years in the model, just for the selected ones.
+ modelProxy.autoRowCategories = false;
+ secondaryProxy.autoRowCategories = false;
+ modelProxy.rowCategories = ["2020", "2021", "2022"];
+ secondaryProxy.rowCategories = ["2020", "2021", "2022"];
+ text = "Show yearly totals";
+ }
+ }
+ //! [1]
+
+ contentItem: Text {
+ text: changeDataButton.text
+ opacity: changeDataButton.enabled ? 1.0 : 0.3
+ color: barGraph.theme.labelTextColor
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ elide: Text.ElideRight
+ }
+
+ background: Rectangle {
+ opacity: changeDataButton.enabled ? 1 : 0.3
+ color: changeDataButton.down ? barGraph.theme.gridLineColor : barGraph.theme.windowColor
+ border.color: changeDataButton.down ? barGraph.theme.labelTextColor : barGraph.theme.gridLineColor
+ border.width: 1
+ radius: 2
+ }
+ }
+
+ Button {
+ id: shadowToggle
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ text: barGraph.shadowsSupported ? "Hide Shadows" : "Shadows not supported"
+ clip: true
+ enabled: barGraph.shadowsSupported
+ onClicked: {
+ if (barGraph.shadowQuality == AbstractGraph3D.ShadowQualityNone) {
+ barGraph.shadowQuality = AbstractGraph3D.ShadowQualitySoftHigh;
+ text = "Hide Shadows";
+ } else {
+ barGraph.shadowQuality = AbstractGraph3D.ShadowQualityNone;
+ text = "Show Shadows";
+ }
+ }
+ contentItem: Text {
+ text: shadowToggle.text
+ opacity: shadowToggle.enabled ? 1.0 : 0.3
+ color: barGraph.theme.labelTextColor
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ elide: Text.ElideRight
+ }
+
+ background: Rectangle {
+ opacity: shadowToggle.enabled ? 1 : 0.3
+ color: shadowToggle.down ? barGraph.theme.gridLineColor : barGraph.theme.windowColor
+ border.color: shadowToggle.down ? barGraph.theme.labelTextColor : barGraph.theme.gridLineColor
+ border.width: 1
+ radius: 2
+ }
+ }
+
+ Button {
+ id: seriesToggle
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ text: "Show Expenses"
+ clip: true
+ //! [0]
+ onClicked: {
+ if (text === "Show Expenses") {
+ barSeries.visible = false;
+ secondarySeries.visible = true;
+ barGraph.valueAxis.labelFormat = "-%.2f M\u20AC";
+ secondarySeries.itemLabelFormat = "Expenses, @colLabel, @rowLabel: @valueLabel";
+ text = "Show Both";
+ } else if (text === "Show Both") {
+ barSeries.visible = true;
+ barGraph.valueAxis.labelFormat = "%.2f M\u20AC";
+ secondarySeries.itemLabelFormat = "Expenses, @colLabel, @rowLabel: -@valueLabel";
+ text = "Show Income";
+ } else { // text === "Show Income"
+ secondarySeries.visible = false;
+ text = "Show Expenses";
+ }
+ }
+ //! [0]
+ contentItem: Text {
+ text: seriesToggle.text
+ opacity: seriesToggle.enabled ? 1.0 : 0.3
+ color: barGraph.theme.labelTextColor
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ elide: Text.ElideRight
+ }
+
+ background: Rectangle {
+ opacity: seriesToggle.enabled ? 1 : 0.3
+ color: seriesToggle.down ? barGraph.theme.gridLineColor : barGraph.theme.windowColor
+ border.color: seriesToggle.down ? barGraph.theme.labelTextColor : barGraph.theme.gridLineColor
+ border.width: 1
+ radius: 2
+ }
+ }
+
+ Button {
+ id: marginToggle
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ text: "Use Margin"
+ clip: true
+
+ onClicked: {
+ if (text === "Use Margin") {
+ barGraph.barSeriesMargin = Qt.size(0.2, 0.2);
+ barGraph.barSpacing = Qt.size(0.0, 0.0);
+ text = "Use Spacing"
+ } else if (text === "Use Spacing") {
+ barGraph.barSeriesMargin = Qt.size(0.0, 0.0);
+ barGraph.barSpacing = Qt.size(0.5, 0.5);
+ text = "Use Margin";
+ }
+ }
+ contentItem: Text {
+ text: marginToggle.text
+ opacity: marginToggle.enabled ? 1.0 : 0.3
+ color: barGraph.theme.labelTextColor
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ elide: Text.ElideRight
+ }
+
+ background: Rectangle {
+ opacity: marginToggle.enabled ? 1 : 0.3
+ color: marginToggle.down ? barGraph.theme.gridLineColor : barGraph.theme.windowColor
+ border.color: marginToggle.down ? barGraph.theme.labelTextColor : barGraph.theme.gridLineColor
+ border.width: 1
+ radius: 2
+ }
+ }
+ }
+
+ states: [
+ State {
+ name: "landscape"
+ PropertyChanges {
+ target: dataView
+ width: mainview.width / 4 * 3
+ height: mainview.height
+ }
+ PropertyChanges {
+ target: tableViewLayout
+ height: mainview.height - buttonLayoutHeight
+ anchors.right: dataView.left
+ anchors.left: mainview.left
+ anchors.bottom: undefined
+ }
+ PropertyChanges {
+ target: controlLayout
+ width: mainview.width / 4
+ height: buttonLayoutHeight
+ anchors.top: tableViewLayout.bottom
+ anchors.bottom: mainview.bottom
+ anchors.left: mainview.left
+ anchors.right: dataView.left
+ }
+ },
+ State {
+ name: "portrait"
+ PropertyChanges {
+ target: dataView
+ width: mainview.width
+ height: mainview.width
+ }
+ PropertyChanges {
+ target: tableViewLayout
+ height: mainview.width
+ anchors.right: controlLayout.left
+ anchors.left: mainview.left
+ anchors.bottom: dataView.top
+ }
+ PropertyChanges {
+ target: controlLayout
+ width: mainview.height / 4
+ height: mainview.width / 4
+ anchors.top: mainview.top
+ anchors.bottom: dataView.top
+ anchors.left: undefined
+ anchors.right: mainview.right
+ }
+ }
+ ]
+}
diff --git a/examples/graphs/qmlbars/qmlbars.pro b/examples/graphs/qmlbars/qmlbars.pro
new file mode 100644
index 0000000..b20c977
--- /dev/null
+++ b/examples/graphs/qmlbars/qmlbars.pro
@@ -0,0 +1,11 @@
+!include( ../examples.pri ) {
+ error( "Couldn't find the examples.pri file!" )
+}
+
+SOURCES += main.cpp
+
+RESOURCES += qmlbars.qrc
+
+OTHER_FILES += doc/src/* \
+ doc/images/* \
+ qml/qmlbars/*
diff --git a/examples/graphs/qmlbars/qmlbars.qrc b/examples/graphs/qmlbars/qmlbars.qrc
new file mode 100644
index 0000000..2240fbe
--- /dev/null
+++ b/examples/graphs/qmlbars/qmlbars.qrc
@@ -0,0 +1,7 @@
+<RCC>
+ <qresource prefix="/">
+ <file>qml/qmlbars/Axes.qml</file>
+ <file>qml/qmlbars/Data.qml</file>
+ <file>qml/qmlbars/main.qml</file>
+ </qresource>
+</RCC>
diff --git a/examples/graphs/qmlscatter/CMakeLists.txt b/examples/graphs/qmlscatter/CMakeLists.txt
new file mode 100644
index 0000000..eaa2d67
--- /dev/null
+++ b/examples/graphs/qmlscatter/CMakeLists.txt
@@ -0,0 +1,53 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+cmake_minimum_required(VERSION 3.16)
+project(qmlscatter LANGUAGES CXX)
+
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+set(CMAKE_AUTOMOC ON)
+set(CMAKE_AUTORCC ON)
+set(CMAKE_AUTOUIC ON)
+
+if(NOT DEFINED INSTALL_EXAMPLESDIR)
+ set(INSTALL_EXAMPLESDIR "examples")
+endif()
+
+set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}")
+
+find_package(Qt6 COMPONENTS Core)
+find_package(Qt6 COMPONENTS Gui)
+find_package(Qt6 COMPONENTS Qml)
+find_package(Qt6 COMPONENTS Quick)
+find_package(Qt6 COMPONENTS Graphs)
+
+qt_add_executable(qmlscatter
+ main.cpp
+)
+set_target_properties(qmlscatter PROPERTIES
+ WIN32_EXECUTABLE TRUE
+ MACOSX_BUNDLE TRUE
+)
+target_link_libraries(qmlscatter PUBLIC
+ Qt::Core
+ Qt::Gui
+ Qt::Qml
+ Qt::Quick
+ Qt::Graphs
+)
+
+qt6_add_qml_module(qmlscatter
+ URI Scatter
+ VERSION 1.0
+ NO_RESOURCE_TARGET_PATH
+ QML_FILES
+ qml/qmlscatter/Data.qml
+ qml/qmlscatter/main.qml
+)
+
+install(TARGETS qmlscatter
+ RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
+ BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
+ LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
+)
diff --git a/examples/graphs/qmlscatter/doc/images/qmlscatter-example.png b/examples/graphs/qmlscatter/doc/images/qmlscatter-example.png
new file mode 100644
index 0000000..5d5de70
--- /dev/null
+++ b/examples/graphs/qmlscatter/doc/images/qmlscatter-example.png
Binary files differ
diff --git a/examples/graphs/qmlscatter/doc/src/qmlscatter.qdoc b/examples/graphs/qmlscatter/doc/src/qmlscatter.qdoc
new file mode 100644
index 0000000..d3b9ed0
--- /dev/null
+++ b/examples/graphs/qmlscatter/doc/src/qmlscatter.qdoc
@@ -0,0 +1,164 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ \example qmlscatter
+ \meta tags {Graphs, Scatter3D, Multiple Series}
+ \meta category {Graphics}
+ \title Simple Scatter Graph
+ \ingroup qtdatavisualization_qmlexamples
+ \brief Using Scatter3D in a QML application.
+
+ \e {Simple Scatter Graph} shows how to make a simple scatter graph visualization using
+ Scatter3D and QML.
+
+ For instructions about how to interact with the graph, see
+ \l{Qt Data Visualization Interacting with Data}{this page}.
+
+ For instructions on how to create a new Qt Quick application of your own, see Qt Creator help.
+
+ \image qmlscatter-example.png
+
+ \include examples-run.qdocinc
+
+ \section1 Application Basics
+
+ Before diving into the QML code, take a look at the application \c main.cpp.
+
+ This application implements a 'Quit' button in the UI, so you want to connect the QQmlEngine::quit()
+ signal to the application's QWindow::close() slot:
+
+ \snippet qmlscatter/main.cpp 4
+
+ To make deployment a little simpler, gather all of the application's \c .qml files to a resource
+ file (\c qmlscatter.qrc):
+
+ \badcode
+ <RCC>
+ <qresource prefix="/">
+ <file>qml/qmlscatter/Data.qml</file>
+ <file>qml/qmlscatter/main.qml</file>
+ </qresource>
+ </RCC>
+ \endcode
+
+ This also requires setting the \c main.qml to be read from the resource (\c{qrc:}):
+
+ \snippet qmlscatter/main.cpp 3
+
+ When using cmake instead of qmake, the \c .qml files are added into a QML module in the
+ \c {CMakeLists.txt} instead:
+
+ \badcode
+ qt6_add_qml_module(qmlscatter
+ URI Scatter
+ VERSION 1.0
+ NO_RESOURCE_TARGET_PATH
+ QML_FILES
+ qml/qmlscatter/Data.qml
+ qml/qmlscatter/main.qml
+ )
+ \endcode
+
+ Finally, make the application run in a maximized window:
+
+ \snippet qmlscatter/main.cpp 2
+
+ \section1 Setting up the Graph
+
+ First, import all the needed QML modules:
+
+ \snippet qmlscatter/qml/qmlscatter/main.qml 0
+
+ Then, create the main \c Item and call it \c mainView:
+
+ \snippet qmlscatter/qml/qmlscatter/main.qml 1
+
+ Then, add another \c Item inside the main \c Item, and call it \c {dataView}.
+ This will be the item to hold the Scatter3D graph. Anchor it to the parent bottom:
+
+ \snippet qmlscatter/qml/qmlscatter/main.qml 9
+
+ Next, add the Scatter3D graph itself. Add it inside the \c dataView and
+ name it \c {scatterGraph}. Make it fill the \c {dataView}:
+
+ \snippet qmlscatter/qml/qmlscatter/main.qml 2
+
+ Now the graph is ready for use, but has no data. It also has the default axes and visual
+ properties.
+
+ Next, modify some visual properties first by adding the following inside \c {scatterGraph}:
+
+ \snippet qmlscatter/qml/qmlscatter/main.qml 3
+
+ A customized theme was added, the shadow quality changed, and the camera position adjusted.
+ The other visual properties are fine, so there is no need to change them.
+
+ The custom theme is based on a predefined theme, \c {Theme3D.ThemeQt}, but the font in it
+ is changed:
+
+ \snippet qmlscatter/qml/qmlscatter/main.qml 13
+
+ Then, start feeding the graph some data.
+
+ \section1 Adding Data to the Graph
+
+ Create a \c Data item inside the \c mainView and name it \c seriesData:
+
+ \snippet qmlscatter/qml/qmlscatter/main.qml 4
+
+ The \c seriesData item contains the data models for all three series used in this example.
+
+ This is the component that holds the data in \c {Data.qml}. It has an \c Item as the main
+ component.
+
+ In the main component, add the data itself to a \c ListModel and name it \c {dataModel}:
+
+ \snippet qmlscatter/qml/qmlscatter/Data.qml 0
+ \dots
+
+ Add two more of these to the other two series, and name them \c dataModelTwo and
+ \c {dataModelThree}.
+
+ Then, expose the data models to be usable from \c {main.qml}. Do this by defining
+ them as aliases in the main data component:
+
+ \snippet qmlscatter/qml/qmlscatter/Data.qml 1
+
+ Now you can use the data from \c Data.qml with \c scatterGraph in \c {main.qml}. First, add
+ a Scatter3DSeries and call it \c {scatterSeries}:
+
+ \snippet qmlscatter/qml/qmlscatter/main.qml 5
+
+ Then, set up selection label format for the series:
+
+ \snippet qmlscatter/qml/qmlscatter/main.qml 10
+
+ And finally, add the data for series one in a ItemModelScatterDataProxy. Set the data itself as
+ the \c itemModel for the proxy:
+
+ \snippet qmlscatter/qml/qmlscatter/main.qml 11
+
+ Add the other two series in the same way, but modify some series-specific details a bit:
+
+ \snippet qmlscatter/qml/qmlscatter/main.qml 12
+ \dots
+
+ Then, modify the properties of the default axes in \c scatterGraph a bit:
+
+ \snippet qmlscatter/qml/qmlscatter/main.qml 6
+
+ After that, add a few buttons to the \c mainView to control the graph, one of which is shown as
+ an example:
+
+ \snippet qmlscatter/qml/qmlscatter/main.qml 7
+
+ Then, modify \c dataView to make some room for the buttons at the top:
+
+ \snippet qmlscatter/qml/qmlscatter/main.qml 8
+ \dots
+
+ And you're done!
+
+ \section1 Example Contents
+*/
diff --git a/examples/graphs/qmlscatter/main.cpp b/examples/graphs/qmlscatter/main.cpp
new file mode 100644
index 0000000..a953ab2
--- /dev/null
+++ b/examples/graphs/qmlscatter/main.cpp
@@ -0,0 +1,41 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtGui/QGuiApplication>
+#include <QtCore/QDir>
+#include <QtQuick/QQuickView>
+#include <QtQml/QQmlEngine>
+
+int main(int argc, char *argv[])
+{
+ QGuiApplication app(argc, argv);
+
+ QQuickView viewer;
+
+ // The following are needed to make examples run without having to install the module
+ // in desktop environments.
+#ifdef Q_OS_WIN
+ QString extraImportPath(QStringLiteral("%1/../../../../%2"));
+#else
+ QString extraImportPath(QStringLiteral("%1/../../../%2"));
+#endif
+ viewer.engine()->addImportPath(extraImportPath.arg(QGuiApplication::applicationDirPath(),
+ QString::fromLatin1("qml")));
+ //! [4]
+ QObject::connect(viewer.engine(), &QQmlEngine::quit, &viewer, &QWindow::close);
+ //! [4]
+
+ viewer.setTitle(QStringLiteral("Simple Scatter Graph"));
+
+ //! [3]
+ viewer.setSource(QUrl("qrc:/qml/qmlscatter/main.qml"));
+ //! [3]
+
+ viewer.setResizeMode(QQuickView::SizeRootObjectToView);
+
+ //! [2]
+ viewer.showMaximized();
+ //! [2]
+
+ return app.exec();
+}
diff --git a/examples/graphs/qmlscatter/qml/qmlscatter/Data.qml b/examples/graphs/qmlscatter/qml/qmlscatter/Data.qml
new file mode 100644
index 0000000..a6cce66
--- /dev/null
+++ b/examples/graphs/qmlscatter/qml/qmlscatter/Data.qml
@@ -0,0 +1,1084 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+
+Item {
+ //! [1]
+ property alias model: dataModel
+ property alias modelTwo: dataModelTwo
+ property alias modelThree: dataModelThree
+ //! [1]
+
+ //! [0]
+ ListModel {
+ id: dataModel
+ ListElement{ xPos: -10.0; yPos: 5.0; zPos: -5.0 }
+ //! [0]
+ ListElement{ xPos: -9.0; yPos: 3.0; zPos: -4.5 }
+ ListElement{ xPos: -8.5; yPos: 4.1; zPos: -4.0 }
+ ListElement{ xPos: -8.0; yPos: 4.75; zPos: -3.9 }
+ ListElement{ xPos: -9.5; yPos: 4.9; zPos: -4.2 }
+ ListElement{ xPos: -9.9; yPos: 3.42; zPos: -3.5 }
+ ListElement{ xPos: -7.8; yPos: 3.1; zPos: -4.9 }
+ ListElement{ xPos: -7.3; yPos: 2.91; zPos: -4.1 }
+ ListElement{ xPos: -7.1 ; yPos: 3.68 ; zPos: -4.52 }
+ ListElement{ xPos: -8.8 ; yPos: 2.96 ; zPos: -3.6 }
+ ListElement{ xPos: -6.94 ; yPos: 2.4 ; zPos: -2.92 }
+ ListElement{ xPos: -9.02 ; yPos: 4.74 ; zPos: -4.18 }
+ ListElement{ xPos: -9.54 ; yPos: 3.1 ; zPos: -3.8 }
+ ListElement{ xPos: -6.86 ; yPos: 3.66 ; zPos: -3.58 }
+ ListElement{ xPos: -8.16 ; yPos: 1.82 ; zPos: -4.64 }
+ ListElement{ xPos: -7.4 ; yPos: 3.18 ; zPos: -4.22 }
+ ListElement{ xPos: -7.9 ; yPos: 3.06 ; zPos: -4.3 }
+ ListElement{ xPos: -8.98 ; yPos: 2.64 ; zPos: -4.44 }
+ ListElement{ xPos: -6.36 ; yPos: 3.96 ; zPos: -4.38 }
+ ListElement{ xPos: -7.18 ; yPos: 3.32 ; zPos: -4.04 }
+ ListElement{ xPos: -7.9 ; yPos: 3.4 ; zPos: -2.78 }
+ ListElement{ xPos: -7.4 ; yPos: 3.12 ; zPos: -3.1 }
+ ListElement{ xPos: -7.54 ; yPos: 2.8 ; zPos: -3.68 }
+ }
+
+ ListModel {
+ id: dataModelTwo
+ ListElement{ xPos: 2.25 ; yPos: 1.36 ; zPos: -1.3 }
+ ListElement{ xPos: -2 ; yPos: -0.08 ; zPos: 1 }
+ ListElement{ xPos: 2.65 ; yPos: -1.2 ; zPos: 2.7 }
+ ListElement{ xPos: -2.55 ; yPos: 3.48 ; zPos: -0.45 }
+ ListElement{ xPos: -3.85 ; yPos: 0.2 ; zPos: 0.5 }
+ ListElement{ xPos: 2.85 ; yPos: 1.32 ; zPos: 1.05 }
+ ListElement{ xPos: -0.4 ; yPos: -2.36 ; zPos: -1.6 }
+ ListElement{ xPos: 1.5 ; yPos: 0.36 ; zPos: -0.55 }
+ ListElement{ xPos: 0.25 ; yPos: 0.12 ; zPos: -0.75 }
+ ListElement{ xPos: -2.45 ; yPos: -0.72 ; zPos: -1.1 }
+ ListElement{ xPos: 4.1 ; yPos: 1.92 ; zPos: -0.95 }
+ ListElement{ xPos: 2.05 ; yPos: 0.64 ; zPos: -0.1 }
+ ListElement{ xPos: 0.25 ; yPos: 0.8 ; zPos: 3.05 }
+ ListElement{ xPos: 1.5 ; yPos: 0.24 ; zPos: 2.25 }
+ ListElement{ xPos: 1.15 ; yPos: -0.4 ; zPos: 0.8 }
+ ListElement{ xPos: -0.05 ; yPos: 0.12 ; zPos: 0.25 }
+ ListElement{ xPos: 0.9 ; yPos: -0.48 ; zPos: -3.2 }
+ ListElement{ xPos: 3.55 ; yPos: -1.04 ; zPos: -0.95 }
+ ListElement{ xPos: -2.4 ; yPos: -0.96 ; zPos: -0.65 }
+ ListElement{ xPos: 4.25 ; yPos: 0.16 ; zPos: 2.05 }
+ ListElement{ xPos: -0.6 ; yPos: 1 ; zPos: -0.2 }
+ ListElement{ xPos: 0 ; yPos: -0.84 ; zPos: -0.95 }
+ ListElement{ xPos: -4.2 ; yPos: 1.44 ; zPos: 0.85 }
+ ListElement{ xPos: -1.5 ; yPos: -1.2 ; zPos: -1.5 }
+ ListElement{ xPos: -3.35 ; yPos: -1.96 ; zPos: 1.8 }
+ ListElement{ xPos: -2.75 ; yPos: -0.96 ; zPos: -1.2 }
+ ListElement{ xPos: -2.45 ; yPos: 0.28 ; zPos: -4.55 }
+ ListElement{ xPos: 2.7 ; yPos: -1.4 ; zPos: -0.65 }
+ ListElement{ xPos: 2.7 ; yPos: 2.2 ; zPos: -2.4 }
+ ListElement{ xPos: -0.8 ; yPos: -2.04 ; zPos: 3.75 }
+ ListElement{ xPos: -1 ; yPos: -1.84 ; zPos: 2.35 }
+ ListElement{ xPos: -3.25 ; yPos: -1.6 ; zPos: -4.4 }
+ ListElement{ xPos: -0.35 ; yPos: -0.56 ; zPos: -1.75 }
+ ListElement{ xPos: -0.8 ; yPos: -0.28 ; zPos: -0.05 }
+ ListElement{ xPos: 4.75 ; yPos: -1.48 ; zPos: -2.45 }
+ ListElement{ xPos: 4.35 ; yPos: 1.4 ; zPos: 0.8 }
+ ListElement{ xPos: -3.2 ; yPos: 2 ; zPos: 0.5 }
+ ListElement{ xPos: 2 ; yPos: 1.92 ; zPos: 0.55 }
+ ListElement{ xPos: -0.45 ; yPos: 0.68 ; zPos: -0.8 }
+ ListElement{ xPos: -2.7 ; yPos: 2 ; zPos: -2.4 }
+ ListElement{ xPos: -1.65 ; yPos: 1.32 ; zPos: -2.05 }
+ ListElement{ xPos: -1.6 ; yPos: 1.8 ; zPos: -3.65 }
+ ListElement{ xPos: -1.2 ; yPos: -0.72 ; zPos: 0.85 }
+ ListElement{ xPos: 1.4 ; yPos: 1.08 ; zPos: 2.1 }
+ ListElement{ xPos: -3.9 ; yPos: -1.16 ; zPos: -0.85 }
+ ListElement{ xPos: 3.15 ; yPos: 0.12 ; zPos: -0.95 }
+ ListElement{ xPos: -1.35 ; yPos: -2.64 ; zPos: -0.5 }
+ ListElement{ xPos: -4.15 ; yPos: 1.28 ; zPos: -4.4 }
+ ListElement{ xPos: -2 ; yPos: 2.48 ; zPos: -0.15 }
+ ListElement{ xPos: -3.4 ; yPos: 0.6 ; zPos: 0 }
+ ListElement{ xPos: -1.5 ; yPos: -0.96 ; zPos: 2.35 }
+ ListElement{ xPos: -0.75 ; yPos: 1.16 ; zPos: -0.75 }
+ ListElement{ xPos: -0.85 ; yPos: 0.64 ; zPos: -1.75 }
+ ListElement{ xPos: 0.85 ; yPos: 0.48 ; zPos: 2.9 }
+ ListElement{ xPos: 4.05 ; yPos: 3.24 ; zPos: -0.1 }
+ ListElement{ xPos: 0.65 ; yPos: 0.84 ; zPos: 0.3 }
+ ListElement{ xPos: -2.1 ; yPos: 0.92 ; zPos: -2.75 }
+ ListElement{ xPos: -0.65 ; yPos: 0.16 ; zPos: 1.6 }
+ ListElement{ xPos: -1.8 ; yPos: -1.48 ; zPos: -2.15 }
+ ListElement{ xPos: -1.6 ; yPos: 2.96 ; zPos: 1.85 }
+ ListElement{ xPos: -0.75 ; yPos: 0.28 ; zPos: -0.75 }
+ ListElement{ xPos: 2.2 ; yPos: 1.16 ; zPos: 2.65 }
+ ListElement{ xPos: 2.6 ; yPos: 2.92 ; zPos: -0.2 }
+ ListElement{ xPos: -1.65 ; yPos: 3.28 ; zPos: 2.5 }
+ ListElement{ xPos: -0.5 ; yPos: 2.52 ; zPos: -1.15 }
+ ListElement{ xPos: -1.85 ; yPos: 0.52 ; zPos: -2 }
+ ListElement{ xPos: 0.15 ; yPos: -2.36 ; zPos: -0.5 }
+ ListElement{ xPos: -3.9 ; yPos: 0.28 ; zPos: -0.25 }
+ ListElement{ xPos: 0.1 ; yPos: -3.88 ; zPos: -0.7 }
+ ListElement{ xPos: -1 ; yPos: -1.96 ; zPos: 0.95 }
+ ListElement{ xPos: -0.3 ; yPos: -3.52 ; zPos: -1.45 }
+ ListElement{ xPos: 3.2 ; yPos: -2.56 ; zPos: 2.5 }
+ ListElement{ xPos: 0 ; yPos: -2.44 ; zPos: 1.35 }
+ ListElement{ xPos: 1.45 ; yPos: -1.96 ; zPos: 0.1 }
+ ListElement{ xPos: 3.55 ; yPos: -2.16 ; zPos: 1.75 }
+ ListElement{ xPos: 2.8 ; yPos: -0.72 ; zPos: -0.3 }
+ ListElement{ xPos: -1.65 ; yPos: -0.6 ; zPos: 0.25 }
+ ListElement{ xPos: -4.35 ; yPos: -1.32 ; zPos: -2.3 }
+ ListElement{ xPos: 2.75 ; yPos: -1.88 ; zPos: 1.7 }
+ ListElement{ xPos: 2.75 ; yPos: -0.6 ; zPos: 2.45 }
+ ListElement{ xPos: 0.15 ; yPos: -0.88 ; zPos: -0.2 }
+ ListElement{ xPos: 0.15 ; yPos: 0.04 ; zPos: -0.1 }
+ ListElement{ xPos: -1.2 ; yPos: -2.92 ; zPos: -3 }
+ ListElement{ xPos: -0.7 ; yPos: 0.16 ; zPos: 3.5 }
+ ListElement{ xPos: -1.8 ; yPos: -2.4 ; zPos: 0.2 }
+ ListElement{ xPos: 1.25 ; yPos: 2.64 ; zPos: 1.65 }
+ ListElement{ xPos: -0.65 ; yPos: 1.32 ; zPos: -0.55 }
+ ListElement{ xPos: 3.85 ; yPos: 2.32 ; zPos: -1 }
+ ListElement{ xPos: 1.65 ; yPos: -2.28 ; zPos: 0.95 }
+ ListElement{ xPos: -2.45 ; yPos: -2.96 ; zPos: 3.45 }
+ ListElement{ xPos: -1.75 ; yPos: 0.36 ; zPos: 1.6 }
+ ListElement{ xPos: 1.1 ; yPos: -0.84 ; zPos: 0.35 }
+ ListElement{ xPos: -1.55 ; yPos: 1.6 ; zPos: -3.1 }
+ ListElement{ xPos: 0.4 ; yPos: -1.36 ; zPos: -1.55 }
+ ListElement{ xPos: -3.95 ; yPos: 0.2 ; zPos: 3.7 }
+ ListElement{ xPos: -1.8 ; yPos: -0.24 ; zPos: 2.2 }
+ ListElement{ xPos: -0.45 ; yPos: 1.64 ; zPos: 1.35 }
+ ListElement{ xPos: -0.6 ; yPos: -0.72 ; zPos: 2.5 }
+ ListElement{ xPos: -1.2 ; yPos: -3.04 ; zPos: -1.55 }
+ ListElement{ xPos: -0.95 ; yPos: 1.48 ; zPos: 0.95 }
+ ListElement{ xPos: 1.7 ; yPos: -1.36 ; zPos: 2.15 }
+ ListElement{ xPos: -3.9 ; yPos: -0.32 ; zPos: -2.45 }
+ ListElement{ xPos: 2.95 ; yPos: 3.12 ; zPos: -2.4 }
+ ListElement{ xPos: 3.55 ; yPos: -1.48 ; zPos: 0.15 }
+ ListElement{ xPos: -1.4 ; yPos: 3.24 ; zPos: 0.7 }
+ ListElement{ xPos: -3.3 ; yPos: -0.8 ; zPos: 0 }
+ ListElement{ xPos: 1.15 ; yPos: 0.36 ; zPos: -2.7 }
+ ListElement{ xPos: 0.4 ; yPos: 0.12 ; zPos: 1.35 }
+ ListElement{ xPos: 1.9 ; yPos: -3.32 ; zPos: -0.75 }
+ ListElement{ xPos: 0.4 ; yPos: -0.2 ; zPos: -2.4 }
+ ListElement{ xPos: 0.55 ; yPos: 1.56 ; zPos: 2.25 }
+ ListElement{ xPos: 4.35 ; yPos: -0.96 ; zPos: -0.7 }
+ ListElement{ xPos: 3.35 ; yPos: -1.04 ; zPos: -0.85 }
+ ListElement{ xPos: 1.7 ; yPos: -0.28 ; zPos: -0.25 }
+ ListElement{ xPos: -3.05 ; yPos: -0.44 ; zPos: 1.3 }
+ ListElement{ xPos: -3.95 ; yPos: -2.72 ; zPos: -3.8 }
+ ListElement{ xPos: 2.1 ; yPos: 1.32 ; zPos: 1.15 }
+ ListElement{ xPos: 1.4 ; yPos: -0.44 ; zPos: -2.85 }
+ ListElement{ xPos: -2.1 ; yPos: 0.04 ; zPos: 2 }
+ ListElement{ xPos: 2.45 ; yPos: -2.44 ; zPos: -2.85 }
+ ListElement{ xPos: 4.35 ; yPos: 0.24 ; zPos: 1.1 }
+ ListElement{ xPos: 1.8 ; yPos: 1.8 ; zPos: -0.7 }
+ ListElement{ xPos: 1.8 ; yPos: 2.04 ; zPos: -0.95 }
+ ListElement{ xPos: 1.65 ; yPos: -2.48 ; zPos: 0.35 }
+ ListElement{ xPos: -3.3 ; yPos: 0.28 ; zPos: -1.6 }
+ ListElement{ xPos: 1.85 ; yPos: 1 ; zPos: 2.25 }
+ ListElement{ xPos: -2 ; yPos: -0.88 ; zPos: -2.6 }
+ ListElement{ xPos: -0.1 ; yPos: 0.28 ; zPos: -1.75 }
+ ListElement{ xPos: -2.5 ; yPos: -2.76 ; zPos: -0.2 }
+ ListElement{ xPos: 0.3 ; yPos: -1.64 ; zPos: -0.75 }
+ ListElement{ xPos: 2.3 ; yPos: -1.28 ; zPos: -2.7 }
+ ListElement{ xPos: -1.7 ; yPos: -1.48 ; zPos: -2.6 }
+ ListElement{ xPos: 0.5 ; yPos: 0.24 ; zPos: -2.2 }
+ ListElement{ xPos: 0.45 ; yPos: -1.88 ; zPos: -0.95 }
+ ListElement{ xPos: -1.2 ; yPos: 0.96 ; zPos: 2.35 }
+ ListElement{ xPos: -0.55 ; yPos: -0.36 ; zPos: -1.8 }
+ ListElement{ xPos: 2.7 ; yPos: -0.56 ; zPos: -0.4 }
+ ListElement{ xPos: 1.1 ; yPos: 2.76 ; zPos: -2.35 }
+ ListElement{ xPos: -1.2 ; yPos: -2.16 ; zPos: 3.1 }
+ ListElement{ xPos: -0.05 ; yPos: 1.76 ; zPos: 1 }
+ ListElement{ xPos: 1.45 ; yPos: -0.72 ; zPos: 0.7 }
+ ListElement{ xPos: 0.2 ; yPos: -3.2 ; zPos: -0.25 }
+ ListElement{ xPos: -0.8 ; yPos: 1.08 ; zPos: -0.1 }
+ ListElement{ xPos: -2.45 ; yPos: 0.56 ; zPos: -0.55 }
+ ListElement{ xPos: 2 ; yPos: 1.12 ; zPos: 2 }
+ ListElement{ xPos: -1.05 ; yPos: -2.16 ; zPos: -1.8 }
+ ListElement{ xPos: 1.2 ; yPos: -2.12 ; zPos: -1.55 }
+ ListElement{ xPos: -4 ; yPos: -0.76 ; zPos: 0.2 }
+ ListElement{ xPos: -0.15 ; yPos: -0.2 ; zPos: -2 }
+ ListElement{ xPos: -2.95 ; yPos: 1.36 ; zPos: -3.65 }
+ ListElement{ xPos: 2.7 ; yPos: 1.16 ; zPos: 2.05 }
+ ListElement{ xPos: 0.95 ; yPos: -1.52 ; zPos: -1.05 }
+ ListElement{ xPos: -1.8 ; yPos: 2.72 ; zPos: -0.55 }
+ ListElement{ xPos: 0.45 ; yPos: 2.88 ; zPos: -4.4 }
+ ListElement{ xPos: 1.35 ; yPos: 1.08 ; zPos: -0.8 }
+ ListElement{ xPos: -2.7 ; yPos: -1.36 ; zPos: -2.65 }
+ ListElement{ xPos: 0.35 ; yPos: 2.32 ; zPos: -1.6 }
+ ListElement{ xPos: -0.45 ; yPos: 1.6 ; zPos: 0.1 }
+ ListElement{ xPos: 3.75 ; yPos: 1.12 ; zPos: -3.8 }
+ ListElement{ xPos: 0.5 ; yPos: 2.6 ; zPos: 0.5 }
+ ListElement{ xPos: -0.75 ; yPos: -3.52 ; zPos: -3.2 }
+ ListElement{ xPos: 0.25 ; yPos: -1.32 ; zPos: 2.25 }
+ ListElement{ xPos: -2.8 ; yPos: -1.6 ; zPos: -2.45 }
+ ListElement{ xPos: 0.15 ; yPos: -0.56 ; zPos: 3.9 }
+ ListElement{ xPos: 2.15 ; yPos: -1.68 ; zPos: -1.7 }
+ ListElement{ xPos: -4.2 ; yPos: -1.92 ; zPos: 2.35 }
+ ListElement{ xPos: 1.95 ; yPos: -1.32 ; zPos: 0.3 }
+ ListElement{ xPos: 0.5 ; yPos: 0.64 ; zPos: 0.75 }
+ ListElement{ xPos: 0.85 ; yPos: -0.84 ; zPos: -0.5 }
+ ListElement{ xPos: -2.15 ; yPos: -1.64 ; zPos: -2.3 }
+ ListElement{ xPos: -0.85 ; yPos: -2.84 ; zPos: -0.3 }
+ ListElement{ xPos: -3.1 ; yPos: -0.32 ; zPos: -0.85 }
+ ListElement{ xPos: 3.45 ; yPos: 0 ; zPos: 0.8 }
+ ListElement{ xPos: 1.4 ; yPos: 0.2 ; zPos: 0.3 }
+ ListElement{ xPos: 0.45 ; yPos: 1.76 ; zPos: -0.15 }
+ ListElement{ xPos: 0.85 ; yPos: 3.16 ; zPos: -2 }
+ ListElement{ xPos: 2.15 ; yPos: 0.72 ; zPos: -0.95 }
+ ListElement{ xPos: -2.4 ; yPos: 0.6 ; zPos: -0.5 }
+ ListElement{ xPos: 1.45 ; yPos: -0.4 ; zPos: -1.95 }
+ ListElement{ xPos: 0.6 ; yPos: -1.84 ; zPos: 1.8 }
+ ListElement{ xPos: 3.4 ; yPos: -0.2 ; zPos: -0.55 }
+ ListElement{ xPos: 0.2 ; yPos: -0.84 ; zPos: 4.6 }
+ ListElement{ xPos: -0.25 ; yPos: -2.4 ; zPos: 2.1 }
+ ListElement{ xPos: -1.15 ; yPos: -3.16 ; zPos: -3.05 }
+ ListElement{ xPos: 1.45 ; yPos: 0.96 ; zPos: -0.7 }
+ ListElement{ xPos: 0.35 ; yPos: 0.52 ; zPos: -1 }
+ ListElement{ xPos: 2.6 ; yPos: 0.28 ; zPos: -0.85 }
+ ListElement{ xPos: -1.25 ; yPos: 0.76 ; zPos: -1.6 }
+ ListElement{ xPos: -0.65 ; yPos: -2.16 ; zPos: 3 }
+ ListElement{ xPos: -0.25 ; yPos: -2.36 ; zPos: -1.8 }
+ ListElement{ xPos: 0.35 ; yPos: -0.8 ; zPos: 1.85 }
+ ListElement{ xPos: 0.05 ; yPos: 0.6 ; zPos: 0.85 }
+ ListElement{ xPos: -2.5 ; yPos: 2.28 ; zPos: -2.05 }
+ ListElement{ xPos: 1.65 ; yPos: 1.2 ; zPos: 2.1 }
+ ListElement{ xPos: -3 ; yPos: -0.24 ; zPos: 0.7 }
+ ListElement{ xPos: -2.1 ; yPos: -0.84 ; zPos: -0.2 }
+ ListElement{ xPos: 0.45 ; yPos: 0.96 ; zPos: 2.5 }
+ ListElement{ xPos: -2.4 ; yPos: 1.28 ; zPos: 2.75 }
+ ListElement{ xPos: -1.7 ; yPos: -3.2 ; zPos: -2.3 }
+ ListElement{ xPos: 0.95 ; yPos: -0.08 ; zPos: 0.3 }
+ ListElement{ xPos: 1.4 ; yPos: 0.88 ; zPos: 0.45 }
+ ListElement{ xPos: 2.1 ; yPos: 1.08 ; zPos: 0.2 }
+ ListElement{ xPos: -2.1 ; yPos: -0.2 ; zPos: -0.6 }
+ ListElement{ xPos: 0.5 ; yPos: 1.24 ; zPos: -0.45 }
+ ListElement{ xPos: 2.55 ; yPos: -0.56 ; zPos: -2.65 }
+ ListElement{ xPos: -4.5 ; yPos: -0.48 ; zPos: -1.25 }
+ ListElement{ xPos: 3.45 ; yPos: -0.44 ; zPos: 2.2 }
+ ListElement{ xPos: -0.25 ; yPos: 0.64 ; zPos: 1.65 }
+ ListElement{ xPos: 1.25 ; yPos: -0.6 ; zPos: -0.35 }
+ ListElement{ xPos: 1.95 ; yPos: -1.56 ; zPos: -4.1 }
+ ListElement{ xPos: 2.75 ; yPos: -1 ; zPos: 3.3 }
+ ListElement{ xPos: 3.05 ; yPos: 1.04 ; zPos: -0.9 }
+ ListElement{ xPos: -2.95 ; yPos: 1.4 ; zPos: -2.25 }
+ ListElement{ xPos: 1.3 ; yPos: 0.36 ; zPos: -2.9 }
+ ListElement{ xPos: 0.4 ; yPos: -2.48 ; zPos: 3.25 }
+ ListElement{ xPos: -1.15 ; yPos: 0.36 ; zPos: 0.45 }
+ ListElement{ xPos: -2.25 ; yPos: 0.04 ; zPos: 0.65 }
+ ListElement{ xPos: -1.85 ; yPos: -0.96 ; zPos: 4.25 }
+ ListElement{ xPos: 1.5 ; yPos: 0.04 ; zPos: -3.85 }
+ ListElement{ xPos: 0.05 ; yPos: 0.68 ; zPos: -2.45 }
+ ListElement{ xPos: -0.1 ; yPos: -0.48 ; zPos: 1.9 }
+ ListElement{ xPos: 2.1 ; yPos: -1.52 ; zPos: -4 }
+ ListElement{ xPos: 3.4 ; yPos: 0 ; zPos: -2.05 }
+ ListElement{ xPos: 0.7 ; yPos: 2.68 ; zPos: -0.85 }
+ ListElement{ xPos: 1.6 ; yPos: 1.68 ; zPos: -0.8 }
+ ListElement{ xPos: 2.1 ; yPos: -2.44 ; zPos: 1.2 }
+ ListElement{ xPos: -0.15 ; yPos: -1.88 ; zPos: -0.35 }
+ ListElement{ xPos: -0.35 ; yPos: 1.76 ; zPos: 1.6 }
+ ListElement{ xPos: -2.15 ; yPos: 0.08 ; zPos: 0.75 }
+ ListElement{ xPos: -0.15 ; yPos: -3.36 ; zPos: 0.35 }
+ ListElement{ xPos: 2.7 ; yPos: -2.84 ; zPos: 0.35 }
+ ListElement{ xPos: -1.35 ; yPos: 0 ; zPos: -0.5 }
+ ListElement{ xPos: -0.35 ; yPos: -2.56 ; zPos: -0.9 }
+ ListElement{ xPos: -2.75 ; yPos: -3.56 ; zPos: 1.55 }
+ ListElement{ xPos: -2.5 ; yPos: -0.68 ; zPos: 2.7 }
+ ListElement{ xPos: 1.35 ; yPos: 0.6 ; zPos: -2.2 }
+ ListElement{ xPos: -1.3 ; yPos: -0.24 ; zPos: -1.65 }
+ ListElement{ xPos: -1.4 ; yPos: -1.44 ; zPos: 4.45 }
+ ListElement{ xPos: -1.8 ; yPos: 3.64 ; zPos: 2.05 }
+ ListElement{ xPos: -0.65 ; yPos: 0.08 ; zPos: 3.9 }
+ ListElement{ xPos: 2.25 ; yPos: 2.96 ; zPos: -2.35 }
+ ListElement{ xPos: 1.3 ; yPos: 1.64 ; zPos: -0.05 }
+ ListElement{ xPos: 1.75 ; yPos: 2.24 ; zPos: -2.85 }
+ ListElement{ xPos: -0.05 ; yPos: -1.56 ; zPos: -0.05 }
+ ListElement{ xPos: -3.85 ; yPos: -0.8 ; zPos: -0.3 }
+ ListElement{ xPos: 0.95 ; yPos: 0.32 ; zPos: -2.5 }
+ ListElement{ xPos: -2.4 ; yPos: 0.4 ; zPos: -3.55 }
+ ListElement{ xPos: 3.7 ; yPos: 2.36 ; zPos: 3.6 }
+ ListElement{ xPos: 0.55 ; yPos: 0.88 ; zPos: 3.15 }
+ ListElement{ xPos: -1 ; yPos: -1.36 ; zPos: -2.1 }
+ ListElement{ xPos: 2.05 ; yPos: 0.44 ; zPos: -0.8 }
+ ListElement{ xPos: -4.75 ; yPos: 1.8 ; zPos: -1.7 }
+ ListElement{ xPos: 0.15 ; yPos: 2.64 ; zPos: -2.2 }
+ ListElement{ xPos: -2.05 ; yPos: -2.56 ; zPos: 1.15 }
+ ListElement{ xPos: -3.7 ; yPos: 2.12 ; zPos: 1.3 }
+ ListElement{ xPos: 1.05 ; yPos: 0 ; zPos: 4 }
+ ListElement{ xPos: -0.7 ; yPos: -0.88 ; zPos: 1.35 }
+ ListElement{ xPos: 0.95 ; yPos: -1.84 ; zPos: 0.1 }
+ ListElement{ xPos: 0.75 ; yPos: 1.16 ; zPos: 4.25 }
+ ListElement{ xPos: -1.3 ; yPos: 0.72 ; zPos: 0.65 }
+ ListElement{ xPos: 3 ; yPos: -0.08 ; zPos: -1.75 }
+ ListElement{ xPos: 2.85 ; yPos: -0.68 ; zPos: 0.65 }
+ ListElement{ xPos: -0.4 ; yPos: 3.04 ; zPos: -2.35 }
+ ListElement{ xPos: 3.05 ; yPos: -0.6 ; zPos: -0.6 }
+ ListElement{ xPos: -0.85 ; yPos: -0.44 ; zPos: 0.35 }
+ ListElement{ xPos: 0 ; yPos: -1.92 ; zPos: 0 }
+ ListElement{ xPos: -1.25 ; yPos: -1.44 ; zPos: 1.9 }
+ ListElement{ xPos: -1.6 ; yPos: 0.04 ; zPos: -0.7 }
+ ListElement{ xPos: 1.45 ; yPos: -1 ; zPos: -3.4 }
+ ListElement{ xPos: -2.8 ; yPos: -1.24 ; zPos: -3 }
+ ListElement{ xPos: 0.2 ; yPos: -0.68 ; zPos: -3.7 }
+ ListElement{ xPos: 3.35 ; yPos: 0.68 ; zPos: -1.9 }
+ ListElement{ xPos: -2.3 ; yPos: 1.08 ; zPos: -1 }
+ ListElement{ xPos: 1.5 ; yPos: 1.8 ; zPos: -1.2 }
+ ListElement{ xPos: 1.55 ; yPos: 3.64 ; zPos: 1.35 }
+ ListElement{ xPos: 1.15 ; yPos: -1.36 ; zPos: 0.75 }
+ ListElement{ xPos: -1.95 ; yPos: 0.08 ; zPos: -0.85 }
+ ListElement{ xPos: -2.25 ; yPos: -0.52 ; zPos: 1.05 }
+ ListElement{ xPos: -2.6 ; yPos: -1.32 ; zPos: 1.05 }
+ ListElement{ xPos: -0.4 ; yPos: -1.52 ; zPos: 2.45 }
+ ListElement{ xPos: -0.05 ; yPos: 0.64 ; zPos: -2.85 }
+ ListElement{ xPos: 4.45 ; yPos: -0.24 ; zPos: -0.15 }
+ ListElement{ xPos: 3.45 ; yPos: 1.72 ; zPos: -1.1 }
+ ListElement{ xPos: 3.3 ; yPos: -1.72 ; zPos: -0.25 }
+ ListElement{ xPos: 1.7 ; yPos: 2.76 ; zPos: 2 }
+ ListElement{ xPos: 2.1 ; yPos: -2.88 ; zPos: -1.75 }
+ ListElement{ xPos: -3.7 ; yPos: -0.04 ; zPos: 0.25 }
+ ListElement{ xPos: -0.45 ; yPos: -3 ; zPos: -2.95 }
+ ListElement{ xPos: 3.1 ; yPos: -2.32 ; zPos: 0.1 }
+ ListElement{ xPos: 0.7 ; yPos: -2.88 ; zPos: -3.45 }
+ ListElement{ xPos: 2.9 ; yPos: 0.4 ; zPos: -0.65 }
+ ListElement{ xPos: -1.8 ; yPos: -2.48 ; zPos: -3.5 }
+ ListElement{ xPos: 1.65 ; yPos: 3.16 ; zPos: 2.15 }
+ ListElement{ xPos: -1.25 ; yPos: -0.76 ; zPos: 2.15 }
+ ListElement{ xPos: 3.55 ; yPos: 0.28 ; zPos: 2.4 }
+ ListElement{ xPos: 0.25 ; yPos: 0.52 ; zPos: 2.3 }
+ ListElement{ xPos: -1.7 ; yPos: -1.36 ; zPos: -2.5 }
+ ListElement{ xPos: 3.8 ; yPos: 1.04 ; zPos: 0.45 }
+ ListElement{ xPos: 2.45 ; yPos: -0.72 ; zPos: -4.05 }
+ ListElement{ xPos: -0.85 ; yPos: 0.16 ; zPos: -0.55 }
+ ListElement{ xPos: 0.65 ; yPos: 0.52 ; zPos: 3.3 }
+ ListElement{ xPos: 0.85 ; yPos: -0.08 ; zPos: 0.6 }
+ ListElement{ xPos: -0.1 ; yPos: 1.12 ; zPos: -3.15 }
+ ListElement{ xPos: -2.95 ; yPos: 0.24 ; zPos: 0.8 }
+ ListElement{ xPos: 0.2 ; yPos: -0.6 ; zPos: -0.45 }
+ ListElement{ xPos: 0.15 ; yPos: 2.52 ; zPos: 1.5 }
+ ListElement{ xPos: 0.65 ; yPos: -1.4 ; zPos: -1.5 }
+ ListElement{ xPos: 4.75 ; yPos: 1.76 ; zPos: 1.15 }
+ ListElement{ xPos: -1.75 ; yPos: -1.44 ; zPos: -3.6 }
+ ListElement{ xPos: 0.85 ; yPos: 0.04 ; zPos: -0.3 }
+ ListElement{ xPos: -0.55 ; yPos: -0.28 ; zPos: -1.85 }
+ ListElement{ xPos: -4.05 ; yPos: 0.52 ; zPos: -2.35 }
+ ListElement{ xPos: -0.35 ; yPos: -0.96 ; zPos: 0.8 }
+ ListElement{ xPos: -1.8 ; yPos: -0.04 ; zPos: 0.05 }
+ ListElement{ xPos: -2 ; yPos: 0.12 ; zPos: -2.5 }
+ ListElement{ xPos: -1.85 ; yPos: -1.04 ; zPos: -1.2 }
+ ListElement{ xPos: -1 ; yPos: -3.44 ; zPos: -1 }
+ ListElement{ xPos: 2.25 ; yPos: 0.16 ; zPos: -0.05 }
+ ListElement{ xPos: -1.3 ; yPos: 0.24 ; zPos: -1.5 }
+ ListElement{ xPos: -0.05 ; yPos: -0.48 ; zPos: 1.7 }
+ ListElement{ xPos: -2.5 ; yPos: 0.28 ; zPos: -2.1 }
+ ListElement{ xPos: 3.9 ; yPos: -0.44 ; zPos: -0.85 }
+ ListElement{ xPos: -0.3 ; yPos: -1.96 ; zPos: 1.3 }
+ ListElement{ xPos: 1.35 ; yPos: 2.92 ; zPos: -1.15 }
+ ListElement{ xPos: -2.2 ; yPos: -2.2 ; zPos: -0.45 }
+ ListElement{ xPos: -0.2 ; yPos: -0.12 ; zPos: 0.15 }
+ ListElement{ xPos: 0.7 ; yPos: 1.4 ; zPos: -3 }
+ ListElement{ xPos: -0.3 ; yPos: 3.16 ; zPos: 2.65 }
+ ListElement{ xPos: -1.1 ; yPos: -1.44 ; zPos: 0.45 }
+ ListElement{ xPos: 0.25 ; yPos: -1.04 ; zPos: 4.3 }
+ ListElement{ xPos: -2.2 ; yPos: -2.48 ; zPos: 0.4 }
+ ListElement{ xPos: 0.95 ; yPos: -3.4 ; zPos: -1.2 }
+ ListElement{ xPos: 2.75 ; yPos: -1.6 ; zPos: -1.95 }
+ ListElement{ xPos: 0.75 ; yPos: -1.84 ; zPos: -0.15 }
+ ListElement{ xPos: -0.75 ; yPos: -1.84 ; zPos: 0.35 }
+ ListElement{ xPos: -2.85 ; yPos: -2.12 ; zPos: -0.95 }
+ ListElement{ xPos: -0.8 ; yPos: -2.84 ; zPos: -0.15 }
+ ListElement{ xPos: -1.95 ; yPos: -0.44 ; zPos: 2.1 }
+ ListElement{ xPos: -1.05 ; yPos: -2.48 ; zPos: -1.25 }
+ ListElement{ xPos: -2.6 ; yPos: -1.48 ; zPos: 0.8 }
+ ListElement{ xPos: 2.5 ; yPos: -1.08 ; zPos: 1.9 }
+ ListElement{ xPos: 0.45 ; yPos: 2.52 ; zPos: 0.45 }
+ ListElement{ xPos: 1.55 ; yPos: -0.16 ; zPos: 0.25 }
+ ListElement{ xPos: -2.9 ; yPos: 0.36 ; zPos: 0.35 }
+ ListElement{ xPos: 2.95 ; yPos: -1.56 ; zPos: -2.7 }
+ ListElement{ xPos: 3.4 ; yPos: -1.76 ; zPos: 3.05 }
+ ListElement{ xPos: 1.75 ; yPos: -2.84 ; zPos: 0.5 }
+ ListElement{ xPos: -0.95 ; yPos: 0.64 ; zPos: 1.1 }
+ ListElement{ xPos: 2 ; yPos: 2.04 ; zPos: 0.1 }
+ ListElement{ xPos: -0.15 ; yPos: -0.08 ; zPos: -0.9 }
+ ListElement{ xPos: 2.2 ; yPos: -1.2 ; zPos: 2.8 }
+ ListElement{ xPos: -0.8 ; yPos: 2.68 ; zPos: -0.3 }
+ ListElement{ xPos: 3.4 ; yPos: 0.84 ; zPos: -2.65 }
+ ListElement{ xPos: -0.25 ; yPos: -0.24 ; zPos: 0.45 }
+ ListElement{ xPos: 2.7 ; yPos: 1.64 ; zPos: -1.95 }
+ ListElement{ xPos: 1.35 ; yPos: -2.96 ; zPos: 0.65 }
+ ListElement{ xPos: -1.45 ; yPos: -0.2 ; zPos: 0.8 }
+ ListElement{ xPos: 1.45 ; yPos: 1.64 ; zPos: -3.45 }
+ ListElement{ xPos: -1.1 ; yPos: 3.08 ; zPos: 1.1 }
+ ListElement{ xPos: 0.8 ; yPos: 1.88 ; zPos: -2.1 }
+ ListElement{ xPos: 1.15 ; yPos: 3.04 ; zPos: -0.75 }
+ ListElement{ xPos: -0.45 ; yPos: 1.36 ; zPos: -0.15 }
+ ListElement{ xPos: -2.8 ; yPos: -1.24 ; zPos: 2.55 }
+ ListElement{ xPos: 4.4 ; yPos: 2.04 ; zPos: 1.25 }
+ ListElement{ xPos: -0.95 ; yPos: 1.24 ; zPos: 3.65 }
+ ListElement{ xPos: 3.45 ; yPos: 1.32 ; zPos: 2.5 }
+ ListElement{ xPos: -3 ; yPos: 1.12 ; zPos: -1.2 }
+ ListElement{ xPos: -2.45 ; yPos: 0.68 ; zPos: 4.35 }
+ ListElement{ xPos: 0.55 ; yPos: -0.12 ; zPos: -3.95 }
+ ListElement{ xPos: -0.35 ; yPos: 3.08 ; zPos: -0.3 }
+ ListElement{ xPos: 1.35 ; yPos: -2.92 ; zPos: -3.3 }
+ ListElement{ xPos: 0.5 ; yPos: -0.2 ; zPos: 0.95 }
+ ListElement{ xPos: 1.2 ; yPos: -0.24 ; zPos: -0.75 }
+ ListElement{ xPos: -1.2 ; yPos: 2.16 ; zPos: 3.35 }
+ ListElement{ xPos: -3.35 ; yPos: 2.36 ; zPos: 3.45 }
+ ListElement{ xPos: 0.95 ; yPos: -0.88 ; zPos: 1.25 }
+ ListElement{ xPos: 0.1 ; yPos: 1.24 ; zPos: 0 }
+ ListElement{ xPos: 1.8 ; yPos: 1.56 ; zPos: -1.05 }
+ ListElement{ xPos: -1.35 ; yPos: 3.8 ; zPos: -1.45 }
+ ListElement{ xPos: 1.45 ; yPos: -2.64 ; zPos: 0.5 }
+ ListElement{ xPos: 2.05 ; yPos: 1.16 ; zPos: 1.4 }
+ ListElement{ xPos: 2.9 ; yPos: -2.4 ; zPos: 0.15 }
+ ListElement{ xPos: -1.1 ; yPos: 0.76 ; zPos: -1.65 }
+ ListElement{ xPos: -2.5 ; yPos: -2.4 ; zPos: 1.05 }
+ ListElement{ xPos: 3.55 ; yPos: -1.8 ; zPos: -2.55 }
+ ListElement{ xPos: -0.8 ; yPos: 1.24 ; zPos: -3.75 }
+ ListElement{ xPos: -1.35 ; yPos: -2.48 ; zPos: 2.85 }
+ ListElement{ xPos: -2.1 ; yPos: 1.48 ; zPos: -0.15 }
+ ListElement{ xPos: -0.45 ; yPos: 2.68 ; zPos: 2.85 }
+ ListElement{ xPos: 0.8 ; yPos: 1.4 ; zPos: -3.35 }
+ ListElement{ xPos: -2.2 ; yPos: -2.24 ; zPos: 0.35 }
+ ListElement{ xPos: 1.4 ; yPos: -2.32 ; zPos: 0.55 }
+ ListElement{ xPos: 3.65 ; yPos: 1.12 ; zPos: -4.5 }
+ ListElement{ xPos: 0.1 ; yPos: -0.68 ; zPos: 1.85 }
+ ListElement{ xPos: -1.95 ; yPos: -0.56 ; zPos: -2.05 }
+ ListElement{ xPos: 1.85 ; yPos: 1.32 ; zPos: -4.4 }
+ ListElement{ xPos: -0.6 ; yPos: 0.64 ; zPos: -0.3 }
+ ListElement{ xPos: -1.55 ; yPos: 1.52 ; zPos: -3.75 }
+ ListElement{ xPos: 1.85 ; yPos: 0.48 ; zPos: 0.65 }
+ ListElement{ xPos: 3.35 ; yPos: 2.52 ; zPos: 1.9 }
+ ListElement{ xPos: 0.4 ; yPos: 1.4 ; zPos: -1.3 }
+ ListElement{ xPos: -2.7 ; yPos: 0.84 ; zPos: 1.6 }
+ ListElement{ xPos: -0.5 ; yPos: 1 ; zPos: 0.4 }
+ ListElement{ xPos: 2.25 ; yPos: -1 ; zPos: 1.25 }
+ ListElement{ xPos: 1.7 ; yPos: 2.4 ; zPos: -0.95 }
+ ListElement{ xPos: -3.55 ; yPos: 1.04 ; zPos: 3.95 }
+ ListElement{ xPos: 0.9 ; yPos: 1.28 ; zPos: 2.05 }
+ ListElement{ xPos: -3.15 ; yPos: 1.96 ; zPos: -0.3 }
+ ListElement{ xPos: 1.35 ; yPos: -0.92 ; zPos: -1 }
+ ListElement{ xPos: -3.1 ; yPos: -3.08 ; zPos: 0.9 }
+ ListElement{ xPos: 1.25 ; yPos: -2.44 ; zPos: 2.25 }
+ ListElement{ xPos: -3.8 ; yPos: 0.76 ; zPos: -1.05 }
+ ListElement{ xPos: -1.05 ; yPos: -3.8 ; zPos: -0.8 }
+ ListElement{ xPos: -4 ; yPos: 1.24 ; zPos: -2.6 }
+ ListElement{ xPos: 2.45 ; yPos: -1.84 ; zPos: 0.35 }
+ ListElement{ xPos: -1.5 ; yPos: 2.16 ; zPos: 4.3 }
+ ListElement{ xPos: 2.1 ; yPos: -1.8 ; zPos: -0.95 }
+ ListElement{ xPos: -1.8 ; yPos: -0.2 ; zPos: 0.2 }
+ ListElement{ xPos: 0.75 ; yPos: 1.04 ; zPos: -1.3 }
+ ListElement{ xPos: 2.85 ; yPos: 1.6 ; zPos: -2.05 }
+ ListElement{ xPos: -2 ; yPos: -0.2 ; zPos: -2.15 }
+ ListElement{ xPos: 1.75 ; yPos: 2.28 ; zPos: 0.75 }
+ ListElement{ xPos: 1.95 ; yPos: -0.68 ; zPos: -2.3 }
+ ListElement{ xPos: -0.8 ; yPos: 1.08 ; zPos: 1.65 }
+ ListElement{ xPos: -1.85 ; yPos: 1.8 ; zPos: -0.75 }
+ ListElement{ xPos: -3.05 ; yPos: 2.44 ; zPos: 0.35 }
+ ListElement{ xPos: -0.4 ; yPos: -3 ; zPos: -2.85 }
+ ListElement{ xPos: -0.95 ; yPos: 0.64 ; zPos: 2.15 }
+ ListElement{ xPos: -1.35 ; yPos: -0.2 ; zPos: -0.8 }
+ ListElement{ xPos: 2.1 ; yPos: -0.04 ; zPos: 0.65 }
+ ListElement{ xPos: 2.6 ; yPos: 0.2 ; zPos: 2.75 }
+ ListElement{ xPos: 1.65 ; yPos: -0.16 ; zPos: 1.65 }
+ ListElement{ xPos: -1.85 ; yPos: 0.68 ; zPos: 1.95 }
+ ListElement{ xPos: -3.05 ; yPos: -2.28 ; zPos: 0.1 }
+ ListElement{ xPos: 3.4 ; yPos: -1.88 ; zPos: 3 }
+ ListElement{ xPos: -0.75 ; yPos: 0.36 ; zPos: 1.5 }
+ ListElement{ xPos: 2.9 ; yPos: -1 ; zPos: -1.85 }
+ ListElement{ xPos: 0.4 ; yPos: 1.08 ; zPos: 0.8 }
+ ListElement{ xPos: -1.05 ; yPos: 1.04 ; zPos: 2.15 }
+ ListElement{ xPos: 2.6 ; yPos: -2.08 ; zPos: -0.1 }
+ ListElement{ xPos: 0 ; yPos: -2.84 ; zPos: -0.95 }
+ ListElement{ xPos: 0.4 ; yPos: 1.88 ; zPos: 2.05 }
+ ListElement{ xPos: -3.1 ; yPos: -2.76 ; zPos: -2.75 }
+ ListElement{ xPos: -2.65 ; yPos: 3.52 ; zPos: -1.2 }
+ ListElement{ xPos: -4.3 ; yPos: -0.28 ; zPos: 3 }
+ ListElement{ xPos: -2.8 ; yPos: -2.56 ; zPos: -2.85 }
+ ListElement{ xPos: -0.15 ; yPos: 2.72 ; zPos: -2.8 }
+ ListElement{ xPos: -0.95 ; yPos: -0.6 ; zPos: 1.05 }
+ ListElement{ xPos: 1.9 ; yPos: 2.56 ; zPos: 1.25 }
+ ListElement{ xPos: -0.85 ; yPos: 0.24 ; zPos: 0.05 }
+ ListElement{ xPos: 2.4 ; yPos: 2.56 ; zPos: -1.2 }
+ ListElement{ xPos: 2.35 ; yPos: -1.08 ; zPos: 2.7 }
+ ListElement{ xPos: -2.1 ; yPos: -0.76 ; zPos: 2.8 }
+ ListElement{ xPos: 1.4 ; yPos: 1 ; zPos: 2.35 }
+ ListElement{ xPos: -0.9 ; yPos: 1.72 ; zPos: 4.1 }
+ ListElement{ xPos: 3.7 ; yPos: -1.4 ; zPos: 2.05 }
+ ListElement{ xPos: -0.25 ; yPos: 0.76 ; zPos: -1.4 }
+ ListElement{ xPos: -1.6 ; yPos: 0.32 ; zPos: 0.05 }
+ ListElement{ xPos: 2.5 ; yPos: -3.08 ; zPos: 0.85 }
+ ListElement{ xPos: 0.05 ; yPos: -1.96 ; zPos: 1.55 }
+ ListElement{ xPos: -3.7 ; yPos: 0.84 ; zPos: -0.25 }
+ ListElement{ xPos: 2.35 ; yPos: 0.6 ; zPos: -1.5 }
+ ListElement{ xPos: 1.1 ; yPos: 2.64 ; zPos: -1.45 }
+ ListElement{ xPos: 2.55 ; yPos: -1.56 ; zPos: 2.05 }
+ ListElement{ xPos: -2.15 ; yPos: 3.56 ; zPos: 3.25 }
+ ListElement{ xPos: -0.55 ; yPos: -1.24 ; zPos: 2.65 }
+ ListElement{ xPos: -0.3 ; yPos: 1.32 ; zPos: 0.85 }
+ ListElement{ xPos: -1.9 ; yPos: 1.24 ; zPos: 1.15 }
+ ListElement{ xPos: 0.35 ; yPos: -2.44 ; zPos: -1.35 }
+ ListElement{ xPos: 2.9 ; yPos: -1.08 ; zPos: -4.3 }
+ ListElement{ xPos: 1.8 ; yPos: -0.44 ; zPos: 1.25 }
+ ListElement{ xPos: -0.6 ; yPos: -1.08 ; zPos: -0.6 }
+ ListElement{ xPos: -0.3 ; yPos: -0.88 ; zPos: -1.45 }
+ ListElement{ xPos: -1 ; yPos: 2.12 ; zPos: 2.3 }
+ ListElement{ xPos: 3.15 ; yPos: 0.52 ; zPos: -2.8 }
+ ListElement{ xPos: 0.45 ; yPos: 2.48 ; zPos: -1.3 }
+ ListElement{ xPos: 0.5 ; yPos: -0.84 ; zPos: 0.7 }
+ ListElement{ xPos: -0.6 ; yPos: -0.44 ; zPos: -1.35 }
+ ListElement{ xPos: -1.7 ; yPos: -0.12 ; zPos: -2.55 }
+ ListElement{ xPos: -0.5 ; yPos: 0.52 ; zPos: 1.4 }
+ ListElement{ xPos: 4 ; yPos: -1.68 ; zPos: -0.1 }
+ ListElement{ xPos: 1.4 ; yPos: -1.64 ; zPos: 1.35 }
+ ListElement{ xPos: 0.05 ; yPos: 0.28 ; zPos: -2.2 }
+ ListElement{ xPos: 1.55 ; yPos: -1.2 ; zPos: 0.45 }
+ ListElement{ xPos: 3.1 ; yPos: 3.64 ; zPos: 1.45 }
+ ListElement{ xPos: -1.55 ; yPos: 2.16 ; zPos: 0.15 }
+ ListElement{ xPos: 3.9 ; yPos: -2.56 ; zPos: -1.25 }
+ ListElement{ xPos: 4.15 ; yPos: 0.64 ; zPos: 2.65 }
+ ListElement{ xPos: -2.8 ; yPos: 0.56 ; zPos: -1.35 }
+ ListElement{ xPos: 1.3 ; yPos: 1.28 ; zPos: 0.8 }
+ ListElement{ xPos: -2.3 ; yPos: -3.08 ; zPos: 1.2 }
+ ListElement{ xPos: 0.5 ; yPos: -0.36 ; zPos: -2.4 }
+ ListElement{ xPos: 0.6 ; yPos: 0.52 ; zPos: 2.75 }
+ ListElement{ xPos: 3.9 ; yPos: -0.52 ; zPos: -4.25 }
+ ListElement{ xPos: -0.5 ; yPos: 1.28 ; zPos: -0.05 }
+ ListElement{ xPos: -0.25 ; yPos: -3.84 ; zPos: 3.15 }
+ ListElement{ xPos: -0.9 ; yPos: -1.72 ; zPos: -3.15 }
+ ListElement{ xPos: 0.85 ; yPos: 0.84 ; zPos: -1.7 }
+ ListElement{ xPos: -3.35 ; yPos: 0.72 ; zPos: 2.15 }
+ ListElement{ xPos: -1.6 ; yPos: 0.6 ; zPos: 1.65 }
+ ListElement{ xPos: -3.6 ; yPos: 0.8 ; zPos: 2.6 }
+ ListElement{ xPos: -0.7 ; yPos: 1.96 ; zPos: -0.9 }
+ ListElement{ xPos: -2.4 ; yPos: 1.32 ; zPos: -2.75 }
+ ListElement{ xPos: -1.75 ; yPos: 0.72 ; zPos: -0.85 }
+ ListElement{ xPos: -2.7 ; yPos: 0.6 ; zPos: -2.5 }
+ ListElement{ xPos: -2.2 ; yPos: -3.4 ; zPos: -1.85 }
+ ListElement{ xPos: 0.85 ; yPos: 2.2 ; zPos: -3.75 }
+ ListElement{ xPos: -3.85 ; yPos: 2.44 ; zPos: 4.3 }
+ ListElement{ xPos: -3.65 ; yPos: 0.52 ; zPos: 0.2 }
+ ListElement{ xPos: -4.35 ; yPos: -0.52 ; zPos: 1.5 }
+ ListElement{ xPos: 1.45 ; yPos: -0.08 ; zPos: -0.4 }
+ ListElement{ xPos: 1.85 ; yPos: -0.76 ; zPos: -4.6 }
+ ListElement{ xPos: 0.95 ; yPos: 0.52 ; zPos: -1 }
+ ListElement{ xPos: -2.5 ; yPos: -0.88 ; zPos: -0.3 }
+ ListElement{ xPos: -2.9 ; yPos: 1.68 ; zPos: -1.15 }
+ ListElement{ xPos: -3.2 ; yPos: 0.2 ; zPos: 1.1 }
+ ListElement{ xPos: 0.9 ; yPos: -0.2 ; zPos: 0.7 }
+ ListElement{ xPos: 3.6 ; yPos: 1.08 ; zPos: -2.15 }
+ ListElement{ xPos: -0.8 ; yPos: 1.72 ; zPos: 2.85 }
+ ListElement{ xPos: 0.3 ; yPos: 1.76 ; zPos: 0.9 }
+ ListElement{ xPos: -1.3 ; yPos: -0.56 ; zPos: -2.3 }
+ ListElement{ xPos: -2.8 ; yPos: 0.4 ; zPos: 4.2 }
+ ListElement{ xPos: 1 ; yPos: -0.32 ; zPos: 0.35 }
+ ListElement{ xPos: -0.6 ; yPos: -0.24 ; zPos: 4.05 }
+ ListElement{ xPos: -2 ; yPos: -1.84 ; zPos: -2.2 }
+ ListElement{ xPos: -1.95 ; yPos: -0.8 ; zPos: 1.85 }
+ ListElement{ xPos: -0.05 ; yPos: -0.16 ; zPos: 3.85 }
+ ListElement{ xPos: 0.15 ; yPos: -2.64 ; zPos: 1.7 }
+ ListElement{ xPos: -3.85 ; yPos: 1.4 ; zPos: 0.1 }
+ ListElement{ xPos: 0.25 ; yPos: 1 ; zPos: -2.45 }
+ ListElement{ xPos: -4.1 ; yPos: 0 ; zPos: -0.1 }
+ ListElement{ xPos: -1.4 ; yPos: -0.32 ; zPos: -0.8 }
+ ListElement{ xPos: -1.9 ; yPos: -1.84 ; zPos: -3 }
+ ListElement{ xPos: 0.35 ; yPos: 1.32 ; zPos: -3.95 }
+ ListElement{ xPos: -2.6 ; yPos: -1.04 ; zPos: 4.25 }
+ ListElement{ xPos: -3.1 ; yPos: -0.96 ; zPos: -1.65 }
+ ListElement{ xPos: -0.55 ; yPos: -1.16 ; zPos: 4.05 }
+ ListElement{ xPos: -3.1 ; yPos: -1 ; zPos: -0.5 }
+ ListElement{ xPos: 2.25 ; yPos: 1.24 ; zPos: 3.4 }
+ ListElement{ xPos: -1.2 ; yPos: -1.24 ; zPos: -4.05 }
+ ListElement{ xPos: 3.05 ; yPos: 1.92 ; zPos: 1 }
+ ListElement{ xPos: 0.55 ; yPos: -2.76 ; zPos: -3.25 }
+ ListElement{ xPos: -2.05 ; yPos: 1.6 ; zPos: 0.6 }
+ ListElement{ xPos: -0.45 ; yPos: 0.12 ; zPos: -0.15 }
+ ListElement{ xPos: 2.1 ; yPos: 2.96 ; zPos: 1.1 }
+ ListElement{ xPos: 0.05 ; yPos: -2.08 ; zPos: -0.7 }
+ ListElement{ xPos: -3.15 ; yPos: 1.08 ; zPos: -0.1 }
+ ListElement{ xPos: -1.95 ; yPos: 0.44 ; zPos: 0.6 }
+ ListElement{ xPos: 0.15 ; yPos: -0.32 ; zPos: 2.85 }
+ ListElement{ xPos: 0.55 ; yPos: 1 ; zPos: 0.55 }
+ ListElement{ xPos: -2.05 ; yPos: 1.84 ; zPos: -1 }
+ ListElement{ xPos: -0.25 ; yPos: 0.36 ; zPos: 1.2 }
+ ListElement{ xPos: 4.6 ; yPos: -0.2 ; zPos: 0.6 }
+ ListElement{ xPos: -1.95 ; yPos: -0.8 ; zPos: 0.05 }
+ ListElement{ xPos: -0.35 ; yPos: -1.04 ; zPos: 0.8 }
+ ListElement{ xPos: 0.7 ; yPos: 3.04 ; zPos: -3.55 }
+ ListElement{ xPos: -0.15 ; yPos: 1.12 ; zPos: -2.5 }
+ ListElement{ xPos: -0.5 ; yPos: -1.68 ; zPos: 1.5 }
+ ListElement{ xPos: 1 ; yPos: -0.44 ; zPos: -1.5 }
+ ListElement{ xPos: 4.05 ; yPos: -1.32 ; zPos: 2.85 }
+ ListElement{ xPos: 1.6 ; yPos: -1.56 ; zPos: 2.15 }
+ ListElement{ xPos: 0.3 ; yPos: -1.16 ; zPos: 1.25 }
+ ListElement{ xPos: -2.65 ; yPos: -1.28 ; zPos: 0.45 }
+ ListElement{ xPos: 0 ; yPos: 1.52 ; zPos: -1.35 }
+ ListElement{ xPos: 1.3 ; yPos: 1.68 ; zPos: -2.75 }
+ ListElement{ xPos: 1.25 ; yPos: 0.4 ; zPos: 0.3 }
+ ListElement{ xPos: -0.95 ; yPos: 3.68 ; zPos: 1.75 }
+ ListElement{ xPos: -3.55 ; yPos: 0.48 ; zPos: 1.15 }
+ ListElement{ xPos: -1.7 ; yPos: -2.64 ; zPos: -3.3 }
+ ListElement{ xPos: -2.2 ; yPos: -2.4 ; zPos: 1.05 }
+ ListElement{ xPos: 0.1 ; yPos: 0.44 ; zPos: -1.05 }
+ ListElement{ xPos: 2.05 ; yPos: 0.4 ; zPos: 0.8 }
+ ListElement{ xPos: 0.4 ; yPos: 0.04 ; zPos: 4.2 }
+ ListElement{ xPos: -1.25 ; yPos: 0.76 ; zPos: 0.45 }
+ ListElement{ xPos: 1.6 ; yPos: 2.04 ; zPos: -2.95 }
+ ListElement{ xPos: -2.05 ; yPos: -0.44 ; zPos: 1.35 }
+ ListElement{ xPos: -3.25 ; yPos: 0.44 ; zPos: 2.15 }
+ ListElement{ xPos: -1.75 ; yPos: 0.04 ; zPos: 2.35 }
+ ListElement{ xPos: -4.15 ; yPos: 0.68 ; zPos: 0.8 }
+ ListElement{ xPos: -1.6 ; yPos: 1.12 ; zPos: 0.55 }
+ ListElement{ xPos: -0.15 ; yPos: 3.4 ; zPos: 3.05 }
+ ListElement{ xPos: -0.2 ; yPos: 0.04 ; zPos: -1.45 }
+ ListElement{ xPos: 2.9 ; yPos: -0.36 ; zPos: 1.45 }
+ ListElement{ xPos: -1.4 ; yPos: 1.56 ; zPos: 1 }
+ ListElement{ xPos: 3.4 ; yPos: -1.2 ; zPos: -4.2 }
+ ListElement{ xPos: -1.1 ; yPos: 0.6 ; zPos: 1.55 }
+ ListElement{ xPos: -0.35 ; yPos: 1.52 ; zPos: -0.35 }
+ ListElement{ xPos: 0.3 ; yPos: 0.96 ; zPos: 1.8 }
+ ListElement{ xPos: 2.5 ; yPos: -1.84 ; zPos: 1.65 }
+ ListElement{ xPos: -0.6 ; yPos: 2.24 ; zPos: -2.9 }
+ ListElement{ xPos: -0.25 ; yPos: -2.32 ; zPos: -0.7 }
+ ListElement{ xPos: 3.35 ; yPos: -2.24 ; zPos: 1.5 }
+ ListElement{ xPos: 1.6 ; yPos: 1.96 ; zPos: 3.9 }
+ ListElement{ xPos: 1.8 ; yPos: 0.68 ; zPos: -1.55 }
+ ListElement{ xPos: 2.7 ; yPos: 1.76 ; zPos: -2.9 }
+ ListElement{ xPos: -2.35 ; yPos: -0.6 ; zPos: -3.9 }
+ ListElement{ xPos: -1.85 ; yPos: 1.96 ; zPos: -1.2 }
+ ListElement{ xPos: -4.05 ; yPos: 1.12 ; zPos: -2.75 }
+ ListElement{ xPos: -2.85 ; yPos: -1.36 ; zPos: 0.65 }
+ ListElement{ xPos: -1.75 ; yPos: 2.12 ; zPos: -1.35 }
+ ListElement{ xPos: -2.95 ; yPos: -0.08 ; zPos: 3.65 }
+ ListElement{ xPos: -2.4 ; yPos: 2 ; zPos: 4.5 }
+ ListElement{ xPos: -0.05 ; yPos: 0.04 ; zPos: 0.35 }
+ ListElement{ xPos: -3.2 ; yPos: 0.16 ; zPos: 1.1 }
+ ListElement{ xPos: 3 ; yPos: 2.32 ; zPos: 1.3 }
+ ListElement{ xPos: 4.9 ; yPos: -1.88 ; zPos: 0.45 }
+ ListElement{ xPos: -2.35 ; yPos: -1.44 ; zPos: -0.7 }
+ ListElement{ xPos: 2.4 ; yPos: -0.6 ; zPos: 0.1 }
+ ListElement{ xPos: 1 ; yPos: 1.6 ; zPos: 1 }
+ ListElement{ xPos: 1.3 ; yPos: -0.4 ; zPos: -0.4 }
+ ListElement{ xPos: -0.9 ; yPos: -2.72 ; zPos: -3.6 }
+ ListElement{ xPos: 1.2 ; yPos: -0.44 ; zPos: 2.1 }
+ ListElement{ xPos: 0.65 ; yPos: 3 ; zPos: -1.55 }
+ ListElement{ xPos: -1.55 ; yPos: -2.64 ; zPos: 0.55 }
+ ListElement{ xPos: 1.9 ; yPos: -0.32 ; zPos: -2.1 }
+ ListElement{ xPos: 1.35 ; yPos: -2.84 ; zPos: -0.4 }
+ ListElement{ xPos: 0.25 ; yPos: -2.68 ; zPos: -0.2 }
+ ListElement{ xPos: -2.05 ; yPos: -1.6 ; zPos: -3.05 }
+ ListElement{ xPos: 1.65 ; yPos: -0.44 ; zPos: -0.75 }
+ ListElement{ xPos: -1.25 ; yPos: 0.96 ; zPos: -4.15 }
+ ListElement{ xPos: -4.2 ; yPos: -0.56 ; zPos: 1.45 }
+ ListElement{ xPos: 0.95 ; yPos: -2.4 ; zPos: -1.6 }
+ ListElement{ xPos: -0.05 ; yPos: 0.88 ; zPos: 3.15 }
+ ListElement{ xPos: -1.65 ; yPos: -0.88 ; zPos: -3.85 }
+ ListElement{ xPos: 0.35 ; yPos: 0.2 ; zPos: 4.25 }
+ ListElement{ xPos: 0.8 ; yPos: 0.84 ; zPos: 3.1 }
+ ListElement{ xPos: 1.85 ; yPos: -2.16 ; zPos: -2.2 }
+ ListElement{ xPos: -2.05 ; yPos: 0.16 ; zPos: -0.4 }
+ ListElement{ xPos: 0.85 ; yPos: 1.84 ; zPos: -1.45 }
+ ListElement{ xPos: 1.6 ; yPos: 1.56 ; zPos: -1.45 }
+ ListElement{ xPos: 1.25 ; yPos: -0.84 ; zPos: -2.95 }
+ ListElement{ xPos: -1.9 ; yPos: 2.32 ; zPos: -1.8 }
+ ListElement{ xPos: -0.6 ; yPos: 2.4 ; zPos: -3.65 }
+ ListElement{ xPos: 4.45 ; yPos: 0.52 ; zPos: 3 }
+ ListElement{ xPos: 2.7 ; yPos: 0.28 ; zPos: -1.8 }
+ ListElement{ xPos: 0.35 ; yPos: -2.56 ; zPos: 3.9 }
+ ListElement{ xPos: -0.95 ; yPos: -1.28 ; zPos: 1.6 }
+ ListElement{ xPos: 0.4 ; yPos: -0.2 ; zPos: -2.15 }
+ ListElement{ xPos: -0.05 ; yPos: -1.08 ; zPos: -4.1 }
+ ListElement{ xPos: 3.1 ; yPos: -1.2 ; zPos: -3.85 }
+ ListElement{ xPos: 0.75 ; yPos: 0.12 ; zPos: -3.1 }
+ ListElement{ xPos: -1.65 ; yPos: -1.16 ; zPos: -1.65 }
+ ListElement{ xPos: -1.65 ; yPos: 3.84 ; zPos: 4.65 }
+ ListElement{ xPos: 2.8 ; yPos: -2.6 ; zPos: -1.65 }
+ ListElement{ xPos: 1.2 ; yPos: -2.76 ; zPos: -2.2 }
+ ListElement{ xPos: 0.4 ; yPos: 3.04 ; zPos: 3.45 }
+ ListElement{ xPos: -3.6 ; yPos: 1.04 ; zPos: -0.6 }
+ ListElement{ xPos: 2.25 ; yPos: 1.64 ; zPos: 2.9 }
+ ListElement{ xPos: -3.05 ; yPos: -0.76 ; zPos: -2.3 }
+ ListElement{ xPos: -2.85 ; yPos: 2.12 ; zPos: -0.65 }
+ ListElement{ xPos: -0.1 ; yPos: 0.08 ; zPos: -1.6 }
+ ListElement{ xPos: -1.55 ; yPos: -1.36 ; zPos: 2.2 }
+ ListElement{ xPos: -0.4 ; yPos: 1.56 ; zPos: -0.3 }
+ ListElement{ xPos: 0.3 ; yPos: -2.36 ; zPos: 1.35 }
+ ListElement{ xPos: 0.2 ; yPos: -0.6 ; zPos: 1.6 }
+ ListElement{ xPos: -0.65 ; yPos: 1.96 ; zPos: -3.1 }
+ ListElement{ xPos: 0.6 ; yPos: -1.04 ; zPos: 2.5 }
+ ListElement{ xPos: -2 ; yPos: -1.08 ; zPos: -0.85 }
+ ListElement{ xPos: 1.45 ; yPos: -2.56 ; zPos: -2.6 }
+ ListElement{ xPos: -2.45 ; yPos: 0.04 ; zPos: -2.85 }
+ ListElement{ xPos: -0.05 ; yPos: -1.24 ; zPos: -1.25 }
+ ListElement{ xPos: -2.95 ; yPos: -1.8 ; zPos: -2.05 }
+ ListElement{ xPos: 4.3 ; yPos: -0.96 ; zPos: 3.5 }
+ ListElement{ xPos: 2.15 ; yPos: -1.6 ; zPos: -0.8 }
+ ListElement{ xPos: -1.2 ; yPos: -1 ; zPos: 1.75 }
+ ListElement{ xPos: -1.5 ; yPos: -1.28 ; zPos: -0.25 }
+ ListElement{ xPos: 2.5 ; yPos: -2.6 ; zPos: -0.3 }
+ ListElement{ xPos: -1.6 ; yPos: -0.36 ; zPos: 3.15 }
+ ListElement{ xPos: -0.1 ; yPos: -0.64 ; zPos: -3.6 }
+ ListElement{ xPos: -1.9 ; yPos: 1.88 ; zPos: 2.1 }
+ ListElement{ xPos: -2.85 ; yPos: -2.28 ; zPos: -1.55 }
+ ListElement{ xPos: -1.3 ; yPos: -0.48 ; zPos: -2.2 }
+ ListElement{ xPos: 0.5 ; yPos: 0.28 ; zPos: 0.65 }
+ ListElement{ xPos: -1.1 ; yPos: 0.24 ; zPos: -2.25 }
+ ListElement{ xPos: 1.75 ; yPos: -1.4 ; zPos: 0.3 }
+ ListElement{ xPos: 1.5 ; yPos: -1.32 ; zPos: -2.8 }
+ ListElement{ xPos: -0.95 ; yPos: 2.08 ; zPos: -0.3 }
+ ListElement{ xPos: 2.75 ; yPos: 0.24 ; zPos: 0.75 }
+ ListElement{ xPos: 0.75 ; yPos: 0.56 ; zPos: -1.5 }
+ ListElement{ xPos: 1.1 ; yPos: -3.76 ; zPos: 0 }
+ ListElement{ xPos: 0.3 ; yPos: -1.2 ; zPos: 2.6 }
+ ListElement{ xPos: -0.25 ; yPos: 2.68 ; zPos: -3 }
+ ListElement{ xPos: 1.8 ; yPos: -0.76 ; zPos: 4.4 }
+ ListElement{ xPos: 0 ; yPos: 0 ; zPos: -1.9 }
+ ListElement{ xPos: -1.6 ; yPos: -0.12 ; zPos: -2.4 }
+ ListElement{ xPos: -1.25 ; yPos: 2.36 ; zPos: -2.9 }
+ ListElement{ xPos: 2.65 ; yPos: 0.04 ; zPos: 0.1 }
+ ListElement{ xPos: -3.35 ; yPos: 0.08 ; zPos: -1.3 }
+ ListElement{ xPos: 3.35 ; yPos: 0.04 ; zPos: 2.8 }
+ ListElement{ xPos: 4.45 ; yPos: 1.24 ; zPos: -0.95 }
+ ListElement{ xPos: -0.1 ; yPos: -1.52 ; zPos: -4.6 }
+ ListElement{ xPos: 1.1 ; yPos: 1.72 ; zPos: -3.2 }
+ ListElement{ xPos: -0.45 ; yPos: 1.92 ; zPos: 1.2 }
+ ListElement{ xPos: -0.7 ; yPos: -0.16 ; zPos: 0.8 }
+ ListElement{ xPos: 2.3 ; yPos: 0.2 ; zPos: 2.75 }
+ ListElement{ xPos: 1.7 ; yPos: 2.08 ; zPos: -0.95 }
+ ListElement{ xPos: 2.1 ; yPos: 1.56 ; zPos: 1.2 }
+ ListElement{ xPos: 3.05 ; yPos: -1.56 ; zPos: -0.45 }
+ ListElement{ xPos: 0.1 ; yPos: -3.08 ; zPos: -1.3 }
+ ListElement{ xPos: 1.65 ; yPos: -0.32 ; zPos: -0.8 }
+ ListElement{ xPos: 2.05 ; yPos: -1.8 ; zPos: 1.8 }
+ ListElement{ xPos: -0.55 ; yPos: 1.52 ; zPos: -0.8 }
+ ListElement{ xPos: -2.05 ; yPos: 1.52 ; zPos: 2.3 }
+ ListElement{ xPos: -2.35 ; yPos: -3.28 ; zPos: 3.5 }
+ ListElement{ xPos: -2.25 ; yPos: 2.56 ; zPos: -1.9 }
+ ListElement{ xPos: 0.3 ; yPos: -3.28 ; zPos: 3.4 }
+ ListElement{ xPos: 0.95 ; yPos: -1.68 ; zPos: -1.8 }
+ ListElement{ xPos: 2 ; yPos: 2.2 ; zPos: -0.05 }
+ ListElement{ xPos: 3 ; yPos: 2.92 ; zPos: -2.1 }
+ ListElement{ xPos: 2.55 ; yPos: 3.12 ; zPos: 2.95 }
+ ListElement{ xPos: -0.5 ; yPos: -2.8 ; zPos: -0.3 }
+ ListElement{ xPos: 0.35 ; yPos: 0.64 ; zPos: 0.35 }
+ ListElement{ xPos: -1.15 ; yPos: -0.16 ; zPos: -0.45 }
+ ListElement{ xPos: -2.4 ; yPos: -0.88 ; zPos: -0.3 }
+ ListElement{ xPos: 1.55 ; yPos: -1.48 ; zPos: -0.5 }
+ ListElement{ xPos: -0.85 ; yPos: -2.08 ; zPos: -1.1 }
+ ListElement{ xPos: 0.5 ; yPos: -0.8 ; zPos: 1.35 }
+ ListElement{ xPos: -2.15 ; yPos: 1.04 ; zPos: -3.25 }
+ ListElement{ xPos: -0.65 ; yPos: -1.32 ; zPos: -3.1 }
+ ListElement{ xPos: 0.35 ; yPos: -0.84 ; zPos: -1.7 }
+ ListElement{ xPos: -2.1 ; yPos: 0.96 ; zPos: 2.5 }
+ ListElement{ xPos: 1 ; yPos: -0.6 ; zPos: -3.15 }
+ ListElement{ xPos: -1.35 ; yPos: 0.76 ; zPos: 3.45 }
+ ListElement{ xPos: -3.05 ; yPos: -1.2 ; zPos: 1.45 }
+ ListElement{ xPos: -1.6 ; yPos: 0.8 ; zPos: -1.9 }
+ ListElement{ xPos: 0.7 ; yPos: 1.68 ; zPos: 2.3 }
+ ListElement{ xPos: 0 ; yPos: -0.76 ; zPos: -0.95 }
+ ListElement{ xPos: -2.3 ; yPos: -0.16 ; zPos: -1.25 }
+ ListElement{ xPos: -0.55 ; yPos: -2.28 ; zPos: 1.5 }
+ ListElement{ xPos: 0 ; yPos: -0.24 ; zPos: 0.55 }
+ ListElement{ xPos: -0.4 ; yPos: -0.92 ; zPos: -0.7 }
+ ListElement{ xPos: -2.6 ; yPos: -1.64 ; zPos: -0.9 }
+ ListElement{ xPos: 0.25 ; yPos: -1.24 ; zPos: 0.9 }
+ ListElement{ xPos: 0.7 ; yPos: 2.88 ; zPos: 0.4 }
+ ListElement{ xPos: -0.6 ; yPos: -1.84 ; zPos: -2.4 }
+ ListElement{ xPos: 0.5 ; yPos: 2.8 ; zPos: 1.6 }
+ ListElement{ xPos: -3.2 ; yPos: 3.28 ; zPos: -3.45 }
+ ListElement{ xPos: -1.45 ; yPos: 2.52 ; zPos: 2.7 }
+ ListElement{ xPos: 1 ; yPos: -2.68 ; zPos: 3 }
+ ListElement{ xPos: -0.6 ; yPos: -2.56 ; zPos: 1.35 }
+ ListElement{ xPos: 1.95 ; yPos: -1.48 ; zPos: -3.85 }
+ ListElement{ xPos: 3.85 ; yPos: 0.08 ; zPos: -1.65 }
+ ListElement{ xPos: -1.1 ; yPos: -1.6 ; zPos: -1.05 }
+ ListElement{ xPos: 1.25 ; yPos: -0.04 ; zPos: 1.35 }
+ ListElement{ xPos: 2.9 ; yPos: 2.08 ; zPos: 0.2 }
+ ListElement{ xPos: 0.7 ; yPos: 2.52 ; zPos: 0.65 }
+ ListElement{ xPos: -1.85 ; yPos: -1.8 ; zPos: 1.05 }
+ ListElement{ xPos: 1.6 ; yPos: 1.12 ; zPos: -3.05 }
+ ListElement{ xPos: -2.8 ; yPos: 0.12 ; zPos: 0.5 }
+ ListElement{ xPos: -1.4 ; yPos: 1.64 ; zPos: -1.95 }
+ ListElement{ xPos: 2.35 ; yPos: 0.4 ; zPos: -0.95 }
+ ListElement{ xPos: 0.95 ; yPos: -0.28 ; zPos: -0.8 }
+ ListElement{ xPos: -1.5 ; yPos: -1.76 ; zPos: 2.5 }
+ ListElement{ xPos: -3.05 ; yPos: 2.44 ; zPos: -0.2 }
+ ListElement{ xPos: -0.6 ; yPos: 1.12 ; zPos: -0.5 }
+ ListElement{ xPos: -1.7 ; yPos: -0.72 ; zPos: -1.05 }
+ ListElement{ xPos: 0.45 ; yPos: 0.84 ; zPos: -0.1 }
+ ListElement{ xPos: -3.5 ; yPos: 0.36 ; zPos: 1.1 }
+ ListElement{ xPos: 0.2 ; yPos: -2.08 ; zPos: 0.25 }
+ ListElement{ xPos: -2.1 ; yPos: 0.72 ; zPos: 0.85 }
+ ListElement{ xPos: -2.45 ; yPos: -0.72 ; zPos: -0.8 }
+ ListElement{ xPos: 4.25 ; yPos: 1.84 ; zPos: -0.55 }
+ ListElement{ xPos: 0.1 ; yPos: -1.32 ; zPos: -1.45 }
+ ListElement{ xPos: -3.65 ; yPos: 0.36 ; zPos: 0.3 }
+ ListElement{ xPos: 0.9 ; yPos: 0.32 ; zPos: -3.6 }
+ ListElement{ xPos: -0.25 ; yPos: 1.84 ; zPos: 0 }
+ ListElement{ xPos: 3.5 ; yPos: 0.92 ; zPos: -2.1 }
+ ListElement{ xPos: 0.65 ; yPos: 2.36 ; zPos: 4 }
+ ListElement{ xPos: 0.55 ; yPos: -1.08 ; zPos: 0.2 }
+ ListElement{ xPos: 1 ; yPos: 0.04 ; zPos: -3.75 }
+ ListElement{ xPos: 0.05 ; yPos: 0.52 ; zPos: -3.8 }
+ ListElement{ xPos: 3.45 ; yPos: 0.56 ; zPos: 4.2 }
+ ListElement{ xPos: -0.25 ; yPos: 2.36 ; zPos: -3.55 }
+ ListElement{ xPos: 1.95 ; yPos: 1.28 ; zPos: 4.25 }
+ ListElement{ xPos: 1.8 ; yPos: 3.12 ; zPos: 0.65 }
+ ListElement{ xPos: -3.35 ; yPos: 0.8 ; zPos: -1 }
+ ListElement{ xPos: 2.4 ; yPos: -0.16 ; zPos: 1.25 }
+ ListElement{ xPos: 2.5 ; yPos: 0.96 ; zPos: 1.45 }
+ ListElement{ xPos: 0.8 ; yPos: -0.16 ; zPos: -0.55 }
+ ListElement{ xPos: -3.45 ; yPos: -0.08 ; zPos: 0.85 }
+ ListElement{ xPos: -3.4 ; yPos: 0.8 ; zPos: 1.35 }
+ ListElement{ xPos: -0.8 ; yPos: 0.24 ; zPos: -1.2 }
+ ListElement{ xPos: 3.6 ; yPos: 1.16 ; zPos: -0.1 }
+ ListElement{ xPos: -1.45 ; yPos: -3.08 ; zPos: -0.3 }
+ ListElement{ xPos: -3.65 ; yPos: -1.04 ; zPos: 0.15 }
+ ListElement{ xPos: 1.35 ; yPos: -0.76 ; zPos: 0.55 }
+ ListElement{ xPos: -2.75 ; yPos: -1 ; zPos: -1.55 }
+ ListElement{ xPos: 1.6 ; yPos: 2.04 ; zPos: 0.9 }
+ ListElement{ xPos: 3 ; yPos: -3.24 ; zPos: 0.1 }
+ ListElement{ xPos: -4.25 ; yPos: 0.56 ; zPos: -4.35 }
+ ListElement{ xPos: -2.6 ; yPos: -0.44 ; zPos: 0.5 }
+ ListElement{ xPos: 0.2 ; yPos: -0.64 ; zPos: -0.35 }
+ ListElement{ xPos: -3.2 ; yPos: -3.52 ; zPos: 1 }
+ ListElement{ xPos: -0.7 ; yPos: -1.84 ; zPos: -2.45 }
+ ListElement{ xPos: -4.1 ; yPos: 0.12 ; zPos: 3.85 }
+ ListElement{ xPos: -4.05 ; yPos: 0.04 ; zPos: -0.3 }
+ ListElement{ xPos: 2 ; yPos: 2.32 ; zPos: 1.2 }
+ ListElement{ xPos: -0.35 ; yPos: 1 ; zPos: -0.7 }
+ ListElement{ xPos: -0.9 ; yPos: 0.36 ; zPos: 1.55 }
+ ListElement{ xPos: -0.05 ; yPos: 2.4 ; zPos: -4.35 }
+ ListElement{ xPos: 1.5 ; yPos: -0.96 ; zPos: -3.4 }
+ ListElement{ xPos: -2.8 ; yPos: 2.08 ; zPos: -1.75 }
+ ListElement{ xPos: -0.2 ; yPos: 0.72 ; zPos: 0.9 }
+ ListElement{ xPos: -1.1 ; yPos: -1.68 ; zPos: 0.1 }
+ ListElement{ xPos: -1.2 ; yPos: -0.32 ; zPos: -0.85 }
+ ListElement{ xPos: 0.75 ; yPos: 0.56 ; zPos: 3.95 }
+ ListElement{ xPos: 1.15 ; yPos: 2.8 ; zPos: 0.65 }
+ ListElement{ xPos: -2.55 ; yPos: -2.56 ; zPos: 0.75 }
+ ListElement{ xPos: 0.95 ; yPos: 0.84 ; zPos: -0.85 }
+ ListElement{ xPos: -2.15 ; yPos: 0.24 ; zPos: -3.1 }
+ ListElement{ xPos: 0.35 ; yPos: -1.68 ; zPos: 0.45 }
+ ListElement{ xPos: -0.75 ; yPos: -2.4 ; zPos: -0.35 }
+ ListElement{ xPos: 0.2 ; yPos: 2.04 ; zPos: 0.1 }
+ ListElement{ xPos: -2.4 ; yPos: 0.68 ; zPos: 1.8 }
+ ListElement{ xPos: 1.9 ; yPos: 0.88 ; zPos: 1.45 }
+ ListElement{ xPos: 0.25 ; yPos: 1.92 ; zPos: -1.7 }
+ ListElement{ xPos: -0.95 ; yPos: -0.6 ; zPos: -2 }
+ ListElement{ xPos: 0.2 ; yPos: -2.44 ; zPos: 1.05 }
+ ListElement{ xPos: 0.3 ; yPos: -0.6 ; zPos: -3.35 }
+ ListElement{ xPos: 0.05 ; yPos: 0.52 ; zPos: -3.3 }
+ ListElement{ xPos: -0.05 ; yPos: -0.88 ; zPos: -0.8 }
+ ListElement{ xPos: 0.45 ; yPos: 2.84 ; zPos: -1.9 }
+ ListElement{ xPos: 2.85 ; yPos: -0.68 ; zPos: -2.45 }
+ ListElement{ xPos: -1.05 ; yPos: -1.92 ; zPos: 0.9 }
+ ListElement{ xPos: -2.75 ; yPos: -0.16 ; zPos: -1.55 }
+ ListElement{ xPos: 1.9 ; yPos: 1 ; zPos: 3.15 }
+ ListElement{ xPos: -2.25 ; yPos: 0.8 ; zPos: -4.35 }
+ ListElement{ xPos: 1.8 ; yPos: -0.72 ; zPos: -3.8 }
+ ListElement{ xPos: -3.25 ; yPos: -1.16 ; zPos: -1.85 }
+ ListElement{ xPos: -1.6 ; yPos: -2.32 ; zPos: -0.8 }
+ ListElement{ xPos: 3.1 ; yPos: 0.32 ; zPos: 2.15 }
+ ListElement{ xPos: -1.05 ; yPos: -0.56 ; zPos: -3.45 }
+ ListElement{ xPos: 0.75 ; yPos: -1.08 ; zPos: -0.25 }
+ ListElement{ xPos: -0.45 ; yPos: -2.24 ; zPos: 2.5 }
+ ListElement{ xPos: 0.2 ; yPos: 0.12 ; zPos: 0 }
+ ListElement{ xPos: -1 ; yPos: 1.56 ; zPos: -1.6 }
+ ListElement{ xPos: 1.3 ; yPos: -2.84 ; zPos: -0.25 }
+ ListElement{ xPos: 0.55 ; yPos: 1.36 ; zPos: 2.95 }
+ ListElement{ xPos: -1.9 ; yPos: -0.88 ; zPos: 0.05 }
+ ListElement{ xPos: -1.9 ; yPos: 0.56 ; zPos: -0.7 }
+ ListElement{ xPos: -1.25 ; yPos: -0.52 ; zPos: 2.35 }
+ ListElement{ xPos: 2.1 ; yPos: -0.72 ; zPos: -1.4 }
+ ListElement{ xPos: -0.9 ; yPos: -0.72 ; zPos: -2.05 }
+ ListElement{ xPos: 0.9 ; yPos: -0.96 ; zPos: -3 }
+ ListElement{ xPos: 2.7 ; yPos: -2.36 ; zPos: 1.25 }
+ ListElement{ xPos: 3.3 ; yPos: -1 ; zPos: -3.55 }
+ ListElement{ xPos: -0.75 ; yPos: -1.96 ; zPos: 2 }
+ ListElement{ xPos: -0.45 ; yPos: 0.52 ; zPos: -0.1 }
+ ListElement{ xPos: 0.8 ; yPos: 0 ; zPos: -3.3 }
+ ListElement{ xPos: -0.45 ; yPos: -0.44 ; zPos: -2.8 }
+ ListElement{ xPos: -3.2 ; yPos: 3.12 ; zPos: 0.5 }
+ ListElement{ xPos: -3.1 ; yPos: 2.84 ; zPos: -0.45 }
+ ListElement{ xPos: 0.2 ; yPos: -1.2 ; zPos: 0.9 }
+ ListElement{ xPos: -0.65 ; yPos: -1.4 ; zPos: -1.35 }
+ ListElement{ xPos: -0.45 ; yPos: 1.84 ; zPos: -0.95 }
+ ListElement{ xPos: -1.3 ; yPos: 0.8 ; zPos: -1 }
+ ListElement{ xPos: 2.35 ; yPos: -0.28 ; zPos: 1.55 }
+ ListElement{ xPos: 2 ; yPos: -2.4 ; zPos: 1.2 }
+ ListElement{ xPos: 1.2 ; yPos: -1.72 ; zPos: -0.4 }
+ ListElement{ xPos: -2 ; yPos: 3.04 ; zPos: 1.9 }
+ ListElement{ xPos: -1.85 ; yPos: -0.16 ; zPos: -2.9 }
+ ListElement{ xPos: -0.95 ; yPos: -2.76 ; zPos: 2.5 }
+ ListElement{ xPos: 2.55 ; yPos: -0.64 ; zPos: 3.25 }
+ ListElement{ xPos: -0.6 ; yPos: -0.72 ; zPos: -1.45 }
+ ListElement{ xPos: 0.8 ; yPos: 0.16 ; zPos: 2.2 }
+ ListElement{ xPos: 0.6 ; yPos: 0.04 ; zPos: 3.2 }
+ ListElement{ xPos: -3.75 ; yPos: -0.24 ; zPos: 0.2 }
+ ListElement{ xPos: 0.4 ; yPos: -0.32 ; zPos: 0.6 }
+ ListElement{ xPos: -2.1 ; yPos: -3.68 ; zPos: 0.2 }
+ ListElement{ xPos: 0.45 ; yPos: -2.44 ; zPos: 3 }
+ ListElement{ xPos: 0.5 ; yPos: -1.8 ; zPos: -1.9 }
+ ListElement{ xPos: 0 ; yPos: 1.8 ; zPos: -1.25 }
+ ListElement{ xPos: 2.6 ; yPos: 2.2 ; zPos: -4.25 }
+ ListElement{ xPos: 0.25 ; yPos: 2.28 ; zPos: -0.4 }
+ ListElement{ xPos: -0.3 ; yPos: -1.24 ; zPos: 0.7 }
+ ListElement{ xPos: 0.1 ; yPos: -3 ; zPos: 0.05 }
+ ListElement{ xPos: -1.2 ; yPos: -0.76 ; zPos: 2.65 }
+ ListElement{ xPos: 2.8 ; yPos: 0.44 ; zPos: 0.55 }
+ ListElement{ xPos: 2.6 ; yPos: -0.68 ; zPos: 0.2 }
+ ListElement{ xPos: 1.75 ; yPos: -0.16 ; zPos: -3.6 }
+ ListElement{ xPos: -0.55 ; yPos: -0.16 ; zPos: 2.3 }
+ ListElement{ xPos: 0.1 ; yPos: -1.72 ; zPos: -0.4 }
+ ListElement{ xPos: 3.6 ; yPos: -0.84 ; zPos: -0.2 }
+ ListElement{ xPos: -1.4 ; yPos: -1.48 ; zPos: -2.7 }
+ ListElement{ xPos: 1.1 ; yPos: -0.2 ; zPos: -2.75 }
+ ListElement{ xPos: -1.85 ; yPos: 1.76 ; zPos: -0.85 }
+ ListElement{ xPos: 0.05 ; yPos: -0.4 ; zPos: 1.05 }
+ ListElement{ xPos: -1.25 ; yPos: 0.52 ; zPos: -0.3 }
+ ListElement{ xPos: -0.85 ; yPos: -0.24 ; zPos: 1.9 }
+ ListElement{ xPos: -1.95 ; yPos: -0.08 ; zPos: -1.95 }
+ ListElement{ xPos: -2.7 ; yPos: -0.52 ; zPos: -0.35 }
+ ListElement{ xPos: 0.1 ; yPos: 0.16 ; zPos: 4.1 }
+ ListElement{ xPos: -0.6 ; yPos: -0.52 ; zPos: -1.2 }
+ ListElement{ xPos: -2.3 ; yPos: -0.4 ; zPos: -2.85 }
+ ListElement{ xPos: -0.35 ; yPos: -1.2 ; zPos: -1.85 }
+ ListElement{ xPos: 1.7 ; yPos: -0.2 ; zPos: 3.35 }
+ ListElement{ xPos: 2.75 ; yPos: -2.32 ; zPos: 3.15 }
+ ListElement{ xPos: 2.85 ; yPos: 0.44 ; zPos: 0.9 }
+ ListElement{ xPos: -2.1 ; yPos: 0.4 ; zPos: 1 }
+ ListElement{ xPos: 4.45 ; yPos: -0.4 ; zPos: -3.75 }
+ ListElement{ xPos: 0.7 ; yPos: -1.44 ; zPos: 0.85 }
+ ListElement{ xPos: -0.85 ; yPos: 1.76 ; zPos: 1.05 }
+ ListElement{ xPos: -0.4 ; yPos: -0.92 ; zPos: -2.4 }
+ ListElement{ xPos: -1 ; yPos: -0.28 ; zPos: -1.6 }
+ ListElement{ xPos: -0.75 ; yPos: 0.56 ; zPos: -0.75 }
+ ListElement{ xPos: 0.55 ; yPos: 1.72 ; zPos: -0.2 }
+ ListElement{ xPos: 2.75 ; yPos: -0.24 ; zPos: -0.5 }
+ ListElement{ xPos: -2.2 ; yPos: -1.04 ; zPos: -4.05 }
+ ListElement{ xPos: 3.35 ; yPos: -0.56 ; zPos: -2.9 }
+ ListElement{ xPos: 2.4 ; yPos: -0.32 ; zPos: -1.55 }
+ ListElement{ xPos: 0.85 ; yPos: -0.88 ; zPos: -2.1 }
+ ListElement{ xPos: 0.4 ; yPos: 0.24 ; zPos: -0.45 }
+ ListElement{ xPos: -4.1 ; yPos: -0.72 ; zPos: 0.35 }
+ ListElement{ xPos: -0.5 ; yPos: 0.04 ; zPos: -0.95 }
+ ListElement{ xPos: -2.8 ; yPos: 2.92 ; zPos: 0.95 }
+ ListElement{ xPos: -1.65 ; yPos: 0.96 ; zPos: 1.25 }
+ ListElement{ xPos: 1.45 ; yPos: 0.84 ; zPos: 0.25 }
+ ListElement{ xPos: 2.2 ; yPos: -1.76 ; zPos: 0 }
+ ListElement{ xPos: -0.75 ; yPos: 1.08 ; zPos: -2.1 }
+ ListElement{ xPos: -3.15 ; yPos: -0.16 ; zPos: -1.1 }
+ ListElement{ xPos: 2.55 ; yPos: -1.6 ; zPos: 1.1 }
+ ListElement{ xPos: 0.15 ; yPos: 1.84 ; zPos: 0.4 }
+ ListElement{ xPos: -1.5 ; yPos: 2.04 ; zPos: 3.2 }
+ ListElement{ xPos: -1.05 ; yPos: -0.84 ; zPos: -1.9 }
+ ListElement{ xPos: 0.1 ; yPos: -2.96 ; zPos: -0.2 }
+ ListElement{ xPos: 2.6 ; yPos: 0.68 ; zPos: -2.6 }
+ ListElement{ xPos: -0.4 ; yPos: -0.36 ; zPos: -1.05 }
+ ListElement{ xPos: 1.1 ; yPos: 0.04 ; zPos: -2.4 }
+ ListElement{ xPos: -2.3 ; yPos: 1.4 ; zPos: -0.1 }
+ ListElement{ xPos: 0.85 ; yPos: -1.2 ; zPos: 1.5 }
+ ListElement{ xPos: 0.1 ; yPos: 0.24 ; zPos: -2.75 }
+ ListElement{ xPos: 1.15 ; yPos: 1.32 ; zPos: -1.7 }
+ ListElement{ xPos: 0.35 ; yPos: -1.6 ; zPos: -2.75 }
+ ListElement{ xPos: 0.35 ; yPos: -1.36 ; zPos: 2.55 }
+ ListElement{ xPos: -1.65 ; yPos: -0.28 ; zPos: 2.75 }
+ ListElement{ xPos: -3.8 ; yPos: -3.28 ; zPos: 2.55 }
+ ListElement{ xPos: -2.6 ; yPos: 1.08 ; zPos: -0.45 }
+ ListElement{ xPos: 1.6 ; yPos: -2.2 ; zPos: 2.45 }
+ ListElement{ xPos: -0.75 ; yPos: 1.16 ; zPos: -1.9 }
+ ListElement{ xPos: -2.05 ; yPos: 3.04 ; zPos: 1.9 }
+ ListElement{ xPos: -0.6 ; yPos: 2.88 ; zPos: 1 }
+ ListElement{ xPos: 3.25 ; yPos: -2 ; zPos: -0.35 }
+ ListElement{ xPos: -0.7 ; yPos: 1.36 ; zPos: 1.85 }
+ ListElement{ xPos: 0.85 ; yPos: 1.08 ; zPos: 0.95 }
+ ListElement{ xPos: 1.15 ; yPos: 1.32 ; zPos: 3.4 }
+ ListElement{ xPos: 3.6 ; yPos: 0.44 ; zPos: -0.4 }
+ ListElement{ xPos: 0.9 ; yPos: 2.12 ; zPos: 1.95 }
+ ListElement{ xPos: -0.6 ; yPos: -0.24 ; zPos: 1.05 }
+ ListElement{ xPos: -1.6 ; yPos: 0.64 ; zPos: -1.3 }
+ ListElement{ xPos: -1.3 ; yPos: -0.6 ; zPos: -1.95 }
+ ListElement{ xPos: -0.1 ; yPos: 0.36 ; zPos: -1.25 }
+ ListElement{ xPos: 1 ; yPos: -1.48 ; zPos: 0.95 }
+ ListElement{ xPos: 1.2 ; yPos: -1.92 ; zPos: -0.9 }
+ ListElement{ xPos: 0.1 ; yPos: 2.2 ; zPos: -0.4 }
+ ListElement{ xPos: -2.85 ; yPos: 0.64 ; zPos: 0.55 }
+ ListElement{ xPos: -1.6 ; yPos: 2.56 ; zPos: -2.2 }
+ ListElement{ xPos: -3.2 ; yPos: -0.08 ; zPos: -0.65 }
+ ListElement{ xPos: 3.15 ; yPos: -0.76 ; zPos: 1.15 }
+ ListElement{ xPos: 1.2 ; yPos: 0.6 ; zPos: -2.6 }
+ ListElement{ xPos: 2.65 ; yPos: -3.52 ; zPos: 1.5 }
+ ListElement{ xPos: -1.85 ; yPos: -1.76 ; zPos: -1.7 }
+ ListElement{ xPos: -1.55 ; yPos: 1.2 ; zPos: 2.15 }
+ ListElement{ xPos: -0.55 ; yPos: 2.88 ; zPos: -2.85 }
+ ListElement{ xPos: -3.1 ; yPos: -0.28 ; zPos: -3.35 }
+ ListElement{ xPos: 2.45 ; yPos: 0.08 ; zPos: -2.2 }
+ ListElement{ xPos: -2.05 ; yPos: 0.88 ; zPos: -0.65 }
+ ListElement{ xPos: 1.1 ; yPos: -0.56 ; zPos: -3.65 }
+ ListElement{ xPos: -0.5 ; yPos: -0.68 ; zPos: 0.3 }
+ ListElement{ xPos: 0.4 ; yPos: 0.88 ; zPos: -1.65 }
+ ListElement{ xPos: 1.65 ; yPos: 0.16 ; zPos: 0.75 }
+ ListElement{ xPos: -0.2 ; yPos: 1.72 ; zPos: 0.6 }
+ ListElement{ xPos: 1.15 ; yPos: -0.48 ; zPos: 0.1 }
+ ListElement{ xPos: 0.05 ; yPos: 1.04 ; zPos: 2.15 }
+ ListElement{ xPos: 0.15 ; yPos: -0.64 ; zPos: -1.3 }
+ ListElement{ xPos: 1.7 ; yPos: -1.88 ; zPos: -2.85 }
+ ListElement{ xPos: 3.35 ; yPos: 1.28 ; zPos: -1.05 }
+ ListElement{ xPos: -3.7 ; yPos: -2.88 ; zPos: -1.05 }
+ ListElement{ xPos: -1.7 ; yPos: 3.08 ; zPos: 0.15 }
+ ListElement{ xPos: 3 ; yPos: -2.52 ; zPos: -2.45 }
+ ListElement{ xPos: -0.15 ; yPos: 0.16 ; zPos: -0.3 }
+ ListElement{ xPos: -2.15 ; yPos: -0.8 ; zPos: 1.25 }
+ ListElement{ xPos: -1.95 ; yPos: -0.84 ; zPos: -3.1 }
+ ListElement{ xPos: -0.2 ; yPos: -0.8 ; zPos: -3.55 }
+ ListElement{ xPos: 0.4 ; yPos: 0.32 ; zPos: -1.55 }
+ ListElement{ xPos: 1.9 ; yPos: -2.84 ; zPos: -0.55 }
+ ListElement{ xPos: 1.05 ; yPos: 1.32 ; zPos: 0.6 }
+ ListElement{ xPos: 2.4 ; yPos: 1 ; zPos: 2.35 }
+ ListElement{ xPos: -0.7 ; yPos: 0.28 ; zPos: -1.3 }
+ ListElement{ xPos: 0.85 ; yPos: 0.96 ; zPos: -2.7 }
+ ListElement{ xPos: -0.85 ; yPos: -0.88 ; zPos: 1.1 }
+ ListElement{ xPos: 1.5 ; yPos: 0.28 ; zPos: -3.1 }
+ ListElement{ xPos: 1.9 ; yPos: 0.28 ; zPos: -1.75 }
+ ListElement{ xPos: 0.2 ; yPos: -2.04 ; zPos: -0.85 }
+ ListElement{ xPos: -1.5 ; yPos: 2.04 ; zPos: -1.35 }
+ ListElement{ xPos: -0.7 ; yPos: 1.24 ; zPos: 1.95 }
+ ListElement{ xPos: 1.65 ; yPos: -1.04 ; zPos: -0.95 }
+ ListElement{ xPos: -2.35 ; yPos: -1.76 ; zPos: -0.2 }
+ ListElement{ xPos: 0.85 ; yPos: -2.92 ; zPos: 3.4 }
+ ListElement{ xPos: -0.7 ; yPos: 2.52 ; zPos: 1 }
+ ListElement{ xPos: -3.4 ; yPos: 0.6 ; zPos: 0.9 }
+ }
+
+ ListModel {
+ id: dataModelThree
+ ListElement{ xPos: 8.0; yPos: -2.0; zPos: 4.0 }
+ ListElement{ xPos: 7.8; yPos: -2.2; zPos: 5.0 }
+ ListElement{ xPos: 7.6; yPos: -2.4; zPos: 4.5 }
+ ListElement{ xPos: 9.4; yPos: -2.6; zPos: 3.8 }
+ ListElement{ xPos: 7.2; yPos: -2.8; zPos: 4.8 }
+ ListElement{ xPos: 9.0; yPos: -2.3; zPos: 4.1 }
+ ListElement{ xPos: 6.9; yPos: -3.3; zPos: 4.9 }
+ ListElement{ xPos: 6.7; yPos: -3.5; zPos: 3.5 }
+ ListElement{ xPos: 6.5; yPos: -3.7; zPos: 3.3 }
+ ListElement{ xPos: 6.3; yPos: -3.4; zPos: 3.7 }
+ ListElement{ xPos: 7.9 ; yPos: -3.32 ; zPos: 2.48 }
+ ListElement{ xPos: 6.2 ; yPos: -4.04 ; zPos: 3.4 }
+ ListElement{ xPos: 9.06 ; yPos: -4.6 ; zPos: 4.08 }
+ ListElement{ xPos: 5.98 ; yPos: -2.26 ; zPos: 2.82 }
+ ListElement{ xPos: 5.46 ; yPos: -3.9 ; zPos: 3.2 }
+ ListElement{ xPos: 8.14 ; yPos: -3.34 ; zPos: 3.42 }
+ ListElement{ xPos: 6.84 ; yPos: -5.0 ; zPos: 2.36 }
+ ListElement{ xPos: 7.6 ; yPos: -3.82 ; zPos: 2.78 }
+ ListElement{ xPos: 7.1 ; yPos: -3.94 ; zPos: 2.7 }
+ ListElement{ xPos: 6.02 ; yPos: -4.36 ; zPos: 2.56 }
+ ListElement{ xPos: 8.64 ; yPos: -3.04 ; zPos: 2.62 }
+ ListElement{ xPos: 7.82 ; yPos: -3.68 ; zPos: 2.96 }
+ ListElement{ xPos: 7.1 ; yPos: -3.6 ; zPos: 4.22 }
+ ListElement{ xPos: 8.6 ; yPos: -3.88 ; zPos: 3.9 }
+ ListElement{ xPos: 8.46 ; yPos: -4.2 ; zPos: 3.32 }
+ ListElement{ xPos: 6.98 ; yPos: -3.94 ; zPos: 3.1 }
+ ListElement{ xPos: 7.36 ; yPos: -4.24 ; zPos: 1.72 }
+ ListElement{ xPos: 9.42 ; yPos: -4.52 ; zPos: 2.62 }
+ ListElement{ xPos: 6.04 ; yPos: -4.48 ; zPos: 2.74 }
+ ListElement{ xPos: 10.0 ; yPos: -3.92 ; zPos: 3.82 }
+ ListElement{ xPos: 6.76 ; yPos: -3.5 ; zPos: 2.92 }
+ ListElement{ xPos: 7 ; yPos: -4.42 ; zPos: 2.62 }
+ ListElement{ xPos: 5.32 ; yPos: -3.28 ; zPos: 3.34 }
+ ListElement{ xPos: 6.4 ; yPos: -4.6 ; zPos: 2.4 }
+ ListElement{ xPos: 5.66 ; yPos: -4.98 ; zPos: 3.72 }
+ }
+}
diff --git a/examples/graphs/qmlscatter/qml/qmlscatter/main.qml b/examples/graphs/qmlscatter/qml/qmlscatter/main.qml
new file mode 100644
index 0000000..cf4641c
--- /dev/null
+++ b/examples/graphs/qmlscatter/qml/qmlscatter/main.qml
@@ -0,0 +1,228 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//! [0]
+import QtQuick
+import QtQuick.Controls
+import QtGraphs
+//! [0]
+
+//! [1]
+Item {
+ id: mainView
+ //! [1]
+ width: 1600
+ height: 1200
+
+ property bool portraitMode: width < height
+ // Adjust the button width based on screen orientation:
+ // If we're in portrait mode, just fit two buttons side-by-side, otherwise
+ // fit all of the buttons side-by-side.
+ property real buttonWidth: mainView.portraitMode ? (mainView.width / 2 - 8)
+ : (mainView.width / 6 - 6)
+
+ //! [4]
+ Data {
+ id: seriesData
+ }
+ //! [4]
+
+ //! [13]
+ Theme3D {
+ id: themeQt
+ type: Theme3D.ThemeQt
+ font.pointSize: 40
+ }
+ //! [13]
+
+ Theme3D {
+ id: themeRetro
+ type: Theme3D.ThemeRetro
+ }
+
+ //! [8]
+ //! [9]
+ Item {
+ id: dataView
+ anchors.bottom: parent.bottom
+ //! [9]
+ width: parent.width
+ // Adjust the space based on screen orientation:
+ // If we're in portrait mode, we have 3 rows of buttons, otherwise they are all in one row.
+ height: parent.height - (mainView.portraitMode ? shadowToggle.implicitHeight * 3 + 25
+ : shadowToggle.implicitHeight + 10)
+ //! [8]
+
+ //! [2]
+ Scatter3D {
+ id: scatterGraph
+ anchors.fill: parent
+ //! [2]
+ //! [3]
+ theme: themeQt
+ shadowQuality: AbstractGraph3D.ShadowQualityHigh
+ scene.activeCamera.cameraPreset: Camera3D.CameraPresetFront
+ //! [3]
+ //! [6]
+ axisX.segmentCount: 3
+ axisX.subSegmentCount: 2
+ axisX.labelFormat: "%.2f"
+ axisZ.segmentCount: 2
+ axisZ.subSegmentCount: 2
+ axisZ.labelFormat: "%.2f"
+ axisY.segmentCount: 2
+ axisY.subSegmentCount: 2
+ axisY.labelFormat: "%.2f"
+ //! [6]
+ //! [5]
+ Scatter3DSeries {
+ id: scatterSeries
+ //! [5]
+ //! [10]
+ itemLabelFormat: "Series 1: X:@xLabel Y:@yLabel Z:@zLabel"
+ //! [10]
+
+ //! [11]
+ ItemModelScatterDataProxy {
+ itemModel: seriesData.model
+ xPosRole: "xPos"
+ yPosRole: "yPos"
+ zPosRole: "zPos"
+ }
+ //! [11]
+ }
+
+ //! [12]
+ Scatter3DSeries {
+ id: scatterSeriesTwo
+ itemLabelFormat: "Series 2: X:@xLabel Y:@yLabel Z:@zLabel"
+ itemSize: 0.05
+ mesh: Abstract3DSeries.MeshCube
+ //! [12]
+
+ ItemModelScatterDataProxy {
+ itemModel: seriesData.modelTwo
+ xPosRole: "xPos"
+ yPosRole: "yPos"
+ zPosRole: "zPos"
+ }
+ }
+ Scatter3DSeries {
+ id: scatterSeriesThree
+ itemLabelFormat: "Series 3: X:@xLabel Y:@yLabel Z:@zLabel"
+ itemSize: 0.1
+ mesh: Abstract3DSeries.MeshMinimal
+
+ ItemModelScatterDataProxy {
+ itemModel: seriesData.modelThree
+ xPosRole: "xPos"
+ yPosRole: "yPos"
+ zPosRole: "zPos"
+ }
+ }
+ }
+ }
+
+ //! [7]
+ Button {
+ id: shadowToggle
+ width: mainView.buttonWidth // Calculated elsewhere based on screen orientation
+ anchors.left: parent.left
+ anchors.top: parent.top
+ anchors.margins: 5
+ text: scatterGraph.shadowsSupported ? "Hide Shadows" : "Shadows not supported"
+ enabled: scatterGraph.shadowsSupported
+ onClicked: {
+ if (scatterGraph.shadowQuality === AbstractGraph3D.ShadowQualityNone) {
+ scatterGraph.shadowQuality = AbstractGraph3D.ShadowQualityHigh;
+ text = "Hide Shadows";
+ } else {
+ scatterGraph.shadowQuality = AbstractGraph3D.ShadowQualityNone;
+ text = "Show Shadows";
+ }
+ }
+ }
+ //! [7]
+
+ Button {
+ id: smoothToggle
+ width: mainView.buttonWidth
+ anchors.left: shadowToggle.right
+ anchors.top: parent.top
+ anchors.margins: 5
+ text: "Use Smooth for Series One"
+ onClicked: {
+ if (!scatterSeries.meshSmooth) {
+ text = "Use Flat for Series One";
+ scatterSeries.meshSmooth = true;
+ } else {
+ text = "Use Smooth for Series One";
+ scatterSeries.meshSmooth = false;
+ }
+ }
+ }
+
+ Button {
+ id: cameraToggle
+ width: mainView.buttonWidth
+ anchors.left: mainView.portraitMode ? parent.left : smoothToggle.right
+ anchors.top: mainView.portraitMode ? smoothToggle.bottom : parent.top
+ anchors.margins: 5
+ text: "Change Camera Placement"
+ onClicked: {
+ if (scatterGraph.scene.activeCamera.cameraPreset === Camera3D.CameraPresetFront) {
+ scatterGraph.scene.activeCamera.cameraPreset =
+ Camera3D.CameraPresetIsometricRightHigh;
+ } else {
+ scatterGraph.scene.activeCamera.cameraPreset = Camera3D.CameraPresetFront;
+ }
+ }
+ }
+
+ Button {
+ id: themeToggle
+ width: mainView.buttonWidth
+ anchors.left: cameraToggle.right
+ anchors.top: mainView.portraitMode ? smoothToggle.bottom : parent.top
+ anchors.margins: 5
+ text: "Change Theme"
+ onClicked: {
+ if (scatterGraph.theme.type === Theme3D.ThemeRetro)
+ scatterGraph.theme = themeQt;
+ else
+ scatterGraph.theme = themeRetro;
+ if (scatterGraph.theme.backgroundEnabled)
+ backgroundToggle.text = "Hide Background";
+ else
+ backgroundToggle.text = "Show Background";
+ }
+ }
+
+ Button {
+ id: backgroundToggle
+ width: mainView.buttonWidth
+ anchors.left: mainView.portraitMode ? parent.left : themeToggle.right
+ anchors.top: mainView.portraitMode ? themeToggle.bottom : parent.top
+ anchors.margins: 5
+ text: "Hide Background"
+ onClicked: {
+ if (scatterGraph.theme.backgroundEnabled) {
+ scatterGraph.theme.backgroundEnabled = false;
+ text = "Show Background";
+ } else {
+ scatterGraph.theme.backgroundEnabled = true;
+ text = "Hide Background";
+ }
+ }
+ }
+
+ Button {
+ id: exitButton
+ width: mainView.buttonWidth
+ anchors.left: backgroundToggle.right
+ anchors.top: mainView.portraitMode ? themeToggle.bottom : parent.top
+ anchors.margins: 5
+ text: "Quit"
+ onClicked: Qt.quit();
+ }
+}
diff --git a/examples/graphs/qmlscatter/qmlscatter.pro b/examples/graphs/qmlscatter/qmlscatter.pro
new file mode 100644
index 0000000..2ca0087
--- /dev/null
+++ b/examples/graphs/qmlscatter/qmlscatter.pro
@@ -0,0 +1,11 @@
+!include( ../examples.pri ) {
+ error( "Couldn't find the examples.pri file!" )
+}
+
+SOURCES += main.cpp
+
+RESOURCES += qmlscatter.qrc
+
+OTHER_FILES += doc/src/* \
+ doc/images/* \
+ qml/qmlscatter/*
diff --git a/examples/graphs/qmlscatter/qmlscatter.qrc b/examples/graphs/qmlscatter/qmlscatter.qrc
new file mode 100644
index 0000000..f36c81c
--- /dev/null
+++ b/examples/graphs/qmlscatter/qmlscatter.qrc
@@ -0,0 +1,6 @@
+<RCC>
+ <qresource prefix="/">
+ <file>qml/qmlscatter/Data.qml</file>
+ <file>qml/qmlscatter/main.qml</file>
+ </qresource>
+</RCC>
diff --git a/examples/graphs/qmlsurfacegallery/CMakeLists.txt b/examples/graphs/qmlsurfacegallery/CMakeLists.txt
new file mode 100644
index 0000000..7f15787
--- /dev/null
+++ b/examples/graphs/qmlsurfacegallery/CMakeLists.txt
@@ -0,0 +1,60 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+cmake_minimum_required(VERSION 3.16)
+project(qmlsurfacegallery LANGUAGES CXX)
+
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+set(CMAKE_AUTOMOC ON)
+set(CMAKE_AUTORCC ON)
+set(CMAKE_AUTOUIC ON)
+
+if(NOT DEFINED INSTALL_EXAMPLESDIR)
+ set(INSTALL_EXAMPLESDIR "examples")
+endif()
+
+set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}")
+
+find_package(Qt6 COMPONENTS Core)
+find_package(Qt6 COMPONENTS Gui)
+find_package(Qt6 COMPONENTS Qml)
+find_package(Qt6 COMPONENTS Quick)
+find_package(Qt6 COMPONENTS Graphs)
+
+qt_add_executable(qmlsurfacegallery
+ main.cpp
+)
+set_target_properties(qmlsurfacegallery PROPERTIES
+ WIN32_EXECUTABLE TRUE
+ MACOSX_BUNDLE TRUE
+)
+target_link_libraries(qmlsurfacegallery PUBLIC
+ Qt::Core
+ Qt::Gui
+ Qt::Qml
+ Qt::Quick
+ Qt::Graphs
+)
+
+qt6_add_qml_module(qmlsurfacegallery
+ URI SurfaceGallery
+ VERSION 1.0
+ NO_RESOURCE_TARGET_PATH
+ SOURCES
+ datasource.cpp datasource.h
+ QML_FILES
+ qml/qmlsurfacegallery/main.qml
+ qml/qmlsurfacegallery/SpectrogramData.qml
+ qml/qmlsurfacegallery/SurfaceHeightMap.qml
+ qml/qmlsurfacegallery/SurfaceOscilloscope.qml
+ qml/qmlsurfacegallery/SurfaceSpectrogram.qml
+ RESOURCES
+ qml/qmlsurfacegallery/heightmap.png
+)
+
+install(TARGETS qmlsurfacegallery
+ RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
+ BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
+ LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
+)
diff --git a/examples/graphs/qmlsurfacegallery/datasource.cpp b/examples/graphs/qmlsurfacegallery/datasource.cpp
new file mode 100644
index 0000000..8887fff
--- /dev/null
+++ b/examples/graphs/qmlsurfacegallery/datasource.cpp
@@ -0,0 +1,129 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "datasource.h"
+#include <QtCore/qmath.h>
+#include <QtCore/qrandom.h>
+
+DataSource::DataSource(QObject *parent) :
+ QObject(parent),
+ m_index(-1),
+ m_resetArray(nullptr)
+{
+ //! [3]
+ qRegisterMetaType<QSurface3DSeries *>();
+ //! [3]
+}
+
+DataSource::~DataSource()
+{
+ clearData();
+}
+
+void DataSource::generateData(int cacheCount, int rowCount, int columnCount,
+ float xMin, float xMax,
+ float yMin, float yMax,
+ float zMin, float zMax)
+{
+ if (!cacheCount || !rowCount || !columnCount)
+ return;
+
+ clearData();
+
+ // Re-create the cache array
+ m_data.resize(cacheCount);
+ for (int i = 0; i < cacheCount; i++) {
+ QSurfaceDataArray &array = m_data[i];
+ array.reserve(rowCount);
+ for (int j = 0; j < rowCount; j++)
+ array.append(new QSurfaceDataRow(columnCount));
+ }
+
+ float xRange = xMax - xMin;
+ float yRange = yMax - yMin;
+ float zRange = zMax - zMin;
+ int cacheIndexStep = columnCount / cacheCount;
+ float cacheStep = float(cacheIndexStep) * xRange / float(columnCount);
+
+ //! [0]
+ // Populate caches
+ for (int i = 0; i < cacheCount; i++) {
+ QSurfaceDataArray &cache = m_data[i];
+ float cacheXAdjustment = cacheStep * i;
+ float cacheIndexAdjustment = cacheIndexStep * i;
+ for (int j = 0; j < rowCount; j++) {
+ QSurfaceDataRow &row = *(cache[j]);
+ float rowMod = (float(j)) / float(rowCount);
+ float yRangeMod = yRange * rowMod;
+ float zRangeMod = zRange * rowMod;
+ float z = zRangeMod + zMin;
+ qreal rowColWaveAngleMul = M_PI * M_PI * rowMod;
+ float rowColWaveMul = yRangeMod * 0.2f;
+ for (int k = 0; k < columnCount; k++) {
+ float colMod = (float(k)) / float(columnCount);
+ float xRangeMod = xRange * colMod;
+ float x = xRangeMod + xMin + cacheXAdjustment;
+ float colWave = float(qSin((2.0 * M_PI * colMod) - (1.0 / 2.0 * M_PI)) + 1.0);
+ float y = (colWave * ((float(qSin(rowColWaveAngleMul * colMod) + 1.0))))
+ * rowColWaveMul
+ + QRandomGenerator::global()->bounded(0.15f) * yRangeMod;
+
+ int index = k + cacheIndexAdjustment;
+ if (index >= columnCount) {
+ // Wrap over
+ index -= columnCount;
+ x -= xRange;
+ }
+ row[index] = QVector3D(x, y, z);
+ }
+ }
+ }
+ //! [0]
+}
+
+void DataSource::update(QSurface3DSeries *series)
+{
+ if (series && m_data.size()) {
+ //! [1]
+ // Each iteration uses data from a different cached array
+ m_index++;
+ if (m_index > m_data.count() - 1)
+ m_index = 0;
+
+ QSurfaceDataArray array = m_data.at(m_index);
+ int newRowCount = array.size();
+ int newColumnCount = array.at(0)->size();
+
+ // If the first time or the dimensions of the cache array have changed,
+ // reconstruct the reset array
+ if (!m_resetArray || series->dataProxy()->rowCount() != newRowCount
+ || series->dataProxy()->columnCount() != newColumnCount) {
+ m_resetArray = new QSurfaceDataArray();
+ m_resetArray->reserve(newRowCount);
+ for (int i = 0; i < newRowCount; i++)
+ m_resetArray->append(new QSurfaceDataRow(newColumnCount));
+ }
+
+ // Copy items from our cache to the reset array
+ for (int i = 0; i < newRowCount; i++) {
+ const QSurfaceDataRow &sourceRow = *(array.at(i));
+ QSurfaceDataRow &row = *(*m_resetArray)[i];
+ for (int j = 0; j < newColumnCount; j++)
+ row[j].setPosition(sourceRow.at(j).position());
+ }
+
+ // Notify the proxy that data has changed
+ series->dataProxy()->resetArray(m_resetArray);
+ //! [1]
+ }
+}
+
+void DataSource::clearData()
+{
+ for (int i = 0; i < m_data.size(); i++) {
+ QSurfaceDataArray &array = m_data[i];
+ for (int j = 0; j < array.size(); j++)
+ delete array[j];
+ array.clear();
+ }
+}
diff --git a/examples/graphs/qmlsurfacegallery/datasource.h b/examples/graphs/qmlsurfacegallery/datasource.h
new file mode 100644
index 0000000..84c3b5f
--- /dev/null
+++ b/examples/graphs/qmlsurfacegallery/datasource.h
@@ -0,0 +1,38 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef DATASOURCE_H
+#define DATASOURCE_H
+
+#include <QtGraphs/qsurface3dseries.h>
+#include <QtQml/qqmlregistration.h>
+
+//! [0]
+//! [2]
+class DataSource : public QObject
+{
+ Q_OBJECT
+ //! [0]
+ QML_ELEMENT
+ //! [2]
+public:
+ explicit DataSource(QObject *parent = 0);
+ virtual ~DataSource();
+
+ //! [1]
+ Q_INVOKABLE void generateData(int cacheCount, int rowCount, int columnCount,
+ float xMin, float xMax,
+ float yMin, float yMax,
+ float zMin, float zMax);
+
+ Q_INVOKABLE void update(QSurface3DSeries *series);
+ //! [1]
+private:
+ void clearData();
+
+ QList<QSurfaceDataArray> m_data;
+ int m_index;
+ QSurfaceDataArray *m_resetArray;
+};
+
+#endif
diff --git a/examples/graphs/qmlsurfacegallery/doc/images/qmlsurfacegallery-example.png b/examples/graphs/qmlsurfacegallery/doc/images/qmlsurfacegallery-example.png
new file mode 100644
index 0000000..c702316
--- /dev/null
+++ b/examples/graphs/qmlsurfacegallery/doc/images/qmlsurfacegallery-example.png
Binary files differ
diff --git a/examples/graphs/qmlsurfacegallery/doc/src/qmlsurfacegallery.qdoc b/examples/graphs/qmlsurfacegallery/doc/src/qmlsurfacegallery.qdoc
new file mode 100644
index 0000000..189723c
--- /dev/null
+++ b/examples/graphs/qmlsurfacegallery/doc/src/qmlsurfacegallery.qdoc
@@ -0,0 +1,239 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ \example qmlsurfacegallery
+ \meta tags {Graphs, Surface3D, Dynamic Data, Surface Graph, Height Map, Polar Graph}
+ \meta category {Graphics}
+ \title Surface Graph Gallery
+ \ingroup qtdatavisualization_qmlexamples
+ \brief Gallery with three different ways to use a Surface3D graph.
+
+ \e {Surface Graph Gallery} demonstrates three different custom features with Surface3D graphs.
+ The features have their own tabs in the application.
+
+ The following sections concentrate on those features only and skip explaining the basic
+ functionality - for more detailed QML example documentation, see \l{Simple Scatter Graph}.
+
+ \image qmlsurfacegallery-example.png
+
+ \include examples-run.qdocinc
+
+ \section1 Height Map
+
+ In the \uicontrol {Height Map} tab, generate a surface graph from height data. The data used is
+ a height map of Mount Ruapehu and Mount Ngauruhoe in New Zealand.
+
+ \section2 Adding Data to the Graph
+
+ The data is set using HeightMapSurfaceDataProxy, which reads height information from a height
+ map image. The proxy itself is contained in a Surface3DSeries. Inside the
+ HeightMapSurfaceDataProxy the \c heightMapFile property specifies the image file containing the
+ height data. The value properties in the proxy define the minimum and maximum values for surface
+ area width, depth, and height. The \c z and \c x values are in latitude and longitude,
+ approximately at the real-world position, and the \c y is in meters.
+ \note The aspect ratio of the graph is not set to real-life scale, but the height is exaggerated
+ instead.
+
+ \snippet qmlsurfacegallery/qml/qmlsurfacegallery/SurfaceHeightMap.qml 0
+
+ \section2 Displaying the Data
+
+ In \c main.qml, set up the Surface3D element to display the data.
+
+ First, define the custom gradient to be used for the surface. Set the colors from position
+ 0.0 to 1.0 with ColorGradient, with two extra stops to make the graph more vivid:
+
+ \snippet qmlsurfacegallery/qml/qmlsurfacegallery/SurfaceHeightMap.qml 1
+
+ Set this element into the \c baseGradients property in the \c theme used in Surface3D:
+
+ \snippet qmlsurfacegallery/qml/qmlsurfacegallery/SurfaceHeightMap.qml 2
+
+ Use the buttons to control other Surface3D features.
+
+ The first button toggles on and off the surface grid. The draw mode cannot
+ be cleared completely, so unless the surface itself is visible, the surface grid cannot be
+ hidden:
+
+ \snippet qmlsurfacegallery/qml/qmlsurfacegallery/SurfaceHeightMap.qml 3
+
+ The second one sets the surface grid color:
+
+ \snippet qmlsurfacegallery/qml/qmlsurfacegallery/SurfaceHeightMap.qml 4
+
+ The third one toggles the surface on or off in the surface draw mode. The draw mode cannot
+ be cleared completely, so unless the surface grid is visible, the surface itself cannot be
+ hidden:
+
+ \snippet qmlsurfacegallery/qml/qmlsurfacegallery/SurfaceHeightMap.qml 5
+
+ The fourth sets the for shading mode. If you are running the example on OpenGL ES system, flat
+ shading is not available:
+
+ \snippet qmlsurfacegallery/qml/qmlsurfacegallery/SurfaceHeightMap.qml 6
+
+ The remaining buttons control the graph background features.
+
+ \section1 Spectrogram
+
+ In the \uicontrol {Spectrogram} tab, display polar and cartesian spectrograms and use
+ orthographic projection to show them in 2D.
+
+ A spectrogram is a surface graph with a range gradient used to emphasize the different
+ values. Typically, spectrograms are shown with two-dimensional surfaces, which is simulated
+ with a top-down orthographic view of the graph. To enforce the 2D effect, disable the
+ graph rotation via mouse or touch when in the orthographic mode.
+
+ \section2 Creating a Spectrogram
+
+ To create a 2D spectrogram, define a Surface3D item with the data given in the Surface3DSeries
+ with an ItemModelSurfaceDataProxy:
+
+ \snippet qmlsurfacegallery/qml/qmlsurfacegallery/SurfaceSpectrogram.qml 0
+
+ The key properties for enabling the 2D effect are
+ \l{AbstractGraph3D::orthoProjection}{orthoProjection} and
+ \l{Camera3D::cameraPreset}{scene.activeCamera.cameraPreset}. Remove the perspective by
+ enabling orthographic projection for the graph, and the Y-dimension by viewing the graph
+ directly from above:
+
+ \snippet qmlsurfacegallery/qml/qmlsurfacegallery/SurfaceSpectrogram.qml 1
+
+ Since this viewpoint causes the horizontal axis grid to be mostly obscured by the surface,
+ flip the horizontal grid to be drawn on top of the graph:
+
+ \snippet qmlsurfacegallery/qml/qmlsurfacegallery/SurfaceSpectrogram.qml 2
+
+ \section2 Polar Spectrogram
+
+ Depending on the data, it is sometimes more natural to use a polar graph instead of a cartesian
+ one. This is supported via the \l{AbstractGraph3D::polar}{polar} property.
+
+ Add a button to switch between polar and cartesian modes:
+
+ \snippet qmlsurfacegallery/qml/qmlsurfacegallery/SurfaceSpectrogram.qml 3
+
+ In the polar mode, X-axis is converted into the angular polar axis, and Z-axis is converted into
+ a radial polar axis. The surface points are recalculated according to the new axes.
+
+ The radial axis labels are drawn outside the graph by default. To draw them right next to the 0
+ degree angular axis inside the graph, define only a small offset for them:
+
+ \snippet qmlsurfacegallery/qml/qmlsurfacegallery/SurfaceSpectrogram.qml 4
+
+ To enforce the 2D effect, disable graph rotation in orthographic mode by overriding the default
+ input handler with a custom one, which automatically toggles the
+ \l{InputHandler3D::rotationEnabled}{rotationEnabled} property based on the projection mode:
+
+ \snippet qmlsurfacegallery/qml/qmlsurfacegallery/SurfaceSpectrogram.qml 5
+
+ \section1 Oscilloscope
+
+ In the \uicontrol {Oscilloscope} tab, combine C++ and QML in an application, and show data that
+ dynamically changes.
+
+ \section2 Data Source in C++
+
+ The item model based proxies are good for simple or static graphs, but use basic proxies to
+ achieve the best performance when displaying data changing in realtime.
+ These are not supported in QML, as the data items they store do not inherit \l{QObject} and
+ cannot therefore be directly manipulated from QML code.
+ To overcome this limitation, implement a simple \c DataSource class in C++ to populate the
+ data proxy of the series.
+
+ Create a \c DataSource class to provide two methods that can be invoked from QML:
+
+ \snippet qmlsurfacegallery/datasource.h 0
+ \dots 4
+ \snippet qmlsurfacegallery/datasource.h 1
+
+ The first method, \c generateData(), creates a cache of simulated oscilloscope data to display.
+ The data is cached in a format that QSurfaceDataProxy accepts:
+
+ \snippet qmlsurfacegallery/datasource.cpp 0
+
+ The second method, \c update(), copies one set of the cached data into another array, which is
+ set to the data proxy of the series by calling QSurfaceDataProxy::resetArray().
+ To minimize overhead, reuse the same array if the array dimensions have not changed:
+
+ \snippet qmlsurfacegallery/datasource.cpp 1
+
+ Even though we are operating on the array pointer previously set to the proxy,
+ QSurfaceDataProxy::resetArray() still needs to be called after changing the data in it to prompt
+ the graph to render the data.
+
+ To be able to access the \c DataSource methods from QML, expose the data source by
+ making the DataSource a QML_ELEMENT:
+
+ \snippet qmlsurfacegallery/datasource.h 2
+
+ Further, declare it as a QML module in the CMakeLists.txt:
+
+ \badcode
+ qt6_add_qml_module(qmlsurfacegallery
+ URI SurfaceGallery
+ VERSION 1.0
+ NO_RESOURCE_TARGET_PATH
+ SOURCES
+ datasource.cpp datasource.h
+ ...
+ )
+ \endcode
+
+ To use QSurface3DSeries pointers as parameters for the \c DataSource class methods on all
+ environments and builds, make sure the meta type is registered:
+
+ \snippet qmlsurfacegallery/datasource.cpp 3
+
+ \section2 QML Application
+
+ To use the \c{DataSource}, import the QML module and create an instance of \c DataSource to be
+ used:
+
+ \snippet qmlsurfacegallery/qml/qmlsurfacegallery/SurfaceOscilloscope.qml 0
+ \dots 0
+ \snippet qmlsurfacegallery/qml/qmlsurfacegallery/SurfaceOscilloscope.qml 1
+
+ Define a Surface3D graph and give it a Surface3DSeries:
+
+ \snippet qmlsurfacegallery/qml/qmlsurfacegallery/SurfaceOscilloscope.qml 2
+
+ Don't specify a proxy for the Surface3DSeries that you attach to the graph. This makes the
+ series utilize the default QSurfaceDataProxy.
+
+ Hide the item label with \l{Abstract3DSeries::itemLabelVisible}{itemLabelVisible}. With dynamic,
+ fast-changing data, a floating selection label would be distracting and difficult to read.
+
+ \snippet qmlsurfacegallery/qml/qmlsurfacegallery/SurfaceOscilloscope.qml 3
+
+ You can display the selected item information in a \c Text element instead of the default
+ floating label above the selection pointer:
+
+ \snippet qmlsurfacegallery/qml/qmlsurfacegallery/SurfaceOscilloscope.qml 4
+
+ Initialize the \c DataSource cache when the graph is complete by calling a helper function
+ \c generateData(), which calls the method with the same name in \c DataSource:
+
+ \snippet qmlsurfacegallery/qml/qmlsurfacegallery/SurfaceOscilloscope.qml 5
+ \dots 0
+ \snippet qmlsurfacegallery/qml/qmlsurfacegallery/SurfaceOscilloscope.qml 6
+
+ To trigger the updates in data, define a \c{Timer}, which calls the \c update() method in
+ \c DataSource at requested intervals:
+
+ \snippet qmlsurfacegallery/qml/qmlsurfacegallery/SurfaceOscilloscope.qml 7
+
+ \section2 Enabling Direct Rendering
+
+ Since this application potentially deals with a lot of rapidly changing data, it uses direct
+ rendering mode for performance. To enable antialiasing in this mode, change the surface format
+ of the application window. The default format used by QQuickView doesn't support antialiasing.
+ Use the utility function provided to change the surface format in \c main.cpp:
+
+ \snippet qmlsurfacegallery/main.cpp 0
+ \dots 0
+ \snippet qmlsurfacegallery/main.cpp 1
+
+ \section1 Example Contents
+*/
diff --git a/examples/graphs/qmlsurfacegallery/main.cpp b/examples/graphs/qmlsurfacegallery/main.cpp
new file mode 100644
index 0000000..cfd5fc7
--- /dev/null
+++ b/examples/graphs/qmlsurfacegallery/main.cpp
@@ -0,0 +1,49 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//! [0]
+#include <QtGraphs/qutils.h>
+//! [0]
+
+#include <QtGui/qguiapplication.h>
+#include <QtQuick/qquickview.h>
+#include <QtQml/qqmlengine.h>
+
+#ifdef QMAKE_BUILD
+#include "datasource.h"
+#endif
+
+int main(int argc, char *argv[])
+{
+ QGuiApplication app(argc, argv);
+
+#ifdef QMAKE_BUILD
+ qmlRegisterType<DataSource>("SurfaceGallery", 1, 0, "DataSource");
+#endif
+
+ QQuickView viewer;
+
+ //! [1]
+ // Enable antialiasing in direct rendering mode
+ viewer.setFormat(qDefaultSurfaceFormat(true));
+ //! [1]
+
+ // The following are needed to make examples run without having to install the module
+ // in desktop environments.
+#ifdef Q_OS_WIN
+ QString extraImportPath(QStringLiteral("%1/../../../../%2"));
+#else
+ QString extraImportPath(QStringLiteral("%1/../../../%2"));
+#endif
+ viewer.engine()->addImportPath(extraImportPath.arg(QGuiApplication::applicationDirPath(),
+ QString::fromLatin1("qml")));
+ QObject::connect(viewer.engine(), &QQmlEngine::quit, &viewer, &QWindow::close);
+
+ viewer.setTitle(QStringLiteral("Surface Graph Gallery"));
+
+ viewer.setSource(QUrl("qrc:/qml/qmlsurfacegallery/main.qml"));
+ viewer.setResizeMode(QQuickView::SizeRootObjectToView);
+ viewer.show();
+
+ return app.exec();
+}
diff --git a/examples/graphs/qmlsurfacegallery/qml/qmlsurfacegallery/SpectrogramData.qml b/examples/graphs/qmlsurfacegallery/qml/qmlsurfacegallery/SpectrogramData.qml
new file mode 100644
index 0000000..c199514
--- /dev/null
+++ b/examples/graphs/qmlsurfacegallery/qml/qmlsurfacegallery/SpectrogramData.qml
@@ -0,0 +1,1545 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+
+Item {
+ property alias model: dataModel
+
+ ListModel {
+ id: dataModel
+ ListElement{ radius: "0"; angle: "0"; value: "50"; }
+ ListElement{ radius: "0"; angle: "5"; value: "54.3578"; }
+ ListElement{ radius: "0"; angle: "10"; value: "58.6824"; }
+ ListElement{ radius: "0"; angle: "15"; value: "62.941"; }
+ ListElement{ radius: "0"; angle: "20"; value: "67.101"; }
+ ListElement{ radius: "0"; angle: "25"; value: "71.1309"; }
+ ListElement{ radius: "0"; angle: "30"; value: "75"; }
+ ListElement{ radius: "0"; angle: "35"; value: "78.6788"; }
+ ListElement{ radius: "0"; angle: "40"; value: "82.1394"; }
+ ListElement{ radius: "0"; angle: "45"; value: "85.3553"; }
+ ListElement{ radius: "0"; angle: "50"; value: "88.3022"; }
+ ListElement{ radius: "0"; angle: "55"; value: "90.9576"; }
+ ListElement{ radius: "0"; angle: "60"; value: "93.3013"; }
+ ListElement{ radius: "0"; angle: "65"; value: "95.3154"; }
+ ListElement{ radius: "0"; angle: "70"; value: "96.9846"; }
+ ListElement{ radius: "0"; angle: "75"; value: "98.2963"; }
+ ListElement{ radius: "0"; angle: "80"; value: "99.2404"; }
+ ListElement{ radius: "0"; angle: "85"; value: "99.8097"; }
+ ListElement{ radius: "0"; angle: "90"; value: "100"; }
+ ListElement{ radius: "0"; angle: "95"; value: "99.8097"; }
+ ListElement{ radius: "0"; angle: "100"; value: "99.2404"; }
+ ListElement{ radius: "0"; angle: "105"; value: "98.2963"; }
+ ListElement{ radius: "0"; angle: "110"; value: "96.9846"; }
+ ListElement{ radius: "0"; angle: "115"; value: "95.3154"; }
+ ListElement{ radius: "0"; angle: "120"; value: "93.3013"; }
+ ListElement{ radius: "0"; angle: "125"; value: "90.9576"; }
+ ListElement{ radius: "0"; angle: "130"; value: "88.3022"; }
+ ListElement{ radius: "0"; angle: "135"; value: "85.3553"; }
+ ListElement{ radius: "0"; angle: "140"; value: "82.1394"; }
+ ListElement{ radius: "0"; angle: "145"; value: "78.6788"; }
+ ListElement{ radius: "0"; angle: "150"; value: "75"; }
+ ListElement{ radius: "0"; angle: "155"; value: "71.1309"; }
+ ListElement{ radius: "0"; angle: "160"; value: "67.101"; }
+ ListElement{ radius: "0"; angle: "165"; value: "62.941"; }
+ ListElement{ radius: "0"; angle: "170"; value: "58.6824"; }
+ ListElement{ radius: "0"; angle: "175"; value: "54.3578"; }
+ ListElement{ radius: "0"; angle: "180"; value: "50"; }
+ ListElement{ radius: "0"; angle: "185"; value: "45.6422"; }
+ ListElement{ radius: "0"; angle: "190"; value: "41.3176"; }
+ ListElement{ radius: "0"; angle: "195"; value: "37.059"; }
+ ListElement{ radius: "0"; angle: "200"; value: "32.899"; }
+ ListElement{ radius: "0"; angle: "205"; value: "28.8691"; }
+ ListElement{ radius: "0"; angle: "210"; value: "25"; }
+ ListElement{ radius: "0"; angle: "215"; value: "21.3212"; }
+ ListElement{ radius: "0"; angle: "220"; value: "17.8606"; }
+ ListElement{ radius: "0"; angle: "225"; value: "14.6447"; }
+ ListElement{ radius: "0"; angle: "230"; value: "11.6978"; }
+ ListElement{ radius: "0"; angle: "235"; value: "9.0424"; }
+ ListElement{ radius: "0"; angle: "240"; value: "6.69873"; }
+ ListElement{ radius: "0"; angle: "245"; value: "4.68461"; }
+ ListElement{ radius: "0"; angle: "250"; value: "3.01537"; }
+ ListElement{ radius: "0"; angle: "255"; value: "1.70371"; }
+ ListElement{ radius: "0"; angle: "260"; value: "0.759612"; }
+ ListElement{ radius: "0"; angle: "265"; value: "0.190265"; }
+ ListElement{ radius: "0"; angle: "270"; value: "0"; }
+ ListElement{ radius: "0"; angle: "275"; value: "0.190265"; }
+ ListElement{ radius: "0"; angle: "280"; value: "0.759612"; }
+ ListElement{ radius: "0"; angle: "285"; value: "1.70371"; }
+ ListElement{ radius: "0"; angle: "290"; value: "3.01537"; }
+ ListElement{ radius: "0"; angle: "295"; value: "4.68461"; }
+ ListElement{ radius: "0"; angle: "300"; value: "6.69873"; }
+ ListElement{ radius: "0"; angle: "305"; value: "9.0424"; }
+ ListElement{ radius: "0"; angle: "310"; value: "11.6978"; }
+ ListElement{ radius: "0"; angle: "315"; value: "14.6447"; }
+ ListElement{ radius: "0"; angle: "320"; value: "17.8606"; }
+ ListElement{ radius: "0"; angle: "325"; value: "21.3212"; }
+ ListElement{ radius: "0"; angle: "330"; value: "25"; }
+ ListElement{ radius: "0"; angle: "335"; value: "28.8691"; }
+ ListElement{ radius: "0"; angle: "340"; value: "32.899"; }
+ ListElement{ radius: "0"; angle: "345"; value: "37.059"; }
+ ListElement{ radius: "0"; angle: "350"; value: "41.3176"; }
+ ListElement{ radius: "0"; angle: "355"; value: "45.6422"; }
+ ListElement{ radius: "0"; angle: "360"; value: "50"; }
+ ListElement{ radius: "5"; angle: "0"; value: "49.3844"; }
+ ListElement{ radius: "5"; angle: "5"; value: "53.7422"; }
+ ListElement{ radius: "5"; angle: "10"; value: "58.0668"; }
+ ListElement{ radius: "5"; angle: "15"; value: "62.3254"; }
+ ListElement{ radius: "5"; angle: "20"; value: "66.4854"; }
+ ListElement{ radius: "5"; angle: "25"; value: "70.5153"; }
+ ListElement{ radius: "5"; angle: "30"; value: "74.3844"; }
+ ListElement{ radius: "5"; angle: "35"; value: "78.0632"; }
+ ListElement{ radius: "5"; angle: "40"; value: "81.5238"; }
+ ListElement{ radius: "5"; angle: "45"; value: "84.7398"; }
+ ListElement{ radius: "5"; angle: "50"; value: "87.6866"; }
+ ListElement{ radius: "5"; angle: "55"; value: "90.342"; }
+ ListElement{ radius: "5"; angle: "60"; value: "92.6857"; }
+ ListElement{ radius: "5"; angle: "65"; value: "94.6998"; }
+ ListElement{ radius: "5"; angle: "70"; value: "96.369"; }
+ ListElement{ radius: "5"; angle: "75"; value: "97.6807"; }
+ ListElement{ radius: "5"; angle: "80"; value: "98.6248"; }
+ ListElement{ radius: "5"; angle: "85"; value: "99.1942"; }
+ ListElement{ radius: "5"; angle: "90"; value: "99.3844"; }
+ ListElement{ radius: "5"; angle: "95"; value: "99.1942"; }
+ ListElement{ radius: "5"; angle: "100"; value: "98.6248"; }
+ ListElement{ radius: "5"; angle: "105"; value: "97.6807"; }
+ ListElement{ radius: "5"; angle: "110"; value: "96.369"; }
+ ListElement{ radius: "5"; angle: "115"; value: "94.6998"; }
+ ListElement{ radius: "5"; angle: "120"; value: "92.6857"; }
+ ListElement{ radius: "5"; angle: "125"; value: "90.342"; }
+ ListElement{ radius: "5"; angle: "130"; value: "87.6866"; }
+ ListElement{ radius: "5"; angle: "135"; value: "84.7398"; }
+ ListElement{ radius: "5"; angle: "140"; value: "81.5238"; }
+ ListElement{ radius: "5"; angle: "145"; value: "78.0632"; }
+ ListElement{ radius: "5"; angle: "150"; value: "74.3844"; }
+ ListElement{ radius: "5"; angle: "155"; value: "70.5153"; }
+ ListElement{ radius: "5"; angle: "160"; value: "66.4854"; }
+ ListElement{ radius: "5"; angle: "165"; value: "62.3254"; }
+ ListElement{ radius: "5"; angle: "170"; value: "58.0668"; }
+ ListElement{ radius: "5"; angle: "175"; value: "53.7422"; }
+ ListElement{ radius: "5"; angle: "180"; value: "49.3844"; }
+ ListElement{ radius: "5"; angle: "185"; value: "45.0266"; }
+ ListElement{ radius: "5"; angle: "190"; value: "40.702"; }
+ ListElement{ radius: "5"; angle: "195"; value: "36.4435"; }
+ ListElement{ radius: "5"; angle: "200"; value: "32.2834"; }
+ ListElement{ radius: "5"; angle: "205"; value: "28.2535"; }
+ ListElement{ radius: "5"; angle: "210"; value: "24.3844"; }
+ ListElement{ radius: "5"; angle: "215"; value: "20.7056"; }
+ ListElement{ radius: "5"; angle: "220"; value: "17.245"; }
+ ListElement{ radius: "5"; angle: "225"; value: "14.0291"; }
+ ListElement{ radius: "5"; angle: "230"; value: "11.0822"; }
+ ListElement{ radius: "5"; angle: "235"; value: "8.42681"; }
+ ListElement{ radius: "5"; angle: "240"; value: "6.08315"; }
+ ListElement{ radius: "5"; angle: "245"; value: "4.06903"; }
+ ListElement{ radius: "5"; angle: "250"; value: "2.39979"; }
+ ListElement{ radius: "5"; angle: "255"; value: "1.08813"; }
+ ListElement{ radius: "5"; angle: "260"; value: "0.144029"; }
+ ListElement{ radius: "5"; angle: "265"; value: "-0.425318"; }
+ ListElement{ radius: "5"; angle: "270"; value: "-0.615583"; }
+ ListElement{ radius: "5"; angle: "275"; value: "-0.425318"; }
+ ListElement{ radius: "5"; angle: "280"; value: "0.144029"; }
+ ListElement{ radius: "5"; angle: "285"; value: "1.08813"; }
+ ListElement{ radius: "5"; angle: "290"; value: "2.39979"; }
+ ListElement{ radius: "5"; angle: "295"; value: "4.06903"; }
+ ListElement{ radius: "5"; angle: "300"; value: "6.08315"; }
+ ListElement{ radius: "5"; angle: "305"; value: "8.42681"; }
+ ListElement{ radius: "5"; angle: "310"; value: "11.0822"; }
+ ListElement{ radius: "5"; angle: "315"; value: "14.0291"; }
+ ListElement{ radius: "5"; angle: "320"; value: "17.245"; }
+ ListElement{ radius: "5"; angle: "325"; value: "20.7056"; }
+ ListElement{ radius: "5"; angle: "330"; value: "24.3844"; }
+ ListElement{ radius: "5"; angle: "335"; value: "28.2535"; }
+ ListElement{ radius: "5"; angle: "340"; value: "32.2834"; }
+ ListElement{ radius: "5"; angle: "345"; value: "36.4435"; }
+ ListElement{ radius: "5"; angle: "350"; value: "40.702"; }
+ ListElement{ radius: "5"; angle: "355"; value: "45.0266"; }
+ ListElement{ radius: "5"; angle: "360"; value: "49.3844"; }
+ ListElement{ radius: "10"; angle: "0"; value: "47.5528"; }
+ ListElement{ radius: "10"; angle: "5"; value: "51.9106"; }
+ ListElement{ radius: "10"; angle: "10"; value: "56.2352"; }
+ ListElement{ radius: "10"; angle: "15"; value: "60.4938"; }
+ ListElement{ radius: "10"; angle: "20"; value: "64.6538"; }
+ ListElement{ radius: "10"; angle: "25"; value: "68.6837"; }
+ ListElement{ radius: "10"; angle: "30"; value: "72.5528"; }
+ ListElement{ radius: "10"; angle: "35"; value: "76.2316"; }
+ ListElement{ radius: "10"; angle: "40"; value: "79.6922"; }
+ ListElement{ radius: "10"; angle: "45"; value: "82.9082"; }
+ ListElement{ radius: "10"; angle: "50"; value: "85.855"; }
+ ListElement{ radius: "10"; angle: "55"; value: "88.5104"; }
+ ListElement{ radius: "10"; angle: "60"; value: "90.8541"; }
+ ListElement{ radius: "10"; angle: "65"; value: "92.8682"; }
+ ListElement{ radius: "10"; angle: "70"; value: "94.5375"; }
+ ListElement{ radius: "10"; angle: "75"; value: "95.8491"; }
+ ListElement{ radius: "10"; angle: "80"; value: "96.7932"; }
+ ListElement{ radius: "10"; angle: "85"; value: "97.3626"; }
+ ListElement{ radius: "10"; angle: "90"; value: "97.5528"; }
+ ListElement{ radius: "10"; angle: "95"; value: "97.3626"; }
+ ListElement{ radius: "10"; angle: "100"; value: "96.7932"; }
+ ListElement{ radius: "10"; angle: "105"; value: "95.8491"; }
+ ListElement{ radius: "10"; angle: "110"; value: "94.5375"; }
+ ListElement{ radius: "10"; angle: "115"; value: "92.8682"; }
+ ListElement{ radius: "10"; angle: "120"; value: "90.8541"; }
+ ListElement{ radius: "10"; angle: "125"; value: "88.5104"; }
+ ListElement{ radius: "10"; angle: "130"; value: "85.855"; }
+ ListElement{ radius: "10"; angle: "135"; value: "82.9082"; }
+ ListElement{ radius: "10"; angle: "140"; value: "79.6922"; }
+ ListElement{ radius: "10"; angle: "145"; value: "76.2316"; }
+ ListElement{ radius: "10"; angle: "150"; value: "72.5528"; }
+ ListElement{ radius: "10"; angle: "155"; value: "68.6837"; }
+ ListElement{ radius: "10"; angle: "160"; value: "64.6538"; }
+ ListElement{ radius: "10"; angle: "165"; value: "60.4938"; }
+ ListElement{ radius: "10"; angle: "170"; value: "56.2352"; }
+ ListElement{ radius: "10"; angle: "175"; value: "51.9106"; }
+ ListElement{ radius: "10"; angle: "180"; value: "47.5528"; }
+ ListElement{ radius: "10"; angle: "185"; value: "43.195"; }
+ ListElement{ radius: "10"; angle: "190"; value: "38.8704"; }
+ ListElement{ radius: "10"; angle: "195"; value: "34.6119"; }
+ ListElement{ radius: "10"; angle: "200"; value: "30.4518"; }
+ ListElement{ radius: "10"; angle: "205"; value: "26.4219"; }
+ ListElement{ radius: "10"; angle: "210"; value: "22.5528"; }
+ ListElement{ radius: "10"; angle: "215"; value: "18.874"; }
+ ListElement{ radius: "10"; angle: "220"; value: "15.4134"; }
+ ListElement{ radius: "10"; angle: "225"; value: "12.1975"; }
+ ListElement{ radius: "10"; angle: "230"; value: "9.2506"; }
+ ListElement{ radius: "10"; angle: "235"; value: "6.59522"; }
+ ListElement{ radius: "10"; angle: "240"; value: "4.25156"; }
+ ListElement{ radius: "10"; angle: "245"; value: "2.23744"; }
+ ListElement{ radius: "10"; angle: "250"; value: "0.568195"; }
+ ListElement{ radius: "10"; angle: "255"; value: "-0.743465"; }
+ ListElement{ radius: "10"; angle: "260"; value: "-1.68756"; }
+ ListElement{ radius: "10"; angle: "265"; value: "-2.25691"; }
+ ListElement{ radius: "10"; angle: "270"; value: "-2.44717"; }
+ ListElement{ radius: "10"; angle: "275"; value: "-2.25691"; }
+ ListElement{ radius: "10"; angle: "280"; value: "-1.68756"; }
+ ListElement{ radius: "10"; angle: "285"; value: "-0.743465"; }
+ ListElement{ radius: "10"; angle: "290"; value: "0.568195"; }
+ ListElement{ radius: "10"; angle: "295"; value: "2.23744"; }
+ ListElement{ radius: "10"; angle: "300"; value: "4.25156"; }
+ ListElement{ radius: "10"; angle: "305"; value: "6.59522"; }
+ ListElement{ radius: "10"; angle: "310"; value: "9.2506"; }
+ ListElement{ radius: "10"; angle: "315"; value: "12.1975"; }
+ ListElement{ radius: "10"; angle: "320"; value: "15.4134"; }
+ ListElement{ radius: "10"; angle: "325"; value: "18.874"; }
+ ListElement{ radius: "10"; angle: "330"; value: "22.5528"; }
+ ListElement{ radius: "10"; angle: "335"; value: "26.4219"; }
+ ListElement{ radius: "10"; angle: "340"; value: "30.4518"; }
+ ListElement{ radius: "10"; angle: "345"; value: "34.6119"; }
+ ListElement{ radius: "10"; angle: "350"; value: "38.8704"; }
+ ListElement{ radius: "10"; angle: "355"; value: "43.195"; }
+ ListElement{ radius: "10"; angle: "360"; value: "47.5528"; }
+ ListElement{ radius: "15"; angle: "0"; value: "44.5503"; }
+ ListElement{ radius: "15"; angle: "5"; value: "48.9081"; }
+ ListElement{ radius: "15"; angle: "10"; value: "53.2327"; }
+ ListElement{ radius: "15"; angle: "15"; value: "57.4913"; }
+ ListElement{ radius: "15"; angle: "20"; value: "61.6513"; }
+ ListElement{ radius: "15"; angle: "25"; value: "65.6812"; }
+ ListElement{ radius: "15"; angle: "30"; value: "69.5503"; }
+ ListElement{ radius: "15"; angle: "35"; value: "73.2291"; }
+ ListElement{ radius: "15"; angle: "40"; value: "76.6897"; }
+ ListElement{ radius: "15"; angle: "45"; value: "79.9057"; }
+ ListElement{ radius: "15"; angle: "50"; value: "82.8525"; }
+ ListElement{ radius: "15"; angle: "55"; value: "85.5079"; }
+ ListElement{ radius: "15"; angle: "60"; value: "87.8516"; }
+ ListElement{ radius: "15"; angle: "65"; value: "89.8657"; }
+ ListElement{ radius: "15"; angle: "70"; value: "91.535"; }
+ ListElement{ radius: "15"; angle: "75"; value: "92.8466"; }
+ ListElement{ radius: "15"; angle: "80"; value: "93.7907"; }
+ ListElement{ radius: "15"; angle: "85"; value: "94.3601"; }
+ ListElement{ radius: "15"; angle: "90"; value: "94.5503"; }
+ ListElement{ radius: "15"; angle: "95"; value: "94.3601"; }
+ ListElement{ radius: "15"; angle: "100"; value: "93.7907"; }
+ ListElement{ radius: "15"; angle: "105"; value: "92.8466"; }
+ ListElement{ radius: "15"; angle: "110"; value: "91.535"; }
+ ListElement{ radius: "15"; angle: "115"; value: "89.8657"; }
+ ListElement{ radius: "15"; angle: "120"; value: "87.8516"; }
+ ListElement{ radius: "15"; angle: "125"; value: "85.5079"; }
+ ListElement{ radius: "15"; angle: "130"; value: "82.8525"; }
+ ListElement{ radius: "15"; angle: "135"; value: "79.9057"; }
+ ListElement{ radius: "15"; angle: "140"; value: "76.6897"; }
+ ListElement{ radius: "15"; angle: "145"; value: "73.2291"; }
+ ListElement{ radius: "15"; angle: "150"; value: "69.5503"; }
+ ListElement{ radius: "15"; angle: "155"; value: "65.6812"; }
+ ListElement{ radius: "15"; angle: "160"; value: "61.6513"; }
+ ListElement{ radius: "15"; angle: "165"; value: "57.4913"; }
+ ListElement{ radius: "15"; angle: "170"; value: "53.2327"; }
+ ListElement{ radius: "15"; angle: "175"; value: "48.9081"; }
+ ListElement{ radius: "15"; angle: "180"; value: "44.5503"; }
+ ListElement{ radius: "15"; angle: "185"; value: "40.1925"; }
+ ListElement{ radius: "15"; angle: "190"; value: "35.8679"; }
+ ListElement{ radius: "15"; angle: "195"; value: "31.6094"; }
+ ListElement{ radius: "15"; angle: "200"; value: "27.4493"; }
+ ListElement{ radius: "15"; angle: "205"; value: "23.4194"; }
+ ListElement{ radius: "15"; angle: "210"; value: "19.5503"; }
+ ListElement{ radius: "15"; angle: "215"; value: "15.8715"; }
+ ListElement{ radius: "15"; angle: "220"; value: "12.4109"; }
+ ListElement{ radius: "15"; angle: "225"; value: "9.19499"; }
+ ListElement{ radius: "15"; angle: "230"; value: "6.2481"; }
+ ListElement{ radius: "15"; angle: "235"; value: "3.59272"; }
+ ListElement{ radius: "15"; angle: "240"; value: "1.24906"; }
+ ListElement{ radius: "15"; angle: "245"; value: "-0.765063"; }
+ ListElement{ radius: "15"; angle: "250"; value: "-2.4343"; }
+ ListElement{ radius: "15"; angle: "255"; value: "-3.74597"; }
+ ListElement{ radius: "15"; angle: "260"; value: "-4.69006"; }
+ ListElement{ radius: "15"; angle: "265"; value: "-5.25941"; }
+ ListElement{ radius: "15"; angle: "270"; value: "-5.44967"; }
+ ListElement{ radius: "15"; angle: "275"; value: "-5.25941"; }
+ ListElement{ radius: "15"; angle: "280"; value: "-4.69006"; }
+ ListElement{ radius: "15"; angle: "285"; value: "-3.74597"; }
+ ListElement{ radius: "15"; angle: "290"; value: "-2.4343"; }
+ ListElement{ radius: "15"; angle: "295"; value: "-0.765063"; }
+ ListElement{ radius: "15"; angle: "300"; value: "1.24906"; }
+ ListElement{ radius: "15"; angle: "305"; value: "3.59272"; }
+ ListElement{ radius: "15"; angle: "310"; value: "6.2481"; }
+ ListElement{ radius: "15"; angle: "315"; value: "9.19499"; }
+ ListElement{ radius: "15"; angle: "320"; value: "12.4109"; }
+ ListElement{ radius: "15"; angle: "325"; value: "15.8715"; }
+ ListElement{ radius: "15"; angle: "330"; value: "19.5503"; }
+ ListElement{ radius: "15"; angle: "335"; value: "23.4194"; }
+ ListElement{ radius: "15"; angle: "340"; value: "27.4493"; }
+ ListElement{ radius: "15"; angle: "345"; value: "31.6094"; }
+ ListElement{ radius: "15"; angle: "350"; value: "35.8679"; }
+ ListElement{ radius: "15"; angle: "355"; value: "40.1925"; }
+ ListElement{ radius: "15"; angle: "360"; value: "44.5503"; }
+ ListElement{ radius: "20"; angle: "0"; value: "40.4508"; }
+ ListElement{ radius: "20"; angle: "5"; value: "44.8086"; }
+ ListElement{ radius: "20"; angle: "10"; value: "49.1333"; }
+ ListElement{ radius: "20"; angle: "15"; value: "53.3918"; }
+ ListElement{ radius: "20"; angle: "20"; value: "57.5519"; }
+ ListElement{ radius: "20"; angle: "25"; value: "61.5818"; }
+ ListElement{ radius: "20"; angle: "30"; value: "65.4508"; }
+ ListElement{ radius: "20"; angle: "35"; value: "69.1297"; }
+ ListElement{ radius: "20"; angle: "40"; value: "72.5902"; }
+ ListElement{ radius: "20"; angle: "45"; value: "75.8062"; }
+ ListElement{ radius: "20"; angle: "50"; value: "78.7531"; }
+ ListElement{ radius: "20"; angle: "55"; value: "81.4085"; }
+ ListElement{ radius: "20"; angle: "60"; value: "83.7521"; }
+ ListElement{ radius: "20"; angle: "65"; value: "85.7662"; }
+ ListElement{ radius: "20"; angle: "70"; value: "87.4355"; }
+ ListElement{ radius: "20"; angle: "75"; value: "88.7471"; }
+ ListElement{ radius: "20"; angle: "80"; value: "89.6912"; }
+ ListElement{ radius: "20"; angle: "85"; value: "90.2606"; }
+ ListElement{ radius: "20"; angle: "90"; value: "90.4508"; }
+ ListElement{ radius: "20"; angle: "95"; value: "90.2606"; }
+ ListElement{ radius: "20"; angle: "100"; value: "89.6912"; }
+ ListElement{ radius: "20"; angle: "105"; value: "88.7471"; }
+ ListElement{ radius: "20"; angle: "110"; value: "87.4355"; }
+ ListElement{ radius: "20"; angle: "115"; value: "85.7662"; }
+ ListElement{ radius: "20"; angle: "120"; value: "83.7521"; }
+ ListElement{ radius: "20"; angle: "125"; value: "81.4085"; }
+ ListElement{ radius: "20"; angle: "130"; value: "78.7531"; }
+ ListElement{ radius: "20"; angle: "135"; value: "75.8062"; }
+ ListElement{ radius: "20"; angle: "140"; value: "72.5902"; }
+ ListElement{ radius: "20"; angle: "145"; value: "69.1297"; }
+ ListElement{ radius: "20"; angle: "150"; value: "65.4508"; }
+ ListElement{ radius: "20"; angle: "155"; value: "61.5818"; }
+ ListElement{ radius: "20"; angle: "160"; value: "57.5519"; }
+ ListElement{ radius: "20"; angle: "165"; value: "53.3918"; }
+ ListElement{ radius: "20"; angle: "170"; value: "49.1333"; }
+ ListElement{ radius: "20"; angle: "175"; value: "44.8086"; }
+ ListElement{ radius: "20"; angle: "180"; value: "40.4508"; }
+ ListElement{ radius: "20"; angle: "185"; value: "36.0931"; }
+ ListElement{ radius: "20"; angle: "190"; value: "31.7684"; }
+ ListElement{ radius: "20"; angle: "195"; value: "27.5099"; }
+ ListElement{ radius: "20"; angle: "200"; value: "23.3498"; }
+ ListElement{ radius: "20"; angle: "205"; value: "19.3199"; }
+ ListElement{ radius: "20"; angle: "210"; value: "15.4508"; }
+ ListElement{ radius: "20"; angle: "215"; value: "11.772"; }
+ ListElement{ radius: "20"; angle: "220"; value: "8.31147"; }
+ ListElement{ radius: "20"; angle: "225"; value: "5.09551"; }
+ ListElement{ radius: "20"; angle: "230"; value: "2.14863"; }
+ ListElement{ radius: "20"; angle: "235"; value: "-0.506752"; }
+ ListElement{ radius: "20"; angle: "240"; value: "-2.85042"; }
+ ListElement{ radius: "20"; angle: "245"; value: "-4.86454"; }
+ ListElement{ radius: "20"; angle: "250"; value: "-6.53378"; }
+ ListElement{ radius: "20"; angle: "255"; value: "-7.84544"; }
+ ListElement{ radius: "20"; angle: "260"; value: "-8.78954"; }
+ ListElement{ radius: "20"; angle: "265"; value: "-9.35889"; }
+ ListElement{ radius: "20"; angle: "270"; value: "-9.54915"; }
+ ListElement{ radius: "20"; angle: "275"; value: "-9.35889"; }
+ ListElement{ radius: "20"; angle: "280"; value: "-8.78954"; }
+ ListElement{ radius: "20"; angle: "285"; value: "-7.84544"; }
+ ListElement{ radius: "20"; angle: "290"; value: "-6.53378"; }
+ ListElement{ radius: "20"; angle: "295"; value: "-4.86454"; }
+ ListElement{ radius: "20"; angle: "300"; value: "-2.85042"; }
+ ListElement{ radius: "20"; angle: "305"; value: "-0.506752"; }
+ ListElement{ radius: "20"; angle: "310"; value: "2.14863"; }
+ ListElement{ radius: "20"; angle: "315"; value: "5.09551"; }
+ ListElement{ radius: "20"; angle: "320"; value: "8.31147"; }
+ ListElement{ radius: "20"; angle: "325"; value: "11.772"; }
+ ListElement{ radius: "20"; angle: "330"; value: "15.4508"; }
+ ListElement{ radius: "20"; angle: "335"; value: "19.3199"; }
+ ListElement{ radius: "20"; angle: "340"; value: "23.3498"; }
+ ListElement{ radius: "20"; angle: "345"; value: "27.5099"; }
+ ListElement{ radius: "20"; angle: "350"; value: "31.7684"; }
+ ListElement{ radius: "20"; angle: "355"; value: "36.0931"; }
+ ListElement{ radius: "20"; angle: "360"; value: "40.4508"; }
+ ListElement{ radius: "25"; angle: "0"; value: "35.3553"; }
+ ListElement{ radius: "25"; angle: "5"; value: "39.7131"; }
+ ListElement{ radius: "25"; angle: "10"; value: "44.0377"; }
+ ListElement{ radius: "25"; angle: "15"; value: "48.2963"; }
+ ListElement{ radius: "25"; angle: "20"; value: "52.4563"; }
+ ListElement{ radius: "25"; angle: "25"; value: "56.4863"; }
+ ListElement{ radius: "25"; angle: "30"; value: "60.3553"; }
+ ListElement{ radius: "25"; angle: "35"; value: "64.0342"; }
+ ListElement{ radius: "25"; angle: "40"; value: "67.4947"; }
+ ListElement{ radius: "25"; angle: "45"; value: "70.7107"; }
+ ListElement{ radius: "25"; angle: "50"; value: "73.6576"; }
+ ListElement{ radius: "25"; angle: "55"; value: "76.3129"; }
+ ListElement{ radius: "25"; angle: "60"; value: "78.6566"; }
+ ListElement{ radius: "25"; angle: "65"; value: "80.6707"; }
+ ListElement{ radius: "25"; angle: "70"; value: "82.34"; }
+ ListElement{ radius: "25"; angle: "75"; value: "83.6516"; }
+ ListElement{ radius: "25"; angle: "80"; value: "84.5957"; }
+ ListElement{ radius: "25"; angle: "85"; value: "85.1651"; }
+ ListElement{ radius: "25"; angle: "90"; value: "85.3553"; }
+ ListElement{ radius: "25"; angle: "95"; value: "85.1651"; }
+ ListElement{ radius: "25"; angle: "100"; value: "84.5957"; }
+ ListElement{ radius: "25"; angle: "105"; value: "83.6516"; }
+ ListElement{ radius: "25"; angle: "110"; value: "82.34"; }
+ ListElement{ radius: "25"; angle: "115"; value: "80.6707"; }
+ ListElement{ radius: "25"; angle: "120"; value: "78.6566"; }
+ ListElement{ radius: "25"; angle: "125"; value: "76.3129"; }
+ ListElement{ radius: "25"; angle: "130"; value: "73.6576"; }
+ ListElement{ radius: "25"; angle: "135"; value: "70.7107"; }
+ ListElement{ radius: "25"; angle: "140"; value: "67.4947"; }
+ ListElement{ radius: "25"; angle: "145"; value: "64.0342"; }
+ ListElement{ radius: "25"; angle: "150"; value: "60.3553"; }
+ ListElement{ radius: "25"; angle: "155"; value: "56.4863"; }
+ ListElement{ radius: "25"; angle: "160"; value: "52.4563"; }
+ ListElement{ radius: "25"; angle: "165"; value: "48.2963"; }
+ ListElement{ radius: "25"; angle: "170"; value: "44.0377"; }
+ ListElement{ radius: "25"; angle: "175"; value: "39.7131"; }
+ ListElement{ radius: "25"; angle: "180"; value: "35.3553"; }
+ ListElement{ radius: "25"; angle: "185"; value: "30.9976"; }
+ ListElement{ radius: "25"; angle: "190"; value: "26.6729"; }
+ ListElement{ radius: "25"; angle: "195"; value: "22.4144"; }
+ ListElement{ radius: "25"; angle: "200"; value: "18.2543"; }
+ ListElement{ radius: "25"; angle: "205"; value: "14.2244"; }
+ ListElement{ radius: "25"; angle: "210"; value: "10.3553"; }
+ ListElement{ radius: "25"; angle: "215"; value: "6.67652"; }
+ ListElement{ radius: "25"; angle: "220"; value: "3.21596"; }
+ ListElement{ radius: "25"; angle: "225"; value: "5.55112e-15"; }
+ ListElement{ radius: "25"; angle: "230"; value: "-2.94688"; }
+ ListElement{ radius: "25"; angle: "235"; value: "-5.60226"; }
+ ListElement{ radius: "25"; angle: "240"; value: "-7.94593"; }
+ ListElement{ radius: "25"; angle: "245"; value: "-9.96005"; }
+ ListElement{ radius: "25"; angle: "250"; value: "-11.6293"; }
+ ListElement{ radius: "25"; angle: "255"; value: "-12.941"; }
+ ListElement{ radius: "25"; angle: "260"; value: "-13.885"; }
+ ListElement{ radius: "25"; angle: "265"; value: "-14.4544"; }
+ ListElement{ radius: "25"; angle: "270"; value: "-14.6447"; }
+ ListElement{ radius: "25"; angle: "275"; value: "-14.4544"; }
+ ListElement{ radius: "25"; angle: "280"; value: "-13.885"; }
+ ListElement{ radius: "25"; angle: "285"; value: "-12.941"; }
+ ListElement{ radius: "25"; angle: "290"; value: "-11.6293"; }
+ ListElement{ radius: "25"; angle: "295"; value: "-9.96005"; }
+ ListElement{ radius: "25"; angle: "300"; value: "-7.94593"; }
+ ListElement{ radius: "25"; angle: "305"; value: "-5.60226"; }
+ ListElement{ radius: "25"; angle: "310"; value: "-2.94688"; }
+ ListElement{ radius: "25"; angle: "315"; value: "-5.55112e-15"; }
+ ListElement{ radius: "25"; angle: "320"; value: "3.21596"; }
+ ListElement{ radius: "25"; angle: "325"; value: "6.67652"; }
+ ListElement{ radius: "25"; angle: "330"; value: "10.3553"; }
+ ListElement{ radius: "25"; angle: "335"; value: "14.2244"; }
+ ListElement{ radius: "25"; angle: "340"; value: "18.2543"; }
+ ListElement{ radius: "25"; angle: "345"; value: "22.4144"; }
+ ListElement{ radius: "25"; angle: "350"; value: "26.6729"; }
+ ListElement{ radius: "25"; angle: "355"; value: "30.9976"; }
+ ListElement{ radius: "25"; angle: "360"; value: "35.3553"; }
+ ListElement{ radius: "30"; angle: "0"; value: "29.3893"; }
+ ListElement{ radius: "30"; angle: "5"; value: "33.747"; }
+ ListElement{ radius: "30"; angle: "10"; value: "38.0717"; }
+ ListElement{ radius: "30"; angle: "15"; value: "42.3302"; }
+ ListElement{ radius: "30"; angle: "20"; value: "46.4903"; }
+ ListElement{ radius: "30"; angle: "25"; value: "50.5202"; }
+ ListElement{ radius: "30"; angle: "30"; value: "54.3893"; }
+ ListElement{ radius: "30"; angle: "35"; value: "58.0681"; }
+ ListElement{ radius: "30"; angle: "40"; value: "61.5286"; }
+ ListElement{ radius: "30"; angle: "45"; value: "64.7446"; }
+ ListElement{ radius: "30"; angle: "50"; value: "67.6915"; }
+ ListElement{ radius: "30"; angle: "55"; value: "70.3469"; }
+ ListElement{ radius: "30"; angle: "60"; value: "72.6905"; }
+ ListElement{ radius: "30"; angle: "65"; value: "74.7047"; }
+ ListElement{ radius: "30"; angle: "70"; value: "76.3739"; }
+ ListElement{ radius: "30"; angle: "75"; value: "77.6856"; }
+ ListElement{ radius: "30"; angle: "80"; value: "78.6297"; }
+ ListElement{ radius: "30"; angle: "85"; value: "79.199"; }
+ ListElement{ radius: "30"; angle: "90"; value: "79.3893"; }
+ ListElement{ radius: "30"; angle: "95"; value: "79.199"; }
+ ListElement{ radius: "30"; angle: "100"; value: "78.6297"; }
+ ListElement{ radius: "30"; angle: "105"; value: "77.6856"; }
+ ListElement{ radius: "30"; angle: "110"; value: "76.3739"; }
+ ListElement{ radius: "30"; angle: "115"; value: "74.7047"; }
+ ListElement{ radius: "30"; angle: "120"; value: "72.6905"; }
+ ListElement{ radius: "30"; angle: "125"; value: "70.3469"; }
+ ListElement{ radius: "30"; angle: "130"; value: "67.6915"; }
+ ListElement{ radius: "30"; angle: "135"; value: "64.7446"; }
+ ListElement{ radius: "30"; angle: "140"; value: "61.5286"; }
+ ListElement{ radius: "30"; angle: "145"; value: "58.0681"; }
+ ListElement{ radius: "30"; angle: "150"; value: "54.3893"; }
+ ListElement{ radius: "30"; angle: "155"; value: "50.5202"; }
+ ListElement{ radius: "30"; angle: "160"; value: "46.4903"; }
+ ListElement{ radius: "30"; angle: "165"; value: "42.3302"; }
+ ListElement{ radius: "30"; angle: "170"; value: "38.0717"; }
+ ListElement{ radius: "30"; angle: "175"; value: "33.747"; }
+ ListElement{ radius: "30"; angle: "180"; value: "29.3893"; }
+ ListElement{ radius: "30"; angle: "185"; value: "25.0315"; }
+ ListElement{ radius: "30"; angle: "190"; value: "20.7069"; }
+ ListElement{ radius: "30"; angle: "195"; value: "16.4483"; }
+ ListElement{ radius: "30"; angle: "200"; value: "12.2883"; }
+ ListElement{ radius: "30"; angle: "205"; value: "8.25835"; }
+ ListElement{ radius: "30"; angle: "210"; value: "4.38926"; }
+ ListElement{ radius: "30"; angle: "215"; value: "0.710441"; }
+ ListElement{ radius: "30"; angle: "220"; value: "-2.75012"; }
+ ListElement{ radius: "30"; angle: "225"; value: "-5.96608"; }
+ ListElement{ radius: "30"; angle: "230"; value: "-8.91296"; }
+ ListElement{ radius: "30"; angle: "235"; value: "-11.5683"; }
+ ListElement{ radius: "30"; angle: "240"; value: "-13.912"; }
+ ListElement{ radius: "30"; angle: "245"; value: "-15.9261"; }
+ ListElement{ radius: "30"; angle: "250"; value: "-17.5954"; }
+ ListElement{ radius: "30"; angle: "255"; value: "-18.907"; }
+ ListElement{ radius: "30"; angle: "260"; value: "-19.8511"; }
+ ListElement{ radius: "30"; angle: "265"; value: "-20.4205"; }
+ ListElement{ radius: "30"; angle: "270"; value: "-20.6107"; }
+ ListElement{ radius: "30"; angle: "275"; value: "-20.4205"; }
+ ListElement{ radius: "30"; angle: "280"; value: "-19.8511"; }
+ ListElement{ radius: "30"; angle: "285"; value: "-18.907"; }
+ ListElement{ radius: "30"; angle: "290"; value: "-17.5954"; }
+ ListElement{ radius: "30"; angle: "295"; value: "-15.9261"; }
+ ListElement{ radius: "30"; angle: "300"; value: "-13.912"; }
+ ListElement{ radius: "30"; angle: "305"; value: "-11.5683"; }
+ ListElement{ radius: "30"; angle: "310"; value: "-8.91296"; }
+ ListElement{ radius: "30"; angle: "315"; value: "-5.96608"; }
+ ListElement{ radius: "30"; angle: "320"; value: "-2.75012"; }
+ ListElement{ radius: "30"; angle: "325"; value: "0.710441"; }
+ ListElement{ radius: "30"; angle: "330"; value: "4.38926"; }
+ ListElement{ radius: "30"; angle: "335"; value: "8.25835"; }
+ ListElement{ radius: "30"; angle: "340"; value: "12.2883"; }
+ ListElement{ radius: "30"; angle: "345"; value: "16.4483"; }
+ ListElement{ radius: "30"; angle: "350"; value: "20.7069"; }
+ ListElement{ radius: "30"; angle: "355"; value: "25.0315"; }
+ ListElement{ radius: "30"; angle: "360"; value: "29.3893"; }
+ ListElement{ radius: "35"; angle: "0"; value: "22.6995"; }
+ ListElement{ radius: "35"; angle: "5"; value: "27.0573"; }
+ ListElement{ radius: "35"; angle: "10"; value: "31.3819"; }
+ ListElement{ radius: "35"; angle: "15"; value: "35.6405"; }
+ ListElement{ radius: "35"; angle: "20"; value: "39.8005"; }
+ ListElement{ radius: "35"; angle: "25"; value: "43.8304"; }
+ ListElement{ radius: "35"; angle: "30"; value: "47.6995"; }
+ ListElement{ radius: "35"; angle: "35"; value: "51.3783"; }
+ ListElement{ radius: "35"; angle: "40"; value: "54.8389"; }
+ ListElement{ radius: "35"; angle: "45"; value: "58.0549"; }
+ ListElement{ radius: "35"; angle: "50"; value: "61.0017"; }
+ ListElement{ radius: "35"; angle: "55"; value: "63.6571"; }
+ ListElement{ radius: "35"; angle: "60"; value: "66.0008"; }
+ ListElement{ radius: "35"; angle: "65"; value: "68.0149"; }
+ ListElement{ radius: "35"; angle: "70"; value: "69.6842"; }
+ ListElement{ radius: "35"; angle: "75"; value: "70.9958"; }
+ ListElement{ radius: "35"; angle: "80"; value: "71.9399"; }
+ ListElement{ radius: "35"; angle: "85"; value: "72.5093"; }
+ ListElement{ radius: "35"; angle: "90"; value: "72.6995"; }
+ ListElement{ radius: "35"; angle: "95"; value: "72.5093"; }
+ ListElement{ radius: "35"; angle: "100"; value: "71.9399"; }
+ ListElement{ radius: "35"; angle: "105"; value: "70.9958"; }
+ ListElement{ radius: "35"; angle: "110"; value: "69.6842"; }
+ ListElement{ radius: "35"; angle: "115"; value: "68.0149"; }
+ ListElement{ radius: "35"; angle: "120"; value: "66.0008"; }
+ ListElement{ radius: "35"; angle: "125"; value: "63.6571"; }
+ ListElement{ radius: "35"; angle: "130"; value: "61.0017"; }
+ ListElement{ radius: "35"; angle: "135"; value: "58.0549"; }
+ ListElement{ radius: "35"; angle: "140"; value: "54.8389"; }
+ ListElement{ radius: "35"; angle: "145"; value: "51.3783"; }
+ ListElement{ radius: "35"; angle: "150"; value: "47.6995"; }
+ ListElement{ radius: "35"; angle: "155"; value: "43.8304"; }
+ ListElement{ radius: "35"; angle: "160"; value: "39.8005"; }
+ ListElement{ radius: "35"; angle: "165"; value: "35.6405"; }
+ ListElement{ radius: "35"; angle: "170"; value: "31.3819"; }
+ ListElement{ radius: "35"; angle: "175"; value: "27.0573"; }
+ ListElement{ radius: "35"; angle: "180"; value: "22.6995"; }
+ ListElement{ radius: "35"; angle: "185"; value: "18.3417"; }
+ ListElement{ radius: "35"; angle: "190"; value: "14.0171"; }
+ ListElement{ radius: "35"; angle: "195"; value: "9.75857"; }
+ ListElement{ radius: "35"; angle: "200"; value: "5.59852"; }
+ ListElement{ radius: "35"; angle: "205"; value: "1.56861"; }
+ ListElement{ radius: "35"; angle: "210"; value: "-2.30048"; }
+ ListElement{ radius: "35"; angle: "215"; value: "-5.9793"; }
+ ListElement{ radius: "35"; angle: "220"; value: "-9.43986"; }
+ ListElement{ radius: "35"; angle: "225"; value: "-12.6558"; }
+ ListElement{ radius: "35"; angle: "230"; value: "-15.6027"; }
+ ListElement{ radius: "35"; angle: "235"; value: "-18.2581"; }
+ ListElement{ radius: "35"; angle: "240"; value: "-20.6017"; }
+ ListElement{ radius: "35"; angle: "245"; value: "-22.6159"; }
+ ListElement{ radius: "35"; angle: "250"; value: "-24.2851"; }
+ ListElement{ radius: "35"; angle: "255"; value: "-25.5968"; }
+ ListElement{ radius: "35"; angle: "260"; value: "-26.5409"; }
+ ListElement{ radius: "35"; angle: "265"; value: "-27.1102"; }
+ ListElement{ radius: "35"; angle: "270"; value: "-27.3005"; }
+ ListElement{ radius: "35"; angle: "275"; value: "-27.1102"; }
+ ListElement{ radius: "35"; angle: "280"; value: "-26.5409"; }
+ ListElement{ radius: "35"; angle: "285"; value: "-25.5968"; }
+ ListElement{ radius: "35"; angle: "290"; value: "-24.2851"; }
+ ListElement{ radius: "35"; angle: "295"; value: "-22.6159"; }
+ ListElement{ radius: "35"; angle: "300"; value: "-20.6017"; }
+ ListElement{ radius: "35"; angle: "305"; value: "-18.2581"; }
+ ListElement{ radius: "35"; angle: "310"; value: "-15.6027"; }
+ ListElement{ radius: "35"; angle: "315"; value: "-12.6558"; }
+ ListElement{ radius: "35"; angle: "320"; value: "-9.43986"; }
+ ListElement{ radius: "35"; angle: "325"; value: "-5.9793"; }
+ ListElement{ radius: "35"; angle: "330"; value: "-2.30048"; }
+ ListElement{ radius: "35"; angle: "335"; value: "1.56861"; }
+ ListElement{ radius: "35"; angle: "340"; value: "5.59852"; }
+ ListElement{ radius: "35"; angle: "345"; value: "9.75857"; }
+ ListElement{ radius: "35"; angle: "350"; value: "14.0171"; }
+ ListElement{ radius: "35"; angle: "355"; value: "18.3417"; }
+ ListElement{ radius: "35"; angle: "360"; value: "22.6995"; }
+ ListElement{ radius: "40"; angle: "0"; value: "15.4508"; }
+ ListElement{ radius: "40"; angle: "5"; value: "19.8086"; }
+ ListElement{ radius: "40"; angle: "10"; value: "24.1333"; }
+ ListElement{ radius: "40"; angle: "15"; value: "28.3918"; }
+ ListElement{ radius: "40"; angle: "20"; value: "32.5519"; }
+ ListElement{ radius: "40"; angle: "25"; value: "36.5818"; }
+ ListElement{ radius: "40"; angle: "30"; value: "40.4508"; }
+ ListElement{ radius: "40"; angle: "35"; value: "44.1297"; }
+ ListElement{ radius: "40"; angle: "40"; value: "47.5902"; }
+ ListElement{ radius: "40"; angle: "45"; value: "50.8062"; }
+ ListElement{ radius: "40"; angle: "50"; value: "53.7531"; }
+ ListElement{ radius: "40"; angle: "55"; value: "56.4085"; }
+ ListElement{ radius: "40"; angle: "60"; value: "58.7521"; }
+ ListElement{ radius: "40"; angle: "65"; value: "60.7662"; }
+ ListElement{ radius: "40"; angle: "70"; value: "62.4355"; }
+ ListElement{ radius: "40"; angle: "75"; value: "63.7471"; }
+ ListElement{ radius: "40"; angle: "80"; value: "64.6912"; }
+ ListElement{ radius: "40"; angle: "85"; value: "65.2606"; }
+ ListElement{ radius: "40"; angle: "90"; value: "65.4508"; }
+ ListElement{ radius: "40"; angle: "95"; value: "65.2606"; }
+ ListElement{ radius: "40"; angle: "100"; value: "64.6912"; }
+ ListElement{ radius: "40"; angle: "105"; value: "63.7471"; }
+ ListElement{ radius: "40"; angle: "110"; value: "62.4355"; }
+ ListElement{ radius: "40"; angle: "115"; value: "60.7662"; }
+ ListElement{ radius: "40"; angle: "120"; value: "58.7521"; }
+ ListElement{ radius: "40"; angle: "125"; value: "56.4085"; }
+ ListElement{ radius: "40"; angle: "130"; value: "53.7531"; }
+ ListElement{ radius: "40"; angle: "135"; value: "50.8062"; }
+ ListElement{ radius: "40"; angle: "140"; value: "47.5902"; }
+ ListElement{ radius: "40"; angle: "145"; value: "44.1297"; }
+ ListElement{ radius: "40"; angle: "150"; value: "40.4508"; }
+ ListElement{ radius: "40"; angle: "155"; value: "36.5818"; }
+ ListElement{ radius: "40"; angle: "160"; value: "32.5519"; }
+ ListElement{ radius: "40"; angle: "165"; value: "28.3918"; }
+ ListElement{ radius: "40"; angle: "170"; value: "24.1333"; }
+ ListElement{ radius: "40"; angle: "175"; value: "19.8086"; }
+ ListElement{ radius: "40"; angle: "180"; value: "15.4508"; }
+ ListElement{ radius: "40"; angle: "185"; value: "11.0931"; }
+ ListElement{ radius: "40"; angle: "190"; value: "6.76844"; }
+ ListElement{ radius: "40"; angle: "195"; value: "2.5099"; }
+ ListElement{ radius: "40"; angle: "200"; value: "-1.65016"; }
+ ListElement{ radius: "40"; angle: "205"; value: "-5.68006"; }
+ ListElement{ radius: "40"; angle: "210"; value: "-9.54915"; }
+ ListElement{ radius: "40"; angle: "215"; value: "-13.228"; }
+ ListElement{ radius: "40"; angle: "220"; value: "-16.6885"; }
+ ListElement{ radius: "40"; angle: "225"; value: "-19.9045"; }
+ ListElement{ radius: "40"; angle: "230"; value: "-22.8514"; }
+ ListElement{ radius: "40"; angle: "235"; value: "-25.5068"; }
+ ListElement{ radius: "40"; angle: "240"; value: "-27.8504"; }
+ ListElement{ radius: "40"; angle: "245"; value: "-29.8645"; }
+ ListElement{ radius: "40"; angle: "250"; value: "-31.5338"; }
+ ListElement{ radius: "40"; angle: "255"; value: "-32.8454"; }
+ ListElement{ radius: "40"; angle: "260"; value: "-33.7895"; }
+ ListElement{ radius: "40"; angle: "265"; value: "-34.3589"; }
+ ListElement{ radius: "40"; angle: "270"; value: "-34.5492"; }
+ ListElement{ radius: "40"; angle: "275"; value: "-34.3589"; }
+ ListElement{ radius: "40"; angle: "280"; value: "-33.7895"; }
+ ListElement{ radius: "40"; angle: "285"; value: "-32.8454"; }
+ ListElement{ radius: "40"; angle: "290"; value: "-31.5338"; }
+ ListElement{ radius: "40"; angle: "295"; value: "-29.8645"; }
+ ListElement{ radius: "40"; angle: "300"; value: "-27.8504"; }
+ ListElement{ radius: "40"; angle: "305"; value: "-25.5068"; }
+ ListElement{ radius: "40"; angle: "310"; value: "-22.8514"; }
+ ListElement{ radius: "40"; angle: "315"; value: "-19.9045"; }
+ ListElement{ radius: "40"; angle: "320"; value: "-16.6885"; }
+ ListElement{ radius: "40"; angle: "325"; value: "-13.228"; }
+ ListElement{ radius: "40"; angle: "330"; value: "-9.54915"; }
+ ListElement{ radius: "40"; angle: "335"; value: "-5.68006"; }
+ ListElement{ radius: "40"; angle: "340"; value: "-1.65016"; }
+ ListElement{ radius: "40"; angle: "345"; value: "2.5099"; }
+ ListElement{ radius: "40"; angle: "350"; value: "6.76844"; }
+ ListElement{ radius: "40"; angle: "355"; value: "11.0931"; }
+ ListElement{ radius: "40"; angle: "360"; value: "15.4508"; }
+ ListElement{ radius: "45"; angle: "0"; value: "7.82172"; }
+ ListElement{ radius: "45"; angle: "5"; value: "12.1795"; }
+ ListElement{ radius: "45"; angle: "10"; value: "16.5041"; }
+ ListElement{ radius: "45"; angle: "15"; value: "20.7627"; }
+ ListElement{ radius: "45"; angle: "20"; value: "24.9227"; }
+ ListElement{ radius: "45"; angle: "25"; value: "28.9526"; }
+ ListElement{ radius: "45"; angle: "30"; value: "32.8217"; }
+ ListElement{ radius: "45"; angle: "35"; value: "36.5005"; }
+ ListElement{ radius: "45"; angle: "40"; value: "39.9611"; }
+ ListElement{ radius: "45"; angle: "45"; value: "43.1771"; }
+ ListElement{ radius: "45"; angle: "50"; value: "46.1239"; }
+ ListElement{ radius: "45"; angle: "55"; value: "48.7793"; }
+ ListElement{ radius: "45"; angle: "60"; value: "51.123"; }
+ ListElement{ radius: "45"; angle: "65"; value: "53.1371"; }
+ ListElement{ radius: "45"; angle: "70"; value: "54.8064"; }
+ ListElement{ radius: "45"; angle: "75"; value: "56.118"; }
+ ListElement{ radius: "45"; angle: "80"; value: "57.0621"; }
+ ListElement{ radius: "45"; angle: "85"; value: "57.6315"; }
+ ListElement{ radius: "45"; angle: "90"; value: "57.8217"; }
+ ListElement{ radius: "45"; angle: "95"; value: "57.6315"; }
+ ListElement{ radius: "45"; angle: "100"; value: "57.0621"; }
+ ListElement{ radius: "45"; angle: "105"; value: "56.118"; }
+ ListElement{ radius: "45"; angle: "110"; value: "54.8064"; }
+ ListElement{ radius: "45"; angle: "115"; value: "53.1371"; }
+ ListElement{ radius: "45"; angle: "120"; value: "51.123"; }
+ ListElement{ radius: "45"; angle: "125"; value: "48.7793"; }
+ ListElement{ radius: "45"; angle: "130"; value: "46.1239"; }
+ ListElement{ radius: "45"; angle: "135"; value: "43.1771"; }
+ ListElement{ radius: "45"; angle: "140"; value: "39.9611"; }
+ ListElement{ radius: "45"; angle: "145"; value: "36.5005"; }
+ ListElement{ radius: "45"; angle: "150"; value: "32.8217"; }
+ ListElement{ radius: "45"; angle: "155"; value: "28.9526"; }
+ ListElement{ radius: "45"; angle: "160"; value: "24.9227"; }
+ ListElement{ radius: "45"; angle: "165"; value: "20.7627"; }
+ ListElement{ radius: "45"; angle: "170"; value: "16.5041"; }
+ ListElement{ radius: "45"; angle: "175"; value: "12.1795"; }
+ ListElement{ radius: "45"; angle: "180"; value: "7.82172"; }
+ ListElement{ radius: "45"; angle: "185"; value: "3.46394"; }
+ ListElement{ radius: "45"; angle: "190"; value: "-0.860686"; }
+ ListElement{ radius: "45"; angle: "195"; value: "-5.11923"; }
+ ListElement{ radius: "45"; angle: "200"; value: "-9.27928"; }
+ ListElement{ radius: "45"; angle: "205"; value: "-13.3092"; }
+ ListElement{ radius: "45"; angle: "210"; value: "-17.1783"; }
+ ListElement{ radius: "45"; angle: "215"; value: "-20.8571"; }
+ ListElement{ radius: "45"; angle: "220"; value: "-24.3177"; }
+ ListElement{ radius: "45"; angle: "225"; value: "-27.5336"; }
+ ListElement{ radius: "45"; angle: "230"; value: "-30.4805"; }
+ ListElement{ radius: "45"; angle: "235"; value: "-33.1359"; }
+ ListElement{ radius: "45"; angle: "240"; value: "-35.4795"; }
+ ListElement{ radius: "45"; angle: "245"; value: "-37.4937"; }
+ ListElement{ radius: "45"; angle: "250"; value: "-39.1629"; }
+ ListElement{ radius: "45"; angle: "255"; value: "-40.4746"; }
+ ListElement{ radius: "45"; angle: "260"; value: "-41.4187"; }
+ ListElement{ radius: "45"; angle: "265"; value: "-41.988"; }
+ ListElement{ radius: "45"; angle: "270"; value: "-42.1783"; }
+ ListElement{ radius: "45"; angle: "275"; value: "-41.988"; }
+ ListElement{ radius: "45"; angle: "280"; value: "-41.4187"; }
+ ListElement{ radius: "45"; angle: "285"; value: "-40.4746"; }
+ ListElement{ radius: "45"; angle: "290"; value: "-39.1629"; }
+ ListElement{ radius: "45"; angle: "295"; value: "-37.4937"; }
+ ListElement{ radius: "45"; angle: "300"; value: "-35.4795"; }
+ ListElement{ radius: "45"; angle: "305"; value: "-33.1359"; }
+ ListElement{ radius: "45"; angle: "310"; value: "-30.4805"; }
+ ListElement{ radius: "45"; angle: "315"; value: "-27.5336"; }
+ ListElement{ radius: "45"; angle: "320"; value: "-24.3177"; }
+ ListElement{ radius: "45"; angle: "325"; value: "-20.8571"; }
+ ListElement{ radius: "45"; angle: "330"; value: "-17.1783"; }
+ ListElement{ radius: "45"; angle: "335"; value: "-13.3092"; }
+ ListElement{ radius: "45"; angle: "340"; value: "-9.27928"; }
+ ListElement{ radius: "45"; angle: "345"; value: "-5.11923"; }
+ ListElement{ radius: "45"; angle: "350"; value: "-0.860686"; }
+ ListElement{ radius: "45"; angle: "355"; value: "3.46394"; }
+ ListElement{ radius: "45"; angle: "360"; value: "7.82172"; }
+ ListElement{ radius: "50"; angle: "0"; value: "3.06162e-15"; }
+ ListElement{ radius: "50"; angle: "5"; value: "4.35779"; }
+ ListElement{ radius: "50"; angle: "10"; value: "8.68241"; }
+ ListElement{ radius: "50"; angle: "15"; value: "12.941"; }
+ ListElement{ radius: "50"; angle: "20"; value: "17.101"; }
+ ListElement{ radius: "50"; angle: "25"; value: "21.1309"; }
+ ListElement{ radius: "50"; angle: "30"; value: "25"; }
+ ListElement{ radius: "50"; angle: "35"; value: "28.6788"; }
+ ListElement{ radius: "50"; angle: "40"; value: "32.1394"; }
+ ListElement{ radius: "50"; angle: "45"; value: "35.3553"; }
+ ListElement{ radius: "50"; angle: "50"; value: "38.3022"; }
+ ListElement{ radius: "50"; angle: "55"; value: "40.9576"; }
+ ListElement{ radius: "50"; angle: "60"; value: "43.3013"; }
+ ListElement{ radius: "50"; angle: "65"; value: "45.3154"; }
+ ListElement{ radius: "50"; angle: "70"; value: "46.9846"; }
+ ListElement{ radius: "50"; angle: "75"; value: "48.2963"; }
+ ListElement{ radius: "50"; angle: "80"; value: "49.2404"; }
+ ListElement{ radius: "50"; angle: "85"; value: "49.8097"; }
+ ListElement{ radius: "50"; angle: "90"; value: "50"; }
+ ListElement{ radius: "50"; angle: "95"; value: "49.8097"; }
+ ListElement{ radius: "50"; angle: "100"; value: "49.2404"; }
+ ListElement{ radius: "50"; angle: "105"; value: "48.2963"; }
+ ListElement{ radius: "50"; angle: "110"; value: "46.9846"; }
+ ListElement{ radius: "50"; angle: "115"; value: "45.3154"; }
+ ListElement{ radius: "50"; angle: "120"; value: "43.3013"; }
+ ListElement{ radius: "50"; angle: "125"; value: "40.9576"; }
+ ListElement{ radius: "50"; angle: "130"; value: "38.3022"; }
+ ListElement{ radius: "50"; angle: "135"; value: "35.3553"; }
+ ListElement{ radius: "50"; angle: "140"; value: "32.1394"; }
+ ListElement{ radius: "50"; angle: "145"; value: "28.6788"; }
+ ListElement{ radius: "50"; angle: "150"; value: "25"; }
+ ListElement{ radius: "50"; angle: "155"; value: "21.1309"; }
+ ListElement{ radius: "50"; angle: "160"; value: "17.101"; }
+ ListElement{ radius: "50"; angle: "165"; value: "12.941"; }
+ ListElement{ radius: "50"; angle: "170"; value: "8.68241"; }
+ ListElement{ radius: "50"; angle: "175"; value: "4.35779"; }
+ ListElement{ radius: "50"; angle: "180"; value: "9.18485e-15"; }
+ ListElement{ radius: "50"; angle: "185"; value: "-4.35779"; }
+ ListElement{ radius: "50"; angle: "190"; value: "-8.68241"; }
+ ListElement{ radius: "50"; angle: "195"; value: "-12.941"; }
+ ListElement{ radius: "50"; angle: "200"; value: "-17.101"; }
+ ListElement{ radius: "50"; angle: "205"; value: "-21.1309"; }
+ ListElement{ radius: "50"; angle: "210"; value: "-25"; }
+ ListElement{ radius: "50"; angle: "215"; value: "-28.6788"; }
+ ListElement{ radius: "50"; angle: "220"; value: "-32.1394"; }
+ ListElement{ radius: "50"; angle: "225"; value: "-35.3553"; }
+ ListElement{ radius: "50"; angle: "230"; value: "-38.3022"; }
+ ListElement{ radius: "50"; angle: "235"; value: "-40.9576"; }
+ ListElement{ radius: "50"; angle: "240"; value: "-43.3013"; }
+ ListElement{ radius: "50"; angle: "245"; value: "-45.3154"; }
+ ListElement{ radius: "50"; angle: "250"; value: "-46.9846"; }
+ ListElement{ radius: "50"; angle: "255"; value: "-48.2963"; }
+ ListElement{ radius: "50"; angle: "260"; value: "-49.2404"; }
+ ListElement{ radius: "50"; angle: "265"; value: "-49.8097"; }
+ ListElement{ radius: "50"; angle: "270"; value: "-50"; }
+ ListElement{ radius: "50"; angle: "275"; value: "-49.8097"; }
+ ListElement{ radius: "50"; angle: "280"; value: "-49.2404"; }
+ ListElement{ radius: "50"; angle: "285"; value: "-48.2963"; }
+ ListElement{ radius: "50"; angle: "290"; value: "-46.9846"; }
+ ListElement{ radius: "50"; angle: "295"; value: "-45.3154"; }
+ ListElement{ radius: "50"; angle: "300"; value: "-43.3013"; }
+ ListElement{ radius: "50"; angle: "305"; value: "-40.9576"; }
+ ListElement{ radius: "50"; angle: "310"; value: "-38.3022"; }
+ ListElement{ radius: "50"; angle: "315"; value: "-35.3553"; }
+ ListElement{ radius: "50"; angle: "320"; value: "-32.1394"; }
+ ListElement{ radius: "50"; angle: "325"; value: "-28.6788"; }
+ ListElement{ radius: "50"; angle: "330"; value: "-25"; }
+ ListElement{ radius: "50"; angle: "335"; value: "-21.1309"; }
+ ListElement{ radius: "50"; angle: "340"; value: "-17.101"; }
+ ListElement{ radius: "50"; angle: "345"; value: "-12.941"; }
+ ListElement{ radius: "50"; angle: "350"; value: "-8.68241"; }
+ ListElement{ radius: "50"; angle: "355"; value: "-4.35779"; }
+ ListElement{ radius: "50"; angle: "360"; value: "-9.18485e-15"; }
+ ListElement{ radius: "55"; angle: "0"; value: "-7.82172"; }
+ ListElement{ radius: "55"; angle: "5"; value: "-3.46394"; }
+ ListElement{ radius: "55"; angle: "10"; value: "0.860686"; }
+ ListElement{ radius: "55"; angle: "15"; value: "5.11923"; }
+ ListElement{ radius: "55"; angle: "20"; value: "9.27928"; }
+ ListElement{ radius: "55"; angle: "25"; value: "13.3092"; }
+ ListElement{ radius: "55"; angle: "30"; value: "17.1783"; }
+ ListElement{ radius: "55"; angle: "35"; value: "20.8571"; }
+ ListElement{ radius: "55"; angle: "40"; value: "24.3177"; }
+ ListElement{ radius: "55"; angle: "45"; value: "27.5336"; }
+ ListElement{ radius: "55"; angle: "50"; value: "30.4805"; }
+ ListElement{ radius: "55"; angle: "55"; value: "33.1359"; }
+ ListElement{ radius: "55"; angle: "60"; value: "35.4795"; }
+ ListElement{ radius: "55"; angle: "65"; value: "37.4937"; }
+ ListElement{ radius: "55"; angle: "70"; value: "39.1629"; }
+ ListElement{ radius: "55"; angle: "75"; value: "40.4746"; }
+ ListElement{ radius: "55"; angle: "80"; value: "41.4187"; }
+ ListElement{ radius: "55"; angle: "85"; value: "41.988"; }
+ ListElement{ radius: "55"; angle: "90"; value: "42.1783"; }
+ ListElement{ radius: "55"; angle: "95"; value: "41.988"; }
+ ListElement{ radius: "55"; angle: "100"; value: "41.4187"; }
+ ListElement{ radius: "55"; angle: "105"; value: "40.4746"; }
+ ListElement{ radius: "55"; angle: "110"; value: "39.1629"; }
+ ListElement{ radius: "55"; angle: "115"; value: "37.4937"; }
+ ListElement{ radius: "55"; angle: "120"; value: "35.4795"; }
+ ListElement{ radius: "55"; angle: "125"; value: "33.1359"; }
+ ListElement{ radius: "55"; angle: "130"; value: "30.4805"; }
+ ListElement{ radius: "55"; angle: "135"; value: "27.5336"; }
+ ListElement{ radius: "55"; angle: "140"; value: "24.3177"; }
+ ListElement{ radius: "55"; angle: "145"; value: "20.8571"; }
+ ListElement{ radius: "55"; angle: "150"; value: "17.1783"; }
+ ListElement{ radius: "55"; angle: "155"; value: "13.3092"; }
+ ListElement{ radius: "55"; angle: "160"; value: "9.27928"; }
+ ListElement{ radius: "55"; angle: "165"; value: "5.11923"; }
+ ListElement{ radius: "55"; angle: "170"; value: "0.860686"; }
+ ListElement{ radius: "55"; angle: "175"; value: "-3.46394"; }
+ ListElement{ radius: "55"; angle: "180"; value: "-7.82172"; }
+ ListElement{ radius: "55"; angle: "185"; value: "-12.1795"; }
+ ListElement{ radius: "55"; angle: "190"; value: "-16.5041"; }
+ ListElement{ radius: "55"; angle: "195"; value: "-20.7627"; }
+ ListElement{ radius: "55"; angle: "200"; value: "-24.9227"; }
+ ListElement{ radius: "55"; angle: "205"; value: "-28.9526"; }
+ ListElement{ radius: "55"; angle: "210"; value: "-32.8217"; }
+ ListElement{ radius: "55"; angle: "215"; value: "-36.5005"; }
+ ListElement{ radius: "55"; angle: "220"; value: "-39.9611"; }
+ ListElement{ radius: "55"; angle: "225"; value: "-43.1771"; }
+ ListElement{ radius: "55"; angle: "230"; value: "-46.1239"; }
+ ListElement{ radius: "55"; angle: "235"; value: "-48.7793"; }
+ ListElement{ radius: "55"; angle: "240"; value: "-51.123"; }
+ ListElement{ radius: "55"; angle: "245"; value: "-53.1371"; }
+ ListElement{ radius: "55"; angle: "250"; value: "-54.8064"; }
+ ListElement{ radius: "55"; angle: "255"; value: "-56.118"; }
+ ListElement{ radius: "55"; angle: "260"; value: "-57.0621"; }
+ ListElement{ radius: "55"; angle: "265"; value: "-57.6315"; }
+ ListElement{ radius: "55"; angle: "270"; value: "-57.8217"; }
+ ListElement{ radius: "55"; angle: "275"; value: "-57.6315"; }
+ ListElement{ radius: "55"; angle: "280"; value: "-57.0621"; }
+ ListElement{ radius: "55"; angle: "285"; value: "-56.118"; }
+ ListElement{ radius: "55"; angle: "290"; value: "-54.8064"; }
+ ListElement{ radius: "55"; angle: "295"; value: "-53.1371"; }
+ ListElement{ radius: "55"; angle: "300"; value: "-51.123"; }
+ ListElement{ radius: "55"; angle: "305"; value: "-48.7793"; }
+ ListElement{ radius: "55"; angle: "310"; value: "-46.1239"; }
+ ListElement{ radius: "55"; angle: "315"; value: "-43.1771"; }
+ ListElement{ radius: "55"; angle: "320"; value: "-39.9611"; }
+ ListElement{ radius: "55"; angle: "325"; value: "-36.5005"; }
+ ListElement{ radius: "55"; angle: "330"; value: "-32.8217"; }
+ ListElement{ radius: "55"; angle: "335"; value: "-28.9526"; }
+ ListElement{ radius: "55"; angle: "340"; value: "-24.9227"; }
+ ListElement{ radius: "55"; angle: "345"; value: "-20.7627"; }
+ ListElement{ radius: "55"; angle: "350"; value: "-16.5041"; }
+ ListElement{ radius: "55"; angle: "355"; value: "-12.1795"; }
+ ListElement{ radius: "55"; angle: "360"; value: "-7.82172"; }
+ ListElement{ radius: "60"; angle: "0"; value: "-15.4508"; }
+ ListElement{ radius: "60"; angle: "5"; value: "-11.0931"; }
+ ListElement{ radius: "60"; angle: "10"; value: "-6.76844"; }
+ ListElement{ radius: "60"; angle: "15"; value: "-2.5099"; }
+ ListElement{ radius: "60"; angle: "20"; value: "1.65016"; }
+ ListElement{ radius: "60"; angle: "25"; value: "5.68006"; }
+ ListElement{ radius: "60"; angle: "30"; value: "9.54915"; }
+ ListElement{ radius: "60"; angle: "35"; value: "13.228"; }
+ ListElement{ radius: "60"; angle: "40"; value: "16.6885"; }
+ ListElement{ radius: "60"; angle: "45"; value: "19.9045"; }
+ ListElement{ radius: "60"; angle: "50"; value: "22.8514"; }
+ ListElement{ radius: "60"; angle: "55"; value: "25.5068"; }
+ ListElement{ radius: "60"; angle: "60"; value: "27.8504"; }
+ ListElement{ radius: "60"; angle: "65"; value: "29.8645"; }
+ ListElement{ radius: "60"; angle: "70"; value: "31.5338"; }
+ ListElement{ radius: "60"; angle: "75"; value: "32.8454"; }
+ ListElement{ radius: "60"; angle: "80"; value: "33.7895"; }
+ ListElement{ radius: "60"; angle: "85"; value: "34.3589"; }
+ ListElement{ radius: "60"; angle: "90"; value: "34.5492"; }
+ ListElement{ radius: "60"; angle: "95"; value: "34.3589"; }
+ ListElement{ radius: "60"; angle: "100"; value: "33.7895"; }
+ ListElement{ radius: "60"; angle: "105"; value: "32.8454"; }
+ ListElement{ radius: "60"; angle: "110"; value: "31.5338"; }
+ ListElement{ radius: "60"; angle: "115"; value: "29.8645"; }
+ ListElement{ radius: "60"; angle: "120"; value: "27.8504"; }
+ ListElement{ radius: "60"; angle: "125"; value: "25.5068"; }
+ ListElement{ radius: "60"; angle: "130"; value: "22.8514"; }
+ ListElement{ radius: "60"; angle: "135"; value: "19.9045"; }
+ ListElement{ radius: "60"; angle: "140"; value: "16.6885"; }
+ ListElement{ radius: "60"; angle: "145"; value: "13.228"; }
+ ListElement{ radius: "60"; angle: "150"; value: "9.54915"; }
+ ListElement{ radius: "60"; angle: "155"; value: "5.68006"; }
+ ListElement{ radius: "60"; angle: "160"; value: "1.65016"; }
+ ListElement{ radius: "60"; angle: "165"; value: "-2.5099"; }
+ ListElement{ radius: "60"; angle: "170"; value: "-6.76844"; }
+ ListElement{ radius: "60"; angle: "175"; value: "-11.0931"; }
+ ListElement{ radius: "60"; angle: "180"; value: "-15.4508"; }
+ ListElement{ radius: "60"; angle: "185"; value: "-19.8086"; }
+ ListElement{ radius: "60"; angle: "190"; value: "-24.1333"; }
+ ListElement{ radius: "60"; angle: "195"; value: "-28.3918"; }
+ ListElement{ radius: "60"; angle: "200"; value: "-32.5519"; }
+ ListElement{ radius: "60"; angle: "205"; value: "-36.5818"; }
+ ListElement{ radius: "60"; angle: "210"; value: "-40.4508"; }
+ ListElement{ radius: "60"; angle: "215"; value: "-44.1297"; }
+ ListElement{ radius: "60"; angle: "220"; value: "-47.5902"; }
+ ListElement{ radius: "60"; angle: "225"; value: "-50.8062"; }
+ ListElement{ radius: "60"; angle: "230"; value: "-53.7531"; }
+ ListElement{ radius: "60"; angle: "235"; value: "-56.4085"; }
+ ListElement{ radius: "60"; angle: "240"; value: "-58.7521"; }
+ ListElement{ radius: "60"; angle: "245"; value: "-60.7662"; }
+ ListElement{ radius: "60"; angle: "250"; value: "-62.4355"; }
+ ListElement{ radius: "60"; angle: "255"; value: "-63.7471"; }
+ ListElement{ radius: "60"; angle: "260"; value: "-64.6912"; }
+ ListElement{ radius: "60"; angle: "265"; value: "-65.2606"; }
+ ListElement{ radius: "60"; angle: "270"; value: "-65.4508"; }
+ ListElement{ radius: "60"; angle: "275"; value: "-65.2606"; }
+ ListElement{ radius: "60"; angle: "280"; value: "-64.6912"; }
+ ListElement{ radius: "60"; angle: "285"; value: "-63.7471"; }
+ ListElement{ radius: "60"; angle: "290"; value: "-62.4355"; }
+ ListElement{ radius: "60"; angle: "295"; value: "-60.7662"; }
+ ListElement{ radius: "60"; angle: "300"; value: "-58.7521"; }
+ ListElement{ radius: "60"; angle: "305"; value: "-56.4085"; }
+ ListElement{ radius: "60"; angle: "310"; value: "-53.7531"; }
+ ListElement{ radius: "60"; angle: "315"; value: "-50.8062"; }
+ ListElement{ radius: "60"; angle: "320"; value: "-47.5902"; }
+ ListElement{ radius: "60"; angle: "325"; value: "-44.1297"; }
+ ListElement{ radius: "60"; angle: "330"; value: "-40.4508"; }
+ ListElement{ radius: "60"; angle: "335"; value: "-36.5818"; }
+ ListElement{ radius: "60"; angle: "340"; value: "-32.5519"; }
+ ListElement{ radius: "60"; angle: "345"; value: "-28.3918"; }
+ ListElement{ radius: "60"; angle: "350"; value: "-24.1333"; }
+ ListElement{ radius: "60"; angle: "355"; value: "-19.8086"; }
+ ListElement{ radius: "60"; angle: "360"; value: "-15.4508"; }
+ ListElement{ radius: "65"; angle: "0"; value: "-22.6995"; }
+ ListElement{ radius: "65"; angle: "5"; value: "-18.3417"; }
+ ListElement{ radius: "65"; angle: "10"; value: "-14.0171"; }
+ ListElement{ radius: "65"; angle: "15"; value: "-9.75857"; }
+ ListElement{ radius: "65"; angle: "20"; value: "-5.59852"; }
+ ListElement{ radius: "65"; angle: "25"; value: "-1.56861"; }
+ ListElement{ radius: "65"; angle: "30"; value: "2.30048"; }
+ ListElement{ radius: "65"; angle: "35"; value: "5.9793"; }
+ ListElement{ radius: "65"; angle: "40"; value: "9.43986"; }
+ ListElement{ radius: "65"; angle: "45"; value: "12.6558"; }
+ ListElement{ radius: "65"; angle: "50"; value: "15.6027"; }
+ ListElement{ radius: "65"; angle: "55"; value: "18.2581"; }
+ ListElement{ radius: "65"; angle: "60"; value: "20.6017"; }
+ ListElement{ radius: "65"; angle: "65"; value: "22.6159"; }
+ ListElement{ radius: "65"; angle: "70"; value: "24.2851"; }
+ ListElement{ radius: "65"; angle: "75"; value: "25.5968"; }
+ ListElement{ radius: "65"; angle: "80"; value: "26.5409"; }
+ ListElement{ radius: "65"; angle: "85"; value: "27.1102"; }
+ ListElement{ radius: "65"; angle: "90"; value: "27.3005"; }
+ ListElement{ radius: "65"; angle: "95"; value: "27.1102"; }
+ ListElement{ radius: "65"; angle: "100"; value: "26.5409"; }
+ ListElement{ radius: "65"; angle: "105"; value: "25.5968"; }
+ ListElement{ radius: "65"; angle: "110"; value: "24.2851"; }
+ ListElement{ radius: "65"; angle: "115"; value: "22.6159"; }
+ ListElement{ radius: "65"; angle: "120"; value: "20.6017"; }
+ ListElement{ radius: "65"; angle: "125"; value: "18.2581"; }
+ ListElement{ radius: "65"; angle: "130"; value: "15.6027"; }
+ ListElement{ radius: "65"; angle: "135"; value: "12.6558"; }
+ ListElement{ radius: "65"; angle: "140"; value: "9.43986"; }
+ ListElement{ radius: "65"; angle: "145"; value: "5.9793"; }
+ ListElement{ radius: "65"; angle: "150"; value: "2.30048"; }
+ ListElement{ radius: "65"; angle: "155"; value: "-1.56861"; }
+ ListElement{ radius: "65"; angle: "160"; value: "-5.59852"; }
+ ListElement{ radius: "65"; angle: "165"; value: "-9.75857"; }
+ ListElement{ radius: "65"; angle: "170"; value: "-14.0171"; }
+ ListElement{ radius: "65"; angle: "175"; value: "-18.3417"; }
+ ListElement{ radius: "65"; angle: "180"; value: "-22.6995"; }
+ ListElement{ radius: "65"; angle: "185"; value: "-27.0573"; }
+ ListElement{ radius: "65"; angle: "190"; value: "-31.3819"; }
+ ListElement{ radius: "65"; angle: "195"; value: "-35.6405"; }
+ ListElement{ radius: "65"; angle: "200"; value: "-39.8005"; }
+ ListElement{ radius: "65"; angle: "205"; value: "-43.8304"; }
+ ListElement{ radius: "65"; angle: "210"; value: "-47.6995"; }
+ ListElement{ radius: "65"; angle: "215"; value: "-51.3783"; }
+ ListElement{ radius: "65"; angle: "220"; value: "-54.8389"; }
+ ListElement{ radius: "65"; angle: "225"; value: "-58.0549"; }
+ ListElement{ radius: "65"; angle: "230"; value: "-61.0017"; }
+ ListElement{ radius: "65"; angle: "235"; value: "-63.6571"; }
+ ListElement{ radius: "65"; angle: "240"; value: "-66.0008"; }
+ ListElement{ radius: "65"; angle: "245"; value: "-68.0149"; }
+ ListElement{ radius: "65"; angle: "250"; value: "-69.6842"; }
+ ListElement{ radius: "65"; angle: "255"; value: "-70.9958"; }
+ ListElement{ radius: "65"; angle: "260"; value: "-71.9399"; }
+ ListElement{ radius: "65"; angle: "265"; value: "-72.5093"; }
+ ListElement{ radius: "65"; angle: "270"; value: "-72.6995"; }
+ ListElement{ radius: "65"; angle: "275"; value: "-72.5093"; }
+ ListElement{ radius: "65"; angle: "280"; value: "-71.9399"; }
+ ListElement{ radius: "65"; angle: "285"; value: "-70.9958"; }
+ ListElement{ radius: "65"; angle: "290"; value: "-69.6842"; }
+ ListElement{ radius: "65"; angle: "295"; value: "-68.0149"; }
+ ListElement{ radius: "65"; angle: "300"; value: "-66.0008"; }
+ ListElement{ radius: "65"; angle: "305"; value: "-63.6571"; }
+ ListElement{ radius: "65"; angle: "310"; value: "-61.0017"; }
+ ListElement{ radius: "65"; angle: "315"; value: "-58.0549"; }
+ ListElement{ radius: "65"; angle: "320"; value: "-54.8389"; }
+ ListElement{ radius: "65"; angle: "325"; value: "-51.3783"; }
+ ListElement{ radius: "65"; angle: "330"; value: "-47.6995"; }
+ ListElement{ radius: "65"; angle: "335"; value: "-43.8304"; }
+ ListElement{ radius: "65"; angle: "340"; value: "-39.8005"; }
+ ListElement{ radius: "65"; angle: "345"; value: "-35.6405"; }
+ ListElement{ radius: "65"; angle: "350"; value: "-31.3819"; }
+ ListElement{ radius: "65"; angle: "355"; value: "-27.0573"; }
+ ListElement{ radius: "65"; angle: "360"; value: "-22.6995"; }
+ ListElement{ radius: "70"; angle: "0"; value: "-29.3893"; }
+ ListElement{ radius: "70"; angle: "5"; value: "-25.0315"; }
+ ListElement{ radius: "70"; angle: "10"; value: "-20.7069"; }
+ ListElement{ radius: "70"; angle: "15"; value: "-16.4483"; }
+ ListElement{ radius: "70"; angle: "20"; value: "-12.2883"; }
+ ListElement{ radius: "70"; angle: "25"; value: "-8.25835"; }
+ ListElement{ radius: "70"; angle: "30"; value: "-4.38926"; }
+ ListElement{ radius: "70"; angle: "35"; value: "-0.710441"; }
+ ListElement{ radius: "70"; angle: "40"; value: "2.75012"; }
+ ListElement{ radius: "70"; angle: "45"; value: "5.96608"; }
+ ListElement{ radius: "70"; angle: "50"; value: "8.91296"; }
+ ListElement{ radius: "70"; angle: "55"; value: "11.5683"; }
+ ListElement{ radius: "70"; angle: "60"; value: "13.912"; }
+ ListElement{ radius: "70"; angle: "65"; value: "15.9261"; }
+ ListElement{ radius: "70"; angle: "70"; value: "17.5954"; }
+ ListElement{ radius: "70"; angle: "75"; value: "18.907"; }
+ ListElement{ radius: "70"; angle: "80"; value: "19.8511"; }
+ ListElement{ radius: "70"; angle: "85"; value: "20.4205"; }
+ ListElement{ radius: "70"; angle: "90"; value: "20.6107"; }
+ ListElement{ radius: "70"; angle: "95"; value: "20.4205"; }
+ ListElement{ radius: "70"; angle: "100"; value: "19.8511"; }
+ ListElement{ radius: "70"; angle: "105"; value: "18.907"; }
+ ListElement{ radius: "70"; angle: "110"; value: "17.5954"; }
+ ListElement{ radius: "70"; angle: "115"; value: "15.9261"; }
+ ListElement{ radius: "70"; angle: "120"; value: "13.912"; }
+ ListElement{ radius: "70"; angle: "125"; value: "11.5683"; }
+ ListElement{ radius: "70"; angle: "130"; value: "8.91296"; }
+ ListElement{ radius: "70"; angle: "135"; value: "5.96608"; }
+ ListElement{ radius: "70"; angle: "140"; value: "2.75012"; }
+ ListElement{ radius: "70"; angle: "145"; value: "-0.710441"; }
+ ListElement{ radius: "70"; angle: "150"; value: "-4.38926"; }
+ ListElement{ radius: "70"; angle: "155"; value: "-8.25835"; }
+ ListElement{ radius: "70"; angle: "160"; value: "-12.2883"; }
+ ListElement{ radius: "70"; angle: "165"; value: "-16.4483"; }
+ ListElement{ radius: "70"; angle: "170"; value: "-20.7069"; }
+ ListElement{ radius: "70"; angle: "175"; value: "-25.0315"; }
+ ListElement{ radius: "70"; angle: "180"; value: "-29.3893"; }
+ ListElement{ radius: "70"; angle: "185"; value: "-33.747"; }
+ ListElement{ radius: "70"; angle: "190"; value: "-38.0717"; }
+ ListElement{ radius: "70"; angle: "195"; value: "-42.3302"; }
+ ListElement{ radius: "70"; angle: "200"; value: "-46.4903"; }
+ ListElement{ radius: "70"; angle: "205"; value: "-50.5202"; }
+ ListElement{ radius: "70"; angle: "210"; value: "-54.3893"; }
+ ListElement{ radius: "70"; angle: "215"; value: "-58.0681"; }
+ ListElement{ radius: "70"; angle: "220"; value: "-61.5286"; }
+ ListElement{ radius: "70"; angle: "225"; value: "-64.7446"; }
+ ListElement{ radius: "70"; angle: "230"; value: "-67.6915"; }
+ ListElement{ radius: "70"; angle: "235"; value: "-70.3469"; }
+ ListElement{ radius: "70"; angle: "240"; value: "-72.6905"; }
+ ListElement{ radius: "70"; angle: "245"; value: "-74.7047"; }
+ ListElement{ radius: "70"; angle: "250"; value: "-76.3739"; }
+ ListElement{ radius: "70"; angle: "255"; value: "-77.6856"; }
+ ListElement{ radius: "70"; angle: "260"; value: "-78.6297"; }
+ ListElement{ radius: "70"; angle: "265"; value: "-79.199"; }
+ ListElement{ radius: "70"; angle: "270"; value: "-79.3893"; }
+ ListElement{ radius: "70"; angle: "275"; value: "-79.199"; }
+ ListElement{ radius: "70"; angle: "280"; value: "-78.6297"; }
+ ListElement{ radius: "70"; angle: "285"; value: "-77.6856"; }
+ ListElement{ radius: "70"; angle: "290"; value: "-76.3739"; }
+ ListElement{ radius: "70"; angle: "295"; value: "-74.7047"; }
+ ListElement{ radius: "70"; angle: "300"; value: "-72.6905"; }
+ ListElement{ radius: "70"; angle: "305"; value: "-70.3469"; }
+ ListElement{ radius: "70"; angle: "310"; value: "-67.6915"; }
+ ListElement{ radius: "70"; angle: "315"; value: "-64.7446"; }
+ ListElement{ radius: "70"; angle: "320"; value: "-61.5286"; }
+ ListElement{ radius: "70"; angle: "325"; value: "-58.0681"; }
+ ListElement{ radius: "70"; angle: "330"; value: "-54.3893"; }
+ ListElement{ radius: "70"; angle: "335"; value: "-50.5202"; }
+ ListElement{ radius: "70"; angle: "340"; value: "-46.4903"; }
+ ListElement{ radius: "70"; angle: "345"; value: "-42.3302"; }
+ ListElement{ radius: "70"; angle: "350"; value: "-38.0717"; }
+ ListElement{ radius: "70"; angle: "355"; value: "-33.747"; }
+ ListElement{ radius: "70"; angle: "360"; value: "-29.3893"; }
+ ListElement{ radius: "75"; angle: "0"; value: "-35.3553"; }
+ ListElement{ radius: "75"; angle: "5"; value: "-30.9976"; }
+ ListElement{ radius: "75"; angle: "10"; value: "-26.6729"; }
+ ListElement{ radius: "75"; angle: "15"; value: "-22.4144"; }
+ ListElement{ radius: "75"; angle: "20"; value: "-18.2543"; }
+ ListElement{ radius: "75"; angle: "25"; value: "-14.2244"; }
+ ListElement{ radius: "75"; angle: "30"; value: "-10.3553"; }
+ ListElement{ radius: "75"; angle: "35"; value: "-6.67652"; }
+ ListElement{ radius: "75"; angle: "40"; value: "-3.21596"; }
+ ListElement{ radius: "75"; angle: "45"; value: "5.55112e-15"; }
+ ListElement{ radius: "75"; angle: "50"; value: "2.94688"; }
+ ListElement{ radius: "75"; angle: "55"; value: "5.60226"; }
+ ListElement{ radius: "75"; angle: "60"; value: "7.94593"; }
+ ListElement{ radius: "75"; angle: "65"; value: "9.96005"; }
+ ListElement{ radius: "75"; angle: "70"; value: "11.6293"; }
+ ListElement{ radius: "75"; angle: "75"; value: "12.941"; }
+ ListElement{ radius: "75"; angle: "80"; value: "13.885"; }
+ ListElement{ radius: "75"; angle: "85"; value: "14.4544"; }
+ ListElement{ radius: "75"; angle: "90"; value: "14.6447"; }
+ ListElement{ radius: "75"; angle: "95"; value: "14.4544"; }
+ ListElement{ radius: "75"; angle: "100"; value: "13.885"; }
+ ListElement{ radius: "75"; angle: "105"; value: "12.941"; }
+ ListElement{ radius: "75"; angle: "110"; value: "11.6293"; }
+ ListElement{ radius: "75"; angle: "115"; value: "9.96005"; }
+ ListElement{ radius: "75"; angle: "120"; value: "7.94593"; }
+ ListElement{ radius: "75"; angle: "125"; value: "5.60226"; }
+ ListElement{ radius: "75"; angle: "130"; value: "2.94688"; }
+ ListElement{ radius: "75"; angle: "135"; value: "5.55112e-15"; }
+ ListElement{ radius: "75"; angle: "140"; value: "-3.21596"; }
+ ListElement{ radius: "75"; angle: "145"; value: "-6.67652"; }
+ ListElement{ radius: "75"; angle: "150"; value: "-10.3553"; }
+ ListElement{ radius: "75"; angle: "155"; value: "-14.2244"; }
+ ListElement{ radius: "75"; angle: "160"; value: "-18.2543"; }
+ ListElement{ radius: "75"; angle: "165"; value: "-22.4144"; }
+ ListElement{ radius: "75"; angle: "170"; value: "-26.6729"; }
+ ListElement{ radius: "75"; angle: "175"; value: "-30.9976"; }
+ ListElement{ radius: "75"; angle: "180"; value: "-35.3553"; }
+ ListElement{ radius: "75"; angle: "185"; value: "-39.7131"; }
+ ListElement{ radius: "75"; angle: "190"; value: "-44.0377"; }
+ ListElement{ radius: "75"; angle: "195"; value: "-48.2963"; }
+ ListElement{ radius: "75"; angle: "200"; value: "-52.4563"; }
+ ListElement{ radius: "75"; angle: "205"; value: "-56.4863"; }
+ ListElement{ radius: "75"; angle: "210"; value: "-60.3553"; }
+ ListElement{ radius: "75"; angle: "215"; value: "-64.0342"; }
+ ListElement{ radius: "75"; angle: "220"; value: "-67.4947"; }
+ ListElement{ radius: "75"; angle: "225"; value: "-70.7107"; }
+ ListElement{ radius: "75"; angle: "230"; value: "-73.6576"; }
+ ListElement{ radius: "75"; angle: "235"; value: "-76.3129"; }
+ ListElement{ radius: "75"; angle: "240"; value: "-78.6566"; }
+ ListElement{ radius: "75"; angle: "245"; value: "-80.6707"; }
+ ListElement{ radius: "75"; angle: "250"; value: "-82.34"; }
+ ListElement{ radius: "75"; angle: "255"; value: "-83.6516"; }
+ ListElement{ radius: "75"; angle: "260"; value: "-84.5957"; }
+ ListElement{ radius: "75"; angle: "265"; value: "-85.1651"; }
+ ListElement{ radius: "75"; angle: "270"; value: "-85.3553"; }
+ ListElement{ radius: "75"; angle: "275"; value: "-85.1651"; }
+ ListElement{ radius: "75"; angle: "280"; value: "-84.5957"; }
+ ListElement{ radius: "75"; angle: "285"; value: "-83.6516"; }
+ ListElement{ radius: "75"; angle: "290"; value: "-82.34"; }
+ ListElement{ radius: "75"; angle: "295"; value: "-80.6707"; }
+ ListElement{ radius: "75"; angle: "300"; value: "-78.6566"; }
+ ListElement{ radius: "75"; angle: "305"; value: "-76.3129"; }
+ ListElement{ radius: "75"; angle: "310"; value: "-73.6576"; }
+ ListElement{ radius: "75"; angle: "315"; value: "-70.7107"; }
+ ListElement{ radius: "75"; angle: "320"; value: "-67.4947"; }
+ ListElement{ radius: "75"; angle: "325"; value: "-64.0342"; }
+ ListElement{ radius: "75"; angle: "330"; value: "-60.3553"; }
+ ListElement{ radius: "75"; angle: "335"; value: "-56.4863"; }
+ ListElement{ radius: "75"; angle: "340"; value: "-52.4563"; }
+ ListElement{ radius: "75"; angle: "345"; value: "-48.2963"; }
+ ListElement{ radius: "75"; angle: "350"; value: "-44.0377"; }
+ ListElement{ radius: "75"; angle: "355"; value: "-39.7131"; }
+ ListElement{ radius: "75"; angle: "360"; value: "-35.3553"; }
+ ListElement{ radius: "80"; angle: "0"; value: "-40.4508"; }
+ ListElement{ radius: "80"; angle: "5"; value: "-36.0931"; }
+ ListElement{ radius: "80"; angle: "10"; value: "-31.7684"; }
+ ListElement{ radius: "80"; angle: "15"; value: "-27.5099"; }
+ ListElement{ radius: "80"; angle: "20"; value: "-23.3498"; }
+ ListElement{ radius: "80"; angle: "25"; value: "-19.3199"; }
+ ListElement{ radius: "80"; angle: "30"; value: "-15.4508"; }
+ ListElement{ radius: "80"; angle: "35"; value: "-11.772"; }
+ ListElement{ radius: "80"; angle: "40"; value: "-8.31147"; }
+ ListElement{ radius: "80"; angle: "45"; value: "-5.09551"; }
+ ListElement{ radius: "80"; angle: "50"; value: "-2.14863"; }
+ ListElement{ radius: "80"; angle: "55"; value: "0.506752"; }
+ ListElement{ radius: "80"; angle: "60"; value: "2.85042"; }
+ ListElement{ radius: "80"; angle: "65"; value: "4.86454"; }
+ ListElement{ radius: "80"; angle: "70"; value: "6.53378"; }
+ ListElement{ radius: "80"; angle: "75"; value: "7.84544"; }
+ ListElement{ radius: "80"; angle: "80"; value: "8.78954"; }
+ ListElement{ radius: "80"; angle: "85"; value: "9.35889"; }
+ ListElement{ radius: "80"; angle: "90"; value: "9.54915"; }
+ ListElement{ radius: "80"; angle: "95"; value: "9.35889"; }
+ ListElement{ radius: "80"; angle: "100"; value: "8.78954"; }
+ ListElement{ radius: "80"; angle: "105"; value: "7.84544"; }
+ ListElement{ radius: "80"; angle: "110"; value: "6.53378"; }
+ ListElement{ radius: "80"; angle: "115"; value: "4.86454"; }
+ ListElement{ radius: "80"; angle: "120"; value: "2.85042"; }
+ ListElement{ radius: "80"; angle: "125"; value: "0.506752"; }
+ ListElement{ radius: "80"; angle: "130"; value: "-2.14863"; }
+ ListElement{ radius: "80"; angle: "135"; value: "-5.09551"; }
+ ListElement{ radius: "80"; angle: "140"; value: "-8.31147"; }
+ ListElement{ radius: "80"; angle: "145"; value: "-11.772"; }
+ ListElement{ radius: "80"; angle: "150"; value: "-15.4508"; }
+ ListElement{ radius: "80"; angle: "155"; value: "-19.3199"; }
+ ListElement{ radius: "80"; angle: "160"; value: "-23.3498"; }
+ ListElement{ radius: "80"; angle: "165"; value: "-27.5099"; }
+ ListElement{ radius: "80"; angle: "170"; value: "-31.7684"; }
+ ListElement{ radius: "80"; angle: "175"; value: "-36.0931"; }
+ ListElement{ radius: "80"; angle: "180"; value: "-40.4508"; }
+ ListElement{ radius: "80"; angle: "185"; value: "-44.8086"; }
+ ListElement{ radius: "80"; angle: "190"; value: "-49.1333"; }
+ ListElement{ radius: "80"; angle: "195"; value: "-53.3918"; }
+ ListElement{ radius: "80"; angle: "200"; value: "-57.5519"; }
+ ListElement{ radius: "80"; angle: "205"; value: "-61.5818"; }
+ ListElement{ radius: "80"; angle: "210"; value: "-65.4508"; }
+ ListElement{ radius: "80"; angle: "215"; value: "-69.1297"; }
+ ListElement{ radius: "80"; angle: "220"; value: "-72.5902"; }
+ ListElement{ radius: "80"; angle: "225"; value: "-75.8062"; }
+ ListElement{ radius: "80"; angle: "230"; value: "-78.7531"; }
+ ListElement{ radius: "80"; angle: "235"; value: "-81.4085"; }
+ ListElement{ radius: "80"; angle: "240"; value: "-83.7521"; }
+ ListElement{ radius: "80"; angle: "245"; value: "-85.7662"; }
+ ListElement{ radius: "80"; angle: "250"; value: "-87.4355"; }
+ ListElement{ radius: "80"; angle: "255"; value: "-88.7471"; }
+ ListElement{ radius: "80"; angle: "260"; value: "-89.6912"; }
+ ListElement{ radius: "80"; angle: "265"; value: "-90.2606"; }
+ ListElement{ radius: "80"; angle: "270"; value: "-90.4508"; }
+ ListElement{ radius: "80"; angle: "275"; value: "-90.2606"; }
+ ListElement{ radius: "80"; angle: "280"; value: "-89.6912"; }
+ ListElement{ radius: "80"; angle: "285"; value: "-88.7471"; }
+ ListElement{ radius: "80"; angle: "290"; value: "-87.4355"; }
+ ListElement{ radius: "80"; angle: "295"; value: "-85.7662"; }
+ ListElement{ radius: "80"; angle: "300"; value: "-83.7521"; }
+ ListElement{ radius: "80"; angle: "305"; value: "-81.4085"; }
+ ListElement{ radius: "80"; angle: "310"; value: "-78.7531"; }
+ ListElement{ radius: "80"; angle: "315"; value: "-75.8062"; }
+ ListElement{ radius: "80"; angle: "320"; value: "-72.5902"; }
+ ListElement{ radius: "80"; angle: "325"; value: "-69.1297"; }
+ ListElement{ radius: "80"; angle: "330"; value: "-65.4508"; }
+ ListElement{ radius: "80"; angle: "335"; value: "-61.5818"; }
+ ListElement{ radius: "80"; angle: "340"; value: "-57.5519"; }
+ ListElement{ radius: "80"; angle: "345"; value: "-53.3918"; }
+ ListElement{ radius: "80"; angle: "350"; value: "-49.1333"; }
+ ListElement{ radius: "80"; angle: "355"; value: "-44.8086"; }
+ ListElement{ radius: "80"; angle: "360"; value: "-40.4508"; }
+ ListElement{ radius: "85"; angle: "0"; value: "-44.5503"; }
+ ListElement{ radius: "85"; angle: "5"; value: "-40.1925"; }
+ ListElement{ radius: "85"; angle: "10"; value: "-35.8679"; }
+ ListElement{ radius: "85"; angle: "15"; value: "-31.6094"; }
+ ListElement{ radius: "85"; angle: "20"; value: "-27.4493"; }
+ ListElement{ radius: "85"; angle: "25"; value: "-23.4194"; }
+ ListElement{ radius: "85"; angle: "30"; value: "-19.5503"; }
+ ListElement{ radius: "85"; angle: "35"; value: "-15.8715"; }
+ ListElement{ radius: "85"; angle: "40"; value: "-12.4109"; }
+ ListElement{ radius: "85"; angle: "45"; value: "-9.19499"; }
+ ListElement{ radius: "85"; angle: "50"; value: "-6.2481"; }
+ ListElement{ radius: "85"; angle: "55"; value: "-3.59272"; }
+ ListElement{ radius: "85"; angle: "60"; value: "-1.24906"; }
+ ListElement{ radius: "85"; angle: "65"; value: "0.765063"; }
+ ListElement{ radius: "85"; angle: "70"; value: "2.4343"; }
+ ListElement{ radius: "85"; angle: "75"; value: "3.74597"; }
+ ListElement{ radius: "85"; angle: "80"; value: "4.69006"; }
+ ListElement{ radius: "85"; angle: "85"; value: "5.25941"; }
+ ListElement{ radius: "85"; angle: "90"; value: "5.44967"; }
+ ListElement{ radius: "85"; angle: "95"; value: "5.25941"; }
+ ListElement{ radius: "85"; angle: "100"; value: "4.69006"; }
+ ListElement{ radius: "85"; angle: "105"; value: "3.74597"; }
+ ListElement{ radius: "85"; angle: "110"; value: "2.4343"; }
+ ListElement{ radius: "85"; angle: "115"; value: "0.765063"; }
+ ListElement{ radius: "85"; angle: "120"; value: "-1.24906"; }
+ ListElement{ radius: "85"; angle: "125"; value: "-3.59272"; }
+ ListElement{ radius: "85"; angle: "130"; value: "-6.2481"; }
+ ListElement{ radius: "85"; angle: "135"; value: "-9.19499"; }
+ ListElement{ radius: "85"; angle: "140"; value: "-12.4109"; }
+ ListElement{ radius: "85"; angle: "145"; value: "-15.8715"; }
+ ListElement{ radius: "85"; angle: "150"; value: "-19.5503"; }
+ ListElement{ radius: "85"; angle: "155"; value: "-23.4194"; }
+ ListElement{ radius: "85"; angle: "160"; value: "-27.4493"; }
+ ListElement{ radius: "85"; angle: "165"; value: "-31.6094"; }
+ ListElement{ radius: "85"; angle: "170"; value: "-35.8679"; }
+ ListElement{ radius: "85"; angle: "175"; value: "-40.1925"; }
+ ListElement{ radius: "85"; angle: "180"; value: "-44.5503"; }
+ ListElement{ radius: "85"; angle: "185"; value: "-48.9081"; }
+ ListElement{ radius: "85"; angle: "190"; value: "-53.2327"; }
+ ListElement{ radius: "85"; angle: "195"; value: "-57.4913"; }
+ ListElement{ radius: "85"; angle: "200"; value: "-61.6513"; }
+ ListElement{ radius: "85"; angle: "205"; value: "-65.6812"; }
+ ListElement{ radius: "85"; angle: "210"; value: "-69.5503"; }
+ ListElement{ radius: "85"; angle: "215"; value: "-73.2291"; }
+ ListElement{ radius: "85"; angle: "220"; value: "-76.6897"; }
+ ListElement{ radius: "85"; angle: "225"; value: "-79.9057"; }
+ ListElement{ radius: "85"; angle: "230"; value: "-82.8525"; }
+ ListElement{ radius: "85"; angle: "235"; value: "-85.5079"; }
+ ListElement{ radius: "85"; angle: "240"; value: "-87.8516"; }
+ ListElement{ radius: "85"; angle: "245"; value: "-89.8657"; }
+ ListElement{ radius: "85"; angle: "250"; value: "-91.535"; }
+ ListElement{ radius: "85"; angle: "255"; value: "-92.8466"; }
+ ListElement{ radius: "85"; angle: "260"; value: "-93.7907"; }
+ ListElement{ radius: "85"; angle: "265"; value: "-94.3601"; }
+ ListElement{ radius: "85"; angle: "270"; value: "-94.5503"; }
+ ListElement{ radius: "85"; angle: "275"; value: "-94.3601"; }
+ ListElement{ radius: "85"; angle: "280"; value: "-93.7907"; }
+ ListElement{ radius: "85"; angle: "285"; value: "-92.8466"; }
+ ListElement{ radius: "85"; angle: "290"; value: "-91.535"; }
+ ListElement{ radius: "85"; angle: "295"; value: "-89.8657"; }
+ ListElement{ radius: "85"; angle: "300"; value: "-87.8516"; }
+ ListElement{ radius: "85"; angle: "305"; value: "-85.5079"; }
+ ListElement{ radius: "85"; angle: "310"; value: "-82.8525"; }
+ ListElement{ radius: "85"; angle: "315"; value: "-79.9057"; }
+ ListElement{ radius: "85"; angle: "320"; value: "-76.6897"; }
+ ListElement{ radius: "85"; angle: "325"; value: "-73.2291"; }
+ ListElement{ radius: "85"; angle: "330"; value: "-69.5503"; }
+ ListElement{ radius: "85"; angle: "335"; value: "-65.6812"; }
+ ListElement{ radius: "85"; angle: "340"; value: "-61.6513"; }
+ ListElement{ radius: "85"; angle: "345"; value: "-57.4913"; }
+ ListElement{ radius: "85"; angle: "350"; value: "-53.2327"; }
+ ListElement{ radius: "85"; angle: "355"; value: "-48.9081"; }
+ ListElement{ radius: "85"; angle: "360"; value: "-44.5503"; }
+ ListElement{ radius: "90"; angle: "0"; value: "-47.5528"; }
+ ListElement{ radius: "90"; angle: "5"; value: "-43.195"; }
+ ListElement{ radius: "90"; angle: "10"; value: "-38.8704"; }
+ ListElement{ radius: "90"; angle: "15"; value: "-34.6119"; }
+ ListElement{ radius: "90"; angle: "20"; value: "-30.4518"; }
+ ListElement{ radius: "90"; angle: "25"; value: "-26.4219"; }
+ ListElement{ radius: "90"; angle: "30"; value: "-22.5528"; }
+ ListElement{ radius: "90"; angle: "35"; value: "-18.874"; }
+ ListElement{ radius: "90"; angle: "40"; value: "-15.4134"; }
+ ListElement{ radius: "90"; angle: "45"; value: "-12.1975"; }
+ ListElement{ radius: "90"; angle: "50"; value: "-9.2506"; }
+ ListElement{ radius: "90"; angle: "55"; value: "-6.59522"; }
+ ListElement{ radius: "90"; angle: "60"; value: "-4.25156"; }
+ ListElement{ radius: "90"; angle: "65"; value: "-2.23744"; }
+ ListElement{ radius: "90"; angle: "70"; value: "-0.568195"; }
+ ListElement{ radius: "90"; angle: "75"; value: "0.743465"; }
+ ListElement{ radius: "90"; angle: "80"; value: "1.68756"; }
+ ListElement{ radius: "90"; angle: "85"; value: "2.25691"; }
+ ListElement{ radius: "90"; angle: "90"; value: "2.44717"; }
+ ListElement{ radius: "90"; angle: "95"; value: "2.25691"; }
+ ListElement{ radius: "90"; angle: "100"; value: "1.68756"; }
+ ListElement{ radius: "90"; angle: "105"; value: "0.743465"; }
+ ListElement{ radius: "90"; angle: "110"; value: "-0.568195"; }
+ ListElement{ radius: "90"; angle: "115"; value: "-2.23744"; }
+ ListElement{ radius: "90"; angle: "120"; value: "-4.25156"; }
+ ListElement{ radius: "90"; angle: "125"; value: "-6.59522"; }
+ ListElement{ radius: "90"; angle: "130"; value: "-9.2506"; }
+ ListElement{ radius: "90"; angle: "135"; value: "-12.1975"; }
+ ListElement{ radius: "90"; angle: "140"; value: "-15.4134"; }
+ ListElement{ radius: "90"; angle: "145"; value: "-18.874"; }
+ ListElement{ radius: "90"; angle: "150"; value: "-22.5528"; }
+ ListElement{ radius: "90"; angle: "155"; value: "-26.4219"; }
+ ListElement{ radius: "90"; angle: "160"; value: "-30.4518"; }
+ ListElement{ radius: "90"; angle: "165"; value: "-34.6119"; }
+ ListElement{ radius: "90"; angle: "170"; value: "-38.8704"; }
+ ListElement{ radius: "90"; angle: "175"; value: "-43.195"; }
+ ListElement{ radius: "90"; angle: "180"; value: "-47.5528"; }
+ ListElement{ radius: "90"; angle: "185"; value: "-51.9106"; }
+ ListElement{ radius: "90"; angle: "190"; value: "-56.2352"; }
+ ListElement{ radius: "90"; angle: "195"; value: "-60.4938"; }
+ ListElement{ radius: "90"; angle: "200"; value: "-64.6538"; }
+ ListElement{ radius: "90"; angle: "205"; value: "-68.6837"; }
+ ListElement{ radius: "90"; angle: "210"; value: "-72.5528"; }
+ ListElement{ radius: "90"; angle: "215"; value: "-76.2316"; }
+ ListElement{ radius: "90"; angle: "220"; value: "-79.6922"; }
+ ListElement{ radius: "90"; angle: "225"; value: "-82.9082"; }
+ ListElement{ radius: "90"; angle: "230"; value: "-85.855"; }
+ ListElement{ radius: "90"; angle: "235"; value: "-88.5104"; }
+ ListElement{ radius: "90"; angle: "240"; value: "-90.8541"; }
+ ListElement{ radius: "90"; angle: "245"; value: "-92.8682"; }
+ ListElement{ radius: "90"; angle: "250"; value: "-94.5375"; }
+ ListElement{ radius: "90"; angle: "255"; value: "-95.8491"; }
+ ListElement{ radius: "90"; angle: "260"; value: "-96.7932"; }
+ ListElement{ radius: "90"; angle: "265"; value: "-97.3626"; }
+ ListElement{ radius: "90"; angle: "270"; value: "-97.5528"; }
+ ListElement{ radius: "90"; angle: "275"; value: "-97.3626"; }
+ ListElement{ radius: "90"; angle: "280"; value: "-96.7932"; }
+ ListElement{ radius: "90"; angle: "285"; value: "-95.8491"; }
+ ListElement{ radius: "90"; angle: "290"; value: "-94.5375"; }
+ ListElement{ radius: "90"; angle: "295"; value: "-92.8682"; }
+ ListElement{ radius: "90"; angle: "300"; value: "-90.8541"; }
+ ListElement{ radius: "90"; angle: "305"; value: "-88.5104"; }
+ ListElement{ radius: "90"; angle: "310"; value: "-85.855"; }
+ ListElement{ radius: "90"; angle: "315"; value: "-82.9082"; }
+ ListElement{ radius: "90"; angle: "320"; value: "-79.6922"; }
+ ListElement{ radius: "90"; angle: "325"; value: "-76.2316"; }
+ ListElement{ radius: "90"; angle: "330"; value: "-72.5528"; }
+ ListElement{ radius: "90"; angle: "335"; value: "-68.6837"; }
+ ListElement{ radius: "90"; angle: "340"; value: "-64.6538"; }
+ ListElement{ radius: "90"; angle: "345"; value: "-60.4938"; }
+ ListElement{ radius: "90"; angle: "350"; value: "-56.2352"; }
+ ListElement{ radius: "90"; angle: "355"; value: "-51.9106"; }
+ ListElement{ radius: "90"; angle: "360"; value: "-47.5528"; }
+ ListElement{ radius: "95"; angle: "0"; value: "-49.3844"; }
+ ListElement{ radius: "95"; angle: "5"; value: "-45.0266"; }
+ ListElement{ radius: "95"; angle: "10"; value: "-40.702"; }
+ ListElement{ radius: "95"; angle: "15"; value: "-36.4435"; }
+ ListElement{ radius: "95"; angle: "20"; value: "-32.2834"; }
+ ListElement{ radius: "95"; angle: "25"; value: "-28.2535"; }
+ ListElement{ radius: "95"; angle: "30"; value: "-24.3844"; }
+ ListElement{ radius: "95"; angle: "35"; value: "-20.7056"; }
+ ListElement{ radius: "95"; angle: "40"; value: "-17.245"; }
+ ListElement{ radius: "95"; angle: "45"; value: "-14.0291"; }
+ ListElement{ radius: "95"; angle: "50"; value: "-11.0822"; }
+ ListElement{ radius: "95"; angle: "55"; value: "-8.42681"; }
+ ListElement{ radius: "95"; angle: "60"; value: "-6.08315"; }
+ ListElement{ radius: "95"; angle: "65"; value: "-4.06903"; }
+ ListElement{ radius: "95"; angle: "70"; value: "-2.39979"; }
+ ListElement{ radius: "95"; angle: "75"; value: "-1.08813"; }
+ ListElement{ radius: "95"; angle: "80"; value: "-0.144029"; }
+ ListElement{ radius: "95"; angle: "85"; value: "0.425318"; }
+ ListElement{ radius: "95"; angle: "90"; value: "0.615583"; }
+ ListElement{ radius: "95"; angle: "95"; value: "0.425318"; }
+ ListElement{ radius: "95"; angle: "100"; value: "-0.144029"; }
+ ListElement{ radius: "95"; angle: "105"; value: "-1.08813"; }
+ ListElement{ radius: "95"; angle: "110"; value: "-2.39979"; }
+ ListElement{ radius: "95"; angle: "115"; value: "-4.06903"; }
+ ListElement{ radius: "95"; angle: "120"; value: "-6.08315"; }
+ ListElement{ radius: "95"; angle: "125"; value: "-8.42681"; }
+ ListElement{ radius: "95"; angle: "130"; value: "-11.0822"; }
+ ListElement{ radius: "95"; angle: "135"; value: "-14.0291"; }
+ ListElement{ radius: "95"; angle: "140"; value: "-17.245"; }
+ ListElement{ radius: "95"; angle: "145"; value: "-20.7056"; }
+ ListElement{ radius: "95"; angle: "150"; value: "-24.3844"; }
+ ListElement{ radius: "95"; angle: "155"; value: "-28.2535"; }
+ ListElement{ radius: "95"; angle: "160"; value: "-32.2834"; }
+ ListElement{ radius: "95"; angle: "165"; value: "-36.4435"; }
+ ListElement{ radius: "95"; angle: "170"; value: "-40.702"; }
+ ListElement{ radius: "95"; angle: "175"; value: "-45.0266"; }
+ ListElement{ radius: "95"; angle: "180"; value: "-49.3844"; }
+ ListElement{ radius: "95"; angle: "185"; value: "-53.7422"; }
+ ListElement{ radius: "95"; angle: "190"; value: "-58.0668"; }
+ ListElement{ radius: "95"; angle: "195"; value: "-62.3254"; }
+ ListElement{ radius: "95"; angle: "200"; value: "-66.4854"; }
+ ListElement{ radius: "95"; angle: "205"; value: "-70.5153"; }
+ ListElement{ radius: "95"; angle: "210"; value: "-74.3844"; }
+ ListElement{ radius: "95"; angle: "215"; value: "-78.0632"; }
+ ListElement{ radius: "95"; angle: "220"; value: "-81.5238"; }
+ ListElement{ radius: "95"; angle: "225"; value: "-84.7398"; }
+ ListElement{ radius: "95"; angle: "230"; value: "-87.6866"; }
+ ListElement{ radius: "95"; angle: "235"; value: "-90.342"; }
+ ListElement{ radius: "95"; angle: "240"; value: "-92.6857"; }
+ ListElement{ radius: "95"; angle: "245"; value: "-94.6998"; }
+ ListElement{ radius: "95"; angle: "250"; value: "-96.369"; }
+ ListElement{ radius: "95"; angle: "255"; value: "-97.6807"; }
+ ListElement{ radius: "95"; angle: "260"; value: "-98.6248"; }
+ ListElement{ radius: "95"; angle: "265"; value: "-99.1942"; }
+ ListElement{ radius: "95"; angle: "270"; value: "-99.3844"; }
+ ListElement{ radius: "95"; angle: "275"; value: "-99.1942"; }
+ ListElement{ radius: "95"; angle: "280"; value: "-98.6248"; }
+ ListElement{ radius: "95"; angle: "285"; value: "-97.6807"; }
+ ListElement{ radius: "95"; angle: "290"; value: "-96.369"; }
+ ListElement{ radius: "95"; angle: "295"; value: "-94.6998"; }
+ ListElement{ radius: "95"; angle: "300"; value: "-92.6857"; }
+ ListElement{ radius: "95"; angle: "305"; value: "-90.342"; }
+ ListElement{ radius: "95"; angle: "310"; value: "-87.6866"; }
+ ListElement{ radius: "95"; angle: "315"; value: "-84.7398"; }
+ ListElement{ radius: "95"; angle: "320"; value: "-81.5238"; }
+ ListElement{ radius: "95"; angle: "325"; value: "-78.0632"; }
+ ListElement{ radius: "95"; angle: "330"; value: "-74.3844"; }
+ ListElement{ radius: "95"; angle: "335"; value: "-70.5153"; }
+ ListElement{ radius: "95"; angle: "340"; value: "-66.4854"; }
+ ListElement{ radius: "95"; angle: "345"; value: "-62.3254"; }
+ ListElement{ radius: "95"; angle: "350"; value: "-58.0668"; }
+ ListElement{ radius: "95"; angle: "355"; value: "-53.7422"; }
+ ListElement{ radius: "95"; angle: "360"; value: "-49.3844"; }
+ ListElement{ radius: "100"; angle: "0"; value: "-50"; }
+ ListElement{ radius: "100"; angle: "5"; value: "-45.6422"; }
+ ListElement{ radius: "100"; angle: "10"; value: "-41.3176"; }
+ ListElement{ radius: "100"; angle: "15"; value: "-37.059"; }
+ ListElement{ radius: "100"; angle: "20"; value: "-32.899"; }
+ ListElement{ radius: "100"; angle: "25"; value: "-28.8691"; }
+ ListElement{ radius: "100"; angle: "30"; value: "-25"; }
+ ListElement{ radius: "100"; angle: "35"; value: "-21.3212"; }
+ ListElement{ radius: "100"; angle: "40"; value: "-17.8606"; }
+ ListElement{ radius: "100"; angle: "45"; value: "-14.6447"; }
+ ListElement{ radius: "100"; angle: "50"; value: "-11.6978"; }
+ ListElement{ radius: "100"; angle: "55"; value: "-9.0424"; }
+ ListElement{ radius: "100"; angle: "60"; value: "-6.69873"; }
+ ListElement{ radius: "100"; angle: "65"; value: "-4.68461"; }
+ ListElement{ radius: "100"; angle: "70"; value: "-3.01537"; }
+ ListElement{ radius: "100"; angle: "75"; value: "-1.70371"; }
+ ListElement{ radius: "100"; angle: "80"; value: "-0.759612"; }
+ ListElement{ radius: "100"; angle: "85"; value: "-0.190265"; }
+ ListElement{ radius: "100"; angle: "90"; value: "0"; }
+ ListElement{ radius: "100"; angle: "95"; value: "-0.190265"; }
+ ListElement{ radius: "100"; angle: "100"; value: "-0.759612"; }
+ ListElement{ radius: "100"; angle: "105"; value: "-1.70371"; }
+ ListElement{ radius: "100"; angle: "110"; value: "-3.01537"; }
+ ListElement{ radius: "100"; angle: "115"; value: "-4.68461"; }
+ ListElement{ radius: "100"; angle: "120"; value: "-6.69873"; }
+ ListElement{ radius: "100"; angle: "125"; value: "-9.0424"; }
+ ListElement{ radius: "100"; angle: "130"; value: "-11.6978"; }
+ ListElement{ radius: "100"; angle: "135"; value: "-14.6447"; }
+ ListElement{ radius: "100"; angle: "140"; value: "-17.8606"; }
+ ListElement{ radius: "100"; angle: "145"; value: "-21.3212"; }
+ ListElement{ radius: "100"; angle: "150"; value: "-25"; }
+ ListElement{ radius: "100"; angle: "155"; value: "-28.8691"; }
+ ListElement{ radius: "100"; angle: "160"; value: "-32.899"; }
+ ListElement{ radius: "100"; angle: "165"; value: "-37.059"; }
+ ListElement{ radius: "100"; angle: "170"; value: "-41.3176"; }
+ ListElement{ radius: "100"; angle: "175"; value: "-45.6422"; }
+ ListElement{ radius: "100"; angle: "180"; value: "-50"; }
+ ListElement{ radius: "100"; angle: "185"; value: "-54.3578"; }
+ ListElement{ radius: "100"; angle: "190"; value: "-58.6824"; }
+ ListElement{ radius: "100"; angle: "195"; value: "-62.941"; }
+ ListElement{ radius: "100"; angle: "200"; value: "-67.101"; }
+ ListElement{ radius: "100"; angle: "205"; value: "-71.1309"; }
+ ListElement{ radius: "100"; angle: "210"; value: "-75"; }
+ ListElement{ radius: "100"; angle: "215"; value: "-78.6788"; }
+ ListElement{ radius: "100"; angle: "220"; value: "-82.1394"; }
+ ListElement{ radius: "100"; angle: "225"; value: "-85.3553"; }
+ ListElement{ radius: "100"; angle: "230"; value: "-88.3022"; }
+ ListElement{ radius: "100"; angle: "235"; value: "-90.9576"; }
+ ListElement{ radius: "100"; angle: "240"; value: "-93.3013"; }
+ ListElement{ radius: "100"; angle: "245"; value: "-95.3154"; }
+ ListElement{ radius: "100"; angle: "250"; value: "-96.9846"; }
+ ListElement{ radius: "100"; angle: "255"; value: "-98.2963"; }
+ ListElement{ radius: "100"; angle: "260"; value: "-99.2404"; }
+ ListElement{ radius: "100"; angle: "265"; value: "-99.8097"; }
+ ListElement{ radius: "100"; angle: "270"; value: "-100"; }
+ ListElement{ radius: "100"; angle: "275"; value: "-99.8097"; }
+ ListElement{ radius: "100"; angle: "280"; value: "-99.2404"; }
+ ListElement{ radius: "100"; angle: "285"; value: "-98.2963"; }
+ ListElement{ radius: "100"; angle: "290"; value: "-96.9846"; }
+ ListElement{ radius: "100"; angle: "295"; value: "-95.3154"; }
+ ListElement{ radius: "100"; angle: "300"; value: "-93.3013"; }
+ ListElement{ radius: "100"; angle: "305"; value: "-90.9576"; }
+ ListElement{ radius: "100"; angle: "310"; value: "-88.3022"; }
+ ListElement{ radius: "100"; angle: "315"; value: "-85.3553"; }
+ ListElement{ radius: "100"; angle: "320"; value: "-82.1394"; }
+ ListElement{ radius: "100"; angle: "325"; value: "-78.6788"; }
+ ListElement{ radius: "100"; angle: "330"; value: "-75"; }
+ ListElement{ radius: "100"; angle: "335"; value: "-71.1309"; }
+ ListElement{ radius: "100"; angle: "340"; value: "-67.101"; }
+ ListElement{ radius: "100"; angle: "345"; value: "-62.941"; }
+ ListElement{ radius: "100"; angle: "350"; value: "-58.6824"; }
+ ListElement{ radius: "100"; angle: "355"; value: "-54.3578"; }
+ ListElement{ radius: "100"; angle: "360"; value: "-50"; }
+ }
+}
diff --git a/examples/graphs/qmlsurfacegallery/qml/qmlsurfacegallery/SurfaceHeightMap.qml b/examples/graphs/qmlsurfacegallery/qml/qmlsurfacegallery/SurfaceHeightMap.qml
new file mode 100644
index 0000000..c56b1a0
--- /dev/null
+++ b/examples/graphs/qmlsurfacegallery/qml/qmlsurfacegallery/SurfaceHeightMap.qml
@@ -0,0 +1,227 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+import QtQuick.Controls
+import QtGraphs
+
+Rectangle {
+ id: heightMapView
+ color: surfacePlot.theme.windowColor
+
+ required property bool portraitMode
+
+ property real buttonWidth: heightMapView.portraitMode ? (heightMapView.width - 35) / 2
+ : (heightMapView.width - 40) / 3
+
+ Item {
+ id: surfaceView
+ anchors.top: buttons.bottom
+ anchors.bottom: heightMapView.bottom
+ anchors.left: heightMapView.left
+ anchors.right: heightMapView.right
+
+ //! [1]
+ ColorGradient {
+ id: surfaceGradient
+ ColorGradientStop { position: 0.0; color: "darkgreen"}
+ ColorGradientStop { position: 0.15; color: "darkslategray" }
+ ColorGradientStop { position: 0.7; color: "peru" }
+ ColorGradientStop { position: 1.0; color: "white" }
+ }
+ //! [1]
+
+ Surface3D {
+ id: surfacePlot
+ width: surfaceView.width
+ height: surfaceView.height
+ aspectRatio: 3.0
+ //! [2]
+ theme: Theme3D {
+ type: Theme3D.ThemeStoneMoss
+ font.family: "STCaiyun"
+ font.pointSize: 35
+ colorStyle: Theme3D.ColorStyleRangeGradient
+ baseGradients: [surfaceGradient] // Use the custom gradient
+ }
+ //! [2]
+ shadowQuality: AbstractGraph3D.ShadowQualityMedium
+ selectionMode: AbstractGraph3D.SelectionSlice | AbstractGraph3D.SelectionItemAndRow
+ scene.activeCamera.cameraPreset: Camera3D.CameraPresetIsometricLeft
+ axisX.segmentCount: 3
+ axisX.subSegmentCount: 3
+ axisX.labelFormat: "%i"
+ axisZ.segmentCount: 3
+ axisZ.subSegmentCount: 3
+ axisZ.labelFormat: "%i"
+ axisY.segmentCount: 2
+ axisY.subSegmentCount: 2
+ axisY.labelFormat: "%i"
+ axisY.title: "Height (m)"
+ axisX.title: "Longitude 175.x\"E"
+ axisZ.title: "Latitude -39.x\"N"
+ axisY.titleVisible: true
+ axisX.titleVisible: true
+ axisZ.titleVisible: true
+
+ //! [0]
+ Surface3DSeries {
+ id: heightSeries
+ flatShadingEnabled: false
+ drawMode: Surface3DSeries.DrawSurface
+
+ HeightMapSurfaceDataProxy {
+ heightMapFile: "://qml/qmlsurfacegallery/heightmap.png"
+ // We don't want the default data values set by heightmap proxy, but use
+ // actual coordinate and height values instead
+ autoScaleY: true
+ minYValue: 740
+ maxYValue: 2787
+ minZValue: -374 // ~ -39.374411"N
+ maxZValue: -116 // ~ -39.115971"N
+ minXValue: 472 // ~ 175.471767"E
+ maxXValue: 781 // ~ 175.780758"E
+ }
+
+ onDrawModeChanged: heightMapView.checkState()
+ }
+ //! [0]
+ }
+ }
+
+ Item {
+ id: buttons
+ anchors.top: parent.top
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.margins: 10
+ height: heightMapView.portraitMode ? surfaceGridToggle.implicitHeight * 3 + 20
+ : surfaceGridToggle.implicitHeight * 2 + 15
+ opacity: 0.5
+
+ Button {
+ id: surfaceGridToggle
+ anchors.margins: 5
+ anchors.left: parent.left
+ anchors.top: parent.top
+ width: heightMapView.buttonWidth // Calculated elsewhere based on screen orientation
+ text: "Show Surface\nGrid"
+ //! [3]
+ onClicked: {
+ if (heightSeries.drawMode & Surface3DSeries.DrawWireframe)
+ heightSeries.drawMode &= ~Surface3DSeries.DrawWireframe;
+ else
+ heightSeries.drawMode |= Surface3DSeries.DrawWireframe;
+ }
+ //! [3]
+ }
+
+ Button {
+ id: surfaceGridColor
+ anchors.margins: 5
+ anchors.left: surfaceGridToggle.right
+ anchors.top: parent.top
+ width: heightMapView.buttonWidth
+ text: "Red surface\ngrid color"
+ //! [4]
+ onClicked: {
+ if (Qt.colorEqual(heightSeries.wireframeColor, "#000000")) {
+ heightSeries.wireframeColor = "red";
+ text = "Black surface\ngrid color";
+ } else {
+ heightSeries.wireframeColor = "black";
+ text = "Red surface\ngrid color";
+ }
+ }
+ //! [4]
+ }
+
+ Button {
+ id: surfaceToggle
+ anchors.margins: 5
+ anchors.left: heightMapView.portraitMode ? parent.left : surfaceGridColor.right
+ anchors.top: heightMapView.portraitMode ? surfaceGridColor.bottom : parent.top
+ width: heightMapView.buttonWidth
+ text: "Hide\nSurface"
+ //! [5]
+ onClicked: {
+ if (heightSeries.drawMode & Surface3DSeries.DrawSurface)
+ heightSeries.drawMode &= ~Surface3DSeries.DrawSurface;
+ else
+ heightSeries.drawMode |= Surface3DSeries.DrawSurface;
+ }
+ //! [5]
+ }
+
+ Button {
+ id: flatShadingToggle
+ anchors.margins: 5
+ anchors.left: heightMapView.portraitMode ? surfaceToggle.right : parent.left
+ anchors.top: heightMapView.portraitMode ? surfaceGridColor.bottom : surfaceToggle.bottom
+ width: heightMapView.buttonWidth
+ text: heightSeries.flatShadingSupported ? "Show\nFlat" : "Flat not\nsupported"
+ enabled: heightSeries.flatShadingSupported
+ //! [6]
+ onClicked: {
+ if (heightSeries.flatShadingEnabled) {
+ heightSeries.flatShadingEnabled = false;
+ text = "Show\nFlat"
+ } else {
+ heightSeries.flatShadingEnabled = true;
+ text = "Show\nSmooth"
+ }
+ }
+ //! [6]
+ }
+
+ Button {
+ id: backgroundToggle
+ anchors.margins: 5
+ anchors.left: heightMapView.portraitMode ? parent.left : flatShadingToggle.right
+ anchors.top: heightMapView.portraitMode ? flatShadingToggle.bottom
+ : surfaceToggle.bottom
+ width: heightMapView.buttonWidth
+ text: "Hide\nBackground"
+ onClicked: {
+ if (surfacePlot.theme.backgroundEnabled) {
+ surfacePlot.theme.backgroundEnabled = false;
+ text = "Show\nBackground";
+ } else {
+ surfacePlot.theme.backgroundEnabled = true;
+ text = "Hide\nBackground";
+ }
+ }
+ }
+
+ Button {
+ id: gridToggle
+ anchors.margins: 5
+ anchors.left: backgroundToggle.right
+ anchors.top: heightMapView.portraitMode ? flatShadingToggle.bottom
+ : surfaceToggle.bottom
+ width: heightMapView.buttonWidth
+ text: "Hide\nGrid"
+ onClicked: {
+ if (surfacePlot.theme.gridEnabled) {
+ surfacePlot.theme.gridEnabled = false;
+ text = "Show\nGrid";
+ } else {
+ surfacePlot.theme.gridEnabled = true;
+ text = "Hide\nGrid";
+ }
+ }
+ }
+ }
+
+ function checkState() {
+ if (heightSeries.drawMode & Surface3DSeries.DrawSurface)
+ surfaceToggle.text = "Hide\nSurface";
+ else
+ surfaceToggle.text = "Show\nSurface";
+
+ if (heightSeries.drawMode & Surface3DSeries.DrawWireframe)
+ surfaceGridToggle.text = "Hide Surface\nGrid";
+ else
+ surfaceGridToggle.text = "Show Surface\nGrid";
+ }
+}
diff --git a/examples/graphs/qmlsurfacegallery/qml/qmlsurfacegallery/SurfaceOscilloscope.qml b/examples/graphs/qmlsurfacegallery/qml/qmlsurfacegallery/SurfaceOscilloscope.qml
new file mode 100644
index 0000000..013325b
--- /dev/null
+++ b/examples/graphs/qmlsurfacegallery/qml/qmlsurfacegallery/SurfaceOscilloscope.qml
@@ -0,0 +1,430 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+import QtQuick.Controls
+import QtGraphs
+//! [0]
+import SurfaceGallery
+//! [0]
+
+Item {
+ id: oscilloscopeView
+
+ property int sampleColumns: sampleSlider.value
+ property int sampleRows: sampleColumns / 2
+ property int sampleCache: 24
+
+ required property bool portraitMode
+
+ property real controlWidth: oscilloscopeView.portraitMode ? oscilloscopeView.width - 10
+ : oscilloscopeView.width / 4 - 6.66
+
+ property real buttonWidth: oscilloscopeView.portraitMode ? oscilloscopeView.width - 10
+ : oscilloscopeView.width / 3 - 7.5
+
+ onSampleRowsChanged: {
+ surfaceSeries.selectedPoint = surfaceSeries.invalidSelectionPosition
+ generateData()
+ }
+
+ //![1]
+ DataSource {
+ id: dataSource
+ }
+ //![1]
+
+ Item {
+ id: dataView
+ anchors.bottom: parent.bottom
+ width: parent.width
+ height: parent.height - controlArea.height
+
+ //! [2]
+ Surface3D {
+ id: surfaceGraph
+ anchors.fill: parent
+
+ Surface3DSeries {
+ id: surfaceSeries
+ drawMode: Surface3DSeries.DrawSurfaceAndWireframe
+ itemLabelFormat: "@xLabel, @zLabel: @yLabel"
+ //! [2]
+ //! [3]
+ itemLabelVisible: false
+ //! [3]
+
+ //! [4]
+ onItemLabelChanged: {
+ if (surfaceSeries.selectedPoint == surfaceSeries.invalidSelectionPosition)
+ selectionText.text = "No selection";
+ else
+ selectionText.text = surfaceSeries.itemLabel;
+ }
+ //! [4]
+ }
+
+ shadowQuality: AbstractGraph3D.ShadowQualityNone
+ selectionMode: AbstractGraph3D.SelectionSlice | AbstractGraph3D.SelectionItemAndColumn
+ theme: Theme3D {
+ type: Theme3D.ThemeIsabelle
+ backgroundEnabled: false
+ }
+ scene.activeCamera.cameraPreset: Camera3D.CameraPresetFrontHigh
+
+ axisX.labelFormat: "%d ms"
+ axisY.labelFormat: "%d W"
+ axisZ.labelFormat: "%d mV"
+ axisX.min: 0
+ axisY.min: 0
+ axisZ.min: 0
+ axisX.max: 1000
+ axisY.max: 100
+ axisZ.max: 800
+ axisX.segmentCount: 4
+ axisY.segmentCount: 4
+ axisZ.segmentCount: 4
+ measureFps: true
+ renderingMode: AbstractGraph3D.RenderDirectToBackground
+
+ onCurrentFpsChanged: (fps)=> {
+ if (fps > 10)
+ fpsText.text = "FPS: " + Math.round(surfaceGraph.currentFps);
+ else
+ fpsText.text = "FPS: " + Math.round(surfaceGraph.currentFps * 10.0) / 10.0;
+ }
+
+ //! [5]
+ Component.onCompleted: oscilloscopeView.generateData();
+ //! [5]
+ }
+ }
+
+ //! [7]
+ Timer {
+ id: refreshTimer
+ interval: 1000 / frequencySlider.value
+ running: true
+ repeat: true
+ onTriggered: dataSource.update(surfaceSeries);
+ }
+ //! [7]
+
+ Rectangle {
+ id: controlArea
+ height: oscilloscopeView.portraitMode ? flatShadingToggle.implicitHeight * 7
+ : flatShadingToggle.implicitHeight * 2
+ anchors.left: parent.left
+ anchors.top: parent.top
+ anchors.right: parent.right
+ color: surfaceGraph.theme.backgroundColor
+
+ // Samples
+ Rectangle {
+ id: samples
+ width: oscilloscopeView.controlWidth
+ height: flatShadingToggle.implicitHeight
+ anchors.left: parent.left
+ anchors.top: parent.top
+ anchors.margins: 5
+
+ color: surfaceGraph.theme.windowColor
+ border.color: surfaceGraph.theme.gridLineColor
+ border.width: 1
+ radius: 4
+
+ Row {
+ anchors.centerIn: parent
+ spacing: 10
+ padding: 5
+
+ Slider {
+ id: sampleSlider
+ from: oscilloscopeView.sampleCache * 2
+ to: from * 10
+ stepSize: oscilloscopeView.sampleCache
+
+ background: Rectangle {
+ x: sampleSlider.leftPadding
+ y: sampleSlider.topPadding + sampleSlider.availableHeight / 2
+ - height / 2
+ implicitWidth: 200
+ implicitHeight: 4
+ width: sampleSlider.availableWidth
+ height: implicitHeight
+ radius: 2
+ color: surfaceGraph.theme.gridLineColor
+
+ Rectangle {
+ width: sampleSlider.visualPosition * parent.width
+ height: parent.height
+ color: surfaceGraph.theme.labelTextColor
+ radius: 2
+ }
+ }
+
+ handle: Rectangle {
+ x: sampleSlider.leftPadding + sampleSlider.visualPosition
+ * (sampleSlider.availableWidth - width)
+ y: sampleSlider.topPadding + sampleSlider.availableHeight / 2
+ - height / 2
+ implicitWidth: 20
+ implicitHeight: 20
+ radius: 10
+ color: sampleSlider.pressed ? surfaceGraph.theme.gridLineColor
+ : surfaceGraph.theme.windowColor
+ border.color: sampleSlider.pressed ? surfaceGraph.theme.labelTextColor
+ : surfaceGraph.theme.gridLineColor
+ }
+
+ Component.onCompleted: value = from;
+ }
+
+ Text {
+ id: samplesText
+ text: "Samples: " + (oscilloscopeView.sampleRows * oscilloscopeView.sampleColumns)
+ verticalAlignment: Text.AlignVCenter
+ horizontalAlignment: Text.AlignHCenter
+ color: surfaceGraph.theme.labelTextColor
+ }
+ }
+ }
+
+ // Frequency
+ Rectangle {
+ id: frequency
+ width: oscilloscopeView.controlWidth
+ height: flatShadingToggle.implicitHeight
+ anchors.left: oscilloscopeView.portraitMode ? parent.left : samples.right
+ anchors.top: oscilloscopeView.portraitMode ? samples.bottom : parent.top
+ anchors.margins: 5
+
+ color: surfaceGraph.theme.windowColor
+ border.color: surfaceGraph.theme.gridLineColor
+ border.width: 1
+ radius: 4
+
+ Row {
+ anchors.centerIn: parent
+ spacing: 10
+ padding: 5
+
+ Slider {
+ id: frequencySlider
+ from: 2
+ to: 60
+ stepSize: 2
+ value: 30
+
+ background: Rectangle {
+ x: frequencySlider.leftPadding
+ y: frequencySlider.topPadding + frequencySlider.availableHeight / 2
+ - height / 2
+ implicitWidth: 200
+ implicitHeight: 4
+ width: frequencySlider.availableWidth
+ height: implicitHeight
+ radius: 2
+ color: surfaceGraph.theme.gridLineColor
+
+ Rectangle {
+ width: frequencySlider.visualPosition * parent.width
+ height: parent.height
+ color: surfaceGraph.theme.labelTextColor
+ radius: 2
+ }
+ }
+
+ handle: Rectangle {
+ x: frequencySlider.leftPadding + frequencySlider.visualPosition
+ * (frequencySlider.availableWidth - width)
+ y: frequencySlider.topPadding + frequencySlider.availableHeight / 2
+ - height / 2
+ implicitWidth: 20
+ implicitHeight: 20
+ radius: 10
+ color: frequencySlider.pressed ? surfaceGraph.theme.gridLineColor
+ : surfaceGraph.theme.windowColor
+ border.color: frequencySlider.pressed ? surfaceGraph.theme.labelTextColor
+ : surfaceGraph.theme.gridLineColor
+ }
+ }
+
+ Text {
+ id: frequencyText
+ text: "Freq: " + frequencySlider.value + " Hz"
+ verticalAlignment: Text.AlignVCenter
+ horizontalAlignment: Text.AlignHCenter
+ color: surfaceGraph.theme.labelTextColor
+ }
+ }
+ }
+
+ // FPS
+ Rectangle {
+ id: fpsindicator
+ width: oscilloscopeView.controlWidth
+ height: flatShadingToggle.implicitHeight
+ anchors.left: oscilloscopeView.portraitMode ? parent.left : frequency.right
+ anchors.top: oscilloscopeView.portraitMode ? frequency.bottom : parent.top
+ anchors.margins: 5
+
+ color: surfaceGraph.theme.windowColor
+ border.color: surfaceGraph.theme.gridLineColor
+ border.width: 1
+ radius: 4
+
+ Text {
+ id: fpsText
+ anchors.fill: parent
+ verticalAlignment: Text.AlignVCenter
+ horizontalAlignment: Text.AlignHCenter
+ color: surfaceGraph.theme.labelTextColor
+ }
+ }
+
+ // Selection
+ Rectangle {
+ id: selection
+ width: oscilloscopeView.controlWidth
+ height: flatShadingToggle.implicitHeight
+ anchors.left: oscilloscopeView.portraitMode ? parent.left : fpsindicator.right
+ anchors.top: oscilloscopeView.portraitMode ? fpsindicator.bottom : parent.top
+ anchors.margins: 5
+
+ color: surfaceGraph.theme.windowColor
+ border.color: surfaceGraph.theme.gridLineColor
+ border.width: 1
+ radius: 4
+
+ Text {
+ id: selectionText
+ anchors.fill: parent
+ verticalAlignment: Text.AlignVCenter
+ horizontalAlignment: Text.AlignHCenter
+ text: "No selection"
+ color: surfaceGraph.theme.labelTextColor
+ }
+ }
+
+ // Flat shading
+ Button {
+ id: flatShadingToggle
+ width: oscilloscopeView.buttonWidth
+ anchors.left: parent.left
+ anchors.top: selection.bottom
+ anchors.margins: 5
+
+ text: surfaceSeries.flatShadingSupported ? "Show\nSmooth" : "Flat\nnot supported"
+ enabled: surfaceSeries.flatShadingSupported
+
+ onClicked: {
+ if (surfaceSeries.flatShadingEnabled) {
+ surfaceSeries.flatShadingEnabled = false;
+ text = "Show\nFlat"
+ } else {
+ surfaceSeries.flatShadingEnabled = true;
+ text = "Show\nSmooth"
+ }
+ }
+
+ contentItem: Text {
+ text: flatShadingToggle.text
+ opacity: flatShadingToggle.enabled ? 1.0 : 0.3
+ color: surfaceGraph.theme.labelTextColor
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ elide: Text.ElideRight
+ }
+
+ background: Rectangle {
+ opacity: flatShadingToggle.enabled ? 1 : 0.3
+ color: flatShadingToggle.down ? surfaceGraph.theme.gridLineColor
+ : surfaceGraph.theme.windowColor
+ border.color: flatShadingToggle.down ? surfaceGraph.theme.labelTextColor
+ : surfaceGraph.theme.gridLineColor
+ border.width: 1
+ radius: 2
+ }
+ }
+
+ // Surface grid
+ Button {
+ id: surfaceGridToggle
+ width: oscilloscopeView.buttonWidth
+ anchors.left: oscilloscopeView.portraitMode ? parent.left : flatShadingToggle.right
+ anchors.top: oscilloscopeView.portraitMode ? flatShadingToggle.bottom : selection.bottom
+ anchors.margins: 5
+
+ text: "Hide\nSurface Grid"
+
+ onClicked: {
+ if (surfaceSeries.drawMode & Surface3DSeries.DrawWireframe) {
+ surfaceSeries.drawMode &= ~Surface3DSeries.DrawWireframe;
+ text = "Show\nSurface Grid";
+ } else {
+ surfaceSeries.drawMode |= Surface3DSeries.DrawWireframe;
+ text = "Hid\nSurface Grid";
+ }
+ }
+
+ contentItem: Text {
+ text: surfaceGridToggle.text
+ color: surfaceGraph.theme.labelTextColor
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ elide: Text.ElideRight
+ }
+
+ background: Rectangle {
+ color: surfaceGridToggle.down ? surfaceGraph.theme.gridLineColor
+ : surfaceGraph.theme.windowColor
+ border.color: surfaceGridToggle.down ? surfaceGraph.theme.labelTextColor
+ : surfaceGraph.theme.gridLineColor
+ border.width: 1
+ radius: 2
+ }
+ }
+
+ // Exit
+ Button {
+ id: exitButton
+ width: oscilloscopeView.buttonWidth
+ height: surfaceGridToggle.height
+ anchors.left: oscilloscopeView.portraitMode ? parent.left : surfaceGridToggle.right
+ anchors.top: oscilloscopeView.portraitMode ? surfaceGridToggle.bottom : selection.bottom
+ anchors.margins: 5
+
+ text: "Quit"
+
+ onClicked: Qt.quit();
+
+ contentItem: Text {
+ text: exitButton.text
+ color: surfaceGraph.theme.labelTextColor
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ elide: Text.ElideRight
+ }
+
+ background: Rectangle {
+ color: exitButton.down ? surfaceGraph.theme.gridLineColor
+ : surfaceGraph.theme.windowColor
+ border.color: exitButton.down ? surfaceGraph.theme.labelTextColor
+ : surfaceGraph.theme.gridLineColor
+ border.width: 1
+ radius: 2
+ }
+ }
+ }
+
+ //! [6]
+ function generateData() {
+ dataSource.generateData(oscilloscopeView.sampleCache, oscilloscopeView.sampleRows,
+ oscilloscopeView.sampleColumns,
+ surfaceGraph.axisX.min, surfaceGraph.axisX.max,
+ surfaceGraph.axisY.min, surfaceGraph.axisY.max,
+ surfaceGraph.axisZ.min, surfaceGraph.axisZ.max);
+ }
+ //! [6]
+}
diff --git a/examples/graphs/qmlsurfacegallery/qml/qmlsurfacegallery/SurfaceSpectrogram.qml b/examples/graphs/qmlsurfacegallery/qml/qmlsurfacegallery/SurfaceSpectrogram.qml
new file mode 100644
index 0000000..00c4038
--- /dev/null
+++ b/examples/graphs/qmlsurfacegallery/qml/qmlsurfacegallery/SurfaceSpectrogram.qml
@@ -0,0 +1,272 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+import QtQuick.Controls
+import QtGraphs
+
+Rectangle {
+ id: spectrogramView
+ color: surfaceGraph.theme.windowColor
+
+ required property bool portraitMode
+
+ property real buttonWidth: spectrogramView.portraitMode ? (spectrogramView.width - 35) / 2
+ : (spectrogramView.width - 50) / 5
+
+ SpectrogramData {
+ id: surfaceData
+ }
+
+ Item {
+ id: surfaceView
+ anchors.top: buttons.bottom
+ anchors.left: parent.left
+ anchors.right: legend.left
+ anchors.bottom: parent.bottom
+
+ ColorGradient {
+ id: surfaceGradient
+ ColorGradientStop { position: 0.0; color: "black" }
+ ColorGradientStop { position: 0.2; color: "red" }
+ ColorGradientStop { position: 0.5; color: "blue" }
+ ColorGradientStop { position: 0.8; color: "yellow" }
+ ColorGradientStop { position: 1.0; color: "white" }
+ }
+
+ ValueAxis3D {
+ id: xAxis
+ segmentCount: 8
+ labelFormat: "%i\u00B0"
+ title: "Angle"
+ titleVisible: true
+ titleFixed: false
+ }
+
+ ValueAxis3D {
+ id: yAxis
+ segmentCount: 8
+ labelFormat: "%i \%"
+ title: "Value"
+ titleVisible: true
+ labelAutoRotation: 0
+ titleFixed: false
+ }
+
+ ValueAxis3D {
+ id: zAxis
+ segmentCount: 5
+ labelFormat: "%i nm"
+ title: "Radius"
+ titleVisible: true
+ titleFixed: false
+ }
+
+ Theme3D {
+ id: customTheme
+ type: Theme3D.ThemeQt
+ // Don't show specular spotlight as we don't want it to distort the colors
+ lightStrength: 0.0
+ ambientLightStrength: 1.0
+ backgroundEnabled: false
+ gridLineColor: "#AAAAAA"
+ windowColor: "#EEEEEE"
+ }
+
+ //! [0]
+ Surface3D {
+ id: surfaceGraph
+ anchors.fill: parent
+
+ Surface3DSeries {
+ id: surfaceSeries
+ flatShadingEnabled: false
+ drawMode: Surface3DSeries.DrawSurface
+ baseGradient: surfaceGradient
+ colorStyle: Theme3D.ColorStyleRangeGradient
+ itemLabelFormat: "(@xLabel, @zLabel): @yLabel"
+
+ ItemModelSurfaceDataProxy {
+ itemModel: surfaceData.model
+ rowRole: "radius"
+ columnRole: "angle"
+ yPosRole: "value"
+ }
+ }
+ //! [0]
+
+ //! [1]
+ // Remove the perspective and view the graph from top down to achieve 2D effect
+ orthoProjection: true
+ scene.activeCamera.cameraPreset: Camera3D.CameraPresetDirectlyAbove
+ //! [1]
+
+ //! [2]
+ flipHorizontalGrid: true
+ //! [2]
+
+ //! [4]
+ radialLabelOffset: 0.01
+ //! [4]
+
+ //! [5]
+ inputHandler: TouchInputHandler3D {
+ rotationEnabled: !surfaceGraph.orthoProjection
+ }
+ //! [5]
+
+ theme: customTheme
+ shadowQuality: AbstractGraph3D.ShadowQualityNone
+ selectionMode: AbstractGraph3D.SelectionSlice | AbstractGraph3D.SelectionItemAndColumn
+ axisX: xAxis
+ axisY: yAxis
+ axisZ: zAxis
+
+ aspectRatio: 1.0
+ horizontalAspectRatio: 1.0
+ scene.activeCamera.zoomLevel: 140
+ }
+ }
+
+ Item {
+ id: buttons
+ anchors.top: parent.top
+ anchors.left: parent.left
+ anchors.right: parent.right
+ height: spectrogramView.portraitMode ? (polarToggle.height + 10) * 3
+ : polarToggle.height + 30
+ anchors.margins: 10
+
+ //! [3]
+ Button {
+ id: polarToggle
+ anchors.margins: 5
+ anchors.left: parent.left
+ anchors.top: parent.top
+ width: spectrogramView.buttonWidth // Calculated elsewhere based on screen orientation
+ text: "Switch to\n" + (surfaceGraph.polar ? "cartesian" : "polar")
+ onClicked: surfaceGraph.polar = !surfaceGraph.polar;
+ }
+ //! [3]
+
+ Button {
+ id: orthoToggle
+ anchors.margins: 5
+ anchors.left: polarToggle.right
+ anchors.top: parent.top
+ width: spectrogramView.buttonWidth
+ text: "Switch to\n" + (surfaceGraph.orthoProjection ? "perspective" : "orthographic")
+ onClicked: {
+ if (surfaceGraph.orthoProjection) {
+ surfaceGraph.orthoProjection = false;
+ xAxis.labelAutoRotation = 30;
+ yAxis.labelAutoRotation = 30;
+ zAxis.labelAutoRotation = 30;
+ } else {
+ surfaceGraph.orthoProjection = true;
+ surfaceGraph.scene.activeCamera.cameraPreset
+ = Camera3D.CameraPresetDirectlyAbove;
+ surfaceSeries.drawMode &= ~Surface3DSeries.DrawWireframe;
+ xAxis.labelAutoRotation = 0;
+ yAxis.labelAutoRotation = 0;
+ zAxis.labelAutoRotation = 0;
+ }
+ }
+ }
+
+ Button {
+ id: flipGridToggle
+ anchors.margins: 5
+ anchors.left: spectrogramView.portraitMode ? parent.left : orthoToggle.right
+ anchors.top: spectrogramView.portraitMode ? orthoToggle.bottom : parent.top
+ width: spectrogramView.buttonWidth
+ text: "Toggle axis\ngrid on top"
+ onClicked: surfaceGraph.flipHorizontalGrid = !surfaceGraph.flipHorizontalGrid;
+ }
+
+ Button {
+ id: labelOffsetToggle
+ anchors.margins: 5
+ anchors.left: flipGridToggle.right
+ anchors.top: spectrogramView.portraitMode ? orthoToggle.bottom : parent.top
+ width: spectrogramView.buttonWidth
+ text: "Toggle radial\nlabel position"
+ visible: surfaceGraph.polar
+ onClicked: {
+ if (surfaceGraph.radialLabelOffset >= 1.0)
+ surfaceGraph.radialLabelOffset = 0.01;
+ else
+ surfaceGraph.radialLabelOffset = 1.0;
+ }
+ }
+
+ Button {
+ id: surfaceGridToggle
+ anchors.margins: 5
+ anchors.left: spectrogramView.portraitMode ? (labelOffsetToggle.visible ? parent.left
+ : flipGridToggle.right)
+ : (labelOffsetToggle.visible ? labelOffsetToggle.right
+ : flipGridToggle.right)
+ anchors.top: spectrogramView.portraitMode ? (labelOffsetToggle.visible ? labelOffsetToggle.bottom
+ : orthoToggle.bottom)
+ : parent.top
+ width: spectrogramView.buttonWidth
+ text: "Toggle\nsurface grid"
+ visible: !surfaceGraph.orthoProjection
+ onClicked: {
+ if (surfaceSeries.drawMode & Surface3DSeries.DrawWireframe)
+ surfaceSeries.drawMode &= ~Surface3DSeries.DrawWireframe;
+ else
+ surfaceSeries.drawMode |= Surface3DSeries.DrawWireframe;
+ }
+ }
+ }
+
+ Item {
+ id: legend
+ anchors.bottom: parent.bottom
+ anchors.top: buttons.bottom
+ anchors.right: parent.right
+ width: spectrogramView.portraitMode ? 100 : 125
+
+ Rectangle {
+ id: gradient
+ anchors.margins: 20
+ anchors.bottom: legend.bottom
+ anchors.top: legend.top
+ anchors.right: legend.right
+ border.color: "black"
+ border.width: 1
+ width: spectrogramView.portraitMode ? 25 : 50
+ rotation: 180
+ gradient: Gradient {
+ GradientStop { position: 0.0; color: "black" }
+ GradientStop { position: 0.2; color: "red" }
+ GradientStop { position: 0.5; color: "blue" }
+ GradientStop { position: 0.8; color: "yellow" }
+ GradientStop { position: 1.0; color: "white" }
+ }
+ }
+
+ Text {
+ anchors.verticalCenter: gradient.bottom
+ anchors.right: gradient.left
+ anchors.margins: 2
+ text: surfaceGraph.axisY.min + "%"
+ }
+
+ Text {
+ anchors.verticalCenter: gradient.verticalCenter
+ anchors.right: gradient.left
+ anchors.margins: 2
+ text: (surfaceGraph.axisY.max + surfaceGraph.axisY.min) / 2 + "%"
+ }
+
+ Text {
+ anchors.verticalCenter: gradient.top
+ anchors.right: gradient.left
+ anchors.margins: 2
+ text: surfaceGraph.axisY.max + "%"
+ }
+ }
+}
diff --git a/examples/graphs/qmlsurfacegallery/qml/qmlsurfacegallery/heightmap.png b/examples/graphs/qmlsurfacegallery/qml/qmlsurfacegallery/heightmap.png
new file mode 100644
index 0000000..02f4123
--- /dev/null
+++ b/examples/graphs/qmlsurfacegallery/qml/qmlsurfacegallery/heightmap.png
Binary files differ
diff --git a/examples/graphs/qmlsurfacegallery/qml/qmlsurfacegallery/heightmap.readme b/examples/graphs/qmlsurfacegallery/qml/qmlsurfacegallery/heightmap.readme
new file mode 100644
index 0000000..505fee8
--- /dev/null
+++ b/examples/graphs/qmlsurfacegallery/qml/qmlsurfacegallery/heightmap.readme
@@ -0,0 +1,25 @@
+Heightmap of Mt Ruapehu & Mt Ngauruhoe
+
+Authored on: 22 July 2018
+Source: University of Otago (https://koordinates.com/layer/3736-09-taumarunui-15m-dem-nzsosdem-v10/)
+
+Creative Commons Attribution - CC BY-SA 4.0
+https://creativecommons.org/licenses/by-sa/4.0/
+
+You are free to:
+
+ Share — copy and redistribute the material in any medium or format
+ Adapt — remix, transform, and build upon the material for any purpose, even commercially.
+
+This license is acceptable for Free Cultural Works.
+
+ The licensor cannot revoke these freedoms as long as you follow the license terms.
+
+Under the following terms:
+
+ Attribution — You must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use.
+
+ ShareAlike — If you remix, transform, or build upon the material, you must distribute your contributions under the same license as the original.
+
+ No additional restrictions — You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits.
+
diff --git a/examples/graphs/qmlsurfacegallery/qml/qmlsurfacegallery/main.qml b/examples/graphs/qmlsurfacegallery/qml/qmlsurfacegallery/main.qml
new file mode 100644
index 0000000..79e4409
--- /dev/null
+++ b/examples/graphs/qmlsurfacegallery/qml/qmlsurfacegallery/main.qml
@@ -0,0 +1,57 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+Item {
+ id: mainView
+ width: 1280
+ height: 1024
+ visible: true
+
+ property bool portraitMode: width < height
+
+ TabBar {
+ id: tabBar
+ width: parent.width
+
+ TabButton {
+ text: "Height Map"
+ }
+
+ TabButton {
+ text: "Spectrogram"
+ }
+
+ TabButton {
+ text: "Oscilloscope"
+ }
+ }
+
+ StackLayout {
+ anchors.top: tabBar.bottom
+ anchors.bottom: parent.bottom
+ width: parent.width
+ currentIndex: tabBar.currentIndex
+
+ SurfaceHeightMap {
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ portraitMode: mainView.portraitMode
+ }
+
+ SurfaceSpectrogram {
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ portraitMode: mainView.portraitMode
+ }
+
+ SurfaceOscilloscope {
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ portraitMode: mainView.portraitMode
+ }
+ }
+}
diff --git a/examples/graphs/qmlsurfacegallery/qmlsurfacegallery.pro b/examples/graphs/qmlsurfacegallery/qmlsurfacegallery.pro
new file mode 100644
index 0000000..1f7a0c0
--- /dev/null
+++ b/examples/graphs/qmlsurfacegallery/qmlsurfacegallery.pro
@@ -0,0 +1,17 @@
+!include( ../examples.pri ) {
+ error( "Couldn't find the examples.pri file!" )
+}
+
+DEFINES += QMAKE_BUILD
+
+QT += graphs
+
+SOURCES += main.cpp \
+ datasource.cpp
+HEADERS += datasource.h
+
+RESOURCES += qmlsurfacegallery.qrc
+
+OTHER_FILES += doc/src/* \
+ doc/images/* \
+ qml/qmlsurfacegallery/*
diff --git a/examples/graphs/qmlsurfacegallery/qmlsurfacegallery.qrc b/examples/graphs/qmlsurfacegallery/qmlsurfacegallery.qrc
new file mode 100644
index 0000000..191c5cc
--- /dev/null
+++ b/examples/graphs/qmlsurfacegallery/qmlsurfacegallery.qrc
@@ -0,0 +1,10 @@
+<RCC>
+ <qresource prefix="/">
+ <file>qml/qmlsurfacegallery/heightmap.png</file>
+ <file>qml/qmlsurfacegallery/main.qml</file>
+ <file>qml/qmlsurfacegallery/SpectrogramData.qml</file>
+ <file>qml/qmlsurfacegallery/SurfaceHeightMap.qml</file>
+ <file>qml/qmlsurfacegallery/SurfaceOscilloscope.qml</file>
+ <file>qml/qmlsurfacegallery/SurfaceSpectrogram.qml</file>
+ </qresource>
+</RCC>
diff --git a/examples/graphs/volumetric/CMakeLists.txt b/examples/graphs/volumetric/CMakeLists.txt
new file mode 100644
index 0000000..8312678
--- /dev/null
+++ b/examples/graphs/volumetric/CMakeLists.txt
@@ -0,0 +1,56 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+cmake_minimum_required(VERSION 3.16)
+project(volumetric LANGUAGES CXX)
+
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+set(CMAKE_AUTOMOC ON)
+set(CMAKE_AUTORCC ON)
+set(CMAKE_AUTOUIC ON)
+
+if(NOT DEFINED INSTALL_EXAMPLESDIR)
+ set(INSTALL_EXAMPLESDIR "examples")
+endif()
+
+set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}")
+
+find_package(Qt6 COMPONENTS Core)
+find_package(Qt6 COMPONENTS Gui)
+find_package(Qt6 COMPONENTS Widgets)
+find_package(Qt6 COMPONENTS Graphs)
+
+qt_add_executable(volumetric
+ main.cpp
+ volumetric.cpp volumetric.h
+)
+set_target_properties(volumetric PROPERTIES
+ WIN32_EXECUTABLE TRUE
+ MACOSX_BUNDLE TRUE
+)
+target_link_libraries(volumetric PUBLIC
+ Qt::Core
+ Qt::Gui
+ Qt::Widgets
+ Qt::Graphs
+)
+
+set(volumetric_resource_files
+ "layer_ground.png"
+ "layer_magma.png"
+ "layer_water.png"
+)
+
+qt6_add_resources(volumetric "volumetric"
+ PREFIX
+ "/heightmaps"
+ FILES
+ ${volumetric_resource_files}
+)
+
+install(TARGETS volumetric
+ RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
+ BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
+ LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
+)
diff --git a/examples/graphs/volumetric/doc/images/volumetric-example.png b/examples/graphs/volumetric/doc/images/volumetric-example.png
new file mode 100644
index 0000000..6cc231d
--- /dev/null
+++ b/examples/graphs/volumetric/doc/images/volumetric-example.png
Binary files differ
diff --git a/examples/graphs/volumetric/doc/src/volumetric.qdoc b/examples/graphs/volumetric/doc/src/volumetric.qdoc
new file mode 100644
index 0000000..3fbf40b
--- /dev/null
+++ b/examples/graphs/volumetric/doc/src/volumetric.qdoc
@@ -0,0 +1,116 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ \example volumetric
+ \meta tags {Graphs, QCustom3DVolume, Custom Item}
+ \meta category {Graphics}
+ \title Volumetric Rendering
+ \ingroup qtdatavisualization_examples
+ \brief Rendering volumetric objects.
+ \since QtGraphs 1.2
+
+ \e {Volumetric Rendering} shows how to use QCustom3DVolume to display volumetric data.
+
+ \image volumetric-example.png
+
+ \include examples-run.qdocinc
+
+ \section1 Initializing Volume Item
+
+ The QCustom3DVolume items are special custom items (see QCustom3DItem), which can be used
+ to display volumetric data. The volume items are only supported with orthographic projection,
+ so first make sure the graph is using it:
+
+ \snippet volumetric/volumetric.cpp 0
+
+ Create a volumetric item tied to the data ranges of the axes:
+
+ \snippet volumetric/volumetric.cpp 1
+
+ Indicate that the scaling of the volume should follow the changes in the data ranges by setting
+ the QCustom3DItem::scalingAbsolute property to \c{false}. Next, define the internal contents of
+ the volume:
+
+ \snippet volumetric/volumetric.cpp 2
+
+ Use eight bit indexed color for the texture, as it is compact and makes it easy to adjust the
+ colors without needing to reset the whole texture. For the texture data, use the data created
+ earlier based on height maps.
+ Typically, the data for volume items comes pregenerated in the form of a stack of images, so
+ the data generation details can be skipped. For more information about the actual data
+ generation process, see the example code.
+
+ Since eight bit indexed colors are used, a color table is needed to map the eight bit color
+ indexes to actual colors. In a typical use case, you would get the color table from the source
+ images instead of using one manually defined:
+
+ \snippet volumetric/volumetric.cpp 3
+
+ To have an option to show slice frames around the volume, initialize their properties.
+ Initially, the frames will be hidden:
+
+ \snippet volumetric/volumetric.cpp 4
+
+ Finally, add the volume as a custom item to the graph to display it:
+
+ \snippet volumetric/volumetric.cpp 5
+
+ \section1 Slicing into the Volume
+
+ Unless the volume is largely transparent, you can only see the surface of it, which is often
+ not very helpful. One way to inspect the internal structure of the volume is to view the slices
+ of the volume. QCustom3DVolume provides two ways to display the slices. The first is to show
+ the selected slices in place of the volume. For example, to specify a slice perpendicular to
+ the X-axis, use the following method:
+
+ \snippet volumetric/volumetric.cpp 6
+
+ To display the slice specified above, the QCustom3DVolume::drawSlices property must also be set:
+
+ \snippet volumetric/volumetric.cpp 7
+
+ The second way to view slices is to use QCustom3DVolume::renderSlice() method, which produces
+ a QImage from the specified slice. This image can then be displayed on another widget, such
+ as a QLabel:
+
+ \snippet volumetric/volumetric.cpp 8
+
+ \section1 Adjusting Volume Transparency
+
+ Sometimes, viewing just the slices doesn't give you a good understanding of the volume's
+ internal structure. QCustom3DVolume provides two properties that can be used to adjust the
+ volume transparency:
+
+ \snippet volumetric/volumetric.cpp 9
+ \dots
+ \snippet volumetric/volumetric.cpp 10
+
+ The QCustom3DVolume::alphaMultiplier is a general multiplier that is applied to the alpha value
+ of each voxel of the volume. It makes it possible to add uniform transparency to the already
+ somewhat transparent portions of the volume to reveal internal opaque details. This multiplier
+ doesn't affect colors that are fully opaque, unless the QCustom3DVolume::preserveOpacity
+ property is set to \c{false}.
+
+ An alternative way to adjust the transparency of the volume is to adjust the alpha values of the
+ voxels directly. For eight bit indexed textures, this is done simply by modifying and resetting
+ the color table:
+
+ \snippet volumetric/volumetric.cpp 11
+
+ \section1 High Definition vs. Low Definition Shader
+
+ By default, the volumetric rendering uses a high definition shader. It accounts for each
+ voxel of the volume with the correct weight when ray-tracing the volume contents, providing an
+ accurate representation of even the finer details of the volume.
+ However, this is computationally very expensive, so the frame rate suffers.
+ If rendering speed is more important than pixel-perfect accuracy of the volume contents, take
+ the much faster low definition shader into use by setting QCustom3DVolume::useHighDefShader
+ property \c{false}. The low definition shader achieves the speed by making compromises on
+ accuracy, so it doesn't guarantee that every voxel of the volume will be sampled. This can lead
+ to flickering or other rendering artifacts on the finer details of the volume.
+
+ \snippet volumetric/volumetric.cpp 12
+
+ \section1 Example Contents
+*/
diff --git a/examples/graphs/volumetric/layer_ground.png b/examples/graphs/volumetric/layer_ground.png
new file mode 100644
index 0000000..7aa3cf9
--- /dev/null
+++ b/examples/graphs/volumetric/layer_ground.png
Binary files differ
diff --git a/examples/graphs/volumetric/layer_magma.png b/examples/graphs/volumetric/layer_magma.png
new file mode 100644
index 0000000..cc949d5
--- /dev/null
+++ b/examples/graphs/volumetric/layer_magma.png
Binary files differ
diff --git a/examples/graphs/volumetric/layer_water.png b/examples/graphs/volumetric/layer_water.png
new file mode 100644
index 0000000..2816bbf
--- /dev/null
+++ b/examples/graphs/volumetric/layer_water.png
Binary files differ
diff --git a/examples/graphs/volumetric/main.cpp b/examples/graphs/volumetric/main.cpp
new file mode 100644
index 0000000..84cc561
--- /dev/null
+++ b/examples/graphs/volumetric/main.cpp
@@ -0,0 +1,232 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "volumetric.h"
+
+#include <QtWidgets/qapplication.h>
+#include <QtWidgets/qwidget.h>
+#include <QtWidgets/qboxlayout.h>
+#include <QtWidgets/qradiobutton.h>
+#include <QtWidgets/qslider.h>
+#include <QtWidgets/qcheckbox.h>
+#include <QtWidgets/qlabel.h>
+#include <QtWidgets/qgroupbox.h>
+#include <QtWidgets/qmessagebox.h>
+#include <QtGui/qscreen.h>
+
+int main(int argc, char **argv)
+{
+ QApplication app(argc, argv);
+ Q3DScatter *graph = new Q3DScatter();
+
+ QSize screenSize = graph->screen()->size();
+ graph->setMinimumSize(QSize(screenSize.width() / 3, screenSize.height() / 2));
+ graph->setMaximumSize(screenSize);
+ graph->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+ graph->setFocusPolicy(Qt::StrongFocus);
+ graph->setResizeMode(QQuickWidget::SizeRootObjectToView);
+
+ QWidget *widget = new QWidget();
+ QHBoxLayout *hLayout = new QHBoxLayout(widget);
+ QVBoxLayout *vLayout = new QVBoxLayout();
+ QVBoxLayout *vLayout2 = new QVBoxLayout();
+ hLayout->addWidget(graph, 1);
+ hLayout->addLayout(vLayout);
+ hLayout->addLayout(vLayout2);
+
+ widget->setWindowTitle(QStringLiteral("Volumetric Rendering - 3D Terrain"));
+
+ QCheckBox *sliceXCheckBox = new QCheckBox(widget);
+ sliceXCheckBox->setText(QStringLiteral("Slice volume on X axis"));
+ sliceXCheckBox->setChecked(false);
+ QCheckBox *sliceYCheckBox = new QCheckBox(widget);
+ sliceYCheckBox->setText(QStringLiteral("Slice volume on Y axis"));
+ sliceYCheckBox->setChecked(false);
+ QCheckBox *sliceZCheckBox = new QCheckBox(widget);
+ sliceZCheckBox->setText(QStringLiteral("Slice volume on Z axis"));
+ sliceZCheckBox->setChecked(false);
+
+ QSlider *sliceXSlider = new QSlider(Qt::Horizontal, widget);
+ sliceXSlider->setMinimum(0);
+ sliceXSlider->setMaximum(1024);
+ sliceXSlider->setValue(512);
+ sliceXSlider->setEnabled(true);
+ QSlider *sliceYSlider = new QSlider(Qt::Horizontal, widget);
+ sliceYSlider->setMinimum(0);
+ sliceYSlider->setMaximum(1024);
+ sliceYSlider->setValue(512);
+ sliceYSlider->setEnabled(true);
+ QSlider *sliceZSlider = new QSlider(Qt::Horizontal, widget);
+ sliceZSlider->setMinimum(0);
+ sliceZSlider->setMaximum(1024);
+ sliceZSlider->setValue(512);
+ sliceZSlider->setEnabled(true);
+
+ QCheckBox *fpsCheckBox = new QCheckBox(widget);
+ fpsCheckBox->setText(QStringLiteral("Show FPS"));
+ fpsCheckBox->setChecked(false);
+ QLabel *fpsLabel = new QLabel(QStringLiteral(""), widget);
+
+ QGroupBox *textureDetailGroupBox = new QGroupBox(QStringLiteral("Texture detail"));
+
+ QRadioButton *lowDetailRB = new QRadioButton(widget);
+ lowDetailRB->setText(QStringLiteral("Low (256x128x256)"));
+ lowDetailRB->setChecked(true);
+
+ QRadioButton *mediumDetailRB = new QRadioButton(widget);
+ mediumDetailRB->setText(QStringLiteral("Generating..."));
+ mediumDetailRB->setChecked(false);
+ mediumDetailRB->setEnabled(false);
+
+ QRadioButton *highDetailRB = new QRadioButton(widget);
+ highDetailRB->setText(QStringLiteral("Generating..."));
+ highDetailRB->setChecked(false);
+ highDetailRB->setEnabled(false);
+
+ QVBoxLayout *textureDetailVBox = new QVBoxLayout;
+ textureDetailVBox->addWidget(lowDetailRB);
+ textureDetailVBox->addWidget(mediumDetailRB);
+ textureDetailVBox->addWidget(highDetailRB);
+ textureDetailGroupBox->setLayout(textureDetailVBox);
+
+ QGroupBox *areaGroupBox = new QGroupBox(QStringLiteral("Show area"));
+
+ QRadioButton *areaAllRB = new QRadioButton(widget);
+ areaAllRB->setText(QStringLiteral("Whole region"));
+ areaAllRB->setChecked(true);
+
+ QRadioButton *areaMineRB = new QRadioButton(widget);
+ areaMineRB->setText(QStringLiteral("The mine"));
+ areaMineRB->setChecked(false);
+
+ QRadioButton *areaMountainRB = new QRadioButton(widget);
+ areaMountainRB->setText(QStringLiteral("The mountain"));
+ areaMountainRB->setChecked(false);
+
+ QVBoxLayout *areaVBox = new QVBoxLayout;
+ areaVBox->addWidget(areaAllRB);
+ areaVBox->addWidget(areaMineRB);
+ areaVBox->addWidget(areaMountainRB);
+ areaGroupBox->setLayout(areaVBox);
+
+ QCheckBox *colorTableCheckBox = new QCheckBox(widget);
+ colorTableCheckBox->setText(QStringLiteral("Alternate color table"));
+ colorTableCheckBox->setChecked(false);
+
+ QLabel *sliceImageXLabel = new QLabel(widget);
+ QLabel *sliceImageYLabel = new QLabel(widget);
+ QLabel *sliceImageZLabel = new QLabel(widget);
+ sliceImageXLabel->setMinimumSize(QSize(200, 100));
+ sliceImageYLabel->setMinimumSize(QSize(200, 200));
+ sliceImageZLabel->setMinimumSize(QSize(200, 100));
+ sliceImageXLabel->setMaximumSize(QSize(200, 100));
+ sliceImageYLabel->setMaximumSize(QSize(200, 200));
+ sliceImageZLabel->setMaximumSize(QSize(200, 100));
+ sliceImageXLabel->setFrameShape(QFrame::Box);
+ sliceImageYLabel->setFrameShape(QFrame::Box);
+ sliceImageZLabel->setFrameShape(QFrame::Box);
+ sliceImageXLabel->setScaledContents(true);
+ sliceImageYLabel->setScaledContents(true);
+ sliceImageZLabel->setScaledContents(true);
+
+ QSlider *alphaMultiplierSlider = new QSlider(Qt::Horizontal, widget);
+ alphaMultiplierSlider->setMinimum(0);
+ alphaMultiplierSlider->setMaximum(139);
+ alphaMultiplierSlider->setValue(100);
+ alphaMultiplierSlider->setEnabled(true);
+ QLabel *alphaMultiplierLabel = new QLabel(QStringLiteral("Alpha multiplier: 1.0"));
+
+ QCheckBox *preserveOpacityCheckBox = new QCheckBox(widget);
+ preserveOpacityCheckBox->setText(QStringLiteral("Preserve opacity"));
+ preserveOpacityCheckBox->setChecked(true);
+
+ QCheckBox *transparentGroundCheckBox = new QCheckBox(widget);
+ transparentGroundCheckBox->setText(QStringLiteral("Transparent ground"));
+ transparentGroundCheckBox->setChecked(false);
+
+ QCheckBox *useHighDefShaderCheckBox = new QCheckBox(widget);
+ useHighDefShaderCheckBox->setText(QStringLiteral("Use HD shader"));
+ useHighDefShaderCheckBox->setChecked(true);
+
+ QLabel *performanceNoteLabel =
+ new QLabel(QStringLiteral(
+ "Note: A high end graphics card is\nrecommended with the HD shader\nwhen the volume contains a lot of\ntransparent areas."));
+ performanceNoteLabel->setFrameShape(QFrame::Box);
+
+ QCheckBox *drawSliceFramesCheckBox = new QCheckBox(widget);
+ drawSliceFramesCheckBox->setText(QStringLiteral("Draw slice frames"));
+ drawSliceFramesCheckBox->setChecked(false);
+
+ vLayout->addWidget(sliceXCheckBox);
+ vLayout->addWidget(sliceXSlider);
+ vLayout->addWidget(sliceImageXLabel);
+ vLayout->addWidget(sliceYCheckBox);
+ vLayout->addWidget(sliceYSlider);
+ vLayout->addWidget(sliceImageYLabel);
+ vLayout->addWidget(sliceZCheckBox);
+ vLayout->addWidget(sliceZSlider);
+ vLayout->addWidget(sliceImageZLabel);
+ vLayout->addWidget(drawSliceFramesCheckBox, 1, Qt::AlignTop);
+ vLayout2->addWidget(fpsCheckBox);
+ vLayout2->addWidget(fpsLabel);
+ vLayout2->addWidget(textureDetailGroupBox);
+ vLayout2->addWidget(areaGroupBox);
+ vLayout2->addWidget(colorTableCheckBox);
+ vLayout2->addWidget(alphaMultiplierLabel);
+ vLayout2->addWidget(alphaMultiplierSlider);
+ vLayout2->addWidget(preserveOpacityCheckBox);
+ vLayout2->addWidget(transparentGroundCheckBox);
+ vLayout2->addWidget(useHighDefShaderCheckBox);
+ vLayout2->addWidget(performanceNoteLabel, 1, Qt::AlignTop);
+
+ VolumetricModifier *modifier = new VolumetricModifier(graph);
+ modifier->setFpsLabel(fpsLabel);
+ modifier->setMediumDetailRB(mediumDetailRB);
+ modifier->setHighDetailRB(highDetailRB);
+ modifier->setSliceSliders(sliceXSlider, sliceYSlider, sliceZSlider);
+ modifier->setSliceLabels(sliceImageXLabel, sliceImageYLabel, sliceImageZLabel);
+ modifier->setAlphaMultiplierLabel(alphaMultiplierLabel);
+ modifier->setTransparentGround(transparentGroundCheckBox->isChecked());
+
+ QObject::connect(fpsCheckBox, &QCheckBox::stateChanged, modifier,
+ &VolumetricModifier::setFpsMeasurement);
+ QObject::connect(sliceXCheckBox, &QCheckBox::stateChanged, modifier,
+ &VolumetricModifier::sliceX);
+ QObject::connect(sliceYCheckBox, &QCheckBox::stateChanged, modifier,
+ &VolumetricModifier::sliceY);
+ QObject::connect(sliceZCheckBox, &QCheckBox::stateChanged, modifier,
+ &VolumetricModifier::sliceZ);
+ QObject::connect(sliceXSlider, &QSlider::valueChanged, modifier,
+ &VolumetricModifier::adjustSliceX);
+ QObject::connect(sliceYSlider, &QSlider::valueChanged, modifier,
+ &VolumetricModifier::adjustSliceY);
+ QObject::connect(sliceZSlider, &QSlider::valueChanged, modifier,
+ &VolumetricModifier::adjustSliceZ);
+ QObject::connect(lowDetailRB, &QRadioButton::toggled, modifier,
+ &VolumetricModifier::toggleLowDetail);
+ QObject::connect(mediumDetailRB, &QRadioButton::toggled, modifier,
+ &VolumetricModifier::toggleMediumDetail);
+ QObject::connect(highDetailRB, &QRadioButton::toggled, modifier,
+ &VolumetricModifier::toggleHighDetail);
+ QObject::connect(colorTableCheckBox, &QCheckBox::stateChanged, modifier,
+ &VolumetricModifier::changeColorTable);
+ QObject::connect(preserveOpacityCheckBox, &QCheckBox::stateChanged, modifier,
+ &VolumetricModifier::setPreserveOpacity);
+ QObject::connect(transparentGroundCheckBox, &QCheckBox::stateChanged, modifier,
+ &VolumetricModifier::setTransparentGround);
+ QObject::connect(useHighDefShaderCheckBox, &QCheckBox::stateChanged, modifier,
+ &VolumetricModifier::setUseHighDefShader);
+ QObject::connect(alphaMultiplierSlider, &QSlider::valueChanged, modifier,
+ &VolumetricModifier::adjustAlphaMultiplier);
+ QObject::connect(areaAllRB, &QRadioButton::toggled, modifier,
+ &VolumetricModifier::toggleAreaAll);
+ QObject::connect(areaMineRB, &QRadioButton::toggled, modifier,
+ &VolumetricModifier::toggleAreaMine);
+ QObject::connect(areaMountainRB, &QRadioButton::toggled, modifier,
+ &VolumetricModifier::toggleAreaMountain);
+ QObject::connect(drawSliceFramesCheckBox, &QCheckBox::stateChanged, modifier,
+ &VolumetricModifier::setDrawSliceFrames);
+
+ widget->show();
+ return app.exec();
+}
diff --git a/examples/graphs/volumetric/volumetric.cpp b/examples/graphs/volumetric/volumetric.cpp
new file mode 100644
index 0000000..0ae3c29
--- /dev/null
+++ b/examples/graphs/volumetric/volumetric.cpp
@@ -0,0 +1,703 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "volumetric.h"
+#include <QtGraphs/qvalue3daxis.h>
+#include <QtGraphs/qcustom3dlabel.h>
+#include <QtGraphs/q3dinputhandler.h>
+#include <QtCore/qmath.h>
+
+const int lowDetailSize(256);
+const int mediumDetailSize(512);
+const int highDetailSize(1024);
+const int colorTableSize(256);
+const int layerDataSize(512);
+const int mineShaftDiameter(1);
+
+const int airColorIndex(254);
+const int mineShaftColorIndex(255);
+const int layerColorThickness(60);
+const int heightToColorDiv(140);
+const int magmaColorsMin(0);
+const int magmaColorsMax(layerColorThickness);
+const int aboveWaterGroundColorsMin(magmaColorsMax + 1);
+const int aboveWaterGroundColorsMax(aboveWaterGroundColorsMin + layerColorThickness);
+const int underWaterGroundColorsMin(aboveWaterGroundColorsMax + 1);
+const int underWaterGroundColorsMax(underWaterGroundColorsMin + layerColorThickness);
+const int waterColorsMin(underWaterGroundColorsMax + 1);
+const int waterColorsMax(waterColorsMin + layerColorThickness);
+const int terrainTransparency(12);
+
+VolumetricModifier::VolumetricModifier(Q3DScatter *scatter)
+ : m_graph(scatter),
+ m_sliceIndexX(lowDetailSize / 2),
+ m_sliceIndexY(lowDetailSize / 4),
+ m_sliceIndexZ(lowDetailSize / 2)
+{
+ m_graph->activeTheme()->setType(Q3DTheme::ThemePrimaryColors);
+ m_graph->setShadowQuality(QAbstract3DGraph::ShadowQualityNone);
+ m_graph->scene()->activeCamera()->setCameraPreset(Q3DCamera::CameraPresetIsometricLeft);
+ //! [0]
+ // TODO: API missing (QTBUG-111611)
+// m_graph->setOrthoProjection(true);
+ //! [0]
+ m_graph->activeTheme()->setBackgroundEnabled(false);
+
+ // Only allow zooming at the center and limit the zoom to 200% to avoid clipping issues
+ // TODO: API missing (QTBUG-111611)
+// static_cast<Q3DInputHandler *>(m_graph->activeInputHandler())->setZoomAtTargetEnabled(false);
+ m_graph->scene()->activeCamera()->setMaxZoomLevel(200.0f);
+
+ toggleAreaAll(true);
+
+ m_lowDetailData
+ = new QList<uchar>(lowDetailSize * lowDetailSize * lowDetailSize / 2);
+ m_mediumDetailData
+ = new QList<uchar>(mediumDetailSize * mediumDetailSize * mediumDetailSize / 2);
+ m_highDetailData
+ = new QList<uchar>(highDetailSize * highDetailSize * highDetailSize / 2);
+
+ initHeightMap(QStringLiteral(":/heightmaps/layer_ground.png"), m_groundLayer);
+ initHeightMap(QStringLiteral(":/heightmaps/layer_water.png"), m_waterLayer);
+ initHeightMap(QStringLiteral(":/heightmaps/layer_magma.png"), m_magmaLayer);
+
+ initMineShaftArray();
+
+ createVolume(lowDetailSize, 0, lowDetailSize, m_lowDetailData);
+ excavateMineShaft(lowDetailSize, 0, m_mineShaftArray.size(), m_lowDetailData);
+
+ //! [1]
+ m_volumeItem = new QCustom3DVolume;
+ // Adjust water level to zero with a minor tweak to y-coordinate position and scaling
+ m_volumeItem->setScaling(
+ QVector3D(m_graph->axisX()->max() - m_graph->axisX()->min(),
+ (m_graph->axisY()->max() - m_graph->axisY()->min()) * 0.91f,
+ m_graph->axisZ()->max() - m_graph->axisZ()->min()));
+ m_volumeItem->setPosition(
+ QVector3D((m_graph->axisX()->max() + m_graph->axisX()->min()) / 2.0f,
+ -0.045f * (m_graph->axisY()->max() - m_graph->axisY()->min()) +
+ (m_graph->axisY()->max() + m_graph->axisY()->min()) / 2.0f,
+ (m_graph->axisZ()->max() + m_graph->axisZ()->min()) / 2.0f));
+ m_volumeItem->setScalingAbsolute(false);
+ //! [1]
+ //! [2]
+ m_volumeItem->setTextureWidth(lowDetailSize);
+ m_volumeItem->setTextureHeight(lowDetailSize / 2);
+ m_volumeItem->setTextureDepth(lowDetailSize);
+ m_volumeItem->setTextureFormat(QImage::Format_Indexed8);
+ m_volumeItem->setTextureData(new QList<uchar>(*m_lowDetailData));
+ //! [2]
+
+ // Generate color tables
+ m_colorTable1.resize(colorTableSize);
+ m_colorTable2.resize(colorTableSize);
+
+ for (int i = 0; i < colorTableSize - 2; i++) {
+ if (i < magmaColorsMax) {
+ m_colorTable1[i] = qRgba(130 - (i * 2), 0, 0, 255);
+ } else if (i < aboveWaterGroundColorsMax) {
+ m_colorTable1[i] = qRgba((i - magmaColorsMax) * 4,
+ ((i - magmaColorsMax) * 2) + 120,
+ (i - magmaColorsMax) * 5, terrainTransparency);
+ } else if (i < underWaterGroundColorsMax) {
+ m_colorTable1[i] = qRgba(((layerColorThickness - i - aboveWaterGroundColorsMax)) + 70,
+ ((layerColorThickness - i - aboveWaterGroundColorsMax) * 2) + 20,
+ ((layerColorThickness - i - aboveWaterGroundColorsMax)) + 50,
+ terrainTransparency);
+ } else if (i < waterColorsMax) {
+ m_colorTable1[i] = qRgba(0, 0, ((i - underWaterGroundColorsMax) * 2) + 120,
+ terrainTransparency);
+ } else {
+ m_colorTable1[i] = qRgba(0, 0, 0, 0); // Not used
+ }
+ }
+ m_colorTable1[airColorIndex] = qRgba(0, 0, 0, 0);
+ m_colorTable1[mineShaftColorIndex] = qRgba(50, 50, 50, 255);
+
+ // The alternate color table just has gray gradients for all terrain except water
+ for (int i = 0; i < colorTableSize - 2; i++) {
+ if (i < magmaColorsMax) {
+ m_colorTable2[i] = qRgba(((i - aboveWaterGroundColorsMax) * 2),
+ ((i - aboveWaterGroundColorsMax) * 2),
+ ((i - aboveWaterGroundColorsMax) * 2),
+ 255);
+ } else if (i < underWaterGroundColorsMax) {
+ m_colorTable2[i] = qRgba(((i - aboveWaterGroundColorsMax) * 2),
+ ((i - aboveWaterGroundColorsMax) * 2),
+ ((i - aboveWaterGroundColorsMax) * 2),
+ terrainTransparency);
+ } else if (i < waterColorsMax) {
+ m_colorTable2[i] = qRgba(0,
+ 0,
+ ((i - underWaterGroundColorsMax) * 2) + 120,
+ terrainTransparency);
+ } else {
+ m_colorTable2[i] = qRgba(0, 0, 0, 0); // Not used
+ }
+ }
+ m_colorTable2[airColorIndex] = qRgba(0, 0, 0, 0);
+ m_colorTable2[mineShaftColorIndex] = qRgba(255, 255, 0, 255);
+
+ //! [3]
+ m_volumeItem->setColorTable(m_colorTable1);
+ //! [3]
+
+ //! [4]
+ m_volumeItem->setSliceFrameGaps(QVector3D(0.01f, 0.02f, 0.01f));
+ m_volumeItem->setSliceFrameThicknesses(QVector3D(0.0025f, 0.005f, 0.0025f));
+ m_volumeItem->setSliceFrameWidths(QVector3D(0.0025f, 0.005f, 0.0025f));
+ m_volumeItem->setDrawSliceFrames(false);
+ //! [4]
+ handleSlicingChanges();
+
+ //! [5]
+ // TODO: API missing (QTBUG-111611)
+// m_graph->addCustomItem(m_volumeItem);
+ //! [5]
+
+ m_timer.start(0);
+
+ // TODO: API missing (QTBUG-111611)
+// QObject::connect(m_graph, &QAbstract3DGraph::currentFpsChanged, this,
+// &VolumetricModifier::handleFpsChange);
+ QObject::connect(&m_timer, &QTimer::timeout, this,
+ &VolumetricModifier::handleTimeout);
+
+}
+
+VolumetricModifier::~VolumetricModifier()
+{
+ delete m_graph;
+}
+
+void VolumetricModifier::setFpsLabel(QLabel *fpsLabel)
+{
+ m_fpsLabel = fpsLabel;
+}
+
+void VolumetricModifier::setMediumDetailRB(QRadioButton *button)
+{
+ m_mediumDetailRB = button;
+}
+
+void VolumetricModifier::setHighDetailRB(QRadioButton *button)
+{
+ m_highDetailRB = button;
+}
+
+void VolumetricModifier::setSliceLabels(QLabel *xLabel, QLabel *yLabel, QLabel *zLabel)
+{
+ m_sliceLabelX = xLabel;
+ m_sliceLabelY = yLabel;
+ m_sliceLabelZ = zLabel;
+
+ adjustSliceX(m_sliceSliderX->value());
+ adjustSliceY(m_sliceSliderY->value());
+ adjustSliceZ(m_sliceSliderZ->value());
+}
+
+void VolumetricModifier::setAlphaMultiplierLabel(QLabel *label)
+{
+ m_alphaMultiplierLabel = label;
+}
+
+void VolumetricModifier::sliceX(int enabled)
+{
+ m_slicingX = enabled;
+ handleSlicingChanges();
+}
+
+void VolumetricModifier::sliceY(int enabled)
+{
+ m_slicingY = enabled;
+ handleSlicingChanges();
+}
+
+void VolumetricModifier::sliceZ(int enabled)
+{
+ m_slicingZ = enabled;
+ handleSlicingChanges();
+}
+
+void VolumetricModifier::adjustSliceX(int value)
+{
+ if (m_volumeItem) {
+ m_sliceIndexX = value / (1024 / m_volumeItem->textureWidth());
+ if (m_sliceIndexX == m_volumeItem->textureWidth())
+ m_sliceIndexX--;
+ if (m_volumeItem->sliceIndexX() != -1)
+ //! [6]
+ m_volumeItem->setSliceIndexX(m_sliceIndexX);
+ //! [6]
+ //! [8]
+ m_sliceLabelX->setPixmap(
+ QPixmap::fromImage(m_volumeItem->renderSlice(Qt::XAxis, m_sliceIndexX)));
+ //! [8]
+ }
+}
+
+void VolumetricModifier::adjustSliceY(int value)
+{
+ if (m_volumeItem) {
+ m_sliceIndexY = value / (1024 / m_volumeItem->textureHeight());
+ if (m_sliceIndexY == m_volumeItem->textureHeight())
+ m_sliceIndexY--;
+ if (m_volumeItem->sliceIndexY() != -1)
+ m_volumeItem->setSliceIndexY(m_sliceIndexY);
+ m_sliceLabelY->setPixmap(
+ QPixmap::fromImage(m_volumeItem->renderSlice(Qt::YAxis, m_sliceIndexY)));
+ }
+}
+
+void VolumetricModifier::adjustSliceZ(int value)
+{
+ if (m_volumeItem) {
+ m_sliceIndexZ = value / (1024 / m_volumeItem->textureDepth());
+ if (m_sliceIndexZ == m_volumeItem->textureDepth())
+ m_sliceIndexZ--;
+ if (m_volumeItem->sliceIndexZ() != -1)
+ m_volumeItem->setSliceIndexZ(m_sliceIndexZ);
+ m_sliceLabelZ->setPixmap(
+ QPixmap::fromImage(m_volumeItem->renderSlice(Qt::ZAxis, m_sliceIndexZ)));
+ }
+}
+
+void VolumetricModifier::handleFpsChange(qreal fps)
+{
+ const QString fpsFormat = QStringLiteral("FPS: %1");
+ int fps10 = int(fps * 10.0);
+ m_fpsLabel->setText(fpsFormat.arg(qreal(fps10) / 10.0));
+}
+
+void VolumetricModifier::handleTimeout()
+{
+ if (!m_mediumDetailRB->isEnabled()) {
+ if (m_mediumDetailIndex != mediumDetailSize) {
+ m_mediumDetailIndex = createVolume(mediumDetailSize, m_mediumDetailIndex, 4,
+ m_mediumDetailData);
+ } else if (m_mediumDetailShaftIndex != m_mineShaftArray.size()) {
+ m_mediumDetailShaftIndex = excavateMineShaft(mediumDetailSize, m_mediumDetailShaftIndex,
+ 1, m_mediumDetailData );
+ } else {
+ m_mediumDetailRB->setEnabled(true);
+ QString label = QStringLiteral("Medium (%1x%2x%1)");
+ m_mediumDetailRB->setText(label.arg(mediumDetailSize).arg(mediumDetailSize / 2));
+ }
+ } else if (!m_highDetailRB->isEnabled()) {
+ if (m_highDetailIndex != highDetailSize) {
+ m_highDetailIndex = createVolume(highDetailSize, m_highDetailIndex, 1,
+ m_highDetailData);
+ } else if (m_highDetailShaftIndex != m_mineShaftArray.size()) {
+ m_highDetailShaftIndex = excavateMineShaft(highDetailSize, m_highDetailShaftIndex, 1,
+ m_highDetailData);
+ } else {
+ m_highDetailRB->setEnabled(true);
+ QString label = QStringLiteral("High (%1x%2x%1)");
+ m_highDetailRB->setText(label.arg(highDetailSize).arg(highDetailSize / 2));
+ m_timer.stop();
+ }
+ }
+}
+
+void VolumetricModifier::toggleLowDetail(bool enabled)
+{
+ if (enabled && m_volumeItem) {
+ m_volumeItem->setTextureData(new QList<uchar>(*m_lowDetailData));
+ m_volumeItem->setTextureDimensions(lowDetailSize, lowDetailSize / 2, lowDetailSize);
+ adjustSliceX(m_sliceSliderX->value());
+ adjustSliceY(m_sliceSliderY->value());
+ adjustSliceZ(m_sliceSliderZ->value());
+ }
+}
+
+void VolumetricModifier::toggleMediumDetail(bool enabled)
+{
+ if (enabled && m_volumeItem) {
+ m_volumeItem->setTextureData(new QList<uchar>(*m_mediumDetailData));
+ m_volumeItem->setTextureDimensions(mediumDetailSize, mediumDetailSize / 2, mediumDetailSize);
+ adjustSliceX(m_sliceSliderX->value());
+ adjustSliceY(m_sliceSliderY->value());
+ adjustSliceZ(m_sliceSliderZ->value());
+ }
+}
+
+void VolumetricModifier::toggleHighDetail(bool enabled)
+{
+ if (enabled && m_volumeItem) {
+ m_volumeItem->setTextureData(new QList<uchar>(*m_highDetailData));
+ m_volumeItem->setTextureDimensions(highDetailSize, highDetailSize / 2, highDetailSize);
+ adjustSliceX(m_sliceSliderX->value());
+ adjustSliceY(m_sliceSliderY->value());
+ adjustSliceZ(m_sliceSliderZ->value());
+ }
+}
+
+void VolumetricModifier::setFpsMeasurement(bool enabled)
+{
+ // TODO: API missing (QTBUG-111611)
+ // m_graph->setMeasureFps(enabled);
+ if (enabled)
+ m_fpsLabel->setText(QStringLiteral("Measuring..."));
+ else
+ m_fpsLabel->setText(QString());
+}
+
+void VolumetricModifier::setSliceSliders(QSlider *sliderX, QSlider *sliderY, QSlider *sliderZ)
+{
+ m_sliceSliderX = sliderX;
+ m_sliceSliderY = sliderY;
+ m_sliceSliderZ = sliderZ;
+
+ // Set sliders to interesting values
+ m_sliceSliderX->setValue(715);
+ m_sliceSliderY->setValue(612);
+ m_sliceSliderZ->setValue(715);
+}
+
+void VolumetricModifier::changeColorTable(int enabled)
+{
+ if (m_volumeItem) {
+ if (enabled)
+ m_volumeItem->setColorTable(m_colorTable2);
+ else
+ m_volumeItem->setColorTable(m_colorTable1);
+
+ m_usingPrimaryTable = !enabled;
+
+ // Rerender image labels
+ adjustSliceX(m_sliceSliderX->value());
+ adjustSliceY(m_sliceSliderY->value());
+ adjustSliceZ(m_sliceSliderZ->value());
+ }
+}
+
+void VolumetricModifier::setPreserveOpacity(bool enabled)
+{
+
+ if (m_volumeItem) {
+ //! [10]
+ m_volumeItem->setPreserveOpacity(enabled);
+ //! [10]
+
+ // Rerender image labels
+ adjustSliceX(m_sliceSliderX->value());
+ adjustSliceY(m_sliceSliderY->value());
+ adjustSliceZ(m_sliceSliderZ->value());
+ }
+}
+
+void VolumetricModifier::setTransparentGround(bool enabled)
+{
+ if (m_volumeItem) {
+ //! [11]
+ int newAlpha = enabled ? terrainTransparency : 255;
+ for (int i = aboveWaterGroundColorsMin; i < underWaterGroundColorsMax; i++) {
+ QRgb oldColor1 = m_colorTable1.at(i);
+ QRgb oldColor2 = m_colorTable2.at(i);
+ m_colorTable1[i] = qRgba(qRed(oldColor1), qGreen(oldColor1), qBlue(oldColor1), newAlpha);
+ m_colorTable2[i] = qRgba(qRed(oldColor2), qGreen(oldColor2), qBlue(oldColor2), newAlpha);
+ }
+ if (m_usingPrimaryTable)
+ m_volumeItem->setColorTable(m_colorTable1);
+ else
+ m_volumeItem->setColorTable(m_colorTable2);
+ //! [11]
+ adjustSliceX(m_sliceSliderX->value());
+ adjustSliceY(m_sliceSliderY->value());
+ adjustSliceZ(m_sliceSliderZ->value());
+ }
+}
+
+void VolumetricModifier::setUseHighDefShader(bool enabled)
+{
+ if (m_volumeItem) {
+ //! [12]
+ m_volumeItem->setUseHighDefShader(enabled);
+ //! [12]
+ }
+}
+
+void VolumetricModifier::adjustAlphaMultiplier(int value)
+{
+ if (m_volumeItem) {
+ float mult;
+ if (value > 100)
+ mult = float(value - 99) / 2.0f;
+ else
+ mult = float(value) / float(500 - value * 4);
+ //! [9]
+ m_volumeItem->setAlphaMultiplier(mult);
+ //! [9]
+ QString labelFormat = QStringLiteral("Alpha multiplier: %1");
+ m_alphaMultiplierLabel->setText(labelFormat.arg(
+ QString::number(m_volumeItem->alphaMultiplier(), 'f', 3)));
+
+ // Rerender image labels
+ adjustSliceX(m_sliceSliderX->value());
+ adjustSliceY(m_sliceSliderY->value());
+ adjustSliceZ(m_sliceSliderZ->value());
+ }
+}
+
+void VolumetricModifier::toggleAreaAll(bool enabled)
+{
+ if (enabled) {
+ m_graph->axisX()->setRange(0.0f, 1000.0f);
+ m_graph->axisY()->setRange(-600.0f, 600.0f);
+ m_graph->axisZ()->setRange(0.0f, 1000.0f);
+ m_graph->axisX()->setSegmentCount(5);
+ m_graph->axisY()->setSegmentCount(6);
+ m_graph->axisZ()->setSegmentCount(5);
+ }
+}
+
+void VolumetricModifier::toggleAreaMine(bool enabled)
+{
+ if (enabled) {
+ m_graph->axisX()->setRange(350.0f, 850.0f);
+ m_graph->axisY()->setRange(-500.0f, 100.0f);
+ m_graph->axisZ()->setRange(350.0f, 900.0f);
+ m_graph->axisX()->setSegmentCount(10);
+ m_graph->axisY()->setSegmentCount(6);
+ m_graph->axisZ()->setSegmentCount(11);
+ }
+}
+
+void VolumetricModifier::toggleAreaMountain(bool enabled)
+{
+ if (enabled) {
+ m_graph->axisX()->setRange(300.0f, 600.0f);
+ m_graph->axisY()->setRange(-100.0f, 400.0f);
+ m_graph->axisZ()->setRange(300.0f, 600.0f);
+ m_graph->axisX()->setSegmentCount(9);
+ m_graph->axisY()->setSegmentCount(5);
+ m_graph->axisZ()->setSegmentCount(9);
+ }
+}
+
+void VolumetricModifier::setDrawSliceFrames(int enabled)
+{
+ if (m_volumeItem)
+ m_volumeItem->setDrawSliceFrames(enabled);
+}
+
+void VolumetricModifier::initHeightMap(QString fileName, QList<uchar> &layerData)
+{
+ QImage heightImage(fileName);
+
+ layerData.resize(layerDataSize * layerDataSize);
+ const uchar *bits = heightImage.bits();
+ int index = 0;
+ QList<QRgb> colorTable = heightImage.colorTable();
+ for (int i = 0; i < layerDataSize; i++) {
+ for (int j = 0; j < layerDataSize; j++) {
+ layerData[index] = qRed(colorTable.at(bits[index]));
+ index++;
+ }
+ }
+}
+
+int VolumetricModifier::createVolume(int textureSize, int startIndex, int count,
+ QList<uchar> *textureData)
+{
+ // Generate volume from layer data.
+ int index = startIndex * textureSize * textureSize / 2.0f;
+ int endIndex = startIndex + count;
+ if (endIndex > textureSize)
+ endIndex = textureSize;
+ QList<uchar> magmaHeights(textureSize);
+ QList<uchar> waterHeights(textureSize);
+ QList<uchar> groundHeights(textureSize);
+ float multiplier = float(layerDataSize) / float(textureSize);
+ for (int i = startIndex; i < endIndex; i++) {
+ // Generate layer height arrays
+ for (int l = 0; l < textureSize; l++) {
+ int layerIndex = (int(i * multiplier) * layerDataSize + int(l * multiplier));
+ magmaHeights[l] = int(m_magmaLayer.at(layerIndex));
+ waterHeights[l] = int(m_waterLayer.at(layerIndex));
+ groundHeights[l] = int(m_groundLayer.at(layerIndex));
+ }
+ for (int j = 0; j < textureSize / 2; j++) {
+ for (int k = 0; k < textureSize; k++) {
+ int colorIndex;
+ int height((layerDataSize - (j * 2 * multiplier)) / 2);
+ if (height < magmaHeights.at(k)) {
+ // Magma layer
+ colorIndex = int((float(height) / heightToColorDiv)
+ * float(layerColorThickness)) + magmaColorsMin;
+ } else if (height < groundHeights.at(k) && height < waterHeights.at(k)) {
+ // Ground layer below water
+ colorIndex = int((float(waterHeights.at(k) - height) / heightToColorDiv)
+ * float(layerColorThickness)) + underWaterGroundColorsMin;
+ } else if (height < waterHeights.at(k)) {
+ // Water layer where water goes over ground
+ colorIndex = int((float(height - magmaHeights.at(k)) / heightToColorDiv)
+ * float(layerColorThickness)) + waterColorsMin;
+ } else if (height <= groundHeights.at(k)) {
+ // Ground above water
+ colorIndex = int((float(height - waterHeights.at(k)) / heightToColorDiv)
+ * float(layerColorThickness)) + aboveWaterGroundColorsMin;
+ } else {
+ // Rest is air
+ colorIndex = airColorIndex;
+ }
+
+ (*textureData)[index] = colorIndex;
+ index++;
+ }
+ }
+ }
+ return endIndex;
+}
+
+int VolumetricModifier::excavateMineShaft(int textureSize, int startIndex, int count,
+ QList<uchar> *textureData)
+{
+ int endIndex = startIndex + count;
+ if (endIndex > m_mineShaftArray.size())
+ endIndex = m_mineShaftArray.size();
+ int shaftSize = mineShaftDiameter * textureSize / lowDetailSize;
+ for (int i = startIndex; i < endIndex; i++) {
+ QVector3D shaftStart(m_mineShaftArray.at(i).first);
+ QVector3D shaftEnd(m_mineShaftArray.at(i).second);
+ int shaftLen = (shaftEnd - shaftStart).length() * lowDetailSize;
+ int dataX = shaftStart.x() * textureSize - (shaftSize / 2);
+ int dataY = (shaftStart.y() * textureSize - (shaftSize / 2)) / 2;
+ int dataZ = shaftStart.z() * textureSize - (shaftSize / 2);
+ int dataIndex = dataX + (dataY * textureSize) + dataZ * (textureSize * textureSize / 2);
+ if (shaftStart.x() != shaftEnd.x()) {
+ for (int j = 0; j <= shaftLen; j++) {
+ excavateMineBlock(textureSize, dataIndex, shaftSize, textureData);
+ dataIndex += shaftSize;
+ }
+ } else if (shaftStart.y() != shaftEnd.y()) {
+ shaftLen /= 2; // Vertical shafts are half as long
+ for (int j = 0; j <= shaftLen; j++) {
+ excavateMineBlock(textureSize, dataIndex, shaftSize, textureData);
+ dataIndex += textureSize * shaftSize;
+ }
+ } else {
+ for (int j = 0; j <= shaftLen; j++) {
+ excavateMineBlock(textureSize, dataIndex, shaftSize, textureData);
+ dataIndex += (textureSize * textureSize / 2) * shaftSize;
+ }
+ }
+ }
+ return endIndex;
+}
+
+void VolumetricModifier::excavateMineBlock(int textureSize, int dataIndex, int size,
+ QList<uchar> *textureData)
+{
+ for (int k = 0; k < size; k++) {
+ int curIndex = 0;
+ for (int l = 0; l < size; l++) {
+ curIndex = dataIndex + (k * textureSize * textureSize / 2)
+ + (l * textureSize);
+ for (int m = 0; m < size; m++) {
+ if (textureData->at(curIndex) != airColorIndex)
+ (*textureData)[curIndex] = mineShaftColorIndex;
+ curIndex++;
+ }
+ }
+ }
+}
+
+void VolumetricModifier::handleSlicingChanges()
+{
+ if (m_volumeItem) {
+ if (m_slicingX || m_slicingY || m_slicingZ) {
+ // Only show slices of selected dimensions
+ //! [7]
+ m_volumeItem->setDrawSlices(true);
+ //! [7]
+ m_volumeItem->setSliceIndexX(m_slicingX ? m_sliceIndexX : -1);
+ m_volumeItem->setSliceIndexY(m_slicingY ? m_sliceIndexY : -1);
+ m_volumeItem->setSliceIndexZ(m_slicingZ ? m_sliceIndexZ : -1);
+ } else {
+ // Show slice frames for all dimenstions when not actually slicing
+ m_volumeItem->setDrawSlices(false);
+ m_volumeItem->setSliceIndexX(m_sliceIndexX);
+ m_volumeItem->setSliceIndexY(m_sliceIndexY);
+ m_volumeItem->setSliceIndexZ(m_sliceIndexZ);
+ }
+ }
+}
+
+void VolumetricModifier::initMineShaftArray()
+{
+ m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.7f, 0.1f, 0.7f),
+ QVector3D(0.7f, 0.8f, 0.7f));
+ m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.7f, 0.7f, 0.5f),
+ QVector3D(0.7f, 0.7f, 0.7f));
+
+ m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.4f, 0.7f, 0.7f),
+ QVector3D(0.7f, 0.7f, 0.7f));
+ m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.4f, 0.7f, 0.7f),
+ QVector3D(0.4f, 0.7f, 0.8f));
+ m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.45f, 0.7f, 0.7f),
+ QVector3D(0.45f, 0.7f, 0.8f));
+ m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.5f, 0.7f, 0.7f),
+ QVector3D(0.5f, 0.7f, 0.8f));
+ m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.55f, 0.7f, 0.7f),
+ QVector3D(0.55f, 0.7f, 0.8f));
+ m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.6f, 0.7f, 0.7f),
+ QVector3D(0.6f, 0.7f, 0.8f));
+ m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.65f, 0.7f, 0.7f),
+ QVector3D(0.65f, 0.7f, 0.8f));
+
+ m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.5f, 0.6f, 0.7f),
+ QVector3D(0.7f, 0.6f, 0.7f));
+ m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.5f, 0.6f, 0.7f),
+ QVector3D(0.5f, 0.6f, 0.8f));
+ m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.55f, 0.6f, 0.7f),
+ QVector3D(0.55f, 0.6f, 0.8f));
+ m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.6f, 0.6f, 0.7f),
+ QVector3D(0.6f, 0.6f, 0.8f));
+ m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.65f, 0.6f, 0.7f),
+ QVector3D(0.65f, 0.6f, 0.8f));
+
+ m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.7f, 0.6f, 0.4f),
+ QVector3D(0.7f, 0.6f, 0.7f));
+ m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.6f, 0.6f, 0.45f),
+ QVector3D(0.8f, 0.6f, 0.45f));
+ m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.6f, 0.6f, 0.5f),
+ QVector3D(0.8f, 0.6f, 0.5f));
+ m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.6f, 0.6f, 0.55f),
+ QVector3D(0.8f, 0.6f, 0.55f));
+ m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.6f, 0.6f, 0.6f),
+ QVector3D(0.8f, 0.6f, 0.6f));
+ m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.6f, 0.6f, 0.65f),
+ QVector3D(0.8f, 0.6f, 0.65f));
+ m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.6f, 0.6f, 0.7f),
+ QVector3D(0.8f, 0.6f, 0.7f));
+
+ m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.7f, 0.7f, 0.4f),
+ QVector3D(0.7f, 0.7f, 0.7f));
+ m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.6f, 0.7f, 0.45f),
+ QVector3D(0.8f, 0.7f, 0.45f));
+ m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.6f, 0.7f, 0.5f),
+ QVector3D(0.8f, 0.7f, 0.5f));
+ m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.6f, 0.7f, 0.55f),
+ QVector3D(0.8f, 0.7f, 0.55f));
+ m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.6f, 0.7f, 0.6f),
+ QVector3D(0.8f, 0.7f, 0.6f));
+ m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.6f, 0.7f, 0.65f),
+ QVector3D(0.8f, 0.7f, 0.65f));
+ m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.6f, 0.7f, 0.7f),
+ QVector3D(0.8f, 0.7f, 0.7f));
+
+ m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.7f, 0.8f, 0.5f),
+ QVector3D(0.7f, 0.8f, 0.7f));
+ m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.6f, 0.8f, 0.55f),
+ QVector3D(0.8f, 0.8f, 0.55f));
+ m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.6f, 0.8f, 0.6f),
+ QVector3D(0.8f, 0.8f, 0.6f));
+ m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.6f, 0.8f, 0.65f),
+ QVector3D(0.8f, 0.8f, 0.65f));
+ m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.6f, 0.8f, 0.7f),
+ QVector3D(0.8f, 0.8f, 0.7f));
+
+ m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.7f, 0.1f, 0.4f),
+ QVector3D(0.7f, 0.7f, 0.4f));
+}
diff --git a/examples/graphs/volumetric/volumetric.h b/examples/graphs/volumetric/volumetric.h
new file mode 100644
index 0000000..e9f61b2
--- /dev/null
+++ b/examples/graphs/volumetric/volumetric.h
@@ -0,0 +1,95 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef VOLUMETRICMODIFIER_H
+#define VOLUMETRICMODIFIER_H
+
+#include <QtGraphs/q3dscatter.h>
+#include <QtGraphs/qcustom3dvolume.h>
+#include <QtCore/qtimer.h>
+#include <QtGui/qrgb.h>
+#include <QtWidgets/qlabel.h>
+#include <QtWidgets/qslider.h>
+#include <QtWidgets/qradiobutton.h>
+
+class VolumetricModifier : public QObject
+{
+ Q_OBJECT
+public:
+ explicit VolumetricModifier(Q3DScatter *scatter);
+ ~VolumetricModifier();
+
+ void setFpsLabel(QLabel *fpsLabel);
+ void setMediumDetailRB(QRadioButton *button);
+ void setHighDetailRB(QRadioButton *button);
+ void setSliceLabels(QLabel *xLabel, QLabel *yLabel, QLabel *zLabel);
+ void setAlphaMultiplierLabel(QLabel *label);
+
+public Q_SLOTS:
+ void sliceX(int enabled);
+ void sliceY(int enabled);
+ void sliceZ(int enabled);
+ void adjustSliceX(int value);
+ void adjustSliceY(int value);
+ void adjustSliceZ(int value);
+ void handleFpsChange(qreal fps);
+ void handleTimeout();
+ void toggleLowDetail(bool enabled);
+ void toggleMediumDetail(bool enabled);
+ void toggleHighDetail(bool enabled);
+ void setFpsMeasurement(bool enabled);
+ void setSliceSliders(QSlider *sliderX, QSlider *sliderY, QSlider *sliderZ);
+ void changeColorTable(int enabled);
+ void setPreserveOpacity(bool enabled);
+ void setTransparentGround(bool enabled);
+ void setUseHighDefShader(bool enabled);
+ void adjustAlphaMultiplier(int value);
+ void toggleAreaAll(bool enabled);
+ void toggleAreaMine(bool enabled);
+ void toggleAreaMountain(bool enabled);
+ void setDrawSliceFrames(int enabled);
+
+private:
+ void initHeightMap(QString fileName, QList<uchar> &layerData);
+ void initMineShaftArray();
+ int createVolume(int textureSize, int startIndex, int count, QList<uchar> *textureData);
+ int excavateMineShaft(int textureSize, int startIndex, int count, QList<uchar> *textureData);
+ void excavateMineBlock(int textureSize, int dataIndex, int size, QList<uchar> *textureData);
+ void handleSlicingChanges();
+
+ Q3DScatter *m_graph = nullptr;
+ QCustom3DVolume *m_volumeItem = nullptr;
+ int m_sliceIndexX = 0;
+ int m_sliceIndexY = 0;
+ int m_sliceIndexZ = 0;
+ bool m_slicingX = false;
+ bool m_slicingY = false;
+ bool m_slicingZ = false;
+ QLabel *m_fpsLabel = nullptr;
+ QRadioButton *m_mediumDetailRB = nullptr;
+ QRadioButton *m_highDetailRB = nullptr;
+ QList<uchar> *m_lowDetailData = nullptr;
+ QList<uchar> *m_mediumDetailData = nullptr;
+ QList<uchar> *m_highDetailData = nullptr;
+ int m_mediumDetailIndex = 0;
+ int m_highDetailIndex = 0;
+ int m_mediumDetailShaftIndex = 0;
+ int m_highDetailShaftIndex = 0;
+ QSlider *m_sliceSliderX = nullptr;
+ QSlider *m_sliceSliderY = nullptr;
+ QSlider *m_sliceSliderZ = nullptr;
+ QList<QRgb> m_colorTable1 = {};
+ QList<QRgb> m_colorTable2 = {};
+ bool m_usingPrimaryTable = true;
+ QLabel *m_sliceLabelX = nullptr;
+ QLabel *m_sliceLabelY = nullptr;
+ QLabel *m_sliceLabelZ = nullptr;
+ QLabel *m_alphaMultiplierLabel = nullptr;
+ QList<uchar> m_magmaLayer = {};
+ QList<uchar> m_waterLayer = {};
+ QList<uchar> m_groundLayer = {};
+ QList<QPair<QVector3D, QVector3D>> m_mineShaftArray = {};
+ QTimer m_timer;
+};
+
+#endif
diff --git a/examples/graphs/volumetric/volumetric.pro b/examples/graphs/volumetric/volumetric.pro
new file mode 100644
index 0000000..feb0e6e
--- /dev/null
+++ b/examples/graphs/volumetric/volumetric.pro
@@ -0,0 +1,17 @@
+android|ios|winrt {
+ error( "This example is not supported for android, ios, or winrt." )
+}
+
+!include( ../examples.pri ) {
+ error( "Couldn't find the examples.pri file!" )
+}
+
+SOURCES += main.cpp volumetric.cpp
+HEADERS += volumetric.h
+
+QT += widgets
+
+OTHER_FILES += doc/src/* \
+ doc/images/*
+
+RESOURCES += volumetric.qrc
diff --git a/examples/graphs/volumetric/volumetric.qrc b/examples/graphs/volumetric/volumetric.qrc
new file mode 100644
index 0000000..920fd1d
--- /dev/null
+++ b/examples/graphs/volumetric/volumetric.qrc
@@ -0,0 +1,7 @@
+<RCC>
+ <qresource prefix="/heightmaps">
+ <file>layer_ground.png</file>
+ <file>layer_magma.png</file>
+ <file>layer_water.png</file>
+ </qresource>
+</RCC>
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
new file mode 100644
index 0000000..37e8816
--- /dev/null
+++ b/src/CMakeLists.txt
@@ -0,0 +1,4 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+add_subdirectory(graphs)
diff --git a/src/graphs/CMakeLists.txt b/src/graphs/CMakeLists.txt
new file mode 100644
index 0000000..686a8cd
--- /dev/null
+++ b/src/graphs/CMakeLists.txt
@@ -0,0 +1,642 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+set(qml_files
+ "qml/designer/Bars3DSpecifics.qml"
+ "qml/designer/Scatter3DSpecifics.qml"
+ "qml/designer/Surface3DSpecifics.qml"
+ "qml/designer/default/Bars3D.qml"
+ "qml/designer/default/Scatter3D.qml"
+ "qml/designer/default/Surface3D.qml"
+)
+
+set_source_files_properties(${qml_files} PROPERTIES
+ QT_QML_SKIP_QMLDIR_ENTRY TRUE
+)
+
+qt_internal_add_qml_module(Graphs
+ URI "QtGraphs"
+ TARGET_PRODUCT "Qt Graphs (Qt $$QT_VERSION)"
+ TARGET_DESCRIPTION "Qt Graphs 3D Visualization component for Qt."
+ VERSION "${PROJECT_VERSION}"
+ PAST_MAJOR_VERSIONS 1
+ DEPENDENCIES
+ QtQuick
+ QML_FILES
+ ${qml_files}
+ SOURCES
+ axis/qabstract3daxis.cpp axis/qabstract3daxis.h axis/qabstract3daxis_p.h
+ axis/qcategory3daxis.cpp axis/qcategory3daxis.h axis/qcategory3daxis_p.h
+ axis/qlogvalue3daxisformatter.cpp axis/qlogvalue3daxisformatter.h axis/qlogvalue3daxisformatter_p.h
+ axis/qvalue3daxis.cpp axis/qvalue3daxis.h axis/qvalue3daxis_p.h
+ axis/qvalue3daxisformatter.cpp axis/qvalue3daxisformatter.h axis/qvalue3daxisformatter_p.h
+
+ data/abstractitemmodelhandler.cpp data/abstractitemmodelhandler_p.h
+ data/baritemmodelhandler.cpp data/baritemmodelhandler_p.h
+ data/qabstract3dseries.cpp data/qabstract3dseries.h data/qabstract3dseries_p.h
+ data/qabstractdataproxy.cpp data/qabstractdataproxy.h data/qabstractdataproxy_p.h
+ data/qbar3dseries.cpp data/qbar3dseries.h data/qbar3dseries_p.h
+ data/qbardataitem.cpp data/qbardataitem.h data/qbardataitem_p.h
+ data/qbardataproxy.cpp data/qbardataproxy.h data/qbardataproxy_p.h
+ data/qcustom3ditem.cpp data/qcustom3ditem.h data/qcustom3ditem_p.h
+ data/qcustom3dlabel.cpp data/qcustom3dlabel.h data/qcustom3dlabel_p.h
+ data/qcustom3dvolume.cpp data/qcustom3dvolume.h data/qcustom3dvolume_p.h
+ data/qheightmapsurfacedataproxy.cpp data/qheightmapsurfacedataproxy.h data/qheightmapsurfacedataproxy_p.h
+ data/qitemmodelbardataproxy.cpp data/qitemmodelbardataproxy.h data/qitemmodelbardataproxy_p.h
+ data/qitemmodelscatterdataproxy.cpp data/qitemmodelscatterdataproxy.h data/qitemmodelscatterdataproxy_p.h
+ data/qitemmodelsurfacedataproxy.cpp data/qitemmodelsurfacedataproxy.h data/qitemmodelsurfacedataproxy_p.h
+ data/qscatter3dseries.cpp data/qscatter3dseries.h data/qscatter3dseries_p.h
+ data/qscatterdataitem.cpp data/qscatterdataitem.h data/qscatterdataitem_p.h
+ data/qscatterdataproxy.cpp data/qscatterdataproxy.h data/qscatterdataproxy_p.h
+ data/qsurface3dseries.cpp data/qsurface3dseries.h data/qsurface3dseries_p.h
+ data/qsurfacedataitem.cpp data/qsurfacedataitem.h data/qsurfacedataitem_p.h
+ data/qsurfacedataproxy.cpp data/qsurfacedataproxy.h data/qsurfacedataproxy_p.h
+ data/scatteritemmodelhandler.cpp data/scatteritemmodelhandler_p.h
+ data/surfaceitemmodelhandler.cpp data/surfaceitemmodelhandler_p.h
+
+ engine/abstract3dcontroller.cpp engine/abstract3dcontroller_p.h
+ engine/axishelper.cpp engine/axishelper_p.h
+ engine/bars3dcontroller.cpp engine/bars3dcontroller_p.h
+ engine/scatterinstancing.cpp engine/scatterinstancing_p.h
+ engine/q3dbars.cpp engine/q3dbars.h
+ engine/q3dcamera.cpp engine/q3dcamera.h engine/q3dcamera_p.h
+ engine/q3dlight.cpp engine/q3dlight.h engine/q3dlight_p.h
+ engine/q3dobject.cpp engine/q3dobject.h engine/q3dobject_p.h
+ engine/q3dscatter.cpp engine/q3dscatter.h
+ engine/q3dscene.cpp engine/q3dscene.h engine/q3dscene_p.h
+ engine/q3dsurface.cpp engine/q3dsurface.h
+ engine/qabstract3dgraph.cpp engine/qabstract3dgraph.h
+ engine/scatter3dcontroller.cpp engine/scatter3dcontroller_p.h
+ engine/surface3dcontroller.cpp engine/surface3dcontroller_p.h
+ engine/surfaceselectioninstancing.cpp engine/surfaceselectioninstancing_p.h
+
+ global/graphsglobal_p.h
+ global/qgraphsglobal.h
+
+ input/q3dinputhandler.cpp input/q3dinputhandler.h input/q3dinputhandler_p.h
+ input/qabstract3dinputhandler.cpp input/qabstract3dinputhandler.h input/qabstract3dinputhandler_p.h
+ input/qtouch3dinputhandler.cpp input/qtouch3dinputhandler.h input/qtouch3dinputhandler_p.h
+
+ theme/q3dtheme.cpp theme/q3dtheme.h theme/q3dtheme_p.h
+ theme/thememanager.cpp theme/thememanager_p.h
+
+ utils/qutils.h
+ utils/utils.cpp utils/utils_p.h
+
+ qml/foreigntypes_p.h
+ qml/colorgradient.cpp qml/colorgradient_p.h
+ qml/declarativecolor.cpp qml/declarativecolor_p.h
+ qml/declarativescene.cpp qml/declarativescene_p.h
+ qml/declarativeseries.cpp qml/declarativeseries_p.h
+ qml/declarativetheme.cpp qml/declarativetheme_p.h
+ qml/qquickgraphsitem.cpp qml/qquickgraphsitem_p.h
+ qml/qquickgraphsbars.cpp qml/qquickgraphsbars_p.h
+ qml/qquickgraphssurface.cpp qml/qquickgraphssurface_p.h
+ qml/qquickgraphsscatter.cpp qml/qquickgraphsscatter_p.h
+ qml/quickgraphstexturedata.cpp qml/quickgraphstexturedata_p.h
+ INCLUDE_DIRECTORIES
+ axis
+ data
+ engine
+ global
+ input
+ theme
+ utils
+ qml
+ PUBLIC_LIBRARIES
+ Qt::Core
+ Qt::Gui
+ Qt::OpenGL
+ Qt::Qml
+ Qt::Quick
+ Qt::QuickWidgets
+ Qt::Quick3D
+ Qt::Quick3DPrivate
+ Qt::Quick3DRuntimeRenderPrivate
+ PRIVATE_MODULE_INTERFACE
+ Qt::Quick3DPrivate
+ Qt::Quick3DRuntimeRenderPrivate
+ GENERATE_CPP_EXPORTS
+)
+
+set_source_files_properties("qml/resources/DatapointSphere.qml"
+ PROPERTIES QT_RESOURCE_ALIAS "DatapointSphere"
+)
+
+set_source_files_properties("qml/resources/DatapointCube.qml"
+ PROPERTIES QT_RESOURCE_ALIAS "DatapointCube"
+)
+
+set_source_files_properties("qml/resources/AxisLabel.qml"
+ PROPERTIES QT_RESOURCE_ALIAS "AxisLabel"
+)
+
+set_source_files_properties("qml/resources/GridLine.qml"
+ PROPERTIES QT_RESOURCE_ALIAS "GridLine"
+)
+
+set_source_files_properties("qml/resources/ItemLabel.qml"
+ PROPERTIES QT_RESOURCE_ALIAS "ItemLabel"
+)
+
+set_source_files_properties("qml/resources/RangeGradientMaterial.qml"
+ PROPERTIES QT_RESOURCE_ALIAS "RangeGradientMaterial"
+)
+set_source_files_properties("qml/resources/RangeGradientMaterialInstancing.qml"
+ PROPERTIES QT_RESOURCE_ALIAS "RangeGradientMaterialInstancing"
+)
+
+set(qml_files
+ "qml/designer/Bars3DSpecifics.qml"
+ "qml/designer/Scatter3DSpecifics.qml"
+ "qml/designer/Surface3DSpecifics.qml"
+ "qml/designer/default/Bars3D.qml"
+ "qml/designer/default/Scatter3D.qml"
+ "qml/designer/default/Surface3D.qml"
+)
+
+set(qml_component_resources
+ "qml/resources/DatapointSphere.qml"
+ "qml/resources/DatapointCube.qml"
+)
+
+set(qml_axis_resurces
+ "qml/resources/AxisLabel.qml"
+ "qml/resources/GridLine.qml"
+ "qml/resources/ItemLabel.qml"
+)
+
+set(qml_material_resources
+ "qml/resources/RangeGradientMaterial.qml"
+ "qml/resources/RangeGradientMaterialInstancing.qml"
+ "qml/resources/SurfaceShadowNoTex.qml"
+)
+
+set(resources
+ "qml/designer/Graphs.metainfo"
+ "qml/designer/images/bars3d-icon.png"
+ "qml/designer/images/bars3d-icon16.png"
+ "qml/designer/images/scatter3d-icon.png"
+ "qml/designer/images/scatter3d-icon16.png"
+ "qml/designer/images/surface3d-icon.png"
+ "qml/designer/images/surface3d-icon16.png"
+)
+
+set_source_files_properties("engine/meshes/arrowFlat.obj"
+ PROPERTIES QT_RESOURCE_ALIAS "arrow"
+)
+set_source_files_properties("engine/meshes/arrowSmooth.obj"
+ PROPERTIES QT_RESOURCE_ALIAS "arrowSmooth"
+)
+set_source_files_properties("engine/meshes/background.obj"
+ PROPERTIES QT_RESOURCE_ALIAS "background"
+)
+set_source_files_properties("engine/meshes/backgroundNoFloor.obj"
+ PROPERTIES QT_RESOURCE_ALIAS "backgroundNoFloor"
+)
+set_source_files_properties("engine/meshes/barFilledFlat.obj"
+ PROPERTIES QT_RESOURCE_ALIAS "bevelbarFull"
+)
+set_source_files_properties("engine/meshes/barFilledSmooth.obj"
+ PROPERTIES QT_RESOURCE_ALIAS "bevelbarSmoothFull"
+)
+set_source_files_properties("engine/meshes/barFlat.obj"
+ PROPERTIES QT_RESOURCE_ALIAS "bevelbar"
+)
+set_source_files_properties("engine/meshes/barSmooth.obj"
+ PROPERTIES QT_RESOURCE_ALIAS "bevelbarSmooth"
+)
+set_source_files_properties("engine/meshes/coneFilledFlat.obj"
+ PROPERTIES QT_RESOURCE_ALIAS "coneFull"
+)
+set_source_files_properties("engine/meshes/coneFilledSmooth.obj"
+ PROPERTIES QT_RESOURCE_ALIAS "coneSmoothFull"
+)
+set_source_files_properties("engine/meshes/coneFlat.obj"
+ PROPERTIES QT_RESOURCE_ALIAS "cone"
+)
+set_source_files_properties("engine/meshes/coneSmooth.obj"
+ PROPERTIES QT_RESOURCE_ALIAS "coneSmooth"
+)
+set_source_files_properties("engine/meshes/cubeFilledFlat.obj"
+ PROPERTIES QT_RESOURCE_ALIAS "barFull"
+)
+set_source_files_properties("engine/meshes/cubeFilledSmooth.obj"
+ PROPERTIES QT_RESOURCE_ALIAS "barSmoothFull"
+)
+set_source_files_properties("engine/meshes/cubeFlat.obj"
+ PROPERTIES QT_RESOURCE_ALIAS "bar"
+)
+set_source_files_properties("engine/meshes/cubeSmooth.obj"
+ PROPERTIES QT_RESOURCE_ALIAS "barSmooth"
+)
+set_source_files_properties("engine/meshes/cylinderFilledFlat.obj"
+ PROPERTIES QT_RESOURCE_ALIAS "cylinderFull"
+)
+set_source_files_properties("engine/meshes/cylinderFilledSmooth.obj"
+ PROPERTIES QT_RESOURCE_ALIAS "cylinderSmoothFull"
+)
+set_source_files_properties("engine/meshes/cylinderFlat.obj"
+ PROPERTIES QT_RESOURCE_ALIAS "cylinder"
+)
+set_source_files_properties("engine/meshes/cylinderSmooth.obj"
+ PROPERTIES QT_RESOURCE_ALIAS "cylinderSmooth"
+)
+set_source_files_properties("engine/meshes/minimalFlat.obj"
+ PROPERTIES QT_RESOURCE_ALIAS "minimal"
+)
+set_source_files_properties("engine/meshes/minimalSmooth.obj"
+ PROPERTIES QT_RESOURCE_ALIAS "minimalSmooth"
+)
+set_source_files_properties("engine/meshes/plane.obj"
+ PROPERTIES QT_RESOURCE_ALIAS "plane"
+)
+set_source_files_properties("engine/meshes/pyramidFilledFlat.obj"
+ PROPERTIES QT_RESOURCE_ALIAS "pyramidFull"
+)
+set_source_files_properties("engine/meshes/pyramidFilledSmooth.obj"
+ PROPERTIES QT_RESOURCE_ALIAS "pyramidSmoothFull"
+)
+set_source_files_properties("engine/meshes/pyramidFlat.obj"
+ PROPERTIES QT_RESOURCE_ALIAS "pyramid"
+)
+set_source_files_properties("engine/meshes/pyramidSmooth.obj"
+ PROPERTIES QT_RESOURCE_ALIAS "pyramidSmooth"
+)
+set_source_files_properties("engine/meshes/sphere.obj"
+ PROPERTIES QT_RESOURCE_ALIAS "sphere"
+)
+set_source_files_properties("engine/meshes/sphereSmooth.obj"
+ PROPERTIES QT_RESOURCE_ALIAS "sphereSmooth"
+)
+
+# .mesh file aliases
+set_source_files_properties("engine/meshes/arrowFlat.mesh"
+ PROPERTIES QT_RESOURCE_ALIAS "arrowMesh"
+)
+set_source_files_properties("engine/meshes/arrowSmooth.mesh"
+ PROPERTIES QT_RESOURCE_ALIAS "arrowMeshSmooth"
+)
+set_source_files_properties("engine/meshes/background.mesh"
+ PROPERTIES QT_RESOURCE_ALIAS "backgroundMesh"
+)
+set_source_files_properties("engine/meshes/barFilledFlat.mesh"
+ PROPERTIES QT_RESOURCE_ALIAS "bevelBarMeshFull"
+)
+set_source_files_properties("engine/meshes/barFilledSmooth.mesh"
+ PROPERTIES QT_RESOURCE_ALIAS "bevelBarMeshSmoothFull"
+)
+set_source_files_properties("engine/meshes/barFlat.mesh"
+ PROPERTIES QT_RESOURCE_ALIAS "bevelBarMesh"
+)
+set_source_files_properties("engine/meshes/barSmooth.mesh"
+ PROPERTIES QT_RESOURCE_ALIAS "bevelBarMeshSmooth"
+)
+set_source_files_properties("engine/meshes/cubeFilledFlat.mesh"
+ PROPERTIES QT_RESOURCE_ALIAS "barMeshFull"
+)
+set_source_files_properties("engine/meshes/cubeFilledSmooth.mesh"
+ PROPERTIES QT_RESOURCE_ALIAS "barMeshSmoothFull"
+)
+set_source_files_properties("engine/meshes/cubeFlat.mesh"
+ PROPERTIES QT_RESOURCE_ALIAS "barMesh"
+)
+set_source_files_properties("engine/meshes/cubeSmooth.mesh"
+ PROPERTIES QT_RESOURCE_ALIAS "barMeshSmooth"
+)
+set_source_files_properties("engine/meshes/coneFilledFlat.mesh"
+ PROPERTIES QT_RESOURCE_ALIAS "coneMeshFull"
+)
+set_source_files_properties("engine/meshes/coneFilledSmooth.mesh"
+ PROPERTIES QT_RESOURCE_ALIAS "coneMeshSmoothFull"
+)
+set_source_files_properties("engine/meshes/coneFlat.mesh"
+ PROPERTIES QT_RESOURCE_ALIAS "coneMesh"
+)
+set_source_files_properties("engine/meshes/coneSmooth.mesh"
+ PROPERTIES QT_RESOURCE_ALIAS "coneMeshSmooth"
+)
+set_source_files_properties("engine/meshes/cylinderFilledFlat.mesh"
+ PROPERTIES QT_RESOURCE_ALIAS "cylinderMeshFull"
+)
+set_source_files_properties("engine/meshes/cylinderFilledSmooth.mesh"
+ PROPERTIES QT_RESOURCE_ALIAS "cylinderMeshSmoothFull"
+)
+set_source_files_properties("engine/meshes/cylinderFlat.mesh"
+ PROPERTIES QT_RESOURCE_ALIAS "cylinderMesh"
+)
+set_source_files_properties("engine/meshes/cylinderSmooth.mesh"
+ PROPERTIES QT_RESOURCE_ALIAS "cylinderMeshSmooth"
+)
+set_source_files_properties("engine/meshes/minimalFlat.mesh"
+ PROPERTIES QT_RESOURCE_ALIAS "minimalMesh"
+)
+set_source_files_properties("engine/meshes/minimalSmooth.mesh"
+ PROPERTIES QT_RESOURCE_ALIAS "minimalMeshSmooth"
+)
+set_source_files_properties("engine/meshes/plane.mesh"
+ PROPERTIES QT_RESOURCE_ALIAS "planeMesh"
+)
+set_source_files_properties("engine/meshes/pyramidFilledFlat.mesh"
+ PROPERTIES QT_RESOURCE_ALIAS "pyramidMeshFull"
+)
+set_source_files_properties("engine/meshes/pyramidFilledSmooth.mesh"
+ PROPERTIES QT_RESOURCE_ALIAS "pyramidMeshSmoothFull"
+)
+set_source_files_properties("engine/meshes/pyramidFlat.mesh"
+ PROPERTIES QT_RESOURCE_ALIAS "pyramidMesh"
+)
+set_source_files_properties("engine/meshes/pyramidSmooth.mesh"
+ PROPERTIES QT_RESOURCE_ALIAS "pyramidMeshSmooth"
+)
+set_source_files_properties("engine/meshes/sphere.mesh"
+ PROPERTIES QT_RESOURCE_ALIAS "sphereMesh"
+)
+set_source_files_properties("engine/meshes/sphereSmooth.mesh"
+ PROPERTIES QT_RESOURCE_ALIAS "sphereMeshSmooth"
+)
+set_source_files_properties("engine/meshes/backgroundNoFloor.mesh"
+ PROPERTIES QT_RESOURCE_ALIAS "backgroundNoFloorMesh"
+)
+set(mesh_resource_files
+ # .mesh files
+ "engine/meshes/background.mesh"
+ "engine/meshes/arrowFlat.mesh"
+ "engine/meshes/arrowSmooth.mesh"
+ "engine/meshes/barFilledFlat.mesh"
+ "engine/meshes/barFilledSmooth.mesh"
+ "engine/meshes/barFlat.mesh"
+ "engine/meshes/barSmooth.mesh"
+ "engine/meshes/coneFilledFlat.mesh"
+ "engine/meshes/coneFilledSmooth.mesh"
+ "engine/meshes/coneFlat.mesh"
+ "engine/meshes/coneSmooth.mesh"
+ "engine/meshes/cubeFilledFlat.mesh"
+ "engine/meshes/cubeFilledSmooth.mesh"
+ "engine/meshes/cubeFlat.mesh"
+ "engine/meshes/cubeSmooth.mesh"
+ "engine/meshes/cylinderFilledFlat.mesh"
+ "engine/meshes/cylinderFilledSmooth.mesh"
+ "engine/meshes/cylinderFlat.mesh"
+ "engine/meshes/cylinderSmooth.mesh"
+ "engine/meshes/minimalFlat.mesh"
+ "engine/meshes/minimalSmooth.mesh"
+ "engine/meshes/plane.mesh"
+ "engine/meshes/pyramidFilledFlat.mesh"
+ "engine/meshes/pyramidFilledSmooth.mesh"
+ "engine/meshes/pyramidFlat.mesh"
+ "engine/meshes/pyramidSmooth.mesh"
+ "engine/meshes/sphere.mesh"
+ "engine/meshes/sphereSmooth.mesh"
+ "engine/meshes/backgroundNoFloor.mesh"
+)
+
+set(obj_resource_files
+ "engine/meshes/arrowFlat.obj"
+ "engine/meshes/arrowSmooth.obj"
+ "engine/meshes/background.obj"
+ "engine/meshes/backgroundNoFloor.obj"
+ "engine/meshes/barFilledFlat.obj"
+ "engine/meshes/barFilledSmooth.obj"
+ "engine/meshes/barFlat.obj"
+ "engine/meshes/barSmooth.obj"
+ "engine/meshes/coneFilledFlat.obj"
+ "engine/meshes/coneFilledSmooth.obj"
+ "engine/meshes/coneFlat.obj"
+ "engine/meshes/coneSmooth.obj"
+ "engine/meshes/cubeFilledFlat.obj"
+ "engine/meshes/cubeFilledSmooth.obj"
+ "engine/meshes/cubeFlat.obj"
+ "engine/meshes/cubeSmooth.obj"
+ "engine/meshes/cylinderFilledFlat.obj"
+ "engine/meshes/cylinderFilledSmooth.obj"
+ "engine/meshes/cylinderFlat.obj"
+ "engine/meshes/cylinderSmooth.obj"
+ "engine/meshes/minimalFlat.obj"
+ "engine/meshes/minimalSmooth.obj"
+ "engine/meshes/plane.obj"
+ "engine/meshes/pyramidFilledFlat.obj"
+ "engine/meshes/pyramidFilledSmooth.obj"
+ "engine/meshes/pyramidFlat.obj"
+ "engine/meshes/pyramidSmooth.obj"
+ "engine/meshes/sphere.obj"
+ "engine/meshes/sphereSmooth.obj"
+)
+
+set_source_files_properties("engine/shaders/3dsliceframes.frag"
+ PROPERTIES QT_RESOURCE_ALIAS "fragment3DSliceFrames"
+)
+set_source_files_properties("engine/shaders/colorOnY.frag"
+ PROPERTIES QT_RESOURCE_ALIAS "fragmentColorOnY"
+)
+set_source_files_properties("engine/shaders/colorOnY_ES2.frag"
+ PROPERTIES QT_RESOURCE_ALIAS "fragmentColorOnYES2"
+)
+set_source_files_properties("engine/shaders/default.frag"
+ PROPERTIES QT_RESOURCE_ALIAS "fragment"
+)
+set_source_files_properties("engine/shaders/default.vert"
+ PROPERTIES QT_RESOURCE_ALIAS "vertex"
+)
+set_source_files_properties("engine/shaders/defaultNoMatrices.vert"
+ PROPERTIES QT_RESOURCE_ALIAS "vertexNoMatrices"
+)
+set_source_files_properties("engine/shaders/default_ES2.frag"
+ PROPERTIES QT_RESOURCE_ALIAS "fragmentES2"
+)
+set_source_files_properties("engine/shaders/depth.frag"
+ PROPERTIES QT_RESOURCE_ALIAS "fragmentDepth"
+)
+set_source_files_properties("engine/shaders/depth.vert"
+ PROPERTIES QT_RESOURCE_ALIAS "vertexDepth"
+)
+set_source_files_properties("engine/shaders/label.frag"
+ PROPERTIES QT_RESOURCE_ALIAS "fragmentLabel"
+)
+set_source_files_properties("engine/shaders/label.vert"
+ PROPERTIES QT_RESOURCE_ALIAS "vertexLabel"
+)
+set_source_files_properties("engine/shaders/plainColor.frag"
+ PROPERTIES QT_RESOURCE_ALIAS "fragmentPlainColor"
+)
+set_source_files_properties("engine/shaders/plainColor.vert"
+ PROPERTIES QT_RESOURCE_ALIAS "vertexPlainColor"
+)
+set_source_files_properties("engine/shaders/point_ES2.vert"
+ PROPERTIES QT_RESOURCE_ALIAS "vertexPointES2"
+)
+set_source_files_properties("engine/shaders/point_ES2_UV.vert"
+ PROPERTIES QT_RESOURCE_ALIAS "vertexPointES2_UV"
+)
+set_source_files_properties("engine/shaders/position.vert"
+ PROPERTIES QT_RESOURCE_ALIAS "vertexPosition"
+)
+set_source_files_properties("engine/shaders/positionmap.frag"
+ PROPERTIES QT_RESOURCE_ALIAS "fragmentPositionMap"
+)
+set_source_files_properties("engine/shaders/shadow.frag"
+ PROPERTIES QT_RESOURCE_ALIAS "fragmentShadow"
+)
+set_source_files_properties("engine/shaders/shadow.vert"
+ PROPERTIES QT_RESOURCE_ALIAS "vertexShadow"
+)
+set_source_files_properties("engine/shaders/shadowNoMatrices.vert"
+ PROPERTIES QT_RESOURCE_ALIAS "vertexShadowNoMatrices"
+)
+set_source_files_properties("engine/shaders/shadowNoTex.frag"
+ PROPERTIES QT_RESOURCE_ALIAS "fragmentShadowNoTex"
+)
+set_source_files_properties("engine/shaders/shadowNoTexColorOnY.frag"
+ PROPERTIES QT_RESOURCE_ALIAS "fragmentShadowNoTexColorOnY"
+)
+set_source_files_properties("engine/shaders/surface.frag"
+ PROPERTIES QT_RESOURCE_ALIAS "fragmentSurface"
+)
+set_source_files_properties("engine/shaders/surfaceFlat.frag"
+ PROPERTIES QT_RESOURCE_ALIAS "fragmentSurfaceFlat"
+)
+set_source_files_properties("engine/shaders/surfaceFlat.vert"
+ PROPERTIES QT_RESOURCE_ALIAS "vertexSurfaceFlat"
+)
+set_source_files_properties("engine/shaders/surfaceShadowFlat.frag"
+ PROPERTIES QT_RESOURCE_ALIAS "fragmentSurfaceShadowFlat"
+)
+set_source_files_properties("engine/shaders/surfaceShadowFlat.vert"
+ PROPERTIES QT_RESOURCE_ALIAS "vertexSurfaceShadowFlat"
+)
+set_source_files_properties("engine/shaders/surfaceShadowNoTex.frag"
+ PROPERTIES QT_RESOURCE_ALIAS "fragmentSurfaceShadowNoTex"
+)
+set_source_files_properties("engine/shaders/surfaceTexturedFlat.frag"
+ PROPERTIES QT_RESOURCE_ALIAS "fragmentSurfaceTexturedFlat"
+)
+set_source_files_properties("engine/shaders/surfaceTexturedShadow.frag"
+ PROPERTIES QT_RESOURCE_ALIAS "fragmentTexturedSurfaceShadow"
+)
+set_source_files_properties("engine/shaders/surfaceTexturedShadowFlat.frag"
+ PROPERTIES QT_RESOURCE_ALIAS "fragmentTexturedSurfaceShadowFlat"
+)
+set_source_files_properties("engine/shaders/surface_ES2.frag"
+ PROPERTIES QT_RESOURCE_ALIAS "fragmentSurfaceES2"
+)
+set_source_files_properties("engine/shaders/texture.frag"
+ PROPERTIES QT_RESOURCE_ALIAS "fragmentTexture"
+)
+set_source_files_properties("engine/shaders/texture.vert"
+ PROPERTIES QT_RESOURCE_ALIAS "vertexTexture"
+)
+set_source_files_properties("engine/shaders/texture3d.frag"
+ PROPERTIES QT_RESOURCE_ALIAS "fragmentTexture3D"
+)
+set_source_files_properties("engine/shaders/texture3d.vert"
+ PROPERTIES QT_RESOURCE_ALIAS "vertexTexture3D"
+)
+set_source_files_properties("engine/shaders/texture3dlowdef.frag"
+ PROPERTIES QT_RESOURCE_ALIAS "fragmentTexture3DLowDef"
+)
+set_source_files_properties("engine/shaders/texture3dslice.frag"
+ PROPERTIES QT_RESOURCE_ALIAS "fragmentTexture3DSlice"
+)
+set_source_files_properties("engine/shaders/texture_ES2.frag"
+ PROPERTIES QT_RESOURCE_ALIAS "fragmentTextureES2"
+)
+set(shader_resource_files
+ "engine/shaders/3dsliceframes.frag"
+ "engine/shaders/colorOnY.frag"
+ "engine/shaders/colorOnY_ES2.frag"
+ "engine/shaders/default.frag"
+ "engine/shaders/default.vert"
+ "engine/shaders/defaultNoMatrices.vert"
+ "engine/shaders/default_ES2.frag"
+ "engine/shaders/depth.frag"
+ "engine/shaders/depth.vert"
+ "engine/shaders/label.frag"
+ "engine/shaders/label.vert"
+ "engine/shaders/plainColor.frag"
+ "engine/shaders/plainColor.vert"
+ "engine/shaders/point_ES2.vert"
+ "engine/shaders/point_ES2_UV.vert"
+ "engine/shaders/position.vert"
+ "engine/shaders/positionmap.frag"
+ "engine/shaders/shadow.frag"
+ "engine/shaders/shadow.vert"
+ "engine/shaders/shadowNoMatrices.vert"
+ "engine/shaders/shadowNoTex.frag"
+ "engine/shaders/shadowNoTexColorOnY.frag"
+ "engine/shaders/surface.frag"
+ "engine/shaders/surfaceFlat.frag"
+ "engine/shaders/surfaceFlat.vert"
+ "engine/shaders/surfaceShadowFlat.frag"
+ "engine/shaders/surfaceShadowFlat.vert"
+ "engine/shaders/surfaceShadowNoTex.frag"
+ "engine/shaders/surfaceTexturedFlat.frag"
+ "engine/shaders/surfaceTexturedShadow.frag"
+ "engine/shaders/surfaceTexturedShadowFlat.frag"
+ "engine/shaders/surface_ES2.frag"
+ "engine/shaders/texture.frag"
+ "engine/shaders/texture.vert"
+ "engine/shaders/texture3d.frag"
+ "engine/shaders/texture3d.vert"
+ "engine/shaders/texture3dlowdef.frag"
+ "engine/shaders/texture3dslice.frag"
+ "engine/shaders/texture_ES2.frag"
+)
+
+qt_internal_add_resource(Graphs "graphsshaders"
+ PREFIX
+ "/shaders"
+ BASE
+ "engine/shaders"
+ FILES
+ ${shader_resource_files}
+)
+
+qt_internal_add_resource(Graphs "graphsmeshes"
+ PREFIX
+ "/defaultMeshes"
+ BASE
+ "engine/meshes"
+ FILES
+ ${mesh_resource_files}
+ ${obj_resource_files}
+)
+
+qt_internal_add_resource(Graphs "qml_component_resources"
+ PREFIX
+ "/datapointModels"
+ BASE
+ "qml"
+ FILES
+ ${qml_component_resources}
+)
+
+qt_internal_add_resource(Graphs "qml_axis_resurces"
+ PREFIX
+ "/axis"
+ BASE
+ "qml"
+ FILES
+ ${qml_axis_resurces}
+)
+
+qt_internal_add_resource(Graphs "qml_material_resources"
+ PREFIX
+ "/materials"
+ BASE
+ "qml"
+ FILES
+ ${qml_material_resources}
+)
+
+qt_internal_extend_target(Graphs CONDITION MACOS
+ LIBRARIES
+ Qt::GuiPrivate
+ PUBLIC_LIBRARIES
+ Qt::Gui
+ PRIVATE_MODULE_INTERFACE
+ Qt::GuiPrivate
+)
+qt_internal_add_docs(Graphs
+ doc/qtgraphs.qdocconf
+)
+
diff --git a/src/graphs/axis/qabstract3daxis.cpp b/src/graphs/axis/qabstract3daxis.cpp
new file mode 100644
index 0000000..26999af
--- /dev/null
+++ b/src/graphs/axis/qabstract3daxis.cpp
@@ -0,0 +1,549 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qabstract3daxis_p.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ * \class QAbstract3DAxis
+ * \inmodule QtGraphs
+ * \brief The QAbstract3DAxis class is a base class for the axes of a graph.
+ *
+ * This class specifies the enumerations, properties, and functions shared by
+ * graph axes. It should not be used directly, but one of its subclasses should
+ * be used instead.
+ *
+ * \sa QCategory3DAxis, QValue3DAxis
+ */
+
+/*!
+ * \qmltype AbstractAxis3D
+ * \inqmlmodule QtGraphs
+ * \ingroup graphs_qml
+ * \instantiates QAbstract3DAxis
+ * \brief A base type for the axes of a graph.
+ *
+ * This type is uncreatable, but contains properties that are exposed via subtypes.
+ *
+ * For AbstractAxis3D enums, see \l QAbstract3DAxis::AxisOrientation and
+ * \l{QAbstract3DAxis::AxisType}.
+ */
+
+/*!
+ * \qmlproperty string AbstractAxis3D::title
+ * The title for the axis.
+ *
+ * \sa titleVisible, titleFixed
+ */
+
+/*!
+ * \qmlproperty list AbstractAxis3D::labels
+ * The labels for the axis.
+ * \note Setting this property for ValueAxis3D does nothing, as it generates labels automatically.
+ */
+
+/*!
+ * \qmlproperty AbstractAxis3D.AxisOrientation AbstractAxis3D::orientation
+ * The orientation of the axis.
+ */
+
+/*!
+ * \qmlproperty AbstractAxis3D.AxisType AbstractAxis3D::type
+ * The type of the axis.
+ */
+
+/*!
+ * \qmlproperty real AbstractAxis3D::min
+ *
+ * The minimum value on the axis.
+ * When setting this property, the maximum value is adjusted if necessary, to
+ * ensure that the range remains valid.
+ */
+
+/*!
+ * \qmlproperty real AbstractAxis3D::max
+ *
+ * The maximum value on the axis.
+ * When setting this property, the minimum value is adjusted if necessary, to
+ * ensure that the range remains valid.
+ */
+
+/*!
+ * \qmlproperty bool AbstractAxis3D::autoAdjustRange
+ *
+ * Defines whether the axis will automatically adjust the range so that all data fits in it.
+ */
+
+/*!
+ * \qmlproperty real AbstractAxis3D::labelAutoRotation
+ *
+ * The maximum angle the labels can autorotate when the camera angle changes.
+ * The angle can be between 0 and 90, inclusive. The default value is 0.
+ * If the value is 0, axis labels do not automatically rotate.
+ * If the value is greater than zero, labels attempt to orient themselves toward the camera, up to
+ * the specified angle.
+ */
+
+/*!
+ * \qmlproperty bool AbstractAxis3D::titleVisible
+ *
+ * Defines whether the axis title is visible in the primary graph view.
+ *
+ * The default value is \c{false}.
+ *
+ * \sa title, titleFixed
+ */
+
+/*!
+ * \qmlproperty bool AbstractAxis3D::titleFixed
+ *
+ * The rotation of axis titles.
+ *
+ * If \c{true}, axis titles in the primary graph view will be rotated towards the camera similarly
+ * to the axis labels.
+ * If \c{false}, axis titles are only rotated around their axis but are not otherwise oriented
+ * towards the camera.
+ * This property does not have any effect if the labelAutoRotation property
+ * value is zero.
+ * Default value is \c{true}.
+ *
+ * \sa labelAutoRotation, title, titleVisible
+ */
+
+/*!
+ * \enum QAbstract3DAxis::AxisOrientation
+ *
+ * The orientation of the axis object.
+ *
+ * \value AxisOrientationNone
+ * \value AxisOrientationX
+ * \value AxisOrientationY
+ * \value AxisOrientationZ
+ */
+
+/*!
+ * \enum QAbstract3DAxis::AxisType
+ *
+ * The type of the axis object.
+ *
+ * \value AxisTypeNone
+ * \value AxisTypeCategory
+ * \value AxisTypeValue
+ */
+
+/*!
+ * \internal
+ */
+QAbstract3DAxis::QAbstract3DAxis(QAbstract3DAxisPrivate *d, QObject *parent) :
+ QObject(parent),
+ d_ptr(d)
+{
+}
+
+/*!
+ * Destroys QAbstract3DAxis.
+ */
+QAbstract3DAxis::~QAbstract3DAxis()
+{
+}
+
+/*!
+ * \property QAbstract3DAxis::orientation
+ *
+ * \brief The orientation of the axis.
+ *
+ * The value is one of AxisOrientation values.
+ */
+QAbstract3DAxis::AxisOrientation QAbstract3DAxis::orientation() const
+{
+ return d_ptr->m_orientation;
+}
+
+/*!
+ * \property QAbstract3DAxis::type
+ *
+ * \brief The type of the axis.
+ *
+ * The value is one of AxisType values.
+ */
+QAbstract3DAxis::AxisType QAbstract3DAxis::type() const
+{
+ return d_ptr->m_type;
+}
+
+/*!
+ * \property QAbstract3DAxis::title
+ *
+ * \brief The title for the axis.
+ *
+ * \sa titleVisible, titleFixed
+ */
+void QAbstract3DAxis::setTitle(const QString &title)
+{
+ if (d_ptr->m_title != title) {
+ d_ptr->m_title = title;
+ emit titleChanged(title);
+ }
+}
+
+QString QAbstract3DAxis::title() const
+{
+ return d_ptr->m_title;
+}
+
+/*!
+ * \property QAbstract3DAxis::labels
+ *
+ * \brief The labels for the axis.
+ * \note Setting this property for QValue3DAxis does nothing, as it generates labels automatically.
+ */
+void QAbstract3DAxis::setLabels(const QStringList &labels)
+{
+ Q_UNUSED(labels);
+}
+
+QStringList QAbstract3DAxis::labels() const
+{
+ d_ptr->updateLabels();
+ return d_ptr->m_labels;
+}
+
+/*!
+ * Sets the value range of the axis from \a min to \a max.
+ * When setting the range, the maximum value is adjusted if necessary, to ensure
+ * that the range remains valid.
+ * \note For QCategory3DAxis, specifies the index range of rows or columns to
+ * show.
+ */
+void QAbstract3DAxis::setRange(float min, float max)
+{
+ d_ptr->setRange(min, max);
+ setAutoAdjustRange(false);
+}
+
+/*!
+ * \property QAbstract3DAxis::labelAutoRotation
+ *
+ * \brief The maximum angle the labels can autorotate when the camera angle changes.
+ *
+ * The angle can be between 0 and 90, inclusive. The default value is 0.
+ * If the value is 0, axis labels do not automatically rotate.
+ * If the value is greater than zero, labels attempt to orient themselves toward the camera, up to
+ * the specified angle.
+ */
+void QAbstract3DAxis::setLabelAutoRotation(float angle)
+{
+ if (angle < 0.0f)
+ angle = 0.0f;
+ if (angle > 90.0f)
+ angle = 90.0f;
+ if (d_ptr->m_labelAutoRotation != angle) {
+ d_ptr->m_labelAutoRotation = angle;
+ emit labelAutoRotationChanged(angle);
+ }
+}
+
+float QAbstract3DAxis::labelAutoRotation() const
+{
+ return d_ptr->m_labelAutoRotation;
+}
+
+/*!
+ * \property QAbstract3DAxis::titleVisible
+ *
+ * \brief Whether the axis title is visible in the primary graph view.
+ *
+ * The default value is \c{false}.
+ *
+ * \sa title, titleFixed
+ */
+void QAbstract3DAxis::setTitleVisible(bool visible)
+{
+ if (d_ptr->m_titleVisible != visible) {
+ d_ptr->m_titleVisible = visible;
+ emit titleVisibilityChanged(visible);
+ }
+}
+
+bool QAbstract3DAxis::isTitleVisible() const
+{
+ return d_ptr->m_titleVisible;
+}
+
+/*!
+ * \property QAbstract3DAxis::titleFixed
+ *
+ * \brief The rotation of the axis titles.
+ *
+ * If \c{true}, axis titles in the primary graph view will be rotated towards the camera similarly
+ * to the axis labels.
+ * If \c{false}, axis titles are only rotated around their axis but are not otherwise oriented
+ * towards the camera.
+ * This property does not have any effect if the labelAutoRotation property
+ * value is zero.
+ * Default value is \c{true}.
+ *
+ * \sa labelAutoRotation, title, titleVisible
+ */
+void QAbstract3DAxis::setTitleFixed(bool fixed)
+{
+ if (d_ptr->m_titleFixed != fixed) {
+ d_ptr->m_titleFixed = fixed;
+ emit titleFixedChanged(fixed);
+ }
+}
+
+bool QAbstract3DAxis::isTitleFixed() const
+{
+ return d_ptr->m_titleFixed;
+}
+
+/*!
+ * \property QAbstract3DAxis::min
+ *
+ * \brief The minimum value on the axis.
+ *
+ * When setting this property, the maximum value is adjusted if necessary, to
+ * ensure that the range remains valid.
+ * \note For QCategory3DAxis, specifies the index of the first row or column to
+ * show.
+ */
+void QAbstract3DAxis::setMin(float min)
+{
+ d_ptr->setMin(min);
+ setAutoAdjustRange(false);
+}
+
+/*!
+ * \property QAbstract3DAxis::max
+ *
+ * \brief The maximum value on the axis.
+ *
+ * When setting this property, the minimum value is adjusted if necessary, to
+ * ensure that the range remains valid.
+ * \note For QCategory3DAxis, specifies the index of the last row or column to
+ * show.
+ */
+void QAbstract3DAxis::setMax(float max)
+{
+ d_ptr->setMax(max);
+ setAutoAdjustRange(false);
+}
+
+float QAbstract3DAxis::min() const
+{
+ return d_ptr->m_min;
+}
+
+float QAbstract3DAxis::max() const
+{
+ return d_ptr->m_max;
+}
+
+/*!
+ * \property QAbstract3DAxis::autoAdjustRange
+ *
+ * \brief Whether the axis will automatically adjust the range so that all data fits in it.
+ *
+ * \sa setRange(), setMin(), setMax()
+ */
+void QAbstract3DAxis::setAutoAdjustRange(bool autoAdjust)
+{
+ if (d_ptr->m_autoAdjust != autoAdjust) {
+ d_ptr->m_autoAdjust = autoAdjust;
+ emit autoAdjustRangeChanged(autoAdjust);
+ }
+}
+
+bool QAbstract3DAxis::isAutoAdjustRange() const
+{
+ return d_ptr->m_autoAdjust;
+}
+
+/*!
+ * \fn QAbstract3DAxis::rangeChanged(float min, float max)
+ *
+ * Emits the minimum and maximum values of the range, \a min and \a max, when
+ * the range changes.
+ */
+
+// QAbstract3DAxisPrivate
+QAbstract3DAxisPrivate::QAbstract3DAxisPrivate(QAbstract3DAxis *q, QAbstract3DAxis::AxisType type)
+ : QObject(0),
+ q_ptr(q),
+ m_orientation(QAbstract3DAxis::AxisOrientationNone),
+ m_type(type),
+ m_isDefaultAxis(false),
+ m_min(0.0f),
+ m_max(10.0f),
+ m_autoAdjust(true),
+ m_labelAutoRotation(0.0f),
+ m_titleVisible(false),
+ m_titleFixed(true)
+{
+}
+
+QAbstract3DAxisPrivate::~QAbstract3DAxisPrivate()
+{
+}
+
+void QAbstract3DAxisPrivate::setOrientation(QAbstract3DAxis::AxisOrientation orientation)
+{
+ if (m_orientation == QAbstract3DAxis::AxisOrientationNone) {
+ m_orientation = orientation;
+ emit q_ptr->orientationChanged(orientation);
+ } else {
+ Q_ASSERT("Attempted to reset axis orientation.");
+ }
+}
+
+void QAbstract3DAxisPrivate::updateLabels()
+{
+ // Default implementation does nothing
+}
+
+void QAbstract3DAxisPrivate::setRange(float min, float max, bool suppressWarnings)
+{
+ bool adjusted = false;
+ if (!allowNegatives()) {
+ if (allowZero()) {
+ if (min < 0.0f) {
+ min = 0.0f;
+ adjusted = true;
+ }
+ if (max < 0.0f) {
+ max = 0.0f;
+ adjusted = true;
+ }
+ } else {
+ if (min <= 0.0f) {
+ min = 1.0f;
+ adjusted = true;
+ }
+ if (max <= 0.0f) {
+ max = 1.0f;
+ adjusted = true;
+ }
+ }
+ }
+ // If min >= max, we adjust ranges so that
+ // m_max becomes (min + 1.0f)
+ // as axes need some kind of valid range.
+ bool minDirty = false;
+ bool maxDirty = false;
+ if (m_min != min) {
+ m_min = min;
+ minDirty = true;
+ }
+ if (m_max != max || min > max || (!allowMinMaxSame() && min == max)) {
+ if (min > max || (!allowMinMaxSame() && min == max)) {
+ m_max = min + 1.0f;
+ adjusted = true;
+ } else {
+ m_max = max;
+ }
+ maxDirty = true;
+ }
+
+ if (minDirty || maxDirty) {
+ if (adjusted && !suppressWarnings) {
+ qWarning() << "Warning: Tried to set invalid range for axis."
+ " Range automatically adjusted to a valid one:"
+ << min << "-" << max << "-->" << m_min << "-" << m_max;
+ }
+ emit q_ptr->rangeChanged(m_min, m_max);
+ }
+
+ if (minDirty)
+ emit q_ptr->minChanged(m_min);
+ if (maxDirty)
+ emit q_ptr->maxChanged(m_max);
+}
+
+void QAbstract3DAxisPrivate::setMin(float min)
+{
+ if (!allowNegatives()) {
+ if (allowZero()) {
+ if (min < 0.0f) {
+ min = 0.0f;
+ qWarning() << "Warning: Tried to set negative minimum for an axis that only"
+ "supports positive values and zero:" << min;
+ }
+ } else {
+ if (min <= 0.0f) {
+ min = 1.0f;
+ qWarning() << "Warning: Tried to set negative or zero minimum for an axis that only"
+ "supports positive values:" << min;
+ }
+ }
+ }
+
+ if (m_min != min) {
+ bool maxChanged = false;
+ if (min > m_max || (!allowMinMaxSame() && min == m_max)) {
+ float oldMax = m_max;
+ m_max = min + 1.0f;
+ qWarning() << "Warning: Tried to set minimum to equal or larger than maximum for"
+ " value axis. Maximum automatically adjusted to a valid one:"
+ << oldMax << "-->" << m_max;
+ maxChanged = true;
+ }
+ m_min = min;
+
+ emit q_ptr->rangeChanged(m_min, m_max);
+ emit q_ptr->minChanged(m_min);
+ if (maxChanged)
+ emit q_ptr->maxChanged(m_max);
+ }
+}
+
+void QAbstract3DAxisPrivate::setMax(float max)
+{
+ if (!allowNegatives()) {
+ if (allowZero()) {
+ if (max < 0.0f) {
+ max = 0.0f;
+ qWarning() << "Warning: Tried to set negative maximum for an axis that only"
+ "supports positive values and zero:" << max;
+ }
+ } else {
+ if (max <= 0.0f) {
+ max = 1.0f;
+ qWarning() << "Warning: Tried to set negative or zero maximum for an axis that only"
+ "supports positive values:" << max;
+ }
+ }
+ }
+
+ if (m_max != max) {
+ bool minChanged = false;
+ if (m_min > max || (!allowMinMaxSame() && m_min == max)) {
+ float oldMin = m_min;
+ m_min = max - 1.0f;
+ if (!allowNegatives() && m_min < 0.0f) {
+ if (allowZero())
+ m_min = 0.0f;
+ else
+ m_min = max / 2.0f; // Need some positive value smaller than max
+
+ if (!allowMinMaxSame() && max == 0.0f) {
+ m_min = oldMin;
+ qWarning() << "Unable to set maximum value to zero.";
+ return;
+ }
+ }
+ qWarning() << "Warning: Tried to set maximum to equal or smaller than minimum for"
+ " value axis. Minimum automatically adjusted to a valid one:"
+ << oldMin << "-->" << m_min;
+ minChanged = true;
+ }
+ m_max = max;
+ emit q_ptr->rangeChanged(m_min, m_max);
+ emit q_ptr->maxChanged(m_max);
+ if (minChanged)
+ emit q_ptr->minChanged(m_min);
+ }
+}
+
+QT_END_NAMESPACE
diff --git a/src/graphs/axis/qabstract3daxis.h b/src/graphs/axis/qabstract3daxis.h
new file mode 100644
index 0000000..9bc570e
--- /dev/null
+++ b/src/graphs/axis/qabstract3daxis.h
@@ -0,0 +1,107 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QABSTRACT3DAXIS_H
+#define QABSTRACT3DAXIS_H
+
+#include <QtGraphs/qgraphsglobal.h>
+#include <QtCore/QObject>
+#include <QtCore/QScopedPointer>
+#include <QtCore/QStringList>
+
+QT_BEGIN_NAMESPACE
+
+class QAbstract3DAxisPrivate;
+
+class Q_GRAPHS_EXPORT QAbstract3DAxis : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QString title READ title WRITE setTitle NOTIFY titleChanged)
+ Q_PROPERTY(QStringList labels READ labels WRITE setLabels NOTIFY labelsChanged)
+ Q_PROPERTY(AxisOrientation orientation READ orientation NOTIFY orientationChanged)
+ Q_PROPERTY(AxisType type READ type CONSTANT)
+ Q_PROPERTY(float min READ min WRITE setMin NOTIFY minChanged)
+ Q_PROPERTY(float max READ max WRITE setMax NOTIFY maxChanged)
+ Q_PROPERTY(bool autoAdjustRange READ isAutoAdjustRange WRITE setAutoAdjustRange NOTIFY autoAdjustRangeChanged)
+ Q_PROPERTY(float labelAutoRotation READ labelAutoRotation WRITE setLabelAutoRotation NOTIFY labelAutoRotationChanged)
+ Q_PROPERTY(bool titleVisible READ isTitleVisible WRITE setTitleVisible NOTIFY titleVisibilityChanged)
+ Q_PROPERTY(bool titleFixed READ isTitleFixed WRITE setTitleFixed NOTIFY titleFixedChanged)
+
+public:
+ enum AxisOrientation {
+ AxisOrientationNone = 0,
+ AxisOrientationX = 1,
+ AxisOrientationY = 2,
+ AxisOrientationZ = 4
+ };
+ Q_ENUM(AxisOrientation)
+
+ enum AxisType {
+ AxisTypeNone = 0,
+ AxisTypeCategory = 1,
+ AxisTypeValue = 2
+ };
+ Q_ENUM(AxisType)
+
+protected:
+ explicit QAbstract3DAxis(QAbstract3DAxisPrivate *d, QObject *parent = nullptr);
+
+public:
+ virtual ~QAbstract3DAxis();
+
+ void setTitle(const QString &title);
+ QString title() const;
+
+ void setLabels(const QStringList &labels);
+ QStringList labels() const;
+
+ AxisOrientation orientation() const;
+ AxisType type() const;
+
+ void setMin(float min);
+ float min() const;
+
+ void setMax(float max);
+ float max() const;
+
+ void setAutoAdjustRange(bool autoAdjust);
+ bool isAutoAdjustRange() const;
+
+ void setRange(float min, float max);
+
+ void setLabelAutoRotation(float angle);
+ float labelAutoRotation() const;
+
+ void setTitleVisible(bool visible);
+ bool isTitleVisible() const;
+
+ void setTitleFixed(bool fixed);
+ bool isTitleFixed() const;
+
+Q_SIGNALS:
+ void titleChanged(const QString &newTitle);
+ void labelsChanged();
+ void orientationChanged(QAbstract3DAxis::AxisOrientation orientation);
+ void minChanged(float value);
+ void maxChanged(float value);
+ void rangeChanged(float min, float max);
+ void autoAdjustRangeChanged(bool autoAdjust);
+ void labelAutoRotationChanged(float angle);
+ void titleVisibilityChanged(bool visible);
+ void titleFixedChanged(bool fixed);
+
+protected:
+ QScopedPointer<QAbstract3DAxisPrivate> d_ptr;
+
+private:
+ Q_DISABLE_COPY(QAbstract3DAxis)
+
+ friend class Abstract3DController;
+ friend class Bars3DController;
+ friend class QScatterDataProxyPrivate;
+ friend class QSurfaceDataProxyPrivate;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/axis/qabstract3daxis_p.h b/src/graphs/axis/qabstract3daxis_p.h
new file mode 100644
index 0000000..2ff122d
--- /dev/null
+++ b/src/graphs/axis/qabstract3daxis_p.h
@@ -0,0 +1,67 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QtGraphs API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#ifndef QABSTRACT3DAXIS_P_H
+#define QABSTRACT3DAXIS_P_H
+
+#include "graphsglobal_p.h"
+#include "qabstract3daxis.h"
+
+QT_BEGIN_NAMESPACE
+
+class QAbstract3DAxisPrivate : public QObject
+{
+ Q_OBJECT
+public:
+ QAbstract3DAxisPrivate(QAbstract3DAxis *q, QAbstract3DAxis::AxisType type);
+ virtual ~QAbstract3DAxisPrivate();
+
+ void setOrientation(QAbstract3DAxis::AxisOrientation orientation);
+
+ inline bool isDefaultAxis() { return m_isDefaultAxis; }
+ inline void setDefaultAxis(bool isDefault) { m_isDefaultAxis = isDefault; }
+
+ virtual void setRange(float min, float max, bool suppressWarnings = false);
+ virtual void setMin(float min);
+ virtual void setMax (float max);
+
+protected:
+ virtual void updateLabels();
+ virtual bool allowZero() = 0;
+ virtual bool allowNegatives() = 0;
+ virtual bool allowMinMaxSame() = 0;
+
+ QAbstract3DAxis *q_ptr;
+
+ QString m_title;
+ QStringList m_labels;
+ QAbstract3DAxis::AxisOrientation m_orientation;
+ QAbstract3DAxis::AxisType m_type;
+ bool m_isDefaultAxis;
+ float m_min;
+ float m_max;
+ bool m_autoAdjust;
+ float m_labelAutoRotation;
+ bool m_titleVisible;
+ bool m_titleFixed;
+
+ friend class QAbstract3DAxis;
+ friend class QValue3DAxis;
+ friend class QCategory3DAxis;
+ friend class QScatterDataProxyPrivate;
+ friend class QSurfaceDataProxyPrivate;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/axis/qcategory3daxis.cpp b/src/graphs/axis/qcategory3daxis.cpp
new file mode 100644
index 0000000..d88f953
--- /dev/null
+++ b/src/graphs/axis/qcategory3daxis.cpp
@@ -0,0 +1,145 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qcategory3daxis_p.h"
+#include "bars3dcontroller_p.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ * \class QCategory3DAxis
+ * \inmodule QtGraphs
+ * \brief The QCategory3DAxis class manipulates an axis of a graph.
+ *
+ * QCategory3DAxis provides an axis that can be given labels. The axis is divided into equal-sized
+ * categories based on the data window size defined by setting the axis range.
+ *
+ * Grid lines are drawn between categories, if visible. Labels are drawn to positions of categories
+ * if provided.
+ */
+
+/*!
+ * \qmltype CategoryAxis3D
+ * \inqmlmodule QtGraphs
+ * \ingroup graphs_qml
+ * \instantiates QCategory3DAxis
+ * \inherits AbstractAxis3D
+ * \brief Manipulates an axis of a graph.
+ *
+ * This type provides an axis that can be given labels.
+ */
+
+/*!
+ * \qmlproperty list CategoryAxis3D::labels
+ *
+ * The labels for the axis applied to categories. If there are fewer labels than categories, the
+ * remaining ones do not have a label. If category labels are not defined explicitly, labels are
+ * generated from the data row (or column) labels of the primary series of the graph.
+ */
+
+/*!
+ * Constructs a category 3D axis with the parent \a parent.
+ */
+QCategory3DAxis::QCategory3DAxis(QObject *parent) :
+ QAbstract3DAxis(new QCategory3DAxisPrivate(this), parent)
+{
+}
+
+/*!
+ * Destroys the category 3D axis.
+ */
+QCategory3DAxis::~QCategory3DAxis()
+{
+}
+
+/*!
+ * \property QCategory3DAxis::labels
+ *
+ * \brief The labels for the axis applied to categories.
+ *
+ * If there are fewer labels than categories, the
+ * remaining ones do not have a label. If category labels are not defined explicitly, labels are
+ * generated from the data row (or column) labels of the primary series of the graph.
+ */
+QStringList QCategory3DAxis::labels() const
+{
+ return QAbstract3DAxis::labels();
+}
+
+void QCategory3DAxis::setLabels(const QStringList &labels)
+{
+ dptr()->m_labelsExplicitlySet = !labels.isEmpty();
+ bool labelsFromData = false;
+
+ // Get labels from data proxy if axis is attached to a bar controller and an active axis there
+ if (labels.isEmpty()) {
+ Bars3DController *controller = qobject_cast<Bars3DController *>(parent());
+ if (controller) {
+ if (controller->axisX() == this) {
+ controller->handleDataRowLabelsChanged();
+ labelsFromData = true;
+ } else if (controller->axisZ() == this) {
+ controller->handleDataColumnLabelsChanged();
+ labelsFromData = true;
+ }
+ }
+ }
+
+ if (!labelsFromData && d_ptr->m_labels != labels) {
+ d_ptr->m_labels = labels;
+ emit QAbstract3DAxis::labelsChanged();
+ }
+}
+
+/*!
+ * \internal
+ */
+QCategory3DAxisPrivate *QCategory3DAxis::dptr()
+{
+ return static_cast<QCategory3DAxisPrivate *>(d_ptr.data());
+}
+
+QCategory3DAxisPrivate::QCategory3DAxisPrivate(QCategory3DAxis *q)
+ : QAbstract3DAxisPrivate(q, QAbstract3DAxis::AxisTypeCategory),
+ m_labelsExplicitlySet(false)
+{
+}
+
+QCategory3DAxisPrivate::~QCategory3DAxisPrivate()
+{
+}
+
+/*!
+ * \internal
+ * Controller uses this function to set labels from data proxy as category labels.
+ * If the labels have been set explicitly by the user, data proxy labels are not used.
+ */
+void QCategory3DAxisPrivate::setDataLabels(const QStringList &labels)
+{
+ if (!m_labelsExplicitlySet && m_labels != labels) {
+ m_labels = labels;
+ emit qptr()->QAbstract3DAxis::labelsChanged();
+ }
+}
+
+bool QCategory3DAxisPrivate::allowZero()
+{
+ return true;
+}
+
+bool QCategory3DAxisPrivate::allowNegatives()
+{
+ return false;
+}
+
+bool QCategory3DAxisPrivate::allowMinMaxSame()
+{
+ return true;
+}
+
+QCategory3DAxis *QCategory3DAxisPrivate::qptr()
+{
+ return static_cast<QCategory3DAxis *>(q_ptr);
+}
+
+QT_END_NAMESPACE
diff --git a/src/graphs/axis/qcategory3daxis.h b/src/graphs/axis/qcategory3daxis.h
new file mode 100644
index 0000000..565feec
--- /dev/null
+++ b/src/graphs/axis/qcategory3daxis.h
@@ -0,0 +1,35 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QCATEGORY3DAXIS_H
+#define QCATEGORY3DAXIS_H
+
+#include <QtGraphs/qabstract3daxis.h>
+
+QT_BEGIN_NAMESPACE
+
+class QCategory3DAxisPrivate;
+
+class Q_GRAPHS_EXPORT QCategory3DAxis : public QAbstract3DAxis
+{
+ Q_OBJECT
+ Q_PROPERTY(QStringList labels READ labels WRITE setLabels NOTIFY labelsChanged)
+
+public:
+ explicit QCategory3DAxis(QObject *parent = nullptr);
+ virtual ~QCategory3DAxis();
+
+ void setLabels(const QStringList &labels);
+ QStringList labels() const;
+
+protected:
+ QCategory3DAxisPrivate *dptr();
+
+private:
+ Q_DISABLE_COPY(QCategory3DAxis)
+ friend class Bars3DController;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/axis/qcategory3daxis_p.h b/src/graphs/axis/qcategory3daxis_p.h
new file mode 100644
index 0000000..3cf6b4c
--- /dev/null
+++ b/src/graphs/axis/qcategory3daxis_p.h
@@ -0,0 +1,46 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QtGraphs API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#ifndef QCATEGORY3DAXIS_P_H
+#define QCATEGORY3DAXIS_P_H
+
+#include "qcategory3daxis.h"
+#include "qabstract3daxis_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class QCategory3DAxisPrivate : public QAbstract3DAxisPrivate
+{
+ Q_OBJECT
+
+public:
+ QCategory3DAxisPrivate(QCategory3DAxis *q);
+ virtual ~QCategory3DAxisPrivate();
+
+ void setDataLabels(const QStringList &labels);
+
+protected:
+ bool allowZero() override;
+ bool allowNegatives() override;
+ bool allowMinMaxSame() override;
+
+private:
+ QCategory3DAxis *qptr();
+
+ bool m_labelsExplicitlySet;
+ friend class QCategory3DAxis;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/axis/qlogvalue3daxisformatter.cpp b/src/graphs/axis/qlogvalue3daxisformatter.cpp
new file mode 100644
index 0000000..7b249d0
--- /dev/null
+++ b/src/graphs/axis/qlogvalue3daxisformatter.cpp
@@ -0,0 +1,427 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qlogvalue3daxisformatter_p.h"
+#include "qvalue3daxis_p.h"
+#include <QtCore/qmath.h>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ * \class QLogValue3DAxisFormatter
+ * \inmodule QtGraphs
+ * \brief The QLogValue3DAxisFormatter class provides formatting rules for a
+ * logarithmic value axis.
+ *
+ * When a formatter is attached to a value axis, the axis range
+ * cannot include negative values or the zero.
+ *
+ * \sa QValue3DAxisFormatter
+ */
+
+/*!
+ * \qmltype LogValueAxis3DFormatter
+ * \inqmlmodule QtGraphs
+ * \ingroup graphs_qml
+ * \instantiates QLogValue3DAxisFormatter
+ * \inherits ValueAxis3DFormatter
+ * \brief Provides formatting rules for a logarithmic value axis.
+ *
+ * When a formatter is attached to a value axis, the axis range
+ * cannot include negative values or the zero.
+ */
+
+/*!
+ * \qmlproperty real LogValueAxis3DFormatter::base
+ *
+ * The base of the logarithm used to map axis values. If the base is non-zero, the parent axis
+ * segment count will be ignored when the grid line and label positions are calculated.
+ * If you want the range to be divided into equal segments like a normal value axis, set this
+ * property value to zero.
+ *
+ * The base has to be zero or a positive value and it cannot be equal to one.
+ * Defaults to ten.
+ *
+ * \sa ValueAxis3D::segmentCount
+ */
+
+/*!
+ * \qmlproperty bool LogValueAxis3DFormatter::autoSubGrid
+ *
+ * Defines whether sub-grid positions are generated automatically.
+ *
+ * If this property value is set to \c true, the parent axis sub-segment count is ignored
+ * when calculating sub-grid line positions. The sub-grid positions are generated automatically
+ * according to the \l base property value.
+ * The number of sub-grid lines is set to the base value minus one, rounded down.
+ * This property is ignored when the base value is zero.
+ * Defaults to \c true.
+ *
+ * \sa base, ValueAxis3D::subSegmentCount
+ */
+
+/*!
+ * \qmlproperty bool LogValueAxis3DFormatter::showEdgeLabels
+ *
+ * Defines whether the first and last label on the axis are visible.
+ *
+ * When the \l base property value is non-zero, the whole axis range is often
+ * not equally divided into
+ * segments. The first and last segments are often smaller than the other segments.
+ * In extreme cases, this can lead to overlapping labels on the first and last two grid lines.
+ * By setting this property to \c false, you can suppress showing the minimum and maximum labels
+ * for the axis in cases where the segments do not exactly fit the axis.
+ * Defaults to \c true.
+ *
+ * \sa base, AbstractAxis3D::labels
+ */
+
+/*!
+ * \internal
+ */
+QLogValue3DAxisFormatter::QLogValue3DAxisFormatter(QLogValue3DAxisFormatterPrivate *d,
+ QObject *parent) :
+ QValue3DAxisFormatter(d, parent)
+{
+ setAllowNegatives(false);
+ setAllowZero(false);
+}
+
+/*!
+ * Constructs a new logarithmic value 3D axis formatter with the optional
+ * parent \a parent.
+ */
+QLogValue3DAxisFormatter::QLogValue3DAxisFormatter(QObject *parent) :
+ QValue3DAxisFormatter(new QLogValue3DAxisFormatterPrivate(this), parent)
+{
+ setAllowNegatives(false);
+ setAllowZero(false);
+}
+
+/*!
+ * Deletes the logarithmic value 3D axis formatter.
+ */
+QLogValue3DAxisFormatter::~QLogValue3DAxisFormatter()
+{
+}
+
+/*!
+ * \property QLogValue3DAxisFormatter::base
+ *
+ * \brief The base of the logarithm used to map axis values.
+ *
+ * If the base is non-zero, the parent axis
+ * segment count will be ignored when the grid line and label positions are calculated.
+ * If you want the range to be divided into equal segments like a normal value axis, set this
+ * property value to zero.
+ *
+ * The base has to be zero or a positive value and it cannot be equal to one.
+ * Defaults to ten.
+ *
+ * \sa QValue3DAxis::segmentCount
+ */
+void QLogValue3DAxisFormatter::setBase(qreal base)
+{
+ if (base < 0.0f || base == 1.0f) {
+ qWarning() << "Warning: The logarithm base must be greater than 0 and not equal to 1,"
+ << "attempted:" << base;
+ return;
+ }
+ if (dptr()->m_base != base) {
+ dptr()->m_base = base;
+ markDirty(true);
+ emit baseChanged(base);
+ }
+}
+
+qreal QLogValue3DAxisFormatter::base() const
+{
+ return dptrc()->m_base;
+}
+
+/*!
+ * \property QLogValue3DAxisFormatter::autoSubGrid
+ *
+ * \brief Whether sub-grid positions are generated automatically.
+ *
+ * If this property value is set to \c true, the parent axis sub-segment count is ignored
+ * when calculating sub-grid line positions. The sub-grid positions are generated automatically
+ * according to the \l base property value. The number of sub-grid lines is set
+ * to the base value minus one, rounded down. This property is ignored when the
+ * base value is zero.
+ * Defaults to \c true.
+ *
+ * \sa base, QValue3DAxis::subSegmentCount
+ */
+void QLogValue3DAxisFormatter::setAutoSubGrid(bool enabled)
+{
+ if (dptr()->m_autoSubGrid != enabled) {
+ dptr()->m_autoSubGrid = enabled;
+ markDirty(false);
+ emit autoSubGridChanged(enabled);
+ }
+}
+
+bool QLogValue3DAxisFormatter::autoSubGrid() const
+{
+ return dptrc()->m_autoSubGrid;
+}
+
+/*!
+ * \property QLogValue3DAxisFormatter::showEdgeLabels
+ *
+ * \brief Whether the first and last label on the axis are visible.
+ *
+ * When the \l base property value is non-zero, the whole axis range is often
+ * not equally divided into
+ * segments. The first and last segments are often smaller than the other segments.
+ * In extreme cases, this can lead to overlapping labels on the first and last two grid lines.
+ * By setting this property to \c false, you can suppress showing the minimum and maximum labels
+ * for the axis in cases where the segments do not exactly fit the axis.
+ * Defaults to \c true.
+ *
+ * \sa base, QAbstract3DAxis::labels
+ */
+void QLogValue3DAxisFormatter::setShowEdgeLabels(bool enabled)
+{
+ if (dptr()->m_showEdgeLabels != enabled) {
+ dptr()->m_showEdgeLabels = enabled;
+ markDirty(true);
+ emit showEdgeLabelsChanged(enabled);
+ }
+}
+
+bool QLogValue3DAxisFormatter::showEdgeLabels() const
+{
+ return dptrc()->m_showEdgeLabels;
+}
+
+/*!
+ * \internal
+ */
+QValue3DAxisFormatter *QLogValue3DAxisFormatter::createNewInstance() const
+{
+ return new QLogValue3DAxisFormatter();
+}
+
+/*!
+ * \internal
+ */
+void QLogValue3DAxisFormatter::recalculate()
+{
+ dptr()->recalculate();
+}
+
+/*!
+ * \internal
+ */
+float QLogValue3DAxisFormatter::positionAt(float value) const
+{
+ return dptrc()->positionAt(value);
+}
+
+/*!
+ * \internal
+ */
+float QLogValue3DAxisFormatter::valueAt(float position) const
+{
+ return dptrc()->valueAt(position);
+}
+
+/*!
+ * \internal
+ */
+void QLogValue3DAxisFormatter::populateCopy(QValue3DAxisFormatter &copy) const
+{
+ QValue3DAxisFormatter::populateCopy(copy);
+ dptrc()->populateCopy(copy);
+}
+
+/*!
+ * \internal
+ */
+QLogValue3DAxisFormatterPrivate *QLogValue3DAxisFormatter::dptr()
+{
+ return static_cast<QLogValue3DAxisFormatterPrivate *>(d_ptr.data());
+}
+
+/*!
+ * \internal
+ */
+const QLogValue3DAxisFormatterPrivate *QLogValue3DAxisFormatter::dptrc() const
+{
+ return static_cast<const QLogValue3DAxisFormatterPrivate *>(d_ptr.data());
+}
+
+// QLogValue3DAxisFormatterPrivate
+QLogValue3DAxisFormatterPrivate::QLogValue3DAxisFormatterPrivate(QLogValue3DAxisFormatter *q)
+ : QValue3DAxisFormatterPrivate(q),
+ m_base(10.0),
+ m_logMin(0.0),
+ m_logMax(0.0),
+ m_logRangeNormalizer(0.0),
+ m_autoSubGrid(true),
+ m_showEdgeLabels(true),
+ m_evenMinSegment(true),
+ m_evenMaxSegment(true)
+{
+}
+
+QLogValue3DAxisFormatterPrivate::~QLogValue3DAxisFormatterPrivate()
+{
+}
+
+void QLogValue3DAxisFormatterPrivate::recalculate()
+{
+ // When doing position/value mappings, base doesn't matter, so just use natural logarithm
+ m_logMin = qLn(qreal(m_min));
+ m_logMax = qLn(qreal(m_max));
+ m_logRangeNormalizer = m_logMax - m_logMin;
+
+ int subGridCount = m_axis->subSegmentCount() - 1;
+ int segmentCount = m_axis->segmentCount();
+ QString labelFormat = m_axis->labelFormat();
+ qreal segmentStep;
+ if (m_base > 0.0) {
+ // Update parent axis segment counts
+ qreal logMin = qLn(qreal(m_min)) / qLn(m_base);
+ qreal logMax = qLn(qreal(m_max)) / qLn(m_base);
+ qreal logRangeNormalizer = logMax - logMin;
+
+ qreal minDiff = qCeil(logMin) - logMin;
+ qreal maxDiff = logMax - qFloor(logMax);
+
+ m_evenMinSegment = qFuzzyCompare(0.0, minDiff);
+ m_evenMaxSegment = qFuzzyCompare(0.0, maxDiff);
+
+ segmentCount = qRound(logRangeNormalizer - minDiff - maxDiff);
+
+ if (!m_evenMinSegment)
+ segmentCount++;
+ if (!m_evenMaxSegment)
+ segmentCount++;
+
+ segmentStep = 1.0 / logRangeNormalizer;
+
+ if (m_autoSubGrid) {
+ subGridCount = qCeil(m_base) - 2; // -2 for subgrid because subsegment count is base - 1
+ if (subGridCount < 0)
+ subGridCount = 0;
+ }
+
+ m_gridPositions.resize(segmentCount + 1);
+ m_subGridPositions.resize(segmentCount * subGridCount);
+ m_labelPositions.resize(segmentCount + 1);
+ m_labelStrings.clear();
+ m_labelStrings.reserve(segmentCount + 1);
+
+ // Calculate segment positions
+ int index = 0;
+ if (!m_evenMinSegment) {
+ m_gridPositions[0] = 0.0f;
+ m_labelPositions[0] = 0.0f;
+ if (m_showEdgeLabels)
+ m_labelStrings << qptr()->stringForValue(qreal(m_min), labelFormat);
+ else
+ m_labelStrings << QString();
+ index++;
+ }
+ for (int i = 0; i < segmentCount; i++) {
+ float gridValue = float((minDiff + qreal(i)) / qreal(logRangeNormalizer));
+ m_gridPositions[index] = gridValue;
+ m_labelPositions[index] = gridValue;
+ m_labelStrings << qptr()->stringForValue(qPow(m_base, minDiff + qreal(i) + logMin),
+ labelFormat);
+ index++;
+ }
+ // Ensure max value doesn't suffer from any rounding errors
+ m_gridPositions[segmentCount] = 1.0f;
+ m_labelPositions[segmentCount] = 1.0f;
+ QString finalLabel;
+ if (m_showEdgeLabels || m_evenMaxSegment)
+ finalLabel = qptr()->stringForValue(qreal(m_max), labelFormat);
+
+ if (m_labelStrings.size() > segmentCount)
+ m_labelStrings.replace(segmentCount, finalLabel);
+ else
+ m_labelStrings << finalLabel;
+ } else {
+ // Grid lines and label positions are the same as the parent class, so call parent impl
+ // first to populate those
+ QValue3DAxisFormatterPrivate::doRecalculate();
+
+ // Label string list needs to be repopulated
+ segmentStep = 1.0 / qreal(segmentCount);
+
+ m_labelStrings << qptr()->stringForValue(qreal(m_min), labelFormat);
+ for (int i = 1; i < m_labelPositions.size() - 1; i++)
+ m_labelStrings[i] = qptr()->stringForValue(qExp(segmentStep * qreal(i)
+ * m_logRangeNormalizer + m_logMin),
+ labelFormat);
+ m_labelStrings << qptr()->stringForValue(qreal(m_max), labelFormat);
+
+ m_evenMaxSegment = true;
+ m_evenMinSegment = true;
+ }
+
+ // Subgrid line positions are logarithmically spaced
+ if (subGridCount > 0) {
+ float oneSegmentRange = valueAt(float(segmentStep)) - m_min;
+ float subSegmentStep = oneSegmentRange / float(subGridCount + 1);
+
+ // Since the logarithm has the same curvature across whole axis range, we can just calculate
+ // subgrid positions for the first segment and replicate them to other segments.
+ QList<float> actualSubSegmentSteps(subGridCount);
+
+ for (int i = 0; i < subGridCount; i++) {
+ float currentSubPosition = positionAt(m_min + ((i + 1) * subSegmentStep));
+ actualSubSegmentSteps[i] = currentSubPosition;
+ }
+
+ float firstPartialSegmentAdjustment = float(segmentStep) - m_gridPositions.at(1);
+ for (int i = 0; i < segmentCount; i++) {
+ for (int j = 0; j < subGridCount; j++) {
+ float position = m_gridPositions.at(i) + actualSubSegmentSteps.at(j);
+ if (!m_evenMinSegment && i == 0)
+ position -= firstPartialSegmentAdjustment;
+ if (position > 1.0f)
+ position = 1.0f;
+ if (position < 0.0f)
+ position = 0.0f;
+ m_subGridPositions[i * subGridCount + j] = position;
+ }
+ }
+ }
+}
+
+void QLogValue3DAxisFormatterPrivate::populateCopy(QValue3DAxisFormatter &copy) const
+{
+ QLogValue3DAxisFormatter *logFormatter = static_cast<QLogValue3DAxisFormatter *>(&copy);
+ QLogValue3DAxisFormatterPrivate *priv = logFormatter->dptr();
+
+ priv->m_base = m_base;
+ priv->m_logMin = m_logMin;
+ priv->m_logMax = m_logMax;
+ priv->m_logRangeNormalizer = m_logRangeNormalizer;
+}
+
+float QLogValue3DAxisFormatterPrivate::positionAt(float value) const
+{
+ qreal logValue = qLn(qreal(value));
+ float retval = float((logValue - m_logMin) / m_logRangeNormalizer);
+
+ return retval;
+}
+
+float QLogValue3DAxisFormatterPrivate::valueAt(float position) const
+{
+ qreal logValue = (qreal(position) * m_logRangeNormalizer) + m_logMin;
+ return float(qExp(logValue));
+}
+
+QLogValue3DAxisFormatter *QLogValue3DAxisFormatterPrivate::qptr()
+{
+ return static_cast<QLogValue3DAxisFormatter *>(q_ptr);
+}
+
+QT_END_NAMESPACE
diff --git a/src/graphs/axis/qlogvalue3daxisformatter.h b/src/graphs/axis/qlogvalue3daxisformatter.h
new file mode 100644
index 0000000..1724cc0
--- /dev/null
+++ b/src/graphs/axis/qlogvalue3daxisformatter.h
@@ -0,0 +1,57 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QLOGVALUE3DAXISFORMATTER_H
+#define QLOGVALUE3DAXISFORMATTER_H
+
+#include <QtGraphs/qvalue3daxisformatter.h>
+
+QT_BEGIN_NAMESPACE
+
+class QLogValue3DAxisFormatterPrivate;
+
+class Q_GRAPHS_EXPORT QLogValue3DAxisFormatter : public QValue3DAxisFormatter
+{
+ Q_OBJECT
+
+ Q_PROPERTY(qreal base READ base WRITE setBase NOTIFY baseChanged)
+ Q_PROPERTY(bool autoSubGrid READ autoSubGrid WRITE setAutoSubGrid NOTIFY autoSubGridChanged)
+ Q_PROPERTY(bool showEdgeLabels READ showEdgeLabels WRITE setShowEdgeLabels NOTIFY showEdgeLabelsChanged)
+
+protected:
+ explicit QLogValue3DAxisFormatter(QLogValue3DAxisFormatterPrivate *d, QObject *parent = nullptr);
+public:
+ explicit QLogValue3DAxisFormatter(QObject *parent = nullptr);
+ virtual ~QLogValue3DAxisFormatter();
+
+ void setBase(qreal base);
+ qreal base() const;
+ void setAutoSubGrid(bool enabled);
+ bool autoSubGrid() const;
+ void setShowEdgeLabels(bool enabled);
+ bool showEdgeLabels() const;
+
+Q_SIGNALS:
+ void baseChanged(qreal base);
+ void autoSubGridChanged(bool enabled);
+ void showEdgeLabelsChanged(bool enabled);
+
+protected:
+ QValue3DAxisFormatter *createNewInstance() const override;
+ void recalculate() override;
+ float positionAt(float value) const override;
+ float valueAt(float position) const override;
+ void populateCopy(QValue3DAxisFormatter &copy) const override;
+
+ QLogValue3DAxisFormatterPrivate *dptr();
+ const QLogValue3DAxisFormatterPrivate *dptrc() const;
+
+private:
+ Q_DISABLE_COPY(QLogValue3DAxisFormatter)
+
+ friend class QLogValue3DAxisFormatterPrivate;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/axis/qlogvalue3daxisformatter_p.h b/src/graphs/axis/qlogvalue3daxisformatter_p.h
new file mode 100644
index 0000000..06c12e5
--- /dev/null
+++ b/src/graphs/axis/qlogvalue3daxisformatter_p.h
@@ -0,0 +1,56 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QtGraphs API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#ifndef QLOGVALUE3DAXISFORMATTER_P_H
+#define QLOGVALUE3DAXISFORMATTER_P_H
+
+#include "graphsglobal_p.h"
+#include "qlogvalue3daxisformatter.h"
+#include "qvalue3daxisformatter_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class QLogValue3DAxisFormatterPrivate : public QValue3DAxisFormatterPrivate
+{
+ Q_OBJECT
+
+public:
+ QLogValue3DAxisFormatterPrivate(QLogValue3DAxisFormatter *q);
+ virtual ~QLogValue3DAxisFormatterPrivate();
+
+ void recalculate();
+ void populateCopy(QValue3DAxisFormatter &copy) const;
+
+ float positionAt(float value) const;
+ float valueAt(float position) const;
+
+protected:
+ QLogValue3DAxisFormatter *qptr();
+
+ qreal m_base;
+ qreal m_logMin;
+ qreal m_logMax;
+ qreal m_logRangeNormalizer;
+ bool m_autoSubGrid;
+ bool m_showEdgeLabels;
+
+private:
+ bool m_evenMinSegment;
+ bool m_evenMaxSegment;
+
+ friend class QLogValue3DAxisFormatter;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/axis/qvalue3daxis.cpp b/src/graphs/axis/qvalue3daxis.cpp
new file mode 100644
index 0000000..c4c7ed0
--- /dev/null
+++ b/src/graphs/axis/qvalue3daxis.cpp
@@ -0,0 +1,386 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qvalue3daxis_p.h"
+#include "qvalue3daxisformatter_p.h"
+#include "abstract3dcontroller_p.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ * \class QValue3DAxis
+ * \inmodule QtGraphs
+ * \brief The QValue3DAxis class manipulates an axis of a graph.
+ *
+ * A value axis can be given a range of values and segment and subsegment
+ * counts to divide the range into.
+ *
+ * Labels are drawn between each segment. Grid lines are drawn between each segment and each
+ * subsegment. \note If visible, there will always be at least two grid lines and labels indicating
+ * the minimum and the maximum values of the range, as there is always at least one segment.
+ */
+
+/*!
+ * \qmltype ValueAxis3D
+ * \inqmlmodule QtGraphs
+ * \ingroup graphs_qml
+ * \instantiates QValue3DAxis
+ * \inherits AbstractAxis3D
+ * \brief Manipulates an axis of a graph.
+ *
+ * This type provides an axis that can be given a range of values and segment and subsegment
+ * counts to divide the range into.
+ */
+
+
+/*!
+ * \qmlproperty int ValueAxis3D::segmentCount
+ *
+ * The number of segments on the axis. This indicates how many labels are drawn. The number
+ * of grid lines to be drawn is calculated with the following formula:
+ * \c {segments * subsegments + 1}.
+ * The preset default is \c 5. The value cannot be below \c 1.
+ */
+
+/*!
+ * \qmlproperty int ValueAxis3D::subSegmentCount
+ *
+ * The number of subsegments inside each segment on the axis. Grid lines are drawn between
+ * each subsegment, in addition to each segment.
+ * The preset default is \c 1. The value cannot be below \c 1.
+ */
+
+/*!
+ * \qmlproperty string ValueAxis3D::labelFormat
+ *
+ * The label format to be used for the labels on this axis.
+ *
+ * The format string supports the following conversion specifiers, length
+ * modifiers, and flags provided by \c printf() in the standard C++ library:
+ * d, i, o, x, X, f, F, e, E, g, G, c.
+ *
+ * If AbstractGraph3D::locale is anything else than \c{"C"}, the supported
+ * specifiers are limited to: d, e, E, f, g, G, and i. Also, only the precision
+ * modifier is supported. The rest of the formatting comes from the default
+ * \l Locale of the application.
+ *
+ * \sa AbstractGraph3D::locale
+ */
+
+/*!
+ * \qmlproperty ValueAxis3DFormatter ValueAxis3D::formatter
+ *
+ * The axis formatter to be used. Any existing formatter is deleted when a new formatter
+ * is set.
+ *
+ */
+
+/*!
+ * \qmlproperty bool ValueAxis3D::reversed
+ *
+ * If \c{true}, the axis will be rendered in reverse. That is, the positions of
+ * the minimum and maximum values are swapped when the graph is rendered. This
+ * property does not affect the actual minimum and maximum values of the axis.
+ */
+
+/*!
+ * Constructs QValue3DAxis with the given \a parent.
+ */
+QValue3DAxis::QValue3DAxis(QObject *parent) :
+ QAbstract3DAxis(new QValue3DAxisPrivate(this), parent)
+{
+ setFormatter(new QValue3DAxisFormatter);
+}
+
+/*!
+ * Destroys QValue3DAxis.
+ */
+QValue3DAxis::~QValue3DAxis()
+{
+}
+
+/*!
+ * \property QValue3DAxis::segmentCount
+ *
+ * \brief The number of segments on the axis.
+ *
+ * This indicates how many labels are drawn. The number
+ * of grid lines to be drawn is calculated with formula: \c {segments * subsegments + 1}.
+ * The preset default is \c 5. The value cannot be below \c 1.
+ *
+ * \sa setSubSegmentCount()
+ */
+void QValue3DAxis::setSegmentCount(int count)
+{
+ if (count <= 0) {
+ qWarning() << "Warning: Illegal segment count automatically adjusted to a legal one:"
+ << count << "-> 1";
+ count = 1;
+ }
+ if (dptr()->m_segmentCount != count) {
+ dptr()->m_segmentCount = count;
+ dptr()->emitLabelsChanged();
+ emit segmentCountChanged(count);
+ }
+}
+
+int QValue3DAxis::segmentCount() const
+{
+ return dptrc()->m_segmentCount;
+}
+
+/*!
+ * \property QValue3DAxis::subSegmentCount
+ *
+ * \brief The number of subsegments inside each segment on the axis.
+ *
+ * Grid lines are drawn between
+ * each subsegment, in addition to each segment.
+ * The preset default is \c 1. The value cannot be below \c 1.
+ *
+ * \sa setSegmentCount()
+ */
+void QValue3DAxis::setSubSegmentCount(int count)
+{
+ if (count <= 0) {
+ qWarning() << "Warning: Illegal subsegment count automatically adjusted to a legal one:"
+ << count << "-> 1";
+ count = 1;
+ }
+ if (dptr()->m_subSegmentCount != count) {
+ dptr()->m_subSegmentCount = count;
+ emit subSegmentCountChanged(count);
+ }
+}
+
+int QValue3DAxis::subSegmentCount() const
+{
+ return dptrc()->m_subSegmentCount;
+}
+
+/*!
+ * \property QValue3DAxis::labelFormat
+ *
+ * \brief The label format to be used for the labels on this axis.
+ *
+ * The format string supports the following conversion specifiers, length
+ * modifiers, and flags provided by \c printf() in the standard C++ library:
+ * d, i, o, x, X, f, F, e, E, g, G, c.
+ *
+ * If QAbstract3DGraph::locale is anything else than \c{"C"}, the supported
+ * specifiers are limited to: d, e, E, f, g, G, and i. Also, only the precision
+ * modifier is supported. The rest of the formatting comes from the default
+ * QLocale of the application.
+ *
+ * Usage example:
+ *
+ * \c {axis->setLabelFormat("%.2f mm");}
+ *
+ * \sa formatter, QAbstract3DGraph::locale
+ */
+void QValue3DAxis::setLabelFormat(const QString &format)
+{
+ if (dptr()->m_labelFormat != format) {
+ dptr()->m_labelFormat = format;
+ dptr()->emitLabelsChanged();
+ emit labelFormatChanged(format);
+ }
+}
+
+QString QValue3DAxis::labelFormat() const
+{
+ return dptrc()->m_labelFormat;
+}
+
+/*!
+ * \property QValue3DAxis::formatter
+ *
+ * \brief The axis formatter to be used.
+ *
+ * Any existing formatter is deleted when a new formatter
+ * is set.
+ */
+void QValue3DAxis::setFormatter(QValue3DAxisFormatter *formatter)
+{
+ Q_ASSERT(formatter);
+
+ if (formatter != dptr()->m_formatter) {
+ delete dptr()->m_formatter;
+ dptr()->m_formatter = formatter;
+ formatter->setParent(this);
+ formatter->d_ptr->setAxis(this);
+// Abstract3DController *controller = qobject_cast<Abstract3DController *>(parent());
+// if (controller)
+// formatter->setLocale(controller->locale());
+ emit formatterChanged(formatter);
+ emit dptr()->formatterDirty();
+ }
+}
+
+QValue3DAxisFormatter *QValue3DAxis::formatter() const
+{
+ return dptrc()->m_formatter;
+}
+
+/*!
+ * \property QValue3DAxis::reversed
+ *
+ * \brief Whether the axis is rendered in reverse.
+ *
+ * If \c{true}, the axis will be rendered in reverse, i.e. the positions of minimum and maximum
+ * values are swapped when the graph is rendered. This property doesn't affect the actual
+ * minimum and maximum values of the axis.
+ */
+void QValue3DAxis::setReversed(bool enable)
+{
+ if (dptr()->m_reversed != enable) {
+ dptr()->m_reversed = enable;
+ emit reversedChanged(enable);
+ }
+}
+
+bool QValue3DAxis::reversed() const
+{
+ return dptrc()->m_reversed;
+}
+
+void QValue3DAxis::recalculate()
+{
+ formatter()->d_ptr->recalculate();
+}
+
+int QValue3DAxis::gridSize()
+{
+ return formatter()->gridPositions().size();
+}
+
+int QValue3DAxis::subGridSize()
+{
+ return formatter()->subGridPositions().size();
+}
+
+float QValue3DAxis::gridPositionAt(int gridLine)
+{
+ return formatter()->gridPositions().at(gridLine);
+}
+
+float QValue3DAxis::subGridPositionAt(int gridLine)
+{
+ return formatter()->subGridPositions().at(gridLine);
+}
+
+float QValue3DAxis::labelPositionAt(int index)
+{
+ return formatter()->labelPositions().at(index);
+}
+
+float QValue3DAxis::positionAt(float x)
+{
+ return formatter()->positionAt(x);
+}
+
+QString QValue3DAxis::stringForValue(float x)
+{
+ return formatter()->stringForValue(x, labelFormat());
+}
+
+/*!
+ * \internal
+ */
+QValue3DAxisPrivate *QValue3DAxis::dptr()
+{
+ return static_cast<QValue3DAxisPrivate *>(d_ptr.data());
+}
+
+/*!
+ * \internal
+ */
+const QValue3DAxisPrivate *QValue3DAxis::dptrc() const
+{
+ return static_cast<const QValue3DAxisPrivate *>(d_ptr.data());
+}
+
+QValue3DAxisPrivate::QValue3DAxisPrivate(QValue3DAxis *q)
+ : QAbstract3DAxisPrivate(q, QAbstract3DAxis::AxisTypeValue),
+ m_segmentCount(5),
+ m_subSegmentCount(1),
+ m_labelFormat(Utils::defaultLabelFormat()),
+ m_labelsDirty(true),
+ m_formatter(0),
+ m_reversed(false)
+{
+}
+
+QValue3DAxisPrivate::~QValue3DAxisPrivate()
+{
+}
+
+void QValue3DAxisPrivate::setRange(float min, float max, bool suppressWarnings)
+{
+ bool dirty = (min != m_min || max != m_max);
+
+ QAbstract3DAxisPrivate::setRange(min, max, suppressWarnings);
+
+ if (dirty)
+ emitLabelsChanged();
+}
+
+void QValue3DAxisPrivate::setMin(float min)
+{
+ bool dirty = (min != m_min);
+
+ QAbstract3DAxisPrivate::setMin(min);
+
+ if (dirty)
+ emitLabelsChanged();
+}
+
+void QValue3DAxisPrivate::setMax(float max)
+{
+ bool dirty = (max != m_max);
+
+ QAbstract3DAxisPrivate::setMax(max);
+
+ if (dirty)
+ emitLabelsChanged();
+}
+
+void QValue3DAxisPrivate::emitLabelsChanged()
+{
+ m_labelsDirty = true;
+ emit q_ptr->labelsChanged();
+}
+
+void QValue3DAxisPrivate::updateLabels()
+{
+ if (!m_labelsDirty)
+ return;
+
+ m_labelsDirty = false;
+
+ m_formatter->d_ptr->recalculate();
+
+ m_labels = m_formatter->labelStrings();
+}
+
+bool QValue3DAxisPrivate::allowZero()
+{
+ return m_formatter->allowZero();
+}
+
+bool QValue3DAxisPrivate::allowNegatives()
+{
+ return m_formatter->allowNegatives();
+}
+
+bool QValue3DAxisPrivate::allowMinMaxSame()
+{
+ return false;
+}
+
+QValue3DAxis *QValue3DAxisPrivate::qptr()
+{
+ return static_cast<QValue3DAxis *>(q_ptr);
+}
+
+QT_END_NAMESPACE
diff --git a/src/graphs/axis/qvalue3daxis.h b/src/graphs/axis/qvalue3daxis.h
new file mode 100644
index 0000000..e10c4e4
--- /dev/null
+++ b/src/graphs/axis/qvalue3daxis.h
@@ -0,0 +1,75 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QVALUE3DAXIS_H
+#define QVALUE3DAXIS_H
+
+#include <QtGraphs/qabstract3daxis.h>
+#include <QtGraphs/qvalue3daxisformatter.h>
+
+QT_BEGIN_NAMESPACE
+
+class QValue3DAxisPrivate;
+
+class Q_GRAPHS_EXPORT QValue3DAxis : public QAbstract3DAxis
+{
+ Q_OBJECT
+ Q_PROPERTY(int segmentCount READ segmentCount WRITE setSegmentCount NOTIFY segmentCountChanged)
+ Q_PROPERTY(int subSegmentCount READ subSegmentCount WRITE setSubSegmentCount NOTIFY subSegmentCountChanged)
+ Q_PROPERTY(QString labelFormat READ labelFormat WRITE setLabelFormat NOTIFY labelFormatChanged)
+ Q_PROPERTY(QValue3DAxisFormatter* formatter READ formatter WRITE setFormatter NOTIFY formatterChanged)
+ Q_PROPERTY(bool reversed READ reversed WRITE setReversed NOTIFY reversedChanged)
+
+public:
+ explicit QValue3DAxis(QObject *parent = nullptr);
+ virtual ~QValue3DAxis();
+
+ void setSegmentCount(int count);
+ int segmentCount() const;
+
+ void setSubSegmentCount(int count);
+ int subSegmentCount() const;
+
+ void setLabelFormat(const QString &format);
+ QString labelFormat() const;
+
+ void setFormatter(QValue3DAxisFormatter *formatter);
+ QValue3DAxisFormatter *formatter() const;
+
+ void setReversed(bool enable);
+ bool reversed() const;
+
+ void recalculate();
+ int gridSize();
+ int subGridSize();
+ float gridPositionAt(int gridLine);
+ float subGridPositionAt(int gridLine);
+ float labelPositionAt(int index);
+ float positionAt(float x);
+ QString stringForValue(float x);
+
+Q_SIGNALS:
+ void segmentCountChanged(int count);
+ void subSegmentCountChanged(int count);
+ void labelFormatChanged(const QString &format);
+ void formatterChanged(QValue3DAxisFormatter *formatter);
+ void reversedChanged(bool enable);
+
+protected:
+ QValue3DAxisPrivate *dptr();
+ const QValue3DAxisPrivate *dptrc() const;
+
+private:
+ Q_DISABLE_COPY(QValue3DAxis)
+ friend class Abstract3DController;
+ friend class Bars3DController;
+ friend class Scatter3DController;
+ friend class Surface3DController;
+ friend class QValue3DAxisFormatterPrivate;
+ friend class QQuickGraphsScatter;
+ friend class QQuickGraphsBars;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/axis/qvalue3daxis_p.h b/src/graphs/axis/qvalue3daxis_p.h
new file mode 100644
index 0000000..5440fcb
--- /dev/null
+++ b/src/graphs/axis/qvalue3daxis_p.h
@@ -0,0 +1,62 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QtGraphs API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#ifndef QVALUE3DAXIS_P_H
+#define QVALUE3DAXIS_P_H
+
+#include "qvalue3daxis.h"
+#include "qabstract3daxis_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class QValue3DAxisPrivate : public QAbstract3DAxisPrivate
+{
+ Q_OBJECT
+
+public:
+ QValue3DAxisPrivate(QValue3DAxis *q);
+ virtual ~QValue3DAxisPrivate();
+
+ void setRange(float min, float max, bool suppressWarnings = false) override;
+ void setMin(float min) override;
+ void setMax (float max) override;
+
+ void emitLabelsChanged();
+
+Q_SIGNALS:
+ void formatterDirty();
+
+protected:
+ void updateLabels() override;
+
+ bool allowZero() override;
+ bool allowNegatives() override;
+ bool allowMinMaxSame() override;
+
+ int m_segmentCount;
+ int m_subSegmentCount;
+ QString m_labelFormat;
+ bool m_labelsDirty;
+ QValue3DAxisFormatter *m_formatter;
+ bool m_reversed;
+
+private:
+ QValue3DAxis *qptr();
+
+ friend class QValue3DAxis;
+ friend class Abstract3DController;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/axis/qvalue3daxisformatter.cpp b/src/graphs/axis/qvalue3daxisformatter.cpp
new file mode 100644
index 0000000..b200286
--- /dev/null
+++ b/src/graphs/axis/qvalue3daxisformatter.cpp
@@ -0,0 +1,443 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qvalue3daxisformatter_p.h"
+#include "qvalue3daxis_p.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ * \class QValue3DAxisFormatter
+ * \inmodule QtGraphs
+ * \brief The QValue3DAxisFormatter class is a base class for value axis
+ * formatters.
+ *
+ * This class provides formatting rules for a linear value 3D axis. Subclass it if you
+ * want to implement custom value axes.
+ *
+ * The base class has no public API beyond constructors and destructors. It is meant to be only
+ * used internally. However, subclasses may implement public properties as needed.
+ *
+ * \sa QValue3DAxis, QLogValue3DAxisFormatter
+ */
+
+/*!
+ * \qmltype ValueAxis3DFormatter
+ * \inqmlmodule QtGraphs
+ * \ingroup graphs_qml
+ * \instantiates QValue3DAxisFormatter
+ * \brief A base type for value axis formatters.
+ *
+ * This type provides formatting rules for a linear value 3D axis.
+ * This type is the default type for ValueAxis3D and thus never needs to be explicitly created.
+ * This type has no public functionality.
+ *
+ * \sa ValueAxis3D
+ */
+
+/*!
+ * \internal
+ */
+QValue3DAxisFormatter::QValue3DAxisFormatter(QValue3DAxisFormatterPrivate *d, QObject *parent) :
+ QObject(parent),
+ d_ptr(d)
+{
+}
+
+/*!
+ * Constructs a new value 3D axis formatter with the optional parent \a parent.
+ */
+QValue3DAxisFormatter::QValue3DAxisFormatter(QObject *parent) :
+ QObject(parent),
+ d_ptr(new QValue3DAxisFormatterPrivate(this))
+{
+}
+
+/*!
+ * Deletes the value 3D axis formatter.
+ */
+QValue3DAxisFormatter::~QValue3DAxisFormatter()
+{
+}
+
+/*!
+ * Allows the parent axis to have negative values if \a allow is \c true.
+ */
+void QValue3DAxisFormatter::setAllowNegatives(bool allow)
+{
+ d_ptr->m_allowNegatives = allow;
+}
+
+/*!
+ * Returns \c true if negative values are valid values for the parent axis.
+ * The default implementation always returns \c true.
+ */
+bool QValue3DAxisFormatter::allowNegatives() const
+{
+ return d_ptr->m_allowNegatives;
+}
+
+/*!
+ * Allows the parent axis to have a zero value if \a allow is \c true.
+ */
+void QValue3DAxisFormatter::setAllowZero(bool allow)
+{
+ d_ptr->m_allowZero = allow;
+}
+
+/*!
+ * Returns \c true if zero is a valid value for the parent axis.
+ * The default implementation always returns \c true.
+ */
+bool QValue3DAxisFormatter::allowZero() const
+{
+ return d_ptr->m_allowZero;
+}
+
+/*!
+ * Creates a new empty value 3D axis formatter. Must be reimplemented in a
+ * subclass.
+ *
+ * Returns the new formatter. The renderer uses this method to cache a copy of
+ * the formatter. The ownership of the new copy is transferred to the caller.
+ */
+QValue3DAxisFormatter *QValue3DAxisFormatter::createNewInstance() const
+{
+ return new QValue3DAxisFormatter();
+}
+
+/*!
+ * Resizes and populates the label and grid line position arrays and the label
+ * strings array, as well as calculates any values needed to map a value to its
+ * position. The parent axis can be accessed from inside this function.
+ *
+ * This method must be reimplemented in a subclass if the default array contents are not suitable.
+ *
+ * See gridPositions(), subGridPositions(), labelPositions(), and labelStrings() methods for
+ * documentation about the arrays that need to be resized and populated.
+ *
+ * \sa gridPositions(), subGridPositions(), labelPositions(), labelStrings(), axis()
+ */
+void QValue3DAxisFormatter::recalculate()
+{
+ d_ptr->doRecalculate();
+}
+
+/*!
+ * Returns the formatted label string using the specified \a value and
+ * \a format.
+ *
+ * Reimplement this method in a subclass to resolve the formatted string for a given \a value
+ * if the default formatting rules specified for QValue3DAxis::labelFormat property are not
+ * sufficient.
+ *
+ * \sa recalculate(), labelStrings(), QValue3DAxis::labelFormat
+ */
+QString QValue3DAxisFormatter::stringForValue(qreal value, const QString &format) const
+{
+ return d_ptr->stringForValue(value, format);
+}
+
+/*!
+ * Returns the normalized position along the axis for the given \a value.
+ * The returned value should be between \c 0.0 (the minimum value) and
+ * \c 1.0 (the maximum value), inclusive, if the value is within the parent
+ * axis range.
+ *
+ * Reimplement this method if the position cannot be resolved by linear
+ * interpolation between the parent axis minimum and maximum values.
+ *
+ * \sa recalculate(), valueAt()
+ */
+float QValue3DAxisFormatter::positionAt(float value) const
+{
+ return d_ptr->positionAt(value);
+}
+
+/*!
+ * Returns the value at the normalized \a position along the axis.
+ * The \a position value should be between \c 0.0 (the minimum value) and
+ * \c 1.0 (the maximum value), inclusive, to obtain values within the parent
+ * axis range.
+ *
+ * Reimplement this method if the value cannot be resolved by linear
+ * interpolation between the parent axis minimum and maximum values.
+ *
+ * \sa recalculate(), positionAt()
+ */
+float QValue3DAxisFormatter::valueAt(float position) const
+{
+ return d_ptr->valueAt(position);
+}
+
+/*!
+ * Copies all the values necessary for resolving positions, values, and strings
+ * with this formatter to the \a copy of the formatter. When reimplementing
+ * this method in a subclass, call the superclass version at some point.
+ * The renderer uses this method to cache a copy of the formatter.
+ *
+ * Returns the new copy. The ownership of the new copy transfers to the caller.
+ */
+void QValue3DAxisFormatter::populateCopy(QValue3DAxisFormatter &copy) const
+{
+ d_ptr->doPopulateCopy(*(copy.d_ptr.data()));
+}
+
+/*!
+ * Marks this formatter dirty, prompting the renderer to make a new copy of its cache on the next
+ * renderer synchronization. This method should be called by a subclass whenever the formatter
+ * is changed in a way that affects the resolved values. Set \a labelsChange to
+ * \c true if the change requires regenerating the parent axis label strings.
+ */
+void QValue3DAxisFormatter::markDirty(bool labelsChange)
+{
+ d_ptr->markDirty(labelsChange);
+}
+
+/*!
+ * Returns the parent axis. The parent axis must only be accessed in the recalculate()
+ * method to maintain thread safety in environments using a threaded renderer.
+ *
+ * \sa recalculate()
+ */
+QValue3DAxis *QValue3DAxisFormatter::axis() const
+{
+ return d_ptr->m_axis;
+}
+
+/*!
+ * Returns a reference to the array of normalized grid line positions.
+ * The default array size is equal to the segment count of the parent axis plus one, but
+ * a subclassed implementation of the recalculate() method may resize the array differently.
+ * The values should be between \c 0.0 (the minimum value) and \c 1.0 (the
+ * maximum value), inclusive.
+ *
+ * \sa QValue3DAxis::segmentCount, recalculate()
+ */
+QList<float> &QValue3DAxisFormatter::gridPositions() const
+{
+ return d_ptr->m_gridPositions;
+}
+
+/*!
+ * Returns a reference to the array of normalized subgrid line positions.
+ * The default array size is equal to the segment count of the parent axis times
+ * the sub-segment count of the parent axis minus one, but a subclassed
+ * implementation of the recalculate() method may resize the array differently.
+ * The values should be between \c 0.0 (the minimum value) and \c 1.0 (the
+ * maximum value), inclusive.
+ *
+ * \sa QValue3DAxis::segmentCount, QValue3DAxis::subSegmentCount, recalculate()
+ */
+QList<float> &QValue3DAxisFormatter::subGridPositions() const
+{
+ return d_ptr->m_subGridPositions;
+}
+
+/*!
+ * Returns a reference to the array of normalized label positions.
+ * The default array size is equal to the segment count of the parent axis plus one, but
+ * a subclassed implementation of the recalculate() method may resize the array
+ * differently. The values should be between \c 0.0 (the minimum value) and
+ * \c 1.0 (the maximum value), inclusive.
+ * By default, the label at the index zero corresponds to the minimum value
+ * of the axis.
+ *
+ * \sa QValue3DAxis::segmentCount, QAbstract3DAxis::labels, recalculate()
+ */
+QList<float> &QValue3DAxisFormatter::labelPositions() const
+{
+ return d_ptr->m_labelPositions;
+}
+
+/*!
+ * Returns a reference to the string list containing formatter label strings.
+ * The array size must be equal to the size of the label positions array, which
+ * the indexes also correspond to.
+ *
+ * \sa labelPositions()
+ */
+QStringList &QValue3DAxisFormatter::labelStrings() const
+{
+ return d_ptr->m_labelStrings;
+}
+
+/*!
+ * Sets the \a locale that this formatter uses.
+ * The graph automatically sets the formatter's locale to a graph's locale whenever the parent axis
+ * is set as an active axis of the graph, the axis formatter is set to an axis attached to
+ * the graph, or the graph's locale changes.
+ *
+ * \sa locale(), QAbstract3DGraph::locale
+ */
+void QValue3DAxisFormatter::setLocale(const QLocale &locale)
+{
+ d_ptr->m_cLocaleInUse = (locale == QLocale::c());
+ d_ptr->m_locale = locale;
+ markDirty(true);
+}
+/*!
+ * Returns the current locale this formatter is using.
+ */
+QLocale QValue3DAxisFormatter::locale() const
+{
+ return d_ptr->m_locale;
+}
+
+// QValue3DAxisFormatterPrivate
+QValue3DAxisFormatterPrivate::QValue3DAxisFormatterPrivate(QValue3DAxisFormatter *q)
+ : QObject(0),
+ q_ptr(q),
+ m_needsRecalculate(true),
+ m_min(0.0f),
+ m_max(0.0f),
+ m_rangeNormalizer(0.0f),
+ m_axis(0),
+ m_preparsedParamType(Utils::ParamTypeUnknown),
+ m_allowNegatives(true),
+ m_allowZero(true),
+ m_formatPrecision(6), // 6 and 'g' are defaults in Qt API for format precision and spec
+ m_formatSpec('g'),
+ m_cLocaleInUse(true)
+{
+}
+
+QValue3DAxisFormatterPrivate::~QValue3DAxisFormatterPrivate()
+{
+}
+
+void QValue3DAxisFormatterPrivate::recalculate()
+{
+ // Only recalculate if we need to and have m_axis pointer. If we do not have
+ // m_axis, either we are not attached to an axis or this is a renderer cache.
+ if (m_axis && m_needsRecalculate) {
+ m_min = m_axis->min();
+ m_max = m_axis->max();
+ m_rangeNormalizer = (m_max - m_min);
+
+ q_ptr->recalculate();
+ m_needsRecalculate = false;
+ }
+}
+
+void QValue3DAxisFormatterPrivate::doRecalculate()
+{
+ int segmentCount = m_axis->segmentCount();
+ int subGridCount = m_axis->subSegmentCount() - 1;
+ QString labelFormat = m_axis->labelFormat();
+
+ m_gridPositions.resize(segmentCount + 1);
+ m_subGridPositions.resize(segmentCount * subGridCount);
+
+ m_labelPositions.resize(segmentCount + 1);
+ m_labelStrings.clear();
+ m_labelStrings.reserve(segmentCount + 1);
+
+ // Use qreals for intermediate calculations for better accuracy on label values
+ qreal segmentStep = 1.0 / qreal(segmentCount);
+ qreal subSegmentStep = 0;
+ if (subGridCount > 0)
+ subSegmentStep = segmentStep / qreal(subGridCount + 1);
+
+ // Calculate positions
+ qreal rangeNormalizer = qreal(m_max - m_min);
+ for (int i = 0; i < segmentCount; i++) {
+ qreal gridValue = segmentStep * qreal(i);
+ m_gridPositions[i] = float(gridValue);
+ m_labelPositions[i] = float(gridValue);
+ m_labelStrings << q_ptr->stringForValue(gridValue * rangeNormalizer + qreal(m_min),
+ labelFormat);
+ if (m_subGridPositions.size()) {
+ for (int j = 0; j < subGridCount; j++)
+ m_subGridPositions[i * subGridCount + j] = gridValue + subSegmentStep * (j + 1);
+ }
+ }
+
+ // Ensure max value doesn't suffer from any rounding errors
+ m_gridPositions[segmentCount] = 1.0f;
+ m_labelPositions[segmentCount] = 1.0f;
+ m_labelStrings << q_ptr->stringForValue(qreal(m_max), labelFormat);
+}
+
+void QValue3DAxisFormatterPrivate::populateCopy(QValue3DAxisFormatter &copy)
+{
+ recalculate();
+ q_ptr->populateCopy(copy);
+}
+
+void QValue3DAxisFormatterPrivate::doPopulateCopy(QValue3DAxisFormatterPrivate &copy)
+{
+ copy.m_min = m_min;
+ copy.m_max = m_max;
+ copy.m_rangeNormalizer = m_rangeNormalizer;
+
+ copy.m_gridPositions = m_gridPositions;
+ copy.m_labelPositions = m_labelPositions;
+ copy.m_subGridPositions = m_subGridPositions;
+}
+
+QString QValue3DAxisFormatterPrivate::stringForValue(qreal value, const QString &format)
+{
+ if (m_previousLabelFormat.compare(format)) {
+ // Format string different than the previous one used, reparse it
+ m_labelFormatArray = format.toUtf8();
+ m_previousLabelFormat = format;
+ m_preparsedParamType = Utils::preParseFormat(format, m_formatPreStr, m_formatPostStr,
+ m_formatPrecision, m_formatSpec);
+ }
+
+ if (m_cLocaleInUse) {
+ return Utils::formatLabelSprintf(m_labelFormatArray, m_preparsedParamType, value);
+ } else {
+ return Utils::formatLabelLocalized(m_preparsedParamType, value, m_locale, m_formatPreStr,
+ m_formatPostStr, m_formatPrecision, m_formatSpec,
+ m_labelFormatArray);
+ }
+}
+
+float QValue3DAxisFormatterPrivate::positionAt(float value) const
+{
+ return ((value - m_min) / m_rangeNormalizer);
+}
+
+float QValue3DAxisFormatterPrivate::valueAt(float position) const
+{
+ return ((position * m_rangeNormalizer) + m_min);
+}
+
+void QValue3DAxisFormatterPrivate::setAxis(QValue3DAxis *axis)
+{
+ Q_ASSERT(axis);
+
+ // These signals are all connected to markDirtyNoLabelChange slot, even though most of them
+ // do require labels to be regenerated. This is because the label regeneration is triggered
+ // elsewhere in these cases.
+ connect(axis, &QValue3DAxis::segmentCountChanged,
+ this, &QValue3DAxisFormatterPrivate::markDirtyNoLabelChange);
+ connect(axis, &QValue3DAxis::subSegmentCountChanged,
+ this, &QValue3DAxisFormatterPrivate::markDirtyNoLabelChange);
+ connect(axis, &QValue3DAxis::labelFormatChanged,
+ this, &QValue3DAxisFormatterPrivate::markDirtyNoLabelChange);
+ connect(axis, &QAbstract3DAxis::rangeChanged,
+ this, &QValue3DAxisFormatterPrivate::markDirtyNoLabelChange);
+
+ m_axis = axis;
+}
+
+void QValue3DAxisFormatterPrivate::markDirty(bool labelsChange)
+{
+ m_needsRecalculate = true;
+ if (m_axis) {
+ if (labelsChange)
+ m_axis->dptr()->emitLabelsChanged();
+ if (m_axis && m_axis->orientation() != QAbstract3DAxis::AxisOrientationNone)
+ emit m_axis->dptr()->formatterDirty();
+ }
+}
+
+void QValue3DAxisFormatterPrivate::markDirtyNoLabelChange()
+{
+ markDirty(false);
+}
+
+QT_END_NAMESPACE
diff --git a/src/graphs/axis/qvalue3daxisformatter.h b/src/graphs/axis/qvalue3daxisformatter.h
new file mode 100644
index 0000000..15544d6
--- /dev/null
+++ b/src/graphs/axis/qvalue3daxisformatter.h
@@ -0,0 +1,75 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QVALUE3DAXISFORMATTER_H
+#define QVALUE3DAXISFORMATTER_H
+
+#include <QtGraphs/qgraphsglobal.h>
+#include <QtCore/QList>
+#include <QtCore/QLocale>
+#include <QtCore/QObject>
+#include <QtCore/QScopedPointer>
+#include <QtCore/QStringList>
+
+QT_BEGIN_NAMESPACE
+
+class QValue3DAxisFormatterPrivate;
+class QValue3DAxis;
+
+class Q_GRAPHS_EXPORT QValue3DAxisFormatter : public QObject
+{
+ Q_OBJECT
+protected:
+ explicit QValue3DAxisFormatter(QValue3DAxisFormatterPrivate *d, QObject *parent = nullptr);
+public:
+ explicit QValue3DAxisFormatter(QObject *parent = nullptr);
+ virtual ~QValue3DAxisFormatter();
+
+protected:
+ void setAllowNegatives(bool allow);
+ bool allowNegatives() const;
+ void setAllowZero(bool allow);
+ bool allowZero() const;
+
+ virtual QValue3DAxisFormatter *createNewInstance() const;
+ virtual void recalculate();
+ virtual QString stringForValue(qreal value, const QString &format) const;
+ virtual float positionAt(float value) const;
+ virtual float valueAt(float position) const;
+ virtual void populateCopy(QValue3DAxisFormatter &copy) const;
+
+ void markDirty(bool labelsChange = false);
+ QValue3DAxis *axis() const;
+
+ QList<float> &gridPositions() const;
+ QList<float> &subGridPositions() const;
+ QList<float> &labelPositions() const;
+ QStringList &labelStrings() const;
+
+ void setLocale(const QLocale &locale);
+ QLocale locale() const;
+
+ QScopedPointer<QValue3DAxisFormatterPrivate> d_ptr;
+
+private:
+ Q_DISABLE_COPY(QValue3DAxisFormatter)
+
+ friend class Abstract3DController;
+ friend class Scatter3DController;
+ friend class SurfaceObject;
+ friend class QValue3DAxisFormatterPrivate;
+ friend class QLogValue3DAxisFormatter;
+ friend class QValue3DAxis;
+ friend class QValue3DAxisPrivate;
+ friend class AxisHelper;
+ friend class QBar3DSeriesPrivate;
+ friend class QScatter3DSeriesPrivate;
+ friend class QSurface3DSeriesPrivate;
+ friend class QQuickGraphsScatter;
+ friend class QQuickGraphsBars;
+ friend class Bars3DController;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/axis/qvalue3daxisformatter_p.h b/src/graphs/axis/qvalue3daxisformatter_p.h
new file mode 100644
index 0000000..dd88bc5
--- /dev/null
+++ b/src/graphs/axis/qvalue3daxisformatter_p.h
@@ -0,0 +1,84 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QtGraphs API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#ifndef QVALUE3DAXISFORMATTER_P_H
+#define QVALUE3DAXISFORMATTER_P_H
+
+#include "graphsglobal_p.h"
+#include "qvalue3daxisformatter.h"
+#include "utils_p.h"
+#include <QtCore/QLocale>
+
+QT_BEGIN_NAMESPACE
+
+class QValue3DAxis;
+
+class QValue3DAxisFormatterPrivate : public QObject
+{
+ Q_OBJECT
+
+public:
+ QValue3DAxisFormatterPrivate(QValue3DAxisFormatter *q);
+ virtual ~QValue3DAxisFormatterPrivate();
+
+ void recalculate();
+ void doRecalculate();
+ void populateCopy(QValue3DAxisFormatter &copy);
+ void doPopulateCopy(QValue3DAxisFormatterPrivate &copy);
+
+ QString stringForValue(qreal value, const QString &format);
+ float positionAt(float value) const;
+ float valueAt(float position) const;
+
+ void setAxis(QValue3DAxis *axis);
+ void markDirty(bool labelsChange);
+
+public Q_SLOTS:
+ void markDirtyNoLabelChange();
+
+protected:
+ QValue3DAxisFormatter *q_ptr;
+
+ bool m_needsRecalculate;
+
+ float m_min;
+ float m_max;
+ float m_rangeNormalizer;
+
+ QList<float> m_gridPositions;
+ QList<float> m_subGridPositions;
+ QList<float> m_labelPositions;
+ QStringList m_labelStrings;
+
+ QValue3DAxis *m_axis;
+
+ QString m_previousLabelFormat;
+ QByteArray m_labelFormatArray;
+ Utils::ParamType m_preparsedParamType;
+
+ bool m_allowNegatives;
+ bool m_allowZero;
+
+ QLocale m_locale;
+ QString m_formatPreStr;
+ QString m_formatPostStr;
+ int m_formatPrecision;
+ char m_formatSpec;
+ bool m_cLocaleInUse;
+
+ friend class QValue3DAxisFormatter;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/configure.cmake b/src/graphs/configure.cmake
new file mode 100644
index 0000000..d18ccb5
--- /dev/null
+++ b/src/graphs/configure.cmake
@@ -0,0 +1,7 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+qt_extra_definition("QT_VERSION_STR" "\"${PROJECT_VERSION}\"" PUBLIC)
+qt_extra_definition("QT_VERSION_MAJOR" ${PROJECT_VERSION_MAJOR} PUBLIC)
+qt_extra_definition("QT_VERSION_MINOR" ${PROJECT_VERSION_MINOR} PUBLIC)
+qt_extra_definition("QT_VERSION_PATCH" ${PROJECT_VERSION_PATCH} PUBLIC)
diff --git a/src/graphs/data/abstractitemmodelhandler.cpp b/src/graphs/data/abstractitemmodelhandler.cpp
new file mode 100644
index 0000000..51c7b3b
--- /dev/null
+++ b/src/graphs/data/abstractitemmodelhandler.cpp
@@ -0,0 +1,214 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "abstractitemmodelhandler_p.h"
+
+QT_BEGIN_NAMESPACE
+
+AbstractItemModelHandler::AbstractItemModelHandler(QObject *parent)
+ : QObject(parent),
+ resolvePending(0),
+ m_fullReset(true)
+{
+ m_resolveTimer.setSingleShot(true);
+ QObject::connect(&m_resolveTimer, &QTimer::timeout,
+ this, &AbstractItemModelHandler::handlePendingResolve);
+}
+
+AbstractItemModelHandler::~AbstractItemModelHandler()
+{
+}
+
+void AbstractItemModelHandler::setItemModel(QAbstractItemModel *itemModel)
+{
+ if (itemModel != m_itemModel.data()) {
+ if (!m_itemModel.isNull())
+ QObject::disconnect(m_itemModel, 0, this, 0);
+
+ m_itemModel = itemModel;
+
+ if (!m_itemModel.isNull()) {
+ QObject::connect(m_itemModel.data(), &QAbstractItemModel::columnsInserted,
+ this, &AbstractItemModelHandler::handleColumnsInserted);
+ QObject::connect(m_itemModel.data(), &QAbstractItemModel::columnsMoved,
+ this, &AbstractItemModelHandler::handleColumnsMoved);
+ QObject::connect(m_itemModel.data(), &QAbstractItemModel::columnsRemoved,
+ this, &AbstractItemModelHandler::handleColumnsRemoved);
+ QObject::connect(m_itemModel.data(), &QAbstractItemModel::dataChanged,
+ this, &AbstractItemModelHandler::handleDataChanged);
+ QObject::connect(m_itemModel.data(), &QAbstractItemModel::layoutChanged,
+ this, &AbstractItemModelHandler::handleLayoutChanged);
+ QObject::connect(m_itemModel.data(), &QAbstractItemModel::modelReset,
+ this, &AbstractItemModelHandler::handleModelReset);
+ QObject::connect(m_itemModel.data(), &QAbstractItemModel::rowsInserted,
+ this, &AbstractItemModelHandler::handleRowsInserted);
+ QObject::connect(m_itemModel.data(), &QAbstractItemModel::rowsMoved,
+ this, &AbstractItemModelHandler::handleRowsMoved);
+ QObject::connect(m_itemModel.data(), &QAbstractItemModel::rowsRemoved,
+ this, &AbstractItemModelHandler::handleRowsRemoved);
+ }
+ if (!m_resolveTimer.isActive())
+ m_resolveTimer.start(0);
+
+ emit itemModelChanged(itemModel);
+ }
+}
+
+QAbstractItemModel *AbstractItemModelHandler::itemModel() const
+{
+ return m_itemModel.data();
+}
+
+void AbstractItemModelHandler::handleColumnsInserted(const QModelIndex &parent,
+ int start, int end)
+{
+ Q_UNUSED(parent);
+ Q_UNUSED(start);
+ Q_UNUSED(end);
+
+ // Manipulating columns changes all rows in proxies that map rows/columns directly,
+ // and its effects are not clearly defined in others -> always do full reset.
+ if (!m_resolveTimer.isActive()) {
+ m_fullReset = true;
+ m_resolveTimer.start(0);
+ }
+}
+
+void AbstractItemModelHandler::handleColumnsMoved(const QModelIndex &sourceParent,
+ int sourceStart,
+ int sourceEnd,
+ const QModelIndex &destinationParent,
+ int destinationColumn)
+{
+ Q_UNUSED(sourceParent);
+ Q_UNUSED(sourceStart);
+ Q_UNUSED(sourceEnd);
+ Q_UNUSED(destinationParent);
+ Q_UNUSED(destinationColumn);
+
+ // Manipulating columns changes all rows in proxies that map rows/columns directly,
+ // and its effects are not clearly defined in others -> always do full reset.
+ if (!m_resolveTimer.isActive()) {
+ m_fullReset = true;
+ m_resolveTimer.start(0);
+ }
+}
+
+void AbstractItemModelHandler::handleColumnsRemoved(const QModelIndex &parent,
+ int start, int end)
+{
+ Q_UNUSED(parent);
+ Q_UNUSED(start);
+ Q_UNUSED(end);
+
+ // Manipulating columns changes all rows in proxies that map rows/columns directly,
+ // and its effects are not clearly defined in others -> always do full reset.
+ if (!m_resolveTimer.isActive()) {
+ m_fullReset = true;
+ m_resolveTimer.start(0);
+ }
+}
+
+void AbstractItemModelHandler::handleDataChanged(const QModelIndex &topLeft,
+ const QModelIndex &bottomRight,
+ const QList<int> &roles)
+{
+ Q_UNUSED(topLeft);
+ Q_UNUSED(bottomRight);
+ Q_UNUSED(roles);
+
+ // Default handling for dataChanged is to do full reset, as it cannot be optimized
+ // in a general case, where we do not know which row/column/index the item model item
+ // actually ended up to in the proxy.
+ if (!m_resolveTimer.isActive()) {
+ m_fullReset = true;
+ m_resolveTimer.start(0);
+ }
+}
+
+void AbstractItemModelHandler::handleLayoutChanged(const QList<QPersistentModelIndex> &parents,
+ QAbstractItemModel::LayoutChangeHint hint)
+{
+ Q_UNUSED(parents);
+ Q_UNUSED(hint);
+
+ // Resolve entire model if layout changes
+ if (!m_resolveTimer.isActive()) {
+ m_fullReset = true;
+ m_resolveTimer.start(0);
+ }
+}
+
+void AbstractItemModelHandler::handleModelReset()
+{
+ // Data cleared, reset array
+ if (!m_resolveTimer.isActive()) {
+ m_fullReset = true;
+ m_resolveTimer.start(0);
+ }
+}
+
+void AbstractItemModelHandler::handleRowsInserted(const QModelIndex &parent, int start, int end)
+{
+ Q_UNUSED(parent);
+ Q_UNUSED(start);
+ Q_UNUSED(end);
+
+ // Default handling for rowsInserted is to do full reset, as it cannot be optimized
+ // in a general case, where we do not know which row/column/index the item model item
+ // actually ended up to in the proxy.
+ if (!m_resolveTimer.isActive()) {
+ m_fullReset = true;
+ m_resolveTimer.start(0);
+ }
+}
+
+void AbstractItemModelHandler::handleRowsMoved(const QModelIndex &sourceParent,
+ int sourceStart,
+ int sourceEnd,
+ const QModelIndex &destinationParent,
+ int destinationRow)
+{
+ Q_UNUSED(sourceParent);
+ Q_UNUSED(sourceStart);
+ Q_UNUSED(sourceEnd);
+ Q_UNUSED(destinationParent);
+ Q_UNUSED(destinationRow);
+
+ // Default handling for rowsMoved is to do full reset, as it cannot be optimized
+ // in a general case, where we do not know which row/column/index the item model item
+ // actually ended up to in the proxy.
+ if (!m_resolveTimer.isActive()) {
+ m_fullReset = true;
+ m_resolveTimer.start(0);
+ }
+}
+
+void AbstractItemModelHandler::handleRowsRemoved(const QModelIndex &parent, int start, int end)
+{
+ Q_UNUSED(parent);
+ Q_UNUSED(start);
+ Q_UNUSED(end);
+
+ // Default handling for rowsRemoved is to do full reset, as it cannot be optimized
+ // in a general case, where we do not know which row/column/index the item model item
+ // actually ended up to in the proxy.
+ if (!m_resolveTimer.isActive()) {
+ m_fullReset = true;
+ m_resolveTimer.start(0);
+ }
+}
+
+void AbstractItemModelHandler::handleMappingChanged()
+{
+ if (!m_resolveTimer.isActive())
+ m_resolveTimer.start(0);
+}
+
+void AbstractItemModelHandler::handlePendingResolve()
+{
+ resolveModel();
+ m_fullReset = false;
+}
+
+QT_END_NAMESPACE
diff --git a/src/graphs/data/abstractitemmodelhandler_p.h b/src/graphs/data/abstractitemmodelhandler_p.h
new file mode 100644
index 0000000..66fe08e
--- /dev/null
+++ b/src/graphs/data/abstractitemmodelhandler_p.h
@@ -0,0 +1,70 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QtGraphs API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#ifndef ABSTRACTITEMMODELHANDLER_P_H
+#define ABSTRACTITEMMODELHANDLER_P_H
+
+#include "graphsglobal_p.h"
+#include <QtCore/QAbstractItemModel>
+#include <QtCore/QPointer>
+#include <QtCore/QTimer>
+
+QT_BEGIN_NAMESPACE
+
+class AbstractItemModelHandler : public QObject
+{
+ Q_OBJECT
+public:
+ AbstractItemModelHandler(QObject *parent = 0);
+ virtual ~AbstractItemModelHandler();
+
+ virtual void setItemModel(QAbstractItemModel *itemModel);
+ virtual QAbstractItemModel *itemModel() const;
+
+public Q_SLOTS:
+ virtual void handleColumnsInserted(const QModelIndex &parent, int start, int end);
+ virtual void handleColumnsMoved(const QModelIndex &sourceParent, int sourceStart,
+ int sourceEnd, const QModelIndex &destinationParent,
+ int destinationColumn);
+ virtual void handleColumnsRemoved(const QModelIndex &parent, int start, int end);
+ virtual void handleDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight,
+ const QList<int> &roles = QList<int>());
+ virtual void handleLayoutChanged(const QList<QPersistentModelIndex> &parents = QList<QPersistentModelIndex>(),
+ QAbstractItemModel::LayoutChangeHint hint = QAbstractItemModel::NoLayoutChangeHint);
+ virtual void handleModelReset();
+ virtual void handleRowsInserted(const QModelIndex &parent, int start, int end);
+ virtual void handleRowsMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd,
+ const QModelIndex &destinationParent, int destinationRow);
+ virtual void handleRowsRemoved(const QModelIndex &parent, int start, int end);
+
+ virtual void handleMappingChanged();
+ virtual void handlePendingResolve();
+
+Q_SIGNALS:
+ void itemModelChanged(const QAbstractItemModel *itemModel);
+
+protected:
+ virtual void resolveModel() = 0;
+
+ QPointer<QAbstractItemModel> m_itemModel; // Not owned
+ bool resolvePending;
+ QTimer m_resolveTimer;
+ bool m_fullReset;
+
+private:
+ Q_DISABLE_COPY(AbstractItemModelHandler)
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/data/baritemmodelhandler.cpp b/src/graphs/data/baritemmodelhandler.cpp
new file mode 100644
index 0000000..49e7aec
--- /dev/null
+++ b/src/graphs/data/baritemmodelhandler.cpp
@@ -0,0 +1,276 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "baritemmodelhandler_p.h"
+
+QT_BEGIN_NAMESPACE
+
+static const int noRoleIndex = -1;
+
+BarItemModelHandler::BarItemModelHandler(QItemModelBarDataProxy *proxy, QObject *parent)
+ : AbstractItemModelHandler(parent),
+ m_proxy(proxy),
+ m_proxyArray(0),
+ m_columnCount(0),
+ m_valueRole(noRoleIndex),
+ m_rotationRole(noRoleIndex),
+ m_haveValuePattern(false),
+ m_haveRotationPattern(false)
+{
+}
+
+BarItemModelHandler::~BarItemModelHandler()
+{
+}
+
+void BarItemModelHandler::handleDataChanged(const QModelIndex &topLeft,
+ const QModelIndex &bottomRight, const QList<int> &roles)
+{
+ // Do nothing if full reset already pending
+ if (!m_fullReset) {
+ if (!m_proxy->useModelCategories()) {
+ // If the data model doesn't directly map rows and columns, we cannot optimize
+ AbstractItemModelHandler::handleDataChanged(topLeft, bottomRight, roles);
+ } else {
+ int startRow = qMin(topLeft.row(), bottomRight.row());
+ int endRow = qMax(topLeft.row(), bottomRight.row());
+ int startCol = qMin(topLeft.column(), bottomRight.column());
+ int endCol = qMax(topLeft.column(), bottomRight.column());
+
+ for (int i = startRow; i <= endRow; i++) {
+ for (int j = startCol; j <= endCol; j++) {
+ QModelIndex index = m_itemModel->index(i, j);
+ QBarDataItem item;
+ QVariant valueVar = index.data(m_valueRole);
+ float value;
+ if (m_haveValuePattern)
+ value = valueVar.toString().replace(m_valuePattern, m_valueReplace).toFloat();
+ else
+ value = valueVar.toFloat();
+ item.setValue(value);
+ if (m_rotationRole != noRoleIndex) {
+ QVariant rotationVar = index.data(m_rotationRole);
+ float rotation;
+ if (m_haveRotationPattern) {
+ rotation = rotationVar.toString().replace(m_rotationPattern,
+ m_rotationReplace).toFloat();
+ } else {
+ rotation = rotationVar.toFloat();
+ }
+ item.setRotation(rotation);
+ }
+ m_proxy->setItem(i, j, item);
+ }
+ }
+ }
+ }
+}
+
+// Resolve entire item model into QBarDataArray.
+void BarItemModelHandler::resolveModel()
+{
+ if (m_itemModel.isNull()) {
+ m_proxy->resetArray(0);
+ return;
+ }
+
+ if (!m_proxy->useModelCategories()
+ && (m_proxy->rowRole().isEmpty() || m_proxy->columnRole().isEmpty())) {
+ m_proxy->resetArray(0);
+ return;
+ }
+
+ // Value and rotation patterns can be reused on single item changes,
+ // so store them to member variables.
+ QRegularExpression rowPattern(m_proxy->rowRolePattern());
+ QRegularExpression colPattern(m_proxy->columnRolePattern());
+ m_valuePattern = m_proxy->valueRolePattern();
+ m_rotationPattern = m_proxy->rotationRolePattern();
+ QString rowReplace = m_proxy->rowRoleReplace();
+ QString colReplace = m_proxy->columnRoleReplace();
+ m_valueReplace = m_proxy->valueRoleReplace();
+ m_rotationReplace = m_proxy->rotationRoleReplace();
+ bool haveRowPattern = !rowPattern.namedCaptureGroups().isEmpty() && rowPattern.isValid();
+ bool haveColPattern = !colPattern.namedCaptureGroups().isEmpty() && colPattern.isValid();
+ m_haveValuePattern = !m_valuePattern.namedCaptureGroups().isEmpty() && m_valuePattern.isValid();
+ m_haveRotationPattern = !m_rotationPattern.namedCaptureGroups().isEmpty() && m_rotationPattern.isValid();
+
+ QStringList rowLabels;
+ QStringList columnLabels;
+
+ QHash<int, QByteArray> roleHash = m_itemModel->roleNames();
+
+ // Default value role to display role if no mapping
+ m_valueRole = roleHash.key(m_proxy->valueRole().toLatin1(), Qt::DisplayRole);
+ m_rotationRole = roleHash.key(m_proxy->rotationRole().toLatin1(), noRoleIndex);
+ int rowCount = m_itemModel->rowCount();
+ int columnCount = m_itemModel->columnCount();
+
+ if (m_proxy->useModelCategories()) {
+ // If dimensions have changed, recreate the array
+ if (m_proxyArray != m_proxy->array() || columnCount != m_columnCount
+ || rowCount != m_proxyArray->size()) {
+ m_proxyArray = new QBarDataArray;
+ m_proxyArray->reserve(rowCount);
+ for (int i = 0; i < rowCount; i++)
+ m_proxyArray->append(new QBarDataRow(columnCount));
+ }
+ for (int i = 0; i < rowCount; i++) {
+ QBarDataRow &newProxyRow = *m_proxyArray->at(i);
+ for (int j = 0; j < columnCount; j++) {
+ QModelIndex index = m_itemModel->index(i, j);
+ QVariant valueVar = index.data(m_valueRole);
+ float value;
+ if (m_haveValuePattern)
+ value = valueVar.toString().replace(m_valuePattern, m_valueReplace).toFloat();
+ else
+ value = valueVar.toFloat();
+ newProxyRow[j].setValue(value);
+ if (m_rotationRole != noRoleIndex) {
+ QVariant rotationVar = index.data(m_rotationRole);
+ float rotation;
+ if (m_haveRotationPattern) {
+ rotation = rotationVar.toString().replace(m_rotationPattern,
+ m_rotationReplace).toFloat();
+ } else {
+ rotation = rotationVar.toFloat();
+ }
+ newProxyRow[j].setRotation(rotation);
+ }
+ }
+ }
+ // Generate labels from headers if using model rows/columns
+ for (int i = 0; i < rowCount; i++)
+ rowLabels << m_itemModel->headerData(i, Qt::Vertical).toString();
+ for (int i = 0; i < columnCount; i++)
+ columnLabels << m_itemModel->headerData(i, Qt::Horizontal).toString();
+ m_columnCount = columnCount;
+ } else {
+ int rowRole = roleHash.key(m_proxy->rowRole().toLatin1());
+ int columnRole = roleHash.key(m_proxy->columnRole().toLatin1());
+
+ bool generateRows = m_proxy->autoRowCategories();
+ bool generateColumns = m_proxy->autoColumnCategories();
+ QStringList rowList;
+ QStringList columnList;
+ // For detecting duplicates in categories generation, using QHashes should be faster than
+ // simple QStringList::contains() check.
+ QHash<QString, bool> rowListHash;
+ QHash<QString, bool> columnListHash;
+
+ // Sort values into rows and columns
+ typedef QHash<QString, float> ColumnValueMap;
+ QHash<QString, ColumnValueMap> itemValueMap;
+ QHash<QString, ColumnValueMap> itemRotationMap;
+
+ bool cumulative = m_proxy->multiMatchBehavior() == QItemModelBarDataProxy::MMBAverage
+ || m_proxy->multiMatchBehavior() == QItemModelBarDataProxy::MMBCumulative;
+ bool countMatches = m_proxy->multiMatchBehavior() == QItemModelBarDataProxy::MMBAverage;
+ bool takeFirst = m_proxy->multiMatchBehavior() == QItemModelBarDataProxy::MMBFirst;
+ QHash<QString, QHash<QString, int> > *matchCountMap = 0;
+ if (countMatches)
+ matchCountMap = new QHash<QString, QHash<QString, int> >;
+
+ for (int i = 0; i < rowCount; i++) {
+ for (int j = 0; j < columnCount; j++) {
+ QModelIndex index = m_itemModel->index(i, j);
+ QString rowRoleStr = index.data(rowRole).toString();
+ if (haveRowPattern)
+ rowRoleStr.replace(rowPattern, rowReplace);
+ QString columnRoleStr = index.data(columnRole).toString();
+ if (haveColPattern)
+ columnRoleStr.replace(colPattern, colReplace);
+ QVariant valueVar = index.data(m_valueRole);
+ float value;
+ if (m_haveValuePattern)
+ value = valueVar.toString().replace(m_valuePattern, m_valueReplace).toFloat();
+ else
+ value = valueVar.toFloat();
+ if (countMatches)
+ (*matchCountMap)[rowRoleStr][columnRoleStr]++;
+
+ if (cumulative) {
+ itemValueMap[rowRoleStr][columnRoleStr] += value;
+ } else {
+ if (takeFirst && itemValueMap.contains(rowRoleStr)) {
+ if (itemValueMap.value(rowRoleStr).contains(columnRoleStr))
+ continue; // We already have a value for this row/column combo
+ }
+ itemValueMap[rowRoleStr][columnRoleStr] = value;
+ }
+
+ if (m_rotationRole != noRoleIndex) {
+ QVariant rotationVar = index.data(m_rotationRole);
+ float rotation;
+ if (m_haveRotationPattern) {
+ rotation = rotationVar.toString().replace(m_rotationPattern,
+ m_rotationReplace).toFloat();
+ } else {
+ rotation = rotationVar.toFloat();
+ }
+ if (cumulative) {
+ itemRotationMap[rowRoleStr][columnRoleStr] += rotation;
+ } else {
+ // We know we are in take last mode if we get here,
+ // as take first mode skips to next loop already earlier
+ itemRotationMap[rowRoleStr][columnRoleStr] = rotation;
+ }
+ }
+ if (generateRows && !rowListHash.value(rowRoleStr, false)) {
+ rowListHash.insert(rowRoleStr, true);
+ rowList << rowRoleStr;
+ }
+ if (generateColumns && !columnListHash.value(columnRoleStr, false)) {
+ columnListHash.insert(columnRoleStr, true);
+ columnList << columnRoleStr;
+ }
+ }
+ }
+
+ if (generateRows)
+ m_proxy->dptr()->m_rowCategories = rowList;
+ else
+ rowList = m_proxy->rowCategories();
+
+ if (generateColumns)
+ m_proxy->dptr()->m_columnCategories = columnList;
+ else
+ columnList = m_proxy->columnCategories();
+
+ // If dimensions have changed, recreate the array
+ if (m_proxyArray != m_proxy->array() || columnList.size() != m_columnCount
+ || rowList.size() != m_proxyArray->size()) {
+ m_proxyArray = new QBarDataArray;
+ m_proxyArray->reserve(rowList.size());
+ for (int i = 0; i < rowList.size(); i++)
+ m_proxyArray->append(new QBarDataRow(columnList.size()));
+ }
+ // Create new data array from itemValueMap
+ for (int i = 0; i < rowList.size(); i++) {
+ QString rowKey = rowList.at(i);
+ QBarDataRow &newProxyRow = *m_proxyArray->at(i);
+ for (int j = 0; j < columnList.size(); j++) {
+ float value = itemValueMap[rowKey][columnList.at(j)];
+ if (countMatches)
+ value /= float((*matchCountMap)[rowKey][columnList.at(j)]);
+ newProxyRow[j].setValue(value);
+ if (m_rotationRole != noRoleIndex) {
+ float angle = itemRotationMap[rowKey][columnList.at(j)];
+ if (countMatches)
+ angle /= float((*matchCountMap)[rowKey][columnList.at(j)]);
+ newProxyRow[j].setRotation(angle);
+ }
+ }
+ }
+
+ rowLabels = rowList;
+ columnLabels = columnList;
+ m_columnCount = columnList.size();
+
+ delete matchCountMap;
+ }
+
+ m_proxy->resetArray(m_proxyArray, rowLabels, columnLabels);
+}
+
+QT_END_NAMESPACE
diff --git a/src/graphs/data/baritemmodelhandler_p.h b/src/graphs/data/baritemmodelhandler_p.h
new file mode 100644
index 0000000..34819f1
--- /dev/null
+++ b/src/graphs/data/baritemmodelhandler_p.h
@@ -0,0 +1,51 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QtGraphs API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#ifndef BARITEMMODELHANDLER_P_H
+#define BARITEMMODELHANDLER_P_H
+
+#include "abstractitemmodelhandler_p.h"
+#include "qitemmodelbardataproxy_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class BarItemModelHandler : public AbstractItemModelHandler
+{
+ Q_OBJECT
+public:
+ BarItemModelHandler(QItemModelBarDataProxy *proxy, QObject *parent = 0);
+ virtual ~BarItemModelHandler();
+
+public Q_SLOTS:
+ void handleDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight,
+ const QList<int> &roles = QList<int>()) override;
+
+protected:
+ void resolveModel() override;
+
+ QItemModelBarDataProxy *m_proxy; // Not owned
+ QBarDataArray *m_proxyArray; // Not owned
+ int m_columnCount;
+ int m_valueRole;
+ int m_rotationRole;
+ QRegularExpression m_valuePattern;
+ QRegularExpression m_rotationPattern;
+ QString m_valueReplace;
+ QString m_rotationReplace;
+ bool m_haveValuePattern;
+ bool m_haveRotationPattern;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/data/qabstract3dseries.cpp b/src/graphs/data/qabstract3dseries.cpp
new file mode 100644
index 0000000..9a3bc3a
--- /dev/null
+++ b/src/graphs/data/qabstract3dseries.cpp
@@ -0,0 +1,879 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qabstract3dseries_p.h"
+#include "qabstractdataproxy_p.h"
+#include "abstract3dcontroller_p.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ * \class QAbstract3DSeries
+ * \inmodule QtGraphs
+ * \brief The QAbstract3DSeries class is a base class for all data series.
+ *
+ * There are inherited classes for each supported series type: QBar3DSeries,
+ * QScatter3DSeries, and QSurface3DSeries.
+ *
+ * For more information, see \l{Qt Graphs Data Handling}.
+ */
+
+/*!
+ * \class QAbstract3DSeriesChangeBitField
+ * \internal
+ */
+
+/*!
+ * \class QAbstract3DSeriesThemeOverrideBitField
+ * \internal
+ */
+
+/*!
+ * \qmltype Abstract3DSeries
+ * \inqmlmodule QtGraphs
+ * \ingroup graphs_qml
+ * \instantiates QAbstract3DSeries
+ * \brief A base type for all data series.
+ *
+ * This type is uncreatable, but contains properties that are exposed via the
+ * following subtypes: Bar3DSeries, Scatter3DSeries, and Surface3DSeries.
+ *
+ * For more information, see \l{Qt Graphs Data Handling}.
+ */
+
+/*!
+ * \enum QAbstract3DSeries::SeriesType
+ *
+ * Type of the series.
+ *
+ * \value SeriesTypeNone
+ * No series type.
+ * \value SeriesTypeBar
+ * Series type for Q3DBars.
+ * \value SeriesTypeScatter
+ * Series type for Q3DScatter.
+ * \value SeriesTypeSurface
+ * Series type for Q3DSurface.
+ */
+
+/*!
+ * \enum QAbstract3DSeries::Mesh
+ *
+ * Predefined mesh types. All styles are not usable with all graphs types.
+ *
+ * \value MeshUserDefined
+ * User defined mesh, set via QAbstract3DSeries::userDefinedMesh property.
+ * \value MeshBar
+ * Basic rectangular bar.
+ * \value MeshCube
+ * Basic cube.
+ * \value MeshPyramid
+ * Four-sided pyramid.
+ * \value MeshCone
+ * Basic cone.
+ * \value MeshCylinder
+ * Basic cylinder.
+ * \value MeshBevelBar
+ * Slightly beveled (rounded) rectangular bar.
+ * \value MeshBevelCube
+ * Slightly beveled (rounded) cube.
+ * \value MeshSphere
+ * Sphere.
+ * \value MeshMinimal
+ * The minimal 3D mesh: a triangular pyramid. Usable only with Q3DScatter.
+ * \value MeshArrow
+ * Arrow pointing upwards.
+ * \value MeshPoint
+ * 2D point. Usable only with Q3DScatter.
+ * Shadows do not affect this style. Color style Q3DTheme::ColorStyleObjectGradient
+ * is not supported by this style.
+ */
+
+/*!
+ * \qmlproperty Abstract3DSeries.SeriesType Abstract3DSeries::type
+ * The type of the series. One of the QAbstract3DSeries::SeriesType values.
+ *
+ */
+
+/*!
+ * \qmlproperty string Abstract3DSeries::itemLabelFormat
+ *
+ * The label format for data items in this series. This format is used for single item labels,
+ * for example, when an item is selected. How the format is interpreted depends
+ * on series type: Bar3DSeries, Scatter3DSeries, Surface3DSeries.
+ */
+
+/*!
+ * \qmlproperty bool Abstract3DSeries::visible
+ * Sets the visibility of the series. If \c false, the series is not rendered.
+ */
+
+/*!
+ * \qmlproperty Abstract3DSeries.Mesh Abstract3DSeries::mesh
+ *
+ * Sets the mesh of the items in the series, or the selection pointer in case of
+ * Surface3DSeries. If the mesh is \l{QAbstract3DSeries::MeshUserDefined}{Abstract3DSeries.MeshUserDefined},
+ * then the userDefinedMesh property must also be set for items to render properly.
+ * The default value depends on the graph type.
+ *
+ * \sa QAbstract3DSeries::Mesh
+ */
+
+/*!
+ * \qmlproperty bool Abstract3DSeries::meshSmooth
+ *
+ * If \c true, smooth versions of predefined meshes set via the \l mesh property are used.
+ * This property does not affect custom meshes used when the mesh is set to
+ * \l{QAbstract3DSeries::MeshUserDefined}{Abstract3DSeries.MeshUserDefined}.
+ * Defaults to \c{false}.
+ */
+
+/*!
+ * \qmlproperty quaternion Abstract3DSeries::meshRotation
+ *
+ * Sets the mesh rotation that is applied to all items of the series.
+ * The rotation should be a normalized quaternion.
+ * For those series types that support item specific rotation, the rotations are
+ * multiplied together.
+ * Bar3DSeries ignores any rotation that is not around the y-axis.
+ * Surface3DSeries applies the rotation only to the selection pointer.
+ * Defaults to no rotation.
+ */
+
+/*!
+ * \qmlproperty string Abstract3DSeries::userDefinedMesh
+ *
+ * Sets the filename for a user defined custom mesh for objects that is used when \l mesh
+ * is \l{QAbstract3DSeries::MeshUserDefined}{Abstract3DSeries.MeshUserDefined}.
+ * \note The file needs to be in the Wavefront OBJ format and include
+ * vertices, normals, and UVs. It also needs to be in triangles.
+ */
+
+/*!
+ * \qmlproperty Theme3D.ColorStyle Abstract3DSeries::colorStyle
+ *
+ * Sets the color style for the series.
+ *
+ * \sa {Theme3D::colorStyle}{Theme3D.colorStyle}
+ */
+
+/*!
+ * \qmlproperty color Abstract3DSeries::baseColor
+ *
+ * Sets the base color of the series.
+ *
+ * \sa colorStyle, {Theme3D::baseColors}{Theme3D.baseColors}
+ */
+
+/*!
+ * \qmlproperty ColorGradient Abstract3DSeries::baseGradient
+ *
+ * Sets the base gradient of the series.
+ *
+ * \sa colorStyle, {Theme3D::baseGradients}{Theme3D.baseGradients}
+ */
+
+/*!
+ * \qmlproperty color Abstract3DSeries::singleHighlightColor
+ *
+ * Sets the single item highlight color of the series.
+ *
+ * \sa colorStyle, {Theme3D::singleHighlightColor}{Theme3D.singleHighlightColor}
+ */
+
+/*!
+ * \qmlproperty ColorGradient Abstract3DSeries::singleHighlightGradient
+ *
+ * Sets the single item highlight gradient of the series.
+ *
+ * \sa colorStyle, {Theme3D::singleHighlightGradient}{Theme3D.singleHighlightGradient}
+ */
+
+/*!
+ * \qmlproperty color Abstract3DSeries::multiHighlightColor
+ *
+ * Sets the multiple item highlight color of the series.
+ *
+ * \sa colorStyle, {Theme3D::multiHighlightColor}{Theme3D.multiHighlightColor}
+ */
+
+/*!
+ * \qmlproperty ColorGradient Abstract3DSeries::multiHighlightGradient
+ *
+ * Sets the multiple item highlight gradient of the series.
+ *
+ * \sa colorStyle, {Theme3D::multiHighlightGradient}{Theme3D.multiHighlightGradient}
+ */
+
+/*!
+ * \qmlproperty string Abstract3DSeries::name
+ *
+ * The series name.
+ * It can be used in item label format with the tag \c{@seriesName}.
+ *
+ * \sa itemLabelFormat
+ */
+
+/*!
+ * \qmlproperty string Abstract3DSeries::itemLabel
+ *
+ * The formatted item label. If there is no selected item or the selected item is not
+ * visible, returns an empty string.
+ *
+ * \sa itemLabelFormat
+ */
+
+/*!
+ * \qmlproperty bool Abstract3DSeries::itemLabelVisible
+ *
+ * If \c true, item labels are drawn as floating labels in the graph. Otherwise,
+ * item labels are not drawn. To show the item label in an external control,
+ * this property is set to \c false. Defaults to \c true.
+ *
+ * \sa itemLabelFormat, itemLabel
+ */
+
+/*!
+ * \qmlmethod void Abstract3DSeries::setMeshAxisAndAngle(vector3d axis, real angle)
+ *
+ * A convenience function to construct a mesh rotation quaternion from \a axis
+ * and \a angle.
+ *
+ * \sa meshRotation
+ */
+
+/*!
+ * \internal
+ */
+QAbstract3DSeries::QAbstract3DSeries(QAbstract3DSeriesPrivate *d, QObject *parent) :
+ QObject(parent),
+ d_ptr(d)
+{
+}
+
+/*!
+ * Deletes the abstract 3D series.
+ */
+QAbstract3DSeries::~QAbstract3DSeries()
+{
+}
+
+/*!
+ * \property QAbstract3DSeries::type
+ *
+ * \brief The type of the series.
+ */
+QAbstract3DSeries::SeriesType QAbstract3DSeries::type() const
+{
+ return d_ptr->m_type;
+}
+
+/*!
+ * \property QAbstract3DSeries::itemLabelFormat
+ *
+ * \brief The label format for data items in this series.
+ *
+ * This format is used for single item labels,
+ * for example, when an item is selected. How the format is interpreted depends
+ * on series type: QBar3DSeries, QScatter3DSeries, QSurface3DSeries.
+ */
+void QAbstract3DSeries::setItemLabelFormat(const QString &format)
+{
+ if (d_ptr->m_itemLabelFormat != format) {
+ d_ptr->setItemLabelFormat(format);
+ emit itemLabelFormatChanged(format);
+ }
+}
+
+QString QAbstract3DSeries::itemLabelFormat() const
+{
+ return d_ptr->m_itemLabelFormat;
+}
+
+/*!
+ * \property QAbstract3DSeries::visible
+ *
+ * \brief The visibility of the series.
+ *
+ * If this property is \c false, the series is not rendered.
+ * Defaults to \c{true}.
+ */
+void QAbstract3DSeries::setVisible(bool visible)
+{
+ if (d_ptr->m_visible != visible) {
+ d_ptr->setVisible(visible);
+ emit visibilityChanged(visible);
+ }
+}
+
+bool QAbstract3DSeries::isVisible() const
+{
+ return d_ptr->m_visible;
+}
+
+/*!
+ * \property QAbstract3DSeries::mesh
+ *
+ * \brief The mesh of the items in the series.
+ *
+ * For QSurface3DSeries, this property holds the selection pointer.
+ *
+ * If the mesh is MeshUserDefined, then the userDefinedMesh property
+ * must also be set for items to render properly. The default value depends on the graph type.
+ */
+void QAbstract3DSeries::setMesh(QAbstract3DSeries::Mesh mesh)
+{
+ if ((mesh == QAbstract3DSeries::MeshPoint || mesh == QAbstract3DSeries::MeshMinimal
+ || mesh == QAbstract3DSeries::MeshArrow)
+ && type() != QAbstract3DSeries::SeriesTypeScatter) {
+ qWarning() << "Specified style is only supported for QScatter3DSeries.";
+ } else if (d_ptr->m_mesh != mesh) {
+ d_ptr->setMesh(mesh);
+ emit meshChanged(mesh);
+ }
+}
+
+QAbstract3DSeries::Mesh QAbstract3DSeries::mesh() const
+{
+ return d_ptr->m_mesh;
+}
+
+/*!
+ * \property QAbstract3DSeries::meshSmooth
+ *
+ * \brief Whether smooth versions of predefined meshes are used.
+ *
+ * If \c true, smooth versions set via the \l mesh property are used.
+ * This property does not affect custom meshes used when the mesh is set to
+ * MeshUserDefined. Defaults to \c{false}.
+ */
+void QAbstract3DSeries::setMeshSmooth(bool enable)
+{
+ if (d_ptr->m_meshSmooth != enable) {
+ d_ptr->setMeshSmooth(enable);
+ emit meshSmoothChanged(enable);
+ }
+}
+
+bool QAbstract3DSeries::isMeshSmooth() const
+{
+ return d_ptr->m_meshSmooth;
+}
+
+/*!
+ * \property QAbstract3DSeries::meshRotation
+ *
+ * \brief The mesh rotation that is applied to all items of the series.
+ *
+ * The rotation should be a normalized QQuaternion.
+ * For those series types that support item specific rotation, the rotations are
+ * multiplied together.
+ * QBar3DSeries ignores any rotation that is not around the y-axis.
+ * QSurface3DSeries applies the rotation only to the selection pointer.
+ * Defaults to no rotation.
+ */
+void QAbstract3DSeries::setMeshRotation(const QQuaternion &rotation)
+{
+ if (d_ptr->m_meshRotation != rotation) {
+ d_ptr->setMeshRotation(rotation);
+ emit meshRotationChanged(rotation);
+ }
+}
+
+QQuaternion QAbstract3DSeries::meshRotation() const
+{
+ return d_ptr->m_meshRotation;
+}
+
+/*!
+ * A convenience function to construct a mesh rotation quaternion from
+ * \a axis and \a angle.
+ *
+ * \sa meshRotation
+ */
+void QAbstract3DSeries::setMeshAxisAndAngle(const QVector3D &axis, float angle)
+{
+ setMeshRotation(QQuaternion::fromAxisAndAngle(axis, angle));
+}
+
+/*!
+ * \property QAbstract3DSeries::userDefinedMesh
+ *
+ * \brief The filename for a user defined custom mesh for objects.
+ *
+ * The custom mesh is used when \l mesh is MeshUserDefined.
+ * \note The file needs to be in the Wavefront OBJ format and include
+ * vertices, normals, and UVs. It also needs to be in triangles.
+ */
+void QAbstract3DSeries::setUserDefinedMesh(const QString &fileName)
+{
+ if (d_ptr->m_userDefinedMesh != fileName) {
+ d_ptr->setUserDefinedMesh(fileName);
+ emit userDefinedMeshChanged(fileName);
+ }
+}
+
+QString QAbstract3DSeries::userDefinedMesh() const
+{
+ return d_ptr->m_userDefinedMesh;
+}
+
+/*!
+ * \property QAbstract3DSeries::colorStyle
+ *
+ * \brief The color style for the series.
+ *
+ * \sa Q3DTheme::ColorStyle
+ */
+void QAbstract3DSeries::setColorStyle(Q3DTheme::ColorStyle style)
+{
+ if (d_ptr->m_colorStyle != style) {
+ d_ptr->setColorStyle(style);
+ emit colorStyleChanged(style);
+ }
+ d_ptr->m_themeTracker.colorStyleOverride = true;
+}
+
+Q3DTheme::ColorStyle QAbstract3DSeries::colorStyle() const
+{
+ return d_ptr->m_colorStyle;
+}
+
+/*!
+ * \property QAbstract3DSeries::baseColor
+ *
+ * \brief The base color of the series.
+ *
+ * \sa colorStyle, Q3DTheme::baseColors
+ */
+void QAbstract3DSeries::setBaseColor(const QColor &color)
+{
+ if (d_ptr->m_baseColor != color) {
+ d_ptr->setBaseColor(color);
+ emit baseColorChanged(color);
+ }
+ d_ptr->m_themeTracker.baseColorOverride = true;
+}
+
+QColor QAbstract3DSeries::baseColor() const
+{
+ return d_ptr->m_baseColor;
+}
+
+/*!
+ * \property QAbstract3DSeries::baseGradient
+ *
+ * \brief The base gradient of the series.
+ *
+ * \sa colorStyle, Q3DTheme::baseGradients
+ */
+void QAbstract3DSeries::setBaseGradient(const QLinearGradient &gradient)
+{
+ if (d_ptr->m_baseGradient != gradient) {
+ d_ptr->setBaseGradient(gradient);
+ emit baseGradientChanged(gradient);
+ }
+ d_ptr->m_themeTracker.baseGradientOverride = true;
+}
+
+QLinearGradient QAbstract3DSeries::baseGradient() const
+{
+ return d_ptr->m_baseGradient;
+}
+
+/*!
+ * \property QAbstract3DSeries::singleHighlightColor
+ *
+ * \brief The single item highlight color of the series.
+ *
+ * \sa colorStyle, Q3DTheme::singleHighlightColor
+ */
+void QAbstract3DSeries::setSingleHighlightColor(const QColor &color)
+{
+ if (d_ptr->m_singleHighlightColor != color) {
+ d_ptr->setSingleHighlightColor(color);
+ emit singleHighlightColorChanged(color);
+ }
+ d_ptr->m_themeTracker.singleHighlightColorOverride = true;
+}
+
+QColor QAbstract3DSeries::singleHighlightColor() const
+{
+ return d_ptr->m_singleHighlightColor;
+}
+
+/*!
+ * \property QAbstract3DSeries::singleHighlightGradient
+ *
+ * \brief The single item highlight gradient of the series.
+ *
+ * \sa colorStyle, Q3DTheme::singleHighlightGradient
+ */
+void QAbstract3DSeries::setSingleHighlightGradient(const QLinearGradient &gradient)
+{
+ if (d_ptr->m_singleHighlightGradient != gradient) {
+ d_ptr->setSingleHighlightGradient(gradient);
+ emit singleHighlightGradientChanged(gradient);
+ }
+ d_ptr->m_themeTracker.singleHighlightGradientOverride = true;
+}
+
+QLinearGradient QAbstract3DSeries::singleHighlightGradient() const
+{
+ return d_ptr->m_singleHighlightGradient;
+}
+
+/*!
+ * \property QAbstract3DSeries::multiHighlightColor
+ *
+ * \brief The multiple item highlight color of the series.
+ *
+ * \sa colorStyle, Q3DTheme::multiHighlightColor
+ */
+void QAbstract3DSeries::setMultiHighlightColor(const QColor &color)
+{
+ if (d_ptr->m_multiHighlightColor != color) {
+ d_ptr->setMultiHighlightColor(color);
+ emit multiHighlightColorChanged(color);
+ }
+ d_ptr->m_themeTracker.multiHighlightColorOverride = true;
+}
+
+QColor QAbstract3DSeries::multiHighlightColor() const
+{
+ return d_ptr->m_multiHighlightColor;
+}
+
+/*!
+ * \property QAbstract3DSeries::multiHighlightGradient
+ *
+ * \brief The multiple item highlight gradient of the series.
+ *
+ * \sa colorStyle, Q3DTheme::multiHighlightGradient
+ */
+void QAbstract3DSeries::setMultiHighlightGradient(const QLinearGradient &gradient)
+{
+ if (d_ptr->m_multiHighlightGradient != gradient) {
+ d_ptr->setMultiHighlightGradient(gradient);
+ emit multiHighlightGradientChanged(gradient);
+ }
+ d_ptr->m_themeTracker.multiHighlightGradientOverride = true;
+}
+
+QLinearGradient QAbstract3DSeries::multiHighlightGradient() const
+{
+ return d_ptr->m_multiHighlightGradient;
+}
+
+/*!
+ * \property QAbstract3DSeries::name
+ *
+ * \brief The series name.
+ *
+ * The series name can be used in item label format with the tag \c{@seriesName}.
+ *
+ * \sa itemLabelFormat
+ */
+void QAbstract3DSeries::setName(const QString &name)
+{
+ if (d_ptr->m_name != name) {
+ d_ptr->setName(name);
+ emit nameChanged(name);
+ }
+}
+
+QString QAbstract3DSeries::name() const
+{
+ return d_ptr->m_name;
+}
+
+/*!
+ * \property QAbstract3DSeries::itemLabel
+ *
+ * \brief The formatted item label.
+ *
+ * If there is no selected item or the selected item is not
+ * visible, returns an empty string.
+ *
+ * \sa itemLabelFormat
+ */
+QString QAbstract3DSeries::itemLabel() const
+{
+ return d_ptr->itemLabel();
+}
+
+/*!
+ * \property QAbstract3DSeries::itemLabelVisible
+ *
+ * \brief The visibility of item labels in the graph.
+ *
+ * If \c true, item labels are drawn as floating labels in the graph. Otherwise,
+ * item labels are not drawn. To show the item label in an external control,
+ * this property is set to \c false. Defaults to \c true.
+ *
+ * \sa itemLabelFormat, itemLabel
+ */
+void QAbstract3DSeries::setItemLabelVisible(bool visible)
+{
+ if (d_ptr->m_itemLabelVisible != visible) {
+ d_ptr->setItemLabelVisible(visible);
+ emit itemLabelVisibilityChanged(visible);
+ }
+}
+
+bool QAbstract3DSeries::isItemLabelVisible() const
+{
+ return d_ptr->m_itemLabelVisible;
+}
+
+// QAbstract3DSeriesPrivate
+
+QAbstract3DSeriesPrivate::QAbstract3DSeriesPrivate(QAbstract3DSeries *q,
+ QAbstract3DSeries::SeriesType type)
+ : QObject(0),
+ q_ptr(q),
+ m_type(type),
+ m_dataProxy(0),
+ m_visible(true),
+ m_controller(0),
+ m_mesh(QAbstract3DSeries::MeshCube),
+ m_meshSmooth(false),
+ m_colorStyle(Q3DTheme::ColorStyleUniform),
+ m_baseColor(Qt::black),
+ m_singleHighlightColor(Qt::black),
+ m_multiHighlightColor(Qt::black),
+ m_itemLabelDirty(true),
+ m_itemLabelVisible(true)
+{
+}
+
+QAbstract3DSeriesPrivate::~QAbstract3DSeriesPrivate()
+{
+}
+
+QAbstractDataProxy *QAbstract3DSeriesPrivate::dataProxy() const
+{
+ return m_dataProxy;
+}
+
+void QAbstract3DSeriesPrivate::setDataProxy(QAbstractDataProxy *proxy)
+{
+ Q_ASSERT(proxy && proxy != m_dataProxy && !proxy->d_ptr->series());
+
+ delete m_dataProxy;
+ m_dataProxy = proxy;
+
+ proxy->d_ptr->setSeries(q_ptr); // also sets parent
+
+ if (m_controller) {
+ connectControllerAndProxy(m_controller);
+ m_controller->markDataDirty();
+ }
+}
+
+void QAbstract3DSeriesPrivate::setController(Abstract3DController *controller)
+{
+ connectControllerAndProxy(controller);
+ m_controller = controller;
+ q_ptr->setParent(controller);
+ markItemLabelDirty();
+}
+
+void QAbstract3DSeriesPrivate::setItemLabelFormat(const QString &format)
+{
+ m_itemLabelFormat = format;
+ markItemLabelDirty();
+}
+
+void QAbstract3DSeriesPrivate::setVisible(bool visible)
+{
+ m_visible = visible;
+ markItemLabelDirty();
+}
+
+void QAbstract3DSeriesPrivate::setMesh(QAbstract3DSeries::Mesh mesh)
+{
+ m_mesh = mesh;
+ m_changeTracker.meshChanged = true;
+ if (m_controller) {
+ m_controller->markSeriesVisualsDirty();
+
+ if (m_controller->optimizationHints().testFlag(QAbstract3DGraph::OptimizationStatic))
+ m_controller->markDataDirty();
+ }
+}
+
+void QAbstract3DSeriesPrivate::setMeshSmooth(bool enable)
+{
+ m_meshSmooth = enable;
+ m_changeTracker.meshSmoothChanged = true;
+ if (m_controller) {
+ m_controller->markSeriesVisualsDirty();
+
+ if (m_controller->optimizationHints().testFlag(QAbstract3DGraph::OptimizationStatic))
+ m_controller->markDataDirty();
+ }
+}
+
+void QAbstract3DSeriesPrivate::setMeshRotation(const QQuaternion &rotation)
+{
+ m_meshRotation = rotation;
+ m_changeTracker.meshRotationChanged = true;
+ if (m_controller) {
+ m_controller->markSeriesVisualsDirty();
+
+ if (m_controller->optimizationHints().testFlag(QAbstract3DGraph::OptimizationStatic))
+ m_controller->markDataDirty();
+ }
+}
+
+void QAbstract3DSeriesPrivate::setUserDefinedMesh(const QString &meshFile)
+{
+ m_userDefinedMesh = meshFile;
+ m_changeTracker.userDefinedMeshChanged = true;
+ if (m_controller) {
+ m_controller->markSeriesVisualsDirty();
+
+ if (m_controller->optimizationHints().testFlag(QAbstract3DGraph::OptimizationStatic))
+ m_controller->markDataDirty();
+ }
+}
+
+void QAbstract3DSeriesPrivate::setColorStyle(Q3DTheme::ColorStyle style)
+{
+ m_colorStyle = style;
+ m_changeTracker.colorStyleChanged = true;
+ if (m_controller)
+ m_controller->markSeriesVisualsDirty();
+}
+
+void QAbstract3DSeriesPrivate::setBaseColor(const QColor &color)
+{
+ m_baseColor = color;
+ m_changeTracker.baseColorChanged = true;
+ if (m_controller)
+ m_controller->markSeriesVisualsDirty();
+}
+
+void QAbstract3DSeriesPrivate::setBaseGradient(const QLinearGradient &gradient)
+{
+ m_baseGradient = gradient;
+ m_changeTracker.baseGradientChanged = true;
+ if (m_controller)
+ m_controller->markSeriesVisualsDirty();
+}
+
+void QAbstract3DSeriesPrivate::setSingleHighlightColor(const QColor &color)
+{
+ m_singleHighlightColor = color;
+ m_changeTracker.singleHighlightColorChanged = true;
+ if (m_controller)
+ m_controller->markSeriesVisualsDirty();
+}
+
+void QAbstract3DSeriesPrivate::setSingleHighlightGradient(const QLinearGradient &gradient)
+{
+ m_singleHighlightGradient = gradient;
+ m_changeTracker.singleHighlightGradientChanged = true;
+ if (m_controller)
+ m_controller->markSeriesVisualsDirty();
+}
+
+void QAbstract3DSeriesPrivate::setMultiHighlightColor(const QColor &color)
+{
+ m_multiHighlightColor = color;
+ m_changeTracker.multiHighlightColorChanged = true;
+ if (m_controller)
+ m_controller->markSeriesVisualsDirty();
+}
+
+void QAbstract3DSeriesPrivate::setMultiHighlightGradient(const QLinearGradient &gradient)
+{
+ m_multiHighlightGradient = gradient;
+ m_changeTracker.multiHighlightGradientChanged = true;
+ if (m_controller)
+ m_controller->markSeriesVisualsDirty();
+}
+
+void QAbstract3DSeriesPrivate::setName(const QString &name)
+{
+ m_name = name;
+ markItemLabelDirty();
+ m_changeTracker.nameChanged = true;
+}
+
+void QAbstract3DSeriesPrivate::resetToTheme(const Q3DTheme &theme, int seriesIndex, bool force)
+{
+ int themeIndex = seriesIndex;
+ if (force || !m_themeTracker.colorStyleOverride) {
+ q_ptr->setColorStyle(theme.colorStyle());
+ m_themeTracker.colorStyleOverride = false;
+ }
+ if (force || !m_themeTracker.baseColorOverride) {
+ if (theme.baseColors().size() <= seriesIndex)
+ themeIndex = seriesIndex % theme.baseColors().size();
+ q_ptr->setBaseColor(theme.baseColors().at(themeIndex));
+ m_themeTracker.baseColorOverride = false;
+ }
+ if (force || !m_themeTracker.baseGradientOverride) {
+ if (theme.baseGradients().size() <= seriesIndex)
+ themeIndex = seriesIndex % theme.baseGradients().size();
+ q_ptr->setBaseGradient(theme.baseGradients().at(themeIndex));
+ m_themeTracker.baseGradientOverride = false;
+ }
+ if (force || !m_themeTracker.singleHighlightColorOverride) {
+ q_ptr->setSingleHighlightColor(theme.singleHighlightColor());
+ m_themeTracker.singleHighlightColorOverride = false;
+ }
+ if (force || !m_themeTracker.singleHighlightGradientOverride) {
+ q_ptr->setSingleHighlightGradient(theme.singleHighlightGradient());
+ m_themeTracker.singleHighlightGradientOverride = false;
+ }
+ if (force || !m_themeTracker.multiHighlightColorOverride) {
+ q_ptr->setMultiHighlightColor(theme.multiHighlightColor());
+ m_themeTracker.multiHighlightColorOverride = false;
+ }
+ if (force || !m_themeTracker.multiHighlightGradientOverride) {
+ q_ptr->setMultiHighlightGradient(theme.multiHighlightGradient());
+ m_themeTracker.multiHighlightGradientOverride = false;
+ }
+}
+
+QString QAbstract3DSeriesPrivate::itemLabel()
+{
+ if (m_itemLabelDirty) {
+ QString oldLabel = m_itemLabel;
+ if (m_controller && m_visible)
+ createItemLabel();
+ else
+ m_itemLabel = QString();
+ m_itemLabelDirty = false;
+
+ if (oldLabel != m_itemLabel)
+ emit q_ptr->itemLabelChanged(m_itemLabel);
+ }
+
+ return m_itemLabel;
+}
+
+void QAbstract3DSeriesPrivate::markItemLabelDirty()
+{
+ m_itemLabelDirty = true;
+ m_changeTracker.itemLabelChanged = true;
+ if (m_controller)
+ m_controller->markSeriesVisualsDirty();
+}
+
+void QAbstract3DSeriesPrivate::setItemLabelVisible(bool visible)
+{
+ m_itemLabelVisible = visible;
+ markItemLabelDirty();
+ m_changeTracker.itemLabelVisibilityChanged = true;
+}
+
+bool QAbstract3DSeriesPrivate::isUsingGradient()
+{
+ return m_colorStyle != Q3DTheme::ColorStyleUniform ? true : false;
+}
+
+QT_END_NAMESPACE
diff --git a/src/graphs/data/qabstract3dseries.h b/src/graphs/data/qabstract3dseries.h
new file mode 100644
index 0000000..b6148bf
--- /dev/null
+++ b/src/graphs/data/qabstract3dseries.h
@@ -0,0 +1,147 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QABSTRACT3DSERIES_H
+#define QABSTRACT3DSERIES_H
+
+#include <QtGraphs/q3dtheme.h>
+#include <QtCore/QObject>
+#include <QtCore/QScopedPointer>
+#include <QtCore/QString>
+#include <QtGui/QLinearGradient>
+#include <QtGui/QQuaternion>
+
+QT_BEGIN_NAMESPACE
+
+class QAbstract3DSeriesPrivate;
+
+class Q_GRAPHS_EXPORT QAbstract3DSeries : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(SeriesType type READ type CONSTANT)
+ Q_PROPERTY(QString itemLabelFormat READ itemLabelFormat WRITE setItemLabelFormat NOTIFY itemLabelFormatChanged)
+ Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibilityChanged)
+ Q_PROPERTY(Mesh mesh READ mesh WRITE setMesh NOTIFY meshChanged)
+ Q_PROPERTY(bool meshSmooth READ isMeshSmooth WRITE setMeshSmooth NOTIFY meshSmoothChanged)
+ Q_PROPERTY(QQuaternion meshRotation READ meshRotation WRITE setMeshRotation NOTIFY meshRotationChanged)
+ Q_PROPERTY(QString userDefinedMesh READ userDefinedMesh WRITE setUserDefinedMesh NOTIFY userDefinedMeshChanged)
+ Q_PROPERTY(Q3DTheme::ColorStyle colorStyle READ colorStyle WRITE setColorStyle NOTIFY colorStyleChanged)
+ Q_PROPERTY(QColor baseColor READ baseColor WRITE setBaseColor NOTIFY baseColorChanged)
+ Q_PROPERTY(QLinearGradient baseGradient READ baseGradient WRITE setBaseGradient NOTIFY baseGradientChanged)
+ Q_PROPERTY(QColor singleHighlightColor READ singleHighlightColor WRITE setSingleHighlightColor NOTIFY singleHighlightColorChanged)
+ Q_PROPERTY(QLinearGradient singleHighlightGradient READ singleHighlightGradient WRITE setSingleHighlightGradient NOTIFY singleHighlightGradientChanged)
+ Q_PROPERTY(QColor multiHighlightColor READ multiHighlightColor WRITE setMultiHighlightColor NOTIFY multiHighlightColorChanged)
+ Q_PROPERTY(QLinearGradient multiHighlightGradient READ multiHighlightGradient WRITE setMultiHighlightGradient NOTIFY multiHighlightGradientChanged)
+ Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
+ Q_PROPERTY(QString itemLabel READ itemLabel NOTIFY itemLabelChanged)
+ Q_PROPERTY(bool itemLabelVisible READ isItemLabelVisible WRITE setItemLabelVisible NOTIFY itemLabelVisibilityChanged)
+
+public:
+ enum SeriesType {
+ SeriesTypeNone = 0,
+ SeriesTypeBar = 1,
+ SeriesTypeScatter = 2,
+ SeriesTypeSurface = 4
+ };
+ Q_ENUM(SeriesType)
+
+ enum Mesh {
+ MeshUserDefined = 0,
+ MeshBar,
+ MeshCube,
+ MeshPyramid,
+ MeshCone,
+ MeshCylinder,
+ MeshBevelBar,
+ MeshBevelCube,
+ MeshSphere,
+ MeshMinimal,
+ MeshArrow,
+ MeshPoint
+ };
+ Q_ENUM(Mesh)
+
+protected:
+ explicit QAbstract3DSeries(QAbstract3DSeriesPrivate *d, QObject *parent = nullptr);
+
+public:
+ virtual ~QAbstract3DSeries();
+
+ SeriesType type() const;
+
+ void setItemLabelFormat(const QString &format);
+ QString itemLabelFormat() const;
+
+ void setVisible(bool visible);
+ bool isVisible() const;
+
+ void setMesh(Mesh mesh);
+ Mesh mesh() const;
+
+ void setMeshSmooth(bool enable);
+ bool isMeshSmooth() const;
+
+ void setMeshRotation(const QQuaternion &rotation);
+ QQuaternion meshRotation() const;
+ Q_INVOKABLE void setMeshAxisAndAngle(const QVector3D &axis, float angle);
+
+ void setUserDefinedMesh(const QString &fileName);
+ QString userDefinedMesh() const;
+
+ void setColorStyle(Q3DTheme::ColorStyle style);
+ Q3DTheme::ColorStyle colorStyle() const;
+ void setBaseColor(const QColor &color);
+ QColor baseColor() const;
+ void setBaseGradient(const QLinearGradient &gradient);
+ QLinearGradient baseGradient() const;
+ void setSingleHighlightColor(const QColor &color);
+ QColor singleHighlightColor() const;
+ void setSingleHighlightGradient(const QLinearGradient &gradient);
+ QLinearGradient singleHighlightGradient() const;
+ void setMultiHighlightColor(const QColor &color);
+ QColor multiHighlightColor() const;
+ void setMultiHighlightGradient(const QLinearGradient &gradient);
+ QLinearGradient multiHighlightGradient() const;
+
+ void setName(const QString &name);
+ QString name() const;
+
+ QString itemLabel() const;
+ void setItemLabelVisible(bool visible);
+ bool isItemLabelVisible() const;
+
+Q_SIGNALS:
+ void itemLabelFormatChanged(const QString &format);
+ void visibilityChanged(bool visible);
+ void meshChanged(QAbstract3DSeries::Mesh mesh);
+ void meshSmoothChanged(bool enabled);
+ void meshRotationChanged(const QQuaternion &rotation);
+ void userDefinedMeshChanged(const QString &fileName);
+ void colorStyleChanged(Q3DTheme::ColorStyle style);
+ void baseColorChanged(const QColor &color);
+ void baseGradientChanged(const QLinearGradient &gradient);
+ void singleHighlightColorChanged(const QColor &color);
+ void singleHighlightGradientChanged(const QLinearGradient &gradient);
+ void multiHighlightColorChanged(const QColor &color);
+ void multiHighlightGradientChanged(const QLinearGradient &gradient);
+ void nameChanged(const QString &name);
+ void itemLabelChanged(const QString &label);
+ void itemLabelVisibilityChanged(bool visible);
+
+protected:
+ QScopedPointer<QAbstract3DSeriesPrivate> d_ptr;
+
+private:
+ Q_DISABLE_COPY(QAbstract3DSeries)
+
+ friend class Abstract3DController;
+ friend class Bars3DController;
+ friend class Surface3DController;
+ friend class Scatter3DController;
+ friend class QBar3DSeries;
+ friend class SeriesRenderCache;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/data/qabstract3dseries_p.h b/src/graphs/data/qabstract3dseries_p.h
new file mode 100644
index 0000000..9a70160
--- /dev/null
+++ b/src/graphs/data/qabstract3dseries_p.h
@@ -0,0 +1,149 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QtGraphs API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#ifndef QABSTRACT3DSERIES_P_H
+#define QABSTRACT3DSERIES_P_H
+
+#include <private/graphsglobal_p.h>
+
+#include "qabstract3dseries.h"
+
+QT_BEGIN_NAMESPACE
+
+class QAbstractDataProxy;
+class Abstract3DController;
+
+struct QAbstract3DSeriesChangeBitField {
+ bool meshChanged : 1;
+ bool meshSmoothChanged : 1;
+ bool meshRotationChanged : 1;
+ bool userDefinedMeshChanged : 1;
+ bool colorStyleChanged : 1;
+ bool baseColorChanged : 1;
+ bool baseGradientChanged : 1;
+ bool singleHighlightColorChanged : 1;
+ bool singleHighlightGradientChanged : 1;
+ bool multiHighlightColorChanged : 1;
+ bool multiHighlightGradientChanged : 1;
+ bool nameChanged : 1;
+ bool itemLabelChanged : 1;
+ bool itemLabelVisibilityChanged : 1;
+ bool visibilityChanged : 1;
+
+ QAbstract3DSeriesChangeBitField()
+ : meshChanged(true),
+ meshSmoothChanged(true),
+ meshRotationChanged(true),
+ userDefinedMeshChanged(true),
+ colorStyleChanged(true),
+ baseColorChanged(true),
+ baseGradientChanged(true),
+ singleHighlightColorChanged(true),
+ singleHighlightGradientChanged(true),
+ multiHighlightColorChanged(true),
+ multiHighlightGradientChanged(true),
+ nameChanged(true),
+ itemLabelChanged(true),
+ itemLabelVisibilityChanged(true),
+ visibilityChanged(true)
+ {
+ }
+};
+
+struct QAbstract3DSeriesThemeOverrideBitField {
+ bool colorStyleOverride : 1;
+ bool baseColorOverride : 1;
+ bool baseGradientOverride : 1;
+ bool singleHighlightColorOverride : 1;
+ bool singleHighlightGradientOverride : 1;
+ bool multiHighlightColorOverride : 1;
+ bool multiHighlightGradientOverride : 1;
+
+ QAbstract3DSeriesThemeOverrideBitField()
+ : colorStyleOverride(false),
+ baseColorOverride(false),
+ baseGradientOverride(false),
+ singleHighlightColorOverride(false),
+ singleHighlightGradientOverride(false),
+ multiHighlightColorOverride(false),
+ multiHighlightGradientOverride(false)
+ {
+ }
+};
+
+class QAbstract3DSeriesPrivate : public QObject
+{
+ Q_OBJECT
+public:
+ QAbstract3DSeriesPrivate(QAbstract3DSeries *q, QAbstract3DSeries::SeriesType type);
+ virtual ~QAbstract3DSeriesPrivate();
+
+ QAbstractDataProxy *dataProxy() const;
+ virtual void setDataProxy(QAbstractDataProxy *proxy);
+ virtual void setController(Abstract3DController *controller);
+ virtual void connectControllerAndProxy(Abstract3DController *newController) = 0;
+ virtual void createItemLabel() = 0;
+
+ void setItemLabelFormat(const QString &format);
+ void setVisible(bool visible);
+ void setMesh(QAbstract3DSeries::Mesh mesh);
+ void setMeshSmooth(bool enable);
+ void setMeshRotation(const QQuaternion &rotation);
+ void setUserDefinedMesh(const QString &meshFile);
+
+ void setColorStyle(Q3DTheme::ColorStyle style);
+ void setBaseColor(const QColor &color);
+ void setBaseGradient(const QLinearGradient &gradient);
+ void setSingleHighlightColor(const QColor &color);
+ void setSingleHighlightGradient(const QLinearGradient &gradient);
+ void setMultiHighlightColor(const QColor &color);
+ void setMultiHighlightGradient(const QLinearGradient &gradient);
+ void setName(const QString &name);
+
+ void resetToTheme(const Q3DTheme &theme, int seriesIndex, bool force);
+ QString itemLabel();
+ void markItemLabelDirty();
+ inline bool itemLabelDirty() const { return m_itemLabelDirty; }
+ void setItemLabelVisible(bool visible);
+ bool isUsingGradient();
+
+ QAbstract3DSeriesChangeBitField m_changeTracker;
+ QAbstract3DSeriesThemeOverrideBitField m_themeTracker;
+ QAbstract3DSeries *q_ptr;
+ QAbstract3DSeries::SeriesType m_type;
+ QString m_itemLabelFormat;
+ QAbstractDataProxy *m_dataProxy;
+ bool m_visible;
+ Abstract3DController *m_controller;
+ QAbstract3DSeries::Mesh m_mesh;
+ bool m_meshSmooth;
+ QQuaternion m_meshRotation;
+ QString m_userDefinedMesh;
+
+ Q3DTheme::ColorStyle m_colorStyle;
+ QColor m_baseColor;
+ QLinearGradient m_baseGradient;
+ QColor m_singleHighlightColor;
+ QLinearGradient m_singleHighlightGradient;
+ QColor m_multiHighlightColor;
+ QLinearGradient m_multiHighlightGradient;
+
+ QString m_name;
+ QString m_itemLabel;
+ bool m_itemLabelDirty;
+ bool m_itemLabelVisible;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/data/qabstractdataproxy.cpp b/src/graphs/data/qabstractdataproxy.cpp
new file mode 100644
index 0000000..11927c7
--- /dev/null
+++ b/src/graphs/data/qabstractdataproxy.cpp
@@ -0,0 +1,100 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qabstractdataproxy_p.h"
+#include "qabstract3dseries_p.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ * \class QAbstractDataProxy
+ * \inmodule QtGraphs
+ * \brief The QAbstractDataProxy class is a base class for all graphs proxies.
+ *
+ * The following graphs type specific inherited classes are used instead
+ * of the base class: QBarDataProxy, QScatterDataProxy, and QSurfaceDataProxy.
+ *
+ * For more information, see \l{Qt Graphs Data Handling}.
+ */
+
+/*!
+ * \qmltype AbstractDataProxy
+ * \inqmlmodule QtGraphs
+ * \ingroup graphs_qml
+ * \instantiates QAbstractDataProxy
+ * \brief Base type for all QtGraphs data proxies.
+ *
+ * This type is uncreatable, but contains properties that are exposed via the
+ * following subtypes: BarDataProxy, ScatterDataProxy, SurfaceDataProxy.
+ *
+ * For more information, see \l {Qt Graphs Data Handling}.
+ */
+
+/*!
+ * \qmlproperty AbstractDataProxy.DataType AbstractDataProxy::type
+ * The type of the proxy. One of the QAbstractDataProxy::DataType values.
+ */
+
+/*!
+ * \enum QAbstractDataProxy::DataType
+ *
+ * This enum type specifies the data type of the proxy.
+ *
+ * \value DataTypeNone
+ * No data type.
+ * \value DataTypeBar
+ * Data type for Q3DBars.
+ * \value DataTypeScatter
+ * Data type for Q3DScatter.
+ * \value DataTypeSurface
+ * Data type for Q3DSurface.
+ */
+
+/*!
+ * \internal
+ */
+QAbstractDataProxy::QAbstractDataProxy(QAbstractDataProxyPrivate *d, QObject *parent) :
+ QObject(parent),
+ d_ptr(d)
+{
+}
+
+/*!
+ * Deletes the abstract data proxy.
+ */
+QAbstractDataProxy::~QAbstractDataProxy()
+{
+}
+
+/*!
+ * \property QAbstractDataProxy::type
+ *
+ * \brief The data type of the proxy.
+ */
+QAbstractDataProxy::DataType QAbstractDataProxy::type() const
+{
+ return d_ptr->m_type;
+}
+
+// QAbstractDataProxyPrivate
+
+QAbstractDataProxyPrivate::QAbstractDataProxyPrivate(QAbstractDataProxy *q,
+ QAbstractDataProxy::DataType type)
+ : QObject(0),
+ q_ptr(q),
+ m_type(type),
+ m_series(0)
+{
+}
+
+QAbstractDataProxyPrivate::~QAbstractDataProxyPrivate()
+{
+}
+
+void QAbstractDataProxyPrivate::setSeries(QAbstract3DSeries *series)
+{
+ q_ptr->setParent(series);
+ m_series = series;
+}
+
+QT_END_NAMESPACE
diff --git a/src/graphs/data/qabstractdataproxy.h b/src/graphs/data/qabstractdataproxy.h
new file mode 100644
index 0000000..d6227f8
--- /dev/null
+++ b/src/graphs/data/qabstractdataproxy.h
@@ -0,0 +1,48 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QABSTRACTDATAPROXY_H
+#define QABSTRACTDATAPROXY_H
+
+#include <QtGraphs/qgraphsglobal.h>
+#include <QtCore/QObject>
+#include <QtCore/QScopedPointer>
+
+QT_BEGIN_NAMESPACE
+
+class QAbstractDataProxyPrivate;
+
+class Q_GRAPHS_EXPORT QAbstractDataProxy : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(DataType type READ type CONSTANT)
+
+public:
+ enum DataType {
+ DataTypeNone = 0,
+ DataTypeBar = 1,
+ DataTypeScatter = 2,
+ DataTypeSurface = 4
+ };
+ Q_ENUM(DataType)
+
+protected:
+ explicit QAbstractDataProxy(QAbstractDataProxyPrivate *d, QObject *parent = nullptr);
+
+public:
+ virtual ~QAbstractDataProxy();
+
+ DataType type() const;
+
+protected:
+ QScopedPointer<QAbstractDataProxyPrivate> d_ptr;
+
+private:
+ Q_DISABLE_COPY(QAbstractDataProxy)
+
+ friend class QAbstract3DSeriesPrivate;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/data/qabstractdataproxy_p.h b/src/graphs/data/qabstractdataproxy_p.h
new file mode 100644
index 0000000..8af59bd
--- /dev/null
+++ b/src/graphs/data/qabstractdataproxy_p.h
@@ -0,0 +1,45 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QtGraphs API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#ifndef QABSTRACTDATAPROXY_P_H
+#define QABSTRACTDATAPROXY_P_H
+
+#include "graphsglobal_p.h"
+#include "qabstractdataproxy.h"
+
+QT_BEGIN_NAMESPACE
+
+class QAbstract3DSeries;
+
+class QAbstractDataProxyPrivate : public QObject
+{
+ Q_OBJECT
+public:
+ QAbstractDataProxyPrivate(QAbstractDataProxy *q, QAbstractDataProxy::DataType type);
+ virtual ~QAbstractDataProxyPrivate();
+
+ inline QAbstract3DSeries *series() { return m_series; }
+ virtual void setSeries(QAbstract3DSeries *series);
+
+protected:
+ QAbstractDataProxy *q_ptr;
+ QAbstractDataProxy::DataType m_type;
+ QAbstract3DSeries *m_series;
+
+private:
+ friend class QAbstractDataProxy;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/data/qbar3dseries.cpp b/src/graphs/data/qbar3dseries.cpp
new file mode 100644
index 0000000..6ec40c9
--- /dev/null
+++ b/src/graphs/data/qbar3dseries.cpp
@@ -0,0 +1,453 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qbar3dseries_p.h"
+#include "bars3dcontroller_p.h"
+#include "qabstract3daxis_p.h"
+#include "qvalue3daxis_p.h"
+#include "qcategory3daxis_p.h"
+#include <QtCore/qmath.h>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ * \class QBar3DSeries
+ * \inmodule QtGraphs
+ * \brief The QBar3DSeries class represents a data series in a 3D bar graph.
+ *
+ * This class manages the series specific visual elements, as well as the series
+ * data (via a data proxy).
+ *
+ * If no data proxy is set explicitly for the series, the series creates a default
+ * proxy. Setting another proxy will destroy the existing proxy and all data added to it.
+ *
+ * QBar3DSeries supports the following format tags for QAbstract3DSeries::setItemLabelFormat():
+ * \table
+ * \row
+ * \li @rowTitle \li Title from row axis
+ * \row
+ * \li @colTitle \li Title from column axis
+ * \row
+ * \li @valueTitle \li Title from value axis
+ * \row
+ * \li @rowIdx \li Visible row index. Localized using the graph locale.
+ * \row
+ * \li @colIdx \li Visible column index. Localized using the graph locale.
+ * \row
+ * \li @rowLabel \li Label from row axis
+ * \row
+ * \li @colLabel \li Label from column axis
+ * \row
+ * \li @valueLabel \li Item value formatted using the format of the value
+ * axis attached to the graph. For more information,
+ * see \l{QValue3DAxis::labelFormat}.
+ * \row
+ * \li @seriesName \li Name of the series
+ * \row
+ * \li %<format spec> \li Item value in the specified format. Formatted
+ * using the same rules as \l{QValue3DAxis::labelFormat}.
+ * \endtable
+ *
+ * For example:
+ * \snippet doc_src_qtgraphs.cpp 1
+ *
+ * \sa {Qt Graphs Data Handling}, QAbstract3DGraph::locale
+ */
+
+/*!
+ * \qmltype Bar3DSeries
+ * \inqmlmodule QtGraphs
+ * \ingroup graphs_qml
+ * \instantiates QBar3DSeries
+ * \inherits Abstract3DSeries
+ * \brief Represents a data series in a 3D bar graph.
+ *
+ * This type manages the series specific visual elements, as well as the series
+ * data (via a data proxy).
+ *
+ * For a more complete description, see QBar3DSeries.
+ *
+ * \sa {Qt Graphs Data Handling}
+ */
+
+/*!
+ * \qmlproperty BarDataProxy Bar3DSeries::dataProxy
+ *
+ * The active data proxy. The series assumes ownership of any proxy set to
+ * it and deletes any previously set proxy when a new one is added. The proxy cannot be null or
+ * set to another series.
+ */
+
+/*!
+ * \qmlproperty point Bar3DSeries::selectedBar
+ *
+ * The bar in the series that is selected.
+ *
+ * The position of the selected bar is specified as a row and column in the
+ * data array of the series.
+ *
+ * Only one bar can be selected at a time.
+ *
+ * To clear selection from this series, set invalidSelectionPosition as the position.
+ *
+ * If this series is added to a graph, the graph can adjust the selection according to user
+ * interaction or if it becomes invalid. Selecting a bar on another added series will also
+ * clear the selection.
+ *
+ * Removing rows from or inserting rows to the series before the row of the selected bar
+ * will adjust the selection so that the same bar will stay selected.
+ *
+ * \sa {AbstractGraph3D::clearSelection()}{AbstractGraph3D.clearSelection()}
+ */
+
+/*!
+ * \qmlproperty point Bar3DSeries::invalidSelectionPosition
+ * A constant property providing an invalid position for selection. This
+ * position is set to the selectedBar property to clear the selection from this
+ * series.
+ *
+ * \sa {AbstractGraph3D::clearSelection()}{AbstractGraph3D.clearSelection()}
+ */
+
+/*!
+ * \qmlproperty real Bar3DSeries::meshAngle
+ *
+ * A convenience property for defining the series rotation angle in degrees.
+ *
+ * \note When reading this property, it is calculated from the
+ * \l{Abstract3DSeries::meshRotation}{Abstract3DSeries.meshRotation} value
+ * using floating point precision and always returns a value from zero to 360 degrees.
+ *
+ * \sa {Abstract3DSeries::meshRotation}{Abstract3DSeries.meshRotation}
+ */
+
+/*!
+ * \qmlproperty list<ThemeColor> Bar3DSeries::rowColors
+ * This property can be used to draw the rows of the series in different colors.
+ * The \l{Theme3D::colorStyle}{Theme3D.colorStyle} must be set to
+ * \c ColorStyleUniform to use this property.
+ * \note If the property is set and the theme is changed,
+ * the rowColors list is not cleared automatically.
+ *
+ * \sa Q3DTheme::ColorStyleUniform
+ */
+
+/*!
+ * Constructsa bar 3D series with the parent \a parent.
+ */
+QBar3DSeries::QBar3DSeries(QObject *parent) :
+ QAbstract3DSeries(new QBar3DSeriesPrivate(this), parent)
+{
+ // Default proxy
+ dptr()->setDataProxy(new QBarDataProxy);
+ dptr()->connectSignals();
+}
+
+/*!
+ * Constructs a bar 3D series with the data proxy \a dataProxy and the parent
+ * \a parent.
+ */
+QBar3DSeries::QBar3DSeries(QBarDataProxy *dataProxy, QObject *parent) :
+ QAbstract3DSeries(new QBar3DSeriesPrivate(this), parent)
+{
+ dptr()->setDataProxy(dataProxy);
+ dptr()->connectSignals();
+}
+
+/*!
+ * Deletes a bar 3D series.
+ */
+QBar3DSeries::~QBar3DSeries()
+{
+}
+
+/*!
+ * \property QBar3DSeries::dataProxy
+ *
+ * \brief The active data proxy.
+ *
+ * The series assumes ownership of any proxy set to it and deletes any
+ * previously set proxy when a new one is added. The proxy cannot be null or
+ * set to another series.
+ */
+void QBar3DSeries::setDataProxy(QBarDataProxy *proxy)
+{
+ d_ptr->setDataProxy(proxy);
+}
+
+QBarDataProxy *QBar3DSeries::dataProxy() const
+{
+ return static_cast<QBarDataProxy *>(d_ptr->dataProxy());
+}
+
+/*!
+ * \property QBar3DSeries::selectedBar
+ *
+ * \brief The bar in the series that is selected.
+ *
+ */
+
+/*!
+ * Selects the bar at the \a position position, specified as a row and column in
+ * the data array of the series.
+ *
+ * Only one bar can be selected at a time.
+ *
+ * To clear selection from this series, invalidSelectionPosition() is set as
+ * \a position.
+ *
+ * If this series is added to a graph, the graph can adjust the selection according to user
+ * interaction or if it becomes invalid. Selecting a bar on another added series will also
+ * clear the selection.
+ *
+ * Removing rows from or inserting rows to the series before the row of the selected bar
+ * will adjust the selection so that the same bar will stay selected.
+ *
+ * \sa QAbstract3DGraph::clearSelection()
+ */
+void QBar3DSeries::setSelectedBar(const QPoint &position)
+{
+ // Don't do this in private to avoid loops, as that is used for callback from controller.
+ if (d_ptr->m_controller)
+ static_cast<Bars3DController *>(d_ptr->m_controller)->setSelectedBar(position, this, true);
+ else
+ dptr()->setSelectedBar(position);
+}
+
+QPoint QBar3DSeries::selectedBar() const
+{
+ return dptrc()->m_selectedBar;
+}
+
+/*!
+ * Returns an invalid position for selection. This position is set to the
+ * selectedBar property to clear the selection from this series.
+ *
+ * \sa QAbstract3DGraph::clearSelection()
+ */
+QPoint QBar3DSeries::invalidSelectionPosition()
+{
+ return Bars3DController::invalidSelectionPosition();
+}
+
+static inline float quaternionAngle(const QQuaternion &rotation)
+{
+ return qRadiansToDegrees(qAcos(rotation.scalar())) * 2.f;
+}
+
+/*!
+ \property QBar3DSeries::meshAngle
+
+ \brief The series rotation angle in degrees.
+
+ Setting this property is equivalent to the following call:
+
+ \code
+ setMeshRotation(QQuaternion::fromAxisAndAngle(0.0f, 1.0f, 0.0f, angle))
+ \endcode
+
+ \note When reading this property, it is calculated from the
+ QAbstract3DSeries::meshRotation value using floating point precision
+ and always returns a value from zero to 360 degrees.
+
+ \sa QAbstract3DSeries::meshRotation
+ */
+void QBar3DSeries::setMeshAngle(float angle)
+{
+ setMeshRotation(QQuaternion::fromAxisAndAngle(upVector, angle));
+}
+
+float QBar3DSeries::meshAngle() const
+{
+ QQuaternion rotation = meshRotation();
+
+ if (rotation.isIdentity() || rotation.x() != 0.0f || rotation.z() != 0.0f)
+ return 0.0f;
+ else
+ return quaternionAngle(rotation);
+}
+
+/*!
+ * \property QBar3DSeries::rowColors
+ *
+ * \brief The list of row colors in the series.
+ *
+ * This property can be used to color
+ * the rows of the series in different colors.
+ * The Q3DTheme::ColorStyle must be set to
+ * Q3DTheme::ColorStyleUniform to use this property.
+ *
+ * \sa Q3DTheme::ColorStyleUniform
+ */
+void QBar3DSeries::setRowColors(const QList<QColor> &colors)
+{
+ dptr()->setRowColors(colors);
+}
+QList<QColor> QBar3DSeries::rowColors() const
+{
+ return dptrc()->m_rowColors;
+}
+/*!
+ * \internal
+ */
+QBar3DSeriesPrivate *QBar3DSeries::dptr()
+{
+ return static_cast<QBar3DSeriesPrivate *>(d_ptr.data());
+}
+
+/*!
+ * \internal
+ */
+const QBar3DSeriesPrivate *QBar3DSeries::dptrc() const
+{
+ return static_cast<const QBar3DSeriesPrivate *>(d_ptr.data());
+}
+
+// QBar3DSeriesPrivate
+
+QBar3DSeriesPrivate::QBar3DSeriesPrivate(QBar3DSeries *q)
+ : QAbstract3DSeriesPrivate(q, QAbstract3DSeries::SeriesTypeBar),
+ m_selectedBar(Bars3DController::invalidSelectionPosition())
+{
+ m_itemLabelFormat = QStringLiteral("@valueLabel");
+ m_mesh = QAbstract3DSeries::MeshBevelBar;
+}
+
+QBar3DSeriesPrivate::~QBar3DSeriesPrivate()
+{
+}
+
+QBar3DSeries *QBar3DSeriesPrivate::qptr()
+{
+ return static_cast<QBar3DSeries *>(q_ptr);
+}
+
+void QBar3DSeriesPrivate::setDataProxy(QAbstractDataProxy *proxy)
+{
+ Q_ASSERT(proxy->type() == QAbstractDataProxy::DataTypeBar);
+
+ QAbstract3DSeriesPrivate::setDataProxy(proxy);
+
+ emit qptr()->dataProxyChanged(static_cast<QBarDataProxy *>(proxy));
+}
+
+void QBar3DSeriesPrivate::connectControllerAndProxy(Abstract3DController *newController)
+{
+ QBarDataProxy *barDataProxy = static_cast<QBarDataProxy *>(m_dataProxy);
+
+ if (m_controller && barDataProxy) {
+ // Disconnect old controller/old proxy
+ QObject::disconnect(barDataProxy, 0, m_controller, 0);
+ QObject::disconnect(q_ptr, 0, m_controller, 0);
+ }
+
+ if (newController && barDataProxy) {
+ Bars3DController *controller = static_cast<Bars3DController *>(newController);
+ QObject::connect(barDataProxy, &QBarDataProxy::arrayReset, controller,
+ &Bars3DController::handleArrayReset);
+ QObject::connect(barDataProxy, &QBarDataProxy::rowsAdded, controller,
+ &Bars3DController::handleRowsAdded);
+ QObject::connect(barDataProxy, &QBarDataProxy::rowsChanged, controller,
+ &Bars3DController::handleRowsChanged);
+ QObject::connect(barDataProxy, &QBarDataProxy::rowsRemoved, controller,
+ &Bars3DController::handleRowsRemoved);
+ QObject::connect(barDataProxy, &QBarDataProxy::rowsInserted, controller,
+ &Bars3DController::handleRowsInserted);
+ QObject::connect(barDataProxy, &QBarDataProxy::itemChanged, controller,
+ &Bars3DController::handleItemChanged);
+ QObject::connect(barDataProxy, &QBarDataProxy::rowLabelsChanged, controller,
+ &Bars3DController::handleDataRowLabelsChanged);
+ QObject::connect(barDataProxy, &QBarDataProxy::columnLabelsChanged, controller,
+ &Bars3DController::handleDataColumnLabelsChanged);
+ QObject::connect(qptr(), &QBar3DSeries::dataProxyChanged, controller,
+ &Bars3DController::handleArrayReset);
+ QObject::connect(qptr(), &QBar3DSeries::rowColorsChanged, controller,
+ &Bars3DController::handleRowColorsChanged);
+ }
+}
+
+void QBar3DSeriesPrivate::createItemLabel()
+{
+ static const QString rowIndexTag(QStringLiteral("@rowIdx"));
+ static const QString rowLabelTag(QStringLiteral("@rowLabel"));
+ static const QString rowTitleTag(QStringLiteral("@rowTitle"));
+ static const QString colIndexTag(QStringLiteral("@colIdx"));
+ static const QString colLabelTag(QStringLiteral("@colLabel"));
+ static const QString colTitleTag(QStringLiteral("@colTitle"));
+ static const QString valueTitleTag(QStringLiteral("@valueTitle"));
+ static const QString valueLabelTag(QStringLiteral("@valueLabel"));
+ static const QString seriesNameTag(QStringLiteral("@seriesName"));
+
+ if (m_selectedBar == QBar3DSeries::invalidSelectionPosition()) {
+ m_itemLabel = QString();
+ return;
+ }
+
+ QLocale locale(QLocale::c());
+ if (m_controller)
+ locale = m_controller->locale();
+ else
+ return;
+
+ QCategory3DAxis *categoryAxisZ = static_cast<QCategory3DAxis *>(m_controller->axisZ());
+ QCategory3DAxis *categoryAxisX = static_cast<QCategory3DAxis *>(m_controller->axisX());
+ QValue3DAxis *valueAxis = static_cast<QValue3DAxis *>(m_controller->axisY());
+ qreal selectedBarValue = qreal(qptr()->dataProxy()->itemAt(m_selectedBar)->value());
+
+ // Custom format expects printf format specifier. There is no tag for it.
+ m_itemLabel = valueAxis->formatter()->stringForValue(selectedBarValue, m_itemLabelFormat);
+
+ int selBarPosRow = m_selectedBar.x();
+ int selBarPosCol = m_selectedBar.y();
+ m_itemLabel.replace(rowIndexTag, locale.toString(selBarPosRow));
+ if (categoryAxisZ->labels().size() > selBarPosRow)
+ m_itemLabel.replace(rowLabelTag, categoryAxisZ->labels().at(selBarPosRow));
+ else
+ m_itemLabel.replace(rowLabelTag, QString());
+ m_itemLabel.replace(rowTitleTag, categoryAxisZ->title());
+ m_itemLabel.replace(colIndexTag, locale.toString(selBarPosCol));
+ if (categoryAxisX->labels().size() > selBarPosCol)
+ m_itemLabel.replace(colLabelTag, categoryAxisX->labels().at(selBarPosCol));
+ else
+ m_itemLabel.replace(colLabelTag, QString());
+ m_itemLabel.replace(colTitleTag, categoryAxisX->title());
+ m_itemLabel.replace(valueTitleTag, valueAxis->title());
+
+ if (m_itemLabel.contains(valueLabelTag)) {
+ QString valueLabelText = valueAxis->formatter()->stringForValue(selectedBarValue,
+ valueAxis->labelFormat());
+ m_itemLabel.replace(valueLabelTag, valueLabelText);
+ }
+
+ m_itemLabel.replace(seriesNameTag, m_name);
+}
+
+void QBar3DSeriesPrivate::handleMeshRotationChanged(const QQuaternion &rotation)
+{
+ emit qptr()->meshAngleChanged(quaternionAngle(rotation));
+}
+
+void QBar3DSeriesPrivate::setSelectedBar(const QPoint &position)
+{
+ if (position != m_selectedBar) {
+ markItemLabelDirty();
+ m_selectedBar = position;
+ emit qptr()->selectedBarChanged(m_selectedBar);
+ }
+}
+
+void QBar3DSeriesPrivate::connectSignals()
+{
+ QObject::connect(q_ptr, &QAbstract3DSeries::meshRotationChanged, this,
+ &QBar3DSeriesPrivate::handleMeshRotationChanged);
+}
+
+void QBar3DSeriesPrivate::setRowColors(const QList<QColor> &colors)
+{
+ if (m_rowColors != colors) {
+ m_rowColors = colors;
+ emit qptr()->rowColorsChanged(m_rowColors);
+ }
+}
+
+QT_END_NAMESPACE
diff --git a/src/graphs/data/qbar3dseries.h b/src/graphs/data/qbar3dseries.h
new file mode 100644
index 0000000..671fdf6
--- /dev/null
+++ b/src/graphs/data/qbar3dseries.h
@@ -0,0 +1,60 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QBAR3DSERIES_H
+#define QBAR3DSERIES_H
+
+#include <QtGraphs/qabstract3dseries.h>
+#include <QtGraphs/qbardataproxy.h>
+#include <QtCore/QPoint>
+
+QT_BEGIN_NAMESPACE
+
+class QBar3DSeriesPrivate;
+
+class Q_GRAPHS_EXPORT QBar3DSeries : public QAbstract3DSeries
+{
+ Q_OBJECT
+ Q_PROPERTY(QBarDataProxy *dataProxy READ dataProxy WRITE setDataProxy NOTIFY dataProxyChanged)
+ Q_PROPERTY(QPoint selectedBar READ selectedBar WRITE setSelectedBar NOTIFY selectedBarChanged)
+ Q_PROPERTY(float meshAngle READ meshAngle WRITE setMeshAngle NOTIFY meshAngleChanged)
+ Q_PROPERTY(QList<QColor> rowColors READ rowColors WRITE setRowColors NOTIFY rowColorsChanged)
+
+public:
+ explicit QBar3DSeries(QObject *parent = nullptr);
+ explicit QBar3DSeries(QBarDataProxy *dataProxy, QObject *parent = nullptr);
+ virtual ~QBar3DSeries();
+
+ void setDataProxy(QBarDataProxy *proxy);
+ QBarDataProxy *dataProxy() const;
+
+ void setSelectedBar(const QPoint &position);
+ QPoint selectedBar() const;
+ static QPoint invalidSelectionPosition();
+
+ void setMeshAngle(float angle);
+ float meshAngle() const;
+
+ QList<QColor> rowColors() const;
+ void setRowColors(const QList<QColor> &colors);
+
+Q_SIGNALS:
+ void dataProxyChanged(QBarDataProxy *proxy);
+ void selectedBarChanged(const QPoint &position);
+ void meshAngleChanged(float angle);
+ void rowColorsChanged(const QList<QColor> &rowcolors);
+
+protected:
+ QBar3DSeriesPrivate *dptr();
+ const QBar3DSeriesPrivate *dptrc() const;
+
+private:
+ Q_DISABLE_COPY(QBar3DSeries)
+
+ friend class Bars3DController;
+ friend class QQuickGraphsBars;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/data/qbar3dseries_p.h b/src/graphs/data/qbar3dseries_p.h
new file mode 100644
index 0000000..18a2452
--- /dev/null
+++ b/src/graphs/data/qbar3dseries_p.h
@@ -0,0 +1,54 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QtGraphs API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#ifndef QBAR3DSERIES_P_H
+#define QBAR3DSERIES_P_H
+
+#include "qbar3dseries.h"
+#include "qabstract3dseries_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class QBar3DSeriesPrivate : public QAbstract3DSeriesPrivate
+{
+ Q_OBJECT
+public:
+ QBar3DSeriesPrivate(QBar3DSeries *q);
+ virtual ~QBar3DSeriesPrivate();
+
+ void setDataProxy(QAbstractDataProxy *proxy) override;
+ void connectControllerAndProxy(Abstract3DController *newController) override;
+ void createItemLabel() override;
+
+ void handleMeshRotationChanged(const QQuaternion &rotation);
+
+ void setSelectedBar(const QPoint &position);
+
+ void connectSignals();
+
+ void setRowColors(const QList<QColor> &colors);
+
+private:
+ QBar3DSeries *qptr();
+
+ QPoint m_selectedBar;
+
+ QList<QColor> m_rowColors;
+
+private:
+ friend class QBar3DSeries;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/data/qbardataitem.cpp b/src/graphs/data/qbardataitem.cpp
new file mode 100644
index 0000000..a2f299f
--- /dev/null
+++ b/src/graphs/data/qbardataitem.cpp
@@ -0,0 +1,116 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qbardataitem_p.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ * \class QBarDataItem
+ * \inmodule QtGraphs
+ * \brief The QBarDataItem class provides a container for resolved data to be added to bar graphs.
+ *
+ * A bar data item holds the data for a single rendered bar in a graph.
+ * Bar data proxies parse data into QBarDataItem instances for bar graphs.
+ *
+ * \sa QBarDataProxy, {Qt Graphs C++ Classes}
+ */
+
+/*!
+ * Constructs a bar data item.
+ */
+QBarDataItem::QBarDataItem()
+ : d_ptr(0), // private data doesn't exist by default (optimization)
+ m_value(0.0f),
+ m_angle(0.0f)
+{
+}
+
+/*!
+ * Constructs a bar data item with the value \a value.
+ */
+QBarDataItem::QBarDataItem(float value)
+ : d_ptr(0),
+ m_value(value),
+ m_angle(0.0f)
+{
+}
+
+/*!
+ * Constructs a bar data item with the value \a value and angle \a angle.
+ */
+QBarDataItem::QBarDataItem(float value, float angle)
+ : d_ptr(0),
+ m_value(value),
+ m_angle(angle)
+{
+}
+
+/*!
+ * Constructs a copy of \a other.
+ */
+QBarDataItem::QBarDataItem(const QBarDataItem &other)
+{
+ operator=(other);
+}
+
+/*!
+ * Deletes a bar data item.
+ */
+QBarDataItem::~QBarDataItem()
+{
+ delete d_ptr;
+}
+
+/*!
+ * Assigns a copy of \a other to this object.
+ */
+QBarDataItem &QBarDataItem::operator=(const QBarDataItem &other)
+{
+ m_value = other.m_value;
+ m_angle = other.m_angle;
+ if (other.d_ptr)
+ createExtraData();
+ else
+ d_ptr = 0;
+ return *this;
+}
+
+/*!
+ * \fn void QBarDataItem::setValue(float val)
+ * Sets the value \a val to this data item.
+ */
+
+/*!
+ * \fn float QBarDataItem::value() const
+ * Returns the value of this data item.
+ */
+
+/*!
+ * \fn void QBarDataItem::setRotation(float angle)
+ * Sets the rotation angle \a angle in degrees for this data item.
+ */
+
+/*!
+ * \fn float QBarDataItem::rotation() const
+ * Returns the rotation angle in degrees for this data item.
+ */
+
+/*!
+ * \internal
+ */
+void QBarDataItem::createExtraData()
+{
+ if (!d_ptr)
+ d_ptr = new QBarDataItemPrivate;
+}
+
+QBarDataItemPrivate::QBarDataItemPrivate()
+{
+}
+
+QBarDataItemPrivate::~QBarDataItemPrivate()
+{
+}
+
+QT_END_NAMESPACE
diff --git a/src/graphs/data/qbardataitem.h b/src/graphs/data/qbardataitem.h
new file mode 100644
index 0000000..ce5295d
--- /dev/null
+++ b/src/graphs/data/qbardataitem.h
@@ -0,0 +1,41 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QBARDATAITEM_H
+#define QBARDATAITEM_H
+
+#include <QtGraphs/qgraphsglobal.h>
+
+QT_BEGIN_NAMESPACE
+
+class QBarDataItemPrivate;
+
+class Q_GRAPHS_EXPORT QBarDataItem
+{
+public:
+ QBarDataItem();
+ QBarDataItem(float value);
+ QBarDataItem(float value, float angle);
+ QBarDataItem(const QBarDataItem &other);
+ ~QBarDataItem();
+
+ QBarDataItem &operator=(const QBarDataItem &other);
+
+ inline void setValue(float val) { m_value = val; }
+ inline float value() const { return m_value; }
+ inline void setRotation(float angle) { m_angle = angle; }
+ inline float rotation() const { return m_angle; }
+
+protected:
+ void createExtraData();
+
+ QBarDataItemPrivate *d_ptr;
+
+private:
+ float m_value;
+ float m_angle;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/data/qbardataitem_p.h b/src/graphs/data/qbardataitem_p.h
new file mode 100644
index 0000000..86f797b
--- /dev/null
+++ b/src/graphs/data/qbardataitem_p.h
@@ -0,0 +1,31 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QtGraphs API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#ifndef QBARDATAITEM_P_H
+#define QBARDATAITEM_P_H
+
+#include "graphsglobal_p.h"
+#include "qbardataitem.h"
+
+QT_BEGIN_NAMESPACE
+
+class QBarDataItemPrivate
+{
+public:
+ QBarDataItemPrivate();
+ virtual ~QBarDataItemPrivate();
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/data/qbardataproxy.cpp b/src/graphs/data/qbardataproxy.cpp
new file mode 100644
index 0000000..ce3f981
--- /dev/null
+++ b/src/graphs/data/qbardataproxy.cpp
@@ -0,0 +1,786 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qbardataproxy_p.h"
+#include "qbar3dseries_p.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ * \class QBarDataProxy
+ * \inmodule QtGraphs
+ * \brief The QBarDataProxy class is the data proxy for a 3D bars graph.
+ *
+ * A bar data proxy handles adding, inserting, changing, and removing rows of
+ * data.
+ *
+ * The data array is a list of vectors (rows) of QBarDataItem instances.
+ * Each row can contain a different number of items or even be null.
+ *
+ * QBarDataProxy takes ownership of all QtGraphs::QBarDataRow objects
+ * passed to it, whether directly or in a QtGraphs::QBarDataArray container.
+ * If bar data row pointers are used to directly modify data after adding the
+ * array to the proxy, the appropriate signal must be emitted to update the
+ * graph.
+ *
+ * QBarDataProxy optionally keeps track of row and column labels, which QCategory3DAxis can utilize
+ * to show axis labels. The row and column labels are stored in a separate array from the data and
+ * row manipulation methods provide alternate versions that do not affect the row labels.
+ * This enables the option of having row labels that relate to the position of the data in the
+ * array rather than the data itself.
+ *
+ * \sa {Qt Graphs Data Handling}
+ */
+
+/*!
+ * \typedef QBarDataRow
+ * \relates QBarDataProxy
+ *
+ * A list of \l {QBarDataItem} objects.
+ */
+
+/*!
+ * \typedef QBarDataArray
+ * \relates QBarDataProxy
+ *
+ * A list of pointers to \l {QBarDataRow} objects.
+ */
+
+/*!
+ * \qmltype BarDataProxy
+ * \inqmlmodule QtGraphs
+ * \ingroup graphs_qml
+ * \instantiates QBarDataProxy
+ * \inherits AbstractDataProxy
+ * \brief The data proxy for a 3D bars graph.
+ *
+ * This type handles adding, inserting, changing, and removing rows of data with Qt Quick 2.
+ *
+ * This type is uncreatable, but contains properties that are exposed via subtypes.
+ *
+ * For a more complete description, see QBarDataProxy.
+ *
+ * \sa ItemModelBarDataProxy, {Qt Graphs Data Handling}
+ */
+
+/*!
+ * \qmlproperty int BarDataProxy::rowCount
+ * The number of rows in the array.
+ */
+
+/*!
+ * \qmlproperty list BarDataProxy::rowLabels
+ *
+ * The optional row labels for the array. Indexes in this array match the row
+ * indexes in the data array.
+ * If the list is shorter than the number of rows, all rows will not get labels.
+ */
+
+/*!
+ * \qmlproperty list BarDataProxy::columnLabels
+ *
+ * The optional column labels for the array. Indexes in this array match column indexes in rows.
+ * If the list is shorter than the longest row, all columns will not get labels.
+ */
+
+/*!
+ * \qmlproperty Bar3DSeries BarDataProxy::series
+ *
+ * The series this proxy is attached to.
+ */
+
+/*!
+ * Constructs a bar data proxy with the given \a parent.
+ */
+QBarDataProxy::QBarDataProxy(QObject *parent) :
+ QAbstractDataProxy(new QBarDataProxyPrivate(this), parent)
+{
+}
+
+/*!
+ * \internal
+ */
+QBarDataProxy::QBarDataProxy(QBarDataProxyPrivate *d, QObject *parent) :
+ QAbstractDataProxy(d, parent)
+{
+}
+
+/*!
+ * Deletes the bar data proxy.
+ */
+QBarDataProxy::~QBarDataProxy()
+{
+}
+
+/*!
+ * \property QBarDataProxy::series
+ *
+ * \brief The series this proxy is attached to.
+ */
+QBar3DSeries *QBarDataProxy::series() const
+{
+ return static_cast<QBar3DSeries *>(d_ptr->series());
+}
+
+/*!
+ * Clears the existing array and row and column labels.
+ */
+void QBarDataProxy::resetArray()
+{
+ resetArray(0, QStringList(), QStringList());
+ emit rowCountChanged(rowCount());
+ emit colCountChanged(colCount());
+}
+
+/*!
+ * Takes ownership of the array \a newArray. Clears the existing array if the
+ * new array differs from it. If the arrays are the same, this function
+ * just triggers the arrayReset() signal.
+ *
+ * Passing a null array deletes the old array and creates a new empty array.
+ * Row and column labels are not affected.
+ */
+void QBarDataProxy::resetArray(QBarDataArray *newArray)
+{
+ dptr()->resetArray(newArray, 0, 0);
+ emit arrayReset();
+ emit rowCountChanged(rowCount());
+ emit colCountChanged(colCount());
+}
+
+/*!
+ * Takes ownership of the array \a newArray. Clears the existing array if the
+ * new array differs from it. If the arrays are the same, this function
+ * just triggers the arrayReset() signal.
+ *
+ * Passing a null array deletes the old array and creates a new empty array.
+ *
+ * The \a rowLabels and \a columnLabels lists specify the new labels for rows and columns.
+ */
+void QBarDataProxy::resetArray(QBarDataArray *newArray, const QStringList &rowLabels,
+ const QStringList &columnLabels)
+{
+ dptr()->resetArray(newArray, &rowLabels, &columnLabels);
+ emit arrayReset();
+ emit rowCountChanged(rowCount());
+ emit colCountChanged(colCount());
+}
+
+/*!
+ * Changes an existing row by replacing the row at the position \a rowIndex
+ * with the new row specified by \a row. The new row can be
+ * the same as the existing row already stored at \a rowIndex.
+ * Existing row labels are not affected.
+ */
+void QBarDataProxy::setRow(int rowIndex, QBarDataRow *row)
+{
+ dptr()->setRow(rowIndex, row, 0);
+ emit rowsChanged(rowIndex, 1);
+}
+
+/*!
+ * Changes an existing row by replacing the row at the position \a rowIndex
+ * with the new row specified by \a row. The new row can be
+ * the same as the existing row already stored at \a rowIndex.
+ * Changes the row label to \a label.
+ */
+void QBarDataProxy::setRow(int rowIndex, QBarDataRow *row, const QString &label)
+{
+ dptr()->setRow(rowIndex, row, &label);
+ emit rowsChanged(rowIndex, 1);
+}
+
+/*!
+ * Changes existing rows by replacing the rows starting at the position
+ * \a rowIndex with the new rows specifies by \a rows.
+ * Existing row labels are not affected. The rows in the \a rows array can be
+ * the same as the existing rows already stored at \a rowIndex.
+ */
+void QBarDataProxy::setRows(int rowIndex, const QBarDataArray &rows)
+{
+ dptr()->setRows(rowIndex, rows, 0);
+ emit rowsChanged(rowIndex, rows.size());
+}
+
+/*!
+ * Changes existing rows by replacing the rows starting at the position
+ * \a rowIndex with the new rows specifies by \a rows.
+ * The row labels are changed to \a labels. The rows in the \a rows array can be
+ * the same as the existing rows already stored at \a rowIndex.
+ */
+void QBarDataProxy::setRows(int rowIndex, const QBarDataArray &rows, const QStringList &labels)
+{
+ dptr()->setRows(rowIndex, rows, &labels);
+ emit rowsChanged(rowIndex, rows.size());
+}
+
+/*!
+ * Changes a single item at the position specified by \a rowIndex and
+ * \a columnIndex to the item \a item.
+ */
+void QBarDataProxy::setItem(int rowIndex, int columnIndex, const QBarDataItem &item)
+{
+ dptr()->setItem(rowIndex, columnIndex, item);
+ emit itemChanged(rowIndex, columnIndex);
+}
+
+/*!
+ * Changes a single item at the position \a position to the item \a item.
+ * The x-value of \a position indicates the row and the y-value indicates the
+ * column.
+ */
+void QBarDataProxy::setItem(const QPoint &position, const QBarDataItem &item)
+{
+ setItem(position.x(), position.y(), item);
+}
+
+/*!
+ * Adds the new row \a row to the end of an array.
+ * Existing row labels are not affected.
+ *
+ * Returns the index of the added row.
+ */
+int QBarDataProxy::addRow(QBarDataRow *row)
+{
+ int addIndex = dptr()->addRow(row, 0);
+ emit rowsAdded(addIndex, 1);
+ emit rowCountChanged(rowCount());
+ emit colCountChanged(colCount());
+ return addIndex;
+}
+
+/*!
+ * Adds a the new row \a row with the label \a label to the end of an array.
+ *
+ * Returns the index of the added row.
+ */
+int QBarDataProxy::addRow(QBarDataRow *row, const QString &label)
+{
+ int addIndex = dptr()->addRow(row, &label);
+ emit rowsAdded(addIndex, 1);
+ emit rowCountChanged(rowCount());
+ emit colCountChanged(colCount());
+ return addIndex;
+}
+
+/*!
+ * Adds the new \a rows to the end of an array.
+ * Existing row labels are not affected.
+ *
+ * Returns the index of the first added row.
+ */
+int QBarDataProxy::addRows(const QBarDataArray &rows)
+{
+ int addIndex = dptr()->addRows(rows, 0);
+ emit rowsAdded(addIndex, rows.size());
+ emit rowCountChanged(rowCount());
+ emit colCountChanged(colCount());
+ return addIndex;
+}
+
+/*!
+ * Adds the new \a rows with \a labels to the end of the array.
+ *
+ * Returns the index of the first added row.
+ */
+int QBarDataProxy::addRows(const QBarDataArray &rows, const QStringList &labels)
+{
+ int addIndex = dptr()->addRows(rows, &labels);
+ emit rowsAdded(addIndex, rows.size());
+ emit rowCountChanged(rowCount());
+ emit colCountChanged(colCount());
+ return addIndex;
+}
+
+/*!
+ * Inserts the new row \a row into \a rowIndex.
+ * If \a rowIndex is equal to the array size, the rows are added to the end of
+ * the array.
+ * The existing row labels are not affected.
+ * \note The row labels array will be out of sync with the row array after this call
+ * if there were labeled rows beyond the inserted row.
+ */
+void QBarDataProxy::insertRow(int rowIndex, QBarDataRow *row)
+{
+ dptr()->insertRow(rowIndex, row, 0);
+ emit rowsInserted(rowIndex, 1);
+ emit rowCountChanged(rowCount());
+ emit colCountChanged(colCount());
+}
+
+/*!
+ * Inserts the new row \a row with the label \a label into \a rowIndex.
+ * If \a rowIndex is equal to array size, rows are added to the end of the
+ * array.
+ */
+void QBarDataProxy::insertRow(int rowIndex, QBarDataRow *row, const QString &label)
+{
+ dptr()->insertRow(rowIndex, row, &label);
+ emit rowsInserted(rowIndex, 1);
+ emit rowCountChanged(rowCount());
+ emit colCountChanged(colCount());
+}
+
+/*!
+ * Inserts new \a rows into \a rowIndex.
+ * If \a rowIndex is equal to the array size, the rows are added to the end of
+ * the array. The existing row labels are not affected.
+ * \note The row labels array will be out of sync with the row array after this call
+ * if there were labeled rows beyond the inserted rows.
+ */
+void QBarDataProxy::insertRows(int rowIndex, const QBarDataArray &rows)
+{
+ dptr()->insertRows(rowIndex, rows, 0);
+ emit rowsInserted(rowIndex, rows.size());
+ emit rowCountChanged(rowCount());
+ emit colCountChanged(colCount());
+}
+
+/*!
+ * Inserts new \a rows with \a labels into \a rowIndex.
+ * If \a rowIndex is equal to the array size, the rows are added to the end of
+ * the array.
+ */
+void QBarDataProxy::insertRows(int rowIndex, const QBarDataArray &rows, const QStringList &labels)
+{
+ dptr()->insertRows(rowIndex, rows, &labels);
+ emit rowsInserted(rowIndex, rows.size());
+ emit rowCountChanged(rowCount());
+ emit colCountChanged(colCount());
+}
+
+/*!
+ * Removes the number of rows specified by \a removeCount starting at the
+ * position \a rowIndex. Attempting to remove rows past the end of the
+ * array does nothing. If \a removeLabels is \c true, the corresponding row
+ * labels are also removed. Otherwise, the row labels are not affected.
+ * \note If \a removeLabels is \c false, the row labels array will be out of
+ * sync with the row array if there are labeled rows beyond the removed rows.
+ */
+void QBarDataProxy::removeRows(int rowIndex, int removeCount, bool removeLabels)
+{
+ if (rowIndex < rowCount() && removeCount >= 1) {
+ dptr()->removeRows(rowIndex, removeCount, removeLabels);
+ emit rowsRemoved(rowIndex, removeCount);
+ emit rowCountChanged(rowCount());
+ emit colCountChanged(colCount());
+ }
+}
+
+/*!
+ * \property QBarDataProxy::colCount
+ *
+ * \brief The number of columns in the array.
+ */
+int QBarDataProxy::colCount() const
+{
+ if (dptrc()->m_dataArray->size() <= 0)
+ return 0;
+ return dptrc()->m_dataArray->at(0)->size();
+}
+
+/*!
+ * \property QBarDataProxy::rowCount
+ *
+ * \brief The number of rows in the array.
+ */
+int QBarDataProxy::rowCount() const
+{
+ return dptrc()->m_dataArray->size();
+}
+
+/*!
+ * \property QBarDataProxy::rowLabels
+ *
+ * \brief The optional row labels for the array.
+ *
+ * Indexes in this array match the row indexes in the data array.
+ * If the list is shorter than the number of rows, all rows will not get labels.
+ */
+QStringList QBarDataProxy::rowLabels() const
+{
+ return dptrc()->m_rowLabels;
+}
+
+void QBarDataProxy::setRowLabels(const QStringList &labels)
+{
+ if (dptr()->m_rowLabels != labels) {
+ dptr()->m_rowLabels = labels;
+ emit rowLabelsChanged();
+ }
+}
+
+/*!
+ * \property QBarDataProxy::columnLabels
+ *
+ * \brief The optional column labels for the array.
+ *
+ * Indexes in this array match column indexes in rows.
+ * If the list is shorter than the longest row, all columns will not get labels.
+ */
+QStringList QBarDataProxy::columnLabels() const
+{
+ return dptrc()->m_columnLabels;
+}
+
+void QBarDataProxy::setColumnLabels(const QStringList &labels)
+{
+ if (dptr()->m_columnLabels != labels) {
+ dptr()->m_columnLabels = labels;
+ emit columnLabelsChanged();
+ }
+}
+
+/*!
+ * Returns the pointer to the data array.
+ */
+const QBarDataArray *QBarDataProxy::array() const
+{
+ return dptrc()->m_dataArray;
+}
+
+/*!
+ * Returns the pointer to the row at the position \a rowIndex. It is guaranteed
+ * to be valid only until the next call that modifies data.
+ */
+const QBarDataRow *QBarDataProxy::rowAt(int rowIndex) const
+{
+ const QBarDataArray &dataArray = *dptrc()->m_dataArray;
+ Q_ASSERT(rowIndex >= 0 && rowIndex < dataArray.size());
+ return dataArray[rowIndex];
+}
+
+/*!
+ * Returns the pointer to the item at the position specified by \a rowIndex and
+ * \a columnIndex. It is guaranteed to be valid only
+ * until the next call that modifies data.
+ */
+const QBarDataItem *QBarDataProxy::itemAt(int rowIndex, int columnIndex) const
+{
+ const QBarDataArray &dataArray = *dptrc()->m_dataArray;
+ Q_ASSERT(rowIndex >= 0 && rowIndex < dataArray.size());
+ const QBarDataRow &dataRow = *dataArray[rowIndex];
+ Q_ASSERT(columnIndex >= 0 && columnIndex < dataRow.size());
+ return &dataRow.at(columnIndex);
+}
+
+/*!
+ * Returns the pointer to the item at the position \a position. The x-value of
+ * \a position indicates the row and the y-value indicates the column. The item
+ * is guaranteed to be valid only until the next call that modifies data.
+ */
+const QBarDataItem *QBarDataProxy::itemAt(const QPoint &position) const
+{
+ return itemAt(position.x(), position.y());
+}
+
+/*!
+ * \internal
+ */
+QBarDataProxyPrivate *QBarDataProxy::dptr()
+{
+ return static_cast<QBarDataProxyPrivate *>(d_ptr.data());
+}
+
+/*!
+ * \internal
+ */
+const QBarDataProxyPrivate *QBarDataProxy::dptrc() const
+{
+ return static_cast<const QBarDataProxyPrivate *>(d_ptr.data());
+}
+
+/*!
+ * \fn void QBarDataProxy::arrayReset()
+ *
+ * This signal is emitted when the data array is reset.
+ * If the contents of the whole array are changed without calling resetArray(),
+ * this signal needs to be emitted to update the graph.
+ */
+
+/*!
+ * \fn void QBarDataProxy::rowsAdded(int startIndex, int count)
+ *
+ * This signal is emitted when the number of rows specified by \a count is
+ * added starting at the position \a startIndex.
+ * If rows are added to the array without calling addRow() or addRows(),
+ * this signal needs to be emitted to update the graph.
+ */
+
+/*!
+ * \fn void QBarDataProxy::rowsChanged(int startIndex, int count)
+ *
+ * This signal is emitted when the number of rows specified by \a count is
+ * changed starting at the position \a startIndex.
+ * If rows are changed in the array without calling setRow() or setRows(),
+ * this signal needs to be emitted to update the graph.
+ */
+
+/*!
+ * \fn void QBarDataProxy::rowsRemoved(int startIndex, int count)
+ *
+ * This signal is emitted when the number of rows specified by \a count is
+ * removed starting at the position \a startIndex.
+ *
+ * The index is the current array size if the rows were removed from the end of
+ * the array. If rows are removed from the array without calling removeRows(),
+ * this signal needs to be emitted to update the graph.
+ */
+
+/*!
+ * \fn void QBarDataProxy::rowsInserted(int startIndex, int count)
+ *
+ * This signal is emitted when the number of rows specified by \a count is
+ * inserted at the position \a startIndex.
+ *
+ * If rows are inserted into the array without calling insertRow() or
+ * insertRows(), this signal needs to be emitted to update the graph.
+ */
+
+/*!
+ * \fn void QBarDataProxy::itemChanged(int rowIndex, int columnIndex)
+ *
+ * This signal is emitted when the item at the position specified by \a rowIndex
+ * and \a columnIndex changes.
+ * If the item is changed in the array without calling setItem(),
+ * this signal needs to be emitted to update the graph.
+ */
+
+// QBarDataProxyPrivate
+
+QBarDataProxyPrivate::QBarDataProxyPrivate(QBarDataProxy *q)
+ : QAbstractDataProxyPrivate(q, QAbstractDataProxy::DataTypeBar),
+ m_dataArray(new QBarDataArray)
+{
+}
+
+QBarDataProxyPrivate::~QBarDataProxyPrivate()
+{
+ clearArray();
+}
+
+void QBarDataProxyPrivate::resetArray(QBarDataArray *newArray, const QStringList *rowLabels,
+ const QStringList *columnLabels)
+{
+ if (rowLabels)
+ qptr()->setRowLabels(*rowLabels);
+ if (columnLabels)
+ qptr()->setColumnLabels(*columnLabels);
+
+ if (!newArray)
+ newArray = new QBarDataArray;
+
+ if (newArray != m_dataArray) {
+ clearArray();
+ m_dataArray = newArray;
+ }
+}
+
+void QBarDataProxyPrivate::setRow(int rowIndex, QBarDataRow *row, const QString *label)
+{
+ Q_ASSERT(rowIndex >= 0 && rowIndex < m_dataArray->size());
+
+ if (label)
+ fixRowLabels(rowIndex, 1, QStringList(*label), false);
+ if (row != m_dataArray->at(rowIndex)) {
+ clearRow(rowIndex);
+ (*m_dataArray)[rowIndex] = row;
+ }
+}
+
+void QBarDataProxyPrivate::setRows(int rowIndex, const QBarDataArray &rows,
+ const QStringList *labels)
+{
+ QBarDataArray &dataArray = *m_dataArray;
+ Q_ASSERT(rowIndex >= 0 && (rowIndex + rows.size()) <= dataArray.size());
+ if (labels)
+ fixRowLabels(rowIndex, rows.size(), *labels, false);
+ for (int i = 0; i < rows.size(); i++) {
+ if (rows.at(i) != dataArray.at(rowIndex)) {
+ clearRow(rowIndex);
+ dataArray[rowIndex] = rows.at(i);
+ }
+ rowIndex++;
+ }
+}
+
+void QBarDataProxyPrivate::setItem(int rowIndex, int columnIndex, const QBarDataItem &item)
+{
+ Q_ASSERT(rowIndex >= 0 && rowIndex < m_dataArray->size());
+ QBarDataRow &row = *(*m_dataArray)[rowIndex];
+ Q_ASSERT(columnIndex < row.size());
+ row[columnIndex] = item;
+}
+
+int QBarDataProxyPrivate::addRow(QBarDataRow *row, const QString *label)
+{
+ int currentSize = m_dataArray->size();
+ if (label)
+ fixRowLabels(currentSize, 1, QStringList(*label), false);
+ m_dataArray->append(row);
+ return currentSize;
+}
+
+int QBarDataProxyPrivate::addRows(const QBarDataArray &rows, const QStringList *labels)
+{
+ int currentSize = m_dataArray->size();
+ if (labels)
+ fixRowLabels(currentSize, rows.size(), *labels, false);
+ for (int i = 0; i < rows.size(); i++)
+ m_dataArray->append(rows.at(i));
+ return currentSize;
+}
+
+void QBarDataProxyPrivate::insertRow(int rowIndex, QBarDataRow *row, const QString *label)
+{
+ Q_ASSERT(rowIndex >= 0 && rowIndex <= m_dataArray->size());
+ if (label)
+ fixRowLabels(rowIndex, 1, QStringList(*label), true);
+ m_dataArray->insert(rowIndex, row);
+}
+
+void QBarDataProxyPrivate::insertRows(int rowIndex, const QBarDataArray &rows,
+ const QStringList *labels)
+{
+ Q_ASSERT(rowIndex >= 0 && rowIndex <= m_dataArray->size());
+ if (labels)
+ fixRowLabels(rowIndex, rows.size(), *labels, true);
+ for (int i = 0; i < rows.size(); i++)
+ m_dataArray->insert(rowIndex++, rows.at(i));
+}
+
+void QBarDataProxyPrivate::removeRows(int rowIndex, int removeCount, bool removeLabels)
+{
+ Q_ASSERT(rowIndex >= 0);
+ int maxRemoveCount = m_dataArray->size() - rowIndex;
+ removeCount = qMin(removeCount, maxRemoveCount);
+ bool labelsChanged = false;
+ for (int i = 0; i < removeCount; i++) {
+ clearRow(rowIndex);
+ m_dataArray->removeAt(rowIndex);
+ if (removeLabels && m_rowLabels.size() > rowIndex) {
+ m_rowLabels.removeAt(rowIndex);
+ labelsChanged = true;
+ }
+ }
+ if (labelsChanged)
+ emit qptr()->rowLabelsChanged();
+}
+
+QBarDataProxy *QBarDataProxyPrivate::qptr()
+{
+ return static_cast<QBarDataProxy *>(q_ptr);
+}
+
+void QBarDataProxyPrivate::clearRow(int rowIndex)
+{
+ if (m_dataArray->at(rowIndex)) {
+ delete m_dataArray->at(rowIndex);
+ (*m_dataArray)[rowIndex] = 0;
+ }
+}
+
+void QBarDataProxyPrivate::clearArray()
+{
+ for (int i = 0; i < m_dataArray->size(); i++)
+ clearRow(i);
+ m_dataArray->clear();
+ delete m_dataArray;
+}
+
+/*!
+ * \internal
+ * Fixes the row label array to include specified labels.
+ */
+void QBarDataProxyPrivate::fixRowLabels(int startIndex, int count, const QStringList &newLabels,
+ bool isInsert)
+{
+ bool changed = false;
+ int currentSize = m_rowLabels.size();
+
+ int newSize = newLabels.size();
+ if (startIndex >= currentSize) {
+ // Adding labels past old label array, create empty strings to fill intervening space
+ if (newSize) {
+ for (int i = currentSize; i < startIndex; i++)
+ m_rowLabels << QString();
+ // Doesn't matter if insert, append, or just change when there were no existing
+ // strings, just append new strings.
+ m_rowLabels << newLabels;
+ changed = true;
+ }
+ } else {
+ if (isInsert) {
+ int insertIndex = startIndex;
+ if (count)
+ changed = true;
+ for (int i = 0; i < count; i++) {
+ if (i < newSize)
+ m_rowLabels.insert(insertIndex++, newLabels.at(i));
+ else
+ m_rowLabels.insert(insertIndex++, QString());
+ }
+ } else {
+ // Either append or change, replace labels up to array end and then add new ones
+ int lastChangeIndex = count + startIndex;
+ int newIndex = 0;
+ for (int i = startIndex; i < lastChangeIndex; i++) {
+ if (i >= currentSize) {
+ // Label past the current size, so just append the new label
+ if (newSize < newIndex) {
+ changed = true;
+ m_rowLabels << newLabels.at(newIndex);
+ } else {
+ break; // No point appending empty strings, so just exit
+ }
+ } else if (newSize > newIndex) {
+ // Replace existing label
+ if (m_rowLabels.at(i) != newLabels.at(newIndex)) {
+ changed = true;
+ m_rowLabels[i] = newLabels.at(newIndex);
+ }
+ } else {
+ // No more new labels, so clear existing label
+ if (!m_rowLabels.at(i).isEmpty()) {
+ changed = true;
+ m_rowLabels[i] = QString();
+ }
+ }
+ newIndex++;
+ }
+ }
+ }
+ if (changed)
+ emit qptr()->rowLabelsChanged();
+}
+
+QPair<GLfloat, GLfloat> QBarDataProxyPrivate::limitValues(int startRow, int endRow,
+ int startColumn, int endColumn) const
+{
+ QPair<GLfloat, GLfloat> limits = qMakePair(0.0f, 0.0f);
+ endRow = qMin(endRow, m_dataArray->size() - 1);
+ for (int i = startRow; i <= endRow; i++) {
+ QBarDataRow *row = m_dataArray->at(i);
+ if (row) {
+ int lastColumn = qMin(endColumn, row->size() - 1);
+ for (int j = startColumn; j <= lastColumn; j++) {
+ const QBarDataItem &item = row->at(j);
+ float itemValue = item.value();
+ if (limits.second < itemValue)
+ limits.second = itemValue;
+ if (limits.first > itemValue)
+ limits.first = itemValue;
+ }
+ }
+ }
+ return limits;
+}
+
+void QBarDataProxyPrivate::setSeries(QAbstract3DSeries *series)
+{
+ QAbstractDataProxyPrivate::setSeries(series);
+ QBar3DSeries *barSeries = static_cast<QBar3DSeries *>(series);
+ emit qptr()->seriesChanged(barSeries);
+}
+
+QT_END_NAMESPACE
diff --git a/src/graphs/data/qbardataproxy.h b/src/graphs/data/qbardataproxy.h
new file mode 100644
index 0000000..a82598b
--- /dev/null
+++ b/src/graphs/data/qbardataproxy.h
@@ -0,0 +1,102 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QBARDATAPROXY_H
+#define QBARDATAPROXY_H
+
+#include <QtGraphs/qabstractdataproxy.h>
+#include <QtGraphs/qbardataitem.h>
+#include <QtCore/QList>
+#include <QtCore/QStringList>
+
+Q_MOC_INCLUDE(<QtGraphs/qbar3dseries.h>)
+
+QT_BEGIN_NAMESPACE
+
+class QBarDataProxyPrivate;
+class QBar3DSeries;
+
+typedef QList<QBarDataItem> QBarDataRow;
+typedef QList<QBarDataRow *> QBarDataArray;
+
+class Q_GRAPHS_EXPORT QBarDataProxy : public QAbstractDataProxy
+{
+ Q_OBJECT
+
+ Q_PROPERTY(int rowCount READ rowCount NOTIFY rowCountChanged)
+ Q_PROPERTY(int colCount READ colCount NOTIFY colCountChanged)
+ Q_PROPERTY(QStringList rowLabels READ rowLabels WRITE setRowLabels NOTIFY rowLabelsChanged)
+ Q_PROPERTY(QStringList columnLabels READ columnLabels WRITE setColumnLabels NOTIFY columnLabelsChanged)
+ Q_PROPERTY(QBar3DSeries *series READ series NOTIFY seriesChanged)
+public:
+ explicit QBarDataProxy(QObject *parent = nullptr);
+ virtual ~QBarDataProxy();
+
+ QBar3DSeries *series() const;
+ int rowCount() const;
+ int colCount() const;
+
+ QStringList rowLabels() const;
+ void setRowLabels(const QStringList &labels);
+ QStringList columnLabels() const;
+ void setColumnLabels(const QStringList &labels);
+
+ const QBarDataArray *array() const;
+ const QBarDataRow *rowAt(int rowIndex) const;
+ const QBarDataItem *itemAt(int rowIndex, int columnIndex) const;
+ const QBarDataItem *itemAt(const QPoint &position) const;
+
+ void resetArray();
+ void resetArray(QBarDataArray *newArray);
+ void resetArray(QBarDataArray *newArray, const QStringList &rowLabels,
+ const QStringList &columnLabels);
+
+ void setRow(int rowIndex, QBarDataRow *row);
+ void setRow(int rowIndex, QBarDataRow *row, const QString &label);
+ void setRows(int rowIndex, const QBarDataArray &rows);
+ void setRows(int rowIndex, const QBarDataArray &rows, const QStringList &labels);
+
+ void setItem(int rowIndex, int columnIndex, const QBarDataItem &item);
+ void setItem(const QPoint &position, const QBarDataItem &item);
+
+ int addRow(QBarDataRow *row);
+ int addRow(QBarDataRow *row, const QString &label);
+ int addRows(const QBarDataArray &rows);
+ int addRows(const QBarDataArray &rows, const QStringList &labels);
+
+ void insertRow(int rowIndex, QBarDataRow *row);
+ void insertRow(int rowIndex, QBarDataRow *row, const QString &label);
+ void insertRows(int rowIndex, const QBarDataArray &rows);
+ void insertRows(int rowIndex, const QBarDataArray &rows, const QStringList &labels);
+
+ void removeRows(int rowIndex, int removeCount, bool removeLabels = true);
+
+Q_SIGNALS:
+ void arrayReset();
+ void rowsAdded(int startIndex, int count);
+ void rowsChanged(int startIndex, int count);
+ void rowsRemoved(int startIndex, int count);
+ void rowsInserted(int startIndex, int count);
+ void itemChanged(int rowIndex, int columnIndex);
+
+ void rowCountChanged(int count);
+ void colCountChanged(int count);
+ void rowLabelsChanged();
+ void columnLabelsChanged();
+ void seriesChanged(QBar3DSeries *series);
+
+protected:
+ explicit QBarDataProxy(QBarDataProxyPrivate *d, QObject *parent = nullptr);
+ QBarDataProxyPrivate *dptr();
+ const QBarDataProxyPrivate *dptrc() const;
+
+private:
+ Q_DISABLE_COPY(QBarDataProxy)
+
+ friend class Bars3DController;
+ friend class QQuickGraphsBars;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/data/qbardataproxy_p.h b/src/graphs/data/qbardataproxy_p.h
new file mode 100644
index 0000000..97c22e9
--- /dev/null
+++ b/src/graphs/data/qbardataproxy_p.h
@@ -0,0 +1,61 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QtGraphs API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#ifndef QBARDATAPROXY_P_H
+#define QBARDATAPROXY_P_H
+
+#include "qbardataproxy.h"
+#include "qabstractdataproxy_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class QBarDataProxyPrivate : public QAbstractDataProxyPrivate
+{
+ Q_OBJECT
+public:
+ QBarDataProxyPrivate(QBarDataProxy *q);
+ virtual ~QBarDataProxyPrivate();
+
+ void resetArray(QBarDataArray *newArray, const QStringList *rowLabels,
+ const QStringList *columnLabels);
+ void setRow(int rowIndex, QBarDataRow *row, const QString *label);
+ void setRows(int rowIndex, const QBarDataArray &rows, const QStringList *labels);
+ void setItem(int rowIndex, int columnIndex, const QBarDataItem &item);
+ int addRow(QBarDataRow *row, const QString *label);
+ int addRows(const QBarDataArray &rows, const QStringList *labels);
+ void insertRow(int rowIndex, QBarDataRow *row, const QString *label);
+ void insertRows(int rowIndex, const QBarDataArray &rows, const QStringList *labels);
+ void removeRows(int rowIndex, int removeCount, bool removeLabels);
+
+ QPair<GLfloat, GLfloat> limitValues(int startRow, int startColumn, int rowCount,
+ int columnCount) const;
+
+ void setSeries(QAbstract3DSeries *series) override;
+
+private:
+ QBarDataProxy *qptr();
+ void clearRow(int rowIndex);
+ void clearArray();
+ void fixRowLabels(int startIndex, int count, const QStringList &newLabels, bool isInsert);
+
+ QBarDataArray *m_dataArray;
+ QStringList m_rowLabels;
+ QStringList m_columnLabels;
+
+private:
+ friend class QBarDataProxy;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/data/qcustom3ditem.cpp b/src/graphs/data/qcustom3ditem.cpp
new file mode 100644
index 0000000..7bbe9b2
--- /dev/null
+++ b/src/graphs/data/qcustom3ditem.cpp
@@ -0,0 +1,510 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qcustom3ditem_p.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ * \class QCustom3DItem
+ * \inmodule QtGraphs
+ * \brief The QCustom3DItem class adds a custom item to a graph.
+ *
+ * A custom item has a custom mesh, position, scaling, rotation, and an optional
+ * texture.
+ *
+ * \sa QAbstract3DGraph::addCustomItem()
+ */
+
+/*!
+ * \qmltype Custom3DItem
+ * \inqmlmodule QtGraphs
+ * \ingroup graphs_qml
+ * \instantiates QCustom3DItem
+ * \brief Adds a custom item to a graph.
+ *
+ * A custom item has a custom mesh, position, scaling, rotation, and an optional
+ * texture.
+ */
+
+/*! \qmlproperty string Custom3DItem::meshFile
+ *
+ * The item mesh file name. The item in the file must be in Wavefront OBJ format and include
+ * vertices, normals, and UVs. It also needs to be in triangles.
+ */
+
+/*! \qmlproperty string Custom3DItem::textureFile
+ *
+ * The texture file name for the item. If left unset, a solid gray texture will be
+ * used.
+ *
+ * \note To conserve memory, the QImage loaded from the file is cleared after a
+ * texture is created.
+ */
+
+/*! \qmlproperty vector3d Custom3DItem::position
+ *
+ * The item position as a \l vector3d type. Defaults to
+ * \c {vector3d(0.0, 0.0, 0.0)}.
+ *
+ * Item position is specified either in data coordinates or in absolute
+ * coordinates, depending on the value of the positionAbsolute property. When
+ * using absolute coordinates, values between \c{-1.0...1.0} are
+ * within axis ranges.
+ *
+ * \note Items positioned outside any axis range are not rendered if positionAbsolute is \c{false},
+ * unless the item is a Custom3DVolume that would be partially visible and scalingAbsolute is also
+ * \c{false}. In that case, the visible portion of the volume will be rendered.
+ *
+ * \sa positionAbsolute, scalingAbsolute
+ */
+
+/*! \qmlproperty bool Custom3DItem::positionAbsolute
+ *
+ * Defines whether item position is to be handled in data coordinates or in absolute
+ * coordinates. Defaults to \c{false}. Items with absolute coordinates will always be rendered,
+ * whereas items with data coordinates are only rendered if they are within axis ranges.
+ *
+ * \sa position
+ */
+
+/*! \qmlproperty vector3d Custom3DItem::scaling
+ *
+ * The item scaling as a \l vector3d type. Defaults to
+ * \c {vector3d(0.1, 0.1, 0.1)}.
+ *
+ * Item scaling is specified either in data values or in absolute values,
+ * depending on the value of the scalingAbsolute property. The default vector
+ * interpreted as absolute values sets the item to
+ * 10% of the height of the graph, provided the item mesh is normalized and the graph aspect ratios
+ * have not been changed from the defaults.
+ *
+ * \sa scalingAbsolute
+ */
+
+/*! \qmlproperty bool Custom3DItem::scalingAbsolute
+ *
+ * Defines whether item scaling is to be handled in data values or in absolute
+ * values. Defaults to \c{true}. Items with absolute scaling will be rendered at the same
+ * size, regardless of axis ranges. Items with data scaling will change their apparent size
+ * according to the axis ranges. If positionAbsolute is \c{true}, this property is ignored
+ * and scaling is interpreted as an absolute value. If the item has rotation, the data scaling
+ * is calculated on the unrotated item. Similarly, for Custom3DVolume items, the range clipping
+ * is calculated on the unrotated item.
+ *
+ * \note Only absolute scaling is supported for Custom3DLabel items or for custom items used in
+ * \l{AbstractGraph3D::polar}{polar} graphs.
+ *
+ * \note The custom item's mesh must be normalized to the range \c{[-1 ,1]}, or the data
+ * scaling will not be accurate.
+ *
+ * \sa scaling, positionAbsolute
+ */
+
+/*! \qmlproperty quaternion Custom3DItem::rotation
+ *
+ * The item rotation as a \l quaternion. Defaults to
+ * \c {quaternion(0.0, 0.0, 0.0, 0.0)}.
+ */
+
+/*! \qmlproperty bool Custom3DItem::visible
+ *
+ * The visibility of the item. Defaults to \c{true}.
+ */
+
+/*! \qmlproperty bool Custom3DItem::shadowCasting
+ *
+ * Defines whether shadow casting for the item is enabled. Defaults to \c{true}.
+ * If \c{false}, the item does not cast shadows regardless of
+ * \l{QAbstract3DGraph::ShadowQuality}{ShadowQuality}.
+ */
+
+/*!
+ * \qmlmethod void Custom3DItem::setRotationAxisAndAngle(vector3d axis, real angle)
+ *
+ * A convenience function to construct the rotation quaternion from \a axis and
+ * \a angle.
+ *
+ * \sa rotation
+ */
+
+/*!
+ * Constructs a custom 3D item with the specified \a parent.
+ */
+QCustom3DItem::QCustom3DItem(QObject *parent) :
+ QObject(parent),
+ d_ptr(new QCustom3DItemPrivate(this))
+{
+ setTextureImage(QImage());
+}
+
+/*!
+ * \internal
+ */
+QCustom3DItem::QCustom3DItem(QCustom3DItemPrivate *d, QObject *parent) :
+ QObject(parent),
+ d_ptr(d)
+{
+ setTextureImage(QImage());
+}
+
+/*!
+ * Constructs a custom 3D item with the specified \a meshFile, \a position, \a scaling,
+ * \a rotation, \a texture image, and optional \a parent.
+ */
+QCustom3DItem::QCustom3DItem(const QString &meshFile, const QVector3D &position,
+ const QVector3D &scaling, const QQuaternion &rotation,
+ const QImage &texture, QObject *parent) :
+ QObject(parent),
+ d_ptr(new QCustom3DItemPrivate(this, meshFile, position, scaling, rotation))
+{
+ setTextureImage(texture);
+}
+
+/*!
+ * Deletes the custom 3D item.
+ */
+QCustom3DItem::~QCustom3DItem()
+{
+}
+
+/*! \property QCustom3DItem::meshFile
+ *
+ * \brief The item mesh file name.
+ *
+ * The item in the file must be in Wavefront OBJ format and include
+ * vertices, normals, and UVs. It also needs to be in triangles.
+ */
+void QCustom3DItem::setMeshFile(const QString &meshFile)
+{
+ if (d_ptr->m_meshFile != meshFile) {
+ d_ptr->m_meshFile = meshFile;
+ d_ptr->m_dirtyBits.meshDirty = true;
+ emit meshFileChanged(meshFile);
+ emit d_ptr->needUpdate();
+ }
+}
+
+QString QCustom3DItem::meshFile() const
+{
+ return d_ptr->m_meshFile;
+}
+
+/*! \property QCustom3DItem::position
+ *
+ * \brief The item position as a QVector3D.
+ *
+ * Defaults to \c {QVector3D(0.0, 0.0, 0.0)}.
+ *
+ * Item position is specified either in data coordinates or in absolute
+ * coordinates, depending on the
+ * positionAbsolute property. When using absolute coordinates, values between \c{-1.0...1.0} are
+ * within axis ranges.
+ *
+ * \note Items positioned outside any axis range are not rendered if positionAbsolute is \c{false},
+ * unless the item is a QCustom3DVolume that would be partially visible and scalingAbsolute is also
+ * \c{false}. In that case, the visible portion of the volume will be rendered.
+ *
+ * \sa positionAbsolute
+ */
+void QCustom3DItem::setPosition(const QVector3D &position)
+{
+ if (d_ptr->m_position != position) {
+ d_ptr->m_position = position;
+ d_ptr->m_dirtyBits.positionDirty = true;
+ emit positionChanged(position);
+ emit d_ptr->needUpdate();
+ }
+}
+
+QVector3D QCustom3DItem::position() const
+{
+ return d_ptr->m_position;
+}
+
+/*! \property QCustom3DItem::positionAbsolute
+ *
+ * \brief Whether item position is to be handled in data coordinates or in absolute
+ * coordinates.
+ *
+ * Defaults to \c{false}. Items with absolute coordinates will always be rendered,
+ * whereas items with data coordinates are only rendered if they are within axis ranges.
+ *
+ * \sa position
+ */
+void QCustom3DItem::setPositionAbsolute(bool positionAbsolute)
+{
+ if (d_ptr->m_positionAbsolute != positionAbsolute) {
+ d_ptr->m_positionAbsolute = positionAbsolute;
+ d_ptr->m_dirtyBits.positionDirty = true;
+ emit positionAbsoluteChanged(positionAbsolute);
+ emit d_ptr->needUpdate();
+ }
+}
+
+bool QCustom3DItem::isPositionAbsolute() const
+{
+ return d_ptr->m_positionAbsolute;
+}
+
+/*! \property QCustom3DItem::scaling
+ *
+ * \brief The item scaling as a QVector3D.
+ *
+ * Defaults to \c {QVector3D(0.1, 0.1, 0.1)}.
+ *
+ * Item scaling is either in data values or in absolute values, depending on the
+ * scalingAbsolute property. The default vector interpreted as absolute values sets the item to
+ * 10% of the height of the graph, provided the item mesh is normalized and the graph aspect ratios
+ * have not been changed from the defaults.
+ *
+ * \sa scalingAbsolute
+ */
+void QCustom3DItem::setScaling(const QVector3D &scaling)
+{
+ if (d_ptr->m_scaling != scaling) {
+ d_ptr->m_scaling = scaling;
+ d_ptr->m_dirtyBits.scalingDirty = true;
+ emit scalingChanged(scaling);
+ emit d_ptr->needUpdate();
+ }
+}
+
+QVector3D QCustom3DItem::scaling() const
+{
+ return d_ptr->m_scaling;
+}
+
+/*! \property QCustom3DItem::scalingAbsolute
+ *
+ * \brief Whether item scaling is to be handled in data values or in absolute
+ * values.
+ *
+ * Defaults to \c{true}.
+ *
+ * Items with absolute scaling will be rendered at the same
+ * size, regardless of axis ranges. Items with data scaling will change their apparent size
+ * according to the axis ranges. If positionAbsolute is \c{true}, this property is ignored
+ * and scaling is interpreted as an absolute value. If the item has rotation, the data scaling
+ * is calculated on the unrotated item. Similarly, for QCustom3DVolume items, the range clipping
+ * is calculated on the unrotated item.
+ *
+ * \note Only absolute scaling is supported for QCustom3DLabel items or for custom items used in
+ * \l{QAbstract3DGraph::polar}{polar} graphs.
+ *
+ * \note The custom item's mesh must be normalized to the range \c{[-1 ,1]}, or the data
+ * scaling will not be accurate.
+ *
+ * \sa scaling, positionAbsolute
+ */
+void QCustom3DItem::setScalingAbsolute(bool scalingAbsolute)
+{
+ if (d_ptr->m_isLabelItem && !scalingAbsolute) {
+ qWarning() << __FUNCTION__ << "Data bounds are not supported for label items.";
+ } else if (d_ptr->m_scalingAbsolute != scalingAbsolute) {
+ d_ptr->m_scalingAbsolute = scalingAbsolute;
+ d_ptr->m_dirtyBits.scalingDirty = true;
+ emit scalingAbsoluteChanged(scalingAbsolute);
+ emit d_ptr->needUpdate();
+ }
+}
+
+bool QCustom3DItem::isScalingAbsolute() const
+{
+ return d_ptr->m_scalingAbsolute;
+}
+
+/*! \property QCustom3DItem::rotation
+ *
+ * \brief The item rotation as a QQuaternion.
+ *
+ * Defaults to \c {QQuaternion(0.0, 0.0, 0.0, 0.0)}.
+ */
+void QCustom3DItem::setRotation(const QQuaternion &rotation)
+{
+ if (d_ptr->m_rotation != rotation) {
+ d_ptr->m_rotation = rotation;
+ d_ptr->m_dirtyBits.rotationDirty = true;
+ emit rotationChanged(rotation);
+ emit d_ptr->needUpdate();
+ }
+}
+
+QQuaternion QCustom3DItem::rotation()
+{
+ return d_ptr->m_rotation;
+}
+
+/*! \property QCustom3DItem::visible
+ *
+ * \brief The visibility of the item.
+ *
+ * Defaults to \c{true}.
+ */
+void QCustom3DItem::setVisible(bool visible)
+{
+ if (d_ptr->m_visible != visible) {
+ d_ptr->m_visible = visible;
+ d_ptr->m_dirtyBits.visibleDirty = true;
+ emit visibleChanged(visible);
+ emit d_ptr->needUpdate();
+ }
+}
+
+bool QCustom3DItem::isVisible() const
+{
+ return d_ptr->m_visible;
+}
+
+
+/*! \property QCustom3DItem::shadowCasting
+ *
+ * \brief Whether shadow casting for the item is enabled.
+ *
+ * Defaults to \c{true}.
+ * If \c{false}, the item does not cast shadows regardless of QAbstract3DGraph::ShadowQuality.
+ */
+void QCustom3DItem::setShadowCasting(bool enabled)
+{
+ if (d_ptr->m_shadowCasting != enabled) {
+ d_ptr->m_shadowCasting = enabled;
+ d_ptr->m_dirtyBits.shadowCastingDirty = true;
+ emit shadowCastingChanged(enabled);
+ emit d_ptr->needUpdate();
+ }
+}
+
+bool QCustom3DItem::isShadowCasting() const
+{
+ return d_ptr->m_shadowCasting;
+}
+
+/*!
+ * A convenience function to construct the rotation quaternion from \a axis and
+ * \a angle.
+ *
+ * \sa rotation
+ */
+void QCustom3DItem::setRotationAxisAndAngle(const QVector3D &axis, float angle)
+{
+ setRotation(QQuaternion::fromAxisAndAngle(axis, angle));
+}
+
+/*!
+ * Sets the value of \a textureImage as a QImage for the item. The texture
+ * defaults to solid gray.
+ *
+ * \note To conserve memory, the given QImage is cleared after a texture is
+ * created.
+ */
+void QCustom3DItem::setTextureImage(const QImage &textureImage)
+{
+ if (textureImage != d_ptr->m_textureImage) {
+ if (textureImage.isNull()) {
+ // Make a solid gray texture
+ d_ptr->m_textureImage = QImage(2, 2, QImage::Format_RGB32);
+ d_ptr->m_textureImage.fill(Qt::gray);
+ } else {
+ d_ptr->m_textureImage = textureImage;
+ }
+
+ if (!d_ptr->m_textureFile.isEmpty()) {
+ d_ptr->m_textureFile.clear();
+ emit textureFileChanged(d_ptr->m_textureFile);
+ }
+ d_ptr->m_dirtyBits.textureDirty = true;
+ emit d_ptr->needUpdate();
+ }
+}
+
+/*! \property QCustom3DItem::textureFile
+ *
+ * \brief The texture file name for the item.
+ *
+ * If both this property and the texture image are unset, a solid
+ * gray texture will be used.
+ *
+ * \note To conserve memory, the QImage loaded from the file is cleared after a
+ * texture is created.
+ */
+void QCustom3DItem::setTextureFile(const QString &textureFile)
+{
+ if (d_ptr->m_textureFile != textureFile) {
+ d_ptr->m_textureFile = textureFile;
+ if (!textureFile.isEmpty()) {
+ d_ptr->m_textureImage = QImage(textureFile);
+ } else {
+ d_ptr->m_textureImage = QImage(2, 2, QImage::Format_RGB32);
+ d_ptr->m_textureImage.fill(Qt::gray);
+ }
+ emit textureFileChanged(textureFile);
+ d_ptr->m_dirtyBits.textureDirty = true;
+ emit d_ptr->needUpdate();
+ }
+}
+
+QString QCustom3DItem::textureFile() const
+{
+ return d_ptr->m_textureFile;
+}
+
+QCustom3DItemPrivate::QCustom3DItemPrivate(QCustom3DItem *q) :
+ q_ptr(q),
+ m_textureImage(QImage(1, 1, QImage::Format_ARGB32)),
+ m_position(QVector3D(0.0f, 0.0f, 0.0f)),
+ m_positionAbsolute(false),
+ m_scaling(QVector3D(0.1f, 0.1f, 0.1f)),
+ m_scalingAbsolute(true),
+ m_rotation(QQuaternion()),
+ m_visible(true),
+ m_shadowCasting(true),
+ m_isLabelItem(false),
+ m_isVolumeItem(false)
+{
+}
+
+QCustom3DItemPrivate::QCustom3DItemPrivate(QCustom3DItem *q, const QString &meshFile,
+ const QVector3D &position, const QVector3D &scaling,
+ const QQuaternion &rotation) :
+ q_ptr(q),
+ m_textureImage(QImage(1, 1, QImage::Format_ARGB32)),
+ m_meshFile(meshFile),
+ m_position(position),
+ m_positionAbsolute(false),
+ m_scaling(scaling),
+ m_scalingAbsolute(true),
+ m_rotation(rotation),
+ m_visible(true),
+ m_shadowCasting(true),
+ m_isLabelItem(false),
+ m_isVolumeItem(false)
+{
+}
+
+QCustom3DItemPrivate::~QCustom3DItemPrivate()
+{
+}
+
+QImage QCustom3DItemPrivate::textureImage()
+{
+ return m_textureImage;
+}
+
+void QCustom3DItemPrivate::clearTextureImage()
+{
+ m_textureImage = QImage();
+ m_textureFile.clear();
+}
+
+void QCustom3DItemPrivate::resetDirtyBits()
+{
+ m_dirtyBits.textureDirty = false;
+ m_dirtyBits.meshDirty = false;
+ m_dirtyBits.positionDirty = false;
+ m_dirtyBits.scalingDirty = false;
+ m_dirtyBits.rotationDirty = false;
+ m_dirtyBits.visibleDirty = false;
+ m_dirtyBits.shadowCastingDirty = false;
+}
+
+QT_END_NAMESPACE
diff --git a/src/graphs/data/qcustom3ditem.h b/src/graphs/data/qcustom3ditem.h
new file mode 100644
index 0000000..8235033
--- /dev/null
+++ b/src/graphs/data/qcustom3ditem.h
@@ -0,0 +1,92 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QCUSTOM3DITEM_H
+#define QCUSTOM3DITEM_H
+
+#include <QtGraphs/qgraphsglobal.h>
+#include <QtGui/QImage>
+#include <QtGui/QVector3D>
+#include <QtGui/QQuaternion>
+#include <QtCore/QObject>
+
+QT_BEGIN_NAMESPACE
+
+class QCustom3DItemPrivate;
+
+class Q_GRAPHS_EXPORT QCustom3DItem : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QString meshFile READ meshFile WRITE setMeshFile NOTIFY meshFileChanged)
+ Q_PROPERTY(QString textureFile READ textureFile WRITE setTextureFile NOTIFY textureFileChanged)
+ Q_PROPERTY(QVector3D position READ position WRITE setPosition NOTIFY positionChanged)
+ Q_PROPERTY(bool positionAbsolute READ isPositionAbsolute WRITE setPositionAbsolute NOTIFY positionAbsoluteChanged)
+ Q_PROPERTY(QVector3D scaling READ scaling WRITE setScaling NOTIFY scalingChanged)
+ Q_PROPERTY(QQuaternion rotation READ rotation WRITE setRotation NOTIFY rotationChanged)
+ Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibleChanged)
+ Q_PROPERTY(bool shadowCasting READ isShadowCasting WRITE setShadowCasting NOTIFY shadowCastingChanged)
+ Q_PROPERTY(bool scalingAbsolute READ isScalingAbsolute WRITE setScalingAbsolute NOTIFY scalingAbsoluteChanged)
+
+public:
+ explicit QCustom3DItem(QObject *parent = nullptr);
+ explicit QCustom3DItem(const QString &meshFile, const QVector3D &position,
+ const QVector3D &scaling, const QQuaternion &rotation,
+ const QImage &texture, QObject *parent = nullptr);
+ virtual ~QCustom3DItem();
+
+ void setMeshFile(const QString &meshFile);
+ QString meshFile() const;
+
+ void setTextureFile(const QString &textureFile);
+ QString textureFile() const;
+
+ void setPosition(const QVector3D &position);
+ QVector3D position() const;
+
+ void setPositionAbsolute(bool positionAbsolute);
+ bool isPositionAbsolute() const;
+
+ void setScaling(const QVector3D &scaling);
+ QVector3D scaling() const;
+
+ void setScalingAbsolute(bool scalingAbsolute);
+ bool isScalingAbsolute() const;
+
+ void setRotation(const QQuaternion &rotation);
+ QQuaternion rotation();
+
+ void setVisible(bool visible);
+ bool isVisible() const;
+
+ void setShadowCasting(bool enabled);
+ bool isShadowCasting() const;
+
+ Q_INVOKABLE void setRotationAxisAndAngle(const QVector3D &axis, float angle);
+
+ void setTextureImage(const QImage &textureImage);
+
+Q_SIGNALS:
+ void meshFileChanged(const QString &meshFile);
+ void textureFileChanged(const QString &textureFile);
+ void positionChanged(const QVector3D &position);
+ void positionAbsoluteChanged(bool positionAbsolute);
+ void scalingChanged(const QVector3D &scaling);
+ void rotationChanged(const QQuaternion &rotation);
+ void visibleChanged(bool visible);
+ void shadowCastingChanged(bool shadowCasting);
+ void scalingAbsoluteChanged(bool scalingAbsolute);
+
+protected:
+ QCustom3DItem(QCustom3DItemPrivate *d, QObject *parent = nullptr);
+
+ QScopedPointer<QCustom3DItemPrivate> d_ptr;
+
+private:
+ Q_DISABLE_COPY(QCustom3DItem)
+
+ friend class Abstract3DController;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/data/qcustom3ditem_p.h b/src/graphs/data/qcustom3ditem_p.h
new file mode 100644
index 0000000..9c81d87
--- /dev/null
+++ b/src/graphs/data/qcustom3ditem_p.h
@@ -0,0 +1,85 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QtGraphs API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#ifndef QCUSTOM3DITEM_P_H
+#define QCUSTOM3DITEM_P_H
+
+#include "graphsglobal_p.h"
+#include "qcustom3ditem.h"
+
+QT_BEGIN_NAMESPACE
+
+struct QCustomItemDirtyBitField {
+ bool textureDirty : 1;
+ bool meshDirty : 1;
+ bool positionDirty : 1;
+ bool scalingDirty : 1;
+ bool rotationDirty : 1;
+ bool visibleDirty : 1;
+ bool shadowCastingDirty : 1;
+
+ QCustomItemDirtyBitField()
+ : textureDirty(false),
+ meshDirty(false),
+ positionDirty(false),
+ scalingDirty(false),
+ rotationDirty(false),
+ visibleDirty(false),
+ shadowCastingDirty(false)
+ {
+ }
+};
+
+class QCustom3DItemPrivate : public QObject
+{
+ Q_OBJECT
+public:
+ QCustom3DItemPrivate(QCustom3DItem *q);
+ QCustom3DItemPrivate(QCustom3DItem *q, const QString &meshFile, const QVector3D &position,
+ const QVector3D &scaling, const QQuaternion &rotation);
+ virtual ~QCustom3DItemPrivate();
+
+ QImage textureImage();
+ void clearTextureImage();
+ void resetDirtyBits();
+
+public:
+ QCustom3DItem *q_ptr;
+ QImage m_textureImage;
+ QString m_textureFile;
+ QString m_meshFile;
+ QVector3D m_position;
+ bool m_positionAbsolute;
+ QVector3D m_scaling;
+ bool m_scalingAbsolute;
+ QQuaternion m_rotation;
+ bool m_visible;
+ bool m_shadowCasting;
+
+ bool m_isLabelItem;
+ bool m_isVolumeItem;
+
+ QCustomItemDirtyBitField m_dirtyBits;
+
+Q_SIGNALS:
+ void needUpdate();
+
+private:
+ QCustom3DItemPrivate(QCustom3DItemPrivate *d);
+
+ friend class QCustom3DItem;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/data/qcustom3dlabel.cpp b/src/graphs/data/qcustom3dlabel.cpp
new file mode 100644
index 0000000..b144c07
--- /dev/null
+++ b/src/graphs/data/qcustom3dlabel.cpp
@@ -0,0 +1,357 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qcustom3dlabel_p.h"
+#include "utils_p.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ * \class QCustom3DLabel
+ * \inmodule QtGraphs
+ * \brief The QCustom3DLabel class adds a custom label to a graph.
+ *
+ * The text, font, position, scaling, rotation, and colors of a custom label can
+ * be set. In addition, the visibility of the borders and background of the
+ * label can be toggled. Colors, borders, and background are determined by the
+ * active theme unless set explicitly.
+ *
+ * \note In scaling, the z-coordinate has no effect. Setting the same x- and
+ * y-coordinates retains the original font dimensions.
+ *
+ * \sa QAbstract3DGraph::addCustomItem()
+ */
+
+/*!
+ * \qmltype Custom3DLabel
+ * \inqmlmodule QtGraphs
+ * \ingroup graphs_qml
+ * \instantiates QCustom3DLabel
+ * \inherits Custom3DItem
+ * \brief Adds a custom label to a graph.
+ *
+ * The text, font, position, scaling, rotation, and colors of a custom label can
+ * be set. In addition, the visibility of the borders and background of the
+ * label can be toggled. Colors, borders, and background are determined by the
+ * active theme unless set explicitly.
+ *
+ * \note In scaling, the z-coordinate has no effect. Setting the same x- and
+ * y-coordinates retains the original font dimensions.
+ */
+
+/*! \qmlproperty string Custom3DLabel::text
+ *
+ * The text for the label. Rich text is not supported.
+ */
+
+/*! \qmlproperty font Custom3DLabel::font
+ *
+ * The font to be used for the label. Defaults to \c{Font {family: "Arial"; pointSize: 20}}.
+ * Special formatting (for example, outlined) is not supported.
+ */
+
+/*! \qmlproperty color Custom3DLabel::textColor
+ *
+ * The color for the label text. Also affects label border, if enabled. Defaults to \c{"white"}.
+ *
+ * \sa borderEnabled
+ */
+
+/*! \qmlproperty color Custom3DLabel::backgroundColor
+ *
+ * The color for the label background, if enabled. Defaults to \c{"gray"}.
+ *
+ * \sa backgroundEnabled
+ */
+
+/*! \qmlproperty bool Custom3DLabel::backgroundEnabled
+ *
+ * Defines whether the label background is enabled. If set to \c{false},
+ * backgroundColor has no effect. Defaults to \c{true}.
+ */
+
+/*! \qmlproperty bool Custom3DLabel::borderEnabled
+ *
+ * Defines whether label borders are enabled. Defaults to \c{true}.
+ */
+
+/*! \qmlproperty bool Custom3DLabel::facingCamera
+ *
+ * Defines whether the label will always face the camera. Defaults to \c{false}.
+ * If set to \c{true}, \l {QCustom3DItem::}{rotation} has no effect.
+ */
+
+/*!
+ * Constructs a custom 3D label with the given \a parent.
+ */
+QCustom3DLabel::QCustom3DLabel(QObject *parent) :
+ QCustom3DItem(new QCustom3DLabelPrivate(this), parent)
+{
+}
+
+/*!
+ * Constructs a custom 3D label with the given \a text, \a font, \a position, \a scaling,
+ * \a rotation, and optional \a parent.
+ *
+ * \note Setting the same x- and y-coordinates for \a scaling retains the
+ * original font dimensions.
+ */
+QCustom3DLabel::QCustom3DLabel(const QString &text, const QFont &font,
+ const QVector3D &position, const QVector3D &scaling,
+ const QQuaternion &rotation, QObject *parent) :
+ QCustom3DItem(new QCustom3DLabelPrivate(this, text, font, position, scaling, rotation),
+ parent)
+{
+}
+
+/*!
+ * Deletes the custom 3D label.
+ */
+QCustom3DLabel::~QCustom3DLabel()
+{
+}
+
+/*! \property QCustom3DLabel::text
+ *
+ * \brief The text for the label.
+ *
+ * Rich text is not supported.
+ */
+void QCustom3DLabel::setText(const QString &text)
+{
+ if (dptr()->m_text != text) {
+ dptr()->m_text = text;
+ dptr()->handleTextureChange();
+ emit textChanged(text);
+ emit dptr()->needUpdate();
+ }
+}
+
+QString QCustom3DLabel::text() const
+{
+ return dptrc()->m_text;
+}
+
+/*! \property QCustom3DLabel::font
+ *
+ * \brief The font to be used for the label.
+ *
+ * Defaults to \c{QFont("Arial", 20)}. Special formatting
+ * (for example, outlined) is not supported.
+ */
+void QCustom3DLabel::setFont(const QFont &font)
+{
+ if (dptr()->m_font != font) {
+ dptr()->m_font = font;
+ dptr()->handleTextureChange();
+ emit fontChanged(font);
+ emit dptr()->needUpdate();
+ }
+}
+
+QFont QCustom3DLabel::font() const
+{
+ return dptrc()->m_font;
+}
+
+/*! \property QCustom3DLabel::textColor
+ *
+ * \brief The color for the label text.
+ *
+ * Also affects the label border, if enabled. Defaults to \c{Qt::white}.
+ *
+ * \sa borderEnabled
+ */
+void QCustom3DLabel::setTextColor(const QColor &color)
+{
+ if (dptr()->m_txtColor != color) {
+ dptr()->m_txtColor = color;
+ dptr()->m_customVisuals = true;
+ dptr()->handleTextureChange();
+ emit textColorChanged(color);
+ emit dptr()->needUpdate();
+ }
+}
+
+QColor QCustom3DLabel::textColor() const
+{
+ return dptrc()->m_txtColor;
+}
+
+/*! \property QCustom3DLabel::backgroundColor
+ *
+ * \brief The color for the label background, if enabled.
+ *
+ * Defaults to \c{Qt::gray}.
+ *
+ * \sa backgroundEnabled
+ */
+void QCustom3DLabel::setBackgroundColor(const QColor &color)
+{
+ if (dptr()->m_bgrColor != color) {
+ dptr()->m_bgrColor = color;
+ dptr()->m_customVisuals = true;
+ dptr()->handleTextureChange();
+ emit backgroundColorChanged(color);
+ emit dptr()->needUpdate();
+ }
+}
+
+QColor QCustom3DLabel::backgroundColor() const
+{
+ return dptrc()->m_bgrColor;
+}
+
+/*! \property QCustom3DLabel::borderEnabled
+ *
+ * \brief Whether label borders are enabled.
+ *
+ * Defaults to \c{true}.
+ */
+void QCustom3DLabel::setBorderEnabled(bool enabled)
+{
+ if (dptr()->m_borders != enabled) {
+ dptr()->m_borders = enabled;
+ dptr()->m_customVisuals = true;
+ dptr()->handleTextureChange();
+ emit borderEnabledChanged(enabled);
+ emit dptr()->needUpdate();
+ }
+}
+
+bool QCustom3DLabel::isBorderEnabled() const
+{
+ return dptrc()->m_borders;
+}
+
+/*! \property QCustom3DLabel::backgroundEnabled
+ *
+ * \brief Whether the label background is enabled.
+ *
+ * If set to \c{false}, backgroundColor() has no effect. Defaults
+ * to \c{true}.
+ */
+void QCustom3DLabel::setBackgroundEnabled(bool enabled)
+{
+ if (dptr()->m_background != enabled) {
+ dptr()->m_background = enabled;
+ dptr()->m_customVisuals = true;
+ dptr()->handleTextureChange();
+ emit backgroundEnabledChanged(enabled);
+ emit dptr()->needUpdate();
+ }
+}
+
+bool QCustom3DLabel::isBackgroundEnabled() const
+{
+ return dptrc()->m_background;
+}
+
+/*! \property QCustom3DLabel::facingCamera
+ *
+ * \brief Whether the label will always face the camera.
+ *
+ * Defaults to \c{false}. If set to \c{true}, rotation()
+ * has no effect.
+ */
+void QCustom3DLabel::setFacingCamera(bool enabled)
+{
+ if (dptr()->m_facingCamera != enabled) {
+ dptr()->m_facingCamera = enabled;
+ dptr()->m_facingCameraDirty = true;
+ emit facingCameraChanged(enabled);
+ emit dptr()->needUpdate();
+ }
+}
+
+bool QCustom3DLabel::isFacingCamera() const
+{
+ return dptrc()->m_facingCamera;
+}
+
+/*!
+ * \internal
+ */
+QCustom3DLabelPrivate *QCustom3DLabel::dptr()
+{
+ return static_cast<QCustom3DLabelPrivate *>(d_ptr.data());
+}
+
+/*!
+ * \internal
+ */
+const QCustom3DLabelPrivate *QCustom3DLabel::dptrc() const
+{
+ return static_cast<const QCustom3DLabelPrivate *>(d_ptr.data());
+}
+
+QCustom3DLabelPrivate::QCustom3DLabelPrivate(QCustom3DLabel *q) :
+ QCustom3DItemPrivate(q),
+ m_font(QFont(QStringLiteral("Arial"), 20)),
+ m_bgrColor(Qt::gray),
+ m_txtColor(Qt::white),
+ m_background(true),
+ m_borders(true),
+ m_facingCamera(false),
+ m_customVisuals(false),
+ m_facingCameraDirty(false)
+{
+ m_isLabelItem = true;
+ m_shadowCasting = false;
+ m_meshFile = QStringLiteral(":/defaultMeshes/plane");
+ createTextureImage();
+}
+
+QCustom3DLabelPrivate::QCustom3DLabelPrivate(QCustom3DLabel *q, const QString &text,
+ const QFont &font, const QVector3D &position,
+ const QVector3D &scaling,
+ const QQuaternion &rotation) :
+ QCustom3DItemPrivate(q, QStringLiteral(":/defaultMeshes/plane"), position, scaling, rotation),
+ m_text(text),
+ m_font(font),
+ m_bgrColor(Qt::gray),
+ m_txtColor(Qt::white),
+ m_background(true),
+ m_borders(true),
+ m_facingCamera(false),
+ m_customVisuals(false),
+ m_facingCameraDirty(false)
+{
+ m_isLabelItem = true;
+ m_shadowCasting = false;
+ createTextureImage();
+}
+
+QCustom3DLabelPrivate::~QCustom3DLabelPrivate()
+{
+}
+
+void QCustom3DLabelPrivate::resetDirtyBits()
+{
+ QCustom3DItemPrivate::resetDirtyBits();
+ m_facingCameraDirty = false;
+}
+
+void QCustom3DLabelPrivate::createTextureImage()
+{
+ createTextureImage(m_bgrColor, m_txtColor, m_background, m_borders);
+}
+
+void QCustom3DLabelPrivate::createTextureImage(const QColor &bgrColor, const QColor &txtColor,
+ bool background, bool borders)
+{
+ m_textureImage = Utils::printTextToImage(m_font, m_text, bgrColor, txtColor, background,
+ borders, 0);
+}
+
+void QCustom3DLabelPrivate::handleTextureChange()
+{
+ createTextureImage();
+ m_dirtyBits.textureDirty = true;
+ if (!m_textureFile.isEmpty()) {
+ m_textureFile.clear();
+ emit q_ptr->textureFileChanged(m_textureFile);
+ }
+}
+
+QT_END_NAMESPACE
diff --git a/src/graphs/data/qcustom3dlabel.h b/src/graphs/data/qcustom3dlabel.h
new file mode 100644
index 0000000..f1081ee
--- /dev/null
+++ b/src/graphs/data/qcustom3dlabel.h
@@ -0,0 +1,76 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QCUSTOMLABELITEM_H
+#define QCUSTOMLABELITEM_H
+
+#include <QtGraphs/qgraphsglobal.h>
+#include <QtGraphs/QCustom3DItem>
+#include <QtGui/QVector3D>
+#include <QtGui/QQuaternion>
+#include <QtGui/QFont>
+#include <QtGui/QColor>
+
+QT_BEGIN_NAMESPACE
+
+class QCustom3DLabelPrivate;
+
+class Q_GRAPHS_EXPORT QCustom3DLabel : public QCustom3DItem
+{
+ Q_OBJECT
+ Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged)
+ Q_PROPERTY(QFont font READ font WRITE setFont NOTIFY fontChanged)
+ Q_PROPERTY(QColor textColor READ textColor WRITE setTextColor NOTIFY textColorChanged)
+ Q_PROPERTY(QColor backgroundColor READ backgroundColor WRITE setBackgroundColor NOTIFY backgroundColorChanged)
+ Q_PROPERTY(bool borderEnabled READ isBorderEnabled WRITE setBorderEnabled NOTIFY borderEnabledChanged)
+ Q_PROPERTY(bool backgroundEnabled READ isBackgroundEnabled WRITE setBackgroundEnabled NOTIFY backgroundEnabledChanged)
+ Q_PROPERTY(bool facingCamera READ isFacingCamera WRITE setFacingCamera NOTIFY facingCameraChanged)
+
+public:
+ explicit QCustom3DLabel(QObject *parent = nullptr);
+ explicit QCustom3DLabel(const QString &text, const QFont &font, const QVector3D &position,
+ const QVector3D &scaling, const QQuaternion &rotation,
+ QObject *parent = nullptr);
+ virtual ~QCustom3DLabel();
+
+ void setText(const QString &text);
+ QString text() const;
+
+ void setFont(const QFont &font);
+ QFont font() const;
+
+ void setTextColor(const QColor &color);
+ QColor textColor() const;
+
+ void setBackgroundColor(const QColor &color);
+ QColor backgroundColor() const;
+
+ void setBorderEnabled(bool enabled);
+ bool isBorderEnabled() const;
+
+ void setBackgroundEnabled(bool enabled);
+ bool isBackgroundEnabled() const;
+
+ void setFacingCamera(bool enabled);
+ bool isFacingCamera() const;
+
+Q_SIGNALS:
+ void textChanged(const QString &text);
+ void fontChanged(const QFont &font);
+ void textColorChanged(const QColor &color);
+ void backgroundColorChanged(const QColor &color);
+ void borderEnabledChanged(bool enabled);
+ void backgroundEnabledChanged(bool enabled);
+ void facingCameraChanged(bool enabled);
+
+protected:
+ QCustom3DLabelPrivate *dptr();
+ const QCustom3DLabelPrivate *dptrc() const;
+
+private:
+ Q_DISABLE_COPY(QCustom3DLabel)
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/data/qcustom3dlabel_p.h b/src/graphs/data/qcustom3dlabel_p.h
new file mode 100644
index 0000000..8515c4e
--- /dev/null
+++ b/src/graphs/data/qcustom3dlabel_p.h
@@ -0,0 +1,58 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QtGraphs API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#ifndef QCUSTOMLABELITEM_P_H
+#define QCUSTOMLABELITEM_P_H
+
+#include "qcustom3dlabel.h"
+#include "qcustom3ditem_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class QCustom3DLabelPrivate : public QCustom3DItemPrivate
+{
+ Q_OBJECT
+
+public:
+ QCustom3DLabelPrivate(QCustom3DLabel *q);
+ QCustom3DLabelPrivate(QCustom3DLabel *q, const QString &text, const QFont &font,
+ const QVector3D &position, const QVector3D &scaling,
+ const QQuaternion &rotation);
+ virtual ~QCustom3DLabelPrivate();
+
+ void resetDirtyBits();
+ void createTextureImage();
+ void createTextureImage(const QColor &bgrColor, const QColor &txtColor, bool background,
+ bool borders);
+ void handleTextureChange();
+
+public:
+ QString m_text;
+ QFont m_font;
+ QColor m_bgrColor;
+ QColor m_txtColor;
+ bool m_background;
+ bool m_borders;
+ bool m_facingCamera;
+
+ bool m_customVisuals;
+
+ bool m_facingCameraDirty;
+
+private:
+ friend class QCustom3DLabel;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/data/qcustom3dvolume.cpp b/src/graphs/data/qcustom3dvolume.cpp
new file mode 100644
index 0000000..6836001
--- /dev/null
+++ b/src/graphs/data/qcustom3dvolume.cpp
@@ -0,0 +1,1313 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qcustom3dvolume_p.h"
+#include "utils_p.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ * \class QCustom3DVolume
+ * \inmodule QtGraphs
+ * \brief The QCustom3DVolume class adds a volume rendered object to a graph.
+ *
+ * A volume rendered
+ * object is a box with a 3D texture. Three slice planes are supported for the volume, one along
+ * each main axis of the volume.
+ *
+ * Rendering volume objects is very performance intensive, especially when the volume is largely
+ * transparent, as the contents of the volume are ray-traced. The performance scales nearly linearly
+ * with the amount of pixels that the volume occupies on the screen, so showing the volume in a
+ * smaller view or limiting the zoom level of the graph are easy ways to improve performance.
+ * Similarly, the volume texture dimensions have a large impact on performance.
+ * If the frame rate is more important than pixel-perfect rendering of the volume contents, consider
+ * turning the high definition shader off by setting the useHighDefShader property to \c{false}.
+ *
+ * \note Volumetric objects are only supported with orthographic projection.
+ *
+ * \note Volumetric objects utilize 3D textures, which are not supported in OpenGL ES2 environments.
+ *
+ * \sa QAbstract3DGraph::addCustomItem(), QAbstract3DGraph::orthoProjection, useHighDefShader
+ */
+
+/*!
+ * \qmltype Custom3DVolume
+ * \inqmlmodule QtGraphs
+ * \ingroup graphs_qml
+ * \instantiates QCustom3DVolume
+ * \inherits Custom3DItem
+ * \brief Adds a volume rendered object to a graph.
+ *
+ * A volume rendered
+ * object is a box with a 3D texture. Three slice planes are supported for the volume, one along
+ * each main axis of the volume.
+ *
+ * Rendering volume objects is very performance intensive, especially when the volume is largely
+ * transparent, as the contents of the volume are ray-traced. The performance scales nearly linearly
+ * with the amount of pixels that the volume occupies on the screen, so showing the volume in a
+ * smaller view or limiting the zoom level of the graph are easy ways to improve performance.
+ * Similarly, the volume texture dimensions have a large impact on performance.
+ * If the frame rate is more important than pixel-perfect rendering of the volume contents, consider
+ * turning the high definition shader off by setting the useHighDefShader property to \c{false}.
+ *
+ * \note Filling in the volume data would not typically be efficient or practical from pure QML,
+ * so properties directly related to that are not fully supported from QML.
+ * Create a hybrid QML/C++ application if you want to use volume objects with a Qt Quick UI.
+ *
+ * \note Volumetric objects are only supported with orthographic projection.
+ *
+ * \note Volumetric objects utilize 3D textures, which are not supported in OpenGL ES2 environments.
+ *
+ * \sa AbstractGraph3D::orthoProjection, useHighDefShader
+ */
+
+/*! \qmlproperty int Custom3DVolume::textureWidth
+ *
+ * The width of the 3D texture defining the volume content in pixels. Defaults to \c{0}.
+ *
+ * \note Changing this property from QML is not supported, as the texture data cannot be resized
+ * accordingly.
+ */
+
+/*! \qmlproperty int Custom3DVolume::textureHeight
+ *
+ * The height of the 3D texture defining the volume content in pixels. Defaults to \c{0}.
+ *
+ * \note Changing this property from QML is not supported, as the texture data cannot be resized
+ * accordingly.
+ */
+
+/*! \qmlproperty int Custom3DVolume::textureDepth
+ *
+ * The depth of the 3D texture defining the volume content in pixels. Defaults to \c{0}.
+ *
+ * \note Changing this property from QML is not supported, as the texture data cannot be resized
+ * accordingly.
+ */
+
+/*! \qmlproperty int Custom3DVolume::sliceIndexX
+ *
+ * The x-dimension index into the texture data indicating which vertical slice to show.
+ * Setting any dimension to negative indicates no slice or slice frame for that dimension is drawn.
+ * If all dimensions are negative, no slices or slice frames are drawn and the volume is drawn
+ * normally.
+ * Defaults to \c{-1}.
+ *
+ * \sa QCustom3DVolume::textureData, drawSlices, drawSliceFrames
+ */
+
+/*! \qmlproperty int Custom3DVolume::sliceIndexY
+ *
+ * The y-dimension index into the texture data indicating which horizontal slice to show.
+ * Setting any dimension to negative indicates no slice or slice frame for that dimension is drawn.
+ * If all dimensions are negative, no slices or slice frames are drawn and the volume is drawn
+ * normally.
+ * Defaults to \c{-1}.
+ *
+ * \sa QCustom3DVolume::textureData, drawSlices, drawSliceFrames
+ */
+
+/*! \qmlproperty int Custom3DVolume::sliceIndexZ
+ *
+ * The z-dimension index into the texture data indicating which vertical slice to show.
+ * Setting any dimension to negative indicates no slice or slice frame for that dimension is drawn.
+ * If all dimensions are negative, no slices or slice frames are drawn and the volume is drawn
+ * normally.
+ * Defaults to \c{-1}.
+ *
+ * \sa QCustom3DVolume::textureData, drawSlices, drawSliceFrames
+ */
+
+/*!
+ * \qmlproperty real Custom3DVolume::alphaMultiplier
+ *
+ * The alpha value of every texel of the volume texture is multiplied with this value at
+ * the render time. This can be used to introduce uniform transparency to the volume.
+ * If preserveOpacity is \c{true}, only texels with at least some transparency to begin with are
+ * affected, and fully opaque texels are not affected.
+ * The value must not be negative.
+ * Defaults to \c{1.0}.
+ *
+ * \sa preserveOpacity
+ */
+
+/*!
+ * \qmlproperty bool Custom3DVolume::preserveOpacity
+ *
+ * If this property value is \c{true}, alphaMultiplier is only applied to texels that already have
+ * some transparency. If it is \c{false}, the multiplier is applied to the alpha value of all
+ * texels.
+ * Defaults to \c{true}.
+ *
+ * \sa alphaMultiplier
+ */
+
+/*!
+ * \qmlproperty bool Custom3DVolume::useHighDefShader
+ *
+ * If this property value is \c{true}, a high definition shader is used to render the volume.
+ * If it is \c{false}, a low definition shader is used.
+ *
+ * The high definition shader guarantees that every visible texel of the volume texture is sampled
+ * when the volume is rendered.
+ * The low definition shader renders only a rough approximation of the volume contents,
+ * but at a much higher frame rate. The low definition shader does not guarantee every texel of the
+ * volume texture is sampled, so there may be flickering if the volume contains distinct thin
+ * features.
+ *
+ * \note This value does not affect the level of detail when rendering the
+ * slices of the volume.
+ *
+ * Defaults to \c{true}.
+ */
+
+/*!
+ * \qmlproperty bool Custom3DVolume::drawSlices
+ *
+ * If this property value is \c{true}, the slices indicated by slice index properties
+ * will be drawn instead of the full volume.
+ * If it is \c{false}, the full volume will always be drawn.
+ * Defaults to \c{false}.
+ *
+ * \note The slices are always drawn along the item axes, so if the item is rotated, the slices are
+ * rotated as well.
+ *
+ * \sa sliceIndexX, sliceIndexY, sliceIndexZ
+ */
+
+/*!
+ * \qmlproperty bool Custom3DVolume::drawSliceFrames
+ *
+ * If this property value is \c{true}, the frames of slices indicated by slice index properties
+ * will be drawn around the volume.
+ * If it is \c{false}, no slice frames will be drawn.
+ * Drawing slice frames is independent of drawing slices, so you can show the full volume and
+ * still draw the slice frames around it.
+ * Defaults to \c{false}.
+ *
+ * \sa sliceIndexX, sliceIndexY, sliceIndexZ, drawSlices
+ */
+
+/*!
+ * \qmlproperty color Custom3DVolume::sliceFrameColor
+ *
+ * The color of the slice frame. Transparent slice frame color is not supported.
+ *
+ * Defaults to black.
+ *
+ * \sa drawSliceFrames
+ */
+
+/*!
+ * \qmlproperty vector3d Custom3DVolume::sliceFrameWidths
+ *
+ * The widths of the slice frame. The width can be different on different dimensions,
+ * so you can for example omit drawing the frames on certain sides of the volume by setting the
+ * value for that dimension to zero. The values are fractions of the volume thickness in the same
+ * dimension. The values cannot be negative.
+ *
+ * Defaults to \c{vector3d(0.01, 0.01, 0.01)}.
+ *
+ * \sa drawSliceFrames
+ */
+
+/*!
+ * \qmlproperty vector3d Custom3DVolume::sliceFrameGaps
+ *
+ * The size of the air gap left between the volume itself and the frame in each dimension.
+ * The gap can be different on different dimensions. The values are fractions of the volume
+ * thickness in the same dimension. The values cannot be negative.
+ *
+ * Defaults to \c{vector3d(0.01, 0.01, 0.01)}.
+ *
+ * \sa drawSliceFrames
+ */
+
+/*!
+ * \qmlproperty vector3d Custom3DVolume::sliceFrameThicknesses
+ *
+ * The thickness of the slice frames for each dimension. The values are fractions of
+ * the volume thickness in the same dimension. The values cannot be negative.
+ *
+ * Defaults to \c{vector3d(0.01, 0.01, 0.01)}.
+ *
+ * \sa drawSliceFrames
+ */
+
+/*!
+ * Constructs a custom 3D volume with the given \a parent.
+ */
+QCustom3DVolume::QCustom3DVolume(QObject *parent) :
+ QCustom3DItem(new QCustom3DVolumePrivate(this), parent)
+{
+}
+
+/*!
+ * Constructs a custom 3D volume with the given \a position, \a scaling, \a rotation,
+ * \a textureWidth, \a textureHeight, \a textureDepth, \a textureData, \a textureFormat,
+ * \a colorTable, and optional \a parent.
+ *
+ * \sa textureData, setTextureFormat(), colorTable
+ */
+QCustom3DVolume::QCustom3DVolume(const QVector3D &position, const QVector3D &scaling,
+ const QQuaternion &rotation, int textureWidth, int textureHeight,
+ int textureDepth, QList<uchar> *textureData,
+ QImage::Format textureFormat, const QList<QRgb> &colorTable,
+ QObject *parent)
+ : QCustom3DItem(new QCustom3DVolumePrivate(this, position, scaling, rotation, textureWidth,
+ textureHeight, textureDepth, textureData,
+ textureFormat, colorTable),
+ parent)
+{
+}
+
+
+/*!
+ * Deletes the custom 3D volume.
+ */
+QCustom3DVolume::~QCustom3DVolume()
+{
+}
+
+/*! \property QCustom3DVolume::textureWidth
+ *
+ * \brief The width of the 3D texture defining the volume content in pixels.
+ *
+ * Defaults to \c{0}.
+ *
+ * \note The textureData value may need to be resized or recreated if this value
+ * is changed.
+ * Defaults to \c{0}.
+ *
+ * \sa textureData, textureHeight, textureDepth, setTextureFormat(), textureDataWidth()
+ */
+void QCustom3DVolume::setTextureWidth(int value)
+{
+ if (value >= 0) {
+ if (dptr()->m_textureWidth != value) {
+ dptr()->m_textureWidth = value;
+ dptr()->m_dirtyBitsVolume.textureDimensionsDirty = true;
+ emit textureWidthChanged(value);
+ emit dptr()->needUpdate();
+ }
+ } else {
+ qWarning() << __FUNCTION__ << "Cannot set negative value.";
+ }
+}
+
+int QCustom3DVolume::textureWidth() const
+{
+ return dptrc()->m_textureWidth;
+}
+
+/*! \property QCustom3DVolume::textureHeight
+ *
+ * \brief The height of the 3D texture defining the volume content in pixels.
+ *
+ * Defaults to \c{0}.
+ *
+ * \note The textureData value may need to be resized or recreated if this value
+ * is changed.
+ * Defaults to \c{0}.
+ *
+ * \sa textureData, textureWidth, textureDepth, setTextureFormat()
+ */
+void QCustom3DVolume::setTextureHeight(int value)
+{
+ if (value >= 0) {
+ if (dptr()->m_textureHeight != value) {
+ dptr()->m_textureHeight = value;
+ dptr()->m_dirtyBitsVolume.textureDimensionsDirty = true;
+ emit textureHeightChanged(value);
+ emit dptr()->needUpdate();
+ }
+ } else {
+ qWarning() << __FUNCTION__ << "Cannot set negative value.";
+ }
+
+}
+
+int QCustom3DVolume::textureHeight() const
+{
+ return dptrc()->m_textureHeight;
+}
+
+/*! \property QCustom3DVolume::textureDepth
+ *
+ * \brief The depth of the 3D texture defining the volume content in pixels.
+ *
+ * Defaults to \c{0}.
+ *
+ * \note The textureData value may need to be resized or recreated if this value
+ * is changed.
+ * Defaults to \c{0}.
+ *
+ * \sa textureData, textureWidth, textureHeight, setTextureFormat()
+ */
+void QCustom3DVolume::setTextureDepth(int value)
+{
+ if (value >= 0) {
+ if (dptr()->m_textureDepth != value) {
+ dptr()->m_textureDepth = value;
+ dptr()->m_dirtyBitsVolume.textureDimensionsDirty = true;
+ emit textureDepthChanged(value);
+ emit dptr()->needUpdate();
+ }
+ } else {
+ qWarning() << __FUNCTION__ << "Cannot set negative value.";
+ }
+}
+
+int QCustom3DVolume::textureDepth() const
+{
+ return dptrc()->m_textureDepth;
+}
+
+/*!
+ * A convenience function for setting all three texture dimensions
+ * (\a width, \a height, and \a depth) at once.
+ *
+ * \sa textureData
+ */
+void QCustom3DVolume::setTextureDimensions(int width, int height, int depth)
+{
+ setTextureWidth(width);
+ setTextureHeight(height);
+ setTextureDepth(depth);
+}
+
+/*!
+ * Returns the actual texture data width. When the texture format is QImage::Format_Indexed8,
+ * this value equals textureWidth aligned to a 32-bit boundary. Otherwise, this
+ * value equals four times textureWidth.
+ */
+int QCustom3DVolume::textureDataWidth() const
+{
+ int dataWidth = dptrc()->m_textureWidth;
+
+ if (dptrc()->m_textureFormat == QImage::Format_Indexed8)
+ dataWidth += dataWidth % 4;
+ else
+ dataWidth *= 4;
+
+ return dataWidth;
+}
+
+/*! \property QCustom3DVolume::sliceIndexX
+ *
+ * \brief The x-dimension index into the texture data indicating which vertical
+ * slice to show.
+ *
+ * Setting any dimension to negative indicates no slice or slice frame for that dimension is drawn.
+ * If all dimensions are negative, no slices or slice frames are drawn and the volume is drawn
+ * normally.
+ *
+ * Defaults to \c{-1}.
+ *
+ * \sa textureData, drawSlices, drawSliceFrames
+ */
+void QCustom3DVolume::setSliceIndexX(int value)
+{
+ if (dptr()->m_sliceIndexX != value) {
+ dptr()->m_sliceIndexX = value;
+ dptr()->m_dirtyBitsVolume.slicesDirty = true;
+ emit sliceIndexXChanged(value);
+ emit dptr()->needUpdate();
+ }
+}
+
+int QCustom3DVolume::sliceIndexX() const
+{
+ return dptrc()->m_sliceIndexX;
+}
+
+/*! \property QCustom3DVolume::sliceIndexY
+ *
+ * \brief The y-dimension index into the texture data indicating which
+ * horizontal slice to show.
+ *
+ * Setting any dimension to negative indicates no slice or slice frame for that dimension is drawn.
+ * If all dimensions are negative, no slices or slice frames are drawn and the volume is drawn
+ * normally.
+ *
+ * Defaults to \c{-1}.
+ *
+ * \sa textureData, drawSlices, drawSliceFrames
+ */
+void QCustom3DVolume::setSliceIndexY(int value)
+{
+ if (dptr()->m_sliceIndexY != value) {
+ dptr()->m_sliceIndexY = value;
+ dptr()->m_dirtyBitsVolume.slicesDirty = true;
+ emit sliceIndexYChanged(value);
+ emit dptr()->needUpdate();
+ }
+}
+
+int QCustom3DVolume::sliceIndexY() const
+{
+ return dptrc()->m_sliceIndexY;
+}
+
+/*! \property QCustom3DVolume::sliceIndexZ
+ *
+ * \brief The z-dimension index into the texture data indicating which vertical
+ * slice to show.
+ *
+ * Setting any dimension to negative indicates no slice or slice frame for that dimension is drawn.
+ * If all dimensions are negative, no slices or slice frames are drawn and the volume is drawn
+ * normally.
+ *
+ * Defaults to \c{-1}.
+ *
+ * \sa textureData, drawSlices, drawSliceFrames
+ */
+void QCustom3DVolume::setSliceIndexZ(int value)
+{
+ if (dptr()->m_sliceIndexZ != value) {
+ dptr()->m_sliceIndexZ = value;
+ dptr()->m_dirtyBitsVolume.slicesDirty = true;
+ emit sliceIndexZChanged(value);
+ emit dptr()->needUpdate();
+ }
+}
+
+int QCustom3DVolume::sliceIndexZ() const
+{
+ return dptrc()->m_sliceIndexZ;
+}
+
+/*!
+ * A convenience function for setting all three slice indices (\a x, \a y, and \a z) at once.
+ *
+ * \sa textureData
+ */
+void QCustom3DVolume::setSliceIndices(int x, int y, int z)
+{
+ setSliceIndexX(x);
+ setSliceIndexY(y);
+ setSliceIndexZ(z);
+}
+
+/*! \property QCustom3DVolume::colorTable
+ *
+ * \brief The array containing the colors for indexed texture formats.
+ *
+ * If the texture format is not indexed, this array is not used and can be empty.
+ *
+ * Defaults to \c{0}.
+ *
+ * \sa textureData, setTextureFormat(), QImage::colorTable()
+ */
+void QCustom3DVolume::setColorTable(const QList<QRgb> &colors)
+{
+ if (dptr()->m_colorTable != colors) {
+ dptr()->m_colorTable = colors;
+ dptr()->m_dirtyBitsVolume.colorTableDirty = true;
+ emit colorTableChanged();
+ emit dptr()->needUpdate();
+ }
+}
+
+QList<QRgb> QCustom3DVolume::colorTable() const
+{
+ return dptrc()->m_colorTable;
+}
+
+/*! \property QCustom3DVolume::textureData
+ *
+ * \brief The array containing the texture data in the format specified by textureFormat.
+ *
+ * The size of this array must be at least
+ * (\c{textureDataWidth * textureHeight * textureDepth * texture format color depth in bytes}).
+ *
+ * A 3D texture is defined by a stack of 2D subtextures. Each subtexture must be of identical size
+ * (\c{textureDataWidth * textureHeight}), and the depth of the stack is defined
+ * by the textureDepth property. The data in each 2D texture is identical to a
+ * QImage data with the same format, so
+ * QImage::bits() can be used to supply the data for each subtexture.
+ *
+ * Ownership of the new array transfers to the QCustom3DVolume instance.
+ * If another array is set, the previous array is deleted.
+ * If the same array is set again, it is assumed that the array contents have been changed and the
+ * graph rendering is triggered.
+ *
+ * \note Each x-dimension line of the data needs to be 32-bit aligned.
+ * If textureFormat is QImage::Format_Indexed8 and the textureWidth value is not
+ * divisible by four, padding bytes might need to be added to each x-dimension
+ * line of the \a data. The textureDataWidth() function returns the padded byte
+ * count. The padding bytes should indicate a fully transparent color to avoid
+ * rendering artifacts.
+ *
+ * Defaults to \c{0}.
+ *
+ * \sa colorTable, setTextureFormat(), setSubTextureData(), textureDataWidth()
+ */
+void QCustom3DVolume::setTextureData(QList<uchar> *data)
+{
+ if (dptr()->m_textureData != data)
+ delete dptr()->m_textureData;
+
+ // Even if the pointer is same as previously, consider this property changed, as the values
+ // can be changed unbeknownst to us via the array pointer.
+ dptr()->m_textureData = data;
+ dptr()->m_dirtyBitsVolume.textureDataDirty = true;
+ emit textureDataChanged(data);
+ emit dptr()->needUpdate();
+}
+
+/*!
+ * Creates a new texture data array from an array of \a images and sets it as
+ * textureData for this volume object. The texture dimensions are also set according to image
+ * and array dimensions. All of the images in the array must be the same size. If the images are not
+ * all in the QImage::Format_Indexed8 format, all texture data will be converted into the
+ * QImage::Format_ARGB32 format. If the images are in the
+ * QImage::Format_Indexed8 format, the colorTable value
+ * for the entire volume will be taken from the first image.
+ *
+ * Returns a pointer to the newly created array.
+ *
+ * \sa textureData, textureWidth, textureHeight, textureDepth, setTextureFormat()
+ */
+QList<uchar> *QCustom3DVolume::createTextureData(const QList<QImage *> &images)
+{
+ int imageCount = images.size();
+ if (imageCount) {
+ QImage *currentImage = images.at(0);
+ int imageWidth = currentImage->width();
+ int imageHeight = currentImage->height();
+ QImage::Format imageFormat = currentImage->format();
+ bool convert = false;
+ if (imageFormat != QImage::Format_Indexed8 && imageFormat != QImage::Format_ARGB32) {
+ convert = true;
+ imageFormat = QImage::Format_ARGB32;
+ } else {
+ for (int i = 0; i < imageCount; i++) {
+ currentImage = images.at(i);
+ if (imageWidth != currentImage->width() || imageHeight != currentImage->height()) {
+ qWarning() << __FUNCTION__ << "Not all images were of the same size.";
+ setTextureData(0);
+ setTextureWidth(0);
+ setTextureHeight(0);
+ setTextureDepth(0);
+ return 0;
+
+ }
+ if (currentImage->format() != imageFormat) {
+ convert = true;
+ imageFormat = QImage::Format_ARGB32;
+ break;
+ }
+ }
+ }
+ int colorBytes = (imageFormat == QImage::Format_Indexed8) ? 1 : 4;
+ int imageByteWidth = (imageFormat == QImage::Format_Indexed8)
+ ? currentImage->bytesPerLine() : imageWidth;
+ int frameSize = imageByteWidth * imageHeight * colorBytes;
+ QList<uchar> *newTextureData = new QList<uchar>;
+ newTextureData->resize(frameSize * imageCount);
+ uchar *texturePtr = newTextureData->data();
+ QImage convertedImage;
+
+ for (int i = 0; i < imageCount; i++) {
+ currentImage = images.at(i);
+ if (convert) {
+ convertedImage = currentImage->convertToFormat(imageFormat);
+ currentImage = &convertedImage;
+ }
+ memcpy(texturePtr, static_cast<void *>(currentImage->bits()), frameSize);
+ texturePtr += frameSize;
+ }
+
+ if (imageFormat == QImage::Format_Indexed8)
+ setColorTable(images.at(0)->colorTable());
+ setTextureData(newTextureData);
+ setTextureFormat(imageFormat);
+ setTextureWidth(imageWidth);
+ setTextureHeight(imageHeight);
+ setTextureDepth(imageCount);
+ } else {
+ setTextureData(0);
+ setTextureWidth(0);
+ setTextureHeight(0);
+ setTextureDepth(0);
+ }
+ return dptr()->m_textureData;
+}
+
+QList<uchar> *QCustom3DVolume::textureData() const
+{
+ return dptrc()->m_textureData;
+}
+
+/*!
+ * Sets a single 2D subtexture of the 3D texture along the specified
+ * \a axis of the volume.
+ * The \a index parameter specifies the subtexture to set.
+ * The texture \a data must be in the format specified by the textureFormat
+ * property and have the size of
+ * the cross-section of the volume texture along the specified axis multiplied by
+ * the texture format color depth in bytes.
+ * The \a data is expected to be ordered similarly to the data in images
+ * produced by the renderSlice() method along the same axis.
+ *
+ * \note Each x-dimension line of the data needs to be 32-bit aligned when
+ * targeting the y-axis or z-axis. If textureFormat is QImage::Format_Indexed8
+ * and the textureWidth value is not divisible by four, padding bytes might need
+ * to be added to each x-dimension line of the \a data to properly align it. The
+ * padding bytes should indicate a fully transparent color to avoid rendering
+ * artifacts.
+ *
+ * \sa textureData, renderSlice()
+ */
+void QCustom3DVolume::setSubTextureData(Qt::Axis axis, int index, const uchar *data)
+{
+ if (data) {
+ int lineSize = textureDataWidth();
+ int frameSize = lineSize * dptr()->m_textureHeight;
+ int dataSize = dptr()->m_textureData->size();
+ int pixelWidth = (dptr()->m_textureFormat == QImage::Format_Indexed8) ? 1 : 4;
+ int targetIndex;
+ uchar *dataPtr = dptr()->m_textureData->data();
+ bool invalid = (index < 0);
+ if (axis == Qt::XAxis) {
+ targetIndex = index * pixelWidth;
+ if (index >= dptr()->m_textureWidth
+ || (frameSize * (dptr()->m_textureDepth - 1) + targetIndex) > dataSize) {
+ invalid = true;
+ }
+ } else if (axis == Qt::YAxis) {
+ targetIndex = (index * lineSize) + (frameSize * (dptr()->m_textureDepth - 1));
+ if (index >= dptr()->m_textureHeight || (targetIndex + lineSize > dataSize))
+ invalid = true;
+ } else {
+ targetIndex = index * frameSize;
+ if (index >= dptr()->m_textureDepth || ((targetIndex + frameSize) > dataSize))
+ invalid = true;
+ }
+
+ if (invalid) {
+ qWarning() << __FUNCTION__ << "Attempted to set invalid subtexture.";
+ } else {
+ const uchar *sourcePtr = data;
+ uchar *targetPtr = dataPtr + targetIndex;
+ if (axis == Qt::XAxis) {
+ int targetWidth = dptr()->m_textureDepth;
+ int targetHeight = dptr()->m_textureHeight;
+ for (int i = 0; i < targetHeight; i++) {
+ targetPtr = dataPtr + targetIndex + (lineSize * i);
+ for (int j = 0; j < targetWidth; j++) {
+ for (int k = 0; k < pixelWidth; k++)
+ *targetPtr++ = *sourcePtr++;
+ targetPtr += (frameSize - pixelWidth);
+ }
+ }
+ } else if (axis == Qt::YAxis) {
+ int targetHeight = dptr()->m_textureDepth;
+ for (int i = 0; i < targetHeight; i++){
+ for (int j = 0; j < lineSize; j++)
+ *targetPtr++ = *sourcePtr++;
+ targetPtr -= (frameSize + lineSize);
+ }
+ } else {
+ void *subTexPtr = dataPtr + targetIndex;
+ memcpy(subTexPtr, static_cast<const void *>(data), frameSize);
+ }
+ dptr()->m_dirtyBitsVolume.textureDataDirty = true;
+ emit textureDataChanged(dptr()->m_textureData);
+ emit dptr()->needUpdate();
+ }
+ } else {
+ qWarning() << __FUNCTION__ << "Tried to set null data.";
+ }
+}
+
+/*!
+ * Sets a single 2D subtexture of the 3D texture along the specified
+ * \a axis of the volume.
+ * The \a index parameter specifies the subtexture to set.
+ * The source \a image must be in the format specified by the textureFormat property if
+ * textureFormat is indexed. If textureFormat is QImage::Format_ARGB32, the image is converted
+ * to that format. The image must have the size of the cross-section of the volume texture along
+ * the specified axis. The orientation of the image should correspond to the orientation of
+ * the slice image produced by renderSlice() method along the same axis.
+ *
+ * \note Each x-dimension line of the data needs to be 32-bit aligned when
+ * targeting the y-axis or z-axis. If textureFormat is QImage::Format_Indexed8
+ * and the textureWidth value is not divisible by four, padding bytes might need
+ * to be added to each x-dimension line of the image to properly align it. The
+ * padding bytes should indicate a fully transparent color to avoid rendering
+ * artifacts. It is not guaranteed that QImage will do this automatically.
+ *
+ * \sa textureData, renderSlice()
+ */
+void QCustom3DVolume::setSubTextureData(Qt::Axis axis, int index, const QImage &image)
+{
+ int sourceWidth = image.width();
+ int sourceHeight = image.height();
+ int targetWidth;
+ int targetHeight;
+ if (axis == Qt::XAxis) {
+ targetWidth = dptr()->m_textureDepth;
+ targetHeight = dptr()->m_textureHeight;
+ } else if (axis == Qt::YAxis) {
+ targetWidth = dptr()->m_textureWidth;
+ targetHeight = dptr()->m_textureDepth;
+ } else {
+ targetWidth = dptr()->m_textureWidth;
+ targetHeight = dptr()->m_textureHeight;
+ }
+
+ if (sourceWidth == targetWidth
+ && sourceHeight == targetHeight
+ && (image.format() == dptr()->m_textureFormat
+ || dptr()->m_textureFormat == QImage::Format_ARGB32)) {
+ QImage convertedImage;
+ if (dptr()->m_textureFormat == QImage::Format_ARGB32
+ && image.format() != QImage::Format_ARGB32) {
+ convertedImage = image.convertToFormat(QImage::Format_ARGB32);
+ } else {
+ convertedImage = image;
+ }
+ setSubTextureData(axis, index, convertedImage.bits());
+ } else {
+ qWarning() << __FUNCTION__ << "Invalid image size or format.";
+ }
+}
+
+// Note: textureFormat is not a Q_PROPERTY to work around an issue in meta object system that
+// doesn't allow QImage::format to be a property type. Qt 5.2.1 at least has this problem.
+
+/*!
+ * Sets the format of the textureData property to \a format. Only two formats
+ * are supported currently:
+ * QImage::Format_Indexed8 and QImage::Format_ARGB32. If an indexed format is specified, colorTable
+ * must also be set.
+ * Defaults to QImage::Format_ARGB32.
+ *
+ * \sa colorTable, textureData
+ */
+void QCustom3DVolume::setTextureFormat(QImage::Format format)
+{
+ if (format == QImage::Format_ARGB32 || format == QImage::Format_Indexed8) {
+ if (dptr()->m_textureFormat != format) {
+ dptr()->m_textureFormat = format;
+ dptr()->m_dirtyBitsVolume.textureFormatDirty = true;
+ emit textureFormatChanged(format);
+ emit dptr()->needUpdate();
+ }
+ } else {
+ qWarning() << __FUNCTION__ << "Attempted to set invalid texture format.";
+ }
+}
+
+/*!
+ * Returns the format of the textureData property value.
+ *
+ * \sa setTextureFormat()
+ */
+QImage::Format QCustom3DVolume::textureFormat() const
+{
+ return dptrc()->m_textureFormat;
+}
+
+/*!
+ * \fn void QCustom3DVolume::textureFormatChanged(QImage::Format format)
+ *
+ * This signal is emitted when the \a format of the textureData value changes.
+ *
+ * \sa setTextureFormat()
+ */
+
+/*!
+ * \property QCustom3DVolume::alphaMultiplier
+ *
+ * \brief The value that the alpha value of every texel of the volume texture is multiplied with at
+ * the render time.
+ *
+ * This property can be used to introduce uniform transparency to the volume.
+ * If preserveOpacity is \c{true}, only texels with at least some transparency to begin with are
+ * affected, and fully opaque texels are not affected.
+ * The value must not be negative.
+ * Defaults to \c{1.0f}.
+ *
+ * \sa preserveOpacity, textureData
+ */
+void QCustom3DVolume::setAlphaMultiplier(float mult)
+{
+ if (mult >= 0.0f) {
+ if (dptr()->m_alphaMultiplier != mult) {
+ dptr()->m_alphaMultiplier = mult;
+ dptr()->m_dirtyBitsVolume.alphaDirty = true;
+ emit alphaMultiplierChanged(mult);
+ emit dptr()->needUpdate();
+ }
+ } else {
+ qWarning() << __FUNCTION__ << "Attempted to set negative multiplier.";
+ }
+}
+
+float QCustom3DVolume::alphaMultiplier() const
+{
+ return dptrc()->m_alphaMultiplier;
+}
+
+/*!
+ * \property QCustom3DVolume::preserveOpacity
+ *
+ * \brief Whether the alpha multiplier is applied to all texels.
+ *
+ * If this property value is \c{true}, alphaMultiplier is only applied to texels that already have
+ * some transparency. If it is \c{false}, the multiplier is applied to the alpha value of all
+ * texels.
+ * Defaults to \c{true}.
+ *
+ * \sa alphaMultiplier
+ */
+void QCustom3DVolume::setPreserveOpacity(bool enable)
+{
+ if (dptr()->m_preserveOpacity != enable) {
+ dptr()->m_preserveOpacity = enable;
+ dptr()->m_dirtyBitsVolume.alphaDirty = true;
+ emit preserveOpacityChanged(enable);
+ emit dptr()->needUpdate();
+ }
+}
+
+bool QCustom3DVolume::preserveOpacity() const
+{
+ return dptrc()->m_preserveOpacity;
+}
+
+/*!
+ * \property QCustom3DVolume::useHighDefShader
+ *
+ * \brief Whether a high or low definition shader is used to render the volume.
+ *
+ * If this property value is \c{true}, a high definition shader is used.
+ * If it is \c{false}, a low definition shader is used.
+ *
+ * The high definition shader guarantees that every visible texel of the volume texture is sampled
+ * when the volume is rendered.
+ * The low definition shader renders only a rough approximation of the volume contents,
+ * but at a much higher frame rate. The low definition shader does not guarantee
+ * that every texel of the
+ * volume texture is sampled, so there may be flickering if the volume contains distinct thin
+ * features.
+ *
+ * \note This value does not affect the level of detail when rendering the
+ * slices of the volume.
+ *
+ * Defaults to \c{true}.
+ *
+ * \sa renderSlice()
+ */
+void QCustom3DVolume::setUseHighDefShader(bool enable)
+{
+ if (dptr()->m_useHighDefShader != enable) {
+ dptr()->m_useHighDefShader = enable;
+ dptr()->m_dirtyBitsVolume.shaderDirty = true;
+ emit useHighDefShaderChanged(enable);
+ emit dptr()->needUpdate();
+ }
+}
+
+bool QCustom3DVolume::useHighDefShader() const
+{
+ return dptrc()->m_useHighDefShader;
+}
+
+/*!
+ * \property QCustom3DVolume::drawSlices
+ *
+ * \brief Whether the specified slices are drawn instead of the full volume.
+ *
+ * If this property value is \c{true}, the slices indicated by slice index properties
+ * will be drawn instead of the full volume.
+ * If it is \c{false}, the full volume will always be drawn.
+ * Defaults to \c{false}.
+ *
+ * \note The slices are always drawn along the item axes, so if the item is rotated, the slices are
+ * rotated as well.
+ *
+ * \sa sliceIndexX, sliceIndexY, sliceIndexZ
+ */
+void QCustom3DVolume::setDrawSlices(bool enable)
+{
+ if (dptr()->m_drawSlices != enable) {
+ dptr()->m_drawSlices = enable;
+ dptr()->m_dirtyBitsVolume.slicesDirty = true;
+ emit drawSlicesChanged(enable);
+ emit dptr()->needUpdate();
+ }
+}
+
+bool QCustom3DVolume::drawSlices() const
+{
+ return dptrc()->m_drawSlices;
+}
+
+/*!
+ * \property QCustom3DVolume::drawSliceFrames
+ *
+ * \brief Whether slice frames are drawn around the volume.
+ *
+ * If this property value is \c{true}, the frames of slices indicated by slice index properties
+ * will be drawn around the volume.
+ * If it is \c{false}, no slice frames will be drawn.
+ *
+ * Drawing slice frames is independent of drawing slices, so you can show the full volume and
+ * still draw the slice frames around it. This is useful when using renderSlice() to display the
+ * slices outside the graph itself.
+ *
+ * Defaults to \c{false}.
+ *
+ * \sa sliceIndexX, sliceIndexY, sliceIndexZ, drawSlices, renderSlice()
+ */
+void QCustom3DVolume::setDrawSliceFrames(bool enable)
+{
+ if (dptr()->m_drawSliceFrames != enable) {
+ dptr()->m_drawSliceFrames = enable;
+ dptr()->m_dirtyBitsVolume.slicesDirty = true;
+ emit drawSliceFramesChanged(enable);
+ emit dptr()->needUpdate();
+ }
+}
+
+bool QCustom3DVolume::drawSliceFrames() const
+{
+ return dptrc()->m_drawSliceFrames;
+}
+
+/*!
+ * \property QCustom3DVolume::sliceFrameColor
+ *
+ * \brief The color of the slice frame.
+ *
+ * Transparent slice frame color is not supported.
+ *
+ * Defaults to black.
+ *
+ * \sa drawSliceFrames
+ */
+void QCustom3DVolume::setSliceFrameColor(const QColor &color)
+{
+ if (dptr()->m_sliceFrameColor != color) {
+ dptr()->m_sliceFrameColor = color;
+ dptr()->m_dirtyBitsVolume.slicesDirty = true;
+ emit sliceFrameColorChanged(color);
+ emit dptr()->needUpdate();
+ }
+}
+
+QColor QCustom3DVolume::sliceFrameColor() const
+{
+ return dptrc()->m_sliceFrameColor;
+}
+
+/*!
+ * \property QCustom3DVolume::sliceFrameWidths
+ *
+ * \brief The width of the slice frame.
+ *
+ * The width can be different on different dimensions,
+ * so you can for example omit drawing the frames on certain sides of the volume by setting the
+ * value for that dimension to zero. The values are fractions of the volume thickness in the same
+ * dimension. The values cannot be negative.
+ *
+ * Defaults to \c{QVector3D(0.01, 0.01, 0.01)}.
+ *
+ * \sa drawSliceFrames
+ */
+void QCustom3DVolume::setSliceFrameWidths(const QVector3D &values)
+{
+ if (values.x() < 0.0f || values.y() < 0.0f || values.z() < 0.0f) {
+ qWarning() << __FUNCTION__ << "Attempted to set negative values.";
+ } else if (dptr()->m_sliceFrameWidths != values) {
+ dptr()->m_sliceFrameWidths = values;
+ dptr()->m_dirtyBitsVolume.slicesDirty = true;
+ emit sliceFrameWidthsChanged(values);
+ emit dptr()->needUpdate();
+ }
+}
+
+QVector3D QCustom3DVolume::sliceFrameWidths() const
+{
+ return dptrc()->m_sliceFrameWidths;
+}
+
+/*!
+ * \property QCustom3DVolume::sliceFrameGaps
+ *
+ * \brief The size of the air gap left between the volume itself and the frame
+ * in each dimension.
+ *
+ * The gap can be different on different dimensions. The values are fractions of the volume
+ * thickness in the same dimension. The values cannot be negative.
+ *
+ * Defaults to \c{QVector3D(0.01, 0.01, 0.01)}.
+ *
+ * \sa drawSliceFrames
+ */
+void QCustom3DVolume::setSliceFrameGaps(const QVector3D &values)
+{
+ if (values.x() < 0.0f || values.y() < 0.0f || values.z() < 0.0f) {
+ qWarning() << __FUNCTION__ << "Attempted to set negative values.";
+ } else if (dptr()->m_sliceFrameGaps != values) {
+ dptr()->m_sliceFrameGaps = values;
+ dptr()->m_dirtyBitsVolume.slicesDirty = true;
+ emit sliceFrameGapsChanged(values);
+ emit dptr()->needUpdate();
+ }
+}
+
+QVector3D QCustom3DVolume::sliceFrameGaps() const
+{
+ return dptrc()->m_sliceFrameGaps;
+}
+
+/*!
+ * \property QCustom3DVolume::sliceFrameThicknesses
+ *
+ * \brief The thickness of the slice frames for each dimension.
+ *
+ * The values are fractions of
+ * the volume thickness in the same dimension. The values cannot be negative.
+ *
+ * Defaults to \c{QVector3D(0.01, 0.01, 0.01)}.
+ *
+ * \sa drawSliceFrames
+ */
+void QCustom3DVolume::setSliceFrameThicknesses(const QVector3D &values)
+{
+ if (values.x() < 0.0f || values.y() < 0.0f || values.z() < 0.0f) {
+ qWarning() << __FUNCTION__ << "Attempted to set negative values.";
+ } else if (dptr()->m_sliceFrameThicknesses != values) {
+ dptr()->m_sliceFrameThicknesses = values;
+ dptr()->m_dirtyBitsVolume.slicesDirty = true;
+ emit sliceFrameThicknessesChanged(values);
+ emit dptr()->needUpdate();
+ }
+}
+
+QVector3D QCustom3DVolume::sliceFrameThicknesses() const
+{
+ return dptrc()->m_sliceFrameThicknesses;
+}
+
+/*!
+ * Renders the slice specified by \a index along the axis specified by \a axis
+ * into an image.
+ * The texture format of this object is used.
+ *
+ * Returns the rendered image of the slice, or a null image if an invalid index is
+ * specified.
+ *
+ * \sa setTextureFormat()
+ */
+QImage QCustom3DVolume::renderSlice(Qt::Axis axis, int index)
+{
+ return dptr()->renderSlice(axis, index);
+}
+
+/*!
+ * \internal
+ */
+QCustom3DVolumePrivate *QCustom3DVolume::dptr()
+{
+ return static_cast<QCustom3DVolumePrivate *>(d_ptr.data());
+}
+
+/*!
+ * \internal
+ */
+const QCustom3DVolumePrivate *QCustom3DVolume::dptrc() const
+{
+ return static_cast<const QCustom3DVolumePrivate *>(d_ptr.data());
+}
+
+QCustom3DVolumePrivate::QCustom3DVolumePrivate(QCustom3DVolume *q) :
+ QCustom3DItemPrivate(q),
+ m_textureWidth(0),
+ m_textureHeight(0),
+ m_textureDepth(0),
+ m_sliceIndexX(-1),
+ m_sliceIndexY(-1),
+ m_sliceIndexZ(-1),
+ m_textureFormat(QImage::Format_ARGB32),
+ m_textureData(0),
+ m_alphaMultiplier(1.0f),
+ m_preserveOpacity(true),
+ m_useHighDefShader(true),
+ m_drawSlices(false),
+ m_drawSliceFrames(false),
+ m_sliceFrameColor(Qt::black),
+ m_sliceFrameWidths(QVector3D(0.01f, 0.01f, 0.01f)),
+ m_sliceFrameGaps(QVector3D(0.01f, 0.01f, 0.01f)),
+ m_sliceFrameThicknesses(QVector3D(0.01f, 0.01f, 0.01f))
+{
+ m_isVolumeItem = true;
+ m_meshFile = QStringLiteral(":/defaultMeshes/barFull");
+}
+
+QCustom3DVolumePrivate::QCustom3DVolumePrivate(
+ QCustom3DVolume *q, const QVector3D &position, const QVector3D &scaling,
+ const QQuaternion &rotation, int textureWidth, int textureHeight, int textureDepth,
+ QList<uchar> *textureData, QImage::Format textureFormat, const QList<QRgb> &colorTable)
+ : QCustom3DItemPrivate(q, QStringLiteral(":/defaultMeshes/barFull"), position, scaling,
+ rotation),
+ m_textureWidth(textureWidth),
+ m_textureHeight(textureHeight),
+ m_textureDepth(textureDepth),
+ m_sliceIndexX(-1),
+ m_sliceIndexY(-1),
+ m_sliceIndexZ(-1),
+ m_textureFormat(textureFormat),
+ m_colorTable(colorTable),
+ m_textureData(textureData),
+ m_alphaMultiplier(1.0f),
+ m_preserveOpacity(true),
+ m_useHighDefShader(true),
+ m_drawSlices(false),
+ m_drawSliceFrames(false),
+ m_sliceFrameColor(Qt::black),
+ m_sliceFrameWidths(QVector3D(0.01f, 0.01f, 0.01f)),
+ m_sliceFrameGaps(QVector3D(0.01f, 0.01f, 0.01f)),
+ m_sliceFrameThicknesses(QVector3D(0.01f, 0.01f, 0.01f))
+{
+ m_isVolumeItem = true;
+ m_shadowCasting = false;
+
+ if (m_textureWidth < 0)
+ m_textureWidth = 0;
+ if (m_textureHeight < 0)
+ m_textureHeight = 0;
+ if (m_textureDepth < 0)
+ m_textureDepth = 0;
+
+ if (m_textureFormat != QImage::Format_Indexed8)
+ m_textureFormat = QImage::Format_ARGB32;
+
+}
+
+QCustom3DVolumePrivate::~QCustom3DVolumePrivate()
+{
+ delete m_textureData;
+}
+
+void QCustom3DVolumePrivate::resetDirtyBits()
+{
+ QCustom3DItemPrivate::resetDirtyBits();
+
+ m_dirtyBitsVolume.textureDimensionsDirty = false;
+ m_dirtyBitsVolume.slicesDirty = false;
+ m_dirtyBitsVolume.colorTableDirty = false;
+ m_dirtyBitsVolume.textureDataDirty = false;
+ m_dirtyBitsVolume.textureFormatDirty = false;
+ m_dirtyBitsVolume.alphaDirty = false;
+ m_dirtyBitsVolume.shaderDirty = false;
+}
+
+QImage QCustom3DVolumePrivate::renderSlice(Qt::Axis axis, int index)
+{
+ if (index < 0)
+ return QImage();
+
+ int x;
+ int y;
+ if (axis == Qt::XAxis) {
+ if (index >= m_textureWidth)
+ return QImage();
+ x = m_textureDepth;
+ y = m_textureHeight;
+ } else if (axis == Qt::YAxis) {
+ if (index >= m_textureHeight)
+ return QImage();
+ x = m_textureWidth;
+ y = m_textureDepth;
+ } else {
+ if (index >= m_textureDepth)
+ return QImage();
+ x = m_textureWidth;
+ y = m_textureHeight;
+ }
+
+ int padding = 0;
+ int pixelWidth = 4;
+ int dataWidth = qptr()->textureDataWidth();
+ if (m_textureFormat == QImage::Format_Indexed8) {
+ padding = x % 4;
+ pixelWidth = 1;
+ }
+ QList<uchar> data((x + padding) * y * pixelWidth);
+ int frameSize = qptr()->textureDataWidth() * m_textureHeight;
+
+ int dataIndex = 0;
+ if (axis == Qt::XAxis) {
+ for (int i = 0; i < y; i++) {
+ const uchar *p = m_textureData->constData()
+ + (index * pixelWidth) + (dataWidth * i);
+ for (int j = 0; j < x; j++) {
+ for (int k = 0; k < pixelWidth; k++)
+ data[dataIndex++] = *(p + k);
+ p += frameSize;
+ }
+ }
+ } else if (axis == Qt::YAxis) {
+ for (int i = y - 1; i >= 0; i--) {
+ const uchar *p = m_textureData->constData() + (index * dataWidth)
+ + (frameSize * i);
+ for (int j = 0; j < (x * pixelWidth); j++) {
+ data[dataIndex++] = *p;
+ p++;
+ }
+ }
+ } else {
+ for (int i = 0; i < y; i++) {
+ const uchar *p = m_textureData->constData() + (index * frameSize) + (dataWidth * i);
+ for (int j = 0; j < (x * pixelWidth); j++) {
+ data[dataIndex++] = *p;
+ p++;
+ }
+ }
+ }
+
+ if (m_textureFormat != QImage::Format_Indexed8 && m_alphaMultiplier != 1.0f) {
+ for (int i = pixelWidth - 1; i < data.size(); i += pixelWidth)
+ data[i] = static_cast<uchar>(multipliedAlphaValue(data.at(i)));
+ }
+
+ QImage image(data.constData(), x, y, x * pixelWidth, m_textureFormat);
+ image.bits(); // Call bits() to detach the new image from local data
+ if (m_textureFormat == QImage::Format_Indexed8) {
+ QList<QRgb> colorTable = m_colorTable;
+ if (m_alphaMultiplier != 1.0f) {
+ for (int i = 0; i < colorTable.size(); i++) {
+ QRgb curCol = colorTable.at(i);
+ int alpha = multipliedAlphaValue(qAlpha(curCol));
+ if (alpha != qAlpha(curCol))
+ colorTable[i] = qRgba(qRed(curCol), qGreen(curCol), qBlue(curCol), alpha);
+ }
+ }
+ image.setColorTable(colorTable);
+ }
+
+ return image;
+}
+
+int QCustom3DVolumePrivate::multipliedAlphaValue(int alpha)
+{
+ int modifiedAlpha = alpha;
+ if (!m_preserveOpacity || alpha != 255) {
+ modifiedAlpha = int(m_alphaMultiplier * float(alpha));
+ modifiedAlpha = qMin(modifiedAlpha, 255);
+ }
+ return modifiedAlpha;
+}
+
+QCustom3DVolume *QCustom3DVolumePrivate::qptr()
+{
+ return static_cast<QCustom3DVolume *>(q_ptr);
+}
+
+QT_END_NAMESPACE
diff --git a/src/graphs/data/qcustom3dvolume.h b/src/graphs/data/qcustom3dvolume.h
new file mode 100644
index 0000000..0528c61
--- /dev/null
+++ b/src/graphs/data/qcustom3dvolume.h
@@ -0,0 +1,131 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QCUSTOM3DVOLUME_H
+#define QCUSTOM3DVOLUME_H
+
+#include <QtGraphs/qgraphsglobal.h>
+#include <QtGraphs/QCustom3DItem>
+#include <QtGui/QColor>
+#include <QtGui/QImage>
+
+QT_BEGIN_NAMESPACE
+
+class QCustom3DVolumePrivate;
+
+class Q_GRAPHS_EXPORT QCustom3DVolume : public QCustom3DItem
+{
+ Q_OBJECT
+ Q_PROPERTY(int textureWidth READ textureWidth WRITE setTextureWidth NOTIFY textureWidthChanged)
+ Q_PROPERTY(int textureHeight READ textureHeight WRITE setTextureHeight NOTIFY textureHeightChanged)
+ Q_PROPERTY(int textureDepth READ textureDepth WRITE setTextureDepth NOTIFY textureDepthChanged)
+ Q_PROPERTY(int sliceIndexX READ sliceIndexX WRITE setSliceIndexX NOTIFY sliceIndexXChanged)
+ Q_PROPERTY(int sliceIndexY READ sliceIndexY WRITE setSliceIndexY NOTIFY sliceIndexYChanged)
+ Q_PROPERTY(int sliceIndexZ READ sliceIndexZ WRITE setSliceIndexZ NOTIFY sliceIndexZChanged)
+ Q_PROPERTY(QList<QRgb> colorTable READ colorTable WRITE setColorTable NOTIFY colorTableChanged)
+ Q_PROPERTY(QList<uchar> *textureData READ textureData WRITE setTextureData NOTIFY
+ textureDataChanged)
+ Q_PROPERTY(float alphaMultiplier READ alphaMultiplier WRITE setAlphaMultiplier NOTIFY alphaMultiplierChanged)
+ Q_PROPERTY(bool preserveOpacity READ preserveOpacity WRITE setPreserveOpacity NOTIFY preserveOpacityChanged)
+ Q_PROPERTY(bool useHighDefShader READ useHighDefShader WRITE setUseHighDefShader NOTIFY useHighDefShaderChanged)
+ Q_PROPERTY(bool drawSlices READ drawSlices WRITE setDrawSlices NOTIFY drawSlicesChanged)
+ Q_PROPERTY(bool drawSliceFrames READ drawSliceFrames WRITE setDrawSliceFrames NOTIFY drawSliceFramesChanged)
+ Q_PROPERTY(QColor sliceFrameColor READ sliceFrameColor WRITE setSliceFrameColor NOTIFY sliceFrameColorChanged)
+ Q_PROPERTY(QVector3D sliceFrameWidths READ sliceFrameWidths WRITE setSliceFrameWidths NOTIFY sliceFrameWidthsChanged)
+ Q_PROPERTY(QVector3D sliceFrameGaps READ sliceFrameGaps WRITE setSliceFrameGaps NOTIFY sliceFrameGapsChanged)
+ Q_PROPERTY(QVector3D sliceFrameThicknesses READ sliceFrameThicknesses WRITE setSliceFrameThicknesses NOTIFY sliceFrameThicknessesChanged)
+
+public:
+
+ explicit QCustom3DVolume(QObject *parent = nullptr);
+ explicit QCustom3DVolume(const QVector3D &position, const QVector3D &scaling,
+ const QQuaternion &rotation, int textureWidth, int textureHeight,
+ int textureDepth, QList<uchar> *textureData,
+ QImage::Format textureFormat, const QList<QRgb> &colorTable,
+ QObject *parent = nullptr);
+ virtual ~QCustom3DVolume();
+
+ void setTextureWidth(int value);
+ int textureWidth() const;
+ void setTextureHeight(int value);
+ int textureHeight() const;
+ void setTextureDepth(int value);
+ int textureDepth() const;
+ void setTextureDimensions(int width, int height, int depth);
+ int textureDataWidth() const;
+
+ void setSliceIndexX(int value);
+ int sliceIndexX() const;
+ void setSliceIndexY(int value);
+ int sliceIndexY() const;
+ void setSliceIndexZ(int value);
+ int sliceIndexZ() const;
+ void setSliceIndices(int x, int y, int z);
+
+ void setColorTable(const QList<QRgb> &colors);
+ QList<QRgb> colorTable() const;
+
+ void setTextureData(QList<uchar> *data);
+ QList<uchar> *createTextureData(const QList<QImage *> &images);
+ QList<uchar> *textureData() const;
+ void setSubTextureData(Qt::Axis axis, int index, const uchar *data);
+ void setSubTextureData(Qt::Axis axis, int index, const QImage &image);
+
+ void setTextureFormat(QImage::Format format);
+ QImage::Format textureFormat() const;
+
+ void setAlphaMultiplier(float mult);
+ float alphaMultiplier() const;
+ void setPreserveOpacity(bool enable);
+ bool preserveOpacity() const;
+
+ void setUseHighDefShader(bool enable);
+ bool useHighDefShader() const;
+
+ void setDrawSlices(bool enable);
+ bool drawSlices() const;
+ void setDrawSliceFrames(bool enable);
+ bool drawSliceFrames() const;
+
+ void setSliceFrameColor(const QColor &color);
+ QColor sliceFrameColor() const;
+ void setSliceFrameWidths(const QVector3D &values);
+ QVector3D sliceFrameWidths() const;
+ void setSliceFrameGaps(const QVector3D &values);
+ QVector3D sliceFrameGaps() const;
+ void setSliceFrameThicknesses(const QVector3D &values);
+ QVector3D sliceFrameThicknesses() const;
+
+ QImage renderSlice(Qt::Axis axis, int index);
+
+Q_SIGNALS:
+ void textureWidthChanged(int value);
+ void textureHeightChanged(int value);
+ void textureDepthChanged(int value);
+ void sliceIndexXChanged(int value);
+ void sliceIndexYChanged(int value);
+ void sliceIndexZChanged(int value);
+ void colorTableChanged();
+ void textureDataChanged(QList<uchar> *data);
+ void textureFormatChanged(QImage::Format format);
+ void alphaMultiplierChanged(float mult);
+ void preserveOpacityChanged(bool enabled);
+ void useHighDefShaderChanged(bool enabled);
+ void drawSlicesChanged(bool enabled);
+ void drawSliceFramesChanged(bool enabled);
+ void sliceFrameColorChanged(const QColor &color);
+ void sliceFrameWidthsChanged(const QVector3D &values);
+ void sliceFrameGapsChanged(const QVector3D &values);
+ void sliceFrameThicknessesChanged(const QVector3D &values);
+
+protected:
+ QCustom3DVolumePrivate *dptr();
+ const QCustom3DVolumePrivate *dptrc() const;
+
+private:
+ Q_DISABLE_COPY(QCustom3DVolume)
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/data/qcustom3dvolume_p.h b/src/graphs/data/qcustom3dvolume_p.h
new file mode 100644
index 0000000..23536c3
--- /dev/null
+++ b/src/graphs/data/qcustom3dvolume_p.h
@@ -0,0 +1,93 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QtGraphs API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#ifndef QCUSTOM3DVOLUME_P_H
+#define QCUSTOM3DVOLUME_P_H
+
+#include "qcustom3dvolume.h"
+#include "qcustom3ditem_p.h"
+
+QT_BEGIN_NAMESPACE
+
+struct QCustomVolumeDirtyBitField {
+ bool textureDimensionsDirty : 1;
+ bool slicesDirty : 1;
+ bool colorTableDirty : 1;
+ bool textureDataDirty : 1;
+ bool textureFormatDirty : 1;
+ bool alphaDirty : 1;
+ bool shaderDirty : 1;
+
+ QCustomVolumeDirtyBitField()
+ : textureDimensionsDirty(false),
+ slicesDirty(false),
+ colorTableDirty(false),
+ textureDataDirty(false),
+ textureFormatDirty(false),
+ alphaDirty(false),
+ shaderDirty(false)
+ {
+ }
+};
+
+class QCustom3DVolumePrivate : public QCustom3DItemPrivate
+{
+ Q_OBJECT
+
+public:
+ QCustom3DVolumePrivate(QCustom3DVolume *q);
+ QCustom3DVolumePrivate(QCustom3DVolume *q, const QVector3D &position, const QVector3D &scaling,
+ const QQuaternion &rotation, int textureWidth, int textureHeight,
+ int textureDepth, QList<uchar> *textureData,
+ QImage::Format textureFormat, const QList<QRgb> &colorTable);
+ virtual ~QCustom3DVolumePrivate();
+
+ void resetDirtyBits();
+ QImage renderSlice(Qt::Axis axis, int index);
+
+ QCustom3DVolume *qptr();
+
+public:
+ int m_textureWidth;
+ int m_textureHeight;
+ int m_textureDepth;
+ int m_sliceIndexX;
+ int m_sliceIndexY;
+ int m_sliceIndexZ;
+
+ QImage::Format m_textureFormat;
+ QList<QRgb> m_colorTable;
+ QList<uchar> *m_textureData;
+
+ float m_alphaMultiplier;
+ bool m_preserveOpacity;
+ bool m_useHighDefShader;
+
+ bool m_drawSlices;
+ bool m_drawSliceFrames;
+ QColor m_sliceFrameColor;
+ QVector3D m_sliceFrameWidths;
+ QVector3D m_sliceFrameGaps;
+ QVector3D m_sliceFrameThicknesses;
+
+ QCustomVolumeDirtyBitField m_dirtyBitsVolume;
+
+private:
+ int multipliedAlphaValue(int alpha);
+
+ friend class QCustom3DVolume;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/data/qheightmapsurfacedataproxy.cpp b/src/graphs/data/qheightmapsurfacedataproxy.cpp
new file mode 100644
index 0000000..8693b88
--- /dev/null
+++ b/src/graphs/data/qheightmapsurfacedataproxy.cpp
@@ -0,0 +1,745 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qheightmapsurfacedataproxy_p.h"
+
+QT_BEGIN_NAMESPACE
+
+// Default ranges correspond value axis defaults
+const float defaultMinValue = 0.0f;
+const float defaultMaxValue = 10.0f;
+
+/*!
+ * \class QHeightMapSurfaceDataProxy
+ * \inmodule QtGraphs
+ * \brief Base proxy class for Q3DSurface.
+ *
+ * QHeightMapSurfaceDataProxy takes care of surface related height map data handling. It provides a
+ * way to give a height map to be visualized as a surface plot.
+ *
+ * Since height maps do not contain values for X or Z axes, those values need to be given
+ * separately using minXValue, maxXValue, minZValue, and maxZValue properties. X-value corresponds
+ * to image horizontal direction and Z-value to the vertical. Setting any of these
+ * properties triggers asynchronous re-resolving of any existing height map.
+ *
+ * \sa QSurfaceDataProxy, {Qt Graphs Data Handling}
+ */
+
+/*!
+ * \qmltype HeightMapSurfaceDataProxy
+ * \inqmlmodule QtGraphs
+ * \ingroup graphs_qml
+ * \instantiates QHeightMapSurfaceDataProxy
+ * \inherits SurfaceDataProxy
+ * \brief Base proxy type for Surface3D.
+ *
+ * HeightMapSurfaceDataProxy takes care of surface related height map data handling. It provides a
+ * way to give a height map to be visualized as a surface plot.
+ *
+ * For more complete description, see QHeightMapSurfaceDataProxy.
+ *
+ * \sa {Qt Graphs Data Handling}
+ */
+
+/*!
+ * \qmlproperty string HeightMapSurfaceDataProxy::heightMapFile
+ *
+ * A file with a height map image to be visualized. Setting this property replaces current data
+ * with height map data.
+ *
+ * There are several formats the image file can be given in, but if it is not in a directly usable
+ * format, a conversion is made.
+ *
+ * \note If the result seems wrong, the automatic conversion failed
+ * and you should try converting the image yourself before setting it. Preferred format is
+ * QImage::Format_RGB32 in grayscale.
+ *
+ * The height of the image is read from the red component of the pixels if the image is in grayscale,
+ * otherwise it is an average calculated from red, green and blue components of the pixels. Using
+ * grayscale images may improve data conversion speed for large images.
+ *
+ * Since height maps do not contain values for X or Z axes, those values need to be given
+ * separately using minXValue, maxXValue, minZValue, and maxZValue properties. X-value corresponds
+ * to image horizontal direction and Z-value to the vertical. Setting any of these
+ * properties triggers asynchronous re-resolving of any existing height map.
+ *
+ * Not recommended formats: all mono formats (for example QImage::Format_Mono).
+ */
+
+/*!
+ * \qmlproperty real HeightMapSurfaceDataProxy::minXValue
+ *
+ * The minimum X value for the generated surface points. Defaults to \c{0.0}.
+ * When setting this property the corresponding maximum value is adjusted if necessary,
+ * to ensure that the range remains valid.
+ */
+
+/*!
+ * \qmlproperty real HeightMapSurfaceDataProxy::maxXValue
+ *
+ * The maximum X value for the generated surface points. Defaults to \c{10.0}.
+ * When setting this property the corresponding minimum value is adjusted if necessary,
+ * to ensure that the range remains valid.
+ */
+
+/*!
+ * \qmlproperty real HeightMapSurfaceDataProxy::minZValue
+ *
+ * The minimum Z value for the generated surface points. Defaults to \c{0.0}.
+ * When setting this property the corresponding maximum value is adjusted if necessary,
+ * to ensure that the range remains valid.
+ */
+
+/*!
+ * \qmlproperty real HeightMapSurfaceDataProxy::maxZValue
+ *
+ * The maximum Z value for the generated surface points. Defaults to \c{10.0}.
+ * When setting this property the corresponding minimum value is adjusted if necessary,
+ * to ensure that the range remains valid.
+ */
+
+/*!
+ * \qmlproperty real HeightMapSurfaceDataProxy::minYValue
+ *
+ * The minimum Y value for the generated surface points. Defaults to \c{0.0}.
+ * When setting this property the corresponding maximum value is adjusted if necessary,
+ * to ensure that the range remains valid.
+ */
+
+/*!
+ * \qmlproperty real HeightMapSurfaceDataProxy::maxYValue
+ *
+ * The maximum Y value for the generated surface points. Defaults to \c{10.0}.
+ * When setting this property the corresponding minimum value is adjusted if necessary,
+ * to ensure that the range remains valid.
+ */
+
+/*!
+ * \qmlproperty real HeightMapSurfaceDataProxy::autoScaleY
+ *
+ * Scale height values to Y-axis. Defaults to \c{false}. When this property is set to \c{true},
+ * the height values are scaled to fit on the Y-axis between \c{minYValue} and \c{maxYValue}.
+ */
+
+/*!
+ * Constructs QHeightMapSurfaceDataProxy with the given \a parent.
+ */
+QHeightMapSurfaceDataProxy::QHeightMapSurfaceDataProxy(QObject *parent) :
+ QSurfaceDataProxy(new QHeightMapSurfaceDataProxyPrivate(this), parent)
+{
+}
+
+/*!
+ * Constructs QHeightMapSurfaceDataProxy with the given \a image and \a parent. Height map is set
+ * by calling setHeightMap() with \a image.
+ *
+ * \sa heightMap
+ */
+QHeightMapSurfaceDataProxy::QHeightMapSurfaceDataProxy(const QImage &image, QObject *parent) :
+ QSurfaceDataProxy(new QHeightMapSurfaceDataProxyPrivate(this), parent)
+{
+ setHeightMap(image);
+}
+
+/*!
+ * Constructs QHeightMapSurfaceDataProxy from the given image \a filename and \a parent. Height map is set
+ * by calling setHeightMapFile() with \a filename.
+ *
+ * \sa heightMapFile
+ */
+QHeightMapSurfaceDataProxy::QHeightMapSurfaceDataProxy(const QString &filename, QObject *parent) :
+ QSurfaceDataProxy(new QHeightMapSurfaceDataProxyPrivate(this), parent)
+{
+ setHeightMapFile(filename);
+}
+
+/*!
+ * \internal
+ */
+QHeightMapSurfaceDataProxy::QHeightMapSurfaceDataProxy(
+ QHeightMapSurfaceDataProxyPrivate *d, QObject *parent) :
+ QSurfaceDataProxy(d, parent)
+{
+}
+
+/*!
+ * Destroys QHeightMapSurfaceDataProxy.
+ */
+QHeightMapSurfaceDataProxy::~QHeightMapSurfaceDataProxy()
+{
+}
+
+/*!
+ * \property QHeightMapSurfaceDataProxy::heightMap
+ *
+ * \brief The height map image to be visualized.
+ */
+
+/*!
+ * Replaces current data with the height map data specified by \a image.
+ *
+ * There are several formats the \a image can be given in, but if it is not in a directly usable
+ * format, a conversion is made.
+ *
+ * \note If the result seems wrong, the automatic conversion failed
+ * and you should try converting the \a image yourself before setting it. Preferred format is
+ * QImage::Format_RGB32 in grayscale.
+ *
+ * The height of the \a image is read from the red component of the pixels if the \a image is in
+ * grayscale, otherwise it is an average calculated from red, green, and blue components of the
+ * pixels. Using grayscale images may improve data conversion speed for large images.
+ *
+ * Not recommended formats: all mono formats (for example QImage::Format_Mono).
+ *
+ * The height map is resolved asynchronously. QSurfaceDataProxy::arrayReset() is emitted when the
+ * data has been resolved.
+ */
+void QHeightMapSurfaceDataProxy::setHeightMap(const QImage &image)
+{
+ dptr()->m_heightMap = image;
+
+ // We do resolving asynchronously to make qml onArrayReset handlers actually get the initial reset
+ if (!dptr()->m_resolveTimer.isActive())
+ dptr()->m_resolveTimer.start(0);
+}
+
+QImage QHeightMapSurfaceDataProxy::heightMap() const
+{
+ return dptrc()->m_heightMap;
+}
+
+/*!
+ * \property QHeightMapSurfaceDataProxy::heightMapFile
+ *
+ * \brief The name of the file with a height map image to be visualized.
+ */
+
+/*!
+ * Replaces current data with height map data from the file specified by
+ * \a filename.
+ *
+ * \sa heightMap
+ */
+void QHeightMapSurfaceDataProxy::setHeightMapFile(const QString &filename)
+{
+ dptr()->m_heightMapFile = filename;
+ setHeightMap(QImage(filename));
+ emit heightMapFileChanged(filename);
+}
+
+QString QHeightMapSurfaceDataProxy::heightMapFile() const
+{
+ return dptrc()->m_heightMapFile;
+}
+
+/*!
+ * A convenience function for setting all minimum (\a minX and \a minZ) and maximum
+ * (\a maxX and \a maxZ) values at the same time. The minimum values must be smaller than the
+ * corresponding maximum value. Otherwise the values get adjusted so that they are valid.
+ */
+void QHeightMapSurfaceDataProxy::setValueRanges(float minX, float maxX, float minZ, float maxZ)
+{
+ dptr()->setValueRanges(minX, maxX, minZ, maxZ);
+}
+
+/*!
+ * \property QHeightMapSurfaceDataProxy::minXValue
+ *
+ * \brief The minimum X value for the generated surface points.
+ *
+ * Defaults to \c{0.0}.
+ *
+ * When setting this property the corresponding maximum value is adjusted if necessary,
+ * to ensure that the range remains valid.
+ */
+void QHeightMapSurfaceDataProxy::setMinXValue(float min)
+{
+ dptr()->setMinXValue(min);
+}
+
+float QHeightMapSurfaceDataProxy::minXValue() const
+{
+ return dptrc()->m_minXValue;
+}
+
+/*!
+ * \property QHeightMapSurfaceDataProxy::maxXValue
+ *
+ * \brief The maximum X value for the generated surface points.
+ *
+ * Defaults to \c{10.0}.
+ *
+ * When setting this property the corresponding minimum value is adjusted if necessary,
+ * to ensure that the range remains valid.
+ */
+void QHeightMapSurfaceDataProxy::setMaxXValue(float max)
+{
+ dptr()->setMaxXValue(max);
+}
+
+float QHeightMapSurfaceDataProxy::maxXValue() const
+{
+ return dptrc()->m_maxXValue;
+}
+
+/*!
+ * \property QHeightMapSurfaceDataProxy::minZValue
+ *
+ * \brief The minimum Z value for the generated surface points.
+ *
+ * Defaults to \c{0.0}.
+ *
+ * When setting this property the corresponding maximum value is adjusted if necessary,
+ * to ensure that the range remains valid.
+ */
+void QHeightMapSurfaceDataProxy::setMinZValue(float min)
+{
+ dptr()->setMinZValue(min);
+}
+
+float QHeightMapSurfaceDataProxy::minZValue() const
+{
+ return dptrc()->m_minZValue;
+}
+
+/*!
+ * \property QHeightMapSurfaceDataProxy::maxZValue
+ *
+ * \brief The maximum Z value for the generated surface points.
+ *
+ * Defaults to \c{10.0}.
+ *
+ * When setting this property the corresponding minimum value is adjusted if necessary,
+ * to ensure that the range remains valid.
+ */
+void QHeightMapSurfaceDataProxy::setMaxZValue(float max)
+{
+ dptr()->setMaxZValue(max);
+}
+
+float QHeightMapSurfaceDataProxy::maxZValue() const
+{
+ return dptrc()->m_maxZValue;
+}
+
+/*!
+ * \property QHeightMapSurfaceDataProxy::minYValue
+ *
+ * \brief The minimum Y value for the generated surface points.
+ *
+ * Defaults to \c{0.0}.
+ *
+ * When setting this property the corresponding maximum value is adjusted if necessary,
+ * to ensure that the range remains valid.
+ *
+ * \sa autoScaleY
+ */
+void QHeightMapSurfaceDataProxy::setMinYValue(float min)
+{
+ dptr()->setMinYValue(min);
+}
+
+float QHeightMapSurfaceDataProxy::minYValue() const
+{
+ return dptrc()->m_minYValue;
+}
+
+/*!
+ * \property QHeightMapSurfaceDataProxy::maxYValue
+ *
+ * \brief The maximum Y value for the generated surface points.
+ *
+ * Defaults to \c{10.0}.
+ *
+ * When setting this property the corresponding minimum value is adjusted if necessary,
+ * to ensure that the range remains valid.
+ *
+ * \sa autoScaleY
+ */
+void QHeightMapSurfaceDataProxy::setMaxYValue(float max)
+{
+ dptr()->setMaxYValue(max);
+}
+
+float QHeightMapSurfaceDataProxy::maxYValue() const
+{
+ return dptrc()->m_maxYValue;
+}
+
+/*!
+ * \property QHeightMapSurfaceDataProxy::autoScaleY
+ *
+ * \brief Scale height values to Y-axis.
+ *
+ * Defaults to \c{false}.
+ *
+ * When this property is set to \c{true},
+ * the height values are scaled to fit on the Y-axis between minYValue and maxYValue.
+ *
+ * \sa minYValue, maxYValue
+ */
+void QHeightMapSurfaceDataProxy::setAutoScaleY(bool enabled)
+{
+ dptr()->setAutoScaleY(enabled);
+}
+
+bool QHeightMapSurfaceDataProxy::autoScaleY() const
+{
+ return dptrc()->m_autoScaleY;
+}
+
+/*!
+ * \internal
+ */
+QHeightMapSurfaceDataProxyPrivate *QHeightMapSurfaceDataProxy::dptr()
+{
+ return static_cast<QHeightMapSurfaceDataProxyPrivate *>(d_ptr.data());
+}
+
+/*!
+ * \internal
+ */
+const QHeightMapSurfaceDataProxyPrivate *QHeightMapSurfaceDataProxy::dptrc() const
+{
+ return static_cast<const QHeightMapSurfaceDataProxyPrivate *>(d_ptr.data());
+}
+
+// QHeightMapSurfaceDataProxyPrivate
+
+QHeightMapSurfaceDataProxyPrivate::QHeightMapSurfaceDataProxyPrivate(QHeightMapSurfaceDataProxy *q)
+ : QSurfaceDataProxyPrivate(q),
+ m_minXValue(defaultMinValue),
+ m_maxXValue(defaultMaxValue),
+ m_minZValue(defaultMinValue),
+ m_maxZValue(defaultMaxValue),
+ m_minYValue(defaultMinValue),
+ m_maxYValue(defaultMaxValue),
+ m_autoScaleY(false)
+{
+ m_resolveTimer.setSingleShot(true);
+ QObject::connect(&m_resolveTimer, &QTimer::timeout,
+ this, &QHeightMapSurfaceDataProxyPrivate::handlePendingResolve);
+}
+
+QHeightMapSurfaceDataProxyPrivate::~QHeightMapSurfaceDataProxyPrivate()
+{
+}
+
+QHeightMapSurfaceDataProxy *QHeightMapSurfaceDataProxyPrivate::qptr()
+{
+ return static_cast<QHeightMapSurfaceDataProxy *>(q_ptr);
+}
+
+void QHeightMapSurfaceDataProxyPrivate::setValueRanges(float minX, float maxX,
+ float minZ, float maxZ)
+{
+ bool minXChanged = false;
+ bool maxXChanged = false;
+ bool minZChanged = false;
+ bool maxZChanged = false;
+ if (m_minXValue != minX) {
+ m_minXValue = minX;
+ minXChanged = true;
+ }
+ if (m_minZValue != minZ) {
+ m_minZValue = minZ;
+ minZChanged = true;
+ }
+ if (m_maxXValue != maxX || minX >= maxX) {
+ if (minX >= maxX) {
+ m_maxXValue = minX + 1.0f;
+ qWarning() << "Warning: Tried to set invalid range for X value range."
+ " Range automatically adjusted to a valid one:"
+ << minX << "-" << maxX << "-->" << m_minXValue << "-" << m_maxXValue;
+ } else {
+ m_maxXValue = maxX;
+ }
+ maxXChanged = true;
+ }
+ if (m_maxZValue != maxZ || minZ >= maxZ) {
+ if (minZ >= maxZ) {
+ m_maxZValue = minZ + 1.0f;
+ qWarning() << "Warning: Tried to set invalid range for Z value range."
+ " Range automatically adjusted to a valid one:"
+ << minZ << "-" << maxZ << "-->" << m_minZValue << "-" << m_maxZValue;
+ } else {
+ m_maxZValue = maxZ;
+ }
+ maxZChanged = true;
+ }
+
+ if (minXChanged)
+ emit qptr()->minXValueChanged(m_minXValue);
+ if (minZChanged)
+ emit qptr()->minZValueChanged(m_minZValue);
+ if (maxXChanged)
+ emit qptr()->maxXValueChanged(m_maxXValue);
+ if (maxZChanged)
+ emit qptr()->maxZValueChanged(m_maxZValue);
+
+ if ((minXChanged || minZChanged || maxXChanged || maxZChanged) && !m_resolveTimer.isActive())
+ m_resolveTimer.start(0);
+}
+
+void QHeightMapSurfaceDataProxyPrivate::setMinXValue(float min)
+{
+ if (min != m_minXValue) {
+ bool maxChanged = false;
+ if (min >= m_maxXValue) {
+ float oldMax = m_maxXValue;
+ m_maxXValue = min + 1.0f;
+ qWarning() << "Warning: Tried to set minimum X to equal or larger than maximum X for"
+ " value range. Maximum automatically adjusted to a valid one:"
+ << oldMax << "-->" << m_maxXValue;
+ maxChanged = true;
+ }
+ m_minXValue = min;
+ emit qptr()->minXValueChanged(m_minXValue);
+ if (maxChanged)
+ emit qptr()->maxXValueChanged(m_maxXValue);
+
+ if (!m_resolveTimer.isActive())
+ m_resolveTimer.start(0);
+ }
+}
+
+void QHeightMapSurfaceDataProxyPrivate::setMaxXValue(float max)
+{
+ if (m_maxXValue != max) {
+ bool minChanged = false;
+ if (max <= m_minXValue) {
+ float oldMin = m_minXValue;
+ m_minXValue = max - 1.0f;
+ qWarning() << "Warning: Tried to set maximum X to equal or smaller than minimum X for"
+ " value range. Minimum automatically adjusted to a valid one:"
+ << oldMin << "-->" << m_minXValue;
+ minChanged = true;
+ }
+ m_maxXValue = max;
+ emit qptr()->maxXValueChanged(m_maxXValue);
+ if (minChanged)
+ emit qptr()->minXValueChanged(m_minXValue);
+
+ if (!m_resolveTimer.isActive())
+ m_resolveTimer.start(0);
+ }
+}
+
+void QHeightMapSurfaceDataProxyPrivate::setMinZValue(float min)
+{
+ if (min != m_minZValue) {
+ bool maxChanged = false;
+ if (min >= m_maxZValue) {
+ float oldMax = m_maxZValue;
+ m_maxZValue = min + 1.0f;
+ qWarning() << "Warning: Tried to set minimum Z to equal or larger than maximum Z for"
+ " value range. Maximum automatically adjusted to a valid one:"
+ << oldMax << "-->" << m_maxZValue;
+ maxChanged = true;
+ }
+ m_minZValue = min;
+ emit qptr()->minZValueChanged(m_minZValue);
+ if (maxChanged)
+ emit qptr()->maxZValueChanged(m_maxZValue);
+
+ if (!m_resolveTimer.isActive())
+ m_resolveTimer.start(0);
+ }
+}
+
+void QHeightMapSurfaceDataProxyPrivate::setMaxZValue(float max)
+{
+ if (m_maxZValue != max) {
+ bool minChanged = false;
+ if (max <= m_minZValue) {
+ float oldMin = m_minZValue;
+ m_minZValue = max - 1.0f;
+ qWarning() << "Warning: Tried to set maximum Z to equal or smaller than minimum Z for"
+ " value range. Minimum automatically adjusted to a valid one:"
+ << oldMin << "-->" << m_minZValue;
+ minChanged = true;
+ }
+ m_maxZValue = max;
+ emit qptr()->maxZValueChanged(m_maxZValue);
+ if (minChanged)
+ emit qptr()->minZValueChanged(m_minZValue);
+
+ if (!m_resolveTimer.isActive())
+ m_resolveTimer.start(0);
+ }
+}
+
+void QHeightMapSurfaceDataProxyPrivate::setMinYValue(float min)
+{
+ if (m_minYValue != min) {
+ bool maxChanged = false;
+ if (min >= m_maxYValue) {
+ float oldMax = m_maxYValue;
+ m_maxYValue = min + 1.0f;
+ qWarning() << "Warning: Tried to set minimum Y to equal or larger than maximum Y for"
+ " value range. Maximum automatically adjusted to a valid one:"
+ << oldMax << "-->" << m_maxYValue;
+ maxChanged = true;
+ }
+ m_minYValue = min;
+ emit qptr()->minYValueChanged(m_minYValue);
+ if (maxChanged)
+ emit qptr()->maxYValueChanged(m_maxYValue);
+
+ if (!m_resolveTimer.isActive())
+ m_resolveTimer.start(0);
+ }
+}
+
+void QHeightMapSurfaceDataProxyPrivate::setMaxYValue(float max)
+{
+ if (m_maxYValue != max) {
+ bool minChanged = false;
+ if (max <= m_minYValue) {
+ float oldMin = m_minYValue;
+ m_minYValue = max - 1.0f;
+ qWarning() << "Warning: Tried to set maximum Y to equal or smaller than minimum Y for"
+ " value range. Minimum automatically adjusted to a valid one:"
+ << oldMin << "-->" << m_minYValue;
+ minChanged = true;
+ }
+ m_maxYValue = max;
+ emit qptr()->maxYValueChanged(m_maxYValue);
+ if (minChanged)
+ emit qptr()->minYValueChanged(m_minYValue);
+
+ if (!m_resolveTimer.isActive())
+ m_resolveTimer.start(0);
+ }
+}
+
+void QHeightMapSurfaceDataProxyPrivate::setAutoScaleY(bool enabled)
+{
+ if (enabled != m_autoScaleY) {
+ m_autoScaleY = enabled;
+ emit qptr()->autoScaleYChanged(m_autoScaleY);
+
+ if (!m_resolveTimer.isActive())
+ m_resolveTimer.start(0);
+ }
+}
+
+void QHeightMapSurfaceDataProxyPrivate::handlePendingResolve()
+{
+ QImage heightImage = m_heightMap;
+ int bytesInChannel = 1;
+ float yMul = 1.0f / UINT8_MAX;
+
+ bool is16bit = (heightImage.format() == QImage::Format_RGBX64
+ || heightImage.format() == QImage::Format_RGBA64
+ || heightImage.format() == QImage::Format_RGBA64_Premultiplied
+ || heightImage.format() == QImage::Format_Grayscale16);
+
+ // Convert to RGB32 to be sure we're reading the right bytes
+ if (is16bit) {
+ if (heightImage.format() != QImage::Format_RGBX64)
+ heightImage = heightImage.convertToFormat(QImage::Format_RGBX64);
+
+ bytesInChannel = 2;
+ yMul = 1.0f / UINT16_MAX;
+ } else if (heightImage.format() != QImage::Format_RGB32) {
+ heightImage = heightImage.convertToFormat(QImage::Format_RGB32);
+ }
+
+ uchar *bits = heightImage.bits();
+
+ int imageHeight = heightImage.height();
+ int imageWidth = heightImage.width();
+ int bitCount = imageWidth * 4 * (imageHeight - 1) * bytesInChannel;
+ int widthBits = imageWidth * 4 * bytesInChannel;
+ float height = 0;
+
+ // Do not recreate array if dimensions have not changed
+ QSurfaceDataArray *dataArray = m_dataArray;
+ if (imageWidth != qptr()->columnCount() || imageHeight != dataArray->size()) {
+ dataArray = new QSurfaceDataArray;
+ dataArray->reserve(imageHeight);
+ for (int i = 0; i < imageHeight; i++) {
+ QSurfaceDataRow *newProxyRow = new QSurfaceDataRow(imageWidth);
+ dataArray->append(newProxyRow);
+ }
+ }
+ yMul *= m_maxYValue - m_minYValue;
+ float xMul = (m_maxXValue - m_minXValue) / float(imageWidth - 1);
+ float zMul = (m_maxZValue - m_minZValue) / float(imageHeight - 1);
+
+ // Last row and column are explicitly set to max values, as relying
+ // on multiplier can cause rounding errors, resulting in the value being
+ // slightly over the specified maximum, which in turn can lead to it not
+ // getting rendered.
+ int lastRow = imageHeight - 1;
+ int lastCol = imageWidth - 1;
+ if (heightImage.isGrayscale()) {
+ // Grayscale, it's enough to read Red byte
+ for (int i = 0; i < imageHeight; i++, bitCount -= widthBits) {
+ QSurfaceDataRow &newRow = *dataArray->at(i);
+ float zVal;
+ if (i == lastRow)
+ zVal = m_maxZValue;
+ else
+ zVal = (float(i) * zMul) + m_minZValue;
+ int j = 0;
+ float yVal = 0;
+ uchar *pixelptr;
+ for (; j < lastCol; j++) {
+ pixelptr = (uchar *)(bits + bitCount + (j * 4 * bytesInChannel));
+ if (!m_autoScaleY)
+ yVal = *pixelptr;
+ else
+ yVal = float(*pixelptr) * yMul + m_minYValue;
+ newRow[j].setPosition(QVector3D((float(j) * xMul) + m_minXValue,
+ yVal,
+ zVal));
+ }
+ newRow[j].setPosition(QVector3D(m_maxXValue,
+ yVal,
+ zVal));
+ }
+ } else {
+ // Not grayscale, we'll need to calculate height from RGB
+ for (int i = 0; i < imageHeight; i++, bitCount -= widthBits) {
+ QSurfaceDataRow &newRow = *dataArray->at(i);
+ float zVal;
+ if (i == lastRow)
+ zVal = m_maxZValue;
+ else
+ zVal = (float(i) * zMul) + m_minZValue;
+ int j = 0;
+ float yVal = 0;
+ for (; j < lastCol; j++) {
+ int nextpixel = j * 4 * bytesInChannel;
+ uchar *pixelptr = (uchar *)(bits + bitCount + nextpixel);
+ if (is16bit) {
+ height = float(*((ushort *)pixelptr))
+ + float(*(((ushort *)pixelptr) + 1))
+ + float(*(((ushort *)pixelptr) + 2));
+ } else {
+ height = (float(*pixelptr)
+ + float(*(pixelptr + 1))
+ + float(*(pixelptr + 2)));
+ }
+ if (!m_autoScaleY)
+ yVal = height / 3.0f;
+ else
+ yVal = (height / 3.0f * yMul) + m_minYValue;
+
+ newRow[j].setPosition(QVector3D((float(j) * xMul) + m_minXValue,
+ yVal,
+ zVal));
+ }
+ newRow[j].setPosition(QVector3D(m_maxXValue,
+ yVal,
+ zVal));
+ }
+ }
+
+ qptr()->resetArray(dataArray);
+ emit qptr()->heightMapChanged(m_heightMap);
+}
+
+QT_END_NAMESPACE
diff --git a/src/graphs/data/qheightmapsurfacedataproxy.h b/src/graphs/data/qheightmapsurfacedataproxy.h
new file mode 100644
index 0000000..55d1c16
--- /dev/null
+++ b/src/graphs/data/qheightmapsurfacedataproxy.h
@@ -0,0 +1,80 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QHEIGHTMAPSURFACEDATAPROXY_H
+#define QHEIGHTMAPSURFACEDATAPROXY_H
+
+#include <QtGraphs/qsurfacedataproxy.h>
+#include <QtGui/QImage>
+#include <QtCore/QString>
+
+QT_BEGIN_NAMESPACE
+
+class QHeightMapSurfaceDataProxyPrivate;
+
+class Q_GRAPHS_EXPORT QHeightMapSurfaceDataProxy : public QSurfaceDataProxy
+{
+ Q_OBJECT
+
+ Q_PROPERTY(QImage heightMap READ heightMap WRITE setHeightMap NOTIFY heightMapChanged)
+ Q_PROPERTY(QString heightMapFile READ heightMapFile WRITE setHeightMapFile NOTIFY heightMapFileChanged)
+ Q_PROPERTY(float minXValue READ minXValue WRITE setMinXValue NOTIFY minXValueChanged)
+ Q_PROPERTY(float maxXValue READ maxXValue WRITE setMaxXValue NOTIFY maxXValueChanged)
+ Q_PROPERTY(float minZValue READ minZValue WRITE setMinZValue NOTIFY minZValueChanged)
+ Q_PROPERTY(float maxZValue READ maxZValue WRITE setMaxZValue NOTIFY maxZValueChanged)
+ Q_PROPERTY(float minYValue READ minYValue WRITE setMinYValue NOTIFY minYValueChanged)
+ Q_PROPERTY(float maxYValue READ maxYValue WRITE setMaxYValue NOTIFY maxYValueChanged)
+ Q_PROPERTY(bool autoScaleY READ autoScaleY WRITE setAutoScaleY NOTIFY autoScaleYChanged)
+
+public:
+ explicit QHeightMapSurfaceDataProxy(QObject *parent = nullptr);
+ explicit QHeightMapSurfaceDataProxy(const QImage &image, QObject *parent = nullptr);
+ explicit QHeightMapSurfaceDataProxy(const QString &filename, QObject *parent = nullptr);
+ virtual ~QHeightMapSurfaceDataProxy();
+
+ void setHeightMap(const QImage &image);
+ QImage heightMap() const;
+ void setHeightMapFile(const QString &filename);
+ QString heightMapFile() const;
+
+ void setValueRanges(float minX, float maxX, float minZ, float maxZ);
+ void setMinXValue(float min);
+ float minXValue() const;
+ void setMaxXValue(float max);
+ float maxXValue() const;
+ void setMinZValue(float min);
+ float minZValue() const;
+ void setMaxZValue(float max);
+ float maxZValue() const;
+ void setMinYValue(float min);
+ float minYValue() const;
+ void setMaxYValue(float max);
+ float maxYValue() const;
+ void setAutoScaleY(bool enabled);
+ bool autoScaleY() const;
+
+Q_SIGNALS:
+ void heightMapChanged(const QImage &image);
+ void heightMapFileChanged(const QString &filename);
+ void minXValueChanged(float value);
+ void maxXValueChanged(float value);
+ void minZValueChanged(float value);
+ void maxZValueChanged(float value);
+ void minYValueChanged(float value);
+ void maxYValueChanged(float value);
+ void autoScaleYChanged(bool enabled);
+
+protected:
+ explicit QHeightMapSurfaceDataProxy(QHeightMapSurfaceDataProxyPrivate *d, QObject *parent = nullptr);
+ QHeightMapSurfaceDataProxyPrivate *dptr();
+ const QHeightMapSurfaceDataProxyPrivate *dptrc() const;
+
+private:
+ Q_DISABLE_COPY(QHeightMapSurfaceDataProxy)
+
+ friend class Surface3DController;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/data/qheightmapsurfacedataproxy_p.h b/src/graphs/data/qheightmapsurfacedataproxy_p.h
new file mode 100644
index 0000000..1ae0fd4
--- /dev/null
+++ b/src/graphs/data/qheightmapsurfacedataproxy_p.h
@@ -0,0 +1,60 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QtGraphs API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#ifndef QHEIGHTMAPSURFACEDATAPROXY_P_H
+#define QHEIGHTMAPSURFACEDATAPROXY_P_H
+
+#include "qheightmapsurfacedataproxy.h"
+#include "qsurfacedataproxy_p.h"
+#include <QtCore/QTimer>
+
+QT_BEGIN_NAMESPACE
+
+class QHeightMapSurfaceDataProxyPrivate : public QSurfaceDataProxyPrivate
+{
+ Q_OBJECT
+
+public:
+ QHeightMapSurfaceDataProxyPrivate(QHeightMapSurfaceDataProxy *q);
+ virtual ~QHeightMapSurfaceDataProxyPrivate();
+
+ void setValueRanges(float minX, float maxX, float minZ, float maxZ);
+ void setMinXValue(float min);
+ void setMaxXValue(float max);
+ void setMinZValue(float min);
+ void setMaxZValue(float max);
+ void setMinYValue(float min);
+ void setMaxYValue(float max);
+ void setAutoScaleY(bool enabled);
+private:
+ QHeightMapSurfaceDataProxy *qptr();
+ void handlePendingResolve();
+
+ QImage m_heightMap;
+ QString m_heightMapFile;
+ QTimer m_resolveTimer;
+
+ float m_minXValue;
+ float m_maxXValue;
+ float m_minZValue;
+ float m_maxZValue;
+ float m_minYValue;
+ float m_maxYValue;
+ bool m_autoScaleY;
+
+ friend class QHeightMapSurfaceDataProxy;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/data/qitemmodelbardataproxy.cpp b/src/graphs/data/qitemmodelbardataproxy.cpp
new file mode 100644
index 0000000..a0172bf
--- /dev/null
+++ b/src/graphs/data/qitemmodelbardataproxy.cpp
@@ -0,0 +1,934 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qitemmodelbardataproxy_p.h"
+#include "baritemmodelhandler_p.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ * \class QItemModelBarDataProxy
+ * \inmodule QtGraphs
+ * \brief Proxy class for presenting data in item models with Q3DBars.
+ *
+ * QItemModelBarDataProxy allows you to use QAbstractItemModel derived models as a data source
+ * for Q3DBars. It uses the defined mappings to map data from the model to rows, columns, and
+ * values of Q3DBars graph.
+ *
+ * The data is resolved asynchronously whenever mappings or the model changes.
+ * QBarDataProxy::arrayReset() is emitted when the data has been resolved.
+ * However, when useModelCategories property is set to true, single item changes are resolved
+ * synchronously, unless the same frame also contains a change that causes the whole model to be
+ * resolved.
+ *
+ * Mappings can be used in the following ways:
+ *
+ * \list
+ * \li If useModelCategories property is set to true, this proxy will map rows and
+ * columns of QAbstractItemModel directly to rows and columns of Q3DBars, and uses the value
+ * returned for Qt::DisplayRole as bar value by default.
+ * The value role to be used can be redefined if Qt::DisplayRole is not suitable.
+ *
+ * \li For models that do not have data already neatly sorted into rows and columns, such as
+ * QAbstractListModel based models, you can define a role from the model to map for each of row,
+ * column and value.
+ *
+ * \li If you do not want to include all data contained in the model, or the autogenerated rows and
+ * columns are not ordered as you wish, you can specify which rows and columns should be included
+ * and in which order by defining an explicit list of categories for either or both of rows and
+ * columns.
+ * \endlist
+ *
+ * For example, assume that you have a custom QAbstractItemModel for storing various monthly values
+ * related to a business.
+ * Each item in the model has the roles "year", "month", "income", and "expenses".
+ * You could do the following to display the data in a bar graph:
+ *
+ * \snippet doc_src_qtgraphs.cpp 3
+ *
+ * If the fields of the model do not contain the data in the exact format you need, you can specify
+ * a search pattern regular expression and a replace rule for each role to get the value in a
+ * format you need. For more information how the replace using regular expressions works, see
+ * QString::replace(const QRegularExpression &rx, const QString &after) function documentation. Note that
+ * using regular expressions has an impact on the performance, so it's more efficient to utilize
+ * item models where doing search and replace is not necessary to get the desired values.
+ *
+ * For example about using the search patterns in conjunction with the roles, see
+ * \l{Qt Quick 2 Bars Example}.
+ *
+ * \sa {Qt Graphs Data Handling}
+ */
+
+/*!
+ * \qmltype ItemModelBarDataProxy
+ * \inqmlmodule QtGraphs
+ * \ingroup graphs_qml
+ * \instantiates QItemModelBarDataProxy
+ * \inherits BarDataProxy
+ * \brief Proxy class for presenting data in item models with Bars3D.
+ *
+ * This type allows you to use AbstractItemModel derived models as a data source for Bars3D.
+ *
+ * Data is resolved asynchronously whenever the mapping or the model changes.
+ * QBarDataProxy::arrayReset() is emitted when the data has been resolved.
+ *
+ * For ItemModelBarDataProxy enums, see \l{QItemModelBarDataProxy::MultiMatchBehavior}.
+ *
+ * For more details, see QItemModelBarDataProxy documentation.
+ *
+ * Usage example:
+ *
+ * \snippet doc_src_qmlgraphs.cpp 7
+ *
+ * \sa BarDataProxy, {Qt Graphs Data Handling}
+ */
+
+/*!
+ * \qmlproperty model ItemModelBarDataProxy::itemModel
+ * The item model.
+ */
+
+/*!
+ * \qmlproperty string ItemModelBarDataProxy::rowRole
+ * The item model role to map into row category.
+ */
+
+/*!
+ * \qmlproperty string ItemModelBarDataProxy::columnRole
+ * The item model role to map into column category.
+ */
+
+/*!
+ * \qmlproperty string ItemModelBarDataProxy::valueRole
+ * The item model role to map into bar value.
+ */
+
+/*!
+ * \qmlproperty string ItemModelBarDataProxy::rotationRole
+ * The item model role to map into bar rotation angle.
+ */
+
+/*!
+ * \qmlproperty list<String> ItemModelBarDataProxy::rowCategories
+ * The row categories of the mapping. Only items with row role values that are found in this list
+ * are included when the data is resolved. The rows are ordered in the same order as they are in
+ * this list.
+ */
+
+/*!
+ * \qmlproperty list<String> ItemModelBarDataProxy::columnCategories
+ * The column categories of the mapping. Only items with column role values that are found in this
+ * list are included when the data is resolved. The columns are ordered in the same order as they
+ * are in this list.
+ */
+
+/*!
+ * \qmlproperty bool ItemModelBarDataProxy::useModelCategories
+ * When set to \c true, the mapping ignores row and column roles and categories, and uses
+ * the rows and columns from the model instead. Row and column headers are used for row and column
+ * labels. Defaults to \c{false}.
+ */
+
+/*!
+ * \qmlproperty bool ItemModelBarDataProxy::autoRowCategories
+ * When set to \c true, the mapping ignores any explicitly set row categories
+ * and overwrites them with automatically generated ones whenever the
+ * data from the model is resolved. Defaults to \c{true}.
+ */
+
+/*!
+ * \qmlproperty bool ItemModelBarDataProxy::autoColumnCategories
+ * When set to \c true, the mapping ignores any explicitly set column categories
+ * and overwrites them with automatically generated ones whenever the
+ * data from model is resolved. Defaults to \c{true}.
+ */
+
+/*!
+ * \qmlproperty regExp ItemModelBarDataProxy::rowRolePattern
+ * When set, a search and replace is done on the value mapped by row role before it is used as
+ * a row category. This property specifies the regular expression to find the portion of the
+ * mapped value to replace and rowRoleReplace property contains the replacement string.
+ * This is useful for example in parsing row and column categories from a single
+ * timestamp field in the item model.
+ *
+ * \sa rowRole, rowRoleReplace
+ */
+
+/*!
+ * \qmlproperty regExp ItemModelBarDataProxy::columnRolePattern
+ * When set, a search and replace is done on the value mapped by column role before it is used
+ * as a column category. This property specifies the regular expression to find the portion of the
+ * mapped value to replace and columnRoleReplace property contains the replacement string.
+ * This is useful for example in parsing row and column categories from
+ * a single timestamp field in the item model.
+ *
+ * \sa columnRole, columnRoleReplace
+ */
+
+/*!
+ * \qmlproperty regExp ItemModelBarDataProxy::valueRolePattern
+ * When set, a search and replace is done on the value mapped by value role before it is used as
+ * a bar value. This property specifies the regular expression to find the portion of the
+ * mapped value to replace and valueRoleReplace property contains the replacement string.
+ *
+ * \sa valueRole, valueRoleReplace
+ */
+
+/*!
+ * \qmlproperty regExp ItemModelBarDataProxy::rotationRolePattern
+ * When set, a search and replace is done on the value mapped by rotation role before it is used
+ * as a bar rotation angle. This property specifies the regular expression to find the portion
+ * of the mapped value to replace and rotationRoleReplace property contains the replacement string.
+ *
+ * \sa rotationRole, rotationRoleReplace
+ */
+
+/*!
+ * \qmlproperty string ItemModelBarDataProxy::rowRoleReplace
+ * This property defines the replace content to be used in conjunction with rowRolePattern.
+ * Defaults to empty string. For more information on how the search and replace using regular
+ * expressions works, see QString::replace(const QRegularExpression &rx, const QString &after)
+ * function documentation.
+ *
+ * \sa rowRole, rowRolePattern
+ */
+
+/*!
+ * \qmlproperty string ItemModelBarDataProxy::columnRoleReplace
+ * This property defines the replace content to be used in conjunction with columnRolePattern.
+ * Defaults to empty string. For more information on how the search and replace using regular
+ * expressions works, see QString::replace(const QRegularExpression &rx, const QString &after)
+ * function documentation.
+ *
+ * \sa columnRole, columnRolePattern
+ */
+
+/*!
+ * \qmlproperty string ItemModelBarDataProxy::valueRoleReplace
+ * This property defines the replace content to be used in conjunction with valueRolePattern.
+ * Defaults to empty string. For more information on how the search and replace using regular
+ * expressions works, see QString::replace(const QRegularExpression &rx, const QString &after)
+ * function documentation.
+ *
+ * \sa valueRole, valueRolePattern
+ */
+
+/*!
+ * \qmlproperty string ItemModelBarDataProxy::rotationRoleReplace
+ * This property defines the replace content to be used in conjunction with rotationRolePattern.
+ * Defaults to empty string. For more information on how the search and replace using regular
+ * expressions works, see QString::replace(const QRegularExpression &rx, const QString &after)
+ * function documentation.
+ *
+ * \sa rotationRole, rotationRolePattern
+ */
+
+/*!
+ * \qmlproperty ItemModelBarDataProxy.MultiMatchBehavior ItemModelBarDataProxy::multiMatchBehavior
+ * Defines how multiple matches for each row/column combination are handled.
+ * Defaults to \l{QItemModelBarDataProxy::MMBLast}{ItemModelBarDataProxy.MMBLast}. The chosen
+ * behavior affects both bar value and rotation.
+ *
+ * For example, you might have an item model with timestamped data taken at irregular intervals
+ * and you want to visualize total value of data items on each day with a bar graph.
+ * This can be done by specifying row and column categories so that each bar represents a day,
+ * and setting multiMatchBehavior to
+ * \l{QItemModelBarDataProxy::MMBCumulative}{ItemModelBarDataProxy.MMBCumulative}.
+ */
+
+/*!
+ * \enum QItemModelBarDataProxy::MultiMatchBehavior
+ *
+ * Behavior types for QItemModelBarDataProxy::multiMatchBehavior property.
+ *
+ * \value MMBFirst
+ * The value is taken from the first item in the item model that matches
+ * each row/column combination.
+ * \value MMBLast
+ * The value is taken from the last item in the item model that matches
+ * each row/column combination.
+ * \value MMBAverage
+ * The values from all items matching each row/column combination are
+ * averaged together and the average is used as the bar value.
+ * \value MMBCumulative
+ * The values from all items matching each row/column combination are
+ * added together and the total is used as the bar value.
+ */
+
+/*!
+ * Constructs QItemModelBarDataProxy with optional \a parent.
+ */
+QItemModelBarDataProxy::QItemModelBarDataProxy(QObject *parent)
+ : QBarDataProxy(new QItemModelBarDataProxyPrivate(this), parent)
+{
+ dptr()->connectItemModelHandler();
+}
+
+/*!
+ * Constructs QItemModelBarDataProxy with \a itemModel and optional \a parent. Proxy doesn't take
+ * ownership of the \a itemModel, as typically item models are owned by other controls.
+ */
+QItemModelBarDataProxy::QItemModelBarDataProxy(QAbstractItemModel *itemModel, QObject *parent)
+ : QBarDataProxy(new QItemModelBarDataProxyPrivate(this), parent)
+{
+ setItemModel(itemModel);
+ dptr()->connectItemModelHandler();
+}
+
+/*!
+ * Constructs QItemModelBarDataProxy with \a itemModel and optional \a parent. Proxy doesn't take
+ * ownership of the \a itemModel, as typically item models are owned by other controls.
+ * The value role is set to \a valueRole.
+ * This constructor is meant to be used with models that have data properly sorted
+ * in rows and columns already, so it also sets useModelCategories property to true.
+ */
+QItemModelBarDataProxy::QItemModelBarDataProxy(QAbstractItemModel *itemModel,
+ const QString &valueRole, QObject *parent)
+ : QBarDataProxy(new QItemModelBarDataProxyPrivate(this), parent)
+{
+ dptr()->m_itemModelHandler->setItemModel(itemModel);
+ dptr()->m_valueRole = valueRole;
+ dptr()->m_useModelCategories = true;
+ dptr()->connectItemModelHandler();
+}
+
+/*!
+ * Constructs QItemModelBarDataProxy with \a itemModel and optional \a parent. Proxy doesn't take
+ * ownership of the \a itemModel, as typically item models are owned by other controls.
+ * The role mappings are set with \a rowRole, \a columnRole, and \a valueRole.
+ */
+QItemModelBarDataProxy::QItemModelBarDataProxy(QAbstractItemModel *itemModel,
+ const QString &rowRole,
+ const QString &columnRole,
+ const QString &valueRole, QObject *parent)
+ : QBarDataProxy(new QItemModelBarDataProxyPrivate(this), parent)
+{
+ dptr()->m_itemModelHandler->setItemModel(itemModel);
+ dptr()->m_rowRole = rowRole;
+ dptr()->m_columnRole = columnRole;
+ dptr()->m_valueRole = valueRole;
+ dptr()->connectItemModelHandler();
+}
+
+/*!
+ * Constructs QItemModelBarDataProxy with \a itemModel and optional \a parent. Proxy doesn't take
+ * ownership of the \a itemModel, as typically item models are owned by other controls.
+ * The role mappings are set with \a rowRole, \a columnRole, \a valueRole, and \a rotationRole.
+ */
+QItemModelBarDataProxy::QItemModelBarDataProxy(QAbstractItemModel *itemModel,
+ const QString &rowRole,
+ const QString &columnRole,
+ const QString &valueRole,
+ const QString &rotationRole,
+ QObject *parent)
+ : QBarDataProxy(new QItemModelBarDataProxyPrivate(this), parent)
+{
+ dptr()->m_itemModelHandler->setItemModel(itemModel);
+ dptr()->m_rowRole = rowRole;
+ dptr()->m_columnRole = columnRole;
+ dptr()->m_valueRole = valueRole;
+ dptr()->m_rotationRole = rotationRole;
+ dptr()->connectItemModelHandler();
+}
+
+/*!
+ * Constructs QItemModelBarDataProxy with \a itemModel and optional \a parent. Proxy doesn't take
+ * ownership of the \a itemModel, as typically item models are owned by other controls.
+ * The role mappings are set with \a rowRole, \a columnRole, and \a valueRole.
+ * Row and column categories are set with \a rowCategories and \a columnCategories.
+ * This constructor also sets autoRowCategories and autoColumnCategories to false.
+ */
+QItemModelBarDataProxy::QItemModelBarDataProxy(QAbstractItemModel *itemModel,
+ const QString &rowRole,
+ const QString &columnRole,
+ const QString &valueRole,
+ const QStringList &rowCategories,
+ const QStringList &columnCategories,
+ QObject *parent)
+ : QBarDataProxy(new QItemModelBarDataProxyPrivate(this), parent)
+{
+ dptr()->m_itemModelHandler->setItemModel(itemModel);
+ dptr()->m_rowRole = rowRole;
+ dptr()->m_columnRole = columnRole;
+ dptr()->m_valueRole = valueRole;
+ dptr()->m_rowCategories = rowCategories;
+ dptr()->m_columnCategories = columnCategories;
+ dptr()->m_autoRowCategories = false;
+ dptr()->m_autoColumnCategories = false;
+ dptr()->connectItemModelHandler();
+}
+
+/*!
+ * Constructs QItemModelBarDataProxy with \a itemModel and optional \a parent. Proxy doesn't take
+ * ownership of the \a itemModel, as typically item models are owned by other controls.
+ * The role mappings are set with \a rowRole, \a columnRole, \a valueRole, and \a rotationRole.
+ * Row and column categories are set with \a rowCategories and \a columnCategories.
+ * This constructor also sets autoRowCategories and autoColumnCategories to false.
+ */
+QItemModelBarDataProxy::QItemModelBarDataProxy(QAbstractItemModel *itemModel,
+ const QString &rowRole,
+ const QString &columnRole,
+ const QString &valueRole,
+ const QString &rotationRole,
+ const QStringList &rowCategories,
+ const QStringList &columnCategories,
+ QObject *parent)
+ : QBarDataProxy(new QItemModelBarDataProxyPrivate(this), parent)
+{
+ dptr()->m_itemModelHandler->setItemModel(itemModel);
+ dptr()->m_rowRole = rowRole;
+ dptr()->m_columnRole = columnRole;
+ dptr()->m_valueRole = valueRole;
+ dptr()->m_rotationRole = rotationRole;
+ dptr()->m_rowCategories = rowCategories;
+ dptr()->m_columnCategories = columnCategories;
+ dptr()->m_autoRowCategories = false;
+ dptr()->m_autoColumnCategories = false;
+ dptr()->connectItemModelHandler();
+}
+
+/*!
+ * Destroys QItemModelBarDataProxy.
+ */
+QItemModelBarDataProxy::~QItemModelBarDataProxy()
+{
+}
+
+/*!
+ * \property QItemModelBarDataProxy::itemModel
+ *
+ * \brief The item model.
+ */
+
+/*!
+ * Sets the item model to \a itemModel. Does not take ownership of the model,
+ * but does connect to it to listen for changes.
+ */
+void QItemModelBarDataProxy::setItemModel(QAbstractItemModel *itemModel)
+{
+ dptr()->m_itemModelHandler->setItemModel(itemModel);
+}
+
+QAbstractItemModel *QItemModelBarDataProxy::itemModel() const
+{
+ return dptrc()->m_itemModelHandler->itemModel();
+}
+
+/*!
+ * \property QItemModelBarDataProxy::rowRole
+ *
+ * \brief The row role for the mapping.
+ */
+void QItemModelBarDataProxy::setRowRole(const QString &role)
+{
+ if (dptr()->m_rowRole != role) {
+ dptr()->m_rowRole = role;
+ emit rowRoleChanged(role);
+ }
+}
+
+QString QItemModelBarDataProxy::rowRole() const
+{
+ return dptrc()->m_rowRole;
+}
+
+/*!
+ * \property QItemModelBarDataProxy::columnRole
+ *
+ * \brief The column role for the mapping.
+ */
+void QItemModelBarDataProxy::setColumnRole(const QString &role)
+{
+ if (dptr()->m_columnRole != role) {
+ dptr()->m_columnRole = role;
+ emit columnRoleChanged(role);
+ }
+}
+
+QString QItemModelBarDataProxy::columnRole() const
+{
+ return dptrc()->m_columnRole;
+}
+
+/*!
+ * \property QItemModelBarDataProxy::valueRole
+ *
+ * \brief The value role for the mapping.
+ */
+void QItemModelBarDataProxy::setValueRole(const QString &role)
+{
+ if (dptr()->m_valueRole != role) {
+ dptr()->m_valueRole = role;
+ emit valueRoleChanged(role);
+ }
+}
+
+QString QItemModelBarDataProxy::valueRole() const
+{
+ return dptrc()->m_valueRole;
+}
+
+/*!
+ * \property QItemModelBarDataProxy::rotationRole
+ *
+ * \brief The rotation role for the mapping.
+ */
+void QItemModelBarDataProxy::setRotationRole(const QString &role)
+{
+ if (dptr()->m_rotationRole != role) {
+ dptr()->m_rotationRole = role;
+ emit rotationRoleChanged(role);
+ }
+}
+
+QString QItemModelBarDataProxy::rotationRole() const
+{
+ return dptrc()->m_rotationRole;
+}
+
+/*!
+ * \property QItemModelBarDataProxy::rowCategories
+ *
+ * \brief The row categories for the mapping.
+ */
+void QItemModelBarDataProxy::setRowCategories(const QStringList &categories)
+{
+ if (dptr()->m_rowCategories != categories) {
+ dptr()->m_rowCategories = categories;
+ emit rowCategoriesChanged();
+ }
+}
+
+QStringList QItemModelBarDataProxy::rowCategories() const
+{
+ return dptrc()->m_rowCategories;
+}
+
+/*!
+ * \property QItemModelBarDataProxy::columnCategories
+ *
+ * \brief The column categories for the mapping.
+ */
+void QItemModelBarDataProxy::setColumnCategories(const QStringList &categories)
+{
+ if (dptr()->m_columnCategories != categories) {
+ dptr()->m_columnCategories = categories;
+ emit columnCategoriesChanged();
+ }
+}
+
+QStringList QItemModelBarDataProxy::columnCategories() const
+{
+ return dptrc()->m_columnCategories;
+}
+
+/*!
+ * \property QItemModelBarDataProxy::useModelCategories
+ *
+ * \brief Whether row and column roles and categories are used for mapping.
+ *
+ * When set to \c true, the mapping ignores row and column roles and categories, and uses
+ * the rows and columns from the model instead. Defaults to \c{false}.
+ */
+void QItemModelBarDataProxy::setUseModelCategories(bool enable)
+{
+ if (dptr()->m_useModelCategories != enable) {
+ dptr()->m_useModelCategories = enable;
+ emit useModelCategoriesChanged(enable);
+ }
+}
+
+bool QItemModelBarDataProxy::useModelCategories() const
+{
+ return dptrc()->m_useModelCategories;
+}
+
+/*!
+ * \property QItemModelBarDataProxy::autoRowCategories
+ *
+ * \brief Whether row categories are generated automatically.
+ *
+ * When set to \c true, the mapping ignores any explicitly set row categories
+ * and overwrites them with automatically generated ones whenever the
+ * data from model is resolved. Defaults to \c{true}.
+ */
+void QItemModelBarDataProxy::setAutoRowCategories(bool enable)
+{
+ if (dptr()->m_autoRowCategories != enable) {
+ dptr()->m_autoRowCategories = enable;
+ emit autoRowCategoriesChanged(enable);
+ }
+}
+
+bool QItemModelBarDataProxy::autoRowCategories() const
+{
+ return dptrc()->m_autoRowCategories;
+}
+
+/*!
+ * \property QItemModelBarDataProxy::autoColumnCategories
+ *
+ * \brief Whether column categories are generated automatically.
+ *
+ * When set to \c true, the mapping ignores any explicitly set column categories
+ * and overwrites them with automatically generated ones whenever the
+ * data from model is resolved. Defaults to \c{true}.
+ */
+void QItemModelBarDataProxy::setAutoColumnCategories(bool enable)
+{
+ if (dptr()->m_autoColumnCategories != enable) {
+ dptr()->m_autoColumnCategories = enable;
+ emit autoColumnCategoriesChanged(enable);
+ }
+}
+
+bool QItemModelBarDataProxy::autoColumnCategories() const
+{
+ return dptrc()->m_autoColumnCategories;
+}
+
+/*!
+ * Changes \a rowRole, \a columnRole, \a valueRole, \a rotationRole,
+ * \a rowCategories and \a columnCategories to the mapping.
+ */
+void QItemModelBarDataProxy::remap(const QString &rowRole,
+ const QString &columnRole,
+ const QString &valueRole,
+ const QString &rotationRole,
+ const QStringList &rowCategories,
+ const QStringList &columnCategories)
+{
+ setRowRole(rowRole);
+ setColumnRole(columnRole);
+ setValueRole(valueRole);
+ setRotationRole(rotationRole);
+ setRowCategories(rowCategories);
+ setColumnCategories(columnCategories);
+}
+
+/*!
+ * Returns the index of the specified \a category in row categories list.
+ * If the row categories list is empty, -1 is returned.
+ * \note If the automatic row categories generation is in use, this method will
+ * not return a valid index before the data in the model is resolved for the first time.
+ */
+int QItemModelBarDataProxy::rowCategoryIndex(const QString &category)
+{
+ return dptr()->m_rowCategories.indexOf(category);
+}
+
+/*!
+ * Returns the index of the specified \a category in column categories list.
+ * If the category is not found, -1 is returned.
+ * \note If the automatic column categories generation is in use, this method will
+ * not return a valid index before the data in the model is resolved for the first time.
+ */
+int QItemModelBarDataProxy::columnCategoryIndex(const QString &category)
+{
+ return dptr()->m_columnCategories.indexOf(category);
+}
+
+/*!
+ * \property QItemModelBarDataProxy::rowRolePattern
+ *
+ * \brief Whether a search and replace is performed on the value mapped by row
+ * role before it is used as a row category.
+ *
+ * This property specifies the regular expression to find the portion of the
+ * mapped value to replace and rowRoleReplace property contains the replacement string.
+ * This is useful for example in parsing row and column categories from a single
+ * timestamp field in the item model.
+ *
+ * \sa rowRole, rowRoleReplace
+ */
+void QItemModelBarDataProxy::setRowRolePattern(const QRegularExpression &pattern)
+{
+ if (dptr()->m_rowRolePattern != pattern) {
+ dptr()->m_rowRolePattern = pattern;
+ emit rowRolePatternChanged(pattern);
+ }
+}
+
+QRegularExpression QItemModelBarDataProxy::rowRolePattern() const
+{
+ return dptrc()->m_rowRolePattern;
+}
+
+/*!
+ * \property QItemModelBarDataProxy::columnRolePattern
+ *
+ * \brief Whether a search and replace is done on the value mapped by column
+ * role before it is used as a column category.
+ *
+ * This property specifies the regular expression to find the portion of the
+ * mapped value to replace and columnRoleReplace property contains the replacement string.
+ * This is useful for example in parsing row and column categories from
+ * a single timestamp field in the item model.
+ *
+ * \sa columnRole, columnRoleReplace
+ */
+void QItemModelBarDataProxy::setColumnRolePattern(const QRegularExpression &pattern)
+{
+ if (dptr()->m_columnRolePattern != pattern) {
+ dptr()->m_columnRolePattern = pattern;
+ emit columnRolePatternChanged(pattern);
+ }
+}
+
+QRegularExpression QItemModelBarDataProxy::columnRolePattern() const
+{
+ return dptrc()->m_columnRolePattern;
+}
+
+/*!
+ * \property QItemModelBarDataProxy::valueRolePattern
+ *
+ * \brief Whether a search and replace is done on the value mapped by value role
+ * before it is used as a bar value.
+ *
+ * This property specifies the regular expression to find the portion of the
+ * mapped value to replace and valueRoleReplace property contains the replacement string.
+ *
+ * \sa valueRole, valueRoleReplace
+ */
+void QItemModelBarDataProxy::setValueRolePattern(const QRegularExpression &pattern)
+{
+ if (dptr()->m_valueRolePattern != pattern) {
+ dptr()->m_valueRolePattern = pattern;
+ emit valueRolePatternChanged(pattern);
+ }
+}
+
+QRegularExpression QItemModelBarDataProxy::valueRolePattern() const
+{
+ return dptrc()->m_valueRolePattern;
+}
+
+/*!
+ * \property QItemModelBarDataProxy::rotationRolePattern
+ *
+ * \brief Whether a search and replace is done on the value mapped by rotation
+ * role before it is used as a bar rotation angle.
+ *
+ * This property specifies the regular expression to find the portion
+ * of the mapped value to replace and rotationRoleReplace property contains the replacement string.
+ *
+ * \sa rotationRole, rotationRoleReplace
+ */
+void QItemModelBarDataProxy::setRotationRolePattern(const QRegularExpression &pattern)
+{
+ if (dptr()->m_rotationRolePattern != pattern) {
+ dptr()->m_rotationRolePattern = pattern;
+ emit rotationRolePatternChanged(pattern);
+ }
+}
+
+QRegularExpression QItemModelBarDataProxy::rotationRolePattern() const
+{
+ return dptrc()->m_rotationRolePattern;
+}
+
+/*!
+ * \property QItemModelBarDataProxy::rowRoleReplace
+ *
+ * \brief The replace content to be used in conjunction with rowRolePattern.
+ *
+ * Defaults to empty string. For more information on how the search and replace using regular
+ * expressions works, see QString::replace(const QRegularExpression &rx, const QString &after)
+ * function documentation.
+ *
+ * \sa rowRole, rowRolePattern
+ */
+void QItemModelBarDataProxy::setRowRoleReplace(const QString &replace)
+{
+ if (dptr()->m_rowRoleReplace != replace) {
+ dptr()->m_rowRoleReplace = replace;
+ emit rowRoleReplaceChanged(replace);
+ }
+}
+
+QString QItemModelBarDataProxy::rowRoleReplace() const
+{
+ return dptrc()->m_rowRoleReplace;
+}
+
+/*!
+ * \property QItemModelBarDataProxy::columnRoleReplace
+ *
+ * \brief The replace content to be used in conjunction with columnRolePattern.
+ *
+ * Defaults to empty string. For more information on how the search and replace using regular
+ * expressions works, see QString::replace(const QRegularExpression &rx, const QString &after)
+ * function documentation.
+ *
+ * \sa columnRole, columnRolePattern
+ */
+void QItemModelBarDataProxy::setColumnRoleReplace(const QString &replace)
+{
+ if (dptr()->m_columnRoleReplace != replace) {
+ dptr()->m_columnRoleReplace = replace;
+ emit columnRoleReplaceChanged(replace);
+ }
+}
+
+QString QItemModelBarDataProxy::columnRoleReplace() const
+{
+ return dptrc()->m_columnRoleReplace;
+}
+
+/*!
+ * \property QItemModelBarDataProxy::valueRoleReplace
+ *
+ * \brief The replace content to be used in conjunction with valueRolePattern.
+ *
+ * Defaults to empty string. For more information on how the search and replace using regular
+ * expressions works, see QString::replace(const QRegularExpression &rx, const QString &after)
+ * function documentation.
+ *
+ * \sa valueRole, valueRolePattern
+ */
+void QItemModelBarDataProxy::setValueRoleReplace(const QString &replace)
+{
+ if (dptr()->m_valueRoleReplace != replace) {
+ dptr()->m_valueRoleReplace = replace;
+ emit valueRoleReplaceChanged(replace);
+ }
+}
+
+QString QItemModelBarDataProxy::valueRoleReplace() const
+{
+ return dptrc()->m_valueRoleReplace;
+}
+
+/*!
+ * \property QItemModelBarDataProxy::rotationRoleReplace
+ *
+ * \brief The replace content to be used in conjunction with
+ * rotationRolePattern.
+ *
+ * Defaults to empty string. For more information on how the search and replace using regular
+ * expressions works, see QString::replace(const QRegularExpression &rx, const QString &after)
+ * function documentation.
+ *
+ * \sa rotationRole, rotationRolePattern
+ */
+void QItemModelBarDataProxy::setRotationRoleReplace(const QString &replace)
+{
+ if (dptr()->m_rotationRoleReplace != replace) {
+ dptr()->m_rotationRoleReplace = replace;
+ emit rotationRoleReplaceChanged(replace);
+ }
+}
+
+QString QItemModelBarDataProxy::rotationRoleReplace() const
+{
+ return dptrc()->m_rotationRoleReplace;
+}
+
+/*!
+ * \property QItemModelBarDataProxy::multiMatchBehavior
+ *
+ * \brief How multiple matches for each row/column combination are handled.
+ *
+ * Defaults to QItemModelBarDataProxy::MMBLast. The chosen behavior affects both bar value
+ * and rotation.
+ *
+ * For example, you might have an item model with timestamped data taken at irregular intervals
+ * and you want to visualize total value of data items on each day with a bar graph.
+ * This can be done by specifying row and column categories so that each bar represents a day,
+ * and setting multiMatchBehavior to QItemModelBarDataProxy::MMBCumulative.
+ */
+void QItemModelBarDataProxy::setMultiMatchBehavior(QItemModelBarDataProxy::MultiMatchBehavior behavior)
+{
+ if (dptr()->m_multiMatchBehavior != behavior) {
+ dptr()->m_multiMatchBehavior = behavior;
+ emit multiMatchBehaviorChanged(behavior);
+ }
+}
+
+QItemModelBarDataProxy::MultiMatchBehavior QItemModelBarDataProxy::multiMatchBehavior() const
+{
+ return dptrc()->m_multiMatchBehavior;
+}
+
+/*!
+ * \internal
+ */
+QItemModelBarDataProxyPrivate *QItemModelBarDataProxy::dptr()
+{
+ return static_cast<QItemModelBarDataProxyPrivate *>(d_ptr.data());
+}
+
+/*!
+ * \internal
+ */
+const QItemModelBarDataProxyPrivate *QItemModelBarDataProxy::dptrc() const
+{
+ return static_cast<const QItemModelBarDataProxyPrivate *>(d_ptr.data());
+}
+
+// QItemModelBarDataProxyPrivate
+
+QItemModelBarDataProxyPrivate::QItemModelBarDataProxyPrivate(QItemModelBarDataProxy *q)
+ : QBarDataProxyPrivate(q),
+ m_itemModelHandler(new BarItemModelHandler(q)),
+ m_useModelCategories(false),
+ m_autoRowCategories(true),
+ m_autoColumnCategories(true),
+ m_multiMatchBehavior(QItemModelBarDataProxy::MMBLast)
+{
+}
+
+QItemModelBarDataProxyPrivate::~QItemModelBarDataProxyPrivate()
+{
+ delete m_itemModelHandler;
+}
+
+QItemModelBarDataProxy *QItemModelBarDataProxyPrivate::qptr()
+{
+ return static_cast<QItemModelBarDataProxy *>(q_ptr);
+}
+
+void QItemModelBarDataProxyPrivate::connectItemModelHandler()
+{
+ QObject::connect(m_itemModelHandler, &BarItemModelHandler::itemModelChanged,
+ qptr(), &QItemModelBarDataProxy::itemModelChanged);
+ QObject::connect(qptr(), &QItemModelBarDataProxy::rowRoleChanged,
+ m_itemModelHandler, &AbstractItemModelHandler::handleMappingChanged);
+ QObject::connect(qptr(), &QItemModelBarDataProxy::columnRoleChanged,
+ m_itemModelHandler, &AbstractItemModelHandler::handleMappingChanged);
+ QObject::connect(qptr(), &QItemModelBarDataProxy::valueRoleChanged,
+ m_itemModelHandler, &AbstractItemModelHandler::handleMappingChanged);
+ QObject::connect(qptr(), &QItemModelBarDataProxy::rotationRoleChanged,
+ m_itemModelHandler, &AbstractItemModelHandler::handleMappingChanged);
+ QObject::connect(qptr(), &QItemModelBarDataProxy::rowCategoriesChanged,
+ m_itemModelHandler, &AbstractItemModelHandler::handleMappingChanged);
+ QObject::connect(qptr(), &QItemModelBarDataProxy::columnCategoriesChanged,
+ m_itemModelHandler, &AbstractItemModelHandler::handleMappingChanged);
+ QObject::connect(qptr(), &QItemModelBarDataProxy::useModelCategoriesChanged,
+ m_itemModelHandler, &AbstractItemModelHandler::handleMappingChanged);
+ QObject::connect(qptr(), &QItemModelBarDataProxy::autoRowCategoriesChanged,
+ m_itemModelHandler, &AbstractItemModelHandler::handleMappingChanged);
+ QObject::connect(qptr(), &QItemModelBarDataProxy::autoColumnCategoriesChanged,
+ m_itemModelHandler, &AbstractItemModelHandler::handleMappingChanged);
+ QObject::connect(qptr(), &QItemModelBarDataProxy::rowRolePatternChanged,
+ m_itemModelHandler, &AbstractItemModelHandler::handleMappingChanged);
+ QObject::connect(qptr(), &QItemModelBarDataProxy::columnRolePatternChanged,
+ m_itemModelHandler, &AbstractItemModelHandler::handleMappingChanged);
+ QObject::connect(qptr(), &QItemModelBarDataProxy::valueRolePatternChanged,
+ m_itemModelHandler, &AbstractItemModelHandler::handleMappingChanged);
+ QObject::connect(qptr(), &QItemModelBarDataProxy::rotationRolePatternChanged,
+ m_itemModelHandler, &AbstractItemModelHandler::handleMappingChanged);
+ QObject::connect(qptr(), &QItemModelBarDataProxy::rowRoleReplaceChanged,
+ m_itemModelHandler, &AbstractItemModelHandler::handleMappingChanged);
+ QObject::connect(qptr(), &QItemModelBarDataProxy::columnRoleReplaceChanged,
+ m_itemModelHandler, &AbstractItemModelHandler::handleMappingChanged);
+ QObject::connect(qptr(), &QItemModelBarDataProxy::valueRoleReplaceChanged,
+ m_itemModelHandler, &AbstractItemModelHandler::handleMappingChanged);
+ QObject::connect(qptr(), &QItemModelBarDataProxy::rotationRoleReplaceChanged,
+ m_itemModelHandler, &AbstractItemModelHandler::handleMappingChanged);
+ QObject::connect(qptr(), &QItemModelBarDataProxy::multiMatchBehaviorChanged,
+ m_itemModelHandler, &AbstractItemModelHandler::handleMappingChanged);
+}
+
+QT_END_NAMESPACE
diff --git a/src/graphs/data/qitemmodelbardataproxy.h b/src/graphs/data/qitemmodelbardataproxy.h
new file mode 100644
index 0000000..df4d4ca
--- /dev/null
+++ b/src/graphs/data/qitemmodelbardataproxy.h
@@ -0,0 +1,153 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QITEMMODELBARDATAPROXY_H
+#define QITEMMODELBARDATAPROXY_H
+
+#include <QtGraphs/qbardataproxy.h>
+#include <QtCore/QAbstractItemModel>
+#include <QtCore/QRegularExpression>
+
+QT_BEGIN_NAMESPACE
+
+class QItemModelBarDataProxyPrivate;
+
+class Q_GRAPHS_EXPORT QItemModelBarDataProxy : public QBarDataProxy
+{
+ Q_OBJECT
+ Q_PROPERTY(QAbstractItemModel* itemModel READ itemModel WRITE setItemModel NOTIFY itemModelChanged)
+ Q_PROPERTY(QString rowRole READ rowRole WRITE setRowRole NOTIFY rowRoleChanged)
+ Q_PROPERTY(QString columnRole READ columnRole WRITE setColumnRole NOTIFY columnRoleChanged)
+ Q_PROPERTY(QString valueRole READ valueRole WRITE setValueRole NOTIFY valueRoleChanged)
+ Q_PROPERTY(QString rotationRole READ rotationRole WRITE setRotationRole NOTIFY rotationRoleChanged)
+ Q_PROPERTY(QStringList rowCategories READ rowCategories WRITE setRowCategories NOTIFY rowCategoriesChanged)
+ Q_PROPERTY(QStringList columnCategories READ columnCategories WRITE setColumnCategories NOTIFY columnCategoriesChanged)
+ Q_PROPERTY(bool useModelCategories READ useModelCategories WRITE setUseModelCategories NOTIFY useModelCategoriesChanged)
+ Q_PROPERTY(bool autoRowCategories READ autoRowCategories WRITE setAutoRowCategories NOTIFY autoRowCategoriesChanged)
+ Q_PROPERTY(bool autoColumnCategories READ autoColumnCategories WRITE setAutoColumnCategories NOTIFY autoColumnCategoriesChanged)
+ Q_PROPERTY(QRegularExpression rowRolePattern READ rowRolePattern WRITE setRowRolePattern NOTIFY rowRolePatternChanged)
+ Q_PROPERTY(QRegularExpression columnRolePattern READ columnRolePattern WRITE setColumnRolePattern NOTIFY columnRolePatternChanged)
+ Q_PROPERTY(QRegularExpression valueRolePattern READ valueRolePattern WRITE setValueRolePattern NOTIFY valueRolePatternChanged)
+ Q_PROPERTY(QRegularExpression rotationRolePattern READ rotationRolePattern WRITE setRotationRolePattern NOTIFY rotationRolePatternChanged)
+ Q_PROPERTY(QString rowRoleReplace READ rowRoleReplace WRITE setRowRoleReplace NOTIFY rowRoleReplaceChanged)
+ Q_PROPERTY(QString columnRoleReplace READ columnRoleReplace WRITE setColumnRoleReplace NOTIFY columnRoleReplaceChanged)
+ Q_PROPERTY(QString valueRoleReplace READ valueRoleReplace WRITE setValueRoleReplace NOTIFY valueRoleReplaceChanged)
+ Q_PROPERTY(QString rotationRoleReplace READ rotationRoleReplace WRITE setRotationRoleReplace NOTIFY rotationRoleReplaceChanged)
+ Q_PROPERTY(MultiMatchBehavior multiMatchBehavior READ multiMatchBehavior WRITE setMultiMatchBehavior NOTIFY multiMatchBehaviorChanged)
+
+public:
+ enum MultiMatchBehavior {
+ MMBFirst = 0,
+ MMBLast = 1,
+ MMBAverage = 2,
+ MMBCumulative = 3
+ };
+ Q_ENUM(MultiMatchBehavior)
+
+ explicit QItemModelBarDataProxy(QObject *parent = nullptr);
+ explicit QItemModelBarDataProxy(QAbstractItemModel *itemModel, QObject *parent = nullptr);
+ explicit QItemModelBarDataProxy(QAbstractItemModel *itemModel, const QString &valueRole,
+ QObject *parent = nullptr);
+ explicit QItemModelBarDataProxy(QAbstractItemModel *itemModel, const QString &rowRole,
+ const QString &columnRole, const QString &valueRole,
+ QObject *parent = nullptr);
+ explicit QItemModelBarDataProxy(QAbstractItemModel *itemModel, const QString &rowRole,
+ const QString &columnRole, const QString &valueRole,
+ const QString &rotationRole, QObject *parent = nullptr);
+ explicit QItemModelBarDataProxy(QAbstractItemModel *itemModel, const QString &rowRole,
+ const QString &columnRole, const QString &valueRole,
+ const QStringList &rowCategories, const QStringList &columnCategories,
+ QObject *parent = nullptr);
+ explicit QItemModelBarDataProxy(QAbstractItemModel *itemModel, const QString &rowRole,
+ const QString &columnRole, const QString &valueRole,
+ const QString &rotationRole, const QStringList &rowCategories,
+ const QStringList &columnCategories, QObject *parent = nullptr);
+ virtual ~QItemModelBarDataProxy();
+
+ void setItemModel(QAbstractItemModel *itemModel);
+ QAbstractItemModel *itemModel() const;
+
+ void setRowRole(const QString &role);
+ QString rowRole() const;
+ void setColumnRole(const QString &role);
+ QString columnRole() const;
+ void setValueRole(const QString &role);
+ QString valueRole() const;
+ void setRotationRole(const QString &role);
+ QString rotationRole() const;
+
+ void setRowCategories(const QStringList &categories);
+ QStringList rowCategories() const;
+ void setColumnCategories(const QStringList &categories);
+ QStringList columnCategories() const;
+
+ void setUseModelCategories(bool enable);
+ bool useModelCategories() const;
+ void setAutoRowCategories(bool enable);
+ bool autoRowCategories() const;
+ void setAutoColumnCategories(bool enable);
+ bool autoColumnCategories() const;
+
+ void remap(const QString &rowRole, const QString &columnRole,
+ const QString &valueRole, const QString &rotationRole,
+ const QStringList &rowCategories,
+ const QStringList &columnCategories);
+
+ Q_INVOKABLE int rowCategoryIndex(const QString& category);
+ Q_INVOKABLE int columnCategoryIndex(const QString& category);
+
+ void setRowRolePattern(const QRegularExpression &pattern);
+ QRegularExpression rowRolePattern() const;
+ void setColumnRolePattern(const QRegularExpression &pattern);
+ QRegularExpression columnRolePattern() const;
+ void setValueRolePattern(const QRegularExpression &pattern);
+ QRegularExpression valueRolePattern() const;
+ void setRotationRolePattern(const QRegularExpression &pattern);
+ QRegularExpression rotationRolePattern() const;
+
+ void setRowRoleReplace(const QString &replace);
+ QString rowRoleReplace() const;
+ void setColumnRoleReplace(const QString &replace);
+ QString columnRoleReplace() const;
+ void setValueRoleReplace(const QString &replace);
+ QString valueRoleReplace() const;
+ void setRotationRoleReplace(const QString &replace);
+ QString rotationRoleReplace() const;
+
+ void setMultiMatchBehavior(MultiMatchBehavior behavior);
+ MultiMatchBehavior multiMatchBehavior() const;
+
+Q_SIGNALS:
+ void itemModelChanged(const QAbstractItemModel* itemModel);
+ void rowRoleChanged(const QString &role);
+ void columnRoleChanged(const QString &role);
+ void valueRoleChanged(const QString &role);
+ void rotationRoleChanged(const QString &role);
+ void rowCategoriesChanged();
+ void columnCategoriesChanged();
+ void useModelCategoriesChanged(bool enable);
+ void autoRowCategoriesChanged(bool enable);
+ void autoColumnCategoriesChanged(bool enable);
+ void rowRolePatternChanged(const QRegularExpression &pattern);
+ void columnRolePatternChanged(const QRegularExpression &pattern);
+ void valueRolePatternChanged(const QRegularExpression &pattern);
+ void rotationRolePatternChanged(const QRegularExpression &pattern);
+ void rowRoleReplaceChanged(const QString &replace);
+ void columnRoleReplaceChanged(const QString &replace);
+ void valueRoleReplaceChanged(const QString &replace);
+ void rotationRoleReplaceChanged(const QString &replace);
+ void multiMatchBehaviorChanged(QItemModelBarDataProxy::MultiMatchBehavior behavior);
+
+protected:
+ QItemModelBarDataProxyPrivate *dptr();
+ const QItemModelBarDataProxyPrivate *dptrc() const;
+
+private:
+ Q_DISABLE_COPY(QItemModelBarDataProxy)
+
+ friend class BarItemModelHandler;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/data/qitemmodelbardataproxy_p.h b/src/graphs/data/qitemmodelbardataproxy_p.h
new file mode 100644
index 0000000..f923e4f
--- /dev/null
+++ b/src/graphs/data/qitemmodelbardataproxy_p.h
@@ -0,0 +1,69 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QtGraphs API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#ifndef QITEMMODELBARDATAPROXY_P_H
+#define QITEMMODELBARDATAPROXY_P_H
+
+#include "qitemmodelbardataproxy.h"
+#include "qbardataproxy_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class BarItemModelHandler;
+
+class QItemModelBarDataProxyPrivate : public QBarDataProxyPrivate
+{
+ Q_OBJECT
+public:
+ QItemModelBarDataProxyPrivate(QItemModelBarDataProxy *q);
+ virtual ~QItemModelBarDataProxyPrivate();
+
+ void connectItemModelHandler();
+
+private:
+ QItemModelBarDataProxy *qptr();
+
+ BarItemModelHandler *m_itemModelHandler;
+
+ QString m_rowRole;
+ QString m_columnRole;
+ QString m_valueRole;
+ QString m_rotationRole;
+
+ // For row/column items, sort items into these categories. Other categories are ignored.
+ QStringList m_rowCategories;
+ QStringList m_columnCategories;
+
+ bool m_useModelCategories;
+ bool m_autoRowCategories;
+ bool m_autoColumnCategories;
+
+ QRegularExpression m_rowRolePattern;
+ QRegularExpression m_columnRolePattern;
+ QRegularExpression m_valueRolePattern;
+ QRegularExpression m_rotationRolePattern;
+
+ QString m_rowRoleReplace;
+ QString m_columnRoleReplace;
+ QString m_valueRoleReplace;
+ QString m_rotationRoleReplace;
+
+ QItemModelBarDataProxy::MultiMatchBehavior m_multiMatchBehavior;
+
+ friend class BarItemModelHandler;
+ friend class QItemModelBarDataProxy;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/data/qitemmodelscatterdataproxy.cpp b/src/graphs/data/qitemmodelscatterdataproxy.cpp
new file mode 100644
index 0000000..a4e3114
--- /dev/null
+++ b/src/graphs/data/qitemmodelscatterdataproxy.cpp
@@ -0,0 +1,622 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qitemmodelscatterdataproxy_p.h"
+#include "scatteritemmodelhandler_p.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ * \class QItemModelScatterDataProxy
+ * \inmodule QtGraphs
+ * \brief Proxy class for presenting data in item models with Q3DScatter.
+ *
+ * QItemModelScatterDataProxy allows you to use QAbstractItemModel derived models as a data source
+ * for Q3DScatter. It maps roles of QAbstractItemModel to the XYZ-values of Q3DScatter points.
+ *
+ * The data is resolved asynchronously whenever the mapping or the model changes.
+ * QScatterDataProxy::arrayReset() is emitted when the data has been resolved. However, inserts,
+ * removes, and single data item changes after the model initialization are resolved synchronously,
+ * unless the same frame also contains a change that causes the whole model to be resolved.
+ *
+ * Mapping ignores rows and columns of the QAbstractItemModel and treats
+ * all items equally. It requires the model to provide roles for the data items
+ * that can be mapped to X, Y, and Z-values for the scatter points.
+ *
+ * For example, assume that you have a custom QAbstractItemModel for storing various measurements
+ * done on material samples, providing data for roles such as "density", "hardness", and
+ * "conductivity". You could visualize these properties on a scatter graph using this proxy:
+ *
+ * \snippet doc_src_qtgraphs.cpp 4
+ *
+ * If the fields of the model do not contain the data in the exact format you need, you can specify
+ * a search pattern regular expression and a replace rule for each role to get the value in a
+ * format you need. For more information how the replace using regular expressions works, see
+ * QString::replace(const QRegularExpression &rx, const QString &after) function documentation. Note that
+ * using regular expressions has an impact on the performance, so it's more efficient to utilize
+ * item models where doing search and replace is not necessary to get the desired values.
+ *
+ * For example about using the search patterns in conjunction with the roles, see
+ * ItemModelBarDataProxy usage in \l{Qt Quick 2 Bars Example}.
+ *
+ * \sa {Qt Graphs Data Handling}
+ */
+
+/*!
+ * \qmltype ItemModelScatterDataProxy
+ * \inqmlmodule QtGraphs
+ * \ingroup graphs_qml
+ * \instantiates QItemModelScatterDataProxy
+ * \inherits ScatterDataProxy
+ * \brief Proxy class for presenting data in item models with Scatter3D.
+ *
+ * This type allows you to use AbstractItemModel derived models as a data source for Scatter3D.
+ *
+ * The data is resolved asynchronously whenever the mapping or the model changes.
+ * QScatterDataProxy::arrayReset() is emitted when the data has been resolved.
+ *
+ * For more details, see QItemModelScatterDataProxy documentation.
+ *
+ * Usage example:
+ *
+ * \snippet doc_src_qmlgraphs.cpp 8
+ *
+ * \sa ScatterDataProxy, {Qt Graphs Data Handling}
+ */
+
+/*!
+ * \qmlproperty model ItemModelScatterDataProxy::itemModel
+ * The item model to use as a data source for Scatter3D.
+ */
+
+/*!
+ * \qmlproperty string ItemModelScatterDataProxy::xPosRole
+ * The item model role to map into the X position.
+ */
+
+/*!
+ * \qmlproperty string ItemModelScatterDataProxy::yPosRole
+ * The item model role to map into the Y position.
+ */
+
+/*!
+ * \qmlproperty string ItemModelScatterDataProxy::zPosRole
+ * The item model role to map into the Z position.
+ */
+
+/*!
+ * \qmlproperty string ItemModelScatterDataProxy::rotationRole
+ *
+ * The item model role to map into item rotation.
+ * The model may supply the value for rotation as either variant that is directly convertible
+ * to \l quaternion, or as one of the string representations: \c{"scalar,x,y,z"} or
+ * \c{"@angle,x,y,z"}. The first format will construct the \l quaternion directly with given values,
+ * and the second one will construct the \l quaternion using QQuaternion::fromAxisAndAngle() method.
+ */
+
+/*!
+ * \qmlproperty regExp ItemModelScatterDataProxy::xPosRolePattern
+ *
+ * When set, a search and replace is done on the value mapped by the x position
+ * role before it is used as
+ * an item position value. This property specifies the regular expression to find the portion of the
+ * mapped value to replace and xPosRoleReplace property contains the replacement string.
+ *
+ * \sa xPosRole, xPosRoleReplace
+ */
+
+/*!
+ * \qmlproperty regExp ItemModelScatterDataProxy::yPosRolePattern
+ *
+ * When set, a search and replace is done on the value mapped by the y position
+ * role before it is used as
+ * an item position value. This property specifies the regular expression to find the portion of the
+ * mapped value to replace and yPosRoleReplace property contains the replacement string.
+ *
+ * \sa yPosRole, yPosRoleReplace
+ */
+
+/*!
+ * \qmlproperty regExp ItemModelScatterDataProxy::zPosRolePattern
+ *
+ * When set, a search and replace is done on the value mapped by the z position
+ * role before it is used as
+ * an item position value. This property specifies the regular expression to find the portion of the
+ * mapped value to replace and zPosRoleReplace property contains the replacement string.
+ *
+ * \sa zPosRole, zPosRoleReplace
+ */
+
+/*!
+ * \qmlproperty regExp ItemModelScatterDataProxy::rotationRolePattern
+ * When set, a search and replace is done on the value mapped by the rotation
+ * role before it is used
+ * as item rotation. This property specifies the regular expression to find the portion
+ * of the mapped value to replace and rotationRoleReplace property contains the replacement string.
+ *
+ * \sa rotationRole, rotationRoleReplace
+ */
+
+/*!
+ * \qmlproperty string ItemModelScatterDataProxy::xPosRoleReplace
+ *
+ * This property defines the replace content to be used in conjunction with xPosRolePattern.
+ * Defaults to an empty string. For more information on how the search and replace using regular
+ * expressions works, see QString::replace(const QRegularExpression &rx, const QString &after)
+ * function documentation.
+ *
+ * \sa xPosRole, xPosRolePattern
+ */
+
+/*!
+ * \qmlproperty string ItemModelScatterDataProxy::yPosRoleReplace
+ *
+ * This property defines the replace content to be used in conjunction with yPosRolePattern.
+ * Defaults to an empty string. For more information on how the search and replace using regular
+ * expressions works, see QString::replace(const QRegularExpression &rx, const QString &after)
+ * function documentation.
+ *
+ * \sa yPosRole, yPosRolePattern
+ */
+
+/*!
+ * \qmlproperty string ItemModelScatterDataProxy::zPosRoleReplace
+ *
+ * This property defines the replace content to be used in conjunction with zPosRolePattern.
+ * Defaults to an empty string. For more information on how the search and replace using regular
+ * expressions works, see QString::replace(const QRegularExpression &rx, const QString &after)
+ * function documentation.
+ *
+ * \sa zPosRole, zPosRolePattern
+ */
+
+/*!
+ * \qmlproperty string ItemModelScatterDataProxy::rotationRoleReplace
+ * This property defines the replace content to be used in conjunction with rotationRolePattern.
+ * Defaults to an empty string. For more information on how the search and replace using regular
+ * expressions works, see QString::replace(const QRegularExpression &rx, const QString &after)
+ * function documentation.
+ *
+ * \sa rotationRole, rotationRolePattern
+ */
+
+/*!
+ * Constructs QItemModelScatterDataProxy with optional \a parent.
+ */
+QItemModelScatterDataProxy::QItemModelScatterDataProxy(QObject *parent)
+ : QScatterDataProxy(new QItemModelScatterDataProxyPrivate(this), parent)
+{
+ dptr()->connectItemModelHandler();
+}
+
+/*!
+ * Constructs QItemModelScatterDataProxy with \a itemModel and optional \a parent. Proxy doesn't take
+ * ownership of the \a itemModel, as typically item models are owned by other controls.
+ */
+QItemModelScatterDataProxy::QItemModelScatterDataProxy(QAbstractItemModel *itemModel,
+ QObject *parent)
+ : QScatterDataProxy(new QItemModelScatterDataProxyPrivate(this), parent)
+{
+ dptr()->m_itemModelHandler->setItemModel(itemModel);
+ dptr()->connectItemModelHandler();
+}
+
+/*!
+ * Constructs QItemModelScatterDataProxy with \a itemModel and optional \a parent. Proxy doesn't take
+ * ownership of the \a itemModel, as typically item models are owned by other controls.
+ * The xPosRole property is set to \a xPosRole, yPosRole property to \a yPosRole, and zPosRole property
+ * to \a zPosRole.
+ */
+QItemModelScatterDataProxy::QItemModelScatterDataProxy(QAbstractItemModel *itemModel,
+ const QString &xPosRole,
+ const QString &yPosRole,
+ const QString &zPosRole,
+ QObject *parent)
+ : QScatterDataProxy(new QItemModelScatterDataProxyPrivate(this), parent)
+{
+ dptr()->m_itemModelHandler->setItemModel(itemModel);
+ dptr()->m_xPosRole = xPosRole;
+ dptr()->m_yPosRole = yPosRole;
+ dptr()->m_zPosRole = zPosRole;
+ dptr()->connectItemModelHandler();
+}
+
+/*!
+ * Constructs QItemModelScatterDataProxy with \a itemModel and optional \a parent. Proxy doesn't take
+ * ownership of the \a itemModel, as typically item models are owned by other controls.
+ * The xPosRole property is set to \a xPosRole, yPosRole property to \a yPosRole, zPosRole property
+ * to \a zPosRole, and rotationRole property to \a rotationRole.
+ */
+QItemModelScatterDataProxy::QItemModelScatterDataProxy(QAbstractItemModel *itemModel,
+ const QString &xPosRole,
+ const QString &yPosRole,
+ const QString &zPosRole,
+ const QString &rotationRole,
+ QObject *parent)
+ : QScatterDataProxy(new QItemModelScatterDataProxyPrivate(this), parent)
+{
+ dptr()->m_itemModelHandler->setItemModel(itemModel);
+ dptr()->m_xPosRole = xPosRole;
+ dptr()->m_yPosRole = yPosRole;
+ dptr()->m_zPosRole = zPosRole;
+ dptr()->m_rotationRole = rotationRole;
+ dptr()->connectItemModelHandler();
+}
+
+/*!
+ * Destroys QItemModelScatterDataProxy.
+ */
+QItemModelScatterDataProxy::~QItemModelScatterDataProxy()
+{
+}
+
+/*!
+ * \property QItemModelScatterDataProxy::itemModel
+ *
+ * \brief The item model to use as a data source for a 3D scatter series.
+ */
+
+/*!
+ * Sets \a itemModel as the item model for Q3DScatter. Does not take
+ * ownership of the model, but does connect to it to listen for changes.
+ */
+void QItemModelScatterDataProxy::setItemModel(QAbstractItemModel *itemModel)
+{
+ dptr()->m_itemModelHandler->setItemModel(itemModel);
+}
+
+QAbstractItemModel *QItemModelScatterDataProxy::itemModel() const
+{
+ return dptrc()->m_itemModelHandler->itemModel();
+}
+
+/*!
+ * \property QItemModelScatterDataProxy::xPosRole
+ *
+ * \brief The item model role to map into the X position.
+ */
+void QItemModelScatterDataProxy::setXPosRole(const QString &role)
+{
+ if (dptr()->m_xPosRole != role) {
+ dptr()->m_xPosRole = role;
+ emit xPosRoleChanged(role);
+ }
+}
+
+QString QItemModelScatterDataProxy::xPosRole() const
+{
+ return dptrc()->m_xPosRole;
+}
+
+/*!
+ * \property QItemModelScatterDataProxy::yPosRole
+ *
+ * \brief The item model role to map into the Y position.
+ */
+void QItemModelScatterDataProxy::setYPosRole(const QString &role)
+{
+ if (dptr()->m_yPosRole != role) {
+ dptr()->m_yPosRole = role;
+ emit yPosRoleChanged(role);
+ }
+}
+
+QString QItemModelScatterDataProxy::yPosRole() const
+{
+ return dptrc()->m_yPosRole;
+}
+
+/*!
+ * \property QItemModelScatterDataProxy::zPosRole
+ *
+ * \brief The item model role to map into the Z position.
+ */
+void QItemModelScatterDataProxy::setZPosRole(const QString &role)
+{
+ if (dptr()->m_zPosRole != role) {
+ dptr()->m_zPosRole = role;
+ emit zPosRoleChanged(role);
+ }
+}
+
+QString QItemModelScatterDataProxy::zPosRole() const
+{
+ return dptrc()->m_zPosRole;
+}
+
+/*!
+ * \property QItemModelScatterDataProxy::rotationRole
+ *
+ * \brief The item model role to map into item rotation.
+ *
+ * The model may supply the value for rotation as either variant that is directly convertible
+ * to QQuaternion, or as one of the string representations: \c{"scalar,x,y,z"} or \c{"@angle,x,y,z"}.
+ * The first will construct the quaternion directly with given values, and the second one will
+ * construct the quaternion using QQuaternion::fromAxisAndAngle() method.
+ */
+void QItemModelScatterDataProxy::setRotationRole(const QString &role)
+{
+ if (dptr()->m_rotationRole != role) {
+ dptr()->m_rotationRole = role;
+ emit rotationRoleChanged(role);
+ }
+}
+
+QString QItemModelScatterDataProxy::rotationRole() const
+{
+ return dptrc()->m_rotationRole;
+}
+
+/*!
+ * \property QItemModelScatterDataProxy::xPosRolePattern
+ *
+ * \brief Whether search and replace is done on the value mapped by the x
+ * position role before it is used as an item position value.
+ *
+ * This property specifies the regular expression to find the portion of the
+ * mapped value to replace and xPosRoleReplace property contains the replacement string.
+ *
+ * \sa xPosRole, xPosRoleReplace
+ */
+void QItemModelScatterDataProxy::setXPosRolePattern(const QRegularExpression &pattern)
+{
+ if (dptr()->m_xPosRolePattern != pattern) {
+ dptr()->m_xPosRolePattern = pattern;
+ emit xPosRolePatternChanged(pattern);
+ }
+}
+
+QRegularExpression QItemModelScatterDataProxy::xPosRolePattern() const
+{
+ return dptrc()->m_xPosRolePattern;
+}
+
+/*!
+ * \property QItemModelScatterDataProxy::yPosRolePattern
+ *
+ * \brief Whether a search and replace is done on the value mapped by the
+ * y position role before it is used as an item position value.
+ *
+ * This property specifies the regular expression to find the portion of the
+ * mapped value to replace and yPosRoleReplace property contains the replacement string.
+ *
+ * \sa yPosRole, yPosRoleReplace
+ */
+void QItemModelScatterDataProxy::setYPosRolePattern(const QRegularExpression &pattern)
+{
+ if (dptr()->m_yPosRolePattern != pattern) {
+ dptr()->m_yPosRolePattern = pattern;
+ emit yPosRolePatternChanged(pattern);
+ }
+}
+
+QRegularExpression QItemModelScatterDataProxy::yPosRolePattern() const
+{
+ return dptrc()->m_yPosRolePattern;
+}
+
+/*!
+ * \property QItemModelScatterDataProxy::zPosRolePattern
+ *
+ * \brief Whether a search and replace is done on the value mapped by the z
+ * position role before it is used as an item position value.
+ *
+ * This property specifies the regular expression to find the portion of the
+ * mapped value to replace and zPosRoleReplace property contains the replacement string.
+ *
+ * \sa zPosRole, zPosRoleReplace
+ */
+void QItemModelScatterDataProxy::setZPosRolePattern(const QRegularExpression &pattern)
+{
+ if (dptr()->m_zPosRolePattern != pattern) {
+ dptr()->m_zPosRolePattern = pattern;
+ emit zPosRolePatternChanged(pattern);
+ }
+}
+
+QRegularExpression QItemModelScatterDataProxy::zPosRolePattern() const
+{
+ return dptrc()->m_zPosRolePattern;
+}
+
+/*!
+ * \property QItemModelScatterDataProxy::rotationRolePattern
+ *
+ * \brief Whether a search and replace is done on the value mapped by the
+ * rotation role before it is used as item rotation.
+ *
+ * This property specifies the regular expression to find the portion
+ * of the mapped value to replace and rotationRoleReplace property contains the replacement string.
+ *
+ * \sa rotationRole, rotationRoleReplace
+ */
+void QItemModelScatterDataProxy::setRotationRolePattern(const QRegularExpression &pattern)
+{
+ if (dptr()->m_rotationRolePattern != pattern) {
+ dptr()->m_rotationRolePattern = pattern;
+ emit rotationRolePatternChanged(pattern);
+ }
+}
+
+QRegularExpression QItemModelScatterDataProxy::rotationRolePattern() const
+{
+ return dptrc()->m_rotationRolePattern;
+}
+
+/*!
+ * \property QItemModelScatterDataProxy::xPosRoleReplace
+ *
+ * \brief The replace content to be used in conjunction with the x position role
+ * pattern.
+ *
+ * Defaults to an empty string. For more information on how the search and replace using regular
+ * expressions works, see QString::replace(const QRegularExpression &rx, const QString &after)
+ * function documentation.
+ *
+ * \sa xPosRole, xPosRolePattern
+ */
+void QItemModelScatterDataProxy::setXPosRoleReplace(const QString &replace)
+{
+ if (dptr()->m_xPosRoleReplace != replace) {
+ dptr()->m_xPosRoleReplace = replace;
+ emit xPosRoleReplaceChanged(replace);
+ }
+}
+
+QString QItemModelScatterDataProxy::xPosRoleReplace() const
+{
+ return dptrc()->m_xPosRoleReplace;
+}
+
+/*!
+ * \property QItemModelScatterDataProxy::yPosRoleReplace
+ *
+ * \brief The replace content to be used in conjunction with the y position role
+ * pattern.
+ *
+ * Defaults to an empty string. For more information on how the search and replace using regular
+ * expressions works, see QString::replace(const QRegularExpression &rx, const QString &after)
+ * function documentation.
+ *
+ * \sa yPosRole, yPosRolePattern
+ */
+void QItemModelScatterDataProxy::setYPosRoleReplace(const QString &replace)
+{
+ if (dptr()->m_yPosRoleReplace != replace) {
+ dptr()->m_yPosRoleReplace = replace;
+ emit yPosRoleReplaceChanged(replace);
+ }
+}
+
+QString QItemModelScatterDataProxy::yPosRoleReplace() const
+{
+ return dptrc()->m_yPosRoleReplace;
+}
+
+/*!
+ * \property QItemModelScatterDataProxy::zPosRoleReplace
+ *
+ * \brief The replace content to be used in conjunction with the z position role
+ * pattern.
+ *
+ * Defaults to an empty string. For more information on how the search and replace using regular
+ * expressions works, see QString::replace(const QRegularExpression &rx, const QString &after)
+ * function documentation.
+ *
+ * \sa zPosRole, zPosRolePattern
+ */
+void QItemModelScatterDataProxy::setZPosRoleReplace(const QString &replace)
+{
+ if (dptr()->m_zPosRoleReplace != replace) {
+ dptr()->m_zPosRoleReplace = replace;
+ emit zPosRoleReplaceChanged(replace);
+ }
+}
+
+QString QItemModelScatterDataProxy::zPosRoleReplace() const
+{
+ return dptrc()->m_zPosRoleReplace;
+}
+
+/*!
+ * \property QItemModelScatterDataProxy::rotationRoleReplace
+ *
+ * \brief The replace content to be used in conjunction with the rotation role
+ * pattern.
+ *
+ * Defaults to an empty string. For more information on how the search and replace using regular
+ * expressions works, see QString::replace(const QRegularExpression &rx, const QString &after)
+ * function documentation.
+ *
+ * \sa rotationRole, rotationRolePattern
+ */
+void QItemModelScatterDataProxy::setRotationRoleReplace(const QString &replace)
+{
+ if (dptr()->m_rotationRoleReplace != replace) {
+ dptr()->m_rotationRoleReplace = replace;
+ emit rotationRoleReplaceChanged(replace);
+ }
+}
+
+QString QItemModelScatterDataProxy::rotationRoleReplace() const
+{
+ return dptrc()->m_rotationRoleReplace;
+}
+
+/*!
+ * Changes \a xPosRole, \a yPosRole, \a zPosRole, and \a rotationRole mapping.
+ */
+void QItemModelScatterDataProxy::remap(const QString &xPosRole, const QString &yPosRole,
+ const QString &zPosRole, const QString &rotationRole)
+{
+ setXPosRole(xPosRole);
+ setYPosRole(yPosRole);
+ setZPosRole(zPosRole);
+ setRotationRole(rotationRole);
+}
+
+/*!
+ * \internal
+ */
+QItemModelScatterDataProxyPrivate *QItemModelScatterDataProxy::dptr()
+{
+ return static_cast<QItemModelScatterDataProxyPrivate *>(d_ptr.data());
+}
+
+/*!
+ * \internal
+ */
+const QItemModelScatterDataProxyPrivate *QItemModelScatterDataProxy::dptrc() const
+{
+ return static_cast<const QItemModelScatterDataProxyPrivate *>(d_ptr.data());
+}
+
+// QItemModelScatterDataProxyPrivate
+
+QItemModelScatterDataProxyPrivate::QItemModelScatterDataProxyPrivate(QItemModelScatterDataProxy *q)
+ : QScatterDataProxyPrivate(q),
+ m_itemModelHandler(new ScatterItemModelHandler(q))
+{
+}
+
+QItemModelScatterDataProxyPrivate::~QItemModelScatterDataProxyPrivate()
+{
+ delete m_itemModelHandler;
+}
+
+QItemModelScatterDataProxy *QItemModelScatterDataProxyPrivate::qptr()
+{
+ return static_cast<QItemModelScatterDataProxy *>(q_ptr);
+}
+
+void QItemModelScatterDataProxyPrivate::connectItemModelHandler()
+{
+ QObject::connect(m_itemModelHandler, &ScatterItemModelHandler::itemModelChanged,
+ qptr(), &QItemModelScatterDataProxy::itemModelChanged);
+ QObject::connect(qptr(), &QItemModelScatterDataProxy::xPosRoleChanged,
+ m_itemModelHandler, &AbstractItemModelHandler::handleMappingChanged);
+ QObject::connect(qptr(), &QItemModelScatterDataProxy::yPosRoleChanged,
+ m_itemModelHandler, &AbstractItemModelHandler::handleMappingChanged);
+ QObject::connect(qptr(), &QItemModelScatterDataProxy::zPosRoleChanged,
+ m_itemModelHandler, &AbstractItemModelHandler::handleMappingChanged);
+ QObject::connect(qptr(), &QItemModelScatterDataProxy::rotationRoleChanged,
+ m_itemModelHandler, &AbstractItemModelHandler::handleMappingChanged);
+ QObject::connect(qptr(), &QItemModelScatterDataProxy::xPosRolePatternChanged,
+ m_itemModelHandler, &AbstractItemModelHandler::handleMappingChanged);
+ QObject::connect(qptr(), &QItemModelScatterDataProxy::yPosRolePatternChanged,
+ m_itemModelHandler, &AbstractItemModelHandler::handleMappingChanged);
+ QObject::connect(qptr(), &QItemModelScatterDataProxy::zPosRolePatternChanged,
+ m_itemModelHandler, &AbstractItemModelHandler::handleMappingChanged);
+ QObject::connect(qptr(), &QItemModelScatterDataProxy::rotationRolePatternChanged,
+ m_itemModelHandler, &AbstractItemModelHandler::handleMappingChanged);
+ QObject::connect(qptr(), &QItemModelScatterDataProxy::xPosRoleReplaceChanged,
+ m_itemModelHandler, &AbstractItemModelHandler::handleMappingChanged);
+ QObject::connect(qptr(), &QItemModelScatterDataProxy::yPosRoleReplaceChanged,
+ m_itemModelHandler, &AbstractItemModelHandler::handleMappingChanged);
+ QObject::connect(qptr(), &QItemModelScatterDataProxy::zPosRoleReplaceChanged,
+ m_itemModelHandler, &AbstractItemModelHandler::handleMappingChanged);
+ QObject::connect(qptr(), &QItemModelScatterDataProxy::rotationRoleReplaceChanged,
+ m_itemModelHandler, &AbstractItemModelHandler::handleMappingChanged);
+}
+
+QT_END_NAMESPACE
diff --git a/src/graphs/data/qitemmodelscatterdataproxy.h b/src/graphs/data/qitemmodelscatterdataproxy.h
new file mode 100644
index 0000000..0847fe0
--- /dev/null
+++ b/src/graphs/data/qitemmodelscatterdataproxy.h
@@ -0,0 +1,105 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QITEMMODELSCATTERDATAPROXY_H
+#define QITEMMODELSCATTERDATAPROXY_H
+
+#include <QtGraphs/qscatterdataproxy.h>
+#include <QtCore/QAbstractItemModel>
+#include <QtCore/QString>
+#include <QtCore/QRegularExpression>
+
+QT_BEGIN_NAMESPACE
+
+class QItemModelScatterDataProxyPrivate;
+
+class Q_GRAPHS_EXPORT QItemModelScatterDataProxy : public QScatterDataProxy
+{
+ Q_OBJECT
+ Q_PROPERTY(QAbstractItemModel* itemModel READ itemModel WRITE setItemModel NOTIFY itemModelChanged)
+ Q_PROPERTY(QString xPosRole READ xPosRole WRITE setXPosRole NOTIFY xPosRoleChanged)
+ Q_PROPERTY(QString yPosRole READ yPosRole WRITE setYPosRole NOTIFY yPosRoleChanged)
+ Q_PROPERTY(QString zPosRole READ zPosRole WRITE setZPosRole NOTIFY zPosRoleChanged)
+ Q_PROPERTY(QString rotationRole READ rotationRole WRITE setRotationRole NOTIFY rotationRoleChanged)
+ Q_PROPERTY(QRegularExpression xPosRolePattern READ xPosRolePattern WRITE setXPosRolePattern NOTIFY xPosRolePatternChanged)
+ Q_PROPERTY(QRegularExpression yPosRolePattern READ yPosRolePattern WRITE setYPosRolePattern NOTIFY yPosRolePatternChanged)
+ Q_PROPERTY(QRegularExpression zPosRolePattern READ zPosRolePattern WRITE setZPosRolePattern NOTIFY zPosRolePatternChanged)
+ Q_PROPERTY(QRegularExpression rotationRolePattern READ rotationRolePattern WRITE setRotationRolePattern NOTIFY rotationRolePatternChanged)
+ Q_PROPERTY(QString xPosRoleReplace READ xPosRoleReplace WRITE setXPosRoleReplace NOTIFY xPosRoleReplaceChanged)
+ Q_PROPERTY(QString yPosRoleReplace READ yPosRoleReplace WRITE setYPosRoleReplace NOTIFY yPosRoleReplaceChanged)
+ Q_PROPERTY(QString zPosRoleReplace READ zPosRoleReplace WRITE setZPosRoleReplace NOTIFY zPosRoleReplaceChanged)
+ Q_PROPERTY(QString rotationRoleReplace READ rotationRoleReplace WRITE setRotationRoleReplace NOTIFY rotationRoleReplaceChanged)
+
+public:
+ explicit QItemModelScatterDataProxy(QObject *parent = nullptr);
+ explicit QItemModelScatterDataProxy(QAbstractItemModel *itemModel, QObject *parent = nullptr);
+ explicit QItemModelScatterDataProxy(QAbstractItemModel *itemModel,
+ const QString &xPosRole, const QString &yPosRole,
+ const QString &zPosRole, QObject *parent = nullptr);
+ explicit QItemModelScatterDataProxy(QAbstractItemModel *itemModel,
+ const QString &xPosRole, const QString &yPosRole,
+ const QString &zPosRole, const QString &rotationRole,
+ QObject *parent = nullptr);
+ virtual ~QItemModelScatterDataProxy();
+
+ void setItemModel(QAbstractItemModel *itemModel);
+ QAbstractItemModel *itemModel() const;
+
+ void setXPosRole(const QString &role);
+ QString xPosRole() const;
+ void setYPosRole(const QString &role);
+ QString yPosRole() const;
+ void setZPosRole(const QString &role);
+ QString zPosRole() const;
+ void setRotationRole(const QString &role);
+ QString rotationRole() const;
+
+ void remap(const QString &xPosRole, const QString &yPosRole, const QString &zPosRole,
+ const QString &rotationRole);
+
+ void setXPosRolePattern(const QRegularExpression &pattern);
+ QRegularExpression xPosRolePattern() const;
+ void setYPosRolePattern(const QRegularExpression &pattern);
+ QRegularExpression yPosRolePattern() const;
+ void setZPosRolePattern(const QRegularExpression &pattern);
+ QRegularExpression zPosRolePattern() const;
+ void setRotationRolePattern(const QRegularExpression &pattern);
+ QRegularExpression rotationRolePattern() const;
+
+ void setXPosRoleReplace(const QString &replace);
+ QString xPosRoleReplace() const;
+ void setYPosRoleReplace(const QString &replace);
+ QString yPosRoleReplace() const;
+ void setZPosRoleReplace(const QString &replace);
+ QString zPosRoleReplace() const;
+ void setRotationRoleReplace(const QString &replace);
+ QString rotationRoleReplace() const;
+
+Q_SIGNALS:
+ void itemModelChanged(const QAbstractItemModel* itemModel);
+ void xPosRoleChanged(const QString &role);
+ void yPosRoleChanged(const QString &role);
+ void zPosRoleChanged(const QString &role);
+ void rotationRoleChanged(const QString &role);
+ void xPosRolePatternChanged(const QRegularExpression &pattern);
+ void yPosRolePatternChanged(const QRegularExpression &pattern);
+ void zPosRolePatternChanged(const QRegularExpression &pattern);
+ void rotationRolePatternChanged(const QRegularExpression &pattern);
+ void rotationRoleReplaceChanged(const QString &replace);
+ void xPosRoleReplaceChanged(const QString &replace);
+ void yPosRoleReplaceChanged(const QString &replace);
+ void zPosRoleReplaceChanged(const QString &replace);
+
+protected:
+ QItemModelScatterDataProxyPrivate *dptr();
+ const QItemModelScatterDataProxyPrivate *dptrc() const;
+
+private:
+ Q_DISABLE_COPY(QItemModelScatterDataProxy)
+
+ friend class ScatterItemModelHandler;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/data/qitemmodelscatterdataproxy_p.h b/src/graphs/data/qitemmodelscatterdataproxy_p.h
new file mode 100644
index 0000000..ee5c695
--- /dev/null
+++ b/src/graphs/data/qitemmodelscatterdataproxy_p.h
@@ -0,0 +1,58 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QtGraphs API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#ifndef QITEMMODELSCATTERDATAPROXY_P_H
+#define QITEMMODELSCATTERDATAPROXY_P_H
+
+#include "qitemmodelscatterdataproxy.h"
+#include "qscatterdataproxy_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class ScatterItemModelHandler;
+
+class QItemModelScatterDataProxyPrivate : public QScatterDataProxyPrivate
+{
+ Q_OBJECT
+public:
+ QItemModelScatterDataProxyPrivate(QItemModelScatterDataProxy *q);
+ virtual ~QItemModelScatterDataProxyPrivate();
+
+ void connectItemModelHandler();
+
+private:
+ QItemModelScatterDataProxy *qptr();
+
+ ScatterItemModelHandler *m_itemModelHandler;
+ QString m_xPosRole;
+ QString m_yPosRole;
+ QString m_zPosRole;
+ QString m_rotationRole;
+
+ QRegularExpression m_xPosRolePattern;
+ QRegularExpression m_yPosRolePattern;
+ QRegularExpression m_zPosRolePattern;
+ QRegularExpression m_rotationRolePattern;
+
+ QString m_xPosRoleReplace;
+ QString m_yPosRoleReplace;
+ QString m_zPosRoleReplace;
+ QString m_rotationRoleReplace;
+
+ friend class ScatterItemModelHandler;
+ friend class QItemModelScatterDataProxy;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/data/qitemmodelsurfacedataproxy.cpp b/src/graphs/data/qitemmodelsurfacedataproxy.cpp
new file mode 100644
index 0000000..363a39a
--- /dev/null
+++ b/src/graphs/data/qitemmodelsurfacedataproxy.cpp
@@ -0,0 +1,1099 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qitemmodelsurfacedataproxy_p.h"
+#include "surfaceitemmodelhandler_p.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ * \class QItemModelSurfaceDataProxy
+ * \inmodule QtGraphs
+ * \brief Proxy class for presenting data in item models with Q3DSurface.
+ *
+ * QItemModelSurfaceDataProxy allows you to use QAbstractItemModel derived models as a data source
+ * for Q3DSurface. It uses the defined mappings to map data from the model to rows, columns, and
+ * surface points of Q3DSurface graph.
+ *
+ * Data is resolved asynchronously whenever the mapping or the model changes.
+ * QSurfaceDataProxy::arrayReset() is emitted when the data has been resolved.
+ * However, when useModelCategories property is set to \c true, single item changes are resolved
+ * synchronously, unless the same frame also contains a change that causes the whole model to be
+ * resolved.
+ *
+ * Mappings can be used in the following ways:
+ *
+ * \list
+ * \li If useModelCategories property is set to \c true, this proxy will map rows and
+ * columns of QAbstractItemModel to rows and columns of Q3DSurface, and uses the value returned for
+ * Qt::DisplayRole as Y-position by default. Row and column headers are used for Z-position and
+ * X-position by default, if they can be converted to floats. Otherwise row and column indices
+ * are used.
+ * The Y-position role to be used can be redefined if Qt::DisplayRole is not suitable.
+ * The Z-position and X-position roles to be used can be redefined if the headers or indices
+ * are not suitable.
+ *
+ * \li For models that do not have data already neatly sorted into rows and columns, such as
+ * QAbstractListModel based models, you can define a role from the model to map for each of row,
+ * column and Y-position.
+ *
+ * \li If you do not want to include all data contained in the model, or the autogenerated rows and
+ * columns are not ordered as you wish, you can specify which rows and columns should be included
+ * and in which order by defining an explicit list of categories for either or both of rows and
+ * columns.
+ * \endlist
+ *
+ * For example, assume that you have a custom QAbstractItemModel storing surface topography data.
+ * Each item in the model has the roles "longitude", "latitude", and "height".
+ * The item model already contains the data properly sorted so that longitudes and latitudes are
+ * first encountered in correct order, which enables us to utilize the row and column category
+ * autogeneration.
+ * You could do the following to display the data in a surface graph:
+ *
+ * \snippet doc_src_qtgraphs.cpp 5
+ *
+ * If the fields of the model do not contain the data in the exact format you need, you can specify
+ * a search pattern regular expression and a replace rule for each role to get the value in a
+ * format you need. For more information how the replace using regular expressions works, see
+ * QString::replace(const QRegularExpression &rx, const QString &after) function documentation. Note that
+ * using regular expressions has an impact on the performance, so it's more efficient to utilize
+ * item models where doing search and replace is not necessary to get the desired values.
+ *
+ * For example about using the search patterns in conjunction with the roles, see
+ * ItemModelBarDataProxy usage in \l{Qt Quick 2 Bars Example}.
+ *
+ * \sa {Qt Graphs Data Handling}
+ */
+
+/*!
+ * \qmltype ItemModelSurfaceDataProxy
+ * \inqmlmodule QtGraphs
+ * \ingroup graphs_qml
+ * \instantiates QItemModelSurfaceDataProxy
+ * \inherits SurfaceDataProxy
+ * \brief Proxy class for presenting data in item models with Surface3D.
+ *
+ * This type allows you to use \c AbstractItemModel derived models as a data
+ * source for Surface3D.
+ *
+ * Data is resolved asynchronously whenever the mapping or the model changes.
+ *
+ * For ItemModelSurfaceDataProxy enums, see \l{QItemModelSurfaceDataProxy::MultiMatchBehavior}.
+ *
+ * For more details, see QItemModelSurfaceDataProxy documentation.
+ *
+ * Usage example:
+ *
+ * \snippet doc_src_qmlgraphs.cpp 9
+ *
+ * \sa SurfaceDataProxy, {Qt Graphs Data Handling}
+ */
+
+/*!
+ * \qmlproperty model ItemModelSurfaceDataProxy::itemModel
+ * The item model used as a data source for Surface3D.
+ */
+
+/*!
+ * \qmlproperty string ItemModelSurfaceDataProxy::rowRole
+ * The item model role to map to the row category.
+ * In addition to defining which row the data belongs to, the value indicated by the row role
+ * is also set as the Z-coordinate value of QSurfaceDataItem when model data is resolved,
+ * unless a separate z position role is also defined.
+ */
+
+/*!
+ * \qmlproperty string ItemModelSurfaceDataProxy::columnRole
+ * The item model role to map to the column category.
+ * In addition to defining which column the data belongs to, the value indicated by the column role
+ * is also set as the X-coordinate value of QSurfaceDataItem when model data is resolved,
+ * unless a separate x position role is also defined.
+ */
+
+/*!
+ * \qmlproperty string ItemModelSurfaceDataProxy::xPosRole
+ * The item model role to map to the X position. If this role is not defined, columnRole is
+ * used to determine the X-coordinate value of the resolved \c QSurfaceDataItem
+ * items.
+ */
+
+/*!
+ * \qmlproperty string ItemModelSurfaceDataProxy::yPosRole
+ * The item model role to map to the Y position.
+ */
+
+/*!
+ * \qmlproperty string ItemModelSurfaceDataProxy::zPosRole
+ * The item model role to map to the Z position. If this role is not defined, rowRole is
+ * used to determine the Z-coordinate value of the resolved \c QSurfaceDataItem
+ * items.
+ */
+
+/*!
+ * \qmlproperty list<String> ItemModelSurfaceDataProxy::rowCategories
+ * The row categories of the mapping. Only items with row roles that are found in this list are
+ * included when data is resolved. The rows are ordered in the same order as they are in this list.
+ */
+
+/*!
+ * \qmlproperty list<String> ItemModelSurfaceDataProxy::columnCategories
+ * The column categories of the mapping. Only items with column roles that are found in this
+ * list are included when data is resolved. The columns are ordered in the same order as they are
+ * in this list.
+ */
+
+/*!
+ * \qmlproperty bool ItemModelSurfaceDataProxy::useModelCategories
+ * When set to \c true, the mapping ignores row and column roles and categories, and uses
+ * the rows and columns from the model instead. Defaults to \c{false}.
+ */
+
+/*!
+ * \qmlproperty bool ItemModelSurfaceDataProxy::autoRowCategories
+ * When set to \c true, the mapping ignores any explicitly set row categories
+ * and overwrites them with automatically generated ones whenever the
+ * data from the model is resolved. Proxy minimum and maximum row values are also
+ * autogenerated from the data when this is set to \c true. Defaults to \c{true}.
+ */
+
+/*!
+ * \qmlproperty bool ItemModelSurfaceDataProxy::autoColumnCategories
+ * When set to \c true, the mapping ignores any explicitly set column categories
+ * and overwrites them with automatically generated ones whenever the
+ * data from the model is resolved. Proxy minimum and maximum column values are also
+ * autogenerated from the data when this is set to \c true. Defaults to \c{true}.
+ */
+
+/*!
+ * \qmlproperty regExp ItemModelSurfaceDataProxy::rowRolePattern
+ *
+ * When set, a search and replace is done on the value mapped by the row role before it is used as
+ * a row category. This property specifies the regular expression to find the portion of the
+ * mapped value to replace and the rowRoleReplace property contains the replacement string.
+ *
+ * \sa rowRole, rowRoleReplace
+ */
+
+/*!
+ * \qmlproperty regExp ItemModelSurfaceDataProxy::columnRolePattern
+ *
+ * When set, a search and replace is done on the value mapped by the column role before it is used
+ * as a column category. This property specifies the regular expression to find the portion of the
+ * mapped value to replace and the columnRoleReplace property contains the replacement string.
+ *
+ * \sa columnRole, columnRoleReplace
+ */
+
+/*!
+ * \qmlproperty regExp ItemModelSurfaceDataProxy::xPosRolePattern
+ *
+ * When set, a search and replace is done on the value mapped by the x position
+ * role before it is used as an item position value. This property specifies
+ * the regular expression to find the portion of the mapped value to replace and
+ * the xPosRoleReplace property contains the replacement string.
+ *
+ * \sa xPosRole, xPosRoleReplace
+ */
+
+/*!
+ * \qmlproperty regExp ItemModelSurfaceDataProxy::yPosRolePattern
+ *
+ * When set, a search and replace is done on the value mapped by the y position
+ * role before it is used as an item position value. This property specifies
+ * the regular expression to find the portion of the mapped value to replace and
+ * the yPosRoleReplace property contains the replacement string.
+ *
+ * \sa yPosRole, yPosRoleReplace
+ */
+
+/*!
+ * \qmlproperty regExp ItemModelSurfaceDataProxy::zPosRolePattern
+ *
+ * When set, a search and replace is done on the value mapped by the z position
+ * role before it is used as an item position value. This property specifies
+ * the regular expression to find the portion of the mapped value to replace and
+ * the zPosRoleReplace property contains the replacement string.
+ *
+ * \sa zPosRole, zPosRoleReplace
+ */
+
+/*!
+ * \qmlproperty string ItemModelSurfaceDataProxy::rowRoleReplace
+ *
+ * The replace content to be used in conjunction with rowRolePattern.
+ * Defaults to an empty string. For more information on how the search and
+ * replace using regular expressions works, see the
+ * QString::replace(const QRegularExpression &rx, const QString &after)
+ * function documentation.
+ *
+ * \sa rowRole, rowRolePattern
+ */
+
+/*!
+ * \qmlproperty string ItemModelSurfaceDataProxy::columnRoleReplace
+ *
+ * The replace content to be used in conjunction with columnRolePattern.
+ * Defaults to an empty string. For more information on how the search and
+ * replace using regular expressions works, see the
+ * QString::replace(const QRegularExpression &rx, const QString &after)
+ * function documentation.
+ *
+ * \sa columnRole, columnRolePattern
+ */
+
+/*!
+ * \qmlproperty string ItemModelSurfaceDataProxy::xPosRoleReplace
+ *
+ * The replace content to be used in conjunction with xPosRolePattern.
+ * Defaults to an empty string. For more information on how the search and
+ * replace using regular expressions works, see the
+ * QString::replace(const QRegularExpression &rx, const QString &after)
+ * function documentation.
+ *
+ * \sa xPosRole, xPosRolePattern
+ */
+
+/*!
+ * \qmlproperty string ItemModelSurfaceDataProxy::yPosRoleReplace
+ *
+ * The replace content to be used in conjunction with yPosRolePattern.
+ * Defaults to an empty string. For more information on how the search and
+ * replace using regular expressions works, see the
+ * QString::replace(const QRegularExpression &rx, const QString &after)
+ * function documentation.
+ *
+ * \sa yPosRole, yPosRolePattern
+ */
+
+/*!
+ * \qmlproperty string ItemModelSurfaceDataProxy::zPosRoleReplace
+ *
+ * The replace content to be used in conjunction with zPosRolePattern.
+ * Defaults to an empty string. For more information on how the search and
+ * replace using regular expressions works, see the
+ * QString::replace(const QRegularExpression &rx, const QString &after)
+ * function documentation.
+ *
+ * \sa zPosRole, zPosRolePattern
+ */
+
+/*!
+ * \qmlproperty ItemModelSurfaceDataProxy.MultiMatchBehavior ItemModelSurfaceDataProxy::multiMatchBehavior
+ * Defines how multiple matches for each row/column combination are handled.
+ * Defaults to \l{QItemModelSurfaceDataProxy::MMBLast}{ItemModelSurfaceDataProxy.MMBLast}.
+ *
+ * For example, you might have an item model with timestamped data taken at irregular intervals
+ * and you want to visualize an average position of data items on each hour with a surface graph.
+ * This can be done by specifying row and column categories so that each surface point represents
+ * an hour, and setting multiMatchBehavior to
+ * \l{QItemModelSurfaceDataProxy::MMBAverage}{ItemModelSurfaceDataProxy.MMBAverage}.
+ */
+
+/*!
+ * \enum QItemModelSurfaceDataProxy::MultiMatchBehavior
+ *
+ * Behavior types for QItemModelSurfaceDataProxy::multiMatchBehavior property.
+ *
+ * \value MMBFirst
+ * The position values are taken from the first item in the item model that matches
+ * each row/column combination.
+ * \value MMBLast
+ * The position values are taken from the last item in the item model that matches
+ * each row/column combination.
+ * \value MMBAverage
+ * The position values from all items matching each row/column combination are
+ * averaged together and the averages are used as the surface point position.
+ * \value MMBCumulativeY
+ * For X and Z values this acts just like \c{MMBAverage}, but Y values are added together
+ * instead of averaged and the total is used as the surface point Y position.
+ */
+
+/*!
+ * Constructs QItemModelSurfaceDataProxy with optional \a parent.
+ */
+QItemModelSurfaceDataProxy::QItemModelSurfaceDataProxy(QObject *parent)
+ : QSurfaceDataProxy(new QItemModelSurfaceDataProxyPrivate(this), parent)
+{
+ dptr()->connectItemModelHandler();
+}
+
+/*!
+ * Constructs QItemModelSurfaceDataProxy with \a itemModel and optional \a parent. Proxy doesn't take
+ * ownership of the \a itemModel, as typically item models are owned by other controls.
+ */
+QItemModelSurfaceDataProxy::QItemModelSurfaceDataProxy(QAbstractItemModel *itemModel,
+ QObject *parent)
+ : QSurfaceDataProxy(new QItemModelSurfaceDataProxyPrivate(this), parent)
+{
+ dptr()->m_itemModelHandler->setItemModel(itemModel);
+ dptr()->connectItemModelHandler();
+}
+
+/*!
+ * Constructs QItemModelSurfaceDataProxy with \a itemModel and optional \a parent. Proxy doesn't take
+ * ownership of the \a itemModel, as typically item models are owned by other controls.
+ * The yPosRole role is set to \a yPosRole.
+ * This constructor is meant to be used with models that have data properly sorted
+ * in rows and columns already, so it also sets useModelCategories property to \c true.
+ */
+QItemModelSurfaceDataProxy::QItemModelSurfaceDataProxy(QAbstractItemModel *itemModel,
+ const QString &yPosRole,
+ QObject *parent)
+ : QSurfaceDataProxy(new QItemModelSurfaceDataProxyPrivate(this), parent)
+{
+ dptr()->m_itemModelHandler->setItemModel(itemModel);
+ dptr()->m_yPosRole = yPosRole;
+ dptr()->m_useModelCategories = true;
+ dptr()->connectItemModelHandler();
+}
+
+/*!
+ * Constructs QItemModelSurfaceDataProxy with \a itemModel and optional \a parent. Proxy doesn't take
+ * ownership of the \a itemModel, as typically item models are owned by other controls.
+ * The role mappings are set with \a rowRole, \a columnRole, and \a yPosRole.
+ * The zPosRole and the xPosRole are set to \a rowRole and \a columnRole, respectively.
+ */
+QItemModelSurfaceDataProxy::QItemModelSurfaceDataProxy(QAbstractItemModel *itemModel,
+ const QString &rowRole,
+ const QString &columnRole,
+ const QString &yPosRole,
+ QObject *parent)
+ : QSurfaceDataProxy(new QItemModelSurfaceDataProxyPrivate(this), parent)
+{
+ dptr()->m_itemModelHandler->setItemModel(itemModel);
+ dptr()->m_rowRole = rowRole;
+ dptr()->m_columnRole = columnRole;
+ dptr()->m_xPosRole = columnRole;
+ dptr()->m_yPosRole = yPosRole;
+ dptr()->m_zPosRole = rowRole;
+ dptr()->connectItemModelHandler();
+}
+
+/*!
+ * Constructs QItemModelSurfaceDataProxy with \a itemModel and optional \a parent. Proxy doesn't take
+ * ownership of the \a itemModel, as typically item models are owned by other controls.
+ * The role mappings are set with \a rowRole, \a columnRole, \a xPosRole, \a yPosRole, and
+ * \a zPosRole.
+ */
+QItemModelSurfaceDataProxy::QItemModelSurfaceDataProxy(QAbstractItemModel *itemModel,
+ const QString &rowRole,
+ const QString &columnRole,
+ const QString &xPosRole,
+ const QString &yPosRole,
+ const QString &zPosRole,
+ QObject *parent)
+ : QSurfaceDataProxy(new QItemModelSurfaceDataProxyPrivate(this), parent)
+{
+ dptr()->m_itemModelHandler->setItemModel(itemModel);
+ dptr()->m_rowRole = rowRole;
+ dptr()->m_columnRole = columnRole;
+ dptr()->m_xPosRole = xPosRole;
+ dptr()->m_yPosRole = yPosRole;
+ dptr()->m_zPosRole = zPosRole;
+ dptr()->connectItemModelHandler();
+}
+
+/*!
+ * Constructs QItemModelSurfaceDataProxy with \a itemModel and optional \a parent. Proxy doesn't take
+ * ownership of the \a itemModel, as typically item models are owned by other controls.
+ * The role mappings are set with \a rowRole, \a columnRole, and \a yPosRole.
+ * The zPosRole and the xPosRole are set to \a rowRole and \a columnRole, respectively.
+ * Row and column categories are set with \a rowCategories and \a columnCategories.
+ * This constructor also sets autoRowCategories and autoColumnCategories to false.
+ */
+QItemModelSurfaceDataProxy::QItemModelSurfaceDataProxy(QAbstractItemModel *itemModel,
+ const QString &rowRole,
+ const QString &columnRole,
+ const QString &yPosRole,
+ const QStringList &rowCategories,
+ const QStringList &columnCategories,
+ QObject *parent)
+ : QSurfaceDataProxy(new QItemModelSurfaceDataProxyPrivate(this), parent)
+{
+ dptr()->m_itemModelHandler->setItemModel(itemModel);
+ dptr()->m_rowRole = rowRole;
+ dptr()->m_columnRole = columnRole;
+ dptr()->m_xPosRole = columnRole;
+ dptr()->m_yPosRole = yPosRole;
+ dptr()->m_zPosRole = rowRole;
+ dptr()->m_rowCategories = rowCategories;
+ dptr()->m_columnCategories = columnCategories;
+ dptr()->m_autoRowCategories = false;
+ dptr()->m_autoColumnCategories = false;
+ dptr()->connectItemModelHandler();
+}
+
+/*!
+ * Constructs QItemModelSurfaceDataProxy with \a itemModel and optional \a parent. Proxy doesn't take
+ * ownership of the \a itemModel, as typically item models are owned by other controls.
+ * The role mappings are set with \a rowRole, \a columnRole, \a xPosRole, \a yPosRole,
+ * and \a zPosRole.
+ * Row and column categories are set with \a rowCategories and \a columnCategories.
+ * This constructor also sets autoRowCategories and autoColumnCategories to false.
+ */
+QItemModelSurfaceDataProxy::QItemModelSurfaceDataProxy(QAbstractItemModel *itemModel,
+ const QString &rowRole,
+ const QString &columnRole,
+ const QString &xPosRole,
+ const QString &yPosRole,
+ const QString &zPosRole,
+ const QStringList &rowCategories,
+ const QStringList &columnCategories,
+ QObject *parent)
+ : QSurfaceDataProxy(new QItemModelSurfaceDataProxyPrivate(this), parent)
+{
+ dptr()->m_itemModelHandler->setItemModel(itemModel);
+ dptr()->m_rowRole = rowRole;
+ dptr()->m_columnRole = columnRole;
+ dptr()->m_xPosRole = xPosRole;
+ dptr()->m_yPosRole = yPosRole;
+ dptr()->m_zPosRole = zPosRole;
+ dptr()->m_rowCategories = rowCategories;
+ dptr()->m_columnCategories = columnCategories;
+ dptr()->m_autoRowCategories = false;
+ dptr()->m_autoColumnCategories = false;
+ dptr()->connectItemModelHandler();
+}
+
+/*!
+ * Destroys QItemModelSurfaceDataProxy.
+ */
+QItemModelSurfaceDataProxy::~QItemModelSurfaceDataProxy()
+{
+}
+
+/*!
+ * \property QItemModelSurfaceDataProxy::itemModel
+ *
+ * \brief The item model used as a data source for the 3D surface.
+ */
+
+/*!
+ * Sets the item model to \a itemModel. Does not take ownership of the model,
+ * but does connect to it to listen for changes.
+ */
+void QItemModelSurfaceDataProxy::setItemModel(QAbstractItemModel *itemModel)
+{
+ dptr()->m_itemModelHandler->setItemModel(itemModel);
+}
+
+QAbstractItemModel *QItemModelSurfaceDataProxy::itemModel() const
+{
+ return dptrc()->m_itemModelHandler->itemModel();
+}
+
+/*!
+ * \property QItemModelSurfaceDataProxy::rowRole
+ *
+ * \brief The item model role to map to the row category.
+ *
+ * In addition to defining which row the data belongs to, the value indicated by the row role
+ * is also set as the Z-coordinate value of QSurfaceDataItem when model data is resolved,
+ * unless a separate z position role is also defined.
+ */
+void QItemModelSurfaceDataProxy::setRowRole(const QString &role)
+{
+ if (dptr()->m_rowRole != role) {
+ dptr()->m_rowRole = role;
+ emit rowRoleChanged(role);
+ }
+}
+
+QString QItemModelSurfaceDataProxy::rowRole() const
+{
+ return dptrc()->m_rowRole;
+}
+
+/*!
+ * \property QItemModelSurfaceDataProxy::columnRole
+ *
+ * \brief The item model role to map to the column category.
+ *
+ * In addition to defining which column the data belongs to, the value indicated by the column
+ * role is also set as the X-coordinate value of QSurfaceDataItem when model data is resolved,
+ * unless a separate x position role is also defined.
+ */
+void QItemModelSurfaceDataProxy::setColumnRole(const QString &role)
+{
+ if (dptr()->m_columnRole != role) {
+ dptr()->m_columnRole = role;
+ emit columnRoleChanged(role);
+ }
+}
+
+QString QItemModelSurfaceDataProxy::columnRole() const
+{
+ return dptrc()->m_columnRole;
+}
+
+/*!
+ * \property QItemModelSurfaceDataProxy::xPosRole
+ *
+ * \brief The item model role to map to the X position.
+ *
+ * If this role is not defined, columnRole is used to determine the X-coordinate
+ * value of the resolved \l{QSurfaceDataItem} objects.
+ */
+void QItemModelSurfaceDataProxy::setXPosRole(const QString &role)
+{
+ if (dptr()->m_xPosRole != role) {
+ dptr()->m_xPosRole = role;
+ emit xPosRoleChanged(role);
+ }
+}
+
+QString QItemModelSurfaceDataProxy::xPosRole() const
+{
+ return dptrc()->m_xPosRole;
+}
+
+/*!
+ * \property QItemModelSurfaceDataProxy::yPosRole
+ *
+ * \brief The item model role to map to the Y position.
+ */
+void QItemModelSurfaceDataProxy::setYPosRole(const QString &role)
+{
+ if (dptr()->m_yPosRole != role) {
+ dptr()->m_yPosRole = role;
+ emit yPosRoleChanged(role);
+ }
+}
+
+QString QItemModelSurfaceDataProxy::yPosRole() const
+{
+ return dptrc()->m_yPosRole;
+}
+
+/*!
+ * \property QItemModelSurfaceDataProxy::zPosRole
+ *
+ * \brief The item model role to map to the Z position.
+ *
+ * If this role is not defined, rowRole is used to determine the Z-coordinate
+ * value of resolved \l{QSurfaceDataItem} objects.
+ */
+void QItemModelSurfaceDataProxy::setZPosRole(const QString &role)
+{
+ if (dptr()->m_zPosRole != role) {
+ dptr()->m_zPosRole = role;
+ emit zPosRoleChanged(role);
+ }
+}
+
+QString QItemModelSurfaceDataProxy::zPosRole() const
+{
+ return dptrc()->m_zPosRole;
+}
+
+/*!
+ * \property QItemModelSurfaceDataProxy::rowCategories
+ *
+ * \brief The row categories for the mapping.
+ */
+void QItemModelSurfaceDataProxy::setRowCategories(const QStringList &categories)
+{
+ if (dptr()->m_rowCategories != categories) {
+ dptr()->m_rowCategories = categories;
+ emit rowCategoriesChanged();
+ }
+}
+
+QStringList QItemModelSurfaceDataProxy::rowCategories() const
+{
+ return dptrc()->m_rowCategories;
+}
+
+/*!
+ * \property QItemModelSurfaceDataProxy::columnCategories
+ *
+ * \brief The column categories for the mapping.
+ */
+void QItemModelSurfaceDataProxy::setColumnCategories(const QStringList &categories)
+{
+ if (dptr()->m_columnCategories != categories) {
+ dptr()->m_columnCategories = categories;
+ emit columnCategoriesChanged();
+ }
+}
+
+QStringList QItemModelSurfaceDataProxy::columnCategories() const
+{
+ return dptrc()->m_columnCategories;
+}
+
+/*!
+ * \property QItemModelSurfaceDataProxy::useModelCategories
+ *
+ * \brief Whether row and column roles and categories are used for mapping.
+ *
+ * When set to \c true, the mapping ignores row and column roles and categories, and uses
+ * the rows and columns from the model instead. Defaults to \c{false}.
+ */
+void QItemModelSurfaceDataProxy::setUseModelCategories(bool enable)
+{
+ if (dptr()->m_useModelCategories != enable) {
+ dptr()->m_useModelCategories = enable;
+ emit useModelCategoriesChanged(enable);
+ }
+}
+
+bool QItemModelSurfaceDataProxy::useModelCategories() const
+{
+ return dptrc()->m_useModelCategories;
+}
+
+/*!
+ * \property QItemModelSurfaceDataProxy::autoRowCategories
+ *
+ * \brief Whether row categories are generated automatically.
+ *
+ * When set to \c true, the mapping ignores any explicitly set row categories
+ * and overwrites them with automatically generated ones whenever the
+ * data from the model is resolved. Defaults to \c{true}.
+ */
+void QItemModelSurfaceDataProxy::setAutoRowCategories(bool enable)
+{
+ if (dptr()->m_autoRowCategories != enable) {
+ dptr()->m_autoRowCategories = enable;
+ emit autoRowCategoriesChanged(enable);
+ }
+}
+
+bool QItemModelSurfaceDataProxy::autoRowCategories() const
+{
+ return dptrc()->m_autoRowCategories;
+}
+
+/*!
+ * \property QItemModelSurfaceDataProxy::autoColumnCategories
+ *
+ * \brief Whether column categories are generated automatically.
+ *
+ * When set to \c true, the mapping ignores any explicitly set column categories
+ * and overwrites them with automatically generated ones whenever the
+ * data from the model is resolved. Defaults to \c{true}.
+ */
+void QItemModelSurfaceDataProxy::setAutoColumnCategories(bool enable)
+{
+ if (dptr()->m_autoColumnCategories != enable) {
+ dptr()->m_autoColumnCategories = enable;
+ emit autoColumnCategoriesChanged(enable);
+ }
+}
+
+bool QItemModelSurfaceDataProxy::autoColumnCategories() const
+{
+ return dptrc()->m_autoColumnCategories;
+}
+
+/*!
+ * Changes \a rowRole, \a columnRole, \a xPosRole, \a yPosRole, \a zPosRole,
+ * \a rowCategories and \a columnCategories to the mapping.
+ */
+void QItemModelSurfaceDataProxy::remap(const QString &rowRole,
+ const QString &columnRole,
+ const QString &xPosRole,
+ const QString &yPosRole,
+ const QString &zPosRole,
+ const QStringList &rowCategories,
+ const QStringList &columnCategories)
+{
+ setRowRole(rowRole);
+ setColumnRole(columnRole);
+ setXPosRole(xPosRole);
+ setYPosRole(yPosRole);
+ setZPosRole(zPosRole);
+ setRowCategories(rowCategories);
+ setColumnCategories(columnCategories);
+}
+
+/*!
+ * Returns the index of the specified \a category in the row categories list.
+ * If the row categories list is empty, -1 is returned.
+ * \note If the automatic row categories generation is in use, this method will
+ * not return a valid index before the data in the model is resolved for the first time.
+ */
+int QItemModelSurfaceDataProxy::rowCategoryIndex(const QString &category)
+{
+ return dptr()->m_rowCategories.indexOf(category);
+}
+
+/*!
+ * Returns the index of the specified \a category in the column categories list.
+ * If the category is not found, -1 is returned.
+ * \note If the automatic column categories generation is in use, this method will
+ * not return a valid index before the data in the model is resolved for the first time.
+ */
+int QItemModelSurfaceDataProxy::columnCategoryIndex(const QString &category)
+{
+ return dptr()->m_columnCategories.indexOf(category);
+}
+
+/*!
+ * \property QItemModelSurfaceDataProxy::rowRolePattern
+ *
+ * \brief Whether a search and replace is performed on the value mapped by the
+ * row role before it is used as a row category.
+ *
+ * This property specifies the regular expression to find the portion of the
+ * mapped value to replace and the rowRoleReplace property contains the
+ * replacement string.
+ *
+ * \sa rowRole, rowRoleReplace
+ */
+void QItemModelSurfaceDataProxy::setRowRolePattern(const QRegularExpression &pattern)
+{
+ if (dptr()->m_rowRolePattern != pattern) {
+ dptr()->m_rowRolePattern = pattern;
+ emit rowRolePatternChanged(pattern);
+ }
+}
+
+QRegularExpression QItemModelSurfaceDataProxy::rowRolePattern() const
+{
+ return dptrc()->m_rowRolePattern;
+}
+
+/*!
+ * \property QItemModelSurfaceDataProxy::columnRolePattern
+ *
+ * \brief Whether a search and replace is done on the value mapped by the column
+ * role before it is used as a column category.
+ *
+ * This property specifies the regular expression to find the portion of the
+ * mapped value to replace and the columnRoleReplace property contains the
+ * replacement string.
+ *
+ * \sa columnRole, columnRoleReplace
+ */
+void QItemModelSurfaceDataProxy::setColumnRolePattern(const QRegularExpression &pattern)
+{
+ if (dptr()->m_columnRolePattern != pattern) {
+ dptr()->m_columnRolePattern = pattern;
+ emit columnRolePatternChanged(pattern);
+ }
+}
+
+QRegularExpression QItemModelSurfaceDataProxy::columnRolePattern() const
+{
+ return dptrc()->m_columnRolePattern;
+}
+
+/*!
+ * \property QItemModelSurfaceDataProxy::xPosRolePattern
+ *
+ * \brief Whether a search and replace is done on the value mapped by the x
+ * position role before it is used as an item position value.
+ *
+ * This property specifies the regular expression to find the portion of the
+ * mapped value to replace and the xPosRoleReplace property contains the
+ * replacement string.
+ *
+ * \sa xPosRole, xPosRoleReplace
+ */
+void QItemModelSurfaceDataProxy::setXPosRolePattern(const QRegularExpression &pattern)
+{
+ if (dptr()->m_xPosRolePattern != pattern) {
+ dptr()->m_xPosRolePattern = pattern;
+ emit xPosRolePatternChanged(pattern);
+ }
+}
+
+QRegularExpression QItemModelSurfaceDataProxy::xPosRolePattern() const
+{
+ return dptrc()->m_xPosRolePattern;
+}
+
+/*!
+ * \property QItemModelSurfaceDataProxy::yPosRolePattern
+ *
+ * \brief Whether a search and replace is done on the value mapped by the y
+ * position role before it is used as an item position value.
+ *
+ * This property specifies the regular expression to find the portion of the
+ * mapped value to replace and the yPosRoleReplace property contains the
+ * replacement string.
+ *
+ * \sa yPosRole, yPosRoleReplace
+ */
+void QItemModelSurfaceDataProxy::setYPosRolePattern(const QRegularExpression &pattern)
+{
+ if (dptr()->m_yPosRolePattern != pattern) {
+ dptr()->m_yPosRolePattern = pattern;
+ emit yPosRolePatternChanged(pattern);
+ }
+}
+
+QRegularExpression QItemModelSurfaceDataProxy::yPosRolePattern() const
+{
+ return dptrc()->m_yPosRolePattern;
+}
+
+/*!
+ * \property QItemModelSurfaceDataProxy::zPosRolePattern
+ *
+ * \brief Whether a search and replace is done on the value mapped by the z
+ * position role before it is used as an item position value.
+ *
+ * This property specifies the regular expression to find the portion of the
+ * mapped value to replace and the zPosRoleReplace property contains the
+ * replacement string.
+ *
+ * \sa zPosRole, zPosRoleReplace
+ */
+void QItemModelSurfaceDataProxy::setZPosRolePattern(const QRegularExpression &pattern)
+{
+ if (dptr()->m_zPosRolePattern != pattern) {
+ dptr()->m_zPosRolePattern = pattern;
+ emit zPosRolePatternChanged(pattern);
+ }
+}
+
+QRegularExpression QItemModelSurfaceDataProxy::zPosRolePattern() const
+{
+ return dptrc()->m_zPosRolePattern;
+}
+
+/*!
+ * \property QItemModelSurfaceDataProxy::rowRoleReplace
+ *
+ * \brief The replace content to be used in conjunction with the row role
+ * pattern.
+ *
+ * Defaults to an empty string. For more information on how the search and replace using regular
+ * expressions works, see QString::replace(const QRegularExpression &rx, const QString &after)
+ * function documentation.
+ *
+ * \sa rowRole, rowRolePattern
+ */
+void QItemModelSurfaceDataProxy::setRowRoleReplace(const QString &replace)
+{
+ if (dptr()->m_rowRoleReplace != replace) {
+ dptr()->m_rowRoleReplace = replace;
+ emit rowRoleReplaceChanged(replace);
+ }
+}
+
+QString QItemModelSurfaceDataProxy::rowRoleReplace() const
+{
+ return dptrc()->m_rowRoleReplace;
+}
+
+/*!
+ * \property QItemModelSurfaceDataProxy::columnRoleReplace
+ *
+ * \brief The replace content to be used in conjunction with a column role
+ * pattern.
+ *
+ * Defaults to an empty string. For more information on how the search and
+ * replace using regular expressions works, see the
+ * QString::replace(const QRegularExpression &rx, const QString &after)
+ * function documentation.
+ *
+ * \sa columnRole, columnRolePattern
+ */
+void QItemModelSurfaceDataProxy::setColumnRoleReplace(const QString &replace)
+{
+ if (dptr()->m_columnRoleReplace != replace) {
+ dptr()->m_columnRoleReplace = replace;
+ emit columnRoleReplaceChanged(replace);
+ }
+}
+
+QString QItemModelSurfaceDataProxy::columnRoleReplace() const
+{
+ return dptrc()->m_columnRoleReplace;
+}
+
+/*!
+ * \property QItemModelSurfaceDataProxy::xPosRoleReplace
+ *
+ * \brief The replace content to be used in conjunction with an x position role
+ * pattern.
+ *
+ * Defaults to an empty string. For more information on how the search and
+ * replace using regular expressions works, see the
+ * QString::replace(const QRegularExpression &rx, const QString &after)
+ * function documentation.
+ *
+ * \sa xPosRole, xPosRolePattern
+ */
+void QItemModelSurfaceDataProxy::setXPosRoleReplace(const QString &replace)
+{
+ if (dptr()->m_xPosRoleReplace != replace) {
+ dptr()->m_xPosRoleReplace = replace;
+ emit xPosRoleReplaceChanged(replace);
+ }
+}
+
+QString QItemModelSurfaceDataProxy::xPosRoleReplace() const
+{
+ return dptrc()->m_xPosRoleReplace;
+}
+
+/*!
+ * \property QItemModelSurfaceDataProxy::yPosRoleReplace
+ *
+ * \brief The replace content to be used in conjunction with an y position role
+ * pattern.
+ *
+ * Defaults to an empty string. For more information on how the search and
+ * replace using regular expressions works, see the
+ * QString::replace(const QRegularExpression &rx, const QString &after)
+ * function documentation.
+ *
+ * \sa yPosRole, yPosRolePattern
+ */
+void QItemModelSurfaceDataProxy::setYPosRoleReplace(const QString &replace)
+{
+ if (dptr()->m_yPosRoleReplace != replace) {
+ dptr()->m_yPosRoleReplace = replace;
+ emit yPosRoleReplaceChanged(replace);
+ }
+}
+
+QString QItemModelSurfaceDataProxy::yPosRoleReplace() const
+{
+ return dptrc()->m_yPosRoleReplace;
+}
+
+/*!
+ * \property QItemModelSurfaceDataProxy::zPosRoleReplace
+ *
+ * \brief The replace content to be used in conjunction with a z position role
+ * pattern.
+ *
+ * Defaults to an empty string. For more information on how the search and
+ * replace using regular expressions works, see the
+ * QString::replace(const QRegularExpression &rx, const QString &after)
+ * function documentation.
+ *
+ * \sa zPosRole, zPosRolePattern
+ */
+void QItemModelSurfaceDataProxy::setZPosRoleReplace(const QString &replace)
+{
+ if (dptr()->m_zPosRoleReplace != replace) {
+ dptr()->m_zPosRoleReplace = replace;
+ emit zPosRoleReplaceChanged(replace);
+ }
+}
+
+QString QItemModelSurfaceDataProxy::zPosRoleReplace() const
+{
+ return dptrc()->m_zPosRoleReplace;
+}
+
+/*!
+ * \property QItemModelSurfaceDataProxy::multiMatchBehavior
+ *
+ * \brief How multiple matches for each row/column combination are handled.
+ *
+ * Defaults to MMBLast.
+ *
+ * For example, you might have an item model with timestamped data taken at irregular intervals
+ * and you want to visualize an average position of data items on each hour with a surface graph.
+ * This can be done by specifying row and column categories so that each surface point represents
+ * an hour, and setting this property to MMBAverage.
+ */
+
+void QItemModelSurfaceDataProxy::setMultiMatchBehavior(QItemModelSurfaceDataProxy::MultiMatchBehavior behavior)
+{
+ if (dptr()->m_multiMatchBehavior != behavior) {
+ dptr()->m_multiMatchBehavior = behavior;
+ emit multiMatchBehaviorChanged(behavior);
+ }
+}
+
+QItemModelSurfaceDataProxy::MultiMatchBehavior QItemModelSurfaceDataProxy::multiMatchBehavior() const
+{
+ return dptrc()->m_multiMatchBehavior;
+}
+
+/*!
+ * \internal
+ */
+QItemModelSurfaceDataProxyPrivate *QItemModelSurfaceDataProxy::dptr()
+{
+ return static_cast<QItemModelSurfaceDataProxyPrivate *>(d_ptr.data());
+}
+
+/*!
+ * \internal
+ */
+const QItemModelSurfaceDataProxyPrivate *QItemModelSurfaceDataProxy::dptrc() const
+{
+ return static_cast<const QItemModelSurfaceDataProxyPrivate *>(d_ptr.data());
+}
+
+// QItemModelSurfaceDataProxyPrivate
+
+QItemModelSurfaceDataProxyPrivate::QItemModelSurfaceDataProxyPrivate(QItemModelSurfaceDataProxy *q)
+ : QSurfaceDataProxyPrivate(q),
+ m_itemModelHandler(new SurfaceItemModelHandler(q)),
+ m_useModelCategories(false),
+ m_autoRowCategories(true),
+ m_autoColumnCategories(true),
+ m_multiMatchBehavior(QItemModelSurfaceDataProxy::MMBLast)
+{
+}
+
+QItemModelSurfaceDataProxyPrivate::~QItemModelSurfaceDataProxyPrivate()
+{
+ delete m_itemModelHandler;
+}
+
+QItemModelSurfaceDataProxy *QItemModelSurfaceDataProxyPrivate::qptr()
+{
+ return static_cast<QItemModelSurfaceDataProxy *>(q_ptr);
+}
+
+void QItemModelSurfaceDataProxyPrivate::connectItemModelHandler()
+{
+ QObject::connect(m_itemModelHandler, &SurfaceItemModelHandler::itemModelChanged,
+ qptr(), &QItemModelSurfaceDataProxy::itemModelChanged);
+ QObject::connect(qptr(), &QItemModelSurfaceDataProxy::rowRoleChanged,
+ m_itemModelHandler, &AbstractItemModelHandler::handleMappingChanged);
+ QObject::connect(qptr(), &QItemModelSurfaceDataProxy::columnRoleChanged,
+ m_itemModelHandler, &AbstractItemModelHandler::handleMappingChanged);
+ QObject::connect(qptr(), &QItemModelSurfaceDataProxy::xPosRoleChanged,
+ m_itemModelHandler, &AbstractItemModelHandler::handleMappingChanged);
+ QObject::connect(qptr(), &QItemModelSurfaceDataProxy::yPosRoleChanged,
+ m_itemModelHandler, &AbstractItemModelHandler::handleMappingChanged);
+ QObject::connect(qptr(), &QItemModelSurfaceDataProxy::zPosRoleChanged,
+ m_itemModelHandler, &AbstractItemModelHandler::handleMappingChanged);
+ QObject::connect(qptr(), &QItemModelSurfaceDataProxy::rowCategoriesChanged,
+ m_itemModelHandler, &AbstractItemModelHandler::handleMappingChanged);
+ QObject::connect(qptr(), &QItemModelSurfaceDataProxy::columnCategoriesChanged,
+ m_itemModelHandler, &AbstractItemModelHandler::handleMappingChanged);
+ QObject::connect(qptr(), &QItemModelSurfaceDataProxy::useModelCategoriesChanged,
+ m_itemModelHandler, &AbstractItemModelHandler::handleMappingChanged);
+ QObject::connect(qptr(), &QItemModelSurfaceDataProxy::autoRowCategoriesChanged,
+ m_itemModelHandler, &AbstractItemModelHandler::handleMappingChanged);
+ QObject::connect(qptr(), &QItemModelSurfaceDataProxy::autoColumnCategoriesChanged,
+ m_itemModelHandler, &AbstractItemModelHandler::handleMappingChanged);
+ QObject::connect(qptr(), &QItemModelSurfaceDataProxy::rowRolePatternChanged,
+ m_itemModelHandler, &AbstractItemModelHandler::handleMappingChanged);
+ QObject::connect(qptr(), &QItemModelSurfaceDataProxy::columnRolePatternChanged,
+ m_itemModelHandler, &AbstractItemModelHandler::handleMappingChanged);
+ QObject::connect(qptr(), &QItemModelSurfaceDataProxy::xPosRolePatternChanged,
+ m_itemModelHandler, &AbstractItemModelHandler::handleMappingChanged);
+ QObject::connect(qptr(), &QItemModelSurfaceDataProxy::yPosRolePatternChanged,
+ m_itemModelHandler, &AbstractItemModelHandler::handleMappingChanged);
+ QObject::connect(qptr(), &QItemModelSurfaceDataProxy::zPosRolePatternChanged,
+ m_itemModelHandler, &AbstractItemModelHandler::handleMappingChanged);
+ QObject::connect(qptr(), &QItemModelSurfaceDataProxy::rowRoleReplaceChanged,
+ m_itemModelHandler, &AbstractItemModelHandler::handleMappingChanged);
+ QObject::connect(qptr(), &QItemModelSurfaceDataProxy::columnRoleReplaceChanged,
+ m_itemModelHandler, &AbstractItemModelHandler::handleMappingChanged);
+ QObject::connect(qptr(), &QItemModelSurfaceDataProxy::xPosRoleReplaceChanged,
+ m_itemModelHandler, &AbstractItemModelHandler::handleMappingChanged);
+ QObject::connect(qptr(), &QItemModelSurfaceDataProxy::yPosRoleReplaceChanged,
+ m_itemModelHandler, &AbstractItemModelHandler::handleMappingChanged);
+ QObject::connect(qptr(), &QItemModelSurfaceDataProxy::zPosRoleReplaceChanged,
+ m_itemModelHandler, &AbstractItemModelHandler::handleMappingChanged);
+ QObject::connect(qptr(), &QItemModelSurfaceDataProxy::multiMatchBehaviorChanged,
+ m_itemModelHandler, &AbstractItemModelHandler::handleMappingChanged);
+}
+
+QT_END_NAMESPACE
diff --git a/src/graphs/data/qitemmodelsurfacedataproxy.h b/src/graphs/data/qitemmodelsurfacedataproxy.h
new file mode 100644
index 0000000..78338b8
--- /dev/null
+++ b/src/graphs/data/qitemmodelsurfacedataproxy.h
@@ -0,0 +1,170 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QITEMMODELSURFACEDATAPROXY_H
+#define QITEMMODELSURFACEDATAPROXY_H
+
+#include <QtGraphs/qsurfacedataproxy.h>
+#include <QtCore/QAbstractItemModel>
+#include <QtCore/QStringList>
+#include <QtCore/QRegularExpression>
+
+QT_BEGIN_NAMESPACE
+
+class QItemModelSurfaceDataProxyPrivate;
+
+class Q_GRAPHS_EXPORT QItemModelSurfaceDataProxy : public QSurfaceDataProxy
+{
+ Q_OBJECT
+ Q_PROPERTY(QAbstractItemModel* itemModel READ itemModel WRITE setItemModel NOTIFY itemModelChanged)
+ Q_PROPERTY(QString rowRole READ rowRole WRITE setRowRole NOTIFY rowRoleChanged)
+ Q_PROPERTY(QString columnRole READ columnRole WRITE setColumnRole NOTIFY columnRoleChanged)
+ Q_PROPERTY(QString xPosRole READ xPosRole WRITE setXPosRole NOTIFY xPosRoleChanged)
+ Q_PROPERTY(QString yPosRole READ yPosRole WRITE setYPosRole NOTIFY yPosRoleChanged)
+ Q_PROPERTY(QString zPosRole READ zPosRole WRITE setZPosRole NOTIFY zPosRoleChanged)
+ Q_PROPERTY(QStringList rowCategories READ rowCategories WRITE setRowCategories NOTIFY rowCategoriesChanged)
+ Q_PROPERTY(QStringList columnCategories READ columnCategories WRITE setColumnCategories NOTIFY columnCategoriesChanged)
+ Q_PROPERTY(bool useModelCategories READ useModelCategories WRITE setUseModelCategories NOTIFY useModelCategoriesChanged)
+ Q_PROPERTY(bool autoRowCategories READ autoRowCategories WRITE setAutoRowCategories NOTIFY autoRowCategoriesChanged)
+ Q_PROPERTY(bool autoColumnCategories READ autoColumnCategories WRITE setAutoColumnCategories NOTIFY autoColumnCategoriesChanged)
+ Q_PROPERTY(QRegularExpression rowRolePattern READ rowRolePattern WRITE setRowRolePattern NOTIFY rowRolePatternChanged)
+ Q_PROPERTY(QRegularExpression columnRolePattern READ columnRolePattern WRITE setColumnRolePattern NOTIFY columnRolePatternChanged)
+ Q_PROPERTY(QRegularExpression xPosRolePattern READ xPosRolePattern WRITE setXPosRolePattern NOTIFY xPosRolePatternChanged)
+ Q_PROPERTY(QRegularExpression yPosRolePattern READ yPosRolePattern WRITE setYPosRolePattern NOTIFY yPosRolePatternChanged)
+ Q_PROPERTY(QRegularExpression zPosRolePattern READ zPosRolePattern WRITE setZPosRolePattern NOTIFY zPosRolePatternChanged)
+ Q_PROPERTY(QString rowRoleReplace READ rowRoleReplace WRITE setRowRoleReplace NOTIFY rowRoleReplaceChanged)
+ Q_PROPERTY(QString columnRoleReplace READ columnRoleReplace WRITE setColumnRoleReplace NOTIFY columnRoleReplaceChanged)
+ Q_PROPERTY(QString xPosRoleReplace READ xPosRoleReplace WRITE setXPosRoleReplace NOTIFY xPosRoleReplaceChanged)
+ Q_PROPERTY(QString yPosRoleReplace READ yPosRoleReplace WRITE setYPosRoleReplace NOTIFY yPosRoleReplaceChanged)
+ Q_PROPERTY(QString zPosRoleReplace READ zPosRoleReplace WRITE setZPosRoleReplace NOTIFY zPosRoleReplaceChanged)
+ Q_PROPERTY(MultiMatchBehavior multiMatchBehavior READ multiMatchBehavior WRITE setMultiMatchBehavior NOTIFY multiMatchBehaviorChanged)
+
+public:
+ enum MultiMatchBehavior {
+ MMBFirst = 0,
+ MMBLast = 1,
+ MMBAverage = 2,
+ MMBCumulativeY = 3
+ };
+ Q_ENUM(MultiMatchBehavior)
+
+ explicit QItemModelSurfaceDataProxy(QObject *parent = nullptr);
+ explicit QItemModelSurfaceDataProxy(QAbstractItemModel *itemModel, QObject *parent = nullptr);
+ explicit QItemModelSurfaceDataProxy(QAbstractItemModel *itemModel, const QString &yPosRole,
+ QObject *parent = nullptr);
+ explicit QItemModelSurfaceDataProxy(QAbstractItemModel *itemModel, const QString &rowRole,
+ const QString &columnRole, const QString &yPosRole,
+ QObject *parent = nullptr);
+ explicit QItemModelSurfaceDataProxy(QAbstractItemModel *itemModel, const QString &rowRole,
+ const QString &columnRole, const QString &xPosRole,
+ const QString &yPosRole, const QString &zPosRole,
+ QObject *parent = nullptr);
+ explicit QItemModelSurfaceDataProxy(QAbstractItemModel *itemModel, const QString &rowRole,
+ const QString &columnRole, const QString &yPosRole,
+ const QStringList &rowCategories,
+ const QStringList &columnCategories,
+ QObject *parent = nullptr);
+ explicit QItemModelSurfaceDataProxy(QAbstractItemModel *itemModel, const QString &rowRole,
+ const QString &columnRole, const QString &xPosRole,
+ const QString &yPosRole, const QString &zPosRole,
+ const QStringList &rowCategories,
+ const QStringList &columnCategories,
+ QObject *parent = nullptr);
+ virtual ~QItemModelSurfaceDataProxy();
+
+ void setItemModel(QAbstractItemModel *itemModel);
+ QAbstractItemModel *itemModel() const;
+
+ void setRowRole(const QString &role);
+ QString rowRole() const;
+ void setColumnRole(const QString &role);
+ QString columnRole() const;
+ void setXPosRole(const QString &role);
+ QString xPosRole() const;
+ void setYPosRole(const QString &role);
+ QString yPosRole() const;
+ void setZPosRole(const QString &role);
+ QString zPosRole() const;
+
+ void setRowCategories(const QStringList &categories);
+ QStringList rowCategories() const;
+ void setColumnCategories(const QStringList &categories);
+ QStringList columnCategories() const;
+
+ void setUseModelCategories(bool enable);
+ bool useModelCategories() const;
+ void setAutoRowCategories(bool enable);
+ bool autoRowCategories() const;
+ void setAutoColumnCategories(bool enable);
+ bool autoColumnCategories() const;
+
+ void remap(const QString &rowRole, const QString &columnRole,
+ const QString &xPosRole, const QString &yPosRole,
+ const QString &zPosRole, const QStringList &rowCategories,
+ const QStringList &columnCategories);
+
+ Q_INVOKABLE int rowCategoryIndex(const QString& category);
+ Q_INVOKABLE int columnCategoryIndex(const QString& category);
+
+ void setRowRolePattern(const QRegularExpression &pattern);
+ QRegularExpression rowRolePattern() const;
+ void setColumnRolePattern(const QRegularExpression &pattern);
+ QRegularExpression columnRolePattern() const;
+ void setXPosRolePattern(const QRegularExpression &pattern);
+ QRegularExpression xPosRolePattern() const;
+ void setYPosRolePattern(const QRegularExpression &pattern);
+ QRegularExpression yPosRolePattern() const;
+ void setZPosRolePattern(const QRegularExpression &pattern);
+ QRegularExpression zPosRolePattern() const;
+
+ void setRowRoleReplace(const QString &replace);
+ QString rowRoleReplace() const;
+ void setColumnRoleReplace(const QString &replace);
+ QString columnRoleReplace() const;
+ void setXPosRoleReplace(const QString &replace);
+ QString xPosRoleReplace() const;
+ void setYPosRoleReplace(const QString &replace);
+ QString yPosRoleReplace() const;
+ void setZPosRoleReplace(const QString &replace);
+ QString zPosRoleReplace() const;
+
+ void setMultiMatchBehavior(MultiMatchBehavior behavior);
+ MultiMatchBehavior multiMatchBehavior() const;
+
+Q_SIGNALS:
+ void itemModelChanged(const QAbstractItemModel* itemModel);
+ void rowRoleChanged(const QString &role);
+ void columnRoleChanged(const QString &role);
+ void xPosRoleChanged(const QString &role);
+ void yPosRoleChanged(const QString &role);
+ void zPosRoleChanged(const QString &role);
+ void rowCategoriesChanged();
+ void columnCategoriesChanged();
+ void useModelCategoriesChanged(bool enable);
+ void autoRowCategoriesChanged(bool enable);
+ void autoColumnCategoriesChanged(bool enable);
+ void rowRolePatternChanged(const QRegularExpression &pattern);
+ void columnRolePatternChanged(const QRegularExpression &pattern);
+ void xPosRolePatternChanged(const QRegularExpression &pattern);
+ void yPosRolePatternChanged(const QRegularExpression &pattern);
+ void zPosRolePatternChanged(const QRegularExpression &pattern);
+ void rowRoleReplaceChanged(const QString &replace);
+ void columnRoleReplaceChanged(const QString &replace);
+ void xPosRoleReplaceChanged(const QString &replace);
+ void yPosRoleReplaceChanged(const QString &replace);
+ void zPosRoleReplaceChanged(const QString &replace);
+ void multiMatchBehaviorChanged(QItemModelSurfaceDataProxy::MultiMatchBehavior behavior);
+
+protected:
+ QItemModelSurfaceDataProxyPrivate *dptr();
+ const QItemModelSurfaceDataProxyPrivate *dptrc() const;
+
+private:
+ Q_DISABLE_COPY(QItemModelSurfaceDataProxy)
+
+ friend class SurfaceItemModelHandler;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/data/qitemmodelsurfacedataproxy_p.h b/src/graphs/data/qitemmodelsurfacedataproxy_p.h
new file mode 100644
index 0000000..5f4dda4
--- /dev/null
+++ b/src/graphs/data/qitemmodelsurfacedataproxy_p.h
@@ -0,0 +1,72 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QtGraphs API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#ifndef QITEMMODELSURFACEDATAPROXY_P_H
+#define QITEMMODELSURFACEDATAPROXY_P_H
+
+#include "qitemmodelsurfacedataproxy.h"
+#include "qsurfacedataproxy_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class SurfaceItemModelHandler;
+
+class QItemModelSurfaceDataProxyPrivate : public QSurfaceDataProxyPrivate
+{
+ Q_OBJECT
+public:
+ QItemModelSurfaceDataProxyPrivate(QItemModelSurfaceDataProxy *q);
+ virtual ~QItemModelSurfaceDataProxyPrivate();
+
+ void connectItemModelHandler();
+
+private:
+ QItemModelSurfaceDataProxy *qptr();
+
+ SurfaceItemModelHandler *m_itemModelHandler;
+
+ QString m_rowRole;
+ QString m_columnRole;
+ QString m_xPosRole;
+ QString m_yPosRole;
+ QString m_zPosRole;
+
+ // For row/column items, sort items into these categories. Other categories are ignored.
+ QStringList m_rowCategories;
+ QStringList m_columnCategories;
+
+ bool m_useModelCategories;
+ bool m_autoRowCategories;
+ bool m_autoColumnCategories;
+
+ QRegularExpression m_rowRolePattern;
+ QRegularExpression m_columnRolePattern;
+ QRegularExpression m_xPosRolePattern;
+ QRegularExpression m_yPosRolePattern;
+ QRegularExpression m_zPosRolePattern;
+
+ QString m_rowRoleReplace;
+ QString m_columnRoleReplace;
+ QString m_xPosRoleReplace;
+ QString m_yPosRoleReplace;
+ QString m_zPosRoleReplace;
+
+ QItemModelSurfaceDataProxy::MultiMatchBehavior m_multiMatchBehavior;
+
+ friend class SurfaceItemModelHandler;
+ friend class QItemModelSurfaceDataProxy;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/data/qscatter3dseries.cpp b/src/graphs/data/qscatter3dseries.cpp
new file mode 100644
index 0000000..7fa2589
--- /dev/null
+++ b/src/graphs/data/qscatter3dseries.cpp
@@ -0,0 +1,369 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qscatter3dseries_p.h"
+#include "scatter3dcontroller_p.h"
+#include "qvalue3daxis.h"
+#include "qcategory3daxis.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ * \class QScatter3DSeries
+ * \inmodule QtGraphs
+ * \brief The QScatter3DSeries class represents a data series in a 3D scatter
+ * graph.
+ *
+ * This class manages the series specific visual elements, as well as the series
+ * data (via a data proxy).
+ *
+ * If no data proxy is set explicitly for the series, the series creates a default
+ * proxy. Setting another proxy will destroy the existing proxy and all data added to it.
+ *
+ * QScatter3DSeries supports the following format tags for QAbstract3DSeries::setItemLabelFormat():
+ * \table
+ * \row
+ * \li @xTitle \li Title from x-axis
+ * \row
+ * \li @yTitle \li Title from y-axis
+ * \row
+ * \li @zTitle \li Title from z-axis
+ * \row
+ * \li @xLabel \li Item value formatted using the format of the x-axis.
+ * For more information, see
+ * \l{QValue3DAxis::setLabelFormat()}.
+ * \row
+ * \li @yLabel \li Item value formatted using the format of the y-axis.
+ * For more information, see
+ * \l{QValue3DAxis::setLabelFormat()}.
+ * \row
+ * \li @zLabel \li Item value formatted using the format of the z-axis.
+ * For more information, see
+ * \l{QValue3DAxis::setLabelFormat()}.
+ * \row
+ * \li @seriesName \li Name of the series
+ * \endtable
+ *
+ * For example:
+ * \snippet doc_src_qtgraphs.cpp 1
+ *
+ * \sa {Qt Graphs Data Handling}
+ */
+
+/*!
+ * \qmltype Scatter3DSeries
+ * \inqmlmodule QtGraphs
+ * \ingroup graphs_qml
+ * \instantiates QScatter3DSeries
+ * \inherits Abstract3DSeries
+ * \brief Represents a data series in a 3D scatter graph.
+ *
+ * This type manages the series specific visual elements, as well as the series
+ * data (via a data proxy).
+ *
+ * For a more complete description, see QScatter3DSeries.
+ *
+ * \sa {Qt Graphs Data Handling}
+ */
+
+/*!
+ * \qmlproperty ScatterDataProxy Scatter3DSeries::dataProxy
+ *
+ * Sets the active data proxy. The series assumes ownership of any proxy set to
+ * it and deletes any previously set proxy when a new one is added. The proxy
+ * cannot be null or set to another series.
+ */
+
+/*!
+ * \qmlproperty int Scatter3DSeries::selectedItem
+ *
+ * The item that is selected at the index in the data array of the series.
+ * Only one item can be selected at a time.
+ * To clear selection from this series, invalidSelectionIndex is set as the index.
+ * If this series is added to a graph, the graph can adjust the selection according to user
+ * interaction or if it becomes invalid. Selecting an item on another added series will also
+ * clear the selection.
+ * Removing items from or inserting items to the series before the selected item
+ * will adjust the selection so that the same item will stay selected.
+ *
+ * \sa AbstractGraph3D::clearSelection()
+ */
+
+/*!
+ * \qmlproperty float Scatter3DSeries::itemSize
+ *
+ * Sets the item size for the series. The size must be between \c 0.0 and
+ * \c 1.0. Setting the size to \c 0.0 causes the item size to be automatically
+ * scaled based on the total number of items in all the series for the graph.
+ * The preset default is \c 0.0.
+ */
+
+/*!
+ * \qmlproperty int Scatter3DSeries::invalidSelectionIndex
+ * A constant property providing an invalid index for selection. This index is
+ * set to the selectedItem property to clear the selection from this series.
+ *
+ * \sa AbstractGraph3D::clearSelection()
+ */
+
+/*!
+ * Constructs a scatter 3D series with the parent \a parent.
+ */
+QScatter3DSeries::QScatter3DSeries(QObject *parent) :
+ QAbstract3DSeries(new QScatter3DSeriesPrivate(this), parent)
+{
+ // Default proxy
+ dptr()->setDataProxy(new QScatterDataProxy);
+}
+
+/*!
+ * Constructs a scatter 3D series with the data proxy \a dataProxy and the
+ * parent \a parent.
+ */
+QScatter3DSeries::QScatter3DSeries(QScatterDataProxy *dataProxy, QObject *parent) :
+ QAbstract3DSeries(new QScatter3DSeriesPrivate(this), parent)
+{
+ dptr()->setDataProxy(dataProxy);
+}
+
+/*!
+ * \internal
+ */
+QScatter3DSeries::QScatter3DSeries(QScatter3DSeriesPrivate *d, QObject *parent) :
+ QAbstract3DSeries(d, parent)
+{
+}
+
+/*!
+ * Deletes the scatter 3D series.
+ */
+QScatter3DSeries::~QScatter3DSeries()
+{
+}
+
+/*!
+ * \property QScatter3DSeries::dataProxy
+ *
+ * \brief The active data proxy.
+ */
+
+/*!
+ * Sets the active data proxy for the series to \a proxy. The series assumes
+ * ownership of any proxy set to it and deletes any previously set proxy when
+ * a new one is added. The \a proxy argument cannot be null or set to another
+ * series.
+ */
+void QScatter3DSeries::setDataProxy(QScatterDataProxy *proxy)
+{
+ d_ptr->setDataProxy(proxy);
+}
+
+QScatterDataProxy *QScatter3DSeries::dataProxy() const
+{
+ return static_cast<QScatterDataProxy *>(d_ptr->dataProxy());
+}
+
+/*!
+ * \property QScatter3DSeries::selectedItem
+ *
+ * \brief The item that is selected in the series.
+ */
+
+/*!
+ * Selects the item at the index \a index in the data array of the series.
+ * Only one item can be selected at a time.
+ *
+ * To clear selection from this series, invalidSelectionIndex() is set as \a index.
+ * If this series is added to a graph, the graph can adjust the selection according to user
+ * interaction or if it becomes invalid. Selecting an item on another added series will also
+ * clear the selection.
+ *
+ * Removing items from or inserting items to the series before the selected item
+ * will adjust the selection so that the same item will stay selected.
+ *
+ * \sa QAbstract3DGraph::clearSelection()
+ */
+void QScatter3DSeries::setSelectedItem(int index)
+{
+ // Don't do this in private to avoid loops, as that is used for callback from controller.
+ if (d_ptr->m_controller)
+ static_cast<Scatter3DController *>(d_ptr->m_controller)->setSelectedItem(index, this);
+ else
+ dptr()->setSelectedItem(index);
+}
+
+int QScatter3DSeries::selectedItem() const
+{
+ return dptrc()->m_selectedItem;
+}
+
+/*!
+ * \property QScatter3DSeries::itemSize
+ *
+ * \brief Item size for the series.
+ *
+ * The size must be between \c 0.0f and \c 1.0f. Setting the size to \c 0.0f
+ * causes the item size to be automatically scaled based on the total number of
+ * items in all the series for the graph.
+ *
+ * The preset default is \c 0.0f.
+ */
+void QScatter3DSeries::setItemSize(float size)
+{
+ if (size < 0.0f || size > 1.0f) {
+ qWarning("Invalid size. Valid range for itemSize is 0.0f...1.0f");
+ } else if (size != dptr()->m_itemSize) {
+ dptr()->setItemSize(size);
+ emit itemSizeChanged(size);
+ }
+}
+
+float QScatter3DSeries::itemSize() const
+{
+ return dptrc()->m_itemSize;
+}
+
+/*!
+ * Returns an invalid index for selection. This index is set to the selectedItem
+ * property to clear the selection from this series.
+ *
+ * \sa QAbstract3DGraph::clearSelection()
+ */
+int QScatter3DSeries::invalidSelectionIndex()
+{
+ return Scatter3DController::invalidSelectionIndex();
+}
+
+/*!
+ * \internal
+ */
+QScatter3DSeriesPrivate *QScatter3DSeries::dptr()
+{
+ return static_cast<QScatter3DSeriesPrivate *>(d_ptr.data());
+}
+
+/*!
+ * \internal
+ */
+const QScatter3DSeriesPrivate *QScatter3DSeries::dptrc() const
+{
+ return static_cast<const QScatter3DSeriesPrivate *>(d_ptr.data());
+}
+
+// QScatter3DSeriesPrivate
+
+QScatter3DSeriesPrivate::QScatter3DSeriesPrivate(QScatter3DSeries *q)
+ : QAbstract3DSeriesPrivate(q, QAbstract3DSeries::SeriesTypeScatter),
+ m_selectedItem(Scatter3DController::invalidSelectionIndex()),
+ m_itemSize(0.0f)
+{
+ m_itemLabelFormat = QStringLiteral("@xLabel, @yLabel, @zLabel");
+ m_mesh = QAbstract3DSeries::MeshSphere;
+}
+
+QScatter3DSeriesPrivate::~QScatter3DSeriesPrivate()
+{
+}
+
+QScatter3DSeries *QScatter3DSeriesPrivate::qptr()
+{
+ return static_cast<QScatter3DSeries *>(q_ptr);
+}
+
+void QScatter3DSeriesPrivate::setDataProxy(QAbstractDataProxy *proxy)
+{
+ Q_ASSERT(proxy->type() == QAbstractDataProxy::DataTypeScatter);
+
+ QAbstract3DSeriesPrivate::setDataProxy(proxy);
+
+ emit qptr()->dataProxyChanged(static_cast<QScatterDataProxy *>(proxy));
+}
+
+void QScatter3DSeriesPrivate::connectControllerAndProxy(Abstract3DController *newController)
+{
+ QScatterDataProxy *scatterDataProxy = static_cast<QScatterDataProxy *>(m_dataProxy);
+
+ if (m_controller && scatterDataProxy) {
+ //Disconnect old controller/old proxy
+ QObject::disconnect(scatterDataProxy, 0, m_controller, 0);
+ QObject::disconnect(q_ptr, 0, m_controller, 0);
+ }
+
+ if (newController && scatterDataProxy) {
+ Scatter3DController *controller = static_cast<Scatter3DController *>(newController);
+ QObject::connect(scatterDataProxy, &QScatterDataProxy::arrayReset,
+ controller, &Scatter3DController::handleArrayReset);
+ QObject::connect(scatterDataProxy, &QScatterDataProxy::itemsAdded,
+ controller, &Scatter3DController::handleItemsAdded);
+ QObject::connect(scatterDataProxy, &QScatterDataProxy::itemsChanged,
+ controller, &Scatter3DController::handleItemsChanged);
+ QObject::connect(scatterDataProxy, &QScatterDataProxy::itemsRemoved,
+ controller, &Scatter3DController::handleItemsRemoved);
+ QObject::connect(scatterDataProxy, &QScatterDataProxy::itemsInserted,
+ controller, &Scatter3DController::handleItemsInserted);
+ QObject::connect(qptr(), &QScatter3DSeries::dataProxyChanged,
+ controller, &Scatter3DController::handleArrayReset);
+ }
+}
+
+void QScatter3DSeriesPrivate::createItemLabel()
+{
+ static const QString xTitleTag(QStringLiteral("@xTitle"));
+ static const QString yTitleTag(QStringLiteral("@yTitle"));
+ static const QString zTitleTag(QStringLiteral("@zTitle"));
+ static const QString xLabelTag(QStringLiteral("@xLabel"));
+ static const QString yLabelTag(QStringLiteral("@yLabel"));
+ static const QString zLabelTag(QStringLiteral("@zLabel"));
+ static const QString seriesNameTag(QStringLiteral("@seriesName"));
+
+ if (m_selectedItem == QScatter3DSeries::invalidSelectionIndex()) {
+ m_itemLabel = QString();
+ return;
+ }
+
+ QValue3DAxis *axisX = static_cast<QValue3DAxis *>(m_controller->axisX());
+ QValue3DAxis *axisY = static_cast<QValue3DAxis *>(m_controller->axisY());
+ QValue3DAxis *axisZ = static_cast<QValue3DAxis *>(m_controller->axisZ());
+ QVector3D selectedPosition = qptr()->dataProxy()->itemAt(m_selectedItem)->position();
+
+ m_itemLabel = m_itemLabelFormat;
+
+ m_itemLabel.replace(xTitleTag, axisX->title());
+ m_itemLabel.replace(yTitleTag, axisY->title());
+ m_itemLabel.replace(zTitleTag, axisZ->title());
+
+ if (m_itemLabel.contains(xLabelTag)) {
+ QString valueLabelText = axisX->formatter()->stringForValue(
+ qreal(selectedPosition.x()), axisX->labelFormat());
+ m_itemLabel.replace(xLabelTag, valueLabelText);
+ }
+ if (m_itemLabel.contains(yLabelTag)) {
+ QString valueLabelText = axisY->formatter()->stringForValue(
+ qreal(selectedPosition.y()), axisY->labelFormat());
+ m_itemLabel.replace(yLabelTag, valueLabelText);
+ }
+ if (m_itemLabel.contains(zLabelTag)) {
+ QString valueLabelText = axisZ->formatter()->stringForValue(
+ qreal(selectedPosition.z()), axisZ->labelFormat());
+ m_itemLabel.replace(zLabelTag, valueLabelText);
+ }
+ m_itemLabel.replace(seriesNameTag, m_name);
+}
+
+void QScatter3DSeriesPrivate::setSelectedItem(int index)
+{
+ if (index != m_selectedItem) {
+ markItemLabelDirty();
+ m_selectedItem = index;
+ emit qptr()->selectedItemChanged(m_selectedItem);
+ }
+}
+
+void QScatter3DSeriesPrivate::setItemSize(float size)
+{
+ m_itemSize = size;
+ if (m_controller)
+ m_controller->markSeriesVisualsDirty();
+}
+
+QT_END_NAMESPACE
diff --git a/src/graphs/data/qscatter3dseries.h b/src/graphs/data/qscatter3dseries.h
new file mode 100644
index 0000000..b935d89
--- /dev/null
+++ b/src/graphs/data/qscatter3dseries.h
@@ -0,0 +1,55 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QSCATTER3DSERIES_H
+#define QSCATTER3DSERIES_H
+
+#include <QtGraphs/qabstract3dseries.h>
+#include <QtGraphs/qscatterdataproxy.h>
+
+QT_BEGIN_NAMESPACE
+
+class QScatter3DSeriesPrivate;
+
+class Q_GRAPHS_EXPORT QScatter3DSeries : public QAbstract3DSeries
+{
+ Q_OBJECT
+ Q_PROPERTY(QScatterDataProxy *dataProxy READ dataProxy WRITE setDataProxy NOTIFY dataProxyChanged)
+ Q_PROPERTY(int selectedItem READ selectedItem WRITE setSelectedItem NOTIFY selectedItemChanged)
+ Q_PROPERTY(float itemSize READ itemSize WRITE setItemSize NOTIFY itemSizeChanged)
+
+public:
+ explicit QScatter3DSeries(QObject *parent = nullptr);
+ explicit QScatter3DSeries(QScatterDataProxy *dataProxy, QObject *parent = nullptr);
+ virtual ~QScatter3DSeries();
+
+ void setDataProxy(QScatterDataProxy *proxy);
+ QScatterDataProxy *dataProxy() const;
+
+ void setSelectedItem(int index);
+ int selectedItem() const;
+ static int invalidSelectionIndex();
+
+ void setItemSize(float size);
+ float itemSize() const;
+
+Q_SIGNALS:
+ void dataProxyChanged(QScatterDataProxy *proxy);
+ void selectedItemChanged(int index);
+ void itemSizeChanged(float size);
+
+protected:
+ explicit QScatter3DSeries(QScatter3DSeriesPrivate *d, QObject *parent = nullptr);
+ QScatter3DSeriesPrivate *dptr();
+ const QScatter3DSeriesPrivate *dptrc() const;
+
+private:
+ Q_DISABLE_COPY(QScatter3DSeries)
+
+ friend class Scatter3DController;
+ friend class QQuickGraphsScatter;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/data/qscatter3dseries_p.h b/src/graphs/data/qscatter3dseries_p.h
new file mode 100644
index 0000000..afaaa74
--- /dev/null
+++ b/src/graphs/data/qscatter3dseries_p.h
@@ -0,0 +1,47 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QtGraphs API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#ifndef QSCATTER3DSERIES_P_H
+#define QSCATTER3DSERIES_P_H
+
+#include "qscatter3dseries.h"
+#include "qabstract3dseries_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class QScatter3DSeriesPrivate : public QAbstract3DSeriesPrivate
+{
+ Q_OBJECT
+public:
+ QScatter3DSeriesPrivate(QScatter3DSeries *q);
+ virtual ~QScatter3DSeriesPrivate();
+
+ void setDataProxy(QAbstractDataProxy *proxy) override;
+ void connectControllerAndProxy(Abstract3DController *newController) override;
+ void createItemLabel() override;
+
+ void setSelectedItem(int index);
+ void setItemSize(float size);
+
+private:
+ QScatter3DSeries *qptr();
+ int m_selectedItem;
+ float m_itemSize;
+
+private:
+ friend class QScatter3DSeries;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/data/qscatterdataitem.cpp b/src/graphs/data/qscatterdataitem.cpp
new file mode 100644
index 0000000..cfe5ebd
--- /dev/null
+++ b/src/graphs/data/qscatterdataitem.cpp
@@ -0,0 +1,152 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qscatterdataitem_p.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ * \class QScatterDataItem
+ * \inmodule QtGraphs
+ * \brief The QScatterDataItem class provides a container for resolved data to be added to scatter
+ * graphs.
+ *
+ * A scatter data item holds the data for a single rendered item in a scatter
+ * graph. Scatter data proxies parse data into QScatterDataItem instances for
+ * scatter graphs.
+ *
+ * \sa QScatterDataProxy, {Qt Graphs C++ Classes}
+ */
+
+/*!
+ * Constructs a scatter data item.
+ */
+QScatterDataItem::QScatterDataItem()
+ : d_ptr(0) // private data doesn't exist by default (optimization)
+
+{
+}
+
+/*!
+ * Constructs a scatter data item at the position \a position.
+ */
+QScatterDataItem::QScatterDataItem(const QVector3D &position)
+ : d_ptr(0),
+ m_position(position)
+{
+}
+
+/*!
+ * Constructs a scatter data item at the position \a position with the rotation
+ * \a rotation.
+ */
+QScatterDataItem::QScatterDataItem(const QVector3D &position, const QQuaternion &rotation)
+ : d_ptr(0),
+ m_position(position),
+ m_rotation(rotation)
+{
+}
+
+/*!
+ * Constructs a copy of \a other.
+ */
+QScatterDataItem::QScatterDataItem(const QScatterDataItem &other)
+{
+ operator=(other);
+}
+
+/*!
+ * Deletes a scatter data item.
+ */
+QScatterDataItem::~QScatterDataItem()
+{
+}
+
+/*!
+ * Assigns a copy of \a other to this object.
+ */
+QScatterDataItem &QScatterDataItem::operator=(const QScatterDataItem &other)
+{
+ m_position = other.m_position;
+ m_rotation = other.m_rotation;
+
+ if (other.d_ptr)
+ createExtraData();
+ else
+ d_ptr = 0;
+
+ return *this;
+}
+
+/*!
+ * \fn void QScatterDataItem::setPosition(const QVector3D &pos)
+ * Sets the position \a pos for this data item.
+ */
+
+/*!
+ * \fn QVector3D QScatterDataItem::position() const
+ * Returns the position of this data item.
+ */
+
+/*!
+ * \fn void QScatterDataItem::setRotation(const QQuaternion &rot)
+ * Sets the rotation \a rot for this data item.
+ * The value of \a rot should be a normalized QQuaternion.
+ * If the series also has rotation, item rotation is multiplied by it.
+ * Defaults to no rotation.
+ */
+
+/*!
+ * \fn QQuaternion QScatterDataItem::rotation() const
+ * Returns the rotation of this data item.
+ * \sa setRotation()
+ */
+
+/*!
+ * \fn void QScatterDataItem::setX(float value)
+ * Sets the x-coordinate of the item position to the value \a value.
+ */
+
+/*!
+ * \fn void QScatterDataItem::setY(float value)
+ * Sets the y-coordinate of the item position to the value \a value.
+ */
+
+/*!
+ * \fn void QScatterDataItem::setZ(float value)
+ * Sets the z-coordinate of the item position to the value \a value.
+ */
+
+/*!
+ * \fn float QScatterDataItem::x() const
+ * Returns the x-coordinate of the position of this data item.
+ */
+
+/*!
+ * \fn float QScatterDataItem::y() const
+ * Returns the y-coordinate of the position of this data item.
+ */
+
+/*!
+ * \fn float QScatterDataItem::z() const
+ * Returns the z-coordinate of the position of this data item.
+ */
+
+/*!
+ * \internal
+ */
+void QScatterDataItem::createExtraData()
+{
+ if (!d_ptr)
+ d_ptr = new QScatterDataItemPrivate;
+}
+
+QScatterDataItemPrivate::QScatterDataItemPrivate()
+{
+}
+
+QScatterDataItemPrivate::~QScatterDataItemPrivate()
+{
+}
+
+QT_END_NAMESPACE
diff --git a/src/graphs/data/qscatterdataitem.h b/src/graphs/data/qscatterdataitem.h
new file mode 100644
index 0000000..ed72283
--- /dev/null
+++ b/src/graphs/data/qscatterdataitem.h
@@ -0,0 +1,48 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QSCATTERDATAITEM_H
+#define QSCATTERDATAITEM_H
+
+#include <QtGraphs/qgraphsglobal.h>
+#include <QtGui/QQuaternion>
+
+QT_BEGIN_NAMESPACE
+
+class QScatterDataItemPrivate;
+
+class Q_GRAPHS_EXPORT QScatterDataItem
+{
+public:
+ QScatterDataItem();
+ QScatterDataItem(const QVector3D &position);
+ QScatterDataItem(const QVector3D &position, const QQuaternion &rotation);
+ QScatterDataItem(const QScatterDataItem &other);
+ ~QScatterDataItem();
+
+ QScatterDataItem &operator=(const QScatterDataItem &other);
+
+ inline void setPosition(const QVector3D &pos) { m_position = pos; }
+ inline QVector3D position() const { return m_position; }
+ inline void setRotation(const QQuaternion &rot) { m_rotation = rot; }
+ inline QQuaternion rotation() const { return m_rotation; }
+ inline void setX(float value) { m_position.setX(value); }
+ inline void setY(float value) { m_position.setY(value); }
+ inline void setZ(float value) { m_position.setZ(value); }
+ inline float x() const { return m_position.x(); }
+ inline float y() const { return m_position.y(); }
+ inline float z() const { return m_position.z(); }
+
+protected:
+ void createExtraData();
+
+ QScatterDataItemPrivate *d_ptr;
+
+private:
+ QVector3D m_position;
+ QQuaternion m_rotation;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/data/qscatterdataitem_p.h b/src/graphs/data/qscatterdataitem_p.h
new file mode 100644
index 0000000..bc38ac1
--- /dev/null
+++ b/src/graphs/data/qscatterdataitem_p.h
@@ -0,0 +1,31 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QtGraphs API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#ifndef QSCATTERDATAITEM_P_H
+#define QSCATTERDATAITEM_P_H
+
+#include "graphsglobal_p.h"
+#include "qscatterdataitem.h"
+
+QT_BEGIN_NAMESPACE
+
+class QScatterDataItemPrivate
+{
+public:
+ QScatterDataItemPrivate();
+ virtual ~QScatterDataItemPrivate();
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/data/qscatterdataproxy.cpp b/src/graphs/data/qscatterdataproxy.cpp
new file mode 100644
index 0000000..46fa65c
--- /dev/null
+++ b/src/graphs/data/qscatterdataproxy.cpp
@@ -0,0 +1,426 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qscatterdataproxy_p.h"
+#include "qscatter3dseries_p.h"
+#include "qabstract3daxis_p.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ * \class QScatterDataProxy
+ * \inmodule QtGraphs
+ * \brief The QScatterDataProxy class is the data proxy for 3D scatter graphs.
+ *
+ * A scatter data proxy handles adding, inserting, changing, and removing data
+ * items.
+ *
+ * QScatterDataProxy takes ownership of all
+ * QtGraphs::QScatterDataArray and QScatterDataItem objects passed to
+ * it.
+ *
+ * \sa {Qt Graphs Data Handling}
+ */
+
+/*!
+ * \typedef QScatterDataArray
+ * \relates QScatterDataProxy
+ *
+ * A list of \l {QScatterDataItem} objects.
+ */
+
+/*!
+ * \qmltype ScatterDataProxy
+ * \inqmlmodule QtGraphs
+ * \ingroup graphs_qml
+ * \instantiates QScatterDataProxy
+ * \inherits AbstractDataProxy
+ * \brief The data proxy for 3D scatter graphs.
+ *
+ * This type handles adding, inserting, changing, and removing data items.
+ *
+ * This type is uncreatable, but contains properties that are exposed via subtypes.
+ *
+ * \sa ItemModelScatterDataProxy, {Qt Graphs Data Handling}
+ */
+
+/*!
+ * \qmlproperty int ScatterDataProxy::itemCount
+ * The number of items in the array.
+ */
+
+/*!
+ * \qmlproperty Scatter3DSeries ScatterDataProxy::series
+ *
+ * The series this proxy is attached to.
+ */
+
+/*!
+ * Constructs QScatterDataProxy with the given \a parent.
+ */
+QScatterDataProxy::QScatterDataProxy(QObject *parent) :
+ QAbstractDataProxy(new QScatterDataProxyPrivate(this), parent)
+{
+}
+
+/*!
+ * \internal
+ */
+QScatterDataProxy::QScatterDataProxy(QScatterDataProxyPrivate *d, QObject *parent) :
+ QAbstractDataProxy(d, parent)
+{
+}
+
+/*!
+ * Deletes the scatter data proxy.
+ */
+QScatterDataProxy::~QScatterDataProxy()
+{
+}
+
+/*!
+ * \property QScatterDataProxy::series
+ *
+ * \brief The series this proxy is attached to.
+ */
+QScatter3DSeries *QScatterDataProxy::series() const
+{
+ return static_cast<QScatter3DSeries *>(d_ptr->series());
+}
+
+/*!
+ * Takes ownership of the array \a newArray. Clears the existing array if the
+ * new array differs from it. If the arrays are the same, this function
+ * just triggers the arrayReset() signal.
+ *
+ * Passing a null array deletes the old array and creates a new empty array.
+ */
+void QScatterDataProxy::resetArray(QScatterDataArray *newArray)
+{
+ if (dptr()->m_dataArray != newArray)
+ dptr()->resetArray(newArray);
+
+ emit arrayReset();
+ emit itemCountChanged(itemCount());
+}
+
+/*!
+ * Replaces the item at the position \a index with the item \a item.
+ */
+void QScatterDataProxy::setItem(int index, const QScatterDataItem &item)
+{
+ dptr()->setItem(index, item);
+ emit itemsChanged(index, 1);
+}
+
+/*!
+ * Replaces the items starting from the position \a index with the items
+ * specified by \a items.
+ */
+void QScatterDataProxy::setItems(int index, const QScatterDataArray &items)
+{
+ dptr()->setItems(index, items);
+ emit itemsChanged(index, items.size());
+}
+
+/*!
+ * Adds the item \a item to the end of the array.
+ *
+ * Returns the index of the added item.
+ */
+int QScatterDataProxy::addItem(const QScatterDataItem &item)
+{
+ int addIndex = dptr()->addItem(item);
+ emit itemsAdded(addIndex, 1);
+ emit itemCountChanged(itemCount());
+ return addIndex;
+}
+
+/*!
+ * Adds the items specified by \a items to the end of the array.
+ *
+ * Returns the index of the first added item.
+ */
+int QScatterDataProxy::addItems(const QScatterDataArray &items)
+{
+ int addIndex = dptr()->addItems(items);
+ emit itemsAdded(addIndex, items.size());
+ emit itemCountChanged(itemCount());
+ return addIndex;
+}
+
+/*!
+ * Inserts the item \a item to the position \a index. If the index is equal to
+ * the data array size, the item is added to the array.
+ */
+void QScatterDataProxy::insertItem(int index, const QScatterDataItem &item)
+{
+ dptr()->insertItem(index, item);
+ emit itemsInserted(index, 1);
+ emit itemCountChanged(itemCount());
+}
+
+/*!
+ * Inserts the items specified by \a items to the position \a index. If the
+ * index is equal to data array size, the items are added to the array.
+ */
+void QScatterDataProxy::insertItems(int index, const QScatterDataArray &items)
+{
+ dptr()->insertItems(index, items);
+ emit itemsInserted(index, items.size());
+ emit itemCountChanged(itemCount());
+}
+
+/*!
+ * Removes the number of items specified by \a removeCount starting at the
+ * position \a index. Attempting to remove items past the end of
+ * the array does nothing.
+ */
+void QScatterDataProxy::removeItems(int index, int removeCount)
+{
+ if (index >= dptr()->m_dataArray->size())
+ return;
+
+ dptr()->removeItems(index, removeCount);
+ emit itemsRemoved(index, removeCount);
+ emit itemCountChanged(itemCount());
+}
+
+/*!
+ * \property QScatterDataProxy::itemCount
+ *
+ * \brief The number of items in the array.
+ */
+int QScatterDataProxy::itemCount() const
+{
+ return dptrc()->m_dataArray->size();
+}
+
+/*!
+ * Returns the pointer to the data array.
+ */
+const QScatterDataArray *QScatterDataProxy::array() const
+{
+ return dptrc()->m_dataArray;
+}
+
+/*!
+ * Returns the pointer to the item at the index \a index. It is guaranteed to be
+ * valid only until the next call that modifies data.
+ */
+const QScatterDataItem *QScatterDataProxy::itemAt(int index) const
+{
+ return &dptrc()->m_dataArray->at(index);
+}
+
+/*!
+ * \internal
+ */
+QScatterDataProxyPrivate *QScatterDataProxy::dptr()
+{
+ return static_cast<QScatterDataProxyPrivate *>(d_ptr.data());
+}
+
+/*!
+ * \internal
+ */
+const QScatterDataProxyPrivate *QScatterDataProxy::dptrc() const
+{
+ return static_cast<const QScatterDataProxyPrivate *>(d_ptr.data());
+}
+
+/*!
+ * \fn void QScatterDataProxy::arrayReset()
+ *
+ * This signal is emitted when the data array is reset.
+ * If the contents of the whole array are changed without calling resetArray(),
+ * this signal needs to be emitted to update the graph.
+ */
+
+/*!
+ * \fn void QScatterDataProxy::itemsAdded(int startIndex, int count)
+ *
+ * This signal is emitted when the number of items specified by \a count is
+ * added starting at the position \a startIndex.
+ * If items are added to the array without calling addItem() or addItems(),
+ * this signal needs to be emitted to update the graph.
+ */
+
+/*!
+ * \fn void QScatterDataProxy::itemsChanged(int startIndex, int count)
+ *
+ * This signal is emitted when the number of items specified by \a count is
+ * changed starting at the position \a startIndex.
+ * If items are changed in the array without calling setItem() or setItems(),
+ * this signal needs to be emitted to update the graph.
+ */
+
+/*!
+ * \fn void QScatterDataProxy::itemsRemoved(int startIndex, int count)
+ *
+ * This signal is emitted when the number of rows specified by \a count is
+ * removed starting at the position \a startIndex.
+ * The index may be larger than the current array size if items are removed from
+ * the end. If items are removed from the array without calling removeItems(),
+ * this signal needs to be emitted to update the graph.
+ */
+
+/*!
+ * \fn void QScatterDataProxy::itemsInserted(int startIndex, int count)
+ *
+ * This signal is emitted when the number of items specified by \a count is
+ * inserted starting at the position \a startIndex.
+ * If items are inserted into the array without calling insertItem() or
+ * insertItems(), this signal needs to be emitted to update the graph.
+ */
+
+// QScatterDataProxyPrivate
+
+QScatterDataProxyPrivate::QScatterDataProxyPrivate(QScatterDataProxy *q)
+ : QAbstractDataProxyPrivate(q, QAbstractDataProxy::DataTypeScatter),
+ m_dataArray(new QScatterDataArray)
+{
+}
+
+QScatterDataProxyPrivate::~QScatterDataProxyPrivate()
+{
+ m_dataArray->clear();
+ delete m_dataArray;
+}
+
+void QScatterDataProxyPrivate::resetArray(QScatterDataArray *newArray)
+{
+ if (!newArray)
+ newArray = new QScatterDataArray;
+
+ if (newArray != m_dataArray) {
+ m_dataArray->clear();
+ delete m_dataArray;
+ m_dataArray = newArray;
+ }
+}
+
+void QScatterDataProxyPrivate::setItem(int index, const QScatterDataItem &item)
+{
+ Q_ASSERT(index >= 0 && index < m_dataArray->size());
+ (*m_dataArray)[index] = item;
+}
+
+void QScatterDataProxyPrivate::setItems(int index, const QScatterDataArray &items)
+{
+ Q_ASSERT(index >= 0 && (index + items.size()) <= m_dataArray->size());
+ for (int i = 0; i < items.size(); i++)
+ (*m_dataArray)[index++] = items[i];
+}
+
+int QScatterDataProxyPrivate::addItem(const QScatterDataItem &item)
+{
+ int currentSize = m_dataArray->size();
+ m_dataArray->append(item);
+ return currentSize;
+}
+
+int QScatterDataProxyPrivate::addItems(const QScatterDataArray &items)
+{
+ int currentSize = m_dataArray->size();
+ (*m_dataArray) += items;
+ return currentSize;
+}
+
+void QScatterDataProxyPrivate::insertItem(int index, const QScatterDataItem &item)
+{
+ Q_ASSERT(index >= 0 && index <= m_dataArray->size());
+ m_dataArray->insert(index, item);
+}
+
+void QScatterDataProxyPrivate::insertItems(int index, const QScatterDataArray &items)
+{
+ Q_ASSERT(index >= 0 && index <= m_dataArray->size());
+ for (int i = 0; i < items.size(); i++)
+ m_dataArray->insert(index++, items.at(i));
+}
+
+void QScatterDataProxyPrivate::removeItems(int index, int removeCount)
+{
+ Q_ASSERT(index >= 0);
+ int maxRemoveCount = m_dataArray->size() - index;
+ removeCount = qMin(removeCount, maxRemoveCount);
+ m_dataArray->remove(index, removeCount);
+}
+
+void QScatterDataProxyPrivate::limitValues(QVector3D &minValues, QVector3D &maxValues,
+ QAbstract3DAxis *axisX, QAbstract3DAxis *axisY,
+ QAbstract3DAxis *axisZ) const
+{
+ if (m_dataArray->isEmpty())
+ return;
+
+ const QVector3D &firstPos = m_dataArray->at(0).position();
+
+ float minX = firstPos.x();
+ float maxX = minX;
+ float minY = firstPos.y();
+ float maxY = minY;
+ float minZ = firstPos.z();
+ float maxZ = minZ;
+
+ if (m_dataArray->size() > 1) {
+ for (int i = 1; i < m_dataArray->size(); i++) {
+ const QVector3D &pos = m_dataArray->at(i).position();
+
+ float value = pos.x();
+ if (qIsNaN(value) || qIsInf(value))
+ continue;
+ if (isValidValue(minX, value, axisX))
+ minX = value;
+ if (maxX < value)
+ maxX = value;
+
+ value = pos.y();
+ if (qIsNaN(value) || qIsInf(value))
+ continue;
+ if (isValidValue(minY, value, axisY))
+ minY = value;
+ if (maxY < value)
+ maxY = value;
+
+ value = pos.z();
+ if (qIsNaN(value) || qIsInf(value))
+ continue;
+ if (isValidValue(minZ, value, axisZ))
+ minZ = value;
+ if (maxZ < value)
+ maxZ = value;
+ }
+ }
+
+ minValues.setX(minX);
+ minValues.setY(minY);
+ minValues.setZ(minZ);
+
+ maxValues.setX(maxX);
+ maxValues.setY(maxY);
+ maxValues.setZ(maxZ);
+}
+
+bool QScatterDataProxyPrivate::isValidValue(float axisValue, float value,
+ QAbstract3DAxis *axis) const
+{
+ return (axisValue > value && (value > 0.0f
+ || (value == 0.0f && axis->d_ptr->allowZero())
+ || (value < 0.0f && axis->d_ptr->allowNegatives())));
+}
+
+void QScatterDataProxyPrivate::setSeries(QAbstract3DSeries *series)
+{
+ QAbstractDataProxyPrivate::setSeries(series);
+ QScatter3DSeries *scatterSeries = static_cast<QScatter3DSeries *>(series);
+ emit qptr()->seriesChanged(scatterSeries);
+}
+
+QScatterDataProxy *QScatterDataProxyPrivate::qptr()
+{
+ return static_cast<QScatterDataProxy *>(q_ptr);
+}
+
+QT_END_NAMESPACE
diff --git a/src/graphs/data/qscatterdataproxy.h b/src/graphs/data/qscatterdataproxy.h
new file mode 100644
index 0000000..0eda6a8
--- /dev/null
+++ b/src/graphs/data/qscatterdataproxy.h
@@ -0,0 +1,72 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QSCATTERDATAPROXY_H
+#define QSCATTERDATAPROXY_H
+
+#include <QtGraphs/qabstractdataproxy.h>
+#include <QtGraphs/qscatterdataitem.h>
+
+Q_MOC_INCLUDE(<QtGraphs/qscatter3dseries.h>)
+
+QT_BEGIN_NAMESPACE
+
+class QScatterDataProxyPrivate;
+class QScatter3DSeries;
+
+typedef QList<QScatterDataItem> QScatterDataArray;
+
+class Q_GRAPHS_EXPORT QScatterDataProxy : public QAbstractDataProxy
+{
+ Q_OBJECT
+
+ Q_PROPERTY(int itemCount READ itemCount NOTIFY itemCountChanged)
+ Q_PROPERTY(QScatter3DSeries *series READ series NOTIFY seriesChanged)
+
+public:
+ explicit QScatterDataProxy(QObject *parent = nullptr);
+ virtual ~QScatterDataProxy();
+
+ QScatter3DSeries *series() const;
+ int itemCount() const;
+ const QScatterDataArray *array() const;
+ const QScatterDataItem *itemAt(int index) const;
+
+ void resetArray(QScatterDataArray *newArray);
+
+ void setItem(int index, const QScatterDataItem &item);
+ void setItems(int index, const QScatterDataArray &items);
+
+ int addItem(const QScatterDataItem &item);
+ int addItems(const QScatterDataArray &items);
+
+ void insertItem(int index, const QScatterDataItem &item);
+ void insertItems(int index, const QScatterDataArray &items);
+
+ void removeItems(int index, int removeCount);
+
+Q_SIGNALS:
+ void arrayReset();
+ void itemsAdded(int startIndex, int count);
+ void itemsChanged(int startIndex, int count);
+ void itemsRemoved(int startIndex, int count);
+ void itemsInserted(int startIndex, int count);
+
+ void itemCountChanged(int count);
+ void seriesChanged(QScatter3DSeries *series);
+
+protected:
+ explicit QScatterDataProxy(QScatterDataProxyPrivate *d, QObject *parent = nullptr);
+ QScatterDataProxyPrivate *dptr();
+ const QScatterDataProxyPrivate *dptrc() const;
+
+private:
+ Q_DISABLE_COPY(QScatterDataProxy)
+
+ friend class Scatter3DController;
+ friend class QQuickGraphsScatter;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/data/qscatterdataproxy_p.h b/src/graphs/data/qscatterdataproxy_p.h
new file mode 100644
index 0000000..3a6e4cc
--- /dev/null
+++ b/src/graphs/data/qscatterdataproxy_p.h
@@ -0,0 +1,54 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QtGraphs API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#ifndef QSCATTERDATAPROXY_P_H
+#define QSCATTERDATAPROXY_P_H
+
+#include "qscatterdataproxy.h"
+#include "qabstractdataproxy_p.h"
+#include "qscatterdataitem.h"
+
+QT_BEGIN_NAMESPACE
+
+class QAbstract3DAxis;
+
+class QScatterDataProxyPrivate : public QAbstractDataProxyPrivate
+{
+ Q_OBJECT
+public:
+ QScatterDataProxyPrivate(QScatterDataProxy *q);
+ virtual ~QScatterDataProxyPrivate();
+
+ void resetArray(QScatterDataArray *newArray);
+ void setItem(int index, const QScatterDataItem &item);
+ void setItems(int index, const QScatterDataArray &items);
+ int addItem(const QScatterDataItem &item);
+ int addItems(const QScatterDataArray &items);
+ void insertItem(int index, const QScatterDataItem &item);
+ void insertItems(int index, const QScatterDataArray &items);
+ void removeItems(int index, int removeCount);
+ void limitValues(QVector3D &minValues, QVector3D &maxValues, QAbstract3DAxis *axisX,
+ QAbstract3DAxis *axisY, QAbstract3DAxis *axisZ) const;
+ bool isValidValue(float axisValue, float value, QAbstract3DAxis *axis) const;
+
+ void setSeries(QAbstract3DSeries *series) override;
+private:
+ QScatterDataProxy *qptr();
+ QScatterDataArray *m_dataArray;
+
+ friend class QScatterDataProxy;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/data/qsurface3dseries.cpp b/src/graphs/data/qsurface3dseries.cpp
new file mode 100644
index 0000000..7d9918d
--- /dev/null
+++ b/src/graphs/data/qsurface3dseries.cpp
@@ -0,0 +1,560 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qsurface3dseries_p.h"
+#include "surface3dcontroller_p.h"
+#include "qvalue3daxis.h"
+#include "qcategory3daxis.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ * \class QSurface3DSeries
+ * \inmodule QtGraphs
+ * \brief The QSurface3DSeries class represents a data series in a 3D surface
+ * graph.
+ *
+ * This class manages the series specific visual elements, as well as the series
+ * data (via a data proxy).
+ *
+ * If no data proxy is set explicitly for the series, the series creates a default
+ * proxy. Setting another proxy will destroy the existing proxy and all data added to it.
+ *
+ * The object mesh set via the QAbstract3DSeries::mesh property defines the selection
+ * pointer shape in a surface series.
+ *
+ * QSurface3DSeries supports the following format tags for QAbstract3DSeries::setItemLabelFormat():
+ * \table
+ * \row
+ * \li @xTitle \li Title from x-axis
+ * \row
+ * \li @yTitle \li Title from y-axis
+ * \row
+ * \li @zTitle \li Title from z-axis
+ * \row
+ * \li @xLabel \li Item value formatted using the format of the x-axis.
+ * For more information, see
+ * \l{QValue3DAxis::setLabelFormat()}.
+ * \row
+ * \li @yLabel \li Item value formatted using the format of the y-axis.
+ * For more information, see
+ * \l{QValue3DAxis::setLabelFormat()}.
+ * \row
+ * \li @zLabel \li Item value formatted using the format of the z-axis.
+ * For more information, see
+ * \l{QValue3DAxis::setLabelFormat()}.
+ * \row
+ * \li @seriesName \li Name of the series
+ * \endtable
+ *
+ * For example:
+ * \snippet doc_src_qtgraphs.cpp 1
+ *
+ * \sa {Qt Graphs Data Handling}
+ */
+
+/*!
+ * \qmltype Surface3DSeries
+ * \inqmlmodule QtGraphs
+ * \ingroup graphs_qml
+ * \instantiates QSurface3DSeries
+ * \inherits Abstract3DSeries
+ * \brief Represents a data series in a 3D surface graph.
+ *
+ * This type manages the series specific visual elements, as well as the series
+ * data (via a data proxy).
+ *
+ * For a more complete description, see QSurface3DSeries.
+ *
+ * \sa {Qt Graphs Data Handling}
+ */
+
+/*!
+ * \qmlproperty SurfaceDataProxy Surface3DSeries::dataProxy
+ *
+ * The active data proxy. The series assumes ownership of any proxy set to
+ * it and deletes any previously set proxy when a new one is added. The proxy cannot be null or
+ * set to another series.
+ */
+
+/*!
+ * \qmlproperty point Surface3DSeries::selectedPoint
+ *
+ * Sets the surface grid point in the position specified by a row and a column
+ * in the data array of the series as selected.
+ * Only one point can be selected at a time.
+ *
+ * To clear selection from this series, invalidSelectionPosition is set as the position.
+ * If this series is added to a graph, the graph can adjust the selection according to user
+ * interaction or if it becomes invalid.
+ *
+ * Removing rows from or inserting rows to the series before the row of the selected point
+ * will adjust the selection so that the same point will stay selected.
+ *
+ * \sa AbstractGraph3D::clearSelection()
+ */
+
+/*!
+ * \qmlproperty point Surface3DSeries::invalidSelectionPosition
+ * A constant property providing an invalid selection position.
+ * This position is set to the selectedPoint property to clear the selection
+ * from this series.
+ *
+ * \sa AbstractGraph3D::clearSelection()
+ */
+
+/*!
+ * \qmlproperty bool Surface3DSeries::flatShadingEnabled
+ *
+ * Sets surface flat shading to enabled. It is preset to \c true by default.
+ * When disabled, the normals on the surface are interpolated making the edges look round.
+ * When enabled, the normals are kept the same on a triangle making the color of the triangle solid.
+ * This makes the data more readable from the model.
+ * \note Flat shaded surfaces require at least GLSL version 1.2 with GL_EXT_gpu_shader4 extension.
+ * The value of the flatShadingSupported property indicates whether flat shading
+ * is supported at runtime.
+ */
+
+/*!
+ * \qmlproperty bool Surface3DSeries::flatShadingSupported
+ *
+ * Indicates whether flat shading for surfaces is supported by the current system.
+ * It requires at least GLSL version 1.2 with GL_EXT_gpu_shader4 extension.
+ *
+ * \note This read-only property is set to its correct value after the first
+ * render pass. Until then it is always \c true.
+ */
+
+/*!
+ * \qmlproperty DrawFlag Surface3DSeries::drawMode
+ *
+ * Sets the drawing mode to one of \l{QSurface3DSeries::DrawFlag}{Surface3DSeries.DrawFlag}.
+ * Clearing all flags is not allowed.
+ */
+
+/*!
+ * \qmlproperty string Surface3DSeries::textureFile
+ *
+ * The texture file name for the surface texture. To clear the texture, an empty
+ * file name is set.
+ */
+
+/*!
+ * \qmlproperty color Surface3DSeries::wireframeColor
+ *
+ * The color used to draw the gridlines of the surface wireframe.
+ */
+
+/*!
+ * \enum QSurface3DSeries::DrawFlag
+ *
+ * The drawing mode of the surface. Values of this enumeration can be combined
+ * with the OR operator.
+ *
+ * \value DrawWireframe
+ * Only the grid is drawn.
+ * \value DrawSurface
+ * Only the surface is drawn.
+ * \value DrawSurfaceAndWireframe
+ * Both the surface and grid are drawn.
+ */
+
+/*!
+ * Constructs a surface 3D series with the parent \a parent.
+ */
+QSurface3DSeries::QSurface3DSeries(QObject *parent) :
+ QAbstract3DSeries(new QSurface3DSeriesPrivate(this), parent)
+{
+ // Default proxy
+ dptr()->setDataProxy(new QSurfaceDataProxy);
+}
+
+/*!
+ * Constructs a surface 3D series with the data proxy \a dataProxy and the
+ * parent \a parent.
+ */
+QSurface3DSeries::QSurface3DSeries(QSurfaceDataProxy *dataProxy, QObject *parent) :
+ QAbstract3DSeries(new QSurface3DSeriesPrivate(this), parent)
+{
+ dptr()->setDataProxy(dataProxy);
+}
+
+/*!
+ * \internal
+ */
+QSurface3DSeries::QSurface3DSeries(QSurface3DSeriesPrivate *d, QObject *parent) :
+ QAbstract3DSeries(d, parent)
+{
+}
+
+/*!
+ * Deletes the surface 3D series.
+ */
+QSurface3DSeries::~QSurface3DSeries()
+{
+}
+
+/*!
+ * \property QSurface3DSeries::dataProxy
+ *
+ * \brief The active data proxy.
+ *
+ * The series assumes ownership of any proxy set to it and deletes any
+ * previously set proxy when a new one is added. The proxy cannot be null or
+ * set to another series.
+ */
+void QSurface3DSeries::setDataProxy(QSurfaceDataProxy *proxy)
+{
+ d_ptr->setDataProxy(proxy);
+}
+
+QSurfaceDataProxy *QSurface3DSeries::dataProxy() const
+{
+ return static_cast<QSurfaceDataProxy *>(d_ptr->dataProxy());
+}
+
+/*!
+ * \property QSurface3DSeries::selectedPoint
+ *
+ * \brief The surface grid point that is selected in the series.
+ */
+
+/*!
+ * Selects a surface grid point at the position \a position in the data array of
+ * the series specified by a row and a column.
+ *
+ * Only one point can be selected at a time.
+ *
+ * To clear selection from this series, invalidSelectionPosition() is set as \a position.
+ * If this series is added to a graph, the graph can adjust the selection according to user
+ * interaction or if it becomes invalid.
+ *
+ * Removing rows from or inserting rows to the series before the row of the selected point
+ * will adjust the selection so that the same point will stay selected.
+ *
+ * \sa QAbstract3DGraph::clearSelection()
+ */
+void QSurface3DSeries::setSelectedPoint(const QPoint &position)
+{
+ // Don't do this in private to avoid loops, as that is used for callback from controller.
+ if (d_ptr->m_controller)
+ static_cast<Surface3DController *>(d_ptr->m_controller)->setSelectedPoint(position, this, true);
+ else
+ dptr()->setSelectedPoint(position);
+}
+
+QPoint QSurface3DSeries::selectedPoint() const
+{
+ return dptrc()->m_selectedPoint;
+}
+
+/*!
+ * Returns the QPoint signifying an invalid selection position. This is set to
+ * the selectedPoint property to clear the selection from this series.
+ *
+ * \sa QAbstract3DGraph::clearSelection()
+ */
+QPoint QSurface3DSeries::invalidSelectionPosition()
+{
+ return Surface3DController::invalidSelectionPosition();
+}
+
+/*!
+ * \property QSurface3DSeries::flatShadingEnabled
+ *
+ * \brief Whether surface flat shading is enabled.
+ *
+ * Preset to \c true by default.
+ *
+ * When disabled, the normals on the surface are interpolated making the edges look round.
+ * When enabled, the normals are kept the same on a triangle making the color of the triangle solid.
+ * This makes the data more readable from the model.
+ * \note Flat shaded surfaces require at least GLSL version 1.2 with GL_EXT_gpu_shader4 extension.
+ * The value of the flatShadingSupported property indicates whether flat shading
+ * is supported at runtime.
+ */
+void QSurface3DSeries::setFlatShadingEnabled(bool enabled)
+{
+ if (dptr()->m_flatShadingEnabled != enabled) {
+ dptr()->setFlatShadingEnabled(enabled);
+ emit flatShadingEnabledChanged(enabled);
+ }
+}
+
+bool QSurface3DSeries::isFlatShadingEnabled() const
+{
+ return dptrc()->m_flatShadingEnabled;
+}
+
+/*!
+ * \property QSurface3DSeries::flatShadingSupported
+ *
+ * \brief Whether surface flat shading is supported by the current system.
+ *
+ * Flat shading for surfaces requires at least GLSL version 1.2 with GL_EXT_gpu_shader4 extension.
+ * If \c true, flat shading for surfaces is supported.
+ * \note This read-only property is set to its correct value after the first
+ * render pass. Until then it is always \c true.
+ */
+bool QSurface3DSeries::isFlatShadingSupported() const
+{
+ if (d_ptr->m_controller)
+ return static_cast<Surface3DController *>(d_ptr->m_controller)->isFlatShadingSupported();
+ else
+ return true;
+}
+
+/*!
+ * \property QSurface3DSeries::drawMode
+ *
+ * The drawing mode.
+ *
+ * Possible values are the values of DrawFlag. Clearing all flags is not allowed.
+ */
+void QSurface3DSeries::setDrawMode(DrawFlags mode)
+{
+ if (dptr()->m_drawMode != mode) {
+ dptr()->setDrawMode(mode);
+ emit drawModeChanged(mode);
+ }
+}
+
+QSurface3DSeries::DrawFlags QSurface3DSeries::drawMode() const
+{
+ return dptrc()->m_drawMode;
+}
+
+/*!
+ * \property QSurface3DSeries::texture
+ *
+ * \brief The texture for the surface as a QImage.
+ *
+ * Setting an empty QImage clears the texture.
+ */
+void QSurface3DSeries::setTexture(const QImage &texture)
+{
+ if (dptr()->m_texture != texture) {
+ dptr()->setTexture(texture);
+
+ emit textureChanged(texture);
+ dptr()->m_textureFile.clear();
+ }
+}
+
+QImage QSurface3DSeries::texture() const
+{
+ return dptrc()->m_texture;
+}
+
+/*!
+ * \property QSurface3DSeries::textureFile
+ *
+ * \brief The texture for the surface as a file.
+ *
+ * Setting an empty file name clears the texture.
+ */
+void QSurface3DSeries::setTextureFile(const QString &filename)
+{
+ if (dptr()->m_textureFile != filename) {
+ if (filename.isEmpty()) {
+ setTexture(QImage());
+ } else {
+ QImage image(filename);
+ if (image.isNull()) {
+ qWarning() << "Warning: Tried to set invalid image file as surface texture.";
+ return;
+ }
+ setTexture(image);
+ }
+
+ dptr()->m_textureFile = filename;
+ emit textureFileChanged(filename);
+ }
+}
+
+QString QSurface3DSeries::textureFile() const
+{
+ return dptrc()->m_textureFile;
+}
+
+/*!
+ * \property QSurface3DSeries::wireframeColor
+ *
+ * \brief The color for the surface wireframe.
+ */
+void QSurface3DSeries::setWireframeColor(const QColor &color)
+{
+ if (dptr()->m_wireframeColor != color) {
+ dptr()->setWireframeColor(color);
+ emit wireframeColorChanged(color);
+ }
+}
+
+QColor QSurface3DSeries::wireframeColor() const
+{
+ return dptrc()->m_wireframeColor;
+}
+/*!
+ * \internal
+ */
+QSurface3DSeriesPrivate *QSurface3DSeries::dptr()
+{
+ return static_cast<QSurface3DSeriesPrivate *>(d_ptr.data());
+}
+
+/*!
+ * \internal
+ */
+const QSurface3DSeriesPrivate *QSurface3DSeries::dptrc() const
+{
+ return static_cast<const QSurface3DSeriesPrivate *>(d_ptr.data());
+}
+
+// QSurface3DSeriesPrivate
+
+QSurface3DSeriesPrivate::QSurface3DSeriesPrivate(QSurface3DSeries *q)
+ : QAbstract3DSeriesPrivate(q, QAbstract3DSeries::SeriesTypeSurface),
+ m_selectedPoint(Surface3DController::invalidSelectionPosition()),
+ m_flatShadingEnabled(true),
+ m_drawMode(QSurface3DSeries::DrawSurfaceAndWireframe),
+ m_wireframeColor(Qt::black)
+{
+ m_itemLabelFormat = QStringLiteral("@xLabel, @yLabel, @zLabel");
+ m_mesh = QAbstract3DSeries::MeshSphere;
+}
+
+QSurface3DSeriesPrivate::~QSurface3DSeriesPrivate()
+{
+}
+
+QSurface3DSeries *QSurface3DSeriesPrivate::qptr()
+{
+ return static_cast<QSurface3DSeries *>(q_ptr);
+}
+
+void QSurface3DSeriesPrivate::setDataProxy(QAbstractDataProxy *proxy)
+{
+ Q_ASSERT(proxy->type() == QAbstractDataProxy::DataTypeSurface);
+
+ QAbstract3DSeriesPrivate::setDataProxy(proxy);
+
+ emit qptr()->dataProxyChanged(static_cast<QSurfaceDataProxy *>(proxy));
+}
+
+void QSurface3DSeriesPrivate::connectControllerAndProxy(Abstract3DController *newController)
+{
+ QSurfaceDataProxy *surfaceDataProxy = static_cast<QSurfaceDataProxy *>(m_dataProxy);
+
+ if (m_controller && surfaceDataProxy) {
+ //Disconnect old controller/old proxy
+ QObject::disconnect(surfaceDataProxy, 0, m_controller, 0);
+ QObject::disconnect(q_ptr, 0, m_controller, 0);
+ }
+
+ if (newController && surfaceDataProxy) {
+ Surface3DController *controller = static_cast<Surface3DController *>(newController);
+
+ QObject::connect(surfaceDataProxy, &QSurfaceDataProxy::arrayReset, controller,
+ &Surface3DController::handleArrayReset);
+ QObject::connect(surfaceDataProxy, &QSurfaceDataProxy::rowsAdded, controller,
+ &Surface3DController::handleRowsAdded);
+ QObject::connect(surfaceDataProxy, &QSurfaceDataProxy::rowsChanged, controller,
+ &Surface3DController::handleRowsChanged);
+ QObject::connect(surfaceDataProxy, &QSurfaceDataProxy::rowsRemoved, controller,
+ &Surface3DController::handleRowsRemoved);
+ QObject::connect(surfaceDataProxy, &QSurfaceDataProxy::rowsInserted, controller,
+ &Surface3DController::handleRowsInserted);
+ QObject::connect(surfaceDataProxy, &QSurfaceDataProxy::itemChanged, controller,
+ &Surface3DController::handleItemChanged);
+ QObject::connect(qptr(), &QSurface3DSeries::dataProxyChanged, controller,
+ &Surface3DController::handleArrayReset);
+ }
+}
+
+void QSurface3DSeriesPrivate::createItemLabel()
+{
+ static const QString xTitleTag(QStringLiteral("@xTitle"));
+ static const QString yTitleTag(QStringLiteral("@yTitle"));
+ static const QString zTitleTag(QStringLiteral("@zTitle"));
+ static const QString xLabelTag(QStringLiteral("@xLabel"));
+ static const QString yLabelTag(QStringLiteral("@yLabel"));
+ static const QString zLabelTag(QStringLiteral("@zLabel"));
+ static const QString seriesNameTag(QStringLiteral("@seriesName"));
+
+ if (m_selectedPoint == QSurface3DSeries::invalidSelectionPosition()) {
+ m_itemLabel = QString();
+ return;
+ }
+
+ QValue3DAxis *axisX = static_cast<QValue3DAxis *>(m_controller->axisX());
+ QValue3DAxis *axisY = static_cast<QValue3DAxis *>(m_controller->axisY());
+ QValue3DAxis *axisZ = static_cast<QValue3DAxis *>(m_controller->axisZ());
+ QVector3D selectedPosition = qptr()->dataProxy()->itemAt(m_selectedPoint)->position();
+
+ m_itemLabel = m_itemLabelFormat;
+
+ m_itemLabel.replace(xTitleTag, axisX->title());
+ m_itemLabel.replace(yTitleTag, axisY->title());
+ m_itemLabel.replace(zTitleTag, axisZ->title());
+
+ if (m_itemLabel.contains(xLabelTag)) {
+ QString valueLabelText = axisX->formatter()->stringForValue(
+ qreal(selectedPosition.x()), axisX->labelFormat());
+ m_itemLabel.replace(xLabelTag, valueLabelText);
+ }
+ if (m_itemLabel.contains(yLabelTag)) {
+ QString valueLabelText = axisY->formatter()->stringForValue(
+ qreal(selectedPosition.y()), axisY->labelFormat());
+ m_itemLabel.replace(yLabelTag, valueLabelText);
+ }
+ if (m_itemLabel.contains(zLabelTag)) {
+ QString valueLabelText = axisZ->formatter()->stringForValue(
+ qreal(selectedPosition.z()), axisZ->labelFormat());
+ m_itemLabel.replace(zLabelTag, valueLabelText);
+ }
+ m_itemLabel.replace(seriesNameTag, m_name);
+}
+
+void QSurface3DSeriesPrivate::setSelectedPoint(const QPoint &position)
+{
+ if (position != m_selectedPoint) {
+ markItemLabelDirty();
+ m_selectedPoint = position;
+ emit qptr()->selectedPointChanged(m_selectedPoint);
+ }
+}
+
+void QSurface3DSeriesPrivate::setFlatShadingEnabled(bool enabled)
+{
+ m_flatShadingEnabled = enabled;
+ if (m_controller)
+ m_controller->markSeriesVisualsDirty();
+}
+
+void QSurface3DSeriesPrivate::setDrawMode(QSurface3DSeries::DrawFlags mode)
+{
+ if (mode.testFlag(QSurface3DSeries::DrawWireframe)
+ || mode.testFlag(QSurface3DSeries::DrawSurface)) {
+ m_drawMode = mode;
+ if (m_controller)
+ m_controller->markSeriesVisualsDirty();
+ } else {
+ qWarning("You may not clear all draw flags. Mode not changed.");
+ }
+}
+
+void QSurface3DSeriesPrivate::setTexture(const QImage &texture)
+{
+ m_texture = texture;
+ if (static_cast<Surface3DController *>(m_controller))
+ static_cast<Surface3DController *>(m_controller)->updateSurfaceTexture(qptr());
+}
+
+void QSurface3DSeriesPrivate::setWireframeColor(const QColor &color)
+{
+ m_wireframeColor = color;
+ if (m_controller)
+ m_controller->markSeriesVisualsDirty();
+}
+
+QT_END_NAMESPACE
diff --git a/src/graphs/data/qsurface3dseries.h b/src/graphs/data/qsurface3dseries.h
new file mode 100644
index 0000000..683cf9d
--- /dev/null
+++ b/src/graphs/data/qsurface3dseries.h
@@ -0,0 +1,87 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QSURFACE3DSERIES_H
+#define QSURFACE3DSERIES_H
+
+#include <QtGraphs/qabstract3dseries.h>
+#include <QtGraphs/qsurfacedataproxy.h>
+
+QT_BEGIN_NAMESPACE
+
+class QSurface3DSeriesPrivate;
+
+class Q_GRAPHS_EXPORT QSurface3DSeries : public QAbstract3DSeries
+{
+ Q_OBJECT
+ Q_FLAGS(DrawFlag DrawFlags)
+ Q_PROPERTY(QSurfaceDataProxy *dataProxy READ dataProxy WRITE setDataProxy NOTIFY dataProxyChanged)
+ Q_PROPERTY(QPoint selectedPoint READ selectedPoint WRITE setSelectedPoint NOTIFY selectedPointChanged)
+ Q_PROPERTY(bool flatShadingEnabled READ isFlatShadingEnabled WRITE setFlatShadingEnabled NOTIFY flatShadingEnabledChanged)
+ Q_PROPERTY(bool flatShadingSupported READ isFlatShadingSupported NOTIFY flatShadingSupportedChanged)
+ Q_PROPERTY(DrawFlags drawMode READ drawMode WRITE setDrawMode NOTIFY drawModeChanged)
+ Q_PROPERTY(QImage texture READ texture WRITE setTexture NOTIFY textureChanged)
+ Q_PROPERTY(QString textureFile READ textureFile WRITE setTextureFile NOTIFY textureFileChanged)
+ Q_PROPERTY(QColor wireframeColor READ wireframeColor WRITE setWireframeColor NOTIFY wireframeColorChanged)
+
+public:
+ enum DrawFlag {
+ DrawWireframe = 1,
+ DrawSurface = 2,
+ DrawSurfaceAndWireframe = DrawWireframe | DrawSurface
+ };
+ Q_ENUM(DrawFlag)
+ Q_DECLARE_FLAGS(DrawFlags, DrawFlag)
+
+ explicit QSurface3DSeries(QObject *parent = nullptr);
+ explicit QSurface3DSeries(QSurfaceDataProxy *dataProxy, QObject *parent = nullptr);
+ virtual ~QSurface3DSeries();
+
+ void setDataProxy(QSurfaceDataProxy *proxy);
+ QSurfaceDataProxy *dataProxy() const;
+
+ void setSelectedPoint(const QPoint &position);
+ QPoint selectedPoint() const;
+ static QPoint invalidSelectionPosition();
+
+ void setFlatShadingEnabled(bool enabled);
+ bool isFlatShadingEnabled() const;
+
+ void setDrawMode(DrawFlags mode);
+ QSurface3DSeries::DrawFlags drawMode() const;
+
+ bool isFlatShadingSupported() const;
+
+ void setTexture(const QImage &texture);
+ QImage texture() const;
+ void setTextureFile(const QString &filename);
+ QString textureFile() const;
+
+ void setWireframeColor(const QColor &color);
+ QColor wireframeColor() const;
+
+Q_SIGNALS:
+ void dataProxyChanged(QSurfaceDataProxy *proxy);
+ void selectedPointChanged(const QPoint &position);
+ void flatShadingEnabledChanged(bool enable);
+ void flatShadingSupportedChanged(bool enable);
+ void drawModeChanged(QSurface3DSeries::DrawFlags mode);
+ void textureChanged(const QImage &image);
+ void textureFileChanged(const QString &filename);
+ void wireframeColorChanged(const QColor &color);
+
+protected:
+ explicit QSurface3DSeries(QSurface3DSeriesPrivate *d, QObject *parent = nullptr);
+ QSurface3DSeriesPrivate *dptr();
+ const QSurface3DSeriesPrivate *dptrc() const;
+
+private:
+ Q_DISABLE_COPY(QSurface3DSeries)
+
+ friend class Surface3DController;
+};
+Q_DECLARE_OPERATORS_FOR_FLAGS(QSurface3DSeries::DrawFlags)
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/data/qsurface3dseries_p.h b/src/graphs/data/qsurface3dseries_p.h
new file mode 100644
index 0000000..270763a
--- /dev/null
+++ b/src/graphs/data/qsurface3dseries_p.h
@@ -0,0 +1,55 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QtGraphs API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#ifndef QSURFACE3DSERIES_P_H
+#define QSURFACE3DSERIES_P_H
+
+#include "qsurface3dseries.h"
+#include "qabstract3dseries_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class QSurface3DSeriesPrivate : public QAbstract3DSeriesPrivate
+{
+ Q_OBJECT
+public:
+ QSurface3DSeriesPrivate(QSurface3DSeries *q);
+ virtual ~QSurface3DSeriesPrivate();
+
+ void setDataProxy(QAbstractDataProxy *proxy) override;
+ void connectControllerAndProxy(Abstract3DController *newController) override;
+ void createItemLabel() override;
+
+ void setSelectedPoint(const QPoint &position);
+ void setFlatShadingEnabled(bool enabled);
+ void setDrawMode(QSurface3DSeries::DrawFlags mode);
+ void setTexture(const QImage &texture);
+ void setWireframeColor(const QColor &color);
+
+private:
+ QSurface3DSeries *qptr();
+
+ QPoint m_selectedPoint;
+ bool m_flatShadingEnabled;
+ QSurface3DSeries::DrawFlags m_drawMode;
+ QImage m_texture;
+ QString m_textureFile;
+ QColor m_wireframeColor;
+
+private:
+ friend class QSurface3DSeries;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/data/qsurfacedataitem.cpp b/src/graphs/data/qsurfacedataitem.cpp
new file mode 100644
index 0000000..45f47f1
--- /dev/null
+++ b/src/graphs/data/qsurfacedataitem.cpp
@@ -0,0 +1,126 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qsurfacedataitem_p.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ * \class QSurfaceDataItem
+ * \inmodule QtGraphs
+ * \brief The QSurfaceDataItem class provides a container for resolved data to be added to surface
+ * graphs.
+ *
+ * A surface data item holds the data for a single vertex in a surface graph.
+ * Surface data proxies parse data into QSurfaceDataItem instances for
+ * surface graphs.
+ *
+ * \sa QSurfaceDataProxy, {Qt Graphs C++ Classes}
+ */
+
+/*!
+ * Constructs a surface data item.
+ */
+QSurfaceDataItem::QSurfaceDataItem()
+ : d_ptr(0) // private data doesn't exist by default (optimization)
+
+{
+}
+
+/*!
+ * Constructs a surface data item at the position \a position.
+ */
+QSurfaceDataItem::QSurfaceDataItem(const QVector3D &position)
+ : d_ptr(0),
+ m_position(position)
+{
+}
+
+/*!
+ * Constructs a copy of \a other.
+ */
+QSurfaceDataItem::QSurfaceDataItem(const QSurfaceDataItem &other)
+{
+ operator=(other);
+}
+
+/*!
+ * Deletes a surface data item.
+ */
+QSurfaceDataItem::~QSurfaceDataItem()
+{
+}
+
+/*!
+ * Assigns a copy of \a other to this object.
+ */
+QSurfaceDataItem &QSurfaceDataItem::operator=(const QSurfaceDataItem &other)
+{
+ m_position = other.m_position;
+
+ if (other.d_ptr)
+ createExtraData();
+ else
+ d_ptr = 0;
+
+ return *this;
+}
+
+/*!
+ * \fn void QSurfaceDataItem::setPosition(const QVector3D &pos)
+ * Sets the position \a pos to this data item.
+ */
+
+/*!
+ * \fn QVector3D QSurfaceDataItem::position() const
+ * Returns the position of this data item.
+ */
+
+/*!
+ * \fn void QSurfaceDataItem::setX(float value)
+ * Sets the x-coordinate of the item position to the value \a value.
+ */
+
+/*!
+ * \fn void QSurfaceDataItem::setY(float value)
+ * Sets the y-coordinate of the item position to the value \a value.
+ */
+
+/*!
+ * \fn void QSurfaceDataItem::setZ(float value)
+ * Sets the z-coordinate of the item position to the value \a value.
+ */
+
+/*!
+ * \fn float QSurfaceDataItem::x() const
+ * Returns the x-coordinate of the position of this data item.
+ */
+
+/*!
+ * \fn float QSurfaceDataItem::y() const
+ * Returns the y-coordinate of the position of this data item.
+ */
+
+/*!
+ * \fn float QSurfaceDataItem::z() const
+ * Returns the z-coordinate of the position of this data item.
+ */
+
+/*!
+ * \internal
+ */
+void QSurfaceDataItem::createExtraData()
+{
+ if (!d_ptr)
+ d_ptr = new QSurfaceDataItemPrivate;
+}
+
+QSurfaceDataItemPrivate::QSurfaceDataItemPrivate()
+{
+}
+
+QSurfaceDataItemPrivate::~QSurfaceDataItemPrivate()
+{
+}
+
+QT_END_NAMESPACE
diff --git a/src/graphs/data/qsurfacedataitem.h b/src/graphs/data/qsurfacedataitem.h
new file mode 100644
index 0000000..6c013c9
--- /dev/null
+++ b/src/graphs/data/qsurfacedataitem.h
@@ -0,0 +1,44 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QSURFACEDATAITEM_H
+#define QSURFACEDATAITEM_H
+
+#include <QtGraphs/qgraphsglobal.h>
+#include <QtGui/QVector3D>
+
+QT_BEGIN_NAMESPACE
+
+class QSurfaceDataItemPrivate;
+
+class Q_GRAPHS_EXPORT QSurfaceDataItem
+{
+public:
+ QSurfaceDataItem();
+ QSurfaceDataItem(const QVector3D &position);
+ QSurfaceDataItem(const QSurfaceDataItem &other);
+ ~QSurfaceDataItem();
+
+ QSurfaceDataItem &operator=(const QSurfaceDataItem &other);
+
+ inline void setPosition(const QVector3D &pos) { m_position = pos; }
+ inline QVector3D position() const { return m_position; }
+ inline void setX(float value) { m_position.setX(value); }
+ inline void setY(float value) { m_position.setY(value); }
+ inline void setZ(float value) { m_position.setZ(value); }
+ inline float x() const { return m_position.x(); }
+ inline float y() const { return m_position.y(); }
+ inline float z() const { return m_position.z(); }
+
+protected:
+ void createExtraData();
+
+ QSurfaceDataItemPrivate *d_ptr;
+
+private:
+ QVector3D m_position;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/data/qsurfacedataitem_p.h b/src/graphs/data/qsurfacedataitem_p.h
new file mode 100644
index 0000000..632d426
--- /dev/null
+++ b/src/graphs/data/qsurfacedataitem_p.h
@@ -0,0 +1,31 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QtGraphs API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#ifndef QSURFACEDATAITEM_P_H
+#define QSURFACEDATAITEM_P_H
+
+#include "graphsglobal_p.h"
+#include "qsurfacedataitem.h"
+
+QT_BEGIN_NAMESPACE
+
+class QSurfaceDataItemPrivate
+{
+public:
+ QSurfaceDataItemPrivate();
+ virtual ~QSurfaceDataItemPrivate();
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/data/qsurfacedataproxy.cpp b/src/graphs/data/qsurfacedataproxy.cpp
new file mode 100644
index 0000000..46c20ac
--- /dev/null
+++ b/src/graphs/data/qsurfacedataproxy.cpp
@@ -0,0 +1,641 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qsurfacedataproxy_p.h"
+#include "qsurface3dseries_p.h"
+#include "qabstract3daxis_p.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ * \class QSurfaceDataProxy
+ * \inmodule QtGraphs
+ * \brief The QSurfaceDataProxy class is the data proxy for a 3D surface graph.
+ *
+ * A surface data proxy handles surface related data in rows. For this it
+ * provides two auxiliary typedefs: QtGraphs::QSurfaceDataArray and
+ * QtGraphs::QSurfaceDataRow. \c QSurfaceDataArray is a QList that
+ * controls the rows. \c QSurfaceDataRow is a QList that contains
+ * QSurfaceDataItem objects. For more information about how to feed the data to
+ * the proxy, see the sample code in the Q3DSurface documentation.
+ *
+ * All rows must have the same number of items.
+ *
+ * QSurfaceDataProxy takes ownership of all \c QSurfaceDataRow objects passed to
+ * it, whether directly or in a \c QSurfaceDataArray container.
+ * To use surface data row pointers to directly modify data after adding the
+ * array to the proxy, the appropriate signal must be emitted to update the
+ * graph.
+ *
+ * To make a sensible surface, the x-value of each successive item in all rows must be
+ * either ascending or descending throughout the row.
+ * Similarly, the z-value of each successive item in all columns must be either ascending or
+ * descending throughout the column.
+ *
+ * \note Currently only surfaces with straight rows and columns are fully supported. Any row
+ * with items that do not have the exact same z-value or any column with items
+ * that do not have the exact same x-value may get clipped incorrectly if the
+ * whole surface does not completely fit within the visible x-axis or z-axis
+ * ranges.
+ *
+ * \note Surfaces with less than two rows or columns are not considered valid surfaces and will
+ * not be rendered.
+ *
+ * \note On some environments, surfaces with a lot of visible vertices may not render, because
+ * they exceed the per-draw vertex count supported by the graphics driver.
+ * This is mostly an issue on 32-bit and OpenGL ES2 platforms.
+ *
+ * \sa {Qt Graphs Data Handling}
+ */
+
+/*!
+ * \typedef QSurfaceDataRow
+ * \relates QSurfaceDataProxy
+ *
+ * A list of \l {QSurfaceDataItem} objects.
+ */
+
+/*!
+ * \typedef QSurfaceDataArray
+ * \relates QSurfaceDataProxy
+ *
+ * A list of pointers to \l {QSurfaceDataRow} objects.
+ */
+
+/*!
+ * \qmltype SurfaceDataProxy
+ * \inqmlmodule QtGraphs
+ * \ingroup graphs_qml
+ * \instantiates QSurfaceDataProxy
+ * \inherits AbstractDataProxy
+ * \brief The data proxy for a 3D surface graph.
+ *
+ * This type handles surface data items. The data is arranged into rows and columns, and all rows must have
+ * the same number of columns.
+ *
+ * This type is uncreatable, but contains properties that are exposed via subtypes.
+ *
+ * For a more complete description, see QSurfaceDataProxy.
+ *
+ * \sa ItemModelSurfaceDataProxy, {Qt Graphs Data Handling}
+ */
+
+/*!
+ * \qmlproperty int SurfaceDataProxy::rowCount
+ * The number of rows in the data array.
+ */
+
+/*!
+ * \qmlproperty int SurfaceDataProxy::columnCount
+ * The number of columns in the data array.
+ */
+
+/*!
+ * \qmlproperty Surface3DSeries SurfaceDataProxy::series
+ *
+ * The series this proxy is attached to.
+ */
+
+/*!
+ * Constructs QSurfaceDataProxy with the given \a parent.
+ */
+QSurfaceDataProxy::QSurfaceDataProxy(QObject *parent) :
+ QAbstractDataProxy(new QSurfaceDataProxyPrivate(this), parent)
+{
+}
+
+/*!
+ * \internal
+ */
+QSurfaceDataProxy::QSurfaceDataProxy(QSurfaceDataProxyPrivate *d, QObject *parent) :
+ QAbstractDataProxy(d, parent)
+{
+}
+
+/*!
+ * Deletes the surface data proxy.
+ */
+QSurfaceDataProxy::~QSurfaceDataProxy()
+{
+}
+
+/*!
+ * \property QSurfaceDataProxy::series
+ *
+ * \brief The series this proxy is attached to.
+ */
+QSurface3DSeries *QSurfaceDataProxy::series() const
+{
+ return static_cast<QSurface3DSeries *>(d_ptr->series());
+}
+
+/*!
+ * Takes ownership of the array \a newArray. Clears the existing array if the
+ * new array differs from it. If the arrays are the same, this function
+ * just triggers the arrayReset() signal.
+ *
+ * Passing a null array deletes the old array and creates a new empty array.
+ * All rows in \a newArray must be of same length.
+ */
+void QSurfaceDataProxy::resetArray(QSurfaceDataArray *newArray)
+{
+ if (dptr()->m_dataArray != newArray) {
+ dptr()->resetArray(newArray);
+ }
+ emit arrayReset();
+ emit rowCountChanged(rowCount());
+ emit columnCountChanged(columnCount());
+}
+
+/*!
+ * Changes an existing row by replacing the row at the position \a rowIndex
+ * with the new row specified by \a row. The new row can be the same as the
+ * existing row already stored at the \a rowIndex. The new row must have
+ * the same number of columns as the row it is replacing.
+ */
+void QSurfaceDataProxy::setRow(int rowIndex, QSurfaceDataRow *row)
+{
+ dptr()->setRow(rowIndex, row);
+ emit rowsChanged(rowIndex, 1);
+}
+
+/*!
+ * Changes existing rows by replacing the rows starting at the position
+ * \a rowIndex with the new rows specifies by \a rows.
+ * The rows in the \a rows array can be the same as the existing rows already
+ * stored at the \a rowIndex. The new rows must have the same number of columns
+ * as the rows they are replacing.
+ */
+void QSurfaceDataProxy::setRows(int rowIndex, const QSurfaceDataArray &rows)
+{
+ dptr()->setRows(rowIndex, rows);
+ emit rowsChanged(rowIndex, rows.size());
+}
+
+/*!
+ * Changes a single item at the position specified by \a rowIndex and
+ * \a columnIndex to the item \a item.
+ */
+void QSurfaceDataProxy::setItem(int rowIndex, int columnIndex, const QSurfaceDataItem &item)
+{
+ dptr()->setItem(rowIndex, columnIndex, item);
+ emit itemChanged(rowIndex, columnIndex);
+}
+
+/*!
+ * Changes a single item at the position \a position to the item \a item.
+ * The x-value of \a position indicates the row and the y-value indicates the
+ * column.
+ */
+void QSurfaceDataProxy::setItem(const QPoint &position, const QSurfaceDataItem &item)
+{
+ setItem(position.x(), position.y(), item);
+}
+
+/*!
+ * Adds the new row \a row to the end of an array. The new row must have
+ * the same number of columns as the rows in the initial array.
+ *
+ * Returns the index of the added row.
+ */
+int QSurfaceDataProxy::addRow(QSurfaceDataRow *row)
+{
+ int addIndex = dptr()->addRow(row);
+ emit rowsAdded(addIndex, 1);
+ emit rowCountChanged(rowCount());
+ return addIndex;
+}
+
+/*!
+ * Adds new \a rows to the end of an array. The new rows must have the same
+ * number of columns as the rows in the initial array.
+ *
+ * Returns the index of the first added row.
+ */
+int QSurfaceDataProxy::addRows(const QSurfaceDataArray &rows)
+{
+ int addIndex = dptr()->addRows(rows);
+ emit rowsAdded(addIndex, rows.size());
+ emit rowCountChanged(rowCount());
+ return addIndex;
+}
+
+/*!
+ * Inserts the new row \a row into \a rowIndex.
+ * If \a rowIndex is equal to the array size, the rows are added to the end of
+ * the array. The new row must have the same number of columns as the rows in
+ * the initial array.
+ */
+void QSurfaceDataProxy::insertRow(int rowIndex, QSurfaceDataRow *row)
+{
+ dptr()->insertRow(rowIndex, row);
+ emit rowsInserted(rowIndex, 1);
+ emit rowCountChanged(rowCount());
+}
+
+/*!
+ * Inserts new \a rows into \a rowIndex.
+ * If \a rowIndex is equal to the array size, the rows are added to the end of
+ * the array. The new \a rows must have the same number of columns as the rows
+ * in the initial array.
+ */
+void QSurfaceDataProxy::insertRows(int rowIndex, const QSurfaceDataArray &rows)
+{
+ dptr()->insertRows(rowIndex, rows);
+ emit rowsInserted(rowIndex, rows.size());
+ emit rowCountChanged(rowCount());
+}
+
+/*!
+ * Removes the number of rows specified by \a removeCount starting at the
+ * position \a rowIndex. Attempting to remove rows past the end of the
+ * array does nothing.
+ */
+void QSurfaceDataProxy::removeRows(int rowIndex, int removeCount)
+{
+ if (rowIndex < rowCount() && removeCount >= 1) {
+ dptr()->removeRows(rowIndex, removeCount);
+ emit rowsRemoved(rowIndex, removeCount);
+ emit rowCountChanged(rowCount());
+ }
+}
+
+/*!
+ * Returns the pointer to the data array.
+ */
+const QSurfaceDataArray *QSurfaceDataProxy::array() const
+{
+ return dptrc()->m_dataArray;
+}
+
+/*!
+ * Returns the pointer to the item at the position specified by \a rowIndex and
+ * \a columnIndex. It is guaranteed to be valid only
+ * until the next call that modifies data.
+ */
+const QSurfaceDataItem *QSurfaceDataProxy::itemAt(int rowIndex, int columnIndex) const
+{
+ const QSurfaceDataArray &dataArray = *dptrc()->m_dataArray;
+ Q_ASSERT(rowIndex >= 0 && rowIndex < dataArray.size());
+ const QSurfaceDataRow &dataRow = *dataArray[rowIndex];
+ Q_ASSERT(columnIndex >= 0 && columnIndex < dataRow.size());
+ return &dataRow.at(columnIndex);
+}
+
+/*!
+ * Returns the pointer to the item at the position \a position. The x-value of
+ * \a position indicates the row and the y-value indicates the column. The item
+ * is guaranteed to be valid only until the next call that modifies data.
+ */
+const QSurfaceDataItem *QSurfaceDataProxy::itemAt(const QPoint &position) const
+{
+ return itemAt(position.x(), position.y());
+}
+
+/*!
+ * \property QSurfaceDataProxy::rowCount
+ *
+ * \brief The number of rows in the data array.
+ */
+int QSurfaceDataProxy::rowCount() const
+{
+ return dptrc()->m_dataArray->size();
+}
+
+/*!
+ * \property QSurfaceDataProxy::columnCount
+ *
+ * \brief The number of columns in the data array.
+ */
+int QSurfaceDataProxy::columnCount() const
+{
+ if (dptrc()->m_dataArray->size() > 0)
+ return dptrc()->m_dataArray->at(0)->size();
+ else
+ return 0;
+}
+
+/*!
+ * \internal
+ */
+QSurfaceDataProxyPrivate *QSurfaceDataProxy::dptr()
+{
+ return static_cast<QSurfaceDataProxyPrivate *>(d_ptr.data());
+}
+
+/*!
+ * \internal
+ */
+const QSurfaceDataProxyPrivate *QSurfaceDataProxy::dptrc() const
+{
+ return static_cast<const QSurfaceDataProxyPrivate *>(d_ptr.data());
+}
+
+/*!
+ * \fn void QSurfaceDataProxy::arrayReset()
+ *
+ * This signal is emitted when the data array is reset.
+ * If the contents of the whole array are changed without calling resetArray(),
+ * this signal needs to be emitted to update the graph.
+ */
+
+/*!
+ * \fn void QSurfaceDataProxy::rowsAdded(int startIndex, int count)
+ *
+ * This signal is emitted when the number of rows specified by \a count is
+ * added starting at the position \a startIndex.
+ * If rows are added to the array without calling addRow() or addRows(),
+ * this signal needs to be emitted to update the graph.
+ */
+
+/*!
+ * \fn void QSurfaceDataProxy::rowsChanged(int startIndex, int count)
+ *
+ * This signal is emitted when the number of rows specified by \a count is
+ * changed starting at the position \a startIndex.
+ * If rows are changed in the array without calling setRow() or setRows(),
+ * this signal needs to be emitted to update the graph.
+ */
+
+/*!
+ * \fn void QSurfaceDataProxy::rowsRemoved(int startIndex, int count)
+ *
+ * This signal is emitted when the number of rows specified by \a count is
+ * removed starting at the position \a startIndex.
+ *
+ * The index is the current array size if the rows were removed from the end of
+ * the array. If rows are removed from the array without calling removeRows(),
+ * this signal needs to be emitted to update the graph.
+ */
+
+/*!
+ * \fn void QSurfaceDataProxy::rowsInserted(int startIndex, int count)
+ *
+ * This signal is emitted when the number of rows specified by \a count is
+ * inserted at the position \a startIndex.
+ *
+ * If rows are inserted into the array without calling insertRow() or
+ * insertRows(), this signal needs to be emitted to update the graph.
+ */
+
+/*!
+ * \fn void QSurfaceDataProxy::itemChanged(int rowIndex, int columnIndex)
+ *
+ * This signal is emitted when the item at the position specified by \a rowIndex
+ * and \a columnIndex changes.
+ * If the item is changed in the array without calling setItem(),
+ * this signal needs to be emitted to update the graph.
+ */
+
+// QSurfaceDataProxyPrivate
+
+QSurfaceDataProxyPrivate::QSurfaceDataProxyPrivate(QSurfaceDataProxy *q)
+ : QAbstractDataProxyPrivate(q, QAbstractDataProxy::DataTypeSurface),
+ m_dataArray(new QSurfaceDataArray)
+{
+}
+
+QSurfaceDataProxyPrivate::~QSurfaceDataProxyPrivate()
+{
+ clearArray();
+}
+
+void QSurfaceDataProxyPrivate::resetArray(QSurfaceDataArray *newArray)
+{
+ if (!newArray)
+ newArray = new QSurfaceDataArray;
+
+ if (newArray != m_dataArray) {
+ clearArray();
+ m_dataArray = newArray;
+ }
+}
+
+void QSurfaceDataProxyPrivate::setRow(int rowIndex, QSurfaceDataRow *row)
+{
+ Q_ASSERT(rowIndex >= 0 && rowIndex < m_dataArray->size());
+ Q_ASSERT(m_dataArray->at(rowIndex)->size() == row->size());
+
+ if (row != m_dataArray->at(rowIndex)) {
+ clearRow(rowIndex);
+ (*m_dataArray)[rowIndex] = row;
+ }
+}
+
+void QSurfaceDataProxyPrivate::setRows(int rowIndex, const QSurfaceDataArray &rows)
+{
+ QSurfaceDataArray &dataArray = *m_dataArray;
+ Q_ASSERT(rowIndex >= 0 && (rowIndex + rows.size()) <= dataArray.size());
+
+ for (int i = 0; i < rows.size(); i++) {
+ Q_ASSERT(m_dataArray->at(rowIndex)->size() == rows.at(i)->size());
+ if (rows.at(i) != dataArray.at(rowIndex)) {
+ clearRow(rowIndex);
+ dataArray[rowIndex] = rows.at(i);
+ }
+ rowIndex++;
+ }
+}
+
+void QSurfaceDataProxyPrivate::setItem(int rowIndex, int columnIndex, const QSurfaceDataItem &item)
+{
+ Q_ASSERT(rowIndex >= 0 && rowIndex < m_dataArray->size());
+ QSurfaceDataRow &row = *(*m_dataArray)[rowIndex];
+ Q_ASSERT(columnIndex < row.size());
+ row[columnIndex] = item;
+}
+
+int QSurfaceDataProxyPrivate::addRow(QSurfaceDataRow *row)
+{
+ Q_ASSERT(m_dataArray->isEmpty()
+ || m_dataArray->at(0)->size() == row->size());
+ int currentSize = m_dataArray->size();
+ m_dataArray->append(row);
+ return currentSize;
+}
+
+int QSurfaceDataProxyPrivate::addRows(const QSurfaceDataArray &rows)
+{
+ int currentSize = m_dataArray->size();
+ for (int i = 0; i < rows.size(); i++) {
+ Q_ASSERT(m_dataArray->isEmpty()
+ || m_dataArray->at(0)->size() == rows.at(i)->size());
+ m_dataArray->append(rows.at(i));
+ }
+ return currentSize;
+}
+
+void QSurfaceDataProxyPrivate::insertRow(int rowIndex, QSurfaceDataRow *row)
+{
+ Q_ASSERT(rowIndex >= 0 && rowIndex <= m_dataArray->size());
+ Q_ASSERT(m_dataArray->isEmpty()
+ || m_dataArray->at(0)->size() == row->size());
+ m_dataArray->insert(rowIndex, row);
+}
+
+void QSurfaceDataProxyPrivate::insertRows(int rowIndex, const QSurfaceDataArray &rows)
+{
+ Q_ASSERT(rowIndex >= 0 && rowIndex <= m_dataArray->size());
+
+ for (int i = 0; i < rows.size(); i++) {
+ Q_ASSERT(m_dataArray->isEmpty()
+ || m_dataArray->at(0)->size() == rows.at(i)->size());
+ m_dataArray->insert(rowIndex++, rows.at(i));
+ }
+}
+
+void QSurfaceDataProxyPrivate::removeRows(int rowIndex, int removeCount)
+{
+ Q_ASSERT(rowIndex >= 0);
+ int maxRemoveCount = m_dataArray->size() - rowIndex;
+ removeCount = qMin(removeCount, maxRemoveCount);
+ for (int i = 0; i < removeCount; i++) {
+ clearRow(rowIndex);
+ m_dataArray->removeAt(rowIndex);
+ }
+}
+
+QSurfaceDataProxy *QSurfaceDataProxyPrivate::qptr()
+{
+ return static_cast<QSurfaceDataProxy *>(q_ptr);
+}
+
+void QSurfaceDataProxyPrivate::limitValues(QVector3D &minValues, QVector3D &maxValues,
+ QAbstract3DAxis *axisX, QAbstract3DAxis *axisY,
+ QAbstract3DAxis *axisZ) const
+{
+ float min = 0.0f;
+ float max = 0.0f;
+
+ int rows = m_dataArray->size();
+ int columns = 0;
+ if (rows)
+ columns = m_dataArray->at(0)->size();
+
+ if (rows && columns) {
+ min = m_dataArray->at(0)->at(0).y();
+ max = m_dataArray->at(0)->at(0).y();
+ }
+
+ for (int i = 0; i < rows; i++) {
+ QSurfaceDataRow *row = m_dataArray->at(i);
+ if (row) {
+ for (int j = 0; j < columns; j++) {
+ float itemValue = m_dataArray->at(i)->at(j).y();
+ if (qIsNaN(itemValue) || qIsInf(itemValue))
+ continue;
+ if ((min > itemValue || (qIsNaN(min) || qIsInf(min)))
+ && isValidValue(itemValue, axisY)) {
+ min = itemValue;
+ }
+ if (max < itemValue || (qIsNaN(max) || qIsInf(max)))
+ max = itemValue;
+ }
+ }
+ }
+
+ minValues.setY(min);
+ maxValues.setY(max);
+
+ if (columns) {
+ // Have some defaults
+ float xLow = m_dataArray->at(0)->at(0).x();
+ float xHigh = m_dataArray->at(0)->last().x();
+ float zLow = m_dataArray->at(0)->at(0).z();
+ float zHigh = m_dataArray->last()->at(0).z();
+ for (int i = 0; i < rows; i++) {
+ for (int j = 0; j < columns; j++) {
+ float zItemValue = m_dataArray->at(i)->at(j).z();
+ if (qIsNaN(zItemValue) || qIsInf(zItemValue))
+ continue;
+ else if (isValidValue(zItemValue, axisZ))
+ zLow = qMin(zLow,zItemValue);
+ }
+ if (!qIsNaN(zLow) && !qIsInf(zLow))
+ break;
+ }
+ for (int i = rows - 1; i >= 0; i--) {
+ for (int j = 0; j < columns; j++) {
+ float zItemValue = m_dataArray->at(i)->at(j).z();
+ if (qIsNaN(zItemValue) || qIsInf(zItemValue))
+ continue;
+ else if (isValidValue(zItemValue, axisZ))
+ {
+ if (!qIsNaN(zHigh) && !qIsInf(zHigh))
+ zHigh = qMax(zHigh, zItemValue);
+ else
+ zHigh = zItemValue;
+ }
+ }
+ if (!qIsNaN(zHigh) && !qIsInf(zHigh))
+ break;
+ }
+ for (int j = 0; j<columns; j++){
+ for (int i = 0; i < rows; i++) {
+ float xItemValue = m_dataArray->at(i)->at(j).x();
+ if (qIsNaN(xItemValue) || qIsInf(xItemValue))
+ continue;
+ else if (isValidValue(xItemValue, axisX))
+ xLow = qMin(xLow, xItemValue);
+ }
+ if (!qIsNaN(xLow) && !qIsInf(xLow))
+ break;
+ }
+ for (int j = columns-1; j >= 0; j--){
+ for (int i = 0; i < rows; i++) {
+ float xItemValue = m_dataArray->at(i)->at(j).x();
+ if (qIsNaN(xItemValue) || qIsInf(xItemValue))
+ continue;
+ else if (isValidValue(xItemValue, axisX))
+ {
+ if (!qIsNaN(xHigh) && !qIsInf(xHigh))
+ xHigh = qMax(xHigh, xItemValue);
+ else
+ xHigh = xItemValue;
+ }
+ }
+ if (!qIsNaN(xHigh) && !qIsInf(xHigh))
+ break;
+ }
+ minValues.setX(xLow);
+ minValues.setZ(zLow);
+ maxValues.setX(xHigh);
+ maxValues.setZ(zHigh);
+ } else {
+ minValues.setX(axisX->d_ptr->allowZero() ? 0.0f : 1.0f);
+ minValues.setZ(axisZ->d_ptr->allowZero() ? 0.0f : 1.0f);
+ maxValues.setX(axisX->d_ptr->allowZero() ? 0.0f : 1.0f);
+ maxValues.setZ(axisZ->d_ptr->allowZero() ? 0.0f : 1.0f);
+ }
+}
+
+bool QSurfaceDataProxyPrivate::isValidValue(float value, QAbstract3DAxis *axis) const
+{
+ return (value > 0.0f || (value == 0.0f && axis->d_ptr->allowZero())
+ || (value < 0.0f && axis->d_ptr->allowNegatives()));
+}
+
+void QSurfaceDataProxyPrivate::clearRow(int rowIndex)
+{
+ if (m_dataArray->at(rowIndex)) {
+ delete m_dataArray->at(rowIndex);
+ (*m_dataArray)[rowIndex] = 0;
+ }
+}
+
+void QSurfaceDataProxyPrivate::clearArray()
+{
+ for (int i = 0; i < m_dataArray->size(); i++)
+ clearRow(i);
+ m_dataArray->clear();
+ delete m_dataArray;
+}
+
+void QSurfaceDataProxyPrivate::setSeries(QAbstract3DSeries *series)
+{
+ QAbstractDataProxyPrivate::setSeries(series);
+ QSurface3DSeries *surfaceSeries = static_cast<QSurface3DSeries *>(series);
+ emit qptr()->seriesChanged(surfaceSeries);
+}
+
+QT_END_NAMESPACE
diff --git a/src/graphs/data/qsurfacedataproxy.h b/src/graphs/data/qsurfacedataproxy.h
new file mode 100644
index 0000000..9e073d5
--- /dev/null
+++ b/src/graphs/data/qsurfacedataproxy.h
@@ -0,0 +1,80 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QSURFACEDATAPROXY_H
+#define QSURFACEDATAPROXY_H
+
+#include <QtGraphs/qabstractdataproxy.h>
+#include <QtGraphs/qsurfacedataitem.h>
+
+Q_MOC_INCLUDE(<QtGraphs/qsurface3dseries.h>)
+
+QT_BEGIN_NAMESPACE
+
+class QSurfaceDataProxyPrivate;
+class QSurface3DSeries;
+
+typedef QList<QSurfaceDataItem> QSurfaceDataRow;
+typedef QList<QSurfaceDataRow *> QSurfaceDataArray;
+
+class Q_GRAPHS_EXPORT QSurfaceDataProxy : public QAbstractDataProxy
+{
+ Q_OBJECT
+
+ Q_PROPERTY(int rowCount READ rowCount NOTIFY rowCountChanged)
+ Q_PROPERTY(int columnCount READ columnCount NOTIFY columnCountChanged)
+ Q_PROPERTY(QSurface3DSeries *series READ series NOTIFY seriesChanged)
+
+public:
+ explicit QSurfaceDataProxy(QObject *parent = nullptr);
+ virtual ~QSurfaceDataProxy();
+
+ QSurface3DSeries *series() const;
+ int rowCount() const;
+ int columnCount() const;
+ const QSurfaceDataArray *array() const;
+ const QSurfaceDataItem *itemAt(int rowIndex, int columnIndex) const;
+ const QSurfaceDataItem *itemAt(const QPoint &position) const;
+
+ void resetArray(QSurfaceDataArray *newArray);
+
+ void setRow(int rowIndex, QSurfaceDataRow *row);
+ void setRows(int rowIndex, const QSurfaceDataArray &rows);
+
+ void setItem(int rowIndex, int columnIndex, const QSurfaceDataItem &item);
+ void setItem(const QPoint &position, const QSurfaceDataItem &item);
+
+ int addRow(QSurfaceDataRow *row);
+ int addRows(const QSurfaceDataArray &rows);
+
+ void insertRow(int rowIndex, QSurfaceDataRow *row);
+ void insertRows(int rowIndex, const QSurfaceDataArray &rows);
+
+ void removeRows(int rowIndex, int removeCount);
+
+Q_SIGNALS:
+ void arrayReset();
+ void rowsAdded(int startIndex, int count);
+ void rowsChanged(int startIndex, int count);
+ void rowsRemoved(int startIndex, int count);
+ void rowsInserted(int startIndex, int count);
+ void itemChanged(int rowIndex, int columnIndex);
+
+ void rowCountChanged(int count);
+ void columnCountChanged(int count);
+ void seriesChanged(QSurface3DSeries *series);
+
+protected:
+ explicit QSurfaceDataProxy(QSurfaceDataProxyPrivate *d, QObject *parent = nullptr);
+ QSurfaceDataProxyPrivate *dptr();
+ const QSurfaceDataProxyPrivate *dptrc() const;
+
+private:
+ Q_DISABLE_COPY(QSurfaceDataProxy)
+
+ friend class Surface3DController;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/data/qsurfacedataproxy_p.h b/src/graphs/data/qsurfacedataproxy_p.h
new file mode 100644
index 0000000..ee237ad
--- /dev/null
+++ b/src/graphs/data/qsurfacedataproxy_p.h
@@ -0,0 +1,59 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QtGraphs API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#ifndef QSURFACEDATAPROXY_P_H
+#define QSURFACEDATAPROXY_P_H
+
+#include "qsurfacedataproxy.h"
+#include "qabstractdataproxy_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class QAbstract3DAxis;
+
+class QSurfaceDataProxyPrivate : public QAbstractDataProxyPrivate
+{
+ Q_OBJECT
+public:
+ QSurfaceDataProxyPrivate(QSurfaceDataProxy *q);
+ virtual ~QSurfaceDataProxyPrivate();
+
+ void resetArray(QSurfaceDataArray *newArray);
+ void setRow(int rowIndex, QSurfaceDataRow *row);
+ void setRows(int rowIndex, const QSurfaceDataArray &rows);
+ void setItem(int rowIndex, int columnIndex, const QSurfaceDataItem &item);
+ int addRow(QSurfaceDataRow *row);
+ int addRows(const QSurfaceDataArray &rows);
+ void insertRow(int rowIndex, QSurfaceDataRow *row);
+ void insertRows(int rowIndex, const QSurfaceDataArray &rows);
+ void removeRows(int rowIndex, int removeCount);
+ void limitValues(QVector3D &minValues, QVector3D &maxValues, QAbstract3DAxis *axisX,
+ QAbstract3DAxis *axisY, QAbstract3DAxis *axisZ) const;
+ bool isValidValue(float value, QAbstract3DAxis *axis) const;
+
+ void setSeries(QAbstract3DSeries *series) override;
+
+protected:
+ QSurfaceDataArray *m_dataArray;
+
+private:
+ QSurfaceDataProxy *qptr();
+ void clearRow(int rowIndex);
+ void clearArray();
+
+ friend class QSurfaceDataProxy;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/data/scatteritemmodelhandler.cpp b/src/graphs/data/scatteritemmodelhandler.cpp
new file mode 100644
index 0000000..8b87141
--- /dev/null
+++ b/src/graphs/data/scatteritemmodelhandler.cpp
@@ -0,0 +1,218 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "scatteritemmodelhandler_p.h"
+
+QT_BEGIN_NAMESPACE
+
+static const int noRoleIndex = -1;
+
+ScatterItemModelHandler::ScatterItemModelHandler(QItemModelScatterDataProxy *proxy, QObject *parent)
+ : AbstractItemModelHandler(parent),
+ m_proxy(proxy),
+ m_proxyArray(0),
+ m_xPosRole(noRoleIndex),
+ m_yPosRole(noRoleIndex),
+ m_zPosRole(noRoleIndex),
+ m_rotationRole(noRoleIndex),
+ m_haveXPosPattern(false),
+ m_haveYPosPattern(false),
+ m_haveZPosPattern(false),
+ m_haveRotationPattern(false)
+{
+}
+
+ScatterItemModelHandler::~ScatterItemModelHandler()
+{
+}
+
+void ScatterItemModelHandler::handleDataChanged(const QModelIndex &topLeft,
+ const QModelIndex &bottomRight,
+ const QList<int> &roles)
+{
+ // Do nothing if full reset already pending
+ if (!m_fullReset) {
+ if (m_itemModel->columnCount() > 1) {
+ // If the data model is multi-column, do full asynchronous reset to simplify things
+ AbstractItemModelHandler::handleDataChanged(topLeft, bottomRight, roles);
+ } else {
+ int start = qMin(topLeft.row(), bottomRight.row());
+ int end = qMax(topLeft.row(), bottomRight.row());
+
+ QScatterDataArray array(end - start + 1);
+ int count = 0;
+ for (int i = start; i <= end; i++)
+ modelPosToScatterItem(i, 0, array[count++]);
+
+ m_proxy->setItems(start, array);
+ }
+ }
+}
+
+void ScatterItemModelHandler::handleRowsInserted(const QModelIndex &parent, int start, int end)
+{
+ // Do nothing if full reset already pending
+ if (!m_fullReset) {
+ if (!m_proxy->itemCount() || m_itemModel->columnCount() > 1) {
+ // If inserting into an empty array, do full asynchronous reset to avoid multiple
+ // separate inserts when initializing the model.
+ // If the data model is multi-column, do full asynchronous reset to simplify things
+ AbstractItemModelHandler::handleRowsInserted(parent, start, end);
+ } else {
+ QScatterDataArray array(end - start + 1);
+ int count = 0;
+ for (int i = start; i <= end; i++)
+ modelPosToScatterItem(i, 0, array[count++]);
+
+ m_proxy->insertItems(start, array);
+ }
+ }
+}
+
+void ScatterItemModelHandler::handleRowsRemoved(const QModelIndex &parent, int start, int end)
+{
+ Q_UNUSED(parent);
+
+ // Do nothing if full reset already pending
+ if (!m_fullReset) {
+ if (m_itemModel->columnCount() > 1) {
+ // If the data model is multi-column, do full asynchronous reset to simplify things
+ AbstractItemModelHandler::handleRowsRemoved(parent, start, end);
+ } else {
+ m_proxy->removeItems(start, end - start + 1);
+ }
+ }
+}
+
+static inline QQuaternion toQuaternion(const QVariant &variant)
+{
+ if (variant.canConvert<QQuaternion>()) {
+ return variant.value<QQuaternion>();
+ } else if (variant.canConvert<QString>()) {
+ QString s = variant.toString();
+ if (!s.isEmpty()) {
+ bool angleAndAxis = false;
+ if (s.startsWith(QLatin1Char('@'))) {
+ angleAndAxis = true;
+ s = s.mid(1);
+ }
+ if (s.count(QLatin1Char(',')) == 3) {
+ int index = s.indexOf(QLatin1Char(','));
+ int index2 = s.indexOf(QLatin1Char(','), index + 1);
+ int index3 = s.indexOf(QLatin1Char(','), index2 + 1);
+
+ bool sGood, xGood, yGood, zGood;
+ float sCoord = s.left(index).toFloat(&sGood);
+ float xCoord = s.mid(index + 1, index2 - index - 1).toFloat(&xGood);
+ float yCoord = s.mid(index2 + 1, index3 - index2 - 1).toFloat(&yGood);
+ float zCoord = s.mid(index3 + 1).toFloat(&zGood);
+
+ if (sGood && xGood && yGood && zGood) {
+ if (angleAndAxis)
+ return QQuaternion::fromAxisAndAngle(xCoord, yCoord, zCoord, sCoord);
+ else
+ return QQuaternion(sCoord, xCoord, yCoord, zCoord);
+ }
+ }
+ }
+ }
+ return QQuaternion();
+}
+
+void ScatterItemModelHandler::modelPosToScatterItem(int modelRow, int modelColumn,
+ QScatterDataItem &item)
+{
+ QModelIndex index = m_itemModel->index(modelRow, modelColumn);
+ float xPos;
+ float yPos;
+ float zPos;
+ if (m_xPosRole != noRoleIndex) {
+ QVariant xValueVar = index.data(m_xPosRole);
+ if (m_haveXPosPattern)
+ xPos = xValueVar.toString().replace(m_xPosPattern, m_xPosReplace).toFloat();
+ else
+ xPos = xValueVar.toFloat();
+ } else {
+ xPos = 0.0f;
+ }
+ if (m_yPosRole != noRoleIndex) {
+ QVariant yValueVar = index.data(m_yPosRole);
+ if (m_haveYPosPattern)
+ yPos = yValueVar.toString().replace(m_yPosPattern, m_yPosReplace).toFloat();
+ else
+ yPos = yValueVar.toFloat();
+ } else {
+ yPos = 0.0f;
+ }
+ if (m_zPosRole != noRoleIndex) {
+ QVariant zValueVar = index.data(m_zPosRole);
+ if (m_haveZPosPattern)
+ zPos = zValueVar.toString().replace(m_zPosPattern, m_zPosReplace).toFloat();
+ else
+ zPos = zValueVar.toFloat();
+ } else {
+ zPos = 0.0f;
+ }
+ if (m_rotationRole != noRoleIndex) {
+ QVariant rotationVar = index.data(m_rotationRole);
+ if (m_haveRotationPattern) {
+ item.setRotation(
+ toQuaternion(
+ QVariant(rotationVar.toString().replace(m_rotationPattern,
+ m_rotationReplace))));
+ } else {
+ item.setRotation(toQuaternion(rotationVar));
+ }
+ }
+
+ item.setPosition(QVector3D(xPos, yPos, zPos));
+}
+
+// Resolve entire item model into QScatterDataArray.
+void ScatterItemModelHandler::resolveModel()
+{
+ if (m_itemModel.isNull()) {
+ m_proxy->resetArray(0);
+ m_proxyArray = 0;
+ return;
+ }
+
+ m_xPosPattern = m_proxy->xPosRolePattern();
+ m_yPosPattern = m_proxy->yPosRolePattern();
+ m_zPosPattern = m_proxy->zPosRolePattern();
+ m_rotationPattern = m_proxy->rotationRolePattern();
+ m_xPosReplace = m_proxy->xPosRoleReplace();
+ m_yPosReplace = m_proxy->yPosRoleReplace();
+ m_zPosReplace = m_proxy->zPosRoleReplace();
+ m_rotationReplace = m_proxy->rotationRoleReplace();
+ m_haveXPosPattern = !m_xPosPattern.namedCaptureGroups().isEmpty() && m_xPosPattern.isValid();
+ m_haveYPosPattern = !m_yPosPattern.namedCaptureGroups().isEmpty() && m_yPosPattern.isValid();
+ m_haveZPosPattern = !m_zPosPattern.namedCaptureGroups().isEmpty() && m_zPosPattern.isValid();
+ m_haveRotationPattern = !m_rotationPattern.namedCaptureGroups().isEmpty() && m_rotationPattern.isValid();
+
+ QHash<int, QByteArray> roleHash = m_itemModel->roleNames();
+ m_xPosRole = roleHash.key(m_proxy->xPosRole().toLatin1(), noRoleIndex);
+ m_yPosRole = roleHash.key(m_proxy->yPosRole().toLatin1(), noRoleIndex);
+ m_zPosRole = roleHash.key(m_proxy->zPosRole().toLatin1(), noRoleIndex);
+ m_rotationRole = roleHash.key(m_proxy->rotationRole().toLatin1(), noRoleIndex);
+ const int columnCount = m_itemModel->columnCount();
+ const int rowCount = m_itemModel->rowCount();
+ const int totalCount = rowCount * columnCount;
+ int runningCount = 0;
+
+ // If dimensions have changed, recreate the array
+ if (m_proxyArray != m_proxy->array() || totalCount != m_proxyArray->size())
+ m_proxyArray = new QScatterDataArray(totalCount);
+
+ // Parse data into newProxyArray
+ for (int i = 0; i < rowCount; i++) {
+ for (int j = 0; j < columnCount; j++) {
+ modelPosToScatterItem(i, j, (*m_proxyArray)[runningCount]);
+ runningCount++;
+ }
+ }
+
+ m_proxy->resetArray(m_proxyArray);
+}
+
+QT_END_NAMESPACE
diff --git a/src/graphs/data/scatteritemmodelhandler_p.h b/src/graphs/data/scatteritemmodelhandler_p.h
new file mode 100644
index 0000000..fe25ed7
--- /dev/null
+++ b/src/graphs/data/scatteritemmodelhandler_p.h
@@ -0,0 +1,63 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QtGraphs API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#ifndef SCATTERITEMMODELHANDLER_P_H
+#define SCATTERITEMMODELHANDLER_P_H
+
+#include "abstractitemmodelhandler_p.h"
+#include "qitemmodelscatterdataproxy_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class ScatterItemModelHandler : public AbstractItemModelHandler
+{
+ Q_OBJECT
+public:
+ ScatterItemModelHandler(QItemModelScatterDataProxy *proxy, QObject *parent = 0);
+ virtual ~ScatterItemModelHandler();
+
+public Q_SLOTS:
+ void handleDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight,
+ const QList<int> &roles = QList<int>()) override;
+ void handleRowsInserted(const QModelIndex &parent, int start, int end) override;
+ void handleRowsRemoved(const QModelIndex &parent, int start, int end) override;
+
+protected:
+ void resolveModel() override;
+
+private:
+ void modelPosToScatterItem(int modelRow, int modelColumn, QScatterDataItem &item);
+
+ QItemModelScatterDataProxy *m_proxy; // Not owned
+ QScatterDataArray *m_proxyArray; // Not owned
+ int m_xPosRole;
+ int m_yPosRole;
+ int m_zPosRole;
+ int m_rotationRole;
+ QRegularExpression m_xPosPattern;
+ QRegularExpression m_yPosPattern;
+ QRegularExpression m_zPosPattern;
+ QRegularExpression m_rotationPattern;
+ QString m_xPosReplace;
+ QString m_yPosReplace;
+ QString m_zPosReplace;
+ QString m_rotationReplace;
+ bool m_haveXPosPattern;
+ bool m_haveYPosPattern;
+ bool m_haveZPosPattern;
+ bool m_haveRotationPattern;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/data/surfaceitemmodelhandler.cpp b/src/graphs/data/surfaceitemmodelhandler.cpp
new file mode 100644
index 0000000..6f665e0
--- /dev/null
+++ b/src/graphs/data/surfaceitemmodelhandler.cpp
@@ -0,0 +1,310 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "surfaceitemmodelhandler_p.h"
+
+QT_BEGIN_NAMESPACE
+
+static const int noRoleIndex = -1;
+
+SurfaceItemModelHandler::SurfaceItemModelHandler(QItemModelSurfaceDataProxy *proxy, QObject *parent)
+ : AbstractItemModelHandler(parent),
+ m_proxy(proxy),
+ m_proxyArray(0),
+ m_xPosRole(noRoleIndex),
+ m_yPosRole(noRoleIndex),
+ m_zPosRole(noRoleIndex),
+ m_haveXPosPattern(false),
+ m_haveYPosPattern(false),
+ m_haveZPosPattern(false)
+{
+}
+
+SurfaceItemModelHandler::~SurfaceItemModelHandler()
+{
+}
+
+void SurfaceItemModelHandler::handleDataChanged(const QModelIndex &topLeft,
+ const QModelIndex &bottomRight,
+ const QList<int> &roles)
+{
+ // Do nothing if full reset already pending
+ if (!m_fullReset) {
+ if (!m_proxy->useModelCategories()) {
+ // If the data model doesn't directly map rows and columns, we cannot optimize
+ AbstractItemModelHandler::handleDataChanged(topLeft, bottomRight, roles);
+ } else {
+ int startRow = qMin(topLeft.row(), bottomRight.row());
+ int endRow = qMax(topLeft.row(), bottomRight.row());
+ int startCol = qMin(topLeft.column(), bottomRight.column());
+ int endCol = qMax(topLeft.column(), bottomRight.column());
+
+ for (int i = startRow; i <= endRow; i++) {
+ for (int j = startCol; j <= endCol; j++) {
+ QModelIndex index = m_itemModel->index(i, j);
+ QSurfaceDataItem item;
+ QVariant xValueVar = index.data(m_xPosRole);
+ QVariant yValueVar = index.data(m_yPosRole);
+ QVariant zValueVar = index.data(m_zPosRole);
+ const QSurfaceDataItem *oldItem = m_proxy->itemAt(i, j);
+ float xPos;
+ float yPos;
+ float zPos;
+ if (m_xPosRole != noRoleIndex) {
+ if (m_haveXPosPattern)
+ xPos = xValueVar.toString().replace(m_xPosPattern, m_xPosReplace).toFloat();
+ else
+ xPos = xValueVar.toFloat();
+ } else {
+ xPos = oldItem->x();
+ }
+
+ if (m_haveYPosPattern)
+ yPos = yValueVar.toString().replace(m_yPosPattern, m_yPosReplace).toFloat();
+ else
+ yPos = yValueVar.toFloat();
+
+ if (m_zPosRole != noRoleIndex) {
+ if (m_haveZPosPattern)
+ zPos = zValueVar.toString().replace(m_zPosPattern, m_zPosReplace).toFloat();
+ else
+ zPos = zValueVar.toFloat();
+ } else {
+ zPos = oldItem->z();
+ }
+ item.setPosition(QVector3D(xPos, yPos, zPos));
+ m_proxy->setItem(i, j, item);
+ }
+ }
+ }
+ }
+}
+
+// Resolve entire item model into QSurfaceDataArray.
+void SurfaceItemModelHandler::resolveModel()
+{
+ if (m_itemModel.isNull()) {
+ m_proxy->resetArray(0);
+ m_proxyArray = 0;
+ return;
+ }
+
+ if (!m_proxy->useModelCategories()
+ && (m_proxy->rowRole().isEmpty() || m_proxy->columnRole().isEmpty())) {
+ m_proxy->resetArray(0);
+ m_proxyArray = 0;
+ return;
+ }
+
+ // Position patterns can be reused on single item changes, so store them to member variables.
+ QRegularExpression rowPattern(m_proxy->rowRolePattern());
+ QRegularExpression colPattern(m_proxy->columnRolePattern());
+ m_xPosPattern = m_proxy->xPosRolePattern();
+ m_yPosPattern = m_proxy->yPosRolePattern();
+ m_zPosPattern = m_proxy->zPosRolePattern();
+ QString rowReplace = m_proxy->rowRoleReplace();
+ QString colReplace = m_proxy->columnRoleReplace();
+ m_xPosReplace = m_proxy->xPosRoleReplace();
+ m_yPosReplace = m_proxy->yPosRoleReplace();
+ m_zPosReplace = m_proxy->zPosRoleReplace();
+ bool haveRowPattern = !rowPattern.namedCaptureGroups().isEmpty() && rowPattern.isValid();
+ bool haveColPattern = !colPattern.namedCaptureGroups().isEmpty() && colPattern.isValid();
+ m_haveXPosPattern = !m_xPosPattern.namedCaptureGroups().isEmpty() && m_xPosPattern.isValid();
+ m_haveYPosPattern = !m_yPosPattern.namedCaptureGroups().isEmpty() && m_yPosPattern.isValid();
+ m_haveZPosPattern = !m_zPosPattern.namedCaptureGroups().isEmpty() && m_zPosPattern.isValid();
+
+ QHash<int, QByteArray> roleHash = m_itemModel->roleNames();
+
+ // Default to display role if no mapping
+ m_xPosRole = roleHash.key(m_proxy->xPosRole().toLatin1(), noRoleIndex);
+ m_yPosRole = roleHash.key(m_proxy->yPosRole().toLatin1(), Qt::DisplayRole);
+ m_zPosRole = roleHash.key(m_proxy->zPosRole().toLatin1(), noRoleIndex);
+ int rowCount = m_itemModel->rowCount();
+ int columnCount = m_itemModel->columnCount();
+
+ if (m_proxy->useModelCategories()) {
+ // If dimensions have changed, recreate the array
+ if (m_proxyArray != m_proxy->array() || columnCount != m_proxy->columnCount()
+ || rowCount != m_proxyArray->size()) {
+ m_proxyArray = new QSurfaceDataArray;
+ m_proxyArray->reserve(rowCount);
+ for (int i = 0; i < rowCount; i++)
+ m_proxyArray->append(new QSurfaceDataRow(columnCount));
+ }
+ for (int i = 0; i < rowCount; i++) {
+ QSurfaceDataRow &newProxyRow = *m_proxyArray->at(i);
+ for (int j = 0; j < columnCount; j++) {
+ QModelIndex index = m_itemModel->index(i, j);
+ QVariant xValueVar = index.data(m_xPosRole);
+ QVariant yValueVar = index.data(m_yPosRole);
+ QVariant zValueVar = index.data(m_zPosRole);
+ float xPos;
+ float yPos;
+ float zPos;
+ if (m_xPosRole != noRoleIndex) {
+ if (m_haveXPosPattern)
+ xPos = xValueVar.toString().replace(m_xPosPattern, m_xPosReplace).toFloat();
+ else
+ xPos = xValueVar.toFloat();
+ } else {
+ QString header = m_itemModel->headerData(j, Qt::Horizontal).toString();
+ bool ok = false;
+ float headerValue = header.toFloat(&ok);
+ if (ok)
+ xPos = headerValue;
+ else
+ xPos = float(j);
+ }
+
+ if (m_haveYPosPattern)
+ yPos = yValueVar.toString().replace(m_yPosPattern, m_yPosReplace).toFloat();
+ else
+ yPos = yValueVar.toFloat();
+
+ if (m_zPosRole != noRoleIndex) {
+ if (m_haveZPosPattern)
+ zPos = zValueVar.toString().replace(m_zPosPattern, m_zPosReplace).toFloat();
+ else
+ zPos = zValueVar.toFloat();
+ } else {
+ QString header = m_itemModel->headerData(i, Qt::Vertical).toString();
+ bool ok = false;
+ float headerValue = header.toFloat(&ok);
+ if (ok)
+ zPos = headerValue;
+ else
+ zPos = float(i);
+ }
+
+ newProxyRow[j].setPosition(QVector3D(xPos, yPos, zPos));
+ }
+ }
+ } else {
+ int rowRole = roleHash.key(m_proxy->rowRole().toLatin1());
+ int columnRole = roleHash.key(m_proxy->columnRole().toLatin1());
+ if (m_xPosRole == noRoleIndex)
+ m_xPosRole = columnRole;
+ if (m_zPosRole == noRoleIndex)
+ m_zPosRole = rowRole;
+
+ bool generateRows = m_proxy->autoRowCategories();
+ bool generateColumns = m_proxy->autoColumnCategories();
+
+ QStringList rowList;
+ QStringList columnList;
+ // For detecting duplicates in categories generation, using QHashes should be faster than
+ // simple QStringList::contains() check.
+ QHash<QString, bool> rowListHash;
+ QHash<QString, bool> columnListHash;
+
+ bool cumulative = m_proxy->multiMatchBehavior() == QItemModelSurfaceDataProxy::MMBAverage
+ || m_proxy->multiMatchBehavior() == QItemModelSurfaceDataProxy::MMBCumulativeY;
+ bool average = m_proxy->multiMatchBehavior() == QItemModelSurfaceDataProxy::MMBAverage;
+ bool takeFirst = m_proxy->multiMatchBehavior() == QItemModelSurfaceDataProxy::MMBFirst;
+ QHash<QString, QHash<QString, int> > *matchCountMap = 0;
+ if (cumulative)
+ matchCountMap = new QHash<QString, QHash<QString, int> >;
+
+ // Sort values into rows and columns
+ typedef QHash<QString, QVector3D> ColumnValueMap;
+ QHash <QString, ColumnValueMap> itemValueMap;
+ for (int i = 0; i < rowCount; i++) {
+ for (int j = 0; j < columnCount; j++) {
+ QModelIndex index = m_itemModel->index(i, j);
+ QString rowRoleStr = index.data(rowRole).toString();
+ if (haveRowPattern)
+ rowRoleStr.replace(rowPattern, rowReplace);
+ QString columnRoleStr = index.data(columnRole).toString();
+ if (haveColPattern)
+ columnRoleStr.replace(colPattern, colReplace);
+ QVariant xValueVar = index.data(m_xPosRole);
+ QVariant yValueVar = index.data(m_yPosRole);
+ QVariant zValueVar = index.data(m_zPosRole);
+ float xPos;
+ float yPos;
+ float zPos;
+ if (m_haveXPosPattern)
+ xPos = xValueVar.toString().replace(m_xPosPattern, m_xPosReplace).toFloat();
+ else
+ xPos = xValueVar.toFloat();
+ if (m_haveYPosPattern)
+ yPos = yValueVar.toString().replace(m_yPosPattern, m_yPosReplace).toFloat();
+ else
+ yPos = yValueVar.toFloat();
+ if (m_haveZPosPattern)
+ zPos = zValueVar.toString().replace(m_zPosPattern, m_zPosReplace).toFloat();
+ else
+ zPos = zValueVar.toFloat();
+
+ QVector3D itemPos(xPos, yPos, zPos);
+
+ if (cumulative)
+ (*matchCountMap)[rowRoleStr][columnRoleStr]++;
+
+ if (cumulative) {
+ itemValueMap[rowRoleStr][columnRoleStr] += itemPos;
+ } else {
+ if (takeFirst && itemValueMap.contains(rowRoleStr)) {
+ if (itemValueMap.value(rowRoleStr).contains(columnRoleStr))
+ continue; // We already have a value for this row/column combo
+ }
+ itemValueMap[rowRoleStr][columnRoleStr] = itemPos;
+ }
+
+ if (generateRows && !rowListHash.value(rowRoleStr, false)) {
+ rowListHash.insert(rowRoleStr, true);
+ rowList << rowRoleStr;
+ }
+ if (generateColumns && !columnListHash.value(columnRoleStr, false)) {
+ columnListHash.insert(columnRoleStr, true);
+ columnList << columnRoleStr;
+ }
+ }
+ }
+
+ if (generateRows)
+ m_proxy->dptr()->m_rowCategories = rowList;
+ else
+ rowList = m_proxy->rowCategories();
+
+ if (generateColumns)
+ m_proxy->dptr()->m_columnCategories = columnList;
+ else
+ columnList = m_proxy->columnCategories();
+
+ // If dimensions have changed, recreate the array
+ if (m_proxyArray != m_proxy->array() || columnList.size() != m_proxy->columnCount()
+ || rowList.size() != m_proxyArray->size()) {
+ m_proxyArray = new QSurfaceDataArray;
+ m_proxyArray->reserve(rowList.size());
+ for (int i = 0; i < rowList.size(); i++)
+ m_proxyArray->append(new QSurfaceDataRow(columnList.size()));
+ }
+ // Create data array from itemValueMap
+ for (int i = 0; i < rowList.size(); i++) {
+ QString rowKey = rowList.at(i);
+ QSurfaceDataRow &newProxyRow = *m_proxyArray->at(i);
+ for (int j = 0; j < columnList.size(); j++) {
+ QVector3D &itemPos = itemValueMap[rowKey][columnList.at(j)];
+ if (cumulative) {
+ float divisor = float((*matchCountMap)[rowKey][columnList.at(j)]);
+ if (divisor) {
+ if (average) {
+ itemPos /= divisor;
+ } else { // cumulativeY
+ itemPos.setX(itemPos.x() / divisor);
+ itemPos.setZ(itemPos.z() / divisor);
+ }
+ }
+ }
+ newProxyRow[j].setPosition(itemPos);
+ }
+ }
+
+ delete matchCountMap;
+ }
+
+ m_proxy->resetArray(m_proxyArray);
+}
+
+QT_END_NAMESPACE
diff --git a/src/graphs/data/surfaceitemmodelhandler_p.h b/src/graphs/data/surfaceitemmodelhandler_p.h
new file mode 100644
index 0000000..530890b
--- /dev/null
+++ b/src/graphs/data/surfaceitemmodelhandler_p.h
@@ -0,0 +1,54 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QtGraphs API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#ifndef SURFACEITEMMODELHANDLER_P_H
+#define SURFACEITEMMODELHANDLER_P_H
+
+#include "abstractitemmodelhandler_p.h"
+#include "qitemmodelsurfacedataproxy_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class SurfaceItemModelHandler : public AbstractItemModelHandler
+{
+ Q_OBJECT
+public:
+ SurfaceItemModelHandler(QItemModelSurfaceDataProxy *proxy, QObject *parent = 0);
+ virtual ~SurfaceItemModelHandler();
+
+public Q_SLOTS:
+ void handleDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight,
+ const QList<int> &roles = QList<int>()) override;
+
+protected:
+ void resolveModel() override;
+
+ QItemModelSurfaceDataProxy *m_proxy; // Not owned
+ QSurfaceDataArray *m_proxyArray; // Not owned
+ int m_xPosRole;
+ int m_yPosRole;
+ int m_zPosRole;
+ QRegularExpression m_xPosPattern;
+ QRegularExpression m_yPosPattern;
+ QRegularExpression m_zPosPattern;
+ QString m_xPosReplace;
+ QString m_yPosReplace;
+ QString m_zPosReplace;
+ bool m_haveXPosPattern;
+ bool m_haveYPosPattern;
+ bool m_haveZPosPattern;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/doc/images/q3dbars-minimal.png b/src/graphs/doc/images/q3dbars-minimal.png
new file mode 100644
index 0000000..3e94cca
--- /dev/null
+++ b/src/graphs/doc/images/q3dbars-minimal.png
Binary files differ
diff --git a/src/graphs/doc/images/q3dscatter-minimal.png b/src/graphs/doc/images/q3dscatter-minimal.png
new file mode 100644
index 0000000..d5f8198
--- /dev/null
+++ b/src/graphs/doc/images/q3dscatter-minimal.png
Binary files differ
diff --git a/src/graphs/doc/images/q3dsurface-minimal.png b/src/graphs/doc/images/q3dsurface-minimal.png
new file mode 100644
index 0000000..af82c9b
--- /dev/null
+++ b/src/graphs/doc/images/q3dsurface-minimal.png
Binary files differ
diff --git a/src/graphs/doc/qtgraphs.qdocconf b/src/graphs/doc/qtgraphs.qdocconf
new file mode 100644
index 0000000..facd6da
--- /dev/null
+++ b/src/graphs/doc/qtgraphs.qdocconf
@@ -0,0 +1,51 @@
+include($QT_INSTALL_DOCS/global/qt-module-defaults.qdocconf)
+include($QT_INSTALL_DOCS/config/exampleurl-qtdatavis3d.qdocconf) # TODO: exampleurl-qtdatavis3d.qdocconf
+
+project = QtGraphs
+description = Qt Graphs Reference Documentation
+version = $QT_VERSION
+buildversion = Qt Graphs | Commercial or GPLv3
+moduleheader = QtGraphs
+
+examplesinstallpath = graphs
+exampledirs += ../../../examples/datavisualization \
+ snippets
+
+{headerdirs,sourcedirs} += ..
+
+imagedirs += ../images \
+ images
+
+depends = qtcore qtgui qtqml qtquick qtdoc qtcmake qtwidgets
+
+qhp.projects = QtGraphs
+
+qhp.QtGraphs.file = qtgraphsd.qhp
+qhp.QtGraphs.namespace = org.qt-project.qtgraphs.$QT_VERSION_TAG
+qhp.QtGraphs.virtualFolder = qtgraphs
+qhp.QtGraphs.indexTitle = Qt Graphs
+qhp.QtGraphs.indexRoot =
+
+qhp.QtGraphs.subprojects = gettingstarted examples classes types
+qhp.QtGraphs.subprojects.gettingstarted.title = Getting Started
+qhp.QtGraphs.subprojects.gettingstarted.indexTitle = Qt Graphs Getting Started
+qhp.QtGraphs.subprojects.gettingstarted.selectors = doc:page
+qhp.QtGraphs.subprojects.gettingstarted.sortPages = true
+qhp.QtGraphs.subprojects.examples.title = Examples
+qhp.QtGraphs.subprojects.examples.indexTitle = Qt Graphs Examples
+qhp.QtGraphs.subprojects.examples.selectors = doc:example
+qhp.QtGraphs.subprojects.examples.sortPages = true
+qhp.QtGraphs.subprojects.classes.title = C++ Classes
+qhp.QtGraphs.subprojects.classes.indexTitle = Qt Graphs C++ Classes
+qhp.QtGraphs.subprojects.classes.selectors = class
+qhp.QtGraphs.subprojects.classes.sortPages = true
+qhp.QtGraphs.subprojects.types.title = QML Types
+qhp.QtGraphs.subprojects.types.indexTitle = Qt Graphs QML Types
+qhp.QtGraphs.subprojects.types.selectors = qmlclass
+qhp.QtGraphs.subprojects.types.sortPages = true
+
+navigation.landingpage = Qt Graphs
+navigation.cppclassespage = Qt Graphs C++ Classes
+navigation.qmltypespage = Qt Graphs QML Types
+
+manifestmeta.highlighted.names = "QtGraphs/Textured Surface Example" # TODO: Replace with an existing revamped example
diff --git a/src/graphs/doc/snippets/doc_src_q3dbars_construction.cpp b/src/graphs/doc/snippets/doc_src_q3dbars_construction.cpp
new file mode 100644
index 0000000..a5f5488
--- /dev/null
+++ b/src/graphs/doc/snippets/doc_src_q3dbars_construction.cpp
@@ -0,0 +1,33 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//! [3]
+#include <QtGraphs>
+
+int main(int argc, char **argv)
+{
+ qputenv("QSG_RHI_BACKEND", "opengl");
+ QGuiApplication app(argc, argv);
+
+ //! [4]
+ Q3DBars bars;
+ bars.setFlags(bars.flags() ^ Qt::FramelessWindowHint);
+ //! [4]
+ //! [0]
+ bars.rowAxis()->setRange(0, 4);
+ bars.columnAxis()->setRange(0, 4);
+ //! [0]
+ //! [1]
+ QBar3DSeries *series = new QBar3DSeries;
+ QBarDataRow *data = new QBarDataRow;
+ *data << 1.0f << 3.0f << 7.5f << 5.0f << 2.2f;
+ series->dataProxy()->addRow(data);
+ bars.addSeries(series);
+ //! [1]
+ //! [2]
+ bars.show();
+ //! [2]
+
+ return app.exec();
+}
+//! [3]
diff --git a/src/graphs/doc/snippets/doc_src_q3dscatter_construction.cpp b/src/graphs/doc/snippets/doc_src_q3dscatter_construction.cpp
new file mode 100644
index 0000000..8790c8f
--- /dev/null
+++ b/src/graphs/doc/snippets/doc_src_q3dscatter_construction.cpp
@@ -0,0 +1,29 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//! [3]
+#include <QtGraphs>
+
+int main(int argc, char **argv)
+{
+ qputenv("QSG_RHI_BACKEND", "opengl");
+ QGuiApplication app(argc, argv);
+
+ //! [0]
+ Q3DScatter scatter;
+ scatter.setFlags(scatter.flags() ^ Qt::FramelessWindowHint);
+ //! [0]
+ //! [1]
+ QScatter3DSeries *series = new QScatter3DSeries;
+ QScatterDataArray data;
+ data << QVector3D(0.5f, 0.5f, 0.5f) << QVector3D(-0.3f, -0.5f, -0.4f) << QVector3D(0.0f, -0.3f, 0.2f);
+ series->dataProxy()->addItems(data);
+ scatter.addSeries(series);
+ //! [1]
+ //! [2]
+ scatter.show();
+ //! [2]
+
+ return app.exec();
+}
+//! [3]
diff --git a/src/graphs/doc/snippets/doc_src_q3dsurface_construction.cpp b/src/graphs/doc/snippets/doc_src_q3dsurface_construction.cpp
new file mode 100644
index 0000000..8f20f67
--- /dev/null
+++ b/src/graphs/doc/snippets/doc_src_q3dsurface_construction.cpp
@@ -0,0 +1,39 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//! [5]
+#include <QtGraphs>
+
+int main(int argc, char **argv)
+{
+ qputenv("QSG_RHI_BACKEND", "opengl");
+ QGuiApplication app(argc, argv);
+
+ //! [0]
+ Q3DSurface surface;
+ surface.setFlags(surface.flags() ^ Qt::FramelessWindowHint);
+ //! [0]
+ //! [1]
+ QSurfaceDataArray *data = new QSurfaceDataArray;
+ QSurfaceDataRow *dataRow1 = new QSurfaceDataRow;
+ QSurfaceDataRow *dataRow2 = new QSurfaceDataRow;
+ //! [1]
+
+ //! [2]
+ *dataRow1 << QVector3D(0.0f, 0.1f, 0.5f) << QVector3D(1.0f, 0.5f, 0.5f);
+ *dataRow2 << QVector3D(0.0f, 1.8f, 1.0f) << QVector3D(1.0f, 1.2f, 1.0f);
+ *data << dataRow1 << dataRow2;
+ //! [2]
+
+ //! [3]
+ QSurface3DSeries *series = new QSurface3DSeries;
+ series->dataProxy()->resetArray(data);
+ surface.addSeries(series);
+ //! [3]
+ //! [4]
+ surface.show();
+ //! [4]
+
+ return app.exec();
+}
+//! [5]
diff --git a/src/graphs/doc/snippets/doc_src_q3dtheme.cpp b/src/graphs/doc/snippets/doc_src_q3dtheme.cpp
new file mode 100644
index 0000000..fca6119
--- /dev/null
+++ b/src/graphs/doc/snippets/doc_src_q3dtheme.cpp
@@ -0,0 +1,96 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtGraphs/Q3DBars>
+#include <QtGraphs/Q3DTheme>
+
+int main(int argc, char **argv)
+{
+ //! [0]
+ Q3DTheme *theme = new Q3DTheme(Q3DTheme::ThemeQt);
+ //! [0]
+
+ //! [1]
+ Q3DTheme *theme = new Q3DTheme(Q3DTheme::ThemeQt);
+ theme->setBackgroundEnabled(false);
+ theme->setLabelBackgroundEnabled(false);
+ //! [1]
+
+ //! [2]
+ Q3DTheme *theme = new Q3DTheme();
+ theme->setAmbientLightStrength(0.3f);
+ theme->setBackgroundColor(QColor(QRgb(0x99ca53)));
+ theme->setBackgroundEnabled(true);
+ theme->setBaseColor(QColor(QRgb(0x209fdf)));
+ theme->setColorStyle(Q3DTheme::ColorStyleUniform);
+ theme->setFont(QFont(QStringLiteral("Impact"), 35));
+ theme->setGridEnabled(true);
+ theme->setGridLineColor(QColor(QRgb(0x99ca53)));
+ theme->setHighlightLightStrength(7.0f);
+ theme->setLabelBackgroundColor(QColor(0xf6, 0xa6, 0x25, 0xa0));
+ theme->setLabelBackgroundEnabled(true);
+ theme->setLabelBorderEnabled(true);
+ theme->setLabelTextColor(QColor(QRgb(0x404044)));
+ theme->setLightColor(Qt::white);
+ theme->setLightStrength(6.0f);
+ theme->setMultiHighlightColor(QColor(QRgb(0x6d5fd5)));
+ theme->setSingleHighlightColor(QColor(QRgb(0xf6a625)));
+ theme->setWindowColor(QColor(QRgb(0xffffff)));
+ //! [2]
+
+ //! [3]
+ Q3DBars *graph = new Q3DBars();
+ graph->activeTheme()->setType(Q3DTheme::ThemePrimaryColors);
+ graph->activeTheme()->setBaseColor(Qt::red);
+ graph->activeTheme()->setSingleHighlightColor(Qt::yellow);
+ //! [3]
+}
+
+//! [4]
+Scatter3D {
+ ...
+ theme: Theme3D { type: Theme3D.ThemeRetro }
+ ...
+}
+//! [4]
+
+//! [5]
+Bars3D {
+ ...
+ theme: Theme3D {
+ type: Theme3D.ThemeRetro
+ labelBorderEnabled: true
+ font.pointSize: 35
+ labelBackgroundEnabled: false
+ }
+ ...
+}
+//! [5]
+
+//! [6]
+Surface3D {
+ ...
+ theme: Theme3D {
+ ambientLightStrength: 0.5
+ backgroundColor: "red"
+ backgroundEnabled: true
+ baseColor: "blue"
+ colorStyle: Theme3D.ColorStyleUniform
+ font.family: "Lucida Handwriting"
+ font.pointSize: 35
+ gridEnabled: false
+ gridLineColor: "black"
+ highlightLightStrength: 0.5
+ labelBackgroundColor: "black"
+ labelBackgroundEnabled: true
+ labelBorderEnabled: false
+ labelTextColor: "white"
+ lightColor: "yellow"
+ lightStrength: 0.4
+ multiHighlightColor: "green"
+ singleHighlightColor: "darkRed"
+ windowColor: "white"
+ }
+ ...
+}
+//! [6]
diff --git a/src/graphs/doc/snippets/doc_src_qmlgraphs.cpp b/src/graphs/doc/snippets/doc_src_qmlgraphs.cpp
new file mode 100644
index 0000000..334aeff
--- /dev/null
+++ b/src/graphs/doc/snippets/doc_src_qmlgraphs.cpp
@@ -0,0 +1,154 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//! [0]
+import QtGraphs
+//! [0]
+
+//! [1]
+import QtQuick 2.0
+import QtGraphs
+
+Item {
+ width: 640
+ height: 480
+
+ Bars3D {
+ width: parent.width
+ height: parent.height
+
+ Bar3DSeries {
+ itemLabelFormat: "@colLabel, @rowLabel: @valueLabel"
+
+ ItemModelBarDataProxy {
+ itemModel: dataModel
+ // Mapping model roles to bar series rows, columns, and values.
+ rowRole: "year"
+ columnRole: "city"
+ valueRole: "expenses"
+ }
+ }
+ }
+
+ ListModel {
+ id: dataModel
+ ListElement{ year: "2012"; city: "Oulu"; expenses: "4200"; }
+ ListElement{ year: "2012"; city: "Rauma"; expenses: "2100"; }
+ ListElement{ year: "2012"; city: "Helsinki"; expenses: "7040"; }
+ ListElement{ year: "2012"; city: "Tampere"; expenses: "4330"; }
+ ListElement{ year: "2013"; city: "Oulu"; expenses: "3960"; }
+ ListElement{ year: "2013"; city: "Rauma"; expenses: "1990"; }
+ ListElement{ year: "2013"; city: "Helsinki"; expenses: "7230"; }
+ ListElement{ year: "2013"; city: "Tampere"; expenses: "4650"; }
+ }
+}
+//! [1]
+
+//! [2]
+import QtQuick 2.0
+import QtGraphs
+
+Item {
+ width: 640
+ height: 480
+
+ Scatter3D {
+ width: parent.width
+ height: parent.height
+ Scatter3DSeries {
+ ItemModelScatterDataProxy {
+ itemModel: dataModel
+ // Mapping model roles to scatter series item coordinates.
+ xPosRole: "xPos"
+ yPosRole: "yPos"
+ zPosRole: "zPos"
+ }
+ }
+ }
+
+ ListModel {
+ id: dataModel
+ ListElement{ xPos: "2.754"; yPos: "1.455"; zPos: "3.362"; }
+ ListElement{ xPos: "3.164"; yPos: "2.022"; zPos: "4.348"; }
+ ListElement{ xPos: "4.564"; yPos: "1.865"; zPos: "1.346"; }
+ ListElement{ xPos: "1.068"; yPos: "1.224"; zPos: "2.983"; }
+ ListElement{ xPos: "2.323"; yPos: "2.502"; zPos: "3.133"; }
+ }
+}
+//! [2]
+
+//! [3]
+import QtQuick 2.0
+import QtGraphs
+
+Item {
+ width: 640
+ height: 480
+
+ Surface3D {
+ width: parent.width
+ height: parent.height
+ Surface3DSeries {
+ itemLabelFormat: "Pop density at (@xLabel N, @zLabel E): @yLabel"
+ ItemModelSurfaceDataProxy {
+ itemModel: dataModel
+ // Mapping model roles to surface series rows, columns, and values.
+ rowRole: "longitude"
+ columnRole: "latitude"
+ yPosRole: "pop_density"
+ }
+ }
+ }
+ ListModel {
+ id: dataModel
+ ListElement{ longitude: "20"; latitude: "10"; pop_density: "4.75"; }
+ ListElement{ longitude: "21"; latitude: "10"; pop_density: "3.00"; }
+ ListElement{ longitude: "22"; latitude: "10"; pop_density: "1.24"; }
+ ListElement{ longitude: "23"; latitude: "10"; pop_density: "2.53"; }
+ ListElement{ longitude: "20"; latitude: "11"; pop_density: "2.55"; }
+ ListElement{ longitude: "21"; latitude: "11"; pop_density: "2.03"; }
+ ListElement{ longitude: "22"; latitude: "11"; pop_density: "3.46"; }
+ ListElement{ longitude: "23"; latitude: "11"; pop_density: "5.12"; }
+ ListElement{ longitude: "20"; latitude: "12"; pop_density: "1.37"; }
+ ListElement{ longitude: "21"; latitude: "12"; pop_density: "2.98"; }
+ ListElement{ longitude: "22"; latitude: "12"; pop_density: "3.33"; }
+ ListElement{ longitude: "23"; latitude: "12"; pop_density: "3.23"; }
+ ListElement{ longitude: "20"; latitude: "13"; pop_density: "4.34"; }
+ ListElement{ longitude: "21"; latitude: "13"; pop_density: "3.54"; }
+ ListElement{ longitude: "22"; latitude: "13"; pop_density: "1.65"; }
+ ListElement{ longitude: "23"; latitude: "13"; pop_density: "2.67"; }
+ }
+}
+//! [3]
+
+//! [7]
+ItemModelBarDataProxy {
+ itemModel: model // E.g. a list model defined elsewhere containing yearly expenses data.
+ // Mapping model roles to bar series rows, columns, and values.
+ rowRole: "year"
+ columnRole: "city"
+ valueRole: "expenses"
+ rowCategories: ["2010", "2011", "2012", "2013"]
+ columnCategories: ["Oulu", "Rauma", "Helsinki", "Tampere"]
+}
+//! [7]
+
+//! [8]
+ItemModelScatterDataProxy {
+ itemModel: model // E.g. a list model defined elsewhere containing point coordinates.
+ // Mapping model roles to scatter series item coordinates.
+ xPosRole: "xPos"
+ yPosRole: "yPos"
+ zPosRole: "zPos"
+}
+//! [8]
+
+//! [9]
+ItemModelSurfaceDataProxy {
+ itemModel: model // E.g. a list model defined elsewhere containing population data.
+ // Mapping model roles to surface series rows, columns, and values.
+ rowRole: "longitude"
+ columnRole: "latitude"
+ valueRole: "pop_density"
+}
+//! [9]
diff --git a/src/graphs/doc/snippets/doc_src_qtgraphs.cpp b/src/graphs/doc/snippets/doc_src_qtgraphs.cpp
new file mode 100644
index 0000000..d738a3a
--- /dev/null
+++ b/src/graphs/doc/snippets/doc_src_qtgraphs.cpp
@@ -0,0 +1,113 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//! [0]
+#include <QtGraphs>
+//! [0]
+
+//! [1]
+proxy->setItemLabelFormat(QStringLiteral("@valueTitle for (@rowLabel, @colLabel): %.1f"));
+//! [1]
+
+//! [2]
+proxy->setItemLabelFormat(QStringLiteral("@xTitle: @xValue, @yTitle: @yValue, @zTitle: @zValue"));
+//! [2]
+
+//! [3]
+// By defining row and column categories, you tell the mapping which row and column each item
+// belongs to. The categories must match the data stored in the model in the roles you define
+// for row and column mapping. In this example we expect "year" role to return four digit year
+// and "month" to return three letter designation for the month.
+//
+// An example of an item in model would be:
+// Requested role -> Returned data
+// "year" -> "2006" // Matches the first row category, so this item is added to the first row.
+// "month" -> "jan" // Matches the first column category, so this item is added as first item in the row.
+// "income" -> "12.1"
+// "expenses" -> "9.2"
+QStringList years;
+QStringList months;
+years << "2006" << "2007" << "2008" << "2009" << "2010" << "2011" << "2012";
+months << "jan" << "feb" << "mar" << "apr" << "may" << "jun" << "jul" << "aug" << "sep" << "oct" << "nov" << "dec";
+
+QItemModelBarDataProxy *proxy = new QItemModelBarDataProxy(customModel,
+ QStringLiteral("year"), // Row role
+ QStringLiteral("month"), // Column role
+ QStringLiteral("income"), // Value role
+ years, // Row categories
+ months); // Column categories
+
+//...
+
+// To display different data later, you can simply change the mapping.
+proxy->setValueRole(QStringLiteral("expenses"));
+//! [3]
+
+//! [4]
+// Map "density" value to X-axis, "hardness" to Y-axis and "conductivity" to Z-axis.
+QItemModelScatterDataProxy *proxy = new QItemModelScatterDataProxy(customModel,
+ QStringLiteral("density"),
+ QStringLiteral("hardness"),
+ QStringLiteral("conductivity"));
+//! [4]
+
+//! [5]
+QItemModelSurfaceDataProxy *proxy = new QItemModelSurfaceDataProxy(customModel,
+ QStringLiteral("longitude"), // Row role
+ QStringLiteral("latitude"), // Column role
+ QStringLiteral("height")); // Y-position role
+//! [5]
+
+//! [6]
+qmake
+make
+make install
+//! [6]
+
+//! [7]
+qmake CONFIG+=static
+make
+make install
+//! [7]
+
+//! [8]
+qmake
+make
+./qmlsurface
+//! [8]
+
+//! [9]
+Q3DBars *graph = new Q3DBars();
+QWidget *container = QWidget::createWindowContainer(graph);
+//! [9]
+
+//! [10]
+Q3DBars graph;
+QBarDataProxy *newProxy = new QBarDataProxy;
+
+QBarDataArray *dataArray = new QBarDataArray;
+dataArray->reserve(10);
+for (int i = 0; i < 10; i++) {
+ QBarDataRow *dataRow = new QBarDataRow(5);
+ for (int j = 0; j < 5; j++)
+ (*dataRow)[j].setValue(myData->getValue(i, j));
+ dataArray->append(dataRow);
+}
+
+newProxy->resetArray(dataArray);
+graph->addSeries(new QBar3DSeries(newProxy));
+//! [10]
+
+//! [11]
+Q3DBars graph;
+QBar3DSeries *series = new QBar3DSeries;
+QLinearGradient barGradient(0, 0, 1, 100);
+barGradient.setColorAt(1.0, Qt::white);
+barGradient.setColorAt(0.0, Qt::black);
+
+series->setBaseGradient(barGradient);
+series->setColorStyle(Q3DTheme::ColorStyleObjectGradient);
+series->setMesh(QAbstract3DSeries::MeshCylinder);
+
+graph->addSeries(series);
+//! [11]
diff --git a/src/graphs/doc/snippets/doc_src_qtgraphs.pro b/src/graphs/doc/snippets/doc_src_qtgraphs.pro
new file mode 100644
index 0000000..5a50356
--- /dev/null
+++ b/src/graphs/doc/snippets/doc_src_qtgraphs.pro
@@ -0,0 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#! [0]
+QT += graphs
+#! [0]
diff --git a/src/graphs/doc/src/qtgraphs-index.qdoc b/src/graphs/doc/src/qtgraphs-index.qdoc
new file mode 100644
index 0000000..c8a1e36
--- /dev/null
+++ b/src/graphs/doc/src/qtgraphs-index.qdoc
@@ -0,0 +1,61 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ \title Qt Graphs
+ \page qtgraphs-index.html
+ \brief Provides functionality for 3D graphs.
+
+
+ The Qt Graphs module enables you to visualize data in 3D as
+ bar, scatter, and surface graphs. It's especially useful for visualizing
+ depth maps and large quantities of rapidly changing data, such as data
+ received from multiple sensors. The look and feel of graphs can be
+ customized by using themes or by adding custom items and labels.
+
+ Qt Graphs is built on Qt 6 and Qt Quick 3D to take advantage of
+ hardware acceleration and Qt Quick.
+
+ \section1 Using the Module
+
+ \section2 QML API
+
+ \include {module-use.qdocinc} {using the qml api} {QtGraphs}
+
+ \section2 C++ API
+
+ \include {module-use.qdocinc} {using the c++ api}
+
+ \section3 Building with CMake
+
+ \include {module-use.qdocinc} {building with cmake} {graphs}
+
+ \section3 Building with qmake
+
+ \include {module-use.qdocinc} {building_with_qmake} {graphs}
+
+ \section1 Articles and Guides
+ \list
+ \li \l{Qt Graphs Overview}{Overview}
+ \li \l{Qt Graphs Data Handling}{Data Handling}
+ \li \l{Qt Graphs Interacting with Data}{Interacting with
+ Data}
+ \li \l{Qt Graphs Known Issues}{Known Issues}
+ \endlist
+
+ \section1 Examples
+ \list
+ \li \l{Qt Graphs Examples}
+ \endlist
+
+ \section1 API Reference
+ \list
+ \li \l{Qt Graphs C++ Classes}
+ \li \l{Qt Graphs QML Types}
+ \endlist
+
+ \section1 Licenses and Attributions
+ Qt Graphs is available under commercial licenses from \l{The Qt
+ Company}. In addition, it is available under the \l{GNU General Public
+ License, version 3}. See \l{Qt Licensing} for further details.
+*/
diff --git a/src/graphs/doc/src/qtgraphs-overview.qdoc b/src/graphs/doc/src/qtgraphs-overview.qdoc
new file mode 100644
index 0000000..1aee7f2
--- /dev/null
+++ b/src/graphs/doc/src/qtgraphs-overview.qdoc
@@ -0,0 +1,224 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ \title Qt Graphs Overview
+ \page qtgraphs-overview.html
+ \brief An overview of the Qt Graphs module.
+
+ The Qt Graphs module provides a way to develop rapidly
+ responding, complex, and dynamic 3D graphs for analytical demanding
+ industries such as academic research and medical. Qt 3D Graphs
+ provides 3D bars, scatter, and surface graphs. Combining user
+ interaction and real time 3D drawing graphs enables creating user
+ interfaces that use space effectively. Changing between 3D and 2D
+ presentation enables truly utilizing the value of 3D in visualizing data.
+
+ The look and feel of the graphs can be customized by using the predefined
+ themes or defining new ones. In addition, scenes can be customized by
+ specifying settings for the camera, and individual items can be customized
+ by using predefined or user-defined meshes.
+
+ Qt Graphs offers ready-made data proxies that can be used to
+ visualize data from Qt item models and height maps. Each graph type has a
+ basic proxy type, which accepts data in a format suitable for that
+ graph. For more information, see \l{Qt Graphs Data
+ Handling}.
+
+ End users can interact with the data presented by graphs in several ways,
+ including rotating graphs, zooming into and out of data, selecting items,
+ and viewing 2D slices of the 3D data for increased readability. For more
+ information, see \l{Qt Graphs Interacting with Data}.
+
+ \section1 Graph Types
+
+ The Qt Graphs module provides the following 3D graph types:
+
+ \list
+ \li \l{3D Bar Graphs}{3D bar graphs}
+ \li \l{3D Scatter Graphs}{3D scatter graphs}
+ \li \l{3D Surface Graphs}{3D surface graphs}
+ \endlist
+
+ The QAbstract3DGraph class subclasses a QWindow and provides a render loop
+ for its own subclasses that implement the different graph types: Q3DBars,
+ Q3DScatter, and Q3DSurface. The graph type determines how the data is
+ presented.
+
+ \section2 3D Bar Graphs
+
+ 3D bar graphs present data as 3D bars that are grouped by category. The
+ Q3DBars class is used to create a graph and the QBar3DSeries and
+ QBarDataProxy classes are used to set data to the graph, as well as to
+ control the visual properties of the graph, such as draw mode and shading.
+ In QML, the corresponding types are Bars3D, Bar3DSeries, and BarDataProxy.
+
+ \image q3dbars-minimal.png
+
+ For more information, see \l{How to construct a minimal Q3DBars graph},
+ \l {Bars Example}, and \l {Qt Quick 2 Bars Example}.
+
+ \section2 3D Scatter Graphs
+
+ 3D scatter graphs present data as a collection of points. The Q3DScatter
+ class is used to create a graph and the QScatter3DSeries and
+ QScatterDataProxy classes are used to set data to the graph, as well as to
+ control the visual properties of the graph. In QML, the corresponding types
+ are Scatter3D, Scatter3DSeries, and ScatterDataProxy.
+
+ \image q3dscatter-minimal.png
+
+ For more information, see \l{How to construct a minimal Q3DScatter graph},
+ \l{Scatter Example}, and \l{Qt Quick 2 Scatter Example}.
+
+ \section2 3D Surface Graphs
+
+ 3D surface graphs present data as 3D surface plots. The Q3DSurface class is
+ used to create a graph and the QSurface3DSeries and QSurfaceDataProxy
+ classes are used to set data to the graph, as well as to control the visual
+ properties of the graph. In QML, the corresponding types are Surface3D,
+ Surface3DSeries, and SurfaceDataProxy.
+
+ \image q3dsurface-minimal.png
+
+ For more information, see \l{How to construct a minimal Q3DSurface graph},
+ \l{Surface Example}, \l{Textured Surface Example}, \l{Qt Quick 2 Surface
+ Example}, and \l{Qt Quick 2 Surface Multiseries Example}.
+
+ \section1 Using OpenGL for Rendering Data
+
+ It is recommended to use OpenGL 2.1 or later for data rendering.
+ If OpenGL ES2 is used (including Angle builds in Windows), the following
+ features are not supported:
+
+ \list
+ \li Shadows
+ \li Antialiasing
+ \li Flat shading for surfaces
+ \li Volumetric objects, because they use 3D textures
+ \endlist
+
+ Only OpenGL ES2 emulation is available for software renderer (that is, when
+ using QCoreApplication::setAttribute(Qt::AA_UseSoftwareOpenGL)).
+
+ \section2 Selecting Rendering Mode
+
+ In QML, you can set the \l{AbstractGraph3D::renderingMode}
+ {AbstractGraph3D.RenderingMode} property to determine whether the graph will
+ be rendered directly on the window background or to an offscreen surface
+ that is then drawn during normal QML item rendering.
+
+ Background rendering modes offer slightly better performance than the
+ indirect rendering mode, at the cost of non-standard QML behavior. For
+ example, the graphs do not obey the z ordering of QML items and they cannot
+ be partially transparent. Therefore, changing the rendering mode is a
+ question of performance versus quality.
+
+ Qt Quick uses a dedicated scenegraph for data rendering, and is therefore
+ the best choice for graphs.
+
+ \section1 3D Axes
+
+ Qt Graphs supports the following axis types:
+
+ \list
+ \li Value axis
+ \li Category axis
+ \endlist
+
+ An axis can be set up to show a line or a grid. Both axis types are
+ specializations of the QAbstract3DAxis class or the AbstractAxis3D QML type.
+
+ A value axis can be given a range of values and segment and subsegment
+ counts to divide the range into. Labels are drawn between each segment.
+ Grid lines are drawn between each segment and each subsegment. The value
+ axis is implemented using the QValue3DAxis class or the ValueAxis3D QML type.
+
+ A category axis has named ranges and adjustable range widths. It is divided
+ into equal-sized categories based on the data window size defined by the
+ axis range. Labels are drawn to the positions of categories, if provided.
+ Grid lines are drawn between categories, if visible. A category axis is
+ implemented using the QCategory3DAxis class or the CategoryAxis3D QML type.
+
+ If no axes are set explicitly for a graph, temporary default axes with no
+ labels are created. These default axes can be modified via axis accessors,
+ but as soon as any axis is set explicitly for a particular orientation, the
+ default axis for that orientation is destroyed.
+
+ All graph types support showing multiple series simultaneously. All the
+ series do not need to contain the same number of rows and columns. Row and
+ column labels are taken from the first series added, unless they are
+ explicitly defined for the row and column axes.
+
+ Axis formatters can be used to customize value axis grid lines and labels.
+ The QValue3DAxisFormatter class and ValueAxis3DFormatter QML type provide
+ formatting rules for a linear value 3D axis. The QLogValue3DAxisFormatter
+ class and the LogValueAxis3DFormatter QML type provide formatting rules for
+ a logarithmic value 3D axis.
+
+ Polar horizontal axes can be used for surface and scatter graphs by setting
+ the \l{QAbstract3DGraph::}{polar} property.
+
+ \section1 3D Themes
+
+ A theme is a built-in collection of UI style related settings applied to all
+ the visual elements of a graph, such as the colors, fonts, and visibility of
+ the elements, as well as the strenght of the light and ambient light.
+
+ Qt Charts comes with the following predefined themes that can be used as
+ basis for custom themes:
+
+ \list
+ \li \e Qt is a light theme with green as the base color.
+ \li \e {Primary colors} is a light theme with yellow as the base color.
+ \li \e Digia is a light theme with gray as the base color.
+ \li \e {Stone moss} is a medium dark theme with yellow as the base
+ color.
+ \li \e {Army blue} is a medium light theme with blue as the base color.
+ \li \e Retro is a medium light theme with brown as the base color.
+ \li \e Ebony is a dark theme with white as the base color.
+ \li \e Isabelle is a dark theme with yellow as the base color.
+ \li \e {User defined} is the default theme that is meant to be
+ customized. For more information, see \l {Default Theme}.
+ \endlist
+
+ Custom themes can also be created from scratch.
+
+ If a graph displays the data from several data series, some settings can be
+ specified separately for each series. For example, different gradients can
+ be specified for different layers of the graph to make it look more
+ realistic. For an example, see \l{Qt Quick 2 Surface Multiseries Example}.
+
+ \section1 Customizing 3D Scenes
+
+ A 3D scene is implemented by using the Q3DScene class or the Scene3D QML
+ type. A scene contains a single active camera, implemented by using the
+ Q3DCamera class or the Camera3D type, and a single active light source,
+ implemented by using the Q3DLight class or the Light3D type. The light
+ source is always positioned in relation to the camera. By default, the light
+ position follows the camera automatically.
+
+ The camera can be customized by specifying its preset position, rotation,
+ and zoom level. For an example, see \l{Qt Quick 2 Scatter Example}.
+
+ \section1 Customizing Items
+
+ Qt Graphs has predefined mesh types for bars, items, and
+ surfaces. The mesh type determines how a bar, an item, or a surface looks on
+ a graph. A user-defined mesh can be specified as a Wavefront OBJ geometry
+ definition file. For more variety, a quaternion can be set for mesh
+ rotation.
+
+ In addition to customizing individual items, the QCustom3DItem class or the
+ Custom3DItem QML type can be used to add custom items to graphs. The items
+ have a custom mesh, position, scaling, rotation, and an optional texture.
+
+ The QCustom3DVolume class and the Custom3DVolume QML type can be used to
+ create volume rendered objects to be added to a graph. A volume rendered
+ object is a box with a 3D texture. Three slice planes are supported for the
+ volume, one along each main axis of the volume.
+
+ The The QCustom3DLabel class and the Custom3DLabel QML type implement custom
+ labels with the specified text, font, position, scaling, and rotation. Label
+ colors, borders, and background are determined by the active theme.
+*/
diff --git a/src/graphs/doc/src/qtgraphs-qml-abstractdeclarative.qdoc b/src/graphs/doc/src/qtgraphs-qml-abstractdeclarative.qdoc
new file mode 100644
index 0000000..c075d78
--- /dev/null
+++ b/src/graphs/doc/src/qtgraphs-qml-abstractdeclarative.qdoc
@@ -0,0 +1,402 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ \qmltype AbstractGraph3D
+ \inqmlmodule QtGraphs
+ \ingroup graphs_qml
+ \brief Base type for 3D graphs.
+
+ The base type for all 3D graphs in QtGraphs.
+
+ This type is uncreatable, but it contains properties that are shared between
+ the 3D graphs types.
+
+ \sa Bars3D, Scatter3D, Surface3D, {Qt Graphs C++ Classes}
+ */
+
+/*!
+ \qmlproperty AbstractGraph3D.SelectionMode AbstractGraph3D::selectionMode
+ The active selection mode in the graph.
+ One of the QAbstract3DGraph::SelectionFlag enum values.
+
+ \sa QAbstract3DGraph::SelectionFlag
+ */
+
+/*!
+ \qmlproperty AbstractGraph3D.ShadowQuality AbstractGraph3D::shadowQuality
+ The quality of shadows. One of the QAbstract3DGraph::ShadowQuality enum
+ values.
+
+ \sa QAbstract3DGraph::ShadowQuality
+ */
+
+/*!
+ \qmlproperty bool AbstractGraph3D::shadowsSupported
+ This read-only property indicates whether shadows are supported with the
+ current configuration. OpenGL ES2 configurations do not support shadows.
+ */
+
+/*!
+ \qmlproperty Scene3D AbstractGraph3D::scene
+ The Scene3D pointer that can be used to manipulate the scene and access the
+ scene elements, such as the active camera.
+
+ This property is read-only.
+ */
+
+/*!
+ \qmlproperty AbstractInputHandler3D AbstractGraph3D::inputHandler
+ The active input handler used in the graph. You can disable default input
+ handlers by setting this property to null.
+ */
+
+/*!
+ \qmlproperty Theme3D AbstractGraph3D::theme
+ The active theme of the graph.
+
+ \sa Theme3D
+ */
+
+/*!
+ \qmlproperty AbstractGraph3D.RenderingMode AbstractGraph3D::renderingMode
+
+ How the graph will be rendered. Defaults to \c{RenderIndirect}.
+
+ \value RenderDirectToBackground
+ Indicates that the graph will be rendered directly on the window background and QML items are
+ rendered on top of it. Using non-transparent QML item as a background will hide the graph.
+ Clears the whole window before rendering the graph, including the areas
+ outside the graph. If the surface format of the window supports antialiasing, it will be used (see
+ \c {QtGraphs::qDefaultSurfaceFormat()}).
+ This rendering mode offers the best performance at the expense of non-standard QML behavior. For example,
+ the graphs do not obey the z ordering of QML items and the opacity value has no effect on them.
+
+
+ \value RenderDirectToBackground_NoClear
+ \obsolete
+ \note This will work exactly the same way as \c RenderDirectToBackground does in Qt 6
+ as not clearing the window is not supported anymore.
+
+ \value RenderIndirect
+ Indicates the graph will be first rendered to an offscreen surface that
+ is then drawn during normal QML item rendering. The rendered image is
+ antialiased using the multisampling method if it is supported in the current environment and the
+ msaaSamples property value is greater than zero.
+ This rendering mode offers good quality and normal QML item behavior at the expense of performance.
+
+ \note Antialiasing is not supported in OpenGL ES2 environments in any rendering mode.
+
+ \note Setting the \c antialiasing property of the graph does not do anything. However, it is
+ set by the graph itself if the current rendering mode uses antialiasing.
+
+ \sa msaaSamples
+ */
+
+/*!
+ \qmlproperty int AbstractGraph3D::msaaSamples
+ The number of samples used in multisample antialiasing when renderingMode is \c RenderIndirect.
+ When renderingMode is \c RenderDirectToBackground or \c RenderDirectToBackground_NoClear, this
+ property value is read-only and returns the number of samples specified by the window surface
+ format.
+ Defaults to \c{4}.
+
+ \sa renderingMode
+ */
+
+/*!
+ * \qmlproperty bool AbstractGraph3D::measureFps
+ *
+ * If \c {true}, the rendering is done continuously instead of on demand, and
+ * the value of the currentFps property is updated. Defaults to \c{false}.
+ *
+ * \sa currentFps
+ */
+
+/*!
+ * \qmlproperty int AbstractGraph3D::currentFps
+ *
+ * When FPS measuring is enabled, the results for the last second are stored in this read-only
+ * property. It takes at least a second before this value updates after measuring is activated.
+ *
+ * \sa measureFps
+ */
+
+/*!
+ * \qmlproperty list<Custom3DItem> AbstractGraph3D::customItemList
+ *
+ * The list of \l{Custom3DItem} items added to the graph. The graph takes ownership
+ * of the added items.
+ */
+
+/*!
+ * \qmlproperty bool AbstractGraph3D::polar
+ *
+ * If \c {true}, the horizontal axes are changed into polar axes. The x-axis
+ * becomes the angular axis and the z-axis becomes the radial axis.
+ * Polar mode is not available for bar graphs.
+ *
+ * Defaults to \c{false}.
+ *
+ * \sa orthoProjection, radialLabelOffset
+ */
+
+/*!
+ * \qmlproperty real AbstractGraph3D::radialLabelOffset
+ *
+ * This property specifies the normalized horizontal offset for the axis labels of the radial
+ * polar axis. The value \c 0.0 indicates that the labels should be drawn next
+ * to the 0-angle angular axis grid line. The value \c 1.0 indicates that the
+ * labels are drawn in their usual place at the edge of the graph background.
+ * This property is ignored if the polar property value is \c{false}. Defaults
+ * to \c 1.0.
+ *
+ * \sa polar
+ */
+
+/*!
+ * \qmlmethod void AbstractGraph3D::clearSelection()
+ * Clears selection from all attached series.
+ */
+
+/*!
+ * \qmlmethod bool AbstractGraph3D::hasSeries(Abstract3DSeries series)
+ * \since 6.3
+ * Returns whether the \a series has already been added to the graph.
+ */
+
+/*!
+ * \qmlmethod int AbstractGraph3D::addCustomItem(Custom3DItem item)
+ *
+ * Adds a Custom3DItem \a item to the graph. Graph takes ownership of the added item.
+ *
+ * \return index to the added item if add was successful, -1 if trying to add a null item, and
+ * index of the item if trying to add an already added item.
+ *
+ * \sa removeCustomItems(), removeCustomItem(), removeCustomItemAt()
+ *
+ */
+
+/*!
+ * \qmlmethod void AbstractGraph3D::removeCustomItems()
+ *
+ * Removes all custom items. Deletes the resources allocated to them.
+ *
+ */
+
+/*!
+ * \qmlmethod void AbstractGraph3D::removeCustomItem(Custom3DItem item)
+ *
+ * Removes the custom \a {item}. Deletes the resources allocated to it.
+ *
+ */
+
+/*!
+ * \qmlmethod void AbstractGraph3D::removeCustomItemAt(vector3d position)
+ *
+ * Removes all custom items at \a {position}. Deletes the resources allocated to them.
+ *
+ */
+
+/*!
+ * \qmlmethod void AbstractGraph3D::releaseCustomItem(Custom3DItem item)
+ *
+ * Gets ownership of \a item back and removes the \a item from the graph.
+ *
+ * \note If the same item is added back to the graph, the texture file needs to be re-set.
+ *
+ * \sa Custom3DItem::textureFile
+ */
+
+/*!
+ * \qmlmethod int AbstractGraph3D::selectedLabelIndex()
+ *
+ * Can be used to query the index of the selected label after receiving \c selectedElementChanged
+ * signal with any label type. Selection is valid until the next \c selectedElementChanged signal.
+ *
+ * \return index of the selected label, or -1.
+ *
+ * \sa selectedElement
+ */
+
+/*!
+ * \qmlmethod Abstract3DAxis AbstractGraph3D::selectedAxis()
+ *
+ * Can be used to get the selected axis after receiving \c selectedElementChanged signal with any label
+ * type. Selection is valid until the next \c selectedElementChanged signal.
+ *
+ * \return the selected axis, or null.
+ *
+ * \sa selectedElement
+ */
+
+/*!
+ * \qmlmethod int AbstractGraph3D::selectedCustomItemIndex()
+ *
+ * Can be used to query the index of the selected custom item after receiving \c selectedElementChanged
+ * signal with \l{QAbstract3DGraph::ElementCustomItem}{ElementCustomItem} type. Selection is valid
+ * until the next \c selectedElementChanged signal.
+ *
+ * \return index of the selected custom item, or -1.
+ *
+ * \sa selectedElement
+ */
+
+/*!
+ * \qmlmethod Custom3DItem AbstractGraph3D::selectedCustomItem()
+ *
+ * Can be used to get the selected custom item after receiving \c selectedElementChanged signal with
+ * \l{QAbstract3DGraph::ElementCustomItem}{ElementCustomItem} type. Ownership of the item remains
+ * with the graph. Selection is valid until the next \c selectedElementChanged signal.
+ *
+ * \return the selected custom item, or null.
+ *
+ * \sa selectedElement
+ */
+
+/*!
+ * \qmlproperty AbstractGraph3D.ElementType AbstractGraph3D::selectedElement
+ *
+ * The element selected in the graph.
+ *
+ * This property can be used to query the selected element type.
+ * The type is valid until a new selection is made in the graph and the
+ * \c selectedElementChanged signal is emitted.
+ *
+ * The signal can be used for example for implementing customized input
+ * handling, as demonstrated by the \l {Qt Quick 2 Axis Dragging Example}.
+ *
+ * \sa selectedLabelIndex(), selectedAxis(), selectedCustomItemIndex(), selectedCustomItem(),
+ * Bars3D::selectedSeries, Scatter3D::selectedSeries, Scene3D::selectionQueryPosition,
+ * QAbstract3DGraph::ElementType
+ *
+ */
+
+/*!
+ * \qmlproperty bool AbstractGraph3D::orthoProjection
+ *
+ * If \c {true}, orthographic projection will be used for displaying the graph. Defaults to \c{false}.
+ * \note Shadows will be disabled when set to \c{true}.
+ */
+
+/*!
+ * \qmlproperty real AbstractGraph3D::aspectRatio
+ *
+ * The ratio of the graph scaling between the longest axis on the horizontal
+ * plane and the y-axis. Defaults to \c{2.0}.
+ *
+ * \note Has no effect on Bars3D.
+ *
+ * \sa horizontalAspectRatio
+ */
+
+/*!
+ * \qmlproperty real AbstractGraph3D::horizontalAspectRatio
+ *
+ * The ratio of the graph scaling between the x-axis and z-axis.
+ * The value of \c 0.0 indicates automatic scaling according to axis ranges.
+ * Defaults to \c{0.0}.
+ *
+ * \note Has no effect on Bars3D, which handles scaling on the horizontal plane via the
+ * \l{Bars3D::barThickness}{barThickness} and \l{Bars3D::barSpacing}{barSpacing} properties.
+ * Polar graphs also ignore this property.
+ *
+ * \sa aspectRatio, polar, Bars3D::barThickness, Bars3D::barSpacing
+ */
+
+/*!
+ * \qmlproperty AbstractGraph3D.OptimizationHints AbstractGraph3D::optimizationHints
+ *
+ * Whether the default or static mode is used for rendering optimization.
+ *
+ * The default mode provides the full feature set at a reasonable level of
+ * performance. The static mode optimizes graph rendering and is ideal for
+ * large non-changing data sets. It is slower with dynamic data changes and item rotations.
+ * Selection is not optimized, so using the static mode with massive data sets is not advisable.
+ * Static optimization works only on scatter graphs.
+ * Defaults to \l{QAbstract3DGraph::OptimizationDefault}{OptimizationDefault}.
+ *
+ * \note On some environments, large graphs using static optimization may not render, because
+ * all of the items are rendered using a single draw call, and different graphics drivers
+ * support different maximum vertice counts per call.
+ * This is mostly an issue on 32bit and OpenGL ES2 platforms.
+ * To work around this issue, choose an item mesh with a low vertex count or use
+ * the point mesh.
+ *
+ * \sa Abstract3DSeries::mesh, QAbstract3DGraph::OptimizationHint
+ */
+
+/*!
+ * \qmlproperty bool AbstractGraph3D::reflection
+ *
+ * Sets floor reflections on or off. Defaults to \c{false}.
+ *
+ * \note Affects only Bars3D. However, in Bars3D graphs holding both positive
+ * and negative values, reflections are not supported
+ * for custom items that intersect the floor plane. In that case, reflections should be turned off
+ * to avoid incorrect rendering.
+ *
+ * \sa reflectivity
+ */
+
+/*!
+ * \qmlproperty real AbstractGraph3D::reflectivity
+ *
+ * Sets floor reflectivity. Larger numbers make the floor more reflective. The
+ * valid range is \c{[0...1]}.
+ * Defaults to \c{0.5}.
+ *
+ * \note Affects only Bars3D.
+ *
+ * \sa reflection
+ */
+
+/*!
+ * \qmlproperty locale AbstractGraph3D::locale
+ *
+ * Sets the locale used for formatting various numeric labels.
+ * Defaults to the \c{"C"} locale.
+ *
+ * \sa ValueAxis3D::labelFormat
+ */
+
+/*!
+ * \qmlproperty vector3d AbstractGraph3D::queriedGraphPosition
+ *
+ * This read-only property contains the latest graph position values along each axis queried using
+ * Scene3D::graphPositionQuery. The values are normalized to range \c{[-1, 1]}.
+ * If the queried position was outside the graph bounds, the values
+ * will not reflect the real position, but will instead be some undefined position outside
+ * the range \c{[-1, 1]}. The value will be undefined until a query is made.
+ *
+ * There is no single correct 3D coordinate to match a particular screen position, so to be
+ * consistent, the queries are always done against the inner sides of an invisible box surrounding
+ * the graph.
+ *
+ * \note Bar graphs only allow querying graph position at the graph floor level,
+ * so the y-value is always zero for bar graphs and valid queries can be only made at
+ * screen positions that contain the floor of the graph.
+ *
+ * \sa Scene3D::graphPositionQuery
+ */
+
+/*!
+ * \qmlproperty real AbstractGraph3D::margin
+ *
+ * The absolute value used for the space left between the edge of the
+ * plottable graph area and the edge of the graph background.
+ *
+ * If the margin value is negative, the margins are determined automatically and can vary according
+ * to the size of the items in the series and the type of the graph.
+ * The value is interpreted as a fraction of the y-axis range if the graph
+ * aspect ratios have not beed changed from the default values.
+ * Defaults to \c{-1.0}.
+ *
+ * \note Setting a smaller margin for a scatter graph than the automatically
+ * determined margin can cause the scatter items at the edges of the graph to
+ * overlap with the graph background.
+ *
+ * \note On scatter and surface graphs, if the margin is small in comparison to the axis label
+ * size, the positions of the edge labels of the axes are adjusted to avoid overlap with
+ * the edge labels of the neighboring axes.
+ */
diff --git a/src/graphs/doc/src/qtgraphs-qml-bars3d.qdoc b/src/graphs/doc/src/qtgraphs-qml-bars3d.qdoc
new file mode 100644
index 0000000..819ac0b
--- /dev/null
+++ b/src/graphs/doc/src/qtgraphs-qml-bars3d.qdoc
@@ -0,0 +1,160 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ * \qmltype Bars3D
+ * \inherits AbstractGraph3D
+ * \inqmlmodule QtGraphs
+ * \ingroup graphs_qml
+ * \brief 3D bar graph.
+ *
+ * This type enables developers to render bar graphs in 3D with Qt Quick.
+ *
+ * You will need to import Qt Graphs module to use this type:
+ *
+ * \snippet doc_src_qmlgraphs.cpp 0
+ *
+ * After that you can use Bars3D in your qml files:
+ *
+ * \snippet doc_src_qmlgraphs.cpp 1
+ *
+ * See \l{Qt Quick 2 Bars Example} for more thorough usage example.
+ *
+ * \sa Bar3DSeries, ItemModelBarDataProxy, Scatter3D, Surface3D, {Qt Graphs C++ Classes}
+ */
+
+/*!
+ * \qmlproperty CategoryAxis3D Bars3D::rowAxis
+ * The active row axis.
+ *
+ * If an axis is not given, a temporary default axis with no labels is created.
+ * This temporary axis is destroyed if another axis is explicitly set to the
+ * same orientation.
+ */
+
+/*!
+ * \qmlproperty ValueAxis3D Bars3D::valueAxis
+ * The active value axis.
+ *
+ * If an axis is not given, a temporary default axis with no labels and an
+ * automatically adjusting range is created.
+ * This temporary axis is destroyed if another axis is explicitly set to the
+ * same orientation.
+ */
+
+/*!
+ * \qmlproperty CategoryAxis3D Bars3D::columnAxis
+ * The active column axis.
+ *
+ * If an axis is not given, a temporary default axis with no labels is created.
+ * This temporary axis is destroyed if another axis is explicitly set to the
+ * same orientation.
+ */
+
+/*!
+ * \qmlproperty bool Bars3D::multiSeriesUniform
+ * Defines whether bars are to be scaled with proportions set to a single series bar even
+ * if there are multiple series displayed. If set to \c {true}, \l{barSpacing}{bar spacing} will
+ * be correctly applied only to the X-axis. Preset to \c false by default.
+ */
+
+/*!
+ * \qmlproperty real Bars3D::barThickness
+ * The bar thickness ratio between the X and Z dimensions. The value \c 1.0
+ * means that the bars are as wide as they are deep, whereas \c 0.5
+ * makes them twice as deep as they are wide.
+ */
+
+/*!
+ * \qmlproperty size Bars3D::barSpacing
+ * Bar spacing in X and Z dimensions.
+ *
+ * Preset to \c {(1.0, 1.0)} by default. Spacing is affected by the
+ * barSpacingRelative property.
+ */
+
+/*!
+ * \qmlproperty bool Bars3D::barSpacingRelative
+ * Whether spacing is absolute or relative to bar thickness.
+ *
+ * If \c true, the value of \c 0.0 means that the bars are placed
+ * side-to-side, \c 1.0 means that a space as wide as the thickness of one bar
+ * is left between the bars, and so on. Preset to \c true.
+ */
+
+/*!
+ * \qmlproperty size Bars3D::barSeriesMargin
+ * \since 6.3
+ *
+ * Margin between series columns in X and Z dimensions. Preset to \c {(0.0, 0.0)} by default.
+ * Sensible values are on the range [0,1).
+ */
+
+/*!
+ * \qmlproperty Bar3DSeries Bars3D::selectedSeries
+ * The selected series or \c null. If \l {QAbstract3DGraph::selectionMode}{selectionMode} has
+ * the \c SelectionMultiSeries flag set, this property holds the series that
+ * owns the selected bar.
+ */
+
+/*!
+ * \qmlproperty list<Bar3DSeries> Bars3D::seriesList
+ * \qmldefault
+ * The series of the graph.
+ * By default, this property contains an empty list.
+ * To set the series, either use the addSeries() function or define them as children of the graph.
+ */
+
+/*!
+ * \qmlproperty Bar3DSeries Bars3D::primarySeries
+ * The primary series of the graph. It
+ * is used to determine the row and column axis labels when the labels are not explicitly
+ * set to the axes.
+ *
+ * If the specified series is not yet added to the graph, setting it as the
+ * primary series will also implicitly add it to the graph.
+ *
+ * If the primary series itself is removed from the graph, this property
+ * resets to default.
+ *
+ * If the series is null, this property resets to default.
+ * Defaults to the first added series or zero if no series are added to the graph.
+ */
+
+/*!
+ * \qmlproperty real Bars3D::floorLevel
+ *
+ * The floor level for the bar graph in Y-axis data coordinates.
+ *
+ * The actual floor level will be restricted by the Y-axis minimum and maximum
+ * values.
+ * Defaults to zero.
+ */
+
+/*!
+ * \qmlmethod void Bars3D::addSeries(Bar3DSeries series)
+ * Adds the \a series to the graph. A graph can contain multiple series, but only one set of axes,
+ * so the rows and columns of all series must match for the visualized data to be meaningful.
+ * If the graph has multiple visible series, only the first one added will
+ * generate the row or column labels on the axes in cases where the labels are not explicitly set
+ * to the axes. If the newly added series has specified a selected bar, it will be highlighted and
+ * any existing selection will be cleared. Only one added series can have an active selection.
+ * \sa AbstractGraph3D::hasSeries()
+ */
+
+/*!
+ * \qmlmethod void Bars3D::removeSeries(Bar3DSeries series)
+ * Remove the \a series from the graph.
+ * \sa AbstractGraph3D::hasSeries()
+ */
+
+/*!
+ * \qmlmethod void Bars3D::insertSeries(int index, Bar3DSeries series)
+ * Inserts the \a series into the position \a index in the series list.
+ * If the \a series has already been added to the list, it is moved to the
+ * new \a index.
+ * \note When moving a series to a new \a index that is after its old index,
+ * the new position in list is calculated as if the series was still in its old
+ * index, so the final index is actually the \a index decremented by one.
+ * \sa AbstractGraph3D::hasSeries()
+ */
diff --git a/src/graphs/doc/src/qtgraphs-qml-color.qdoc b/src/graphs/doc/src/qtgraphs-qml-color.qdoc
new file mode 100644
index 0000000..0ecdc27
--- /dev/null
+++ b/src/graphs/doc/src/qtgraphs-qml-color.qdoc
@@ -0,0 +1,19 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ \qmltype ThemeColor
+ \inqmlmodule QtGraphs
+ \ingroup graphs_qml
+ \brief Defines the color in a Theme3D.
+
+ Defines a color in Theme3D::baseColors property of Theme3D.
+*/
+
+/*!
+ \qmlproperty color ThemeColor::color
+
+ The color property describes the color of this ThemeColor.
+
+ The default color is black.
+*/
diff --git a/src/graphs/doc/src/qtgraphs-qml-colorgradient.qdoc b/src/graphs/doc/src/qtgraphs-qml-colorgradient.qdoc
new file mode 100644
index 0000000..f811afc
--- /dev/null
+++ b/src/graphs/doc/src/qtgraphs-qml-colorgradient.qdoc
@@ -0,0 +1,64 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ \qmltype ColorGradientStop
+ \inqmlmodule QtGraphs
+ \ingroup graphs_qml
+ \brief Defines the color at a position in ColorGradient.
+
+ Defines the color at a position in a ColorGradient.
+
+ \sa ColorGradient
+*/
+
+/*!
+ \qmlproperty real ColorGradientStop::position
+
+ The position property describes the position of this gradient stop.
+
+ The default position is 0.0.
+
+ \sa ColorGradient
+*/
+
+/*!
+ \qmlproperty color ColorGradientStop::color
+
+ The color property describes the color color of this gradient stop.
+
+ The default color is black.
+
+ \sa ColorGradient
+*/
+
+/*!
+ \qmltype ColorGradient
+ \inqmlmodule QtGraphs
+ \ingroup graphs_qml
+ \brief Defines a color gradient.
+
+ A gradient is defined by two or more colors, which will be blended seamlessly.
+
+ The colors are specified as a set of ColorGradientStop child items, each of
+ which defines a position on the gradient from 0.0 to 1.0 and a color.
+ The position of each ColorGradientStop is defined by setting its
+ \l{ColorGradientStop::}{position} property; its color is defined using its
+ \l{ColorGradientStop::}{color} property.
+
+ A gradient without any gradient stops falls back to QLinearGradient default,
+ which is black at 0.0 and white at 1.0.
+
+ \sa ColorGradientStop
+*/
+
+/*!
+ \qmlproperty list<ColorGradientStop> ColorGradient::stops
+ \qmldefault
+
+ This property holds the gradient stops describing the gradient.
+
+ By default, this property contains an empty list.
+
+ To set the gradient stops, define them as children of the ColorGradient.
+*/
diff --git a/src/graphs/doc/src/qtgraphs-qml-scatter3d.qdoc b/src/graphs/doc/src/qtgraphs-qml-scatter3d.qdoc
new file mode 100644
index 0000000..27c192a
--- /dev/null
+++ b/src/graphs/doc/src/qtgraphs-qml-scatter3d.qdoc
@@ -0,0 +1,82 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ \qmltype Scatter3D
+ \inherits AbstractGraph3D
+ \inqmlmodule QtGraphs
+ \ingroup graphs_qml
+ \brief 3D scatter graph.
+
+ This type enables developers to render scatter graphs in 3D with Qt Quick.
+
+ You will need to import Qt Graphs module to use this type:
+
+ \snippet doc_src_qmlgraphs.cpp 0
+
+ After that you can use Scatter3D in your qml files:
+
+ \snippet doc_src_qmlgraphs.cpp 2
+
+ See \l{Qt Quick 2 Scatter Example} for more thorough usage example.
+
+ \sa Scatter3DSeries, ScatterDataProxy, Bars3D, Surface3D, {Qt Graphs C++ Classes}
+ */
+
+/*!
+ \qmlproperty ValueAxis3D Scatter3D::axisX
+ The active x-axis.
+
+ If an axis is not given, a temporary default axis with no labels and an
+ automatically adjusting range is created.
+ This temporary axis is destroyed if another axis is explicitly set to the same
+ orientation.
+ */
+
+/*!
+ \qmlproperty ValueAxis3D Scatter3D::axisY
+ The active y-axis.
+
+ If an axis is not given, a temporary default axis with no labels and an
+ automatically adjusting range is created.
+ This temporary axis is destroyed if another axis is explicitly set to the same
+ orientation.
+ */
+
+/*!
+ \qmlproperty ValueAxis3D Scatter3D::axisZ
+ The active z-axis.
+
+ If an axis is not given, a temporary default axis with no labels and an
+ automatically adjusting range is created.
+ This temporary axis is destroyed if another axis is explicitly set to the same
+ orientation.
+ */
+
+/*!
+ * \qmlproperty Scatter3DSeries Scatter3D::selectedSeries
+ * The selected series or null.
+ */
+
+/*!
+ * \qmlproperty list<Scatter3DSeries> Scatter3D::seriesList
+ * \qmldefault
+ * This property holds the series of the graph.
+ * By default, this property contains an empty list.
+ * To set the series, either use the addSeries() method or define them as
+ * children of the graph.
+ */
+
+/*!
+ * \qmlmethod void Scatter3D::addSeries(Scatter3DSeries series)
+ * Adds the \a series to the graph. A graph can contain multiple series, but has only one set of
+ * axes. If the newly added series has specified a selected item, it will be highlighted and
+ * any existing selection will be cleared. Only one added series can have an active selection.
+ * \sa AbstractGraph3D::hasSeries()
+ */
+
+/*!
+ * \qmlmethod void Scatter3D::removeSeries(Scatter3DSeries series)
+ * Remove the \a series from the graph.
+ * \sa AbstractGraph3D::hasSeries()
+ */
diff --git a/src/graphs/doc/src/qtgraphs-qml-surface3d.qdoc b/src/graphs/doc/src/qtgraphs-qml-surface3d.qdoc
new file mode 100644
index 0000000..d774191
--- /dev/null
+++ b/src/graphs/doc/src/qtgraphs-qml-surface3d.qdoc
@@ -0,0 +1,92 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ \qmltype Surface3D
+ \inherits AbstractGraph3D
+ \inqmlmodule QtGraphs
+ \ingroup graphs_qml
+ \brief Describes the usage of the 3D surface graph.
+
+ This type enables developers to render surface plots in 3D with Qt Quick.
+
+ You will need to import the Qt Graphs module to use this type:
+
+ \snippet doc_src_qmlgraphs.cpp 0
+
+ After that you can use Surface3D in your qml files:
+
+ \snippet doc_src_qmlgraphs.cpp 3
+
+ See \l{Qt Quick 2 Surface Example} for more thorough usage example.
+
+ \sa Surface3DSeries, ItemModelSurfaceDataProxy, Bars3D, Scatter3D, {Qt Graphs C++ Classes}
+ */
+
+/*!
+ \qmlproperty ValueAxis3D Surface3D::axisX
+ The active x-axis.
+
+ If an axis is not given, a temporary default axis with no labels and an
+ automatically adjusting range is created.
+ This temporary axis is destroyed if another axis is explicitly set to the same orientation.
+ */
+
+/*!
+ \qmlproperty ValueAxis3D Surface3D::axisY
+ The active y-axis.
+
+ If an axis is not given, a temporary default axis with no labels and an
+ automatically adjusting range is created.
+ This temporary axis is destroyed if another axis is explicitly set to the same orientation.
+ */
+
+/*!
+ \qmlproperty ValueAxis3D Surface3D::axisZ
+ The active z-axis.
+
+ If an axis is not given, a temporary default axis with no labels and an
+ automatically adjusting range is created.
+ This temporary axis is destroyed if another axis is explicitly set to the same orientation.
+ */
+
+/*!
+ * \qmlproperty Surface3DSeries Surface3D::selectedSeries
+ * The selected series or null. If \l {QAbstract3DGraph::selectionMode}{selectionMode} has the
+ * \c SelectionMultiSeries flag set, this property holds the series which owns the selected point.
+ */
+
+/*!
+ * \qmlproperty list<Surface3DSeries> Surface3D::seriesList
+ * \qmldefault
+ * This property holds the series of the graph.
+ * By default, this property contains an empty list.
+ * To set the series, either use the addSeries() function or define them as children of the graph.
+ */
+
+/*!
+ * \qmlproperty bool Surface3D::flipHorizontalGrid
+ *
+ * In some use cases the horizontal axis grid is mostly covered by the surface, so it can be more
+ * useful to display the horizontal axis grid on top of the graph rather than on the bottom.
+ * A typical use case for this is showing 2D spectrograms using orthoGraphic projection with
+ * a top-down viewpoint.
+ *
+ * If \c{false}, the horizontal axis grid and labels are drawn on the horizontal background
+ * of the graph.
+ * If \c{true}, the horizontal axis grid and labels are drawn on the opposite side of the graph
+ * from the horizontal background.
+ * Defaults to \c{false}.
+ */
+
+/*!
+ * \qmlmethod void Surface3D::addSeries(Surface3DSeries series)
+ * Adds the \a series to the graph.
+ * \sa AbstractGraph3D::hasSeries()
+ */
+
+/*!
+ * \qmlmethod void Surface3D::removeSeries(Surface3DSeries series)
+ * Removes the \a series from the graph.
+ * \sa AbstractGraph3D::hasSeries()
+ */
diff --git a/src/graphs/doc/src/qtgraphs.qdoc b/src/graphs/doc/src/qtgraphs.qdoc
new file mode 100644
index 0000000..8d5fb1c
--- /dev/null
+++ b/src/graphs/doc/src/qtgraphs.qdoc
@@ -0,0 +1,250 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ \module QtGraphs
+ \title Qt Graphs C++ Classes
+ \ingroup modules
+ \qtcmakepackage graphs
+ \qtvariable graphs
+
+ \brief C++ classes for the Qt Graphs API.
+
+ The Qt Graphs functionality can be accessed via these C++ classes.
+*/
+
+/*!
+ \qmlmodule QtGraphs 1.\QtMinorVersion
+ \title Qt Graphs QML Types
+ \ingroup qmlmodules
+
+ \brief QML types for the Qt Graphs API.
+
+ The Qt Graphs functionality can be accessed via these QML types.
+
+ \section1 QML Types
+*/
+
+/*!
+ \group graphs_examples
+ \ingroup all-examples
+ \title Qt Graphs Examples
+
+ \brief Examples for the Qt Graphs.
+
+ For some code examples, see one of the Qt Graphs examples:
+
+ \section1 Examples
+
+ \annotatedlist qtgraphs_examples
+*/
+
+/*!
+ \page qtgraphs_data_handling.html
+ \title Qt Graphs Data Handling
+
+ \section1 Series
+
+ A series is a combination of a logically connected set of data items (handled by a data proxy)
+ and visual properties that describe how the data items should be rendered, such as item
+ meshes and colors. Each graphs type has its own series type. For example, bar graphs
+ use QBar3DSeries. All graphs can have multiple series added simultaneously.
+
+ This code snippet shows how to use QBar3DSeries to render bars as cylinders and with a
+ gradient instead of a uniform color:
+
+ \snippet doc_src_qtgraphs.cpp 11
+
+ \section1 Data Proxies
+
+ The data that users wish to visualize comes in many formats, all of which cannot obviously be
+ directly supported. Therefore, Qt Graphs implements data proxies into which
+ user can feed their data in a known format. Each graphs type has a basic proxy type,
+ which takes data in a format suitable for that graph.
+ For example, the basic proxy for QBar3DSeries is QBarDataProxy, which stores rows of QBarDataItem
+ objects. Each QBarDataItem stores a single bar value. Additional typedefs are provided for
+ \c QBarDataArray and \c QBarDataRow containers.
+
+ This code snippet shows how to use basic proxy when your data is stored in some hypothetical
+ \c myData object:
+
+ \snippet doc_src_qtgraphs.cpp 10
+
+ \note Series objects can own only a single proxy at a time. The existing proxy is deleted
+ when another is set to the series. Graphs can contain multiple series, though.
+ If you need to switch back and forth between two different sets of data,
+ it is usually more efficient to store each set in a different series and just change the series,
+ rather than reset the data in one proxy every time you need to switch.
+
+ \section1 Item Models and Data Mapping
+
+ For common use cases, Qt Graphs offers specialized proxies. One such case is having
+ data in an item model (QAbstractItemModel subclass), which is a common way to store data in
+ Qt applications. Each of the graphs types offers a special proxy class for this purpose,
+ for example, QItemModelBarDataProxy for QBar3DSeries.
+ These proxies are simple to use: just give them a pointer to the item model containing the
+ data and the rules how to map the data into format the basic proxy can digest.
+
+ Mapping works with item model roles. Each data item in the model can have different
+ values for different roles. For example, with QItemModelBarDataProxy you can specify which
+ role is used to determine which row the item belongs to, which role does the same for columns,
+ and which role specifies the value of the item. When the proxy resolves the data from the model,
+ it uses these mappings to generate the rows and columns of the bar graph.
+
+ Often the item models will have a single role that contains information you want to map to
+ multiple values. A typical example of this is a timestamp field when generating a bar graph
+ with two time related axes, for example years and months. To enable mapping a single item
+ model role to more than one data field, pattern matching and replacing mechanism is provided
+ by item model proxies. You can also use this mechanism to reformat data even in one-to-one
+ mapping cases.
+
+ Depending on the grpahs type, proxies may support other functionalities as well,
+ such as QItemModelBarDataProxy optionally mapping QAbstractItemModel rows and columns directly
+ into bar graph rows and columns.
+
+ See individual proxy classes for more information and examples
+ about how to use them: QItemModelBarDataProxy, QItemModelScatterDataProxy, and
+ QItemModelSurfaceDataProxy.
+
+ \section1 Other Custom Proxies
+
+ QHeightMapSurfaceDataProxy is a specialized proxy for generating a surface graph from a
+ heightmap image. See the QHeightMapSurfaceDataProxy documentation for more information.
+
+ The \l{Custom Proxy Example}{Custom Proxy} example shows how a custom proxy can be created. It
+ defines a custom data set based on variant lists and an extension of the basic proxy to resolve
+ that data with an associated mapper.
+
+ \section1 Dealing with Real-time Data
+
+ When you have a data set that updates rapidly, it is important to handle data properly to
+ ensure good performance. Since memory allocation is a costly operation, always use
+ QList::reserve() and QList::resize() where possible to avoid unnecessary reallocations when
+ constructing the array to give to the proxy. If you need to change the entire data set
+ for each frame, it is in most cases best to reuse the existing array - especially if the
+ array dimensions do not change. If you need to add, insert, remove, or change several
+ rows or items for each frame, it is always more efficient to do it with one method call
+ instead of multiple calls affecting a single row or item each. For example, adding ten
+ rows with a single QBarDataProxy::addRows() call is much more efficient than ten
+ separate QBarDataProxy::addRow() calls.
+
+ Bars renderer is optimized to access only data that is within the data window and thus
+ should not suffer noticeable slowdown even if more data is continually added to the proxy.
+
+ Due to the unsorted nature of the scatter data, any change in the data window ranges requires
+ all data points to be checked for visibility, which can cause increasing slowdown if data is
+ continually added to the proxy. For the best performance with the scatter graphs, only keep
+ the data you need in the proxy.
+
+ Surface data, while on item level similar to scatter data, is already assigned into rows and
+ columns, so the surface renderer can optimize drawing by making the assumption that
+ the data in the rows and columns is sorted along their respective axes. It is not quite as
+ efficient as in the bars case, but nearly so.
+*/
+
+/*!
+ \page qtgraphs_interacting_with_data.html
+ \title Qt Graphs Interacting with Data
+
+ \section1 Interacting with Data
+
+ End users can interact with the rendered graph by using either the mouse or
+ touch to rotate, zoom, or select data. Graphs can be rotated freely by
+ holding down the right mouse button and moving the mouse. Zooming is done by
+ rolling the mouse wheel. Selecting, if enabled, is done by pressing the left
+ mouse button. The scene can be reset to the default camera view by clicking
+ the mouse wheel. In touch devices, rotation is done by tap-and-move,
+ selection by tap-and-hold, and zoom by pinch.
+
+ Qt Graphs has default handlers for mouse actions and touch
+ gestures. For the default mouse controls, see Q3DInputHandler, and for
+ the default touch controls, see QTouch3DInputHandler. The default handlers
+ must be disabled when using customized input handlers.
+
+ The \l{Custom Input Example} illustrates how to use a custom input handler
+ to select items upon mouseover instead of mouse click. The information
+ below the mouse cursor is displayed as a popup.
+
+ In addition to perspective projection, orthographic projection can be used
+ to create 2D graphs by replacing the default input handler with one that
+ does not allow rotating the graph and setting the camera to view the graph
+ directly from the side or from the top.
+
+ \section1 Data Selection Modes
+
+ All graphs types support selecting a single data item - a bar, a scatter item, or a surface
+ vertex - using mouse, touch, and programmatically via the series APIs. The selected item is highlighted
+ in the rendered graph, and selecting causes emission of a series specific signal for this purpose,
+ for example, QBar3DSeries::selectedBarChanged(), which the application can handle.
+
+ Bar and surface graphs support slice selection modes, where the selected row or column is drawn
+ in a separate viewport as a pseudo-2D graph. This makes it easier to see the actual values of
+ a single row or column.
+
+ Bar graph additionally supports simply highlighting the whole row and/or column of the selected bar
+ without opening the slice view. Bar graph also supports selecting/slicing a whole row and/or
+ column by clicking the axis label, based on selection mode.
+
+ When multiple series are added to a graph, selecting an item in one of them will clear the selection
+ on other series.
+*/
+
+/*!
+ \page qtgraphs_known_issues.html
+ \title Qt Graphs Known Issues
+
+ \list
+ \li As OpenGL is not necessarily the default rendering backend anymore in Qt 6.x (it is
+ Metal on macOS and Direct3D on Windows, for example), it is necessary to define
+ the rendering backend explicitly either on your environment variables, or in your
+ application main. It can be defined by adding \c {qputenv("QSG_RHI_BACKEND", "opengl");}
+ in the beginning of your main function.
+ \li Some platforms like Android and WinRT cannot handle multiple native windows properly,
+ so only the Qt Quick 2 graphs are available in practice for those platforms.
+ \li Surfaces with non-straight rows and columns do not always render properly.
+ \li Q3DLight class (and Light3D QML item) are currently not usable for anything.
+ \li Changing most of Q3DScene properties affecting subviewports currently has no effect.
+ \li Widget based examples layout incorrectly in iOS.
+ \li Reparenting a graph to an item in another QQuickWindow is not supported.
+ \li Android builds of QML applications importing QtGraphs also require
+ "QT += graphs" in the pro file. This is because Qt Graphs
+ QML plugin has a dependency to Qt Graphs C++ library, which Qt Creator
+ doesn't automatically add to the deployment package.
+ \endlist
+*/
+
+/*!
+ * \fn QSurfaceFormat qDefaultSurfaceFormat(bool antialias)
+ * \relates QAbstract3DGraph
+ *
+ * This convenience function can be used to create a custom surface format suitable for use by
+ * Qt Graphs graphs.
+ *
+ * The \a antialias parameter specifies whether or not antialiasing is activated.
+ *
+ * Give the surface format returned by this function to the graph constructor (C++) or set
+ * it as the window format for QQuickView (QML) before calling \c show() on it.
+ *
+ * For example, disable antialiasing on C++ application:
+ *
+ * \code
+ * #include <QtGraphs/qutils.h>
+ *
+ * // ...
+ *
+ * Q3DBars *graph = new Q3DBars(qDefaultSurfaceFormat(false));
+ * \endcode
+ *
+ * For example, enable antialiasing for direct rendering modes on QML application:
+ *
+ * \code
+ * #include <QtGraphs/qutils.h>
+ *
+ * // ...
+ *
+ * QQuickView viewer;
+ * viewer.setFormat(qDefaultSurfaceFormat(true));
+ * \endcode
+ *
+ * \note Antialiasing is not supported in OpenGL ES2 environments.
+ */
diff --git a/src/graphs/engine/abstract3dcontroller.cpp b/src/graphs/engine/abstract3dcontroller.cpp
new file mode 100644
index 0000000..adf349c
--- /dev/null
+++ b/src/graphs/engine/abstract3dcontroller.cpp
@@ -0,0 +1,1290 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "abstract3dcontroller_p.h"
+#include "qabstract3daxis_p.h"
+#include "qvalue3daxis_p.h"
+#include "qcategory3daxis_p.h"
+#include "qquickgraphsitem_p.h"
+#include "qabstract3dseries_p.h"
+#include "q3dscene_p.h"
+#include "qabstract3dinputhandler_p.h"
+#include "qtouch3dinputhandler.h"
+#include "thememanager_p.h"
+#include "q3dtheme_p.h"
+#include "qcustom3ditem_p.h"
+#include "utils_p.h"
+#include <QtCore/QThread>
+#include <QtOpenGL/QOpenGLFramebufferObject>
+#include <QtCore/QMutexLocker>
+
+QT_BEGIN_NAMESPACE
+
+Abstract3DController::Abstract3DController(QRect initialViewport, Q3DScene *scene,
+ QObject *parent) :
+ QObject(parent),
+ m_themeManager(new ThemeManager(this)),
+ m_selectionMode(QAbstract3DGraph::SelectionItem),
+ m_shadowQuality(QAbstract3DGraph::ShadowQualityMedium),
+ m_useOrthoProjection(false),
+ m_aspectRatio(2.0),
+ m_horizontalAspectRatio(0.0),
+ m_optimizationHints(QAbstract3DGraph::OptimizationDefault),
+ m_reflectionEnabled(false),
+ m_reflectivity(0.5),
+ m_locale(QLocale::c()),
+ m_scene(scene),
+ m_activeInputHandler(0),
+ m_axisX(0),
+ m_axisY(0),
+ m_axisZ(0),
+ m_isDataDirty(true),
+ m_isCustomDataDirty(true),
+ m_isCustomItemDirty(true),
+ m_isSeriesVisualsDirty(true),
+ m_renderPending(false),
+ m_isPolar(false),
+ m_radialLabelOffset(1.0f),
+ m_measureFps(false),
+ m_numFrames(0),
+ m_currentFps(0.0),
+ m_clickedType(QAbstract3DGraph::ElementNone),
+ m_selectedLabelIndex(-1),
+ m_selectedCustomItemIndex(-1),
+ m_margin(-1.0)
+{
+ if (!m_scene)
+ m_scene = new Q3DScene;
+ m_scene->setParent(this);
+
+ // Set initial theme
+ Q3DTheme *defaultTheme = new Q3DTheme(Q3DTheme::ThemeQt);
+ defaultTheme->d_ptr->setDefaultTheme(true);
+ setActiveTheme(defaultTheme);
+
+ m_scene->d_ptr->setViewport(initialViewport);
+ m_scene->activeLight()->setAutoPosition(true);
+
+ // Create initial default input handler
+ QAbstract3DInputHandler *inputHandler;
+ inputHandler = new QTouch3DInputHandler();
+ inputHandler->d_ptr->m_isDefaultHandler = true;
+ setActiveInputHandler(inputHandler);
+ connect(m_scene->d_ptr.data(), &Q3DScenePrivate::needRender, this,
+ &Abstract3DController::emitNeedRender);
+}
+
+Abstract3DController::~Abstract3DController()
+{
+ delete m_scene;
+ delete m_themeManager;
+ foreach (QCustom3DItem *item, m_customItems)
+ delete item;
+ m_customItems.clear();
+}
+
+void Abstract3DController::addSeries(QAbstract3DSeries *series)
+{
+ insertSeries(m_seriesList.size(), series);
+}
+
+void Abstract3DController::insertSeries(int index, QAbstract3DSeries *series)
+{
+ if (series) {
+ if (m_seriesList.contains(series)) {
+ int oldIndex = m_seriesList.indexOf(series);
+ if (index != oldIndex) {
+ m_seriesList.removeOne(series);
+ if (oldIndex < index)
+ index--;
+ m_seriesList.insert(index, series);
+ }
+ } else {
+ int oldSize = m_seriesList.size();
+ m_seriesList.insert(index, series);
+ series->d_ptr->setController(this);
+ QObject::connect(series, &QAbstract3DSeries::visibilityChanged,
+ this, &Abstract3DController::handleSeriesVisibilityChanged);
+ series->d_ptr->resetToTheme(*m_themeManager->activeTheme(), oldSize, false);
+ }
+ if (series->isVisible())
+ handleSeriesVisibilityChangedBySender(series);
+ }
+}
+
+void Abstract3DController::removeSeries(QAbstract3DSeries *series)
+{
+ if (series && series->d_ptr->m_controller == this) {
+ m_seriesList.removeAll(series);
+ QObject::disconnect(series, &QAbstract3DSeries::visibilityChanged,
+ this, &Abstract3DController::handleSeriesVisibilityChanged);
+ series->d_ptr->setController(0);
+ m_isDataDirty = true;
+ m_isSeriesVisualsDirty = true;
+ emitNeedRender();
+ }
+}
+
+bool Abstract3DController::hasSeries(QAbstract3DSeries *series)
+{
+ return m_seriesList.contains(series);
+}
+
+QList<QAbstract3DSeries *> Abstract3DController::seriesList()
+{
+ return m_seriesList;
+}
+
+void Abstract3DController::mouseDoubleClickEvent(QMouseEvent *event)
+{
+ if (m_activeInputHandler)
+ m_activeInputHandler->mouseDoubleClickEvent(event);
+}
+
+void Abstract3DController::touchEvent(QTouchEvent *event)
+{
+ if (m_activeInputHandler)
+ m_activeInputHandler->touchEvent(event);
+}
+
+void Abstract3DController::mousePressEvent(QMouseEvent *event, const QPoint &mousePos)
+{
+ if (m_activeInputHandler)
+ m_activeInputHandler->mousePressEvent(event, mousePos);
+}
+
+void Abstract3DController::mouseReleaseEvent(QMouseEvent *event, const QPoint &mousePos)
+{
+ if (m_activeInputHandler)
+ m_activeInputHandler->mouseReleaseEvent(event, mousePos);
+}
+
+void Abstract3DController::mouseMoveEvent(QMouseEvent *event, const QPoint &mousePos)
+{
+ if (m_activeInputHandler)
+ m_activeInputHandler->mouseMoveEvent(event, mousePos);
+}
+
+#if QT_CONFIG(wheelevent)
+void Abstract3DController::wheelEvent(QWheelEvent *event)
+{
+ if (m_activeInputHandler)
+ m_activeInputHandler->wheelEvent(event);
+}
+#endif
+
+void Abstract3DController::handleThemeColorStyleChanged(Q3DTheme::ColorStyle style)
+{
+ // Set value for series that have not explicitly set this value
+ foreach (QAbstract3DSeries *series, m_seriesList) {
+ if (!series->d_ptr->m_themeTracker.colorStyleOverride) {
+ series->setColorStyle(style);
+ series->d_ptr->m_themeTracker.colorStyleOverride = false;
+ }
+ }
+ markSeriesVisualsDirty();
+}
+
+void Abstract3DController::handleThemeBaseColorsChanged(const QList<QColor> &colors)
+{
+ int colorIdx = 0;
+ // Set value for series that have not explicitly set this value
+ foreach (QAbstract3DSeries *series, m_seriesList) {
+ if (!series->d_ptr->m_themeTracker.baseColorOverride) {
+ series->setBaseColor(colors.at(colorIdx));
+ series->d_ptr->m_themeTracker.baseColorOverride = false;
+ }
+ if (++colorIdx >= colors.size())
+ colorIdx = 0;
+ }
+ markSeriesVisualsDirty();
+}
+
+void Abstract3DController::handleThemeBaseGradientsChanged(const QList<QLinearGradient> &gradients)
+{
+ int gradientIdx = 0;
+ // Set value for series that have not explicitly set this value
+ foreach (QAbstract3DSeries *series, m_seriesList) {
+ if (!series->d_ptr->m_themeTracker.baseGradientOverride) {
+ series->setBaseGradient(gradients.at(gradientIdx));
+ series->d_ptr->m_themeTracker.baseGradientOverride = false;
+ }
+ if (++gradientIdx >= gradients.size())
+ gradientIdx = 0;
+ }
+ markSeriesVisualsDirty();
+}
+
+void Abstract3DController::handleThemeSingleHighlightColorChanged(const QColor &color)
+{
+ // Set value for series that have not explicitly set this value
+ foreach (QAbstract3DSeries *series, m_seriesList) {
+ if (!series->d_ptr->m_themeTracker.singleHighlightColorOverride) {
+ series->setSingleHighlightColor(color);
+ series->d_ptr->m_themeTracker.singleHighlightColorOverride = false;
+ }
+ }
+ markSeriesVisualsDirty();
+}
+
+void Abstract3DController::handleThemeSingleHighlightGradientChanged(
+ const QLinearGradient &gradient)
+{
+ // Set value for series that have not explicitly set this value
+ foreach (QAbstract3DSeries *series, m_seriesList) {
+ if (!series->d_ptr->m_themeTracker.singleHighlightGradientOverride) {
+ series->setSingleHighlightGradient(gradient);
+ series->d_ptr->m_themeTracker.singleHighlightGradientOverride = false;
+ }
+ }
+ markSeriesVisualsDirty();
+}
+
+void Abstract3DController::handleThemeMultiHighlightColorChanged(const QColor &color)
+{
+ // Set value for series that have not explicitly set this value
+ foreach (QAbstract3DSeries *series, m_seriesList) {
+ if (!series->d_ptr->m_themeTracker.multiHighlightColorOverride) {
+ series->setMultiHighlightColor(color);
+ series->d_ptr->m_themeTracker.multiHighlightColorOverride = false;
+ }
+ }
+ markSeriesVisualsDirty();
+}
+
+void Abstract3DController::handleThemeMultiHighlightGradientChanged(const QLinearGradient &gradient)
+{
+ // Set value for series that have not explicitly set this value
+ foreach (QAbstract3DSeries *series, m_seriesList) {
+ if (!series->d_ptr->m_themeTracker.multiHighlightGradientOverride) {
+ series->setMultiHighlightGradient(gradient);
+ series->d_ptr->m_themeTracker.multiHighlightGradientOverride = false;
+ }
+ }
+ markSeriesVisualsDirty();
+}
+
+void Abstract3DController::handleThemeTypeChanged(Q3DTheme::Theme theme)
+{
+ Q_UNUSED(theme);
+
+ // Changing theme type is logically equivalent of changing the entire theme
+ // object, so reset all attached series to the new theme.
+ bool force = m_qml->isReady();
+ Q3DTheme *activeTheme = m_themeManager->activeTheme();
+ for (int i = 0; i < m_seriesList.size(); i++)
+ m_seriesList.at(i)->d_ptr->resetToTheme(*activeTheme, i, force);
+
+ markSeriesVisualsDirty();
+
+ emit themeTypeChanged();
+}
+
+void Abstract3DController::setAxisX(QAbstract3DAxis *axis)
+{
+ // Setting null axis will always create new default axis
+ if (!axis || axis != m_axisX) {
+ setAxisHelper(QAbstract3DAxis::AxisOrientationX, axis, &m_axisX);
+ emit axisXChanged(m_axisX);
+ }
+}
+
+QAbstract3DAxis *Abstract3DController::axisX() const
+{
+ return m_axisX;
+}
+
+void Abstract3DController::setAxisY(QAbstract3DAxis *axis)
+{
+ // Setting null axis will always create new default axis
+ if (!axis || axis != m_axisY) {
+ setAxisHelper(QAbstract3DAxis::AxisOrientationY, axis, &m_axisY);
+ emit axisYChanged(m_axisY);
+ }
+}
+
+QAbstract3DAxis *Abstract3DController::axisY() const
+{
+ return m_axisY;
+}
+
+void Abstract3DController::setAxisZ(QAbstract3DAxis *axis)
+{
+ // Setting null axis will always create new default axis
+ if (!axis || axis != m_axisZ) {
+ setAxisHelper(QAbstract3DAxis::AxisOrientationZ, axis, &m_axisZ);
+ emit axisZChanged(m_axisZ);
+ }
+}
+
+QAbstract3DAxis *Abstract3DController::axisZ() const
+{
+ return m_axisZ;
+}
+
+void Abstract3DController::addAxis(QAbstract3DAxis *axis)
+{
+ Q_ASSERT(axis);
+ Abstract3DController *owner = qobject_cast<Abstract3DController *>(axis->parent());
+ if (owner != this) {
+ Q_ASSERT_X(!owner, "addAxis", "Axis already attached to a graph.");
+ axis->setParent(this);
+ }
+ if (!m_axes.contains(axis))
+ m_axes.append(axis);
+}
+
+void Abstract3DController::releaseAxis(QAbstract3DAxis *axis)
+{
+ if (axis && m_axes.contains(axis)) {
+ // Clear the default status from released default axes
+ if (axis->d_ptr->isDefaultAxis())
+ axis->d_ptr->setDefaultAxis(false);
+
+ // If the axis is in use, replace it with a temporary one
+ switch (axis->orientation()) {
+ case QAbstract3DAxis::AxisOrientationX:
+ setAxisX(0);
+ break;
+ case QAbstract3DAxis::AxisOrientationY:
+ setAxisY(0);
+ break;
+ case QAbstract3DAxis::AxisOrientationZ:
+ setAxisZ(0);
+ break;
+ default:
+ break;
+ }
+
+ m_axes.removeAll(axis);
+ axis->setParent(0);
+ }
+}
+
+QList<QAbstract3DAxis *> Abstract3DController::axes() const
+{
+ return m_axes;
+}
+
+void Abstract3DController::addInputHandler(QAbstract3DInputHandler *inputHandler)
+{
+ Q_ASSERT(inputHandler);
+ Abstract3DController *owner = qobject_cast<Abstract3DController *>(inputHandler->parent());
+ if (owner != this) {
+ Q_ASSERT_X(!owner, "addInputHandler",
+ "Input handler already attached to another component.");
+ inputHandler->setParent(this);
+ }
+
+ if (!m_inputHandlers.contains(inputHandler))
+ m_inputHandlers.append(inputHandler);
+}
+
+void Abstract3DController::releaseInputHandler(QAbstract3DInputHandler *inputHandler)
+{
+ if (inputHandler && m_inputHandlers.contains(inputHandler)) {
+ // Clear the default status from released default input handler
+ if (inputHandler->d_ptr->m_isDefaultHandler)
+ inputHandler->d_ptr->m_isDefaultHandler = false;
+
+ // If the input handler is in use, remove it
+ if (m_activeInputHandler == inputHandler)
+ setActiveInputHandler(0);
+
+ m_inputHandlers.removeAll(inputHandler);
+ inputHandler->setParent(0);
+ }
+}
+
+void Abstract3DController::setActiveInputHandler(QAbstract3DInputHandler *inputHandler)
+{
+ if (inputHandler == m_activeInputHandler)
+ return;
+
+ // If existing input handler is the default input handler, delete it
+ if (m_activeInputHandler) {
+ if (m_activeInputHandler->d_ptr->m_isDefaultHandler) {
+ m_inputHandlers.removeAll(m_activeInputHandler);
+ delete m_activeInputHandler;
+ } else {
+ // Disconnect the old input handler
+ m_activeInputHandler->setScene(0);
+ QObject::disconnect(m_activeInputHandler, 0, this, 0);
+ }
+ }
+
+ // Assume ownership and connect to this controller's scene
+ if (inputHandler)
+ addInputHandler(inputHandler);
+
+ m_activeInputHandler = inputHandler;
+ if (m_activeInputHandler) {
+ m_activeInputHandler->setScene(m_scene);
+
+ // Connect the input handler
+ QObject::connect(m_activeInputHandler, &QAbstract3DInputHandler::inputViewChanged, this,
+ &Abstract3DController::handleInputViewChanged);
+ QObject::connect(m_activeInputHandler, &QAbstract3DInputHandler::positionChanged, this,
+ &Abstract3DController::handleInputPositionChanged);
+ }
+
+ // Notify change of input handler
+ emit activeInputHandlerChanged(m_activeInputHandler);
+}
+
+QAbstract3DInputHandler *Abstract3DController::activeInputHandler()
+{
+ return m_activeInputHandler;
+}
+
+QList<QAbstract3DInputHandler *> Abstract3DController::inputHandlers() const
+{
+ return m_inputHandlers;
+}
+
+void Abstract3DController::addTheme(Q3DTheme *theme)
+{
+ m_themeManager->addTheme(theme);
+}
+
+void Abstract3DController::releaseTheme(Q3DTheme *theme)
+{
+ Q3DTheme *oldTheme = m_themeManager->activeTheme();
+
+ m_themeManager->releaseTheme(theme);
+
+ if (oldTheme != m_themeManager->activeTheme())
+ emit activeThemeChanged(m_themeManager->activeTheme());
+}
+
+QList<Q3DTheme *> Abstract3DController::themes() const
+{
+ return m_themeManager->themes();
+}
+
+void Abstract3DController::setActiveTheme(Q3DTheme *theme, bool force)
+{
+ if (theme != m_themeManager->activeTheme()) {
+ m_themeManager->setActiveTheme(theme);
+ m_changeTracker.themeChanged = true;
+ // Default theme can be created by theme manager, so ensure we have correct theme
+ Q3DTheme *newActiveTheme = m_themeManager->activeTheme();
+ // Reset all attached series to the new theme
+ for (int i = 0; i < m_seriesList.size(); i++)
+ m_seriesList.at(i)->d_ptr->resetToTheme(*newActiveTheme, i, force);
+ markSeriesVisualsDirty();
+ emit activeThemeChanged(newActiveTheme);
+ }
+}
+
+Q3DTheme *Abstract3DController::activeTheme() const
+{
+ return m_themeManager->activeTheme();
+}
+
+void Abstract3DController::setSelectionMode(QAbstract3DGraph::SelectionFlags mode)
+{
+ if (mode != m_selectionMode) {
+ m_selectionMode = mode;
+ m_changeTracker.selectionModeChanged = true;
+ emit selectionModeChanged(mode);
+ emitNeedRender();
+ }
+}
+
+QAbstract3DGraph::SelectionFlags Abstract3DController::selectionMode() const
+{
+ return m_selectionMode;
+}
+
+void Abstract3DController::setShadowQuality(QAbstract3DGraph::ShadowQuality quality)
+{
+ if (!m_useOrthoProjection)
+ doSetShadowQuality(quality);
+}
+
+void Abstract3DController::doSetShadowQuality(QAbstract3DGraph::ShadowQuality quality)
+{
+ if (quality != m_shadowQuality) {
+ m_shadowQuality = quality;
+ m_changeTracker.shadowQualityChanged = true;
+ emit shadowQualityChanged(m_shadowQuality);
+ emitNeedRender();
+ }
+}
+
+QAbstract3DGraph::ShadowQuality Abstract3DController::shadowQuality() const
+{
+ return m_shadowQuality;
+}
+
+void Abstract3DController::setOptimizationHints(QAbstract3DGraph::OptimizationHints hints)
+{
+ if (hints != m_optimizationHints) {
+ m_optimizationHints = hints;
+ m_changeTracker.optimizationHintChanged = true;
+ m_isDataDirty = true;
+ emit optimizationHintsChanged(hints);
+ emitNeedRender();
+ }
+}
+
+QAbstract3DGraph::OptimizationHints Abstract3DController::optimizationHints() const
+{
+ return m_optimizationHints;
+}
+
+bool Abstract3DController::shadowsSupported() const
+{
+ return !isOpenGLES();
+}
+
+bool Abstract3DController::isSlicingActive() const
+{
+ return m_scene->isSlicingActive();
+}
+
+void Abstract3DController::setSlicingActive(bool isSlicing)
+{
+ m_scene->setSlicingActive(isSlicing);
+}
+
+Q3DScene *Abstract3DController::scene()
+{
+ return m_scene;
+}
+
+void Abstract3DController::markDataDirty()
+{
+ m_isDataDirty = true;
+
+ markSeriesItemLabelsDirty();
+ emitNeedRender();
+}
+
+void Abstract3DController::markSeriesVisualsDirty()
+{
+ m_isSeriesVisualsDirty = true;
+ emitNeedRender();
+}
+
+int Abstract3DController::addCustomItem(QCustom3DItem *item)
+{
+ if (!item)
+ return -1;
+
+ int index = m_customItems.indexOf(item);
+
+ if (index != -1)
+ return index;
+
+ item->setParent(this);
+ connect(item->d_ptr.data(), &QCustom3DItemPrivate::needUpdate,
+ this, &Abstract3DController::updateCustomItem);
+ m_customItems.append(item);
+ item->d_ptr->resetDirtyBits();
+ m_isCustomDataDirty = true;
+ emitNeedRender();
+ return m_customItems.size() - 1;
+}
+
+void Abstract3DController::deleteCustomItems()
+{
+ foreach (QCustom3DItem *item, m_customItems)
+ delete item;
+ m_customItems.clear();
+ m_isCustomDataDirty = true;
+ emitNeedRender();
+}
+
+void Abstract3DController::deleteCustomItem(QCustom3DItem *item)
+{
+ if (!item)
+ return;
+
+ m_customItems.removeOne(item);
+ delete item;
+ item = 0;
+ m_isCustomDataDirty = true;
+ emitNeedRender();
+}
+
+void Abstract3DController::deleteCustomItem(const QVector3D &position)
+{
+ // Get the item for the position
+ foreach (QCustom3DItem *item, m_customItems) {
+ if (item->position() == position)
+ deleteCustomItem(item);
+ }
+}
+
+void Abstract3DController::releaseCustomItem(QCustom3DItem *item)
+{
+ if (item && m_customItems.contains(item)) {
+ disconnect(item->d_ptr.data(), &QCustom3DItemPrivate::needUpdate,
+ this, &Abstract3DController::updateCustomItem);
+ m_customItems.removeOne(item);
+ item->setParent(0);
+ m_isCustomDataDirty = true;
+ emitNeedRender();
+ }
+}
+
+QList<QCustom3DItem *> Abstract3DController::customItems() const
+{
+ return m_customItems;
+}
+
+void Abstract3DController::updateCustomItem()
+{
+ m_isCustomItemDirty = true;
+ emitNeedRender();
+}
+
+void Abstract3DController::handleAxisTitleChanged(const QString &title)
+{
+ Q_UNUSED(title);
+ handleAxisTitleChangedBySender(sender());
+}
+
+void Abstract3DController::handleAxisTitleChangedBySender(QObject *sender)
+{
+ if (sender == m_axisX)
+ m_changeTracker.axisXTitleChanged = true;
+ else if (sender == m_axisY)
+ m_changeTracker.axisYTitleChanged = true;
+ else if (sender == m_axisZ)
+ m_changeTracker.axisZTitleChanged = true;
+ else
+ qWarning() << __FUNCTION__ << "invoked for invalid axis";
+
+ markSeriesItemLabelsDirty();
+ emitNeedRender();
+}
+
+void Abstract3DController::handleAxisLabelsChanged()
+{
+ handleAxisLabelsChangedBySender(sender());
+}
+
+void Abstract3DController::handleAxisLabelsChangedBySender(QObject *sender)
+{
+ if (sender == m_axisX)
+ m_changeTracker.axisXLabelsChanged = true;
+ else if (sender == m_axisY)
+ m_changeTracker.axisYLabelsChanged = true;
+ else if (sender == m_axisZ)
+ m_changeTracker.axisZLabelsChanged = true;
+ else
+ qWarning() << __FUNCTION__ << "invoked for invalid axis";
+
+ markSeriesItemLabelsDirty();
+ emitNeedRender();
+}
+
+void Abstract3DController::handleAxisRangeChanged(float min, float max)
+{
+ Q_UNUSED(min);
+ Q_UNUSED(max);
+ handleAxisRangeChangedBySender(sender());
+}
+
+void Abstract3DController::handleAxisRangeChangedBySender(QObject *sender)
+{
+ if (sender == m_axisX) {
+ m_isDataDirty = true;
+ m_changeTracker.axisXRangeChanged = true;
+ } else if (sender == m_axisY) {
+ m_isDataDirty = true;
+ m_changeTracker.axisYRangeChanged = true;
+ } else if (sender == m_axisZ) {
+ m_isDataDirty = true;
+ m_changeTracker.axisZRangeChanged = true;
+ } else {
+ qWarning() << __FUNCTION__ << "invoked for invalid axis";
+ }
+ emitNeedRender();
+}
+
+void Abstract3DController::handleAxisSegmentCountChanged(int count)
+{
+ Q_UNUSED(count);
+ handleAxisSegmentCountChangedBySender(sender());
+}
+
+void Abstract3DController::handleAxisSegmentCountChangedBySender(QObject *sender)
+{
+ if (sender == m_axisX)
+ m_changeTracker.axisXSegmentCountChanged = true;
+ else if (sender == m_axisY)
+ m_changeTracker.axisYSegmentCountChanged = true;
+ else if (sender == m_axisZ)
+ m_changeTracker.axisZSegmentCountChanged = true;
+ else
+ qWarning() << __FUNCTION__ << "invoked for invalid axis";
+ emitNeedRender();
+}
+
+void Abstract3DController::handleAxisSubSegmentCountChanged(int count)
+{
+ Q_UNUSED(count);
+ handleAxisSubSegmentCountChangedBySender(sender());
+}
+
+void Abstract3DController::handleAxisSubSegmentCountChangedBySender(QObject *sender)
+{
+ if (sender == m_axisX)
+ m_changeTracker.axisXSubSegmentCountChanged = true;
+ else if (sender == m_axisY)
+ m_changeTracker.axisYSubSegmentCountChanged = true;
+ else if (sender == m_axisZ)
+ m_changeTracker.axisZSubSegmentCountChanged = true;
+ else
+ qWarning() << __FUNCTION__ << "invoked for invalid axis";
+ emitNeedRender();
+}
+
+void Abstract3DController::handleAxisAutoAdjustRangeChanged(bool autoAdjust)
+{
+ QObject *sender = QObject::sender();
+ if (sender != m_axisX && sender != m_axisY && sender != m_axisZ)
+ return;
+
+ QAbstract3DAxis *axis = static_cast<QAbstract3DAxis*>(sender);
+ handleAxisAutoAdjustRangeChangedInOrientation(axis->orientation(), autoAdjust);
+}
+
+void Abstract3DController::handleAxisLabelFormatChanged(const QString &format)
+{
+ Q_UNUSED(format);
+ handleAxisLabelFormatChangedBySender(sender());
+}
+
+void Abstract3DController::handleAxisReversedChanged(bool enable)
+{
+ Q_UNUSED(enable);
+ handleAxisReversedChangedBySender(sender());
+}
+
+void Abstract3DController::handleAxisFormatterDirty()
+{
+ handleAxisFormatterDirtyBySender(sender());
+}
+
+void Abstract3DController::handleAxisLabelAutoRotationChanged(float angle)
+{
+ Q_UNUSED(angle);
+ handleAxisLabelAutoRotationChangedBySender(sender());
+}
+
+void Abstract3DController::handleAxisTitleVisibilityChanged(bool visible)
+{
+ Q_UNUSED(visible);
+ handleAxisTitleVisibilityChangedBySender(sender());
+}
+
+void Abstract3DController::handleAxisTitleFixedChanged(bool fixed)
+{
+ Q_UNUSED(fixed);
+ handleAxisTitleFixedChangedBySender(sender());
+}
+
+void Abstract3DController::handleInputViewChanged(QAbstract3DInputHandler::InputView view)
+{
+ // When in automatic slicing mode, input view change to primary disables slice mode
+ if (m_selectionMode.testFlag(QAbstract3DGraph::SelectionSlice)
+ && view == QAbstract3DInputHandler::InputViewOnPrimary) {
+ setSlicingActive(false);
+ }
+
+ emitNeedRender();
+}
+
+void Abstract3DController::handleInputPositionChanged(const QPoint &position)
+{
+ Q_UNUSED(position);
+ emitNeedRender();
+}
+
+void Abstract3DController::handleSeriesVisibilityChanged(bool visible)
+{
+ Q_UNUSED(visible);
+
+ handleSeriesVisibilityChangedBySender(sender());
+}
+
+void Abstract3DController::handleRequestShadowQuality(QAbstract3DGraph::ShadowQuality quality)
+{
+ setShadowQuality(quality);
+}
+
+void Abstract3DController::setMeasureFps(bool enable)
+{
+ if (m_measureFps != enable) {
+ m_measureFps = enable;
+ m_currentFps = 0.0;
+
+ if (enable) {
+ m_frameTimer.start();
+ m_numFrames = -1;
+ emitNeedRender();
+ }
+ emit measureFpsChanged(enable);
+ }
+}
+
+void Abstract3DController::handleAxisLabelFormatChangedBySender(QObject *sender)
+{
+ // Label format changing needs to dirty the data so that labels are reset.
+ if (sender == m_axisX) {
+ m_isDataDirty = true;
+ m_changeTracker.axisXLabelFormatChanged = true;
+ } else if (sender == m_axisY) {
+ m_isDataDirty = true;
+ m_changeTracker.axisYLabelFormatChanged = true;
+ } else if (sender == m_axisZ) {
+ m_isDataDirty = true;
+ m_changeTracker.axisZLabelFormatChanged = true;
+ } else {
+ qWarning() << __FUNCTION__ << "invoked for invalid axis";
+ }
+ emitNeedRender();
+}
+
+void Abstract3DController::handleAxisReversedChangedBySender(QObject *sender)
+{
+ // Reversing change needs to dirty the data so item positions are recalculated
+ if (sender == m_axisX) {
+ m_isDataDirty = true;
+ m_changeTracker.axisXReversedChanged = true;
+ } else if (sender == m_axisY) {
+ m_isDataDirty = true;
+ m_changeTracker.axisYReversedChanged = true;
+ } else if (sender == m_axisZ) {
+ m_isDataDirty = true;
+ m_changeTracker.axisZReversedChanged = true;
+ } else {
+ qWarning() << __FUNCTION__ << "invoked for invalid axis";
+ }
+ emitNeedRender();
+}
+
+void Abstract3DController::handleAxisFormatterDirtyBySender(QObject *sender)
+{
+ // Sender is QValue3DAxisPrivate
+ QValue3DAxis *valueAxis = static_cast<QValue3DAxisPrivate *>(sender)->qptr();
+ if (valueAxis == m_axisX) {
+ m_isDataDirty = true;
+ m_changeTracker.axisXFormatterChanged = true;
+ } else if (valueAxis == m_axisY) {
+ m_isDataDirty = true;
+ m_changeTracker.axisYFormatterChanged = true;
+ } else if (valueAxis == m_axisZ) {
+ m_isDataDirty = true;
+ m_changeTracker.axisZFormatterChanged = true;
+ } else {
+ qWarning() << __FUNCTION__ << "invoked for invalid axis";
+ }
+ emitNeedRender();
+}
+
+void Abstract3DController::handleAxisLabelAutoRotationChangedBySender(QObject *sender)
+{
+ if (sender == m_axisX)
+ m_changeTracker.axisXLabelAutoRotationChanged = true;
+ else if (sender == m_axisY)
+ m_changeTracker.axisYLabelAutoRotationChanged = true;
+ else if (sender == m_axisZ)
+ m_changeTracker.axisZLabelAutoRotationChanged = true;
+ else
+ qWarning() << __FUNCTION__ << "invoked for invalid axis";
+
+ emitNeedRender();
+}
+
+void Abstract3DController::handleAxisTitleVisibilityChangedBySender(QObject *sender)
+{
+ if (sender == m_axisX)
+ m_changeTracker.axisXTitleVisibilityChanged = true;
+ else if (sender == m_axisY)
+ m_changeTracker.axisYTitleVisibilityChanged = true;
+ else if (sender == m_axisZ)
+ m_changeTracker.axisZTitleVisibilityChanged = true;
+ else
+ qWarning() << __FUNCTION__ << "invoked for invalid axis";
+
+ emitNeedRender();
+}
+
+void Abstract3DController::handleAxisTitleFixedChangedBySender(QObject *sender)
+{
+ if (sender == m_axisX)
+ m_changeTracker.axisXTitleFixedChanged = true;
+ else if (sender == m_axisY)
+ m_changeTracker.axisYTitleFixedChanged = true;
+ else if (sender == m_axisZ)
+ m_changeTracker.axisZTitleFixedChanged = true;
+ else
+ qWarning() << __FUNCTION__ << "invoked for invalid axis";
+
+ emitNeedRender();
+}
+
+void Abstract3DController::handleSeriesVisibilityChangedBySender(QObject *sender)
+{
+ QAbstract3DSeries *series = static_cast<QAbstract3DSeries *>(sender);
+ series->d_ptr->m_changeTracker.visibilityChanged = true;
+
+ m_isDataDirty = true;
+ m_isSeriesVisualsDirty = true;
+
+ adjustAxisRanges();
+
+ emitNeedRender();
+}
+
+void Abstract3DController::markSeriesItemLabelsDirty()
+{
+ for (int i = 0; i < m_seriesList.size(); i++)
+ m_seriesList.at(i)->d_ptr->markItemLabelDirty();
+}
+
+bool Abstract3DController::isOpenGLES() const
+{
+ return Utils::isOpenGLES();
+}
+
+void Abstract3DController::setAxisHelper(QAbstract3DAxis::AxisOrientation orientation,
+ QAbstract3DAxis *axis, QAbstract3DAxis **axisPtr)
+{
+ // Setting null axis indicates using default axis
+ if (!axis)
+ axis = createDefaultAxis(orientation);
+
+ // If old axis is default axis, delete it
+ QAbstract3DAxis *oldAxis = *axisPtr;
+ if (oldAxis) {
+ if (oldAxis->d_ptr->isDefaultAxis()) {
+ m_axes.removeAll(oldAxis);
+ delete oldAxis;
+ oldAxis = 0;
+ } else {
+ // Disconnect the old axis from use
+ QObject::disconnect(oldAxis, 0, this, 0);
+ oldAxis->d_ptr->setOrientation(QAbstract3DAxis::AxisOrientationNone);
+ }
+ }
+
+ // Assume ownership
+ addAxis(axis);
+
+ // Connect the new axis
+ *axisPtr = axis;
+
+ axis->d_ptr->setOrientation(orientation);
+
+ QObject::connect(axis, &QAbstract3DAxis::titleChanged,
+ this, &Abstract3DController::handleAxisTitleChanged);
+ QObject::connect(axis, &QAbstract3DAxis::labelsChanged,
+ this, &Abstract3DController::handleAxisLabelsChanged);
+ QObject::connect(axis, &QAbstract3DAxis::rangeChanged,
+ this, &Abstract3DController::handleAxisRangeChanged);
+ QObject::connect(axis, &QAbstract3DAxis::autoAdjustRangeChanged,
+ this, &Abstract3DController::handleAxisAutoAdjustRangeChanged);
+ QObject::connect(axis, &QAbstract3DAxis::labelAutoRotationChanged,
+ this, &Abstract3DController::handleAxisLabelAutoRotationChanged);
+ QObject::connect(axis, &QAbstract3DAxis::titleVisibilityChanged,
+ this, &Abstract3DController::handleAxisTitleVisibilityChanged);
+ QObject::connect(axis, &QAbstract3DAxis::titleFixedChanged,
+ this, &Abstract3DController::handleAxisTitleFixedChanged);
+
+ if (orientation == QAbstract3DAxis::AxisOrientationX)
+ m_changeTracker.axisXTypeChanged = true;
+ else if (orientation == QAbstract3DAxis::AxisOrientationY)
+ m_changeTracker.axisYTypeChanged = true;
+ else if (orientation == QAbstract3DAxis::AxisOrientationZ)
+ m_changeTracker.axisZTypeChanged = true;
+
+ handleAxisTitleChangedBySender(axis);
+ handleAxisLabelsChangedBySender(axis);
+ handleAxisRangeChangedBySender(axis);
+ handleAxisAutoAdjustRangeChangedInOrientation(axis->orientation(),
+ axis->isAutoAdjustRange());
+ handleAxisLabelAutoRotationChangedBySender(axis);
+ handleAxisTitleVisibilityChangedBySender(axis);
+ handleAxisTitleFixedChangedBySender(axis);
+
+ if (axis->type() & QAbstract3DAxis::AxisTypeValue) {
+ QValue3DAxis *valueAxis = static_cast<QValue3DAxis *>(axis);
+ QObject::connect(valueAxis, &QValue3DAxis::segmentCountChanged,
+ this, &Abstract3DController::handleAxisSegmentCountChanged);
+ QObject::connect(valueAxis, &QValue3DAxis::subSegmentCountChanged,
+ this, &Abstract3DController::handleAxisSubSegmentCountChanged);
+ QObject::connect(valueAxis, &QValue3DAxis::labelFormatChanged,
+ this, &Abstract3DController::handleAxisLabelFormatChanged);
+ QObject::connect(valueAxis, &QValue3DAxis::reversedChanged,
+ this, &Abstract3DController::handleAxisReversedChanged);
+ QObject::connect(valueAxis->dptr(), &QValue3DAxisPrivate::formatterDirty,
+ this, &Abstract3DController::handleAxisFormatterDirty);
+
+ handleAxisSegmentCountChangedBySender(valueAxis);
+ handleAxisSubSegmentCountChangedBySender(valueAxis);
+ handleAxisLabelFormatChangedBySender(valueAxis);
+ handleAxisReversedChangedBySender(valueAxis);
+ handleAxisFormatterDirtyBySender(valueAxis->dptr());
+
+ valueAxis->formatter()->setLocale(m_locale);
+ }
+}
+
+QAbstract3DAxis *Abstract3DController::createDefaultAxis(
+ QAbstract3DAxis::AxisOrientation orientation)
+{
+ Q_UNUSED(orientation);
+
+ // The default default axis is a value axis. If the graph type has a different default axis
+ // for some orientation, this function needs to be overridden.
+ QAbstract3DAxis *defaultAxis = createDefaultValueAxis();
+ return defaultAxis;
+}
+
+QValue3DAxis *Abstract3DController::createDefaultValueAxis()
+{
+ // Default value axis has single segment, empty label format, and auto scaling
+ QValue3DAxis *defaultAxis = new QValue3DAxis;
+ defaultAxis->d_ptr->setDefaultAxis(true);
+
+ return defaultAxis;
+}
+
+QCategory3DAxis *Abstract3DController::createDefaultCategoryAxis()
+{
+ // Default category axis has no labels
+ QCategory3DAxis *defaultAxis = new QCategory3DAxis;
+ defaultAxis->d_ptr->setDefaultAxis(true);
+ return defaultAxis;
+}
+
+void Abstract3DController::startRecordingRemovesAndInserts()
+{
+ // Default implementation does nothing
+}
+
+void Abstract3DController::emitNeedRender()
+{
+ if (!m_renderPending) {
+ emit needRender();
+ m_renderPending = true;
+ }
+}
+
+int Abstract3DController::selectedLabelIndex() const
+{
+ int index = m_selectedLabelIndex;
+ QAbstract3DAxis *axis = selectedAxis();
+ if (axis && axis->labels().size() <= index)
+ index = -1;
+ return index;
+}
+
+QAbstract3DAxis *Abstract3DController::selectedAxis() const
+{
+ QAbstract3DAxis *axis = 0;
+ QAbstract3DGraph::ElementType type = m_clickedType;
+ switch (type) {
+ case QAbstract3DGraph::ElementAxisXLabel:
+ axis = axisX();
+ break;
+ case QAbstract3DGraph::ElementAxisYLabel:
+ axis = axisY();
+ break;
+ case QAbstract3DGraph::ElementAxisZLabel:
+ axis = axisZ();
+ break;
+ default:
+ axis = 0;
+ break;
+ }
+
+ return axis;
+}
+
+int Abstract3DController::selectedCustomItemIndex() const
+{
+ int index = m_selectedCustomItemIndex;
+ if (m_customItems.size() <= index)
+ index = -1;
+ return index;
+}
+
+QCustom3DItem *Abstract3DController::selectedCustomItem() const
+{
+ QCustom3DItem *item = 0;
+ int index = selectedCustomItemIndex();
+ if (index >= 0)
+ item = m_customItems[index];
+ return item;
+}
+
+QAbstract3DGraph::ElementType Abstract3DController::selectedElement() const
+{
+ return m_clickedType;
+}
+
+void Abstract3DController::setOrthoProjection(bool enable)
+{
+ if (enable != m_useOrthoProjection) {
+ m_useOrthoProjection = enable;
+ m_changeTracker.projectionChanged = true;
+ emit orthoProjectionChanged(m_useOrthoProjection);
+ // If changed to ortho, disable shadows
+ if (m_useOrthoProjection)
+ doSetShadowQuality(QAbstract3DGraph::ShadowQualityNone);
+ emitNeedRender();
+ }
+}
+
+bool Abstract3DController::isOrthoProjection() const
+{
+ return m_useOrthoProjection;
+}
+
+void Abstract3DController::setAspectRatio(qreal ratio)
+{
+ if (m_aspectRatio != ratio) {
+ m_aspectRatio = ratio;
+ m_changeTracker.aspectRatioChanged = true;
+ emit aspectRatioChanged(m_aspectRatio);
+ m_isDataDirty = true;
+ emitNeedRender();
+ }
+}
+
+qreal Abstract3DController::aspectRatio()
+{
+ return m_aspectRatio;
+}
+
+void Abstract3DController::setHorizontalAspectRatio(qreal ratio)
+{
+ if (m_horizontalAspectRatio != ratio) {
+ m_horizontalAspectRatio = ratio;
+ m_changeTracker.horizontalAspectRatioChanged = true;
+ emit horizontalAspectRatioChanged(m_horizontalAspectRatio);
+ m_isDataDirty = true;
+ emitNeedRender();
+ }
+}
+
+qreal Abstract3DController::horizontalAspectRatio() const
+{
+ return m_horizontalAspectRatio;
+}
+
+void Abstract3DController::setReflection(bool enable)
+{
+ if (m_reflectionEnabled != enable) {
+ m_reflectionEnabled = enable;
+ m_changeTracker.reflectionChanged = true;
+ emit reflectionChanged(m_reflectionEnabled);
+ emitNeedRender();
+ }
+}
+
+bool Abstract3DController::reflection() const
+{
+ return m_reflectionEnabled;
+}
+
+void Abstract3DController::setReflectivity(qreal reflectivity)
+{
+ if (m_reflectivity != reflectivity) {
+ m_reflectivity = reflectivity;
+ m_changeTracker.reflectivityChanged = true;
+ emit reflectivityChanged(m_reflectivity);
+ emitNeedRender();
+ }
+}
+
+qreal Abstract3DController::reflectivity() const
+{
+ return m_reflectivity;
+}
+
+void Abstract3DController::setPolar(bool enable)
+{
+ if (enable != m_isPolar) {
+ m_isPolar = enable;
+ m_changeTracker.polarChanged = true;
+ m_isDataDirty = true;
+ emit polarChanged(m_isPolar);
+ emitNeedRender();
+ }
+}
+
+bool Abstract3DController::isPolar() const
+{
+ return m_isPolar;
+}
+
+void Abstract3DController::setRadialLabelOffset(float offset)
+{
+ if (m_radialLabelOffset != offset) {
+ m_radialLabelOffset = offset;
+ m_changeTracker.radialLabelOffsetChanged = true;
+ emit radialLabelOffsetChanged(m_radialLabelOffset);
+ emitNeedRender();
+ }
+}
+
+float Abstract3DController::radialLabelOffset() const
+{
+ return m_radialLabelOffset;
+}
+
+void Abstract3DController::setLocale(const QLocale &locale)
+{
+ if (m_locale != locale) {
+ m_locale = locale;
+
+ // Value axis formatters need to be updated
+ QValue3DAxis *axis = qobject_cast<QValue3DAxis *>(m_axisX);
+ if (axis)
+ axis->formatter()->setLocale(m_locale);
+ axis = qobject_cast<QValue3DAxis *>(m_axisY);
+ if (axis)
+ axis->formatter()->setLocale(m_locale);
+ axis = qobject_cast<QValue3DAxis *>(m_axisZ);
+ if (axis)
+ axis->formatter()->setLocale(m_locale);
+ emit localeChanged(m_locale);
+ }
+}
+
+QLocale Abstract3DController::locale() const
+{
+ return m_locale;
+}
+
+QVector3D Abstract3DController::queriedGraphPosition() const
+{
+ return m_queriedGraphPosition;
+}
+
+void Abstract3DController::setMargin(qreal margin)
+{
+ if (m_margin != margin) {
+ m_margin = margin;
+ m_changeTracker.marginChanged = true;
+ emit marginChanged(margin);
+ emitNeedRender();
+ }
+}
+
+qreal Abstract3DController::margin() const
+{
+ return m_margin;
+}
+
+
+QT_END_NAMESPACE
diff --git a/src/graphs/engine/abstract3dcontroller_p.h b/src/graphs/engine/abstract3dcontroller_p.h
new file mode 100644
index 0000000..f4b7a1a
--- /dev/null
+++ b/src/graphs/engine/abstract3dcontroller_p.h
@@ -0,0 +1,413 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QtGraphs API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#ifndef ABSTRACT3DCONTROLLER_P_H
+#define ABSTRACT3DCONTROLLER_P_H
+
+#include <private/graphsglobal_p.h>
+
+#include "qabstract3daxis.h"
+#include "qabstract3dinputhandler.h"
+#include "qabstract3dgraph.h"
+#include "qcustom3ditem.h"
+#include <QtGui/QLinearGradient>
+#include <QtCore/QElapsedTimer>
+#include <QtCore/QLocale>
+#include <QtCore/QMutex>
+
+QT_FORWARD_DECLARE_CLASS(QOpenGLFramebufferObject)
+
+QT_BEGIN_NAMESPACE
+
+class QValue3DAxis;
+class QCategory3DAxis;
+class AbstractDeclarative;
+class QAbstract3DSeries;
+class ThemeManager;
+
+struct Abstract3DChangeBitField {
+ bool themeChanged : 1;
+ bool shadowQualityChanged : 1;
+ bool selectionModeChanged : 1;
+ bool optimizationHintChanged : 1;
+ bool axisXTypeChanged : 1;
+ bool axisYTypeChanged : 1;
+ bool axisZTypeChanged : 1;
+ bool axisXTitleChanged : 1;
+ bool axisYTitleChanged : 1;
+ bool axisZTitleChanged : 1;
+ bool axisXLabelsChanged : 1;
+ bool axisYLabelsChanged : 1;
+ bool axisZLabelsChanged : 1;
+ bool axisXRangeChanged : 1;
+ bool axisYRangeChanged : 1;
+ bool axisZRangeChanged : 1;
+ bool axisXSegmentCountChanged : 1;
+ bool axisYSegmentCountChanged : 1;
+ bool axisZSegmentCountChanged : 1;
+ bool axisXSubSegmentCountChanged : 1;
+ bool axisYSubSegmentCountChanged : 1;
+ bool axisZSubSegmentCountChanged : 1;
+ bool axisXLabelFormatChanged : 1;
+ bool axisYLabelFormatChanged : 1;
+ bool axisZLabelFormatChanged : 1;
+ bool axisXReversedChanged : 1;
+ bool axisYReversedChanged : 1;
+ bool axisZReversedChanged : 1;
+ bool axisXFormatterChanged : 1;
+ bool axisYFormatterChanged : 1;
+ bool axisZFormatterChanged : 1;
+ bool projectionChanged : 1;
+ bool axisXLabelAutoRotationChanged : 1;
+ bool axisYLabelAutoRotationChanged : 1;
+ bool axisZLabelAutoRotationChanged : 1;
+ bool aspectRatioChanged : 1;
+ bool horizontalAspectRatioChanged : 1;
+ bool axisXTitleVisibilityChanged : 1;
+ bool axisYTitleVisibilityChanged : 1;
+ bool axisZTitleVisibilityChanged : 1;
+ bool axisXTitleFixedChanged : 1;
+ bool axisYTitleFixedChanged : 1;
+ bool axisZTitleFixedChanged : 1;
+ bool polarChanged : 1;
+ bool radialLabelOffsetChanged : 1;
+ bool reflectionChanged : 1;
+ bool reflectivityChanged : 1;
+ bool marginChanged : 1;
+
+ Abstract3DChangeBitField() :
+ themeChanged(true),
+ shadowQualityChanged(true),
+ selectionModeChanged(true),
+ optimizationHintChanged(true),
+ axisXTypeChanged(true),
+ axisYTypeChanged(true),
+ axisZTypeChanged(true),
+ axisXTitleChanged(true),
+ axisYTitleChanged(true),
+ axisZTitleChanged(true),
+ axisXLabelsChanged(true),
+ axisYLabelsChanged(true),
+ axisZLabelsChanged(true),
+ axisXRangeChanged(true),
+ axisYRangeChanged(true),
+ axisZRangeChanged(true),
+ axisXSegmentCountChanged(true),
+ axisYSegmentCountChanged(true),
+ axisZSegmentCountChanged(true),
+ axisXSubSegmentCountChanged(true),
+ axisYSubSegmentCountChanged(true),
+ axisZSubSegmentCountChanged(true),
+ axisXLabelFormatChanged(true),
+ axisYLabelFormatChanged(true),
+ axisZLabelFormatChanged(true),
+ axisXReversedChanged(true),
+ axisYReversedChanged(true),
+ axisZReversedChanged(true),
+ axisXFormatterChanged(true),
+ axisYFormatterChanged(true),
+ axisZFormatterChanged(true),
+ projectionChanged(true),
+ axisXLabelAutoRotationChanged(true),
+ axisYLabelAutoRotationChanged(true),
+ axisZLabelAutoRotationChanged(true),
+ aspectRatioChanged(true),
+ horizontalAspectRatioChanged(true),
+ axisXTitleVisibilityChanged(true),
+ axisYTitleVisibilityChanged(true),
+ axisZTitleVisibilityChanged(true),
+ axisXTitleFixedChanged(true),
+ axisYTitleFixedChanged(true),
+ axisZTitleFixedChanged(true),
+ polarChanged(true),
+ radialLabelOffsetChanged(true),
+ reflectionChanged(true),
+ reflectivityChanged(true),
+ marginChanged(true)
+ {
+ }
+};
+
+class Q_GRAPHS_EXPORT Abstract3DController : public QObject
+{
+ Q_OBJECT
+
+public:
+ enum SelectionType {
+ SelectionNone = 0,
+ SelectionItem,
+ SelectionRow,
+ SelectionColumn
+ };
+
+private:
+ Abstract3DChangeBitField m_changeTracker;
+ ThemeManager *m_themeManager;
+ QAbstract3DGraph::SelectionFlags m_selectionMode;
+ QAbstract3DGraph::ShadowQuality m_shadowQuality;
+ bool m_useOrthoProjection;
+ qreal m_aspectRatio;
+ qreal m_horizontalAspectRatio;
+ QAbstract3DGraph::OptimizationHints m_optimizationHints;
+ bool m_reflectionEnabled;
+ qreal m_reflectivity;
+ QLocale m_locale;
+ QVector3D m_queriedGraphPosition;
+ bool m_graphPositionQueryPending = false;
+
+protected:
+ Q3DScene *m_scene;
+ QList<QAbstract3DInputHandler *> m_inputHandlers; // List of all added input handlers
+ QAbstract3DInputHandler *m_activeInputHandler;
+ // Active axes
+ QAbstract3DAxis *m_axisX;
+ QAbstract3DAxis *m_axisY;
+ QAbstract3DAxis *m_axisZ;
+
+ QList<QAbstract3DAxis *> m_axes; // List of all added axes
+ bool m_isDataDirty;
+ bool m_isCustomDataDirty;
+ bool m_isCustomItemDirty;
+ bool m_isSeriesVisualsDirty;
+ bool m_renderPending;
+ bool m_isPolar;
+ float m_radialLabelOffset;
+
+ QList<QAbstract3DSeries *> m_seriesList;
+
+ bool m_measureFps;
+ QElapsedTimer m_frameTimer;
+ int m_numFrames;
+ qreal m_currentFps;
+
+ QList<QAbstract3DSeries *> m_changedSeriesList;
+
+ QList<QCustom3DItem *> m_customItems;
+
+ QAbstract3DGraph::ElementType m_clickedType;
+ int m_selectedLabelIndex;
+ int m_selectedCustomItemIndex;
+ qreal m_margin;
+
+ QMutex m_renderMutex;
+ QQuickGraphsItem *m_qml = nullptr;
+
+ explicit Abstract3DController(QRect initialViewport, Q3DScene *scene, QObject *parent = 0);
+
+public:
+ virtual ~Abstract3DController();
+
+ virtual void addSeries(QAbstract3DSeries *series);
+ virtual void insertSeries(int index, QAbstract3DSeries *series);
+ virtual void removeSeries(QAbstract3DSeries *series);
+ virtual bool hasSeries(QAbstract3DSeries *series);
+ QList<QAbstract3DSeries *> seriesList();
+
+ virtual void setAxisX(QAbstract3DAxis *axis);
+ virtual QAbstract3DAxis *axisX() const;
+ virtual void setAxisY(QAbstract3DAxis *axis);
+ virtual QAbstract3DAxis *axisY() const;
+ virtual void setAxisZ(QAbstract3DAxis *axis);
+ virtual QAbstract3DAxis *axisZ() const;
+ virtual void addAxis(QAbstract3DAxis *axis);
+ virtual void releaseAxis(QAbstract3DAxis *axis);
+ virtual QList<QAbstract3DAxis *> axes() const; // Omits default axes
+
+ virtual void addInputHandler(QAbstract3DInputHandler *inputHandler);
+ virtual void releaseInputHandler(QAbstract3DInputHandler *inputHandler);
+ virtual void setActiveInputHandler(QAbstract3DInputHandler *inputHandler);
+ virtual QAbstract3DInputHandler *activeInputHandler();
+ virtual QList<QAbstract3DInputHandler *> inputHandlers() const;
+
+ virtual void addTheme(Q3DTheme *theme);
+ virtual void releaseTheme(Q3DTheme *theme);
+ virtual void setActiveTheme(Q3DTheme *theme, bool force = true);
+ virtual Q3DTheme *activeTheme() const;
+ virtual QList<Q3DTheme *> themes() const;
+
+ virtual void setSelectionMode(QAbstract3DGraph::SelectionFlags mode);
+ virtual QAbstract3DGraph::SelectionFlags selectionMode() const;
+
+ virtual void setShadowQuality(QAbstract3DGraph::ShadowQuality quality);
+ virtual void doSetShadowQuality(QAbstract3DGraph::ShadowQuality quality);
+ virtual QAbstract3DGraph::ShadowQuality shadowQuality() const;
+ virtual bool shadowsSupported() const;
+
+ void setOptimizationHints(QAbstract3DGraph::OptimizationHints hints);
+ QAbstract3DGraph::OptimizationHints optimizationHints() const;
+
+ bool isSlicingActive() const;
+ void setSlicingActive(bool isSlicing);
+
+ Q3DScene *scene();
+
+ void markDataDirty();
+ void markSeriesVisualsDirty();
+
+ void requestRender(QOpenGLFramebufferObject *fbo);
+
+ int addCustomItem(QCustom3DItem *item);
+ void deleteCustomItems();
+ void deleteCustomItem(QCustom3DItem *item);
+ void deleteCustomItem(const QVector3D &position);
+ void releaseCustomItem(QCustom3DItem *item);
+ QList<QCustom3DItem *> customItems() const;
+
+ int selectedLabelIndex() const;
+ QAbstract3DAxis *selectedAxis() const;
+ int selectedCustomItemIndex() const;
+ QCustom3DItem *selectedCustomItem() const;
+
+ void setOrthoProjection(bool enable);
+ bool isOrthoProjection() const;
+
+ void setMeasureFps(bool enable);
+ inline bool measureFps() const { return m_measureFps; }
+ inline qreal currentFps() const { return m_currentFps; }
+
+ QAbstract3DGraph::ElementType selectedElement() const;
+
+ void setAspectRatio(qreal ratio);
+ qreal aspectRatio();
+ void setHorizontalAspectRatio(qreal ratio);
+ qreal horizontalAspectRatio() const;
+
+ void setReflection(bool enable);
+ bool reflection() const;
+ void setReflectivity(qreal reflectivity);
+ qreal reflectivity() const;
+
+ void setPolar(bool enable);
+ bool isPolar() const;
+ void setRadialLabelOffset(float offset);
+ float radialLabelOffset() const;
+
+ void setLocale(const QLocale &locale);
+ QLocale locale() const;
+
+ QVector3D queriedGraphPosition() const;
+ void setQueriedGraphPosition(const QVector3D &position) { m_queriedGraphPosition = position; }
+
+ void setMargin(qreal margin);
+ qreal margin() const;
+
+ void emitNeedRender();
+
+ virtual void clearSelection() = 0;
+
+ virtual void mouseDoubleClickEvent(QMouseEvent *event);
+ virtual void touchEvent(QTouchEvent *event);
+ virtual void mousePressEvent(QMouseEvent *event, const QPoint &mousePos);
+ virtual void mouseReleaseEvent(QMouseEvent *event, const QPoint &mousePos);
+ virtual void mouseMoveEvent(QMouseEvent *event, const QPoint &mousePos);
+#if QT_CONFIG(wheelevent)
+ virtual void wheelEvent(QWheelEvent *event);
+#endif
+
+ virtual void handleAxisTitleChangedBySender(QObject *sender);
+ virtual void handleAxisLabelsChangedBySender(QObject *sender);
+ virtual void handleAxisRangeChangedBySender(QObject *sender);
+ virtual void handleAxisSegmentCountChangedBySender(QObject *sender);
+ virtual void handleAxisSubSegmentCountChangedBySender(QObject *sender);
+ virtual void handleAxisAutoAdjustRangeChangedInOrientation(
+ QAbstract3DAxis::AxisOrientation orientation, bool autoAdjust) = 0;
+ virtual void handleAxisLabelFormatChangedBySender(QObject *sender);
+ virtual void handleAxisReversedChangedBySender(QObject *sender);
+ virtual void handleAxisFormatterDirtyBySender(QObject *sender);
+ virtual void handleAxisLabelAutoRotationChangedBySender(QObject *sender);
+ virtual void handleAxisTitleVisibilityChangedBySender(QObject *sender);
+ virtual void handleAxisTitleFixedChangedBySender(QObject *sender);
+ virtual void handleSeriesVisibilityChangedBySender(QObject *sender);
+ virtual void adjustAxisRanges() = 0;
+
+ void markSeriesItemLabelsDirty();
+ bool isOpenGLES() const;
+
+ bool graphPositionQueryPending() const { return m_graphPositionQueryPending; }
+ void setGraphPositionQueryPending(const bool &pending) { m_graphPositionQueryPending = pending; }
+
+public Q_SLOTS:
+ void handleAxisTitleChanged(const QString &title);
+ void handleAxisLabelsChanged();
+ void handleAxisRangeChanged(float min, float max);
+ void handleAxisSegmentCountChanged(int count);
+ void handleAxisSubSegmentCountChanged(int count);
+ void handleAxisAutoAdjustRangeChanged(bool autoAdjust);
+ void handleAxisLabelFormatChanged(const QString &format);
+ void handleAxisReversedChanged(bool enable);
+ void handleAxisFormatterDirty();
+ void handleAxisLabelAutoRotationChanged(float angle);
+ void handleAxisTitleVisibilityChanged(bool visible);
+ void handleAxisTitleFixedChanged(bool fixed);
+ void handleInputViewChanged(QAbstract3DInputHandler::InputView view);
+ void handleInputPositionChanged(const QPoint &position);
+ void handleSeriesVisibilityChanged(bool visible);
+
+ void handleThemeColorStyleChanged(Q3DTheme::ColorStyle style);
+ void handleThemeBaseColorsChanged(const QList<QColor> &color);
+ void handleThemeBaseGradientsChanged(const QList<QLinearGradient> &gradient);
+ void handleThemeSingleHighlightColorChanged(const QColor &color);
+ void handleThemeSingleHighlightGradientChanged(const QLinearGradient &gradient);
+ void handleThemeMultiHighlightColorChanged(const QColor &color);
+ void handleThemeMultiHighlightGradientChanged(const QLinearGradient &gradient);
+ void handleThemeTypeChanged(Q3DTheme::Theme theme);
+
+ // Renderer callback handlers
+ void handleRequestShadowQuality(QAbstract3DGraph::ShadowQuality quality);
+
+ void updateCustomItem();
+
+Q_SIGNALS:
+ void shadowQualityChanged(QAbstract3DGraph::ShadowQuality quality);
+ void activeInputHandlerChanged(QAbstract3DInputHandler *inputHandler);
+ void activeThemeChanged(Q3DTheme *activeTheme);
+ void selectionModeChanged(QAbstract3DGraph::SelectionFlags mode);
+ void needRender();
+ void axisXChanged(QAbstract3DAxis *axis);
+ void axisYChanged(QAbstract3DAxis *axis);
+ void axisZChanged(QAbstract3DAxis *axis);
+ void elementSelected(QAbstract3DGraph::ElementType type);
+ void measureFpsChanged(bool enabled);
+ void currentFpsChanged(qreal fps);
+ void orthoProjectionChanged(bool enabled);
+ void aspectRatioChanged(qreal ratio);
+ void horizontalAspectRatioChanged(qreal ratio);
+ void optimizationHintsChanged(QAbstract3DGraph::OptimizationHints hints);
+ void polarChanged(bool enabled);
+ void radialLabelOffsetChanged(float offset);
+ void reflectionChanged(bool enabled);
+ void reflectivityChanged(qreal reflectivity);
+ void localeChanged(const QLocale &locale);
+ void queriedGraphPositionChanged(const QVector3D &data);
+ void marginChanged(qreal margin);
+ void themeTypeChanged();
+
+protected:
+ virtual QAbstract3DAxis *createDefaultAxis(QAbstract3DAxis::AxisOrientation orientation);
+ QValue3DAxis *createDefaultValueAxis();
+ QCategory3DAxis *createDefaultCategoryAxis();
+ virtual void startRecordingRemovesAndInserts();
+
+private:
+ void setAxisHelper(QAbstract3DAxis::AxisOrientation orientation, QAbstract3DAxis *axis,
+ QAbstract3DAxis **axisPtr);
+
+ friend class AbstractDeclarative;
+ friend class QQuickGraphsItem;
+ friend class Bars3DController;
+ friend class QAbstract3DGraphPrivate;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/engine/axishelper.cpp b/src/graphs/engine/axishelper.cpp
new file mode 100644
index 0000000..ece4227
--- /dev/null
+++ b/src/graphs/engine/axishelper.cpp
@@ -0,0 +1,14 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "axishelper_p.h"
+
+AxisHelper::AxisHelper()
+{
+
+}
+
+AxisHelper::~AxisHelper()
+{
+
+}
diff --git a/src/graphs/engine/axishelper_p.h b/src/graphs/engine/axishelper_p.h
new file mode 100644
index 0000000..c04fb73
--- /dev/null
+++ b/src/graphs/engine/axishelper_p.h
@@ -0,0 +1,83 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QtGraphs API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#ifndef AXISHELPER_P_H
+#define AXISHELPER_P_H
+
+#include "graphsglobal_p.h"
+#include <qvalue3daxisformatter_p.h>
+#include <QList>
+
+QT_BEGIN_NAMESPACE
+class AxisHelper : public QObject
+{
+ Q_OBJECT
+public:
+ AxisHelper();
+ virtual ~AxisHelper();
+ void updatePositions();
+
+ inline float scale() { return m_scale; }
+ inline void setScale(float scale) { m_scale = scale; }
+
+ inline float translate() { return m_translate; }
+ inline void setTranslate(float translate) { m_translate = translate; }
+
+ inline QValue3DAxisFormatter *formatter() { return m_formatter; }
+ inline void setFormatter(QValue3DAxisFormatter *formatter) { m_formatter = formatter; }
+
+ inline float itemPositionAt(float value)
+ {
+ return m_formatter->positionAt(value) * m_scale + m_translate;
+ }
+
+ inline float labelPositionAt(int index)
+ {
+ return m_formatter->labelPositions().at(index) * m_scale + m_translate;
+ }
+ inline float gridPositionAt(int gridLine)
+ {
+ return m_formatter->gridPositions().at(gridLine) * m_scale + m_translate;
+ }
+
+ inline float subGridPositionAt(int gridLine)
+ {
+ return m_formatter->subGridPositions().at(gridLine) * m_scale + m_translate;
+ }
+
+ inline void setMin(float min) { m_min = min; }
+ inline float min() const { return m_min; }
+ inline void setMax(float max) { m_max = max; }
+ inline float max() const { return m_max; }
+
+ inline void setReversed(bool enable) { m_reversed = enable; }
+ inline bool isReversed() const { return m_reversed; }
+
+private:
+ QList<float> m_adjustedGridPositions;
+ QList<float> m_adjustedSubGridPositions;
+
+ float m_scale;
+ float m_translate;
+ QValue3DAxisFormatter *m_formatter;
+
+ float m_min;
+ float m_max;
+
+ bool m_reversed = false;
+};
+
+QT_END_NAMESPACE
+
+#endif // AXISHELPER_P_H
diff --git a/src/graphs/engine/bars3dcontroller.cpp b/src/graphs/engine/bars3dcontroller.cpp
new file mode 100644
index 0000000..c940a17
--- /dev/null
+++ b/src/graphs/engine/bars3dcontroller.cpp
@@ -0,0 +1,629 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "bars3dcontroller_p.h"
+#include "qvalue3daxis_p.h"
+#include "qcategory3daxis_p.h"
+#include "qbardataproxy_p.h"
+#include "qbar3dseries_p.h"
+#include "thememanager_p.h"
+#include "q3dtheme_p.h"
+#include <QtCore/QMutexLocker>
+
+QT_BEGIN_NAMESPACE
+
+Bars3DController::Bars3DController(QRect boundRect, Q3DScene *scene)
+ : Abstract3DController(boundRect, scene),
+ m_selectedBar(invalidSelectionPosition()),
+ m_selectedBarSeries(0),
+ m_primarySeries(0),
+ m_isMultiSeriesUniform(false),
+ m_isBarSpecRelative(true),
+ m_barThicknessRatio(1.0f),
+ m_barSpacing(QSizeF(1.0, 1.0)),
+ m_floorLevel(0.0f),
+ m_barSeriesMargin(0.0f, 0.0f)
+{
+ // Setting a null axis creates a new default axis according to orientation and graph type.
+ // Note: these cannot be set in the Abstract3DController constructor, as they will call virtual
+ // functions implemented by subclasses.
+ setAxisX(0);
+ setAxisY(0);
+ setAxisZ(0);
+}
+
+Bars3DController::~Bars3DController()
+{
+}
+
+void Bars3DController::handleArrayReset()
+{
+ QBar3DSeries *series;
+ if (qobject_cast<QBarDataProxy *>(sender()))
+ series = static_cast<QBarDataProxy *>(sender())->series();
+ else
+ series = static_cast<QBar3DSeries *>(sender());
+
+ if (series->isVisible()) {
+ adjustAxisRanges();
+ m_isDataDirty = true;
+ series->d_ptr->markItemLabelDirty();
+ }
+ if (!m_changedSeriesList.contains(series))
+ m_changedSeriesList.append(series);
+ // Clear selection unless still valid
+ setSelectedBar(m_selectedBar, m_selectedBarSeries, false);
+ series->d_ptr->markItemLabelDirty();
+ emitNeedRender();
+}
+
+void Bars3DController::handleRowsAdded(int startIndex, int count)
+{
+ Q_UNUSED(startIndex);
+ Q_UNUSED(count);
+ QBar3DSeries *series = static_cast<QBarDataProxy *>(sender())->series();
+ if (series->isVisible()) {
+ adjustAxisRanges();
+ m_isDataDirty = true;
+ }
+ if (!m_changedSeriesList.contains(series))
+ m_changedSeriesList.append(series);
+ emitNeedRender();
+}
+
+void Bars3DController::handleRowsChanged(int startIndex, int count)
+{
+ QBar3DSeries *series = static_cast<QBarDataProxy *>(sender())->series();
+ int oldChangeCount = m_changedRows.size();
+ if (!oldChangeCount)
+ m_changedRows.reserve(count);
+
+ for (int i = 0; i < count; i++) {
+ bool newItem = true;
+ int candidate = startIndex + i;
+ for (int j = 0; j < oldChangeCount; j++) {
+ const ChangeRow &oldChangeItem = m_changedRows.at(j);
+ if (oldChangeItem.row == candidate && series == oldChangeItem.series) {
+ newItem = false;
+ break;
+ }
+ }
+ if (newItem) {
+ ChangeRow newChangeItem = {series, candidate};
+ m_changedRows.append(newChangeItem);
+ if (series == m_selectedBarSeries && m_selectedBar.x() == candidate)
+ series->d_ptr->markItemLabelDirty();
+ }
+ }
+ if (count) {
+ m_changeTracker.rowsChanged = true;
+
+ if (series->isVisible())
+ adjustAxisRanges();
+
+ // Clear selection unless still valid (row length might have changed)
+ setSelectedBar(m_selectedBar, m_selectedBarSeries, false);
+ emitNeedRender();
+ }
+}
+
+void Bars3DController::handleRowsRemoved(int startIndex, int count)
+{
+ Q_UNUSED(startIndex);
+ Q_UNUSED(count);
+
+ QBar3DSeries *series = static_cast<QBarDataProxy *>(sender())->series();
+ if (series == m_selectedBarSeries) {
+ // If rows removed from selected series before the selection, adjust the selection
+ int selectedRow = m_selectedBar.x();
+ if (startIndex <= selectedRow) {
+ if ((startIndex + count) > selectedRow)
+ selectedRow = -1; // Selected row removed
+ else
+ selectedRow -= count; // Move selected row down by amount of rows removed
+
+ setSelectedBar(QPoint(selectedRow, m_selectedBar.y()), m_selectedBarSeries, false);
+ }
+ }
+
+ if (series->isVisible()) {
+ adjustAxisRanges();
+ m_isDataDirty = true;
+ }
+ if (!m_changedSeriesList.contains(series))
+ m_changedSeriesList.append(series);
+
+ emitNeedRender();
+}
+
+void Bars3DController::handleRowsInserted(int startIndex, int count)
+{
+ Q_UNUSED(startIndex);
+ Q_UNUSED(count);
+ QBar3DSeries *series = static_cast<QBarDataProxy *>(sender())->series();
+ if (series == m_selectedBarSeries) {
+ // If rows inserted to selected series before the selection, adjust the selection
+ int selectedRow = m_selectedBar.x();
+ if (startIndex <= selectedRow) {
+ selectedRow += count;
+ setSelectedBar(QPoint(selectedRow, m_selectedBar.y()), m_selectedBarSeries, false);
+ }
+ }
+
+ if (series->isVisible()) {
+ adjustAxisRanges();
+ m_isDataDirty = true;
+ }
+ if (!m_changedSeriesList.contains(series))
+ m_changedSeriesList.append(series);
+
+ emitNeedRender();
+}
+
+void Bars3DController::handleItemChanged(int rowIndex, int columnIndex)
+{
+ QBar3DSeries *series = static_cast<QBarDataProxy *>(sender())->series();
+
+ bool newItem = true;
+ QPoint candidate(rowIndex, columnIndex);
+ foreach (ChangeItem item, m_changedItems) {
+ if (item.point == candidate && item.series == series) {
+ newItem = false;
+ break;
+ }
+ }
+
+ if (newItem) {
+ ChangeItem newItem = {series, candidate};
+ m_changedItems.append(newItem);
+ m_changeTracker.itemChanged = true;
+
+ if (series == m_selectedBarSeries && m_selectedBar == candidate)
+ series->d_ptr->markItemLabelDirty();
+ if (series->isVisible())
+ adjustAxisRanges();
+ emitNeedRender();
+ }
+}
+
+void Bars3DController::handleDataRowLabelsChanged()
+{
+ if (m_axisZ) {
+ // Grab a sublist equal to data window (no need to have more labels in axis)
+ int min = int(m_axisZ->min());
+ int count = int(m_axisZ->max()) - min + 1;
+ QStringList subList;
+ if (m_primarySeries && m_primarySeries->dataProxy())
+ subList = m_primarySeries->dataProxy()->rowLabels().mid(min, count);
+ static_cast<QCategory3DAxis *>(m_axisZ)->dptr()->setDataLabels(subList);
+ }
+}
+
+void Bars3DController::handleDataColumnLabelsChanged()
+{
+ if (m_axisX) {
+ // Grab a sublist equal to data window (no need to have more labels in axis)
+ int min = int(m_axisX->min());
+ int count = int(m_axisX->max()) - min + 1;
+ QStringList subList;
+ if (m_primarySeries && m_primarySeries->dataProxy()) {
+ subList = static_cast<QBarDataProxy *>(m_primarySeries->dataProxy())
+ ->columnLabels().mid(min, count);
+ }
+ static_cast<QCategory3DAxis *>(m_axisX)->dptr()->setDataLabels(subList);
+ }
+}
+
+void Bars3DController::handleRowColorsChanged()
+{
+ emitNeedRender();
+}
+
+void Bars3DController::handleAxisAutoAdjustRangeChangedInOrientation(
+ QAbstract3DAxis::AxisOrientation orientation, bool autoAdjust)
+{
+ Q_UNUSED(orientation);
+ Q_UNUSED(autoAdjust);
+ adjustAxisRanges();
+}
+
+void Bars3DController::handleSeriesVisibilityChangedBySender(QObject *sender)
+{
+ Abstract3DController::handleSeriesVisibilityChangedBySender(sender);
+
+ // Visibility changes may require disabling slicing,
+ // so just reset selection to ensure everything is still valid.
+ setSelectedBar(m_selectedBar, m_selectedBarSeries, false);
+}
+
+QPoint Bars3DController::invalidSelectionPosition()
+{
+ static QPoint invalidSelectionPos(-1, -1);
+ return invalidSelectionPos;
+}
+
+void Bars3DController::setAxisX(QAbstract3DAxis *axis)
+{
+ Abstract3DController::setAxisX(axis);
+ handleDataColumnLabelsChanged();
+}
+
+void Bars3DController::setAxisZ(QAbstract3DAxis *axis)
+{
+ Abstract3DController::setAxisZ(axis);
+ handleDataRowLabelsChanged();
+}
+
+void Bars3DController::setPrimarySeries(QBar3DSeries *series)
+{
+ if (!series) {
+ if (m_seriesList.size())
+ series = static_cast<QBar3DSeries *>(m_seriesList.at(0));
+ } else if (!m_seriesList.contains(series)) {
+ // Add nonexistent series.
+ addSeries(series);
+ }
+
+ if (m_primarySeries != series) {
+ m_primarySeries = series;
+ handleDataRowLabelsChanged();
+ handleDataColumnLabelsChanged();
+ emit primarySeriesChanged(m_primarySeries);
+ }
+}
+
+QBar3DSeries *Bars3DController::primarySeries() const
+{
+ return m_primarySeries;
+}
+
+void Bars3DController::addSeries(QAbstract3DSeries *series)
+{
+ insertSeries(m_seriesList.size(), series);
+}
+
+void Bars3DController::removeSeries(QAbstract3DSeries *series)
+{
+ bool wasVisible = (series && series->d_ptr->m_controller == this && series->isVisible());
+
+ Abstract3DController::removeSeries(series);
+
+ if (m_selectedBarSeries == series)
+ setSelectedBar(invalidSelectionPosition(), 0, false);
+
+ if (wasVisible)
+ adjustAxisRanges();
+
+ // If primary series is removed, reset it to default
+ if (series == m_primarySeries) {
+ if (m_seriesList.size())
+ m_primarySeries = static_cast<QBar3DSeries *>(m_seriesList.at(0));
+ else
+ m_primarySeries = 0;
+
+ handleDataRowLabelsChanged();
+ handleDataColumnLabelsChanged();
+
+ emit primarySeriesChanged(m_primarySeries);
+ }
+}
+
+void Bars3DController::insertSeries(int index, QAbstract3DSeries *series)
+{
+ Q_ASSERT(series && series->type() == QAbstract3DSeries::SeriesTypeBar);
+
+ int oldSize = m_seriesList.size();
+
+ Abstract3DController::insertSeries(index, series);
+
+ if (oldSize != m_seriesList.size()) {
+ QBar3DSeries *barSeries = static_cast<QBar3DSeries *>(series);
+ if (!oldSize) {
+ m_primarySeries = barSeries;
+ handleDataRowLabelsChanged();
+ handleDataColumnLabelsChanged();
+ }
+
+ if (barSeries->selectedBar() != invalidSelectionPosition())
+ setSelectedBar(barSeries->selectedBar(), barSeries, false);
+
+ if (!oldSize)
+ emit primarySeriesChanged(m_primarySeries);
+ }
+}
+
+QList<QBar3DSeries *> Bars3DController::barSeriesList()
+{
+ QList<QAbstract3DSeries *> abstractSeriesList = seriesList();
+ QList<QBar3DSeries *> barSeriesList;
+ foreach (QAbstract3DSeries *abstractSeries, abstractSeriesList) {
+ QBar3DSeries *barSeries = qobject_cast<QBar3DSeries *>(abstractSeries);
+ if (barSeries)
+ barSeriesList.append(barSeries);
+ }
+
+ return barSeriesList;
+}
+
+void Bars3DController::handleAxisRangeChangedBySender(QObject *sender)
+{
+ // Data window changed
+ if (sender == m_axisX || sender == m_axisZ) {
+ if (sender == m_axisX)
+ handleDataColumnLabelsChanged();
+ if (sender == m_axisZ)
+ handleDataRowLabelsChanged();
+ }
+
+ Abstract3DController::handleAxisRangeChangedBySender(sender);
+
+ // Update selected bar - may be moved offscreen
+ setSelectedBar(m_selectedBar, m_selectedBarSeries, false);
+}
+
+void Bars3DController::setMultiSeriesScaling(bool uniform)
+{
+ m_isMultiSeriesUniform = uniform;
+
+ m_changeTracker.multiSeriesScalingChanged = true;
+ emitNeedRender();
+}
+
+bool Bars3DController::multiSeriesScaling() const
+{
+ return m_isMultiSeriesUniform;
+}
+
+void Bars3DController::setBarSpecs(GLfloat thicknessRatio, const QSizeF &spacing, bool relative)
+{
+ m_barThicknessRatio = thicknessRatio;
+ m_barSpacing = spacing;
+ m_isBarSpecRelative = relative;
+
+ m_changeTracker.barSpecsChanged = true;
+ emitNeedRender();
+}
+
+GLfloat Bars3DController::barThickness()
+{
+ return m_barThicknessRatio;
+}
+
+QSizeF Bars3DController::barSpacing()
+{
+ return m_barSpacing;
+}
+
+void Bars3DController::setBarSeriesMargin(const QSizeF &margin)
+{
+ m_barSeriesMargin = margin;
+ m_changeTracker.barSeriesMarginChanged = true;
+ emitNeedRender();
+}
+
+QSizeF Bars3DController::barSeriesMargin()
+{
+ return m_barSeriesMargin;
+}
+
+bool Bars3DController::isBarSpecRelative()
+{
+ return m_isBarSpecRelative;
+}
+
+void Bars3DController::setFloorLevel(float level)
+{
+ m_floorLevel = level;
+ m_isDataDirty = true;
+ m_changeTracker.floorLevelChanged = true;
+ emitNeedRender();
+}
+
+float Bars3DController::floorLevel() const
+{
+ return m_floorLevel;
+}
+
+void Bars3DController::setSelectionMode(QAbstract3DGraph::SelectionFlags mode)
+{
+ if (mode.testFlag(QAbstract3DGraph::SelectionSlice)
+ && (mode.testFlag(QAbstract3DGraph::SelectionRow)
+ == mode.testFlag(QAbstract3DGraph::SelectionColumn))) {
+ qWarning("Must specify one of either row or column selection mode in conjunction with slicing mode.");
+ } else {
+ QAbstract3DGraph::SelectionFlags oldMode = selectionMode();
+
+ Abstract3DController::setSelectionMode(mode);
+
+ if (mode != oldMode) {
+ // Refresh selection upon mode change to ensure slicing is correctly updated
+ // according to series the visibility.
+ setSelectedBar(m_selectedBar, m_selectedBarSeries, true);
+
+ // Special case: Always deactivate slicing when changing away from slice
+ // automanagement, as this can't be handled in setSelectedBar.
+ if (!mode.testFlag(QAbstract3DGraph::SelectionSlice)
+ && oldMode.testFlag(QAbstract3DGraph::SelectionSlice)) {
+ scene()->setSlicingActive(false);
+ }
+ }
+ }
+}
+
+void Bars3DController::setSelectedBar(const QPoint &position, QBar3DSeries *series, bool enterSlice)
+{
+ // If the selection targets non-existent bar, clear selection instead.
+ QPoint pos = position;
+
+ // Series may already have been removed, so check it before setting the selection.
+ if (!m_seriesList.contains(series))
+ series = nullptr;
+
+ adjustSelectionPosition(pos, series);
+
+ if (series && selectionMode().testFlag(QAbstract3DGraph::SelectionSlice)) {
+ // If the selected bar is outside data window, or there is no visible selected bar,
+ // disable slicing.
+ if (pos.x() < m_axisZ->min() || pos.x() > m_axisZ->max()
+ || pos.y() < m_axisX->min() || pos.y() > m_axisX->max()
+ || !series->isVisible()) {
+ scene()->setSlicingActive(false);
+ } else if (enterSlice) {
+ scene()->setSlicingActive(true);
+ }
+ emitNeedRender();
+ }
+
+ if (pos != m_selectedBar || series != m_selectedBarSeries) {
+ bool seriesChanged = (series != m_selectedBarSeries);
+ m_selectedBar = pos;
+ m_selectedBarSeries = series;
+ m_changeTracker.selectedBarChanged = true;
+
+ // Clear selection from other series and finally set new selection to the specified series
+ foreach (QAbstract3DSeries *otherSeries, m_seriesList) {
+ QBar3DSeries *barSeries = static_cast<QBar3DSeries *>(otherSeries);
+ if (barSeries != m_selectedBarSeries)
+ barSeries->dptr()->setSelectedBar(invalidSelectionPosition());
+ }
+ if (m_selectedBarSeries)
+ m_selectedBarSeries->dptr()->setSelectedBar(m_selectedBar);
+
+ if (seriesChanged)
+ emit selectedSeriesChanged(m_selectedBarSeries);
+
+ emitNeedRender();
+ }
+}
+
+void Bars3DController::clearSelection()
+{
+ setSelectedBar(invalidSelectionPosition(), 0, false);
+}
+
+void Bars3DController::adjustAxisRanges()
+{
+ QCategory3DAxis *categoryAxisZ = static_cast<QCategory3DAxis *>(m_axisZ);
+ QCategory3DAxis *categoryAxisX = static_cast<QCategory3DAxis *>(m_axisX);
+ QValue3DAxis *valueAxis = static_cast<QValue3DAxis *>(m_axisY);
+
+ bool adjustZ = (categoryAxisZ && categoryAxisZ->isAutoAdjustRange());
+ bool adjustX = (categoryAxisX && categoryAxisX->isAutoAdjustRange());
+ bool adjustY = (valueAxis && categoryAxisX && categoryAxisZ && valueAxis->isAutoAdjustRange());
+
+ if (adjustZ || adjustX || adjustY) {
+ int maxRowCount = 0;
+ int maxColumnCount = 0;
+ float minValue = 0.0f;
+ float maxValue = 0.0f;
+
+ // First figure out row and column counts
+ int seriesCount = m_seriesList.size();
+ if (adjustZ || adjustX) {
+ for (int series = 0; series < seriesCount; series++) {
+ const QBar3DSeries *barSeries =
+ static_cast<QBar3DSeries *>(m_seriesList.at(series));
+ if (barSeries->isVisible()) {
+ const QBarDataProxy *proxy = barSeries->dataProxy();
+
+ if (adjustZ && proxy) {
+ int rowCount = proxy->rowCount();
+ if (rowCount)
+ rowCount--;
+
+ maxRowCount = qMax(maxRowCount, rowCount);
+ }
+
+ if (adjustX && proxy) {
+ const QBarDataArray *array = proxy->array();
+ int columnCount = 0;
+ for (int i = 0; i < array->size(); i++) {
+ if (columnCount < array->at(i)->size())
+ columnCount = array->at(i)->size();
+ }
+ if (columnCount)
+ columnCount--;
+
+ maxColumnCount = qMax(maxColumnCount, columnCount);
+ }
+ }
+ }
+ // Call private implementations of setRange to avoid unsetting auto adjust flag
+ if (adjustZ)
+ categoryAxisZ->dptr()->setRange(0.0f, float(maxRowCount), true);
+ if (adjustX)
+ categoryAxisX->dptr()->setRange(0.0f, float(maxColumnCount), true);
+ }
+
+ // Now that we know the row and column ranges, figure out the value axis range
+ if (adjustY) {
+ for (int series = 0; series < seriesCount; series++) {
+ const QBar3DSeries *barSeries =
+ static_cast<QBar3DSeries *>(m_seriesList.at(series));
+ if (barSeries->isVisible()) {
+ const QBarDataProxy *proxy = barSeries->dataProxy();
+ if (adjustY && proxy) {
+ QPair<GLfloat, GLfloat> limits =
+ proxy->dptrc()->limitValues(categoryAxisZ->min(),
+ categoryAxisZ->max(),
+ categoryAxisX->min(),
+ categoryAxisX->max());
+ if (!series) {
+ // First series initializes the values
+ minValue = limits.first;
+ maxValue = limits.second;
+ } else {
+ minValue = qMin(minValue, limits.first);
+ maxValue = qMax(maxValue, limits.second);
+ }
+ }
+ }
+ }
+
+ if (maxValue < 0.0f)
+ maxValue = 0.0f;
+ if (minValue > 0.0f)
+ minValue = 0.0f;
+ if (minValue == 0.0f && maxValue == 0.0f) {
+ // Only zero value values in data set, set range to something.
+ minValue = 0.0f;
+ maxValue = 1.0f;
+ }
+ valueAxis->dptr()->setRange(minValue, maxValue, true);
+ }
+ }
+}
+
+// Invalidate selection position if outside data for the series
+void Bars3DController::adjustSelectionPosition(QPoint &pos, const QBar3DSeries *series)
+{
+ const QBarDataProxy *proxy = 0;
+ if (series)
+ proxy = series->dataProxy();
+
+ if (!proxy)
+ pos = invalidSelectionPosition();
+
+ if (pos != invalidSelectionPosition()) {
+ int maxRow = proxy->rowCount() - 1;
+ int maxCol = (pos.x() <= maxRow && pos.x() >= 0 && proxy->rowAt(pos.x()))
+ ? proxy->rowAt(pos.x())->size() - 1 : -1;
+
+ if (pos.x() < 0 || pos.x() > maxRow || pos.y() < 0 || pos.y() > maxCol)
+ pos = invalidSelectionPosition();
+ }
+}
+
+QAbstract3DAxis *Bars3DController::createDefaultAxis(QAbstract3DAxis::AxisOrientation orientation)
+{
+ QAbstract3DAxis *defaultAxis = 0;
+
+ if (orientation == QAbstract3DAxis::AxisOrientationY)
+ defaultAxis = createDefaultValueAxis();
+ else
+ defaultAxis = createDefaultCategoryAxis();
+
+ return defaultAxis;
+}
+
+QT_END_NAMESPACE
diff --git a/src/graphs/engine/bars3dcontroller_p.h b/src/graphs/engine/bars3dcontroller_p.h
new file mode 100644
index 0000000..e901f8e
--- /dev/null
+++ b/src/graphs/engine/bars3dcontroller_p.h
@@ -0,0 +1,161 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QtGraphs API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#ifndef Q3DBARSCONTROLLER_p_H
+#define Q3DBARSCONTROLLER_p_H
+
+#include "axishelper_p.h"
+#include <private/graphsglobal_p.h>
+#include <private/abstract3dcontroller_p.h>
+#include <QtQuick3D/private/qquick3drepeater_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QBarDataProxy;
+class QBar3DSeries;
+class QQuick3DModel;
+
+struct Bars3DChangeBitField {
+ bool multiSeriesScalingChanged : 1;
+ bool barSpecsChanged : 1;
+ bool selectedBarChanged : 1;
+ bool rowsChanged : 1;
+ bool itemChanged : 1;
+ bool floorLevelChanged : 1;
+ bool barSeriesMarginChanged : 1;
+
+ Bars3DChangeBitField() :
+ multiSeriesScalingChanged(true),
+ barSpecsChanged(true),
+ selectedBarChanged(true),
+ rowsChanged(false),
+ itemChanged(false),
+ floorLevelChanged(false),
+ barSeriesMarginChanged(false)
+ {
+ }
+};
+
+class Q_GRAPHS_EXPORT Bars3DController : public Abstract3DController
+{
+ Q_OBJECT
+
+public:
+ struct ChangeItem {
+ QBar3DSeries *series;
+ QPoint point;
+ };
+ struct ChangeRow {
+ QBar3DSeries *series;
+ int row;
+ };
+
+private:
+ Bars3DChangeBitField m_changeTracker;
+ QList<ChangeItem> m_changedItems;
+ QList<ChangeRow> m_changedRows;
+
+ // Interaction
+ QPoint m_selectedBar; // Points to row & column in data window.
+ QBar3DSeries *m_selectedBarSeries; // Points to the series for which the bar is selected in
+ // single series selection cases.
+ QBar3DSeries *m_primarySeries; // Category axis labels are taken from the primary series
+
+ // Look'n'feel
+ bool m_isMultiSeriesUniform;
+ bool m_isBarSpecRelative;
+ GLfloat m_barThicknessRatio;
+ QSizeF m_barSpacing;
+ float m_floorLevel;
+ QSizeF m_barSeriesMargin;
+
+public:
+ explicit Bars3DController(QRect rect, Q3DScene *scene = 0);
+ ~Bars3DController();
+
+ void setMultiSeriesScaling(bool uniform);
+ bool multiSeriesScaling() const;
+
+ // bar thickness, spacing between bars, and is spacing relative to thickness or absolute
+ // y -component sets the thickness/spacing of z -direction
+ // With relative 0.0f means side-to-side, 1.0f = one thickness in between
+ void setBarSpecs(GLfloat thicknessRatio = 1.0f,
+ const QSizeF &spacing = QSizeF(1.0, 1.0),
+ bool relative = true);
+ void setBarSeriesMargin(const QSizeF &margin);
+ QSizeF barSeriesMargin();
+
+ GLfloat barThickness();
+ QSizeF barSpacing();
+ bool isBarSpecRelative();
+ void setFloorLevel(float level);
+ float floorLevel() const;
+
+ inline QBar3DSeries *selectedSeries() const { return m_selectedBarSeries; }
+
+ void setSelectionMode(QAbstract3DGraph::SelectionFlags mode) override;
+ void setSelectedBar(const QPoint &position, QBar3DSeries *series, bool enterSlice);
+ void clearSelection() override;
+
+ void handleAxisAutoAdjustRangeChangedInOrientation(
+ QAbstract3DAxis::AxisOrientation orientation, bool autoAdjust) override;
+ void handleSeriesVisibilityChangedBySender(QObject *sender) override;
+
+ static QPoint invalidSelectionPosition();
+
+ void setAxisX(QAbstract3DAxis *axis) override;
+ void setAxisZ(QAbstract3DAxis *axis) override;
+
+ virtual void setPrimarySeries(QBar3DSeries *series);
+ virtual QBar3DSeries *primarySeries() const;
+ void addSeries(QAbstract3DSeries *series) override;
+ void removeSeries(QAbstract3DSeries *series) override;
+ void insertSeries(int index, QAbstract3DSeries *series) override;
+ virtual QList<QBar3DSeries *> barSeriesList();
+
+ void handleAxisRangeChangedBySender(QObject *sender) override;
+ void adjustAxisRanges() override;
+
+public Q_SLOTS:
+ void handleArrayReset();
+ void handleRowsAdded(int startIndex, int count);
+ void handleRowsChanged(int startIndex, int count);
+ void handleRowsRemoved(int startIndex, int count);
+ void handleRowsInserted(int startIndex, int count);
+ void handleItemChanged(int rowIndex, int columnIndex);
+ void handleDataRowLabelsChanged();
+ void handleDataColumnLabelsChanged();
+ void handleRowColorsChanged();
+
+Q_SIGNALS:
+ void primarySeriesChanged(QBar3DSeries *series);
+ void selectedSeriesChanged(QBar3DSeries *series);
+
+protected:
+ QAbstract3DAxis *createDefaultAxis(QAbstract3DAxis::AxisOrientation orientation) override;
+ QSizeF m_cachedBarThickness;
+ QSizeF m_cachedBarSpacing;
+
+ void updateBarSpecs(float thicknessRatio, const QSizeF &spacing, bool relative);
+
+private:
+ void adjustSelectionPosition(QPoint &pos, const QBar3DSeries *series);
+
+ Q_DISABLE_COPY(Bars3DController)
+ friend class QQuickGraphsItem;
+ friend class QQuickGraphsBars;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/engine/meshes/arrowFlat.mesh b/src/graphs/engine/meshes/arrowFlat.mesh
new file mode 100644
index 0000000..f152dc0
--- /dev/null
+++ b/src/graphs/engine/meshes/arrowFlat.mesh
Binary files differ
diff --git a/src/graphs/engine/meshes/arrowFlat.obj b/src/graphs/engine/meshes/arrowFlat.obj
new file mode 100644
index 0000000..bb9762d
--- /dev/null
+++ b/src/graphs/engine/meshes/arrowFlat.obj
@@ -0,0 +1,403 @@
+# Blender v2.69 (sub 0) OBJ File: 'arrow.blend'
+# www.blender.org
+v 0.000000 1.000000 0.000000
+v 0.000000 0.000000 -1.000000
+v -0.195090 0.000000 -0.980785
+v -0.382683 0.000000 -0.923880
+v -0.555570 0.000000 -0.831470
+v -0.707107 0.000000 -0.707107
+v -0.831470 0.000000 -0.555570
+v -0.923880 0.000000 -0.382683
+v -0.980785 0.000000 -0.195090
+v -1.000000 0.000000 -0.000000
+v -0.980785 0.000000 0.195090
+v -0.923880 0.000000 0.382683
+v -0.831470 0.000000 0.555570
+v -0.707107 0.000000 0.707107
+v -0.555570 0.000000 0.831470
+v -0.382683 0.000000 0.923880
+v -0.195090 0.000000 0.980785
+v 0.000000 0.000000 1.000000
+v 0.195091 0.000000 0.980785
+v 0.382684 0.000000 0.923879
+v 0.555571 0.000000 0.831469
+v 0.707107 0.000000 0.707106
+v 0.831470 0.000000 0.555570
+v 0.923880 0.000000 0.382683
+v 0.980785 0.000000 0.195089
+v 1.000000 0.000000 -0.000001
+v 0.980785 0.000000 -0.195091
+v 0.923879 0.000000 -0.382684
+v 0.831469 0.000000 -0.555571
+v 0.707106 0.000000 -0.707108
+v 0.555569 0.000000 -0.831470
+v 0.382682 0.000000 -0.923880
+v 0.195089 0.000000 -0.980786
+v 0.000000 0.000000 0.000000
+v 0.000000 0.000000 0.000000
+v -0.000000 0.000000 -0.578860
+v -0.112930 0.000000 -0.567737
+v -0.221520 0.000000 -0.534796
+v -0.321597 0.000000 -0.481304
+v -0.409315 0.000000 -0.409316
+v -0.481304 0.000000 -0.321597
+v -0.534796 0.000000 -0.221520
+v -0.567737 0.000000 -0.112930
+v -0.578859 0.000000 -0.000000
+v -0.567737 0.000000 0.112930
+v -0.534796 0.000000 0.221520
+v -0.481304 0.000000 0.321597
+v -0.409315 0.000000 0.409315
+v -0.321597 0.000000 0.481304
+v -0.221520 0.000000 0.534796
+v -0.112930 0.000000 0.567737
+v 0.000000 0.000000 0.578859
+v 0.112930 0.000000 0.567737
+v 0.221520 0.000000 0.534796
+v 0.321597 0.000000 0.481304
+v 0.409316 0.000000 0.409315
+v 0.481304 0.000000 0.321597
+v 0.534796 0.000000 0.221519
+v 0.567737 0.000000 0.112929
+v 0.578859 0.000000 -0.000001
+v 0.567737 0.000000 -0.112931
+v 0.534796 0.000000 -0.221521
+v 0.481304 0.000000 -0.321598
+v 0.409315 0.000000 -0.409316
+v 0.321596 0.000000 -0.481305
+v 0.221519 0.000000 -0.534797
+v 0.112929 0.000000 -0.567737
+v 0.578859 -1.000000 -0.000001
+v 0.567737 -1.000000 0.112929
+v -0.221520 -1.000000 -0.534796
+v -0.112930 -1.000000 -0.567737
+v -0.321597 -1.000000 0.481304
+v -0.409315 -1.000000 0.409315
+v 0.534796 -1.000000 0.221519
+v 0.481304 -1.000000 0.321597
+v -0.481304 -1.000000 0.321597
+v -0.534796 -1.000000 0.221520
+v 0.112929 -1.000000 -0.567737
+v 0.221519 -1.000000 -0.534797
+v 0.409316 -1.000000 0.409315
+v 0.321597 -1.000000 0.481304
+v -0.567737 -1.000000 0.112930
+v -0.578859 -1.000000 -0.000000
+v 0.321596 -1.000000 -0.481305
+v 0.409315 -1.000000 -0.409316
+v 0.221520 -1.000000 0.534796
+v 0.112930 -1.000000 0.567737
+v -0.567737 -1.000000 -0.112930
+v -0.534796 -1.000000 -0.221520
+v 0.481304 -1.000000 -0.321598
+v 0.534796 -1.000000 -0.221521
+v -0.481304 -1.000000 -0.321597
+v -0.409315 -1.000000 -0.409316
+v 0.000000 -1.000000 0.578859
+v -0.112930 -1.000000 0.567737
+v 0.567737 -1.000000 -0.112931
+v -0.321597 -1.000000 -0.481304
+v -0.221520 -1.000000 0.534796
+v -0.000000 -1.000000 -0.578860
+vt 0.000000 0.000000
+vt 0.549236 0.999900
+vt 0.450764 0.999900
+vt 0.645815 0.980689
+vt 0.354184 0.980689
+vt 0.263208 0.943005
+vt 0.181332 0.888297
+vt 0.111702 0.818667
+vt 0.056994 0.736790
+vt 0.019311 0.645815
+vt 0.000100 0.549235
+vt 0.000100 0.450763
+vt 0.019310 0.354184
+vt 0.056994 0.263208
+vt 0.111702 0.181332
+vt 0.943005 0.263208
+vt 0.818666 0.111702
+vt 0.888296 0.181332
+vt 0.980688 0.354184
+vt 0.999899 0.549236
+vt 0.999899 0.450764
+vt 0.943005 0.736791
+vt 0.980689 0.645815
+vt 0.181332 0.111702
+vt 0.736791 0.943006
+vt 0.263209 0.056994
+vt 0.354185 0.019311
+vt 0.450764 0.000100
+vt 0.818667 0.888298
+vt 0.888297 0.818668
+vt 0.549235 0.000100
+vt 0.645815 0.019311
+vt 0.736791 0.056994
+vn -0.069476 0.705398 -0.705398
+vn -0.205757 0.705398 -0.678290
+vn -0.334131 0.705398 -0.625116
+vn -0.449665 0.705398 -0.547918
+vn -0.547918 0.705398 -0.449665
+vn -0.625116 0.705398 -0.334131
+vn -0.678290 0.705398 -0.205757
+vn -0.705398 0.705398 -0.069476
+vn -0.705398 0.705398 0.069476
+vn -0.678290 0.705398 0.205757
+vn -0.625116 0.705398 0.334131
+vn -0.547918 0.705398 0.449665
+vn -0.449665 0.705398 0.547918
+vn -0.334131 0.705398 0.625116
+vn -0.205757 0.705398 0.678290
+vn -0.069476 0.705398 0.705398
+vn 0.069476 0.705398 0.705398
+vn 0.205757 0.705398 0.678290
+vn 0.334132 0.705398 0.625116
+vn 0.449665 0.705398 0.547918
+vn 0.547919 0.705398 0.449665
+vn 0.625116 0.705398 0.334131
+vn 0.678290 0.705398 0.205756
+vn 0.705398 0.705398 0.069475
+vn 0.705398 0.705398 -0.069476
+vn 0.678290 0.705398 -0.205758
+vn 0.625115 0.705398 -0.334132
+vn 0.547918 0.705398 -0.449666
+vn 0.449664 0.705398 -0.547919
+vn 0.334130 0.705398 -0.625116
+vn 0.205756 0.705398 -0.678290
+vn 0.069475 0.705398 -0.705398
+vn 0.000000 -1.000000 0.000000
+vn -0.290284 0.000000 0.956940
+vn -0.098016 0.000000 0.995185
+vn -0.634393 0.000000 -0.773010
+vn -0.773011 0.000000 -0.634393
+vn 0.956940 0.000000 -0.290286
+vn 0.881921 0.000000 -0.471398
+vn -0.881922 0.000000 -0.471396
+vn -0.956940 0.000000 -0.290285
+vn 0.098017 0.000000 0.995185
+vn 0.290284 0.000000 0.956940
+vn 0.773010 0.000000 -0.634394
+vn 0.634392 0.000000 -0.773011
+vn -0.995185 0.000000 -0.098017
+vn -0.995185 0.000000 0.098017
+vn 0.471397 0.000000 0.881921
+vn 0.634394 0.000000 0.773010
+vn 0.471395 0.000000 -0.881922
+vn 0.290283 0.000000 -0.956941
+vn 0.098017 0.000000 -0.995185
+vn -0.956940 0.000000 0.290285
+vn -0.881921 0.000000 0.471396
+vn 0.773011 0.000000 0.634393
+vn 0.881922 0.000000 0.471396
+vn -0.773010 0.000000 0.634393
+vn -0.634393 0.000000 0.773010
+vn -0.471397 0.000000 0.881921
+vn -0.098018 0.000000 -0.995185
+vn -0.290284 0.000000 -0.956940
+vn -0.471397 0.000000 -0.881921
+vn 0.956941 0.000000 0.290284
+vn 0.995185 0.000000 0.098016
+vn 0.995185 0.000000 -0.098018
+vn 0.000000 -1.000000 -0.000004
+vn 0.000000 -1.000000 -0.000001
+vn 0.000000 -1.000000 0.000001
+vn -0.098017 0.000000 0.995185
+vn -0.773010 0.000000 -0.634393
+vn -0.881921 0.000000 -0.471396
+vn 0.098018 0.000000 0.995185
+vn 0.290285 0.000000 0.956940
+vn 0.773009 0.000000 -0.634394
+vn 0.471396 0.000000 -0.881922
+vn -0.881921 0.000000 0.471397
+vn -0.098017 0.000000 -0.995185
+vn -0.471396 0.000000 -0.881921
+s off
+f 1/1/1 2/1/1 3/1/1
+f 1/1/2 3/1/2 4/1/2
+f 1/1/3 4/1/3 5/1/3
+f 1/1/4 5/1/4 6/1/4
+f 1/1/5 6/1/5 7/1/5
+f 1/1/6 7/1/6 8/1/6
+f 1/1/7 8/1/7 9/1/7
+f 1/1/8 9/1/8 10/1/8
+f 1/1/9 10/1/9 11/1/9
+f 1/1/10 11/1/10 12/1/10
+f 1/1/11 12/1/11 13/1/11
+f 1/1/12 13/1/12 14/1/12
+f 1/1/13 14/1/13 15/1/13
+f 1/1/14 15/1/14 16/1/14
+f 1/1/15 16/1/15 17/1/15
+f 1/1/16 17/1/16 18/1/16
+f 1/1/17 18/1/17 19/1/17
+f 1/1/18 19/1/18 20/1/18
+f 1/1/19 20/1/19 21/1/19
+f 1/1/20 21/1/20 22/1/20
+f 1/1/21 22/1/21 23/1/21
+f 1/1/22 23/1/22 24/1/22
+f 1/1/23 24/1/23 25/1/23
+f 1/1/24 25/1/24 26/1/24
+f 1/1/25 26/1/25 27/1/25
+f 1/1/26 27/1/26 28/1/26
+f 1/1/27 28/1/27 29/1/27
+f 1/1/28 29/1/28 30/1/28
+f 1/1/29 30/1/29 31/1/29
+f 1/1/30 31/1/30 32/1/30
+f 1/1/31 32/1/31 33/1/31
+f 1/1/32 33/1/32 2/1/32
+f 24/1/33 23/1/33 58/1/33
+f 13/1/33 12/1/33 47/1/33
+f 2/1/33 33/1/33 36/1/33
+f 23/1/33 22/1/33 57/1/33
+f 12/1/33 11/1/33 46/1/33
+f 33/1/33 32/1/33 67/1/33
+f 22/1/33 21/1/33 56/1/33
+f 11/1/33 10/1/33 45/1/33
+f 32/1/33 31/1/33 66/1/33
+f 21/1/33 20/1/33 55/1/33
+f 10/1/33 9/1/33 44/1/33
+f 31/1/33 30/1/33 65/1/33
+f 20/1/33 19/1/33 54/1/33
+f 9/1/33 8/1/33 43/1/33
+f 30/1/33 29/1/33 64/1/33
+f 19/1/33 18/1/33 53/1/33
+f 3/1/33 2/1/33 37/1/33
+f 8/1/33 7/1/33 42/1/33
+f 29/1/33 28/1/33 63/1/33
+f 7/1/33 6/1/33 41/1/33
+f 18/1/33 17/1/33 52/1/33
+f 28/1/33 27/1/33 62/1/33
+f 6/1/33 5/1/33 40/1/33
+f 17/1/33 16/1/33 51/1/33
+f 27/1/33 26/1/33 61/1/33
+f 5/1/33 4/1/33 39/1/33
+f 16/1/33 15/1/33 50/1/33
+f 26/1/33 25/1/33 60/1/33
+f 4/1/33 3/1/33 38/1/33
+f 15/1/33 14/1/33 49/1/33
+f 25/1/33 24/1/33 59/1/33
+f 14/1/33 13/1/33 48/1/33
+f 81/2/33 86/3/33 80/4/33
+f 51/1/34 50/1/34 95/1/34
+f 52/1/35 51/1/35 94/1/35
+f 40/1/36 39/1/36 93/1/36
+f 41/1/37 40/1/37 92/1/37
+f 62/1/38 61/1/38 91/1/38
+f 63/1/39 62/1/39 90/1/39
+f 42/1/40 41/1/40 89/1/40
+f 43/1/41 42/1/41 88/1/41
+f 53/1/42 52/1/42 87/1/42
+f 54/1/43 53/1/43 86/1/43
+f 64/1/44 63/1/44 85/1/44
+f 65/1/45 64/1/45 84/1/45
+f 44/1/46 43/1/46 83/1/46
+f 45/1/47 44/1/47 82/1/47
+f 55/1/48 54/1/48 81/1/48
+f 56/1/49 55/1/49 80/1/49
+f 66/1/50 65/1/50 79/1/50
+f 67/1/51 66/1/51 78/1/51
+f 36/1/52 67/1/52 99/1/52
+f 46/1/53 45/1/53 77/1/53
+f 47/1/54 46/1/54 76/1/54
+f 57/1/55 56/1/55 75/1/55
+f 58/1/56 57/1/56 74/1/56
+f 48/1/57 47/1/57 73/1/57
+f 49/1/58 48/1/58 72/1/58
+f 50/1/59 49/1/59 98/1/59
+f 37/1/60 36/1/60 71/1/60
+f 38/1/61 37/1/61 70/1/61
+f 39/1/62 38/1/62 97/1/62
+f 59/1/63 58/1/63 69/1/63
+f 60/1/64 59/1/64 68/1/64
+f 61/1/65 60/1/65 96/1/65
+f 23/1/33 57/1/33 58/1/33
+f 12/1/33 46/1/33 47/1/33
+f 33/1/33 67/1/33 36/1/33
+f 22/1/33 56/1/33 57/1/33
+f 11/1/33 45/1/33 46/1/33
+f 32/1/33 66/1/33 67/1/33
+f 21/1/33 55/1/33 56/1/33
+f 10/1/33 44/1/33 45/1/33
+f 31/1/33 65/1/33 66/1/33
+f 20/1/33 54/1/33 55/1/33
+f 9/1/33 43/1/33 44/1/33
+f 30/1/33 64/1/33 65/1/33
+f 19/1/33 53/1/33 54/1/33
+f 8/1/33 42/1/33 43/1/33
+f 29/1/33 63/1/33 64/1/33
+f 18/1/33 52/1/33 53/1/33
+f 2/1/33 36/1/33 37/1/33
+f 7/1/33 41/1/33 42/1/33
+f 28/1/33 62/1/33 63/1/33
+f 6/1/33 40/1/33 41/1/33
+f 17/1/33 51/1/33 52/1/33
+f 27/1/33 61/1/33 62/1/33
+f 5/1/33 39/1/33 40/1/33
+f 16/1/33 50/1/33 51/1/33
+f 26/1/33 60/1/33 61/1/33
+f 4/1/33 38/1/33 39/1/33
+f 15/1/33 49/1/33 50/1/33
+f 25/1/33 59/1/33 60/1/33
+f 3/1/33 37/1/33 38/1/33
+f 14/1/33 48/1/33 49/1/33
+f 24/1/33 58/1/33 59/1/33
+f 13/1/33 47/1/33 48/1/33
+f 86/3/33 87/5/33 80/4/33
+f 87/5/66 94/6/66 80/4/66
+f 94/6/33 95/7/33 80/4/33
+f 95/7/33 98/8/33 80/4/33
+f 98/8/33 72/9/33 80/4/33
+f 72/9/33 73/10/33 80/4/33
+f 73/10/33 76/11/33 80/4/33
+f 76/11/33 77/12/33 80/4/33
+f 77/12/67 82/13/67 80/4/67
+f 82/13/33 83/14/33 80/4/33
+f 83/14/68 88/15/68 80/4/68
+f 84/16/33 78/17/33 79/18/33
+f 85/19/66 78/17/66 84/16/66
+f 91/20/33 85/19/33 90/21/33
+f 91/20/33 78/17/33 85/19/33
+f 68/22/33 91/20/33 96/23/33
+f 68/22/33 78/17/33 91/20/33
+f 88/15/67 89/24/67 80/4/67
+f 80/4/33 89/24/33 75/25/33
+f 89/24/33 92/26/33 75/25/33
+f 92/26/68 93/27/68 75/25/68
+f 93/27/33 97/28/33 75/25/33
+f 75/25/33 97/28/33 74/29/33
+f 69/30/33 78/17/33 68/22/33
+f 74/29/33 97/28/33 69/30/33
+f 97/28/33 70/31/33 69/30/33
+f 70/31/68 71/32/68 69/30/68
+f 71/32/33 99/33/33 69/30/33
+f 99/33/33 78/17/33 69/30/33
+f 50/1/34 98/1/34 95/1/34
+f 51/1/69 95/1/69 94/1/69
+f 39/1/36 97/1/36 93/1/36
+f 40/1/70 93/1/70 92/1/70
+f 61/1/38 96/1/38 91/1/38
+f 62/1/39 91/1/39 90/1/39
+f 41/1/71 92/1/71 89/1/71
+f 42/1/41 89/1/41 88/1/41
+f 52/1/72 94/1/72 87/1/72
+f 53/1/73 87/1/73 86/1/73
+f 63/1/74 90/1/74 85/1/74
+f 64/1/45 85/1/45 84/1/45
+f 43/1/46 88/1/46 83/1/46
+f 44/1/47 83/1/47 82/1/47
+f 54/1/48 86/1/48 81/1/48
+f 55/1/49 81/1/49 80/1/49
+f 65/1/75 84/1/75 79/1/75
+f 66/1/51 79/1/51 78/1/51
+f 67/1/52 78/1/52 99/1/52
+f 45/1/53 82/1/53 77/1/53
+f 46/1/76 77/1/76 76/1/76
+f 56/1/55 80/1/55 75/1/55
+f 57/1/56 75/1/56 74/1/56
+f 47/1/57 76/1/57 73/1/57
+f 48/1/58 73/1/58 72/1/58
+f 49/1/59 72/1/59 98/1/59
+f 36/1/77 99/1/77 71/1/77
+f 37/1/61 71/1/61 70/1/61
+f 38/1/78 70/1/78 97/1/78
+f 58/1/63 74/1/63 69/1/63
+f 59/1/64 69/1/64 68/1/64
+f 60/1/65 68/1/65 96/1/65
diff --git a/src/graphs/engine/meshes/arrowSmooth.mesh b/src/graphs/engine/meshes/arrowSmooth.mesh
new file mode 100644
index 0000000..85494d0
--- /dev/null
+++ b/src/graphs/engine/meshes/arrowSmooth.mesh
Binary files differ
diff --git a/src/graphs/engine/meshes/arrowSmooth.obj b/src/graphs/engine/meshes/arrowSmooth.obj
new file mode 100644
index 0000000..898c4a2
--- /dev/null
+++ b/src/graphs/engine/meshes/arrowSmooth.obj
@@ -0,0 +1,422 @@
+# Blender v2.69 (sub 0) OBJ File: 'arrow.blend'
+# www.blender.org
+v 0.000000 1.000000 0.000000
+v 0.000000 0.000000 -1.000000
+v -0.195090 0.000000 -0.980785
+v -0.382683 0.000000 -0.923880
+v -0.555570 0.000000 -0.831470
+v -0.707107 0.000000 -0.707107
+v -0.831470 0.000000 -0.555570
+v -0.923880 0.000000 -0.382683
+v -0.980785 0.000000 -0.195090
+v -1.000000 0.000000 -0.000000
+v -0.980785 0.000000 0.195090
+v -0.923880 0.000000 0.382683
+v -0.831470 0.000000 0.555570
+v -0.707107 0.000000 0.707107
+v -0.555570 0.000000 0.831470
+v -0.382683 0.000000 0.923880
+v -0.195090 0.000000 0.980785
+v 0.000000 0.000000 1.000000
+v 0.195091 0.000000 0.980785
+v 0.382684 0.000000 0.923879
+v 0.555571 0.000000 0.831469
+v 0.707107 0.000000 0.707106
+v 0.831470 0.000000 0.555570
+v 0.923880 0.000000 0.382683
+v 0.980785 0.000000 0.195089
+v 1.000000 0.000000 -0.000001
+v 0.980785 0.000000 -0.195091
+v 0.923879 0.000000 -0.382684
+v 0.831469 0.000000 -0.555571
+v 0.707106 0.000000 -0.707108
+v 0.555569 0.000000 -0.831470
+v 0.382682 0.000000 -0.923880
+v 0.195089 0.000000 -0.980786
+v 0.000000 0.000000 0.000000
+v 0.000000 0.000000 0.000000
+v -0.000000 0.000000 -0.578860
+v -0.112930 0.000000 -0.567737
+v -0.221520 0.000000 -0.534796
+v -0.321597 0.000000 -0.481304
+v -0.409315 0.000000 -0.409316
+v -0.481304 0.000000 -0.321597
+v -0.534796 0.000000 -0.221520
+v -0.567737 0.000000 -0.112930
+v -0.578859 0.000000 -0.000000
+v -0.567737 0.000000 0.112930
+v -0.534796 0.000000 0.221520
+v -0.481304 0.000000 0.321597
+v -0.409315 0.000000 0.409315
+v -0.321597 0.000000 0.481304
+v -0.221520 0.000000 0.534796
+v -0.112930 0.000000 0.567737
+v 0.000000 0.000000 0.578859
+v 0.112930 0.000000 0.567737
+v 0.221520 0.000000 0.534796
+v 0.321597 0.000000 0.481304
+v 0.409316 0.000000 0.409315
+v 0.481304 0.000000 0.321597
+v 0.534796 0.000000 0.221519
+v 0.567737 0.000000 0.112929
+v 0.578859 0.000000 -0.000001
+v 0.567737 0.000000 -0.112931
+v 0.534796 0.000000 -0.221521
+v 0.481304 0.000000 -0.321598
+v 0.409315 0.000000 -0.409316
+v 0.321596 0.000000 -0.481305
+v 0.221519 0.000000 -0.534797
+v 0.112929 0.000000 -0.567737
+v 0.578859 -1.000000 -0.000001
+v 0.567737 -1.000000 0.112929
+v -0.221520 -1.000000 -0.534796
+v -0.112930 -1.000000 -0.567737
+v -0.321597 -1.000000 0.481304
+v -0.409315 -1.000000 0.409315
+v 0.534796 -1.000000 0.221519
+v 0.481304 -1.000000 0.321597
+v -0.481304 -1.000000 0.321597
+v -0.534796 -1.000000 0.221520
+v 0.112929 -1.000000 -0.567737
+v 0.221519 -1.000000 -0.534797
+v 0.409316 -1.000000 0.409315
+v 0.321597 -1.000000 0.481304
+v -0.567737 -1.000000 0.112930
+v -0.578859 -1.000000 -0.000000
+v 0.321596 -1.000000 -0.481305
+v 0.409315 -1.000000 -0.409316
+v 0.221520 -1.000000 0.534796
+v 0.112930 -1.000000 0.567737
+v -0.567737 -1.000000 -0.112930
+v -0.534796 -1.000000 -0.221520
+v 0.481304 -1.000000 -0.321598
+v 0.534796 -1.000000 -0.221521
+v -0.481304 -1.000000 -0.321597
+v -0.409315 -1.000000 -0.409316
+v 0.000000 -1.000000 0.578859
+v -0.112930 -1.000000 0.567737
+v 0.567737 -1.000000 -0.112931
+v -0.321597 -1.000000 -0.481304
+v -0.221520 -1.000000 0.534796
+v -0.000000 -1.000000 -0.578860
+vt 0.000000 0.000000
+vt 0.549236 0.999900
+vt 0.450764 0.999900
+vt 0.645815 0.980689
+vt 0.354184 0.980689
+vt 0.263208 0.943005
+vt 0.181332 0.888297
+vt 0.111702 0.818667
+vt 0.056994 0.736790
+vt 0.019311 0.645815
+vt 0.000100 0.549235
+vt 0.000100 0.450763
+vt 0.019310 0.354184
+vt 0.056994 0.263208
+vt 0.111702 0.181332
+vt 0.943005 0.263208
+vt 0.818666 0.111702
+vt 0.888296 0.181332
+vt 0.980688 0.354184
+vt 0.999899 0.549236
+vt 0.999899 0.450764
+vt 0.943005 0.736791
+vt 0.980689 0.645815
+vt 0.181332 0.111702
+vt 0.736791 0.943006
+vt 0.263209 0.056994
+vt 0.354185 0.019311
+vt 0.450764 0.000100
+vt 0.818667 0.888298
+vt 0.888297 0.818668
+vt 0.549235 0.000100
+vt 0.645815 0.019311
+vt 0.736791 0.056994
+vn 0.000000 1.000000 0.000000
+vn 0.000000 -0.363689 -0.931516
+vn -0.181707 -0.363689 -0.913602
+vn -0.356456 -0.363689 -0.860591
+vn -0.517502 -0.363689 -0.774499
+vn -0.658681 -0.363689 -0.658681
+vn -0.774499 -0.363689 -0.517502
+vn -0.860591 -0.363689 -0.356456
+vn -0.913602 -0.363689 -0.181707
+vn -0.931516 -0.363689 0.000000
+vn -0.913602 -0.363689 0.181707
+vn -0.860591 -0.363689 0.356456
+vn -0.774499 -0.363689 0.517502
+vn -0.658681 -0.363689 0.658681
+vn -0.517502 -0.363689 0.774499
+vn -0.356456 -0.363689 0.860591
+vn -0.181707 -0.363689 0.913602
+vn 0.000000 -0.363689 0.931516
+vn 0.181707 -0.363689 0.913602
+vn 0.356456 -0.363689 0.860591
+vn 0.517502 -0.363689 0.774499
+vn 0.658681 -0.363689 0.658681
+vn 0.774499 -0.363689 0.517502
+vn 0.860591 -0.363689 0.356456
+vn 0.913602 -0.363689 0.181707
+vn 0.931516 -0.363689 0.000000
+vn 0.913602 -0.363689 -0.181707
+vn 0.860591 -0.363689 -0.356456
+vn 0.774499 -0.363689 -0.517502
+vn 0.658681 -0.363689 -0.658681
+vn 0.517502 -0.363689 -0.774499
+vn 0.356456 -0.363689 -0.860591
+vn 0.181707 -0.363689 -0.913602
+vn 0.631550 -0.729820 0.261605
+vn -0.568377 -0.729820 0.379772
+vn 0.000000 -0.729820 -0.683584
+vn 0.568377 -0.729820 0.379772
+vn -0.631550 -0.729820 0.261605
+vn 0.133335 -0.729820 -0.670461
+vn 0.483383 -0.729820 0.483383
+vn -0.670461 -0.729820 0.133335
+vn 0.261574 -0.729820 -0.631550
+vn 0.379772 -0.729820 0.568377
+vn -0.683584 -0.729820 0.000000
+vn 0.379772 -0.729820 -0.568377
+vn 0.261605 -0.729820 0.631550
+vn -0.670461 -0.729820 -0.133335
+vn 0.483383 -0.729820 -0.483383
+vn 0.133335 -0.729820 0.670461
+vn -0.133335 -0.729820 -0.670461
+vn -0.631550 -0.729820 -0.261605
+vn 0.568377 -0.729820 -0.379772
+vn -0.568377 -0.729820 -0.379772
+vn 0.000000 -0.729820 0.683584
+vn 0.631550 -0.729820 -0.261605
+vn -0.483383 -0.729820 -0.483383
+vn -0.133335 -0.729820 0.670461
+vn 0.670461 -0.729820 -0.133366
+vn -0.379772 -0.729820 -0.568377
+vn -0.261605 -0.729820 0.631550
+vn 0.683584 -0.729820 0.000000
+vn -0.261605 -0.729820 -0.631550
+vn -0.379772 -0.729820 0.568377
+vn 0.670461 -0.729820 0.133335
+vn -0.483383 -0.729820 0.483383
+vn 0.404370 -0.685690 0.605213
+vn 0.278542 -0.685690 0.672475
+vn 0.514695 -0.685690 0.514664
+vn -0.142003 -0.685690 0.713889
+vn 0.000000 -0.685690 0.727866
+vn -0.514664 -0.685690 -0.514664
+vn -0.605213 -0.685690 -0.404370
+vn 0.672475 -0.685690 -0.278542
+vn 0.605213 -0.685690 -0.404370
+vn -0.672475 -0.685690 -0.278542
+vn -0.713889 -0.685690 -0.142003
+vn 0.142003 -0.685690 0.713889
+vn 0.514664 -0.685690 -0.514695
+vn 0.404370 -0.685690 -0.605213
+vn -0.727866 -0.685690 0.000000
+vn -0.713889 -0.685690 0.142003
+vn 0.278542 -0.685690 -0.672475
+vn 0.142003 -0.685690 -0.713889
+vn 0.000000 -0.685690 -0.727866
+vn -0.672475 -0.685690 0.278542
+vn -0.605213 -0.685690 0.404370
+vn 0.605213 -0.685690 0.404370
+vn 0.672475 -0.685690 0.278542
+vn -0.514664 -0.685690 0.514664
+vn -0.404370 -0.685690 0.605213
+vn -0.278542 -0.685690 0.672475
+vn -0.142003 -0.685690 -0.713889
+vn -0.278542 -0.685690 -0.672475
+vn -0.404370 -0.685690 -0.605213
+vn 0.713889 -0.685690 0.142003
+vn 0.727866 -0.685690 0.000000
+vn 0.713889 -0.685690 -0.142003
+s 1
+f 1/1/1 2/1/2 3/1/3
+f 1/1/1 3/1/3 4/1/4
+f 1/1/1 4/1/4 5/1/5
+f 1/1/1 5/1/5 6/1/6
+f 1/1/1 6/1/6 7/1/7
+f 1/1/1 7/1/7 8/1/8
+f 1/1/1 8/1/8 9/1/9
+f 1/1/1 9/1/9 10/1/10
+f 1/1/1 10/1/10 11/1/11
+f 1/1/1 11/1/11 12/1/12
+f 1/1/1 12/1/12 13/1/13
+f 1/1/1 13/1/13 14/1/14
+f 1/1/1 14/1/14 15/1/15
+f 1/1/1 15/1/15 16/1/16
+f 1/1/1 16/1/16 17/1/17
+f 1/1/1 17/1/17 18/1/18
+f 1/1/1 18/1/18 19/1/19
+f 1/1/1 19/1/19 20/1/20
+f 1/1/1 20/1/20 21/1/21
+f 1/1/1 21/1/21 22/1/22
+f 1/1/1 22/1/22 23/1/23
+f 1/1/1 23/1/23 24/1/24
+f 1/1/1 24/1/24 25/1/25
+f 1/1/1 25/1/25 26/1/26
+f 1/1/1 26/1/26 27/1/27
+f 1/1/1 27/1/27 28/1/28
+f 1/1/1 28/1/28 29/1/29
+f 1/1/1 29/1/29 30/1/30
+f 1/1/1 30/1/30 31/1/31
+f 1/1/1 31/1/31 32/1/32
+f 1/1/1 32/1/32 33/1/33
+f 1/1/1 33/1/33 2/1/2
+f 24/1/24 23/1/23 58/1/34
+f 13/1/13 12/1/12 47/1/35
+f 2/1/2 33/1/33 36/1/36
+f 23/1/23 22/1/22 57/1/37
+f 12/1/12 11/1/11 46/1/38
+f 33/1/33 32/1/32 67/1/39
+f 22/1/22 21/1/21 56/1/40
+f 11/1/11 10/1/10 45/1/41
+f 32/1/32 31/1/31 66/1/42
+f 21/1/21 20/1/20 55/1/43
+f 10/1/10 9/1/9 44/1/44
+f 31/1/31 30/1/30 65/1/45
+f 20/1/20 19/1/19 54/1/46
+f 9/1/9 8/1/8 43/1/47
+f 30/1/30 29/1/29 64/1/48
+f 19/1/19 18/1/18 53/1/49
+f 3/1/3 2/1/2 37/1/50
+f 8/1/8 7/1/7 42/1/51
+f 29/1/29 28/1/28 63/1/52
+f 7/1/7 6/1/6 41/1/53
+f 18/1/18 17/1/17 52/1/54
+f 28/1/28 27/1/27 62/1/55
+f 6/1/6 5/1/5 40/1/56
+f 17/1/17 16/1/16 51/1/57
+f 27/1/27 26/1/26 61/1/58
+f 5/1/5 4/1/4 39/1/59
+f 16/1/16 15/1/15 50/1/60
+f 26/1/26 25/1/25 60/1/61
+f 4/1/4 3/1/3 38/1/62
+f 15/1/15 14/1/14 49/1/63
+f 25/1/25 24/1/24 59/1/64
+f 14/1/14 13/1/13 48/1/65
+f 81/2/66 86/3/67 80/4/68
+f 51/1/57 50/1/60 95/1/69
+f 52/1/54 51/1/57 94/1/70
+f 40/1/56 39/1/59 93/1/71
+f 41/1/53 40/1/56 92/1/72
+f 62/1/55 61/1/58 91/1/73
+f 63/1/52 62/1/55 90/1/74
+f 42/1/51 41/1/53 89/1/75
+f 43/1/47 42/1/51 88/1/76
+f 53/1/49 52/1/54 87/1/77
+f 54/1/46 53/1/49 86/1/67
+f 64/1/48 63/1/52 85/1/78
+f 65/1/45 64/1/48 84/1/79
+f 44/1/44 43/1/47 83/1/80
+f 45/1/41 44/1/44 82/1/81
+f 55/1/43 54/1/46 81/1/66
+f 56/1/40 55/1/43 80/1/68
+f 66/1/42 65/1/45 79/1/82
+f 67/1/39 66/1/42 78/1/83
+f 36/1/36 67/1/39 99/1/84
+f 46/1/38 45/1/41 77/1/85
+f 47/1/35 46/1/38 76/1/86
+f 57/1/37 56/1/40 75/1/87
+f 58/1/34 57/1/37 74/1/88
+f 48/1/65 47/1/35 73/1/89
+f 49/1/63 48/1/65 72/1/90
+f 50/1/60 49/1/63 98/1/91
+f 37/1/50 36/1/36 71/1/92
+f 38/1/62 37/1/50 70/1/93
+f 39/1/59 38/1/62 97/1/94
+f 59/1/64 58/1/34 69/1/95
+f 60/1/61 59/1/64 68/1/96
+f 61/1/58 60/1/61 96/1/97
+f 23/1/23 57/1/37 58/1/34
+f 12/1/12 46/1/38 47/1/35
+f 33/1/33 67/1/39 36/1/36
+f 22/1/22 56/1/40 57/1/37
+f 11/1/11 45/1/41 46/1/38
+f 32/1/32 66/1/42 67/1/39
+f 21/1/21 55/1/43 56/1/40
+f 10/1/10 44/1/44 45/1/41
+f 31/1/31 65/1/45 66/1/42
+f 20/1/20 54/1/46 55/1/43
+f 9/1/9 43/1/47 44/1/44
+f 30/1/30 64/1/48 65/1/45
+f 19/1/19 53/1/49 54/1/46
+f 8/1/8 42/1/51 43/1/47
+f 29/1/29 63/1/52 64/1/48
+f 18/1/18 52/1/54 53/1/49
+f 2/1/2 36/1/36 37/1/50
+f 7/1/7 41/1/53 42/1/51
+f 28/1/28 62/1/55 63/1/52
+f 6/1/6 40/1/56 41/1/53
+f 17/1/17 51/1/57 52/1/54
+f 27/1/27 61/1/58 62/1/55
+f 5/1/5 39/1/59 40/1/56
+f 16/1/16 50/1/60 51/1/57
+f 26/1/26 60/1/61 61/1/58
+f 4/1/4 38/1/62 39/1/59
+f 15/1/15 49/1/63 50/1/60
+f 25/1/25 59/1/64 60/1/61
+f 3/1/3 37/1/50 38/1/62
+f 14/1/14 48/1/65 49/1/63
+f 24/1/24 58/1/34 59/1/64
+f 13/1/13 47/1/35 48/1/65
+f 86/3/67 87/5/77 80/4/68
+f 87/5/77 94/6/70 80/4/68
+f 94/6/70 95/7/69 80/4/68
+f 95/7/69 98/8/91 80/4/68
+f 98/8/91 72/9/90 80/4/68
+f 72/9/90 73/10/89 80/4/68
+f 73/10/89 76/11/86 80/4/68
+f 76/11/86 77/12/85 80/4/68
+f 77/12/85 82/13/81 80/4/68
+f 82/13/81 83/14/80 80/4/68
+f 83/14/80 88/15/76 80/4/68
+f 84/16/79 78/17/83 79/18/82
+f 85/19/78 78/17/83 84/16/79
+f 91/20/73 85/19/78 90/21/74
+f 91/20/73 78/17/83 85/19/78
+f 68/22/96 91/20/73 96/23/97
+f 68/22/96 78/17/83 91/20/73
+f 88/15/76 89/24/75 80/4/68
+f 80/4/68 89/24/75 75/25/87
+f 89/24/75 92/26/72 75/25/87
+f 92/26/72 93/27/71 75/25/87
+f 93/27/71 97/28/94 75/25/87
+f 75/25/87 97/28/94 74/29/88
+f 69/30/95 78/17/83 68/22/96
+f 74/29/88 97/28/94 69/30/95
+f 97/28/94 70/31/93 69/30/95
+f 70/31/93 71/32/92 69/30/95
+f 71/32/92 99/33/84 69/30/95
+f 99/33/84 78/17/83 69/30/95
+f 50/1/60 98/1/91 95/1/69
+f 51/1/57 95/1/69 94/1/70
+f 39/1/59 97/1/94 93/1/71
+f 40/1/56 93/1/71 92/1/72
+f 61/1/58 96/1/97 91/1/73
+f 62/1/55 91/1/73 90/1/74
+f 41/1/53 92/1/72 89/1/75
+f 42/1/51 89/1/75 88/1/76
+f 52/1/54 94/1/70 87/1/77
+f 53/1/49 87/1/77 86/1/67
+f 63/1/52 90/1/74 85/1/78
+f 64/1/48 85/1/78 84/1/79
+f 43/1/47 88/1/76 83/1/80
+f 44/1/44 83/1/80 82/1/81
+f 54/1/46 86/1/67 81/1/66
+f 55/1/43 81/1/66 80/1/68
+f 65/1/45 84/1/79 79/1/82
+f 66/1/42 79/1/82 78/1/83
+f 67/1/39 78/1/83 99/1/84
+f 45/1/41 82/1/81 77/1/85
+f 46/1/38 77/1/85 76/1/86
+f 56/1/40 80/1/68 75/1/87
+f 57/1/37 75/1/87 74/1/88
+f 47/1/35 76/1/86 73/1/89
+f 48/1/65 73/1/89 72/1/90
+f 49/1/63 72/1/90 98/1/91
+f 36/1/36 99/1/84 71/1/92
+f 37/1/50 71/1/92 70/1/93
+f 38/1/62 70/1/93 97/1/94
+f 58/1/34 74/1/88 69/1/95
+f 59/1/64 69/1/95 68/1/96
+f 60/1/61 68/1/96 96/1/97
diff --git a/src/graphs/engine/meshes/background.mesh b/src/graphs/engine/meshes/background.mesh
new file mode 100644
index 0000000..3f7cb47
--- /dev/null
+++ b/src/graphs/engine/meshes/background.mesh
Binary files differ
diff --git a/src/graphs/engine/meshes/background.obj b/src/graphs/engine/meshes/background.obj
new file mode 100644
index 0000000..5eeb1f2
--- /dev/null
+++ b/src/graphs/engine/meshes/background.obj
@@ -0,0 +1,32 @@
+# Blender v2.66 (sub 0) OBJ File: 'backgroud.blend'
+# www.blender.org
+o Cube
+v 1.000000 -1.000000 -1.000000
+v 1.000000 -1.000000 1.000000
+v -1.000000 -1.000000 1.000000
+v -1.000000 -1.000000 -1.000000
+v 1.000000 1.000000 1.000000
+v -1.000000 1.000000 1.000000
+v -1.000000 1.000000 -1.000000
+vt 0.000000 0.500000
+vt 0.000000 1.000000
+vt 0.500000 1.000000
+vt 0.500000 0.500000
+vt 1.000000 0.500000
+vt 1.000000 0.000000
+vt 0.500000 0.500000
+vt 0.500000 0.000000
+vt 0.000000 0.000000
+vt 0.500000 0.500000
+vt 0.500000 0.000000
+vt 0.000000 0.500000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 0.000000 -1.000000
+vn 1.000000 0.000000 0.000000
+s off
+f 1/1/1 4/2/1 3/3/1
+f 2/4/2 3/5/2 6/6/2
+f 3/7/3 4/8/3 7/9/3
+f 2/10/1 1/1/1 3/3/1
+f 5/11/2 2/4/2 6/6/2
+f 6/12/3 3/7/3 7/9/3
diff --git a/src/graphs/engine/meshes/backgroundNoFloor.mesh b/src/graphs/engine/meshes/backgroundNoFloor.mesh
new file mode 100644
index 0000000..2944ff8
--- /dev/null
+++ b/src/graphs/engine/meshes/backgroundNoFloor.mesh
Binary files differ
diff --git a/src/graphs/engine/meshes/backgroundNoFloor.obj b/src/graphs/engine/meshes/backgroundNoFloor.obj
new file mode 100644
index 0000000..0b94617
--- /dev/null
+++ b/src/graphs/engine/meshes/backgroundNoFloor.obj
@@ -0,0 +1,22 @@
+# Blender v2.66 (sub 0) OBJ File: 'backgroudNegativesWall.blend'
+# www.blender.org
+o Cube
+v 1.000000 1.000000 1.000000
+v -1.000000 1.000000 1.000000
+v -1.000000 1.000000 -1.000000
+v 1.000000 -1.000000 1.000000
+v -1.000000 -1.000000 1.000000
+v -1.000000 -1.000000 -1.000000
+vt 0.000000 0.000000
+vt 0.500000 0.000000
+vt 0.500000 1.000000
+vt 1.000000 0.000000
+vt 1.000000 1.000000
+vt 0.000000 1.000000
+vn 0.000000 -0.000000 -1.000000
+vn 1.000000 0.000000 0.000000
+s off
+f 4/1/1 5/2/1 2/3/1
+f 5/2/2 6/4/2 3/5/2
+f 1/6/1 4/1/1 2/3/1
+f 2/3/2 5/2/2 3/5/2
diff --git a/src/graphs/engine/meshes/barFilledFlat.mesh b/src/graphs/engine/meshes/barFilledFlat.mesh
new file mode 100644
index 0000000..1ff54a1
--- /dev/null
+++ b/src/graphs/engine/meshes/barFilledFlat.mesh
Binary files differ
diff --git a/src/graphs/engine/meshes/barFilledFlat.obj b/src/graphs/engine/meshes/barFilledFlat.obj
new file mode 100644
index 0000000..5f62709
--- /dev/null
+++ b/src/graphs/engine/meshes/barFilledFlat.obj
@@ -0,0 +1,242 @@
+# Blender v2.66 (sub 0) OBJ File: 'smoothcube_filled.blend'
+# www.blender.org
+o Cube
+v -0.980000 -1.000000 1.000000
+v -1.000000 -1.000000 0.980000
+v -0.994142 -1.000000 0.994142
+v -1.000000 -1.000000 -0.980000
+v -0.980000 -1.000000 -1.000000
+v -0.994142 -1.000000 -0.994142
+v 0.980000 -1.000000 -1.000000
+v 1.000000 -1.000000 -0.980000
+v 0.994142 -1.000000 -0.994142
+v 1.000000 -1.000000 0.980000
+v 0.980000 -1.000000 1.000000
+v 0.994142 -1.000000 0.994142
+v -0.980000 0.980000 1.000000
+v -0.980000 1.000000 0.980000
+v -1.000000 0.980000 0.980000
+v -0.980000 0.994142 0.994142
+v -0.994142 0.994142 0.980000
+v -0.994142 0.980000 0.994142
+v -0.992998 0.992998 0.992998
+v -1.000000 0.980000 -0.980000
+v -0.980000 1.000000 -0.980000
+v -0.980000 0.980000 -1.000000
+v -0.994142 0.994142 -0.980000
+v -0.980000 0.994142 -0.994142
+v -0.994142 0.980000 -0.994142
+v -0.992998 0.992998 -0.992998
+v 0.980000 0.980000 -1.000000
+v 0.980000 1.000000 -0.980000
+v 1.000000 0.980000 -0.980000
+v 0.980000 0.994142 -0.994142
+v 0.994142 0.994142 -0.980000
+v 0.994142 0.980000 -0.994142
+v 0.992998 0.992998 -0.992998
+v 1.000000 0.980000 0.980000
+v 0.980000 1.000000 0.980000
+v 0.980000 0.980000 1.000000
+v 0.994142 0.994142 0.980000
+v 0.980000 0.994142 0.994142
+v 0.994142 0.980000 0.994142
+v 0.992998 0.992998 0.992998
+vt 0.339018 0.659342
+vt 0.338105 0.335826
+vt 0.664825 0.343846
+vt 0.669233 0.664931
+vt 0.668227 0.337019
+vt 0.995472 0.336015
+vt 0.337976 0.003372
+vt 0.665888 0.002366
+vt 0.666894 0.330278
+vt 0.672755 0.331283
+vt 0.671749 0.003372
+vt 1.000000 0.330280
+vt 0.335610 0.335927
+vt 0.337937 0.333650
+vt 0.338982 0.331284
+vt 0.338989 0.333650
+vt 0.336616 0.331291
+vt 0.326720 0.346123
+vt 0.326875 0.343630
+vt 0.329215 0.346022
+vt 0.327633 0.669639
+vt 0.330128 0.669538
+vt 0.327801 0.671815
+vt 0.669260 0.330271
+vt 0.666901 0.332644
+vt 0.672610 0.333650
+vt 0.670266 0.331291
+vt 0.669260 0.003379
+vt 0.669412 0.001204
+vt 0.665880 0.000000
+vt 0.668056 0.000185
+vt 0.669089 0.667298
+vt 0.666910 0.667113
+vt 0.665738 0.337027
+vt 0.665891 0.334852
+vt 0.335610 0.003379
+vt 0.335795 0.001204
+vt 0.338863 0.661835
+vt 0.336686 0.661624
+vt 0.001081 0.663795
+vt 0.664657 0.341670
+vt 0.000155 0.335610
+vt 0.000000 0.338102
+vt 0.996478 0.663927
+vt 0.996334 0.666294
+vt 0.671590 0.001006
+vt 0.998835 0.000002
+vt 0.998994 0.002368
+vt 0.337969 0.001006
+vt 0.666744 0.664939
+vt 0.665738 0.667363
+vt 0.665583 0.669855
+vt 0.668068 0.334654
+vt 0.995313 0.333650
+vt 0.336523 0.659443
+vt 0.001006 0.332274
+vt 0.000000 0.004362
+vt 0.001993 0.334637
+vt 0.335761 0.333817
+vt 0.336814 0.333465
+vt 0.329052 0.343841
+vt 0.329977 0.671648
+vt 0.669075 0.332446
+vt 0.670432 0.333465
+vt 0.668254 0.002359
+vt 0.999855 0.332646
+vt 0.000913 0.661618
+vt 0.000973 0.001993
+vt 0.003336 0.001006
+vt 0.331248 0.000000
+vt 0.333617 0.000973
+vt 0.334604 0.003336
+vt 0.335610 0.331248
+vt 0.334637 0.333617
+vt 0.332274 0.334604
+vt 0.004362 0.335610
+vn 0.000000 0.000000 1.000000
+vn 1.000000 -0.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 0.000000 -1.000000
+vn -0.357407 0.357407 0.862856
+vn -0.357409 0.862855 0.357407
+vn -0.862855 0.357409 0.357407
+vn -0.862855 0.357407 -0.357409
+vn -0.357407 0.862855 -0.357409
+vn -0.357408 0.357408 -0.862855
+vn 0.114222 0.380181 -0.917832
+vn 0.380181 0.917832 -0.114222
+vn 0.917832 0.114222 -0.380181
+vn 0.917832 0.380181 0.114222
+vn 0.114222 0.917832 0.380181
+vn 0.380181 0.114222 0.917832
+vn -0.382685 0.923879 0.000000
+vn -0.923879 0.382685 0.000000
+vn -0.382685 0.000000 -0.923879
+vn -0.923879 0.000000 -0.382685
+vn -0.382685 0.000000 0.923879
+vn -0.923879 0.000000 0.382685
+vn 0.000000 0.923879 -0.382685
+vn 0.000000 0.382685 -0.923879
+vn 0.923879 0.000000 -0.382685
+vn 0.382685 0.000000 -0.923879
+vn 0.382685 0.923879 0.000000
+vn 0.923879 0.382685 0.000000
+vn 0.382685 -0.000000 0.923879
+vn 0.923879 -0.000000 0.382685
+vn -0.000000 0.923879 0.382685
+vn -0.000000 0.382685 0.923879
+vn -1.000000 0.000000 0.000000
+vn -0.000000 -1.000000 -0.000000
+vn -0.095602 0.095602 0.990818
+vn -0.095602 0.990818 0.095602
+vn -0.990818 0.095602 0.095602
+vn -0.990818 0.095602 -0.095602
+vn -0.095602 0.990818 -0.095602
+vn -0.095602 0.095602 -0.990818
+vn 0.380181 0.114222 -0.917832
+vn 0.114222 0.917832 -0.380181
+vn 0.917832 0.380181 -0.114222
+vn 0.917832 0.114222 0.380181
+vn 0.380181 0.917832 0.114222
+vn 0.114222 0.380181 0.917832
+s off
+f 36/1/1 13/2/1 1/3/1
+f 29/4/2 34/5/2 10/6/2
+f 35/7/3 28/8/3 21/9/3
+f 22/10/4 27/11/4 5/12/4
+f 13/2/5 16/13/5 18/14/5
+f 14/15/6 17/16/6 16/17/6
+f 15/18/7 18/19/7 17/20/7
+f 20/21/8 23/22/8 25/23/8
+f 21/9/9 24/24/9 23/25/9
+f 22/10/10 25/26/10 24/27/10
+f 27/11/11 30/28/11 33/29/11
+f 28/8/12 31/30/12 33/31/12
+f 29/4/13 32/32/13 33/33/13
+f 34/5/14 37/34/14 40/35/14
+f 35/7/15 38/36/15 40/37/15
+f 36/1/16 39/38/16 40/39/16
+f 14/15/17 21/9/17 17/16/17
+f 17/20/18 23/22/18 15/18/18
+f 22/10/19 5/12/19 25/26/19
+f 25/23/20 6/40/20 20/21/20
+f 1/3/21 13/2/21 3/41/21
+f 3/42/22 18/19/22 2/43/22
+f 21/9/23 28/8/23 24/24/23
+f 24/27/24 30/28/24 22/10/24
+f 29/4/25 8/44/25 9/45/25
+f 32/46/26 9/47/26 7/48/26
+f 28/8/27 35/7/27 37/49/27
+f 31/50/28 37/34/28 34/5/28
+f 36/1/29 11/51/29 12/52/29
+f 39/53/30 12/54/30 10/6/30
+f 35/7/31 14/15/31 16/17/31
+f 38/55/32 16/13/32 13/2/32
+f 15/18/33 20/21/33 2/43/33
+f 5/56/34 7/57/34 6/58/34
+f 11/51/1 36/1/1 1/3/1
+f 8/44/2 29/4/2 10/6/2
+f 14/15/3 35/7/3 21/9/3
+f 27/11/4 7/48/4 5/12/4
+f 16/13/35 19/59/35 18/14/35
+f 17/16/36 19/60/36 16/17/36
+f 18/19/37 19/61/37 17/20/37
+f 23/22/38 26/62/38 25/23/38
+f 24/24/39 26/63/39 23/25/39
+f 25/26/40 26/64/40 24/27/40
+f 32/46/41 27/11/41 33/29/41
+f 30/65/42 28/8/42 33/31/42
+f 31/50/43 29/4/43 33/33/43
+f 39/53/44 34/5/44 40/35/44
+f 37/49/45 35/7/45 40/37/45
+f 38/55/46 36/1/46 40/39/46
+f 21/9/17 23/25/17 17/16/17
+f 23/22/18 20/21/18 15/18/18
+f 5/12/19 6/66/19 25/26/19
+f 6/40/20 4/67/20 20/21/20
+f 13/2/21 18/14/21 3/41/21
+f 18/19/22 15/18/22 2/43/22
+f 28/8/23 30/65/23 24/24/23
+f 30/28/24 27/11/24 22/10/24
+f 32/32/25 29/4/25 9/45/25
+f 27/11/26 32/46/26 7/48/26
+f 31/30/27 28/8/27 37/49/27
+f 29/4/28 31/50/28 34/5/28
+f 39/38/29 36/1/29 12/52/29
+f 34/5/30 39/53/30 10/6/30
+f 38/36/31 35/7/31 16/17/31
+f 36/1/32 38/55/32 13/2/32
+f 20/21/33 4/67/33 2/43/33
+f 7/57/34 9/68/34 6/58/34
+f 9/68/34 8/69/34 6/58/34
+f 8/69/34 10/70/34 6/58/34
+f 10/70/34 12/71/34 6/58/34
+f 12/71/34 11/72/34 6/58/34
+f 11/72/34 1/73/34 6/58/34
+f 1/73/34 3/74/34 6/58/34
+f 3/74/34 2/75/34 4/76/34
+f 6/58/34 3/74/34 4/76/34
diff --git a/src/graphs/engine/meshes/barFilledSmooth.mesh b/src/graphs/engine/meshes/barFilledSmooth.mesh
new file mode 100644
index 0000000..6924420
--- /dev/null
+++ b/src/graphs/engine/meshes/barFilledSmooth.mesh
Binary files differ
diff --git a/src/graphs/engine/meshes/barFilledSmooth.obj b/src/graphs/engine/meshes/barFilledSmooth.obj
new file mode 100644
index 0000000..efc4317
--- /dev/null
+++ b/src/graphs/engine/meshes/barFilledSmooth.obj
@@ -0,0 +1,236 @@
+# Blender v2.66 (sub 0) OBJ File: 'smoothcube_filled.blend'
+# www.blender.org
+o Cube
+v -0.980000 -1.000000 1.000000
+v -1.000000 -1.000000 0.980000
+v -0.994142 -1.000000 0.994142
+v -1.000000 -1.000000 -0.980000
+v -0.980000 -1.000000 -1.000000
+v -0.994142 -1.000000 -0.994142
+v 0.980000 -1.000000 -1.000000
+v 1.000000 -1.000000 -0.980000
+v 0.994142 -1.000000 -0.994142
+v 1.000000 -1.000000 0.980000
+v 0.980000 -1.000000 1.000000
+v 0.994142 -1.000000 0.994142
+v -0.980000 0.980000 1.000000
+v -0.980000 1.000000 0.980000
+v -1.000000 0.980000 0.980000
+v -0.980000 0.994142 0.994142
+v -0.994142 0.994142 0.980000
+v -0.994142 0.980000 0.994142
+v -0.992998 0.992998 0.992998
+v -1.000000 0.980000 -0.980000
+v -0.980000 1.000000 -0.980000
+v -0.980000 0.980000 -1.000000
+v -0.994142 0.994142 -0.980000
+v -0.980000 0.994142 -0.994142
+v -0.994142 0.980000 -0.994142
+v -0.992998 0.992998 -0.992998
+v 0.980000 0.980000 -1.000000
+v 0.980000 1.000000 -0.980000
+v 1.000000 0.980000 -0.980000
+v 0.980000 0.994142 -0.994142
+v 0.994142 0.994142 -0.980000
+v 0.994142 0.980000 -0.994142
+v 0.992998 0.992998 -0.992998
+v 1.000000 0.980000 0.980000
+v 0.980000 1.000000 0.980000
+v 0.980000 0.980000 1.000000
+v 0.994142 0.994142 0.980000
+v 0.980000 0.994142 0.994142
+v 0.994142 0.980000 0.994142
+v 0.992998 0.992998 0.992998
+vt 0.339018 0.659342
+vt 0.338105 0.335826
+vt 0.664825 0.343846
+vt 0.669233 0.664931
+vt 0.668227 0.337019
+vt 0.995472 0.336015
+vt 0.337976 0.003372
+vt 0.665888 0.002366
+vt 0.666894 0.330278
+vt 0.672755 0.331283
+vt 0.671749 0.003372
+vt 0.998994 0.002368
+vt 0.335610 0.335927
+vt 0.335761 0.333817
+vt 0.338982 0.331284
+vt 0.338989 0.333650
+vt 0.336814 0.333465
+vt 0.326720 0.346123
+vt 0.326875 0.343630
+vt 0.329052 0.343841
+vt 0.327633 0.669639
+vt 0.330128 0.669538
+vt 0.329977 0.671648
+vt 0.669260 0.330271
+vt 0.669075 0.332446
+vt 0.672610 0.333650
+vt 0.670432 0.333465
+vt 0.669260 0.003379
+vt 0.669412 0.001204
+vt 0.665880 0.000000
+vt 0.668056 0.000185
+vt 0.669089 0.667298
+vt 0.666910 0.667113
+vt 0.665738 0.337027
+vt 0.665891 0.334852
+vt 0.335610 0.003379
+vt 0.335795 0.001204
+vt 0.338863 0.661835
+vt 0.336686 0.661624
+vt 0.666901 0.332644
+vt 0.329215 0.346022
+vt 1.000000 0.330280
+vt 0.999855 0.332646
+vt 0.327801 0.671815
+vt 0.001081 0.663795
+vt 0.000913 0.661618
+vt 0.337937 0.333650
+vt 0.000155 0.335610
+vt 0.668254 0.002359
+vt 0.670266 0.331291
+vt 0.996478 0.663927
+vt 0.996334 0.666294
+vt 0.671590 0.001006
+vt 0.998835 0.000002
+vt 0.337969 0.001006
+vt 0.666744 0.664939
+vt 0.665738 0.667363
+vt 0.665583 0.669855
+vt 0.668068 0.334654
+vt 0.995313 0.333650
+vt 0.336616 0.331291
+vt 0.336523 0.659443
+vt 0.000000 0.004362
+vt 0.000973 0.001993
+vt 0.003336 0.001006
+vt 0.664657 0.341670
+vt 0.000000 0.338102
+vt 0.334604 0.003336
+vt 0.331248 0.000000
+vt 0.333617 0.000973
+vt 0.332274 0.334604
+vt 0.335610 0.331248
+vt 0.334637 0.333617
+vt 0.001993 0.334637
+vt 0.001006 0.332274
+vt 0.004362 0.335610
+vn 0.161046 0.161046 0.973693
+vn -0.161046 0.161046 0.973693
+vn -0.145573 -0.665700 0.731864
+vn 0.973693 0.161046 -0.161046
+vn 0.973693 0.161046 0.161046
+vn 0.731864 -0.665700 0.145573
+vn 0.161046 0.973693 0.161046
+vn 0.161046 0.973693 -0.161046
+vn -0.161046 0.973693 -0.161046
+vn -0.161046 0.161046 -0.973693
+vn 0.161046 0.161046 -0.973693
+vn 0.145573 -0.665700 -0.731864
+vn -0.060945 0.705771 0.705771
+vn -0.577349 0.577349 0.577349
+vn -0.161046 0.973693 0.161046
+vn -0.705771 0.705771 0.060945
+vn -0.973693 0.161046 0.161046
+vn -0.705771 0.060945 0.705771
+vn -0.973693 0.161046 -0.161046
+vn -0.705771 0.705771 -0.060945
+vn -0.577349 0.577349 -0.577349
+vn -0.060945 0.705771 -0.705771
+vn -0.705771 0.060945 -0.705771
+vn 0.060945 0.705771 -0.705771
+vn 0.577349 0.577349 -0.577349
+vn 0.705771 0.705771 -0.060945
+vn 0.705771 0.060945 -0.705771
+vn 0.705771 0.705771 0.060945
+vn 0.577349 0.577349 0.577349
+vn 0.060945 0.705771 0.705771
+vn 0.705771 0.060945 0.705771
+vn -0.145573 -0.665700 -0.731864
+vn -0.548967 -0.630238 -0.548967
+vn -0.731864 -0.665700 -0.145573
+vn -0.548967 -0.630238 0.548967
+vn 0.731864 -0.665700 -0.145573
+vn 0.548967 -0.630238 -0.548967
+vn 0.145573 -0.665700 0.731864
+vn 0.548967 -0.630238 0.548967
+vn -0.731864 -0.665700 0.145573
+s 1
+f 36/1/1 13/2/2 1/3/3
+f 29/4/4 34/5/5 10/6/6
+f 35/7/7 28/8/8 21/9/9
+f 22/10/10 27/11/11 7/12/12
+f 13/2/2 16/13/13 19/14/14
+f 14/15/15 17/16/16 19/17/14
+f 15/18/17 18/19/18 19/20/14
+f 20/21/19 23/22/20 26/23/21
+f 21/9/9 24/24/22 26/25/21
+f 22/10/10 25/26/23 26/27/21
+f 27/11/11 30/28/24 33/29/25
+f 28/8/8 31/30/26 33/31/25
+f 29/4/4 32/32/27 33/33/25
+f 34/5/5 37/34/28 40/35/29
+f 35/7/7 38/36/30 40/37/29
+f 36/1/1 39/38/31 40/39/29
+f 14/15/15 21/9/9 23/40/20
+f 17/41/16 23/22/20 20/21/19
+f 22/10/10 5/42/32 6/43/33
+f 25/44/23 6/45/33 4/46/34
+f 1/3/3 13/2/2 18/47/18
+f 3/48/35 18/19/18 15/18/17
+f 21/9/9 28/8/8 30/49/24
+f 24/50/22 30/28/24 27/11/11
+f 29/4/4 8/51/36 9/52/37
+f 32/53/27 9/54/37 7/12/12
+f 28/8/8 35/7/7 37/55/28
+f 31/56/26 37/34/28 34/5/5
+f 36/1/1 11/57/38 12/58/39
+f 39/59/31 12/60/39 10/6/6
+f 35/7/7 14/15/15 16/61/13
+f 38/62/30 16/13/13 13/2/2
+f 15/18/17 20/21/19 4/46/34
+f 7/63/12 9/64/37 8/65/36
+f 11/57/38 36/1/1 1/3/3
+f 8/51/36 29/4/4 10/6/6
+f 14/15/15 35/7/7 21/9/9
+f 5/42/32 22/10/10 7/12/12
+f 18/47/18 13/2/2 19/14/14
+f 16/61/13 14/15/15 19/17/14
+f 17/41/16 15/18/17 19/20/14
+f 25/44/23 20/21/19 26/23/21
+f 23/40/20 21/9/9 26/25/21
+f 24/50/22 22/10/10 26/27/21
+f 32/53/27 27/11/11 33/29/25
+f 30/49/24 28/8/8 33/31/25
+f 31/56/26 29/4/4 33/33/25
+f 39/59/31 34/5/5 40/35/29
+f 37/55/28 35/7/7 40/37/29
+f 38/62/30 36/1/1 40/39/29
+f 17/16/16 14/15/15 23/40/20
+f 15/18/17 17/41/16 20/21/19
+f 25/26/23 22/10/10 6/43/33
+f 20/21/19 25/44/23 4/46/34
+f 3/66/35 1/3/3 18/47/18
+f 2/67/40 3/48/35 15/18/17
+f 24/24/22 21/9/9 30/49/24
+f 22/10/10 24/50/22 27/11/11
+f 32/32/27 29/4/4 9/52/37
+f 27/11/11 32/53/27 7/12/12
+f 31/30/26 28/8/8 37/55/28
+f 29/4/4 31/56/26 34/5/5
+f 39/38/31 36/1/1 12/58/39
+f 34/5/5 39/59/31 10/6/6
+f 38/36/30 35/7/7 16/61/13
+f 36/1/1 38/62/30 13/2/2
+f 2/67/40 15/18/17 4/46/34
+f 11/68/38 10/69/6 12/70/39
+f 2/71/40 1/72/3 3/73/35
+f 6/74/33 5/75/32 4/76/34
+f 5/75/32 7/63/12 4/76/34
+f 2/71/40 4/76/34 8/65/36
+f 8/65/36 10/69/6 2/71/40
+f 10/69/6 11/68/38 1/72/3
+f 2/71/40 10/69/6 1/72/3
+f 4/76/34 7/63/12 8/65/36
diff --git a/src/graphs/engine/meshes/barFlat.mesh b/src/graphs/engine/meshes/barFlat.mesh
new file mode 100644
index 0000000..c88b025
--- /dev/null
+++ b/src/graphs/engine/meshes/barFlat.mesh
Binary files differ
diff --git a/src/graphs/engine/meshes/barFlat.obj b/src/graphs/engine/meshes/barFlat.obj
new file mode 100644
index 0000000..b802fea
--- /dev/null
+++ b/src/graphs/engine/meshes/barFlat.obj
@@ -0,0 +1,219 @@
+# Blender v2.66 (sub 0) OBJ File: 'smoothcube.blend'
+# www.blender.org
+o Cube
+v -0.980000 -1.000000 1.000000
+v -1.000000 -1.000000 0.980000
+v -0.994142 -1.000000 0.994142
+v -1.000000 -1.000000 -0.980000
+v -0.980000 -1.000000 -1.000000
+v -0.994142 -1.000000 -0.994142
+v 0.980000 -1.000000 -1.000000
+v 1.000000 -1.000000 -0.980000
+v 0.994142 -1.000000 -0.994142
+v 1.000000 -1.000000 0.980000
+v 0.980000 -1.000000 1.000000
+v 0.994142 -1.000000 0.994142
+v -0.980000 0.980000 1.000000
+v -0.980000 1.000000 0.980000
+v -1.000000 0.980000 0.980000
+v -0.980000 0.994142 0.994142
+v -0.994142 0.994142 0.980000
+v -0.994142 0.980000 0.994142
+v -0.992998 0.992998 0.992998
+v -1.000000 0.980000 -0.980000
+v -0.980000 1.000000 -0.980000
+v -0.980000 0.980000 -1.000000
+v -0.994142 0.994142 -0.980000
+v -0.980000 0.994142 -0.994142
+v -0.994142 0.980000 -0.994142
+v -0.992998 0.992998 -0.992998
+v 0.980000 0.980000 -1.000000
+v 0.980000 1.000000 -0.980000
+v 1.000000 0.980000 -0.980000
+v 0.980000 0.994142 -0.994142
+v 0.994142 0.994142 -0.980000
+v 0.994142 0.980000 -0.994142
+v 0.992998 0.992998 -0.992998
+v 1.000000 0.980000 0.980000
+v 0.980000 1.000000 0.980000
+v 0.980000 0.980000 1.000000
+v 0.994142 0.994142 0.980000
+v 0.980000 0.994142 0.994142
+v 0.994142 0.980000 0.994142
+v 0.992998 0.992998 0.992998
+vt 0.338795 0.668183
+vt 0.337784 0.338666
+vt 0.666630 0.337658
+vt 0.003512 0.668185
+vt 0.002502 0.338668
+vt 0.331348 0.337660
+vt 0.002378 0.003388
+vt 0.331894 0.002378
+vt 0.332905 0.331894
+vt 0.671154 0.332902
+vt 0.670143 0.003386
+vt 1.000000 0.331893
+vt 0.335282 0.338674
+vt 0.337624 0.336289
+vt 0.003388 0.332905
+vt 0.003396 0.335282
+vt 0.001011 0.332912
+vt 0.338795 0.332902
+vt 0.338649 0.335280
+vt 0.336293 0.332910
+vt 0.337784 0.003386
+vt 0.335282 0.003394
+vt 0.337624 0.001009
+vt 0.335282 0.331887
+vt 0.332912 0.334272
+vt 0.671008 0.335280
+vt 0.668652 0.332910
+vt 0.667641 0.003394
+vt 0.667795 0.001208
+vt 0.331887 0.000000
+vt 0.334073 0.000186
+vt 0.003367 0.670563
+vt 0.001178 0.670377
+vt 0.000000 0.338676
+vt 0.000153 0.336490
+vt 0.000000 0.003396
+vt 0.000186 0.001210
+vt 0.338649 0.670561
+vt 0.336460 0.670375
+vt 0.666470 0.000000
+vt 0.666470 0.335280
+vt 0.667496 0.334272
+vt 0.667641 0.331894
+vt 0.332359 0.667176
+vt 0.332213 0.669554
+vt 0.669983 0.001009
+vt 0.998829 0.000000
+vt 0.998989 0.002377
+vt 0.002370 0.001011
+vt 0.001011 0.668192
+vt 0.667641 0.667174
+vt 0.667496 0.669552
+vt 0.002341 0.336291
+vt 0.331188 0.335282
+vt 0.336293 0.668191
+vt 0.335436 0.336488
+vt 0.001210 0.335097
+vt 0.336460 0.335095
+vt 0.335436 0.001208
+vt 0.335097 0.334073
+vt 0.668819 0.335095
+vt 0.334272 0.002370
+vt 0.999855 0.334272
+vt 0.666630 0.002377
+vn 0.000000 0.000000 1.000000
+vn 1.000000 -0.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 0.000000 -1.000000
+vn -0.357407 0.357407 0.862856
+vn -0.357409 0.862855 0.357407
+vn -0.862855 0.357409 0.357407
+vn -0.862855 0.357407 -0.357409
+vn -0.357407 0.862855 -0.357409
+vn -0.357408 0.357408 -0.862855
+vn 0.114222 0.380181 -0.917832
+vn 0.380181 0.917832 -0.114222
+vn 0.917832 0.114222 -0.380181
+vn 0.917832 0.380181 0.114222
+vn 0.114222 0.917832 0.380181
+vn 0.380181 0.114222 0.917832
+vn -0.382685 0.923879 0.000000
+vn -0.923879 0.382685 0.000000
+vn -0.382685 0.000000 -0.923879
+vn -0.923879 0.000000 -0.382685
+vn -0.382685 0.000000 0.923879
+vn -0.923879 0.000000 0.382685
+vn 0.000000 0.923879 -0.382685
+vn 0.000000 0.382685 -0.923879
+vn 0.923879 0.000000 -0.382685
+vn 0.382685 0.000000 -0.923879
+vn 0.382685 0.923879 0.000000
+vn 0.923879 0.382685 0.000000
+vn 0.382685 -0.000000 0.923879
+vn 0.923879 -0.000000 0.382685
+vn -0.000000 0.923879 0.382685
+vn -0.000000 0.382685 0.923879
+vn -1.000000 0.000000 0.000000
+vn -0.095602 0.095602 0.990818
+vn -0.095602 0.990818 0.095602
+vn -0.990818 0.095602 0.095602
+vn -0.990818 0.095602 -0.095602
+vn -0.095602 0.990818 -0.095602
+vn -0.095602 0.095602 -0.990818
+vn 0.380181 0.114222 -0.917832
+vn 0.114222 0.917832 -0.380181
+vn 0.917832 0.380181 -0.114222
+vn 0.917832 0.114222 0.380181
+vn 0.380181 0.917832 0.114222
+vn 0.114222 0.380181 0.917832
+s off
+f 36/1/1 13/2/1 1/3/1
+f 29/4/2 34/5/2 10/6/2
+f 35/7/3 28/8/3 21/9/3
+f 22/10/4 27/11/4 5/12/4
+f 13/2/5 16/13/5 18/14/5
+f 14/15/6 17/16/6 16/17/6
+f 15/18/7 18/19/7 17/20/7
+f 20/21/8 23/22/8 25/23/8
+f 21/9/9 24/24/9 23/25/9
+f 22/10/10 25/26/10 24/27/10
+f 27/11/11 30/28/11 33/29/11
+f 28/8/12 31/30/12 33/31/12
+f 29/4/13 32/32/13 33/33/13
+f 34/5/14 37/34/14 40/35/14
+f 35/7/15 38/36/15 40/37/15
+f 36/1/16 39/38/16 40/39/16
+f 14/15/17 21/9/17 17/16/17
+f 17/20/18 23/22/18 15/18/18
+f 22/10/19 5/12/19 25/26/19
+f 25/23/20 6/40/20 20/21/20
+f 1/3/21 13/2/21 3/41/21
+f 3/42/22 18/19/22 2/43/22
+f 21/9/23 28/8/23 24/24/23
+f 24/27/24 30/28/24 22/10/24
+f 29/4/25 8/44/25 9/45/25
+f 32/46/26 9/47/26 7/48/26
+f 28/8/27 35/7/27 37/49/27
+f 31/50/28 37/34/28 34/5/28
+f 36/1/29 11/51/29 12/52/29
+f 39/53/30 12/54/30 10/6/30
+f 35/7/31 14/15/31 16/17/31
+f 38/55/32 16/13/32 13/2/32
+f 15/18/33 20/21/33 2/43/33
+f 11/51/1 36/1/1 1/3/1
+f 8/44/2 29/4/2 10/6/2
+f 14/15/3 35/7/3 21/9/3
+f 27/11/4 7/48/4 5/12/4
+f 16/13/34 19/56/34 18/14/34
+f 17/16/35 19/57/35 16/17/35
+f 18/19/36 19/58/36 17/20/36
+f 23/22/37 26/59/37 25/23/37
+f 24/24/38 26/60/38 23/25/38
+f 25/26/39 26/61/39 24/27/39
+f 32/46/40 27/11/40 33/29/40
+f 30/62/41 28/8/41 33/31/41
+f 31/50/42 29/4/42 33/33/42
+f 39/53/43 34/5/43 40/35/43
+f 37/49/44 35/7/44 40/37/44
+f 38/55/45 36/1/45 40/39/45
+f 21/9/17 23/25/17 17/16/17
+f 23/22/18 20/21/18 15/18/18
+f 5/12/19 6/63/19 25/26/19
+f 6/40/20 4/64/20 20/21/20
+f 13/2/21 18/14/21 3/41/21
+f 18/19/22 15/18/22 2/43/22
+f 28/8/23 30/62/23 24/24/23
+f 30/28/24 27/11/24 22/10/24
+f 32/32/25 29/4/25 9/45/25
+f 27/11/26 32/46/26 7/48/26
+f 31/30/27 28/8/27 37/49/27
+f 29/4/28 31/50/28 34/5/28
+f 39/38/29 36/1/29 12/52/29
+f 34/5/30 39/53/30 10/6/30
+f 38/36/31 35/7/31 16/17/31
+f 36/1/32 38/55/32 13/2/32
+f 20/21/33 4/64/33 2/43/33
diff --git a/src/graphs/engine/meshes/barSmooth.mesh b/src/graphs/engine/meshes/barSmooth.mesh
new file mode 100644
index 0000000..ec9631d
--- /dev/null
+++ b/src/graphs/engine/meshes/barSmooth.mesh
Binary files differ
diff --git a/src/graphs/engine/meshes/barSmooth.obj b/src/graphs/engine/meshes/barSmooth.obj
new file mode 100644
index 0000000..aa4fdd9
--- /dev/null
+++ b/src/graphs/engine/meshes/barSmooth.obj
@@ -0,0 +1,214 @@
+# Blender v2.66 (sub 0) OBJ File: 'smoothcube.blend'
+# www.blender.org
+o Cube
+v -0.980000 -1.000000 1.000000
+v -1.000000 -1.000000 0.980000
+v -0.994142 -1.000000 0.994142
+v -1.000000 -1.000000 -0.980000
+v -0.980000 -1.000000 -1.000000
+v -0.994142 -1.000000 -0.994142
+v 0.980000 -1.000000 -1.000000
+v 1.000000 -1.000000 -0.980000
+v 0.994142 -1.000000 -0.994142
+v 1.000000 -1.000000 0.980000
+v 0.980000 -1.000000 1.000000
+v 0.994142 -1.000000 0.994142
+v -0.980000 0.980000 1.000000
+v -0.980000 1.000000 0.980000
+v -1.000000 0.980000 0.980000
+v -0.980000 0.994142 0.994142
+v -0.994142 0.994142 0.980000
+v -0.994142 0.980000 0.994142
+v -0.992998 0.992998 0.992998
+v -1.000000 0.980000 -0.980000
+v -0.980000 1.000000 -0.980000
+v -0.980000 0.980000 -1.000000
+v -0.994142 0.994142 -0.980000
+v -0.980000 0.994142 -0.994142
+v -0.994142 0.980000 -0.994142
+v -0.992998 0.992998 -0.992998
+v 0.980000 0.980000 -1.000000
+v 0.980000 1.000000 -0.980000
+v 1.000000 0.980000 -0.980000
+v 0.980000 0.994142 -0.994142
+v 0.994142 0.994142 -0.980000
+v 0.994142 0.980000 -0.994142
+v 0.992998 0.992998 -0.992998
+v 1.000000 0.980000 0.980000
+v 0.980000 1.000000 0.980000
+v 0.980000 0.980000 1.000000
+v 0.994142 0.994142 0.980000
+v 0.980000 0.994142 0.994142
+v 0.994142 0.980000 0.994142
+v 0.992998 0.992998 0.992998
+vt 0.338795 0.668183
+vt 0.337784 0.338666
+vt 0.666630 0.337658
+vt 0.003512 0.668185
+vt 0.002502 0.338668
+vt 0.331348 0.337660
+vt 0.002378 0.003388
+vt 0.331894 0.002378
+vt 0.332905 0.331894
+vt 0.671154 0.332902
+vt 0.670143 0.003386
+vt 1.000000 0.331893
+vt 0.335282 0.338674
+vt 0.337624 0.336289
+vt 0.003388 0.332905
+vt 0.003396 0.335282
+vt 0.001011 0.332912
+vt 0.338795 0.332902
+vt 0.338649 0.335280
+vt 0.336293 0.332910
+vt 0.337784 0.003386
+vt 0.335282 0.003394
+vt 0.337624 0.001009
+vt 0.335282 0.331887
+vt 0.332912 0.334272
+vt 0.671008 0.335280
+vt 0.668652 0.332910
+vt 0.667641 0.003394
+vt 0.667795 0.001208
+vt 0.331887 0.000000
+vt 0.334073 0.000186
+vt 0.003367 0.670563
+vt 0.001178 0.670377
+vt 0.000000 0.338676
+vt 0.000153 0.336490
+vt 0.000000 0.003396
+vt 0.000186 0.001210
+vt 0.338649 0.670561
+vt 0.336460 0.670375
+vt 0.666470 0.000000
+vt 0.666470 0.335280
+vt 0.667496 0.334272
+vt 0.667641 0.331894
+vt 0.332359 0.667176
+vt 0.332213 0.669554
+vt 0.669983 0.001009
+vt 0.998829 0.000000
+vt 0.998989 0.002377
+vt 0.002370 0.001011
+vt 0.001011 0.668192
+vt 0.667641 0.667174
+vt 0.667496 0.669552
+vt 0.002341 0.336291
+vt 0.331188 0.335282
+vt 0.336293 0.668191
+vt 0.335436 0.336488
+vt 0.001210 0.335097
+vt 0.336460 0.335095
+vt 0.335436 0.001208
+vt 0.335097 0.334073
+vt 0.668819 0.335095
+vt 0.334272 0.002370
+vt 0.999855 0.334272
+vt 0.666630 0.002377
+vn 0.161046 0.161046 0.973693
+vn -0.187689 0.187689 0.964110
+vn -0.195074 0.000000 0.980773
+vn 0.973693 0.161046 -0.161046
+vn 0.973693 0.161046 0.161046
+vn 0.980773 0.000000 0.195074
+vn 0.161046 0.973693 0.161046
+vn 0.161046 0.973693 -0.161046
+vn -0.187689 0.964110 -0.187689
+vn -0.187689 0.187689 -0.964110
+vn 0.161046 0.161046 -0.973693
+vn -0.195074 0.000000 -0.980773
+vn -0.135655 0.700552 0.700552
+vn -0.700552 0.135655 0.700552
+vn -0.187689 0.964110 0.187689
+vn -0.700552 0.700552 0.135655
+vn -0.964110 0.187689 0.187689
+vn -0.964110 0.187689 -0.187689
+vn -0.700552 0.700552 -0.135655
+vn -0.700552 0.135655 -0.700552
+vn -0.135655 0.700552 -0.700552
+vn 0.060945 0.705771 -0.705771
+vn 0.577349 0.577349 -0.577349
+vn 0.705771 0.705771 -0.060945
+vn 0.705771 0.060945 -0.705771
+vn 0.705771 0.705771 0.060945
+vn 0.577349 0.577349 0.577349
+vn 0.060945 0.705771 0.705771
+vn 0.705771 0.060945 0.705771
+vn -0.707083 0.000000 -0.707083
+vn -0.707083 0.000000 0.707083
+vn -0.980773 0.000000 0.195074
+vn 0.980773 0.000000 -0.195074
+vn 0.707083 0.000000 -0.707083
+vn 0.195074 0.000000 -0.980773
+vn 0.195074 0.000000 0.980773
+vn 0.707083 0.000000 0.707083
+vn -0.577349 0.577349 0.577349
+vn -0.577349 0.577349 -0.577349
+vn -0.980773 0.000000 -0.195074
+s 1
+f 36/1/1 13/2/2 1/3/3
+f 29/4/4 34/5/5 10/6/6
+f 35/7/7 28/8/8 21/9/9
+f 22/10/10 27/11/11 5/12/12
+f 13/2/2 16/13/13 18/14/14
+f 14/15/15 17/16/16 16/17/13
+f 15/18/17 18/19/14 17/20/16
+f 20/21/18 23/22/19 25/23/20
+f 21/9/9 24/24/21 23/25/19
+f 22/10/10 25/26/20 24/27/21
+f 27/11/11 30/28/22 33/29/23
+f 28/8/8 31/30/24 33/31/23
+f 29/4/4 32/32/25 33/33/23
+f 34/5/5 37/34/26 40/35/27
+f 35/7/7 38/36/28 40/37/27
+f 36/1/1 39/38/29 40/39/27
+f 14/15/15 21/9/9 17/16/16
+f 17/20/16 23/22/19 15/18/17
+f 22/10/10 5/12/12 25/26/20
+f 25/23/20 6/40/30 20/21/18
+f 1/3/3 13/2/2 3/41/31
+f 3/42/31 18/19/14 2/43/32
+f 21/9/9 28/8/8 24/24/21
+f 24/27/21 30/28/22 22/10/10
+f 29/4/4 8/44/33 9/45/34
+f 32/46/25 9/47/34 7/48/35
+f 28/8/8 35/7/7 37/49/26
+f 31/50/24 37/34/26 34/5/5
+f 36/1/1 11/51/36 12/52/37
+f 39/53/29 12/54/37 10/6/6
+f 35/7/7 14/15/15 16/17/13
+f 38/55/28 16/13/13 13/2/2
+f 15/18/17 20/21/18 2/43/32
+f 11/51/36 36/1/1 1/3/3
+f 8/44/33 29/4/4 10/6/6
+f 14/15/15 35/7/7 21/9/9
+f 27/11/11 7/48/35 5/12/12
+f 16/13/13 19/56/38 18/14/14
+f 17/16/16 19/57/38 16/17/13
+f 18/19/14 19/58/38 17/20/16
+f 23/22/19 26/59/39 25/23/20
+f 24/24/21 26/60/39 23/25/19
+f 25/26/20 26/61/39 24/27/21
+f 32/46/25 27/11/11 33/29/23
+f 30/62/22 28/8/8 33/31/23
+f 31/50/24 29/4/4 33/33/23
+f 39/53/29 34/5/5 40/35/27
+f 37/49/26 35/7/7 40/37/27
+f 38/55/28 36/1/1 40/39/27
+f 21/9/9 23/25/19 17/16/16
+f 23/22/19 20/21/18 15/18/17
+f 5/12/12 6/63/30 25/26/20
+f 6/40/30 4/64/40 20/21/18
+f 13/2/2 18/14/14 3/41/31
+f 18/19/14 15/18/17 2/43/32
+f 28/8/8 30/62/22 24/24/21
+f 30/28/22 27/11/11 22/10/10
+f 32/32/25 29/4/4 9/45/34
+f 27/11/11 32/46/25 7/48/35
+f 31/30/24 28/8/8 37/49/26
+f 29/4/4 31/50/24 34/5/5
+f 39/38/29 36/1/1 12/52/37
+f 34/5/5 39/53/29 10/6/6
+f 38/36/28 35/7/7 16/17/13
+f 36/1/1 38/55/28 13/2/2
+f 20/21/18 4/64/40 2/43/32
diff --git a/src/graphs/engine/meshes/coneFilledFlat.mesh b/src/graphs/engine/meshes/coneFilledFlat.mesh
new file mode 100644
index 0000000..5075998
--- /dev/null
+++ b/src/graphs/engine/meshes/coneFilledFlat.mesh
Binary files differ
diff --git a/src/graphs/engine/meshes/coneFilledFlat.obj b/src/graphs/engine/meshes/coneFilledFlat.obj
new file mode 100644
index 0000000..cbbffaf
--- /dev/null
+++ b/src/graphs/engine/meshes/coneFilledFlat.obj
@@ -0,0 +1,128 @@
+# Blender v2.66 (sub 0) OBJ File: 'cone_filled.blend'
+# www.blender.org
+o Cone_Cone.001
+v 0.000000 -1.000000 -1.000000
+v 0.309017 -1.000000 -0.951057
+v 0.587785 -1.000000 -0.809017
+v 0.809017 -1.000000 -0.587785
+v 0.951057 -1.000000 -0.309017
+v 1.000000 -1.000000 0.000000
+v 0.951056 -1.000000 0.309017
+v 0.809017 -1.000000 0.587785
+v 0.000000 1.000000 0.000000
+v 0.587785 -1.000000 0.809017
+v 0.309017 -1.000000 0.951057
+v -0.000000 -1.000000 1.000000
+v -0.309017 -1.000000 0.951056
+v -0.587786 -1.000000 0.809017
+v -0.809017 -1.000000 0.587785
+v -0.951057 -1.000000 0.309016
+v -1.000000 -1.000000 -0.000001
+v -0.951056 -1.000000 -0.309018
+v -0.809016 -1.000000 -0.587786
+v -0.587784 -1.000000 -0.809018
+v -0.309016 -1.000000 -0.951057
+vt 0.636534 0.537407
+vt 0.609784 0.926283
+vt 0.571680 0.562142
+vt 0.911640 0.504376
+vt 0.571680 0.368442
+vt 0.953118 0.448721
+vt 0.853526 0.537297
+vt 0.973901 0.375780
+vt 0.712378 0.537297
+vt 0.791787 0.561822
+vt 0.866987 0.608581
+vt 0.930618 0.672998
+vt 0.976451 0.748767
+vt 1.000000 0.828470
+vt 0.998959 0.904307
+vt 0.973430 0.968853
+vt 0.629860 0.006965
+vt 0.571746 0.039886
+vt 0.698921 0.000000
+vt 0.772169 0.019673
+vt 0.842434 0.064059
+vt 0.902837 0.128812
+vt 0.947467 0.207594
+vt 0.971954 0.292693
+vt 0.154453 0.543699
+vt 0.081201 0.490478
+vt 0.240567 0.571679
+vt 0.027980 0.417226
+vt 0.000000 0.331112
+vt 0.000000 0.240567
+vt 0.027980 0.154454
+vt 0.081201 0.081201
+vt 0.154454 0.027980
+vt 0.240567 0.000000
+vt 0.331112 0.000000
+vt 0.417226 0.027980
+vt 0.490479 0.081201
+vt 0.543700 0.154454
+vt 0.571680 0.240567
+vt 0.571680 0.331113
+vt 0.543699 0.417226
+vt 0.490478 0.490479
+vt 0.417226 0.543700
+vt 0.331112 0.571680
+vn -0.407058 0.442793 -0.798898
+vn 0.140263 0.442793 -0.885585
+vn -0.140262 0.442793 -0.885585
+vn 0.407059 0.442793 -0.798898
+vn -0.634008 0.442793 -0.634010
+vn -0.798897 0.442793 -0.407060
+vn -0.885585 0.442793 -0.140264
+vn -0.885585 0.442793 0.140262
+vn -0.798898 0.442793 0.407058
+vn -0.634009 0.442793 0.634009
+vn -0.407059 0.442793 0.798898
+vn -0.140263 0.442793 0.885585
+vn 0.140263 0.442793 0.885585
+vn 0.407059 0.442793 0.798898
+vn 0.634009 0.442793 0.634009
+vn 0.798898 0.442793 0.407059
+vn 0.885585 0.442793 0.140263
+vn 0.885585 0.442793 -0.140263
+vn 0.798898 0.442793 -0.407059
+vn 0.634009 0.442793 -0.634009
+vn -0.000000 -1.000000 -0.000000
+s off
+f 20/1/1 9/2/1 21/3/1
+f 1/4/2 9/5/2 2/6/2
+f 21/7/3 9/5/3 1/4/3
+f 2/6/4 9/5/4 3/8/4
+f 19/9/5 9/2/5 20/1/5
+f 18/10/6 9/2/6 19/9/6
+f 17/11/7 9/2/7 18/10/7
+f 16/12/8 9/2/8 17/11/8
+f 15/13/9 9/2/9 16/12/9
+f 14/14/10 9/2/10 15/13/10
+f 13/15/11 9/2/11 14/14/11
+f 12/16/12 9/2/12 13/15/12
+f 11/17/13 9/5/13 12/18/13
+f 10/19/14 9/5/14 11/17/14
+f 8/20/15 9/5/15 10/19/15
+f 7/21/16 9/5/16 8/20/16
+f 6/22/17 9/5/17 7/21/17
+f 5/23/18 9/5/18 6/22/18
+f 4/24/19 9/5/19 5/23/19
+f 3/8/20 9/5/20 4/24/20
+f 21/25/21 1/26/21 20/27/21
+f 1/26/21 2/28/21 20/27/21
+f 2/28/21 3/29/21 20/27/21
+f 3/29/21 4/30/21 20/27/21
+f 4/30/21 5/31/21 20/27/21
+f 5/31/21 6/32/21 20/27/21
+f 6/32/21 7/33/21 20/27/21
+f 7/33/21 8/34/21 20/27/21
+f 8/34/21 10/35/21 20/27/21
+f 10/35/21 11/36/21 20/27/21
+f 11/36/21 12/37/21 20/27/21
+f 12/37/21 13/38/21 20/27/21
+f 13/38/21 14/39/21 20/27/21
+f 14/39/21 15/40/21 20/27/21
+f 15/40/21 16/41/21 20/27/21
+f 16/41/21 17/42/21 20/27/21
+f 17/42/21 18/43/21 19/44/21
+f 20/27/21 17/42/21 19/44/21
diff --git a/src/graphs/engine/meshes/coneFilledSmooth.mesh b/src/graphs/engine/meshes/coneFilledSmooth.mesh
new file mode 100644
index 0000000..91e9bec
--- /dev/null
+++ b/src/graphs/engine/meshes/coneFilledSmooth.mesh
Binary files differ
diff --git a/src/graphs/engine/meshes/coneFilledSmooth.obj b/src/graphs/engine/meshes/coneFilledSmooth.obj
new file mode 100644
index 0000000..ea3a870
--- /dev/null
+++ b/src/graphs/engine/meshes/coneFilledSmooth.obj
@@ -0,0 +1,128 @@
+# Blender v2.66 (sub 0) OBJ File: 'cone_filled.blend'
+# www.blender.org
+o Cone_Cone.001
+v 0.000000 -1.000000 -1.000000
+v 0.309017 -1.000000 -0.951057
+v 0.587785 -1.000000 -0.809017
+v 0.809017 -1.000000 -0.587785
+v 0.951057 -1.000000 -0.309017
+v 1.000000 -1.000000 0.000000
+v 0.951056 -1.000000 0.309017
+v 0.809017 -1.000000 0.587785
+v 0.000000 1.000000 0.000000
+v 0.587785 -1.000000 0.809017
+v 0.309017 -1.000000 0.951057
+v -0.000000 -1.000000 1.000000
+v -0.309017 -1.000000 0.951056
+v -0.587786 -1.000000 0.809017
+v -0.809017 -1.000000 0.587785
+v -0.951057 -1.000000 0.309016
+v -1.000000 -1.000000 -0.000001
+v -0.951056 -1.000000 -0.309018
+v -0.809016 -1.000000 -0.587786
+v -0.587784 -1.000000 -0.809018
+v -0.309016 -1.000000 -0.951057
+vt 0.636534 0.537407
+vt 0.609784 0.926283
+vt 0.571680 0.562142
+vt 0.911640 0.504376
+vt 0.571680 0.368442
+vt 0.953118 0.448721
+vt 0.853526 0.537297
+vt 0.973901 0.375780
+vt 0.712378 0.537297
+vt 0.791787 0.561822
+vt 0.866987 0.608581
+vt 0.930618 0.672998
+vt 0.976451 0.748767
+vt 1.000000 0.828470
+vt 0.998959 0.904307
+vt 0.973430 0.968853
+vt 0.629860 0.006965
+vt 0.571746 0.039886
+vt 0.698921 0.000000
+vt 0.772169 0.019673
+vt 0.842434 0.064059
+vt 0.902837 0.128812
+vt 0.947467 0.207594
+vt 0.971954 0.292693
+vt 0.154453 0.543699
+vt 0.081201 0.490478
+vt 0.240567 0.571679
+vt 0.027980 0.417226
+vt 0.000000 0.331112
+vt 0.000000 0.240567
+vt 0.027980 0.154454
+vt 0.081201 0.081201
+vt 0.154454 0.027980
+vt 0.240567 0.000000
+vt 0.331112 0.000000
+vt 0.417226 0.027980
+vt 0.490479 0.081201
+vt 0.543700 0.154454
+vt 0.571680 0.240567
+vt 0.571680 0.331113
+vt 0.543699 0.417226
+vt 0.490478 0.490479
+vt 0.417226 0.543700
+vt 0.331112 0.571680
+vn -0.512009 -0.491043 -0.704733
+vn 0.000000 1.000000 0.000000
+vn -0.269173 -0.491043 -0.828486
+vn 0.000000 -0.491043 -0.871120
+vn 0.269173 -0.491043 -0.828486
+vn 0.512009 -0.491043 -0.704733
+vn -0.704733 -0.491043 -0.512040
+vn -0.828486 -0.491043 -0.269173
+vn -0.871120 -0.491043 0.000000
+vn -0.828486 -0.491043 0.269173
+vn -0.704733 -0.491043 0.512009
+vn -0.512009 -0.491043 0.704733
+vn -0.269173 -0.491043 0.828486
+vn 0.000000 -0.491043 0.871120
+vn 0.269173 -0.491043 0.828486
+vn 0.512009 -0.491043 0.704733
+vn 0.704733 -0.491043 0.512009
+vn 0.828486 -0.491043 0.269173
+vn 0.871120 -0.491043 0.000000
+vn 0.828486 -0.491043 -0.269173
+vn 0.704733 -0.491043 -0.512009
+s 1
+f 20/1/1 9/2/2 21/3/3
+f 1/4/4 9/5/2 2/6/5
+f 21/7/3 9/5/2 1/4/4
+f 2/6/5 9/5/2 3/8/6
+f 19/9/7 9/2/2 20/1/1
+f 18/10/8 9/2/2 19/9/7
+f 17/11/9 9/2/2 18/10/8
+f 16/12/10 9/2/2 17/11/9
+f 15/13/11 9/2/2 16/12/10
+f 14/14/12 9/2/2 15/13/11
+f 13/15/13 9/2/2 14/14/12
+f 12/16/14 9/2/2 13/15/13
+f 11/17/15 9/5/2 12/18/14
+f 10/19/16 9/5/2 11/17/15
+f 8/20/17 9/5/2 10/19/16
+f 7/21/18 9/5/2 8/20/17
+f 6/22/19 9/5/2 7/21/18
+f 5/23/20 9/5/2 6/22/19
+f 4/24/21 9/5/2 5/23/20
+f 3/8/6 9/5/2 4/24/21
+f 21/25/3 1/26/4 20/27/1
+f 1/26/4 2/28/5 20/27/1
+f 2/28/5 3/29/6 20/27/1
+f 3/29/6 4/30/21 20/27/1
+f 4/30/21 5/31/20 20/27/1
+f 5/31/20 6/32/19 20/27/1
+f 6/32/19 7/33/18 20/27/1
+f 7/33/18 8/34/17 20/27/1
+f 8/34/17 10/35/16 20/27/1
+f 10/35/16 11/36/15 20/27/1
+f 11/36/15 12/37/14 20/27/1
+f 12/37/14 13/38/13 20/27/1
+f 13/38/13 14/39/12 20/27/1
+f 14/39/12 15/40/11 20/27/1
+f 15/40/11 16/41/10 20/27/1
+f 16/41/10 17/42/9 20/27/1
+f 17/42/9 18/43/8 19/44/7
+f 20/27/1 17/42/9 19/44/7
diff --git a/src/graphs/engine/meshes/coneFlat.mesh b/src/graphs/engine/meshes/coneFlat.mesh
new file mode 100644
index 0000000..b6984f7
--- /dev/null
+++ b/src/graphs/engine/meshes/coneFlat.mesh
Binary files differ
diff --git a/src/graphs/engine/meshes/coneFlat.obj b/src/graphs/engine/meshes/coneFlat.obj
new file mode 100644
index 0000000..51c3821
--- /dev/null
+++ b/src/graphs/engine/meshes/coneFlat.obj
@@ -0,0 +1,89 @@
+# Blender v2.66 (sub 0) OBJ File: 'cone.blend'
+# www.blender.org
+o Cone_Cone.001
+v 0.000000 -1.000000 -1.000000
+v 0.309017 -1.000000 -0.951057
+v 0.587785 -1.000000 -0.809017
+v 0.809017 -1.000000 -0.587785
+v 0.951057 -1.000000 -0.309017
+v 1.000000 -1.000000 0.000000
+v 0.951056 -1.000000 0.309017
+v 0.809017 -1.000000 0.587785
+v 0.000000 1.000000 0.000000
+v 0.587785 -1.000000 0.809017
+v 0.309017 -1.000000 0.951057
+v -0.000000 -1.000000 1.000000
+v -0.309017 -1.000000 0.951056
+v -0.587786 -1.000000 0.809017
+v -0.809017 -1.000000 0.587785
+v -0.951057 -1.000000 0.309016
+v -1.000000 -1.000000 -0.000001
+v -0.951056 -1.000000 -0.309018
+v -0.809016 -1.000000 -0.587786
+v -0.587784 -1.000000 -0.809018
+v -0.309016 -1.000000 -0.951057
+vt 0.018984 0.308330
+vt 0.500000 0.482045
+vt 0.000000 0.410924
+vt 0.011213 0.505476
+vt 0.051524 0.582730
+vt 0.116989 0.635124
+vt 0.066306 0.207736
+vt 0.137334 0.118990
+vt 0.225115 0.050777
+vt 0.321057 0.009776
+vt 0.415768 0.000000
+vt 0.499978 0.022406
+vt 0.551525 0.582730
+vt 1.000000 0.482045
+vt 0.616989 0.635125
+vt 0.511213 0.505476
+vt 0.500000 0.410924
+vt 0.518984 0.308330
+vt 0.566306 0.207736
+vt 0.637334 0.118990
+vt 0.725115 0.050777
+vt 0.821057 0.009776
+vt 0.915768 0.000000
+vt 0.999978 0.022406
+vn -0.407058 0.442793 -0.798898
+vn 0.140263 0.442793 -0.885585
+vn -0.140262 0.442793 -0.885585
+vn 0.407059 0.442793 -0.798898
+vn -0.634008 0.442793 -0.634010
+vn -0.798897 0.442793 -0.407060
+vn -0.885585 0.442793 -0.140264
+vn -0.885585 0.442793 0.140262
+vn -0.798898 0.442793 0.407058
+vn -0.634009 0.442793 0.634009
+vn -0.407059 0.442793 0.798898
+vn -0.140263 0.442793 0.885585
+vn 0.140263 0.442793 0.885585
+vn 0.407059 0.442793 0.798898
+vn 0.634009 0.442793 0.634009
+vn 0.798898 0.442793 0.407059
+vn 0.885585 0.442793 0.140263
+vn 0.885585 0.442793 -0.140263
+vn 0.798898 0.442793 -0.407059
+vn 0.634009 0.442793 -0.634009
+s off
+f 20/1/1 9/2/1 21/3/1
+f 1/4/2 9/2/2 2/5/2
+f 21/3/3 9/2/3 1/4/3
+f 2/5/4 9/2/4 3/6/4
+f 19/7/5 9/2/5 20/1/5
+f 18/8/6 9/2/6 19/7/6
+f 17/9/7 9/2/7 18/8/7
+f 16/10/8 9/2/8 17/9/8
+f 15/11/9 9/2/9 16/10/9
+f 14/12/10 9/2/10 15/11/10
+f 13/13/11 9/14/11 14/15/11
+f 12/16/12 9/14/12 13/13/12
+f 11/17/13 9/14/13 12/16/13
+f 10/18/14 9/14/14 11/17/14
+f 8/19/15 9/14/15 10/18/15
+f 7/20/16 9/14/16 8/19/16
+f 6/21/17 9/14/17 7/20/17
+f 5/22/18 9/14/18 6/21/18
+f 4/23/19 9/14/19 5/22/19
+f 3/24/20 9/14/20 4/23/20
diff --git a/src/graphs/engine/meshes/coneSmooth.mesh b/src/graphs/engine/meshes/coneSmooth.mesh
new file mode 100644
index 0000000..522f731
--- /dev/null
+++ b/src/graphs/engine/meshes/coneSmooth.mesh
Binary files differ
diff --git a/src/graphs/engine/meshes/coneSmooth.obj b/src/graphs/engine/meshes/coneSmooth.obj
new file mode 100644
index 0000000..48c48ba
--- /dev/null
+++ b/src/graphs/engine/meshes/coneSmooth.obj
@@ -0,0 +1,90 @@
+# Blender v2.66 (sub 0) OBJ File: 'cone.blend'
+# www.blender.org
+o Cone_Cone.001
+v 0.000000 -1.000000 -1.000000
+v 0.309017 -1.000000 -0.951057
+v 0.587785 -1.000000 -0.809017
+v 0.809017 -1.000000 -0.587785
+v 0.951057 -1.000000 -0.309017
+v 1.000000 -1.000000 0.000000
+v 0.951056 -1.000000 0.309017
+v 0.809017 -1.000000 0.587785
+v 0.000000 1.000000 0.000000
+v 0.587785 -1.000000 0.809017
+v 0.309017 -1.000000 0.951057
+v -0.000000 -1.000000 1.000000
+v -0.309017 -1.000000 0.951056
+v -0.587786 -1.000000 0.809017
+v -0.809017 -1.000000 0.587785
+v -0.951057 -1.000000 0.309016
+v -1.000000 -1.000000 -0.000001
+v -0.951056 -1.000000 -0.309018
+v -0.809016 -1.000000 -0.587786
+v -0.587784 -1.000000 -0.809018
+v -0.309016 -1.000000 -0.951057
+vt 0.018984 0.308330
+vt 0.500000 0.482045
+vt 0.000000 0.410924
+vt 0.011213 0.505476
+vt 0.051524 0.582730
+vt 0.116989 0.635124
+vt 0.066306 0.207736
+vt 0.137334 0.118990
+vt 0.225115 0.050777
+vt 0.321057 0.009776
+vt 0.415768 0.000000
+vt 0.499978 0.022406
+vt 0.551525 0.582730
+vt 1.000000 0.482045
+vt 0.616989 0.635125
+vt 0.511213 0.505476
+vt 0.500000 0.410924
+vt 0.518984 0.308330
+vt 0.566306 0.207736
+vt 0.637334 0.118990
+vt 0.725115 0.050777
+vt 0.821057 0.009776
+vt 0.915768 0.000000
+vt 0.999978 0.022406
+vn -0.525712 0.447188 -0.723594
+vn 0.000000 1.000000 0.000000
+vn -0.276376 0.447188 -0.850642
+vn 0.000000 0.447188 -0.894406
+vn 0.276376 0.447188 -0.850642
+vn 0.525712 0.447188 -0.723594
+vn -0.723594 0.447188 -0.525712
+vn -0.850642 0.447188 -0.276376
+vn -0.894406 0.447188 0.000000
+vn -0.850642 0.447188 0.276376
+vn -0.723594 0.447188 0.525712
+vn -0.525712 0.447188 0.723594
+vn -0.276376 0.447188 0.850642
+vn 0.000000 0.447188 0.894406
+vn 0.276376 0.447188 0.850642
+vn 0.525712 0.447188 0.723594
+vn 0.723594 0.447188 0.525712
+vn 0.850642 0.447188 0.276376
+vn 0.894406 0.447188 0.000000
+vn 0.850642 0.447188 -0.276376
+vn 0.723594 0.447188 -0.525712
+s 1
+f 20/1/1 9/2/2 21/3/3
+f 1/4/4 9/2/2 2/5/5
+f 21/3/3 9/2/2 1/4/4
+f 2/5/5 9/2/2 3/6/6
+f 19/7/7 9/2/2 20/1/1
+f 18/8/8 9/2/2 19/7/7
+f 17/9/9 9/2/2 18/8/8
+f 16/10/10 9/2/2 17/9/9
+f 15/11/11 9/2/2 16/10/10
+f 14/12/12 9/2/2 15/11/11
+f 13/13/13 9/14/2 14/15/12
+f 12/16/14 9/14/2 13/13/13
+f 11/17/15 9/14/2 12/16/14
+f 10/18/16 9/14/2 11/17/15
+f 8/19/17 9/14/2 10/18/16
+f 7/20/18 9/14/2 8/19/17
+f 6/21/19 9/14/2 7/20/18
+f 5/22/20 9/14/2 6/21/19
+f 4/23/21 9/14/2 5/22/20
+f 3/24/6 9/14/2 4/23/21
diff --git a/src/graphs/engine/meshes/cubeFilledFlat.mesh b/src/graphs/engine/meshes/cubeFilledFlat.mesh
new file mode 100644
index 0000000..55fba64
--- /dev/null
+++ b/src/graphs/engine/meshes/cubeFilledFlat.mesh
Binary files differ
diff --git a/src/graphs/engine/meshes/cubeFilledFlat.obj b/src/graphs/engine/meshes/cubeFilledFlat.obj
new file mode 100644
index 0000000..108cf7a
--- /dev/null
+++ b/src/graphs/engine/meshes/cubeFilledFlat.obj
@@ -0,0 +1,54 @@
+# Blender v2.66 (sub 0) OBJ File: 'cube_filled.blend'
+# www.blender.org
+o Cube
+v -1.000000 -1.000000 1.000000
+v -1.000000 -1.000000 -1.000000
+v 1.000000 -1.000000 -1.000000
+v 1.000000 -1.000000 1.000000
+v -1.000000 1.000000 1.000000
+v -1.000000 1.000000 -1.000000
+v 1.000000 1.000000 -1.000000
+v 1.000000 1.000000 1.000000
+vt 0.666667 0.332314
+vt 0.334353 0.333333
+vt 0.665647 0.000000
+vt 0.001020 0.333333
+vt 0.000000 0.001020
+vt 0.333333 0.332314
+vt 0.333333 0.665647
+vt 0.001019 0.666667
+vt 0.000000 0.334353
+vt 0.334353 0.666667
+vt 0.333333 0.334353
+vt 0.665647 0.333333
+vt 0.333333 0.667686
+vt 0.665647 0.666667
+vt 0.666667 0.998980
+vt 0.667686 0.333333
+vt 0.666667 0.001019
+vt 0.998980 0.000000
+vt 0.333333 0.001019
+vt 0.332314 0.000000
+vt 0.332314 0.333333
+vt 0.666667 0.665647
+vt 0.334353 1.000000
+vt 1.000000 0.332314
+vn -1.000000 0.000000 0.000000
+vn 0.000000 0.000000 -1.000000
+vn 1.000000 -0.000000 0.000000
+vn 0.000000 0.000000 1.000000
+vn 0.000000 1.000000 0.000000
+vn -0.000000 -1.000000 -0.000000
+s off
+f 5/1/1 6/2/1 1/3/1
+f 6/4/2 7/5/2 2/6/2
+f 7/7/3 8/8/3 4/9/3
+f 8/10/4 5/11/4 1/12/4
+f 8/13/5 7/14/5 6/15/5
+f 2/16/6 3/17/6 4/18/6
+f 6/2/1 2/19/1 1/3/1
+f 7/5/2 3/20/2 2/6/2
+f 3/21/3 7/7/3 4/9/3
+f 4/22/4 8/10/4 1/12/4
+f 5/23/5 8/13/5 6/15/5
+f 1/24/6 2/16/6 4/18/6
diff --git a/src/graphs/engine/meshes/cubeFilledSmooth.mesh b/src/graphs/engine/meshes/cubeFilledSmooth.mesh
new file mode 100644
index 0000000..a67070d
--- /dev/null
+++ b/src/graphs/engine/meshes/cubeFilledSmooth.mesh
Binary files differ
diff --git a/src/graphs/engine/meshes/cubeFilledSmooth.obj b/src/graphs/engine/meshes/cubeFilledSmooth.obj
new file mode 100644
index 0000000..07350f0
--- /dev/null
+++ b/src/graphs/engine/meshes/cubeFilledSmooth.obj
@@ -0,0 +1,56 @@
+# Blender v2.66 (sub 0) OBJ File: 'cube_filled.blend'
+# www.blender.org
+o Cube
+v -1.000000 -1.000000 1.000000
+v -1.000000 -1.000000 -1.000000
+v 1.000000 -1.000000 -1.000000
+v 1.000000 -1.000000 1.000000
+v -1.000000 1.000000 1.000000
+v -1.000000 1.000000 -1.000000
+v 1.000000 1.000000 -1.000000
+v 1.000000 1.000000 1.000000
+vt 0.666667 0.332314
+vt 0.334353 0.333333
+vt 0.665647 0.000000
+vt 0.001020 0.333333
+vt 0.000000 0.001020
+vt 0.333333 0.332314
+vt 0.333333 0.665647
+vt 0.001019 0.666667
+vt 0.000000 0.334353
+vt 0.334353 0.666667
+vt 0.333333 0.334353
+vt 0.665647 0.333333
+vt 0.333333 0.667686
+vt 0.665647 0.666667
+vt 0.666667 0.998980
+vt 0.667686 0.333333
+vt 0.666667 0.001019
+vt 0.998980 0.000000
+vt 0.333333 0.001019
+vt 0.332314 0.000000
+vt 0.332314 0.333333
+vt 0.666667 0.665647
+vt 0.334353 1.000000
+vt 1.000000 0.332314
+vn -0.577349 0.577349 0.577349
+vn -0.577349 0.577349 -0.577349
+vn -0.577349 -0.577349 0.577349
+vn 0.577349 0.577349 -0.577349
+vn -0.577349 -0.577349 -0.577349
+vn 0.577349 0.577349 0.577349
+vn 0.577349 -0.577349 0.577349
+vn 0.577349 -0.577349 -0.577349
+s 1
+f 5/1/1 6/2/2 1/3/3
+f 6/4/2 7/5/4 2/6/5
+f 7/7/4 8/8/6 4/9/7
+f 8/10/6 5/11/1 1/12/3
+f 8/13/6 7/14/4 6/15/2
+f 2/16/5 3/17/8 4/18/7
+f 6/2/2 2/19/5 1/3/3
+f 7/5/4 3/20/8 2/6/5
+f 3/21/8 7/7/4 4/9/7
+f 4/22/7 8/10/6 1/12/3
+f 5/23/1 8/13/6 6/15/2
+f 1/24/3 2/16/5 4/18/7
diff --git a/src/graphs/engine/meshes/cubeFlat.mesh b/src/graphs/engine/meshes/cubeFlat.mesh
new file mode 100644
index 0000000..07d82d3
--- /dev/null
+++ b/src/graphs/engine/meshes/cubeFlat.mesh
Binary files differ
diff --git a/src/graphs/engine/meshes/cubeFlat.obj b/src/graphs/engine/meshes/cubeFlat.obj
new file mode 100644
index 0000000..3c8d6d0
--- /dev/null
+++ b/src/graphs/engine/meshes/cubeFlat.obj
@@ -0,0 +1,47 @@
+# Blender v2.66 (sub 0) OBJ File: 'cube.blend'
+# www.blender.org
+o Cube
+v -1.000000 -1.000000 1.000000
+v -1.000000 -1.000000 -1.000000
+v 1.000000 -1.000000 -1.000000
+v 1.000000 -1.000000 1.000000
+v -1.000000 1.000000 1.000000
+v -1.000000 1.000000 -1.000000
+v 1.000000 1.000000 -1.000000
+v 1.000000 1.000000 1.000000
+vt 0.998999 0.334334
+vt 0.667668 0.334334
+vt 0.998999 0.665666
+vt 0.332332 0.001001
+vt 0.001001 0.001001
+vt 0.332332 0.332332
+vt 0.332332 0.334334
+vt 0.001001 0.334334
+vt 0.001001 0.665666
+vt 0.665666 0.001001
+vt 0.334334 0.001001
+vt 0.334334 0.332332
+vt 0.665666 0.334334
+vt 0.334334 0.334334
+vt 0.334334 0.665666
+vt 0.667668 0.665666
+vt 0.001001 0.332332
+vt 0.332332 0.665666
+vt 0.665666 0.332332
+vt 0.665666 0.665666
+vn -1.000000 0.000000 0.000000
+vn 0.000000 0.000000 -1.000000
+vn 1.000000 -0.000000 0.000000
+vn 0.000000 0.000000 1.000000
+vn 0.000000 1.000000 0.000000
+s off
+f 5/1/1 6/2/1 1/3/1
+f 6/4/2 7/5/2 2/6/2
+f 7/7/3 8/8/3 4/9/3
+f 8/10/4 5/11/4 1/12/4
+f 8/13/5 7/14/5 6/15/5
+f 6/2/1 2/16/1 1/3/1
+f 7/5/2 3/17/2 2/6/2
+f 3/18/3 7/7/3 4/9/3
+f 4/19/4 8/10/4 1/12/4
+f 5/20/5 8/13/5 6/15/5
diff --git a/src/graphs/engine/meshes/cubeSmooth.mesh b/src/graphs/engine/meshes/cubeSmooth.mesh
new file mode 100644
index 0000000..a951d47
--- /dev/null
+++ b/src/graphs/engine/meshes/cubeSmooth.mesh
Binary files differ
diff --git a/src/graphs/engine/meshes/cubeSmooth.obj b/src/graphs/engine/meshes/cubeSmooth.obj
new file mode 100644
index 0000000..9d147bf
--- /dev/null
+++ b/src/graphs/engine/meshes/cubeSmooth.obj
@@ -0,0 +1,50 @@
+# Blender v2.66 (sub 0) OBJ File: 'cube.blend'
+# www.blender.org
+o Cube
+v -1.000000 -1.000000 1.000000
+v -1.000000 -1.000000 -1.000000
+v 1.000000 -1.000000 -1.000000
+v 1.000000 -1.000000 1.000000
+v -1.000000 1.000000 1.000000
+v -1.000000 1.000000 -1.000000
+v 1.000000 1.000000 -1.000000
+v 1.000000 1.000000 1.000000
+vt 0.998999 0.334334
+vt 0.667668 0.334334
+vt 0.998999 0.665666
+vt 0.332332 0.001001
+vt 0.001001 0.001001
+vt 0.332332 0.332332
+vt 0.332332 0.334334
+vt 0.001001 0.334334
+vt 0.001001 0.665666
+vt 0.665666 0.001001
+vt 0.334334 0.001001
+vt 0.334334 0.332332
+vt 0.665666 0.334334
+vt 0.334334 0.334334
+vt 0.334334 0.665666
+vt 0.667668 0.665666
+vt 0.001001 0.332332
+vt 0.332332 0.665666
+vt 0.665666 0.332332
+vt 0.665666 0.665666
+vn -0.577349 0.577349 0.577349
+vn -0.577349 0.577349 -0.577349
+vn -0.707083 0.000000 0.707083
+vn 0.577349 0.577349 -0.577349
+vn -0.707083 0.000000 -0.707083
+vn 0.577349 0.577349 0.577349
+vn 0.707083 0.000000 0.707083
+vn 0.707083 0.000000 -0.707083
+s 1
+f 5/1/1 6/2/2 1/3/3
+f 6/4/2 7/5/4 2/6/5
+f 7/7/4 8/8/6 4/9/7
+f 8/10/6 5/11/1 1/12/3
+f 8/13/6 7/14/4 6/15/2
+f 6/2/2 2/16/5 1/3/3
+f 7/5/4 3/17/8 2/6/5
+f 3/18/8 7/7/4 4/9/7
+f 4/19/7 8/10/6 1/12/3
+f 5/20/1 8/13/6 6/15/2
diff --git a/src/graphs/engine/meshes/cylinderFilledFlat.mesh b/src/graphs/engine/meshes/cylinderFilledFlat.mesh
new file mode 100644
index 0000000..7cb81dc
--- /dev/null
+++ b/src/graphs/engine/meshes/cylinderFilledFlat.mesh
Binary files differ
diff --git a/src/graphs/engine/meshes/cylinderFilledFlat.obj b/src/graphs/engine/meshes/cylinderFilledFlat.obj
new file mode 100644
index 0000000..16c2ef3
--- /dev/null
+++ b/src/graphs/engine/meshes/cylinderFilledFlat.obj
@@ -0,0 +1,361 @@
+# Blender v2.66 (sub 0) OBJ File: 'cylinder_filled.blend'
+# www.blender.org
+o Cylinder
+v 0.000000 -1.000000 -1.000000
+v 0.000000 1.000000 -1.000000
+v 0.195090 -1.000000 -0.980785
+v 0.195090 1.000000 -0.980785
+v 0.382683 -1.000000 -0.923880
+v 0.382683 1.000000 -0.923880
+v 0.555570 -1.000000 -0.831470
+v 0.555570 1.000000 -0.831470
+v 0.707107 -1.000000 -0.707107
+v 0.707107 1.000000 -0.707107
+v 0.831470 -1.000000 -0.555570
+v 0.831470 1.000000 -0.555570
+v 0.923880 -1.000000 -0.382683
+v 0.923880 1.000000 -0.382683
+v 0.980785 -1.000000 -0.195090
+v 0.980785 1.000000 -0.195090
+v 1.000000 -1.000000 -0.000000
+v 1.000000 1.000000 -0.000000
+v 0.980785 -1.000000 0.195090
+v 0.980785 1.000000 0.195090
+v 0.923880 -1.000000 0.382683
+v 0.923880 1.000000 0.382683
+v 0.831470 -1.000000 0.555570
+v 0.831470 1.000000 0.555570
+v 0.707107 -1.000000 0.707107
+v 0.707107 1.000000 0.707107
+v 0.555570 -1.000000 0.831470
+v 0.555570 1.000000 0.831470
+v 0.382683 -1.000000 0.923880
+v 0.382683 1.000000 0.923880
+v 0.195090 -1.000000 0.980785
+v 0.195090 1.000000 0.980785
+v -0.000000 -1.000000 1.000000
+v -0.000000 1.000000 1.000000
+v -0.195091 -1.000000 0.980785
+v -0.195091 1.000000 0.980785
+v -0.382684 -1.000000 0.923879
+v -0.382684 1.000000 0.923879
+v -0.555571 -1.000000 0.831469
+v -0.555571 1.000000 0.831469
+v -0.707107 -1.000000 0.707106
+v -0.707107 1.000000 0.707106
+v -0.831470 -1.000000 0.555570
+v -0.831470 1.000000 0.555570
+v -0.923880 -1.000000 0.382683
+v -0.923880 1.000000 0.382683
+v -0.980785 -1.000000 0.195089
+v -0.980785 1.000000 0.195089
+v -1.000000 -1.000000 -0.000001
+v -1.000000 1.000000 -0.000001
+v -0.980785 -1.000000 -0.195091
+v -0.980785 1.000000 -0.195091
+v -0.923879 -1.000000 -0.382684
+v -0.923879 1.000000 -0.382684
+v -0.831469 -1.000000 -0.555571
+v -0.831469 1.000000 -0.555571
+v -0.707106 -1.000000 -0.707108
+v -0.707106 1.000000 -0.707108
+v -0.555569 -1.000000 -0.831470
+v -0.555569 1.000000 -0.831470
+v -0.382682 -1.000000 -0.923880
+v -0.382682 1.000000 -0.923880
+v -0.195089 -1.000000 -0.980786
+v -0.195089 1.000000 -0.980786
+vt 0.059001 0.355439
+vt 0.057906 0.712417
+vt 0.027048 0.712322
+vt 0.028143 0.355344
+vt 0.000000 0.712239
+vt 0.998905 0.355261
+vt 1.000000 0.712239
+vt 0.972952 0.712322
+vt 0.971857 0.355344
+vt 0.942094 0.712417
+vt 0.940999 0.355439
+vt 0.908611 0.712520
+vt 0.907515 0.355541
+vt 0.873789 0.712627
+vt 0.872694 0.355648
+vt 0.838968 0.712733
+vt 0.837872 0.355755
+vt 0.805484 0.712836
+vt 0.777341 0.713350
+vt 0.776246 1.070328
+vt 0.742763 1.070225
+vt 0.743858 0.713247
+vt 0.707941 1.070119
+vt 0.709036 0.713140
+vt 0.673120 1.070012
+vt 0.674215 0.713033
+vt 0.639636 1.069909
+vt 0.640732 0.712931
+vt 0.608778 1.069814
+vt 0.609873 0.712836
+vt 0.608778 0.355858
+vt 0.642261 0.355755
+vt 0.643357 0.712733
+vt 0.677083 0.355648
+vt 0.678178 0.712627
+vt 0.711904 0.355541
+vt 0.713000 0.712520
+vt 0.746483 0.712417
+vt 0.745388 0.355439
+vt 0.777341 0.712322
+vt 0.776246 0.355344
+vt 0.804389 0.712239
+vt 0.607683 0.355261
+vt 0.608778 0.712239
+vt 0.580635 0.355344
+vt 0.581730 0.712322
+vt 0.549777 0.355439
+vt 0.550872 0.712417
+vt 0.516293 0.355541
+vt 0.517389 0.712520
+vt 0.481472 0.355648
+vt 0.482567 0.712627
+vt 0.446650 0.355755
+vt 0.447746 0.712733
+vt 0.413167 0.355858
+vt 0.414262 0.712836
+vt 0.382309 0.355952
+vt 0.383404 0.712931
+vt 0.355261 0.356035
+vt 0.226469 0.355952
+vt 0.225374 0.712931
+vt 0.195611 0.355858
+vt 0.194516 0.712836
+vt 0.162127 0.355755
+vt 0.161032 0.712733
+vt 0.127306 0.355648
+vt 0.000000 0.160135
+vt 0.006826 0.125818
+vt 0.000000 0.195126
+vt 0.092484 0.355541
+vt 0.091389 0.712520
+vt 0.126211 0.712627
+vt 0.394917 0.290863
+vt 0.375477 0.261770
+vt 0.419658 0.315605
+vt 0.001095 0.355261
+vt 0.804389 0.355858
+vt 0.803294 0.355261
+vt 0.356356 0.713014
+vt 0.020216 0.093491
+vt 0.039656 0.064398
+vt 0.064397 0.039656
+vt 0.093491 0.020217
+vt 0.125817 0.006826
+vt 0.160135 0.000000
+vt 0.195125 0.000000
+vt 0.229443 0.006826
+vt 0.261770 0.020216
+vt 0.290863 0.039656
+vt 0.315605 0.064398
+vt 0.335045 0.093491
+vt 0.348435 0.125818
+vt 0.355261 0.160135
+vt 0.355261 0.195126
+vt 0.348435 0.229443
+vt 0.335045 0.261770
+vt 0.315605 0.290863
+vt 0.290863 0.315605
+vt 0.261770 0.335045
+vt 0.229443 0.348435
+vt 0.195126 0.355261
+vt 0.160135 0.355261
+vt 0.125818 0.348435
+vt 0.093491 0.335045
+vt 0.064398 0.315605
+vt 0.039656 0.290863
+vt 0.020216 0.261770
+vt 0.006826 0.229443
+vt 0.362087 0.229443
+vt 0.362087 0.125818
+vt 0.375477 0.093491
+vt 0.394917 0.064398
+vt 0.419659 0.039656
+vt 0.448752 0.020216
+vt 0.481079 0.006826
+vt 0.515396 0.000000
+vt 0.550387 0.000000
+vt 0.584704 0.006826
+vt 0.617031 0.020216
+vt 0.646124 0.039656
+vt 0.670866 0.064398
+vt 0.690306 0.093491
+vt 0.703696 0.125818
+vt 0.710522 0.160136
+vt 0.710522 0.195126
+vt 0.703696 0.229444
+vt 0.690306 0.261770
+vt 0.670866 0.290864
+vt 0.646124 0.315605
+vt 0.617031 0.335045
+vt 0.584704 0.348435
+vt 0.550386 0.355261
+vt 0.515396 0.355261
+vt 0.481078 0.348435
+vt 0.448752 0.335045
+vn 0.098017 0.000000 -0.995185
+vn 0.290285 0.000000 -0.956940
+vn 0.471397 0.000000 -0.881921
+vn 0.634393 0.000000 -0.773010
+vn 0.773010 0.000000 -0.634393
+vn 0.881921 0.000000 -0.471397
+vn 0.956940 0.000000 -0.290285
+vn 0.995185 0.000000 -0.098017
+vn 0.995185 0.000000 0.098017
+vn 0.956940 0.000000 0.290285
+vn 0.881921 0.000000 0.471396
+vn 0.773010 0.000000 0.634393
+vn 0.634393 0.000000 0.773010
+vn 0.471397 0.000000 0.881921
+vn 0.290284 0.000000 0.956940
+vn 0.098017 0.000000 0.995185
+vn -0.098018 0.000000 0.995185
+vn -0.290285 0.000000 0.956940
+vn -0.471397 0.000000 0.881921
+vn -0.634394 0.000000 0.773010
+vn -0.773011 0.000000 0.634393
+vn -0.881922 0.000000 0.471396
+vn -0.956941 0.000000 0.290284
+vn -0.995185 0.000000 0.098016
+vn -0.995185 -0.000000 -0.098018
+vn -0.956940 -0.000000 -0.290286
+vn -0.881921 -0.000000 -0.471398
+vn -0.773010 -0.000000 -0.634394
+vn -0.634392 -0.000000 -0.773011
+vn -0.471395 -0.000000 -0.881922
+vn -0.000000 1.000000 0.000000
+vn -0.098017 -0.000000 -0.995185
+vn -0.290283 -0.000000 -0.956941
+vn -0.000000 -1.000000 -0.000000
+s off
+f 1/1/1 2/2/1 4/3/1
+f 3/4/2 4/3/2 6/5/2
+f 5/6/3 6/7/3 8/8/3
+f 7/9/4 8/8/4 10/10/4
+f 9/11/5 10/10/5 12/12/5
+f 11/13/6 12/12/6 14/14/6
+f 13/15/7 14/14/7 16/16/7
+f 15/17/8 16/16/8 18/18/8
+f 17/19/9 18/20/9 20/21/9
+f 19/22/10 20/21/10 22/23/10
+f 21/24/11 22/23/11 24/25/11
+f 23/26/12 24/25/12 26/27/12
+f 25/28/13 26/27/13 28/29/13
+f 27/30/14 28/31/14 30/32/14
+f 29/33/15 30/32/15 32/34/15
+f 31/35/16 32/34/16 34/36/16
+f 33/37/17 34/36/17 35/38/17
+f 35/38/18 36/39/18 37/40/18
+f 37/40/19 38/41/19 39/42/19
+f 39/43/20 40/44/20 41/45/20
+f 41/45/21 42/46/21 43/47/21
+f 43/47/22 44/48/22 45/49/22
+f 45/49/23 46/50/23 47/51/23
+f 47/51/24 48/52/24 49/53/24
+f 49/53/25 50/54/25 51/55/25
+f 51/55/26 52/56/26 53/57/26
+f 53/57/27 54/58/27 55/59/27
+f 55/60/28 56/61/28 57/62/28
+f 57/62/29 58/63/29 59/64/29
+f 59/64/30 60/65/30 61/66/30
+f 4/67/31 2/68/31 6/69/31
+f 63/70/32 64/71/32 1/1/32
+f 61/66/33 62/72/33 63/70/33
+f 63/73/34 1/74/34 61/75/34
+f 3/4/1 1/1/1 4/3/1
+f 5/76/2 3/4/2 6/5/2
+f 7/9/3 5/6/3 8/8/3
+f 9/11/4 7/9/4 10/10/4
+f 11/13/5 9/11/5 12/12/5
+f 13/15/6 11/13/6 14/14/6
+f 15/17/7 13/15/7 16/16/7
+f 17/77/8 15/17/8 18/18/8
+f 19/22/9 17/19/9 20/21/9
+f 21/24/10 19/22/10 22/23/10
+f 23/26/11 21/24/11 24/25/11
+f 25/28/12 23/26/12 26/27/12
+f 27/30/13 25/28/13 28/29/13
+f 29/33/14 27/30/14 30/32/14
+f 31/35/15 29/33/15 32/34/15
+f 33/37/16 31/35/16 34/36/16
+f 34/36/17 36/39/17 35/38/17
+f 36/39/18 38/41/18 37/40/18
+f 38/41/19 40/78/19 39/42/19
+f 40/44/20 42/46/20 41/45/20
+f 42/46/21 44/48/21 43/47/21
+f 44/48/22 46/50/22 45/49/22
+f 46/50/23 48/52/23 47/51/23
+f 48/52/24 50/54/24 49/53/24
+f 50/54/25 52/56/25 51/55/25
+f 52/56/26 54/58/26 53/57/26
+f 54/58/27 56/79/27 55/59/27
+f 56/61/28 58/63/28 57/62/28
+f 58/63/29 60/65/29 59/64/29
+f 60/65/30 62/72/30 61/66/30
+f 2/68/31 64/80/31 6/69/31
+f 64/80/31 62/81/31 6/69/31
+f 62/81/31 60/82/31 6/69/31
+f 60/82/31 58/83/31 6/69/31
+f 58/83/31 56/84/31 6/69/31
+f 56/84/31 54/85/31 6/69/31
+f 54/85/31 52/86/31 6/69/31
+f 52/86/31 50/87/31 6/69/31
+f 50/87/31 48/88/31 6/69/31
+f 48/88/31 46/89/31 6/69/31
+f 46/89/31 44/90/31 6/69/31
+f 44/90/31 42/91/31 6/69/31
+f 42/91/31 40/92/31 6/69/31
+f 40/92/31 38/93/31 6/69/31
+f 38/93/31 36/94/31 6/69/31
+f 36/94/31 34/95/31 6/69/31
+f 34/95/31 32/96/31 6/69/31
+f 32/96/31 30/97/31 6/69/31
+f 30/97/31 28/98/31 6/69/31
+f 28/98/31 26/99/31 6/69/31
+f 26/99/31 24/100/31 6/69/31
+f 24/100/31 22/101/31 6/69/31
+f 22/101/31 20/102/31 6/69/31
+f 20/102/31 18/103/31 6/69/31
+f 18/103/31 16/104/31 6/69/31
+f 16/104/31 14/105/31 6/69/31
+f 14/105/31 12/106/31 6/69/31
+f 12/106/31 10/107/31 8/108/31
+f 6/69/31 12/106/31 8/108/31
+f 64/71/32 2/2/32 1/1/32
+f 62/72/33 64/71/33 63/70/33
+f 1/74/34 3/109/34 61/75/34
+f 3/109/34 5/94/34 61/75/34
+f 5/94/34 7/93/34 61/75/34
+f 7/93/34 9/110/34 61/75/34
+f 9/110/34 11/111/34 61/75/34
+f 11/111/34 13/112/34 61/75/34
+f 13/112/34 15/113/34 61/75/34
+f 15/113/34 17/114/34 61/75/34
+f 17/114/34 19/115/34 61/75/34
+f 19/115/34 21/116/34 61/75/34
+f 21/116/34 23/117/34 61/75/34
+f 23/117/34 25/118/34 61/75/34
+f 25/118/34 27/119/34 61/75/34
+f 27/119/34 29/120/34 61/75/34
+f 29/120/34 31/121/34 61/75/34
+f 31/121/34 33/122/34 61/75/34
+f 33/122/34 35/123/34 61/75/34
+f 35/123/34 37/124/34 61/75/34
+f 37/124/34 39/125/34 61/75/34
+f 39/125/34 41/126/34 61/75/34
+f 41/126/34 43/127/34 61/75/34
+f 43/127/34 45/128/34 61/75/34
+f 45/128/34 47/129/34 61/75/34
+f 47/129/34 49/130/34 61/75/34
+f 49/130/34 51/131/34 61/75/34
+f 51/131/34 53/132/34 61/75/34
+f 53/132/34 55/133/34 61/75/34
+f 55/133/34 57/134/34 59/135/34
+f 61/75/34 55/133/34 59/135/34
diff --git a/src/graphs/engine/meshes/cylinderFilledSmooth.mesh b/src/graphs/engine/meshes/cylinderFilledSmooth.mesh
new file mode 100644
index 0000000..94c87f3
--- /dev/null
+++ b/src/graphs/engine/meshes/cylinderFilledSmooth.mesh
Binary files differ
diff --git a/src/graphs/engine/meshes/cylinderFilledSmooth.obj b/src/graphs/engine/meshes/cylinderFilledSmooth.obj
new file mode 100644
index 0000000..90db7d6
--- /dev/null
+++ b/src/graphs/engine/meshes/cylinderFilledSmooth.obj
@@ -0,0 +1,391 @@
+# Blender v2.66 (sub 0) OBJ File: 'cylinder_filled.blend'
+# www.blender.org
+o Cylinder
+v 0.000000 -1.000000 -1.000000
+v 0.000000 1.000000 -1.000000
+v 0.195090 -1.000000 -0.980785
+v 0.195090 1.000000 -0.980785
+v 0.382683 -1.000000 -0.923880
+v 0.382683 1.000000 -0.923880
+v 0.555570 -1.000000 -0.831470
+v 0.555570 1.000000 -0.831470
+v 0.707107 -1.000000 -0.707107
+v 0.707107 1.000000 -0.707107
+v 0.831470 -1.000000 -0.555570
+v 0.831470 1.000000 -0.555570
+v 0.923880 -1.000000 -0.382683
+v 0.923880 1.000000 -0.382683
+v 0.980785 -1.000000 -0.195090
+v 0.980785 1.000000 -0.195090
+v 1.000000 -1.000000 -0.000000
+v 1.000000 1.000000 -0.000000
+v 0.980785 -1.000000 0.195090
+v 0.980785 1.000000 0.195090
+v 0.923880 -1.000000 0.382683
+v 0.923880 1.000000 0.382683
+v 0.831470 -1.000000 0.555570
+v 0.831470 1.000000 0.555570
+v 0.707107 -1.000000 0.707107
+v 0.707107 1.000000 0.707107
+v 0.555570 -1.000000 0.831470
+v 0.555570 1.000000 0.831470
+v 0.382683 -1.000000 0.923880
+v 0.382683 1.000000 0.923880
+v 0.195090 -1.000000 0.980785
+v 0.195090 1.000000 0.980785
+v -0.000000 -1.000000 1.000000
+v -0.000000 1.000000 1.000000
+v -0.195091 -1.000000 0.980785
+v -0.195091 1.000000 0.980785
+v -0.382684 -1.000000 0.923879
+v -0.382684 1.000000 0.923879
+v -0.555571 -1.000000 0.831469
+v -0.555571 1.000000 0.831469
+v -0.707107 -1.000000 0.707106
+v -0.707107 1.000000 0.707106
+v -0.831470 -1.000000 0.555570
+v -0.831470 1.000000 0.555570
+v -0.923880 -1.000000 0.382683
+v -0.923880 1.000000 0.382683
+v -0.980785 -1.000000 0.195089
+v -0.980785 1.000000 0.195089
+v -1.000000 -1.000000 -0.000001
+v -1.000000 1.000000 -0.000001
+v -0.980785 -1.000000 -0.195091
+v -0.980785 1.000000 -0.195091
+v -0.923879 -1.000000 -0.382684
+v -0.923879 1.000000 -0.382684
+v -0.831469 -1.000000 -0.555571
+v -0.831469 1.000000 -0.555571
+v -0.707106 -1.000000 -0.707108
+v -0.707106 1.000000 -0.707108
+v -0.555569 -1.000000 -0.831470
+v -0.555569 1.000000 -0.831470
+v -0.382682 -1.000000 -0.923880
+v -0.382682 1.000000 -0.923880
+v -0.195089 -1.000000 -0.980786
+v -0.195089 1.000000 -0.980786
+vt 0.059001 0.355439
+vt 0.057906 0.712417
+vt 0.027048 0.712322
+vt 0.028143 0.355344
+vt 0.000000 0.712239
+vt 0.998905 0.355261
+vt 1.000000 0.712239
+vt 0.972952 0.712322
+vt 0.971857 0.355344
+vt 0.942094 0.712417
+vt 0.940999 0.355439
+vt 0.908611 0.712520
+vt 0.907515 0.355541
+vt 0.873789 0.712627
+vt 0.872694 0.355648
+vt 0.838968 0.712733
+vt 0.837872 0.355755
+vt 0.805484 0.712836
+vt 0.777341 0.713350
+vt 0.776246 1.070328
+vt 0.742763 1.070225
+vt 0.743858 0.713247
+vt 0.707941 1.070119
+vt 0.709036 0.713140
+vt 0.673120 1.070012
+vt 0.674215 0.713033
+vt 0.639636 1.069909
+vt 0.640732 0.712931
+vt 0.608778 1.069814
+vt 0.609873 0.712836
+vt 0.608778 0.355858
+vt 0.642261 0.355755
+vt 0.643357 0.712733
+vt 0.677083 0.355648
+vt 0.678178 0.712627
+vt 0.711904 0.355541
+vt 0.713000 0.712520
+vt 0.746483 0.712417
+vt 0.745388 0.355439
+vt 0.777341 0.712322
+vt 0.776246 0.355344
+vt 0.804389 0.712239
+vt 0.607683 0.355261
+vt 0.608778 0.712239
+vt 0.580635 0.355344
+vt 0.581730 0.712322
+vt 0.549777 0.355439
+vt 0.550872 0.712417
+vt 0.516293 0.355541
+vt 0.517389 0.712520
+vt 0.481472 0.355648
+vt 0.482567 0.712627
+vt 0.446650 0.355755
+vt 0.447746 0.712733
+vt 0.413167 0.355858
+vt 0.414262 0.712836
+vt 0.382309 0.355952
+vt 0.383404 0.712931
+vt 0.355261 0.356035
+vt 0.226469 0.355952
+vt 0.225374 0.712931
+vt 0.195611 0.355858
+vt 0.194516 0.712836
+vt 0.162127 0.355755
+vt 0.161032 0.712733
+vt 0.127306 0.355648
+vt 0.000000 0.160135
+vt 0.006826 0.125818
+vt 0.000000 0.195126
+vt 0.092484 0.355541
+vt 0.091389 0.712520
+vt 0.126211 0.712627
+vt 0.394917 0.290863
+vt 0.375477 0.261770
+vt 0.419658 0.315605
+vt 0.001095 0.355261
+vt 0.804389 0.355858
+vt 0.803294 0.355261
+vt 0.356356 0.713014
+vt 0.020216 0.093491
+vt 0.039656 0.064398
+vt 0.064397 0.039656
+vt 0.093491 0.020217
+vt 0.125817 0.006826
+vt 0.160135 0.000000
+vt 0.195125 0.000000
+vt 0.229443 0.006826
+vt 0.261770 0.020216
+vt 0.290863 0.039656
+vt 0.315605 0.064398
+vt 0.335045 0.093491
+vt 0.348435 0.125818
+vt 0.355261 0.160135
+vt 0.355261 0.195126
+vt 0.348435 0.229443
+vt 0.335045 0.261770
+vt 0.315605 0.290863
+vt 0.290863 0.315605
+vt 0.261770 0.335045
+vt 0.229443 0.348435
+vt 0.195126 0.355261
+vt 0.160135 0.355261
+vt 0.125818 0.348435
+vt 0.093491 0.335045
+vt 0.064398 0.315605
+vt 0.039656 0.290863
+vt 0.020216 0.261770
+vt 0.006826 0.229443
+vt 0.362087 0.229443
+vt 0.362087 0.125818
+vt 0.375477 0.093491
+vt 0.394917 0.064398
+vt 0.419659 0.039656
+vt 0.448752 0.020216
+vt 0.481079 0.006826
+vt 0.515396 0.000000
+vt 0.550387 0.000000
+vt 0.584704 0.006826
+vt 0.617031 0.020216
+vt 0.646124 0.039656
+vt 0.670866 0.064398
+vt 0.690306 0.093491
+vt 0.703696 0.125818
+vt 0.710522 0.160136
+vt 0.710522 0.195126
+vt 0.703696 0.229444
+vt 0.690306 0.261770
+vt 0.670866 0.290864
+vt 0.646124 0.315605
+vt 0.617031 0.335045
+vt 0.584704 0.348435
+vt 0.550386 0.355261
+vt 0.515396 0.355261
+vt 0.481078 0.348435
+vt 0.448752 0.335045
+vn 0.000000 -0.685690 -0.727866
+vn 0.000000 0.685690 -0.727866
+vn 0.142003 0.685690 -0.713889
+vn 0.142003 -0.685690 -0.713889
+vn 0.278542 0.685690 -0.672475
+vn 0.278542 -0.685690 -0.672475
+vn 0.404370 0.685690 -0.605213
+vn 0.404370 -0.685690 -0.605213
+vn 0.514664 0.685690 -0.514664
+vn 0.514664 -0.685690 -0.514664
+vn 0.605213 0.685690 -0.404370
+vn 0.605213 -0.685690 -0.404370
+vn 0.672475 0.685690 -0.278542
+vn 0.672475 -0.685690 -0.278542
+vn 0.713889 0.685690 -0.142003
+vn 0.713889 -0.685690 -0.142003
+vn 0.727866 0.685690 0.000000
+vn 0.727866 -0.685690 0.000000
+vn 0.713889 0.685690 0.142003
+vn 0.713889 -0.685690 0.142003
+vn 0.672475 0.685690 0.278542
+vn 0.672475 -0.685690 0.278542
+vn 0.605213 0.685690 0.404370
+vn 0.605213 -0.685690 0.404370
+vn 0.514664 0.685690 0.514664
+vn 0.514664 -0.685690 0.514664
+vn 0.404370 0.685690 0.605213
+vn 0.404370 -0.685690 0.605213
+vn 0.278542 0.685690 0.672475
+vn 0.278542 -0.685690 0.672475
+vn 0.142003 0.685690 0.713889
+vn 0.142003 -0.685690 0.713889
+vn 0.000000 0.685690 0.727866
+vn 0.000000 -0.685690 0.727866
+vn -0.142003 -0.685690 0.713889
+vn -0.142003 0.685690 0.713889
+vn -0.278542 -0.685690 0.672475
+vn -0.278542 0.685690 0.672475
+vn -0.404370 -0.685690 0.605213
+vn -0.404370 0.685690 0.605213
+vn -0.514695 -0.685690 0.514664
+vn -0.514664 0.685690 0.514664
+vn -0.605213 -0.685690 0.404370
+vn -0.605213 0.685690 0.404370
+vn -0.672475 -0.685690 0.278542
+vn -0.672475 0.685690 0.278542
+vn -0.713889 -0.685690 0.142003
+vn -0.713889 0.685690 0.142003
+vn -0.727866 -0.685690 0.000000
+vn -0.727866 0.685690 0.000000
+vn -0.713889 -0.685690 -0.142003
+vn -0.713889 0.685690 -0.142003
+vn -0.672475 -0.685690 -0.278542
+vn -0.672475 0.685690 -0.278542
+vn -0.605213 -0.685690 -0.404370
+vn -0.605213 0.685690 -0.404370
+vn -0.514664 -0.685690 -0.514695
+vn -0.514664 0.685690 -0.514695
+vn -0.404370 -0.685690 -0.605213
+vn -0.404370 0.685690 -0.605213
+vn -0.278542 -0.685690 -0.672475
+vn -0.142003 -0.685690 -0.713889
+vn -0.142003 0.685690 -0.713889
+vn -0.278542 0.685690 -0.672475
+s 1
+f 1/1/1 2/2/2 4/3/3
+f 3/4/4 4/3/3 6/5/5
+f 5/6/6 6/7/5 8/8/7
+f 7/9/8 8/8/7 10/10/9
+f 9/11/10 10/10/9 12/12/11
+f 11/13/12 12/12/11 14/14/13
+f 13/15/14 14/14/13 16/16/15
+f 15/17/16 16/16/15 18/18/17
+f 17/19/18 18/20/17 20/21/19
+f 19/22/20 20/21/19 22/23/21
+f 21/24/22 22/23/21 24/25/23
+f 23/26/24 24/25/23 26/27/25
+f 25/28/26 26/27/25 28/29/27
+f 27/30/28 28/31/27 30/32/29
+f 29/33/30 30/32/29 32/34/31
+f 31/35/32 32/34/31 34/36/33
+f 33/37/34 34/36/33 35/38/35
+f 35/38/35 36/39/36 37/40/37
+f 37/40/37 38/41/38 39/42/39
+f 39/43/39 40/44/40 41/45/41
+f 41/45/41 42/46/42 43/47/43
+f 43/47/43 44/48/44 45/49/45
+f 45/49/45 46/50/46 47/51/47
+f 47/51/47 48/52/48 49/53/49
+f 49/53/49 50/54/50 51/55/51
+f 51/55/51 52/56/52 53/57/53
+f 53/57/53 54/58/54 55/59/55
+f 55/60/55 56/61/56 57/62/57
+f 57/62/57 58/63/58 59/64/59
+f 59/64/59 60/65/60 61/66/61
+f 4/67/3 2/68/2 6/69/5
+f 63/70/62 64/71/63 1/1/1
+f 61/66/61 62/72/64 63/70/62
+f 63/73/62 1/74/1 61/75/61
+f 3/4/4 1/1/1 4/3/3
+f 5/76/6 3/4/4 6/5/5
+f 7/9/8 5/6/6 8/8/7
+f 9/11/10 7/9/8 10/10/9
+f 11/13/12 9/11/10 12/12/11
+f 13/15/14 11/13/12 14/14/13
+f 15/17/16 13/15/14 16/16/15
+f 17/77/18 15/17/16 18/18/17
+f 19/22/20 17/19/18 20/21/19
+f 21/24/22 19/22/20 22/23/21
+f 23/26/24 21/24/22 24/25/23
+f 25/28/26 23/26/24 26/27/25
+f 27/30/28 25/28/26 28/29/27
+f 29/33/30 27/30/28 30/32/29
+f 31/35/32 29/33/30 32/34/31
+f 33/37/34 31/35/32 34/36/33
+f 34/36/33 36/39/36 35/38/35
+f 36/39/36 38/41/38 37/40/37
+f 38/41/38 40/78/40 39/42/39
+f 40/44/40 42/46/42 41/45/41
+f 42/46/42 44/48/44 43/47/43
+f 44/48/44 46/50/46 45/49/45
+f 46/50/46 48/52/48 47/51/47
+f 48/52/48 50/54/50 49/53/49
+f 50/54/50 52/56/52 51/55/51
+f 52/56/52 54/58/54 53/57/53
+f 54/58/54 56/79/56 55/59/55
+f 56/61/56 58/63/58 57/62/57
+f 58/63/58 60/65/60 59/64/59
+f 60/65/60 62/72/64 61/66/61
+f 2/68/2 64/80/63 6/69/5
+f 64/80/63 62/81/64 6/69/5
+f 62/81/64 60/82/60 6/69/5
+f 60/82/60 58/83/58 6/69/5
+f 58/83/58 56/84/56 6/69/5
+f 56/84/56 54/85/54 6/69/5
+f 54/85/54 52/86/52 6/69/5
+f 52/86/52 50/87/50 6/69/5
+f 50/87/50 48/88/48 6/69/5
+f 48/88/48 46/89/46 6/69/5
+f 46/89/46 44/90/44 6/69/5
+f 44/90/44 42/91/42 6/69/5
+f 42/91/42 40/92/40 6/69/5
+f 40/92/40 38/93/38 6/69/5
+f 38/93/38 36/94/36 6/69/5
+f 36/94/36 34/95/33 6/69/5
+f 34/95/33 32/96/31 6/69/5
+f 32/96/31 30/97/29 6/69/5
+f 30/97/29 28/98/27 6/69/5
+f 28/98/27 26/99/25 6/69/5
+f 26/99/25 24/100/23 6/69/5
+f 24/100/23 22/101/21 6/69/5
+f 22/101/21 20/102/19 6/69/5
+f 20/102/19 18/103/17 6/69/5
+f 18/103/17 16/104/15 6/69/5
+f 16/104/15 14/105/13 6/69/5
+f 14/105/13 12/106/11 6/69/5
+f 12/106/11 10/107/9 8/108/7
+f 6/69/5 12/106/11 8/108/7
+f 64/71/63 2/2/2 1/1/1
+f 62/72/64 64/71/63 63/70/62
+f 1/74/1 3/109/4 61/75/61
+f 3/109/4 5/94/6 61/75/61
+f 5/94/6 7/93/8 61/75/61
+f 7/93/8 9/110/10 61/75/61
+f 9/110/10 11/111/12 61/75/61
+f 11/111/12 13/112/14 61/75/61
+f 13/112/14 15/113/16 61/75/61
+f 15/113/16 17/114/18 61/75/61
+f 17/114/18 19/115/20 61/75/61
+f 19/115/20 21/116/22 61/75/61
+f 21/116/22 23/117/24 61/75/61
+f 23/117/24 25/118/26 61/75/61
+f 25/118/26 27/119/28 61/75/61
+f 27/119/28 29/120/30 61/75/61
+f 29/120/30 31/121/32 61/75/61
+f 31/121/32 33/122/34 61/75/61
+f 33/122/34 35/123/35 61/75/61
+f 35/123/35 37/124/37 61/75/61
+f 37/124/37 39/125/39 61/75/61
+f 39/125/39 41/126/41 61/75/61
+f 41/126/41 43/127/43 61/75/61
+f 43/127/43 45/128/45 61/75/61
+f 45/128/45 47/129/47 61/75/61
+f 47/129/47 49/130/49 61/75/61
+f 49/130/49 51/131/51 61/75/61
+f 51/131/51 53/132/53 61/75/61
+f 53/132/53 55/133/55 61/75/61
+f 55/133/55 57/134/57 59/135/59
+f 61/75/61 55/133/55 59/135/59
diff --git a/src/graphs/engine/meshes/cylinderFlat.mesh b/src/graphs/engine/meshes/cylinderFlat.mesh
new file mode 100644
index 0000000..997799e
--- /dev/null
+++ b/src/graphs/engine/meshes/cylinderFlat.mesh
Binary files differ
diff --git a/src/graphs/engine/meshes/cylinderFlat.obj b/src/graphs/engine/meshes/cylinderFlat.obj
new file mode 100644
index 0000000..2b7e3e5
--- /dev/null
+++ b/src/graphs/engine/meshes/cylinderFlat.obj
@@ -0,0 +1,299 @@
+# Blender v2.66 (sub 0) OBJ File: 'cylinder.blend'
+# www.blender.org
+o Cylinder
+v 0.000000 -1.000000 -1.000000
+v 0.000000 1.000000 -1.000000
+v 0.195090 -1.000000 -0.980785
+v 0.195090 1.000000 -0.980785
+v 0.382683 -1.000000 -0.923880
+v 0.382683 1.000000 -0.923880
+v 0.555570 -1.000000 -0.831470
+v 0.555570 1.000000 -0.831470
+v 0.707107 -1.000000 -0.707107
+v 0.707107 1.000000 -0.707107
+v 0.831470 -1.000000 -0.555570
+v 0.831470 1.000000 -0.555570
+v 0.923880 -1.000000 -0.382683
+v 0.923880 1.000000 -0.382683
+v 0.980785 -1.000000 -0.195090
+v 0.980785 1.000000 -0.195090
+v 1.000000 -1.000000 -0.000000
+v 1.000000 1.000000 -0.000000
+v 0.980785 -1.000000 0.195090
+v 0.980785 1.000000 0.195090
+v 0.923880 -1.000000 0.382683
+v 0.923880 1.000000 0.382683
+v 0.831470 -1.000000 0.555570
+v 0.831470 1.000000 0.555570
+v 0.707107 -1.000000 0.707107
+v 0.707107 1.000000 0.707107
+v 0.555570 -1.000000 0.831470
+v 0.555570 1.000000 0.831470
+v 0.382683 -1.000000 0.923880
+v 0.382683 1.000000 0.923880
+v 0.195090 -1.000000 0.980785
+v 0.195090 1.000000 0.980785
+v -0.000000 -1.000000 1.000000
+v -0.000000 1.000000 1.000000
+v -0.195091 -1.000000 0.980785
+v -0.195091 1.000000 0.980785
+v -0.382684 -1.000000 0.923879
+v -0.382684 1.000000 0.923879
+v -0.555571 -1.000000 0.831469
+v -0.555571 1.000000 0.831469
+v -0.707107 -1.000000 0.707106
+v -0.707107 1.000000 0.707106
+v -0.831470 -1.000000 0.555570
+v -0.831470 1.000000 0.555570
+v -0.923880 -1.000000 0.382683
+v -0.923880 1.000000 0.382683
+v -0.980785 -1.000000 0.195089
+v -0.980785 1.000000 0.195089
+v -1.000000 -1.000000 -0.000001
+v -1.000000 1.000000 -0.000001
+v -0.980785 -1.000000 -0.195091
+v -0.980785 1.000000 -0.195091
+v -0.923879 -1.000000 -0.382684
+v -0.923879 1.000000 -0.382684
+v -0.831469 -1.000000 -0.555571
+v -0.831469 1.000000 -0.555571
+v -0.707106 -1.000000 -0.707108
+v -0.707106 1.000000 -0.707108
+v -0.555569 -1.000000 -0.831470
+v -0.555569 1.000000 -0.831470
+v -0.382682 -1.000000 -0.923880
+v -0.382682 1.000000 -0.923880
+v -0.195089 -1.000000 -0.980786
+v -0.195089 1.000000 -0.980786
+vt 0.289718 0.879351
+vt 0.288367 0.438844
+vt 0.330714 0.438714
+vt 0.332066 0.879221
+vt 0.370605 0.438592
+vt 0.371956 0.879099
+vt 0.406505 0.438482
+vt 0.407857 0.878988
+vt 0.437036 0.438388
+vt 0.778904 0.000000
+vt 0.780256 0.440507
+vt 0.749725 0.440601
+vt 0.748373 0.000094
+vt 0.713824 0.440711
+vt 0.712473 0.000204
+vt 0.673934 0.440833
+vt 0.672582 0.000326
+vt 0.631586 0.440963
+vt 0.630235 0.000456
+vt 0.588409 0.441095
+vt 0.587057 0.000588
+vt 0.546061 0.441225
+vt 0.544710 0.000718
+vt 0.506171 0.441348
+vt 0.504819 0.000841
+vt 0.470270 0.441458
+vt 0.468919 0.000951
+vt 0.439739 0.441552
+vt 0.720545 0.882916
+vt 0.719194 0.442409
+vt 0.755094 0.442299
+vt 0.756446 0.882806
+vt 0.794985 0.442176
+vt 0.796336 0.882683
+vt 0.837333 0.442046
+vt 0.838684 0.882553
+vt 0.881861 0.882421
+vt 0.880510 0.441914
+vt 0.924209 0.882291
+vt 0.922857 0.441784
+vt 0.964099 0.882168
+vt 0.962748 0.441662
+vt 1.000000 0.882058
+vt 0.717842 0.441552
+vt 0.719194 0.882058
+vt 0.681942 0.441662
+vt 0.683293 0.882169
+vt 0.642051 0.441784
+vt 0.643403 0.882291
+vt 0.599704 0.441914
+vt 0.601055 0.882421
+vt 0.556526 0.442046
+vt 0.557878 0.882553
+vt 0.514179 0.442176
+vt 0.515530 0.882683
+vt 0.474288 0.442299
+vt 0.475640 0.882806
+vt 0.438388 0.442409
+vt 0.097872 0.879939
+vt 0.096520 0.439433
+vt 0.128403 0.879846
+vt 0.127051 0.439339
+vt 0.164303 0.879735
+vt 0.162952 0.439229
+vt 0.204194 0.879613
+vt 0.000000 0.197605
+vt 0.008423 0.155257
+vt 0.000000 0.240783
+vt 0.246541 0.879483
+vt 0.245190 0.438976
+vt 0.202842 0.439106
+vt 0.438388 0.878895
+vt 0.438388 0.001045
+vt 0.998649 0.441552
+vt 0.439739 0.882916
+vt 0.024947 0.115367
+vt 0.048935 0.079466
+vt 0.079466 0.048935
+vt 0.115366 0.024947
+vt 0.155257 0.008424
+vt 0.197605 0.000000
+vt 0.240782 0.000000
+vt 0.283130 0.008423
+vt 0.323021 0.024947
+vt 0.358922 0.048935
+vt 0.389453 0.079466
+vt 0.413441 0.115367
+vt 0.429964 0.155257
+vt 0.438388 0.197605
+vt 0.438388 0.240783
+vt 0.429964 0.283130
+vt 0.413441 0.323021
+vt 0.389453 0.358922
+vt 0.358922 0.389453
+vt 0.323021 0.413441
+vt 0.283130 0.429964
+vt 0.240783 0.438388
+vt 0.197605 0.438388
+vt 0.155257 0.429964
+vt 0.115367 0.413441
+vt 0.079466 0.389453
+vt 0.048935 0.358922
+vt 0.024947 0.323021
+vt 0.008423 0.283130
+vn 0.098017 0.000000 -0.995185
+vn 0.290285 0.000000 -0.956940
+vn 0.471397 0.000000 -0.881921
+vn 0.634393 0.000000 -0.773010
+vn 0.773010 0.000000 -0.634393
+vn 0.881921 0.000000 -0.471397
+vn 0.956940 0.000000 -0.290285
+vn 0.995185 0.000000 -0.098017
+vn 0.995185 0.000000 0.098017
+vn 0.956940 0.000000 0.290285
+vn 0.881921 0.000000 0.471396
+vn 0.773010 0.000000 0.634393
+vn 0.634393 0.000000 0.773010
+vn 0.471397 0.000000 0.881921
+vn 0.290284 0.000000 0.956940
+vn 0.098017 0.000000 0.995185
+vn -0.098018 0.000000 0.995185
+vn -0.290285 0.000000 0.956940
+vn -0.471397 0.000000 0.881921
+vn -0.634394 0.000000 0.773010
+vn -0.773011 0.000000 0.634393
+vn -0.881922 0.000000 0.471396
+vn -0.956941 0.000000 0.290284
+vn -0.995185 0.000000 0.098016
+vn -0.995185 -0.000000 -0.098018
+vn -0.956940 -0.000000 -0.290286
+vn -0.881921 -0.000000 -0.471398
+vn -0.773010 -0.000000 -0.634394
+vn -0.634392 -0.000000 -0.773011
+vn -0.471395 -0.000000 -0.881922
+vn -0.000000 1.000000 0.000000
+vn -0.098017 -0.000000 -0.995185
+vn -0.290283 -0.000000 -0.956941
+s off
+f 1/1/1 2/2/1 4/3/1
+f 3/4/2 4/3/2 6/5/2
+f 5/6/3 6/5/3 8/7/3
+f 7/8/4 8/7/4 10/9/4
+f 9/10/5 10/11/5 12/12/5
+f 11/13/6 12/12/6 14/14/6
+f 13/15/7 14/14/7 16/16/7
+f 15/17/8 16/16/8 18/18/8
+f 17/19/9 18/18/9 20/20/9
+f 19/21/10 20/20/10 22/22/10
+f 21/23/11 22/22/11 24/24/11
+f 23/25/12 24/24/12 26/26/12
+f 25/27/13 26/26/13 28/28/13
+f 27/29/14 28/30/14 30/31/14
+f 29/32/15 30/31/15 32/33/15
+f 31/34/16 32/33/16 34/35/16
+f 33/36/17 34/35/17 35/37/17
+f 35/37/18 36/38/18 37/39/18
+f 37/39/19 38/40/19 39/41/19
+f 39/41/20 40/42/20 41/43/20
+f 41/44/21 42/45/21 43/46/21
+f 43/46/22 44/47/22 45/48/22
+f 45/48/23 46/49/23 47/50/23
+f 47/50/24 48/51/24 49/52/24
+f 49/52/25 50/53/25 51/54/25
+f 51/54/26 52/55/26 53/56/26
+f 53/56/27 54/57/27 55/58/27
+f 55/59/28 56/60/28 57/61/28
+f 57/61/29 58/62/29 59/63/29
+f 59/63/30 60/64/30 61/65/30
+f 4/66/31 2/67/31 6/68/31
+f 63/69/32 64/70/32 1/1/32
+f 61/65/33 62/71/33 63/69/33
+f 3/4/1 1/1/1 4/3/1
+f 5/6/2 3/4/2 6/5/2
+f 7/8/3 5/6/3 8/7/3
+f 9/72/4 7/8/4 10/9/4
+f 11/13/5 9/10/5 12/12/5
+f 13/15/6 11/13/6 14/14/6
+f 15/17/7 13/15/7 16/16/7
+f 17/19/8 15/17/8 18/18/8
+f 19/21/9 17/19/9 20/20/9
+f 21/23/10 19/21/10 22/22/10
+f 23/25/11 21/23/11 24/24/11
+f 25/27/12 23/25/12 26/26/12
+f 27/73/13 25/27/13 28/28/13
+f 29/32/14 27/29/14 30/31/14
+f 31/34/15 29/32/15 32/33/15
+f 33/36/16 31/34/16 34/35/16
+f 34/35/17 36/38/17 35/37/17
+f 36/38/18 38/40/18 37/39/18
+f 38/40/19 40/42/19 39/41/19
+f 40/42/20 42/74/20 41/43/20
+f 42/45/21 44/47/21 43/46/21
+f 44/47/22 46/49/22 45/48/22
+f 46/49/23 48/51/23 47/50/23
+f 48/51/24 50/53/24 49/52/24
+f 50/53/25 52/55/25 51/54/25
+f 52/55/26 54/57/26 53/56/26
+f 54/57/27 56/75/27 55/58/27
+f 56/60/28 58/62/28 57/61/28
+f 58/62/29 60/64/29 59/63/29
+f 60/64/30 62/71/30 61/65/30
+f 2/67/31 64/76/31 6/68/31
+f 64/76/31 62/77/31 6/68/31
+f 62/77/31 60/78/31 6/68/31
+f 60/78/31 58/79/31 6/68/31
+f 58/79/31 56/80/31 6/68/31
+f 56/80/31 54/81/31 6/68/31
+f 54/81/31 52/82/31 6/68/31
+f 52/82/31 50/83/31 6/68/31
+f 50/83/31 48/84/31 6/68/31
+f 48/84/31 46/85/31 6/68/31
+f 46/85/31 44/86/31 6/68/31
+f 44/86/31 42/87/31 6/68/31
+f 42/87/31 40/88/31 6/68/31
+f 40/88/31 38/89/31 6/68/31
+f 38/89/31 36/90/31 6/68/31
+f 36/90/31 34/91/31 6/68/31
+f 34/91/31 32/92/31 6/68/31
+f 32/92/31 30/93/31 6/68/31
+f 30/93/31 28/94/31 6/68/31
+f 28/94/31 26/95/31 6/68/31
+f 26/95/31 24/96/31 6/68/31
+f 24/96/31 22/97/31 6/68/31
+f 22/97/31 20/98/31 6/68/31
+f 20/98/31 18/99/31 6/68/31
+f 18/99/31 16/100/31 6/68/31
+f 16/100/31 14/101/31 6/68/31
+f 14/101/31 12/102/31 6/68/31
+f 12/102/31 10/103/31 8/104/31
+f 6/68/31 12/102/31 8/104/31
+f 64/70/32 2/2/32 1/1/32
+f 62/71/33 64/70/33 63/69/33
diff --git a/src/graphs/engine/meshes/cylinderSmooth.mesh b/src/graphs/engine/meshes/cylinderSmooth.mesh
new file mode 100644
index 0000000..5da8039
--- /dev/null
+++ b/src/graphs/engine/meshes/cylinderSmooth.mesh
Binary files differ
diff --git a/src/graphs/engine/meshes/cylinderSmooth.obj b/src/graphs/engine/meshes/cylinderSmooth.obj
new file mode 100644
index 0000000..6ccbb28
--- /dev/null
+++ b/src/graphs/engine/meshes/cylinderSmooth.obj
@@ -0,0 +1,330 @@
+# Blender v2.66 (sub 0) OBJ File: 'cylinder.blend'
+# www.blender.org
+o Cylinder
+v 0.000000 -1.000000 -1.000000
+v 0.000000 1.000000 -1.000000
+v 0.195090 -1.000000 -0.980785
+v 0.195090 1.000000 -0.980785
+v 0.382683 -1.000000 -0.923880
+v 0.382683 1.000000 -0.923880
+v 0.555570 -1.000000 -0.831470
+v 0.555570 1.000000 -0.831470
+v 0.707107 -1.000000 -0.707107
+v 0.707107 1.000000 -0.707107
+v 0.831470 -1.000000 -0.555570
+v 0.831470 1.000000 -0.555570
+v 0.923880 -1.000000 -0.382683
+v 0.923880 1.000000 -0.382683
+v 0.980785 -1.000000 -0.195090
+v 0.980785 1.000000 -0.195090
+v 1.000000 -1.000000 -0.000000
+v 1.000000 1.000000 -0.000000
+v 0.980785 -1.000000 0.195090
+v 0.980785 1.000000 0.195090
+v 0.923880 -1.000000 0.382683
+v 0.923880 1.000000 0.382683
+v 0.831470 -1.000000 0.555570
+v 0.831470 1.000000 0.555570
+v 0.707107 -1.000000 0.707107
+v 0.707107 1.000000 0.707107
+v 0.555570 -1.000000 0.831470
+v 0.555570 1.000000 0.831470
+v 0.382683 -1.000000 0.923880
+v 0.382683 1.000000 0.923880
+v 0.195090 -1.000000 0.980785
+v 0.195090 1.000000 0.980785
+v -0.000000 -1.000000 1.000000
+v -0.000000 1.000000 1.000000
+v -0.195091 -1.000000 0.980785
+v -0.195091 1.000000 0.980785
+v -0.382684 -1.000000 0.923879
+v -0.382684 1.000000 0.923879
+v -0.555571 -1.000000 0.831469
+v -0.555571 1.000000 0.831469
+v -0.707107 -1.000000 0.707106
+v -0.707107 1.000000 0.707106
+v -0.831470 -1.000000 0.555570
+v -0.831470 1.000000 0.555570
+v -0.923880 -1.000000 0.382683
+v -0.923880 1.000000 0.382683
+v -0.980785 -1.000000 0.195089
+v -0.980785 1.000000 0.195089
+v -1.000000 -1.000000 -0.000001
+v -1.000000 1.000000 -0.000001
+v -0.980785 -1.000000 -0.195091
+v -0.980785 1.000000 -0.195091
+v -0.923879 -1.000000 -0.382684
+v -0.923879 1.000000 -0.382684
+v -0.831469 -1.000000 -0.555571
+v -0.831469 1.000000 -0.555571
+v -0.707106 -1.000000 -0.707108
+v -0.707106 1.000000 -0.707108
+v -0.555569 -1.000000 -0.831470
+v -0.555569 1.000000 -0.831470
+v -0.382682 -1.000000 -0.923880
+v -0.382682 1.000000 -0.923880
+v -0.195089 -1.000000 -0.980786
+v -0.195089 1.000000 -0.980786
+vt 0.289718 0.879351
+vt 0.288367 0.438844
+vt 0.330714 0.438714
+vt 0.332066 0.879221
+vt 0.370605 0.438592
+vt 0.371956 0.879099
+vt 0.406505 0.438482
+vt 0.407857 0.878988
+vt 0.437036 0.438388
+vt 0.778904 0.000000
+vt 0.780256 0.440507
+vt 0.749725 0.440601
+vt 0.748373 0.000094
+vt 0.713824 0.440711
+vt 0.712473 0.000204
+vt 0.673934 0.440833
+vt 0.672582 0.000326
+vt 0.631586 0.440963
+vt 0.630235 0.000456
+vt 0.588409 0.441095
+vt 0.587057 0.000588
+vt 0.546061 0.441225
+vt 0.544710 0.000718
+vt 0.506171 0.441348
+vt 0.504819 0.000841
+vt 0.470270 0.441458
+vt 0.468919 0.000951
+vt 0.439739 0.441552
+vt 0.720545 0.882916
+vt 0.719194 0.442409
+vt 0.755094 0.442299
+vt 0.756446 0.882806
+vt 0.794985 0.442176
+vt 0.796336 0.882683
+vt 0.837333 0.442046
+vt 0.838684 0.882553
+vt 0.881861 0.882421
+vt 0.880510 0.441914
+vt 0.924209 0.882291
+vt 0.922857 0.441784
+vt 0.964099 0.882168
+vt 0.962748 0.441662
+vt 1.000000 0.882058
+vt 0.717842 0.441552
+vt 0.719194 0.882058
+vt 0.681942 0.441662
+vt 0.683293 0.882169
+vt 0.642051 0.441784
+vt 0.643403 0.882291
+vt 0.599704 0.441914
+vt 0.601055 0.882421
+vt 0.556526 0.442046
+vt 0.557878 0.882553
+vt 0.514179 0.442176
+vt 0.515530 0.882683
+vt 0.474288 0.442299
+vt 0.475640 0.882806
+vt 0.438388 0.442409
+vt 0.097872 0.879939
+vt 0.096520 0.439433
+vt 0.128403 0.879846
+vt 0.127051 0.439339
+vt 0.164303 0.879735
+vt 0.162952 0.439229
+vt 0.204194 0.879613
+vt 0.000000 0.197605
+vt 0.008423 0.155257
+vt 0.000000 0.240783
+vt 0.246541 0.879483
+vt 0.245190 0.438976
+vt 0.202842 0.439106
+vt 0.438388 0.878895
+vt 0.438388 0.001045
+vt 0.998649 0.441552
+vt 0.439739 0.882916
+vt 0.024947 0.115367
+vt 0.048935 0.079466
+vt 0.079466 0.048935
+vt 0.115366 0.024947
+vt 0.155257 0.008424
+vt 0.197605 0.000000
+vt 0.240782 0.000000
+vt 0.283130 0.008423
+vt 0.323021 0.024947
+vt 0.358922 0.048935
+vt 0.389453 0.079466
+vt 0.413441 0.115367
+vt 0.429964 0.155257
+vt 0.438388 0.197605
+vt 0.438388 0.240783
+vt 0.429964 0.283130
+vt 0.413441 0.323021
+vt 0.389453 0.358922
+vt 0.358922 0.389453
+vt 0.323021 0.413441
+vt 0.283130 0.429964
+vt 0.240783 0.438388
+vt 0.197605 0.438388
+vt 0.155257 0.429964
+vt 0.115367 0.413441
+vt 0.079466 0.389453
+vt 0.048935 0.358922
+vt 0.024947 0.323021
+vt 0.008423 0.283130
+vn 0.000000 0.000000 -1.000000
+vn 0.000000 0.685690 -0.727866
+vn 0.142003 0.685690 -0.713889
+vn 0.195074 0.000000 -0.980773
+vn 0.278542 0.685690 -0.672475
+vn 0.382672 0.000000 -0.923856
+vn 0.404370 0.685690 -0.605213
+vn 0.555559 0.000000 -0.831446
+vn 0.514664 0.685690 -0.514664
+vn 0.707083 0.000000 -0.707083
+vn 0.605213 0.685690 -0.404370
+vn 0.831446 0.000000 -0.555559
+vn 0.672475 0.685690 -0.278542
+vn 0.923856 0.000000 -0.382672
+vn 0.713889 0.685690 -0.142003
+vn 0.980773 0.000000 -0.195074
+vn 0.727866 0.685690 0.000000
+vn 1.000000 0.000000 0.000000
+vn 0.713889 0.685690 0.142003
+vn 0.980773 0.000000 0.195074
+vn 0.672475 0.685690 0.278542
+vn 0.923856 0.000000 0.382672
+vn 0.605213 0.685690 0.404370
+vn 0.831446 0.000000 0.555559
+vn 0.514664 0.685690 0.514664
+vn 0.707083 0.000000 0.707083
+vn 0.404370 0.685690 0.605213
+vn 0.555559 0.000000 0.831446
+vn 0.278542 0.685690 0.672475
+vn 0.382672 0.000000 0.923856
+vn 0.142003 0.685690 0.713889
+vn 0.195074 0.000000 0.980773
+vn 0.000000 0.685690 0.727866
+vn 0.000000 0.000000 0.999969
+vn -0.195074 0.000000 0.980773
+vn -0.142003 0.685690 0.713889
+vn -0.382672 0.000000 0.923856
+vn -0.278542 0.685690 0.672475
+vn -0.555559 0.000000 0.831446
+vn -0.404370 0.685690 0.605213
+vn -0.707083 0.000000 0.707083
+vn -0.514664 0.685690 0.514664
+vn -0.831446 0.000000 0.555559
+vn -0.605213 0.685690 0.404370
+vn -0.923856 0.000000 0.382672
+vn -0.672475 0.685690 0.278542
+vn -0.980773 0.000000 0.195074
+vn -0.713889 0.685690 0.142003
+vn -1.000000 0.000000 0.000000
+vn -0.727866 0.685690 0.000000
+vn -0.980773 0.000000 -0.195074
+vn -0.713889 0.685690 -0.142003
+vn -0.923856 0.000000 -0.382672
+vn -0.672475 0.685690 -0.278542
+vn -0.831446 0.000000 -0.555559
+vn -0.605213 0.685690 -0.404370
+vn -0.707083 0.000000 -0.707083
+vn -0.514664 0.685690 -0.514695
+vn -0.555559 0.000000 -0.831446
+vn -0.404370 0.685690 -0.605213
+vn -0.382672 0.000000 -0.923856
+vn -0.195074 0.000000 -0.980773
+vn -0.142003 0.685690 -0.713889
+vn -0.278542 0.685690 -0.672475
+s 1
+f 1/1/1 2/2/2 4/3/3
+f 3/4/4 4/3/3 6/5/5
+f 5/6/6 6/5/5 8/7/7
+f 7/8/8 8/7/7 10/9/9
+f 9/10/10 10/11/9 12/12/11
+f 11/13/12 12/12/11 14/14/13
+f 13/15/14 14/14/13 16/16/15
+f 15/17/16 16/16/15 18/18/17
+f 17/19/18 18/18/17 20/20/19
+f 19/21/20 20/20/19 22/22/21
+f 21/23/22 22/22/21 24/24/23
+f 23/25/24 24/24/23 26/26/25
+f 25/27/26 26/26/25 28/28/27
+f 27/29/28 28/30/27 30/31/29
+f 29/32/30 30/31/29 32/33/31
+f 31/34/32 32/33/31 34/35/33
+f 33/36/34 34/35/33 35/37/35
+f 35/37/35 36/38/36 37/39/37
+f 37/39/37 38/40/38 39/41/39
+f 39/41/39 40/42/40 41/43/41
+f 41/44/41 42/45/42 43/46/43
+f 43/46/43 44/47/44 45/48/45
+f 45/48/45 46/49/46 47/50/47
+f 47/50/47 48/51/48 49/52/49
+f 49/52/49 50/53/50 51/54/51
+f 51/54/51 52/55/52 53/56/53
+f 53/56/53 54/57/54 55/58/55
+f 55/59/55 56/60/56 57/61/57
+f 57/61/57 58/62/58 59/63/59
+f 59/63/59 60/64/60 61/65/61
+f 4/66/3 2/67/2 6/68/5
+f 63/69/62 64/70/63 1/1/1
+f 61/65/61 62/71/64 63/69/62
+f 3/4/4 1/1/1 4/3/3
+f 5/6/6 3/4/4 6/5/5
+f 7/8/8 5/6/6 8/7/7
+f 9/72/10 7/8/8 10/9/9
+f 11/13/12 9/10/10 12/12/11
+f 13/15/14 11/13/12 14/14/13
+f 15/17/16 13/15/14 16/16/15
+f 17/19/18 15/17/16 18/18/17
+f 19/21/20 17/19/18 20/20/19
+f 21/23/22 19/21/20 22/22/21
+f 23/25/24 21/23/22 24/24/23
+f 25/27/26 23/25/24 26/26/25
+f 27/73/28 25/27/26 28/28/27
+f 29/32/30 27/29/28 30/31/29
+f 31/34/32 29/32/30 32/33/31
+f 33/36/34 31/34/32 34/35/33
+f 34/35/33 36/38/36 35/37/35
+f 36/38/36 38/40/38 37/39/37
+f 38/40/38 40/42/40 39/41/39
+f 40/42/40 42/74/42 41/43/41
+f 42/45/42 44/47/44 43/46/43
+f 44/47/44 46/49/46 45/48/45
+f 46/49/46 48/51/48 47/50/47
+f 48/51/48 50/53/50 49/52/49
+f 50/53/50 52/55/52 51/54/51
+f 52/55/52 54/57/54 53/56/53
+f 54/57/54 56/75/56 55/58/55
+f 56/60/56 58/62/58 57/61/57
+f 58/62/58 60/64/60 59/63/59
+f 60/64/60 62/71/64 61/65/61
+f 2/67/2 64/76/63 6/68/5
+f 64/76/63 62/77/64 6/68/5
+f 62/77/64 60/78/60 6/68/5
+f 60/78/60 58/79/58 6/68/5
+f 58/79/58 56/80/56 6/68/5
+f 56/80/56 54/81/54 6/68/5
+f 54/81/54 52/82/52 6/68/5
+f 52/82/52 50/83/50 6/68/5
+f 50/83/50 48/84/48 6/68/5
+f 48/84/48 46/85/46 6/68/5
+f 46/85/46 44/86/44 6/68/5
+f 44/86/44 42/87/42 6/68/5
+f 42/87/42 40/88/40 6/68/5
+f 40/88/40 38/89/38 6/68/5
+f 38/89/38 36/90/36 6/68/5
+f 36/90/36 34/91/33 6/68/5
+f 34/91/33 32/92/31 6/68/5
+f 32/92/31 30/93/29 6/68/5
+f 30/93/29 28/94/27 6/68/5
+f 28/94/27 26/95/25 6/68/5
+f 26/95/25 24/96/23 6/68/5
+f 24/96/23 22/97/21 6/68/5
+f 22/97/21 20/98/19 6/68/5
+f 20/98/19 18/99/17 6/68/5
+f 18/99/17 16/100/15 6/68/5
+f 16/100/15 14/101/13 6/68/5
+f 14/101/13 12/102/11 6/68/5
+f 12/102/11 10/103/9 8/104/7
+f 6/68/5 12/102/11 8/104/7
+f 64/70/63 2/2/2 1/1/1
+f 62/71/64 64/70/63 63/69/62
diff --git a/src/graphs/engine/meshes/minimalFlat.mesh b/src/graphs/engine/meshes/minimalFlat.mesh
new file mode 100644
index 0000000..e327025
--- /dev/null
+++ b/src/graphs/engine/meshes/minimalFlat.mesh
Binary files differ
diff --git a/src/graphs/engine/meshes/minimalFlat.obj b/src/graphs/engine/meshes/minimalFlat.obj
new file mode 100644
index 0000000..9ab60a2
--- /dev/null
+++ b/src/graphs/engine/meshes/minimalFlat.obj
@@ -0,0 +1,27 @@
+# Blender v2.66 (sub 0) OBJ File: 'scatterdot.blend'
+# www.blender.org
+o Cone
+v 0.000000 -1.000000 -1.414000
+v 1.224560 -1.000000 0.707000
+v 0.000000 1.000000 0.000000
+v -1.224560 -1.000000 0.707000
+vt 0.499966 0.866025
+vt 0.000000 1.153999
+vt 0.000590 0.577029
+vt 0.999376 0.000000
+vt 1.000000 0.577029
+vt 0.500000 0.288996
+vt 1.000000 0.866025
+vt 0.500000 1.154058
+vt 0.500624 0.577029
+vt 0.000000 0.577029
+vt 0.000624 0.000000
+vn -0.000000 -1.000000 0.000000
+vn 0.816510 0.333289 -0.471412
+vn -0.000000 0.333289 0.942825
+vn -0.816510 0.333289 -0.471413
+s off
+f 4/1/1 1/2/1 2/3/1
+f 1/4/2 3/5/2 2/6/2
+f 2/7/3 3/8/3 4/9/3
+f 4/6/4 3/10/4 1/11/4
diff --git a/src/graphs/engine/meshes/minimalSmooth.mesh b/src/graphs/engine/meshes/minimalSmooth.mesh
new file mode 100644
index 0000000..3ada3ac
--- /dev/null
+++ b/src/graphs/engine/meshes/minimalSmooth.mesh
Binary files differ
diff --git a/src/graphs/engine/meshes/minimalSmooth.obj b/src/graphs/engine/meshes/minimalSmooth.obj
new file mode 100644
index 0000000..dd2e77d
--- /dev/null
+++ b/src/graphs/engine/meshes/minimalSmooth.obj
@@ -0,0 +1,27 @@
+# Blender v2.66 (sub 0) OBJ File: 'scatterdot.blend'
+# www.blender.org
+o Cone
+v 0.000000 -1.000000 -1.414000
+v 1.224560 -1.000000 0.707000
+v 0.000000 1.000000 0.000000
+v -1.224560 -1.000000 0.707000
+vt 0.499966 0.866025
+vt 0.000000 1.153999
+vt 0.000590 0.577029
+vt 0.999376 0.000000
+vt 1.000000 0.577029
+vt 0.500000 0.288996
+vt 1.000000 0.866025
+vt 0.500000 1.154058
+vt 0.500624 0.577029
+vt 0.000000 0.577029
+vt 0.000624 0.000000
+vn -0.816462 -0.333354 0.471389
+vn 0.000000 -0.333354 -0.942778
+vn 0.816462 -0.333354 0.471389
+vn 0.000000 1.000000 0.000000
+s 1
+f 4/1/1 1/2/2 2/3/3
+f 1/4/2 3/5/4 2/6/3
+f 2/7/3 3/8/4 4/9/1
+f 4/6/1 3/10/4 1/11/2
diff --git a/src/graphs/engine/meshes/plane.mesh b/src/graphs/engine/meshes/plane.mesh
new file mode 100644
index 0000000..8805be2
--- /dev/null
+++ b/src/graphs/engine/meshes/plane.mesh
Binary files differ
diff --git a/src/graphs/engine/meshes/plane.obj b/src/graphs/engine/meshes/plane.obj
new file mode 100644
index 0000000..a307f4d
--- /dev/null
+++ b/src/graphs/engine/meshes/plane.obj
@@ -0,0 +1,15 @@
+# Blender v2.66 (sub 0) OBJ File: 'plane.blend'
+# www.blender.org
+o Plane
+v -1.000000 -1.000000 -0.000000
+v -1.000000 1.000000 -0.000000
+v 1.000000 -1.000000 0.000000
+v 1.000000 1.000000 0.000000
+vt 0.000000 1.000000
+vt 0.000000 0.000000
+vt 1.000000 1.000000
+vt 1.000000 0.000000
+vn 0.000000 0.000000 1.000000
+s off
+f 2/1/1 1/2/1 4/3/1
+f 1/2/1 3/4/1 4/3/1
diff --git a/src/graphs/engine/meshes/pyramidFilledFlat.mesh b/src/graphs/engine/meshes/pyramidFilledFlat.mesh
new file mode 100644
index 0000000..86a0a65
--- /dev/null
+++ b/src/graphs/engine/meshes/pyramidFilledFlat.mesh
Binary files differ
diff --git a/src/graphs/engine/meshes/pyramidFilledFlat.obj b/src/graphs/engine/meshes/pyramidFilledFlat.obj
new file mode 100644
index 0000000..0cf73bb
--- /dev/null
+++ b/src/graphs/engine/meshes/pyramidFilledFlat.obj
@@ -0,0 +1,36 @@
+# Blender v2.66 (sub 0) OBJ File: 'pyramid_filled.blend'
+# www.blender.org
+o Cone_Cone.001
+v 1.000000 -1.000000 -0.999999
+v 0.999999 -1.000000 1.000000
+v -1.000000 -1.000000 0.999999
+v 0.000000 1.000000 0.000000
+v -0.999999 -1.000000 -1.000000
+vt 0.323067 0.577790
+vt 0.000097 0.866793
+vt 0.000000 0.433396
+vt 0.646133 0.144393
+vt 0.323164 0.433396
+vt 0.323067 0.000000
+vt 0.646133 0.577790
+vt 0.323163 0.866793
+vt 0.323067 0.433396
+vt 0.323067 0.144393
+vt 0.000097 0.433396
+vt 0.000000 0.000000
+vt 0.646133 0.787263
+vt 0.646133 0.433396
+vt 1.000000 0.433396
+vt 1.000000 0.787263
+vn -0.894427 0.447214 -0.000000
+vn 0.894427 0.447213 0.000000
+vn 0.000000 0.447214 -0.894427
+vn -0.000000 0.447214 0.894427
+vn 0.000000 -1.000000 -0.000000
+s off
+f 3/1/1 4/2/1 5/3/1
+f 1/4/2 4/5/2 2/6/2
+f 5/7/3 4/8/3 1/9/3
+f 2/10/4 4/11/4 3/12/4
+f 5/13/5 1/14/5 2/15/5
+f 3/16/5 5/13/5 2/15/5
diff --git a/src/graphs/engine/meshes/pyramidFilledSmooth.mesh b/src/graphs/engine/meshes/pyramidFilledSmooth.mesh
new file mode 100644
index 0000000..1b8a1b7
--- /dev/null
+++ b/src/graphs/engine/meshes/pyramidFilledSmooth.mesh
Binary files differ
diff --git a/src/graphs/engine/meshes/pyramidFilledSmooth.obj b/src/graphs/engine/meshes/pyramidFilledSmooth.obj
new file mode 100644
index 0000000..306bda5
--- /dev/null
+++ b/src/graphs/engine/meshes/pyramidFilledSmooth.obj
@@ -0,0 +1,36 @@
+# Blender v2.66 (sub 0) OBJ File: 'pyramid_filled.blend'
+# www.blender.org
+o Cone_Cone.001
+v 1.000000 -1.000000 -0.999999
+v 0.999999 -1.000000 1.000000
+v -1.000000 -1.000000 0.999999
+v 0.000000 1.000000 0.000000
+v -0.999999 -1.000000 -1.000000
+vt 0.323067 0.577790
+vt 0.000097 0.866793
+vt 0.000000 0.433396
+vt 0.646133 0.144393
+vt 0.323164 0.433396
+vt 0.323067 0.000000
+vt 0.646133 0.577790
+vt 0.323163 0.866793
+vt 0.323067 0.433396
+vt 0.323067 0.144393
+vt 0.000097 0.433396
+vt 0.000000 0.000000
+vt 0.646133 0.787263
+vt 0.646133 0.433396
+vt 1.000000 0.433396
+vt 1.000000 0.787263
+vn -0.662618 -0.349040 0.662618
+vn 0.000000 1.000000 0.000000
+vn -0.662618 -0.349040 -0.662618
+vn 0.662618 -0.349040 -0.662618
+vn 0.662618 -0.349040 0.662618
+s 1
+f 3/1/1 4/2/2 5/3/3
+f 1/4/4 4/5/2 2/6/5
+f 5/7/3 4/8/2 1/9/4
+f 2/10/5 4/11/2 3/12/1
+f 5/13/3 1/14/4 2/15/5
+f 3/16/1 5/13/3 2/15/5
diff --git a/src/graphs/engine/meshes/pyramidFlat.mesh b/src/graphs/engine/meshes/pyramidFlat.mesh
new file mode 100644
index 0000000..7f2c396
--- /dev/null
+++ b/src/graphs/engine/meshes/pyramidFlat.mesh
Binary files differ
diff --git a/src/graphs/engine/meshes/pyramidFlat.obj b/src/graphs/engine/meshes/pyramidFlat.obj
new file mode 100644
index 0000000..35edb47
--- /dev/null
+++ b/src/graphs/engine/meshes/pyramidFlat.obj
@@ -0,0 +1,22 @@
+# Blender v2.66 (sub 0) OBJ File: 'pyramid.blend'
+# www.blender.org
+o Cone_Cone.001
+v 1.000000 -1.000000 -0.999999
+v 0.999999 -1.000000 1.000000
+v -1.000000 -1.000000 0.999999
+v 0.000000 1.000000 0.000000
+v -0.999999 -1.000000 -1.000000
+vt 0.999900 0.000100
+vt 0.500000 0.500000
+vt 0.000100 0.000100
+vt 0.000100 0.999900
+vt 0.999900 0.999900
+vn -0.894427 0.447214 -0.000000
+vn 0.894427 0.447213 0.000000
+vn 0.000000 0.447214 -0.894427
+vn -0.000000 0.447214 0.894427
+s off
+f 3/1/1 4/2/1 5/3/1
+f 1/4/2 4/2/2 2/5/2
+f 5/3/3 4/2/3 1/4/3
+f 2/5/4 4/2/4 3/1/4
diff --git a/src/graphs/engine/meshes/pyramidSmooth.mesh b/src/graphs/engine/meshes/pyramidSmooth.mesh
new file mode 100644
index 0000000..a741298
--- /dev/null
+++ b/src/graphs/engine/meshes/pyramidSmooth.mesh
Binary files differ
diff --git a/src/graphs/engine/meshes/pyramidSmooth.obj b/src/graphs/engine/meshes/pyramidSmooth.obj
new file mode 100644
index 0000000..b11c875
--- /dev/null
+++ b/src/graphs/engine/meshes/pyramidSmooth.obj
@@ -0,0 +1,23 @@
+# Blender v2.66 (sub 0) OBJ File: 'pyramid.blend'
+# www.blender.org
+o Cone_Cone.001
+v 1.000000 -1.000000 -0.999999
+v 0.999999 -1.000000 1.000000
+v -1.000000 -1.000000 0.999999
+v 0.000000 1.000000 0.000000
+v -0.999999 -1.000000 -1.000000
+vt 0.999900 0.000100
+vt 0.500000 0.500000
+vt 0.000100 0.000100
+vt 0.000100 0.999900
+vt 0.999900 0.999900
+vn -0.577349 0.577349 0.577349
+vn 0.000000 1.000000 0.000000
+vn -0.577349 0.577349 -0.577349
+vn 0.577349 0.577349 -0.577349
+vn 0.577349 0.577349 0.577349
+s 1
+f 3/1/1 4/2/2 5/3/3
+f 1/4/4 4/2/2 2/5/5
+f 5/3/3 4/2/2 1/4/4
+f 2/5/5 4/2/2 3/1/1
diff --git a/src/graphs/engine/meshes/sphere.mesh b/src/graphs/engine/meshes/sphere.mesh
new file mode 100644
index 0000000..2dbfefa
--- /dev/null
+++ b/src/graphs/engine/meshes/sphere.mesh
Binary files differ
diff --git a/src/graphs/engine/meshes/sphere.obj b/src/graphs/engine/meshes/sphere.obj
new file mode 100644
index 0000000..671a7bc
--- /dev/null
+++ b/src/graphs/engine/meshes/sphere.obj
@@ -0,0 +1,1301 @@
+# Blender v2.66 (sub 0) OBJ File: 'sphere.blend'
+# www.blender.org
+o Sphere
+v -0.195090 0.980785 0.000000
+v -0.382683 0.923880 0.000000
+v -0.555570 0.831470 0.000000
+v -0.707107 0.707107 0.000000
+v -0.831470 0.555570 0.000000
+v -0.923880 0.382683 0.000000
+v -0.980785 0.195090 0.000000
+v -1.000000 0.000000 0.000000
+v -0.980785 -0.195090 0.000000
+v -0.923880 -0.382683 0.000000
+v -0.831470 -0.555570 0.000000
+v -0.707107 -0.707107 0.000000
+v -0.555570 -0.831470 0.000000
+v -0.382683 -0.923880 0.000000
+v -0.195090 -0.980785 0.000000
+v -0.180240 0.980785 -0.074658
+v -0.353553 0.923880 -0.146447
+v -0.513280 0.831470 -0.212608
+v -0.653281 0.707107 -0.270598
+v -0.768178 0.555570 -0.318190
+v -0.853553 0.382683 -0.353553
+v -0.906127 0.195090 -0.375330
+v -0.923880 0.000000 -0.382684
+v -0.906127 -0.195090 -0.375330
+v -0.853553 -0.382683 -0.353554
+v -0.768178 -0.555570 -0.318190
+v -0.653281 -0.707107 -0.270598
+v -0.513280 -0.831470 -0.212608
+v -0.353553 -0.923880 -0.146447
+v -0.180240 -0.980785 -0.074658
+v 0.000000 -1.000000 0.000000
+v -0.137950 0.980785 -0.137950
+v -0.270598 0.923880 -0.270598
+v -0.392847 0.831470 -0.392848
+v -0.500000 0.707107 -0.500000
+v -0.587938 0.555570 -0.587938
+v -0.653281 0.382683 -0.653282
+v -0.693520 0.195090 -0.693520
+v -0.707107 0.000000 -0.707107
+v -0.693520 -0.195090 -0.693520
+v -0.653281 -0.382683 -0.653282
+v -0.587938 -0.555570 -0.587938
+v -0.500000 -0.707107 -0.500000
+v -0.392847 -0.831470 -0.392848
+v -0.270598 -0.923880 -0.270598
+v -0.137949 -0.980785 -0.137950
+v -0.074658 0.980785 -0.180240
+v -0.146446 0.923880 -0.353554
+v -0.212607 0.831470 -0.513280
+v -0.270598 0.707107 -0.653282
+v -0.318189 0.555570 -0.768178
+v -0.353553 0.382683 -0.853554
+v -0.375330 0.195090 -0.906128
+v -0.382683 0.000000 -0.923880
+v -0.375330 -0.195090 -0.906128
+v -0.353553 -0.382683 -0.853554
+v -0.318189 -0.555570 -0.768178
+v -0.270598 -0.707107 -0.653282
+v -0.212607 -0.831470 -0.513280
+v -0.146446 -0.923880 -0.353554
+v -0.074658 -0.980785 -0.180240
+v 0.000000 0.980785 -0.195091
+v 0.000000 0.923880 -0.382684
+v 0.000000 0.831470 -0.555570
+v 0.000000 0.707107 -0.707107
+v 0.000000 0.555570 -0.831470
+v 0.000000 0.382683 -0.923880
+v 0.000000 0.195090 -0.980785
+v 0.000000 0.000000 -1.000000
+v 0.000000 -0.195090 -0.980785
+v 0.000000 -0.382683 -0.923880
+v 0.000000 -0.555570 -0.831470
+v 0.000000 -0.707107 -0.707107
+v 0.000000 -0.831470 -0.555570
+v 0.000000 -0.923880 -0.382684
+v 0.000000 -0.980785 -0.195090
+v 0.074658 0.980785 -0.180240
+v 0.146447 0.923880 -0.353554
+v 0.212608 0.831470 -0.513280
+v 0.270598 0.707107 -0.653282
+v 0.318190 0.555570 -0.768178
+v 0.353554 0.382683 -0.853553
+v 0.375331 0.195090 -0.906128
+v 0.382684 0.000000 -0.923880
+v 0.375331 -0.195090 -0.906128
+v 0.353554 -0.382683 -0.853553
+v 0.318190 -0.555570 -0.768178
+v 0.270598 -0.707107 -0.653282
+v 0.212608 -0.831470 -0.513280
+v 0.146447 -0.923880 -0.353554
+v 0.074658 -0.980785 -0.180240
+v 0.137950 0.980785 -0.137950
+v 0.270599 0.923880 -0.270598
+v 0.392848 0.831470 -0.392848
+v 0.500000 0.707107 -0.500000
+v 0.587938 0.555570 -0.587938
+v 0.653282 0.382683 -0.653282
+v 0.693520 0.195090 -0.693520
+v 0.707107 0.000000 -0.707107
+v 0.693520 -0.195090 -0.693520
+v 0.653282 -0.382683 -0.653281
+v 0.587938 -0.555570 -0.587938
+v 0.500000 -0.707107 -0.500000
+v 0.392848 -0.831470 -0.392848
+v 0.270599 -0.923880 -0.270598
+v 0.137950 -0.980785 -0.137950
+v 0.180241 0.980785 -0.074658
+v 0.353554 0.923880 -0.146447
+v 0.513280 0.831470 -0.212608
+v 0.653282 0.707107 -0.270598
+v 0.768178 0.555570 -0.318190
+v 0.853554 0.382683 -0.353553
+v 0.906128 0.195090 -0.375330
+v 0.923880 0.000000 -0.382683
+v 0.906128 -0.195090 -0.375330
+v 0.853554 -0.382683 -0.353553
+v 0.768178 -0.555570 -0.318190
+v 0.653282 -0.707107 -0.270598
+v 0.513280 -0.831470 -0.212608
+v 0.353554 -0.923880 -0.146447
+v 0.180240 -0.980785 -0.074658
+v 0.195091 0.980785 0.000000
+v 0.382684 0.923880 0.000000
+v 0.555571 0.831470 0.000000
+v 0.707107 0.707107 0.000000
+v 0.831470 0.555570 0.000000
+v 0.923880 0.382683 0.000000
+v 0.980786 0.195090 0.000000
+v 1.000000 0.000000 0.000000
+v 0.980786 -0.195090 0.000000
+v 0.923880 -0.382683 0.000000
+v 0.831470 -0.555570 0.000000
+v 0.707107 -0.707107 0.000000
+v 0.555571 -0.831470 0.000000
+v 0.382684 -0.923880 0.000000
+v 0.195091 -0.980785 0.000000
+v 0.180241 0.980785 0.074658
+v 0.353554 0.923880 0.146447
+v 0.513280 0.831470 0.212608
+v 0.653282 0.707107 0.270598
+v 0.768178 0.555570 0.318190
+v 0.853554 0.382683 0.353553
+v 0.906128 0.195090 0.375330
+v 0.923880 0.000000 0.382684
+v 0.906128 -0.195090 0.375330
+v 0.853554 -0.382683 0.353553
+v 0.768178 -0.555570 0.318190
+v 0.653282 -0.707107 0.270598
+v 0.513280 -0.831470 0.212608
+v 0.353554 -0.923880 0.146447
+v 0.180240 -0.980785 0.074658
+v 0.137950 0.980785 0.137950
+v 0.270599 0.923880 0.270598
+v 0.392848 0.831470 0.392848
+v 0.500000 0.707107 0.500000
+v 0.587938 0.555570 0.587938
+v 0.653282 0.382683 0.653282
+v 0.693520 0.195090 0.693520
+v 0.707107 0.000000 0.707107
+v 0.693520 -0.195090 0.693520
+v 0.653282 -0.382683 0.653282
+v 0.587938 -0.555570 0.587938
+v 0.500000 -0.707107 0.500000
+v 0.392848 -0.831470 0.392848
+v 0.270599 -0.923880 0.270598
+v 0.137950 -0.980785 0.137950
+v 0.074658 0.980785 0.180240
+v 0.146447 0.923880 0.353554
+v 0.212608 0.831470 0.513280
+v 0.270598 0.707107 0.653282
+v 0.318190 0.555570 0.768178
+v 0.353554 0.382683 0.853553
+v 0.375331 0.195090 0.906128
+v 0.382684 0.000000 0.923880
+v 0.375331 -0.195090 0.906128
+v 0.353554 -0.382683 0.853553
+v 0.318190 -0.555570 0.768178
+v 0.270598 -0.707107 0.653282
+v 0.212608 -0.831470 0.513280
+v 0.146447 -0.923880 0.353554
+v 0.074658 -0.980785 0.180240
+v 0.000000 0.980785 0.195091
+v 0.000000 0.923880 0.382684
+v 0.000000 0.831470 0.555570
+v 0.000000 0.707107 0.707107
+v 0.000000 0.555570 0.831470
+v 0.000000 0.382683 0.923880
+v 0.000000 0.195090 0.980785
+v 0.000000 0.000000 1.000000
+v 0.000000 -0.195090 0.980785
+v 0.000000 -0.382683 0.923879
+v 0.000000 -0.555570 0.831470
+v 0.000000 -0.707107 0.707107
+v 0.000000 -0.831470 0.555570
+v 0.000000 -0.923880 0.382684
+v 0.000000 -0.980785 0.195090
+v -0.074658 0.980785 0.180240
+v -0.146446 0.923880 0.353554
+v -0.212607 0.831470 0.513280
+v -0.270598 0.707107 0.653282
+v -0.318189 0.555570 0.768178
+v -0.353553 0.382683 0.853553
+v -0.375330 0.195090 0.906128
+v -0.382683 0.000000 0.923880
+v -0.375330 -0.195090 0.906128
+v -0.353553 -0.382683 0.853553
+v -0.318189 -0.555570 0.768178
+v -0.270598 -0.707107 0.653282
+v -0.212607 -0.831470 0.513280
+v -0.146446 -0.923880 0.353554
+v -0.074658 -0.980785 0.180240
+v 0.000000 1.000000 0.000000
+v -0.137950 0.980785 0.137950
+v -0.270598 0.923880 0.270598
+v -0.392847 0.831470 0.392848
+v -0.500000 0.707107 0.500000
+v -0.587938 0.555570 0.587938
+v -0.653281 0.382683 0.653281
+v -0.693520 0.195090 0.693520
+v -0.707107 0.000000 0.707107
+v -0.693520 -0.195090 0.693520
+v -0.653281 -0.382683 0.653281
+v -0.587938 -0.555570 0.587938
+v -0.500000 -0.707107 0.500000
+v -0.392847 -0.831470 0.392848
+v -0.270598 -0.923880 0.270598
+v -0.137949 -0.980785 0.137950
+v -0.180240 0.980785 0.074658
+v -0.353553 0.923880 0.146447
+v -0.513280 0.831470 0.212608
+v -0.653281 0.707107 0.270598
+v -0.768177 0.555570 0.318190
+v -0.853553 0.382683 0.353553
+v -0.906127 0.195090 0.375330
+v -0.923879 0.000000 0.382683
+v -0.906127 -0.195090 0.375330
+v -0.853553 -0.382683 0.353553
+v -0.768177 -0.555570 0.318190
+v -0.653281 -0.707107 0.270598
+v -0.513280 -0.831470 0.212608
+v -0.353553 -0.923880 0.146447
+v -0.180240 -0.980785 0.074658
+vt 0.040867 0.325557
+vt 0.048386 0.386583
+vt 0.001015 0.334499
+vt 0.081872 0.692529
+vt 0.092315 0.752973
+vt 0.006404 0.709363
+vt 1.031336 0.264931
+vt 1.040867 0.325557
+vt 0.999822 0.272029
+vt 0.073887 0.631595
+vt 0.005301 0.646891
+vt 0.333915 0.937286
+vt 0.448527 0.905466
+vt 0.447682 0.976645
+vt 1.017784 0.205113
+vt 0.998129 0.209571
+vt 0.067156 0.570413
+vt 0.004397 0.584414
+vt 0.196016 0.918498
+vt 0.034176 0.958662
+vt 0.060989 0.509119
+vt 0.003585 0.521934
+vt 0.135028 0.869370
+vt 0.015221 0.896641
+vt 0.054891 0.447811
+vt 0.002796 0.459454
+vt 0.107782 0.812401
+vt 0.010297 0.834265
+vt 0.001965 0.396975
+vt 0.007907 0.771825
+vt 0.978319 0.207096
+vt 1.004397 0.584414
+vt 1.005301 0.646891
+vt 0.941200 0.576589
+vt 1.034176 0.958662
+vt 0.823807 0.933258
+vt 1.003585 0.521934
+vt 0.945911 0.514786
+vt 1.015221 0.896641
+vt 0.885287 0.880701
+vt 1.002796 0.459454
+vt 0.950535 0.452971
+vt 1.010297 0.834265
+vt 0.908873 0.821779
+vt 1.001965 0.396975
+vt 0.955439 0.391196
+vt 1.007907 0.771825
+vt 0.921503 0.761167
+vt 1.001015 0.334499
+vt 0.961079 0.329532
+vt 1.006404 0.709363
+vt 0.929787 0.699898
+vt 0.968202 0.268089
+vt 0.936013 0.638321
+vt 0.488610 0.915189
+vt 0.647808 0.950687
+vt 0.921611 0.311033
+vt 0.859136 0.666985
+vt 0.937209 0.253354
+vt 0.870446 0.607941
+vt 0.532175 0.909643
+vt 0.657607 0.903302
+vt 0.959449 0.197860
+vt 0.880347 0.548455
+vt 0.745300 0.879338
+vt 0.889690 0.488794
+vt 0.796385 0.834663
+vt 0.899158 0.429173
+vt 0.825891 0.781668
+vt 0.909472 0.369816
+vt 0.845097 0.725165
+vt 0.848920 0.390841
+vt 0.761226 0.728711
+vt 0.864196 0.334814
+vt 0.780693 0.674598
+vt 0.882867 0.280332
+vt 0.796418 0.618773
+vt 0.907539 0.228698
+vt 0.810089 0.562031
+vt 0.558962 0.891763
+vt 0.641452 0.857179
+vt 0.942759 0.182515
+vt 0.822818 0.504873
+vt 0.697492 0.824480
+vt 0.835477 0.447684
+vt 0.735147 0.779653
+vt 0.879896 0.195479
+vt 0.753309 0.508011
+vt 0.618082 0.815427
+vt 0.767315 0.452094
+vt 0.657173 0.773912
+vt 0.782092 0.396562
+vt 0.685241 0.725518
+vt 0.798714 0.341964
+vt 0.706593 0.673391
+vt 0.818709 0.289116
+vt 0.723996 0.619193
+vt 0.844487 0.239416
+vt 0.739178 0.563865
+vt 0.567268 0.868303
+vt 0.563300 0.844314
+vt 0.770454 0.236269
+vt 0.670392 0.566231
+vt 0.805029 0.190637
+vt 0.683774 0.509644
+vt 0.551952 0.822879
+vt 0.929896 0.162212
+vt 0.855308 0.155368
+vt 0.697011 0.452991
+vt 0.591182 0.780044
+vt 0.710973 0.396675
+vt 0.618699 0.730484
+vt 0.726705 0.341193
+vt 0.639317 0.677374
+vt 0.745717 0.287291
+vt 0.655961 0.622327
+vt 0.665037 0.288362
+vt 0.594131 0.638967
+vt 0.684737 0.232805
+vt 0.605914 0.580518
+vt 0.713639 0.180821
+vt 0.616615 0.521685
+vt 0.760428 0.136665
+vt 0.627046 0.462757
+vt 0.536166 0.805967
+vt 0.923407 0.138703
+vt 0.836350 0.110271
+vt 0.637946 0.403995
+vt 0.562017 0.752770
+vt 0.650183 0.345708
+vt 0.580132 0.696622
+vt 0.517784 0.794886
+vt 0.927547 0.114679
+vt 0.833915 0.062714
+vt 0.573887 0.368405
+vt 0.531336 0.735069
+vt 0.581872 0.307471
+vt 0.540867 0.674443
+vt 0.592315 0.247027
+vt 0.548386 0.613417
+vt 0.607782 0.187599
+vt 0.554891 0.552189
+vt 0.635028 0.130630
+vt 0.560989 0.490881
+vt 0.696016 0.081502
+vt 0.567156 0.429587
+vt 0.502796 0.540546
+vt 0.515221 0.103359
+vt 0.503585 0.478066
+vt 0.534176 0.041338
+vt 0.504397 0.415586
+vt 0.498129 0.790428
+vt 0.948527 0.094534
+vt 0.947682 0.023355
+vt 0.505301 0.353109
+vt 0.499822 0.727971
+vt 0.506404 0.290637
+vt 0.501015 0.665501
+vt 0.507907 0.228175
+vt 0.501965 0.603025
+vt 0.510297 0.165735
+vt 0.429787 0.300102
+vt 0.461079 0.670468
+vt 0.421503 0.238833
+vt 0.455439 0.608804
+vt 0.408873 0.178221
+vt 0.445911 0.485214
+vt 0.385287 0.119299
+vt 1.323807 0.066742
+vt 0.441200 0.423411
+vt 0.478319 0.792904
+vt 0.988610 0.084811
+vt 1.147808 0.049313
+vt 0.436013 0.361679
+vt 0.468202 0.731911
+vt 0.147808 0.049313
+vt 0.323807 0.066742
+vt 0.245300 0.120661
+vt 0.380347 0.451545
+vt 0.459449 0.802140
+vt 0.032175 0.090357
+vt 0.157607 0.096698
+vt 0.370446 0.392059
+vt 0.437209 0.746646
+vt 0.359136 0.333015
+vt 0.421611 0.688967
+vt 0.345097 0.274835
+vt 0.450536 0.547029
+vt 0.409472 0.630184
+vt 0.325892 0.218332
+vt 0.399158 0.570827
+vt 0.296385 0.165337
+vt 0.389690 0.511206
+vt 0.261226 0.271289
+vt 0.348920 0.609159
+vt 0.235147 0.220347
+vt 0.335478 0.552316
+vt 0.197492 0.175520
+vt 0.322818 0.495127
+vt 0.442759 0.817485
+vt 0.058962 0.108237
+vt 0.141452 0.142821
+vt 0.310089 0.437969
+vt 0.407539 0.771302
+vt 0.296418 0.381227
+vt 0.382867 0.719668
+vt 0.280693 0.325402
+vt 0.364196 0.665186
+vt 0.223996 0.380807
+vt 0.318709 0.710884
+vt 0.206593 0.326609
+vt 0.298714 0.658036
+vt 0.185241 0.274482
+vt 0.282092 0.603438
+vt 0.157173 0.226088
+vt 0.267315 0.547906
+vt 0.118082 0.184573
+vt 0.253309 0.491989
+vt 0.379896 0.804521
+vt 0.067268 0.131697
+vt 0.063300 0.155685
+vt 0.239178 0.436135
+vt 0.344487 0.760584
+vt 0.091182 0.219955
+vt 0.197011 0.547009
+vt 0.429896 0.837788
+vt 0.355308 0.844632
+vt 0.051952 0.177121
+vt 0.183774 0.490356
+vt 0.305029 0.809363
+vt 0.170392 0.433769
+vt 0.270454 0.763731
+vt 0.155961 0.377673
+vt 0.245717 0.712709
+vt 0.226706 0.658807
+vt 0.118699 0.269516
+vt 0.210973 0.603325
+vt 0.139317 0.322626
+vt 0.094131 0.361033
+vt 0.165037 0.711638
+vt 0.080132 0.303378
+vt 0.150183 0.654292
+vt 0.062017 0.247229
+vt 0.137946 0.596005
+vt 0.423407 0.861296
+vt 0.336350 0.889729
+vt 0.036166 0.194032
+vt 0.127046 0.537243
+vt 0.260428 0.863335
+vt 0.116615 0.478315
+vt 0.213639 0.819179
+vt 0.105914 0.419482
+vt 0.184737 0.767195
+vt 0.495265 0.852856
+vt 0.995265 0.147143
+vt 1.032175 0.090357
+vt 1.058962 0.108237
+vt 1.067268 0.131697
+vt 1.063300 0.155685
+vt 1.051952 0.177121
+vt 0.427547 0.885321
+vt 1.036166 0.194032
+vt 0.031336 0.264931
+vt 0.017784 0.205113
+vn -0.629402 -0.766928 -0.125196
+vn -0.940062 0.285165 -0.186990
+vn -0.469338 -0.878070 -0.093357
+vn -0.976241 0.096152 -0.194186
+vn -0.289802 0.955349 -0.057645
+vn -0.289802 -0.955349 -0.057645
+vn -0.976241 -0.096151 -0.194186
+vn -0.469338 0.878070 -0.093357
+vn -0.940062 -0.285165 -0.186990
+vn -0.629402 0.766928 -0.125196
+vn -0.868657 -0.464306 -0.172787
+vn -0.764031 0.627024 -0.151975
+vn -0.764031 -0.627024 -0.151975
+vn -0.868657 0.464306 -0.172787
+vn -0.245682 -0.955349 -0.164159
+vn -0.827617 -0.096152 -0.552996
+vn -0.397886 0.878069 -0.265859
+vn -0.796946 -0.285165 -0.532502
+vn -0.533581 0.766929 -0.356527
+vn -0.736412 -0.464306 -0.492055
+vn -0.647715 0.627024 -0.432789
+vn -0.647714 -0.627024 -0.432789
+vn -0.736412 0.464306 -0.492055
+vn -0.533581 -0.766928 -0.356528
+vn -0.796946 0.285165 -0.532502
+vn -0.397886 -0.878070 -0.265859
+vn -0.827617 0.096152 -0.552996
+vn -0.245682 0.955349 -0.164160
+vn -0.356527 -0.766928 -0.533581
+vn -0.532502 0.285165 -0.796946
+vn -0.265859 -0.878070 -0.397886
+vn -0.552996 0.096152 -0.827617
+vn -0.164160 0.955349 -0.245682
+vn -0.164159 -0.955349 -0.245682
+vn -0.552996 -0.096152 -0.827617
+vn -0.265859 0.878069 -0.397886
+vn -0.532502 -0.285165 -0.796946
+vn -0.356527 0.766929 -0.533581
+vn -0.492054 -0.464306 -0.736412
+vn -0.432789 0.627024 -0.647715
+vn -0.432789 -0.627024 -0.647715
+vn -0.492054 0.464306 -0.736412
+vn -0.172786 -0.464306 -0.868657
+vn -0.151975 0.627024 -0.764032
+vn -0.151975 -0.627024 -0.764032
+vn -0.172786 0.464306 -0.868657
+vn -0.125196 -0.766928 -0.629402
+vn -0.186990 0.285165 -0.940062
+vn -0.093357 -0.878070 -0.469338
+vn -0.194186 0.096152 -0.976241
+vn -0.057645 0.955349 -0.289802
+vn -0.057645 -0.955349 -0.289801
+vn -0.194186 -0.096152 -0.976241
+vn -0.093357 0.878069 -0.469338
+vn -0.186990 -0.285165 -0.940062
+vn -0.125196 0.766929 -0.629402
+vn 0.057645 -0.955349 -0.289801
+vn 0.194186 -0.096152 -0.976241
+vn 0.093357 0.878069 -0.469338
+vn 0.186990 -0.285165 -0.940062
+vn 0.125196 0.766929 -0.629402
+vn 0.172787 -0.464306 -0.868657
+vn 0.151975 0.627024 -0.764031
+vn 0.151975 -0.627024 -0.764031
+vn 0.172787 0.464306 -0.868657
+vn 0.125196 -0.766928 -0.629402
+vn 0.186990 0.285165 -0.940062
+vn 0.093357 -0.878070 -0.469338
+vn 0.194186 0.096152 -0.976241
+vn 0.057645 0.955349 -0.289802
+vn 0.356528 -0.766928 -0.533581
+vn 0.532502 0.285165 -0.796946
+vn 0.265859 -0.878070 -0.397886
+vn 0.552996 0.096152 -0.827617
+vn 0.164160 0.955349 -0.245682
+vn 0.164159 -0.955349 -0.245682
+vn 0.552996 -0.096152 -0.827617
+vn 0.265859 0.878069 -0.397886
+vn 0.532502 -0.285165 -0.796946
+vn 0.356527 0.766929 -0.533581
+vn 0.492055 -0.464306 -0.736412
+vn 0.432789 0.627024 -0.647715
+vn 0.432789 -0.627024 -0.647714
+vn 0.492055 0.464306 -0.736412
+vn 0.736412 -0.464306 -0.492055
+vn 0.647715 0.627024 -0.432789
+vn 0.647715 -0.627024 -0.432789
+vn 0.736412 0.464306 -0.492054
+vn 0.533581 -0.766928 -0.356528
+vn 0.796946 0.285165 -0.532502
+vn 0.397886 -0.878070 -0.265859
+vn 0.827617 0.096152 -0.552996
+vn 0.245682 0.955349 -0.164160
+vn 0.245682 -0.955349 -0.164159
+vn 0.827617 -0.096152 -0.552996
+vn 0.397886 0.878069 -0.265859
+vn 0.796946 -0.285165 -0.532502
+vn 0.533581 0.766929 -0.356527
+vn 0.289802 0.955349 -0.057645
+vn 0.289801 -0.955349 -0.057645
+vn 0.976241 -0.096152 -0.194186
+vn 0.469338 0.878069 -0.093357
+vn 0.940062 -0.285165 -0.186990
+vn 0.629402 0.766929 -0.125196
+vn 0.868657 -0.464306 -0.172786
+vn 0.764032 0.627024 -0.151975
+vn 0.764032 -0.627024 -0.151975
+vn 0.868657 0.464307 -0.172786
+vn 0.629402 -0.766928 -0.125196
+vn 0.940062 0.285165 -0.186990
+vn 0.469338 -0.878069 -0.093357
+vn 0.976241 0.096152 -0.194186
+vn 0.868657 0.464306 0.172787
+vn 0.629402 -0.766928 0.125196
+vn 0.940062 0.285165 0.186990
+vn 0.469338 -0.878069 0.093357
+vn 0.976241 0.096152 0.194186
+vn 0.289802 0.955349 0.057645
+vn 0.289801 -0.955349 0.057645
+vn 0.976241 -0.096152 0.194187
+vn 0.469338 0.878069 0.093357
+vn 0.940061 -0.285165 0.186990
+vn 0.629402 0.766929 0.125196
+vn 0.868657 -0.464306 0.172787
+vn 0.764032 0.627024 0.151975
+vn 0.764032 -0.627024 0.151976
+vn 0.796946 -0.285165 0.532502
+vn 0.533581 0.766929 0.356527
+vn 0.736412 -0.464306 0.492055
+vn 0.647715 0.627024 0.432789
+vn 0.647715 -0.627024 0.432789
+vn 0.736411 0.464307 0.492054
+vn 0.533581 -0.766928 0.356528
+vn 0.796946 0.285165 0.532502
+vn 0.397886 -0.878070 0.265859
+vn 0.827617 0.096151 0.552996
+vn 0.245682 0.955349 0.164160
+vn 0.245682 -0.955349 0.164159
+vn 0.827617 -0.096151 0.552996
+vn 0.397886 0.878070 0.265859
+vn 0.265859 -0.878070 0.397886
+vn 0.552996 0.096151 0.827617
+vn 0.164160 0.955349 0.245682
+vn 0.164159 -0.955349 0.245682
+vn 0.552996 -0.096151 0.827617
+vn 0.265859 0.878070 0.397886
+vn 0.532502 -0.285165 0.796946
+vn 0.356527 0.766929 0.533581
+vn 0.492054 -0.464306 0.736412
+vn 0.432789 0.627024 0.647715
+vn 0.432789 -0.627024 0.647715
+vn 0.492054 0.464307 0.736412
+vn 0.356528 -0.766928 0.533581
+vn 0.532502 0.285165 0.796946
+vn 0.151975 -0.627024 0.764032
+vn 0.172786 0.464307 0.868656
+vn 0.125196 -0.766928 0.629402
+vn 0.186990 0.285165 0.940062
+vn 0.093357 -0.878070 0.469338
+vn 0.194186 0.096151 0.976241
+vn 0.057645 0.955349 0.289802
+vn 0.057645 -0.955349 0.289801
+vn 0.194186 -0.096151 0.976241
+vn 0.093357 0.878070 0.469338
+vn 0.186990 -0.285166 0.940061
+vn 0.125196 0.766929 0.629402
+vn 0.172786 -0.464306 0.868657
+vn 0.151975 0.627023 0.764032
+vn -0.186990 -0.285166 0.940061
+vn -0.125196 0.766929 0.629402
+vn -0.172787 -0.464306 0.868657
+vn -0.151976 0.627023 0.764032
+vn -0.151975 -0.627024 0.764032
+vn -0.172787 0.464307 0.868656
+vn -0.125196 -0.766928 0.629402
+vn -0.186990 0.285165 0.940062
+vn -0.093357 -0.878069 0.469338
+vn -0.194186 0.096151 0.976241
+vn -0.057645 0.955349 0.289802
+vn -0.057645 -0.955349 0.289801
+vn -0.194186 -0.096151 0.976241
+vn -0.093357 0.878069 0.469338
+vn -0.265859 -0.878070 0.397886
+vn -0.552996 0.096151 0.827617
+vn -0.164160 0.955349 0.245682
+vn -0.164159 -0.955349 0.245682
+vn -0.552996 -0.096151 0.827617
+vn -0.265859 0.878070 0.397886
+vn -0.532502 -0.285166 0.796945
+vn -0.356527 0.766929 0.533581
+vn -0.492054 -0.464306 0.736412
+vn -0.432790 0.627023 0.647715
+vn -0.432789 -0.627024 0.647715
+vn -0.492054 0.464307 0.736411
+vn -0.356528 -0.766928 0.533581
+vn -0.532502 0.285165 0.796946
+vn -0.647715 -0.627024 0.432789
+vn -0.736411 0.464307 0.492054
+vn -0.533581 -0.766928 0.356528
+vn -0.796946 0.285165 0.532502
+vn -0.397886 -0.878070 0.265859
+vn -0.827617 0.096151 0.552996
+vn -0.245682 0.955349 0.164160
+vn -0.245682 -0.955349 0.164159
+vn -0.827617 -0.096151 0.552996
+vn -0.397886 0.878070 0.265859
+vn -0.796946 -0.285166 0.532502
+vn -0.533581 0.766929 0.356527
+vn -0.736412 -0.464306 0.492054
+vn -0.647715 0.627023 0.432789
+vn -0.097999 0.994996 -0.019493
+vn -0.097998 -0.994996 -0.019493
+vn -0.083079 -0.994996 -0.055512
+vn -0.083079 0.994996 -0.055512
+vn -0.055512 0.994996 -0.083079
+vn -0.055512 -0.994996 -0.083079
+vn -0.019493 0.994996 -0.097998
+vn -0.019493 -0.994996 -0.097998
+vn 0.019493 -0.994996 -0.097998
+vn 0.019493 0.994996 -0.097998
+vn 0.055512 0.994996 -0.083079
+vn 0.055512 -0.994996 -0.083079
+vn 0.083079 0.994996 -0.055512
+vn 0.083079 -0.994996 -0.055512
+vn 0.097998 -0.994996 -0.019493
+vn 0.097998 0.994996 -0.019493
+vn 0.097998 0.994996 0.019493
+vn 0.097998 -0.994996 0.019493
+vn 0.083079 0.994996 0.055512
+vn 0.083079 -0.994996 0.055512
+vn 0.055512 -0.994996 0.083079
+vn 0.055512 0.994996 0.083079
+vn 0.019493 0.994996 0.097999
+vn 0.019493 -0.994996 0.097998
+vn -0.019493 -0.994996 0.097998
+vn -0.019493 0.994996 0.097999
+vn -0.055512 -0.994996 0.083079
+vn -0.055512 0.994996 0.083079
+vn -0.083079 0.994996 0.055512
+vn -0.083079 -0.994996 0.055512
+vn -0.097998 -0.994996 0.019493
+vn -0.940061 -0.285166 0.186991
+vn -0.629402 0.766929 0.125196
+vn -0.868656 -0.464306 0.172788
+vn -0.764032 0.627023 0.151977
+vn -0.764032 -0.627024 0.151976
+vn -0.868656 0.464307 0.172787
+vn -0.629402 -0.766928 0.125196
+vn -0.940061 0.285165 0.186990
+vn -0.097999 0.994996 0.019493
+vn -0.469338 -0.878070 0.093357
+vn -0.976241 0.096151 0.194187
+vn -0.289802 0.955349 0.057646
+vn -0.289801 -0.955349 0.057645
+vn -0.976241 -0.096151 0.194187
+vn -0.469338 0.878070 0.093358
+vn -0.976241 -0.096152 -0.194186
+vn -0.469338 0.878069 -0.093357
+vn -0.629401 0.766929 -0.125196
+vn -0.764032 0.627024 -0.151975
+vn -0.736411 -0.464306 -0.492055
+vn -0.736411 0.464306 -0.492055
+vn -0.356528 -0.766928 -0.533581
+vn -0.151975 -0.627024 -0.764031
+vn -0.194186 -0.096151 -0.976241
+vn 0.186990 -0.285165 -0.940061
+vn 0.151975 0.627024 -0.764032
+vn 0.432789 -0.627024 -0.647715
+vn 0.492055 0.464306 -0.736411
+vn 0.736412 -0.464306 -0.492054
+vn 0.736411 0.464307 -0.492054
+vn 0.976241 -0.096151 -0.194186
+vn 0.940062 -0.285166 -0.186990
+vn 0.868657 -0.464306 -0.172787
+vn 0.868657 0.464306 -0.172786
+vn 0.940061 0.285165 -0.186990
+vn 0.868656 0.464307 0.172787
+vn 0.976241 0.096151 0.194187
+vn 0.976241 -0.096151 0.194186
+vn 0.469338 0.878070 0.093357
+vn 0.940061 -0.285166 0.186990
+vn 0.764032 0.627024 0.151976
+vn 0.764032 -0.627024 0.151975
+vn 0.796945 -0.285166 0.532502
+vn 0.647715 0.627023 0.432790
+vn 0.736411 0.464307 0.492055
+vn 0.532502 -0.285166 0.796946
+vn 0.432789 0.627023 0.647715
+vn 0.492054 0.464307 0.736411
+vn 0.151975 -0.627023 0.764032
+vn 0.186990 0.285165 0.940061
+vn 0.093357 0.878069 0.469338
+vn -0.151975 0.627023 0.764032
+vn -0.151976 -0.627023 0.764032
+vn -0.186990 0.285165 0.940061
+vn -0.093357 -0.878070 0.469338
+vn -0.093357 0.878070 0.469338
+vn -0.532502 -0.285166 0.796946
+vn -0.492055 -0.464306 0.736412
+vn -0.432789 -0.627023 0.647715
+vn -0.647715 -0.627023 0.432789
+vn -0.796945 -0.285166 0.532502
+vn -0.647715 0.627023 0.432790
+vn -0.940062 -0.285165 0.186990
+vn -0.629402 0.766928 0.125196
+vn -0.764031 0.627024 0.151975
+vn -0.764031 -0.627024 0.151976
+vn -0.868657 0.464306 0.172788
+vn -0.940062 0.285165 0.186991
+vn -0.976241 0.096152 0.194187
+vn -0.289802 -0.955349 0.057645
+s off
+f 13/1/1 12/2/1 28/3/1
+f 7/4/2 6/5/2 22/6/2
+f 14/7/3 13/8/3 29/9/3
+f 8/10/4 7/4/4 23/11/4
+f 2/12/5 1/13/5 17/14/5
+f 15/15/6 14/7/6 30/16/6
+f 9/17/7 8/10/7 24/18/7
+f 3/19/8 2/12/8 18/20/8
+f 10/21/9 9/17/9 25/22/9
+f 4/23/10 3/19/10 19/24/10
+f 11/25/11 10/21/11 26/26/11
+f 5/27/12 4/23/12 20/28/12
+f 12/2/13 11/25/13 27/29/13
+f 6/5/14 5/27/14 21/30/14
+f 30/16/15 29/9/15 46/31/15
+f 24/32/16 23/33/16 40/34/16
+f 18/35/17 17/14/17 34/36/17
+f 25/37/18 24/32/18 41/38/18
+f 19/39/19 18/35/19 35/40/19
+f 26/41/20 25/37/20 42/42/20
+f 20/43/21 19/39/21 36/44/21
+f 27/45/22 26/41/22 43/46/22
+f 21/47/23 20/43/23 37/48/23
+f 28/49/24 27/45/24 44/50/24
+f 22/51/25 21/47/25 38/52/25
+f 29/9/26 28/49/26 45/53/26
+f 23/33/27 22/51/27 39/54/27
+f 17/14/28 16/55/28 33/56/28
+f 44/50/29 43/46/29 59/57/29
+f 38/52/30 37/48/30 53/58/30
+f 45/53/31 44/50/31 60/59/31
+f 39/54/32 38/52/32 54/60/32
+f 33/56/33 32/61/33 48/62/33
+f 46/31/34 45/53/34 61/63/34
+f 40/34/35 39/54/35 55/64/35
+f 34/36/36 33/56/36 49/65/36
+f 41/38/37 40/34/37 56/66/37
+f 35/40/38 34/36/38 50/67/38
+f 42/42/39 41/38/39 57/68/39
+f 36/44/40 35/40/40 51/69/40
+f 43/46/41 42/42/41 58/70/41
+f 37/48/42 36/44/42 52/71/42
+f 57/68/43 56/66/43 72/72/43
+f 51/69/44 50/67/44 66/73/44
+f 58/70/45 57/68/45 73/74/45
+f 52/71/46 51/69/46 67/75/46
+f 59/57/47 58/70/47 74/76/47
+f 53/58/48 52/71/48 68/77/48
+f 60/59/49 59/57/49 75/78/49
+f 54/60/50 53/58/50 69/79/50
+f 48/62/51 47/80/51 63/81/51
+f 61/63/52 60/59/52 76/82/52
+f 55/64/53 54/60/53 70/83/53
+f 49/65/54 48/62/54 64/84/54
+f 56/66/55 55/64/55 71/85/55
+f 50/67/56 49/65/56 65/86/56
+f 76/82/57 75/78/57 90/87/57
+f 70/83/58 69/79/58 84/88/58
+f 64/84/59 63/81/59 78/89/59
+f 71/85/60 70/83/60 85/90/60
+f 65/86/61 64/84/61 79/91/61
+f 72/72/62 71/85/62 86/92/62
+f 66/73/63 65/86/63 80/93/63
+f 73/74/64 72/72/64 87/94/64
+f 67/75/65 66/73/65 81/95/65
+f 74/76/66 73/74/66 88/96/66
+f 68/77/67 67/75/67 82/97/67
+f 75/78/68 74/76/68 89/98/68
+f 69/79/69 68/77/69 83/99/69
+f 63/81/70 62/100/70 77/101/70
+f 89/98/71 88/96/71 103/102/71
+f 83/99/72 82/97/72 97/103/72
+f 90/87/73 89/98/73 104/104/73
+f 84/88/74 83/99/74 98/105/74
+f 78/89/75 77/101/75 92/106/75
+f 91/107/76 90/87/76 105/108/76
+f 85/90/77 84/88/77 99/109/77
+f 79/91/78 78/89/78 93/110/78
+f 86/92/79 85/90/79 100/111/79
+f 80/93/80 79/91/80 94/112/80
+f 87/94/81 86/92/81 101/113/81
+f 81/95/82 80/93/82 95/114/82
+f 88/96/83 87/94/83 102/115/83
+f 82/97/84 81/95/84 96/116/84
+f 102/115/85 101/113/85 116/117/85
+f 96/116/86 95/114/86 110/118/86
+f 103/102/87 102/115/87 117/119/87
+f 97/103/88 96/116/88 111/120/88
+f 104/104/89 103/102/89 118/121/89
+f 98/105/90 97/103/90 112/122/90
+f 105/108/91 104/104/91 119/123/91
+f 99/109/92 98/105/92 113/124/92
+f 93/110/93 92/106/93 107/125/93
+f 106/126/94 105/108/94 120/127/94
+f 100/111/95 99/109/95 114/128/95
+f 94/112/96 93/110/96 108/129/96
+f 101/113/97 100/111/97 115/130/97
+f 95/114/98 94/112/98 109/131/98
+f 108/129/99 107/125/99 122/132/99
+f 121/133/100 120/127/100 135/134/100
+f 115/130/101 114/128/101 129/135/101
+f 109/131/102 108/129/102 123/136/102
+f 116/117/103 115/130/103 130/137/103
+f 110/118/104 109/131/104 124/138/104
+f 117/119/105 116/117/105 131/139/105
+f 111/120/106 110/118/106 125/140/106
+f 118/121/107 117/119/107 132/141/107
+f 112/122/108 111/120/108 126/142/108
+f 119/123/109 118/121/109 133/143/109
+f 113/124/110 112/122/110 127/144/110
+f 120/127/111 119/123/111 134/145/111
+f 114/128/112 113/124/112 128/146/112
+f 127/144/113 126/142/113 141/147/113
+f 134/145/114 133/143/114 148/148/114
+f 128/146/115 127/144/115 142/149/115
+f 135/134/116 134/145/116 149/150/116
+f 129/135/117 128/146/117 143/151/117
+f 123/136/118 122/132/118 137/152/118
+f 136/153/119 135/134/119 150/154/119
+f 130/137/120 129/135/120 144/155/120
+f 124/138/121 123/136/121 138/156/121
+f 131/139/122 130/137/122 145/157/122
+f 125/140/123 124/138/123 139/158/123
+f 132/141/124 131/139/124 146/159/124
+f 126/142/125 125/140/125 140/160/125
+f 133/143/126 132/141/126 147/161/126
+f 146/159/127 145/157/127 160/162/127
+f 140/160/128 139/158/128 154/163/128
+f 147/161/129 146/159/129 161/164/129
+f 141/147/130 140/160/130 155/165/130
+f 148/148/131 147/161/131 162/166/131
+f 142/149/132 141/147/132 157/167/132
+f 149/150/133 148/148/133 163/168/133
+f 143/151/134 142/149/134 157/167/134
+f 150/154/135 149/150/135 164/169/135
+f 144/155/136 143/151/136 158/170/136
+f 138/156/137 137/152/137 152/171/137
+f 151/172/138 150/154/138 165/173/138
+f 145/157/139 144/155/139 159/174/139
+f 139/158/140 138/156/140 153/175/140
+f 165/176/141 164/177/141 179/178/141
+f 159/174/142 158/170/142 173/179/142
+f 153/175/143 152/171/143 167/180/143
+f 166/181/144 165/176/144 180/182/144
+f 160/162/145 159/174/145 174/183/145
+f 154/163/146 153/175/146 168/184/146
+f 161/164/147 160/162/147 175/185/147
+f 155/165/148 154/163/148 169/186/148
+f 162/166/149 161/164/149 176/187/149
+f 156/188/150 155/165/150 170/189/150
+f 163/168/151 162/166/151 177/190/151
+f 157/167/152 156/188/152 171/191/152
+f 164/177/153 163/168/153 178/192/153
+f 158/170/154 157/167/154 172/193/154
+f 178/192/155 177/190/155 192/194/155
+f 172/193/156 171/191/156 186/195/156
+f 179/178/157 178/192/157 193/196/157
+f 173/179/158 172/193/158 187/197/158
+f 180/182/159 179/178/159 194/198/159
+f 174/183/160 173/179/160 188/199/160
+f 168/184/161 167/180/161 182/200/161
+f 181/201/162 180/182/162 195/202/162
+f 175/185/163 174/183/163 189/203/163
+f 169/186/164 168/184/164 183/204/164
+f 176/187/165 175/185/165 190/205/165
+f 170/189/166 169/186/166 184/206/166
+f 177/190/167 176/187/167 191/207/167
+f 171/191/168 170/189/168 185/208/168
+f 191/207/169 190/205/169 206/209/169
+f 185/208/170 184/206/170 200/210/170
+f 192/194/171 191/207/171 207/211/171
+f 186/195/172 185/208/172 201/212/172
+f 193/196/173 192/194/173 208/213/173
+f 187/197/174 186/195/174 202/214/174
+f 194/198/175 193/196/175 209/215/175
+f 188/199/176 187/197/176 203/216/176
+f 195/202/177 194/198/177 210/217/177
+f 189/203/178 188/199/178 204/218/178
+f 183/204/179 182/200/179 198/219/179
+f 196/220/180 195/202/180 211/221/180
+f 190/205/181 189/203/181 205/222/181
+f 184/206/182 183/204/182 199/223/182
+f 210/217/183 209/215/183 226/224/183
+f 204/218/184 203/216/184 220/225/184
+f 198/219/185 197/226/185 214/227/185
+f 211/221/186 210/217/186 227/228/186
+f 205/222/187 204/218/187 221/229/187
+f 199/223/188 198/219/188 215/230/188
+f 206/209/189 205/222/189 222/231/189
+f 200/210/190 199/223/190 216/232/190
+f 207/211/191 206/209/191 223/233/191
+f 201/212/192 200/210/192 217/234/192
+f 208/213/193 207/211/193 223/233/193
+f 202/214/194 201/212/194 218/235/194
+f 209/215/195 208/213/195 225/236/195
+f 203/216/196 202/214/196 219/237/196
+f 224/238/197 223/233/197 239/239/197
+f 218/235/198 217/234/198 233/240/198
+f 225/236/199 224/238/199 240/241/199
+f 219/237/200 218/235/200 234/242/200
+f 226/224/201 225/236/201 241/243/201
+f 220/225/202 219/237/202 235/244/202
+f 214/227/203 213/245/203 229/246/203
+f 227/228/204 226/224/204 242/247/204
+f 221/229/205 220/225/205 236/248/205
+f 215/230/206 214/227/206 230/249/206
+f 222/231/207 221/229/207 237/250/207
+f 216/232/208 215/230/208 231/251/208
+f 223/233/209 222/231/209 238/252/209
+f 217/234/210 216/232/210 232/253/210
+f 1/13/211 212/254/211 16/55/211
+f 31/255/212 15/15/212 30/16/212
+f 31/255/213 30/16/213 46/31/213
+f 16/55/214 212/254/214 32/61/214
+f 32/61/215 212/254/215 47/80/215
+f 31/255/216 46/31/216 61/63/216
+f 47/80/217 212/254/217 62/100/217
+f 31/255/218 61/63/218 76/82/218
+f 31/255/219 76/82/219 91/107/219
+f 62/100/220 212/254/220 77/101/220
+f 77/101/221 212/254/221 92/106/221
+f 31/255/222 91/107/222 106/126/222
+f 92/106/223 212/254/223 107/125/223
+f 31/255/224 106/126/224 121/133/224
+f 31/255/225 121/133/225 136/153/225
+f 107/125/226 212/254/226 122/132/226
+f 122/132/227 212/254/227 137/152/227
+f 31/255/228 136/153/228 151/172/228
+f 137/152/229 212/254/229 152/171/229
+f 31/255/230 151/172/230 166/256/230
+f 31/255/231 166/256/231 181/257/231
+f 152/171/232 212/254/232 167/180/232
+f 167/180/233 212/254/233 182/200/233
+f 31/255/234 181/257/234 196/258/234
+f 31/255/235 196/258/235 211/259/235
+f 182/200/236 212/254/236 197/226/236
+f 31/255/237 211/259/237 227/260/237
+f 197/226/238 212/254/238 213/245/238
+f 213/245/239 212/254/239 228/261/239
+f 31/255/240 227/260/240 242/262/240
+f 31/255/241 242/262/241 15/15/241
+f 237/250/242 236/248/242 10/21/242
+f 231/251/243 230/249/243 4/23/243
+f 238/252/244 237/250/244 10/21/244
+f 232/253/245 231/251/245 5/27/245
+f 239/239/246 238/252/246 11/25/246
+f 233/240/247 232/253/247 6/5/247
+f 240/241/248 239/239/248 13/1/248
+f 234/242/249 233/240/249 7/4/249
+f 228/261/250 212/254/250 1/13/250
+f 241/243/251 240/241/251 14/263/251
+f 235/244/252 234/242/252 8/10/252
+f 229/246/253 228/261/253 2/12/253
+f 242/247/254 241/243/254 15/264/254
+f 236/248/255 235/244/255 9/17/255
+f 230/249/256 229/246/256 3/19/256
+f 12/2/1 27/29/1 28/3/1
+f 6/5/2 21/30/2 22/6/2
+f 13/8/3 28/49/3 29/9/3
+f 7/4/4 22/6/4 23/11/4
+f 1/13/5 16/55/5 17/14/5
+f 14/7/6 29/9/6 30/16/6
+f 8/10/257 23/11/257 24/18/257
+f 2/12/258 17/14/258 18/20/258
+f 9/17/9 24/18/9 25/22/9
+f 3/19/259 18/20/259 19/24/259
+f 10/21/11 25/22/11 26/26/11
+f 4/23/260 19/24/260 20/28/260
+f 11/25/13 26/26/13 27/29/13
+f 5/27/14 20/28/14 21/30/14
+f 29/9/15 45/53/15 46/31/15
+f 23/33/16 39/54/16 40/34/16
+f 17/14/17 33/56/17 34/36/17
+f 24/32/18 40/34/18 41/38/18
+f 18/35/19 34/36/19 35/40/19
+f 25/37/261 41/38/261 42/42/261
+f 19/39/21 35/40/21 36/44/21
+f 26/41/22 42/42/22 43/46/22
+f 20/43/262 36/44/262 37/48/262
+f 27/45/24 43/46/24 44/50/24
+f 21/47/25 37/48/25 38/52/25
+f 28/49/26 44/50/26 45/53/26
+f 22/51/27 38/52/27 39/54/27
+f 16/55/28 32/61/28 33/56/28
+f 43/46/263 58/70/263 59/57/263
+f 37/48/30 52/71/30 53/58/30
+f 44/50/31 59/57/31 60/59/31
+f 38/52/32 53/58/32 54/60/32
+f 32/61/33 47/80/33 48/62/33
+f 45/53/34 60/59/34 61/63/34
+f 39/54/35 54/60/35 55/64/35
+f 33/56/36 48/62/36 49/65/36
+f 40/34/37 55/64/37 56/66/37
+f 34/36/38 49/65/38 50/67/38
+f 41/38/39 56/66/39 57/68/39
+f 35/40/40 50/67/40 51/69/40
+f 42/42/41 57/68/41 58/70/41
+f 36/44/42 51/69/42 52/71/42
+f 56/66/43 71/85/43 72/72/43
+f 50/67/44 65/86/44 66/73/44
+f 57/68/264 72/72/264 73/74/264
+f 51/69/46 66/73/46 67/75/46
+f 58/70/47 73/74/47 74/76/47
+f 52/71/48 67/75/48 68/77/48
+f 59/57/49 74/76/49 75/78/49
+f 53/58/50 68/77/50 69/79/50
+f 47/80/51 62/100/51 63/81/51
+f 60/59/52 75/78/52 76/82/52
+f 54/60/265 69/79/265 70/83/265
+f 48/62/54 63/81/54 64/84/54
+f 55/64/55 70/83/55 71/85/55
+f 49/65/56 64/84/56 65/86/56
+f 91/107/57 76/82/57 90/87/57
+f 85/90/58 70/83/58 84/88/58
+f 79/91/59 64/84/59 78/89/59
+f 86/92/266 71/85/266 85/90/266
+f 80/93/61 65/86/61 79/91/61
+f 87/94/62 72/72/62 86/92/62
+f 81/95/267 66/73/267 80/93/267
+f 88/96/64 73/74/64 87/94/64
+f 82/97/65 67/75/65 81/95/65
+f 89/98/66 74/76/66 88/96/66
+f 83/99/67 68/77/67 82/97/67
+f 90/87/68 75/78/68 89/98/68
+f 84/88/69 69/79/69 83/99/69
+f 78/89/70 63/81/70 77/101/70
+f 104/104/71 89/98/71 103/102/71
+f 98/105/72 83/99/72 97/103/72
+f 105/108/73 90/87/73 104/104/73
+f 99/109/74 84/88/74 98/105/74
+f 93/110/75 78/89/75 92/106/75
+f 106/126/76 91/107/76 105/108/76
+f 100/111/77 85/90/77 99/109/77
+f 94/112/78 79/91/78 93/110/78
+f 101/113/79 86/92/79 100/111/79
+f 95/114/80 80/93/80 94/112/80
+f 102/115/81 87/94/81 101/113/81
+f 96/116/82 81/95/82 95/114/82
+f 103/102/268 88/96/268 102/115/268
+f 97/103/269 82/97/269 96/116/269
+f 117/119/270 102/115/270 116/117/270
+f 111/120/86 96/116/86 110/118/86
+f 118/121/87 103/102/87 117/119/87
+f 112/122/271 97/103/271 111/120/271
+f 119/123/89 104/104/89 118/121/89
+f 113/124/90 98/105/90 112/122/90
+f 120/127/91 105/108/91 119/123/91
+f 114/128/92 99/109/92 113/124/92
+f 108/129/93 93/110/93 107/125/93
+f 121/133/94 106/126/94 120/127/94
+f 115/130/95 100/111/95 114/128/95
+f 109/131/96 94/112/96 108/129/96
+f 116/117/97 101/113/97 115/130/97
+f 110/118/98 95/114/98 109/131/98
+f 123/136/99 108/129/99 122/132/99
+f 136/153/100 121/133/100 135/134/100
+f 130/137/272 115/130/272 129/135/272
+f 124/138/102 109/131/102 123/136/102
+f 131/139/273 116/117/273 130/137/273
+f 125/140/104 110/118/104 124/138/104
+f 132/141/274 117/119/274 131/139/274
+f 126/142/106 111/120/106 125/140/106
+f 133/143/107 118/121/107 132/141/107
+f 127/144/275 112/122/275 126/142/275
+f 134/145/109 119/123/109 133/143/109
+f 128/146/276 113/124/276 127/144/276
+f 135/134/111 120/127/111 134/145/111
+f 129/135/112 114/128/112 128/146/112
+f 142/149/277 127/144/277 141/147/277
+f 149/150/114 134/145/114 148/148/114
+f 143/151/115 128/146/115 142/149/115
+f 150/154/116 135/134/116 149/150/116
+f 144/155/278 129/135/278 143/151/278
+f 138/156/118 123/136/118 137/152/118
+f 151/172/119 136/153/119 150/154/119
+f 145/157/279 130/137/279 144/155/279
+f 139/158/280 124/138/280 138/156/280
+f 146/159/281 131/139/281 145/157/281
+f 140/160/123 125/140/123 139/158/123
+f 147/161/124 132/141/124 146/159/124
+f 141/147/282 126/142/282 140/160/282
+f 148/148/283 133/143/283 147/161/283
+f 161/164/284 146/159/284 160/162/284
+f 155/165/128 140/160/128 154/163/128
+f 162/166/129 147/161/129 161/164/129
+f 156/188/285 141/147/285 155/165/285
+f 163/168/131 148/148/131 162/166/131
+f 141/147/286 156/188/286 157/167/286
+f 164/177/133 149/150/133 163/168/133
+f 158/170/134 143/151/134 157/167/134
+f 165/173/135 150/154/135 164/169/135
+f 159/174/136 144/155/136 158/170/136
+f 153/175/137 138/156/137 152/171/137
+f 166/256/138 151/172/138 165/173/138
+f 160/162/139 145/157/139 159/174/139
+f 154/163/140 139/158/140 153/175/140
+f 180/182/141 165/176/141 179/178/141
+f 174/183/142 159/174/142 173/179/142
+f 168/184/143 153/175/143 167/180/143
+f 181/201/144 166/181/144 180/182/144
+f 175/185/145 160/162/145 174/183/145
+f 169/186/146 154/163/146 168/184/146
+f 176/187/287 161/164/287 175/185/287
+f 170/189/148 155/165/148 169/186/148
+f 177/190/149 162/166/149 176/187/149
+f 171/191/288 156/188/288 170/189/288
+f 178/192/151 163/168/151 177/190/151
+f 172/193/289 157/167/289 171/191/289
+f 179/178/153 164/177/153 178/192/153
+f 173/179/154 158/170/154 172/193/154
+f 193/196/290 178/192/290 192/194/290
+f 187/197/156 172/193/156 186/195/156
+f 194/198/157 179/178/157 193/196/157
+f 188/199/291 173/179/291 187/197/291
+f 195/202/159 180/182/159 194/198/159
+f 189/203/160 174/183/160 188/199/160
+f 183/204/161 168/184/161 182/200/161
+f 196/220/162 181/201/162 195/202/162
+f 190/205/163 175/185/163 189/203/163
+f 184/206/292 169/186/292 183/204/292
+f 191/207/165 176/187/165 190/205/165
+f 185/208/166 170/189/166 184/206/166
+f 192/194/167 177/190/167 191/207/167
+f 186/195/168 171/191/168 185/208/168
+f 190/205/169 205/222/169 206/209/169
+f 184/206/170 199/223/170 200/210/170
+f 191/207/171 206/209/171 207/211/171
+f 185/208/293 200/210/293 201/212/293
+f 192/194/294 207/211/294 208/213/294
+f 186/195/174 201/212/174 202/214/174
+f 193/196/175 208/213/175 209/215/175
+f 187/197/295 202/214/295 203/216/295
+f 194/198/296 209/215/296 210/217/296
+f 188/199/178 203/216/178 204/218/178
+f 182/200/179 197/226/179 198/219/179
+f 195/202/180 210/217/180 211/221/180
+f 189/203/181 204/218/181 205/222/181
+f 183/204/297 198/219/297 199/223/297
+f 209/215/183 225/236/183 226/224/183
+f 203/216/184 219/237/184 220/225/184
+f 197/226/185 213/245/185 214/227/185
+f 210/217/186 226/224/186 227/228/186
+f 204/218/187 220/225/187 221/229/187
+f 198/219/188 214/227/188 215/230/188
+f 205/222/298 221/229/298 222/231/298
+f 199/223/190 215/230/190 216/232/190
+f 206/209/299 222/231/299 223/233/299
+f 200/210/192 216/232/192 217/234/192
+f 224/238/300 208/213/300 223/233/300
+f 201/212/194 217/234/194 218/235/194
+f 208/213/195 224/238/195 225/236/195
+f 202/214/196 218/235/196 219/237/196
+f 223/233/301 238/252/301 239/239/301
+f 217/234/198 232/253/198 233/240/198
+f 224/238/199 239/239/199 240/241/199
+f 218/235/200 233/240/200 234/242/200
+f 225/236/201 240/241/201 241/243/201
+f 219/237/202 234/242/202 235/244/202
+f 213/245/203 228/261/203 229/246/203
+f 226/224/204 241/243/204 242/247/204
+f 220/225/205 235/244/205 236/248/205
+f 214/227/206 229/246/206 230/249/206
+f 221/229/302 236/248/302 237/250/302
+f 215/230/208 230/249/208 231/251/208
+f 222/231/209 237/250/209 238/252/209
+f 216/232/303 231/251/303 232/253/303
+f 236/248/304 9/17/304 10/21/304
+f 230/249/305 3/19/305 4/23/305
+f 11/25/244 238/252/244 10/21/244
+f 231/251/306 4/23/306 5/27/306
+f 12/2/307 239/239/307 11/25/307
+f 232/253/308 5/27/308 6/5/308
+f 239/239/248 12/2/248 13/1/248
+f 233/240/309 6/5/309 7/4/309
+f 240/241/251 13/1/251 14/263/251
+f 234/242/310 7/4/310 8/10/310
+f 228/261/253 1/13/253 2/12/253
+f 241/243/311 14/263/311 15/264/311
+f 235/244/255 8/10/255 9/17/255
+f 229/246/256 2/12/256 3/19/256
diff --git a/src/graphs/engine/meshes/sphereSmooth.mesh b/src/graphs/engine/meshes/sphereSmooth.mesh
new file mode 100644
index 0000000..4877218
--- /dev/null
+++ b/src/graphs/engine/meshes/sphereSmooth.mesh
Binary files differ
diff --git a/src/graphs/engine/meshes/sphereSmooth.obj b/src/graphs/engine/meshes/sphereSmooth.obj
new file mode 100644
index 0000000..3c5b129
--- /dev/null
+++ b/src/graphs/engine/meshes/sphereSmooth.obj
@@ -0,0 +1,1232 @@
+# Blender v2.66 (sub 0) OBJ File: 'sphere.blend'
+# www.blender.org
+o Sphere
+v -0.195090 0.980785 0.000000
+v -0.382683 0.923880 0.000000
+v -0.555570 0.831470 0.000000
+v -0.707107 0.707107 0.000000
+v -0.831470 0.555570 0.000000
+v -0.923880 0.382683 0.000000
+v -0.980785 0.195090 0.000000
+v -1.000000 0.000000 0.000000
+v -0.980785 -0.195090 0.000000
+v -0.923880 -0.382683 0.000000
+v -0.831470 -0.555570 0.000000
+v -0.707107 -0.707107 0.000000
+v -0.555570 -0.831470 0.000000
+v -0.382683 -0.923880 0.000000
+v -0.195090 -0.980785 0.000000
+v -0.180240 0.980785 -0.074658
+v -0.353553 0.923880 -0.146447
+v -0.513280 0.831470 -0.212608
+v -0.653281 0.707107 -0.270598
+v -0.768178 0.555570 -0.318190
+v -0.853553 0.382683 -0.353553
+v -0.906127 0.195090 -0.375330
+v -0.923880 0.000000 -0.382684
+v -0.906127 -0.195090 -0.375330
+v -0.853553 -0.382683 -0.353554
+v -0.768178 -0.555570 -0.318190
+v -0.653281 -0.707107 -0.270598
+v -0.513280 -0.831470 -0.212608
+v -0.353553 -0.923880 -0.146447
+v -0.180240 -0.980785 -0.074658
+v 0.000000 -1.000000 0.000000
+v -0.137950 0.980785 -0.137950
+v -0.270598 0.923880 -0.270598
+v -0.392847 0.831470 -0.392848
+v -0.500000 0.707107 -0.500000
+v -0.587938 0.555570 -0.587938
+v -0.653281 0.382683 -0.653282
+v -0.693520 0.195090 -0.693520
+v -0.707107 0.000000 -0.707107
+v -0.693520 -0.195090 -0.693520
+v -0.653281 -0.382683 -0.653282
+v -0.587938 -0.555570 -0.587938
+v -0.500000 -0.707107 -0.500000
+v -0.392847 -0.831470 -0.392848
+v -0.270598 -0.923880 -0.270598
+v -0.137949 -0.980785 -0.137950
+v -0.074658 0.980785 -0.180240
+v -0.146446 0.923880 -0.353554
+v -0.212607 0.831470 -0.513280
+v -0.270598 0.707107 -0.653282
+v -0.318189 0.555570 -0.768178
+v -0.353553 0.382683 -0.853554
+v -0.375330 0.195090 -0.906128
+v -0.382683 0.000000 -0.923880
+v -0.375330 -0.195090 -0.906128
+v -0.353553 -0.382683 -0.853554
+v -0.318189 -0.555570 -0.768178
+v -0.270598 -0.707107 -0.653282
+v -0.212607 -0.831470 -0.513280
+v -0.146446 -0.923880 -0.353554
+v -0.074658 -0.980785 -0.180240
+v 0.000000 0.980785 -0.195091
+v 0.000000 0.923880 -0.382684
+v 0.000000 0.831470 -0.555570
+v 0.000000 0.707107 -0.707107
+v 0.000000 0.555570 -0.831470
+v 0.000000 0.382683 -0.923880
+v 0.000000 0.195090 -0.980785
+v 0.000000 0.000000 -1.000000
+v 0.000000 -0.195090 -0.980785
+v 0.000000 -0.382683 -0.923880
+v 0.000000 -0.555570 -0.831470
+v 0.000000 -0.707107 -0.707107
+v 0.000000 -0.831470 -0.555570
+v 0.000000 -0.923880 -0.382684
+v 0.000000 -0.980785 -0.195090
+v 0.074658 0.980785 -0.180240
+v 0.146447 0.923880 -0.353554
+v 0.212608 0.831470 -0.513280
+v 0.270598 0.707107 -0.653282
+v 0.318190 0.555570 -0.768178
+v 0.353554 0.382683 -0.853553
+v 0.375331 0.195090 -0.906128
+v 0.382684 0.000000 -0.923880
+v 0.375331 -0.195090 -0.906128
+v 0.353554 -0.382683 -0.853553
+v 0.318190 -0.555570 -0.768178
+v 0.270598 -0.707107 -0.653282
+v 0.212608 -0.831470 -0.513280
+v 0.146447 -0.923880 -0.353554
+v 0.074658 -0.980785 -0.180240
+v 0.137950 0.980785 -0.137950
+v 0.270599 0.923880 -0.270598
+v 0.392848 0.831470 -0.392848
+v 0.500000 0.707107 -0.500000
+v 0.587938 0.555570 -0.587938
+v 0.653282 0.382683 -0.653282
+v 0.693520 0.195090 -0.693520
+v 0.707107 0.000000 -0.707107
+v 0.693520 -0.195090 -0.693520
+v 0.653282 -0.382683 -0.653281
+v 0.587938 -0.555570 -0.587938
+v 0.500000 -0.707107 -0.500000
+v 0.392848 -0.831470 -0.392848
+v 0.270599 -0.923880 -0.270598
+v 0.137950 -0.980785 -0.137950
+v 0.180241 0.980785 -0.074658
+v 0.353554 0.923880 -0.146447
+v 0.513280 0.831470 -0.212608
+v 0.653282 0.707107 -0.270598
+v 0.768178 0.555570 -0.318190
+v 0.853554 0.382683 -0.353553
+v 0.906128 0.195090 -0.375330
+v 0.923880 0.000000 -0.382683
+v 0.906128 -0.195090 -0.375330
+v 0.853554 -0.382683 -0.353553
+v 0.768178 -0.555570 -0.318190
+v 0.653282 -0.707107 -0.270598
+v 0.513280 -0.831470 -0.212608
+v 0.353554 -0.923880 -0.146447
+v 0.180240 -0.980785 -0.074658
+v 0.195091 0.980785 0.000000
+v 0.382684 0.923880 0.000000
+v 0.555571 0.831470 0.000000
+v 0.707107 0.707107 0.000000
+v 0.831470 0.555570 0.000000
+v 0.923880 0.382683 0.000000
+v 0.980786 0.195090 0.000000
+v 1.000000 0.000000 0.000000
+v 0.980786 -0.195090 0.000000
+v 0.923880 -0.382683 0.000000
+v 0.831470 -0.555570 0.000000
+v 0.707107 -0.707107 0.000000
+v 0.555571 -0.831470 0.000000
+v 0.382684 -0.923880 0.000000
+v 0.195091 -0.980785 0.000000
+v 0.180241 0.980785 0.074658
+v 0.353554 0.923880 0.146447
+v 0.513280 0.831470 0.212608
+v 0.653282 0.707107 0.270598
+v 0.768178 0.555570 0.318190
+v 0.853554 0.382683 0.353553
+v 0.906128 0.195090 0.375330
+v 0.923880 0.000000 0.382684
+v 0.906128 -0.195090 0.375330
+v 0.853554 -0.382683 0.353553
+v 0.768178 -0.555570 0.318190
+v 0.653282 -0.707107 0.270598
+v 0.513280 -0.831470 0.212608
+v 0.353554 -0.923880 0.146447
+v 0.180240 -0.980785 0.074658
+v 0.137950 0.980785 0.137950
+v 0.270599 0.923880 0.270598
+v 0.392848 0.831470 0.392848
+v 0.500000 0.707107 0.500000
+v 0.587938 0.555570 0.587938
+v 0.653282 0.382683 0.653282
+v 0.693520 0.195090 0.693520
+v 0.707107 0.000000 0.707107
+v 0.693520 -0.195090 0.693520
+v 0.653282 -0.382683 0.653282
+v 0.587938 -0.555570 0.587938
+v 0.500000 -0.707107 0.500000
+v 0.392848 -0.831470 0.392848
+v 0.270599 -0.923880 0.270598
+v 0.137950 -0.980785 0.137950
+v 0.074658 0.980785 0.180240
+v 0.146447 0.923880 0.353554
+v 0.212608 0.831470 0.513280
+v 0.270598 0.707107 0.653282
+v 0.318190 0.555570 0.768178
+v 0.353554 0.382683 0.853553
+v 0.375331 0.195090 0.906128
+v 0.382684 0.000000 0.923880
+v 0.375331 -0.195090 0.906128
+v 0.353554 -0.382683 0.853553
+v 0.318190 -0.555570 0.768178
+v 0.270598 -0.707107 0.653282
+v 0.212608 -0.831470 0.513280
+v 0.146447 -0.923880 0.353554
+v 0.074658 -0.980785 0.180240
+v 0.000000 0.980785 0.195091
+v 0.000000 0.923880 0.382684
+v 0.000000 0.831470 0.555570
+v 0.000000 0.707107 0.707107
+v 0.000000 0.555570 0.831470
+v 0.000000 0.382683 0.923880
+v 0.000000 0.195090 0.980785
+v 0.000000 0.000000 1.000000
+v 0.000000 -0.195090 0.980785
+v 0.000000 -0.382683 0.923879
+v 0.000000 -0.555570 0.831470
+v 0.000000 -0.707107 0.707107
+v 0.000000 -0.831470 0.555570
+v 0.000000 -0.923880 0.382684
+v 0.000000 -0.980785 0.195090
+v -0.074658 0.980785 0.180240
+v -0.146446 0.923880 0.353554
+v -0.212607 0.831470 0.513280
+v -0.270598 0.707107 0.653282
+v -0.318189 0.555570 0.768178
+v -0.353553 0.382683 0.853553
+v -0.375330 0.195090 0.906128
+v -0.382683 0.000000 0.923880
+v -0.375330 -0.195090 0.906128
+v -0.353553 -0.382683 0.853553
+v -0.318189 -0.555570 0.768178
+v -0.270598 -0.707107 0.653282
+v -0.212607 -0.831470 0.513280
+v -0.146446 -0.923880 0.353554
+v -0.074658 -0.980785 0.180240
+v 0.000000 1.000000 0.000000
+v -0.137950 0.980785 0.137950
+v -0.270598 0.923880 0.270598
+v -0.392847 0.831470 0.392848
+v -0.500000 0.707107 0.500000
+v -0.587938 0.555570 0.587938
+v -0.653281 0.382683 0.653281
+v -0.693520 0.195090 0.693520
+v -0.707107 0.000000 0.707107
+v -0.693520 -0.195090 0.693520
+v -0.653281 -0.382683 0.653281
+v -0.587938 -0.555570 0.587938
+v -0.500000 -0.707107 0.500000
+v -0.392847 -0.831470 0.392848
+v -0.270598 -0.923880 0.270598
+v -0.137949 -0.980785 0.137950
+v -0.180240 0.980785 0.074658
+v -0.353553 0.923880 0.146447
+v -0.513280 0.831470 0.212608
+v -0.653281 0.707107 0.270598
+v -0.768177 0.555570 0.318190
+v -0.853553 0.382683 0.353553
+v -0.906127 0.195090 0.375330
+v -0.923879 0.000000 0.382683
+v -0.906127 -0.195090 0.375330
+v -0.853553 -0.382683 0.353553
+v -0.768177 -0.555570 0.318190
+v -0.653281 -0.707107 0.270598
+v -0.513280 -0.831470 0.212608
+v -0.353553 -0.923880 0.146447
+v -0.180240 -0.980785 0.074658
+vt 0.040867 0.325557
+vt 0.048386 0.386583
+vt 0.001015 0.334499
+vt 0.081872 0.692529
+vt 0.092315 0.752973
+vt 0.006404 0.709363
+vt 1.031336 0.264931
+vt 1.040867 0.325557
+vt 0.999822 0.272029
+vt 0.073887 0.631595
+vt 0.005301 0.646891
+vt 0.333915 0.937286
+vt 0.448527 0.905466
+vt 0.447682 0.976645
+vt 1.017784 0.205113
+vt 0.998129 0.209571
+vt 0.067156 0.570413
+vt 0.004397 0.584414
+vt 0.196016 0.918498
+vt 0.034176 0.958662
+vt 0.060989 0.509119
+vt 0.003585 0.521934
+vt 0.135028 0.869370
+vt 0.015221 0.896641
+vt 0.054891 0.447811
+vt 0.002796 0.459454
+vt 0.107782 0.812401
+vt 0.010297 0.834265
+vt 0.001965 0.396975
+vt 0.007907 0.771825
+vt 0.978319 0.207096
+vt 1.004397 0.584414
+vt 1.005301 0.646891
+vt 0.941200 0.576589
+vt 1.034176 0.958662
+vt 0.823807 0.933258
+vt 1.003585 0.521934
+vt 0.945911 0.514786
+vt 1.015221 0.896641
+vt 0.885287 0.880701
+vt 1.002796 0.459454
+vt 0.950535 0.452971
+vt 1.010297 0.834265
+vt 0.908873 0.821779
+vt 1.001965 0.396975
+vt 0.955439 0.391196
+vt 1.007907 0.771825
+vt 0.921503 0.761167
+vt 1.001015 0.334499
+vt 0.961079 0.329532
+vt 1.006404 0.709363
+vt 0.929787 0.699898
+vt 0.968202 0.268089
+vt 0.936013 0.638321
+vt 0.488610 0.915189
+vt 0.647808 0.950687
+vt 0.921611 0.311033
+vt 0.859136 0.666985
+vt 0.937209 0.253354
+vt 0.870446 0.607941
+vt 0.532175 0.909643
+vt 0.657607 0.903302
+vt 0.959449 0.197860
+vt 0.880347 0.548455
+vt 0.745300 0.879338
+vt 0.889690 0.488794
+vt 0.796385 0.834663
+vt 0.899158 0.429173
+vt 0.825891 0.781668
+vt 0.909472 0.369816
+vt 0.845097 0.725165
+vt 0.848920 0.390841
+vt 0.761226 0.728711
+vt 0.864196 0.334814
+vt 0.780693 0.674598
+vt 0.882867 0.280332
+vt 0.796418 0.618773
+vt 0.907539 0.228698
+vt 0.810089 0.562031
+vt 0.558962 0.891763
+vt 0.641452 0.857179
+vt 0.942759 0.182515
+vt 0.822818 0.504873
+vt 0.697492 0.824480
+vt 0.835477 0.447684
+vt 0.735147 0.779653
+vt 0.879896 0.195479
+vt 0.753309 0.508011
+vt 0.618082 0.815427
+vt 0.767315 0.452094
+vt 0.657173 0.773912
+vt 0.782092 0.396562
+vt 0.685241 0.725518
+vt 0.798714 0.341964
+vt 0.706593 0.673391
+vt 0.818709 0.289116
+vt 0.723996 0.619193
+vt 0.844487 0.239416
+vt 0.739178 0.563865
+vt 0.567268 0.868303
+vt 0.563300 0.844314
+vt 0.770454 0.236269
+vt 0.670392 0.566231
+vt 0.805029 0.190637
+vt 0.683774 0.509644
+vt 0.551952 0.822879
+vt 0.929896 0.162212
+vt 0.855308 0.155368
+vt 0.697011 0.452991
+vt 0.591182 0.780044
+vt 0.710973 0.396675
+vt 0.618699 0.730484
+vt 0.726705 0.341193
+vt 0.639317 0.677374
+vt 0.745717 0.287291
+vt 0.655961 0.622327
+vt 0.665037 0.288362
+vt 0.594131 0.638967
+vt 0.684737 0.232805
+vt 0.605914 0.580518
+vt 0.713639 0.180821
+vt 0.616615 0.521685
+vt 0.760428 0.136665
+vt 0.627046 0.462757
+vt 0.536166 0.805967
+vt 0.923407 0.138703
+vt 0.836350 0.110271
+vt 0.637946 0.403995
+vt 0.562017 0.752770
+vt 0.650183 0.345708
+vt 0.580132 0.696622
+vt 0.517784 0.794886
+vt 0.927547 0.114679
+vt 0.833915 0.062714
+vt 0.573887 0.368405
+vt 0.531336 0.735069
+vt 0.581872 0.307471
+vt 0.540867 0.674443
+vt 0.592315 0.247027
+vt 0.548386 0.613417
+vt 0.607782 0.187599
+vt 0.554891 0.552189
+vt 0.635028 0.130630
+vt 0.560989 0.490881
+vt 0.696016 0.081502
+vt 0.567156 0.429587
+vt 0.502796 0.540546
+vt 0.515221 0.103359
+vt 0.503585 0.478066
+vt 0.534176 0.041338
+vt 0.504397 0.415586
+vt 0.498129 0.790428
+vt 0.948527 0.094534
+vt 0.947682 0.023355
+vt 0.505301 0.353109
+vt 0.499822 0.727971
+vt 0.506404 0.290637
+vt 0.501015 0.665501
+vt 0.507907 0.228175
+vt 0.501965 0.603025
+vt 0.510297 0.165735
+vt 0.429787 0.300102
+vt 0.461079 0.670468
+vt 0.421503 0.238833
+vt 0.455439 0.608804
+vt 0.408873 0.178221
+vt 0.445911 0.485214
+vt 0.385287 0.119299
+vt 1.323807 0.066742
+vt 0.441200 0.423411
+vt 0.478319 0.792904
+vt 0.988610 0.084811
+vt 1.147808 0.049313
+vt 0.436013 0.361679
+vt 0.468202 0.731911
+vt 0.147808 0.049313
+vt 0.323807 0.066742
+vt 0.245300 0.120661
+vt 0.380347 0.451545
+vt 0.459449 0.802140
+vt 0.032175 0.090357
+vt 0.157607 0.096698
+vt 0.370446 0.392059
+vt 0.437209 0.746646
+vt 0.359136 0.333015
+vt 0.421611 0.688967
+vt 0.345097 0.274835
+vt 0.450536 0.547029
+vt 0.409472 0.630184
+vt 0.325892 0.218332
+vt 0.399158 0.570827
+vt 0.296385 0.165337
+vt 0.389690 0.511206
+vt 0.261226 0.271289
+vt 0.348920 0.609159
+vt 0.235147 0.220347
+vt 0.335478 0.552316
+vt 0.197492 0.175520
+vt 0.322818 0.495127
+vt 0.442759 0.817485
+vt 0.058962 0.108237
+vt 0.141452 0.142821
+vt 0.310089 0.437969
+vt 0.407539 0.771302
+vt 0.296418 0.381227
+vt 0.382867 0.719668
+vt 0.280693 0.325402
+vt 0.364196 0.665186
+vt 0.223996 0.380807
+vt 0.318709 0.710884
+vt 0.206593 0.326609
+vt 0.298714 0.658036
+vt 0.185241 0.274482
+vt 0.282092 0.603438
+vt 0.157173 0.226088
+vt 0.267315 0.547906
+vt 0.118082 0.184573
+vt 0.253309 0.491989
+vt 0.379896 0.804521
+vt 0.067268 0.131697
+vt 0.063300 0.155685
+vt 0.239178 0.436135
+vt 0.344487 0.760584
+vt 0.091182 0.219955
+vt 0.197011 0.547009
+vt 0.429896 0.837788
+vt 0.355308 0.844632
+vt 0.051952 0.177121
+vt 0.183774 0.490356
+vt 0.305029 0.809363
+vt 0.170392 0.433769
+vt 0.270454 0.763731
+vt 0.155961 0.377673
+vt 0.245717 0.712709
+vt 0.226706 0.658807
+vt 0.118699 0.269516
+vt 0.210973 0.603325
+vt 0.139317 0.322626
+vt 0.094131 0.361033
+vt 0.165037 0.711638
+vt 0.080132 0.303378
+vt 0.150183 0.654292
+vt 0.062017 0.247229
+vt 0.137946 0.596005
+vt 0.423407 0.861296
+vt 0.336350 0.889729
+vt 0.036166 0.194032
+vt 0.127046 0.537243
+vt 0.260428 0.863335
+vt 0.116615 0.478315
+vt 0.213639 0.819179
+vt 0.105914 0.419482
+vt 0.184737 0.767195
+vt 0.495265 0.852856
+vt 0.995265 0.147143
+vt 1.032175 0.090357
+vt 1.058962 0.108237
+vt 1.067268 0.131697
+vt 1.063300 0.155685
+vt 1.051952 0.177121
+vt 0.427547 0.885321
+vt 1.036166 0.194032
+vt 0.031336 0.264931
+vt 0.017784 0.205113
+vn -0.563891 -0.825831 0.000000
+vn -0.713095 -0.701041 0.000000
+vn -0.520981 -0.825831 -0.215796
+vn -0.981231 0.192785 0.000000
+vn -0.925596 0.378430 0.000000
+vn -0.906522 0.192785 -0.375500
+vn -0.393017 -0.919523 0.000000
+vn -0.363109 -0.919523 -0.150395
+vn -1.000000 0.000000 0.000000
+vn -0.923856 0.000000 -0.382672
+vn -0.393017 0.919523 0.000000
+vn -0.206793 0.978362 0.000000
+vn -0.363109 0.919523 -0.150395
+vn -0.206793 -0.978362 0.000000
+vn -0.191046 -0.978362 -0.079134
+vn -0.981231 -0.192785 0.000000
+vn -0.906522 -0.192785 -0.375500
+vn -0.563891 0.825831 0.000000
+vn -0.520981 0.825831 -0.215796
+vn -0.925596 -0.378430 0.000000
+vn -0.855159 -0.378430 -0.354198
+vn -0.713095 0.701041 0.000000
+vn -0.658803 0.701041 -0.272866
+vn -0.835139 -0.550005 0.000000
+vn -0.771569 -0.550005 -0.319590
+vn -0.835139 0.550005 0.000000
+vn -0.771569 0.550005 -0.319590
+vn -0.658803 -0.701041 -0.272866
+vn -0.855159 0.378430 -0.354198
+vn -0.146214 -0.978362 -0.146214
+vn -0.693838 -0.192785 -0.693838
+vn -0.398724 0.825831 -0.398724
+vn -0.654500 -0.378430 -0.654500
+vn -0.504227 0.701041 -0.504227
+vn -0.590533 -0.550005 -0.590533
+vn -0.590533 0.550005 -0.590533
+vn -0.504227 -0.701041 -0.504227
+vn -0.654500 0.378430 -0.654500
+vn -0.398724 -0.825831 -0.398724
+vn -0.693838 0.192785 -0.693838
+vn -0.277902 -0.919523 -0.277902
+vn -0.707083 0.000000 -0.707083
+vn -0.191046 0.978362 -0.079134
+vn -0.277902 0.919523 -0.277902
+vn -0.215796 -0.825831 -0.520981
+vn -0.375500 0.192785 -0.906522
+vn -0.150395 -0.919523 -0.363109
+vn -0.382672 0.000000 -0.923856
+vn -0.146214 0.978362 -0.146214
+vn -0.150395 0.919523 -0.363109
+vn -0.079134 -0.978362 -0.191046
+vn -0.375500 -0.192785 -0.906522
+vn -0.215796 0.825831 -0.520981
+vn -0.354198 -0.378430 -0.855159
+vn -0.272866 0.701041 -0.658803
+vn -0.319590 -0.550005 -0.771569
+vn -0.319590 0.550005 -0.771569
+vn -0.272866 -0.701041 -0.658803
+vn -0.354198 0.378430 -0.855159
+vn 0.000000 -0.550005 -0.835139
+vn 0.000000 0.550005 -0.835139
+vn 0.000000 -0.701041 -0.713095
+vn 0.000000 0.378430 -0.925596
+vn 0.000000 -0.825831 -0.563891
+vn 0.000000 0.192785 -0.981231
+vn 0.000000 -0.919523 -0.393017
+vn 0.000000 0.000000 -1.000000
+vn -0.079134 0.978362 -0.191046
+vn 0.000000 0.919523 -0.393017
+vn 0.000000 -0.978362 -0.206793
+vn 0.000000 -0.192785 -0.981231
+vn 0.000000 0.825831 -0.563891
+vn 0.000000 -0.378430 -0.925596
+vn 0.000000 0.701041 -0.713095
+vn 0.150395 -0.919523 -0.363109
+vn 0.382672 0.000000 -0.923856
+vn 0.150395 0.919523 -0.363109
+vn 0.375500 -0.192785 -0.906522
+vn 0.215796 0.825831 -0.520981
+vn 0.354198 -0.378430 -0.855159
+vn 0.272866 0.701041 -0.658803
+vn 0.319590 -0.550005 -0.771569
+vn 0.319590 0.550005 -0.771569
+vn 0.272866 -0.701041 -0.658803
+vn 0.354198 0.378430 -0.855159
+vn 0.215796 -0.825831 -0.520981
+vn 0.375500 0.192785 -0.906522
+vn 0.000000 0.978362 -0.206793
+vn 0.079134 0.978362 -0.191046
+vn 0.504227 -0.701041 -0.504227
+vn 0.654500 0.378430 -0.654500
+vn 0.398724 -0.825831 -0.398724
+vn 0.693838 0.192785 -0.693838
+vn 0.146214 0.978362 -0.146214
+vn 0.079134 -0.978362 -0.191046
+vn 0.277902 -0.919523 -0.277902
+vn 0.707083 0.000000 -0.707083
+vn 0.277902 0.919523 -0.277902
+vn 0.693838 -0.192785 -0.693838
+vn 0.398724 0.825831 -0.398724
+vn 0.654500 -0.378430 -0.654500
+vn 0.504227 0.701041 -0.504227
+vn 0.590533 -0.550005 -0.590533
+vn 0.590533 0.550005 -0.590533
+vn 0.855159 -0.378430 -0.354198
+vn 0.658803 0.701041 -0.272866
+vn 0.771569 -0.550005 -0.319590
+vn 0.771569 0.550005 -0.319590
+vn 0.658803 -0.701041 -0.272866
+vn 0.855159 0.378430 -0.354198
+vn 0.520981 -0.825831 -0.215796
+vn 0.906522 0.192785 -0.375500
+vn 0.191046 0.978362 -0.079134
+vn 0.146214 -0.978362 -0.146214
+vn 0.363109 -0.919523 -0.150395
+vn 0.923856 0.000000 -0.382672
+vn 0.363109 0.919523 -0.150395
+vn 0.906522 -0.192785 -0.375500
+vn 0.520981 0.825831 -0.215796
+vn 0.206793 0.978362 0.000000
+vn 0.191046 -0.978362 -0.079134
+vn 0.393017 -0.919523 0.000000
+vn 1.000000 0.000000 0.000000
+vn 0.393017 0.919523 0.000000
+vn 0.981231 -0.192785 0.000000
+vn 0.563891 0.825831 0.000000
+vn 0.925596 -0.378430 0.000000
+vn 0.713095 0.701041 0.000000
+vn 0.835139 -0.550005 0.000000
+vn 0.835139 0.550005 0.000000
+vn 0.713095 -0.701041 0.000000
+vn 0.925596 0.378430 0.000000
+vn 0.563891 -0.825831 0.000000
+vn 0.981231 0.192785 0.000000
+vn 0.771569 0.550005 0.319590
+vn 0.658803 -0.701041 0.272866
+vn 0.855159 0.378430 0.354198
+vn 0.520981 -0.825831 0.215796
+vn 0.906522 0.192785 0.375500
+vn 0.191046 0.978362 0.079134
+vn 0.206793 -0.978362 0.000000
+vn 0.363109 -0.919523 0.150395
+vn 0.923856 0.000000 0.382672
+vn 0.363109 0.919523 0.150395
+vn 0.906522 -0.192785 0.375500
+vn 0.520981 0.825831 0.215796
+vn 0.855159 -0.378430 0.354198
+vn 0.658803 0.701041 0.272866
+vn 0.771569 -0.550005 0.319590
+vn 0.693838 -0.192785 0.693838
+vn 0.398724 0.825831 0.398724
+vn 0.654500 -0.378430 0.654500
+vn 0.504227 0.701041 0.504227
+vn 0.590533 -0.550005 0.590533
+vn 0.654500 0.378430 0.654500
+vn 0.504227 -0.701041 0.504227
+vn 0.398724 -0.825831 0.398724
+vn 0.693838 0.192785 0.693838
+vn 0.146214 0.978362 0.146214
+vn 0.191046 -0.978362 0.079134
+vn 0.277902 -0.919523 0.277902
+vn 0.707083 0.000000 0.707083
+vn 0.277902 0.919523 0.277902
+vn 0.215796 -0.825831 0.520981
+vn 0.375500 0.192785 0.906522
+vn 0.079134 0.978362 0.191046
+vn 0.146214 -0.978362 0.146214
+vn 0.150395 -0.919523 0.363109
+vn 0.382672 0.000000 0.923856
+vn 0.150395 0.919523 0.363109
+vn 0.375500 -0.192785 0.906522
+vn 0.215796 0.825831 0.520981
+vn 0.354198 -0.378430 0.855159
+vn 0.590533 0.550005 0.590533
+vn 0.272866 0.701041 0.658803
+vn 0.319590 -0.550005 0.771569
+vn 0.319590 0.550005 0.771569
+vn 0.272866 -0.701041 0.658803
+vn 0.354198 0.378430 0.855159
+vn 0.000000 -0.550005 0.835139
+vn 0.000000 0.550005 0.835139
+vn 0.000000 -0.701041 0.713095
+vn 0.000000 0.378430 0.925596
+vn 0.000000 -0.825831 0.563891
+vn 0.000000 0.192785 0.981231
+vn 0.000000 0.978362 0.206793
+vn 0.079134 -0.978362 0.191046
+vn 0.000000 -0.919523 0.393017
+vn 0.000000 0.000000 1.000000
+vn 0.000000 0.919523 0.393017
+vn 0.000000 -0.192785 0.981231
+vn 0.000000 0.825831 0.563891
+vn 0.000000 -0.378430 0.925596
+vn 0.000000 0.701041 0.713095
+vn -0.354198 -0.378430 0.855159
+vn -0.272866 0.701041 0.658803
+vn -0.319590 -0.550005 0.771569
+vn -0.319590 0.550005 0.771569
+vn -0.272866 -0.701041 0.658803
+vn -0.354198 0.378430 0.855159
+vn -0.215796 -0.825831 0.520981
+vn -0.375500 0.192785 0.906522
+vn -0.150395 -0.919523 0.363109
+vn -0.382672 0.000000 0.923856
+vn -0.150395 0.919523 0.363109
+vn 0.000000 -0.978362 0.206793
+vn -0.079134 -0.978362 0.191046
+vn -0.375500 -0.192785 0.906522
+vn -0.215796 0.825831 0.520981
+vn -0.277902 -0.919523 0.277902
+vn -0.707083 0.000000 0.707083
+vn -0.079134 0.978362 0.191046
+vn -0.277902 0.919523 0.277902
+vn -0.146214 -0.978362 0.146214
+vn -0.693838 -0.192785 0.693838
+vn -0.398724 0.825831 0.398724
+vn -0.654500 -0.378430 0.654500
+vn -0.504227 0.701041 0.504227
+vn -0.590533 -0.550005 0.590533
+vn -0.590533 0.550005 0.590533
+vn -0.654500 0.378430 0.654500
+vn -0.398724 -0.825831 0.398724
+vn -0.693838 0.192785 0.693838
+vn -0.504227 -0.701041 0.504227
+vn -0.658803 -0.701041 0.272866
+vn -0.855159 0.378430 0.354198
+vn -0.520981 -0.825831 0.215796
+vn -0.906522 0.192785 0.375500
+vn -0.363109 -0.919523 0.150395
+vn -0.923856 0.000000 0.382672
+vn -0.146214 0.978362 0.146214
+vn -0.363109 0.919523 0.150395
+vn -0.191046 -0.978362 0.079134
+vn -0.906522 -0.192785 0.375500
+vn -0.520981 0.825831 0.215796
+vn -0.855159 -0.378430 0.354198
+vn -0.658803 0.701041 0.272866
+vn -0.771569 -0.550005 0.319590
+vn -0.771569 0.550005 0.319590
+vn 0.000000 0.999969 0.000000
+vn 0.000000 -1.000000 0.000000
+vn -0.191046 0.978362 0.079134
+s 1
+f 13/1/1 12/2/2 28/3/3
+f 7/4/4 6/5/5 22/6/6
+f 14/7/7 13/8/1 29/9/8
+f 8/10/9 7/4/4 23/11/10
+f 2/12/11 1/13/12 17/14/13
+f 15/15/14 14/7/7 30/16/15
+f 9/17/16 8/10/9 24/18/17
+f 3/19/18 2/12/11 18/20/19
+f 10/21/20 9/17/16 25/22/21
+f 4/23/22 3/19/18 19/24/23
+f 11/25/24 10/21/20 26/26/25
+f 5/27/26 4/23/22 20/28/27
+f 12/2/2 11/25/24 27/29/28
+f 6/5/5 5/27/26 21/30/29
+f 30/16/15 29/9/8 46/31/30
+f 24/32/17 23/33/10 40/34/31
+f 18/35/19 17/14/13 34/36/32
+f 25/37/21 24/32/17 41/38/33
+f 19/39/23 18/35/19 35/40/34
+f 26/41/25 25/37/21 42/42/35
+f 20/43/27 19/39/23 36/44/36
+f 27/45/28 26/41/25 43/46/37
+f 21/47/29 20/43/27 37/48/38
+f 28/49/3 27/45/28 44/50/39
+f 22/51/6 21/47/29 38/52/40
+f 29/9/8 28/49/3 45/53/41
+f 23/33/10 22/51/6 39/54/42
+f 17/14/13 16/55/43 33/56/44
+f 44/50/39 43/46/37 59/57/45
+f 38/52/40 37/48/38 53/58/46
+f 45/53/41 44/50/39 60/59/47
+f 39/54/42 38/52/40 54/60/48
+f 33/56/44 32/61/49 48/62/50
+f 46/31/30 45/53/41 61/63/51
+f 40/34/31 39/54/42 55/64/52
+f 34/36/32 33/56/44 49/65/53
+f 41/38/33 40/34/31 56/66/54
+f 35/40/34 34/36/32 50/67/55
+f 42/42/35 41/38/33 57/68/56
+f 36/44/36 35/40/34 51/69/57
+f 43/46/37 42/42/35 58/70/58
+f 37/48/38 36/44/36 52/71/59
+f 57/68/56 56/66/54 72/72/60
+f 51/69/57 50/67/55 66/73/61
+f 58/70/58 57/68/56 73/74/62
+f 52/71/59 51/69/57 67/75/63
+f 59/57/45 58/70/58 74/76/64
+f 53/58/46 52/71/59 68/77/65
+f 60/59/47 59/57/45 75/78/66
+f 54/60/48 53/58/46 69/79/67
+f 48/62/50 47/80/68 63/81/69
+f 61/63/51 60/59/47 76/82/70
+f 55/64/52 54/60/48 70/83/71
+f 49/65/53 48/62/50 64/84/72
+f 56/66/54 55/64/52 71/85/73
+f 50/67/55 49/65/53 65/86/74
+f 76/82/70 75/78/66 90/87/75
+f 70/83/71 69/79/67 84/88/76
+f 64/84/72 63/81/69 78/89/77
+f 71/85/73 70/83/71 85/90/78
+f 65/86/74 64/84/72 79/91/79
+f 72/72/60 71/85/73 86/92/80
+f 66/73/61 65/86/74 80/93/81
+f 73/74/62 72/72/60 87/94/82
+f 67/75/63 66/73/61 81/95/83
+f 74/76/64 73/74/62 88/96/84
+f 68/77/65 67/75/63 82/97/85
+f 75/78/66 74/76/64 89/98/86
+f 69/79/67 68/77/65 83/99/87
+f 63/81/69 62/100/88 77/101/89
+f 89/98/86 88/96/84 103/102/90
+f 83/99/87 82/97/85 97/103/91
+f 90/87/75 89/98/86 104/104/92
+f 84/88/76 83/99/87 98/105/93
+f 78/89/77 77/101/89 92/106/94
+f 91/107/95 90/87/75 105/108/96
+f 85/90/78 84/88/76 99/109/97
+f 79/91/79 78/89/77 93/110/98
+f 86/92/80 85/90/78 100/111/99
+f 80/93/81 79/91/79 94/112/100
+f 87/94/82 86/92/80 101/113/101
+f 81/95/83 80/93/81 95/114/102
+f 88/96/84 87/94/82 102/115/103
+f 82/97/85 81/95/83 96/116/104
+f 102/115/103 101/113/101 116/117/105
+f 96/116/104 95/114/102 110/118/106
+f 103/102/90 102/115/103 117/119/107
+f 97/103/91 96/116/104 111/120/108
+f 104/104/92 103/102/90 118/121/109
+f 98/105/93 97/103/91 112/122/110
+f 105/108/96 104/104/92 119/123/111
+f 99/109/97 98/105/93 113/124/112
+f 93/110/98 92/106/94 107/125/113
+f 106/126/114 105/108/96 120/127/115
+f 100/111/99 99/109/97 114/128/116
+f 94/112/100 93/110/98 108/129/117
+f 101/113/101 100/111/99 115/130/118
+f 95/114/102 94/112/100 109/131/119
+f 108/129/117 107/125/113 122/132/120
+f 121/133/121 120/127/115 135/134/122
+f 115/130/118 114/128/116 129/135/123
+f 109/131/119 108/129/117 123/136/124
+f 116/117/105 115/130/118 130/137/125
+f 110/118/106 109/131/119 124/138/126
+f 117/119/107 116/117/105 131/139/127
+f 111/120/108 110/118/106 125/140/128
+f 118/121/109 117/119/107 132/141/129
+f 112/122/110 111/120/108 126/142/130
+f 119/123/111 118/121/109 133/143/131
+f 113/124/112 112/122/110 127/144/132
+f 120/127/115 119/123/111 134/145/133
+f 114/128/116 113/124/112 128/146/134
+f 127/144/132 126/142/130 141/147/135
+f 134/145/133 133/143/131 148/148/136
+f 128/146/134 127/144/132 142/149/137
+f 135/134/122 134/145/133 149/150/138
+f 129/135/123 128/146/134 143/151/139
+f 123/136/124 122/132/120 137/152/140
+f 136/153/141 135/134/122 150/154/142
+f 130/137/125 129/135/123 144/155/143
+f 124/138/126 123/136/124 138/156/144
+f 131/139/127 130/137/125 145/157/145
+f 125/140/128 124/138/126 139/158/146
+f 132/141/129 131/139/127 146/159/147
+f 126/142/130 125/140/128 140/160/148
+f 133/143/131 132/141/129 147/161/149
+f 146/159/147 145/157/145 160/162/150
+f 140/160/148 139/158/146 154/163/151
+f 147/161/149 146/159/147 161/164/152
+f 141/147/135 140/160/148 155/165/153
+f 148/148/136 147/161/149 162/166/154
+f 142/149/137 141/147/135 157/167/155
+f 149/150/138 148/148/136 163/168/156
+f 143/151/139 142/149/137 157/167/155
+f 150/154/142 149/150/138 164/169/157
+f 144/155/143 143/151/139 158/170/158
+f 138/156/144 137/152/140 152/171/159
+f 151/172/160 150/154/142 165/173/161
+f 145/157/145 144/155/143 159/174/162
+f 139/158/146 138/156/144 153/175/163
+f 165/176/161 164/177/157 179/178/164
+f 159/174/162 158/170/158 173/179/165
+f 153/175/163 152/171/159 167/180/166
+f 166/181/167 165/176/161 180/182/168
+f 160/162/150 159/174/162 174/183/169
+f 154/163/151 153/175/163 168/184/170
+f 161/164/152 160/162/150 175/185/171
+f 155/165/153 154/163/151 169/186/172
+f 162/166/154 161/164/152 176/187/173
+f 156/188/174 155/165/153 170/189/175
+f 163/168/156 162/166/154 177/190/176
+f 157/167/155 156/188/174 171/191/177
+f 164/177/157 163/168/156 178/192/178
+f 158/170/158 157/167/155 172/193/179
+f 178/192/178 177/190/176 192/194/180
+f 172/193/179 171/191/177 186/195/181
+f 179/178/164 178/192/178 193/196/182
+f 173/179/165 172/193/179 187/197/183
+f 180/182/168 179/178/164 194/198/184
+f 174/183/169 173/179/165 188/199/185
+f 168/184/170 167/180/166 182/200/186
+f 181/201/187 180/182/168 195/202/188
+f 175/185/171 174/183/169 189/203/189
+f 169/186/172 168/184/170 183/204/190
+f 176/187/173 175/185/171 190/205/191
+f 170/189/175 169/186/172 184/206/192
+f 177/190/176 176/187/173 191/207/193
+f 171/191/177 170/189/175 185/208/194
+f 191/207/193 190/205/191 206/209/195
+f 185/208/194 184/206/192 200/210/196
+f 192/194/180 191/207/193 207/211/197
+f 186/195/181 185/208/194 201/212/198
+f 193/196/182 192/194/180 208/213/199
+f 187/197/183 186/195/181 202/214/200
+f 194/198/184 193/196/182 209/215/201
+f 188/199/185 187/197/183 203/216/202
+f 195/202/188 194/198/184 210/217/203
+f 189/203/189 188/199/185 204/218/204
+f 183/204/190 182/200/186 198/219/205
+f 196/220/206 195/202/188 211/221/207
+f 190/205/191 189/203/189 205/222/208
+f 184/206/192 183/204/190 199/223/209
+f 210/217/203 209/215/201 226/224/210
+f 204/218/204 203/216/202 220/225/211
+f 198/219/205 197/226/212 214/227/213
+f 211/221/207 210/217/203 227/228/214
+f 205/222/208 204/218/204 221/229/215
+f 199/223/209 198/219/205 215/230/216
+f 206/209/195 205/222/208 222/231/217
+f 200/210/196 199/223/209 216/232/218
+f 207/211/197 206/209/195 223/233/219
+f 201/212/198 200/210/196 217/234/220
+f 208/213/199 207/211/197 223/233/219
+f 202/214/200 201/212/198 218/235/221
+f 209/215/201 208/213/199 225/236/222
+f 203/216/202 202/214/200 219/237/223
+f 224/238/224 223/233/219 239/239/225
+f 218/235/221 217/234/220 233/240/226
+f 225/236/222 224/238/224 240/241/227
+f 219/237/223 218/235/221 234/242/228
+f 226/224/210 225/236/222 241/243/229
+f 220/225/211 219/237/223 235/244/230
+f 214/227/213 213/245/231 229/246/232
+f 227/228/214 226/224/210 242/247/233
+f 221/229/215 220/225/211 236/248/234
+f 215/230/216 214/227/213 230/249/235
+f 222/231/217 221/229/215 237/250/236
+f 216/232/218 215/230/216 231/251/237
+f 223/233/219 222/231/217 238/252/238
+f 217/234/220 216/232/218 232/253/239
+f 1/13/12 212/254/240 16/55/43
+f 31/255/241 15/15/14 30/16/15
+f 31/255/241 30/16/15 46/31/30
+f 16/55/43 212/254/240 32/61/49
+f 32/61/49 212/254/240 47/80/68
+f 31/255/241 46/31/30 61/63/51
+f 47/80/68 212/254/240 62/100/88
+f 31/255/241 61/63/51 76/82/70
+f 31/255/241 76/82/70 91/107/95
+f 62/100/88 212/254/240 77/101/89
+f 77/101/89 212/254/240 92/106/94
+f 31/255/241 91/107/95 106/126/114
+f 92/106/94 212/254/240 107/125/113
+f 31/255/241 106/126/114 121/133/121
+f 31/255/241 121/133/121 136/153/141
+f 107/125/113 212/254/240 122/132/120
+f 122/132/120 212/254/240 137/152/140
+f 31/255/241 136/153/141 151/172/160
+f 137/152/140 212/254/240 152/171/159
+f 31/255/241 151/172/160 166/256/167
+f 31/255/241 166/256/167 181/257/187
+f 152/171/159 212/254/240 167/180/166
+f 167/180/166 212/254/240 182/200/186
+f 31/255/241 181/257/187 196/258/206
+f 31/255/241 196/258/206 211/259/207
+f 182/200/186 212/254/240 197/226/212
+f 31/255/241 211/259/207 227/260/214
+f 197/226/212 212/254/240 213/245/231
+f 213/245/231 212/254/240 228/261/242
+f 31/255/241 227/260/214 242/262/233
+f 31/255/241 242/262/233 15/15/14
+f 237/250/236 236/248/234 10/21/20
+f 231/251/237 230/249/235 4/23/22
+f 238/252/238 237/250/236 10/21/20
+f 232/253/239 231/251/237 5/27/26
+f 239/239/225 238/252/238 11/25/24
+f 233/240/226 232/253/239 6/5/5
+f 240/241/227 239/239/225 13/1/1
+f 234/242/228 233/240/226 7/4/4
+f 228/261/242 212/254/240 1/13/12
+f 241/243/229 240/241/227 14/263/7
+f 235/244/230 234/242/228 8/10/9
+f 229/246/232 228/261/242 2/12/11
+f 242/247/233 241/243/229 15/264/14
+f 236/248/234 235/244/230 9/17/16
+f 230/249/235 229/246/232 3/19/18
+f 12/2/2 27/29/28 28/3/3
+f 6/5/5 21/30/29 22/6/6
+f 13/8/1 28/49/3 29/9/8
+f 7/4/4 22/6/6 23/11/10
+f 1/13/12 16/55/43 17/14/13
+f 14/7/7 29/9/8 30/16/15
+f 8/10/9 23/11/10 24/18/17
+f 2/12/11 17/14/13 18/20/19
+f 9/17/16 24/18/17 25/22/21
+f 3/19/18 18/20/19 19/24/23
+f 10/21/20 25/22/21 26/26/25
+f 4/23/22 19/24/23 20/28/27
+f 11/25/24 26/26/25 27/29/28
+f 5/27/26 20/28/27 21/30/29
+f 29/9/8 45/53/41 46/31/30
+f 23/33/10 39/54/42 40/34/31
+f 17/14/13 33/56/44 34/36/32
+f 24/32/17 40/34/31 41/38/33
+f 18/35/19 34/36/32 35/40/34
+f 25/37/21 41/38/33 42/42/35
+f 19/39/23 35/40/34 36/44/36
+f 26/41/25 42/42/35 43/46/37
+f 20/43/27 36/44/36 37/48/38
+f 27/45/28 43/46/37 44/50/39
+f 21/47/29 37/48/38 38/52/40
+f 28/49/3 44/50/39 45/53/41
+f 22/51/6 38/52/40 39/54/42
+f 16/55/43 32/61/49 33/56/44
+f 43/46/37 58/70/58 59/57/45
+f 37/48/38 52/71/59 53/58/46
+f 44/50/39 59/57/45 60/59/47
+f 38/52/40 53/58/46 54/60/48
+f 32/61/49 47/80/68 48/62/50
+f 45/53/41 60/59/47 61/63/51
+f 39/54/42 54/60/48 55/64/52
+f 33/56/44 48/62/50 49/65/53
+f 40/34/31 55/64/52 56/66/54
+f 34/36/32 49/65/53 50/67/55
+f 41/38/33 56/66/54 57/68/56
+f 35/40/34 50/67/55 51/69/57
+f 42/42/35 57/68/56 58/70/58
+f 36/44/36 51/69/57 52/71/59
+f 56/66/54 71/85/73 72/72/60
+f 50/67/55 65/86/74 66/73/61
+f 57/68/56 72/72/60 73/74/62
+f 51/69/57 66/73/61 67/75/63
+f 58/70/58 73/74/62 74/76/64
+f 52/71/59 67/75/63 68/77/65
+f 59/57/45 74/76/64 75/78/66
+f 53/58/46 68/77/65 69/79/67
+f 47/80/68 62/100/88 63/81/69
+f 60/59/47 75/78/66 76/82/70
+f 54/60/48 69/79/67 70/83/71
+f 48/62/50 63/81/69 64/84/72
+f 55/64/52 70/83/71 71/85/73
+f 49/65/53 64/84/72 65/86/74
+f 91/107/95 76/82/70 90/87/75
+f 85/90/78 70/83/71 84/88/76
+f 79/91/79 64/84/72 78/89/77
+f 86/92/80 71/85/73 85/90/78
+f 80/93/81 65/86/74 79/91/79
+f 87/94/82 72/72/60 86/92/80
+f 81/95/83 66/73/61 80/93/81
+f 88/96/84 73/74/62 87/94/82
+f 82/97/85 67/75/63 81/95/83
+f 89/98/86 74/76/64 88/96/84
+f 83/99/87 68/77/65 82/97/85
+f 90/87/75 75/78/66 89/98/86
+f 84/88/76 69/79/67 83/99/87
+f 78/89/77 63/81/69 77/101/89
+f 104/104/92 89/98/86 103/102/90
+f 98/105/93 83/99/87 97/103/91
+f 105/108/96 90/87/75 104/104/92
+f 99/109/97 84/88/76 98/105/93
+f 93/110/98 78/89/77 92/106/94
+f 106/126/114 91/107/95 105/108/96
+f 100/111/99 85/90/78 99/109/97
+f 94/112/100 79/91/79 93/110/98
+f 101/113/101 86/92/80 100/111/99
+f 95/114/102 80/93/81 94/112/100
+f 102/115/103 87/94/82 101/113/101
+f 96/116/104 81/95/83 95/114/102
+f 103/102/90 88/96/84 102/115/103
+f 97/103/91 82/97/85 96/116/104
+f 117/119/107 102/115/103 116/117/105
+f 111/120/108 96/116/104 110/118/106
+f 118/121/109 103/102/90 117/119/107
+f 112/122/110 97/103/91 111/120/108
+f 119/123/111 104/104/92 118/121/109
+f 113/124/112 98/105/93 112/122/110
+f 120/127/115 105/108/96 119/123/111
+f 114/128/116 99/109/97 113/124/112
+f 108/129/117 93/110/98 107/125/113
+f 121/133/121 106/126/114 120/127/115
+f 115/130/118 100/111/99 114/128/116
+f 109/131/119 94/112/100 108/129/117
+f 116/117/105 101/113/101 115/130/118
+f 110/118/106 95/114/102 109/131/119
+f 123/136/124 108/129/117 122/132/120
+f 136/153/141 121/133/121 135/134/122
+f 130/137/125 115/130/118 129/135/123
+f 124/138/126 109/131/119 123/136/124
+f 131/139/127 116/117/105 130/137/125
+f 125/140/128 110/118/106 124/138/126
+f 132/141/129 117/119/107 131/139/127
+f 126/142/130 111/120/108 125/140/128
+f 133/143/131 118/121/109 132/141/129
+f 127/144/132 112/122/110 126/142/130
+f 134/145/133 119/123/111 133/143/131
+f 128/146/134 113/124/112 127/144/132
+f 135/134/122 120/127/115 134/145/133
+f 129/135/123 114/128/116 128/146/134
+f 142/149/137 127/144/132 141/147/135
+f 149/150/138 134/145/133 148/148/136
+f 143/151/139 128/146/134 142/149/137
+f 150/154/142 135/134/122 149/150/138
+f 144/155/143 129/135/123 143/151/139
+f 138/156/144 123/136/124 137/152/140
+f 151/172/160 136/153/141 150/154/142
+f 145/157/145 130/137/125 144/155/143
+f 139/158/146 124/138/126 138/156/144
+f 146/159/147 131/139/127 145/157/145
+f 140/160/148 125/140/128 139/158/146
+f 147/161/149 132/141/129 146/159/147
+f 141/147/135 126/142/130 140/160/148
+f 148/148/136 133/143/131 147/161/149
+f 161/164/152 146/159/147 160/162/150
+f 155/165/153 140/160/148 154/163/151
+f 162/166/154 147/161/149 161/164/152
+f 156/188/174 141/147/135 155/165/153
+f 163/168/156 148/148/136 162/166/154
+f 141/147/135 156/188/174 157/167/155
+f 164/177/157 149/150/138 163/168/156
+f 158/170/158 143/151/139 157/167/155
+f 165/173/161 150/154/142 164/169/157
+f 159/174/162 144/155/143 158/170/158
+f 153/175/163 138/156/144 152/171/159
+f 166/256/167 151/172/160 165/173/161
+f 160/162/150 145/157/145 159/174/162
+f 154/163/151 139/158/146 153/175/163
+f 180/182/168 165/176/161 179/178/164
+f 174/183/169 159/174/162 173/179/165
+f 168/184/170 153/175/163 167/180/166
+f 181/201/187 166/181/167 180/182/168
+f 175/185/171 160/162/150 174/183/169
+f 169/186/172 154/163/151 168/184/170
+f 176/187/173 161/164/152 175/185/171
+f 170/189/175 155/165/153 169/186/172
+f 177/190/176 162/166/154 176/187/173
+f 171/191/177 156/188/174 170/189/175
+f 178/192/178 163/168/156 177/190/176
+f 172/193/179 157/167/155 171/191/177
+f 179/178/164 164/177/157 178/192/178
+f 173/179/165 158/170/158 172/193/179
+f 193/196/182 178/192/178 192/194/180
+f 187/197/183 172/193/179 186/195/181
+f 194/198/184 179/178/164 193/196/182
+f 188/199/185 173/179/165 187/197/183
+f 195/202/188 180/182/168 194/198/184
+f 189/203/189 174/183/169 188/199/185
+f 183/204/190 168/184/170 182/200/186
+f 196/220/206 181/201/187 195/202/188
+f 190/205/191 175/185/171 189/203/189
+f 184/206/192 169/186/172 183/204/190
+f 191/207/193 176/187/173 190/205/191
+f 185/208/194 170/189/175 184/206/192
+f 192/194/180 177/190/176 191/207/193
+f 186/195/181 171/191/177 185/208/194
+f 190/205/191 205/222/208 206/209/195
+f 184/206/192 199/223/209 200/210/196
+f 191/207/193 206/209/195 207/211/197
+f 185/208/194 200/210/196 201/212/198
+f 192/194/180 207/211/197 208/213/199
+f 186/195/181 201/212/198 202/214/200
+f 193/196/182 208/213/199 209/215/201
+f 187/197/183 202/214/200 203/216/202
+f 194/198/184 209/215/201 210/217/203
+f 188/199/185 203/216/202 204/218/204
+f 182/200/186 197/226/212 198/219/205
+f 195/202/188 210/217/203 211/221/207
+f 189/203/189 204/218/204 205/222/208
+f 183/204/190 198/219/205 199/223/209
+f 209/215/201 225/236/222 226/224/210
+f 203/216/202 219/237/223 220/225/211
+f 197/226/212 213/245/231 214/227/213
+f 210/217/203 226/224/210 227/228/214
+f 204/218/204 220/225/211 221/229/215
+f 198/219/205 214/227/213 215/230/216
+f 205/222/208 221/229/215 222/231/217
+f 199/223/209 215/230/216 216/232/218
+f 206/209/195 222/231/217 223/233/219
+f 200/210/196 216/232/218 217/234/220
+f 224/238/224 208/213/199 223/233/219
+f 201/212/198 217/234/220 218/235/221
+f 208/213/199 224/238/224 225/236/222
+f 202/214/200 218/235/221 219/237/223
+f 223/233/219 238/252/238 239/239/225
+f 217/234/220 232/253/239 233/240/226
+f 224/238/224 239/239/225 240/241/227
+f 218/235/221 233/240/226 234/242/228
+f 225/236/222 240/241/227 241/243/229
+f 219/237/223 234/242/228 235/244/230
+f 213/245/231 228/261/242 229/246/232
+f 226/224/210 241/243/229 242/247/233
+f 220/225/211 235/244/230 236/248/234
+f 214/227/213 229/246/232 230/249/235
+f 221/229/215 236/248/234 237/250/236
+f 215/230/216 230/249/235 231/251/237
+f 222/231/217 237/250/236 238/252/238
+f 216/232/218 231/251/237 232/253/239
+f 236/248/234 9/17/16 10/21/20
+f 230/249/235 3/19/18 4/23/22
+f 11/25/24 238/252/238 10/21/20
+f 231/251/237 4/23/22 5/27/26
+f 12/2/2 239/239/225 11/25/24
+f 232/253/239 5/27/26 6/5/5
+f 239/239/225 12/2/2 13/1/1
+f 233/240/226 6/5/5 7/4/4
+f 240/241/227 13/1/1 14/263/7
+f 234/242/228 7/4/4 8/10/9
+f 228/261/242 1/13/12 2/12/11
+f 241/243/229 14/263/7 15/264/14
+f 235/244/230 8/10/9 9/17/16
+f 229/246/232 2/12/11 3/19/18
diff --git a/src/graphs/engine/q3dbars.cpp b/src/graphs/engine/q3dbars.cpp
new file mode 100644
index 0000000..371f3ad
--- /dev/null
+++ b/src/graphs/engine/q3dbars.cpp
@@ -0,0 +1,460 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "q3dbars.h"
+#include "qquickgraphsbars_p.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ * \class Q3DBars
+ * \inmodule QtGraphs
+ * \brief The Q3DBars class provides methods for rendering 3D bar graphs.
+ *
+ * This class enables developers to render bar graphs in 3D and to view them by rotating the scene
+ * freely. Rotation is done by holding down the right mouse button and moving the mouse. Zooming
+ * is done by mouse wheel. Selection, if enabled, is done by left mouse button. The scene can be
+ * reset to default camera view by clicking mouse wheel. In touch devices rotation is done
+ * by tap-and-move, selection by tap-and-hold and zoom by pinch.
+ *
+ * If no axes are set explicitly to Q3DBars, temporary default axes with no labels are created.
+ * These default axes can be modified via axis accessors, but as soon any axis is set explicitly
+ * for the orientation, the default axis for that orientation is destroyed.
+ *
+ * Q3DBars supports more than one series visible at the same time. It is not necessary for all series
+ * to have the same amount of rows and columns.
+ * Row and column labels are taken from the first added series, unless explicitly defined to
+ * row and column axes.
+ *
+ * \section1 How to construct a minimal Q3DBars graph
+ *
+ * First, construct an instance of Q3DBars. Since we are running the graph as top level window
+ * in this example, we need to clear the \c Qt::FramelessWindowHint flag, which gets set by
+ * default:
+ *
+ * \snippet doc_src_q3dbars_construction.cpp 4
+ *
+ * After constructing Q3DBars, you can set the data window by changing the range on the row and
+ * column axes. It is not mandatory, as data window will default to showing all of the data in
+ * the series. If the amount of data is large, it is usually preferable to show just a
+ * portion of it. For the example, let's set the data window to show first five rows and columns:
+ *
+ * \snippet doc_src_q3dbars_construction.cpp 0
+ *
+ * Now Q3DBars is ready to receive data to be rendered. Create a series with one row of 5 values:
+ *
+ * \snippet doc_src_q3dbars_construction.cpp 1
+ *
+ * \note We set the data window to 5 x 5, but we are adding only one row of data. This is ok,
+ * the rest of the rows will just be blank.
+ *
+ * Finally you will need to set it visible:
+ *
+ * \snippet doc_src_q3dbars_construction.cpp 2
+ *
+ * The complete code needed to create and display this graph is:
+ *
+ * \snippet doc_src_q3dbars_construction.cpp 3
+ *
+ * And this is what those few lines of code produce:
+ *
+ * \image q3dbars-minimal.png
+ *
+ * The scene can be rotated, zoomed into, and a bar can be selected to view its value,
+ * but no other interaction is included in this minimal code example. You can learn more by
+ * familiarizing yourself with the examples provided, like the \l{Bars Example}.
+ *
+ * \sa Q3DScatter, Q3DSurface, {Qt Graphs C++ Classes}
+ */
+
+/*!
+ * Constructs a new 3D bar graph with optional \a parent window
+ * and surface \a format.
+ */
+
+/*!
+ * Destroys the 3D bar graph.
+ */
+
+/*!
+ * \property Q3DBars::primarySeries
+ *
+ * \brief The primary series of the graph.
+ */
+
+/*!
+ * Sets \a series as the primary series of the graph. The primary series
+ * determines the row and column axis labels when the labels are not explicitly
+ * set to the axes.
+ *
+ * If the specified series is not yet added to the graph, setting it as the
+ * primary series will also implicitly add it to the graph.
+ *
+ * If the primary series itself is removed from the graph, this property
+ * resets to default.
+ *
+ * If \a series is null, this property resets to default.
+ * Defaults to the first added series or zero if no series are added to the graph.
+ */
+
+/*!
+ * Adds the \a series to the graph. A graph can contain multiple series, but only one set of axes,
+ * so the rows and columns of all series must match for the visualized data to be meaningful.
+ * If the graph has multiple visible series, only the primary series will
+ * generate the row or column labels on the axes in cases where the labels are not explicitly set
+ * to the axes. If the newly added series has specified a selected bar, it will be highlighted and
+ * any existing selection will be cleared. Only one added series can have an active selection.
+ *
+ * \sa seriesList(), primarySeries, QAbstract3DGraph::hasSeries()
+ */
+
+/*!
+ * Removes the \a series from the graph.
+ *
+ * \sa QAbstract3DGraph::hasSeries()
+ */
+
+/*!
+ * Inserts the \a series into the position \a index in the series list.
+ * If the \a series has already been added to the list, it is moved to the
+ * new \a index.
+ * \note When moving a series to a new \a index that is after its old index,
+ * the new position in list is calculated as if the series was still in its old
+ * index, so the final index is actually the \a index decremented by one.
+ *
+ * \sa addSeries(), seriesList(), QAbstract3DGraph::hasSeries()
+ */
+
+/*!
+ * Returns the list of series added to this graph.
+ *
+ * \sa QAbstract3DGraph::hasSeries()
+ */
+
+/*!
+ * \property Q3DBars::multiSeriesUniform
+ *
+ * \brief Whether bars are to be scaled with proportions set to a single series
+ * bar even if there are multiple series displayed.
+ *
+ * If set to \c {true}, \l{barSpacing}{bar spacing} will be correctly applied
+ * only to the X-axis. Preset to \c false by default.
+ */
+
+/*!
+ * \property Q3DBars::barThickness
+ *
+ * \brief The bar thickness ratio between the X and Z dimensions.
+ *
+ * The value \c 1.0 means that the bars are as wide as they are deep, whereas
+ *\c 0.5 makes them twice as deep as they are wide. Preset to \c 1.0 by default.
+ */
+
+/*!
+ * \property Q3DBars::barSpacing
+ *
+ * \brief Bar spacing in the X and Z dimensions.
+ *
+ * Preset to \c {(1.0, 1.0)} by default. Spacing is affected by the
+ * barSpacingRelative property.
+ *
+ * \sa barSpacingRelative, multiSeriesUniform, barSeriesMargin
+ */
+
+/*!
+ * \property Q3DBars::barSpacingRelative
+ *
+ * \brief Whether spacing is absolute or relative to bar thickness.
+ *
+ * If it is \c true, the value of \c 0.0 means that the bars are placed
+ * side-to-side, \c 1.0 means that a space as wide as the thickness of one bar
+ * is left between the bars, and so on. Preset to \c true.
+ */
+
+/*!
+ * \property Q3DBars::barSeriesMargin
+ * \since 6.3
+ *
+ * \brief Margin between series columns in X and Z dimensions.
+ * Sensible values are on the range [0,1).
+ *
+ * Preset to \c {(0.0, 0.0)} by default. This property enables
+ * showing bars from different series side by side, but with space between columns.
+ *
+ * \sa barSpacing
+ */
+
+/*!
+ * \property Q3DBars::rowAxis
+ *
+ * \brief The axis attached to the active row.
+ */
+
+/*!
+ * Sets the axis of the active row to \a axis. Implicitly calls addAxis() to
+ * transfer the ownership of the axis to this graph.
+ *
+ * If \a axis is null, a temporary default axis with no labels is created.
+ * This temporary axis is destroyed if another axis is set explicitly to the
+ * same orientation.
+ *
+ * \sa addAxis(), releaseAxis()
+ */
+
+/*!
+ * \property Q3DBars::columnAxis
+ *
+ * \brief The axis attached to the active column.
+ */
+
+/*!
+ * Sets the axis of the active column to \a axis. Implicitly calls addAxis() to
+ * transfer the ownership of the axis to this graph.
+ *
+ * If \a axis is null, a temporary default axis with no labels is created.
+ * This temporary axis is destroyed if another axis is set explicitly to the
+ * same orientation.
+ *
+ * \sa addAxis(), releaseAxis()
+ */
+
+/*!
+ * \property Q3DBars::valueAxis
+ *
+ * Sets the active value axis (the Y-axis) to \a axis. Implicitly calls
+ * addAxis() to transfer the ownership of \a axis to this graph.
+ *
+ * If \a axis is null, a temporary default axis with no labels and
+ * an automatically adjusting range is created.
+ * This temporary axis is destroyed if another axis is set explicitly to the
+ * same orientation.
+ *
+ * \sa addAxis(), releaseAxis()
+ */
+
+/*!
+ * \property Q3DBars::selectedSeries
+ *
+ * \brief The selected series or a null value.
+ *
+ * If selectionMode has the \c SelectionMultiSeries flag set, this
+ * property holds the series that owns the selected bar.
+ */
+
+/*!
+ * \property Q3DBars::floorLevel
+ *
+ * \brief The floor level for the bar graph in Y-axis data coordinates.
+ *
+ * The actual floor level will be restricted by the Y-axis minimum and maximum
+ * values.
+ * Defaults to zero.
+ */
+
+/*!
+ * Adds \a axis to the graph. The axes added via addAxis are not yet taken to use,
+ * addAxis is simply used to give the ownership of the \a axis to the graph.
+ * The \a axis must not be null or added to another graph.
+ *
+ * \sa releaseAxis(), setValueAxis(), setRowAxis(), setColumnAxis()
+ */
+
+/*!
+ * Releases the ownership of the \a axis back to the caller, if it is added to this graph.
+ * If the released \a axis is in use, a new default axis will be created and set active.
+ *
+ * If the default axis is released and added back later, it behaves as any other axis would.
+ *
+ * \sa addAxis(), setValueAxis(), setRowAxis(), setColumnAxis()
+ */
+
+/*!
+ * Returns the list of all added axes.
+ *
+ * \sa addAxis()
+ */
+
+Q3DBars::Q3DBars() : QAbstract3DGraph()
+{
+ QQmlComponent *component = new QQmlComponent(engine(), this);
+ component->setData("import QtQuick; import QtGraphs; Bars3D { anchors.fill: parent; }", QUrl());
+ d_ptr.reset(qobject_cast<QQuickGraphsBars *>(component->create()));
+ setContent(component->url(), component, d_ptr.data());
+}
+
+Q3DBars::~Q3DBars()
+{
+}
+
+void Q3DBars::setPrimarySeries(QBar3DSeries *series)
+{
+ dptr()->setPrimarySeries(series);
+ emit primarySeriesChanged(series);
+}
+
+QBar3DSeries *Q3DBars::primarySeries() const
+{
+ return dptrc()->primarySeries();
+}
+
+void Q3DBars::addSeries(QBar3DSeries *series)
+{
+ dptr()->addSeries(series);
+}
+
+void Q3DBars::removeSeries(QBar3DSeries *series)
+{
+ dptr()->removeSeries(series);
+}
+
+void Q3DBars::insertSeries(int index, QBar3DSeries *series)
+{
+ dptr()->insertSeries(index, series);
+}
+
+QList<QBar3DSeries *> Q3DBars::seriesList() const
+{
+ return dptrc()->m_barsController->barSeriesList();
+}
+
+void Q3DBars::setMultiSeriesUniform(bool uniform)
+{
+ dptr()->setMultiSeriesUniform(uniform);
+ emit multiSeriesUniformChanged(uniform);
+}
+
+bool Q3DBars::isMultiSeriesUniform() const
+{
+ return dptrc()->isMultiSeriesUniform();
+}
+
+void Q3DBars::setBarThickness(float thicknessRatio)
+{
+ dptr()->setBarThickness(thicknessRatio);
+ emit barThicknessChanged(thicknessRatio);
+}
+
+float Q3DBars::barThickness() const
+{
+ return dptrc()->barThickness();
+}
+
+void Q3DBars::setBarSpacing(const QSizeF &spacing)
+{
+ dptr()->setBarSpacing(spacing);
+ emit barSpacingChanged(spacing);
+}
+
+QSizeF Q3DBars::barSpacing() const
+{
+ return dptrc()->barSpacing();
+}
+
+void Q3DBars::setBarSpacingRelative(bool relative)
+{
+ dptr()->setBarSpacingRelative(relative);
+ emit barSpacingRelativeChanged(relative);
+}
+
+bool Q3DBars::isBarSpacingRelative() const
+{
+ return dptrc()->isBarSpacingRelative();
+}
+
+void Q3DBars::setBarSeriesMargin(const QSizeF &margin)
+{
+ dptr()->setBarSeriesMargin(margin);
+ emit barSeriesMarginChanged(margin);
+}
+
+QSizeF Q3DBars::barSeriesMargin() const
+{
+ return dptrc()->barSeriesMargin();
+}
+
+void Q3DBars::setRowAxis(QCategory3DAxis *axis)
+{
+ dptr()->setRowAxis(axis);
+ emit rowAxisChanged(axis);
+}
+
+QCategory3DAxis *Q3DBars::rowAxis() const
+{
+ return dptrc()->rowAxis();
+}
+
+void Q3DBars::setColumnAxis(QCategory3DAxis *axis)
+{
+ dptr()->setColumnAxis(axis);
+ emit columnAxisChanged(axis);
+}
+
+QCategory3DAxis *Q3DBars::columnAxis() const
+{
+ return dptrc()->columnAxis();
+}
+
+void Q3DBars::setValueAxis(QValue3DAxis *axis)
+{
+ dptr()->setValueAxis(axis);
+ emit valueAxisChanged(axis);
+}
+
+QValue3DAxis *Q3DBars::valueAxis() const
+{
+ return dptrc()->valueAxis();
+}
+
+QBar3DSeries *Q3DBars::selectedSeries() const
+{
+ return dptrc()->selectedSeries();
+}
+
+void Q3DBars::setFloorLevel(float level)
+{
+ dptr()->setFloorLevel(level);
+ emit floorLevelChanged(level);
+}
+
+float Q3DBars::floorLevel() const
+{
+ return dptrc()->floorLevel();
+}
+
+void Q3DBars::addAxis(QAbstract3DAxis *axis)
+{
+ dptr()->m_barsController->addAxis(axis);
+}
+
+void Q3DBars::releaseAxis(QAbstract3DAxis *axis)
+{
+ dptr()->m_barsController->releaseAxis(axis);
+}
+
+QList<QAbstract3DAxis *> Q3DBars::axes() const
+{
+ return dptrc()->m_barsController->axes();
+}
+
+bool Q3DBars::isReflection() const
+{
+ return dptrc()->isReflection();
+}
+
+void Q3DBars::setReflection(bool reflection)
+{
+ dptr()->setReflection(reflection);
+ emit reflectionChanged(reflection);
+}
+
+QQuickGraphsBars *Q3DBars::dptr()
+{
+ return static_cast<QQuickGraphsBars *>(d_ptr.data());
+}
+
+const QQuickGraphsBars *Q3DBars::dptrc() const
+{
+ return static_cast<const QQuickGraphsBars *>(d_ptr.data());
+}
+
+QT_END_NAMESPACE
diff --git a/src/graphs/engine/q3dbars.h b/src/graphs/engine/q3dbars.h
new file mode 100644
index 0000000..a8bc22a
--- /dev/null
+++ b/src/graphs/engine/q3dbars.h
@@ -0,0 +1,96 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef Q3DBARS_H
+#define Q3DBARS_H
+
+#include <QtGraphs/qabstract3dgraph.h>
+#include <QtGraphs/qvalue3daxis.h>
+#include <QtGraphs/qcategory3daxis.h>
+#include <QtGraphs/qbar3dseries.h>
+
+QT_BEGIN_NAMESPACE
+
+class QQuickGraphsBars;
+
+class Q_GRAPHS_EXPORT Q3DBars : public QAbstract3DGraph
+{
+ Q_OBJECT
+ Q_PROPERTY(bool multiSeriesUniform READ isMultiSeriesUniform WRITE setMultiSeriesUniform NOTIFY multiSeriesUniformChanged)
+ Q_PROPERTY(float barThickness READ barThickness WRITE setBarThickness NOTIFY barThicknessChanged)
+ Q_PROPERTY(QSizeF barSpacing READ barSpacing WRITE setBarSpacing NOTIFY barSpacingChanged)
+ Q_PROPERTY(bool barSpacingRelative READ isBarSpacingRelative WRITE setBarSpacingRelative NOTIFY barSpacingRelativeChanged)
+ Q_PROPERTY(QSizeF barSeriesMargin READ barSeriesMargin WRITE setBarSeriesMargin NOTIFY barSeriesMarginChanged)
+ Q_PROPERTY(QCategory3DAxis *rowAxis READ rowAxis WRITE setRowAxis NOTIFY rowAxisChanged)
+ Q_PROPERTY(QCategory3DAxis *columnAxis READ columnAxis WRITE setColumnAxis NOTIFY columnAxisChanged)
+ Q_PROPERTY(QValue3DAxis *valueAxis READ valueAxis WRITE setValueAxis NOTIFY valueAxisChanged)
+ Q_PROPERTY(QBar3DSeries *primarySeries READ primarySeries WRITE setPrimarySeries NOTIFY primarySeriesChanged)
+ Q_PROPERTY(QBar3DSeries *selectedSeries READ selectedSeries NOTIFY selectedSeriesChanged)
+ Q_PROPERTY(float floorLevel READ floorLevel WRITE setFloorLevel NOTIFY floorLevelChanged)
+ Q_PROPERTY(bool reflection READ isReflection WRITE setReflection NOTIFY reflectionChanged)
+
+public:
+ Q3DBars();
+ ~Q3DBars();
+
+ void setPrimarySeries(QBar3DSeries *series);
+ QBar3DSeries *primarySeries() const;
+ void addSeries(QBar3DSeries *series);
+ void removeSeries(QBar3DSeries *series);
+ void insertSeries(int index, QBar3DSeries *series);
+ QList<QBar3DSeries *> seriesList() const;
+
+ void setMultiSeriesUniform(bool uniform);
+ bool isMultiSeriesUniform() const;
+
+ void setBarThickness(float thicknessRatio);
+ float barThickness() const;
+
+ void setBarSpacing(const QSizeF &spacing);
+ QSizeF barSpacing() const;
+
+ void setBarSpacingRelative(bool relative);
+ bool isBarSpacingRelative() const;
+
+ void setBarSeriesMargin(const QSizeF &margin);
+ QSizeF barSeriesMargin() const;
+
+ void setRowAxis(QCategory3DAxis *axis);
+ QCategory3DAxis *rowAxis() const;
+ void setColumnAxis(QCategory3DAxis *axis);
+ QCategory3DAxis *columnAxis() const;
+ void setValueAxis(QValue3DAxis *axis);
+ QValue3DAxis *valueAxis() const;
+ void addAxis(QAbstract3DAxis *axis);
+ void releaseAxis(QAbstract3DAxis *axis);
+ QList<QAbstract3DAxis *> axes() const;
+
+ QBar3DSeries *selectedSeries() const;
+ void setFloorLevel(float level);
+ float floorLevel() const;
+
+ bool isReflection() const;
+ void setReflection(bool reflection);
+
+Q_SIGNALS:
+ void multiSeriesUniformChanged(bool uniform);
+ void barThicknessChanged(float thicknessRatio);
+ void barSpacingChanged(const QSizeF &spacing);
+ void barSpacingRelativeChanged(bool relative);
+ void barSeriesMarginChanged(const QSizeF &margin);
+ void rowAxisChanged(QCategory3DAxis *axis);
+ void columnAxisChanged(QCategory3DAxis *axis);
+ void valueAxisChanged(QValue3DAxis *axis);
+ void primarySeriesChanged(QBar3DSeries *series);
+ void selectedSeriesChanged(QBar3DSeries *series);
+ void floorLevelChanged(float level);
+ void reflectionChanged(bool reflection);
+
+private:
+ QQuickGraphsBars *dptr();
+ const QQuickGraphsBars *dptrc() const;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/engine/q3dcamera.cpp b/src/graphs/engine/q3dcamera.cpp
new file mode 100644
index 0000000..0565a19
--- /dev/null
+++ b/src/graphs/engine/q3dcamera.cpp
@@ -0,0 +1,911 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "q3dcamera_p.h"
+#include "utils_p.h"
+
+#include <QtCore/qmath.h>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ * \class Q3DCamera
+ * \inmodule QtGraphs
+ * \brief Representation of a camera in 3D space.
+ *
+ * Q3DCamera represents a basic orbit around centerpoint 3D camera that is used when rendering the
+ * graphs. The class offers simple methods for rotating the camera around the origin
+ * and setting the zoom level.
+ */
+
+/*!
+ * \enum Q3DCamera::CameraPreset
+ *
+ * Predefined positions for camera.
+ *
+ * \value CameraPresetNone
+ * Used to indicate a preset has not been set, or the scene has been rotated freely.
+ * \value CameraPresetFrontLow
+ * \value CameraPresetFront
+ * \value CameraPresetFrontHigh
+ * \value CameraPresetLeftLow
+ * \value CameraPresetLeft
+ * \value CameraPresetLeftHigh
+ * \value CameraPresetRightLow
+ * \value CameraPresetRight
+ * \value CameraPresetRightHigh
+ * \value CameraPresetBehindLow
+ * \value CameraPresetBehind
+ * \value CameraPresetBehindHigh
+ * \value CameraPresetIsometricLeft
+ * \value CameraPresetIsometricLeftHigh
+ * \value CameraPresetIsometricRight
+ * \value CameraPresetIsometricRightHigh
+ * \value CameraPresetDirectlyAbove
+ * \value CameraPresetDirectlyAboveCW45
+ * \value CameraPresetDirectlyAboveCCW45
+ * \value CameraPresetFrontBelow
+ * In Q3DBars from CameraPresetFrontBelow onward these only work for graphs including negative
+ * values. They act as Preset...Low for positive-only values.
+ * \value CameraPresetLeftBelow
+ * \value CameraPresetRightBelow
+ * \value CameraPresetBehindBelow
+ * \value CameraPresetDirectlyBelow
+ * Acts as CameraPresetFrontLow for positive-only bars.
+ */
+
+/*!
+ * \qmltype Camera3D
+ * \inqmlmodule QtGraphs
+ * \ingroup graphs_qml
+ * \instantiates Q3DCamera
+ * \brief Representation of a camera in 3D space.
+ *
+ * Camera3D represents a basic orbit around centerpoint 3D camera that is used when rendering the
+ * graphs. The type offers simple methods for rotating the camera around the origin
+ * and setting the zoom level.
+ *
+ * For Camera3D enums, see \l{Q3DCamera::CameraPreset}.
+ */
+
+/*!
+ * \qmlproperty float Camera3D::xRotation
+ *
+ * The X-rotation angle of the camera around the target point in degrees
+ * starting from the current base position.
+ */
+
+/*!
+ * \qmlproperty float Camera3D::yRotation
+ *
+ * The Y-rotation angle of the camera around the target point in degrees
+ * starting from the current base position.
+ */
+
+/*!
+ * \qmlproperty Camera3D.CameraPreset Camera3D::cameraPreset
+ *
+ * The currently active camera preset, which is one of
+ * \l{Q3DCamera::CameraPreset}{Camera3D.CameraPreset}. If no preset is active, the value
+ * is \l{Q3DCamera::CameraPresetNone}{Camera3D.CameraPresetNone}.
+ */
+
+/*!
+ * \qmlproperty float Camera3D::zoomLevel
+ *
+ * The camera zoom level in percentage. The default value of \c{100.0}
+ * means there is no zoom in or out set in the camera.
+ * The value is limited by the minZoomLevel and maxZoomLevel properties.
+ *
+ * \sa minZoomLevel, maxZoomLevel
+ */
+
+/*!
+ * \qmlproperty float Camera3D::minZoomLevel
+ *
+ * Sets the minimum allowed camera zoom level.
+ * If the new minimum level is higher than the existing maximum level, the maximum level is
+ * adjusted to the new minimum as well.
+ * If the current zoomLevel is outside the new bounds, it is adjusted as well.
+ * The minZoomLevel cannot be set below \c{1.0}.
+ * Defaults to \c{10.0}.
+ *
+ * \sa zoomLevel, maxZoomLevel
+ */
+
+/*!
+ * \qmlproperty float Camera3D::maxZoomLevel
+ *
+ * Sets the maximum allowed camera zoom level.
+ * If the new maximum level is lower than the existing minimum level, the minimum level is
+ * adjusted to the new maximum as well.
+ * If the current zoomLevel is outside the new bounds, it is adjusted as well.
+ * Defaults to \c{500.0f}.
+ *
+ * \sa zoomLevel, minZoomLevel
+ */
+
+/*!
+ * \qmlproperty bool Camera3D::wrapXRotation
+ *
+ * The behavior of the minimum and maximum limits in the X-rotation.
+ * By default, the X-rotation wraps from minimum value to maximum and from
+ * maximum to minimum.
+ *
+ * If set to \c true, the X-rotation of the camera is wrapped from minimum to
+ * maximum and from maximum to minimum. If set to \c false, the X-rotation of
+ * the camera is limited to the sector determined by the minimum and maximum
+ * values.
+ */
+
+/*!
+ * \qmlproperty bool Camera3D::wrapYRotation
+ *
+ * The behavior of the minimum and maximum limits in the Y-rotation.
+ * By default, the Y-rotation is limited between the minimum and maximum values
+ * without any wrapping.
+ *
+ * If \c true, the Y-rotation of the camera is wrapped from minimum to maximum
+ * and from maximum to minimum. If \c false, the Y-rotation of the camera is
+ * limited to the sector determined by the minimum and maximum values.
+ */
+
+/*!
+ * \qmlproperty vector3d Camera3D::target
+ *
+ * The camera target as a vector3d. Defaults to \c {vector3d(0.0, 0.0, 0.0)}.
+ *
+ * Valid coordinate values are between \c{-1.0...1.0}, where the edge values indicate
+ * the edges of the corresponding axis range. Any values outside this range are clamped to the edge.
+ *
+ * \note For bar graphs, the Y-coordinate is ignored and camera always targets a point on
+ * the horizontal background.
+ */
+
+/*!
+ * Constructs a new 3D camera with position set to origin, up direction facing towards the Y-axis
+ * and looking at origin by default. An optional \a parent parameter can be given and is then passed
+ * to QObject constructor.
+ */
+Q3DCamera::Q3DCamera(QObject *parent) :
+ Q3DObject(parent),
+ d_ptr(new Q3DCameraPrivate(this))
+{
+}
+
+/*!
+ * Destroys the camera object.
+ */
+Q3DCamera::~Q3DCamera()
+{
+}
+
+/*!
+ * Copies the 3D camera's properties from the given source camera.
+ * Values are copied from the \a source to this object.
+ */
+void Q3DCamera::copyValuesFrom(const Q3DObject &source)
+{
+ // Note: Do not copy values from parent, as we are handling the position internally
+
+ const Q3DCamera &sourceCamera = static_cast<const Q3DCamera &>(source);
+
+ d_ptr->m_requestedTarget = sourceCamera.d_ptr->m_requestedTarget;
+
+ d_ptr->m_xRotation = sourceCamera.d_ptr->m_xRotation;
+ d_ptr->m_yRotation = sourceCamera.d_ptr->m_yRotation;
+
+ d_ptr->m_minXRotation = sourceCamera.d_ptr->m_minXRotation;
+ d_ptr->m_minYRotation = sourceCamera.d_ptr->m_minYRotation;
+ d_ptr->m_maxXRotation = sourceCamera.d_ptr->m_maxXRotation;
+ d_ptr->m_maxYRotation = sourceCamera.d_ptr->m_maxYRotation;
+
+ d_ptr->m_wrapXRotation = sourceCamera.d_ptr->m_wrapXRotation;
+ d_ptr->m_wrapYRotation = sourceCamera.d_ptr->m_wrapYRotation;
+
+ d_ptr->m_zoomLevel = sourceCamera.d_ptr->m_zoomLevel;
+ d_ptr->m_minZoomLevel = sourceCamera.d_ptr->m_minZoomLevel;
+ d_ptr->m_maxZoomLevel = sourceCamera.d_ptr->m_maxZoomLevel;
+ d_ptr->m_activePreset = sourceCamera.d_ptr->m_activePreset;
+}
+
+/*!
+ * \property Q3DCamera::xRotation
+ *
+ * \brief The X-rotation angle of the camera around the target point in degrees.
+ */
+float Q3DCamera::xRotation() const {
+ return d_ptr->m_xRotation;
+}
+
+void Q3DCamera::setXRotation(float rotation)
+{
+ if (d_ptr->m_wrapXRotation) {
+ rotation = Utils::wrapValue(rotation, d_ptr->m_minXRotation, d_ptr->m_maxXRotation);
+ } else {
+ rotation = qBound(float(d_ptr->m_minXRotation), float(rotation),
+ float(d_ptr->m_maxXRotation));
+ }
+
+ if (d_ptr->m_xRotation != rotation) {
+ d_ptr->setXRotation(rotation);
+ if (d_ptr->m_activePreset != CameraPresetNone) {
+ d_ptr->m_activePreset = CameraPresetNone;
+ setDirty(true);
+ }
+
+ emit xRotationChanged(d_ptr->m_xRotation);
+ }
+}
+
+/*!
+ * \property Q3DCamera::yRotation
+ *
+ * \brief The Y-rotation angle of the camera around the target point in degrees.
+ */
+float Q3DCamera::yRotation() const {
+ return d_ptr->m_yRotation;
+}
+
+void Q3DCamera::setYRotation(float rotation)
+{
+ if (d_ptr->m_wrapYRotation) {
+ rotation = Utils::wrapValue(rotation, d_ptr->m_minYRotation, d_ptr->m_maxYRotation);
+ } else {
+ rotation = qBound(float(d_ptr->m_minYRotation), float(rotation),
+ float(d_ptr->m_maxYRotation));
+ }
+
+ if (d_ptr->m_yRotation != rotation) {
+ d_ptr->setYRotation(rotation);
+ if (d_ptr->m_activePreset != CameraPresetNone) {
+ d_ptr->m_activePreset = CameraPresetNone;
+ setDirty(true);
+ }
+
+ emit yRotationChanged(d_ptr->m_yRotation);
+ }
+}
+
+/*!
+ * \property Q3DCamera::cameraPreset
+ *
+ * \brief The currently active camera preset.
+ *
+ * If no CameraPreset value is set, CameraPresetNone is used by default.
+ */
+Q3DCamera::CameraPreset Q3DCamera::cameraPreset() const
+{
+ return d_ptr->m_activePreset;
+}
+
+void Q3DCamera::setCameraPreset(CameraPreset preset)
+{
+ switch (preset) {
+ case CameraPresetFrontLow: {
+ setXRotation(0.0f);
+ setYRotation(0.0f);
+ break;
+ }
+ case CameraPresetFront: {
+ setXRotation(0.0f);
+ setYRotation(22.5f);
+ break;
+ }
+ case CameraPresetFrontHigh: {
+ setXRotation(0.0f);
+ setYRotation(45.0f);
+ break;
+ }
+ case CameraPresetLeftLow: {
+ setXRotation(90.0f);
+ setYRotation(0.0f);
+ break;
+ }
+ case CameraPresetLeft: {
+ setXRotation(90.0f);
+ setYRotation(22.5f);
+ break;
+ }
+ case CameraPresetLeftHigh: {
+ setXRotation(90.0f);
+ setYRotation(45.0f);
+ break;
+ }
+ case CameraPresetRightLow: {
+ setXRotation(-90.0f);
+ setYRotation(0.0f);
+ break;
+ }
+ case CameraPresetRight: {
+ setXRotation(-90.0f);
+ setYRotation(22.5f);
+ break;
+ }
+ case CameraPresetRightHigh: {
+ setXRotation(-90.0f);
+ setYRotation(45.0f);
+ break;
+ }
+ case CameraPresetBehindLow: {
+ setXRotation(180.0f);
+ setYRotation(0.0f);
+ break;
+ }
+ case CameraPresetBehind: {
+ setXRotation(180.0f);
+ setYRotation(22.5f);
+ break;
+ }
+ case CameraPresetBehindHigh: {
+ setXRotation(180.0f);
+ setYRotation(45.0f);
+ break;
+ }
+ case CameraPresetIsometricLeft: {
+ setXRotation(45.0f);
+ setYRotation(22.5f);
+ break;
+ }
+ case CameraPresetIsometricLeftHigh: {
+ setXRotation(45.0f);
+ setYRotation(45.0f);
+ break;
+ }
+ case CameraPresetIsometricRight: {
+ setXRotation(-45.0f);
+ setYRotation(22.5f);
+ break;
+ }
+ case CameraPresetIsometricRightHigh: {
+ setXRotation(-45.0f);
+ setYRotation(45.0f);
+ break;
+ }
+ case CameraPresetDirectlyAbove: {
+ setXRotation(0.0f);
+ setYRotation(90.0f);
+ break;
+ }
+ case CameraPresetDirectlyAboveCW45: {
+ setXRotation(-45.0f);
+ setYRotation(90.0f);
+ break;
+ }
+ case CameraPresetDirectlyAboveCCW45: {
+ setXRotation(45.0f);
+ setYRotation(90.0f);
+ break;
+ }
+ case CameraPresetFrontBelow: {
+ setXRotation(0.0f);
+ setYRotation(-45.0f);
+ break;
+ }
+ case CameraPresetLeftBelow: {
+ setXRotation(90.0f);
+ setYRotation(-45.0f);
+ break;
+ }
+ case CameraPresetRightBelow: {
+ setXRotation(-90.0f);
+ setYRotation(-45.0f);
+ break;
+ }
+ case CameraPresetBehindBelow: {
+ setXRotation(180.0f);
+ setYRotation(-45.0f);
+ break;
+ }
+ case CameraPresetDirectlyBelow: {
+ setXRotation(0.0f);
+ setYRotation(-90.0f);
+ break;
+ }
+ default:
+ preset = CameraPresetNone;
+ break;
+ }
+
+ // All presets target the center of the graph
+ setTarget(zeroVector);
+
+ if (d_ptr->m_activePreset != preset) {
+ d_ptr->m_activePreset = preset;
+ setDirty(true);
+ emit cameraPresetChanged(preset);
+ }
+}
+
+/*!
+ * \property Q3DCamera::zoomLevel
+ *
+ * \brief The camera zoom level in percentage.
+ *
+ * The default value of \c{100.0f} means there is no zoom in or out set in the
+ * camera. The value is limited by the minZoomLevel and maxZoomLevel properties.
+ *
+ * \sa minZoomLevel, maxZoomLevel
+ */
+float Q3DCamera::zoomLevel() const
+{
+ return d_ptr->m_zoomLevel;
+}
+
+void Q3DCamera::setZoomLevel(float zoomLevel)
+{
+ float newZoomLevel = qBound(d_ptr->m_minZoomLevel, zoomLevel, d_ptr->m_maxZoomLevel);
+
+ if (d_ptr->m_zoomLevel != newZoomLevel) {
+ d_ptr->m_zoomLevel = newZoomLevel;
+ setDirty(true);
+ emit zoomLevelChanged(newZoomLevel);
+ }
+}
+
+/*!
+ * \property Q3DCamera::minZoomLevel
+ *
+ * \brief The minimum allowed camera zoom level.
+ *
+ * If the minimum level is set to a new value that is higher than the existing
+ * maximum level, the maximum level is adjusted to the new minimum as well.
+ * If the current zoomLevel is outside the new bounds, it is adjusted as well.
+ * The minZoomLevel cannot be set below \c{1.0f}.
+ * Defaults to \c{10.0f}.
+ *
+ * \sa zoomLevel, maxZoomLevel
+ */
+float Q3DCamera::minZoomLevel() const
+{
+ return d_ptr->m_minZoomLevel;
+}
+
+void Q3DCamera::setMinZoomLevel(float zoomLevel)
+{
+ // Don't allow minimum to be below one, as that can cause zoom to break.
+ float newMinLevel = qMax(zoomLevel, 1.0f);
+ if (d_ptr->m_minZoomLevel != newMinLevel) {
+ d_ptr->m_minZoomLevel = newMinLevel;
+ if (d_ptr->m_maxZoomLevel < newMinLevel)
+ setMaxZoomLevel(newMinLevel);
+ setZoomLevel(d_ptr->m_zoomLevel);
+ setDirty(true);
+ emit minZoomLevelChanged(newMinLevel);
+ }
+}
+
+/*!
+ * \property Q3DCamera::maxZoomLevel
+ *
+ * \brief The maximum allowed camera zoom level.
+ *
+ * If the maximum level is set to a new value that is lower than the existing
+ * minimum level, the minimum level is adjusted to the new maximum as well.
+ * If the current zoomLevel is outside the new bounds, it is adjusted as well.
+ * Defaults to \c{500.0f}.
+ *
+ * \sa zoomLevel, minZoomLevel
+ */
+float Q3DCamera::maxZoomLevel() const
+{
+ return d_ptr->m_maxZoomLevel;
+}
+
+void Q3DCamera::setMaxZoomLevel(float zoomLevel)
+{
+ // Don't allow maximum to be below one, as that can cause zoom to break.
+ float newMaxLevel = qMax(zoomLevel, 1.0f);
+ if (d_ptr->m_maxZoomLevel != newMaxLevel) {
+ d_ptr->m_maxZoomLevel = newMaxLevel;
+ if (d_ptr->m_minZoomLevel > newMaxLevel)
+ setMinZoomLevel(newMaxLevel);
+ setZoomLevel(d_ptr->m_zoomLevel);
+ setDirty(true);
+ emit maxZoomLevelChanged(newMaxLevel);
+ }
+}
+
+/*!
+ * \property Q3DCamera::wrapXRotation
+ *
+ * \brief The behavior of the minimum and maximum limits in the X-rotation.
+ *
+ * If set to \c true, the X-rotation of the camera is wrapped from minimum to
+ * maximum and from maximum to minimum. If set to \c false, the X-rotation of
+ * the camera is limited to the sector determined by the minimum and maximum
+ * values. Set to \c true by default.
+ */
+bool Q3DCamera::wrapXRotation() const
+{
+ return d_ptr->m_wrapXRotation;
+}
+
+void Q3DCamera::setWrapXRotation(bool isEnabled)
+{
+ d_ptr->m_wrapXRotation = isEnabled;
+}
+
+/*!
+ * \property Q3DCamera::wrapYRotation
+ *
+ * \brief The behavior of the minimum and maximum limits in the Y-rotation.
+ *
+ * If \c true, the Y-rotation of the camera is wrapped from minimum to maximum
+ * and from maximum to minimum. If \c false, the Y-rotation of the camera is
+ * limited to the sector determined by the minimum and maximum values.
+ * Set to \c true by default.
+ */
+bool Q3DCamera::wrapYRotation() const
+{
+ return d_ptr->m_wrapYRotation;
+}
+
+void Q3DCamera::setWrapYRotation(bool isEnabled)
+{
+ d_ptr->m_wrapYRotation = isEnabled;
+}
+
+/*!
+ * Utility function that sets the camera rotations and distance.\a horizontal and \a vertical
+ * define the camera rotations to be used.
+ * Optional \a zoom parameter can be given to set the zoom percentage of the camera within
+ * the bounds defined by minZoomLevel and maxZoomLevel properties.
+ */
+void Q3DCamera::setCameraPosition(float horizontal, float vertical, float zoom)
+{
+ setZoomLevel(zoom);
+ setXRotation(horizontal);
+ setYRotation(vertical);
+}
+
+/*!
+ * \property Q3DCamera::target
+ *
+ * \brief The camera target as a vector or vertex in the 3D space.
+ *
+ * Defaults to \c {QVector3D(0.0, 0.0, 0.0)}.
+ *
+ * Valid coordinate values are between \c{-1.0...1.0}, where the edge values indicate
+ * the edges of the corresponding axis range. Any values outside this range are clamped to the edge.
+ *
+ * \note For bar graphs, the Y-coordinate is ignored and camera always targets a point on
+ * the horizontal background.
+ */
+QVector3D Q3DCamera::target() const
+{
+ return d_ptr->m_requestedTarget;
+}
+
+void Q3DCamera::setTarget(const QVector3D &target)
+{
+ QVector3D newTarget = target;
+
+ if (newTarget.x() < -1.0f)
+ newTarget.setX(-1.0f);
+ else if (newTarget.x() > 1.0f)
+ newTarget.setX(1.0f);
+
+ if (newTarget.y() < -1.0f)
+ newTarget.setY(-1.0f);
+ else if (newTarget.y() > 1.0f)
+ newTarget.setY(1.0f);
+
+ if (newTarget.z() < -1.0f)
+ newTarget.setZ(-1.0f);
+ else if (newTarget.z() > 1.0f)
+ newTarget.setZ(1.0f);
+
+ if (d_ptr->m_requestedTarget != newTarget) {
+ if (d_ptr->m_activePreset != CameraPresetNone)
+ d_ptr->m_activePreset = CameraPresetNone;
+ d_ptr->m_requestedTarget = newTarget;
+ setDirty(true);
+ emit targetChanged(newTarget);
+ }
+}
+
+Q3DCameraPrivate::Q3DCameraPrivate(Q3DCamera *q) :
+ q_ptr(q),
+ m_isViewMatrixUpdateActive(true),
+ m_xRotation(0.0f),
+ m_yRotation(0.0f),
+ m_minXRotation(-180.0f),
+ m_minYRotation(0.0f),
+ m_maxXRotation(180.0f),
+ m_maxYRotation(90.0f),
+ m_zoomLevel(100.0f),
+ m_minZoomLevel(10.0f),
+ m_maxZoomLevel(500.0f),
+ m_wrapXRotation(true),
+ m_wrapYRotation(false),
+ m_activePreset(Q3DCamera::CameraPresetNone)
+{
+}
+
+Q3DCameraPrivate::~Q3DCameraPrivate()
+{
+}
+
+// Copies changed values from this camera to the other camera. If the other camera had same changes,
+// those changes are discarded.
+void Q3DCameraPrivate::sync(Q3DCamera &other)
+{
+ if (q_ptr->isDirty()) {
+ other.copyValuesFrom(*q_ptr);
+ q_ptr->setDirty(false);
+ other.setDirty(false);
+ }
+}
+
+void Q3DCameraPrivate::setXRotation(const float rotation)
+{
+ if (m_xRotation != rotation) {
+ m_xRotation = rotation;
+ q_ptr->setDirty(true);
+ }
+}
+
+void Q3DCameraPrivate::setYRotation(const float rotation)
+{
+ if (m_yRotation != rotation) {
+ m_yRotation = rotation;
+ q_ptr->setDirty(true);
+ }
+}
+
+/*!
+ * \internal
+ * The current minimum X-rotation for the camera.
+ * The full circle range is \c{[-180, 180]} and the minimum value is limited to \c -180.
+ * Also the value can't be higher than the maximum, and is adjusted if necessary.
+ *
+ * \sa wrapXRotation, maxXRotation
+ */
+float Q3DCameraPrivate::minXRotation() const
+{
+ return m_minXRotation;
+}
+
+void Q3DCameraPrivate::setMinXRotation(float minRotation)
+{
+ minRotation = qBound(-180.0f, minRotation, 180.0f);
+ if (minRotation > m_maxXRotation)
+ minRotation = m_maxXRotation;
+
+ if (m_minXRotation != minRotation) {
+ m_minXRotation = minRotation;
+ emit minXRotationChanged(minRotation);
+
+ if (m_xRotation < m_minXRotation)
+ setXRotation(m_xRotation);
+ q_ptr->setDirty(true);
+ }
+}
+
+/*!
+ * \internal
+ * The current minimum Y-rotation for the camera.
+ * The full Y angle range is \c{[-90, 90]} and the minimum value is limited to \c -90.
+ * Also the value can't be higher than the maximum, and is adjusted if necessary.
+ *
+ * \sa wrapYRotation, maxYRotation
+ */
+float Q3DCameraPrivate::minYRotation() const
+{
+ return m_minYRotation;
+}
+
+void Q3DCameraPrivate::setMinYRotation(float minRotation)
+{
+ minRotation = qBound(-90.0f, minRotation, 90.0f);
+ if (minRotation > m_maxYRotation)
+ minRotation = m_maxYRotation;
+
+ if (m_minYRotation != minRotation) {
+ m_minYRotation = minRotation;
+ emit minYRotationChanged(minRotation);
+
+ if (m_yRotation < m_minYRotation)
+ setYRotation(m_yRotation);
+ q_ptr->setDirty(true);
+ }
+}
+
+/*!
+ * \internal
+ * The current maximum X-rotation for the camera.
+ * The full circle range is \c{[-180, 180]} and the maximum value is limited to \c 180.
+ * Also the value can't be lower than the minimum, and is adjusted if necessary.
+ *
+ * \sa wrapXRotation, minXRotation
+ */
+float Q3DCameraPrivate::maxXRotation() const
+{
+ return m_maxXRotation;
+}
+
+void Q3DCameraPrivate::setMaxXRotation(float maxRotation)
+{
+ maxRotation = qBound(-180.0f, maxRotation, 180.0f);
+
+ if (maxRotation < m_minXRotation)
+ maxRotation = m_minXRotation;
+
+ if (m_maxXRotation != maxRotation) {
+ m_maxXRotation = maxRotation;
+ emit maxXRotationChanged(maxRotation);
+
+ if (m_xRotation > m_maxXRotation)
+ setXRotation(m_xRotation);
+ q_ptr->setDirty(true);
+ }
+}
+
+/*!
+ * \internal
+ * The current maximum Y-rotation for the camera.
+ * The full Y angle range is \c{[-90, 90]} and the maximum value is limited to \c 90.
+ * Also the value can't be lower than the minimum, and is adjusted if necessary.
+ *
+ * \sa wrapYRotation, minYRotation
+ */
+float Q3DCameraPrivate::maxYRotation() const
+{
+ return m_maxYRotation;
+}
+
+void Q3DCameraPrivate::setMaxYRotation(float maxRotation)
+{
+ maxRotation = qBound(-90.0f, maxRotation, 90.0f);
+
+ if (maxRotation < m_minYRotation)
+ maxRotation = m_minYRotation;
+
+ if (m_maxYRotation != maxRotation) {
+ m_maxYRotation = maxRotation;
+ emit maxYRotationChanged(maxRotation);
+
+ if (m_yRotation > m_maxYRotation)
+ setYRotation(m_yRotation);
+ q_ptr->setDirty(true);
+ }
+}
+
+// Recalculates the view matrix based on the currently set base orientation, rotation and zoom level values.
+// zoomAdjustment is adjustment to ensure that the 3D visualization stays inside the view area in the 100% zoom.
+void Q3DCameraPrivate::updateViewMatrix(float zoomAdjustment)
+{
+ if (!m_isViewMatrixUpdateActive)
+ return;
+
+ GLfloat zoom = m_zoomLevel * zoomAdjustment;
+ QMatrix4x4 viewMatrix;
+
+ // Apply to view matrix
+ viewMatrix.lookAt(q_ptr->position(), m_actualTarget, m_up);
+ // Compensate for translation (if d_ptr->m_target is off origin)
+ viewMatrix.translate(m_actualTarget.x(), m_actualTarget.y(), m_actualTarget.z());
+ // Apply rotations
+ // Handle x and z rotation when y -angle is other than 0
+ viewMatrix.rotate(m_xRotation, 0, qCos(qDegreesToRadians(m_yRotation)),
+ qSin(qDegreesToRadians(m_yRotation)));
+ // y rotation is always "clean"
+ viewMatrix.rotate(m_yRotation, 1.0f, 0.0f, 0.0f);
+ // handle zoom by scaling
+ viewMatrix.scale(zoom / 100.0f);
+ // Compensate for translation (if d_ptr->m_target is off origin)
+ viewMatrix.translate(-m_actualTarget.x(), -m_actualTarget.y(), -m_actualTarget.z());
+
+ setViewMatrix(viewMatrix);
+}
+
+/*!
+ * \internal
+ * The view matrix used in the 3D calculations. When the default orbiting
+ * camera behavior is sufficient, there is no need to touch this property. If the default
+ * behavior is insufficient, the view matrix can be set directly.
+ * \note When setting the view matrix directly remember to set viewMatrixAutoUpdateEnabled to
+ * \c false.
+ */
+QMatrix4x4 Q3DCameraPrivate::viewMatrix() const
+{
+ return m_viewMatrix;
+}
+
+void Q3DCameraPrivate::setViewMatrix(const QMatrix4x4 &viewMatrix)
+{
+ if (m_viewMatrix != viewMatrix) {
+ m_viewMatrix = viewMatrix;
+ q_ptr->setDirty(true);
+ emit viewMatrixChanged(m_viewMatrix);
+ }
+}
+
+/*!
+ * \internal
+ * This property determines if view matrix is automatically updated each render cycle using the
+ * current base orientation and rotations. If set to \c false, no automatic recalculation is done
+ * and the view matrix can be set using the viewMatrix property.
+ */
+bool Q3DCameraPrivate::isViewMatrixAutoUpdateEnabled() const
+{
+ return m_isViewMatrixUpdateActive;
+}
+
+void Q3DCameraPrivate::setViewMatrixAutoUpdateEnabled(bool isEnabled)
+{
+ m_isViewMatrixUpdateActive = isEnabled;
+ emit viewMatrixAutoUpdateChanged(isEnabled);
+}
+
+/*!
+ * \internal
+ * Sets the base values for the camera that are used when calculating the camera position using the
+ * rotation values. The base position of the camera is defined by \a basePosition, expectation is
+ * that the x and y values are 0. Look at target point is defined by \a target and the camera
+ * rotates around it. Up direction for the camera is defined by \a baseUp, normally this is a
+ * vector with only y value set to 1.
+ */
+void Q3DCameraPrivate::setBaseOrientation(const QVector3D &basePosition,
+ const QVector3D &target,
+ const QVector3D &baseUp)
+{
+ if (q_ptr->position() != basePosition || m_actualTarget != target || m_up != baseUp) {
+ q_ptr->setPosition(basePosition);
+ m_actualTarget = target;
+ m_up = baseUp;
+ q_ptr->setDirty(true);
+ }
+}
+
+/*!
+ * \internal
+ * Calculates and returns a position relative to the camera using the given parameters
+ * and the current camera viewMatrix property.
+ * The relative 3D offset to the current camera position is defined in \a relativePosition.
+ * An optional fixed rotation of the calculated point around the graph area can be
+ * given in \a fixedRotation. The rotation is given in degrees.
+ * An optional \a distanceModifier modifies the distance of the calculated point from the graph.
+ * \return calculated position relative to this camera's position.
+ */
+QVector3D Q3DCameraPrivate::calculatePositionRelativeToCamera(const QVector3D &relativePosition,
+ float fixedRotation,
+ float distanceModifier) const
+{
+ // Move the position with camera
+ const float radiusFactor = cameraDistance * (1.5f + distanceModifier);
+ float xAngle;
+ float yAngle;
+
+ if (!fixedRotation) {
+ xAngle = qDegreesToRadians(m_xRotation);
+ float yRotation = m_yRotation;
+ // Light must not be paraller to eye vector, so fudge the y rotation a bit.
+ // Note: This needs redoing if we ever allow arbitrary light positioning.
+ const float yMargin = 0.1f; // Smaller margins cause weird shadow artifacts on tops of bars
+ const float absYRotation = qAbs(yRotation);
+ if (absYRotation < 90.0f + yMargin && absYRotation > 90.0f - yMargin) {
+ if (yRotation < 0.0f)
+ yRotation = -90.0f + yMargin;
+ else
+ yRotation = 90.0f - yMargin;
+ }
+ yAngle = qDegreesToRadians(yRotation);
+ } else {
+ xAngle = qDegreesToRadians(fixedRotation);
+ yAngle = 0;
+ }
+ // Set radius to match the highest height of the position
+ const float radius = (radiusFactor + relativePosition.y());
+ const float zPos = radius * qCos(xAngle) * qCos(yAngle);
+ const float xPos = radius * qSin(xAngle) * qCos(yAngle);
+ const float yPos = radius * qSin(yAngle);
+
+ // Keep in the set position in relation to camera
+ return QVector3D(-xPos + relativePosition.x(),
+ yPos + relativePosition.y(),
+ zPos + relativePosition.z());
+}
+
+QT_END_NAMESPACE
diff --git a/src/graphs/engine/q3dcamera.h b/src/graphs/engine/q3dcamera.h
new file mode 100644
index 0000000..08aa24e
--- /dev/null
+++ b/src/graphs/engine/q3dcamera.h
@@ -0,0 +1,117 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef Q3DCAMERA_H
+#define Q3DCAMERA_H
+
+#include <QtGraphs/q3dobject.h>
+
+QT_BEGIN_NAMESPACE
+
+class Q3DCameraPrivate;
+
+class Q_GRAPHS_EXPORT Q3DCamera : public Q3DObject
+{
+ Q_OBJECT
+ Q_PROPERTY(float xRotation READ xRotation WRITE setXRotation NOTIFY xRotationChanged)
+ Q_PROPERTY(float yRotation READ yRotation WRITE setYRotation NOTIFY yRotationChanged)
+ Q_PROPERTY(float zoomLevel READ zoomLevel WRITE setZoomLevel NOTIFY zoomLevelChanged)
+ Q_PROPERTY(CameraPreset cameraPreset READ cameraPreset WRITE setCameraPreset NOTIFY cameraPresetChanged)
+ Q_PROPERTY(bool wrapXRotation READ wrapXRotation WRITE setWrapXRotation NOTIFY wrapXRotationChanged)
+ Q_PROPERTY(bool wrapYRotation READ wrapYRotation WRITE setWrapYRotation NOTIFY wrapYRotationChanged)
+ Q_PROPERTY(QVector3D target READ target WRITE setTarget NOTIFY targetChanged)
+ Q_PROPERTY(float minZoomLevel READ minZoomLevel WRITE setMinZoomLevel NOTIFY minZoomLevelChanged)
+ Q_PROPERTY(float maxZoomLevel READ maxZoomLevel WRITE setMaxZoomLevel NOTIFY maxZoomLevelChanged)
+
+public:
+ enum CameraPreset {
+ CameraPresetNone = -1,
+ CameraPresetFrontLow = 0,
+ CameraPresetFront,
+ CameraPresetFrontHigh,
+ CameraPresetLeftLow,
+ CameraPresetLeft,
+ CameraPresetLeftHigh,
+ CameraPresetRightLow,
+ CameraPresetRight,
+ CameraPresetRightHigh,
+ CameraPresetBehindLow,
+ CameraPresetBehind,
+ CameraPresetBehindHigh,
+ CameraPresetIsometricLeft,
+ CameraPresetIsometricLeftHigh,
+ CameraPresetIsometricRight,
+ CameraPresetIsometricRightHigh,
+ CameraPresetDirectlyAbove,
+ CameraPresetDirectlyAboveCW45,
+ CameraPresetDirectlyAboveCCW45,
+ CameraPresetFrontBelow,
+ CameraPresetLeftBelow,
+ CameraPresetRightBelow,
+ CameraPresetBehindBelow,
+ CameraPresetDirectlyBelow
+ };
+ Q_ENUM(CameraPreset)
+
+ explicit Q3DCamera(QObject *parent = nullptr);
+ virtual ~Q3DCamera();
+
+ float xRotation() const;
+ void setXRotation(float rotation);
+ float yRotation() const;
+ void setYRotation(float rotation);
+
+ bool wrapXRotation() const;
+ void setWrapXRotation(bool isEnabled);
+
+ bool wrapYRotation() const;
+ void setWrapYRotation(bool isEnabled);
+
+ void copyValuesFrom(const Q3DObject &source) override;
+
+ CameraPreset cameraPreset() const;
+ void setCameraPreset(CameraPreset preset);
+
+ float zoomLevel() const;
+ void setZoomLevel(float zoomLevel);
+ float minZoomLevel() const;
+ void setMinZoomLevel(float zoomLevel);
+ float maxZoomLevel() const;
+ void setMaxZoomLevel(float zoomLevel);
+
+ void setCameraPosition(float horizontal, float vertical, float zoom = 100.0f);
+
+ QVector3D target() const;
+ void setTarget(const QVector3D &target);
+
+Q_SIGNALS:
+ void xRotationChanged(float rotation);
+ void yRotationChanged(float rotation);
+ void zoomLevelChanged(float zoomLevel);
+ void cameraPresetChanged(Q3DCamera::CameraPreset preset);
+ void wrapXRotationChanged(bool isEnabled);
+ void wrapYRotationChanged(bool isEnabled);
+ void targetChanged(const QVector3D &target);
+ void minZoomLevelChanged(float zoomLevel);
+ void maxZoomLevelChanged(float zoomLevel);
+
+private:
+ QScopedPointer<Q3DCameraPrivate> d_ptr;
+
+ Q_DISABLE_COPY(Q3DCamera)
+
+ friend class Q3DCameraPrivate;
+ friend class Q3DScenePrivate;
+ friend class Bars3DController;
+ friend class Scatter3DController;
+ friend class SelectionPointer;
+ friend class Q3DInputHandler;
+ friend class QTouch3DInputHandlerPrivate;
+ friend class QQuickGraphsScatter;
+ friend class QQuickGraphsBars;
+ friend class GraphSceneNode3D;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/engine/q3dcamera_p.h b/src/graphs/engine/q3dcamera_p.h
new file mode 100644
index 0000000..9dcaccf
--- /dev/null
+++ b/src/graphs/engine/q3dcamera_p.h
@@ -0,0 +1,99 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QtGraphs API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#ifndef Q3DCAMERA_P_H
+#define Q3DCAMERA_P_H
+
+#include "graphsglobal_p.h"
+#include "q3dcamera.h"
+#include <QtGui/QMatrix4x4>
+
+QT_BEGIN_NAMESPACE
+
+class Q3DCamera;
+
+class Q3DCameraPrivate : public QObject
+{
+ Q_OBJECT
+public:
+ Q3DCameraPrivate(Q3DCamera *q);
+ ~Q3DCameraPrivate();
+
+ void sync(Q3DCamera &other);
+
+ void setXRotation(float rotation);
+ void setYRotation(float rotation);
+ void setMinXRotation(float rotation);
+ float minXRotation() const;
+ void setMinYRotation(float rotation);
+ float minYRotation() const;
+ void setMaxXRotation(float rotation);
+ float maxXRotation() const;
+ void setMaxYRotation(float rotation);
+ float maxYRotation() const;
+
+ void updateViewMatrix(float zoomAdjustment);
+
+ QMatrix4x4 viewMatrix() const;
+ void setViewMatrix(const QMatrix4x4 &viewMatrix);
+
+ bool isViewMatrixAutoUpdateEnabled() const;
+ void setViewMatrixAutoUpdateEnabled(bool isEnabled);
+
+ void setBaseOrientation(const QVector3D &defaultPosition,
+ const QVector3D &defaultTarget,
+ const QVector3D &defaultUp);
+
+ QVector3D calculatePositionRelativeToCamera(const QVector3D &relativePosition,
+ float fixedRotation,
+ float distanceModifier) const;
+
+Q_SIGNALS:
+ void minXRotationChanged(float rotation);
+ void minYRotationChanged(float rotation);
+ void maxXRotationChanged(float rotation);
+ void maxYRotationChanged(float rotation);
+ void viewMatrixChanged(const QMatrix4x4 &viewMatrix);
+ void viewMatrixAutoUpdateChanged(bool enabled);
+
+public:
+ Q3DCamera *q_ptr;
+
+ QVector3D m_actualTarget;
+ QVector3D m_up;
+
+ QMatrix4x4 m_viewMatrix;
+ bool m_isViewMatrixUpdateActive;
+
+ float m_xRotation;
+ float m_yRotation;
+ float m_minXRotation;
+ float m_minYRotation;
+ float m_maxXRotation;
+ float m_maxYRotation;
+ float m_zoomLevel;
+ float m_minZoomLevel;
+ float m_maxZoomLevel;
+ bool m_wrapXRotation;
+ bool m_wrapYRotation;
+ Q3DCamera::CameraPreset m_activePreset;
+ QVector3D m_requestedTarget;
+
+ friend class SelectionPointer;
+ friend class Q3DInputHandler;
+ friend class QTouch3DInputHandler;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/engine/q3dlight.cpp b/src/graphs/engine/q3dlight.cpp
new file mode 100644
index 0000000..f0fbef2
--- /dev/null
+++ b/src/graphs/engine/q3dlight.cpp
@@ -0,0 +1,95 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "q3dlight_p.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ * \class Q3DLight
+ * \inmodule QtGraphs
+ * \brief Representation of a light source in 3D space.
+ *
+ * Q3DLight represents a monochrome light source in 3D space.
+ *
+ * \note Default light has isAutoPosition() \c true.
+ */
+
+/*!
+ * \qmltype Light3D
+ * \inqmlmodule QtGraphs
+ * \ingroup graphs_qml
+ * \instantiates Q3DLight
+ * \brief Representation of a light source in 3D space.
+ *
+ * Light3D represents a monochrome light source in 3D space.
+ *
+ * \note Default light has autoPosition \c true.
+ */
+
+/*!
+ * \qmlproperty bool Light3D::autoPosition
+ * Defines whether the light position follows the camera automatically.
+ * \note Has no effect if shadows are enabled. Remember to disable shadows before setting light's
+ * position, or it will be overwritten by automatic positioning if this
+ * property is \c false.
+ */
+
+/*!
+ * Constructs a new 3D light located at origin. An optional \a parent parameter can be given
+ * and is then passed to QObject constructor.
+ */
+Q3DLight::Q3DLight(QObject *parent) :
+ Q3DObject(parent),
+ d_ptr(new Q3DLightPrivate(this))
+{
+}
+
+/*!
+ * Destroys the light object.
+ */
+Q3DLight::~Q3DLight()
+{
+}
+
+/*!
+ * \property Q3DLight::autoPosition
+ * \brief Whether the light position follows the camera automatically.
+ * \note Has no effect if shadows are enabled. Remember to disable shadows before setting light's
+ * position, or it will be overwritten by automatic positioning if
+ * \c isAutoPosition() is \c false.
+ */
+void Q3DLight::setAutoPosition(bool enabled)
+{
+ if (enabled != d_ptr->m_automaticLight) {
+ d_ptr->m_automaticLight = enabled;
+ setDirty(true);
+ emit autoPositionChanged(enabled);
+ }
+}
+
+bool Q3DLight::isAutoPosition()
+{
+ return d_ptr->m_automaticLight;
+}
+
+Q3DLightPrivate::Q3DLightPrivate(Q3DLight *q) :
+ q_ptr(q),
+ m_automaticLight(false)
+{
+}
+
+Q3DLightPrivate::~Q3DLightPrivate()
+{
+}
+
+void Q3DLightPrivate::sync(Q3DLight &other)
+{
+ if (q_ptr->isDirty()) {
+ other.setPosition(q_ptr->position());
+ other.setAutoPosition(q_ptr->isAutoPosition());
+ q_ptr->setDirty(false);
+ }
+}
+
+QT_END_NAMESPACE
diff --git a/src/graphs/engine/q3dlight.h b/src/graphs/engine/q3dlight.h
new file mode 100644
index 0000000..257c994
--- /dev/null
+++ b/src/graphs/engine/q3dlight.h
@@ -0,0 +1,39 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef Q3DLIGHT_H
+#define Q3DLIGHT_H
+
+#include <QtGraphs/q3dobject.h>
+
+QT_BEGIN_NAMESPACE
+
+class Q3DLightPrivate;
+
+class Q_GRAPHS_EXPORT Q3DLight : public Q3DObject
+{
+ Q_OBJECT
+ Q_PROPERTY(bool autoPosition READ isAutoPosition WRITE setAutoPosition NOTIFY autoPositionChanged)
+
+public:
+ explicit Q3DLight(QObject *parent = nullptr);
+ virtual ~Q3DLight();
+
+ void setAutoPosition(bool enabled);
+ bool isAutoPosition();
+
+Q_SIGNALS:
+ void autoPositionChanged(bool autoPosition);
+
+private:
+ QScopedPointer<Q3DLightPrivate> d_ptr;
+
+ Q_DISABLE_COPY(Q3DLight)
+
+ friend class Q3DLightPrivate;
+ friend class Q3DScenePrivate;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/engine/q3dlight_p.h b/src/graphs/engine/q3dlight_p.h
new file mode 100644
index 0000000..d59ef75
--- /dev/null
+++ b/src/graphs/engine/q3dlight_p.h
@@ -0,0 +1,45 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QtGraphs API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#ifndef Q3DLIGHT_P_H
+#define Q3DLIGHT_P_H
+
+#include "graphsglobal_p.h"
+#include "q3dlight.h"
+
+QT_BEGIN_NAMESPACE
+
+class Q3DScene;
+class Q3DLight;
+
+class Q3DLightPrivate
+{
+public:
+ Q3DLightPrivate(Q3DLight *q);
+ ~Q3DLightPrivate();
+
+ void sync(Q3DLight &other);
+
+public:
+ Q3DLight *q_ptr;
+ bool m_automaticLight;
+};
+
+QT_END_NAMESPACE
+
+#endif
+
+
+
+
+
diff --git a/src/graphs/engine/q3dobject.cpp b/src/graphs/engine/q3dobject.cpp
new file mode 100644
index 0000000..2565e77
--- /dev/null
+++ b/src/graphs/engine/q3dobject.cpp
@@ -0,0 +1,130 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "q3dobject_p.h"
+#include "q3dscene_p.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class Q3DObject
+ \inmodule QtGraphs
+ \brief The Q3DObject class is a simple base class for all the objects in a
+ 3D scene.
+
+ Contains position information for an object in a 3D scene.
+ The object is considered to be a single point in the coordinate space without dimensions.
+*/
+
+/*!
+ \qmltype Object3D
+ \inqmlmodule QtGraphs
+ \ingroup graphs_qml
+ \instantiates Q3DObject
+ \brief A base type for all the objects in a 3D scene.
+
+ An uncreatable base type that contains position information for an object in
+ a 3D scene. The object is considered to be a single point in the coordinate space without
+ dimensions.
+*/
+
+/*!
+ * \qmlproperty vector3d Object3D::position
+ *
+ * The 3D position of the object.
+ *
+ * \note Currently setting this property has no effect for Camera3D, as the position is handled
+ * internally.
+ */
+
+/*!
+ * Constructs a new 3D object with the position set to origin by default. An
+ * optional \a parent parameter can be given and is then passed to the QObject
+ * constructor.
+ */
+Q3DObject::Q3DObject(QObject *parent) :
+ QObject(parent),
+ d_ptr(new Q3DObjectPrivate(this))
+{
+}
+
+/*!
+ * Destroys the 3D object.
+ */
+Q3DObject::~Q3DObject()
+{
+}
+
+/*!
+ * Copies the 3D object position from the given \a source 3D object to this 3D object instance.
+ */
+void Q3DObject::copyValuesFrom(const Q3DObject &source)
+{
+ d_ptr->m_position = source.d_ptr->m_position;
+ setDirty(true);
+}
+
+/*!
+ * \property Q3DObject::parentScene
+ *
+ * \brief The parent scene as a read only value.
+ *
+ * If the object has no parent scene, the value is 0.
+ */
+Q3DScene *Q3DObject::parentScene()
+{
+ return qobject_cast<Q3DScene *>(parent());
+}
+
+/*!
+ * \property Q3DObject::position
+ *
+ * \brief The 3D position of the object.
+ *
+ * \note Currently setting this property has no effect for Q3DCamera, as the position is handled
+ * internally.
+ */
+QVector3D Q3DObject::position() const
+{
+ return d_ptr->m_position;
+}
+
+void Q3DObject::setPosition(const QVector3D &position)
+{
+ if (d_ptr->m_position != position) {
+ d_ptr->m_position = position;
+ setDirty(true);
+ emit positionChanged(d_ptr->m_position);
+ }
+}
+
+/*!
+ * Sets \a dirty to \c true if the 3D object has changed since the last update.
+ */
+void Q3DObject::setDirty(bool dirty)
+{
+ d_ptr->m_isDirty = dirty;
+ if (parentScene())
+ parentScene()->d_ptr->markDirty();
+}
+
+/*!
+ * Returns whether the 3D object has changed.
+ */
+bool Q3DObject::isDirty() const
+{
+ return d_ptr->m_isDirty;
+}
+
+Q3DObjectPrivate::Q3DObjectPrivate(Q3DObject *q) :
+ q_ptr(q),
+ m_isDirty(true)
+{
+}
+
+Q3DObjectPrivate::~Q3DObjectPrivate()
+{
+
+}
+
+QT_END_NAMESPACE
diff --git a/src/graphs/engine/q3dobject.h b/src/graphs/engine/q3dobject.h
new file mode 100644
index 0000000..27eb38b
--- /dev/null
+++ b/src/graphs/engine/q3dobject.h
@@ -0,0 +1,52 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef Q3DOBJECT_H
+#define Q3DOBJECT_H
+
+#include <QtGraphs/qgraphsglobal.h>
+#include <QtCore/QObject>
+#include <QtGui/QVector3D>
+
+Q_MOC_INCLUDE(<QtGraphs/q3dscene.h>)
+
+QT_BEGIN_NAMESPACE
+
+class Q3DObjectPrivate;
+class Q3DScene;
+
+class Q_GRAPHS_EXPORT Q3DObject : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(Q3DScene* parentScene READ parentScene CONSTANT)
+ Q_PROPERTY(QVector3D position READ position WRITE setPosition NOTIFY positionChanged)
+
+public:
+ explicit Q3DObject(QObject *parent = nullptr);
+ virtual ~Q3DObject();
+
+ virtual void copyValuesFrom(const Q3DObject &source);
+
+ Q3DScene *parentScene();
+
+ QVector3D position() const;
+ void setPosition(const QVector3D &position);
+
+Q_SIGNALS:
+ void positionChanged(const QVector3D &position);
+
+protected:
+ void setDirty(bool dirty);
+ bool isDirty() const;
+
+private:
+ QScopedPointer<Q3DObjectPrivate> d_ptr;
+
+ Q_DISABLE_COPY(Q3DObject)
+
+ friend class Q3DScenePrivate;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/engine/q3dobject_p.h b/src/graphs/engine/q3dobject_p.h
new file mode 100644
index 0000000..9fd8d73
--- /dev/null
+++ b/src/graphs/engine/q3dobject_p.h
@@ -0,0 +1,38 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QtGraphs API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#ifndef Q3DOBJECT_P_H
+#define Q3DOBJECT_P_H
+
+#include "graphsglobal_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class Q3DObject;
+class Q3DScene;
+
+class Q3DObjectPrivate
+{
+public:
+ Q3DObjectPrivate(Q3DObject *q);
+ ~Q3DObjectPrivate();
+
+public:
+ Q3DObject *q_ptr;
+ QVector3D m_position;
+ bool m_isDirty;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/engine/q3dscatter.cpp b/src/graphs/engine/q3dscatter.cpp
new file mode 100644
index 0000000..b0e2317
--- /dev/null
+++ b/src/graphs/engine/q3dscatter.cpp
@@ -0,0 +1,267 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "q3dscatter.h"
+#include "qquickgraphsscatter_p.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ * \class Q3DScatter
+ * \inmodule QtGraphs
+ * \brief The Q3DScatter class provides methods for rendering 3D scatter graphs.
+ *
+ * This class enables developers to render scatter graphs in 3D and to view them by rotating the scene
+ * freely. Rotation is done by holding down the right mouse button and moving the mouse. Zooming
+ * is done by mouse wheel. Selection, if enabled, is done by left mouse button. The scene can be
+ * reset to default camera view by clicking mouse wheel. In touch devices rotation is done
+ * by tap-and-move, selection by tap-and-hold and zoom by pinch.
+ *
+ * If no axes are set explicitly to Q3DScatter, temporary default axes with no labels are created.
+ * These default axes can be modified via axis accessors, but as soon any axis is set explicitly
+ * for the orientation, the default axis for that orientation is destroyed.
+ *
+ * Q3DScatter supports more than one series visible at the same time.
+ *
+ * \section1 How to construct a minimal Q3DScatter graph
+ *
+ * First, construct Q3DScatter. Since we are running the graph as top level window
+ * in this example, we need to clear the \c Qt::FramelessWindowHint flag, which gets set by
+ * default:
+ *
+ * \snippet doc_src_q3dscatter_construction.cpp 0
+ *
+ * Now Q3DScatter is ready to receive data to be rendered. Add one series of 3 QVector3D items:
+ *
+ * \snippet doc_src_q3dscatter_construction.cpp 1
+ *
+ * Finally you will need to set it visible:
+ *
+ * \snippet doc_src_q3dscatter_construction.cpp 2
+ *
+ * The complete code needed to create and display this graph is:
+ *
+ * \snippet doc_src_q3dscatter_construction.cpp 3
+ *
+ * And this is what those few lines of code produce:
+ *
+ * \image q3dscatter-minimal.png
+ *
+ * The scene can be rotated, zoomed into, and an item can be selected to view its position,
+ * but no other interaction is included in this minimal code example.
+ * You can learn more by familiarizing yourself with the examples provided, like
+ * the \l{Scatter Example}.
+ *
+ * \sa Q3DBars, Q3DSurface, {Qt Graphs C++ Classes}
+ */
+
+/*!
+ * Constructs a new 3D scatter graph with optional \a parent window
+ * and surface \a format.
+ */
+
+/*!
+ * Destroys the 3D scatter graph.
+ */
+
+/*!
+ * Adds the \a series to the graph. A graph can contain multiple series, but has only one set of
+ * axes. If the newly added series has specified a selected item, it will be highlighted and
+ * any existing selection will be cleared. Only one added series can have an active selection.
+ *
+ * \sa QAbstract3DGraph::hasSeries()
+ */
+
+/*!
+ * Removes the \a series from the graph.
+ *
+ * \sa QAbstract3DGraph::hasSeries()
+ */
+
+/*!
+ * Returns the list of series added to this graph.
+ *
+ * \sa QAbstract3DGraph::hasSeries()
+ */
+
+/*!
+ * \property Q3DScatter::axisX
+ *
+ * \brief The active x-axis.
+ */
+
+/*!
+ * Sets \a axis as the active x-axis. Implicitly calls addAxis() to transfer the
+ * ownership of the axis to this graph.
+ *
+ * If \a axis is null, a temporary default axis with no labels and an automatically adjusting
+ * range is created.
+ * This temporary axis is destroyed if another axis is set explicitly to the
+ * same orientation.
+ *
+ * \sa addAxis(), releaseAxis()
+ */
+
+/*!
+ * \property Q3DScatter::axisY
+ *
+ * \brief The active y-axis.
+ */
+
+/*!
+ * Sets \a axis as the active y-axis. Implicitly calls addAxis() to transfer the
+ * ownership of the axis to this graph.
+ *
+ * If \a axis is null, a temporary default axis with no labels and an automatically adjusting
+ * range is created.
+ * This temporary axis is destroyed if another axis is set explicitly to the
+ * same orientation.
+ *
+ * \sa addAxis(), releaseAxis()
+ */
+
+/*!
+ * \property Q3DScatter::axisZ
+ *
+ * \brief The active z-axis.
+ */
+
+/*!
+ * Sets \a axis as the active z-axis. Implicitly calls addAxis() to transfer the
+ * ownership of the axis to this graph.
+ *
+ * If \a axis is null, a temporary default axis with no labels and an automatically adjusting
+ * range is created.
+ * This temporary axis is destroyed if another axis is set explicitly to the
+ * same orientation.
+ *
+ * \sa addAxis(), releaseAxis()
+ */
+
+/*!
+ * Returns the used z-axis.
+ */
+
+/*!
+ * \property Q3DScatter::selectedSeries
+ *
+ * \brief The selected series or null.
+ */
+
+/*!
+ * Adds \a axis to the graph. The axes added via addAxis are not yet taken to use,
+ * addAxis is simply used to give the ownership of the \a axis to the graph.
+ * The \a axis must not be null or added to another graph.
+ *
+ * \sa releaseAxis(), setAxisX(), setAxisY(), setAxisZ()
+ */
+
+/*!
+ * Releases the ownership of the \a axis back to the caller, if it is added to this graph.
+ * If the released \a axis is in use, a new default axis will be created and set active.
+ *
+ * If the default axis is released and added back later, it behaves as any other axis would.
+ *
+ * \sa addAxis(), setAxisX(), setAxisY(), setAxisZ()
+ */
+
+/*!
+ * Returns the list of all added axes.
+ *
+ * \sa addAxis()
+ */
+
+Q3DScatter::Q3DScatter() : QAbstract3DGraph()
+{
+ QQmlComponent *component = new QQmlComponent(engine(), this);
+ component->setData("import QtQuick; import QtGraphs; Scatter3D { anchors.fill: parent; }", QUrl());
+ d_ptr.reset(qobject_cast<QQuickGraphsScatter *>(component->create()));
+ setContent(component->url(), component, d_ptr.data());
+}
+
+Q3DScatter::~Q3DScatter()
+{
+}
+
+void Q3DScatter::addSeries(QScatter3DSeries *series)
+{
+ dptr()->addSeries(series);
+}
+
+void Q3DScatter::removeSeries(QScatter3DSeries *series)
+{
+ dptr()->removeSeries(series);
+}
+
+QList<QScatter3DSeries *> Q3DScatter::seriesList() const
+{
+ return dptrc()->m_scatterController->scatterSeriesList();
+}
+
+QQuickGraphsScatter *Q3DScatter::dptr()
+{
+ return static_cast<QQuickGraphsScatter *>(d_ptr.data());
+}
+
+const QQuickGraphsScatter *Q3DScatter::dptrc() const
+{
+ return static_cast<const QQuickGraphsScatter *>(d_ptr.data());
+}
+
+void Q3DScatter::setAxisX(QValue3DAxis *axis)
+{
+ dptr()->setAxisX(axis);
+}
+
+QValue3DAxis *Q3DScatter::axisX() const
+{
+ return static_cast<QValue3DAxis *>(dptrc()->axisX());
+}
+
+void Q3DScatter::setAxisY(QValue3DAxis *axis)
+{
+ dptr()->setAxisY(axis);
+}
+
+QValue3DAxis *Q3DScatter::axisY() const
+{
+ return static_cast<QValue3DAxis *>(dptrc()->axisY());
+}
+
+void Q3DScatter::setAxisZ(QValue3DAxis *axis)
+{
+ dptr()->setAxisZ(axis);
+}
+
+QValue3DAxis *Q3DScatter::axisZ() const
+{
+ return static_cast<QValue3DAxis *>(dptrc()->axisZ());
+}
+
+QScatter3DSeries *Q3DScatter::selectedSeries() const
+{
+ return dptrc()->selectedSeries();
+}
+
+void Q3DScatter::addAxis(QValue3DAxis *axis)
+{
+ dptr()->m_scatterController->addAxis(axis);
+}
+
+void Q3DScatter::releaseAxis(QValue3DAxis *axis)
+{
+ dptr()->m_scatterController->releaseAxis(axis);
+}
+
+QList<QValue3DAxis *> Q3DScatter::axes() const
+{
+ QList<QAbstract3DAxis *> abstractAxes = dptrc()->m_scatterController->axes();
+ QList<QValue3DAxis *> retList;
+ for (QAbstract3DAxis *axis : abstractAxes)
+ retList.append(static_cast<QValue3DAxis *>(axis));
+
+ return retList;
+}
+
+QT_END_NAMESPACE
+
diff --git a/src/graphs/engine/q3dscatter.h b/src/graphs/engine/q3dscatter.h
new file mode 100644
index 0000000..930ce7c
--- /dev/null
+++ b/src/graphs/engine/q3dscatter.h
@@ -0,0 +1,57 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef Q3DSCATTER_H
+#define Q3DSCATTER_H
+
+#include <QtGraphs/qabstract3dgraph.h>
+#include <QtGraphs/qvalue3daxis.h>
+#include <QtGraphs/qscatter3dseries.h>
+
+QT_BEGIN_NAMESPACE
+
+class QQuickGraphsScatter;
+
+class Q_GRAPHS_EXPORT Q3DScatter : public QAbstract3DGraph
+{
+ Q_OBJECT
+ Q_PROPERTY(QValue3DAxis *axisX READ axisX WRITE setAxisX NOTIFY axisXChanged)
+ Q_PROPERTY(QValue3DAxis *axisY READ axisY WRITE setAxisY NOTIFY axisYChanged)
+ Q_PROPERTY(QValue3DAxis *axisZ READ axisZ WRITE setAxisZ NOTIFY axisZChanged)
+ Q_PROPERTY(QScatter3DSeries *selectedSeries READ selectedSeries NOTIFY selectedSeriesChanged)
+
+public:
+ Q3DScatter();
+ ~Q3DScatter();
+
+ void addSeries(QScatter3DSeries *series);
+ void removeSeries(QScatter3DSeries *series);
+ QList<QScatter3DSeries *> seriesList() const;
+
+ void setAxisX(QValue3DAxis *axis);
+ QValue3DAxis *axisX() const;
+ void setAxisY(QValue3DAxis *axis);
+ QValue3DAxis *axisY() const;
+ void setAxisZ(QValue3DAxis *axis);
+ QValue3DAxis *axisZ() const;
+ void addAxis(QValue3DAxis *axis);
+ void releaseAxis(QValue3DAxis *axis);
+ QList<QValue3DAxis *> axes() const;
+
+ QScatter3DSeries *selectedSeries() const;
+
+Q_SIGNALS:
+ void axisXChanged(QValue3DAxis *axis);
+ void axisYChanged(QValue3DAxis *axis);
+ void axisZChanged(QValue3DAxis *axis);
+ void selectedSeriesChanged(QScatter3DSeries *series);
+
+private:
+ QQuickGraphsScatter *dptr();
+ const QQuickGraphsScatter *dptrc() const;
+ Q_DISABLE_COPY(Q3DScatter)
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/engine/q3dscene.cpp b/src/graphs/engine/q3dscene.cpp
new file mode 100644
index 0000000..2a00144
--- /dev/null
+++ b/src/graphs/engine/q3dscene.cpp
@@ -0,0 +1,807 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "q3dscene_p.h"
+#include "q3dcamera_p.h"
+#include "q3dlight_p.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ * \class Q3DScene
+ * \inmodule QtGraphs
+ * \brief Q3DScene class provides description of the 3D scene being visualized.
+ *
+ * The 3D scene contains a single active camera and a single active light source.
+ * Visualized data is assumed to be at a fixed location.
+ *
+ * The 3D scene also keeps track of the viewport in which graph rendering is done,
+ * the primary subviewport inside the viewport where the main 3D graphs view resides
+ * and the secondary subviewport where the 2D sliced view of the data resides. The subviewports are
+ * by default resized by the \a Q3DScene. To override the resize behavior you need to listen to both
+ * \l viewportChanged() and \l slicingActiveChanged() signals and recalculate the subviewports accordingly.
+ *
+ * Also the scene has flag for tracking if the secondary 2D slicing view is currently active or not.
+ * \note Not all graphs support the secondary 2D slicing view.
+ */
+
+/*!
+ * \class Q3DSceneChangeBitField
+ * \internal
+ */
+
+/*!
+ * \qmltype Scene3D
+ * \inqmlmodule QtGraphs
+ * \ingroup graphs_qml
+ * \instantiates Q3DScene
+ * \brief Scene3D type provides description of the 3D scene being visualized.
+ *
+ * The 3D scene contains a single active camera and a single active light source.
+ * Visualized data is assumed to be at a fixed location.
+ *
+ * The 3D scene also keeps track of the viewport in which graph rendering is done,
+ * the primary subviewport inside the viewport where the main 3D graphs view resides
+ * and the secondary subviewport where the 2D sliced view of the data resides.
+ *
+ * Also the scene has flag for tracking if the secondary 2D slicing view is currently active or not.
+ * \note Not all graphs support the secondary 2D slicing view.
+ */
+
+/*!
+ * \qmlproperty rect Scene3D::viewport
+ *
+ * The current viewport rectangle where all 3D rendering is targeted.
+ */
+
+/*!
+ * \qmlproperty rect Scene3D::primarySubViewport
+ *
+ * The current subviewport rectangle inside the viewport where the
+ * primary view of the graphs is targeted.
+ *
+ * If slicingActive is \c false, the primary sub viewport will be equal to the
+ * viewport. If slicingActive is \c true and the primary sub viewport has not
+ * been explicitly set, it will be one fifth of the viewport.
+ * \note Setting primarySubViewport larger than or outside of viewport resizes viewport accordingly.
+ */
+
+/*!
+ * \qmlproperty rect Scene3D::secondarySubViewport
+ *
+ * The secondary viewport rectangle inside the viewport. The secondary viewport
+ * is used for drawing the 2D slice view in some graphs. If it has not
+ * been explicitly set, it will be null. If slicingActive is \c true, it will
+ * be equal to the viewport.
+ * \note If the secondary sub viewport is larger than or outside of the
+ * viewport, the viewport is resized accordingly.
+*/
+
+/*!
+ * \qmlproperty point Scene3D::selectionQueryPosition
+ *
+ * The coordinates for the user input that should be processed
+ * by the scene as a selection. If this property is set to a value other than
+ * invalidSelectionPoint, the
+ * graph tries to select a data item at the given point within the primary viewport.
+ * After the rendering pass, the property is returned to its default state of
+ * invalidSelectionPoint.
+ */
+
+/*!
+ * \qmlproperty point Scene3D::graphPositionQuery
+ *
+ * The coordinates for the user input that should be processed by the scene as a
+ * graph position query. If this property is set to value other than
+ * invalidSelectionPoint, the graph tries to match a graph position to the given point
+ * within the primary viewport.
+ * After the rendering pass, this property is returned to its default state of
+ * invalidSelectionPoint. The queried graph position can be read from the
+ * AbstractGraph3D::queriedGraphPosition property after the next render pass.
+ *
+ * There is no single correct 3D coordinate to match a particular screen position, so to be
+ * consistent, the queries are always done against the inner sides of an invisible box surrounding
+ * the graph.
+ *
+ * \note Bar graphs allow graph position queries only at the graph floor level.
+ *
+ * \sa AbstractGraph3D::queriedGraphPosition
+ */
+
+/*!
+ * \qmlproperty bool Scene3D::slicingActive
+ *
+ * Defines whether the 2D slicing view is currently active. If \c true,
+ * AbstractGraph3D::selectionMode must have either the
+ * \l{QAbstract3DGraph::SelectionRow}{AbstractGraph3D.SelectionRow} or
+ * \l{QAbstract3DGraph::SelectionColumn}{AbstractGraph3D.SelectionColumn}
+ * set to a valid selection.
+ * \note Not all graphs support the 2D slicing view.
+ */
+
+/*!
+ * \qmlproperty bool Scene3D::secondarySubviewOnTop
+ *
+ * Defines whether the 2D slicing view or the 3D view is drawn on top.
+ */
+
+/*!
+ * \qmlproperty Camera3D Scene3D::activeCamera
+ *
+ * The currently active camera in the 3D scene.
+ * When a Camera3D is set in the property, it is automatically added as child of
+ * the scene.
+ */
+
+/*!
+ * \qmlproperty Light3D Scene3D::activeLight
+ *
+ * The currently active light in the 3D scene.
+ * When a Light3D is set in the property, it is automatically added as child of
+ * the scene.
+ */
+
+/*!
+ * \qmlproperty float Scene3D::devicePixelRatio
+ *
+ * The current device pixel ratio that is used when mapping input
+ * coordinates to pixel coordinates.
+ */
+
+/*!
+ * \qmlproperty point Scene3D::invalidSelectionPoint
+ * A constant property providing an invalid point for selection.
+ */
+
+/*!
+ * Constructs a basic scene with one light and one camera in it. An
+ * optional \a parent parameter can be given and is then passed to QObject constructor.
+ */
+Q3DScene::Q3DScene(QObject *parent) :
+ QObject(parent),
+ d_ptr(new Q3DScenePrivate(this))
+{
+ setActiveCamera(new Q3DCamera(0));
+ setActiveLight(new Q3DLight(0));
+}
+
+/*!
+ * Destroys the 3D scene and all the objects contained within it.
+ */
+Q3DScene::~Q3DScene()
+{
+}
+
+/*!
+ * \property Q3DScene::viewport
+ *
+ * \brief A read only property that contains the current viewport rectangle
+ * where all the 3D rendering is targeted.
+ */
+QRect Q3DScene::viewport() const
+{
+ return d_ptr->m_viewport;
+}
+
+/*!
+ * \property Q3DScene::primarySubViewport
+ *
+ * \brief The current subviewport rectangle inside the viewport where the
+ * primary view of the graphs is targeted.
+ *
+ * If isSlicingActive() is \c false, the primary sub viewport is equal to
+ * viewport(). If isSlicingActive() is \c true and the primary sub viewport has
+ * not been explicitly set, it will be one fifth of viewport().
+ *
+ * \note Setting primarySubViewport larger than or outside of the viewport
+ * resizes the viewport accordingly.
+ */
+QRect Q3DScene::primarySubViewport() const
+{
+ QRect primary = d_ptr->m_primarySubViewport;
+ if (primary.isNull()) {
+ if (d_ptr->m_isSlicingActive)
+ primary = d_ptr->m_defaultSmallViewport;
+ else
+ primary = d_ptr->m_defaultLargeViewport;
+ }
+ return primary;
+}
+
+void Q3DScene::setPrimarySubViewport(const QRect &primarySubViewport)
+{
+ if (d_ptr->m_primarySubViewport != primarySubViewport) {
+ if (!primarySubViewport.isValid() && !primarySubViewport.isNull()) {
+ qWarning("Viewport is invalid.");
+ return;
+ }
+
+ // If viewport is smaller than primarySubViewport, enlarge it
+ if ((d_ptr->m_viewport.width() < (primarySubViewport.width()
+ + primarySubViewport.x()))
+ || (d_ptr->m_viewport.height() < (primarySubViewport.height()
+ + primarySubViewport.y()))) {
+ d_ptr->m_viewport.setWidth(qMax(d_ptr->m_viewport.width(),
+ primarySubViewport.width() + primarySubViewport.x()));
+ d_ptr->m_viewport.setHeight(qMax(d_ptr->m_viewport.height(),
+ primarySubViewport.height() + primarySubViewport.y()));
+ d_ptr->calculateSubViewports();
+ }
+
+ d_ptr->m_primarySubViewport = primarySubViewport;
+ d_ptr->updateGLSubViewports();
+ d_ptr->m_changeTracker.primarySubViewportChanged = true;
+ d_ptr->m_sceneDirty = true;
+
+ emit primarySubViewportChanged(primarySubViewport);
+ emit d_ptr->needRender();
+ }
+}
+
+/*!
+ * Returns whether the given \a point resides inside the primary subview or not.
+ * \return \c true if the point is inside the primary subview.
+ * \note If subviews are superimposed, and the given \a point resides inside both, result is
+ * \c true only when the primary subview is on top.
+ */
+bool Q3DScene::isPointInPrimarySubView(const QPoint &point)
+{
+ int x = point.x();
+ int y = point.y();
+ bool isInSecondary = d_ptr->isInArea(secondarySubViewport(), x, y);
+ if (!isInSecondary || (isInSecondary && !d_ptr->m_isSecondarySubviewOnTop))
+ return d_ptr->isInArea(primarySubViewport(), x, y);
+ else
+ return false;
+}
+
+/*!
+ * Returns whether the given \a point resides inside the secondary subview or not.
+ * \return \c true if the point is inside the secondary subview.
+ * \note If subviews are superimposed, and the given \a point resides inside both, result is
+ * \c true only when the secondary subview is on top.
+ */
+bool Q3DScene::isPointInSecondarySubView(const QPoint &point)
+{
+ int x = point.x();
+ int y = point.y();
+ bool isInPrimary = d_ptr->isInArea(primarySubViewport(), x, y);
+ if (!isInPrimary || (isInPrimary && d_ptr->m_isSecondarySubviewOnTop))
+ return d_ptr->isInArea(secondarySubViewport(), x, y);
+ else
+ return false;
+}
+
+/*!
+ * \property Q3DScene::secondarySubViewport
+ *
+ * \brief The secondary viewport rectangle inside the viewport.
+ *
+ * The secondary viewport is used for drawing the 2D slice view in some
+ * graphs. If it has not been explicitly set, it will be equal to
+ * QRect. If isSlicingActive() is \c true, it will be equal to \l viewport.
+ * \note If the secondary sub viewport is larger than or outside of the
+ * viewport, the viewport is resized accordingly.
+ */
+QRect Q3DScene::secondarySubViewport() const
+{
+ QRect secondary = d_ptr->m_secondarySubViewport;
+ if (secondary.isNull() && d_ptr->m_isSlicingActive)
+ secondary = d_ptr->m_defaultLargeViewport;
+ return secondary;
+}
+
+void Q3DScene::setSecondarySubViewport(const QRect &secondarySubViewport)
+{
+ if (d_ptr->m_secondarySubViewport != secondarySubViewport) {
+ if (!secondarySubViewport.isValid() && !secondarySubViewport.isNull()) {
+ qWarning("Viewport is invalid.");
+ return;
+ }
+
+ // If viewport is smaller than secondarySubViewport, enlarge it
+ if ((d_ptr->m_viewport.width() < (secondarySubViewport.width()
+ + secondarySubViewport.x()))
+ || (d_ptr->m_viewport.height() < (secondarySubViewport.height()
+ + secondarySubViewport.y()))) {
+ d_ptr->m_viewport.setWidth(qMax(d_ptr->m_viewport.width(),
+ secondarySubViewport.width()
+ + secondarySubViewport.x()));
+ d_ptr->m_viewport.setHeight(qMax(d_ptr->m_viewport.height(),
+ secondarySubViewport.height()
+ + secondarySubViewport.y()));
+ d_ptr->calculateSubViewports();
+ }
+
+ d_ptr->m_secondarySubViewport = secondarySubViewport;
+ d_ptr->updateGLSubViewports();
+ d_ptr->m_changeTracker.secondarySubViewportChanged = true;
+ d_ptr->m_sceneDirty = true;
+
+ emit secondarySubViewportChanged(secondarySubViewport);
+ emit d_ptr->needRender();
+ }
+}
+
+/*!
+ * \property Q3DScene::selectionQueryPosition
+ *
+ * \brief The coordinates for the user input that should be processed
+ * by the scene as a selection.
+ *
+ * If this property is set to a value other than invalidSelectionPoint(), the
+ * graph tries to select a data item, axis label, or a custom item at the
+ * specified coordinates within the primary viewport.
+ * After the rendering pass, the property is returned to its default state of
+ * invalidSelectionPoint().
+ *
+ * \sa QAbstract3DGraph::selectedElement
+ */
+void Q3DScene::setSelectionQueryPosition(const QPoint &point)
+{
+ if (point != d_ptr->m_selectionQueryPosition) {
+ d_ptr->m_selectionQueryPosition = point;
+ d_ptr->m_changeTracker.selectionQueryPositionChanged = true;
+ d_ptr->m_sceneDirty = true;
+
+ emit selectionQueryPositionChanged(point);
+ emit d_ptr->needRender();
+ }
+}
+
+QPoint Q3DScene::selectionQueryPosition() const
+{
+ return d_ptr->m_selectionQueryPosition;
+}
+
+/*!
+ * \return a QPoint signifying an invalid selection position.
+ */
+QPoint Q3DScene::invalidSelectionPoint()
+{
+ static const QPoint invalidSelectionPos(-1, -1);
+ return invalidSelectionPos;
+}
+
+/*!
+ * \property Q3DScene::graphPositionQuery
+ *
+ * \brief The coordinates for the user input that should be processed
+ * by the scene as a graph position query.
+ *
+ * If this property is set to a value other than invalidSelectionPoint(), the
+ * graph tries to match a graph position to the specified coordinates
+ * within the primary viewport.
+ * After the rendering pass, this property is returned to its default state of
+ * invalidSelectionPoint(). The queried graph position can be read from the
+ * QAbstract3DGraph::queriedGraphPosition property after the next render pass.
+ *
+ * There is no single correct 3D coordinate to match a particular screen position, so to be
+ * consistent, the queries are always done against the inner sides of an invisible box surrounding
+ * the graph.
+ *
+ * \note Bar graphs allow graph position queries only at the graph floor level.
+ *
+ * \sa QAbstract3DGraph::queriedGraphPosition
+ */
+void Q3DScene::setGraphPositionQuery(const QPoint &point)
+{
+ if (point != d_ptr->m_graphPositionQueryPosition) {
+ d_ptr->m_graphPositionQueryPosition = point;
+ d_ptr->m_changeTracker.graphPositionQueryPositionChanged = true;
+ d_ptr->m_sceneDirty = true;
+
+ emit graphPositionQueryChanged(point);
+ emit d_ptr->needRender();
+ }
+}
+
+QPoint Q3DScene::graphPositionQuery() const
+{
+ return d_ptr->m_graphPositionQueryPosition;
+}
+
+/*!
+ * \property Q3DScene::slicingActive
+ *
+ * \brief Whether the 2D slicing view is currently active.
+ *
+ * If \c true, QAbstract3DGraph::selectionMode must have either
+ * QAbstract3DGraph::SelectionRow or QAbstract3DGraph::SelectionColumn set
+ * to a valid selection.
+ * \note Not all graphs support the 2D slicing view.
+ */
+bool Q3DScene::isSlicingActive() const
+{
+ return d_ptr->m_isSlicingActive;
+}
+
+void Q3DScene::setSlicingActive(bool isSlicing)
+{
+ if (d_ptr->m_isSlicingActive != isSlicing) {
+ d_ptr->m_isSlicingActive = isSlicing;
+ d_ptr->m_changeTracker.slicingActivatedChanged = true;
+ d_ptr->m_sceneDirty = true;
+
+ // Set secondary subview behind primary to achieve default functionality (= clicking on
+ // primary disables slice)
+ setSecondarySubviewOnTop(!isSlicing);
+
+ d_ptr->calculateSubViewports();
+ emit slicingActiveChanged(isSlicing);
+ emit d_ptr->needRender();
+ }
+}
+
+/*!
+ * \property Q3DScene::secondarySubviewOnTop
+ *
+ * \brief Whether the 2D slicing view or the 3D view is drawn on top.
+ */
+bool Q3DScene::isSecondarySubviewOnTop() const
+{
+ return d_ptr->m_isSecondarySubviewOnTop;
+}
+
+void Q3DScene::setSecondarySubviewOnTop(bool isSecondaryOnTop)
+{
+ if (d_ptr->m_isSecondarySubviewOnTop != isSecondaryOnTop) {
+ d_ptr->m_isSecondarySubviewOnTop = isSecondaryOnTop;
+ d_ptr->m_changeTracker.subViewportOrderChanged = true;
+ d_ptr->m_sceneDirty = true;
+
+ emit secondarySubviewOnTopChanged(isSecondaryOnTop);
+ emit d_ptr->needRender();
+ }
+}
+
+/*!
+ * \property Q3DScene::activeCamera
+ *
+ * \brief The currently active camera in the 3D scene.
+ *
+ * When a new Q3DCamera object is set, it is automatically added as child of
+ * the scene.
+ */
+Q3DCamera *Q3DScene::activeCamera() const
+{
+ return d_ptr->m_camera;
+}
+
+void Q3DScene::setActiveCamera(Q3DCamera *camera)
+{
+ Q_ASSERT(camera);
+
+ // Add new camera as child of the scene
+ if (camera->parent() != this)
+ camera->setParent(this);
+
+ if (camera != d_ptr->m_camera) {
+ if (d_ptr->m_camera) {
+ disconnect(d_ptr->m_camera, &Q3DCamera::xRotationChanged, d_ptr.data(),
+ &Q3DScenePrivate::needRender);
+ disconnect(d_ptr->m_camera, &Q3DCamera::yRotationChanged, d_ptr.data(),
+ &Q3DScenePrivate::needRender);
+ disconnect(d_ptr->m_camera, &Q3DCamera::zoomLevelChanged, d_ptr.data(),
+ &Q3DScenePrivate::needRender);
+ }
+
+ d_ptr->m_camera = camera;
+ d_ptr->m_changeTracker.cameraChanged = true;
+ d_ptr->m_sceneDirty = true;
+
+
+ if (camera) {
+ connect(camera, &Q3DCamera::xRotationChanged, d_ptr.data(),
+ &Q3DScenePrivate::needRender);
+ connect(camera, &Q3DCamera::yRotationChanged, d_ptr.data(),
+ &Q3DScenePrivate::needRender);
+ connect(camera, &Q3DCamera::zoomLevelChanged, d_ptr.data(),
+ &Q3DScenePrivate::needRender);
+ }
+
+ emit activeCameraChanged(camera);
+ emit d_ptr->needRender();
+ }
+}
+
+/*!
+ * \property Q3DScene::activeLight
+ *
+ * \brief The currently active light in the 3D scene.
+ *
+ * When a new Q3DLight objects is set, it is automatically added as child of
+ * the scene.
+ */
+Q3DLight *Q3DScene::activeLight() const
+{
+ return d_ptr->m_light;
+}
+
+void Q3DScene::setActiveLight(Q3DLight *light)
+{
+ Q_ASSERT(light);
+
+ // Add new light as child of the scene
+ if (light->parent() != this)
+ light->setParent(this);
+
+ if (light != d_ptr->m_light) {
+ d_ptr->m_light = light;
+ d_ptr->m_changeTracker.lightChanged = true;
+ d_ptr->m_sceneDirty = true;
+
+ emit activeLightChanged(light);
+ emit d_ptr->needRender();
+ }
+}
+
+/*!
+ * \property Q3DScene::devicePixelRatio
+ *
+ * \brief The device pixel ratio that is used when mapping input
+ * coordinates to pixel coordinates.
+ */
+float Q3DScene::devicePixelRatio() const
+{
+ return d_ptr->m_devicePixelRatio;
+}
+
+void Q3DScene::setDevicePixelRatio(float pixelRatio)
+{
+ if (d_ptr->m_devicePixelRatio != pixelRatio) {
+ d_ptr->m_devicePixelRatio = pixelRatio;
+ d_ptr->m_changeTracker.devicePixelRatioChanged = true;
+ d_ptr->m_sceneDirty = true;
+
+ emit devicePixelRatioChanged(pixelRatio);
+ d_ptr->updateGLViewport();
+ emit d_ptr->needRender();
+ }
+}
+
+Q3DScenePrivate::Q3DScenePrivate(Q3DScene *q) :
+ QObject(0),
+ q_ptr(q),
+ m_isSecondarySubviewOnTop(true),
+ m_devicePixelRatio(1.f),
+ m_camera(),
+ m_light(),
+ m_isUnderSideCameraEnabled(false),
+ m_isSlicingActive(false),
+ m_selectionQueryPosition(Q3DScene::invalidSelectionPoint()),
+ m_graphPositionQueryPosition(Q3DScene::invalidSelectionPoint()),
+ m_windowSize(QSize(0, 0)),
+ m_sceneDirty(true)
+{
+}
+
+Q3DScenePrivate::~Q3DScenePrivate()
+{
+ delete m_camera;
+ delete m_light;
+}
+
+// Copies changed values from this scene to the other scene. If the other scene had same changes,
+// those changes are discarded.
+void Q3DScenePrivate::sync(Q3DScenePrivate &other)
+{
+ if (m_changeTracker.windowSizeChanged) {
+ other.setWindowSize(windowSize());
+ m_changeTracker.windowSizeChanged = false;
+ other.m_changeTracker.windowSizeChanged = false;
+ }
+ if (m_changeTracker.viewportChanged) {
+ other.setViewport(m_viewport);
+ m_changeTracker.viewportChanged = false;
+ other.m_changeTracker.viewportChanged = false;
+ }
+ if (m_changeTracker.subViewportOrderChanged) {
+ other.q_ptr->setSecondarySubviewOnTop(q_ptr->isSecondarySubviewOnTop());
+ m_changeTracker.subViewportOrderChanged = false;
+ other.m_changeTracker.subViewportOrderChanged = false;
+ }
+ if (m_changeTracker.primarySubViewportChanged) {
+ other.q_ptr->setPrimarySubViewport(q_ptr->primarySubViewport());
+ m_changeTracker.primarySubViewportChanged = false;
+ other.m_changeTracker.primarySubViewportChanged = false;
+ }
+ if (m_changeTracker.secondarySubViewportChanged) {
+ other.q_ptr->setSecondarySubViewport(q_ptr->secondarySubViewport());
+ m_changeTracker.secondarySubViewportChanged = false;
+ other.m_changeTracker.secondarySubViewportChanged = false;
+ }
+ if (m_changeTracker.selectionQueryPositionChanged) {
+ other.q_ptr->setSelectionQueryPosition(q_ptr->selectionQueryPosition());
+ m_changeTracker.selectionQueryPositionChanged = false;
+ other.m_changeTracker.selectionQueryPositionChanged = false;
+ }
+ if (m_changeTracker.graphPositionQueryPositionChanged) {
+ other.q_ptr->setGraphPositionQuery(q_ptr->graphPositionQuery());
+ m_changeTracker.graphPositionQueryPositionChanged = false;
+ other.m_changeTracker.graphPositionQueryPositionChanged = false;
+ }
+ if (m_changeTracker.cameraChanged) {
+ m_camera->setDirty(true);
+ m_changeTracker.cameraChanged = false;
+ other.m_changeTracker.cameraChanged = false;
+ }
+ m_camera->d_ptr->sync(*other.m_camera);
+
+ if (m_changeTracker.lightChanged) {
+ m_light->setDirty(true);
+ m_changeTracker.lightChanged = false;
+ other.m_changeTracker.lightChanged = false;
+ }
+ m_light->d_ptr->sync(*other.m_light);
+
+ if (m_changeTracker.slicingActivatedChanged) {
+ other.q_ptr->setSlicingActive(q_ptr->isSlicingActive());
+ m_changeTracker.slicingActivatedChanged = false;
+ other.m_changeTracker.slicingActivatedChanged = false;
+ }
+
+ if (m_changeTracker.devicePixelRatioChanged) {
+ other.q_ptr->setDevicePixelRatio(q_ptr->devicePixelRatio());
+ m_changeTracker.devicePixelRatioChanged = false;
+ other.m_changeTracker.devicePixelRatioChanged = false;
+ }
+
+ m_sceneDirty = false;
+ other.m_sceneDirty = false;
+}
+
+void Q3DScenePrivate::setViewport(const QRect &viewport)
+{
+ if (m_viewport != viewport && viewport.isValid()) {
+ m_viewport = viewport;
+ calculateSubViewports();
+ emit needRender();
+ }
+}
+
+void Q3DScenePrivate::setViewportSize(int width, int height)
+{
+ if (m_viewport.width() != width || m_viewport.height() != height) {
+ m_viewport.setWidth(width);
+ m_viewport.setHeight(height);
+ calculateSubViewports();
+ emit needRender();
+ }
+}
+
+/*!
+ * \internal
+ * Sets the size of the window being rendered to. With widget based graphs, this
+ * is equal to the size of the QWindow and is same as the bounding rectangle.
+ * With declarative graphs this is equal to the size of the QQuickWindow and
+ * can be different from the bounding rectangle.
+ */
+void Q3DScenePrivate::setWindowSize(const QSize &size)
+{
+ if (m_windowSize != size) {
+ m_windowSize = size;
+ updateGLViewport();
+ m_changeTracker.windowSizeChanged = true;
+ emit needRender();
+ }
+}
+
+QSize Q3DScenePrivate::windowSize() const
+{
+ return m_windowSize;
+}
+
+void Q3DScenePrivate::calculateSubViewports()
+{
+ // Calculates the default subviewport layout, used when slicing
+ const float smallerViewPortRatio = 0.2f;
+ m_defaultSmallViewport = QRect(0, 0,
+ m_viewport.width() * smallerViewPortRatio,
+ m_viewport.height() * smallerViewPortRatio);
+ m_defaultLargeViewport = QRect(0, 0,
+ m_viewport.width(),
+ m_viewport.height());
+
+ updateGLViewport();
+}
+
+void Q3DScenePrivate::updateGLViewport()
+{
+ // Update GL viewport
+ m_glViewport.setX(m_viewport.x() * m_devicePixelRatio);
+ m_glViewport.setY((m_windowSize.height() - (m_viewport.y() + m_viewport.height()))
+ * m_devicePixelRatio);
+ m_glViewport.setWidth(m_viewport.width() * m_devicePixelRatio);
+ m_glViewport.setHeight(m_viewport.height() * m_devicePixelRatio);
+
+ m_changeTracker.viewportChanged = true;
+ m_sceneDirty = true;
+
+ // Do default subviewport changes first, then allow signal listeners to override.
+ updateGLSubViewports();
+ emit q_ptr->viewportChanged(m_viewport);
+}
+
+void Q3DScenePrivate::updateGLSubViewports()
+{
+ if (m_isSlicingActive) {
+ QRect primary = m_primarySubViewport;
+ QRect secondary = m_secondarySubViewport;
+ if (primary.isNull())
+ primary = m_defaultSmallViewport;
+ if (secondary.isNull())
+ secondary = m_defaultLargeViewport;
+
+ m_glPrimarySubViewport.setX((primary.x() + m_viewport.x()) * m_devicePixelRatio);
+ m_glPrimarySubViewport.setY((m_windowSize.height()
+ - (primary.y() + primary.height() + m_viewport.y()))
+ * m_devicePixelRatio);
+ m_glPrimarySubViewport.setWidth(primary.width() * m_devicePixelRatio);
+ m_glPrimarySubViewport.setHeight(primary.height() * m_devicePixelRatio);
+
+ m_glSecondarySubViewport.setX((secondary.x() + m_viewport.x()) * m_devicePixelRatio);
+ m_glSecondarySubViewport.setY((m_windowSize.height()
+ - (secondary.y() + secondary.height() + m_viewport.y()))
+ * m_devicePixelRatio);
+ m_glSecondarySubViewport.setWidth(secondary.width() * m_devicePixelRatio);
+ m_glSecondarySubViewport.setHeight(secondary.height() * m_devicePixelRatio);
+ } else {
+ m_glPrimarySubViewport.setX(m_viewport.x() * m_devicePixelRatio);
+ m_glPrimarySubViewport.setY((m_windowSize.height() - (m_viewport.y() + m_viewport.height()))
+ * m_devicePixelRatio);
+ m_glPrimarySubViewport.setWidth(m_viewport.width() * m_devicePixelRatio);
+ m_glPrimarySubViewport.setHeight(m_viewport.height() * m_devicePixelRatio);
+
+ m_glSecondarySubViewport = QRect();
+ }
+}
+
+QRect Q3DScenePrivate::glViewport()
+{
+ return m_glViewport;
+}
+
+QRect Q3DScenePrivate::glPrimarySubViewport()
+{
+ return m_glPrimarySubViewport;
+}
+
+QRect Q3DScenePrivate::glSecondarySubViewport()
+{
+ return m_glSecondarySubViewport;
+}
+
+/*!
+ * \internal
+ * Calculates and sets the light position relative to the currently active camera using the given
+ * parameters.
+ * The relative 3D offset to the current camera position is defined in \a relativePosition.
+ * Optional \a fixedRotation fixes the light rotation around the graph area to the
+ * given value in degrees.
+ * Optional \a distanceModifier modifies the distance of the light from the graph.
+ */
+void Q3DScenePrivate::setLightPositionRelativeToCamera(const QVector3D &relativePosition,
+ float fixedRotation, float distanceModifier)
+{
+ m_light->setPosition(m_camera->d_ptr->calculatePositionRelativeToCamera(relativePosition,
+ fixedRotation,
+ distanceModifier));
+}
+
+void Q3DScenePrivate::markDirty()
+{
+ m_sceneDirty = true;
+ emit needRender();
+}
+
+bool Q3DScenePrivate::isInArea(const QRect &area, int x, int y) const
+{
+ int areaMinX = area.x();
+ int areaMaxX = area.x() + area.width();
+ int areaMinY = area.y();
+ int areaMaxY = area.y() + area.height();
+ return ( x >= areaMinX && x <= areaMaxX && y >= areaMinY && y <= areaMaxY );
+}
+
+QT_END_NAMESPACE
diff --git a/src/graphs/engine/q3dscene.h b/src/graphs/engine/q3dscene.h
new file mode 100644
index 0000000..9d26fb5
--- /dev/null
+++ b/src/graphs/engine/q3dscene.h
@@ -0,0 +1,97 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef Q3DSCENE_H
+#define Q3DSCENE_H
+
+#include <QtGraphs/qgraphsglobal.h>
+#include <QtGraphs/q3dcamera.h>
+#include <QtGraphs/q3dlight.h>
+#include <QtCore/QObject>
+#include <QtCore/QRect>
+
+QT_BEGIN_NAMESPACE
+
+class Q3DScenePrivate;
+
+class Q_GRAPHS_EXPORT Q3DScene : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QRect viewport READ viewport NOTIFY viewportChanged)
+ Q_PROPERTY(QRect primarySubViewport READ primarySubViewport WRITE setPrimarySubViewport NOTIFY primarySubViewportChanged)
+ Q_PROPERTY(QRect secondarySubViewport READ secondarySubViewport WRITE setSecondarySubViewport NOTIFY secondarySubViewportChanged)
+ Q_PROPERTY(QPoint selectionQueryPosition READ selectionQueryPosition WRITE setSelectionQueryPosition NOTIFY selectionQueryPositionChanged)
+ Q_PROPERTY(bool secondarySubviewOnTop READ isSecondarySubviewOnTop WRITE setSecondarySubviewOnTop NOTIFY secondarySubviewOnTopChanged)
+ Q_PROPERTY(bool slicingActive READ isSlicingActive WRITE setSlicingActive NOTIFY slicingActiveChanged)
+ Q_PROPERTY(Q3DCamera* activeCamera READ activeCamera WRITE setActiveCamera NOTIFY activeCameraChanged)
+ Q_PROPERTY(Q3DLight* activeLight READ activeLight WRITE setActiveLight NOTIFY activeLightChanged)
+ Q_PROPERTY(float devicePixelRatio READ devicePixelRatio WRITE setDevicePixelRatio NOTIFY devicePixelRatioChanged)
+ Q_PROPERTY(QPoint graphPositionQuery READ graphPositionQuery WRITE setGraphPositionQuery NOTIFY graphPositionQueryChanged)
+
+public:
+ explicit Q3DScene(QObject *parent = nullptr);
+ virtual ~Q3DScene();
+
+ QRect viewport() const;
+
+ QRect primarySubViewport() const;
+ void setPrimarySubViewport(const QRect &primarySubViewport);
+ bool isPointInPrimarySubView(const QPoint &point);
+
+ QRect secondarySubViewport() const;
+ void setSecondarySubViewport(const QRect &secondarySubViewport);
+ bool isPointInSecondarySubView(const QPoint &point);
+
+ void setSelectionQueryPosition(const QPoint &point);
+ QPoint selectionQueryPosition() const;
+ static QPoint invalidSelectionPoint();
+
+ void setGraphPositionQuery(const QPoint &point);
+ QPoint graphPositionQuery() const;
+
+ void setSlicingActive(bool isSlicing);
+ bool isSlicingActive() const;
+
+ void setSecondarySubviewOnTop(bool isSecondaryOnTop);
+ bool isSecondarySubviewOnTop() const;
+
+ Q3DCamera *activeCamera() const;
+ void setActiveCamera(Q3DCamera *camera);
+
+ Q3DLight *activeLight() const;
+ void setActiveLight(Q3DLight *light);
+
+ float devicePixelRatio() const;
+ void setDevicePixelRatio(float pixelRatio);
+
+Q_SIGNALS:
+ void viewportChanged(const QRect &viewport);
+ void primarySubViewportChanged(const QRect &subViewport);
+ void secondarySubViewportChanged(const QRect &subViewport);
+ void secondarySubviewOnTopChanged(bool isSecondaryOnTop);
+ void slicingActiveChanged(bool isSlicingActive);
+ void activeCameraChanged(Q3DCamera *camera);
+ void activeLightChanged(Q3DLight *light);
+ void devicePixelRatioChanged(float pixelRatio);
+ void selectionQueryPositionChanged(const QPoint &position);
+ void graphPositionQueryChanged(const QPoint &position);
+
+private:
+ QScopedPointer<Q3DScenePrivate> d_ptr;
+
+ Q_DISABLE_COPY(Q3DScene)
+
+ friend class AbstractDeclarative;
+ friend class QAbstract3DGraph;
+ friend class QAbstract3DGraphPrivate;
+ friend class Abstract3DController;
+ friend class Bars3DController;
+ friend class Q3DScenePrivate;
+ friend class Q3DCameraPrivate;
+ friend class Q3DObject;
+ friend class QQuickGraphsItem;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/engine/q3dscene_p.h b/src/graphs/engine/q3dscene_p.h
new file mode 100644
index 0000000..7eb9501
--- /dev/null
+++ b/src/graphs/engine/q3dscene_p.h
@@ -0,0 +1,112 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QtGraphs API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#ifndef Q3DSCENE_P_H
+#define Q3DSCENE_P_H
+
+#include <private/graphsglobal_p.h>
+#include <QtGraphs/q3dscene.h>
+
+QT_BEGIN_NAMESPACE
+
+class Q3DCamera;
+class Q3DLight;
+
+struct Q3DSceneChangeBitField {
+ bool viewportChanged : 1;
+ bool primarySubViewportChanged : 1;
+ bool secondarySubViewportChanged : 1;
+ bool subViewportOrderChanged : 1;
+ bool cameraChanged : 1;
+ bool lightChanged : 1;
+ bool slicingActivatedChanged : 1;
+ bool devicePixelRatioChanged : 1;
+ bool selectionQueryPositionChanged : 1;
+ bool graphPositionQueryPositionChanged : 1;
+ bool windowSizeChanged : 1;
+
+ Q3DSceneChangeBitField()
+ : viewportChanged(true),
+ primarySubViewportChanged(true),
+ secondarySubViewportChanged(true),
+ subViewportOrderChanged(true),
+ cameraChanged(true),
+ lightChanged(true),
+ slicingActivatedChanged(true),
+ devicePixelRatioChanged(true),
+ selectionQueryPositionChanged(false),
+ graphPositionQueryPositionChanged(false),
+ windowSizeChanged(true)
+ {
+ }
+};
+
+class Q_GRAPHS_EXPORT Q3DScenePrivate : public QObject
+{
+ Q_OBJECT
+public:
+ Q3DScenePrivate(Q3DScene *q);
+ ~Q3DScenePrivate();
+
+ void sync(Q3DScenePrivate &other);
+
+ void setViewport(const QRect &viewport);
+ void setViewportSize(int width, int height);
+ void setWindowSize(const QSize &size);
+ QSize windowSize() const;
+ void calculateSubViewports();
+ void updateGLViewport();
+ void updateGLSubViewports();
+
+ QRect glViewport();
+ QRect glPrimarySubViewport();
+ QRect glSecondarySubViewport();
+
+ void setLightPositionRelativeToCamera(const QVector3D &relativePosition,
+ float fixedRotation = 0.0f,
+ float distanceModifier = 0.0f);
+
+ void markDirty();
+
+ bool isInArea(const QRect &area, int x, int y) const;
+
+Q_SIGNALS:
+ void needRender();
+
+public:
+ Q3DScene *q_ptr;
+ Q3DSceneChangeBitField m_changeTracker;
+
+ QRect m_viewport;
+ QRect m_primarySubViewport;
+ QRect m_secondarySubViewport;
+ bool m_isSecondarySubviewOnTop;
+ float m_devicePixelRatio;
+ Q3DCamera *m_camera;
+ Q3DLight *m_light;
+ bool m_isUnderSideCameraEnabled;
+ bool m_isSlicingActive;
+ QPoint m_selectionQueryPosition;
+ QPoint m_graphPositionQueryPosition;
+ QSize m_windowSize;
+ QRect m_glViewport;
+ QRect m_glPrimarySubViewport;
+ QRect m_glSecondarySubViewport;
+ bool m_sceneDirty;
+ QRect m_defaultSmallViewport;
+ QRect m_defaultLargeViewport;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/engine/q3dsurface.cpp b/src/graphs/engine/q3dsurface.cpp
new file mode 100644
index 0000000..54fe48c
--- /dev/null
+++ b/src/graphs/engine/q3dsurface.cpp
@@ -0,0 +1,312 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "q3dsurface.h"
+#include "qquickgraphssurface_p.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ * \class Q3DSurface
+ * \inmodule QtGraphs
+ * \brief The Q3DSurface class provides methods for rendering 3D surface plots.
+ *
+ * This class enables developers to render 3D surface plots and to view them by rotating the scene
+ * freely. The visual properties of the surface such as draw mode and shading can be controlled
+ * via QSurface3DSeries.
+ *
+ * The Q3DSurface supports selection by showing a highlighted ball on the data point where the user has clicked
+ * with left mouse button (when default input handler is in use) or selected via QSurface3DSeries.
+ * The selection pointer is accompanied with a label which in default case shows the value of the
+ * data point and the coordinates of the point.
+ *
+ * The value range and the label format shown on the axis can be controlled through QValue3DAxis.
+ *
+ * To rotate the graph, hold down the right mouse button and move the mouse. Zooming is done using mouse
+ * wheel. Both assume the default input handler is in use.
+ *
+ * If no axes are set explicitly to Q3DSurface, temporary default axes with no labels are created.
+ * These default axes can be modified via axis accessors, but as soon any axis is set explicitly
+ * for the orientation, the default axis for that orientation is destroyed.
+ *
+ * \section1 How to construct a minimal Q3DSurface graph
+ *
+ * First, construct Q3DSurface. Since we are running the graph as top level window
+ * in this example, we need to clear the \c Qt::FramelessWindowHint flag, which gets set by
+ * default:
+ *
+ * \snippet doc_src_q3dsurface_construction.cpp 0
+ *
+ * Now Q3DSurface is ready to receive data to be rendered. Create data elements to receive values:
+ *
+ * \snippet doc_src_q3dsurface_construction.cpp 1
+ *
+ * First feed the data to the row elements and then add their pointers to the data element:
+ *
+ * \snippet doc_src_q3dsurface_construction.cpp 2
+ *
+ * Create a new series and set data to it:
+ *
+ * \snippet doc_src_q3dsurface_construction.cpp 3
+ *
+ * Finally you will need to set it visible:
+ *
+ * \snippet doc_src_q3dsurface_construction.cpp 4
+ *
+ * The complete code needed to create and display this graph is:
+ *
+ * \snippet doc_src_q3dsurface_construction.cpp 5
+ *
+ * And this is what those few lines of code produce:
+ *
+ * \image q3dsurface-minimal.png
+ *
+ * The scene can be rotated, zoomed into, and a surface point can be selected to view its position,
+ * but no other interaction is included in this minimal code example.
+ * You can learn more by familiarizing yourself with the examples provided,
+ * like the \l{Surface Example}.
+ *
+ *
+ * \sa Q3DBars, Q3DScatter, {Qt Graphs C++ Classes}
+ */
+
+/*!
+ * Constructs a new 3D surface graph with optional \a parent window
+ * and surface \a format.
+ */
+
+/*!
+ * Destroys the 3D surface graph.
+ */
+
+/*!
+ * Adds the \a series to the graph. A graph can contain multiple series, but has only one set of
+ * axes. If the newly added series has specified a selected item, it will be highlighted and
+ * any existing selection will be cleared. Only one added series can have an active selection.
+ *
+ * \sa QAbstract3DGraph::hasSeries()
+ */
+
+/*!
+ * Removes the \a series from the graph.
+ *
+ * \sa QAbstract3DGraph::hasSeries()
+ */
+
+/*!
+ * Returns the list of series added to this graph.
+ *
+ * \sa QAbstract3DGraph::hasSeries()
+ */
+
+/*!
+ * \property Q3DSurface::axisX
+ *
+ * \brief The active x-axis.
+ */
+
+/*!
+ * Sets \a axis as the active x-axis. Implicitly calls addAxis() to transfer the
+ * ownership of the axis to this graph.
+ *
+ * If \a axis is null, a temporary default axis with no labels and an
+ * automatically adjusting range is created.
+ *
+ * This temporary axis is destroyed if another axis is set explicitly to the
+ * same orientation.
+ *
+ * \sa addAxis(), releaseAxis()
+ */
+
+/*!
+ * \property Q3DSurface::axisY
+ *
+ * \brief The active y-axis.
+ */
+
+/*!
+ * Sets \a axis as the active y-axis. Implicitly calls addAxis() to transfer the
+ * ownership of the axis to this graph.
+ *
+ * If \a axis is null, a temporary default axis with no labels and an
+ * automatically adjusting range is created.
+ *
+ * This temporary axis is destroyed if another axis is set explicitly to the
+ * same orientation.
+ *
+ * \sa addAxis(), releaseAxis()
+ */
+
+/*!
+ * \property Q3DSurface::axisZ
+ *
+ * \brief The active z-axis.
+ */
+
+/*!
+ * Sets \a axis as the active z-axis. Implicitly calls addAxis() to transfer the
+ * ownership of the axis to this graph.
+ *
+ * If \a axis is null, a temporary default axis with no labels and an
+ * automatically adjusting range is created.
+ *
+ * This temporary axis is destroyed if another axis is set explicitly to the
+ * same orientation.
+ *
+ * \sa addAxis(), releaseAxis()
+ */
+
+/*!
+ * \property Q3DSurface::selectedSeries
+ *
+ * \brief The selected series or null.
+ *
+ * If selectionMode has \c SelectionMultiSeries set, this
+ * property holds the series which owns the selected point.
+ */
+
+/*!
+ * \property Q3DSurface::flipHorizontalGrid
+ *
+ * \brief Whether the horizontal axis grid is displayed on top of the graph
+ * rather than on the bottom.
+ *
+ * In some use cases the horizontal axis grid is mostly covered by the surface, so it can be more
+ * useful to display the horizontal axis grid on top of the graph rather than on the bottom.
+ * A typical use case for this is showing 2D spectrograms using orthoGraphic projection with
+ * a top-down viewpoint.
+ *
+ * If \c{false}, the horizontal axis grid and labels are drawn on the horizontal background
+ * of the graph.
+ * If \c{true}, the horizontal axis grid and labels are drawn on the opposite side of the graph
+ * from the horizontal background.
+ * Defaults to \c{false}.
+ */
+
+/*!
+ * Adds \a axis to the graph. The axes added via addAxis are not yet taken to use,
+ * addAxis is simply used to give the ownership of the \a axis to the graph.
+ * The \a axis must not be null or added to another graph.
+ *
+ * \sa releaseAxis(), setAxisX(), setAxisY(), setAxisZ()
+ */
+
+/*!
+ * Releases the ownership of the \a axis back to the caller, if it is added to this graph.
+ * If the released \a axis is in use, a new default axis will be created and set active.
+ *
+ * If the default axis is released and added back later, it behaves as any other axis would.
+ *
+ * \sa addAxis(), setAxisX(), setAxisY(), setAxisZ()
+ */
+
+/*!
+ * Returns the list of all added axes.
+ *
+ * \sa addAxis()
+ */
+
+Q3DSurface::Q3DSurface() : QAbstract3DGraph()
+{
+ QQmlComponent *component = new QQmlComponent(engine(), this);
+ component->setData("import QtQuick; import QtGraphs; Surface3D { anchors.fill: parent; }", QUrl());
+ d_ptr.reset(qobject_cast<QQuickGraphsSurface *>(component->create()));
+ setContent(component->url(), component, d_ptr.data());
+}
+
+Q3DSurface::~Q3DSurface()
+{
+}
+
+void Q3DSurface::addSeries(QSurface3DSeries *series)
+{
+ dptr()->addSeries(series);
+}
+
+void Q3DSurface::removeSeries(QSurface3DSeries *series)
+{
+ dptr()->removeSeries(series);
+}
+
+QList<QSurface3DSeries *> Q3DSurface::seriesList() const
+{
+ return dptrc()->m_surfaceController->surfaceSeriesList();
+}
+
+void Q3DSurface::setAxisX(QValue3DAxis *axis)
+{
+ dptr()->setAxisX(axis);
+}
+
+QValue3DAxis *Q3DSurface::axisX() const
+{
+ return static_cast<QValue3DAxis *>(dptrc()->axisX());
+}
+
+void Q3DSurface::setAxisY(QValue3DAxis *axis)
+{
+ dptr()->setAxisY(axis);
+}
+
+QValue3DAxis *Q3DSurface::axisY() const
+{
+ return static_cast<QValue3DAxis *>(dptrc()->axisY());
+}
+
+void Q3DSurface::setAxisZ(QValue3DAxis *axis)
+{
+ dptr()->setAxisZ(axis);
+}
+
+QValue3DAxis *Q3DSurface::axisZ() const
+{
+ return static_cast<QValue3DAxis *>(dptrc()->axisZ());
+}
+
+QSurface3DSeries *Q3DSurface::selectedSeries() const
+{
+ return dptrc()->selectedSeries();
+}
+
+void Q3DSurface::setFlipHorizontalGrid(bool flip)
+{
+ dptr()->setFlipHorizontalGrid(flip);
+}
+
+bool Q3DSurface::flipHorizontalGrid() const
+{
+ return dptrc()->flipHorizontalGrid();
+}
+
+void Q3DSurface::addAxis(QValue3DAxis *axis)
+{
+ return dptrc()->m_surfaceController->addAxis(axis);
+}
+
+void Q3DSurface::releaseAxis(QValue3DAxis *axis)
+{
+ return dptrc()->m_surfaceController->releaseAxis(axis);
+}
+
+QList<QValue3DAxis *> Q3DSurface::axes() const
+{
+ QList<QAbstract3DAxis *> abstractAxes = dptrc()->m_surfaceController->axes();
+ QList<QValue3DAxis *> retList;
+ for (QAbstract3DAxis *axis : abstractAxes)
+ retList.append(static_cast<QValue3DAxis *>(axis));
+
+ return retList;
+}
+
+QQuickGraphsSurface *Q3DSurface::dptr()
+{
+ return static_cast<QQuickGraphsSurface *>(d_ptr.data());
+}
+
+const QQuickGraphsSurface *Q3DSurface::dptrc() const
+{
+ return static_cast<const QQuickGraphsSurface *>(d_ptr.data());
+}
+
+
+QT_END_NAMESPACE
diff --git a/src/graphs/engine/q3dsurface.h b/src/graphs/engine/q3dsurface.h
new file mode 100644
index 0000000..3460283
--- /dev/null
+++ b/src/graphs/engine/q3dsurface.h
@@ -0,0 +1,62 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef Q3DSURFACE_H
+#define Q3DSURFACE_H
+
+#include <QtGraphs/qabstract3dgraph.h>
+#include <QtGraphs/qvalue3daxis.h>
+#include <QtGraphs/qsurface3dseries.h>
+#include <QtQuick/QQuickWindow>
+
+QT_BEGIN_NAMESPACE
+
+class QQuickGraphsSurface;
+
+class Q_GRAPHS_EXPORT Q3DSurface : public QAbstract3DGraph
+{
+ Q_OBJECT
+ Q_PROPERTY(QValue3DAxis *axisX READ axisX WRITE setAxisX NOTIFY axisXChanged)
+ Q_PROPERTY(QValue3DAxis *axisY READ axisY WRITE setAxisY NOTIFY axisYChanged)
+ Q_PROPERTY(QValue3DAxis *axisZ READ axisZ WRITE setAxisZ NOTIFY axisZChanged)
+ Q_PROPERTY(QSurface3DSeries *selectedSeries READ selectedSeries NOTIFY selectedSeriesChanged)
+ Q_PROPERTY(bool flipHorizontalGrid READ flipHorizontalGrid WRITE setFlipHorizontalGrid NOTIFY flipHorizontalGridChanged)
+
+public:
+ explicit Q3DSurface();
+ virtual ~Q3DSurface();
+
+ void addSeries(QSurface3DSeries *series);
+ void removeSeries(QSurface3DSeries *series);
+ QList<QSurface3DSeries *> seriesList() const;
+
+ // Axes
+ void setAxisX(QValue3DAxis *axis);
+ QValue3DAxis *axisX() const;
+ void setAxisY(QValue3DAxis *axis);
+ QValue3DAxis *axisY() const;
+ void setAxisZ(QValue3DAxis *axis);
+ QValue3DAxis *axisZ() const;
+ void addAxis(QValue3DAxis *axis);
+ void releaseAxis(QValue3DAxis *axis);
+ QList<QValue3DAxis *> axes() const;
+
+ QSurface3DSeries *selectedSeries() const;
+ void setFlipHorizontalGrid(bool flip);
+ bool flipHorizontalGrid() const;
+
+Q_SIGNALS:
+ void axisXChanged(QValue3DAxis *axis);
+ void axisYChanged(QValue3DAxis *axis);
+ void axisZChanged(QValue3DAxis *axis);
+ void selectedSeriesChanged(QSurface3DSeries *series);
+ void flipHorizontalGridChanged(bool flip);
+
+private:
+ QQuickGraphsSurface *dptr();
+ const QQuickGraphsSurface *dptrc() const;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/engine/qabstract3dgraph.cpp b/src/graphs/engine/qabstract3dgraph.cpp
new file mode 100644
index 0000000..e045a12
--- /dev/null
+++ b/src/graphs/engine/qabstract3dgraph.cpp
@@ -0,0 +1,634 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qabstract3dgraph.h"
+#include "abstract3dcontroller_p.h"
+#include "qabstract3dinputhandler_p.h"
+#include "q3dscene_p.h"
+#include "utils_p.h"
+#include "qquickgraphsitem_p.h"
+
+#include <QtGui/QGuiApplication>
+#include <QtGui/QOpenGLContext>
+#include <QtOpenGL/QOpenGLPaintDevice>
+#include <QtGui/QPainter>
+#include <QtOpenGL/QOpenGLFramebufferObject>
+#include <QtGui/QOffscreenSurface>
+#if defined(Q_OS_OSX)
+#include <qpa/qplatformnativeinterface.h>
+#endif
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ * \class QAbstract3DGraph
+ * \inmodule QtGraphs
+ * \brief The QAbstract3DGraph class provides a window and render loop for graphs.
+ *
+ * This class subclasses a QWindow and provides render loop for graphs inheriting it.
+ *
+ * You should not need to use this class directly, but one of its subclasses instead.
+ *
+ * Anti-aliasing is turned on by default on C++, except in OpenGL ES2
+ * environments, where anti-aliasing is not supported by Qt Graphs.
+ * To specify non-default anti-aliasing for a graph, give a custom surface format as
+ * a constructor parameter. You can use the convenience function \c qDefaultSurfaceFormat()
+ * to create the surface format object.
+ *
+ * \note QAbstract3DGraph sets window flag \c Qt::FramelessWindowHint on by default. If you want to display
+ * graph windows as standalone windows with regular window frame, clear this flag after constructing
+ * the graph. For example:
+ *
+ * \code
+ * Q3DBars *graphWindow = new Q3DBars;
+ * graphWindow->setFlags(graphWindow->flags() ^ Qt::FramelessWindowHint);
+ * \endcode
+ *
+ * \sa Q3DBars, Q3DScatter, Q3DSurface, {Qt Graphs C++ Classes}
+ */
+
+/*!
+ \enum QAbstract3DGraph::SelectionFlag
+
+ Item selection modes. Values of this enumeration can be combined with OR operator.
+
+ \value SelectionNone
+ Selection mode disabled.
+ \value SelectionItem
+ Selection highlights a single item.
+ \value SelectionRow
+ Selection highlights a single row.
+ \value SelectionItemAndRow
+ Combination flag for highlighting both item and row with different colors.
+ \value SelectionColumn
+ Selection highlights a single column.
+ \value SelectionItemAndColumn
+ Combination flag for highlighting both item and column with different colors.
+ \value SelectionRowAndColumn
+ Combination flag for highlighting both row and column.
+ \value SelectionItemRowAndColumn
+ Combination flag for highlighting item, row, and column.
+ \value SelectionSlice
+ Setting this mode flag indicates that the graph should take care of the slice view handling
+ automatically. If you wish to control the slice view yourself via Q3DScene, do not set this
+ flag. When setting this mode flag, either \c SelectionRow or \c SelectionColumn must also
+ be set, but not both. Slicing is supported by Q3DBars and Q3DSurface only.
+ When this flag is set, slice mode is entered in the following situations:
+ \list
+ \li When selection is changed explicitly via series API to a visible item
+ \li When selection is changed by clicking on the graph
+ \li When the selection mode changes and the selected item is visible
+ \endlist
+ \value SelectionMultiSeries
+ Setting this mode means that items for all series at same position are highlighted, instead
+ of just the selected item. The actual selection in the other series doesn't change.
+ When setting this mode flag, one or more of the basic selection flags (\c {SelectionItem},
+ \c {SelectionRow}, or \c SelectionColumn) must also be set.
+ Multi-series selection is not supported for Q3DScatter.
+*/
+
+/*!
+ \enum QAbstract3DGraph::ShadowQuality
+
+ Quality of shadows.
+
+ \value ShadowQualityNone
+ Shadows are disabled.
+ \value ShadowQualityLow
+ Shadows are rendered in low quality.
+ \value ShadowQualityMedium
+ Shadows are rendered in medium quality.
+ \value ShadowQualityHigh
+ Shadows are rendered in high quality.
+ \value ShadowQualitySoftLow
+ Shadows are rendered in low quality with softened edges.
+ \value ShadowQualitySoftMedium
+ Shadows are rendered in medium quality with softened edges.
+ \value ShadowQualitySoftHigh
+ Shadows are rendered in high quality with softened edges.
+*/
+
+/*!
+ \enum QAbstract3DGraph::ElementType
+
+ Type of an element in the graph.
+
+ \value ElementNone
+ No defined element.
+ \value ElementSeries
+ A series (that is, an item in a series).
+ \value ElementAxisXLabel
+ The x-axis label.
+ \value ElementAxisYLabel
+ The y-axis label.
+ \value ElementAxisZLabel
+ The z-axis label.
+ \value ElementCustomItem
+ A custom item.
+*/
+
+/*!
+ \enum QAbstract3DGraph::OptimizationHint
+ \since Qt Graphs 1.1
+
+ The optimization hint for rendering.
+
+ \value OptimizationDefault
+ Provides the full feature set at a reasonable performance.
+ \value OptimizationStatic
+ Optimizes the rendering of static data sets at the expense of some features.
+*/
+
+/*!
+ * Destroys QAbstract3DGraph.
+ */
+
+/*!
+ * Adds the given \a inputHandler to the graph. The input handlers added via addInputHandler
+ * are not taken in to use directly. Only the ownership of the \a inputHandler is given to the graph.
+ * The \a inputHandler must not be null or already added to another graph.
+ *
+ * \sa releaseInputHandler(), setActiveInputHandler()
+ */
+
+/*!
+ * Releases the ownership of the \a inputHandler back to the caller, if it was added to this graph.
+ * If the released \a inputHandler is in use there will be no input handler active after this call.
+ *
+ * If the default input handler is released and added back later, it behaves as any other input handler would.
+ *
+ * \sa addInputHandler(), setActiveInputHandler()
+ */
+
+/*!
+ * \property QAbstract3DGraph::activeInputHandler
+ *
+ * \brief The active input handler used in the graph.
+ */
+
+/*!
+ * Sets \a inputHandler as the active input handler used in the graph.
+ * Implicitly calls addInputHandler() to transfer ownership of \a inputHandler
+ * to this graph.
+ *
+ * If \a inputHandler is null, no input handler will be active after this call.
+ *
+ * \sa addInputHandler(), releaseInputHandler()
+ */
+
+/*!
+ * Returns the list of all added input handlers.
+ *
+ * \sa addInputHandler()
+ */
+
+/*!
+ * Adds the given \a theme to the graph. The themes added via addTheme are not taken in to use
+ * directly. Only the ownership of the theme is given to the graph.
+ * The \a theme must not be null or already added to another graph.
+ *
+ * \sa releaseTheme(), setActiveTheme()
+ */
+
+/*!
+ * Releases the ownership of the \a theme back to the caller, if it was added to this graph.
+ * If the released \a theme is in use, a new default theme will be created and set active.
+ *
+ * If the default theme is released and added back later, it behaves as any other theme would.
+ *
+ * \sa addTheme(), setActiveTheme()
+ */
+
+/*!
+ * \property QAbstract3DGraph::activeTheme
+ *
+ * \brief The active theme of the graph.
+ */
+
+/*!
+ * Sets \a theme as the active theme to be used for the graph. Implicitly calls
+ * addTheme() to transfer the ownership of the theme to this graph.
+ *
+ * If \a theme is null, a temporary default theme is created. This temporary theme is destroyed
+ * if any theme is explicitly set later.
+ * Properties of the theme can be modified even after setting it, and the modifications take
+ * effect immediately.
+ */
+
+/*!
+ * Returns the list of all added themes.
+ *
+ * \sa addTheme()
+ */
+
+/*!
+ * \property QAbstract3DGraph::selectionMode
+ *
+ * \brief Item selection mode.
+ *
+ * A combination of SelectionFlags. By default, \c SelectionItem.
+ * Different graph types support different selection modes.
+ *
+ * \sa SelectionFlags
+ */
+
+/*!
+ * \property QAbstract3DGraph::shadowQuality
+ *
+ * \brief The quality of the shadow.
+ *
+ * One of the ShadowQuality enum values. By default, \c ShadowQualityMedium.
+ *
+ * \note If setting the shadow quality to a certain level fails, the level is lowered
+ * until it is successfully set. The \c shadowQualityChanged signal is emitted each time
+ * a change is made.
+ *
+ * \sa ShadowQuality
+ */
+
+/*!
+ * Returns \c true if shadows are supported with the current configuration.
+ * OpenGL ES2 configurations do not support shadows.
+ */
+
+/*!
+ * \property QAbstract3DGraph::scene
+ *
+ * \brief The Q3DScene pointer that can be used to manipulate the scene and
+ * access the scene elements, such as the active camera.
+ *
+ * This property is read-only.
+ */
+
+/*!
+ * Clears selection from all attached series.
+ */
+
+/*!
+ * Returns whether the \a series has already been added to the graph.
+ *
+ * \since 6.3
+ */
+
+/*!
+ * Adds a QCustom3DItem \a item to the graph. Graph takes ownership of the added item.
+ *
+ * Returns the index to the added item if the add operation was successful, -1
+ * if trying to add a null item, and the index of the item if trying to add an
+ * already added item.
+ *
+ * Items are rendered in the order they have been inserted. The rendering order needs to
+ * be taken into account when having solid and transparent items.
+ *
+ * \sa removeCustomItems(), removeCustomItem(), removeCustomItemAt(), customItems()
+ *
+ */
+
+/*!
+ * Removes all custom items. Deletes the resources allocated to them.
+ *
+ */
+
+/*!
+ * Removes the custom \a {item}. Deletes the resources allocated to it.
+ *
+ */
+
+/*!
+ * Removes all custom items at \a {position}. Deletes the resources allocated to them.
+ *
+ */
+
+/*!
+ * Gets ownership of given \a item back and removes the \a item from the graph.
+ *
+ *
+ * \note If the same item is added back to the graph, the texture or the texture file needs to be
+ * re-set.
+ *
+ * \sa QCustom3DItem::setTextureImage(), QCustom3DItem::setTextureFile()
+ */
+
+/*!
+ * Returns the list of all added custom items.
+ * \sa addCustomItem()
+ */
+
+/*!
+ * Can be used to query the index of the selected label after receiving \c selectedElementChanged
+ * signal with any label type. Selection is valid until the next \c selectedElementChanged signal.
+ *
+ * Returns the index of the selected label, or -1.
+ *
+ * \sa selectedElement
+ */
+
+/*!
+ * Can be used to get the selected axis after receiving \c selectedElementChanged signal with any label
+ * type. Selection is valid until the next \c selectedElementChanged signal.
+ *
+ * Returns the pointer to the selected axis, or null.
+ *
+ * \sa selectedElement
+ */
+
+/*!
+ * Can be used to query the index of the selected custom item after receiving \c selectedElementChanged
+ * signal with QAbstract3DGraph::ElementCustomItem type. Selection is valid until the next
+ * \c selectedElementChanged signal.
+ *
+ * Returns the index of the selected custom item, or -1.
+ *
+ * \sa selectedElement
+ */
+
+/*!
+ * Can be used to get the selected custom item after receiving \c selectedElementChanged signal with
+ * QAbstract3DGraph::ElementCustomItem type. Ownership of the item remains with the graph.
+ * Selection is valid until the next \c selectedElementChanged signal.
+ *
+ * Returns the pointer to the selected custom item, or null.
+ *
+ * \sa selectedElement
+ */
+
+/*!
+ * \property QAbstract3DGraph::selectedElement
+ *
+ * \brief The element selected in the graph.
+ *
+ * This property can be used to query the selected element type. The type is
+ * valid until a new selection is made in the graph and the
+ * \c selectedElementChanged signal is emitted.
+ *
+ * The signal can be used for example for implementing custom input handlers, as
+ * demonstrated by the \l {Axis Range Dragging With Labels Example}.
+ *
+ * \sa selectedLabelIndex(), selectedAxis(), selectedCustomItemIndex(), selectedCustomItem(),
+ * Q3DBars::selectedSeries(), Q3DScatter::selectedSeries(), Q3DSurface::selectedSeries(),
+ * Q3DScene::setSelectionQueryPosition()
+ *
+ */
+
+/*!
+ * Renders current frame to an image of \a imageSize. Default size is the window size. Image is
+ * rendered with antialiasing level given in \a msaaSamples. Default level is \c{0}.
+ *
+ * Returns the rendered image.
+ *
+ * \note OpenGL ES2 does not support anitialiasing, so \a msaaSamples is always forced to \c{0}.
+ */
+
+/*!
+ * \property QAbstract3DGraph::measureFps
+ *
+ * \brief Whether rendering is done continuously instead of on demand.
+ *
+ * If \c {true}, rendering is continuous and the value of the currentFps
+ * property is updated. Defaults to \c{false}.
+ *
+ * \sa currentFps
+ */
+
+/*!
+ * \property QAbstract3DGraph::currentFps
+ *
+ * \brief The rendering results for the last second.
+ *
+ * The results are stored in this read-only property when FPS measuring is
+ * enabled. It takes at least a second before this value is updated after
+ * measuring is activated.
+ *
+ * \sa measureFps
+ */
+
+/*!
+ * \property QAbstract3DGraph::orthoProjection
+ *
+ * \brief Whether orthographic projection is used for displaying the graph.
+ *
+ * Defaults to \c{false}.
+ * \note Shadows will be disabled when set to \c{true}.
+ *
+ * \sa QAbstract3DAxis::labelAutoRotation, Q3DCamera::cameraPreset
+ */
+
+/*!
+ * \property QAbstract3DGraph::aspectRatio
+ *
+ * \brief The ratio of the graph scaling between the longest axis on the
+ * horizontal plane and the y-axis.
+ *
+ * Defaults to \c{2.0}.
+ *
+ * \note Has no effect on Q3DBars.
+ *
+ * \sa horizontalAspectRatio
+ */
+
+/*!
+ * \property QAbstract3DGraph::optimizationHints
+ *
+ * \brief Whether the default or static mode is used for rendering optimization.
+ *
+ * The default mode provides the full feature set at a reasonable level of
+ * performance. The static mode optimizes graph rendering and is ideal for
+ * large non-changing data sets. It is slower with dynamic data changes and item rotations.
+ * Selection is not optimized, so using the static mode with massive data sets is not advisable.
+ * Static optimization works only on scatter graphs.
+ * Defaults to \l{OptimizationDefault}.
+ *
+ * \note On some environments, large graphs using static optimization may not render, because
+ * all of the items are rendered using a single draw call, and different graphics drivers
+ * support different maximum vertice counts per call.
+ * This is mostly an issue on 32bit and OpenGL ES2 platforms.
+ * To work around this issue, choose an item mesh with a low vertex count or use
+ * the point mesh.
+ *
+ * \sa QAbstract3DSeries::mesh
+ */
+
+/*!
+ * \property QAbstract3DGraph::polar
+ *
+ * \brief Whether horizontal axes are changed into polar axes.
+ *
+ * If \c {true}, the x-axis becomes the angular axis and the z-axis becomes the
+ * radial axis.
+ * Polar mode is not available for bar graphs.
+ *
+ * Defaults to \c{false}.
+ *
+ * \sa orthoProjection, radialLabelOffset
+ */
+
+/*!
+ * \property QAbstract3DGraph::radialLabelOffset
+ *
+ * \brief The normalized horizontal offset for the axis labels of the radial
+ * polar axis.
+ *
+ * The value \c 0.0 indicates that the labels should be drawn next to the 0-angle
+ * angular axis grid line. The value \c 1.0 indicates that the labels are drawn
+ * in their usual place at the edge of the graph background. Defaults to \c 1.0.
+ *
+ * This property is ignored if the \l polar property value is \c{false}.
+ *
+ * \sa polar
+ */
+
+/*!
+ * \property QAbstract3DGraph::horizontalAspectRatio
+ *
+ * \brief The ratio of the graph scaling between the x-axis and z-axis.
+ *
+ * The value of \c 0.0 indicates automatic scaling according to axis ranges.
+ * Defaults to \c{0.0}.
+ *
+ * Has no effect on Q3DBars, which handles scaling on the horizontal plane via
+ * the \l{Q3DBars::barThickness}{barThickness} and \l{Q3DBars::barSpacing}{barSpacing} properties.
+ * Polar graphs also ignore this property.
+ *
+ * \sa aspectRatio, polar, Q3DBars::barThickness, Q3DBars::barSpacing
+ */
+
+/*!
+ * \property QAbstract3DGraph::reflection
+ *
+ * \brief Whether floor reflections are on or off.
+ *
+ * Defaults to \c{false}.
+ *
+ * Affects only Q3DBars. However, in Q3DBars graphs holding both positive and
+ * negative values, reflections are not supported for custom items that
+ * intersect the floor plane. In that case, reflections should be turned off
+ * to avoid incorrect rendering.
+ *
+ * If using a custom surface format, the stencil buffer needs to be defined
+ * (QSurfaceFormat::setStencilBufferSize()) for reflections to work.
+ *
+ * \sa reflectivity
+ */
+
+/*!
+ * \property QAbstract3DGraph::reflectivity
+ *
+ * \brief Floor reflectivity.
+ *
+ * Larger numbers make the floor more reflective. The valid range is \c{[0...1]}.
+ * Defaults to \c{0.5}.
+ *
+ * \note Affects only Q3DBars.
+ *
+ * \sa reflection
+ */
+
+/*!
+ * \property QAbstract3DGraph::locale
+ *
+ * \brief The locale used for formatting various numeric labels.
+ *
+ * Defaults to the \c{"C"} locale.
+ *
+ * \sa QValue3DAxis::labelFormat
+ */
+
+/*!
+ * \property QAbstract3DGraph::queriedGraphPosition
+ *
+ * \brief The latest queried graph position values along each axis.
+ *
+ * This read-only property contains the results from
+ * Q3DScene::graphPositionQuery. The values are normalized to the range \c{[-1, 1]}.
+ * If the queried position was outside the graph bounds, the values
+ * will not reflect the real position, but will instead indicate an undefined position outside
+ * the range \c{[-1, 1]}. The value will be undefined until a query is made.
+ *
+ * There is no single correct 3D coordinate to match a particular screen position, so to be
+ * consistent, the queries are always done against the inner sides of an invisible box surrounding
+ * the graph.
+ *
+ * \note Bar graphs only allow querying graph position at the graph floor level,
+ * so the y-value is always zero for bar graphs and the valid queries can be only made at
+ * screen positions that contain the floor of the graph.
+ *
+ * \sa Q3DScene::graphPositionQuery
+ */
+
+/*!
+ * \property QAbstract3DGraph::margin
+ *
+ * \brief The absolute value used for the space left between the edge of the
+ * plottable graph area and the edge of the graph background.
+ *
+ * If the margin value is negative, the margins are determined automatically and can vary according
+ * to the size of the items in the series and the type of the graph.
+ * The value is interpreted as a fraction of the y-axis range if the graph
+ * aspect ratios have not beed changed from the default values.
+ * Defaults to \c{-1.0}.
+ *
+ * \note Setting a smaller margin for a scatter graph than the automatically
+ * determined margin can cause the scatter items at the edges of the graph to
+ * overlap with the graph background.
+ *
+ * \note On scatter and surface graphs, if the margin is small in comparison to the axis label
+ * size, the positions of the edge labels of the axes are adjusted to avoid overlap with
+ * the edge labels of the neighboring axes.
+ */
+
+/*!
+ * Returns \c{true} if the OpenGL context of the graph has been successfully initialized.
+ * Trying to use a graph when the context initialization has failed typically results in a crash.
+ * A common reason for a context initialization failure is lack of sufficient platform support
+ * for OpenGL.
+ */
+
+QAbstract3DGraph::QAbstract3DGraph()
+{
+}
+
+QAbstract3DGraph::~QAbstract3DGraph()
+{
+}
+
+Q3DScene *QAbstract3DGraph::scene() const
+{
+ return (Q3DScene *)d_ptr->scene();
+}
+
+QAbstract3DGraph::ShadowQuality QAbstract3DGraph::shadowQuality() const
+{
+ return QAbstract3DGraph::ShadowQuality(d_ptr->shadowQuality());
+}
+
+void QAbstract3DGraph::setShadowQuality(const QAbstract3DGraph::ShadowQuality &shadowQuality)
+{
+ d_ptr->setShadowQuality(QQuickGraphsItem::ShadowQuality(shadowQuality));
+ emit shadowQualityChanged(shadowQuality);
+}
+
+Q3DTheme *QAbstract3DGraph::activeTheme() const
+{
+ return d_ptr->theme();
+}
+
+void QAbstract3DGraph::setActiveTheme(Q3DTheme *activeTheme)
+{
+ d_ptr->setTheme(activeTheme);
+ emit activeThemeChanged(activeTheme);
+}
+
+QAbstract3DGraph::SelectionFlags QAbstract3DGraph::selectionMode() const
+{
+ int intmode = int(d_ptr->selectionMode());
+ return QAbstract3DGraph::SelectionFlags(intmode);
+}
+
+void QAbstract3DGraph::setSelectionMode(const QAbstract3DGraph::SelectionFlags &selectionMode)
+{
+ int intmode = int(selectionMode);
+ d_ptr->setSelectionMode(QQuickGraphsItem::SelectionFlags(intmode));
+ emit selectionModeChanged(selectionMode);
+}
+
+QT_END_NAMESPACE
diff --git a/src/graphs/engine/qabstract3dgraph.h b/src/graphs/engine/qabstract3dgraph.h
new file mode 100644
index 0000000..8eb263a
--- /dev/null
+++ b/src/graphs/engine/qabstract3dgraph.h
@@ -0,0 +1,108 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QABSTRACT3DGRAPH_H
+#define QABSTRACT3DGRAPH_H
+
+#include <QtGraphs/qgraphsglobal.h>
+#include <QtGraphs/q3dtheme.h>
+#include <QtGraphs/q3dscene.h>
+#include <QtGraphs/qabstract3dinputhandler.h>
+#include <QtGui/QWindow>
+#include <QtGui/QOpenGLFunctions>
+#include <QtCore/QLocale>
+#include <QtQuick/QQuickWindow>
+#include <QtQuickWidgets/QQuickWidget>
+
+QT_BEGIN_NAMESPACE
+
+class QCustom3DItem;
+class QAbstract3DAxis;
+class QAbstract3DSeries;
+class QQuickGraphsItem;
+
+class Q_GRAPHS_EXPORT QAbstract3DGraph : public QQuickWidget
+{
+ Q_OBJECT
+ Q_FLAGS(SelectionFlag SelectionFlags)
+ Q_FLAGS(OptimizationHint OptimizationHints)
+ Q_PROPERTY(Q3DTheme* activeTheme READ activeTheme WRITE setActiveTheme NOTIFY activeThemeChanged)
+ Q_PROPERTY(QAbstract3DGraph::SelectionFlags selectionMode READ selectionMode WRITE setSelectionMode NOTIFY selectionModeChanged)
+ Q_PROPERTY(QAbstract3DGraph::ShadowQuality shadowQuality READ shadowQuality WRITE setShadowQuality NOTIFY shadowQualityChanged)
+ Q_PROPERTY(Q3DScene* scene READ scene CONSTANT)
+
+public:
+ enum SelectionFlag {
+ SelectionNone = 0,
+ SelectionItem = 1,
+ SelectionRow = 2,
+ SelectionItemAndRow = SelectionItem | SelectionRow,
+ SelectionColumn = 4,
+ SelectionItemAndColumn = SelectionItem | SelectionColumn,
+ SelectionRowAndColumn = SelectionRow | SelectionColumn,
+ SelectionItemRowAndColumn = SelectionItem | SelectionRow | SelectionColumn,
+ SelectionSlice = 8,
+ SelectionMultiSeries = 16
+ };
+ Q_ENUM(SelectionFlag)
+ Q_DECLARE_FLAGS(SelectionFlags, SelectionFlag)
+
+ enum ShadowQuality {
+ ShadowQualityNone = 0,
+ ShadowQualityLow,
+ ShadowQualityMedium,
+ ShadowQualityHigh,
+ ShadowQualitySoftLow,
+ ShadowQualitySoftMedium,
+ ShadowQualitySoftHigh
+ };
+ Q_ENUM(ShadowQuality)
+
+ enum ElementType {
+ ElementNone = 0,
+ ElementSeries,
+ ElementAxisXLabel,
+ ElementAxisYLabel,
+ ElementAxisZLabel,
+ ElementCustomItem
+ };
+ Q_ENUM(ElementType)
+
+ enum OptimizationHint {
+ OptimizationDefault = 0,
+ OptimizationStatic = 1
+ };
+ Q_ENUM(OptimizationHint)
+ Q_DECLARE_FLAGS(OptimizationHints, OptimizationHint)
+
+ Q3DScene *scene() const;
+ QAbstract3DGraph::ShadowQuality shadowQuality() const;
+ void setShadowQuality(const QAbstract3DGraph::ShadowQuality &shadowQuality);
+ Q3DTheme *activeTheme() const;
+ void setActiveTheme(Q3DTheme *activeTheme);
+ QAbstract3DGraph::SelectionFlags selectionMode() const;
+ void setSelectionMode(const QAbstract3DGraph::SelectionFlags &selectionMode);
+ virtual ~QAbstract3DGraph();
+
+protected:
+ QAbstract3DGraph();
+
+Q_SIGNALS:
+ void shadowQualityChanged(QAbstract3DGraph::ShadowQuality quality);
+ void activeThemeChanged(Q3DTheme *activeTheme);
+ void selectionModeChanged(const QAbstract3DGraph::SelectionFlags selectionMode);
+
+private:
+ Q_DISABLE_COPY(QAbstract3DGraph)
+ QScopedPointer<QQuickGraphsItem> d_ptr;
+
+ friend class Q3DBars;
+ friend class Q3DScatter;
+ friend class Q3DSurface;
+};
+Q_DECLARE_OPERATORS_FOR_FLAGS(QAbstract3DGraph::SelectionFlags)
+Q_DECLARE_OPERATORS_FOR_FLAGS(QAbstract3DGraph::OptimizationHints)
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/engine/scatter3dcontroller.cpp b/src/graphs/engine/scatter3dcontroller.cpp
new file mode 100644
index 0000000..17a3114
--- /dev/null
+++ b/src/graphs/engine/scatter3dcontroller.cpp
@@ -0,0 +1,394 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "scatter3dcontroller_p.h"
+#include "qvalue3daxis_p.h"
+#include "qscatterdataproxy_p.h"
+#include "qscatter3dseries_p.h"
+#include <QtCore/QMutexLocker>
+
+QT_BEGIN_NAMESPACE
+
+static const int insertRemoveRecordReserveSize = 31;
+
+Scatter3DController::Scatter3DController(QRect boundRect, Q3DScene *scene)
+ : Abstract3DController(boundRect, scene),
+ m_selectedItem(invalidSelectionIndex()),
+ m_selectedItemSeries(0),
+ m_recordInsertsAndRemoves(false)
+{
+ // Setting a null axis creates a new default axis according to orientation and graph type.
+ // Note: These cannot be set in Abstract3DController constructor, as they will call virtual
+ // functions implemented by subclasses.
+ setAxisX(0);
+ setAxisY(0);
+ setAxisZ(0);
+}
+
+Scatter3DController::~Scatter3DController()
+{
+}
+
+void Scatter3DController::addSeries(QAbstract3DSeries *series)
+{
+ Q_ASSERT(series && series->type() == QAbstract3DSeries::SeriesTypeScatter);
+
+ Abstract3DController::addSeries(series);
+
+ QScatter3DSeries *scatterSeries = static_cast<QScatter3DSeries *>(series);
+ if (scatterSeries->selectedItem() != invalidSelectionIndex())
+ setSelectedItem(scatterSeries->selectedItem(), scatterSeries);
+}
+
+void Scatter3DController::removeSeries(QAbstract3DSeries *series)
+{
+ bool wasVisible = (series && series->d_ptr->m_controller == this && series->isVisible());
+
+ Abstract3DController::removeSeries(series);
+
+ if (m_selectedItemSeries == series)
+ setSelectedItem(invalidSelectionIndex(), 0);
+
+ if (wasVisible)
+ adjustAxisRanges();
+}
+
+QList<QScatter3DSeries *> Scatter3DController::scatterSeriesList()
+{
+ QList<QAbstract3DSeries *> abstractSeriesList = seriesList();
+ QList<QScatter3DSeries *> scatterSeriesList;
+ foreach (QAbstract3DSeries *abstractSeries, abstractSeriesList) {
+ QScatter3DSeries *scatterSeries = qobject_cast<QScatter3DSeries *>(abstractSeries);
+ if (scatterSeries)
+ scatterSeriesList.append(scatterSeries);
+ }
+
+ return scatterSeriesList;
+}
+
+void Scatter3DController::clearChangedItems()
+{
+ m_changedItems.clear();
+}
+
+void Scatter3DController::handleArrayReset()
+{
+ QScatter3DSeries *series;
+ if (qobject_cast<QScatterDataProxy *>(sender()))
+ series = static_cast<QScatterDataProxy *>(sender())->series();
+ else
+ series = static_cast<QScatter3DSeries *>(sender());
+
+ if (series->isVisible()) {
+ adjustAxisRanges();
+ m_isDataDirty = true;
+ }
+ if (!m_changedSeriesList.contains(series))
+ m_changedSeriesList.append(series);
+ setSelectedItem(m_selectedItem, m_selectedItemSeries);
+ series->d_ptr->markItemLabelDirty();
+ emitNeedRender();
+}
+
+void Scatter3DController::handleItemsAdded(int startIndex, int count)
+{
+ Q_UNUSED(startIndex);
+ Q_UNUSED(count);
+ QScatter3DSeries *series = static_cast<QScatterDataProxy *>(sender())->series();
+ if (series->isVisible()) {
+ adjustAxisRanges();
+ m_isDataDirty = true;
+ }
+ if (!m_changedSeriesList.contains(series))
+ m_changedSeriesList.append(series);
+ emitNeedRender();
+}
+
+void Scatter3DController::handleItemsChanged(int startIndex, int count)
+{
+ QScatter3DSeries *series = static_cast<QScatterDataProxy *>(sender())->series();
+ int oldChangeCount = m_changedItems.size();
+ if (!oldChangeCount)
+ m_changedItems.reserve(count);
+
+ for (int i = 0; i < count; i++) {
+ bool newItem = true;
+ int candidate = startIndex + i;
+ for (int j = 0; j < oldChangeCount; j++) {
+ const ChangeItem &oldChangeItem = m_changedItems.at(j);
+ if (oldChangeItem.index == candidate && series == oldChangeItem.series) {
+ newItem = false;
+ break;
+ }
+ }
+ if (newItem) {
+ ChangeItem newChangeItem = {series, candidate};
+ m_changedItems.append(newChangeItem);
+ if (series == m_selectedItemSeries && m_selectedItem == candidate)
+ series->d_ptr->markItemLabelDirty();
+ }
+ }
+
+ if (count) {
+ m_changeTracker.itemChanged = true;
+ if (series->isVisible())
+ adjustAxisRanges();
+ emitNeedRender();
+ }
+}
+
+void Scatter3DController::handleItemsRemoved(int startIndex, int count)
+{
+ Q_UNUSED(startIndex);
+ Q_UNUSED(count);
+ QScatter3DSeries *series = static_cast<QScatterDataProxy *>(sender())->series();
+ if (series == m_selectedItemSeries) {
+ // If items removed from selected series before the selection, adjust the selection
+ int selectedItem = m_selectedItem;
+ if (startIndex <= selectedItem) {
+ if ((startIndex + count) > selectedItem)
+ selectedItem = -1; // Selected item removed
+ else
+ selectedItem -= count; // Move selected item down by amount of item removed
+
+ setSelectedItem(selectedItem, m_selectedItemSeries);
+ }
+ }
+
+ if (series->isVisible()) {
+ adjustAxisRanges();
+ m_isDataDirty = true;
+ }
+ if (!m_changedSeriesList.contains(series))
+ m_changedSeriesList.append(series);
+
+ if (m_recordInsertsAndRemoves) {
+ InsertRemoveRecord record(false, startIndex, count, series);
+ m_insertRemoveRecords.append(record);
+ }
+
+ emitNeedRender();
+}
+
+void Scatter3DController::handleItemsInserted(int startIndex, int count)
+{
+ Q_UNUSED(startIndex);
+ Q_UNUSED(count);
+ QScatter3DSeries *series = static_cast<QScatterDataProxy *>(sender())->series();
+ if (series == m_selectedItemSeries) {
+ // If items inserted to selected series before the selection, adjust the selection
+ int selectedItem = m_selectedItem;
+ if (startIndex <= selectedItem) {
+ selectedItem += count;
+ setSelectedItem(selectedItem, m_selectedItemSeries);
+ }
+ }
+
+ if (series->isVisible()) {
+ adjustAxisRanges();
+ m_isDataDirty = true;
+ }
+ if (!m_changedSeriesList.contains(series))
+ m_changedSeriesList.append(series);
+
+ if (m_recordInsertsAndRemoves) {
+ InsertRemoveRecord record(true, startIndex, count, series);
+ m_insertRemoveRecords.append(record);
+ }
+
+ emitNeedRender();
+}
+
+void Scatter3DController::startRecordingRemovesAndInserts()
+{
+ m_recordInsertsAndRemoves = false;
+
+ if (m_scene->selectionQueryPosition() != Q3DScene::invalidSelectionPoint()) {
+ m_recordInsertsAndRemoves = true;
+ if (m_insertRemoveRecords.size()) {
+ m_insertRemoveRecords.clear();
+ // Reserve some space for remove/insert records to avoid unnecessary reallocations.
+ m_insertRemoveRecords.reserve(insertRemoveRecordReserveSize);
+ }
+ }
+}
+
+void Scatter3DController::handleAxisAutoAdjustRangeChangedInOrientation(
+ QAbstract3DAxis::AxisOrientation orientation, bool autoAdjust)
+{
+ Q_UNUSED(orientation);
+ Q_UNUSED(autoAdjust);
+ adjustAxisRanges();
+}
+
+void Scatter3DController::handleAxisRangeChangedBySender(QObject *sender)
+{
+ Abstract3DController::handleAxisRangeChangedBySender(sender);
+
+ // Update selected index - may be moved offscreen
+ setSelectedItem(m_selectedItem, m_selectedItemSeries);
+}
+
+void Scatter3DController::setSelectionMode(QAbstract3DGraph::SelectionFlags mode)
+{
+ // We only support single item selection mode and no selection mode
+ if (mode != QAbstract3DGraph::SelectionItem && mode != QAbstract3DGraph::SelectionNone) {
+ qWarning("Unsupported selection mode - only none and item selection modes are supported.");
+ return;
+ }
+
+ Abstract3DController::setSelectionMode(mode);
+}
+
+void Scatter3DController::setSelectedItem(int index, QScatter3DSeries *series)
+{
+ const QScatterDataProxy *proxy = 0;
+
+ // Series may already have been removed, so check it before setting the selection.
+ if (!m_seriesList.contains(series))
+ series = 0;
+
+ if (series)
+ proxy = series->dataProxy();
+
+ if (!proxy || index < 0 || index >= proxy->itemCount())
+ index = invalidSelectionIndex();
+
+ if (index != m_selectedItem || series != m_selectedItemSeries) {
+ bool seriesChanged = (series != m_selectedItemSeries);
+ m_selectedItem = index;
+ m_selectedItemSeries = series;
+ m_changeTracker.selectedItemChanged = true;
+
+ // Clear selection from other series and finally set new selection to the specified series
+ foreach (QAbstract3DSeries *otherSeries, m_seriesList) {
+ QScatter3DSeries *scatterSeries = static_cast<QScatter3DSeries *>(otherSeries);
+ if (scatterSeries != m_selectedItemSeries)
+ scatterSeries->dptr()->setSelectedItem(invalidSelectionIndex());
+ }
+ if (m_selectedItemSeries)
+ m_selectedItemSeries->dptr()->setSelectedItem(m_selectedItem);
+
+ if (seriesChanged)
+ emit selectedSeriesChanged(m_selectedItemSeries);
+
+ emitNeedRender();
+ }
+}
+
+void Scatter3DController::clearSelection()
+{
+ setSelectedItem(invalidSelectionIndex(), 0);
+}
+
+void Scatter3DController::adjustAxisRanges()
+{
+ QValue3DAxis *valueAxisX = static_cast<QValue3DAxis *>(m_axisX);
+ QValue3DAxis *valueAxisY = static_cast<QValue3DAxis *>(m_axisY);
+ QValue3DAxis *valueAxisZ = static_cast<QValue3DAxis *>(m_axisZ);
+ bool adjustX = (valueAxisX && valueAxisX->isAutoAdjustRange());
+ bool adjustY = (valueAxisY && valueAxisY->isAutoAdjustRange());
+ bool adjustZ = (valueAxisZ && valueAxisZ->isAutoAdjustRange());
+
+ if (adjustX || adjustY || adjustZ) {
+ float minValueX = 0.0f;
+ float maxValueX = 0.0f;
+ float minValueY = 0.0f;
+ float maxValueY = 0.0f;
+ float minValueZ = 0.0f;
+ float maxValueZ = 0.0f;
+ int seriesCount = m_seriesList.size();
+ for (int series = 0; series < seriesCount; series++) {
+ const QScatter3DSeries *scatterSeries =
+ static_cast<QScatter3DSeries *>(m_seriesList.at(series));
+ const QScatterDataProxy *proxy = scatterSeries->dataProxy();
+ if (scatterSeries->isVisible() && proxy) {
+ QVector3D minLimits;
+ QVector3D maxLimits;
+ proxy->dptrc()->limitValues(minLimits, maxLimits, valueAxisX, valueAxisY, valueAxisZ);
+ if (adjustX) {
+ if (!series) {
+ // First series initializes the values
+ minValueX = minLimits.x();
+ maxValueX = maxLimits.x();
+ } else {
+ minValueX = qMin(minValueX, minLimits.x());
+ maxValueX = qMax(maxValueX, maxLimits.x());
+ }
+ }
+ if (adjustY) {
+ if (!series) {
+ // First series initializes the values
+ minValueY = minLimits.y();
+ maxValueY = maxLimits.y();
+ } else {
+ minValueY = qMin(minValueY, minLimits.y());
+ maxValueY = qMax(maxValueY, maxLimits.y());
+ }
+ }
+ if (adjustZ) {
+ if (!series) {
+ // First series initializes the values
+ minValueZ = minLimits.z();
+ maxValueZ = maxLimits.z();
+ } else {
+ minValueZ = qMin(minValueZ, minLimits.z());
+ maxValueZ = qMax(maxValueZ, maxLimits.z());
+ }
+ }
+ }
+ }
+
+ static const float adjustmentRatio = 20.0f;
+ static const float defaultAdjustment = 1.0f;
+
+ if (adjustX) {
+ // If all points at same coordinate, need to default to some valid range
+ float adjustment = 0.0f;
+ if (minValueX == maxValueX) {
+ if (adjustZ) {
+ // X and Z are linked to have similar unit size, so choose the valid range based on it
+ if (minValueZ == maxValueZ)
+ adjustment = defaultAdjustment;
+ else
+ adjustment = qAbs(maxValueZ - minValueZ) / adjustmentRatio;
+ } else {
+ if (valueAxisZ)
+ adjustment = qAbs(valueAxisZ->max() - valueAxisZ->min()) / adjustmentRatio;
+ else
+ adjustment = defaultAdjustment;
+ }
+ }
+ valueAxisX->dptr()->setRange(minValueX - adjustment, maxValueX + adjustment, true);
+ }
+ if (adjustY) {
+ // If all points at same coordinate, need to default to some valid range
+ // Y-axis unit is not dependent on other axes, so simply adjust +-1.0f
+ float adjustment = 0.0f;
+ if (minValueY == maxValueY)
+ adjustment = defaultAdjustment;
+ valueAxisY->dptr()->setRange(minValueY - adjustment, maxValueY + adjustment, true);
+ }
+ if (adjustZ) {
+ // If all points at same coordinate, need to default to some valid range
+ float adjustment = 0.0f;
+ if (minValueZ == maxValueZ) {
+ if (adjustX) {
+ // X and Z are linked to have similar unit size, so choose the valid range based on it
+ if (minValueX == maxValueX)
+ adjustment = defaultAdjustment;
+ else
+ adjustment = qAbs(maxValueX - minValueX) / adjustmentRatio;
+ } else {
+ if (valueAxisX)
+ adjustment = qAbs(valueAxisX->max() - valueAxisX->min()) / adjustmentRatio;
+ else
+ adjustment = defaultAdjustment;
+ }
+ }
+ valueAxisZ->dptr()->setRange(minValueZ - adjustment, maxValueZ + adjustment, true);
+ }
+ }
+}
+
+QT_END_NAMESPACE
diff --git a/src/graphs/engine/scatter3dcontroller_p.h b/src/graphs/engine/scatter3dcontroller_p.h
new file mode 100644
index 0000000..94bcbce
--- /dev/null
+++ b/src/graphs/engine/scatter3dcontroller_p.h
@@ -0,0 +1,132 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QtGraphs API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#ifndef Q3DSCATTERCONTROLLER_p_H
+#define Q3DSCATTERCONTROLLER_p_H
+
+#include <private/graphsglobal_p.h>
+#include <private/abstract3dcontroller_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QScatterDataProxy;
+class QScatter3DSeries;
+
+struct Scatter3DChangeBitField {
+ bool selectedItemChanged : 1;
+ bool itemChanged : 1;
+
+ Scatter3DChangeBitField() :
+ selectedItemChanged(true),
+ itemChanged(false)
+ {
+ }
+};
+
+class Q_GRAPHS_EXPORT Scatter3DController : public Abstract3DController
+{
+ Q_OBJECT
+
+public:
+ struct ChangeItem {
+ QScatter3DSeries *series;
+ int index;
+ };
+private:
+ Scatter3DChangeBitField m_changeTracker;
+ QList<ChangeItem> m_changedItems;
+
+ // Rendering
+ int m_selectedItem;
+ QScatter3DSeries *m_selectedItemSeries; // Points to the series for which the bar is selected
+ // in single series selection cases.
+
+ struct InsertRemoveRecord {
+ bool m_isInsert;
+ int m_startIndex;
+ int m_count;
+ QAbstract3DSeries *m_series;
+
+ InsertRemoveRecord() :
+ m_isInsert(false),
+ m_startIndex(0),
+ m_count(0),
+ m_series(0)
+ {}
+
+ InsertRemoveRecord(bool isInsert, int startIndex, int count, QAbstract3DSeries *series) :
+ m_isInsert(isInsert),
+ m_startIndex(startIndex),
+ m_count(count),
+ m_series(series)
+ {}
+ };
+
+ QList<InsertRemoveRecord> m_insertRemoveRecords;
+ bool m_recordInsertsAndRemoves;
+
+public:
+ explicit Scatter3DController(QRect rect, Q3DScene *scene = 0);
+ ~Scatter3DController();
+
+ // Change selection mode
+ void setSelectionMode(QAbstract3DGraph::SelectionFlags mode) override;
+
+ inline QScatter3DSeries *selectedSeries() const { return m_selectedItemSeries; }
+
+ void setSelectedItem(int index, QScatter3DSeries *series);
+ static inline int invalidSelectionIndex() { return -1; }
+ void clearSelection() override;
+
+ void addSeries(QAbstract3DSeries *series) override;
+ void removeSeries(QAbstract3DSeries *series) override;
+ virtual QList<QScatter3DSeries *> scatterSeriesList();
+
+ inline bool hasSelectedItemChanged() const { return m_changeTracker.selectedItemChanged; }
+ inline void setSelectedItemChanged(bool changed) { m_changeTracker.selectedItemChanged = changed; }
+ inline bool hasItemChanged() const { return m_changeTracker.itemChanged; }
+ inline void setItemChanged(bool changed) { m_changeTracker.itemChanged = changed; }
+
+ void clearChangedItems();
+
+ void handleAxisAutoAdjustRangeChangedInOrientation(
+ QAbstract3DAxis::AxisOrientation orientation, bool autoAdjust) override;
+ void handleAxisRangeChangedBySender(QObject *sender) override;
+ void adjustAxisRanges() override;
+ inline bool hasChangedSeriesList() const { return !m_changedSeriesList.empty(); }
+ inline bool isSeriesVisualsDirty() const { return m_isSeriesVisualsDirty; }
+ inline void setSeriesVisualsDirty() { m_isSeriesVisualsDirty = true; }
+ inline bool isDataDirty() const { return m_isDataDirty; }
+
+public Q_SLOTS:
+ void handleArrayReset();
+ void handleItemsAdded(int startIndex, int count);
+ void handleItemsChanged(int startIndex, int count);
+ void handleItemsRemoved(int startIndex, int count);
+ void handleItemsInserted(int startIndex, int count);
+
+Q_SIGNALS:
+ void selectedSeriesChanged(QScatter3DSeries *series);
+
+protected:
+ void startRecordingRemovesAndInserts() override;
+
+private:
+
+ friend class QQuickGraphsScatter;
+ Q_DISABLE_COPY(Scatter3DController)
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/engine/scatterinstancing.cpp b/src/graphs/engine/scatterinstancing.cpp
new file mode 100644
index 0000000..aa58ea8
--- /dev/null
+++ b/src/graphs/engine/scatterinstancing.cpp
@@ -0,0 +1,74 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "scatterinstancing_p.h"
+
+ScatterInstancing::ScatterInstancing()
+{
+
+}
+
+QByteArray ScatterInstancing::getInstanceBuffer(int *instanceCount) {
+ if (m_dirty) {
+ m_instanceData.resize(0);
+ int instanceNumber = 0;
+
+ for (int i = 0; i < m_dataArray.size(); ++i) {
+ auto item = m_dataArray.at(i);
+ float x = item.position.x();
+ float y = item.position.y();
+ float z = item.position.z();
+ QVector4D customData{};
+ if (m_rangeGradient)
+ customData.setX(m_customData.at(i));
+ auto entry = calculateTableEntryFromQuaternion({x,y,z}, item.scale, item.rotation, QColor(Qt::white), customData);
+ m_instanceData.append(reinterpret_cast<char *>(&entry), sizeof(entry));
+ instanceNumber++;
+ }
+ m_instanceCount = instanceNumber;
+ m_dirty = false;
+ }
+
+ if (instanceCount)
+ *instanceCount = m_instanceCount;
+
+ return m_instanceData;
+}
+
+bool ScatterInstancing::rangeGradient() const
+{
+ return m_rangeGradient;
+}
+
+void ScatterInstancing::setRangeGradient(bool newRangeGradient)
+{
+ m_rangeGradient = newRangeGradient;
+}
+
+QList<float> &ScatterInstancing::customData()
+{
+ return m_customData;
+}
+
+void ScatterInstancing::setCustomData(const QList<float> &newCustomData)
+{
+ m_customData = newCustomData;
+ markDataDirty();
+}
+
+void ScatterInstancing::markDataDirty()
+{
+ m_dirty = true;
+ markDirty();
+}
+
+const QList<DataItemHolder> &ScatterInstancing::dataArray() const
+{
+ return m_dataArray;
+}
+
+void ScatterInstancing::setDataArray(const QList<DataItemHolder> &newDataArray)
+{
+ m_dataArray = newDataArray;
+ markDataDirty();
+}
diff --git a/src/graphs/engine/scatterinstancing_p.h b/src/graphs/engine/scatterinstancing_p.h
new file mode 100644
index 0000000..6c6a26c
--- /dev/null
+++ b/src/graphs/engine/scatterinstancing_p.h
@@ -0,0 +1,55 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QtGraphs API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#ifndef SCATTERINSTANCING_H
+#define SCATTERINSTANCING_H
+
+#include <QtQuick3D/private/qquick3dinstancing_p.h>
+
+struct DataItemHolder {
+ QVector3D position;
+ QQuaternion rotation;
+ QVector3D scale;
+};
+
+class ScatterInstancing : public QQuick3DInstancing
+{
+ Q_OBJECT
+public:
+ ScatterInstancing();
+
+ const QList<DataItemHolder> &dataArray() const;
+ void setDataArray(const QList<DataItemHolder> &newDataArray);
+
+ QList<float> &customData();
+ void setCustomData(const QList<float> &newCustomData);
+
+ void markDataDirty();
+ bool rangeGradient() const;
+ void setRangeGradient(bool newRangeGradient);
+
+ // QQuick3DInstancing interface
+
+protected:
+ QByteArray getInstanceBuffer(int *instanceCount) override;
+
+private:
+ QByteArray m_instanceData;
+ QList<DataItemHolder> m_dataArray;
+ QList<float> m_customData;
+ int m_instanceCount = 0;
+ bool m_dirty = true;
+ bool m_rangeGradient = false;
+};
+
+#endif // SCATTERINSTANCING_H
diff --git a/src/graphs/engine/shaders/3dsliceframes.frag b/src/graphs/engine/shaders/3dsliceframes.frag
new file mode 100644
index 0000000..44c080d
--- /dev/null
+++ b/src/graphs/engine/shaders/3dsliceframes.frag
@@ -0,0 +1,15 @@
+#version 120
+
+uniform highp vec4 color_mdl;
+uniform highp vec2 sliceFrameWidth;
+
+varying highp vec3 pos;
+
+void main() {
+ highp vec2 absPos = min(vec2(1.0, 1.0), abs(pos.xy));
+ if (absPos.x > sliceFrameWidth.x || absPos.y > sliceFrameWidth.y)
+ gl_FragColor = color_mdl;
+ else
+ discard;
+}
+
diff --git a/src/graphs/engine/shaders/colorOnY.frag b/src/graphs/engine/shaders/colorOnY.frag
new file mode 100644
index 0000000..7a5eb46
--- /dev/null
+++ b/src/graphs/engine/shaders/colorOnY.frag
@@ -0,0 +1,39 @@
+#version 120
+
+uniform highp vec3 lightPosition_wrld;
+uniform highp float lightStrength;
+uniform highp float ambientStrength;
+uniform sampler2D textureSampler;
+uniform highp float gradMin;
+uniform highp float gradHeight;
+uniform highp vec4 lightColor;
+
+varying highp vec3 position_wrld;
+varying highp vec3 normal_cmr;
+varying highp vec3 eyeDirection_cmr;
+varying highp vec3 lightDirection_cmr;
+varying highp vec2 coords_mdl;
+
+void main() {
+ highp vec2 gradientUV = vec2(0.0, gradMin + ((coords_mdl.y + 1.0) * gradHeight));
+ highp vec3 materialDiffuseColor = texture2D(textureSampler, gradientUV).xyz;
+ highp vec3 materialAmbientColor = lightColor.rgb * ambientStrength * materialDiffuseColor;
+ highp vec3 materialSpecularColor = lightColor.rgb;
+
+ highp float distance = length(lightPosition_wrld - position_wrld);
+ highp vec3 n = normalize(normal_cmr);
+ highp vec3 l = normalize(lightDirection_cmr);
+ highp float cosTheta = clamp(dot(n, l), 0.0, 1.0);
+
+ highp vec3 E = normalize(eyeDirection_cmr);
+ highp vec3 R = reflect(-l, n);
+ highp float cosAlpha = clamp(dot(E, R), 0.0, 1.0);
+
+ gl_FragColor.rgb =
+ materialAmbientColor +
+ materialDiffuseColor * lightStrength * (cosTheta * cosTheta) / distance +
+ materialSpecularColor * lightStrength * pow(cosAlpha, 5) / distance;
+ gl_FragColor.rgb = clamp(gl_FragColor.rgb, 0.0, 1.0);
+ gl_FragColor.a = 1.0;
+}
+
diff --git a/src/graphs/engine/shaders/colorOnY_ES2.frag b/src/graphs/engine/shaders/colorOnY_ES2.frag
new file mode 100644
index 0000000..4352de0
--- /dev/null
+++ b/src/graphs/engine/shaders/colorOnY_ES2.frag
@@ -0,0 +1,40 @@
+uniform highp float lightStrength;
+uniform highp float ambientStrength;
+uniform sampler2D textureSampler;
+uniform highp float gradMin;
+uniform highp float gradHeight;
+uniform highp vec4 lightColor;
+
+varying highp vec3 lightPosition_wrld_frag;
+varying highp vec3 position_wrld;
+varying highp vec3 normal_cmr;
+varying highp vec3 eyeDirection_cmr;
+varying highp vec3 lightDirection_cmr;
+varying highp vec2 coords_mdl;
+
+void main() {
+ highp vec2 gradientUV = vec2(0.0, gradMin + ((coords_mdl.y + 1.0) * gradHeight));
+ highp vec3 materialDiffuseColor = texture2D(textureSampler, gradientUV).xyz;
+ highp vec3 materialAmbientColor = lightColor.rgb * ambientStrength * materialDiffuseColor;
+ highp vec3 materialSpecularColor = lightColor.rgb;
+
+ highp float distance = length(lightPosition_wrld_frag - position_wrld);
+ highp vec3 n = normalize(normal_cmr);
+ highp vec3 l = normalize(lightDirection_cmr);
+ highp float cosTheta = dot(n, l);
+ if (cosTheta < 0.0) cosTheta = 0.0;
+ else if (cosTheta > 1.0) cosTheta = 1.0;
+
+ highp vec3 E = normalize(eyeDirection_cmr);
+ highp vec3 R = reflect(-l, n);
+ highp float cosAlpha = dot(E, R);
+ if (cosAlpha < 0.0) cosAlpha = 0.0;
+ else if (cosAlpha > 1.0) cosAlpha = 1.0;
+
+ gl_FragColor.rgb =
+ materialAmbientColor +
+ materialDiffuseColor * lightStrength * (cosTheta * cosTheta) / distance +
+ materialSpecularColor * lightStrength * (cosAlpha * cosAlpha * cosAlpha * cosAlpha * cosAlpha) / distance;
+ gl_FragColor.a = 1.0;
+}
+
diff --git a/src/graphs/engine/shaders/default.frag b/src/graphs/engine/shaders/default.frag
new file mode 100644
index 0000000..0276ed9
--- /dev/null
+++ b/src/graphs/engine/shaders/default.frag
@@ -0,0 +1,38 @@
+#version 120
+
+varying highp vec2 UV;
+varying highp vec2 coords_mdl;
+varying highp vec3 position_wrld;
+varying highp vec3 normal_cmr;
+varying highp vec3 eyeDirection_cmr;
+varying highp vec3 lightDirection_cmr;
+
+uniform highp vec3 lightPosition_wrld;
+uniform highp vec4 color_mdl;
+uniform highp float lightStrength;
+uniform highp float ambientStrength;
+uniform highp vec4 lightColor;
+
+void main() {
+ highp vec3 materialDiffuseColor = color_mdl.rgb;
+ highp vec3 materialAmbientColor = lightColor.rgb * ambientStrength * materialDiffuseColor;
+ highp vec3 materialSpecularColor = lightColor.rgb;
+
+ highp float distance = length(lightPosition_wrld - position_wrld);
+
+ highp vec3 n = normalize(normal_cmr);
+ highp vec3 l = normalize(lightDirection_cmr);
+ highp float cosTheta = clamp(dot(n, l), 0.0, 1.0);
+
+ highp vec3 E = normalize(eyeDirection_cmr);
+ highp vec3 R = reflect(-l, n);
+ highp float cosAlpha = clamp(dot(E, R), 0.0, 1.0);
+
+ gl_FragColor.rgb =
+ materialAmbientColor +
+ materialDiffuseColor * lightStrength * pow(cosTheta, 2) / distance +
+ materialSpecularColor * lightStrength * pow(cosAlpha, 5) / distance;
+ gl_FragColor.a = color_mdl.a;
+ gl_FragColor = clamp(gl_FragColor, 0.0, 1.0);
+}
+
diff --git a/src/graphs/engine/shaders/default.vert b/src/graphs/engine/shaders/default.vert
new file mode 100644
index 0000000..b454913
--- /dev/null
+++ b/src/graphs/engine/shaders/default.vert
@@ -0,0 +1,28 @@
+attribute highp vec3 vertexPosition_mdl;
+attribute highp vec2 vertexUV;
+attribute highp vec3 vertexNormal_mdl;
+
+uniform highp mat4 MVP;
+uniform highp mat4 V;
+uniform highp mat4 M;
+uniform highp mat4 itM;
+uniform highp vec3 lightPosition_wrld;
+
+varying highp vec3 lightPosition_wrld_frag;
+varying highp vec3 position_wrld;
+varying highp vec3 normal_cmr;
+varying highp vec3 eyeDirection_cmr;
+varying highp vec3 lightDirection_cmr;
+varying highp vec2 coords_mdl;
+
+void main() {
+ gl_Position = MVP * vec4(vertexPosition_mdl, 1.0);
+ coords_mdl = vertexPosition_mdl.xy;
+ position_wrld = vec4(M * vec4(vertexPosition_mdl, 1.0)).xyz;
+ vec3 vertexPosition_cmr = vec4(V * M * vec4(vertexPosition_mdl, 1.0)).xyz;
+ eyeDirection_cmr = vec3(0.0, 0.0, 0.0) - vertexPosition_cmr;
+ vec3 lightPosition_cmr = vec4(V * vec4(lightPosition_wrld, 1.0)).xyz;
+ lightDirection_cmr = lightPosition_cmr + eyeDirection_cmr;
+ normal_cmr = vec4(V * itM * vec4(vertexNormal_mdl, 0.0)).xyz;
+ lightPosition_wrld_frag = lightPosition_wrld;
+}
diff --git a/src/graphs/engine/shaders/defaultNoMatrices.vert b/src/graphs/engine/shaders/defaultNoMatrices.vert
new file mode 100644
index 0000000..cef10c1
--- /dev/null
+++ b/src/graphs/engine/shaders/defaultNoMatrices.vert
@@ -0,0 +1,26 @@
+attribute highp vec3 vertexPosition_mdl;
+attribute highp vec2 vertexUV;
+attribute highp vec3 vertexNormal_mdl;
+
+uniform highp mat4 MVP;
+uniform highp mat4 V;
+uniform highp vec3 lightPosition_wrld;
+
+varying highp vec3 lightPosition_wrld_frag;
+varying highp vec3 position_wrld;
+varying highp vec3 normal_cmr;
+varying highp vec3 eyeDirection_cmr;
+varying highp vec3 lightDirection_cmr;
+varying highp vec2 coords_mdl;
+
+void main() {
+ gl_Position = MVP * vec4(vertexPosition_mdl, 1.0);
+ coords_mdl = vertexPosition_mdl.xy;
+ position_wrld = vertexPosition_mdl;
+ vec3 vertexPosition_cmr = vec4(V * vec4(vertexPosition_mdl, 1.0)).xyz;
+ eyeDirection_cmr = vec3(0.0, 0.0, 0.0) - vertexPosition_cmr;
+ vec3 lightPosition_cmr = vec4(V * vec4(lightPosition_wrld, 1.0)).xyz;
+ lightDirection_cmr = lightPosition_cmr + eyeDirection_cmr;
+ normal_cmr = vec4(V * vec4(vertexNormal_mdl, 0.0)).xyz;
+ lightPosition_wrld_frag = lightPosition_wrld;
+}
diff --git a/src/graphs/engine/shaders/default_ES2.frag b/src/graphs/engine/shaders/default_ES2.frag
new file mode 100644
index 0000000..60fa3c4
--- /dev/null
+++ b/src/graphs/engine/shaders/default_ES2.frag
@@ -0,0 +1,39 @@
+varying highp vec2 UV;
+varying highp vec2 coords_mdl;
+varying highp vec3 position_wrld;
+varying highp vec3 normal_cmr;
+varying highp vec3 eyeDirection_cmr;
+varying highp vec3 lightDirection_cmr;
+varying highp vec3 lightPosition_wrld_frag;
+
+uniform highp vec4 color_mdl;
+uniform highp float lightStrength;
+uniform highp float ambientStrength;
+uniform highp vec4 lightColor;
+
+void main() {
+ highp vec3 materialDiffuseColor = color_mdl.rgb;
+ highp vec3 materialAmbientColor = lightColor.rgb * ambientStrength * materialDiffuseColor;
+ highp vec3 materialSpecularColor = lightColor.rgb;
+
+ highp float distance = length(lightPosition_wrld_frag - position_wrld);
+
+ highp vec3 n = normalize(normal_cmr);
+ highp vec3 l = normalize(lightDirection_cmr);
+ highp float cosTheta = dot(n, l);
+ if (cosTheta < 0.0) cosTheta = 0.0;
+ else if (cosTheta > 1.0) cosTheta = 1.0;
+
+ highp vec3 E = normalize(eyeDirection_cmr);
+ highp vec3 R = reflect(-l, n);
+ highp float cosAlpha = dot(E, R);
+ if (cosAlpha < 0.0) cosAlpha = 0.0;
+ else if (cosAlpha > 1.0) cosAlpha = 1.0;
+
+ gl_FragColor.rgb =
+ materialAmbientColor +
+ materialDiffuseColor * lightStrength * (cosTheta * cosTheta) / distance +
+ materialSpecularColor * lightStrength * (cosAlpha * cosAlpha * cosAlpha * cosAlpha * cosAlpha) / distance;
+ gl_FragColor.a = color_mdl.a;
+}
+
diff --git a/src/graphs/engine/shaders/depth.frag b/src/graphs/engine/shaders/depth.frag
new file mode 100644
index 0000000..5cfd4b1
--- /dev/null
+++ b/src/graphs/engine/shaders/depth.frag
@@ -0,0 +1,3 @@
+void main() {
+ gl_FragDepth = gl_FragCoord.z;
+}
diff --git a/src/graphs/engine/shaders/depth.vert b/src/graphs/engine/shaders/depth.vert
new file mode 100644
index 0000000..9fe5a19
--- /dev/null
+++ b/src/graphs/engine/shaders/depth.vert
@@ -0,0 +1,8 @@
+uniform highp mat4 MVP;
+
+attribute highp vec3 vertexPosition_mdl;
+
+void main() {
+ gl_Position = MVP * vec4(vertexPosition_mdl, 1.0);
+}
+
diff --git a/src/graphs/engine/shaders/label.frag b/src/graphs/engine/shaders/label.frag
new file mode 100644
index 0000000..8602141
--- /dev/null
+++ b/src/graphs/engine/shaders/label.frag
@@ -0,0 +1,8 @@
+uniform sampler2D textureSampler;
+
+varying highp vec2 UV;
+
+void main() {
+ gl_FragColor = texture2D(textureSampler, UV);
+}
+
diff --git a/src/graphs/engine/shaders/label.vert b/src/graphs/engine/shaders/label.vert
new file mode 100644
index 0000000..b4debe5
--- /dev/null
+++ b/src/graphs/engine/shaders/label.vert
@@ -0,0 +1,11 @@
+uniform highp mat4 MVP;
+
+attribute highp vec3 vertexPosition_mdl;
+attribute highp vec2 vertexUV;
+
+varying highp vec2 UV;
+
+void main() {
+ gl_Position = MVP * vec4(vertexPosition_mdl, 1.0);
+ UV = vertexUV;
+}
diff --git a/src/graphs/engine/shaders/plainColor.frag b/src/graphs/engine/shaders/plainColor.frag
new file mode 100644
index 0000000..da9ee06
--- /dev/null
+++ b/src/graphs/engine/shaders/plainColor.frag
@@ -0,0 +1,6 @@
+uniform highp vec4 color_mdl;
+
+void main() {
+ gl_FragColor = color_mdl;
+}
+
diff --git a/src/graphs/engine/shaders/plainColor.vert b/src/graphs/engine/shaders/plainColor.vert
new file mode 100644
index 0000000..64d17e1
--- /dev/null
+++ b/src/graphs/engine/shaders/plainColor.vert
@@ -0,0 +1,7 @@
+uniform highp mat4 MVP;
+
+attribute highp vec3 vertexPosition_mdl;
+
+void main() {
+ gl_Position = MVP * vec4(vertexPosition_mdl, 1.0);
+}
diff --git a/src/graphs/engine/shaders/point_ES2.vert b/src/graphs/engine/shaders/point_ES2.vert
new file mode 100644
index 0000000..b2dfdd7
--- /dev/null
+++ b/src/graphs/engine/shaders/point_ES2.vert
@@ -0,0 +1,8 @@
+uniform highp mat4 MVP;
+
+attribute highp vec3 vertexPosition_mdl;
+
+void main() {
+ gl_PointSize = 5.0;
+ gl_Position = MVP * vec4(vertexPosition_mdl, 1.0);
+}
diff --git a/src/graphs/engine/shaders/point_ES2_UV.vert b/src/graphs/engine/shaders/point_ES2_UV.vert
new file mode 100644
index 0000000..f181db4
--- /dev/null
+++ b/src/graphs/engine/shaders/point_ES2_UV.vert
@@ -0,0 +1,12 @@
+uniform highp mat4 MVP;
+
+attribute highp vec3 vertexPosition_mdl;
+attribute highp vec2 vertexUV;
+
+varying highp vec2 UV;
+
+void main() {
+ gl_PointSize = 5.0;
+ gl_Position = MVP * vec4(vertexPosition_mdl, 1.0);
+ UV = vertexUV;
+}
diff --git a/src/graphs/engine/shaders/position.vert b/src/graphs/engine/shaders/position.vert
new file mode 100644
index 0000000..34849ea
--- /dev/null
+++ b/src/graphs/engine/shaders/position.vert
@@ -0,0 +1,10 @@
+uniform highp mat4 MVP;
+
+attribute highp vec3 vertexPosition_mdl;
+
+varying highp vec3 pos;
+
+void main() {
+ gl_Position = MVP * vec4(vertexPosition_mdl, 1.0);
+ pos = vertexPosition_mdl;
+}
diff --git a/src/graphs/engine/shaders/positionmap.frag b/src/graphs/engine/shaders/positionmap.frag
new file mode 100644
index 0000000..9a277ab
--- /dev/null
+++ b/src/graphs/engine/shaders/positionmap.frag
@@ -0,0 +1,11 @@
+varying highp vec3 pos;
+
+void main() {
+ // This shader encodes the axis position into the vertex color, assuming the object
+ // is a cube filling the entire data area of the graph
+ gl_FragColor = vec4((pos.x + 1.0) / 2.0,
+ (pos.y + 1.0) / 2.0,
+ (-pos.z + 1.0) / 2.0,
+ 0.0);
+}
+
diff --git a/src/graphs/engine/shaders/shadow.frag b/src/graphs/engine/shaders/shadow.frag
new file mode 100644
index 0000000..237e978
--- /dev/null
+++ b/src/graphs/engine/shaders/shadow.frag
@@ -0,0 +1,67 @@
+#version 120
+
+uniform highp float lightStrength;
+uniform highp float ambientStrength;
+uniform highp float shadowQuality;
+uniform highp sampler2D textureSampler;
+uniform highp sampler2DShadow shadowMap;
+uniform highp vec4 lightColor;
+
+varying highp vec4 shadowCoord;
+varying highp vec2 UV;
+varying highp vec3 position_wrld;
+varying highp vec3 normal_cmr;
+varying highp vec3 eyeDirection_cmr;
+varying highp vec3 lightDirection_cmr;
+
+highp vec2 poissonDisk[16] = vec2[16](vec2(-0.94201624, -0.39906216),
+ vec2(0.94558609, -0.76890725),
+ vec2(-0.094184101, -0.92938870),
+ vec2(0.34495938, 0.29387760),
+ vec2(-0.91588581, 0.45771432),
+ vec2(-0.81544232, -0.87912464),
+ vec2(-0.38277543, 0.27676845),
+ vec2(0.97484398, 0.75648379),
+ vec2(0.44323325, -0.97511554),
+ vec2(0.53742981, -0.47373420),
+ vec2(-0.26496911, -0.41893023),
+ vec2(0.79197514, 0.19090188),
+ vec2(-0.24188840, 0.99706507),
+ vec2(-0.81409955, 0.91437590),
+ vec2(0.19984126, 0.78641367),
+ vec2(0.14383161, -0.14100790));
+
+void main() {
+ highp vec3 materialDiffuseColor = texture2D(textureSampler, UV).rgb;
+ highp vec3 materialAmbientColor = lightColor.rgb * ambientStrength * materialDiffuseColor;
+ highp vec3 materialSpecularColor = lightColor.rgb * 0.2;
+
+ highp vec3 n = normalize(normal_cmr);
+ highp vec3 l = normalize(lightDirection_cmr);
+ highp float cosTheta = clamp(dot(n, l), 0.0, 1.0);
+
+ highp vec3 E = normalize(eyeDirection_cmr);
+ highp vec3 R = reflect(-l, n);
+ highp float cosAlpha = clamp(dot(E, R), 0.0, 1.0);
+
+ highp float bias = 0.005 * tan(acos(cosTheta));
+ bias = clamp(bias, 0.0, 0.01);
+
+ vec4 shadCoords = shadowCoord;
+ shadCoords.z -= bias;
+
+ highp float visibility = 0.6;
+ for (int i = 0; i < 15; i++) {
+ vec4 shadCoordsPD = shadCoords;
+ shadCoordsPD.x += cos(poissonDisk[i].x) / shadowQuality;
+ shadCoordsPD.y += sin(poissonDisk[i].y) / shadowQuality;
+ visibility += 0.025 * shadow2DProj(shadowMap, shadCoordsPD).r;
+ }
+
+ gl_FragColor.rgb =
+ (materialAmbientColor +
+ materialDiffuseColor * lightStrength * cosTheta +
+ materialSpecularColor * lightStrength * pow(cosAlpha, 10));
+ gl_FragColor.a = texture2D(textureSampler, UV).a;
+ gl_FragColor.rgb = visibility * clamp(gl_FragColor.rgb, 0.0, 1.0);
+}
diff --git a/src/graphs/engine/shaders/shadow.vert b/src/graphs/engine/shaders/shadow.vert
new file mode 100644
index 0000000..0adcd43
--- /dev/null
+++ b/src/graphs/engine/shaders/shadow.vert
@@ -0,0 +1,37 @@
+#version 120
+
+uniform highp mat4 MVP;
+uniform highp mat4 V;
+uniform highp mat4 M;
+uniform highp mat4 itM;
+uniform highp mat4 depthMVP;
+uniform highp vec3 lightPosition_wrld;
+
+attribute highp vec3 vertexPosition_mdl;
+attribute highp vec3 vertexNormal_mdl;
+attribute highp vec2 vertexUV;
+
+varying highp vec2 UV;
+varying highp vec3 position_wrld;
+varying highp vec3 normal_cmr;
+varying highp vec3 eyeDirection_cmr;
+varying highp vec3 lightDirection_cmr;
+varying highp vec4 shadowCoord;
+varying highp vec2 coords_mdl;
+
+const highp mat4 bias = mat4(0.5, 0.0, 0.0, 0.0,
+ 0.0, 0.5, 0.0, 0.0,
+ 0.0, 0.0, 0.5, 0.0,
+ 0.5, 0.5, 0.5, 1.0);
+
+void main() {
+ gl_Position = MVP * vec4(vertexPosition_mdl, 1.0);
+ coords_mdl = vertexPosition_mdl.xy;
+ shadowCoord = bias * depthMVP * vec4(vertexPosition_mdl, 1.0);
+ position_wrld = vec4(M * vec4(vertexPosition_mdl, 1.0)).xyz;
+ vec3 vertexPosition_cmr = vec4(V * M * vec4(vertexPosition_mdl, 1.0)).xyz;
+ eyeDirection_cmr = vec3(0.0, 0.0, 0.0) - vertexPosition_cmr;
+ lightDirection_cmr = vec4(V * vec4(lightPosition_wrld, 0.0)).xyz;
+ normal_cmr = vec4(V * itM * vec4(vertexNormal_mdl, 0.0)).xyz;
+ UV = vertexUV;
+}
diff --git a/src/graphs/engine/shaders/shadowNoMatrices.vert b/src/graphs/engine/shaders/shadowNoMatrices.vert
new file mode 100644
index 0000000..3174850
--- /dev/null
+++ b/src/graphs/engine/shaders/shadowNoMatrices.vert
@@ -0,0 +1,36 @@
+#version 120
+
+uniform highp mat4 MVP;
+uniform highp mat4 V;
+uniform highp mat4 M;
+uniform highp mat4 depthMVP;
+uniform highp vec3 lightPosition_wrld;
+
+attribute highp vec3 vertexPosition_mdl;
+attribute highp vec3 vertexNormal_mdl;
+attribute highp vec2 vertexUV;
+
+varying highp vec2 UV;
+varying highp vec3 position_wrld;
+varying highp vec3 normal_cmr;
+varying highp vec3 eyeDirection_cmr;
+varying highp vec3 lightDirection_cmr;
+varying highp vec4 shadowCoord;
+varying highp vec2 coords_mdl;
+
+const highp mat4 bias = mat4(0.5, 0.0, 0.0, 0.0,
+ 0.0, 0.5, 0.0, 0.0,
+ 0.0, 0.0, 0.5, 0.0,
+ 0.5, 0.5, 0.5, 1.0);
+
+void main() {
+ gl_Position = MVP * vec4(vertexPosition_mdl, 1.0);
+ coords_mdl = vertexPosition_mdl.xy;
+ shadowCoord = bias * depthMVP * vec4(vertexPosition_mdl, 1.0);
+ position_wrld = vertexPosition_mdl;
+ vec3 vertexPosition_cmr = vec4(V * vec4(vertexPosition_mdl, 1.0)).xyz;
+ eyeDirection_cmr = vec3(0.0, 0.0, 0.0) - vertexPosition_cmr;
+ lightDirection_cmr = vec4(V * vec4(lightPosition_wrld, 0.0)).xyz;
+ normal_cmr = vec4(V * vec4(vertexNormal_mdl, 0.0)).xyz;
+ UV = vertexUV;
+}
diff --git a/src/graphs/engine/shaders/shadowNoTex.frag b/src/graphs/engine/shaders/shadowNoTex.frag
new file mode 100644
index 0000000..84e2f20
--- /dev/null
+++ b/src/graphs/engine/shaders/shadowNoTex.frag
@@ -0,0 +1,66 @@
+#version 120
+
+uniform highp float lightStrength;
+uniform highp float ambientStrength;
+uniform highp float shadowQuality;
+uniform highp vec4 color_mdl;
+uniform highp sampler2DShadow shadowMap;
+uniform highp vec4 lightColor;
+
+varying highp vec4 shadowCoord;
+varying highp vec3 position_wrld;
+varying highp vec3 normal_cmr;
+varying highp vec3 eyeDirection_cmr;
+varying highp vec3 lightDirection_cmr;
+
+highp vec2 poissonDisk[16] = vec2[16](vec2(-0.94201624, -0.39906216),
+ vec2(0.94558609, -0.76890725),
+ vec2(-0.094184101, -0.92938870),
+ vec2(0.34495938, 0.29387760),
+ vec2(-0.91588581, 0.45771432),
+ vec2(-0.81544232, -0.87912464),
+ vec2(-0.38277543, 0.27676845),
+ vec2(0.97484398, 0.75648379),
+ vec2(0.44323325, -0.97511554),
+ vec2(0.53742981, -0.47373420),
+ vec2(-0.26496911, -0.41893023),
+ vec2(0.79197514, 0.19090188),
+ vec2(-0.24188840, 0.99706507),
+ vec2(-0.81409955, 0.91437590),
+ vec2(0.19984126, 0.78641367),
+ vec2(0.14383161, -0.14100790));
+
+void main() {
+ highp vec3 materialDiffuseColor = color_mdl.rgb;
+ highp vec3 materialAmbientColor = lightColor.rgb * ambientStrength * materialDiffuseColor;
+ highp vec3 materialSpecularColor = lightColor.rgb;
+
+ highp vec3 n = normalize(normal_cmr);
+ highp vec3 l = normalize(lightDirection_cmr);
+ highp float cosTheta = clamp(dot(n, l), 0.0, 1.0);
+
+ highp vec3 E = normalize(eyeDirection_cmr);
+ highp vec3 R = reflect(-l, n);
+ highp float cosAlpha = clamp(dot(E, R), 0.0, 1.0);
+
+ highp float bias = 0.005 * tan(acos(cosTheta));
+ bias = clamp(bias, 0.0, 0.01);
+
+ vec4 shadCoords = shadowCoord;
+ shadCoords.z -= bias;
+
+ highp float visibility = 0.6;
+ for (int i = 0; i < 15; i++) {
+ vec4 shadCoordsPD = shadCoords;
+ shadCoordsPD.x += cos(poissonDisk[i].x) / shadowQuality;
+ shadCoordsPD.y += sin(poissonDisk[i].y) / shadowQuality;
+ visibility += 0.025 * shadow2DProj(shadowMap, shadCoordsPD).r;
+ }
+
+ gl_FragColor.rgb =
+ (materialAmbientColor +
+ materialDiffuseColor * lightStrength * cosTheta +
+ materialSpecularColor * lightStrength * pow(cosAlpha, 10));
+ gl_FragColor.a = color_mdl.a;
+ gl_FragColor.rgb = visibility * clamp(gl_FragColor.rgb, 0.0, 1.0);
+}
diff --git a/src/graphs/engine/shaders/shadowNoTexColorOnY.frag b/src/graphs/engine/shaders/shadowNoTexColorOnY.frag
new file mode 100644
index 0000000..73b8413
--- /dev/null
+++ b/src/graphs/engine/shaders/shadowNoTexColorOnY.frag
@@ -0,0 +1,70 @@
+#version 120
+
+uniform highp float lightStrength;
+uniform highp float ambientStrength;
+uniform highp float shadowQuality;
+uniform highp sampler2DShadow shadowMap;
+uniform sampler2D textureSampler;
+uniform highp float gradMin;
+uniform highp float gradHeight;
+uniform highp vec4 lightColor;
+
+varying highp vec4 shadowCoord;
+varying highp vec3 position_wrld;
+varying highp vec3 normal_cmr;
+varying highp vec3 eyeDirection_cmr;
+varying highp vec3 lightDirection_cmr;
+varying highp vec2 coords_mdl;
+
+highp vec2 poissonDisk[16] = vec2[16](vec2(-0.94201624, -0.39906216),
+ vec2(0.94558609, -0.76890725),
+ vec2(-0.094184101, -0.92938870),
+ vec2(0.34495938, 0.29387760),
+ vec2(-0.91588581, 0.45771432),
+ vec2(-0.81544232, -0.87912464),
+ vec2(-0.38277543, 0.27676845),
+ vec2(0.97484398, 0.75648379),
+ vec2(0.44323325, -0.97511554),
+ vec2(0.53742981, -0.47373420),
+ vec2(-0.26496911, -0.41893023),
+ vec2(0.79197514, 0.19090188),
+ vec2(-0.24188840, 0.99706507),
+ vec2(-0.81409955, 0.91437590),
+ vec2(0.19984126, 0.78641367),
+ vec2(0.14383161, -0.14100790));
+
+void main() {
+ highp vec2 gradientUV = vec2(0.0, gradMin + ((coords_mdl.y + 1.0) * gradHeight));
+ highp vec3 materialDiffuseColor = texture2D(textureSampler, gradientUV).xyz;
+ highp vec3 materialAmbientColor = lightColor.rgb * ambientStrength * materialDiffuseColor;
+ highp vec3 materialSpecularColor = lightColor.rgb;
+
+ highp vec3 n = normalize(normal_cmr);
+ highp vec3 l = normalize(lightDirection_cmr);
+ highp float cosTheta = clamp(dot(n, l), 0.0, 1.0);
+
+ highp vec3 E = normalize(eyeDirection_cmr);
+ highp vec3 R = reflect(-l, n);
+ highp float cosAlpha = clamp(dot(E, R), 0.0, 1.0);
+
+ highp float bias = 0.005 * tan(acos(cosTheta));
+ bias = clamp(bias, 0.0, 0.01);
+
+ vec4 shadCoords = shadowCoord;
+ shadCoords.z -= bias;
+
+ highp float visibility = 0.6;
+ for (int i = 0; i < 15; i++) {
+ vec4 shadCoordsPD = shadCoords;
+ shadCoordsPD.x += cos(poissonDisk[i].x) / shadowQuality;
+ shadCoordsPD.y += sin(poissonDisk[i].y) / shadowQuality;
+ visibility += 0.025 * shadow2DProj(shadowMap, shadCoordsPD).r;
+ }
+
+ gl_FragColor.rgb =
+ (materialAmbientColor +
+ materialDiffuseColor * lightStrength * cosTheta +
+ materialSpecularColor * lightStrength * pow(cosAlpha, 10));
+ gl_FragColor.a = 1.0;
+ gl_FragColor.rgb = visibility * clamp(gl_FragColor.rgb, 0.0, 1.0);
+}
diff --git a/src/graphs/engine/shaders/surface.frag b/src/graphs/engine/shaders/surface.frag
new file mode 100644
index 0000000..238e5fe
--- /dev/null
+++ b/src/graphs/engine/shaders/surface.frag
@@ -0,0 +1,39 @@
+#version 120
+
+varying highp vec2 coords_mdl;
+varying highp vec3 position_wrld;
+varying highp vec3 normal_cmr;
+varying highp vec3 eyeDirection_cmr;
+varying highp vec3 lightDirection_cmr;
+
+uniform sampler2D textureSampler;
+uniform highp vec3 lightPosition_wrld;
+uniform highp float lightStrength;
+uniform highp float ambientStrength;
+uniform highp vec4 lightColor;
+uniform highp float gradMin;
+uniform highp float gradHeight;
+
+void main() {
+ highp vec2 gradientUV = vec2(0.0, gradMin + coords_mdl.y * gradHeight);
+ highp vec3 materialDiffuseColor = texture2D(textureSampler, gradientUV).xyz;
+ highp vec3 materialAmbientColor = lightColor.rgb * ambientStrength * materialDiffuseColor;
+ highp vec3 materialSpecularColor = lightColor.rgb;
+
+ highp float distance = length(lightPosition_wrld - position_wrld);
+
+ highp vec3 n = normalize(normal_cmr);
+ highp vec3 l = normalize(lightDirection_cmr);
+ highp float cosTheta = clamp(dot(n, l), 0.0, 1.0);
+
+ highp vec3 E = normalize(eyeDirection_cmr);
+ highp vec3 R = reflect(-l, n);
+ highp float cosAlpha = clamp(dot(E, R), 0.0, 1.0);
+
+ gl_FragColor.rgb =
+ materialAmbientColor +
+ materialDiffuseColor * lightStrength * pow(cosTheta, 2) / distance +
+ materialSpecularColor * lightStrength * pow(cosAlpha, 10) / distance;
+ gl_FragColor.a = 1.0;
+}
+
diff --git a/src/graphs/engine/shaders/surfaceFlat.frag b/src/graphs/engine/shaders/surfaceFlat.frag
new file mode 100644
index 0000000..1a0bbde
--- /dev/null
+++ b/src/graphs/engine/shaders/surfaceFlat.frag
@@ -0,0 +1,41 @@
+#version 120
+
+#extension GL_EXT_gpu_shader4 : require
+
+varying highp vec3 coords_mdl;
+varying highp vec3 position_wrld;
+flat varying highp vec3 normal_cmr;
+varying highp vec3 eyeDirection_cmr;
+varying highp vec3 lightDirection_cmr;
+
+uniform sampler2D textureSampler;
+uniform highp vec3 lightPosition_wrld;
+uniform highp float lightStrength;
+uniform highp float ambientStrength;
+uniform highp vec4 lightColor;
+uniform highp float gradMin;
+uniform highp float gradHeight;
+
+void main() {
+ highp vec2 gradientUV = vec2(0.0, gradMin + coords_mdl.y * gradHeight);
+ highp vec3 materialDiffuseColor = texture2D(textureSampler, gradientUV).xyz;
+ highp vec3 materialAmbientColor = lightColor.rgb * ambientStrength * materialDiffuseColor;
+ highp vec3 materialSpecularColor = lightColor.rgb;
+
+ highp float distance = length(lightPosition_wrld - position_wrld);
+
+ highp vec3 n = normalize(normal_cmr);
+ highp vec3 l = normalize(lightDirection_cmr);
+ highp float cosTheta = clamp(dot(n, l), 0.0, 1.0);
+
+ highp vec3 E = normalize(eyeDirection_cmr);
+ highp vec3 R = reflect(-l, n);
+ highp float cosAlpha = clamp(dot(E, R), 0.0, 1.0);
+
+ gl_FragColor.rgb =
+ materialAmbientColor +
+ materialDiffuseColor * lightStrength * pow(cosTheta, 2) / distance +
+ materialSpecularColor * lightStrength * pow(cosAlpha, 10) / distance;
+ gl_FragColor.a = 1.0;
+}
+
diff --git a/src/graphs/engine/shaders/surfaceFlat.vert b/src/graphs/engine/shaders/surfaceFlat.vert
new file mode 100644
index 0000000..0f953f4
--- /dev/null
+++ b/src/graphs/engine/shaders/surfaceFlat.vert
@@ -0,0 +1,32 @@
+#version 120
+
+#extension GL_EXT_gpu_shader4 : require
+
+attribute highp vec3 vertexPosition_mdl;
+attribute highp vec3 vertexNormal_mdl;
+attribute highp vec2 vertexUV;
+
+uniform highp mat4 MVP;
+uniform highp mat4 V;
+uniform highp mat4 M;
+uniform highp mat4 itM;
+uniform highp vec3 lightPosition_wrld;
+
+varying highp vec2 UV;
+varying highp vec3 position_wrld;
+flat varying highp vec3 normal_cmr;
+varying highp vec3 eyeDirection_cmr;
+varying highp vec3 lightDirection_cmr;
+varying highp vec3 coords_mdl;
+
+void main() {
+ gl_Position = MVP * vec4(vertexPosition_mdl, 1.0);
+ coords_mdl = vertexPosition_mdl;
+ position_wrld = vec4(M * vec4(vertexPosition_mdl, 1.0)).xyz;
+ vec3 vertexPosition_cmr = vec4(V * M * vec4(vertexPosition_mdl, 1.0)).xyz;
+ eyeDirection_cmr = vec3(0.0, 0.0, 0.0) - vertexPosition_cmr;
+ vec3 lightPosition_cmr = vec4(V * vec4(lightPosition_wrld, 1.0)).xyz;
+ lightDirection_cmr = lightPosition_cmr + eyeDirection_cmr;
+ normal_cmr = vec4(V * itM * vec4(vertexNormal_mdl, 0.0)).xyz;
+ UV = vertexUV;
+}
diff --git a/src/graphs/engine/shaders/surfaceShadowFlat.frag b/src/graphs/engine/shaders/surfaceShadowFlat.frag
new file mode 100644
index 0000000..7eaba7f
--- /dev/null
+++ b/src/graphs/engine/shaders/surfaceShadowFlat.frag
@@ -0,0 +1,74 @@
+#version 120
+
+#extension GL_EXT_gpu_shader4 : require
+
+varying highp vec2 coords_mdl;
+varying highp vec3 position_wrld;
+flat varying highp vec3 normal_cmr;
+varying highp vec3 eyeDirection_cmr;
+varying highp vec3 lightDirection_cmr;
+varying highp vec4 shadowCoord;
+
+uniform highp sampler2DShadow shadowMap;
+uniform sampler2D textureSampler;
+uniform highp vec3 lightPosition_wrld;
+uniform highp float lightStrength;
+uniform highp float ambientStrength;
+uniform highp float shadowQuality;
+uniform highp vec4 lightColor;
+uniform highp float gradMin;
+uniform highp float gradHeight;
+
+highp vec2 poissonDisk[16] = vec2[16](vec2(-0.94201624, -0.39906216),
+ vec2(0.94558609, -0.76890725),
+ vec2(-0.094184101, -0.92938870),
+ vec2(0.34495938, 0.29387760),
+ vec2(-0.91588581, 0.45771432),
+ vec2(-0.81544232, -0.87912464),
+ vec2(-0.38277543, 0.27676845),
+ vec2(0.97484398, 0.75648379),
+ vec2(0.44323325, -0.97511554),
+ vec2(0.53742981, -0.47373420),
+ vec2(-0.26496911, -0.41893023),
+ vec2(0.79197514, 0.19090188),
+ vec2(-0.24188840, 0.99706507),
+ vec2(-0.81409955, 0.91437590),
+ vec2(0.19984126, 0.78641367),
+ vec2(0.14383161, -0.14100790));
+
+void main() {
+ highp vec2 gradientUV = vec2(0.0, gradMin + coords_mdl.y * gradHeight);
+ highp vec3 materialDiffuseColor = texture2D(textureSampler, gradientUV).xyz;
+ highp vec3 materialAmbientColor = lightColor.rgb * ambientStrength * materialDiffuseColor;
+ highp vec3 materialSpecularColor = lightColor.rgb;
+
+ highp vec3 n = normalize(normal_cmr);
+ highp vec3 l = normalize(lightDirection_cmr);
+ highp float cosTheta = clamp(dot(n, l), 0.0, 1.0);
+
+ highp vec3 E = normalize(eyeDirection_cmr);
+ highp vec3 R = reflect(-l, n);
+ highp float cosAlpha = clamp(dot(E, R), 0.0, 1.0);
+
+ highp float bias = 0.005 * tan(acos(cosTheta));
+ bias = clamp(bias, 0.001, 0.01);
+
+ vec4 shadCoords = shadowCoord;
+ shadCoords.z -= bias;
+
+ highp float visibility = 0.6;
+ for (int i = 0; i < 15; i++) {
+ vec4 shadCoordsPD = shadCoords;
+ shadCoordsPD.x += cos(poissonDisk[i].x) / shadowQuality;
+ shadCoordsPD.y += sin(poissonDisk[i].y) / shadowQuality;
+ visibility += 0.025 * shadow2DProj(shadowMap, shadCoordsPD).r;
+ }
+
+ gl_FragColor.rgb =
+ (materialAmbientColor +
+ materialDiffuseColor * lightStrength * cosTheta +
+ materialSpecularColor * lightStrength * pow(cosAlpha, 10));
+ gl_FragColor.a = 1.0;
+ gl_FragColor.rgb = visibility * clamp(gl_FragColor.rgb, 0.0, 1.0);
+}
+
diff --git a/src/graphs/engine/shaders/surfaceShadowFlat.vert b/src/graphs/engine/shaders/surfaceShadowFlat.vert
new file mode 100644
index 0000000..98fdde3
--- /dev/null
+++ b/src/graphs/engine/shaders/surfaceShadowFlat.vert
@@ -0,0 +1,39 @@
+#version 120
+
+#extension GL_EXT_gpu_shader4 : require
+
+uniform highp mat4 MVP;
+uniform highp mat4 V;
+uniform highp mat4 M;
+uniform highp mat4 itM;
+uniform highp mat4 depthMVP;
+uniform highp vec3 lightPosition_wrld;
+
+attribute highp vec3 vertexPosition_mdl;
+attribute highp vec3 vertexNormal_mdl;
+attribute highp vec2 vertexUV;
+
+varying highp vec2 UV;
+varying highp vec3 position_wrld;
+flat varying highp vec3 normal_cmr;
+varying highp vec3 eyeDirection_cmr;
+varying highp vec3 lightDirection_cmr;
+varying highp vec4 shadowCoord;
+varying highp vec2 coords_mdl;
+
+const highp mat4 bias = mat4(0.5, 0.0, 0.0, 0.0,
+ 0.0, 0.5, 0.0, 0.0,
+ 0.0, 0.0, 0.5, 0.0,
+ 0.5, 0.5, 0.5, 1.0);
+
+void main() {
+ gl_Position = MVP * vec4(vertexPosition_mdl, 1.0);
+ coords_mdl = vertexPosition_mdl.xy;
+ shadowCoord = bias * depthMVP * vec4(vertexPosition_mdl, 1.0);
+ position_wrld = vec4(M * vec4(vertexPosition_mdl, 1.0)).xyz;
+ vec3 vertexPosition_cmr = vec4(V * M * vec4(vertexPosition_mdl, 1.0)).xyz;
+ eyeDirection_cmr = vec3(0.0, 0.0, 0.0) - vertexPosition_cmr;
+ lightDirection_cmr = vec4(V * vec4(lightPosition_wrld, 0.0)).xyz;
+ normal_cmr = vec4(V * itM * vec4(vertexNormal_mdl, 0.0)).xyz;
+ UV = vertexUV;
+}
diff --git a/src/graphs/engine/shaders/surfaceShadowNoTex.frag b/src/graphs/engine/shaders/surfaceShadowNoTex.frag
new file mode 100644
index 0000000..985214b
--- /dev/null
+++ b/src/graphs/engine/shaders/surfaceShadowNoTex.frag
@@ -0,0 +1,72 @@
+#version 120
+
+varying highp vec2 coords_mdl;
+varying highp vec3 position_wrld;
+varying highp vec3 normal_cmr;
+varying highp vec3 eyeDirection_cmr;
+varying highp vec3 lightDirection_cmr;
+varying highp vec4 shadowCoord;
+
+uniform highp sampler2DShadow shadowMap;
+uniform sampler2D textureSampler;
+uniform highp vec3 lightPosition_wrld;
+uniform highp float lightStrength;
+uniform highp float ambientStrength;
+uniform highp float shadowQuality;
+uniform highp vec4 lightColor;
+uniform highp float gradMin;
+uniform highp float gradHeight;
+
+highp vec2 poissonDisk[16] = vec2[16](vec2(-0.94201624, -0.39906216),
+ vec2(0.94558609, -0.76890725),
+ vec2(-0.094184101, -0.92938870),
+ vec2(0.34495938, 0.29387760),
+ vec2(-0.91588581, 0.45771432),
+ vec2(-0.81544232, -0.87912464),
+ vec2(-0.38277543, 0.27676845),
+ vec2(0.97484398, 0.75648379),
+ vec2(0.44323325, -0.97511554),
+ vec2(0.53742981, -0.47373420),
+ vec2(-0.26496911, -0.41893023),
+ vec2(0.79197514, 0.19090188),
+ vec2(-0.24188840, 0.99706507),
+ vec2(-0.81409955, 0.91437590),
+ vec2(0.19984126, 0.78641367),
+ vec2(0.14383161, -0.14100790));
+
+void main() {
+ highp vec2 gradientUV = vec2(0.0, gradMin + coords_mdl.y * gradHeight);
+ highp vec3 materialDiffuseColor = texture2D(textureSampler, gradientUV).xyz;
+ highp vec3 materialAmbientColor = lightColor.rgb * ambientStrength * materialDiffuseColor;
+ highp vec3 materialSpecularColor = lightColor.rgb;
+
+ highp vec3 n = normalize(normal_cmr);
+ highp vec3 l = normalize(lightDirection_cmr);
+ highp float cosTheta = clamp(dot(n, l), 0.0, 1.0);
+
+ highp vec3 E = normalize(eyeDirection_cmr);
+ highp vec3 R = reflect(-l, n);
+ highp float cosAlpha = clamp(dot(E, R), 0.0, 1.0);
+
+ highp float bias = 0.005 * tan(acos(cosTheta));
+ bias = clamp(bias, 0.001, 0.01);
+
+ vec4 shadCoords = shadowCoord;
+ shadCoords.z -= bias;
+
+ highp float visibility = 0.6;
+ for (int i = 0; i < 15; i++) {
+ vec4 shadCoordsPD = shadCoords;
+ shadCoordsPD.x += cos(poissonDisk[i].x) / shadowQuality;
+ shadCoordsPD.y += sin(poissonDisk[i].y) / shadowQuality;
+ visibility += 0.025 * shadow2DProj(shadowMap, shadCoordsPD).r;
+ }
+
+ gl_FragColor.rgb =
+ (materialAmbientColor +
+ materialDiffuseColor * lightStrength * cosTheta +
+ materialSpecularColor * lightStrength * pow(cosAlpha, 10));
+ gl_FragColor.a = 1.0;
+ gl_FragColor.rgb = visibility * clamp(gl_FragColor.rgb, 0.0, 1.0);
+}
+
diff --git a/src/graphs/engine/shaders/surfaceTexturedFlat.frag b/src/graphs/engine/shaders/surfaceTexturedFlat.frag
new file mode 100644
index 0000000..6b1ea91
--- /dev/null
+++ b/src/graphs/engine/shaders/surfaceTexturedFlat.frag
@@ -0,0 +1,38 @@
+#version 120
+
+#extension GL_EXT_gpu_shader4 : require
+
+varying highp vec3 position_wrld;
+flat varying highp vec3 normal_cmr;
+varying highp vec3 eyeDirection_cmr;
+varying highp vec3 lightDirection_cmr;
+varying highp vec2 UV;
+
+uniform sampler2D textureSampler;
+uniform highp vec3 lightPosition_wrld;
+uniform highp float lightStrength;
+uniform highp float ambientStrength;
+uniform highp vec4 lightColor;
+
+void main() {
+ highp vec3 materialDiffuseColor = texture2D(textureSampler, UV).xyz;
+ highp vec3 materialAmbientColor = lightColor.rgb * ambientStrength * materialDiffuseColor;
+ highp vec3 materialSpecularColor = lightColor.rgb;
+
+ highp float distance = length(lightPosition_wrld - position_wrld);
+
+ highp vec3 n = normalize(normal_cmr);
+ highp vec3 l = normalize(lightDirection_cmr);
+ highp float cosTheta = clamp(dot(n, l), 0.0, 1.0);
+
+ highp vec3 E = normalize(eyeDirection_cmr);
+ highp vec3 R = reflect(-l, n);
+ highp float cosAlpha = clamp(dot(E, R), 0.0, 1.0);
+
+ gl_FragColor.rgb =
+ materialAmbientColor +
+ materialDiffuseColor * lightStrength * pow(cosTheta, 2) / distance +
+ materialSpecularColor * lightStrength * pow(cosAlpha, 10) / distance;
+ gl_FragColor.a = 1.0;
+}
+
diff --git a/src/graphs/engine/shaders/surfaceTexturedShadow.frag b/src/graphs/engine/shaders/surfaceTexturedShadow.frag
new file mode 100644
index 0000000..a425956
--- /dev/null
+++ b/src/graphs/engine/shaders/surfaceTexturedShadow.frag
@@ -0,0 +1,67 @@
+#version 120
+
+uniform highp float lightStrength;
+uniform highp float ambientStrength;
+uniform highp float shadowQuality;
+uniform highp sampler2D textureSampler;
+uniform highp sampler2DShadow shadowMap;
+uniform highp vec4 lightColor;
+
+varying highp vec4 shadowCoord;
+varying highp vec2 UV;
+varying highp vec3 position_wrld;
+varying highp vec3 normal_cmr;
+varying highp vec3 eyeDirection_cmr;
+varying highp vec3 lightDirection_cmr;
+
+highp vec2 poissonDisk[16] = vec2[16](vec2(-0.94201624, -0.39906216),
+ vec2(0.94558609, -0.76890725),
+ vec2(-0.094184101, -0.92938870),
+ vec2(0.34495938, 0.29387760),
+ vec2(-0.91588581, 0.45771432),
+ vec2(-0.81544232, -0.87912464),
+ vec2(-0.38277543, 0.27676845),
+ vec2(0.97484398, 0.75648379),
+ vec2(0.44323325, -0.97511554),
+ vec2(0.53742981, -0.47373420),
+ vec2(-0.26496911, -0.41893023),
+ vec2(0.79197514, 0.19090188),
+ vec2(-0.24188840, 0.99706507),
+ vec2(-0.81409955, 0.91437590),
+ vec2(0.19984126, 0.78641367),
+ vec2(0.14383161, -0.14100790));
+
+void main() {
+ highp vec3 materialDiffuseColor = texture2D(textureSampler, UV).rgb;
+ highp vec3 materialAmbientColor = lightColor.rgb * ambientStrength * materialDiffuseColor;
+ highp vec3 materialSpecularColor = lightColor.rgb * 0.2;
+
+ highp vec3 n = normalize(normal_cmr);
+ highp vec3 l = normalize(lightDirection_cmr);
+ highp float cosTheta = clamp(dot(n, l), 0.0, 1.0);
+
+ highp vec3 E = normalize(eyeDirection_cmr);
+ highp vec3 R = reflect(-l, n);
+ highp float cosAlpha = clamp(dot(E, R), 0.0, 1.0);
+
+ highp float bias = 0.005 * tan(acos(cosTheta));
+ bias = clamp(bias, 0.001, 0.01);
+
+ vec4 shadCoords = shadowCoord;
+ shadCoords.z -= bias;
+
+ highp float visibility = 0.6;
+ for (int i = 0; i < 15; i++) {
+ vec4 shadCoordsPD = shadCoords;
+ shadCoordsPD.x += cos(poissonDisk[i].x) / shadowQuality;
+ shadCoordsPD.y += sin(poissonDisk[i].y) / shadowQuality;
+ visibility += 0.025 * shadow2DProj(shadowMap, shadCoordsPD).r;
+ }
+
+ gl_FragColor.rgb =
+ (materialAmbientColor +
+ materialDiffuseColor * lightStrength * cosTheta +
+ materialSpecularColor * lightStrength * pow(cosAlpha, 10));
+ gl_FragColor.a = texture2D(textureSampler, UV).a;
+ gl_FragColor.rgb = visibility * clamp(gl_FragColor.rgb, 0.0, 1.0);
+}
diff --git a/src/graphs/engine/shaders/surfaceTexturedShadowFlat.frag b/src/graphs/engine/shaders/surfaceTexturedShadowFlat.frag
new file mode 100644
index 0000000..4a4284e
--- /dev/null
+++ b/src/graphs/engine/shaders/surfaceTexturedShadowFlat.frag
@@ -0,0 +1,72 @@
+#version 120
+
+#extension GL_EXT_gpu_shader4 : require
+
+varying highp vec2 coords_mdl;
+varying highp vec3 position_wrld;
+flat varying highp vec3 normal_cmr;
+varying highp vec3 eyeDirection_cmr;
+varying highp vec3 lightDirection_cmr;
+varying highp vec2 UV;
+
+uniform highp sampler2DShadow shadowMap;
+uniform sampler2D textureSampler;
+varying highp vec4 shadowCoord;
+uniform highp vec3 lightPosition_wrld;
+uniform highp float lightStrength;
+uniform highp float ambientStrength;
+uniform highp float shadowQuality;
+uniform highp vec4 lightColor;
+
+highp vec2 poissonDisk[16] = vec2[16](vec2(-0.94201624, -0.39906216),
+ vec2(0.94558609, -0.76890725),
+ vec2(-0.094184101, -0.92938870),
+ vec2(0.34495938, 0.29387760),
+ vec2(-0.91588581, 0.45771432),
+ vec2(-0.81544232, -0.87912464),
+ vec2(-0.38277543, 0.27676845),
+ vec2(0.97484398, 0.75648379),
+ vec2(0.44323325, -0.97511554),
+ vec2(0.53742981, -0.47373420),
+ vec2(-0.26496911, -0.41893023),
+ vec2(0.79197514, 0.19090188),
+ vec2(-0.24188840, 0.99706507),
+ vec2(-0.81409955, 0.91437590),
+ vec2(0.19984126, 0.78641367),
+ vec2(0.14383161, -0.14100790));
+
+void main() {
+ highp vec3 materialDiffuseColor = texture2D(textureSampler, UV).xyz;
+ highp vec3 materialAmbientColor = lightColor.rgb * ambientStrength * materialDiffuseColor;
+ highp vec3 materialSpecularColor = lightColor.rgb;
+
+ highp vec3 n = normalize(normal_cmr);
+ highp vec3 l = normalize(lightDirection_cmr);
+ highp float cosTheta = clamp(dot(n, l), 0.0, 1.0);
+
+ highp vec3 E = normalize(eyeDirection_cmr);
+ highp vec3 R = reflect(-l, n);
+ highp float cosAlpha = clamp(dot(E, R), 0.0, 1.0);
+
+ highp float bias = 0.005 * tan(acos(cosTheta));
+ bias = clamp(bias, 0.001, 0.01);
+
+ vec4 shadCoords = shadowCoord;
+ shadCoords.z -= bias;
+
+ highp float visibility = 0.6;
+ for (int i = 0; i < 15; i++) {
+ vec4 shadCoordsPD = shadCoords;
+ shadCoordsPD.x += cos(poissonDisk[i].x) / shadowQuality;
+ shadCoordsPD.y += sin(poissonDisk[i].y) / shadowQuality;
+ visibility += 0.025 * shadow2DProj(shadowMap, shadCoordsPD).r;
+ }
+
+ gl_FragColor.rgb =
+ (materialAmbientColor +
+ materialDiffuseColor * lightStrength * cosTheta +
+ materialSpecularColor * lightStrength * pow(cosAlpha, 10));
+ gl_FragColor.a = 1.0;
+ gl_FragColor.rgb = visibility * clamp(gl_FragColor.rgb, 0.0, 1.0);
+}
+
diff --git a/src/graphs/engine/shaders/surface_ES2.frag b/src/graphs/engine/shaders/surface_ES2.frag
new file mode 100644
index 0000000..1d1bdc3
--- /dev/null
+++ b/src/graphs/engine/shaders/surface_ES2.frag
@@ -0,0 +1,42 @@
+varying highp vec2 UV;
+varying highp vec2 coords_mdl;
+varying highp vec3 position_wrld;
+varying highp vec3 normal_cmr;
+varying highp vec3 eyeDirection_cmr;
+varying highp vec3 lightDirection_cmr;
+varying highp vec3 lightPosition_wrld_frag;
+
+uniform sampler2D textureSampler;
+uniform highp float lightStrength;
+uniform highp float ambientStrength;
+uniform highp vec4 lightColor;
+uniform highp float gradMin;
+uniform highp float gradHeight;
+
+void main() {
+ highp vec2 gradientUV = vec2(0.0, gradMin + coords_mdl.y * gradHeight);
+ highp vec3 materialDiffuseColor = texture2D(textureSampler, gradientUV).xyz;
+ highp vec3 materialAmbientColor = lightColor.rgb * ambientStrength * materialDiffuseColor;
+ highp vec3 materialSpecularColor = lightColor.rgb;
+
+ highp float distance = length(lightPosition_wrld_frag - position_wrld);
+
+ highp vec3 n = normalize(normal_cmr);
+ highp vec3 l = normalize(lightDirection_cmr);
+ highp float cosTheta = dot(n, l);
+ if (cosTheta < 0.0) cosTheta = 0.0;
+ else if (cosTheta > 1.0) cosTheta = 1.0;
+
+ highp vec3 E = normalize(eyeDirection_cmr);
+ highp vec3 R = reflect(-l, n);
+ highp float cosAlpha = dot(E, R);
+ if (cosAlpha < 0.0) cosAlpha = 0.0;
+ else if (cosAlpha > 1.0) cosAlpha = 1.0;
+
+ gl_FragColor.rgb =
+ materialAmbientColor +
+ materialDiffuseColor * lightStrength * cosTheta * cosTheta / distance +
+ materialSpecularColor * lightStrength * cosAlpha * cosAlpha * cosAlpha * cosAlpha * cosAlpha * cosAlpha * cosAlpha * cosAlpha * cosAlpha * cosAlpha / distance;
+ gl_FragColor.a = 1.0;
+}
+
diff --git a/src/graphs/engine/shaders/texture.frag b/src/graphs/engine/shaders/texture.frag
new file mode 100644
index 0000000..41c4259
--- /dev/null
+++ b/src/graphs/engine/shaders/texture.frag
@@ -0,0 +1,37 @@
+#version 120
+
+varying highp vec2 UV;
+varying highp vec3 position_wrld;
+varying highp vec3 normal_cmr;
+varying highp vec3 eyeDirection_cmr;
+varying highp vec3 lightDirection_cmr;
+
+uniform highp vec3 lightPosition_wrld;
+uniform highp sampler2D textureSampler;
+uniform highp float lightStrength;
+uniform highp float ambientStrength;
+uniform highp vec4 lightColor;
+
+void main() {
+ highp vec3 materialDiffuseColor = texture2D(textureSampler, UV).rgb;
+ highp vec3 materialAmbientColor = lightColor.rgb * ambientStrength * materialDiffuseColor;
+ highp vec3 materialSpecularColor = lightColor.rgb;
+
+ highp float distance = length(lightPosition_wrld - position_wrld);
+
+ highp vec3 n = normalize(normal_cmr);
+ highp vec3 l = normalize(lightDirection_cmr);
+ highp float cosTheta = clamp(dot(n, l), 0.0, 1.0);
+
+ highp vec3 E = normalize(eyeDirection_cmr);
+ highp vec3 R = reflect(-l, n);
+ highp float cosAlpha = clamp(dot(E, R), 0.0, 1.0);
+
+ gl_FragColor.rgb =
+ materialAmbientColor +
+ materialDiffuseColor * lightStrength * pow(cosTheta, 2) / distance +
+ materialSpecularColor * lightStrength * pow(cosAlpha, 10) / distance;
+ gl_FragColor.a = texture2D(textureSampler, UV).a;
+ gl_FragColor.rgb = clamp(gl_FragColor.rgb, 0.0, 1.0);
+}
+
diff --git a/src/graphs/engine/shaders/texture.vert b/src/graphs/engine/shaders/texture.vert
new file mode 100644
index 0000000..90c0ac2
--- /dev/null
+++ b/src/graphs/engine/shaders/texture.vert
@@ -0,0 +1,28 @@
+uniform highp mat4 MVP;
+uniform highp mat4 V;
+uniform highp mat4 M;
+uniform highp mat4 itM;
+uniform highp vec3 lightPosition_wrld;
+
+attribute highp vec3 vertexPosition_mdl;
+attribute highp vec2 vertexUV;
+attribute highp vec3 vertexNormal_mdl;
+
+varying highp vec3 lightPosition_wrld_frag;
+varying highp vec2 UV;
+varying highp vec3 position_wrld;
+varying highp vec3 normal_cmr;
+varying highp vec3 eyeDirection_cmr;
+varying highp vec3 lightDirection_cmr;
+
+void main() {
+ gl_Position = MVP * vec4(vertexPosition_mdl, 1.0);
+ position_wrld = vec4(M * vec4(vertexPosition_mdl, 1.0)).xyz;
+ vec3 vertexPosition_cmr = vec4(V * M * vec4(vertexPosition_mdl, 1.0)).xyz;
+ eyeDirection_cmr = vec3(0.0, 0.0, 0.0) - vertexPosition_cmr;
+ vec3 lightPosition_cmr = vec4(V * vec4(lightPosition_wrld, 1.0)).xyz;
+ lightDirection_cmr = lightPosition_cmr + eyeDirection_cmr;
+ normal_cmr = vec4(V * itM * vec4(vertexNormal_mdl, 0.0)).xyz;
+ UV = vertexUV;
+ lightPosition_wrld_frag = lightPosition_wrld;
+}
diff --git a/src/graphs/engine/shaders/texture3d.frag b/src/graphs/engine/shaders/texture3d.frag
new file mode 100644
index 0000000..3f9c42f
--- /dev/null
+++ b/src/graphs/engine/shaders/texture3d.frag
@@ -0,0 +1,155 @@
+#version 120
+
+varying highp vec3 pos;
+varying highp vec3 rayDir;
+
+uniform highp sampler3D textureSampler;
+uniform highp vec4 colorIndex[256];
+uniform highp int color8Bit;
+uniform highp vec3 textureDimensions;
+uniform highp int sampleCount; // This is the maximum sample count
+uniform highp float alphaMultiplier;
+uniform highp int preserveOpacity;
+uniform highp vec3 minBounds;
+uniform highp vec3 maxBounds;
+
+// Ray traveling straight through a single 'alpha thickness' applies 100% of the encountered alpha.
+// Rays traveling shorter distances apply a fraction. This is used to normalize the alpha over
+// entire volume, regardless of texture dimensions
+const highp float alphaThicknesses = 32.0;
+
+void main() {
+ vec3 rayStart = pos;
+
+ highp vec3 startBounds = minBounds;
+ highp vec3 endBounds = maxBounds;
+ if (rayDir.x < 0.0) {
+ startBounds.x = maxBounds.x;
+ endBounds.x = minBounds.x;
+ }
+ if (rayDir.y > 0.0) {
+ startBounds.y = maxBounds.y;
+ endBounds.y = minBounds.y;
+ }
+ if (rayDir.z > 0.0) {
+ startBounds.z = maxBounds.z;
+ endBounds.z = minBounds.z;
+ }
+
+ // Calculate ray intersection endpoint
+ highp vec3 rayStop;
+ highp vec3 invRayDir = 1.0 / rayDir;
+ highp vec3 t = (endBounds - rayStart) * invRayDir;
+ highp float endT = min(t.x, min(t.y, t.z));
+ rayStop = rayStart + endT * rayDir;
+ if (endT <= 0.0)
+ discard;
+
+ // Convert intersections to texture coords
+ rayStart = 0.5 * (rayStart + 1.0);
+ rayStop = 0.5 * (rayStop + 1.0);
+
+ highp vec3 ray = rayStop - rayStart;
+
+ highp vec3 absRay = abs(ray);
+ highp vec3 invAbsRay = 1.0 / absRay;
+ highp float fullDist = length(ray);
+ highp vec3 curPos = rayStart;
+
+ highp vec4 curColor = vec4(0, 0, 0, 0);
+ highp float curAlpha = 0.0;
+ highp float curLen = 0.0;
+ highp vec3 curRgb = vec3(0, 0, 0);
+
+ highp vec4 destColor = vec4(0, 0, 0, 0);
+ highp float totalOpacity = 1.0;
+
+ highp float extraAlphaMultiplier = fullDist * alphaThicknesses * alphaMultiplier;
+
+ // nextEdges vector indicates the next edges of the texel boundaries along each axis that
+ // the ray is about to cross. The first edges are offset by a fraction of a texel to
+ // avoid artifacts from rounding errors later.
+ highp vec3 nextEdges = vec3(floor(curPos.x / textureDimensions.x) * textureDimensions.x,
+ floor(curPos.y / textureDimensions.y) * textureDimensions.y,
+ floor(curPos.z / textureDimensions.z) * textureDimensions.z);
+
+ highp vec3 textureSteps = textureDimensions;
+ highp vec3 textureOffset = textureDimensions * 0.001;
+ if (ray.x > 0) {
+ nextEdges.x += textureDimensions.x + textureOffset.x;
+ } else {
+ nextEdges.x -= textureOffset.x;
+ textureSteps.x = -textureDimensions.x;
+ }
+ if (ray.y > 0) {
+ nextEdges.y += textureDimensions.y + textureOffset.y;
+ } else {
+ nextEdges.y -= textureOffset.y;
+ textureSteps.y = -textureDimensions.y;
+ }
+ if (ray.z > 0) {
+ nextEdges.z += textureDimensions.z + textureOffset.z;
+ } else {
+ nextEdges.z -= textureOffset.z;
+ textureSteps.z = -textureDimensions.z;
+ }
+
+ // Raytrace into volume, need to sample pixels along the eye ray until we hit opacity 1
+ for (int i = 0; i < sampleCount; i++) {
+ curColor = texture3D(textureSampler, curPos);
+ if (color8Bit != 0)
+ curColor = colorIndex[int(curColor.r * 255.0)];
+
+ // Find which dimension has least to go to figure out the next step distance
+ highp vec3 delta = abs(nextEdges - curPos);
+ highp vec3 modDelta = delta * invAbsRay;
+ highp float minDelta = min(modDelta.x, min(modDelta.y, modDelta.z));
+ highp float stepSize;
+ if (minDelta == modDelta.x) {
+ nextEdges.x += textureSteps.x;
+ stepSize = delta.x * invAbsRay.x;
+ }
+ if (minDelta == modDelta.y) {
+ nextEdges.y += textureSteps.y;
+ stepSize = delta.y * invAbsRay.y;
+ }
+ if (minDelta == modDelta.z) {
+ nextEdges.z += textureSteps.z;
+ stepSize = delta.z * invAbsRay.z;
+ }
+
+ curPos += stepSize * ray;
+ curLen += stepSize;
+
+ if (curColor.a >= 0.0) {
+ if (curColor.a == 1.0 && (preserveOpacity == 1 || alphaMultiplier >= 1.0))
+ curAlpha = 1.0;
+ else
+ curAlpha = curColor.a * extraAlphaMultiplier * stepSize;
+ highp float nextOpacity = totalOpacity - curAlpha;
+ // If opacity goes beyond full opacity, we need to adjust current alpha according
+ // to the fraction of the distance the material is visible, so that we don't get
+ // box artifacts around texels.
+ if (nextOpacity < 0.0) {
+ curAlpha *= totalOpacity / curAlpha;
+ nextOpacity = 0.0;
+ }
+ curRgb = curColor.rgb * curAlpha * (totalOpacity + nextOpacity);
+ totalOpacity = nextOpacity;
+ destColor.rgb += curRgb;
+ }
+
+ if (curLen >= 1.0 || totalOpacity <= 0.0)
+ break;
+ }
+
+ if (totalOpacity == 1.0)
+ discard;
+
+ // Brighten up the final color if there is some transparency left
+ if (totalOpacity >= 0.0 && totalOpacity < 1.0)
+ destColor *= (1.0 - (totalOpacity * 0.5)) / (1.0 - totalOpacity);
+
+ destColor.a = (1.0 - totalOpacity);
+ gl_FragColor = clamp(destColor, 0.0, 1.0);
+}
diff --git a/src/graphs/engine/shaders/texture3d.vert b/src/graphs/engine/shaders/texture3d.vert
new file mode 100644
index 0000000..ef3f1b2
--- /dev/null
+++ b/src/graphs/engine/shaders/texture3d.vert
@@ -0,0 +1,36 @@
+uniform highp mat4 MVP;
+uniform highp vec3 minBounds;
+uniform highp vec3 maxBounds;
+uniform highp vec3 cameraPositionRelativeToModel;
+
+attribute highp vec3 vertexPosition_mdl;
+attribute highp vec2 vertexUV;
+attribute highp vec3 vertexNormal_mdl;
+
+varying highp vec3 pos;
+varying highp vec3 rayDir;
+
+void main() {
+ gl_Position = MVP * vec4(vertexPosition_mdl, 1.0);
+
+ highp vec3 minBoundsNorm = minBounds;
+ highp vec3 maxBoundsNorm = maxBounds;
+
+ // Y and Z are flipped in bounds to be directly usable in texture calculations,
+ // so flip them back to normal for position calculations
+ minBoundsNorm.yz = -minBoundsNorm.yz;
+ maxBoundsNorm.yz = -maxBoundsNorm.yz;
+
+ minBoundsNorm = 0.5 * (minBoundsNorm + 1.0);
+ maxBoundsNorm = 0.5 * (maxBoundsNorm + 1.0);
+
+ pos = vertexPosition_mdl
+ + ((1.0 - vertexPosition_mdl) * minBoundsNorm)
+ - ((1.0 + vertexPosition_mdl) * (1.0 - maxBoundsNorm));
+
+ rayDir = -(cameraPositionRelativeToModel - pos);
+
+ // Flip Y and Z so QImage bits work directly for texture and first image is in the front
+ rayDir.yz = -rayDir.yz;
+ pos.yz = -pos.yz;
+}
diff --git a/src/graphs/engine/shaders/texture3dlowdef.frag b/src/graphs/engine/shaders/texture3dlowdef.frag
new file mode 100644
index 0000000..ed0d41c
--- /dev/null
+++ b/src/graphs/engine/shaders/texture3dlowdef.frag
@@ -0,0 +1,109 @@
+#version 120
+
+varying highp vec3 pos;
+varying highp vec3 rayDir;
+
+uniform highp sampler3D textureSampler;
+uniform highp vec4 colorIndex[256];
+uniform highp int color8Bit;
+uniform highp vec3 textureDimensions;
+uniform highp int sampleCount; // This is the maximum sample count
+uniform highp float alphaMultiplier;
+uniform highp int preserveOpacity;
+uniform highp vec3 minBounds;
+uniform highp vec3 maxBounds;
+
+// Ray traveling straight through a single 'alpha thickness' applies 100% of the encountered alpha.
+// Rays traveling shorter distances apply a fraction. This is used to normalize the alpha over
+// entire volume, regardless of texture dimensions
+const highp float alphaThicknesses = 32.0;
+const highp float SQRT3 = 1.73205081;
+
+void main() {
+ vec3 rayStart = pos;
+ highp vec3 startBounds = minBounds;
+ highp vec3 endBounds = maxBounds;
+ if (rayDir.x < 0.0) {
+ startBounds.x = maxBounds.x;
+ endBounds.x = minBounds.x;
+ }
+ if (rayDir.y > 0.0) {
+ startBounds.y = maxBounds.y;
+ endBounds.y = minBounds.y;
+ }
+ if (rayDir.z > 0.0) {
+ startBounds.z = maxBounds.z;
+ endBounds.z = minBounds.z;
+ }
+
+ // Calculate ray intersection endpoint
+ highp vec3 rayStop;
+ highp vec3 invRayDir = 1.0 / rayDir;
+ highp vec3 t = (endBounds - rayStart) * invRayDir;
+ highp float endT = min(t.x, min(t.y, t.z));
+ if (endT <= 0.0)
+ discard;
+ rayStop = rayStart + endT * rayDir;
+
+ // Convert intersections to texture coords
+ rayStart = 0.5 * (rayStart + 1.0);
+ rayStop = 0.5 * (rayStop + 1.0);
+
+ highp vec3 ray = rayStop - rayStart;
+
+ highp float fullDist = length(ray);
+ highp float stepSize = SQRT3 / sampleCount;
+ highp vec3 step = (SQRT3 * normalize(ray)) / sampleCount;
+
+ rayStart += (step * 0.001);
+
+ highp vec3 curPos = rayStart;
+ highp float curLen = 0.0;
+ highp vec4 curColor = vec4(0, 0, 0, 0);
+ highp float curAlpha = 0.0;
+ highp vec3 curRgb = vec3(0, 0, 0);
+ highp vec4 destColor = vec4(0, 0, 0, 0);
+ highp float totalOpacity = 1.0;
+
+ highp float extraAlphaMultiplier = stepSize * alphaThicknesses * alphaMultiplier;
+
+ // Raytrace into volume, need to sample pixels along the eye ray until we hit opacity 1
+ for (int i = 0; i < sampleCount; i++) {
+ curColor = texture3D(textureSampler, curPos);
+ if (color8Bit != 0)
+ curColor = colorIndex[int(curColor.r * 255.0)];
+
+ if (curColor.a >= 0.0) {
+ if (curColor.a == 1.0 && (preserveOpacity == 1 || alphaMultiplier >= 1.0))
+ curAlpha = 1.0;
+ else
+ curAlpha = curColor.a * extraAlphaMultiplier;
+ highp float nextOpacity = totalOpacity - curAlpha;
+ // If opacity goes beyond full opacity, we need to adjust current alpha according
+ // to the fraction of the distance the material is visible, so that we don't get
+ // box artifacts around texels.
+ if (nextOpacity < 0.0) {
+ curAlpha *= totalOpacity / curAlpha;
+ nextOpacity = 0.0;
+ }
+
+ curRgb = curColor.rgb * curAlpha * (totalOpacity + nextOpacity);
+ destColor.rgb += curRgb;
+ totalOpacity = nextOpacity;
+ }
+ curPos += step;
+ curLen += stepSize;
+ if (curLen >= fullDist || totalOpacity <= 0.0)
+ break;
+ }
+
+ if (totalOpacity == 1.0)
+ discard;
+
+ // Brighten up the final color if there is some transparency left
+ if (totalOpacity >= 0.0 && totalOpacity < 1.0)
+ destColor *= (1.0 - (totalOpacity * 0.5)) / (1.0 - totalOpacity);
+
+ destColor.a = (1.0 - totalOpacity);
+ gl_FragColor = clamp(destColor, 0.0, 1.0);
+}
diff --git a/src/graphs/engine/shaders/texture3dslice.frag b/src/graphs/engine/shaders/texture3dslice.frag
new file mode 100644
index 0000000..63abf9e
--- /dev/null
+++ b/src/graphs/engine/shaders/texture3dslice.frag
@@ -0,0 +1,155 @@
+#version 120
+
+varying highp vec3 pos;
+varying highp vec3 rayDir;
+
+uniform highp sampler3D textureSampler;
+uniform highp vec3 volumeSliceIndices;
+uniform highp vec4 colorIndex[256];
+uniform highp int color8Bit;
+uniform highp float alphaMultiplier;
+uniform highp int preserveOpacity;
+uniform highp vec3 minBounds;
+uniform highp vec3 maxBounds;
+
+const highp vec3 xPlaneNormal = vec3(1.0, 0, 0);
+const highp vec3 yPlaneNormal = vec3(0, 1.0, 0);
+const highp vec3 zPlaneNormal = vec3(0, 0, 1.0);
+
+void main() {
+ // Find out where ray intersects the slice planes
+ vec3 normRayDir = normalize(rayDir);
+ highp vec3 rayStart = pos;
+ highp float minT = 2.0;
+ if (normRayDir.x != 0.0 && normRayDir.y != 0.0 && normRayDir.z != 0.0) {
+ highp vec3 boxBounds = vec3(1.0, 1.0, 1.0);
+ highp vec3 invRayDir = 1.0 / normRayDir;
+ if (normRayDir.x < 0)
+ boxBounds.x = -1.0;
+ if (normRayDir.y < 0)
+ boxBounds.y = -1.0;
+ if (normRayDir.z < 0)
+ boxBounds.z = -1.0;
+ highp vec3 t = (boxBounds - rayStart) * invRayDir;
+ minT = min(t.x, min(t.y, t.z));
+ }
+
+ highp vec3 xPoint = vec3(volumeSliceIndices.x, 0, 0);
+ highp vec3 yPoint = vec3(0, volumeSliceIndices.y, 0);
+ highp vec3 zPoint = vec3(0, 0, volumeSliceIndices.z);
+ highp float firstD = minT + 1.0;
+ highp float secondD = firstD;
+ highp float thirdD = firstD;
+ if (volumeSliceIndices.x >= -1.0) {
+ highp float dx = dot(xPoint - rayStart, xPlaneNormal) / dot(normRayDir, xPlaneNormal);
+ if (dx >= 0.0 && dx <= minT)
+ firstD = min(dx, firstD);
+ }
+ if (volumeSliceIndices.y >= -1.0) {
+ highp float dy = dot(yPoint - rayStart, yPlaneNormal) / dot(normRayDir, yPlaneNormal);
+ if (dy >= 0.0 && dy <= minT) {
+ if (dy < firstD) {
+ secondD = firstD;
+ firstD = dy;
+ } else {
+ secondD = dy;
+ }
+ }
+ }
+ if (volumeSliceIndices.z >= -1.0) {
+ highp float dz = dot(zPoint - rayStart, zPlaneNormal) / dot(normRayDir, zPlaneNormal);
+ if (dz >= 0.0) {
+ if (dz < firstD && dz <= minT) {
+ thirdD = secondD;
+ secondD = firstD;
+ firstD = dz;
+ } else if (dz < secondD){
+ thirdD = secondD;
+ secondD = dz;
+ } else {
+ thirdD = dz;
+ }
+ }
+ }
+
+ highp vec4 destColor = vec4(0.0, 0.0, 0.0, 0.0);
+ highp vec4 curColor = vec4(0.0, 0.0, 0.0, 0.0);
+ highp float totalAlpha = 0.0;
+ highp vec3 curRgb = vec3(0, 0, 0);
+ highp float curAlpha = 0.0;
+
+ // Convert intersection to texture coords
+
+ if (firstD <= minT) {
+ highp vec3 texelVec = rayStart + normRayDir * firstD;
+ if (clamp(texelVec.x, minBounds.x, maxBounds.x) == texelVec.x
+ && clamp(texelVec.y, maxBounds.y, minBounds.y) == texelVec.y
+ && clamp(texelVec.z, maxBounds.z, minBounds.z) == texelVec.z) {
+ texelVec = 0.5 * (texelVec + 1.0);
+ curColor = texture3D(textureSampler, texelVec);
+ if (color8Bit != 0)
+ curColor = colorIndex[int(curColor.r * 255.0)];
+
+ if (curColor.a > 0.0) {
+ curAlpha = curColor.a;
+ if (curColor.a == 1.0 && preserveOpacity != 0)
+ curAlpha = 1.0;
+ else
+ curAlpha = clamp(curColor.a * alphaMultiplier, 0.0, 1.0);
+ destColor.rgb = curColor.rgb * curAlpha;
+ totalAlpha = curAlpha;
+ }
+ }
+ if (secondD <= minT && totalAlpha < 1.0) {
+ texelVec = rayStart + normRayDir * secondD;
+ if (clamp(texelVec.x, minBounds.x, maxBounds.x) == texelVec.x
+ && clamp(texelVec.y, maxBounds.y, minBounds.y) == texelVec.y
+ && clamp(texelVec.z, maxBounds.z, minBounds.z) == texelVec.z) {
+ texelVec = 0.5 * (texelVec + 1.0);
+ curColor = texture3D(textureSampler, texelVec);
+ if (color8Bit != 0)
+ curColor = colorIndex[int(curColor.r * 255.0)];
+ if (curColor.a > 0.0) {
+ if (curColor.a == 1.0 && preserveOpacity != 0)
+ curAlpha = 1.0;
+ else
+ curAlpha = clamp(curColor.a * alphaMultiplier, 0.0, 1.0);
+ curRgb = curColor.rgb * curAlpha * (1.0 - totalAlpha);
+ destColor.rgb += curRgb;
+ totalAlpha += curAlpha;
+ }
+ }
+ if (thirdD <= minT && totalAlpha < 1.0) {
+ texelVec = rayStart + normRayDir * thirdD;
+ if (clamp(texelVec.x, minBounds.x, maxBounds.x) == texelVec.x
+ && clamp(texelVec.y, maxBounds.y, minBounds.y) == texelVec.y
+ && clamp(texelVec.z, maxBounds.z, minBounds.z) == texelVec.z) {
+ texelVec = 0.5 * (texelVec + 1.0);
+ curColor = texture3D(textureSampler, texelVec);
+ if (curColor.a > 0.0) {
+ if (color8Bit != 0)
+ curColor = colorIndex[int(curColor.r * 255.0)];
+ if (curColor.a == 1.0 && preserveOpacity != 0)
+ curAlpha = 1.0;
+ else
+ curAlpha = clamp(curColor.a * alphaMultiplier, 0.0, 1.0);
+ curRgb = curColor.rgb * curAlpha * (1.0 - totalAlpha);
+ destColor.rgb += curRgb;
+ totalAlpha += curAlpha;
+ }
+ }
+ }
+ }
+ }
+
+ if (totalAlpha == 0.0)
+ discard;
+
+ // Brighten up the final color if there is some transparency left
+ if (totalAlpha > 0.0 && totalAlpha < 1.0)
+ destColor *= 1.0 / totalAlpha;
+
+ destColor.a = totalAlpha;
+ gl_FragColor = clamp(destColor, 0.0, 1.0);
+}
+
diff --git a/src/graphs/engine/shaders/texture_ES2.frag b/src/graphs/engine/shaders/texture_ES2.frag
new file mode 100644
index 0000000..82ad661
--- /dev/null
+++ b/src/graphs/engine/shaders/texture_ES2.frag
@@ -0,0 +1,40 @@
+varying highp vec2 UV;
+varying highp vec2 coords_mdl;
+varying highp vec3 position_wrld;
+varying highp vec3 normal_cmr;
+varying highp vec3 eyeDirection_cmr;
+varying highp vec3 lightDirection_cmr;
+varying highp vec3 lightPosition_wrld_frag;
+
+uniform highp sampler2D textureSampler;
+uniform highp float lightStrength;
+uniform highp float ambientStrength;
+uniform highp vec4 lightColor;
+
+void main() {
+ highp vec3 materialDiffuseColor = texture2D(textureSampler, UV).rgb;
+ highp vec3 materialAmbientColor = lightColor.rgb * ambientStrength * materialDiffuseColor;
+ highp vec3 materialSpecularColor = lightColor.rgb;
+
+ highp float distance = length(lightPosition_wrld_frag - position_wrld);
+
+ highp vec3 n = normalize(normal_cmr);
+ highp vec3 l = normalize(lightDirection_cmr);
+ highp float cosTheta = dot(n, l);
+ if (cosTheta < 0.0) cosTheta = 0.0;
+ else if (cosTheta > 1.0) cosTheta = 1.0;
+
+ highp vec3 E = normalize(eyeDirection_cmr);
+ highp vec3 R = reflect(-l, n);
+ highp float cosAlpha = dot(E, R);
+ if (cosAlpha < 0.0) cosAlpha = 0.0;
+ else if (cosAlpha > 1.0) cosAlpha = 1.0;
+
+ gl_FragColor.rgb =
+ materialAmbientColor +
+ materialDiffuseColor * lightStrength * (cosTheta * cosTheta) / distance +
+ materialSpecularColor * lightStrength * (cosAlpha * cosAlpha * cosAlpha * cosAlpha * cosAlpha * cosAlpha * cosAlpha * cosAlpha * cosAlpha * cosAlpha) / distance;
+ gl_FragColor.a = texture2D(textureSampler, UV).a;
+ gl_FragColor.rgb = clamp(gl_FragColor.rgb, 0.0, 1.0);
+}
+
diff --git a/src/graphs/engine/surface3dcontroller.cpp b/src/graphs/engine/surface3dcontroller.cpp
new file mode 100644
index 0000000..ffbe38f
--- /dev/null
+++ b/src/graphs/engine/surface3dcontroller.cpp
@@ -0,0 +1,511 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "surface3dcontroller_p.h"
+#include "qvalue3daxis_p.h"
+#include "qsurfacedataproxy_p.h"
+#include "qsurface3dseries_p.h"
+#include <QtCore/QMutexLocker>
+
+QT_BEGIN_NAMESPACE
+
+Surface3DController::Surface3DController(QRect rect, Q3DScene *scene)
+ : Abstract3DController(rect, scene),
+ m_selectedPoint(invalidSelectionPosition()),
+ m_selectedSeries(0),
+ m_flatShadingSupported(true),
+ m_flipHorizontalGrid(false)
+{
+ // Setting a null axis creates a new default axis according to orientation and graph type.
+ // Note: these cannot be set in the Abstract3DController constructor, as they will call virtual
+ // functions implemented by subclasses.
+ setAxisX(0);
+ setAxisY(0);
+ setAxisZ(0);
+}
+
+Surface3DController::~Surface3DController()
+{
+}
+
+void Surface3DController::handleAxisAutoAdjustRangeChangedInOrientation(
+ QAbstract3DAxis::AxisOrientation orientation, bool autoAdjust)
+{
+ Q_UNUSED(orientation);
+ Q_UNUSED(autoAdjust);
+
+ adjustAxisRanges();
+}
+
+void Surface3DController::handleAxisRangeChangedBySender(QObject *sender)
+{
+ Abstract3DController::handleAxisRangeChangedBySender(sender);
+
+ // Update selected point - may be moved offscreen
+ setSelectedPoint(m_selectedPoint, m_selectedSeries, false);
+}
+
+void Surface3DController::handleSeriesVisibilityChangedBySender(QObject *sender)
+{
+ Abstract3DController::handleSeriesVisibilityChangedBySender(sender);
+
+ // Visibility changes may require disabling slicing,
+ // so just reset selection to ensure everything is still valid.
+ setSelectedPoint(m_selectedPoint, m_selectedSeries, false);
+}
+
+QPoint Surface3DController::invalidSelectionPosition()
+{
+ static QPoint invalidSelectionPoint(-1, -1);
+ return invalidSelectionPoint;
+}
+
+bool Surface3DController::isFlatShadingSupported()
+{
+ return m_flatShadingSupported;
+}
+
+void Surface3DController::addSeries(QAbstract3DSeries *series)
+{
+ Q_ASSERT(series && series->type() == QAbstract3DSeries::SeriesTypeSurface);
+
+ Abstract3DController::addSeries(series);
+
+ QSurface3DSeries *surfaceSeries = static_cast<QSurface3DSeries *>(series);
+ if (surfaceSeries->selectedPoint() != invalidSelectionPosition())
+ setSelectedPoint(surfaceSeries->selectedPoint(), surfaceSeries, false);
+
+ if (!surfaceSeries->texture().isNull())
+ updateSurfaceTexture(surfaceSeries);
+}
+
+void Surface3DController::removeSeries(QAbstract3DSeries *series)
+{
+ bool wasVisible = (series && series->d_ptr->m_controller == this && series->isVisible());
+
+ Abstract3DController::removeSeries(series);
+
+ if (m_selectedSeries == series)
+ setSelectedPoint(invalidSelectionPosition(), 0, false);
+
+ if (wasVisible)
+ adjustAxisRanges();
+}
+
+QList<QSurface3DSeries *> Surface3DController::surfaceSeriesList()
+{
+ QList<QAbstract3DSeries *> abstractSeriesList = seriesList();
+ QList<QSurface3DSeries *> surfaceSeriesList;
+ foreach (QAbstract3DSeries *abstractSeries, abstractSeriesList) {
+ QSurface3DSeries *surfaceSeries = qobject_cast<QSurface3DSeries *>(abstractSeries);
+ if (surfaceSeries)
+ surfaceSeriesList.append(surfaceSeries);
+ }
+
+ return surfaceSeriesList;
+}
+
+void Surface3DController::setFlipHorizontalGrid(bool flip)
+{
+ if (m_flipHorizontalGrid != flip) {
+ m_flipHorizontalGrid = flip;
+ m_changeTracker.flipHorizontalGridChanged = true;
+ emit flipHorizontalGridChanged(flip);
+ emitNeedRender();
+ }
+}
+
+bool Surface3DController::flipHorizontalGrid() const
+{
+ return m_flipHorizontalGrid;
+}
+
+void Surface3DController::setSelectionMode(QAbstract3DGraph::SelectionFlags mode)
+{
+ // Currently surface only supports row and column modes when also slicing
+ if ((mode.testFlag(QAbstract3DGraph::SelectionRow)
+ || mode.testFlag(QAbstract3DGraph::SelectionColumn))
+ && !mode.testFlag(QAbstract3DGraph::SelectionSlice)) {
+ qWarning("Unsupported selection mode.");
+ return;
+ } else if (mode.testFlag(QAbstract3DGraph::SelectionSlice)
+ && (mode.testFlag(QAbstract3DGraph::SelectionRow)
+ == mode.testFlag(QAbstract3DGraph::SelectionColumn))) {
+ qWarning("Must specify one of either row or column selection mode in conjunction with slicing mode.");
+ } else {
+ QAbstract3DGraph::SelectionFlags oldMode = selectionMode();
+
+ Abstract3DController::setSelectionMode(mode);
+
+ if (mode != oldMode) {
+ // Refresh selection upon mode change to ensure slicing is correctly updated
+ // according to series the visibility.
+ setSelectedPoint(m_selectedPoint, m_selectedSeries, true);
+
+ // Special case: Always deactivate slicing when changing away from slice
+ // automanagement, as this can't be handled in setSelectedBar.
+ if (!mode.testFlag(QAbstract3DGraph::SelectionSlice)
+ && oldMode.testFlag(QAbstract3DGraph::SelectionSlice)) {
+ scene()->setSlicingActive(false);
+ }
+ }
+ }
+}
+
+void Surface3DController::setSelectedPoint(const QPoint &position, QSurface3DSeries *series,
+ bool enterSlice)
+{
+ // If the selection targets non-existent point, clear selection instead.
+ QPoint pos = position;
+
+ // Series may already have been removed, so check it before setting the selection.
+ if (!m_seriesList.contains(series))
+ series = 0;
+
+ const QSurfaceDataProxy *proxy = 0;
+ if (series)
+ proxy = series->dataProxy();
+
+ if (!proxy)
+ pos = invalidSelectionPosition();
+
+ if (pos != invalidSelectionPosition()) {
+ int maxRow = proxy->rowCount() - 1;
+ int maxCol = proxy->columnCount() - 1;
+
+ if (pos.x() < 0 || pos.x() > maxRow || pos.y() < 0 || pos.y() > maxCol)
+ pos = invalidSelectionPosition();
+ }
+
+ if (selectionMode().testFlag(QAbstract3DGraph::SelectionSlice)) {
+ if (pos == invalidSelectionPosition() || !series->isVisible()) {
+ scene()->setSlicingActive(false);
+ } else {
+ // If the selected point is outside data window, or there is no selected point, disable slicing
+ float axisMinX = m_axisX->min();
+ float axisMaxX = m_axisX->max();
+ float axisMinZ = m_axisZ->min();
+ float axisMaxZ = m_axisZ->max();
+
+ QSurfaceDataItem item = proxy->array()->at(pos.x())->at(pos.y());
+ if (item.x() < axisMinX || item.x() > axisMaxX
+ || item.z() < axisMinZ || item.z() > axisMaxZ) {
+ scene()->setSlicingActive(false);
+ } else if (enterSlice) {
+ scene()->setSlicingActive(true);
+ }
+ }
+ emitNeedRender();
+ }
+
+ if (pos != m_selectedPoint || series != m_selectedSeries) {
+ bool seriesChanged = (series != m_selectedSeries);
+ m_selectedPoint = pos;
+ m_selectedSeries = series;
+ m_changeTracker.selectedPointChanged = true;
+
+ // Clear selection from other series and finally set new selection to the specified series
+ foreach (QAbstract3DSeries *otherSeries, m_seriesList) {
+ QSurface3DSeries *surfaceSeries = static_cast<QSurface3DSeries *>(otherSeries);
+ if (surfaceSeries != m_selectedSeries)
+ surfaceSeries->dptr()->setSelectedPoint(invalidSelectionPosition());
+ }
+ if (m_selectedSeries)
+ m_selectedSeries->dptr()->setSelectedPoint(m_selectedPoint);
+
+ if (seriesChanged)
+ emit selectedSeriesChanged(m_selectedSeries);
+
+ emitNeedRender();
+ }
+}
+
+void Surface3DController::clearSelection()
+{
+ setSelectedPoint(invalidSelectionPosition(), 0, false);
+}
+
+void Surface3DController::handleArrayReset()
+{
+ QSurface3DSeries *series;
+ if (qobject_cast<QSurfaceDataProxy *>(sender()))
+ series = static_cast<QSurfaceDataProxy *>(sender())->series();
+ else
+ series = static_cast<QSurface3DSeries *>(sender());
+
+ if (series->isVisible()) {
+ adjustAxisRanges();
+ m_isDataDirty = true;
+ }
+ if (!m_changedSeriesList.contains(series))
+ m_changedSeriesList.append(series);
+
+ // Clear selection unless still valid
+ setSelectedPoint(m_selectedPoint, m_selectedSeries, false);
+ series->d_ptr->markItemLabelDirty();
+ emitNeedRender();
+}
+
+void Surface3DController::handleFlatShadingSupportedChange(bool supported)
+{
+ // Handle renderer flat surface support indicator signal. This happens exactly once per renderer.
+ if (m_flatShadingSupported != supported) {
+ m_flatShadingSupported = supported;
+ // Emit the change for all added surfaces
+ foreach (QAbstract3DSeries *series, m_seriesList) {
+ QSurface3DSeries *surfaceSeries = static_cast<QSurface3DSeries *>(series);
+ emit surfaceSeries->flatShadingSupportedChanged(m_flatShadingSupported);
+ }
+ }
+}
+
+void Surface3DController::handleRowsChanged(int startIndex, int count)
+{
+ QSurface3DSeries *series = static_cast<QSurfaceDataProxy *>(QObject::sender())->series();
+ int oldChangeCount = m_changedRows.size();
+ if (!oldChangeCount)
+ m_changedRows.reserve(count);
+
+ int selectedRow = m_selectedPoint.x();
+ for (int i = 0; i < count; i++) {
+ bool newItem = true;
+ int candidate = startIndex + i;
+ for (int j = 0; j < oldChangeCount; j++) {
+ const ChangeRow &oldChangeItem = m_changedRows.at(j);
+ if (oldChangeItem.row == candidate && series == oldChangeItem.series) {
+ newItem = false;
+ break;
+ }
+ }
+ if (newItem) {
+ ChangeRow newChangeItem = {series, candidate};
+ m_changedRows.append(newChangeItem);
+ if (series == m_selectedSeries && selectedRow == candidate)
+ series->d_ptr->markItemLabelDirty();
+ }
+ }
+ if (count) {
+ m_changeTracker.rowsChanged = true;
+
+ if (series->isVisible())
+ adjustAxisRanges();
+ emitNeedRender();
+ }
+}
+
+void Surface3DController::handleItemChanged(int rowIndex, int columnIndex)
+{
+ QSurfaceDataProxy *sender = static_cast<QSurfaceDataProxy *>(QObject::sender());
+ QSurface3DSeries *series = sender->series();
+
+ bool newItem = true;
+ QPoint candidate(rowIndex, columnIndex);
+ foreach (ChangeItem item, m_changedItems) {
+ if (item.point == candidate && item.series == series) {
+ newItem = false;
+ break;
+ }
+ }
+ if (newItem) {
+ ChangeItem newItem = {series, candidate};
+ m_changedItems.append(newItem);
+ m_changeTracker.itemChanged = true;
+
+ if (series == m_selectedSeries && m_selectedPoint == candidate)
+ series->d_ptr->markItemLabelDirty();
+
+ if (series->isVisible())
+ adjustAxisRanges();
+ emitNeedRender();
+ }
+}
+
+void Surface3DController::handleRowsAdded(int startIndex, int count)
+{
+ Q_UNUSED(startIndex);
+ Q_UNUSED(count);
+ QSurface3DSeries *series = static_cast<QSurfaceDataProxy *>(sender())->series();
+ if (series->isVisible()) {
+ adjustAxisRanges();
+ m_isDataDirty = true;
+ }
+ if (!m_changedSeriesList.contains(series))
+ m_changedSeriesList.append(series);
+ emitNeedRender();
+}
+
+void Surface3DController::handleRowsInserted(int startIndex, int count)
+{
+ Q_UNUSED(startIndex);
+ Q_UNUSED(count);
+ QSurface3DSeries *series = static_cast<QSurfaceDataProxy *>(sender())->series();
+ if (series == m_selectedSeries) {
+ // If rows inserted to selected series before the selection, adjust the selection
+ int selectedRow = m_selectedPoint.x();
+ if (startIndex <= selectedRow) {
+ selectedRow += count;
+ setSelectedPoint(QPoint(selectedRow, m_selectedPoint.y()), m_selectedSeries, false);
+ }
+ }
+
+ if (series->isVisible()) {
+ adjustAxisRanges();
+ m_isDataDirty = true;
+ }
+ if (!m_changedSeriesList.contains(series))
+ m_changedSeriesList.append(series);
+
+ emitNeedRender();
+}
+
+void Surface3DController::handleRowsRemoved(int startIndex, int count)
+{
+ Q_UNUSED(startIndex);
+ Q_UNUSED(count);
+ QSurface3DSeries *series = static_cast<QSurfaceDataProxy *>(sender())->series();
+ if (series == m_selectedSeries) {
+ // If rows removed from selected series before the selection, adjust the selection
+ int selectedRow = m_selectedPoint.x();
+ if (startIndex <= selectedRow) {
+ if ((startIndex + count) > selectedRow)
+ selectedRow = -1; // Selected row removed
+ else
+ selectedRow -= count; // Move selected row down by amount of rows removed
+
+ setSelectedPoint(QPoint(selectedRow, m_selectedPoint.y()), m_selectedSeries, false);
+ }
+ }
+
+ if (series->isVisible()) {
+ adjustAxisRanges();
+ m_isDataDirty = true;
+ }
+ if (!m_changedSeriesList.contains(series))
+ m_changedSeriesList.append(series);
+
+ emitNeedRender();
+}
+
+void Surface3DController::updateSurfaceTexture(QSurface3DSeries *series)
+{
+ m_changeTracker.surfaceTextureChanged = true;
+
+ if (!m_changedTextures.contains(series))
+ m_changedTextures.append(series);
+
+ emitNeedRender();
+}
+
+void Surface3DController::adjustAxisRanges()
+{
+ QValue3DAxis *valueAxisX = static_cast<QValue3DAxis *>(m_axisX);
+ QValue3DAxis *valueAxisY = static_cast<QValue3DAxis *>(m_axisY);
+ QValue3DAxis *valueAxisZ = static_cast<QValue3DAxis *>(m_axisZ);
+ bool adjustX = (valueAxisX && valueAxisX->isAutoAdjustRange());
+ bool adjustY = (valueAxisY && valueAxisY->isAutoAdjustRange());
+ bool adjustZ = (valueAxisZ && valueAxisZ->isAutoAdjustRange());
+ bool first = true;
+
+ if (adjustX || adjustY || adjustZ) {
+ float minValueX = 0.0f;
+ float maxValueX = 0.0f;
+ float minValueY = 0.0f;
+ float maxValueY = 0.0f;
+ float minValueZ = 0.0f;
+ float maxValueZ = 0.0f;
+ int seriesCount = m_seriesList.size();
+ for (int series = 0; series < seriesCount; series++) {
+ const QSurface3DSeries *surfaceSeries =
+ static_cast<QSurface3DSeries *>(m_seriesList.at(series));
+ const QSurfaceDataProxy *proxy = surfaceSeries->dataProxy();
+ if (surfaceSeries->isVisible() && proxy) {
+ QVector3D minLimits;
+ QVector3D maxLimits;
+ proxy->dptrc()->limitValues(minLimits, maxLimits, valueAxisX, valueAxisY, valueAxisZ);
+ if (adjustX) {
+ if (first) {
+ // First series initializes the values
+ minValueX = minLimits.x();
+ maxValueX = maxLimits.x();
+ } else {
+ minValueX = qMin(minValueX, minLimits.x());
+ maxValueX = qMax(maxValueX, maxLimits.x());
+ }
+ }
+ if (adjustY) {
+ if (first) {
+ // First series initializes the values
+ minValueY = minLimits.y();
+ maxValueY = maxLimits.y();
+ } else {
+ minValueY = qMin(minValueY, minLimits.y());
+ maxValueY = qMax(maxValueY, maxLimits.y());
+ }
+ }
+ if (adjustZ) {
+ if (first) {
+ // First series initializes the values
+ minValueZ = minLimits.z();
+ maxValueZ = maxLimits.z();
+ } else {
+ minValueZ = qMin(minValueZ, minLimits.z());
+ maxValueZ = qMax(maxValueZ, maxLimits.z());
+ }
+ }
+ first = false;
+ }
+ }
+
+ static const float adjustmentRatio = 20.0f;
+ static const float defaultAdjustment = 1.0f;
+
+ if (adjustX) {
+ // If all points at same coordinate, need to default to some valid range
+ float adjustment = 0.0f;
+ if (minValueX == maxValueX) {
+ if (adjustZ) {
+ // X and Z are linked to have similar unit size, so choose the valid range based on it
+ if (minValueZ == maxValueZ)
+ adjustment = defaultAdjustment;
+ else
+ adjustment = qAbs(maxValueZ - minValueZ) / adjustmentRatio;
+ } else {
+ if (valueAxisZ)
+ adjustment = qAbs(valueAxisZ->max() - valueAxisZ->min()) / adjustmentRatio;
+ else
+ adjustment = defaultAdjustment;
+ }
+ }
+ valueAxisX->dptr()->setRange(minValueX - adjustment, maxValueX + adjustment, true);
+ }
+ if (adjustY) {
+ // If all points at same coordinate, need to default to some valid range
+ // Y-axis unit is not dependent on other axes, so simply adjust +-1.0f
+ float adjustment = 0.0f;
+ if (minValueY == maxValueY)
+ adjustment = defaultAdjustment;
+ valueAxisY->dptr()->setRange(minValueY - adjustment, maxValueY + adjustment, true);
+ }
+ if (adjustZ) {
+ // If all points at same coordinate, need to default to some valid range
+ float adjustment = 0.0f;
+ if (minValueZ == maxValueZ) {
+ if (adjustX) {
+ // X and Z are linked to have similar unit size, so choose the valid range based on it
+ if (minValueX == maxValueX)
+ adjustment = defaultAdjustment;
+ else
+ adjustment = qAbs(maxValueX - minValueX) / adjustmentRatio;
+ } else {
+ if (valueAxisX)
+ adjustment = qAbs(valueAxisX->max() - valueAxisX->min()) / adjustmentRatio;
+ else
+ adjustment = defaultAdjustment;
+ }
+ }
+ valueAxisZ->dptr()->setRange(minValueZ - adjustment, maxValueZ + adjustment, true);
+ }
+ }
+}
+
+QT_END_NAMESPACE
diff --git a/src/graphs/engine/surface3dcontroller_p.h b/src/graphs/engine/surface3dcontroller_p.h
new file mode 100644
index 0000000..067e182
--- /dev/null
+++ b/src/graphs/engine/surface3dcontroller_p.h
@@ -0,0 +1,134 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QtGraphs API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#ifndef SURFACE3DCONTROLLER_P_H
+#define SURFACE3DCONTROLLER_P_H
+
+#include <private/abstract3dcontroller_p.h>
+#include <private/graphsglobal_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QSurface3DSeries;
+
+struct Surface3DChangeBitField {
+ bool selectedPointChanged : 1;
+ bool rowsChanged : 1;
+ bool itemChanged : 1;
+ bool flipHorizontalGridChanged : 1;
+ bool surfaceTextureChanged : 1;
+
+ Surface3DChangeBitField() :
+ selectedPointChanged(true),
+ rowsChanged(false),
+ itemChanged(false),
+ flipHorizontalGridChanged(true),
+ surfaceTextureChanged(true)
+ {
+ }
+};
+
+class Q_GRAPHS_EXPORT Surface3DController : public Abstract3DController
+{
+ Q_OBJECT
+
+public:
+ struct ChangeItem {
+ QSurface3DSeries *series;
+ QPoint point;
+ };
+ struct ChangeRow {
+ QSurface3DSeries *series;
+ int row;
+ };
+ enum DataDimension {
+ BothAscending = 0,
+ XDescending = 1,
+ ZDescending = 2,
+ BothDescending = XDescending | ZDescending
+ };
+ Q_DECLARE_FLAGS(DataDimensions, DataDimension)
+
+ explicit Surface3DController(QRect rect, Q3DScene *scene = 0);
+ ~Surface3DController();
+
+ void setSelectionMode(QAbstract3DGraph::SelectionFlags mode) override;
+ void setSelectedPoint(const QPoint &position, QSurface3DSeries *series, bool enterSlice);
+ void clearSelection() override;
+
+ inline QSurface3DSeries *selectedSeries() const { return m_selectedSeries; }
+
+ void handleAxisAutoAdjustRangeChangedInOrientation(
+ QAbstract3DAxis::AxisOrientation orientation, bool autoAdjust) override;
+ void handleAxisRangeChangedBySender(QObject *sender) override;
+ void handleSeriesVisibilityChangedBySender(QObject *sender) override;
+ void adjustAxisRanges() override;
+
+ static QPoint invalidSelectionPosition();
+ bool isFlatShadingSupported();
+
+ void addSeries(QAbstract3DSeries *series) override;
+ void removeSeries(QAbstract3DSeries *series) override;
+ virtual QList<QSurface3DSeries *> surfaceSeriesList();
+
+ void setFlipHorizontalGrid(bool flip);
+ bool flipHorizontalGrid() const;
+
+ void updateSurfaceTexture(QSurface3DSeries *series);
+
+ void setDataDimensions(DataDimensions dimension) { m_dataDimensions = dimension; }
+ DataDimensions dataDimensions() { return m_dataDimensions; }
+
+ bool hasChangedSeriesList() { return !m_changedSeriesList.isEmpty(); }
+ bool isSeriesVisibilityDirty() { return m_isSeriesVisualsDirty; }
+
+ bool isDataDirty() { return m_isDataDirty; }
+
+ QList<QAbstract3DSeries *> changedSeriesList() { return m_changedSeriesList; }
+
+ bool isSelectedPointChanged() const { return m_changeTracker.selectedPointChanged; }
+ void setSelectedPointChanged(bool changed) { m_changeTracker.selectedPointChanged = changed; }
+
+public Q_SLOTS:
+ void handleArrayReset();
+ void handleRowsAdded(int startIndex, int count);
+ void handleRowsChanged(int startIndex, int count);
+ void handleRowsRemoved(int startIndex, int count);
+ void handleRowsInserted(int startIndex, int count);
+ void handleItemChanged(int rowIndex, int columnIndex);
+
+ void handleFlatShadingSupportedChange(bool supported);
+
+Q_SIGNALS:
+ void selectedSeriesChanged(QSurface3DSeries *series);
+ void flipHorizontalGridChanged(bool flip);
+
+private:
+ Surface3DChangeBitField m_changeTracker;
+ QPoint m_selectedPoint;
+ QSurface3DSeries *m_selectedSeries; // Points to the series for which the point is selected in
+ // single series selection cases.
+ bool m_flatShadingSupported;
+ QList<ChangeItem> m_changedItems;
+ QList<ChangeRow> m_changedRows;
+ bool m_flipHorizontalGrid;
+ QList<QSurface3DSeries *> m_changedTextures;
+
+ Surface3DController::DataDimensions m_dataDimensions;
+
+ Q_DISABLE_COPY(Surface3DController)
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/engine/surfaceselectioninstancing.cpp b/src/graphs/engine/surfaceselectioninstancing.cpp
new file mode 100644
index 0000000..8e3df1c
--- /dev/null
+++ b/src/graphs/engine/surfaceselectioninstancing.cpp
@@ -0,0 +1,47 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "surfaceselectioninstancing_p.h"
+
+SurfaceSelectionInstancing::SurfaceSelectionInstancing()
+{
+}
+
+SurfaceSelectionInstancing::~SurfaceSelectionInstancing()
+{
+}
+
+void SurfaceSelectionInstancing::addPosition(const QVector3D &position)
+{
+ m_positions.append(position);
+ markDirty();
+ m_dirty = true;
+}
+
+void SurfaceSelectionInstancing::resetPositions()
+{
+ m_positions.clear();
+ markDirty();
+ m_dirty = true;
+}
+
+QByteArray SurfaceSelectionInstancing::getInstanceBuffer(int *instanceCount)
+{
+ if (m_dirty) {
+ m_instanceData.resize(0);
+ int instanceNumber = 0;
+
+ for (auto position : m_positions) {
+ auto entry = calculateTableEntry(position, m_scale, m_rotation, m_color);
+ m_instanceData.append(reinterpret_cast<const char*>(&entry), sizeof(entry));
+ instanceNumber++;
+ }
+ m_instanceCount = instanceNumber;
+ m_dirty = false;
+ }
+
+ if (instanceCount)
+ *instanceCount = m_instanceCount;
+
+ return m_instanceData;
+}
diff --git a/src/graphs/engine/surfaceselectioninstancing_p.h b/src/graphs/engine/surfaceselectioninstancing_p.h
new file mode 100644
index 0000000..634d9c6
--- /dev/null
+++ b/src/graphs/engine/surfaceselectioninstancing_p.h
@@ -0,0 +1,50 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QtGraphs API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#ifndef SURFACESELECTIONINSTANCING_H
+#define SURFACESELECTIONINSTANCING_H
+
+#include <QtQuick3D/private/qquick3dinstancing_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class SurfaceSelectionInstancing : public QQuick3DInstancing
+{
+ Q_OBJECT
+
+public:
+ SurfaceSelectionInstancing();
+ ~SurfaceSelectionInstancing();
+
+ void setRotation(const QVector3D &rotation) { m_rotation = rotation; }
+ void setScale(const QVector3D &scale) { m_scale = scale; }
+ void addPosition(const QVector3D &position);
+ void resetPositions();
+ void setColor(const QColor &color) { m_color = color; }
+
+protected:
+ QByteArray getInstanceBuffer(int *instanceCount) override;
+
+private:
+ QByteArray m_instanceData;
+ int m_instanceCount = 0;
+ QVector3D m_rotation = {.0f, .0f, .0f};
+ QVector3D m_scale = {.0f, .0f, .0f};
+ QList<QVector3D> m_positions;
+ QColor m_color = {0, 0, 0};
+ bool m_dirty = true;
+};
+
+QT_END_NAMESPACE
+
+#endif // SURFACESELECTIONINSTANCING_H
diff --git a/src/graphs/global/graphsglobal_p.h b/src/graphs/global/graphsglobal_p.h
new file mode 100644
index 0000000..5c67f1d
--- /dev/null
+++ b/src/graphs/global/graphsglobal_p.h
@@ -0,0 +1,62 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QtGraphs API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#ifndef GRAPHSGLOBAL_P_H
+#define GRAPHSGLOBAL_P_H
+
+#include "qgraphsglobal.h"
+#include <QtGui/QOpenGLFunctions>
+#include <QtOpenGL/QOpenGLFramebufferObject>
+#include <QtGui/QVector3D>
+#include <QtGui/QQuaternion>
+#include <QtCore/QDebug>
+
+QT_BEGIN_NAMESPACE
+
+// Constants used in several files
+// Distance from camera to origin
+static const float cameraDistance = 6.0f;
+// Size of font to be used in label texture rendering. Doesn't affect the actual font size.
+static const int textureFontSize = 50;
+static const float defaultRatio = 1.0f / 1.6f; // default aspect ratio 16:10
+#if !(defined QT_OPENGL_ES)
+static const float gridLineOffset = 0.0001f; // Offset for lifting grid lines off background
+#else
+static const float gridLineOffset = 0.0035f; // Offset for lifting grid lines off background
+#endif
+// Default light position. To have shadows working correctly, light should be as far as camera, or a bit further
+// y position is added to the minimum height (or can be thought to be that much above or below the camera)
+static const QVector3D defaultLightPos = QVector3D(0.0f, 0.5f, 0.0f);
+static const QVector3D zeroVector = QVector3D(0.0f, 0.0f, 0.0f);
+static const QVector3D oneVector = QVector3D(1.0f, 1.0f, 1.0f);
+static const QVector3D upVector = QVector3D(0.0f, 1.0f, 0.0f);
+static const QVector3D cameraDistanceVector = QVector3D(0.0f, 0.0f, cameraDistance);
+
+// Skip color == selection texture's background color
+static const QVector4D selectionSkipColor = QVector4D(255.0f, 255.0f, 255.0f, 255.0f);
+static const QVector4D invalidColorVector = QVector4D(-1.0f, -1.0f, -1.0f, -1.0f);
+static const float itemAlpha = 0.0f;
+static const float customItemAlpha = 252.0f;
+static const float labelValueAlpha = 253.0f;
+static const float labelRowAlpha = 254.0f;
+static const float labelColumnAlpha = 255.0f;
+static const float gradientTextureHeight = 1024.0f;
+static const float gradientTextureWidth = 2.0f;
+static const float uniformTextureHeight = 64.0f;
+static const float uniformTextureWidth = 2.0f;
+static const float labelMargin = 0.05f;
+static const float gridLineWidth = 0.005f;
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/global/qgraphsglobal.h b/src/graphs/global/qgraphsglobal.h
new file mode 100644
index 0000000..68d639e
--- /dev/null
+++ b/src/graphs/global/qgraphsglobal.h
@@ -0,0 +1,24 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef GRAPHSGLOBAL_H
+#define GRAPHSGLOBAL_H
+
+#include <QtCore/qglobal.h>
+#include <QtGraphs/qtgraphsexports.h>
+
+QT_BEGIN_NAMESPACE
+
+#define QT_GRAPHS_VERSION_STR QT_VERSION_STR
+/*
+ QT_GRAPHS_VERSION is (major << 16) + (minor << 8) + patch.
+*/
+#define QT_GRAPHS_VERSION QT_VERSION
+/*
+ can be used like #if (QT_GRAPHS_VERSION >= QT_GRAPHS_VERSION_CHECK(1, 0, 0))
+*/
+#define QT_GRAPHS_VERSION_CHECK(major, minor, patch) ((major<<16)|(minor<<8)|(patch))
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/input/q3dinputhandler.cpp b/src/graphs/input/q3dinputhandler.cpp
new file mode 100644
index 0000000..7ed5b49
--- /dev/null
+++ b/src/graphs/input/q3dinputhandler.cpp
@@ -0,0 +1,403 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "graphsglobal_p.h"
+#include "q3dinputhandler_p.h"
+#include "abstract3dcontroller_p.h"
+
+QT_BEGIN_NAMESPACE
+
+static const int halfSizeZoomLevel = 50;
+static const int oneToOneZoomLevel = 100;
+static const int driftTowardCenterLevel = 175;
+static const float wheelZoomDrift = 0.1f;
+
+static const int nearZoomRangeDivider = 12;
+static const int midZoomRangeDivider = 60;
+static const int farZoomRangeDivider = 120;
+
+static const float rotationSpeed = 100.0f;
+
+/*!
+ * \class Q3DInputHandler
+ * \inmodule QtGraphs
+ * \brief Basic wheel mouse based input handler.
+ *
+ * Q3DInputHandler is the basic input handler for wheel mouse type of input devices.
+ *
+ * Default input handler has the following functionalty:
+ * \table
+ * \header
+ * \li Mouse action
+ * \li Action
+ * \row
+ * \li Drag with right button pressed
+ * \li Rotate graph within limits set for Q3DCamera.
+ * \row
+ * \li Left click
+ * \li Select item under cursor or remove selection if none.
+ * May open the secondary view depending on the
+ * \l {QAbstract3DGraph::selectionMode}{selection mode}.
+ * \row
+ * \li Mouse wheel
+ * \li Zoom in/out within the allowable zoom range set for Q3DCamera.
+ * \row
+ * \li Left click on the primary view when the secondary view is visible
+ * \li Closes the secondary view.
+ * \note Secondary view is available only for Q3DBars and Q3DSurface graphs.
+ * \endtable
+ *
+ * Rotation, zoom, and selection can each be individually disabled using
+ * corresponding properties of this class.
+ */
+
+/*!
+ * \qmltype InputHandler3D
+ * \inqmlmodule QtGraphs
+ * \ingroup graphs_qml
+ * \instantiates Q3DInputHandler
+ * \brief Basic wheel mouse based input handler.
+ *
+ * InputHandler3D is the basic input handler for wheel mouse type of input devices.
+ *
+ * See Q3DInputHandler documentation for more details.
+ */
+
+/*!
+ * \qmlproperty bool InputHandler3D::rotationEnabled
+ *
+ * Defines whether this input handler allows graph rotation.
+ * Defaults to \c{true}.
+ */
+
+/*!
+ * \qmlproperty bool InputHandler3D::zoomEnabled
+ *
+ * Defines whether this input handler allows graph zooming.
+ * Defaults to \c{true}.
+ */
+
+/*!
+ * \qmlproperty bool InputHandler3D::selectionEnabled
+ *
+ * Defines whether this input handler allows selection from the graph.
+ * Defaults to \c{true}.
+ */
+
+/*!
+ * \qmlproperty bool InputHandler3D::zoomAtTargetEnabled
+ *
+ * Defines whether zooming changes the camera target to the position of the input
+ * at the time of the zoom.
+ * Defaults to \c{true}.
+ */
+
+/*!
+ * Constructs the basic mouse input handler. An optional \a parent parameter can be given
+ * and is then passed to QObject constructor.
+ */
+Q3DInputHandler::Q3DInputHandler(QObject *parent) :
+ QAbstract3DInputHandler(parent),
+ d_ptr(new Q3DInputHandlerPrivate(this))
+{
+}
+
+/*!
+ * Destroys the input handler.
+ */
+Q3DInputHandler::~Q3DInputHandler()
+{
+}
+
+// Input event listeners
+/*!
+ * Override this to change handling of mouse press events.
+ * Mouse press event is given in the \a event and the mouse position in \a mousePos.
+ */
+void Q3DInputHandler::mousePressEvent(QMouseEvent *event, const QPoint &mousePos)
+{
+#if defined(Q_OS_IOS)
+ Q_UNUSED(event);
+ Q_UNUSED(mousePos);
+#else
+ if (Qt::LeftButton == event->button()) {
+ if (isSelectionEnabled()) {
+ if (scene()->isSlicingActive()) {
+ if (scene()->isPointInPrimarySubView(mousePos))
+ setInputView(InputViewOnPrimary);
+ else if (scene()->isPointInSecondarySubView(mousePos))
+ setInputView(InputViewOnSecondary);
+ else
+ setInputView(InputViewNone);
+ } else {
+ // update mouse positions to prevent jumping when releasing or repressing a button
+ setInputPosition(mousePos);
+ scene()->setSelectionQueryPosition(mousePos);
+ setInputView(InputViewOnPrimary);
+ d_ptr->m_inputState = QAbstract3DInputHandlerPrivate::InputStateSelecting;
+ }
+ }
+ } else if (Qt::MiddleButton == event->button()) {
+ if (isRotationEnabled()) {
+ // reset rotations
+ setInputPosition(QPoint(0, 0));
+ }
+ } else if (Qt::RightButton == event->button()) {
+ if (isRotationEnabled()) {
+ // disable rotating when in slice view
+ if (!scene()->isSlicingActive())
+ d_ptr->m_inputState = QAbstract3DInputHandlerPrivate::InputStateRotating;
+ // update mouse positions to prevent jumping when releasing or repressing a button
+ setInputPosition(mousePos);
+ }
+ }
+#endif
+}
+
+/*!
+ * Override this to change handling of mouse release events.
+ * Mouse release event is given in the \a event and the mouse position in \a mousePos.
+ */
+void Q3DInputHandler::mouseReleaseEvent(QMouseEvent *event, const QPoint &mousePos)
+{
+ Q_UNUSED(event);
+#if defined(Q_OS_IOS)
+ Q_UNUSED(mousePos);
+#else
+ if (QAbstract3DInputHandlerPrivate::InputStateRotating == d_ptr->m_inputState) {
+ // update mouse positions to prevent jumping when releasing or repressing a button
+ setInputPosition(mousePos);
+ }
+ d_ptr->m_inputState = QAbstract3DInputHandlerPrivate::InputStateNone;
+ setInputView(InputViewNone);
+#endif
+}
+
+/*!
+ * Override this to change handling of mouse move events.
+ * Mouse move event is given in the \a event and the mouse position in \a mousePos.
+ */
+void Q3DInputHandler::mouseMoveEvent(QMouseEvent *event, const QPoint &mousePos)
+{
+ Q_UNUSED(event);
+#if defined(Q_OS_IOS)
+ Q_UNUSED(mousePos);
+#else
+ if (QAbstract3DInputHandlerPrivate::InputStateRotating == d_ptr->m_inputState
+ && isRotationEnabled()) {
+ // Calculate mouse movement since last frame
+ float xRotation = scene()->activeCamera()->xRotation();
+ float yRotation = scene()->activeCamera()->yRotation();
+ float mouseMoveX = float(inputPosition().x() - mousePos.x())
+ / (scene()->viewport().width() / rotationSpeed);
+ float mouseMoveY = float(inputPosition().y() - mousePos.y())
+ / (scene()->viewport().height() / rotationSpeed);
+ // Apply to rotations
+ xRotation -= mouseMoveX;
+ yRotation -= mouseMoveY;
+ scene()->activeCamera()->setXRotation(xRotation);
+ scene()->activeCamera()->setYRotation(yRotation);
+
+ setPreviousInputPos(inputPosition());
+ setInputPosition(mousePos);
+ }
+#endif
+}
+
+#if QT_CONFIG(wheelevent)
+/*!
+ * Override this to change handling of wheel events.
+ * The wheel event is given in the \a event.
+ */
+void Q3DInputHandler::wheelEvent(QWheelEvent *event)
+{
+ if (isZoomEnabled()) {
+ // disable zooming if in slice view
+ if (scene()->isSlicingActive())
+ return;
+
+ // Adjust zoom level based on what zoom range we're in.
+ Q3DCamera *camera = scene()->activeCamera();
+ int zoomLevel = int(camera->zoomLevel());
+ const int minZoomLevel = int(camera->minZoomLevel());
+ const int maxZoomLevel = int(camera->maxZoomLevel());
+ if (zoomLevel > oneToOneZoomLevel)
+ zoomLevel += event->angleDelta().y() / nearZoomRangeDivider;
+ else if (zoomLevel > halfSizeZoomLevel)
+ zoomLevel += event->angleDelta().y() / midZoomRangeDivider;
+ else
+ zoomLevel += event->angleDelta().y() / farZoomRangeDivider;
+ zoomLevel = qBound(minZoomLevel, zoomLevel, maxZoomLevel);
+
+ if (isZoomAtTargetEnabled()) {
+ d_ptr->m_controller->setGraphPositionQueryPending(true);
+ scene()->setGraphPositionQuery(event->position().toPoint());
+ d_ptr->m_zoomAtTargetPending = true;
+ // If zoom at target is enabled, we don't want to zoom yet, as that causes
+ // jitter. Instead, we zoom next frame, when we apply the camera position.
+ d_ptr->m_requestedZoomLevel = zoomLevel;
+ d_ptr->m_driftMultiplier = wheelZoomDrift;
+ } else {
+ camera->setZoomLevel(zoomLevel);
+ }
+ }
+}
+#endif
+
+/*!
+ * \property Q3DInputHandler::rotationEnabled
+ *
+ * \brief Whether this input handler allows graph rotation.
+ *
+ * Defaults to \c{true}.
+ */
+void Q3DInputHandler::setRotationEnabled(bool enable)
+{
+ if (d_ptr->m_rotationEnabled != enable) {
+ d_ptr->m_rotationEnabled = enable;
+ emit rotationEnabledChanged(enable);
+ }
+}
+
+bool Q3DInputHandler::isRotationEnabled() const
+{
+ return d_ptr->m_rotationEnabled;
+}
+
+/*!
+ * \property Q3DInputHandler::zoomEnabled
+ *
+ * \brief Whether this input handler allows graph zooming.
+ *
+ * Defaults to \c{true}.
+ */
+void Q3DInputHandler::setZoomEnabled(bool enable)
+{
+ if (d_ptr->m_zoomEnabled != enable) {
+ d_ptr->m_zoomEnabled = enable;
+ emit zoomEnabledChanged(enable);
+ }
+}
+
+bool Q3DInputHandler::isZoomEnabled() const
+{
+ return d_ptr->m_zoomEnabled;
+}
+
+/*!
+ * \property Q3DInputHandler::selectionEnabled
+ *
+ * \brief Whether this input handler allows selection from the graph.
+ *
+ * Defaults to \c{true}.
+ */
+void Q3DInputHandler::setSelectionEnabled(bool enable)
+{
+ if (d_ptr->m_selectionEnabled != enable) {
+ d_ptr->m_selectionEnabled = enable;
+ emit selectionEnabledChanged(enable);
+ }
+}
+
+bool Q3DInputHandler::isSelectionEnabled() const
+{
+ return d_ptr->m_selectionEnabled;
+}
+
+/*!
+ * \property Q3DInputHandler::zoomAtTargetEnabled
+ *
+ * \brief Whether zooming should change the camera target so that the zoomed point
+ * of the graph stays at the same location after the zoom.
+ *
+ * Defaults to \c{true}.
+ */
+void Q3DInputHandler::setZoomAtTargetEnabled(bool enable)
+{
+ if (d_ptr->m_zoomAtTargetEnabled != enable) {
+ d_ptr->m_zoomAtTargetEnabled = enable;
+ emit zoomAtTargetEnabledChanged(enable);
+ }
+}
+
+bool Q3DInputHandler::isZoomAtTargetEnabled() const
+{
+ return d_ptr->m_zoomAtTargetEnabled;
+}
+
+Q3DInputHandlerPrivate::Q3DInputHandlerPrivate(Q3DInputHandler *q)
+ : q_ptr(q),
+ m_inputState(QAbstract3DInputHandlerPrivate::InputStateNone),
+ m_rotationEnabled(true),
+ m_zoomEnabled(true),
+ m_selectionEnabled(true),
+ m_zoomAtTargetEnabled(true),
+ m_zoomAtTargetPending(false),
+ m_controller(0),
+ m_requestedZoomLevel(0.0f),
+ m_driftMultiplier(0.0f)
+{
+ QObject::connect(q, &QAbstract3DInputHandler::sceneChanged,
+ this, &Q3DInputHandlerPrivate::handleSceneChange);
+}
+
+Q3DInputHandlerPrivate::~Q3DInputHandlerPrivate()
+{
+}
+
+void Q3DInputHandlerPrivate::handleSceneChange(Q3DScene *scene)
+{
+ if (scene) {
+ if (m_controller) {
+ QObject::disconnect(m_controller, &Abstract3DController::queriedGraphPositionChanged,
+ this, &Q3DInputHandlerPrivate::handleQueriedGraphPositionChange);
+ }
+
+ m_controller = qobject_cast<Abstract3DController *>(scene->parent());
+
+ if (m_controller) {
+ QObject::connect(m_controller, &Abstract3DController::queriedGraphPositionChanged,
+ this, &Q3DInputHandlerPrivate::handleQueriedGraphPositionChange);
+ }
+ }
+}
+
+void Q3DInputHandlerPrivate::handleQueriedGraphPositionChange()
+{
+ if (m_zoomAtTargetPending) {
+ // Check if the zoom point is on graph
+ QVector3D newTarget = m_controller->queriedGraphPosition();
+ float currentZoom = m_requestedZoomLevel;
+ float previousZoom = q_ptr->scene()->activeCamera()->zoomLevel();
+ q_ptr->scene()->activeCamera()->setZoomLevel(currentZoom);
+ float diffAdj = 0.0f;
+
+ // If zooming in/out outside the graph, or zooming out after certain point,
+ // move towards the center.
+ if ((qAbs(newTarget.x()) > 2.0f
+ || qAbs(newTarget.y()) > 2.0f
+ || qAbs(newTarget.z()) > 2.0f)
+ || (previousZoom > currentZoom && currentZoom <= driftTowardCenterLevel)) {
+ newTarget = zeroVector;
+ // Add some extra correction so that we actually reach the center eventually
+ diffAdj = m_driftMultiplier;
+ if (previousZoom > currentZoom)
+ diffAdj *= 2.0f; // Correct towards center little more when zooming out
+ }
+
+ float zoomFraction = 1.0f - (previousZoom / currentZoom);
+
+ // Adjust camera towards the zoom point, attempting to keep the cursor at same graph point
+ QVector3D oldTarget = q_ptr->scene()->activeCamera()->target();
+ QVector3D origDiff = newTarget - oldTarget;
+ QVector3D diff = origDiff * zoomFraction + (origDiff.normalized() * diffAdj);
+ if (diff.length() > origDiff.length())
+ diff = origDiff;
+ q_ptr->scene()->activeCamera()->setTarget(oldTarget + diff);
+
+ if (q_ptr->scene()->selectionQueryPosition() == Q3DScene::invalidSelectionPoint())
+ m_zoomAtTargetPending = false;
+ }
+}
+
+QT_END_NAMESPACE
diff --git a/src/graphs/input/q3dinputhandler.h b/src/graphs/input/q3dinputhandler.h
new file mode 100644
index 0000000..b0dec42
--- /dev/null
+++ b/src/graphs/input/q3dinputhandler.h
@@ -0,0 +1,56 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef Q3DINPUTHANDLER_H
+#define Q3DINPUTHANDLER_H
+
+#include <QtGraphs/qabstract3dinputhandler.h>
+
+QT_BEGIN_NAMESPACE
+
+class Q3DInputHandlerPrivate;
+
+class Q_GRAPHS_EXPORT Q3DInputHandler : public QAbstract3DInputHandler
+{
+ Q_OBJECT
+ Q_PROPERTY(bool rotationEnabled READ isRotationEnabled WRITE setRotationEnabled NOTIFY rotationEnabledChanged)
+ Q_PROPERTY(bool zoomEnabled READ isZoomEnabled WRITE setZoomEnabled NOTIFY zoomEnabledChanged)
+ Q_PROPERTY(bool selectionEnabled READ isSelectionEnabled WRITE setSelectionEnabled NOTIFY selectionEnabledChanged)
+ Q_PROPERTY(bool zoomAtTargetEnabled READ isZoomAtTargetEnabled WRITE setZoomAtTargetEnabled NOTIFY zoomAtTargetEnabledChanged)
+
+public:
+ explicit Q3DInputHandler(QObject *parent = nullptr);
+ virtual ~Q3DInputHandler();
+
+ void setRotationEnabled(bool enable);
+ bool isRotationEnabled() const;
+ void setZoomEnabled(bool enable);
+ bool isZoomEnabled() const;
+ void setSelectionEnabled(bool enable);
+ bool isSelectionEnabled() const;
+ void setZoomAtTargetEnabled(bool enable);
+ bool isZoomAtTargetEnabled() const;
+
+ // Input event listeners
+ void mousePressEvent(QMouseEvent *event, const QPoint &mousePos) override;
+ void mouseReleaseEvent(QMouseEvent *event, const QPoint &mousePos) override;
+ void mouseMoveEvent(QMouseEvent *event, const QPoint &mousePos) override;
+#if QT_CONFIG(wheelevent)
+ void wheelEvent(QWheelEvent *event) override;
+#endif
+
+Q_SIGNALS:
+ void rotationEnabledChanged(bool enable);
+ void zoomEnabledChanged(bool enable);
+ void selectionEnabledChanged(bool enable);
+ void zoomAtTargetEnabledChanged(bool enable);
+
+private:
+ Q_DISABLE_COPY(Q3DInputHandler)
+
+ QScopedPointer<Q3DInputHandlerPrivate> d_ptr;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/input/q3dinputhandler_p.h b/src/graphs/input/q3dinputhandler_p.h
new file mode 100644
index 0000000..45fa456
--- /dev/null
+++ b/src/graphs/input/q3dinputhandler_p.h
@@ -0,0 +1,56 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QtGraphs API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#ifndef Q3DINPUTHANDLER_P_H
+#define Q3DINPUTHANDLER_P_H
+
+#include "qabstract3dinputhandler_p.h"
+#include "q3dinputhandler.h"
+
+QT_BEGIN_NAMESPACE
+
+class Abstract3DController;
+
+class Q3DInputHandlerPrivate : public QObject
+{
+ Q_OBJECT
+public:
+ Q3DInputHandlerPrivate(Q3DInputHandler *q);
+ ~Q3DInputHandlerPrivate();
+
+public Q_SLOTS:
+ void handleSceneChange(Q3DScene *scene);
+ void handleQueriedGraphPositionChange();
+
+private:
+ Q3DInputHandler *q_ptr;
+protected:
+ QAbstract3DInputHandlerPrivate::InputState m_inputState;
+
+ bool m_rotationEnabled;
+ bool m_zoomEnabled;
+ bool m_selectionEnabled;
+ bool m_zoomAtTargetEnabled;
+ bool m_zoomAtTargetPending;
+
+ Abstract3DController *m_controller; // Not owned
+
+ float m_requestedZoomLevel;
+ float m_driftMultiplier;
+
+ friend class Q3DInputHandler;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/input/qabstract3dinputhandler.cpp b/src/graphs/input/qabstract3dinputhandler.cpp
new file mode 100644
index 0000000..5268523
--- /dev/null
+++ b/src/graphs/input/qabstract3dinputhandler.cpp
@@ -0,0 +1,234 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qabstract3dinputhandler_p.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ * \class QAbstract3DInputHandler
+ * \inmodule QtGraphs
+ * \brief The base class for implementations of input handlers.
+ *
+ * QAbstract3DInputHandler is the base class that is subclassed by different input handling implementations
+ * that take input events and translate those to camera and light movements. Input handlers also translate
+ * raw input events to slicing and selection events in the scene.
+ */
+
+/*!
+ * \enum QAbstract3DInputHandler::InputView
+ *
+ * Predefined input views for mouse and touch based input handlers.
+ *
+ * \value InputViewNone
+ * Mouse or touch not on a view.
+ * \value InputViewOnPrimary
+ * Mouse or touch input received on the primary view area. If secondary view is displayed when
+ * inputView becomes InputViewOnPrimary, secondary view is closed.
+ * \value InputViewOnSecondary
+ * Mouse or touch input received on the secondary view area.
+ */
+
+/*!
+ * \qmltype AbstractInputHandler3D
+ * \inqmlmodule QtGraphs
+ * \ingroup graphs_qml
+ * \instantiates QAbstract3DInputHandler
+ * \brief Base type for all QtGraphs input handlers.
+ *
+ * This type is uncreatable.
+ *
+ * For AbstractInputHandler3D enums, see \l{QAbstract3DInputHandler::InputView}.
+ */
+
+/*!
+ * Constructs the base class. An optional \a parent parameter can be given
+ * and is then passed to QObject constructor.
+ */
+QAbstract3DInputHandler::QAbstract3DInputHandler(QObject *parent) :
+ QObject(parent),
+ d_ptr(new QAbstract3DInputHandlerPrivate(this))
+{
+}
+
+/*!
+ * Destroys the base class.
+ */
+QAbstract3DInputHandler::~QAbstract3DInputHandler()
+{
+}
+
+// Input event listeners
+/*!
+ * Override this to handle mouse double click events.
+ * Mouse double click event is given in the \a event.
+ */
+void QAbstract3DInputHandler::mouseDoubleClickEvent(QMouseEvent *event)
+{
+ Q_UNUSED(event);
+}
+
+/*!
+ * Override this to handle touch input events.
+ * Touch event is given in the \a event.
+ */
+void QAbstract3DInputHandler::touchEvent(QTouchEvent *event)
+{
+ Q_UNUSED(event);
+}
+
+/*!
+ * Override this to handle mouse press events.
+ * Mouse press event is given in the \a event and the mouse position in \a mousePos.
+ */
+void QAbstract3DInputHandler::mousePressEvent(QMouseEvent *event, const QPoint &mousePos)
+{
+ Q_UNUSED(event);
+ Q_UNUSED(mousePos);
+}
+
+/*!
+ * Override this to handle mouse release events.
+ * Mouse release event is given in the \a event and the mouse position in \a mousePos.
+ */
+void QAbstract3DInputHandler::mouseReleaseEvent(QMouseEvent *event, const QPoint &mousePos)
+{
+ Q_UNUSED(event);
+ Q_UNUSED(mousePos);
+}
+
+/*!
+ * Override this to handle mouse move events.
+ * Mouse move event is given in the \a event and the mouse position in \a mousePos.
+ */
+void QAbstract3DInputHandler::mouseMoveEvent(QMouseEvent *event, const QPoint &mousePos)
+{
+ Q_UNUSED(event);
+ Q_UNUSED(mousePos);
+}
+
+#if QT_CONFIG(wheelevent)
+/*!
+ * Override this to handle wheel events.
+ * Wheel event is given in the \a event.
+ */
+void QAbstract3DInputHandler::wheelEvent(QWheelEvent *event)
+{
+ Q_UNUSED(event);
+}
+#endif
+
+// Property get/set
+/*!
+ * \property QAbstract3DInputHandler::inputView
+ *
+ * \brief The current enumerated input view based on the view of the processed
+ * input events.
+ *
+ * One of the InputView enum values.
+ *
+ * When the view changes, the \c inputViewChanged signal is emitted.
+ *
+ * \sa InputView
+ */
+QAbstract3DInputHandler::InputView QAbstract3DInputHandler::inputView() const
+{
+ return d_ptr->m_inputView;
+}
+
+void QAbstract3DInputHandler::setInputView(InputView inputView)
+{
+ if (inputView != d_ptr->m_inputView) {
+ d_ptr->m_inputView = inputView;
+ emit inputViewChanged(inputView);
+ }
+}
+
+/*!
+ * \property QAbstract3DInputHandler::inputPosition
+ *
+ * \brief The last input position based on the processed input events.
+ */
+QPoint QAbstract3DInputHandler::inputPosition() const
+{
+ return d_ptr->m_inputPosition;
+}
+
+void QAbstract3DInputHandler::setInputPosition(const QPoint &position)
+{
+ if (position != d_ptr->m_inputPosition) {
+ d_ptr->m_inputPosition = position;
+ emit positionChanged(position);
+ }
+}
+
+/*!
+ * Returns the manhattan length between last two input positions.
+ */
+int QAbstract3DInputHandler::prevDistance() const
+{
+ return d_ptr->m_prevDistance;
+}
+
+/*!
+ * Sets the \a distance (manhattan length) between last two input positions.
+ */
+void QAbstract3DInputHandler::setPrevDistance(int distance)
+{
+ d_ptr->m_prevDistance = distance;
+}
+
+/*!
+ * \property QAbstract3DInputHandler::scene
+ *
+ * \brief The 3D scene this abstract input handler is controlling.
+ *
+ * One input handler can control one scene. Setting a scene to an input handler
+ * does not transfer the ownership of the scene.
+ */
+Q3DScene *QAbstract3DInputHandler::scene() const
+{
+ return d_ptr->m_scene;
+}
+
+void QAbstract3DInputHandler::setScene(Q3DScene *scene)
+{
+ if (scene != d_ptr->m_scene) {
+ d_ptr->m_scene = scene;
+ emit sceneChanged(scene);
+ }
+}
+
+/*!
+ * Sets the previous input position to the point given by \a position.
+ */
+void QAbstract3DInputHandler::setPreviousInputPos(const QPoint &position)
+{
+ d_ptr->m_previousInputPos = position;
+}
+
+/*!
+ * Returns the previous input position.
+ */
+QPoint QAbstract3DInputHandler::previousInputPos() const
+{
+ return d_ptr->m_previousInputPos;
+}
+
+QAbstract3DInputHandlerPrivate::QAbstract3DInputHandlerPrivate(QAbstract3DInputHandler *q) :
+ q_ptr(q),
+ m_prevDistance(0),
+ m_previousInputPos(QPoint(0,0)),
+ m_inputView(QAbstract3DInputHandler::InputViewNone),
+ m_inputPosition(QPoint(0,0)),
+ m_scene(0),
+ m_isDefaultHandler(false)
+{
+}
+
+QAbstract3DInputHandlerPrivate::~QAbstract3DInputHandlerPrivate()
+{
+
+}
+
+QT_END_NAMESPACE
diff --git a/src/graphs/input/qabstract3dinputhandler.h b/src/graphs/input/qabstract3dinputhandler.h
new file mode 100644
index 0000000..31b83e1
--- /dev/null
+++ b/src/graphs/input/qabstract3dinputhandler.h
@@ -0,0 +1,80 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QABSTRACT3DINPUTHANDLER_H
+#define QABSTRACT3DINPUTHANDLER_H
+
+#include <QtGraphs/qgraphsglobal.h>
+#include <QtGraphs/q3dscene.h>
+#include <QtCore/QObject>
+#include <QtCore/QPoint>
+#include <QtGui/QWheelEvent>
+#include <QtGui/QMouseEvent>
+#include <QtGui/QTouchEvent>
+
+QT_BEGIN_NAMESPACE
+
+class QAbstract3DInputHandlerPrivate;
+
+class Q_GRAPHS_EXPORT QAbstract3DInputHandler : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(InputView inputView READ inputView WRITE setInputView NOTIFY inputViewChanged)
+ Q_PROPERTY(QPoint inputPosition READ inputPosition WRITE setInputPosition NOTIFY positionChanged)
+ Q_PROPERTY(Q3DScene *scene READ scene WRITE setScene NOTIFY sceneChanged)
+
+public:
+ enum InputView {
+ InputViewNone = 0,
+ InputViewOnPrimary,
+ InputViewOnSecondary
+ };
+ Q_ENUM(InputView)
+
+protected:
+ explicit QAbstract3DInputHandler(QObject *parent = nullptr);
+public:
+ virtual ~QAbstract3DInputHandler();
+
+ // Input event listeners
+ virtual void mouseDoubleClickEvent(QMouseEvent *event);
+ virtual void touchEvent(QTouchEvent *event);
+ virtual void mousePressEvent(QMouseEvent *event, const QPoint &mousePos);
+ virtual void mouseReleaseEvent(QMouseEvent *event, const QPoint &mousePos);
+ virtual void mouseMoveEvent(QMouseEvent *event, const QPoint &mousePos);
+#if QT_CONFIG(wheelevent)
+ virtual void wheelEvent(QWheelEvent *event);
+#endif
+
+ InputView inputView() const;
+ void setInputView(InputView inputView);
+
+ QPoint inputPosition() const;
+ void setInputPosition(const QPoint &position);
+
+ Q3DScene *scene() const;
+ void setScene(Q3DScene *scene);
+
+Q_SIGNALS:
+ void positionChanged(const QPoint &position);
+ void inputViewChanged(QAbstract3DInputHandler::InputView view);
+ void sceneChanged(Q3DScene *scene);
+
+protected:
+ void setPrevDistance(int distance);
+ int prevDistance() const;
+ void setPreviousInputPos(const QPoint &position);
+ QPoint previousInputPos() const;
+
+private:
+ Q_DISABLE_COPY(QAbstract3DInputHandler)
+
+ QScopedPointer<QAbstract3DInputHandlerPrivate> d_ptr;
+
+ friend class Abstract3DController;
+ friend class QTouch3DInputHandlerPrivate;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/input/qabstract3dinputhandler_p.h b/src/graphs/input/qabstract3dinputhandler_p.h
new file mode 100644
index 0000000..1a52035
--- /dev/null
+++ b/src/graphs/input/qabstract3dinputhandler_p.h
@@ -0,0 +1,56 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QtGraphs API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#ifndef QABSTRACT3DINPUTHANDLER_P_H
+#define QABSTRACT3DINPUTHANDLER_P_H
+
+#include "graphsglobal_p.h"
+#include "qabstract3dinputhandler.h"
+
+QT_BEGIN_NAMESPACE
+
+class QAbstract3DInputHandler;
+class Q3DScene;
+
+class QAbstract3DInputHandlerPrivate
+{
+public:
+ QAbstract3DInputHandlerPrivate(QAbstract3DInputHandler *q);
+ ~QAbstract3DInputHandlerPrivate();
+
+public:
+ enum InputState {
+ InputStateNone = 0,
+ InputStateSelecting,
+ InputStateRotating,
+ InputStatePinching
+ };
+
+ QAbstract3DInputHandler *q_ptr;
+ int m_prevDistance;
+ QPoint m_previousInputPos;
+
+private:
+ QAbstract3DInputHandler::InputView m_inputView;
+ QPoint m_inputPosition;
+
+ Q3DScene *m_scene;
+ bool m_isDefaultHandler;
+
+ friend class QAbstract3DInputHandler;
+ friend class Abstract3DController;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/input/qtouch3dinputhandler.cpp b/src/graphs/input/qtouch3dinputhandler.cpp
new file mode 100644
index 0000000..2fe4752
--- /dev/null
+++ b/src/graphs/input/qtouch3dinputhandler.cpp
@@ -0,0 +1,253 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qtouch3dinputhandler_p.h"
+#include <QtCore/QTimer>
+#include <QtCore/qmath.h>
+
+QT_BEGIN_NAMESPACE
+
+static const float maxTapAndHoldJitter = 20.0f;
+static const int maxPinchJitter = 10;
+#if defined (Q_OS_ANDROID) || defined(Q_OS_IOS)
+static const int maxSelectionJitter = 10;
+#else
+static const int maxSelectionJitter = 5;
+#endif
+static const int tapAndHoldTime = 250;
+static const float rotationSpeed = 200.0f;
+static const float touchZoomDrift = 0.02f;
+
+/*!
+ * \class QTouch3DInputHandler
+ * \inmodule QtGraphs
+ * \brief Basic touch display based input handler.
+ *
+ * QTouch3DInputHandler is the basic input handler for touch screen devices.
+ *
+ * Default touch input handler has the following functionalty:
+ * \table
+ * \header
+ * \li Gesture
+ * \li Action
+ * \row
+ * \li Touch-And-Move
+ * \li Rotate graph within limits set for Q3DCamera
+ * \row
+ * \li Tap
+ * \li Select the item tapped or remove selection if none.
+ * May open the secondary view depending on the
+ * \l {QAbstract3DGraph::selectionMode}{selection mode}.
+ * \row
+ * \li Tap-And-Hold
+ * \li Same as tap.
+ * \row
+ * \li Pinch
+ * \li Zoom in/out within the allowable zoom range set for Q3DCamera.
+ * \row
+ * \li Tap on the primary view when the secondary view is visible
+ * \li Closes the secondary view.
+ * \note Secondary view is available only for Q3DBars and Q3DSurface graphs.
+ * \endtable
+ *
+ * Rotation, zoom, and selection can each be individually disabled using
+ * corresponding Q3DInputHandler properties.
+ */
+
+/*!
+ * \qmltype TouchInputHandler3D
+ * \inqmlmodule QtGraphs
+ * \ingroup graphs_qml
+ * \instantiates QTouch3DInputHandler
+ * \inherits InputHandler3D
+ * \brief Basic touch display based input handler.
+ *
+ * TouchInputHandler3D is the basic input handler for touch screen devices.
+ *
+ * See QTouch3DInputHandler documentation for more details.
+ */
+
+/*!
+ * Constructs the basic touch display input handler. An optional \a parent parameter can be given
+ * and is then passed to QObject constructor.
+ */
+QTouch3DInputHandler::QTouch3DInputHandler(QObject *parent)
+ : Q3DInputHandler(parent),
+ d_ptr(new QTouch3DInputHandlerPrivate(this))
+{
+}
+
+/*!
+ * Destroys the input handler.
+ */
+QTouch3DInputHandler::~QTouch3DInputHandler()
+{
+}
+
+/*!
+ * Override this to change handling of touch events.
+ * Touch event is given in the \a event.
+ */
+void QTouch3DInputHandler::touchEvent(QTouchEvent *event)
+{
+ QList<QTouchEvent::TouchPoint> points;
+ points = event->points();
+
+ if (!scene()->isSlicingActive() && points.size() == 2) {
+ d_ptr->m_holdTimer->stop();
+ QPointF distance = points.at(0).position() - points.at(1).position();
+ QPoint midPoint = ((points.at(0).position() + points.at(1).position()) / 2.0).toPoint();
+ d_ptr->handlePinchZoom(distance.manhattanLength(), midPoint);
+ } else if (points.size() == 1) {
+ QPointF pointerPos = points.at(0).position();
+ if (event->type() == QEvent::TouchBegin) {
+ // Flush input state
+ d_ptr->m_inputState = QAbstract3DInputHandlerPrivate::InputStateNone;
+ if (scene()->isSlicingActive()) {
+ if (isSelectionEnabled()) {
+ if (scene()->isPointInPrimarySubView(pointerPos.toPoint()))
+ setInputView(InputViewOnPrimary);
+ else if (scene()->isPointInSecondarySubView(pointerPos.toPoint()))
+ setInputView(InputViewOnSecondary);
+ else
+ setInputView(InputViewNone);
+ }
+ } else {
+ // Handle possible tap-and-hold selection
+ if (isSelectionEnabled()) {
+ d_ptr->m_startHoldPos = pointerPos;
+ d_ptr->m_touchHoldPos = d_ptr->m_startHoldPos;
+ d_ptr->m_holdTimer->start();
+ setInputView(InputViewOnPrimary);
+ }
+ // Start rotating
+ if (isRotationEnabled()) {
+ d_ptr->m_inputState = QAbstract3DInputHandlerPrivate::InputStateRotating;
+ setInputPosition(pointerPos.toPoint());
+ setInputView(InputViewOnPrimary);
+ }
+ }
+ } else if (event->type() == QEvent::TouchEnd) {
+ setInputView(InputViewNone);
+ d_ptr->m_holdTimer->stop();
+ // Handle possible selection
+ if (!scene()->isSlicingActive()
+ && QAbstract3DInputHandlerPrivate::InputStatePinching
+ != d_ptr->m_inputState) {
+ d_ptr->handleSelection(pointerPos);
+ }
+ } else if (event->type() == QEvent::TouchUpdate) {
+ if (!scene()->isSlicingActive()) {
+ d_ptr->m_touchHoldPos = pointerPos;
+ // Handle rotation
+ d_ptr->handleRotation(pointerPos);
+ }
+ }
+ } else {
+ d_ptr->m_holdTimer->stop();
+ }
+}
+
+QTouch3DInputHandlerPrivate::QTouch3DInputHandlerPrivate(QTouch3DInputHandler *q)
+ : Q3DInputHandlerPrivate(q),
+ q_ptr(q),
+ m_holdTimer(0),
+ m_inputState(QAbstract3DInputHandlerPrivate::InputStateNone)
+{
+ m_holdTimer = new QTimer();
+ m_holdTimer->setSingleShot(true);
+ m_holdTimer->setInterval(tapAndHoldTime);
+ connect(m_holdTimer, &QTimer::timeout, this, &QTouch3DInputHandlerPrivate::handleTapAndHold);
+}
+
+QTouch3DInputHandlerPrivate::~QTouch3DInputHandlerPrivate()
+{
+ m_holdTimer->stop();
+ delete m_holdTimer;
+}
+
+void QTouch3DInputHandlerPrivate::handlePinchZoom(float distance, const QPoint &pos)
+{
+ if (q_ptr->isZoomEnabled()) {
+ int newDistance = distance;
+ int prevDist = q_ptr->prevDistance();
+ if (prevDist > 0 && qAbs(prevDist - newDistance) < maxPinchJitter)
+ return;
+ m_inputState = QAbstract3DInputHandlerPrivate::InputStatePinching;
+ Q3DCamera *camera = q_ptr->scene()->activeCamera();
+ int zoomLevel = int(camera->zoomLevel());
+ const int minZoomLevel = int(camera->minZoomLevel());
+ const int maxZoomLevel = int(camera->maxZoomLevel());
+ float zoomRate = qSqrt(qSqrt(zoomLevel));
+ if (newDistance > prevDist)
+ zoomLevel += zoomRate;
+ else
+ zoomLevel -= zoomRate;
+ zoomLevel = qBound(minZoomLevel, zoomLevel, maxZoomLevel);
+
+ if (q_ptr->isZoomAtTargetEnabled()) {
+ q_ptr->scene()->setGraphPositionQuery(pos);
+ m_zoomAtTargetPending = true;
+ // If zoom at target is enabled, we don't want to zoom yet, as that causes
+ // jitter. Instead, we zoom next frame, when we apply the camera position.
+ m_requestedZoomLevel = zoomLevel;
+ m_driftMultiplier = touchZoomDrift;
+ } else {
+ camera->setZoomLevel(zoomLevel);
+ }
+
+ q_ptr->setPrevDistance(newDistance);
+ }
+}
+
+void QTouch3DInputHandlerPrivate::handleTapAndHold()
+{
+ if (q_ptr->isSelectionEnabled()) {
+ QPointF distance = m_startHoldPos - m_touchHoldPos;
+ if (distance.manhattanLength() < maxTapAndHoldJitter) {
+ q_ptr->setInputPosition(m_touchHoldPos.toPoint());
+ q_ptr->scene()->setSelectionQueryPosition(m_touchHoldPos.toPoint());
+ m_inputState = QAbstract3DInputHandlerPrivate::InputStateSelecting;
+ }
+ }
+}
+
+void QTouch3DInputHandlerPrivate::handleSelection(const QPointF &position)
+{
+ if (q_ptr->isSelectionEnabled()) {
+ QPointF distance = m_startHoldPos - position;
+ if (distance.manhattanLength() < maxSelectionJitter) {
+ m_inputState = QAbstract3DInputHandlerPrivate::InputStateSelecting;
+ q_ptr->scene()->setSelectionQueryPosition(position.toPoint());
+ } else {
+ m_inputState = QAbstract3DInputHandlerPrivate::InputStateNone;
+ q_ptr->setInputView(QAbstract3DInputHandler::InputViewNone);
+ }
+ q_ptr->setPreviousInputPos(position.toPoint());
+ }
+}
+
+void QTouch3DInputHandlerPrivate::handleRotation(const QPointF &position)
+{
+ if (q_ptr->isRotationEnabled()
+ && QAbstract3DInputHandlerPrivate::InputStateRotating == m_inputState) {
+ Q3DScene *scene = q_ptr->scene();
+ Q3DCamera *camera = scene->activeCamera();
+ float xRotation = camera->xRotation();
+ float yRotation = camera->yRotation();
+ QPointF inputPos = q_ptr->inputPosition();
+ float mouseMoveX = float(inputPos.x() - position.x())
+ / (scene->viewport().width() / rotationSpeed);
+ float mouseMoveY = float(inputPos.y() - position.y())
+ / (scene->viewport().height() / rotationSpeed);
+ xRotation -= mouseMoveX;
+ yRotation -= mouseMoveY;
+ camera->setXRotation(xRotation);
+ camera->setYRotation(yRotation);
+
+ q_ptr->setPreviousInputPos(inputPos.toPoint());
+ q_ptr->setInputPosition(position.toPoint());
+ }
+}
+
+QT_END_NAMESPACE
diff --git a/src/graphs/input/qtouch3dinputhandler.h b/src/graphs/input/qtouch3dinputhandler.h
new file mode 100644
index 0000000..a9ab1de
--- /dev/null
+++ b/src/graphs/input/qtouch3dinputhandler.h
@@ -0,0 +1,32 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QTOUCH3DINPUTHANDLER_H
+#define QTOUCH3DINPUTHANDLER_H
+
+#include <QtGraphs/q3dinputhandler.h>
+
+QT_BEGIN_NAMESPACE
+
+class QTouch3DInputHandlerPrivate;
+
+class Q_GRAPHS_EXPORT QTouch3DInputHandler : public Q3DInputHandler
+{
+ Q_OBJECT
+
+public:
+ explicit QTouch3DInputHandler(QObject *parent = nullptr);
+ virtual ~QTouch3DInputHandler();
+
+ // Input event listeners
+ void touchEvent(QTouchEvent *event) override;
+
+private:
+ Q_DISABLE_COPY(QTouch3DInputHandler)
+
+ QScopedPointer<QTouch3DInputHandlerPrivate> d_ptr;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/input/qtouch3dinputhandler_p.h b/src/graphs/input/qtouch3dinputhandler_p.h
new file mode 100644
index 0000000..c8d0c41
--- /dev/null
+++ b/src/graphs/input/qtouch3dinputhandler_p.h
@@ -0,0 +1,50 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QTOUCH3DINPUTHANDLER_P_H
+#define QTOUCH3DINPUTHANDLER_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QtGraphs API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#include "q3dinputhandler_p.h"
+#include "qtouch3dinputhandler.h"
+
+QT_FORWARD_DECLARE_CLASS(QTimer)
+
+QT_BEGIN_NAMESPACE
+
+class QAbstract3DInputHandler;
+
+class QTouch3DInputHandlerPrivate : public Q3DInputHandlerPrivate
+{
+ Q_OBJECT
+
+public:
+ QTouch3DInputHandlerPrivate(QTouch3DInputHandler *q);
+ ~QTouch3DInputHandlerPrivate();
+
+ void handlePinchZoom(float distance, const QPoint &pos);
+ void handleTapAndHold();
+ void handleSelection(const QPointF &position);
+ void handleRotation(const QPointF &position);
+
+private:
+ QTouch3DInputHandler *q_ptr;
+public:
+ QTimer *m_holdTimer;
+ QAbstract3DInputHandlerPrivate::InputState m_inputState;
+ QPointF m_startHoldPos;
+ QPointF m_touchHoldPos;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/qml/colorgradient.cpp b/src/graphs/qml/colorgradient.cpp
new file mode 100644
index 0000000..c62c1a1
--- /dev/null
+++ b/src/graphs/qml/colorgradient.cpp
@@ -0,0 +1,62 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "colorgradient_p.h"
+
+QT_BEGIN_NAMESPACE
+
+ColorGradientStop::ColorGradientStop(QObject *parent)
+ : QObject(parent)
+{
+}
+
+qreal ColorGradientStop::position() const
+{
+ return m_position;
+}
+
+void ColorGradientStop::setPosition(qreal position)
+{
+ m_position = position;
+ updateGradient();
+ emit positionChanged(position);
+}
+
+QColor ColorGradientStop::color() const
+{
+ return m_color;
+}
+
+void ColorGradientStop::setColor(const QColor &color)
+{
+ m_color = color;
+ updateGradient();
+ emit colorChanged(color);
+}
+
+void ColorGradientStop::updateGradient()
+{
+ if (ColorGradient *grad = qobject_cast<ColorGradient*>(parent()))
+ grad->doUpdate();
+}
+
+ColorGradient::ColorGradient(QObject *parent)
+: QObject(parent)
+{
+}
+
+ColorGradient::~ColorGradient()
+{
+}
+
+QQmlListProperty<ColorGradientStop> ColorGradient::stops()
+{
+ return QQmlListProperty<ColorGradientStop>(this, &m_stops);
+}
+
+void ColorGradient::doUpdate()
+{
+ emit updated();
+}
+
+QT_END_NAMESPACE
diff --git a/src/graphs/qml/colorgradient_p.h b/src/graphs/qml/colorgradient_p.h
new file mode 100644
index 0000000..90e8478
--- /dev/null
+++ b/src/graphs/qml/colorgradient_p.h
@@ -0,0 +1,79 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QtGraphs API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#ifndef COLORGRADIENT_P_H
+#define COLORGRADIENT_P_H
+
+#include <private/graphsglobal_p.h>
+#include <QtGui/QColor>
+#include <QtQml/qqml.h>
+
+QT_BEGIN_NAMESPACE
+
+class ColorGradientStop : public QObject
+{
+ Q_OBJECT
+
+ Q_PROPERTY(qreal position READ position WRITE setPosition NOTIFY positionChanged)
+ Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)
+
+ QML_ELEMENT
+ QML_ADDED_IN_VERSION(6, 6)
+
+public:
+ ColorGradientStop(QObject *parent = 0);
+
+ qreal position() const;
+ void setPosition(qreal position);
+
+ QColor color() const;
+ void setColor(const QColor &color);
+
+Q_SIGNALS:
+ void positionChanged(qreal position);
+ void colorChanged(const QColor &color);
+
+private:
+ void updateGradient();
+
+private:
+ qreal m_position;
+ QColor m_color;
+};
+
+class ColorGradient : public QObject
+{
+ Q_OBJECT
+
+ Q_PROPERTY(QQmlListProperty<ColorGradientStop> stops READ stops CONSTANT)
+ Q_CLASSINFO("DefaultProperty", "stops")
+
+ QML_ELEMENT
+ QML_ADDED_IN_VERSION(6, 6)
+
+public:
+ ColorGradient(QObject *parent = 0);
+ ~ColorGradient();
+
+ QQmlListProperty<ColorGradientStop> stops();
+
+ void doUpdate();
+ QList<ColorGradientStop *> m_stops;
+
+Q_SIGNALS:
+ void updated();
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/qml/declarativecolor.cpp b/src/graphs/qml/declarativecolor.cpp
new file mode 100644
index 0000000..54ceb2e
--- /dev/null
+++ b/src/graphs/qml/declarativecolor.cpp
@@ -0,0 +1,26 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "declarativecolor_p.h"
+
+QT_BEGIN_NAMESPACE
+
+DeclarativeColor::DeclarativeColor(QObject *parent)
+ : QObject(parent)
+{
+}
+
+void DeclarativeColor::setColor(const QColor &color)
+{
+ if (m_color != color) {
+ m_color = color;
+ emit colorChanged(color);
+ }
+}
+
+QColor DeclarativeColor::color() const
+{
+ return m_color;
+}
+
+QT_END_NAMESPACE
diff --git a/src/graphs/qml/declarativecolor_p.h b/src/graphs/qml/declarativecolor_p.h
new file mode 100644
index 0000000..8c8cb6b
--- /dev/null
+++ b/src/graphs/qml/declarativecolor_p.h
@@ -0,0 +1,46 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QtGraphs API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#ifndef DECLARATIVECOLOR_P_H
+#define DECLARATIVECOLOR_P_H
+
+#include <private/graphsglobal_p.h>
+#include <QtGui/QColor>
+#include <QtQml/qqml.h>
+
+QT_BEGIN_NAMESPACE
+
+class DeclarativeColor : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)
+
+ QML_NAMED_ELEMENT(ThemeColor)
+ QML_ADDED_IN_VERSION(6, 6)
+
+public:
+ DeclarativeColor(QObject *parent = 0);
+
+ void setColor(const QColor &color);
+ QColor color() const;
+
+Q_SIGNALS:
+ void colorChanged(const QColor &color);
+
+private:
+ QColor m_color;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/qml/declarativescene.cpp b/src/graphs/qml/declarativescene.cpp
new file mode 100644
index 0000000..12ebb8a
--- /dev/null
+++ b/src/graphs/qml/declarativescene.cpp
@@ -0,0 +1,34 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "declarativescene_p.h"
+
+QT_BEGIN_NAMESPACE
+
+Declarative3DScene::Declarative3DScene(QObject *parent)
+ : Q3DScene(parent)
+{
+ QObject::connect(this, &Q3DScene::selectionQueryPositionChanged, this,
+ &Declarative3DScene::selectionQueryPositionChanged);
+}
+
+Declarative3DScene::~Declarative3DScene()
+{
+}
+
+void Declarative3DScene::setSelectionQueryPosition(const QPointF &point)
+{
+ Q3DScene::setSelectionQueryPosition(point.toPoint());
+}
+
+QPointF Declarative3DScene::selectionQueryPosition() const
+{
+ return QPointF(Q3DScene::selectionQueryPosition());
+}
+
+QPoint Declarative3DScene::invalidSelectionPoint() const
+{
+ return Q3DScene::invalidSelectionPoint();
+}
+
+QT_END_NAMESPACE
diff --git a/src/graphs/qml/declarativescene_p.h b/src/graphs/qml/declarativescene_p.h
new file mode 100644
index 0000000..a715067
--- /dev/null
+++ b/src/graphs/qml/declarativescene_p.h
@@ -0,0 +1,50 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QtGraphs API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#ifndef DECLARATIVESCENE_P_H
+#define DECLARATIVESCENE_P_H
+
+#include <QtQml/qqml.h>
+#include <private/graphsglobal_p.h>
+#include <QtGraphs/q3dscene.h>
+
+QT_BEGIN_NAMESPACE
+
+class Declarative3DScene : public Q3DScene
+{
+ Q_OBJECT
+ // This property is overloaded to use QPointF instead of QPoint to work around qml bug
+ // where Qt.point(0, 0) can't be assigned due to error "Cannot assign QPointF to QPoint".
+ Q_PROPERTY(QPointF selectionQueryPosition READ selectionQueryPosition WRITE setSelectionQueryPosition NOTIFY selectionQueryPositionChanged)
+ // This is static method in parent class, overload as constant property for qml.
+ Q_PROPERTY(QPoint invalidSelectionPoint READ invalidSelectionPoint CONSTANT)
+
+ QML_NAMED_ELEMENT(Scene3D)
+ QML_ADDED_IN_VERSION(6, 6)
+ QML_UNCREATABLE("Trying to create uncreatable: Scene3D.")
+
+public:
+ Declarative3DScene(QObject *parent = 0);
+ virtual ~Declarative3DScene();
+
+ void setSelectionQueryPosition(const QPointF &point);
+ QPointF selectionQueryPosition() const;
+ QPoint invalidSelectionPoint() const;
+
+Q_SIGNALS:
+ void selectionQueryPositionChanged(const QPointF &position);
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/qml/declarativeseries.cpp b/src/graphs/qml/declarativeseries.cpp
new file mode 100644
index 0000000..32fa102
--- /dev/null
+++ b/src/graphs/qml/declarativeseries.cpp
@@ -0,0 +1,435 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "declarativeseries_p.h"
+#include <QtCore/QMetaMethod>
+
+QT_BEGIN_NAMESPACE
+
+static void setSeriesGradient(QAbstract3DSeries *series, const ColorGradient &gradient,
+ GradientType type)
+{
+ QLinearGradient newGradient;
+ QGradientStops stops;
+ QList<ColorGradientStop *> qmlstops = gradient.m_stops;
+
+ // Get sorted gradient stops
+ for (int i = 0; i < qmlstops.size(); i++) {
+ int j = 0;
+ while (j < stops.size() && stops.at(j).first < qmlstops[i]->position())
+ j++;
+ stops.insert(j, QGradientStop(qmlstops.at(i)->position(), qmlstops.at(i)->color()));
+ }
+
+ newGradient.setStops(stops);
+ switch (type) {
+ case GradientTypeBase:
+ series->setBaseGradient(newGradient);
+ break;
+ case GradientTypeSingle:
+ series->setSingleHighlightGradient(newGradient);
+ break;
+ case GradientTypeMulti:
+ series->setMultiHighlightGradient(newGradient);
+ break;
+ default: // Never goes here
+ break;
+ }
+}
+
+static void connectSeriesGradient(QAbstract3DSeries *series, ColorGradient *newGradient,
+ GradientType type, ColorGradient **memberGradient)
+{
+ // connect new / disconnect old
+ if (newGradient != *memberGradient) {
+ if (*memberGradient)
+ QObject::disconnect(*memberGradient, 0, series, 0);
+
+ *memberGradient = newGradient;
+
+ int updatedIndex = newGradient->metaObject()->indexOfSignal("updated()");
+ QMetaMethod updateFunction = newGradient->metaObject()->method(updatedIndex);
+ int handleIndex = -1;
+ switch (type) {
+ case GradientTypeBase:
+ handleIndex = series->metaObject()->indexOfSlot("handleBaseGradientUpdate()");
+ break;
+ case GradientTypeSingle:
+ handleIndex = series->metaObject()->indexOfSlot("handleSingleHighlightGradientUpdate()");
+ break;
+ case GradientTypeMulti:
+ handleIndex = series->metaObject()->indexOfSlot("handleMultiHighlightGradientUpdate()");
+ break;
+ default: // Never goes here
+ break;
+ }
+ QMetaMethod handleFunction = series->metaObject()->method(handleIndex);
+
+ if (*memberGradient)
+ QObject::connect(*memberGradient, updateFunction, series, handleFunction);
+ }
+
+ if (*memberGradient)
+ setSeriesGradient(series, **memberGradient, type);
+}
+
+DeclarativeBar3DSeries::DeclarativeBar3DSeries(QObject *parent)
+ : QBar3DSeries(parent),
+ m_baseGradient(0),
+ m_singleHighlightGradient(0),
+ m_multiHighlightGradient(0),
+ m_dummyColors(false)
+{
+ QObject::connect(this, &QBar3DSeries::selectedBarChanged, this,
+ &DeclarativeBar3DSeries::selectedBarChanged);
+}
+
+DeclarativeBar3DSeries::~DeclarativeBar3DSeries()
+{
+}
+
+QQmlListProperty<QObject> DeclarativeBar3DSeries::seriesChildren()
+{
+ return QQmlListProperty<QObject>(this, this, &DeclarativeBar3DSeries::appendSeriesChildren
+ , 0, 0, 0);
+}
+
+void DeclarativeBar3DSeries::appendSeriesChildren(QQmlListProperty<QObject> *list, QObject *element)
+{
+ QBarDataProxy *proxy = qobject_cast<QBarDataProxy *>(element);
+ if (proxy)
+ reinterpret_cast<DeclarativeBar3DSeries *>(list->data)->setDataProxy(proxy);
+}
+
+void DeclarativeBar3DSeries::setSelectedBar(const QPointF &position)
+{
+ QBar3DSeries::setSelectedBar(position.toPoint());
+}
+
+QPointF DeclarativeBar3DSeries::selectedBar() const
+{
+ return QPointF(QBar3DSeries::selectedBar());
+}
+
+QPointF DeclarativeBar3DSeries::invalidSelectionPosition() const
+{
+ return QPointF(QBar3DSeries::invalidSelectionPosition());
+}
+
+void DeclarativeBar3DSeries::setBaseGradient(ColorGradient *gradient)
+{
+ connectSeriesGradient(this, gradient, GradientTypeBase, &m_baseGradient);
+}
+
+ColorGradient *DeclarativeBar3DSeries::baseGradient() const
+{
+ return m_baseGradient;
+}
+
+void DeclarativeBar3DSeries::setSingleHighlightGradient(ColorGradient *gradient)
+{
+ connectSeriesGradient(this, gradient, GradientTypeSingle, &m_singleHighlightGradient);
+}
+
+ColorGradient *DeclarativeBar3DSeries::singleHighlightGradient() const
+{
+ return m_singleHighlightGradient;
+}
+
+void DeclarativeBar3DSeries::setMultiHighlightGradient(ColorGradient *gradient)
+{
+ connectSeriesGradient(this, gradient, GradientTypeMulti, &m_multiHighlightGradient);
+}
+
+ColorGradient *DeclarativeBar3DSeries::multiHighlightGradient() const
+{
+ return m_multiHighlightGradient;
+}
+
+QQmlListProperty<DeclarativeColor> DeclarativeBar3DSeries::rowColors()
+{
+ return QQmlListProperty<DeclarativeColor>(this, this,
+ &DeclarativeBar3DSeries::appendRowColorsFunc,
+ &DeclarativeBar3DSeries::countRowColorsFunc,
+ &DeclarativeBar3DSeries::atRowColorsFunc,
+ &DeclarativeBar3DSeries::clearRowColorsFunc);
+}
+
+void DeclarativeBar3DSeries::appendRowColorsFunc(QQmlListProperty<DeclarativeColor> *list,
+ DeclarativeColor *color)
+{
+ reinterpret_cast<DeclarativeBar3DSeries *>(list->data)->addColor(color);
+}
+
+qsizetype DeclarativeBar3DSeries::countRowColorsFunc(QQmlListProperty<DeclarativeColor> *list)
+{
+ return reinterpret_cast<DeclarativeBar3DSeries *>(list->data)->colorList().size();
+}
+
+DeclarativeColor *DeclarativeBar3DSeries::atRowColorsFunc(QQmlListProperty<DeclarativeColor> *list,
+ qsizetype index)
+{
+ return reinterpret_cast<DeclarativeBar3DSeries *>(list->data)->colorList().at(index);
+}
+
+void DeclarativeBar3DSeries::clearRowColorsFunc(QQmlListProperty<DeclarativeColor> *list)
+{
+ reinterpret_cast<DeclarativeBar3DSeries *>(list->data)->clearColors();
+}
+
+void DeclarativeBar3DSeries::handleBaseGradientUpdate()
+{
+ if (m_baseGradient)
+ setSeriesGradient(this, *m_baseGradient, GradientTypeBase);
+}
+
+void DeclarativeBar3DSeries::handleSingleHighlightGradientUpdate()
+{
+ if (m_singleHighlightGradient)
+ setSeriesGradient(this, *m_singleHighlightGradient, GradientTypeSingle);
+}
+
+void DeclarativeBar3DSeries::handleMultiHighlightGradientUpdate()
+{
+ if (m_multiHighlightGradient)
+ setSeriesGradient(this, *m_multiHighlightGradient, GradientTypeMulti);
+}
+
+void DeclarativeBar3DSeries::handleRowColorUpdate()
+{
+ int colorCount = m_rowColors.size();
+ int changed = 0;
+
+ DeclarativeColor *color = qobject_cast<DeclarativeColor*>(QObject::sender());
+ for (int i = 0; i < colorCount; i++) {
+ if (color == m_rowColors.at(i)) {
+ changed = i;
+ break;
+ }
+ }
+ QList<QColor> list = QBar3DSeries::rowColors();
+ list[changed] = m_rowColors.at(changed)->color();
+ QBar3DSeries::setRowColors(list);
+}
+
+void DeclarativeBar3DSeries::addColor(DeclarativeColor *color)
+{
+ if (!color) {
+ qWarning("Color is invalid, use ThemeColor");
+ return;
+ }
+ clearDummyColors();
+ m_rowColors.append(color);
+ connect(color, &DeclarativeColor::colorChanged, this,
+ &DeclarativeBar3DSeries::handleRowColorUpdate);
+ QList<QColor> list = QBar3DSeries::rowColors();
+ list.append(color->color());
+ QBar3DSeries::setRowColors(list);
+}
+
+QList<DeclarativeColor *> DeclarativeBar3DSeries::colorList()
+{
+ if (m_rowColors.isEmpty()) {
+ m_dummyColors = true;
+ const QList<QColor> list = QBar3DSeries::rowColors();
+ for (const QColor &item : list) {
+ DeclarativeColor *color = new DeclarativeColor(this);
+ color->setColor(item);
+ m_rowColors.append(color);
+ connect(color, &DeclarativeColor::colorChanged, this,
+ &DeclarativeBar3DSeries::handleRowColorUpdate);
+ }
+ }
+ return m_rowColors;
+}
+
+void DeclarativeBar3DSeries::clearColors()
+{
+ clearDummyColors();
+ for (const auto color : std::as_const(m_rowColors))
+ disconnect(color, 0, this, 0);
+
+ m_rowColors.clear();
+ QBar3DSeries::setRowColors(QList<QColor>());
+}
+
+void DeclarativeBar3DSeries::clearDummyColors()
+{
+ if (m_dummyColors) {
+ qDeleteAll(m_rowColors);
+ m_rowColors.clear();
+ m_dummyColors = false;
+ }
+}
+
+DeclarativeScatter3DSeries::DeclarativeScatter3DSeries(QObject *parent)
+ : QScatter3DSeries(parent),
+ m_baseGradient(0),
+ m_singleHighlightGradient(0),
+ m_multiHighlightGradient(0)
+{
+}
+
+DeclarativeScatter3DSeries::~DeclarativeScatter3DSeries()
+{
+}
+
+QQmlListProperty<QObject> DeclarativeScatter3DSeries::seriesChildren()
+{
+ return QQmlListProperty<QObject>(this, this, &DeclarativeScatter3DSeries::appendSeriesChildren
+ , 0, 0, 0);
+}
+
+void DeclarativeScatter3DSeries::appendSeriesChildren(QQmlListProperty<QObject> *list,
+ QObject *element)
+{
+ QScatterDataProxy *proxy = qobject_cast<QScatterDataProxy *>(element);
+ if (proxy)
+ reinterpret_cast<DeclarativeScatter3DSeries *>(list->data)->setDataProxy(proxy);
+}
+
+void DeclarativeScatter3DSeries::setBaseGradient(ColorGradient *gradient)
+{
+ connectSeriesGradient(this, gradient, GradientTypeBase, &m_baseGradient);
+}
+
+ColorGradient *DeclarativeScatter3DSeries::baseGradient() const
+{
+ return m_baseGradient;
+}
+
+void DeclarativeScatter3DSeries::setSingleHighlightGradient(ColorGradient *gradient)
+{
+ connectSeriesGradient(this, gradient, GradientTypeSingle, &m_singleHighlightGradient);
+}
+
+ColorGradient *DeclarativeScatter3DSeries::singleHighlightGradient() const
+{
+ return m_singleHighlightGradient;
+}
+
+void DeclarativeScatter3DSeries::setMultiHighlightGradient(ColorGradient *gradient)
+{
+ connectSeriesGradient(this, gradient, GradientTypeMulti, &m_multiHighlightGradient);
+}
+
+ColorGradient *DeclarativeScatter3DSeries::multiHighlightGradient() const
+{
+ return m_multiHighlightGradient;
+}
+
+int DeclarativeScatter3DSeries::invalidSelectionIndex() const
+{
+ return QScatter3DSeries::invalidSelectionIndex();
+}
+
+void DeclarativeScatter3DSeries::handleBaseGradientUpdate()
+{
+ if (m_baseGradient)
+ setSeriesGradient(this, *m_baseGradient, GradientTypeBase);
+}
+
+void DeclarativeScatter3DSeries::handleSingleHighlightGradientUpdate()
+{
+ if (m_singleHighlightGradient)
+ setSeriesGradient(this, *m_singleHighlightGradient, GradientTypeSingle);
+}
+
+void DeclarativeScatter3DSeries::handleMultiHighlightGradientUpdate()
+{
+ if (m_multiHighlightGradient)
+ setSeriesGradient(this, *m_multiHighlightGradient, GradientTypeMulti);
+}
+
+DeclarativeSurface3DSeries::DeclarativeSurface3DSeries(QObject *parent)
+ : QSurface3DSeries(parent),
+ m_baseGradient(0),
+ m_singleHighlightGradient(0),
+ m_multiHighlightGradient(0)
+{
+ QObject::connect(this, &QSurface3DSeries::selectedPointChanged, this,
+ &DeclarativeSurface3DSeries::selectedPointChanged);
+}
+
+DeclarativeSurface3DSeries::~DeclarativeSurface3DSeries()
+{
+}
+
+void DeclarativeSurface3DSeries::setSelectedPoint(const QPointF &position)
+{
+ QSurface3DSeries::setSelectedPoint(position.toPoint());
+}
+
+QPointF DeclarativeSurface3DSeries::selectedPoint() const
+{
+ return QPointF(QSurface3DSeries::selectedPoint());
+}
+
+QPointF DeclarativeSurface3DSeries::invalidSelectionPosition() const
+{
+ return QPointF(QSurface3DSeries::invalidSelectionPosition());
+}
+
+QQmlListProperty<QObject> DeclarativeSurface3DSeries::seriesChildren()
+{
+ return QQmlListProperty<QObject>(this, this, &DeclarativeSurface3DSeries::appendSeriesChildren
+ , 0, 0, 0);
+}
+
+void DeclarativeSurface3DSeries::appendSeriesChildren(QQmlListProperty<QObject> *list,
+ QObject *element)
+{
+ QSurfaceDataProxy *proxy = qobject_cast<QSurfaceDataProxy *>(element);
+ if (proxy)
+ reinterpret_cast<DeclarativeSurface3DSeries *>(list->data)->setDataProxy(proxy);
+}
+
+void DeclarativeSurface3DSeries::setBaseGradient(ColorGradient *gradient)
+{
+ connectSeriesGradient(this, gradient, GradientTypeBase, &m_baseGradient);
+}
+
+ColorGradient *DeclarativeSurface3DSeries::baseGradient() const
+{
+ return m_baseGradient;
+}
+
+void DeclarativeSurface3DSeries::setSingleHighlightGradient(ColorGradient *gradient)
+{
+ connectSeriesGradient(this, gradient, GradientTypeSingle, &m_singleHighlightGradient);
+}
+
+ColorGradient *DeclarativeSurface3DSeries::singleHighlightGradient() const
+{
+ return m_singleHighlightGradient;
+}
+
+void DeclarativeSurface3DSeries::setMultiHighlightGradient(ColorGradient *gradient)
+{
+ connectSeriesGradient(this, gradient, GradientTypeMulti, &m_multiHighlightGradient);
+}
+
+ColorGradient *DeclarativeSurface3DSeries::multiHighlightGradient() const
+{
+ return m_multiHighlightGradient;
+}
+
+void DeclarativeSurface3DSeries::handleBaseGradientUpdate()
+{
+ if (m_baseGradient)
+ setSeriesGradient(this, *m_baseGradient, GradientTypeBase);
+}
+
+void DeclarativeSurface3DSeries::handleSingleHighlightGradientUpdate()
+{
+ if (m_singleHighlightGradient)
+ setSeriesGradient(this, *m_singleHighlightGradient, GradientTypeSingle);
+}
+
+void DeclarativeSurface3DSeries::handleMultiHighlightGradientUpdate()
+{
+ if (m_multiHighlightGradient)
+ setSeriesGradient(this, *m_multiHighlightGradient, GradientTypeMulti);
+}
+
+QT_END_NAMESPACE
diff --git a/src/graphs/qml/declarativeseries_p.h b/src/graphs/qml/declarativeseries_p.h
new file mode 100644
index 0000000..6b6031b
--- /dev/null
+++ b/src/graphs/qml/declarativeseries_p.h
@@ -0,0 +1,204 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QtGraphs API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#ifndef DECLARATIVESERIES_P_H
+#define DECLARATIVESERIES_P_H
+
+#include "qbar3dseries.h"
+#include "qscatter3dseries.h"
+#include "qsurface3dseries.h"
+#include "colorgradient_p.h"
+#include "declarativecolor_p.h"
+
+#include <private/graphsglobal_p.h>
+#include <QtQml/qqml.h>
+
+QT_BEGIN_NAMESPACE
+
+enum GradientType {
+ GradientTypeBase,
+ GradientTypeSingle,
+ GradientTypeMulti
+};
+
+class DeclarativeBar3DSeries : public QBar3DSeries
+{
+ Q_OBJECT
+ Q_PROPERTY(QQmlListProperty<QObject> seriesChildren READ seriesChildren CONSTANT)
+ // This property is overloaded to use QPointF instead of QPoint to work around qml bug
+ // where Qt.point(0, 0) can't be assigned due to error "Cannot assign QPointF to QPoint".
+ Q_PROPERTY(QPointF selectedBar READ selectedBar WRITE setSelectedBar NOTIFY selectedBarChanged)
+ // This is static method in parent class, overload as constant property for qml.
+ Q_PROPERTY(QPointF invalidSelectionPosition READ invalidSelectionPosition CONSTANT)
+ Q_PROPERTY(ColorGradient *baseGradient READ baseGradient WRITE setBaseGradient NOTIFY baseGradientChanged)
+ Q_PROPERTY(ColorGradient *singleHighlightGradient READ singleHighlightGradient WRITE setSingleHighlightGradient NOTIFY singleHighlightGradientChanged)
+ Q_PROPERTY(ColorGradient *multiHighlightGradient READ multiHighlightGradient WRITE setMultiHighlightGradient NOTIFY multiHighlightGradientChanged)
+ Q_PROPERTY(QQmlListProperty<DeclarativeColor> rowColors READ rowColors CONSTANT)
+ Q_CLASSINFO("DefaultProperty", "seriesChildren")
+
+ QML_NAMED_ELEMENT(Bar3DSeries)
+ QML_ADDED_IN_VERSION(6, 6)
+
+public:
+ DeclarativeBar3DSeries(QObject *parent = 0);
+ virtual ~DeclarativeBar3DSeries();
+
+ QQmlListProperty<QObject> seriesChildren();
+ static void appendSeriesChildren(QQmlListProperty<QObject> *list, QObject *element);
+
+ void setSelectedBar(const QPointF &position);
+ QPointF selectedBar() const;
+ QPointF invalidSelectionPosition() const;
+
+ void setBaseGradient(ColorGradient *gradient);
+ ColorGradient *baseGradient() const;
+ void setSingleHighlightGradient(ColorGradient *gradient);
+ ColorGradient *singleHighlightGradient() const;
+ void setMultiHighlightGradient(ColorGradient *gradient);
+ ColorGradient *multiHighlightGradient() const;
+
+ QQmlListProperty<DeclarativeColor> rowColors();
+ static void appendRowColorsFunc(QQmlListProperty<DeclarativeColor> *list,
+ DeclarativeColor *color);
+ static qsizetype countRowColorsFunc(QQmlListProperty<DeclarativeColor> *list);
+ static DeclarativeColor *atRowColorsFunc(QQmlListProperty<DeclarativeColor> *list,
+ qsizetype index);
+ static void clearRowColorsFunc(QQmlListProperty<DeclarativeColor> *list);
+
+public Q_SLOTS:
+ void handleBaseGradientUpdate();
+ void handleSingleHighlightGradientUpdate();
+ void handleMultiHighlightGradientUpdate();
+ void handleRowColorUpdate();
+
+Q_SIGNALS:
+ void selectedBarChanged(const QPointF &position);
+ void baseGradientChanged(ColorGradient *gradient);
+ void singleHighlightGradientChanged(ColorGradient *gradient);
+ void multiHighlightGradientChanged(ColorGradient *gradient);
+
+private:
+ ColorGradient *m_baseGradient; // Not owned
+ ColorGradient *m_singleHighlightGradient; // Not owned
+ ColorGradient *m_multiHighlightGradient; // Not owned
+
+ QList<DeclarativeColor *> m_rowColors;
+ bool m_dummyColors;
+
+ void addColor(DeclarativeColor *color);
+ QList<DeclarativeColor *> colorList();
+ void clearColors();
+ void clearDummyColors();
+};
+
+class DeclarativeScatter3DSeries : public QScatter3DSeries
+{
+ Q_OBJECT
+ Q_PROPERTY(QQmlListProperty<QObject> seriesChildren READ seriesChildren CONSTANT)
+ Q_PROPERTY(ColorGradient *baseGradient READ baseGradient WRITE setBaseGradient NOTIFY baseGradientChanged)
+ Q_PROPERTY(ColorGradient *singleHighlightGradient READ singleHighlightGradient WRITE setSingleHighlightGradient NOTIFY singleHighlightGradientChanged)
+ Q_PROPERTY(ColorGradient *multiHighlightGradient READ multiHighlightGradient WRITE setMultiHighlightGradient NOTIFY multiHighlightGradientChanged)
+ // This is static method in parent class, overload as constant property for qml.
+ Q_PROPERTY(int invalidSelectionIndex READ invalidSelectionIndex CONSTANT)
+ Q_CLASSINFO("DefaultProperty", "seriesChildren")
+
+ QML_NAMED_ELEMENT(Scatter3DSeries)
+ QML_ADDED_IN_VERSION(6, 6)
+
+public:
+ DeclarativeScatter3DSeries(QObject *parent = 0);
+ virtual ~DeclarativeScatter3DSeries();
+
+ QQmlListProperty<QObject> seriesChildren();
+ static void appendSeriesChildren(QQmlListProperty<QObject> *list, QObject *element);
+
+ void setBaseGradient(ColorGradient *gradient);
+ ColorGradient *baseGradient() const;
+ void setSingleHighlightGradient(ColorGradient *gradient);
+ ColorGradient *singleHighlightGradient() const;
+ void setMultiHighlightGradient(ColorGradient *gradient);
+ ColorGradient *multiHighlightGradient() const;
+
+ int invalidSelectionIndex() const;
+
+public Q_SLOTS:
+ void handleBaseGradientUpdate();
+ void handleSingleHighlightGradientUpdate();
+ void handleMultiHighlightGradientUpdate();
+
+Q_SIGNALS:
+ void baseGradientChanged(ColorGradient *gradient);
+ void singleHighlightGradientChanged(ColorGradient *gradient);
+ void multiHighlightGradientChanged(ColorGradient *gradient);
+
+private:
+ ColorGradient *m_baseGradient; // Not owned
+ ColorGradient *m_singleHighlightGradient; // Not owned
+ ColorGradient *m_multiHighlightGradient; // Not owned
+};
+
+class DeclarativeSurface3DSeries : public QSurface3DSeries
+{
+ Q_OBJECT
+ Q_PROPERTY(QQmlListProperty<QObject> seriesChildren READ seriesChildren CONSTANT)
+ // This property is overloaded to use QPointF instead of QPoint to work around qml bug
+ // where Qt.point(0, 0) can't be assigned due to error "Cannot assign QPointF to QPoint".
+ Q_PROPERTY(QPointF selectedPoint READ selectedPoint WRITE setSelectedPoint NOTIFY selectedPointChanged)
+ // This is static method in parent class, overload as constant property for qml.
+ Q_PROPERTY(QPointF invalidSelectionPosition READ invalidSelectionPosition CONSTANT)
+ Q_PROPERTY(ColorGradient *baseGradient READ baseGradient WRITE setBaseGradient NOTIFY baseGradientChanged)
+ Q_PROPERTY(ColorGradient *singleHighlightGradient READ singleHighlightGradient WRITE setSingleHighlightGradient NOTIFY singleHighlightGradientChanged)
+ Q_PROPERTY(ColorGradient *multiHighlightGradient READ multiHighlightGradient WRITE setMultiHighlightGradient NOTIFY multiHighlightGradientChanged)
+ Q_CLASSINFO("DefaultProperty", "seriesChildren")
+
+ QML_NAMED_ELEMENT(Surface3DSeries)
+ QML_ADDED_IN_VERSION(6, 6)
+
+public:
+ DeclarativeSurface3DSeries(QObject *parent = 0);
+ virtual ~DeclarativeSurface3DSeries();
+
+ void setSelectedPoint(const QPointF &position);
+ QPointF selectedPoint() const;
+ QPointF invalidSelectionPosition() const;
+
+ QQmlListProperty<QObject> seriesChildren();
+ static void appendSeriesChildren(QQmlListProperty<QObject> *list, QObject *element);
+
+ void setBaseGradient(ColorGradient *gradient);
+ ColorGradient *baseGradient() const;
+ void setSingleHighlightGradient(ColorGradient *gradient);
+ ColorGradient *singleHighlightGradient() const;
+ void setMultiHighlightGradient(ColorGradient *gradient);
+ ColorGradient *multiHighlightGradient() const;
+
+public Q_SLOTS:
+ void handleBaseGradientUpdate();
+ void handleSingleHighlightGradientUpdate();
+ void handleMultiHighlightGradientUpdate();
+
+Q_SIGNALS:
+ void selectedPointChanged(const QPointF &position);
+ void baseGradientChanged(ColorGradient *gradient);
+ void singleHighlightGradientChanged(ColorGradient *gradient);
+ void multiHighlightGradientChanged(ColorGradient *gradient);
+
+private:
+ ColorGradient *m_baseGradient; // Not owned
+ ColorGradient *m_singleHighlightGradient; // Not owned
+ ColorGradient *m_multiHighlightGradient; // Not owned
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/qml/declarativetheme.cpp b/src/graphs/qml/declarativetheme.cpp
new file mode 100644
index 0000000..42f0ff0
--- /dev/null
+++ b/src/graphs/qml/declarativetheme.cpp
@@ -0,0 +1,386 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "declarativetheme_p.h"
+
+QT_BEGIN_NAMESPACE
+
+DeclarativeTheme3D::DeclarativeTheme3D(QObject *parent)
+ : Q3DTheme(parent),
+ m_colors(QList<DeclarativeColor *>()),
+ m_gradients(QList<ColorGradient *>()),
+ m_singleHLGradient(0),
+ m_multiHLGradient(0),
+ m_dummyGradients(false),
+ m_dummyColors(false)
+{
+ connect(this, &Q3DTheme::typeChanged, this, &DeclarativeTheme3D::handleTypeChange);
+}
+
+DeclarativeTheme3D::~DeclarativeTheme3D()
+{
+}
+
+QQmlListProperty<QObject> DeclarativeTheme3D::themeChildren()
+{
+ return QQmlListProperty<QObject>(this, this, &DeclarativeTheme3D::appendThemeChildren,
+ 0, 0, 0);
+}
+
+void DeclarativeTheme3D::appendThemeChildren(QQmlListProperty<QObject> *list, QObject *element)
+{
+ Q_UNUSED(list);
+ Q_UNUSED(element);
+ // Nothing to do, themeChildren is there only to enable scoping gradient items in Theme3D item.
+}
+
+void DeclarativeTheme3D::handleTypeChange(Theme themeType)
+{
+ Q_UNUSED(themeType);
+
+ // Theme changed, disconnect base color/gradient connections
+ if (!m_colors.isEmpty()) {
+ foreach (DeclarativeColor *item, m_colors)
+ disconnect(item, 0, this, 0);
+ m_colors.clear();
+ }
+ if (!m_gradients.isEmpty()) {
+ foreach (ColorGradient *item, m_gradients)
+ disconnect(item, 0, this, 0);
+ m_gradients.clear();
+ }
+}
+
+void DeclarativeTheme3D::handleBaseColorUpdate()
+{
+ int colorCount = m_colors.size();
+ int changed = 0;
+ // Check which one changed
+ DeclarativeColor *color = qobject_cast<DeclarativeColor *>(QObject::sender());
+ for (int i = 0; i < colorCount; i++) {
+ if (color == m_colors.at(i)) {
+ changed = i;
+ break;
+ }
+ }
+ // Update the changed one from the list
+ QList<QColor> list = Q3DTheme::baseColors();
+ list[changed] = m_colors.at(changed)->color();
+ // Set the changed list
+ Q3DTheme::setBaseColors(list);
+}
+
+void DeclarativeTheme3D::handleBaseGradientUpdate()
+{
+ int gradientCount = m_gradients.size();
+ int changed = 0;
+ // Check which one changed
+ ColorGradient *gradient = qobject_cast<ColorGradient *>(QObject::sender());
+ for (int i = 0; i < gradientCount; i++) {
+ if (gradient == m_gradients.at(i)) {
+ changed = i;
+ break;
+ }
+ }
+ // Update the changed one from the list
+ QList<QLinearGradient> list = Q3DTheme::baseGradients();
+ list[changed] = convertGradient(gradient);
+ // Set the changed list
+ Q3DTheme::setBaseGradients(list);
+}
+
+void DeclarativeTheme3D::handleSingleHLGradientUpdate()
+{
+ if (m_singleHLGradient)
+ setThemeGradient(m_singleHLGradient, GradientTypeSingleHL);
+}
+
+void DeclarativeTheme3D::handleMultiHLGradientUpdate()
+{
+ if (m_multiHLGradient)
+ setThemeGradient(m_multiHLGradient, GradientTypeMultiHL);
+}
+
+void DeclarativeTheme3D::setSingleHighlightGradient(ColorGradient *gradient)
+{
+ // connect new / disconnect old
+ if (gradient != m_singleHLGradient) {
+ if (m_singleHLGradient)
+ QObject::disconnect(m_singleHLGradient, 0, this, 0);
+
+ m_singleHLGradient = gradient;
+
+ if (m_singleHLGradient) {
+ QObject::connect(m_singleHLGradient, &ColorGradient::updated, this,
+ &DeclarativeTheme3D::handleSingleHLGradientUpdate);
+ }
+
+ emit singleHighlightGradientChanged(m_singleHLGradient);
+ }
+
+ if (m_singleHLGradient)
+ setThemeGradient(m_singleHLGradient, GradientTypeSingleHL);
+}
+
+ColorGradient *DeclarativeTheme3D::singleHighlightGradient() const
+{
+ return m_singleHLGradient;
+}
+
+void DeclarativeTheme3D::setMultiHighlightGradient(ColorGradient *gradient)
+{
+ // connect new / disconnect old
+ if (gradient != m_multiHLGradient) {
+ if (m_multiHLGradient)
+ QObject::disconnect(m_multiHLGradient, 0, this, 0);
+
+ m_multiHLGradient = gradient;
+
+ if (m_multiHLGradient) {
+ QObject::connect(m_multiHLGradient, &ColorGradient::updated, this,
+ &DeclarativeTheme3D::handleMultiHLGradientUpdate);
+ }
+
+ emit multiHighlightGradientChanged(m_multiHLGradient);
+ }
+
+ if (m_multiHLGradient)
+ setThemeGradient(m_multiHLGradient, GradientTypeMultiHL);
+}
+
+ColorGradient *DeclarativeTheme3D::multiHighlightGradient() const
+{
+ return m_multiHLGradient;
+}
+
+void DeclarativeTheme3D::classBegin()
+{
+ // Turn off predefined type forcing for the duration of initial class construction
+ // so that predefined type customization can be done.
+ d_ptr->setForcePredefinedType(false);
+}
+
+void DeclarativeTheme3D::componentComplete()
+{
+ d_ptr->setForcePredefinedType(true);
+}
+
+
+void DeclarativeTheme3D::setThemeGradient(ColorGradient *gradient, GradientType type)
+{
+ QLinearGradient newGradient = convertGradient(gradient);
+
+ switch (type) {
+ case GradientTypeSingleHL:
+ Q3DTheme::setSingleHighlightGradient(newGradient);
+ break;
+ case GradientTypeMultiHL:
+ Q3DTheme::setMultiHighlightGradient(newGradient);
+ break;
+ default:
+ qWarning("Incorrect usage. Type may be GradientTypeSingleHL or GradientTypeMultiHL.");
+ break;
+ }
+}
+
+QLinearGradient DeclarativeTheme3D::convertGradient(ColorGradient *gradient)
+{
+ QLinearGradient newGradient;
+ QGradientStops stops;
+ QList<ColorGradientStop *> qmlstops = gradient->m_stops;
+
+ // Get sorted gradient stops
+ for (int i = 0; i < qmlstops.size(); i++) {
+ int j = 0;
+ while (j < stops.size() && stops.at(j).first < qmlstops[i]->position())
+ j++;
+ stops.insert(j, QGradientStop(qmlstops.at(i)->position(), qmlstops.at(i)->color()));
+ }
+
+ newGradient.setStops(stops);
+
+ return newGradient;
+}
+
+ColorGradient *DeclarativeTheme3D::convertGradient(const QLinearGradient &gradient)
+{
+ ColorGradient *newGradient = new ColorGradient(this);
+ QGradientStops stops = gradient.stops();
+ ColorGradientStop *qmlstop;
+
+ // Convert stops
+ for (int i = 0; i < stops.size(); i++) {
+ qmlstop = new ColorGradientStop(newGradient);
+ qmlstop->setColor(stops.at(i).second);
+ qmlstop->setPosition(stops.at(i).first);
+ newGradient->m_stops.append(qmlstop);
+ }
+
+ return newGradient;
+}
+
+void DeclarativeTheme3D::addColor(DeclarativeColor *color)
+{
+ if (!color) {
+ qWarning("Color is invalid, use ThemeColor");
+ return;
+ }
+ clearDummyColors();
+ m_colors.append(color);
+ connect(color, &DeclarativeColor::colorChanged,
+ this, &DeclarativeTheme3D::handleBaseColorUpdate);
+ QList<QColor> list = Q3DTheme::baseColors();
+ list.append(color->color());
+ Q3DTheme::setBaseColors(list);
+}
+
+QList<DeclarativeColor *> DeclarativeTheme3D::colorList()
+{
+ if (m_colors.isEmpty()) {
+ // Create dummy ThemeColors from theme's colors
+ m_dummyColors = true;
+ QList<QColor> list = Q3DTheme::baseColors();
+ foreach (QColor item, list) {
+ DeclarativeColor *color = new DeclarativeColor(this);
+ color->setColor(item);
+ m_colors.append(color);
+ connect(color, &DeclarativeColor::colorChanged,
+ this, &DeclarativeTheme3D::handleBaseColorUpdate);
+ }
+ }
+ return m_colors;
+}
+
+void DeclarativeTheme3D::clearColors()
+{
+ clearDummyColors();
+ foreach (DeclarativeColor *item, m_colors)
+ disconnect(item, 0, this, 0);
+ m_colors.clear();
+ Q3DTheme::setBaseColors(QList<QColor>());
+}
+
+void DeclarativeTheme3D::clearDummyColors()
+{
+ if (m_dummyColors) {
+ foreach (DeclarativeColor *item, m_colors)
+ delete item;
+ m_colors.clear();
+ m_dummyColors = false;
+ }
+}
+
+void DeclarativeTheme3D::addGradient(ColorGradient *gradient)
+{
+ if (!gradient) {
+ qWarning("Gradient is invalid, use ColorGradient");
+ return;
+ }
+ clearDummyGradients();
+ m_gradients.append(gradient);
+ connect(gradient, &ColorGradient::updated,
+ this, &DeclarativeTheme3D::handleBaseGradientUpdate);
+ QList<QLinearGradient> list = Q3DTheme::baseGradients();
+ list.append(convertGradient(gradient));
+ Q3DTheme::setBaseGradients(list);
+}
+
+QList<ColorGradient *> DeclarativeTheme3D::gradientList()
+{
+ if (m_gradients.isEmpty()) {
+ // Create dummy ColorGradients from theme's gradients
+ m_dummyGradients = true;
+ QList<QLinearGradient> list = Q3DTheme::baseGradients();
+ foreach (QLinearGradient item, list) {
+ ColorGradient *gradient = convertGradient(item);
+ m_gradients.append(gradient);
+ connect(gradient, &ColorGradient::updated,
+ this, &DeclarativeTheme3D::handleBaseGradientUpdate);
+ }
+ }
+
+ return m_gradients;
+}
+
+void DeclarativeTheme3D::clearGradients()
+{
+ clearDummyGradients();
+ foreach (ColorGradient *item, m_gradients)
+ disconnect(item, 0, this, 0);
+ m_gradients.clear();
+ Q3DTheme::setBaseGradients(QList<QLinearGradient>());
+}
+
+void DeclarativeTheme3D::clearDummyGradients()
+{
+ if (m_dummyGradients) {
+ foreach (ColorGradient *item, m_gradients)
+ delete item;
+ m_gradients.clear();
+ m_dummyGradients = false;
+ }
+}
+
+QQmlListProperty<DeclarativeColor> DeclarativeTheme3D::baseColors()
+{
+ return QQmlListProperty<DeclarativeColor>(this, this,
+ &DeclarativeTheme3D::appendBaseColorsFunc,
+ &DeclarativeTheme3D::countBaseColorsFunc,
+ &DeclarativeTheme3D::atBaseColorsFunc,
+ &DeclarativeTheme3D::clearBaseColorsFunc);
+}
+
+void DeclarativeTheme3D::appendBaseColorsFunc(QQmlListProperty<DeclarativeColor> *list,
+ DeclarativeColor *color)
+{
+ reinterpret_cast<DeclarativeTheme3D *>(list->data)->addColor(color);
+}
+
+qsizetype DeclarativeTheme3D::countBaseColorsFunc(QQmlListProperty<DeclarativeColor> *list)
+{
+ return reinterpret_cast<DeclarativeTheme3D *>(list->data)->colorList().size();
+}
+
+DeclarativeColor *DeclarativeTheme3D::atBaseColorsFunc(QQmlListProperty<DeclarativeColor> *list,
+ qsizetype index)
+{
+ return reinterpret_cast<DeclarativeTheme3D *>(list->data)->colorList().at(index);
+}
+
+void DeclarativeTheme3D::clearBaseColorsFunc(QQmlListProperty<DeclarativeColor> *list)
+{
+ reinterpret_cast<DeclarativeTheme3D *>(list->data)->clearColors();
+}
+
+QQmlListProperty<ColorGradient> DeclarativeTheme3D::baseGradients()
+{
+ return QQmlListProperty<ColorGradient>(this, this,
+ &DeclarativeTheme3D::appendBaseGradientsFunc,
+ &DeclarativeTheme3D::countBaseGradientsFunc,
+ &DeclarativeTheme3D::atBaseGradientsFunc,
+ &DeclarativeTheme3D::clearBaseGradientsFunc);
+}
+
+void DeclarativeTheme3D::appendBaseGradientsFunc(QQmlListProperty<ColorGradient> *list,
+ ColorGradient *gradient)
+{
+ reinterpret_cast<DeclarativeTheme3D *>(list->data)->addGradient(gradient);
+}
+
+qsizetype DeclarativeTheme3D::countBaseGradientsFunc(QQmlListProperty<ColorGradient> *list)
+{
+ return reinterpret_cast<DeclarativeTheme3D *>(list->data)->gradientList().size();
+}
+
+ColorGradient *DeclarativeTheme3D::atBaseGradientsFunc(QQmlListProperty<ColorGradient> *list,
+ qsizetype index)
+{
+ return reinterpret_cast<DeclarativeTheme3D *>(list->data)->gradientList().at(index);
+}
+
+void DeclarativeTheme3D::clearBaseGradientsFunc(QQmlListProperty<ColorGradient> *list)
+{
+ reinterpret_cast<DeclarativeTheme3D *>(list->data)->clearGradients();
+}
+
+QT_END_NAMESPACE
diff --git a/src/graphs/qml/declarativetheme_p.h b/src/graphs/qml/declarativetheme_p.h
new file mode 100644
index 0000000..ff9e085
--- /dev/null
+++ b/src/graphs/qml/declarativetheme_p.h
@@ -0,0 +1,117 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QtGraphs API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#ifndef DECLARATIVETHEME_P_H
+#define DECLARATIVETHEME_P_H
+
+#include <private/graphsglobal_p.h>
+#include <private/q3dtheme_p.h>
+
+#include "declarativecolor_p.h"
+#include "colorgradient_p.h"
+
+#include <QtQml/qqml.h>
+#include <QtQml/qqmlparserstatus.h>
+
+QT_BEGIN_NAMESPACE
+
+class DeclarativeTheme3D : public Q3DTheme, public QQmlParserStatus
+{
+ Q_OBJECT
+ Q_INTERFACES(QQmlParserStatus)
+ Q_PROPERTY(QQmlListProperty<QObject> themeChildren READ themeChildren CONSTANT)
+ Q_PROPERTY(QQmlListProperty<DeclarativeColor> baseColors READ baseColors CONSTANT)
+ Q_PROPERTY(QQmlListProperty<ColorGradient> baseGradients READ baseGradients CONSTANT)
+ Q_PROPERTY(ColorGradient *singleHighlightGradient READ singleHighlightGradient WRITE setSingleHighlightGradient NOTIFY singleHighlightGradientChanged)
+ Q_PROPERTY(ColorGradient *multiHighlightGradient READ multiHighlightGradient WRITE setMultiHighlightGradient NOTIFY multiHighlightGradientChanged)
+ Q_CLASSINFO("DefaultProperty", "themeChildren")
+ QML_NAMED_ELEMENT(Theme3D)
+ QML_ADDED_IN_VERSION(6, 6)
+
+public:
+ DeclarativeTheme3D(QObject *parent = 0);
+ virtual ~DeclarativeTheme3D();
+
+ QQmlListProperty<QObject> themeChildren();
+ static void appendThemeChildren(QQmlListProperty<QObject> *list, QObject *element);
+
+ QQmlListProperty<DeclarativeColor> baseColors();
+ static void appendBaseColorsFunc(QQmlListProperty<DeclarativeColor> *list,
+ DeclarativeColor *color);
+ static qsizetype countBaseColorsFunc(QQmlListProperty<DeclarativeColor> *list);
+ static DeclarativeColor *atBaseColorsFunc(QQmlListProperty<DeclarativeColor> *list,
+ qsizetype index);
+ static void clearBaseColorsFunc(QQmlListProperty<DeclarativeColor> *list);
+
+ QQmlListProperty<ColorGradient> baseGradients();
+ static void appendBaseGradientsFunc(QQmlListProperty<ColorGradient> *list,
+ ColorGradient *gradient);
+ static qsizetype countBaseGradientsFunc(QQmlListProperty<ColorGradient> *list);
+ static ColorGradient *atBaseGradientsFunc(QQmlListProperty<ColorGradient> *list,
+ qsizetype index);
+ static void clearBaseGradientsFunc(QQmlListProperty<ColorGradient> *list);
+
+ void setSingleHighlightGradient(ColorGradient *gradient);
+ ColorGradient *singleHighlightGradient() const;
+
+ void setMultiHighlightGradient(ColorGradient *gradient);
+ ColorGradient *multiHighlightGradient() const;
+
+ // From QQmlParserStatus
+ void classBegin() override;
+ void componentComplete() override;
+
+Q_SIGNALS:
+ void singleHighlightGradientChanged(ColorGradient *gradient);
+ void multiHighlightGradientChanged(ColorGradient *gradient);
+
+protected:
+ void handleTypeChange(Theme themeType);
+ void handleBaseColorUpdate();
+ void handleBaseGradientUpdate();
+ void handleSingleHLGradientUpdate();
+ void handleMultiHLGradientUpdate();
+
+ enum GradientType {
+ GradientTypeBase = 0,
+ GradientTypeSingleHL,
+ GradientTypeMultiHL
+ };
+
+private:
+ void addColor(DeclarativeColor *color);
+ QList<DeclarativeColor *> colorList();
+ void clearColors();
+ void clearDummyColors();
+
+ void addGradient(ColorGradient *gradient);
+ QList<ColorGradient *> gradientList();
+ void clearGradients();
+ void clearDummyGradients();
+
+ void setThemeGradient(ColorGradient *gradient, GradientType type);
+ QLinearGradient convertGradient(ColorGradient *gradient);
+ ColorGradient *convertGradient(const QLinearGradient &gradient);
+
+ QList<DeclarativeColor *> m_colors; // Not owned
+ QList<ColorGradient *> m_gradients; // Not owned
+ ColorGradient *m_singleHLGradient; // Not owned
+ ColorGradient *m_multiHLGradient; // Not owned
+
+ bool m_dummyGradients;
+ bool m_dummyColors;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/qml/designer/Bars3DSpecifics.qml b/src/graphs/qml/designer/Bars3DSpecifics.qml
new file mode 100644
index 0000000..dc1910c
--- /dev/null
+++ b/src/graphs/qml/designer/Bars3DSpecifics.qml
@@ -0,0 +1,387 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick 2.0
+import HelperWidgets 2.0
+import QtQuick.Layouts 1.0
+import QtQuick.Controls 1.1 as Controls
+
+Column {
+ anchors.left: parent.left
+ anchors.right: parent.right
+
+ Section {
+ anchors.left: parent.left
+ anchors.right: parent.right
+ caption: qsTr("Bars3D")
+
+ SectionLayout {
+ Label {
+ text: qsTr("multiSeriesUniform")
+ tooltip: qsTr("Multiseries Uniform")
+ Layout.fillWidth: true
+ }
+ SecondColumnLayout {
+ CheckBox {
+ backendValue: backendValues.multiSeriesUniform
+ Layout.fillWidth: true
+ }
+ }
+ Label {
+ text: qsTr("barThickness")
+ tooltip: qsTr("Bar Thickness Ratio")
+ Layout.fillWidth: true
+ }
+ SecondColumnLayout {
+ SpinBox {
+ backendValue: backendValues.barThickness
+ minimumValue: 0.01
+ maximumValue: 100.0
+ stepSize: 0.01
+ decimals: 2
+ Layout.fillWidth: true
+ }
+ }
+ Label {
+ text: qsTr("barSpacing")
+ tooltip: qsTr("Bar Spacing")
+ Layout.fillWidth: true
+ }
+ SecondColumnLayout {
+ SpinBox {
+ prefix: "col: "
+ backendValue: backendValues.barSpacing_width
+ minimumValue: 0.0
+ maximumValue: 10.0
+ stepSize: 0.01
+ decimals: 2
+ Layout.fillWidth: true
+ }
+ SpinBox {
+ prefix: "row: "
+ backendValue: backendValues.barSpacing_height
+ minimumValue: 0.0
+ maximumValue: 10.0
+ stepSize: 0.01
+ decimals: 2
+ Layout.fillWidth: true
+ }
+ }
+ Label {
+ text: qsTr("barSpacingRelative")
+ tooltip: qsTr("Bar Spacing Relative")
+ Layout.fillWidth: true
+ }
+ SecondColumnLayout {
+ CheckBox {
+ backendValue: backendValues.barSpacingRelative
+ Layout.fillWidth: true
+ }
+ }
+ Label {
+ text: qsTr("renderingMode")
+ tooltip: qsTr("Rendering Mode")
+ Layout.fillWidth: true
+ }
+ SecondColumnLayout {
+ ComboBox {
+ backendValue: backendValues.renderingMode
+ model: ["RenderIndirect", "RenderDirectToBackground",
+ "RenderDirectToBackground_NoClear"]
+ Layout.fillWidth: true
+ scope: "AbstractGraph3D"
+ }
+ }
+ Label {
+ text: qsTr("msaaSamples")
+ tooltip: qsTr("MSAA Sample Count")
+ Layout.fillWidth: true
+ }
+ SpinBox {
+ suffix: " x MSAA"
+ backendValue: backendValues.msaaSamples
+ minimumValue: 0
+ maximumValue: 16
+ Layout.fillWidth: true
+ }
+ Label {
+ text: qsTr("shadowQuality")
+ tooltip: qsTr("Shadow Quality")
+ Layout.fillWidth: true
+ }
+ SecondColumnLayout {
+ ComboBox {
+ backendValue: backendValues.shadowQuality
+ model: ["ShadowQualityNone", "ShadowQualityLow", "ShadowQualityMedium",
+ "ShadowQualityHigh", "ShadowQualitySoftLow", "ShadowQualitySoftMedium",
+ "ShadowQualitySoftHigh"]
+ Layout.fillWidth: true
+ scope: "AbstractGraph3D"
+ }
+ }
+ Label {
+ text: qsTr("selectionMode")
+ tooltip: qsTr("Selection Mode")
+ Layout.fillWidth: true
+ }
+ SecondColumnLayout {
+ id: selectionLayout
+ property bool isInModel: backendValue.isInModel;
+ property bool isInSubState: backendValue.isInSubState;
+ property bool selectionChangedFlag: selectionChanged
+ property variant backendValue: backendValues.selectionMode
+ property variant valueFromBackend: backendValue.value
+ property string enumScope: "AbstractGraph3D"
+ property string enumSeparator: " | "
+ property int checkedCount: 0
+ property bool selectionItem: false
+ property bool selectionRow: false
+ property bool selectionColumn: false
+ property bool selectionSlice: false
+ property bool selectionMulti: false
+
+ function checkValue(checkedVariable, variableText, expressionBase) {
+ var expressionStr = expressionBase
+ if (checkedVariable) {
+ if (expressionStr !== "") {
+ expressionStr += enumSeparator
+ }
+ expressionStr += enumScope
+ expressionStr += "."
+ expressionStr += variableText
+ checkedCount++
+ }
+ return expressionStr
+ }
+
+ function composeSelectionMode() {
+ var expressionStr = ""
+ checkedCount = 0
+ expressionStr = checkValue(selectionItem, "SelectionItem", expressionStr)
+ expressionStr = checkValue(selectionRow, "SelectionRow", expressionStr)
+ expressionStr = checkValue(selectionColumn, "SelectionColumn", expressionStr)
+ expressionStr = checkValue(selectionSlice, "SelectionSlice", expressionStr)
+ expressionStr = checkValue(selectionMulti, "SelectionMultiSeries", expressionStr)
+
+ if (checkedCount === 0)
+ backendValue.expression = enumScope + ".SelectionNone"
+ else
+ backendValue.expression = expressionStr
+ }
+
+ function evaluate() {
+ if (backendValue.value === undefined)
+ return
+
+ selectionItem = (backendValue.expression.indexOf("SelectionItem") !== -1)
+ selectionRow = (backendValue.expression.indexOf("SelectionRow") !== -1)
+ selectionColumn = (backendValue.expression.indexOf("SelectionColumn") !== -1)
+ selectionSlice = (backendValue.expression.indexOf("SelectionSlice") !== -1)
+ selectionMulti = (backendValue.expression.indexOf("SelectionMultiSeries") !== -1)
+
+ selectionItemBox.checked = selectionItem
+ selectionRowBox.checked = selectionRow
+ selectionColumnBox.checked = selectionColumn
+ selectionSliceBox.checked = selectionSlice
+ selectionMultiSeriesBox.checked = selectionMulti
+ }
+
+ onSelectionChangedFlagChanged: evaluate()
+
+ onIsInModelChanged: evaluate()
+
+ onIsInSubStateChanged: evaluate()
+
+ onBackendValueChanged: evaluate()
+
+ onValueFromBackendChanged: evaluate()
+
+ ColumnLayout {
+ anchors.fill: parent
+
+ Controls.CheckBox {
+ id: selectionItemBox
+ style: checkBox.style
+ text: "SelectionItem"
+ Layout.fillWidth: true
+ onClicked: {
+ selectionLayout.selectionItem = checked
+ selectionLayout.composeSelectionMode()
+ }
+ }
+ Controls.CheckBox {
+ id: selectionRowBox
+ style: checkBox.style
+ text: "SelectionRow"
+ Layout.fillWidth: true
+ onClicked: {
+ selectionLayout.selectionRow = checked
+ selectionLayout.composeSelectionMode()
+ }
+ }
+ Controls.CheckBox {
+ id: selectionColumnBox
+ style: checkBox.style
+ text: "SelectionColumn"
+ Layout.fillWidth: true
+ onClicked: {
+ selectionLayout.selectionColumn = checked
+ selectionLayout.composeSelectionMode()
+ }
+ }
+ Controls.CheckBox {
+ id: selectionSliceBox
+ style: checkBox.style
+ text: "SelectionSlice"
+ Layout.fillWidth: true
+ onClicked: {
+ selectionLayout.selectionSlice = checked
+ selectionLayout.composeSelectionMode()
+ }
+ }
+ Controls.CheckBox {
+ id: selectionMultiSeriesBox
+ style: checkBox.style
+ text: "SelectionMultiSeries"
+ Layout.fillWidth: true
+ onClicked: {
+ selectionLayout.selectionMulti = checked
+ selectionLayout.composeSelectionMode()
+ }
+ }
+ }
+ }
+ Label {
+ text: qsTr("measureFps")
+ tooltip: qsTr("Measure Frames Per Second")
+ Layout.fillWidth: true
+ }
+ SecondColumnLayout {
+ CheckBox {
+ backendValue: backendValues.measureFps
+ Layout.fillWidth: true
+ }
+ }
+ Label {
+ text: qsTr("orthoProjection")
+ tooltip: qsTr("Use Orthographic Projection")
+ Layout.fillWidth: true
+ }
+ SecondColumnLayout {
+ CheckBox {
+ backendValue: backendValues.orthoProjection
+ Layout.fillWidth: true
+ }
+ }
+ Label {
+ text: qsTr("aspectRatio")
+ tooltip: qsTr("Aspect Ratio")
+ Layout.fillWidth: true
+ }
+ SecondColumnLayout {
+ SpinBox {
+ backendValue: backendValues.aspectRatio
+ minimumValue: 0.01
+ maximumValue: 100.0
+ stepSize: 0.01
+ decimals: 2
+ Layout.fillWidth: true
+ }
+ }
+ Label {
+ text: qsTr("floorLevel")
+ tooltip: qsTr("Floor Level")
+ Layout.fillWidth: true
+ }
+ SecondColumnLayout {
+ LineEdit {
+ backendValue: backendValues.floorLevel
+ inputMethodHints: Qt.ImhFormattedNumbersOnly
+ Layout.fillWidth: true
+ }
+ }
+ Label {
+ text: qsTr("horizontalAspectRatio")
+ tooltip: qsTr("Horizontal Aspect Ratio")
+ Layout.fillWidth: true
+ }
+ SecondColumnLayout {
+ SpinBox {
+ backendValue: backendValues.horizontalAspectRatio
+ minimumValue: 0.0
+ maximumValue: 100.0
+ stepSize: 0.01
+ decimals: 2
+ Layout.fillWidth: true
+ }
+ }
+ Label {
+ text: qsTr("reflection")
+ tooltip: qsTr("Reflection")
+ Layout.fillWidth: true
+ }
+ SecondColumnLayout {
+ CheckBox {
+ id: reflectionCheckbox
+ backendValue: backendValues.reflection
+ Layout.fillWidth: true
+ }
+ }
+ Label {
+ text: qsTr("reflectivity")
+ tooltip: qsTr("Reflectivity")
+ Layout.fillWidth: true
+ visible: reflectionCheckbox.checked
+ }
+ SecondColumnLayout {
+ visible: reflectionCheckbox.checked
+ SpinBox {
+ backendValue: backendValues.reflectivity
+ minimumValue: 0.0
+ maximumValue: 1.0
+ stepSize: 0.01
+ decimals: 1
+ Layout.fillWidth: true
+ }
+ }
+ Label {
+ text: qsTr("margin")
+ tooltip: qsTr("Graph Margin")
+ Layout.fillWidth: true
+ }
+ SecondColumnLayout {
+ SpinBox {
+ backendValue: backendValues.margin
+ minimumValue: -1.0
+ maximumValue: 100.0
+ stepSize: 0.1
+ decimals: 1
+ Layout.fillWidth: true
+ }
+ }
+
+ // Kept for debugging
+ Label { }
+ SecondColumnLayout {
+ TextEdit {
+ id: debugLabel
+ Layout.fillWidth: true
+ wrapMode: TextEdit.WordWrap
+ textFormat: TextEdit.RichText
+ width: 400
+ visible: false
+ }
+ }
+ Controls.CheckBox {
+ property color textColor: colorLogic.textColor
+ id: checkBox
+ style: CustomCheckBoxStyle {}
+ visible: false
+ ColorLogic {
+ id: colorLogic
+ backendValue: backendValues.selectionMode
+ }
+ }
+ }
+ }
+}
diff --git a/src/graphs/qml/designer/Scatter3DSpecifics.qml b/src/graphs/qml/designer/Scatter3DSpecifics.qml
new file mode 100644
index 0000000..2594faa
--- /dev/null
+++ b/src/graphs/qml/designer/Scatter3DSpecifics.qml
@@ -0,0 +1,183 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick 2.0
+import HelperWidgets 2.0
+import QtQuick.Layouts 1.0
+
+Column {
+ anchors.left: parent.left
+ anchors.right: parent.right
+
+ Section {
+ anchors.left: parent.left
+ anchors.right: parent.right
+ caption: qsTr("Scatter3D")
+
+ SectionLayout {
+ Label {
+ text: qsTr("renderingMode")
+ tooltip: qsTr("Rendering Mode")
+ Layout.fillWidth: true
+ }
+ SecondColumnLayout {
+ ComboBox {
+ backendValue: backendValues.renderingMode
+ model: ["RenderIndirect", "RenderDirectToBackground",
+ "RenderDirectToBackground_NoClear"]
+ Layout.fillWidth: true
+ scope: "AbstractGraph3D"
+ }
+ }
+ Label {
+ text: qsTr("msaaSamples")
+ tooltip: qsTr("MSAA Sample Count")
+ Layout.fillWidth: true
+ }
+ SpinBox {
+ suffix: " x MSAA"
+ backendValue: backendValues.msaaSamples
+ minimumValue: 0
+ maximumValue: 16
+ Layout.fillWidth: true
+ }
+ Label {
+ text: qsTr("shadowQuality")
+ tooltip: qsTr("Shadow Quality")
+ Layout.fillWidth: true
+ }
+ SecondColumnLayout {
+ ComboBox {
+ backendValue: backendValues.shadowQuality
+ model: ["ShadowQualityNone", "ShadowQualityLow", "ShadowQualityMedium",
+ "ShadowQualityHigh", "ShadowQualitySoftLow", "ShadowQualitySoftMedium",
+ "ShadowQualitySoftHigh"]
+ Layout.fillWidth: true
+ scope: "AbstractGraph3D"
+ }
+ }
+ Label {
+ text: qsTr("selectionMode")
+ tooltip: qsTr("Selection Mode")
+ Layout.fillWidth: true
+ }
+ SecondColumnLayout {
+ ComboBox {
+ backendValue: backendValues.selectionMode
+ model: ["SelectionNone", "SelectionItem"]
+ Layout.fillWidth: true
+ scope: "AbstractGraph3D"
+ }
+ }
+ Label {
+ text: qsTr("measureFps")
+ tooltip: qsTr("Measure Frames Per Second")
+ Layout.fillWidth: true
+ }
+ SecondColumnLayout {
+ CheckBox {
+ backendValue: backendValues.measureFps
+ Layout.fillWidth: true
+ }
+ }
+ Label {
+ text: qsTr("orthoProjection")
+ tooltip: qsTr("Use Orthographic Projection")
+ Layout.fillWidth: true
+ }
+ SecondColumnLayout {
+ CheckBox {
+ backendValue: backendValues.orthoProjection
+ Layout.fillWidth: true
+ }
+ }
+ Label {
+ text: qsTr("aspectRatio")
+ tooltip: qsTr("Horizontal to Vertical Aspect Ratio")
+ Layout.fillWidth: true
+ }
+ SecondColumnLayout {
+ SpinBox {
+ backendValue: backendValues.aspectRatio
+ minimumValue: 0.1
+ maximumValue: 10.0
+ stepSize: 0.1
+ decimals: 1
+ Layout.fillWidth: true
+ }
+ }
+ Label {
+ text: qsTr("optimizationHints")
+ tooltip: qsTr("Optimization Hints")
+ Layout.fillWidth: true
+ }
+ SecondColumnLayout {
+ ComboBox {
+ backendValue: backendValues.optimizationHints
+ model: ["OptimizationDefault", "OptimizationStatic"]
+ Layout.fillWidth: true
+ scope: "AbstractGraph3D"
+ }
+ }
+ Label {
+ text: qsTr("polar")
+ tooltip: qsTr("Use Polar Coordinates")
+ Layout.fillWidth: true
+ }
+ SecondColumnLayout {
+ CheckBox {
+ id: polarCheckbox
+ backendValue: backendValues.polar
+ Layout.fillWidth: true
+ }
+ }
+ Label {
+ text: qsTr("radialLabelOffset")
+ tooltip: qsTr("Radial Label Offset")
+ Layout.fillWidth: true
+ visible: polarCheckbox.checked
+ }
+ SecondColumnLayout {
+ visible: polarCheckbox.checked
+ SpinBox {
+ backendValue: backendValues.radialLabelOffset
+ minimumValue: 0.0
+ maximumValue: 1.0
+ stepSize: 0.01
+ decimals: 2
+ Layout.fillWidth: true
+ }
+ }
+ Label {
+ text: qsTr("horizontalAspectRatio")
+ tooltip: qsTr("Horizontal Aspect Ratio")
+ Layout.fillWidth: true
+ }
+ SecondColumnLayout {
+ SpinBox {
+ backendValue: backendValues.horizontalAspectRatio
+ minimumValue: 0.0
+ maximumValue: 100.0
+ stepSize: 0.01
+ decimals: 2
+ Layout.fillWidth: true
+ }
+ }
+ Label {
+ text: qsTr("margin")
+ tooltip: qsTr("Graph Margin")
+ Layout.fillWidth: true
+ }
+ SecondColumnLayout {
+ SpinBox {
+ backendValue: backendValues.margin
+ minimumValue: -1.0
+ maximumValue: 100.0
+ stepSize: 0.1
+ decimals: 1
+ Layout.fillWidth: true
+ }
+ }
+ }
+ }
+}
diff --git a/src/graphs/qml/designer/Surface3DSpecifics.qml b/src/graphs/qml/designer/Surface3DSpecifics.qml
new file mode 100644
index 0000000..c1a3f6d
--- /dev/null
+++ b/src/graphs/qml/designer/Surface3DSpecifics.qml
@@ -0,0 +1,324 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick 2.0
+import HelperWidgets 2.0
+import QtQuick.Layouts 1.0
+import QtQuick.Controls 1.1 as Controls
+
+Column {
+ anchors.left: parent.left
+ anchors.right: parent.right
+
+ Section {
+ anchors.left: parent.left
+ anchors.right: parent.right
+ caption: qsTr("Surface3D")
+
+ SectionLayout {
+ Label {
+ text: qsTr("renderingMode")
+ tooltip: qsTr("Rendering Mode")
+ Layout.fillWidth: true
+ }
+ SecondColumnLayout {
+ ComboBox {
+ backendValue: backendValues.renderingMode
+ model: ["RenderIndirect", "RenderDirectToBackground",
+ "RenderDirectToBackground_NoClear"]
+ Layout.fillWidth: true
+ scope: "AbstractGraph3D"
+ }
+ }
+ Label {
+ text: qsTr("msaaSamples")
+ tooltip: qsTr("MSAA Sample Count")
+ Layout.fillWidth: true
+ }
+ SpinBox {
+ suffix: " x MSAA"
+ backendValue: backendValues.msaaSamples
+ minimumValue: 0
+ maximumValue: 16
+ Layout.fillWidth: true
+ }
+ Label {
+ text: qsTr("shadowQuality")
+ tooltip: qsTr("Shadow Quality")
+ Layout.fillWidth: true
+ }
+ SecondColumnLayout {
+ ComboBox {
+ backendValue: backendValues.shadowQuality
+ model: ["ShadowQualityNone", "ShadowQualityLow", "ShadowQualityMedium",
+ "ShadowQualityHigh", "ShadowQualitySoftLow", "ShadowQualitySoftMedium",
+ "ShadowQualitySoftHigh"]
+ Layout.fillWidth: true
+ scope: "AbstractGraph3D"
+ }
+ }
+ Label {
+ text: qsTr("selectionMode")
+ tooltip: qsTr("Selection Mode")
+ Layout.fillWidth: true
+ }
+ SecondColumnLayout {
+ id: selectionLayout
+ property bool isInModel: backendValue.isInModel;
+ property bool isInSubState: backendValue.isInSubState;
+ property bool selectionChangedFlag: selectionChanged
+ property variant backendValue: backendValues.selectionMode
+ property variant valueFromBackend: backendValue.value
+ property string enumScope: "AbstractGraph3D"
+ property string enumSeparator: " | "
+ property int checkedCount: 0
+ property bool selectionItem: false
+ property bool selectionRow: false
+ property bool selectionColumn: false
+ property bool selectionSlice: false
+ property bool selectionMulti: false
+
+ function checkValue(checkedVariable, variableText, expressionBase) {
+ var expressionStr = expressionBase
+ if (checkedVariable) {
+ if (expressionStr !== "") {
+ expressionStr += enumSeparator
+ }
+ expressionStr += enumScope
+ expressionStr += "."
+ expressionStr += variableText
+ checkedCount++
+ }
+ return expressionStr
+ }
+
+ function composeSelectionMode() {
+ var expressionStr = ""
+ checkedCount = 0
+ expressionStr = checkValue(selectionItem, "SelectionItem", expressionStr)
+ expressionStr = checkValue(selectionRow, "SelectionRow", expressionStr)
+ expressionStr = checkValue(selectionColumn, "SelectionColumn", expressionStr)
+ expressionStr = checkValue(selectionSlice, "SelectionSlice", expressionStr)
+ expressionStr = checkValue(selectionMulti, "SelectionMultiSeries", expressionStr)
+
+ if (checkedCount === 0)
+ backendValue.expression = enumScope + ".SelectionNone"
+ else
+ backendValue.expression = expressionStr
+ }
+
+ function evaluate() {
+ if (backendValue.value === undefined)
+ return
+
+ selectionItem = (backendValue.expression.indexOf("SelectionItem") !== -1)
+ selectionRow = (backendValue.expression.indexOf("SelectionRow") !== -1)
+ selectionColumn = (backendValue.expression.indexOf("SelectionColumn") !== -1)
+ selectionSlice = (backendValue.expression.indexOf("SelectionSlice") !== -1)
+ selectionMulti = (backendValue.expression.indexOf("SelectionMultiSeries") !== -1)
+
+ selectionItemBox.checked = selectionItem
+ selectionRowBox.checked = selectionRow
+ selectionColumnBox.checked = selectionColumn
+ selectionSliceBox.checked = selectionSlice
+ selectionMultiSeriesBox.checked = selectionMulti
+ }
+
+ onSelectionChangedFlagChanged: evaluate()
+
+ onIsInModelChanged: evaluate()
+
+ onIsInSubStateChanged: evaluate()
+
+ onBackendValueChanged: evaluate()
+
+ onValueFromBackendChanged: evaluate()
+
+ ColumnLayout {
+ anchors.fill: parent
+
+ Controls.CheckBox {
+ id: selectionItemBox
+ style: checkBox.style
+ text: "SelectionItem"
+ Layout.fillWidth: true
+ onClicked: {
+ selectionLayout.selectionItem = checked
+ selectionLayout.composeSelectionMode()
+ }
+ }
+ Controls.CheckBox {
+ id: selectionRowBox
+ style: checkBox.style
+ text: "SelectionRow"
+ Layout.fillWidth: true
+ onClicked: {
+ selectionLayout.selectionRow = checked
+ selectionLayout.composeSelectionMode()
+ }
+ }
+ Controls.CheckBox {
+ id: selectionColumnBox
+ style: checkBox.style
+ text: "SelectionColumn"
+ Layout.fillWidth: true
+ onClicked: {
+ selectionLayout.selectionColumn = checked
+ selectionLayout.composeSelectionMode()
+ }
+ }
+ Controls.CheckBox {
+ id: selectionSliceBox
+ style: checkBox.style
+ text: "SelectionSlice"
+ Layout.fillWidth: true
+ onClicked: {
+ selectionLayout.selectionSlice = checked
+ selectionLayout.composeSelectionMode()
+ }
+ }
+ Controls.CheckBox {
+ id: selectionMultiSeriesBox
+ style: checkBox.style
+ text: "SelectionMultiSeries"
+ Layout.fillWidth: true
+ onClicked: {
+ selectionLayout.selectionMulti = checked
+ selectionLayout.composeSelectionMode()
+ }
+ }
+ }
+ }
+ Label {
+ text: qsTr("measureFps")
+ tooltip: qsTr("Measure Frames Per Second")
+ Layout.fillWidth: true
+ }
+ SecondColumnLayout {
+ CheckBox {
+ backendValue: backendValues.measureFps
+ Layout.fillWidth: true
+ }
+ }
+ Label {
+ text: qsTr("orthoProjection")
+ tooltip: qsTr("Use Orthographic Projection")
+ Layout.fillWidth: true
+ }
+ SecondColumnLayout {
+ CheckBox {
+ backendValue: backendValues.orthoProjection
+ Layout.fillWidth: true
+ }
+ }
+ Label {
+ text: qsTr("aspectRatio")
+ tooltip: qsTr("Horizontal to Vertical Aspect Ratio")
+ Layout.fillWidth: true
+ }
+ SecondColumnLayout {
+ SpinBox {
+ backendValue: backendValues.aspectRatio
+ minimumValue: 0.1
+ maximumValue: 10.0
+ stepSize: 0.1
+ decimals: 1
+ Layout.fillWidth: true
+ }
+ }
+ Label {
+ text: qsTr("flipHorizontalGrid")
+ tooltip: qsTr("Flip Horizontal Grid")
+ Layout.fillWidth: true
+ }
+ SecondColumnLayout {
+ CheckBox {
+ backendValue: backendValues.flipHorizontalGrid
+ Layout.fillWidth: true
+ }
+ }
+ Label {
+ text: qsTr("polar")
+ tooltip: qsTr("Use Polar Coordinates")
+ Layout.fillWidth: true
+ }
+ SecondColumnLayout {
+ CheckBox {
+ id: polarCheckbox
+ backendValue: backendValues.polar
+ Layout.fillWidth: true
+ }
+ }
+ Label {
+ text: qsTr("radialLabelOffset")
+ tooltip: qsTr("Radial Label Offset")
+ Layout.fillWidth: true
+ visible: polarCheckbox.checked
+ }
+ SecondColumnLayout {
+ visible: polarCheckbox.checked
+ SpinBox {
+ backendValue: backendValues.radialLabelOffset
+ minimumValue: 0.0
+ maximumValue: 1.0
+ stepSize: 0.01
+ decimals: 2
+ Layout.fillWidth: true
+ }
+ }
+ Label {
+ text: qsTr("horizontalAspectRatio")
+ tooltip: qsTr("Horizontal Aspect Ratio")
+ Layout.fillWidth: true
+ }
+ SecondColumnLayout {
+ SpinBox {
+ backendValue: backendValues.horizontalAspectRatio
+ minimumValue: 0.0
+ maximumValue: 100.0
+ stepSize: 0.01
+ decimals: 2
+ Layout.fillWidth: true
+ }
+ }
+ Label {
+ text: qsTr("margin")
+ tooltip: qsTr("Graph Margin")
+ Layout.fillWidth: true
+ }
+ SecondColumnLayout {
+ SpinBox {
+ backendValue: backendValues.margin
+ minimumValue: -1.0
+ maximumValue: 100.0
+ stepSize: 0.1
+ decimals: 1
+ Layout.fillWidth: true
+ }
+ }
+
+ // Kept for debugging
+ Label { }
+ SecondColumnLayout {
+ TextEdit {
+ id: debugLabel
+ Layout.fillWidth: true
+ wrapMode: TextEdit.WordWrap
+ textFormat: TextEdit.RichText
+ width: 400
+ visible: false
+ }
+ }
+ Controls.CheckBox {
+ property color textColor: colorLogic.textColor
+ id: checkBox
+ style: CustomCheckBoxStyle {}
+ visible: false
+ ColorLogic {
+ id: colorLogic
+ backendValue: backendValues.selectionMode
+ }
+ }
+ }
+ }
+}
diff --git a/src/graphs/qml/designer/default/Bars3D.qml b/src/graphs/qml/designer/default/Bars3D.qml
new file mode 100644
index 0000000..b01827f
--- /dev/null
+++ b/src/graphs/qml/designer/default/Bars3D.qml
@@ -0,0 +1,23 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick 2.0
+import QtGraphs
+
+Bars3D {
+ width: 300
+ height: 300
+ Bar3DSeries {
+ ItemModelBarDataProxy {
+ itemModel: ListModel {
+ ListElement{ row: "row 1"; column: "column 1"; value: "1"; }
+ ListElement{ row: "row 1"; column: "column 2"; value: "2"; }
+ ListElement{ row: "row 1"; column: "column 3"; value: "3"; }
+ }
+
+ rowRole: "row"
+ columnRole: "column"
+ valueRole: "value"
+ }
+ }
+}
diff --git a/src/graphs/qml/designer/default/Scatter3D.qml b/src/graphs/qml/designer/default/Scatter3D.qml
new file mode 100644
index 0000000..e0f6b23
--- /dev/null
+++ b/src/graphs/qml/designer/default/Scatter3D.qml
@@ -0,0 +1,23 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick 2.0
+import QtGraphs
+
+Scatter3D {
+ width: 300
+ height: 300
+ Scatter3DSeries {
+ ItemModelScatterDataProxy {
+ itemModel: ListModel {
+ ListElement{ x: "1"; y: "2"; z: "3"; }
+ ListElement{ x: "2"; y: "3"; z: "4"; }
+ ListElement{ x: "3"; y: "4"; z: "1"; }
+ }
+
+ xPosRole: "x"
+ yPosRole: "y"
+ zPosRole: "z"
+ }
+ }
+}
diff --git a/src/graphs/qml/designer/default/Surface3D.qml b/src/graphs/qml/designer/default/Surface3D.qml
new file mode 100644
index 0000000..728f08f
--- /dev/null
+++ b/src/graphs/qml/designer/default/Surface3D.qml
@@ -0,0 +1,24 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick 2.0
+import QtGraphs
+
+Surface3D {
+ width: 300
+ height: 300
+ Surface3DSeries {
+ ItemModelSurfaceDataProxy {
+ itemModel: ListModel {
+ ListElement{ row: "1"; column: "1"; y: "1"; }
+ ListElement{ row: "1"; column: "2"; y: "2"; }
+ ListElement{ row: "2"; column: "1"; y: "3"; }
+ ListElement{ row: "2"; column: "2"; y: "4"; }
+ }
+
+ rowRole: "row"
+ columnRole: "column"
+ yPosRole: "y"
+ }
+ }
+}
diff --git a/src/graphs/qml/designer/images/bars3d-icon.png b/src/graphs/qml/designer/images/bars3d-icon.png
new file mode 100644
index 0000000..7f38078
--- /dev/null
+++ b/src/graphs/qml/designer/images/bars3d-icon.png
Binary files differ
diff --git a/src/graphs/qml/designer/images/bars3d-icon16.png b/src/graphs/qml/designer/images/bars3d-icon16.png
new file mode 100644
index 0000000..e85ff50
--- /dev/null
+++ b/src/graphs/qml/designer/images/bars3d-icon16.png
Binary files differ
diff --git a/src/graphs/qml/designer/images/scatter3d-icon.png b/src/graphs/qml/designer/images/scatter3d-icon.png
new file mode 100644
index 0000000..e1ac5c1
--- /dev/null
+++ b/src/graphs/qml/designer/images/scatter3d-icon.png
Binary files differ
diff --git a/src/graphs/qml/designer/images/scatter3d-icon16.png b/src/graphs/qml/designer/images/scatter3d-icon16.png
new file mode 100644
index 0000000..75b2db1
--- /dev/null
+++ b/src/graphs/qml/designer/images/scatter3d-icon16.png
Binary files differ
diff --git a/src/graphs/qml/designer/images/surface3d-icon.png b/src/graphs/qml/designer/images/surface3d-icon.png
new file mode 100644
index 0000000..956c675
--- /dev/null
+++ b/src/graphs/qml/designer/images/surface3d-icon.png
Binary files differ
diff --git a/src/graphs/qml/designer/images/surface3d-icon16.png b/src/graphs/qml/designer/images/surface3d-icon16.png
new file mode 100644
index 0000000..6caa643
--- /dev/null
+++ b/src/graphs/qml/designer/images/surface3d-icon16.png
Binary files differ
diff --git a/src/graphs/qml/designer/qtgraphs.metainfo b/src/graphs/qml/designer/qtgraphs.metainfo
new file mode 100644
index 0000000..14e43c0
--- /dev/null
+++ b/src/graphs/qml/designer/qtgraphs.metainfo
@@ -0,0 +1,44 @@
+MetaInfo {
+ Type {
+ name: "QtGraphs.Bars3D"
+ icon: "images/bars3d-icon16.png"
+
+ ItemLibraryEntry {
+ name: "Bars3D"
+ category: "Qt Graphs"
+ libraryIcon: "images/bars3d-icon.png"
+ version: "1.0"
+ requiredImport: "QtGraphs"
+
+ QmlSource { source: "default/Bars3D.qml" }
+ }
+ }
+ Type {
+ name: "QtGraphs.Scatter3D"
+ icon: "images/scatter3d-icon16.png"
+
+ ItemLibraryEntry {
+ name: "Scatter3D"
+ category: "Qt Graphs"
+ libraryIcon: "images/scatter3d-icon.png"
+ version: "1.0"
+ requiredImport: "QtGraphs"
+
+ QmlSource { source: "default/Scatter3D.qml" }
+ }
+ }
+ Type {
+ name: "QtGraphs.Surface3D"
+ icon: "images/surface3d-icon16.png"
+
+ ItemLibraryEntry {
+ name: "Surface3D"
+ category: "Qt Graphs"
+ libraryIcon: "images/surface3d-icon.png"
+ version: "1.0"
+ requiredImport: "QtGraphs"
+
+ QmlSource { source: "default/Surface3D.qml" }
+ }
+ }
+}
diff --git a/src/graphs/qml/foreigntypes_p.h b/src/graphs/qml/foreigntypes_p.h
new file mode 100644
index 0000000..436e5ad
--- /dev/null
+++ b/src/graphs/qml/foreigntypes_p.h
@@ -0,0 +1,122 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QtGraphs API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#ifndef FOREIGNTYPES_P_H
+#define FOREIGNTYPES_P_H
+
+#include <QtQml/qqml.h>
+
+#include <QtCore/qabstractitemmodel.h>
+
+#include <QtGraphs/q3dcamera.h>
+#include <QtGraphs/q3dinputhandler.h>
+#include <QtGraphs/q3dlight.h>
+#include <QtGraphs/q3dobject.h>
+#include <QtGraphs/q3dscene.h>
+#include <QtGraphs/q3dtheme.h>
+#include <QtGraphs/qabstract3daxis.h>
+#include <QtGraphs/qabstract3dinputhandler.h>
+#include <QtGraphs/qabstract3dseries.h>
+#include <QtGraphs/qabstractdataproxy.h>
+#include <QtGraphs/qbar3dseries.h>
+#include <QtGraphs/qbardataproxy.h>
+#include <QtGraphs/qcategory3daxis.h>
+#include <QtGraphs/qcustom3ditem.h>
+#include <QtGraphs/qcustom3dlabel.h>
+#include <QtGraphs/qcustom3dvolume.h>
+#include <QtGraphs/qheightmapsurfacedataproxy.h>
+#include <QtGraphs/qitemmodelbardataproxy.h>
+#include <QtGraphs/qitemmodelscatterdataproxy.h>
+#include <QtGraphs/qitemmodelsurfacedataproxy.h>
+#include <QtGraphs/qlogvalue3daxisformatter.h>
+#include <QtGraphs/qscatter3dseries.h>
+#include <QtGraphs/qscatterdataproxy.h>
+#include <QtGraphs/qsurface3dseries.h>
+#include <QtGraphs/qsurfacedataproxy.h>
+#include <QtGraphs/qtouch3dinputhandler.h>
+#include <QtGraphs/qvalue3daxis.h>
+#include <QtGraphs/qvalue3daxisformatter.h>
+#include <QtCore/private/qglobal_p.h>
+
+QT_BEGIN_NAMESPACE
+
+#define DEFINE_FOREIGN_BASE_ATTRIBUTES(type, name, minor) \
+ Q_GADGET \
+ QML_NAMED_ELEMENT(name) \
+ QML_FOREIGN(type) \
+ QML_ADDED_IN_VERSION(6, minor) \
+
+#define DEFINE_FOREIGN_UNCREATABLE_TYPE(type, name) \
+ struct type##GraphsForeign \
+ { \
+ DEFINE_FOREIGN_BASE_ATTRIBUTES(type, name, 6) \
+ QML_UNCREATABLE("Trying to create uncreatable: " #name ".") \
+ };
+
+#define DEFINE_FOREIGN_CREATABLE_TYPE(type, name, minor) \
+ struct type##GraphsForeign \
+ { \
+ DEFINE_FOREIGN_BASE_ATTRIBUTES(type, name, minor) \
+ };
+
+#define DEFINE_FOREIGN_REPLACED_TYPE(type, name, better) \
+ struct type##GraphsForeign \
+ { \
+ DEFINE_FOREIGN_BASE_ATTRIBUTES(type, name, 6) \
+ QML_UNCREATABLE("Trying to create uncreatable: " #name ", use " #better " instead.") \
+ };
+
+struct Q3DSceneForeign
+{
+ Q_GADGET
+ QML_ANONYMOUS
+ QML_ADDED_IN_VERSION(6, 6)
+ QML_FOREIGN(Q3DScene)
+};
+
+DEFINE_FOREIGN_CREATABLE_TYPE(Q3DCamera, Camera3D, 6)
+DEFINE_FOREIGN_CREATABLE_TYPE(Q3DLight, Light3D, 6)
+DEFINE_FOREIGN_CREATABLE_TYPE(QCategory3DAxis, CategoryAxis3D, 6)
+DEFINE_FOREIGN_CREATABLE_TYPE(QHeightMapSurfaceDataProxy, HeightMapSurfaceDataProxy, 6)
+DEFINE_FOREIGN_CREATABLE_TYPE(QItemModelBarDataProxy, ItemModelBarDataProxy, 6)
+DEFINE_FOREIGN_CREATABLE_TYPE(QItemModelScatterDataProxy, ItemModelScatterDataProxy, 6)
+DEFINE_FOREIGN_CREATABLE_TYPE(QItemModelSurfaceDataProxy, ItemModelSurfaceDataProxy, 6)
+DEFINE_FOREIGN_CREATABLE_TYPE(QValue3DAxis, ValueAxis3D, 6)
+
+DEFINE_FOREIGN_CREATABLE_TYPE(QCustom3DItem, Custom3DItem, 6)
+DEFINE_FOREIGN_CREATABLE_TYPE(QCustom3DLabel, Custom3DLabel, 6)
+DEFINE_FOREIGN_CREATABLE_TYPE(QLogValue3DAxisFormatter, LogValueAxis3DFormatter, 6)
+DEFINE_FOREIGN_CREATABLE_TYPE(QValue3DAxisFormatter, ValueAxis3DFormatter, 6)
+
+DEFINE_FOREIGN_CREATABLE_TYPE(Q3DInputHandler, InputHandler3D, 6)
+DEFINE_FOREIGN_CREATABLE_TYPE(QCustom3DVolume, Custom3DVolume, 6)
+DEFINE_FOREIGN_CREATABLE_TYPE(QTouch3DInputHandler, TouchInputHandler3D, 6)
+
+DEFINE_FOREIGN_REPLACED_TYPE(Q3DTheme, Q3DTheme, Theme3D)
+DEFINE_FOREIGN_REPLACED_TYPE(QBar3DSeries, QBar3DSeries, Bar3DSeries)
+DEFINE_FOREIGN_REPLACED_TYPE(QScatter3DSeries, QScatter3DSeries, Scatter3DSeries)
+DEFINE_FOREIGN_REPLACED_TYPE(QSurface3DSeries, QSurface3DSeries, Surface3DSeries)
+
+DEFINE_FOREIGN_UNCREATABLE_TYPE(Q3DObject, Object3D)
+DEFINE_FOREIGN_UNCREATABLE_TYPE(QAbstract3DAxis, AbstractAxis3D)
+DEFINE_FOREIGN_UNCREATABLE_TYPE(QAbstract3DInputHandler, AbstractInputHandler3D)
+DEFINE_FOREIGN_UNCREATABLE_TYPE(QAbstract3DSeries, Abstract3DSeries)
+DEFINE_FOREIGN_UNCREATABLE_TYPE(QAbstractDataProxy, AbstractDataProxy)
+DEFINE_FOREIGN_UNCREATABLE_TYPE(QAbstractItemModel, AbstractItemModel)
+DEFINE_FOREIGN_UNCREATABLE_TYPE(QBarDataProxy, BarDataProxy)
+DEFINE_FOREIGN_UNCREATABLE_TYPE(QScatterDataProxy, ScatterDataProxy)
+DEFINE_FOREIGN_UNCREATABLE_TYPE(QSurfaceDataProxy, SurfaceDataProxy)
+
+QT_END_NAMESPACE
+
+#endif // FOREIGNTYPES_P_H
diff --git a/src/graphs/qml/qquickgraphsbars.cpp b/src/graphs/qml/qquickgraphsbars.cpp
new file mode 100644
index 0000000..d358e6d
--- /dev/null
+++ b/src/graphs/qml/qquickgraphsbars.cpp
@@ -0,0 +1,1331 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qquickgraphsbars_p.h"
+#include "quickgraphstexturedata_p.h"
+#include "bars3dcontroller_p.h"
+#include "declarativescene_p.h"
+#include "qbar3dseries_p.h"
+#include "qvalue3daxis_p.h"
+#include "qcategory3daxis_p.h"
+#include "q3dcamera_p.h"
+#include <QtCore/QMutexLocker>
+#include <QColor>
+
+#include <QtQuick3D/private/qquick3drepeater_p.h>
+#include <QtQuick3D/private/qquick3dprincipledmaterial_p.h>
+#include "quickgraphstexturedata_p.h"
+#include <QtQuick3D/private/qquick3dcustommaterial_p.h>
+
+QQuickGraphsBars::QQuickGraphsBars(QQuickItem *parent)
+ : QQuickGraphsItem(parent),
+ m_barsController(0),
+ m_cachedRowCount(0),
+ m_cachedColumnCount(0),
+ m_minRow(0),
+ m_maxRow(0),
+ m_minCol(0),
+ m_maxCol(0),
+ m_newRows(0),
+ m_newCols(0),
+ m_maxSceneSize(40.0f),
+ m_rowWidth(0),
+ m_columnDepth(0),
+ m_maxDimension(0),
+ m_scaleFactor(0),
+ m_xScaleFactor(1.0f),
+ m_zScaleFactor(1.0f),
+ m_cachedBarSeriesMargin(0.0f, 0.0f),
+ m_hasNegativeValues(false),
+ m_noZeroInRange(false),
+ m_actualFloorLevel(0.0f),
+ m_heightNormalizer(1.0f),
+ m_backgroundAdjustment(0.0f),
+ m_selectedBarSeries(0),
+ m_selectedBarCoord(Bars3DController::invalidSelectionPosition()),
+ m_selectedBarPos(0.0f, 0.0f, 0.0f),
+ m_keepSeriesUniform(false),
+ m_seriesScaleX(0.0f),
+ m_seriesScaleZ(0.0f),
+ m_seriesStep(0.0f),
+ m_seriesStart(0.0f),
+ m_zeroPosition(0.0f),
+ m_visibleSeriesCount(0)
+{
+ setAcceptedMouseButtons(Qt::AllButtons);
+ setFlags(ItemHasContents);
+ // Create the shared component on the main GUI thread.
+ m_barsController = new Bars3DController(boundingRect().toRect(), new Declarative3DScene);
+ setSharedController(m_barsController);
+
+ QQuick3DSceneEnvironment *scene = environment();
+ scene->setBackgroundMode(QQuick3DSceneEnvironment::QQuick3DEnvironmentBackgroundTypes::Color);
+ scene->setClearColor(Qt::blue);
+
+ QObject::connect(m_barsController, &Bars3DController::primarySeriesChanged,
+ this, &QQuickGraphsBars::primarySeriesChanged);
+ QObject::connect(m_barsController, &Bars3DController::selectedSeriesChanged,
+ this, &QQuickGraphsBars::selectedSeriesChanged);
+ QObject::connect(m_barsController, &Abstract3DController::optimizationHintsChanged,
+ this, &QQuickGraphsBars::handleOptimizationHintsChanged);
+}
+
+QQuickGraphsBars::~QQuickGraphsBars()
+{
+ QMutexLocker locker(m_nodeMutex.data());
+ const QMutexLocker locker2(mutex());
+ delete m_barsController;
+ m_barModelsMap.clear();
+}
+
+QCategory3DAxis *QQuickGraphsBars::rowAxis() const
+{
+ return static_cast<QCategory3DAxis *>(m_barsController->axisZ());
+}
+
+void QQuickGraphsBars::setRowAxis(QCategory3DAxis *axis)
+{
+ m_barsController->setAxisZ(axis);
+}
+
+QValue3DAxis *QQuickGraphsBars::valueAxis() const
+{
+ return static_cast<QValue3DAxis *>(m_barsController->axisY());
+}
+
+void QQuickGraphsBars::setValueAxis(QValue3DAxis *axis)
+{
+ m_barsController->setAxisY(axis);
+ if (segmentLineRepeaterY()) {
+ int segmentCount = 0;
+ int subSegmentCount = 0;
+ int gridLineCount = 0;
+ int subGridLineCount = 0;
+ if (axis->type() & QAbstract3DAxis::AxisTypeValue) {
+ QValue3DAxis *valueAxis = static_cast<QValue3DAxis *>(axis);
+ segmentCount = valueAxis->segmentCount();
+ subSegmentCount = valueAxis->subSegmentCount();
+ gridLineCount = 2 * (segmentCount + 1);
+ subGridLineCount = 2 * (segmentCount * (subSegmentCount - 1));
+ } else if (axis->type() & QAbstract3DAxis::AxisTypeCategory) {
+ gridLineCount = axis->labels().size();
+ }
+ segmentLineRepeaterY()->setModel(gridLineCount);
+ subsegmentLineRepeaterY()->setModel(subGridLineCount);
+ repeaterY()->setModel(2 * axis->labels().size());
+ }
+}
+
+QCategory3DAxis *QQuickGraphsBars::columnAxis() const
+{
+ return static_cast<QCategory3DAxis *>(m_barsController->axisX());
+}
+
+void QQuickGraphsBars::setColumnAxis(QCategory3DAxis *axis)
+{
+ m_barsController->setAxisX(axis);
+}
+
+void QQuickGraphsBars::setMultiSeriesUniform(bool uniform)
+{
+ if (uniform != isMultiSeriesUniform()) {
+ m_barsController->setMultiSeriesScaling(uniform);
+ emit multiSeriesUniformChanged(uniform);
+ }
+}
+
+bool QQuickGraphsBars::isMultiSeriesUniform() const
+{
+ return m_barsController->multiSeriesScaling();
+}
+
+void QQuickGraphsBars::setBarThickness(float thicknessRatio)
+{
+ if (thicknessRatio != barThickness()) {
+ m_barsController->setBarSpecs(thicknessRatio, barSpacing(),
+ isBarSpacingRelative());
+ emit barThicknessChanged(thicknessRatio);
+ }
+}
+
+float QQuickGraphsBars::barThickness() const
+{
+ return m_barsController->barThickness();
+}
+
+void QQuickGraphsBars::setBarSpacing(const QSizeF &spacing)
+{
+ if (spacing != barSpacing()) {
+ m_barsController->setBarSpecs(barThickness(), spacing, isBarSpacingRelative());
+ emit barSpacingChanged(spacing);
+ }
+}
+
+QSizeF QQuickGraphsBars::barSpacing() const
+{
+ return m_barsController->barSpacing();
+}
+
+void QQuickGraphsBars::setBarSpacingRelative(bool relative)
+{
+ if (relative != isBarSpacingRelative()) {
+ m_barsController->setBarSpecs(barThickness(), barSpacing(), relative);
+ emit barSpacingRelativeChanged(relative);
+ }
+}
+
+bool QQuickGraphsBars::isBarSpacingRelative() const
+{
+ return m_barsController->isBarSpecRelative();
+}
+
+void QQuickGraphsBars::setBarSeriesMargin(const QSizeF &margin)
+{
+ if (margin != barSeriesMargin()) {
+ m_barsController->setBarSeriesMargin(margin);
+ emit barSeriesMarginChanged(barSeriesMargin());
+ }
+}
+
+QSizeF QQuickGraphsBars::barSeriesMargin() const
+{
+ return m_barsController->barSeriesMargin();
+}
+
+QQmlListProperty<QBar3DSeries> QQuickGraphsBars::seriesList()
+{
+ return QQmlListProperty<QBar3DSeries>(this, this,
+ &QQuickGraphsBars::appendSeriesFunc,
+ &QQuickGraphsBars::countSeriesFunc,
+ &QQuickGraphsBars::atSeriesFunc,
+ &QQuickGraphsBars::clearSeriesFunc);
+}
+
+void QQuickGraphsBars::appendSeriesFunc(QQmlListProperty<QBar3DSeries> *list, QBar3DSeries *series)
+{
+ reinterpret_cast<QQuickGraphsBars *>(list->data)->addSeries(series);
+}
+
+qsizetype QQuickGraphsBars::countSeriesFunc(QQmlListProperty<QBar3DSeries> *list)
+{
+ return reinterpret_cast<QQuickGraphsBars *>(list->data)->m_barsController->barSeriesList().size();
+}
+
+QBar3DSeries *QQuickGraphsBars::atSeriesFunc(QQmlListProperty<QBar3DSeries> *list, qsizetype index)
+{
+ return reinterpret_cast<QQuickGraphsBars *>(list->data)->m_barsController->barSeriesList().at(index);
+}
+
+void QQuickGraphsBars::clearSeriesFunc(QQmlListProperty<QBar3DSeries> *list)
+{
+ QQuickGraphsBars *declBars = reinterpret_cast<QQuickGraphsBars *>(list->data);
+ QList<QBar3DSeries *> realList = declBars->m_barsController->barSeriesList();
+ int count = realList.size();
+ for (int i = 0; i < count; i++)
+ declBars->removeSeries(realList.at(i));
+}
+
+void QQuickGraphsBars::addSeries(QBar3DSeries *series)
+{
+ m_barsController->addSeries(series);
+ connectSeries(series);
+ if (series->selectedBar() != invalidSelectionPosition())
+ updateSelectedBar();
+}
+
+void QQuickGraphsBars::removeSeries(QBar3DSeries *series)
+{
+ m_barsController->removeSeries(series);
+ series->setParent(this); // Reparent as removing will leave series parentless
+}
+
+void QQuickGraphsBars::insertSeries(int index, QBar3DSeries *series)
+{
+ m_barsController->insertSeries(index, series);
+}
+
+void QQuickGraphsBars::setPrimarySeries(QBar3DSeries *series)
+{
+ m_barsController->setPrimarySeries(series);
+}
+
+QBar3DSeries *QQuickGraphsBars::primarySeries() const
+{
+ return m_barsController->primarySeries();
+}
+
+QBar3DSeries *QQuickGraphsBars::selectedSeries() const
+{
+ return m_barsController->selectedSeries();
+}
+
+void QQuickGraphsBars::setFloorLevel(float level)
+{
+ if (level != floorLevel()) {
+ m_barsController->setFloorLevel(level);
+ emit floorLevelChanged(level);
+ }
+}
+
+float QQuickGraphsBars::floorLevel() const
+{
+ return m_barsController->floorLevel();
+}
+
+void QQuickGraphsBars::componentComplete()
+{
+ QQuickGraphsItem::componentComplete();
+
+ auto wallBackground = background();
+ QUrl wallUrl = QUrl(QStringLiteral("defaultMeshes/backgroundNoFloorMesh"));
+ wallBackground->setSource(wallUrl);
+ setBackground(wallBackground);
+
+ QUrl floorUrl = QUrl(QStringLiteral(":/defaultMeshes/planeMesh"));
+ m_floorBackground = new QQuick3DModel();
+ m_floorBackgroundScale = new QQuick3DNode();
+ m_floorBackgroundRotation = new QQuick3DNode();
+
+ m_floorBackgroundScale->setParent(rootNode());
+ m_floorBackgroundScale->setParentItem(rootNode());
+
+ m_floorBackgroundRotation->setParent(m_floorBackgroundScale);
+ m_floorBackgroundRotation->setParentItem(m_floorBackgroundScale);
+
+ m_floorBackground->setObjectName("Floor Background");
+ m_floorBackground->setParent(m_floorBackgroundRotation);
+ m_floorBackground->setParentItem(m_floorBackgroundRotation);
+
+ m_floorBackground->setSource(floorUrl);
+
+ QValue3DAxis *axisY = static_cast<QValue3DAxis *>(m_barsController->axisY());
+ m_helperAxisY.setFormatter(axisY->formatter());
+
+ setFloorGridInRange(true);
+ setVerticalSegmentLine(false);
+}
+
+void QQuickGraphsBars::synchData()
+{
+ if (!m_noZeroInRange) {
+ m_barsController->m_scene->activeCamera()->d_ptr->setMinYRotation(-90.0f);
+ m_barsController->m_scene->activeCamera()->d_ptr->setMaxYRotation(90.0f);
+ } else {
+ if ((m_hasNegativeValues && !m_helperAxisY.isReversed())
+ || (!m_hasNegativeValues && m_helperAxisY.isReversed())) {
+ m_barsController->m_scene->activeCamera()->d_ptr->setMinYRotation(-90.0f);
+ m_barsController->m_scene->activeCamera()->d_ptr->setMaxYRotation(0.0f);
+ } else {
+ m_barsController->m_scene->activeCamera()->d_ptr->setMinYRotation(0.0f);
+ m_barsController->m_scene->activeCamera()->d_ptr->setMaxYRotation(90.0f);
+ }
+ }
+ if (m_barsController->m_changeTracker.barSpecsChanged || !m_cachedBarThickness.isValid()) {
+ updateBarSpecs(m_barsController->m_barThicknessRatio, m_barsController->m_barSpacing,
+ m_barsController->m_isBarSpecRelative);
+ m_barsController->m_changeTracker.barSpecsChanged = false;
+ }
+
+ // Floor level update requires data update, so do before abstract sync
+ if (m_barsController->m_changeTracker.floorLevelChanged) {
+ updateFloorLevel(m_barsController->m_floorLevel);
+ m_barsController->m_changeTracker.floorLevelChanged = false;
+ }
+
+ if (m_barsController->m_changeTracker.barSeriesMarginChanged) {
+ updateBarSeriesMargin(barSeriesMargin());
+ m_barsController->m_changeTracker.barSeriesMarginChanged = false;
+ }
+
+ auto axisY = static_cast<QValue3DAxis *>(m_barsController->axisY());
+ axisY->formatter()->d_ptr->recalculate();
+ m_helperAxisY.setFormatter(axisY->formatter());
+
+ QQuickGraphsItem::synchData();
+
+ // Needs to be done after data is set, as it needs to know the visual array.
+ if (m_barsController->m_changeTracker.selectedBarChanged) {
+ if (m_barsController->m_selectedBar != m_selectedBarCoord || m_barsController->m_selectedBarSeries != m_selectedBarSeries)
+ setSelectedBar(m_barsController->m_selectedBarSeries, m_barsController->m_selectedBar);
+ updateSelectedBar();
+ m_barsController->m_changeTracker.selectedBarChanged = false;
+ }
+
+ QMatrix4x4 modelMatrix;
+
+ // Draw floor
+ m_floorBackground->setPickable(false);
+ m_floorBackgroundScale->setScale(scaleWithBackground());
+ modelMatrix.scale(scaleWithBackground());
+ m_floorBackgroundScale->setPosition(QVector3D(0.0f, -m_backgroundAdjustment, 0.0f));
+
+ QQuaternion m_xRightAngleRotation(QQuaternion::fromAxisAndAngle(1.0f, 0.0f, 0.0f, 90.0f));
+ QQuaternion m_xRightAngleRotationNeg(QQuaternion::fromAxisAndAngle(1.0f, 0.0f, 0.0f, -90.0f));
+
+ if (isYFlipped()) {
+ m_floorBackgroundRotation->setRotation(m_xRightAngleRotation);
+ modelMatrix.rotate(m_xRightAngleRotation);
+ } else {
+ m_floorBackgroundRotation->setRotation(m_xRightAngleRotationNeg);
+ modelMatrix.rotate(m_xRightAngleRotationNeg);
+ }
+
+ auto bgFloor = m_floorBackground;
+ bgFloor->setPickable(false);
+ QQmlListReference materialsRefF(bgFloor, "materials");
+ QQuick3DPrincipledMaterial * bgMatFloor;
+
+ if (!materialsRefF.size()) {
+ bgMatFloor = new QQuick3DPrincipledMaterial();
+ bgMatFloor->setParent(this);
+ bgMatFloor->setRoughness(.3f);
+ bgMatFloor->setEmissiveFactor(QVector3D(.075f, .075f, .075f));
+ materialsRefF.append(bgMatFloor);
+ } else {
+ bgMatFloor = static_cast<QQuick3DPrincipledMaterial *>(materialsRefF.at(0));
+ }
+ Q3DTheme *theme = m_barsController->activeTheme();
+ bgMatFloor->setBaseColor(theme->backgroundColor());
+
+ if (m_axisRangeChanged) {
+ updateGrid();
+ updateLabels();
+ m_axisRangeChanged = false;
+ }
+}
+
+void QQuickGraphsBars::updateParameters() {
+ m_minRow = m_barsController->m_axisZ->min();
+ m_maxRow = m_barsController->m_axisZ->max();
+ m_minCol = m_barsController->m_axisX->min();
+ m_maxCol = m_barsController->m_axisX->max();
+ m_newRows = m_maxRow - m_minRow + 1;
+ m_newCols = m_maxCol - m_minCol + 1;
+
+ if (m_cachedRowCount!= m_newRows || m_cachedColumnCount != m_newCols) {
+ // Force update for selection related items
+ if (isSliceEnabled() && m_barsController->isSlicingActive()) {
+ setSliceEnabled(false);
+ setSliceActivatedChanged(true);
+ }
+
+ m_cachedColumnCount = m_newCols;
+ m_cachedRowCount = m_newRows;
+
+ // Calculate max scene size
+ float sceneRatio = qMin(float(m_newCols) / float(m_newRows),
+ float(m_newRows) / float(m_newCols));
+ m_maxSceneSize = 2.0f * qSqrt(sceneRatio * m_newCols * m_newRows);
+
+ if (m_cachedBarThickness.isValid())
+ calculateSceneScalingFactors();
+ }
+
+ m_axisRangeChanged = true;
+ createSliceView();
+ update();
+}
+
+void QQuickGraphsBars::updateFloorLevel(float level)
+{
+ setFloorLevel(level);
+ calculateHeightAdjustment();
+}
+
+void QQuickGraphsBars::updateGraph()
+{
+ QList<QBar3DSeries *> barSeriesList = m_barsController->barSeriesList();
+ calculateSceneScalingFactors();
+
+ if (m_barsController->m_changedSeriesList.size()) {
+ for (auto series : m_barsController->barSeriesList()) {
+ if (m_barModelsMap.contains(series))
+ removeDataItems(series);
+ }
+ }
+ generateBars(barSeriesList);
+ int visualIndex = 0;
+ for (auto barSeries : m_barsController->barSeriesList()) {
+ if (barSeries->isVisible()) {
+ updateBarVisuality(barSeries, visualIndex);
+ updateBarPositions(barSeries);
+ updateBarVisuals(barSeries);
+ ++visualIndex;
+ }
+ else
+ updateBarVisuality(barSeries, -1);
+ }
+}
+
+void QQuickGraphsBars::updateAxisRange(float min, float max)
+{
+ QQuickGraphsItem::updateAxisRange(min, max);
+
+ m_helperAxisY.setMin(min);
+ m_helperAxisY.setMax(max);
+
+ calculateHeightAdjustment();
+}
+
+void QQuickGraphsBars::updateAxisReversed(bool enable)
+{
+ m_helperAxisY.setReversed(enable);
+ calculateHeightAdjustment();
+}
+
+void QQuickGraphsBars::calculateSceneScalingFactors()
+{
+ m_rowWidth = (m_cachedColumnCount * m_cachedBarSpacing.width()) * 0.5f;
+ m_columnDepth = (m_cachedRowCount * m_cachedBarSpacing.height()) * 0.5f;
+ m_maxDimension = qMax(m_rowWidth, m_columnDepth);
+ m_scaleFactor = qMin((m_cachedColumnCount *(m_maxDimension / m_maxSceneSize)),
+ (m_cachedRowCount * (m_maxDimension / m_maxSceneSize)));
+
+ // Single bar scaling
+ m_xScale = m_cachedBarThickness.width() / m_scaleFactor;
+ m_zScale = m_cachedBarThickness.height() / m_scaleFactor;
+
+ // Adjust scaling according to margin
+ m_xScale = m_xScale - m_xScale * m_cachedBarSeriesMargin.width();
+ m_zScale = m_zScale - m_zScale * m_cachedBarSeriesMargin.height();
+
+ // Whole graph scale factors
+ m_xScaleFactor = m_rowWidth / m_scaleFactor;
+ m_zScaleFactor = m_columnDepth / m_scaleFactor;
+
+ if (m_requestedMargin < 0.0f) {
+ m_hBackgroundMargin = 0.0f;
+ m_vBackgroundMargin = 0.0f;
+ } else {
+ m_hBackgroundMargin = m_requestedMargin;
+ m_vBackgroundMargin = m_requestedMargin;
+ }
+
+ m_scaleXWithBackground = m_xScaleFactor + m_hBackgroundMargin;
+ m_scaleYWithBackground = 1.0f + m_vBackgroundMargin;
+ m_scaleZWithBackground = m_zScaleFactor + m_hBackgroundMargin;
+
+ auto scale = QVector3D(m_xScaleFactor, 1.0f, m_zScaleFactor);
+ setScaleWithBackground(scale);
+ setBackgroundScaleMargin({m_hBackgroundMargin, m_vBackgroundMargin, m_hBackgroundMargin});
+ setScale(scale);
+
+ m_helperAxisX.setScale(m_scaleXWithBackground * 2);
+ m_helperAxisY.setScale(m_yScale);
+ m_helperAxisZ.setScale(-m_scaleZWithBackground * 2);
+ m_helperAxisX.setTranslate(-m_xScale);
+ m_helperAxisY.setTranslate(0.0f);
+}
+
+void QQuickGraphsBars::calculateHeightAdjustment()
+{
+ m_minHeight = m_helperAxisY.min();
+ m_maxHeight = m_helperAxisY.max();
+ float newAdjustment = 1.0f;
+ m_actualFloorLevel = qBound(m_minHeight, floorLevel(), m_maxHeight);
+ float maxAbs = qFabs(m_maxHeight - m_actualFloorLevel);
+
+ // Check if we have negative values
+ if (m_minHeight < m_actualFloorLevel)
+ m_hasNegativeValues = true;
+ else if (m_minHeight >= m_actualFloorLevel)
+ m_hasNegativeValues = false;
+
+ if (m_maxHeight < m_actualFloorLevel) {
+ m_heightNormalizer = float(qFabs(m_minHeight) - qFabs(m_maxHeight));
+ maxAbs = qFabs(m_maxHeight) - qFabs(m_minHeight);
+ } else {
+ m_heightNormalizer = float(m_maxHeight - m_minHeight);
+ }
+
+ // Height fractions are used in gradient calculations and are therefore doubled
+ // Note that if max or min is exactly zero, we still consider it outside the range
+ if (m_maxHeight <= m_actualFloorLevel || m_minHeight >= m_actualFloorLevel) {
+ m_noZeroInRange = true;
+ m_gradientFraction = 2.0f;
+ } else {
+ m_noZeroInRange = false;
+ float minAbs = qFabs(m_minHeight - m_actualFloorLevel);
+ m_gradientFraction = qMax(minAbs, maxAbs) / m_heightNormalizer * 2.0f;
+ }
+
+ // Calculate translation adjustment for background floor
+ newAdjustment = (qBound(0.0f, (maxAbs / m_heightNormalizer), 1.0f) - 0.5f) * 2.0f;
+ if (m_helperAxisY.isReversed())
+ newAdjustment = -newAdjustment;
+
+ if (newAdjustment != m_backgroundAdjustment)
+ m_backgroundAdjustment = newAdjustment;
+}
+
+void QQuickGraphsBars::calculateSeriesStartPosition()
+{
+ m_seriesStart = -((float(m_visibleSeriesCount) - 1.0f) * 0.5f)
+ * (m_seriesStep - (m_seriesStep * m_cachedBarSeriesMargin.width()));
+}
+
+QVector3D QQuickGraphsBars::calculateCategoryLabelPosition(QAbstract3DAxis *axis,
+ QVector3D labelPosition, int index)
+{
+ QVector3D ret = labelPosition;
+ if (axis->orientation() == QAbstract3DAxis::AxisOrientationX) {
+ float xPos = (index + 0.5f) * m_cachedBarSpacing.width();
+ ret.setX((xPos - m_rowWidth) / m_scaleFactor);
+ }
+ if (axis->orientation() == QAbstract3DAxis::AxisOrientationZ) {
+ float zPos = (index + 0.5f) * m_cachedBarSpacing.height();
+ ret.setZ((m_columnDepth - zPos) / m_scaleFactor);
+ }
+ ret.setY(-m_backgroundAdjustment);
+ return ret;
+}
+
+float QQuickGraphsBars::calculateCategoryGridLinePosition(QAbstract3DAxis *axis, int index)
+{
+ float ret = 0.0f;
+ if (axis->orientation() == QAbstract3DAxis::AxisOrientationZ) {
+ float colPos = index * -(m_cachedBarSpacing.height() / m_scaleFactor);
+ ret = colPos + scale().z();
+ }
+ if (axis->orientation() == QAbstract3DAxis::AxisOrientationX) {
+ float rowPos = index * (m_cachedBarSpacing.width() / m_scaleFactor);
+ ret = rowPos - scale().x();
+ }
+ if (axis->orientation() == QAbstract3DAxis::AxisOrientationY)
+ ret = -m_backgroundAdjustment;
+ return ret;
+}
+
+void QQuickGraphsBars::handleAxisXChanged(QAbstract3DAxis *axis)
+{
+ emit columnAxisChanged(static_cast<QCategory3DAxis *>(axis));
+}
+
+void QQuickGraphsBars::handleAxisYChanged(QAbstract3DAxis *axis)
+{
+ emit valueAxisChanged(static_cast<QValue3DAxis *>(axis));
+}
+
+void QQuickGraphsBars::handleAxisZChanged(QAbstract3DAxis *axis)
+{
+ emit rowAxisChanged(static_cast<QCategory3DAxis *>(axis));
+}
+
+void QQuickGraphsBars::handleSeriesMeshChanged(QAbstract3DSeries::Mesh mesh)
+{
+ QList<QBar3DSeries *> barSeriesList = m_barsController->barSeriesList();
+ m_meshType = mesh;
+ if (m_barsController->optimizationHints() == QAbstract3DGraph::OptimizationDefault) {
+ for (auto series : m_barsController->barSeriesList()) {
+ if (m_barModelsMap.contains(series))
+ removeDataItems(series);
+ }
+ generateBars(barSeriesList);
+ } else if (m_barsController->optimizationHints() == QAbstract3DGraph::OptimizationStatic) {
+ resetClickedStatus();
+// m_instancingRootItem->setSource(QUrl(getMeshFileName()));
+ m_selectionIndicator->setSource(QUrl(getMeshFileName()));
+ m_barsController->markDataDirty();
+ m_barsController->markSeriesVisualsDirty();
+ generateBars(barSeriesList);
+ }
+}
+
+void QQuickGraphsBars::handleOptimizationHintsChanged(QAbstract3DGraph::OptimizationHints hints)
+{
+ Q_UNUSED(hints);
+// setup();
+}
+
+void QQuickGraphsBars::handleMeshSmoothChanged(bool enable)
+{
+ QList<QBar3DSeries *> barSeriesList = m_barsController->barSeriesList();
+ m_smooth = enable;
+
+ if (m_barsController->optimizationHints() == QAbstract3DGraph::OptimizationDefault) {
+ for (auto series : m_barsController->barSeriesList()) {
+ if (m_barModelsMap.contains(series))
+ removeDataItems(series);
+ }
+ generateBars(barSeriesList);
+ } else if (m_barsController->optimizationHints() == QAbstract3DGraph::OptimizationStatic) {
+ resetClickedStatus();
+// m_instancingRootItem->setSource(QUrl(getMeshFileName()));
+ m_selectionIndicator->setSource(QUrl(getMeshFileName()));
+ m_barsController->markDataDirty();
+ m_barsController->markSeriesVisualsDirty();
+ generateBars(barSeriesList);
+ }
+}
+
+void QQuickGraphsBars::handleRowCountChanged()
+{
+ QCategory3DAxis *categoryAxisZ = static_cast<QCategory3DAxis *>(m_barsController->axisZ());
+ segmentLineRepeaterZ()->setModel(categoryAxisZ->labels().size());
+ repeaterZ()->setModel(categoryAxisZ->labels().size());
+ updateParameters();
+}
+
+void QQuickGraphsBars::handleColCountChanged()
+{
+ QCategory3DAxis *categoryAxisX = static_cast<QCategory3DAxis *>(m_barsController->axisX());
+ segmentLineRepeaterX()->setModel(categoryAxisX->labels().size());
+ repeaterX()->setModel(categoryAxisX->labels().size());
+ updateParameters();
+}
+
+void QQuickGraphsBars::connectSeries(QBar3DSeries *series)
+{
+ m_meshType = series->mesh();
+ m_smooth = series->isMeshSmooth();
+
+ QObject::connect(series, &QBar3DSeries::meshChanged, this,
+ &QQuickGraphsBars::handleSeriesMeshChanged);
+ QObject::connect(series, &QBar3DSeries::meshSmoothChanged, this,
+ &QQuickGraphsBars::handleMeshSmoothChanged);
+ QObject::connect(series->dataProxy(), &QBarDataProxy::rowCountChanged, this,
+ &QQuickGraphsBars::handleRowCountChanged);
+ QObject::connect(series->dataProxy(), &QBarDataProxy::colCountChanged, this,
+ &QQuickGraphsBars::handleColCountChanged);
+}
+
+void QQuickGraphsBars::disconnectSeries(QBar3DSeries *series)
+{
+ QObject::disconnect(series, 0, this, 0);
+}
+
+void QQuickGraphsBars::generateBars(QList<QBar3DSeries *> &barSeriesList)
+{
+ int seriesCount = barSeriesList.size();
+ m_visibleSeriesCount = 0;
+ for (int i = 0; i < seriesCount; i++) {
+ QBar3DSeries *barSeries = static_cast<QBar3DSeries *>(barSeriesList[i]);
+ QVector<BarModel *> *barList = m_barModelsMap.value(barSeries);
+ if (!barList) {
+ barList = new QVector<BarModel *>;
+ m_barModelsMap[barSeries] = barList;
+ }
+ if (barList->isEmpty()) {
+ QQuick3DTexture *texture = createTexture();
+ texture->setParent(this);
+ auto gradient = barSeries->baseGradient();
+ auto textureData = static_cast<QuickGraphsTextureData *>(texture->textureData());
+ textureData->createGradient(gradient);
+
+ bool visible = barSeries->isVisible();
+ int minRow = m_barsController->m_axisZ->min();
+ int dataRowCount = 0;
+ int dataColCount = 0;
+
+ const QBarDataArray *array = barSeries->dataProxy()->array();
+ QBarDataProxy *dataProxy = barSeries->dataProxy();
+ dataRowCount = dataProxy->rowCount();
+ dataColCount = dataProxy->colCount();
+ int dataRowIndex = minRow;
+
+ while (dataRowIndex < dataRowCount) {
+ const QBarDataRow *dataRow = array->at(dataRowIndex);
+ Q_ASSERT(dataRow->size() == dataColCount);
+ for (int i = 0; i < dataColCount; i++) {
+ QBarDataItem *dataItem = const_cast <QBarDataItem *> (&(dataRow->at(i)));
+ auto scene = QQuick3DViewport::scene();
+ QQuick3DModel *model = createDataItem(scene);
+ model->setVisible(visible);
+
+ BarModel *barModel = new BarModel();
+ barModel->model = model;
+ barModel->barItem = dataItem;
+ barModel->coord = QPoint(dataRowIndex, i);
+ barModel->texture = texture;
+
+ if (!barList->contains(barModel))
+ barList->append(barModel);
+ }
+ ++dataRowIndex;
+ }
+ }
+ if (barSeries->isVisible())
+ m_visibleSeriesCount++;
+ }
+}
+
+QQuick3DModel *QQuickGraphsBars::createDataItem(QQuick3DNode *scene)
+{
+ auto model = new QQuick3DModel();
+ model->setParent(scene);
+ model->setParentItem(scene);
+ model->setObjectName(QStringLiteral("BarModel"));
+ QString fileName = getMeshFileName();
+ model->setSource(QUrl(fileName));
+ return model;
+}
+
+QString QQuickGraphsBars::getMeshFileName()
+{
+ QString fileName;
+ QString smoothString = QStringLiteral("Smooth");
+ switch (m_meshType) {
+ case QAbstract3DSeries::MeshSphere:
+ fileName = QStringLiteral("defaultMeshes/sphereMesh");
+ break;
+ case QAbstract3DSeries::MeshBar:
+ case QAbstract3DSeries::MeshCube:
+ fileName = QStringLiteral("defaultMeshes/barMesh");
+ break;
+ case QAbstract3DSeries::MeshPyramid:
+ fileName = QStringLiteral("defaultMeshes/pyramidMesh");
+ break;
+ case QAbstract3DSeries::MeshCone:
+ fileName = QStringLiteral("defaultMeshes/coneMesh");
+ break;
+ case QAbstract3DSeries::MeshCylinder:
+ fileName = QStringLiteral("defaultMeshes/cylinderMesh");
+ break;
+ case QAbstract3DSeries::MeshBevelBar:
+ case QAbstract3DSeries::MeshBevelCube:
+ fileName = QStringLiteral("defaultMeshes/bevelBarMesh");
+ break;
+ default:
+ fileName = QStringLiteral("defaultMeshes/sphereMesh");
+ }
+ if (m_smooth && m_meshType != QAbstract3DSeries::MeshPoint)
+ fileName += smoothString;
+
+ fixMeshFileName(fileName, m_meshType);
+
+ return fileName;
+}
+
+void QQuickGraphsBars::fixMeshFileName(QString &fileName, QAbstract3DSeries::Mesh meshType)
+{
+ if (!m_barsController->activeTheme()->isBackgroundEnabled()
+ && meshType != QAbstract3DSeries::MeshSphere) {
+ fileName.append(QStringLiteral("Full"));
+ }
+}
+
+void QQuickGraphsBars::updateBarVisuality(QBar3DSeries *series, int visualIndex)
+{
+ QVector<BarModel *> barList = *m_barModelsMap.value(series);
+ for (int i = 0; i < barList.count(); i++) {
+ if (barList.at(i)->model->visible() != series->isVisible() && isSliceEnabled()) {
+ setSliceEnabled(false);
+ setSliceActivatedChanged(true);
+ }
+ barList.at(i)->visualIndex = visualIndex;
+ barList.at(i)->model->setVisible(series->isVisible());
+ }
+ setSelectedBar(m_selectedBarSeries, m_selectedBarCoord);
+ itemLabel()->setVisible(false);
+}
+
+void QQuickGraphsBars::updateBarPositions(QBar3DSeries *series)
+{
+ QBarDataProxy *dataProxy = series->dataProxy();
+ int dataRowCount = 0;
+ int dataColCount = 0;
+
+
+ m_seriesScaleX = 1.0f / float(m_visibleSeriesCount);
+ m_seriesStep = 1.0f / float(m_visibleSeriesCount);
+ m_seriesStart = -((float(m_visibleSeriesCount) - 1.0f) * 0.5f)
+ * (m_seriesStep - (m_seriesStep * m_cachedBarSeriesMargin.width()));
+
+ if (m_keepSeriesUniform)
+ m_seriesScaleZ = m_seriesScaleX;
+ else
+ m_seriesScaleZ = 1.0f;
+
+ m_meshRotation = dataProxy->series()->meshRotation();
+ m_zeroPosition = m_helperAxisY.itemPositionAt(m_actualFloorLevel);
+
+ QVector<BarModel *> barList = *m_barModelsMap.value(series);
+ if (m_barsController->optimizationHints() == QAbstract3DGraph::OptimizationDefault) {
+ for (int i = 0; i < barList.count(); i++) {
+ QBarDataItem *item = const_cast<QBarDataItem *>((barList.at(i)->barItem));
+ QQuick3DModel *model = barList.at(i)->model;
+ float value = item->value();
+ float heightValue = m_helperAxisY.itemPositionAt(value);
+
+ if (m_noZeroInRange) {
+ if (m_hasNegativeValues) {
+ heightValue = -1.0f + heightValue;
+ if (heightValue > 0.0f)
+ heightValue = 0.0f;
+ } else {
+ if (heightValue < 0.0f)
+ heightValue = 0.0f;
+ }
+ } else {
+ heightValue -= m_zeroPosition;
+ }
+
+ if (m_helperAxisY.isReversed())
+ heightValue = -heightValue;
+
+ float angle = item->rotation();
+
+ if (angle) {
+ model->setRotation(
+ QQuaternion::fromAxisAndAngle(
+ upVector, angle));
+ } else {
+ model->setRotation(QQuaternion());
+ }
+
+ if (heightValue < 0.f) {
+ const QVector3D rot = model->eulerRotation();
+ model->setEulerRotation(QVector3D(-180.f, rot.y(), rot.z()));
+ }
+
+ float seriesPos = m_seriesStart + m_seriesStep
+ * (barList.at(i)->visualIndex - (barList.at(i)->visualIndex
+ * m_cachedBarSeriesMargin.width())) + 0.5f;
+
+
+ float colPos = (dataColCount + seriesPos) * m_cachedBarSpacing.width();
+ float xPos = (colPos - m_rowWidth) / m_scaleFactor;
+ float rowPos = (dataRowCount + 0.5f) * (m_cachedBarSpacing.height());
+ float zPos = (m_columnDepth - rowPos) / m_scaleFactor;
+
+ barList.at(i)->heightValue = heightValue;
+ model->setPosition(QVector3D(xPos, heightValue - m_backgroundAdjustment, zPos));
+ model->setScale(QVector3D(m_xScale * m_seriesScaleX, qAbs(heightValue),
+ m_zScale * m_seriesScaleZ));
+
+ if (heightValue == 0) {
+ model->setPickable(false);
+ model->setVisible(false);
+ } else {
+ model->setPickable(true);
+ }
+
+ if (dataColCount < dataProxy->colCount() - 1) {
+ ++dataColCount;
+ } else {
+ dataColCount = 0;
+ if (dataRowCount < dataProxy->rowCount() - 1)
+ ++dataRowCount;
+ else
+ dataRowCount = 0;
+ }
+ }
+ }
+}
+
+void QQuickGraphsBars::updateBarVisuals(QBar3DSeries *series)
+{
+ QVector<BarModel *> barList = *m_barModelsMap.value(series);
+ bool useGradient = series->d_ptr->isUsingGradient();
+
+ if (useGradient) {
+ if (!m_hasHighlightTexture) {
+ m_highlightTexture = createTexture();
+ m_highlightTexture->setParent(this);
+ m_hasHighlightTexture = true;
+ }
+ auto highlightGradient = series->singleHighlightGradient();
+ auto highlightTextureData = static_cast<QuickGraphsTextureData *>(m_highlightTexture->textureData());
+ highlightTextureData->createGradient(highlightGradient);
+ } else {
+ if (m_hasHighlightTexture) {
+ m_highlightTexture->deleteLater();
+ m_hasHighlightTexture = false;
+ }
+ }
+
+ bool rangeGradient = (useGradient && series->d_ptr->m_colorStyle == Q3DTheme::ColorStyleRangeGradient)
+ ? true : false;
+
+ if (m_barsController->optimizationHints() == QAbstract3DGraph::OptimizationDefault) {
+ if (!rangeGradient) {
+ for (int i = 0; i < barList.count(); i++) {
+ QQuick3DModel *model = barList.at(i)->model;
+ updateItemMaterial(model, useGradient, rangeGradient);
+ updatePrincipledMaterial(model, series->baseColor(), useGradient, false,
+ barList.at(i)->texture);
+ }
+ } else {
+ for (int i = 0; i < barList.count(); i++) {
+ QQuick3DModel *model = barList.at(i)->model;
+ updateItemMaterial(model, useGradient, rangeGradient);
+ updateCustomMaterial(model, barList.at(i)->texture);
+ }
+ }
+ }
+}
+
+void QQuickGraphsBars::updateItemMaterial(QQuick3DModel *item, bool useGradient, bool rangeGradient)
+{
+ Q_UNUSED(useGradient);
+ QQmlListReference materialsRef(item, "materials");
+ if (!rangeGradient) {
+ if (materialsRef.size()) {
+ if (!qobject_cast<QQuick3DPrincipledMaterial *>(materialsRef.at(0))) {
+ auto principledMaterial = new QQuick3DPrincipledMaterial();
+ principledMaterial->setParent(this);
+ auto oldCustomMaterial = materialsRef.at(0);
+ materialsRef.replace(0, principledMaterial);
+ delete oldCustomMaterial;
+ }
+ } else {
+ auto principledMaterial = new QQuick3DPrincipledMaterial();
+ principledMaterial->setParent(this);
+ materialsRef.append(principledMaterial);
+ }
+ } else {
+ if (materialsRef.size()) {
+ if (!qobject_cast<QQuick3DCustomMaterial *>(materialsRef.at(0))) {
+ auto customMaterial = createQmlCustomMaterial(QStringLiteral(":/materials/RangeGradientMaterial"));
+ auto oldPrincipledMaterial = materialsRef.at(0);
+ materialsRef.replace(0, customMaterial);
+ delete oldPrincipledMaterial;
+ }
+ } else {
+ auto customMaterial = createQmlCustomMaterial(QStringLiteral(":/materials/RangeGradientMaterial"));
+ materialsRef.append(customMaterial);
+ }
+ }
+}
+
+void QQuickGraphsBars::updateCustomMaterial(QQuick3DModel *item, bool isHighlight,
+ QQuick3DTexture *texture)
+{
+ QQmlListReference materialsRef(item, "materials");
+ auto customMaterial = static_cast<QQuick3DCustomMaterial *>(materialsRef.at(0));
+ QVariant textureInputAsVariant = customMaterial->property("custex");
+ QQuick3DShaderUtilsTextureInput *textureInput = textureInputAsVariant.value<QQuick3DShaderUtilsTextureInput *>();
+
+ if (!isHighlight)
+ textureInput->setTexture(texture);
+ else
+ textureInput->setTexture(m_highlightTexture);
+
+ float rangeGradientYScaler = 0.5f / m_yScale;
+ float value = (item->y() + m_yScale) * rangeGradientYScaler;
+ customMaterial->setProperty("gradientPos", value);
+}
+
+void QQuickGraphsBars::updatePrincipledMaterial(QQuick3DModel *model, const QColor &color,
+ bool useGradient, bool isHighlight,
+ QQuick3DTexture *texture)
+{
+ QQmlListReference materialsRef(model, "materials");
+ auto principledMaterial = static_cast<QQuick3DPrincipledMaterial *>(materialsRef.at(0));
+ principledMaterial->setParent(this);
+
+ if (useGradient) {
+ principledMaterial->setBaseColor(QColor(Qt::white));
+ if (!isHighlight)
+ principledMaterial->setBaseColorMap(texture);
+ else
+ principledMaterial->setBaseColorMap(m_highlightTexture);
+ } else {
+ principledMaterial->setBaseColor(color);
+ }
+}
+
+void QQuickGraphsBars::removeDataItems(QBar3DSeries *series)
+{
+ if (m_barModelsMap.value(series)->isEmpty())
+ return;
+ QVector<BarModel *> barList = *m_barModelsMap.value(series);
+ for (int i = 0; i < barList.count(); i++) {
+ barList.at(i)->model->setPickable(false);
+ barList.at(i)->model->setVisible(false);
+ QQmlListReference materialsRef(barList.at(i)->model, "materials");
+ if (materialsRef.size()) {
+ auto material = materialsRef.at(0);
+ delete material;
+ }
+ delete barList.at(i)->model;
+ }
+ m_barModelsMap.remove(series);
+ setSelectedBar(m_selectedBarSeries, m_selectedBarCoord);
+ itemLabel()->setVisible(false);
+}
+
+QQuick3DTexture *QQuickGraphsBars::createTexture()
+{
+ QQuick3DTexture *texture = new QQuick3DTexture();
+ texture->setParent(this);
+ texture->setRotationUV(-90.0f);
+ texture->setHorizontalTiling(QQuick3DTexture::ClampToEdge);
+ texture->setVerticalTiling(QQuick3DTexture::ClampToEdge);
+ QuickGraphsTextureData *textureData = new QuickGraphsTextureData();
+ textureData->setParent(texture);
+ textureData->setParentItem(texture);
+ texture->setTextureData(textureData);
+
+ return texture;
+}
+
+bool QQuickGraphsBars::handleMousePressedEvent(QMouseEvent *event)
+{
+ QQuickGraphsItem::handleMousePressedEvent(event);
+
+ if (Qt::LeftButton == event->button()) {
+ auto mousePos = event->pos();
+ QList<QQuick3DPickResult> pickResults = pickAll(mousePos.x(), mousePos.y());
+ auto selectionMode = m_barsController->selectionMode();
+ QQuick3DModel *selectedModel = nullptr;
+ if (!selectionMode.testFlag(QAbstract3DGraph::SelectionNone)) {
+ for (const auto &picked : std::as_const(pickResults)) {
+ if (picked.objectHit()->visible()) {
+ if (picked.objectHit() == backgroundBB() || picked.objectHit() == background()) {
+ resetClickedStatus();
+ continue;
+ } else if (picked.objectHit()->objectName().contains(QStringLiteral("BarModel"))) {
+ selectedModel = picked.objectHit();
+ break;
+ }
+ }
+ }
+
+ if (selectedModel) {
+ QBar3DSeries *series = 0;
+ QPoint coord = m_barsController->invalidSelectionPosition();
+ for (auto it = m_barModelsMap.begin(); it != m_barModelsMap.end(); it++) {
+ if (!it.key()->isVisible())
+ continue;
+ for (int i = 0; i < it.value()->count(); i++) {
+ QQuick3DModel *model = it.value()->at(i)->model;
+ if (model == selectedModel) {
+ series = it.key();
+ coord = it.value()->at(i)->coord;
+ }
+ }
+ }
+ setSelectedBar(series, coord);
+ } else {
+ resetClickedStatus();
+ }
+ }
+ }
+
+ return true;
+}
+
+void QQuickGraphsBars::setSelectedBar(QBar3DSeries *series, const QPoint &coord)
+{
+ if (!m_barModelsMap.contains(series))
+ series = 0;
+
+ if (coord != m_selectedBarCoord || series != m_selectedBarSeries) {
+ m_selectedBarSeries = series;
+ m_selectedBarCoord = coord;
+ if (isSliceEnabled()) {
+ m_barsController->setSlicingActive(true);
+ setSliceActivatedChanged(true);
+ }
+
+ // Clear selection from other series and finally set new selection to the specified series
+ for (auto it = m_barModelsMap.begin(); it != m_barModelsMap.end(); it++) {
+ if (it.key() != m_selectedBarSeries)
+ it.key()->dptr()->setSelectedBar(invalidSelectionPosition());
+ }
+ if (m_selectedBarSeries) {
+ m_selectedBarSeries->dptr()->setSelectedBar(m_selectedBarCoord);
+ m_barsController->setSelectedBar(m_selectedBarCoord, m_selectedBarSeries, false);
+ }
+ }
+}
+
+void QQuickGraphsBars::updateSelectedBar()
+{
+ bool visible = false;
+ if (m_selectedBarSeries) {
+ for (auto it = m_barModelsMap.begin(); it != m_barModelsMap.end(); it++) {
+ QVector<BarModel *> barList = *it.value();
+ for (int i = 0; i < barList.count(); i++) {
+ Bars3DController::SelectionType selectionType =
+ isSelected(barList.at(i)->coord.x(), barList.at(i)->coord.y(), it.key());
+ switch (selectionType) {
+ case Bars3DController::SelectionItem: {
+ updatePrincipledMaterial(barList.at(i)->model,
+ m_selectedBarSeries->singleHighlightColor(),
+ m_selectedBarSeries->d_ptr->isUsingGradient(), true,
+ barList.at(i)->texture);
+
+ m_selectedBarPos = barList.at(i)->model->position();
+ visible = m_selectedBarSeries->isVisible() && !m_selectedBarPos.isNull();
+ QString label = (m_selectedBarSeries->dptr()->itemLabel());
+
+ if (barList.at(i)->heightValue >= 0.0f) {
+ m_selectedBarPos.setY(m_selectedBarPos.y() + barList.at(i)->heightValue
+ + 0.2f);
+ } else {
+ m_selectedBarPos.setY(m_selectedBarPos.y() + barList.at(i)->heightValue
+ - 0.2f);
+ }
+
+ itemLabel()->setPosition(m_selectedBarPos);
+ itemLabel()->setProperty("labelText", label);
+ itemLabel()->setEulerRotation(
+ QVector3D(-m_barsController->scene()->activeCamera()->yRotation(),
+ -m_barsController->scene()->activeCamera()->xRotation(),
+ 0));
+
+ if (isSliceEnabled()) {
+ sliceItemLabel()->setPosition(QVector3D((m_selectedBarPos.x() + .05f),
+ (m_selectedBarPos.y() + .5f), 0.0f));
+ sliceItemLabel()->setScale(sliceItemLabel()->scale() / 1.5f);
+ sliceItemLabel()->setProperty("labelText", label);
+ sliceItemLabel()->setEulerRotation(QVector3D(0.0f, 0.0f, 90.0f));
+ sliceItemLabel()->setVisible(true);
+ }
+
+ break;
+ }
+ case Bars3DController::SelectionRow: {
+ updatePrincipledMaterial(barList.at(i)->model,
+ m_selectedBarSeries->multiHighlightColor(),
+ m_selectedBarSeries->d_ptr->isUsingGradient(), true,
+ barList.at(i)->texture);
+ break;
+ }
+ case Bars3DController::SelectionColumn: {
+ updatePrincipledMaterial(barList.at(i)->model,
+ m_selectedBarSeries->multiHighlightColor(),
+ m_selectedBarSeries->d_ptr->isUsingGradient(), true,
+ barList.at(i)->texture);
+ }
+ default:
+ break;
+ }
+ }
+ }
+ }
+ itemLabel()->setVisible(visible);
+}
+
+Abstract3DController::SelectionType QQuickGraphsBars::isSelected(int row, int bar,
+ QBar3DSeries *series)
+{
+ Bars3DController::SelectionType isSelectedType = Bars3DController::SelectionNone;
+ auto selectionMode = m_barsController->selectionMode();
+ if ((selectionMode.testFlag(QAbstract3DGraph::SelectionMultiSeries)
+ && m_selectedBarSeries) || series == m_selectedBarSeries) {
+ if (row == m_selectedBarCoord.x() && bar == m_selectedBarCoord.y()
+ && (selectionMode.testFlag(QAbstract3DGraph::SelectionItem))) {
+ isSelectedType = Bars3DController::SelectionItem;
+ } else if (row == m_selectedBarCoord.x()
+ && (selectionMode.testFlag(QAbstract3DGraph::SelectionRow))) {
+ isSelectedType = Bars3DController::SelectionRow;
+ } else if (bar == m_selectedBarCoord.y()
+ && (selectionMode.testFlag(QAbstract3DGraph::SelectionColumn))) {
+ isSelectedType = Bars3DController::SelectionColumn;
+ }
+ }
+
+ return isSelectedType;
+}
+
+void QQuickGraphsBars::resetClickedStatus()
+{
+ m_barsController->m_isSeriesVisualsDirty = true;
+ m_selectedBarPos = QVector3D(0.0f, 0.0f, 0.0f);
+ m_selectedBarCoord = Bars3DController::invalidSelectionPosition();
+ m_selectedBarSeries = 0;
+ m_barsController->clearSelection();
+}
+
+void QQuickGraphsBars::updateSliceGraph()
+{
+ QQuickGraphsItem::updateSliceGraph();
+
+ if (!sliceView()->isVisible()) {
+ if (!m_sliceViewBars.isEmpty()) {
+ for (int i = 0; i < m_sliceViewBars.count(); i++) {
+ m_sliceViewBars.at(i)->model->setPickable(false);
+ m_sliceViewBars.at(i)->model->setVisible(false);
+ QQmlListReference materialsRef(m_sliceViewBars.at(i)->model, "materials");
+ if (materialsRef.size()) {
+ auto material = materialsRef.at(0);
+ delete material;
+ }
+ delete m_sliceViewBars.at(i)->model;
+ }
+ m_sliceViewBars.clear();
+ }
+ return;
+ }
+
+ auto selectionMode = m_barsController->selectionMode();
+ QVector<BarModel *> barList = *m_barModelsMap.value(m_selectedBarSeries);
+ if (selectionMode.testFlag(QAbstract3DGraph::SelectionRow)) {
+ for (int col = 0; col < m_selectedBarSeries->dataProxy()->colCount(); col++) {
+ auto index = (m_selectedBarCoord.x() * m_selectedBarSeries->dataProxy()->colCount()) + col;
+
+ QQuick3DViewport *sliceParent = sliceView();
+ QQuick3DModel *model = createDataItem(sliceParent->scene());
+
+ BarModel *barModel = new BarModel();
+ barModel->model = model;
+ barModel->model->setVisible(sliceView()->isVisible());
+
+ QQuick3DTexture *texture = createTexture();
+ texture->setParent(barModel->model);
+ texture->setParentItem(barModel->model);
+ auto gradient = m_selectedBarSeries->baseGradient();
+ auto textureData = static_cast<QuickGraphsTextureData *>(texture->textureData());
+ textureData->createGradient(gradient);
+
+ barModel->texture = texture;
+ barModel->barItem = barList.at(index)->barItem;
+ barModel->coord = barList.at(index)->coord;
+ barModel->visualIndex = barList.at(index)->visualIndex;
+ barModel->heightValue = barList.at(index)->heightValue;
+
+ barModel->model->setPosition(QVector3D(barList.at(index)->model->x(),
+ barList.at(index)->model->y(), 0.0f));
+ barModel->model->setScale(barList.at(index)->model->scale());
+
+ bool useGradient = m_selectedBarSeries->d_ptr->isUsingGradient();
+ bool rangeGradient = (useGradient && m_selectedBarSeries->d_ptr->m_colorStyle ==
+ Q3DTheme::ColorStyleRangeGradient) ? true : false;
+
+ updateItemMaterial(barModel->model, useGradient, rangeGradient);
+ updatePrincipledMaterial(barModel->model,
+ m_selectedBarSeries->baseColor(),
+ m_selectedBarSeries->d_ptr->isUsingGradient(), false,
+ barModel->texture);
+
+ m_sliceViewBars.append(barModel);
+ }
+ }
+}
+
+void QQuickGraphsBars::updateBarSpecs(float thicknessRatio, const QSizeF &spacing, bool relative)
+{
+ // Convert ratio to QSizeF, as we need it in that format for autoscaling calculations
+ m_cachedBarThickness.setWidth(1.0);
+ m_cachedBarThickness.setHeight(1.0f / thicknessRatio);
+
+ if (relative) {
+ m_cachedBarSpacing.setWidth((m_cachedBarThickness.width() * 2)
+ * (spacing.width() + 1.0f));
+ m_cachedBarSpacing.setHeight((m_cachedBarThickness.height() * 2)
+ * (spacing.height() + 1.0f));
+ } else {
+ m_cachedBarSpacing = m_cachedBarThickness * 2 + spacing * 2;
+ }
+
+ m_axisRangeChanged = true;
+ // Slice mode doesn't update correctly without this
+ if (isSliceEnabled() && m_barsController->isSlicingActive()) {
+ setSliceEnabled(false);
+ setSliceActivatedChanged(true);
+ }
+
+ // Calculate here and at setting sample space
+ calculateSceneScalingFactors();
+}
+
+void QQuickGraphsBars::updateBarSeriesMargin(const QSizeF &margin)
+{
+ m_cachedBarSeriesMargin = margin;
+ calculateSeriesStartPosition();
+ calculateSceneScalingFactors();
+ m_barsController->m_isSeriesVisualsDirty = true;
+}
diff --git a/src/graphs/qml/qquickgraphsbars_p.h b/src/graphs/qml/qquickgraphsbars_p.h
new file mode 100644
index 0000000..0d64144
--- /dev/null
+++ b/src/graphs/qml/qquickgraphsbars_p.h
@@ -0,0 +1,248 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QtGraphs API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#ifndef QQUICKGRAPHSBARS_H
+#define QQUICKGRAPHSBARS_H
+
+#include "qbar3dseries.h"
+#include "qquickgraphsitem_p.h"
+#include "bars3dcontroller_p.h"
+
+#include <QtQuick3D/private/qquick3dmaterial_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class Q3DBars;
+
+class QQuickGraphsBars : public QQuickGraphsItem
+{
+ Q_OBJECT
+ Q_PROPERTY(QCategory3DAxis *rowAxis READ rowAxis WRITE setRowAxis NOTIFY rowAxisChanged)
+ Q_PROPERTY(QValue3DAxis *valueAxis READ valueAxis WRITE setValueAxis NOTIFY valueAxisChanged)
+ Q_PROPERTY(QCategory3DAxis *columnAxis READ columnAxis WRITE setColumnAxis NOTIFY columnAxisChanged)
+ Q_PROPERTY(bool multiSeriesUniform READ isMultiSeriesUniform WRITE setMultiSeriesUniform NOTIFY multiSeriesUniformChanged)
+ Q_PROPERTY(float barThickness READ barThickness WRITE setBarThickness NOTIFY barThicknessChanged)
+ Q_PROPERTY(QSizeF barSpacing READ barSpacing WRITE setBarSpacing NOTIFY barSpacingChanged)
+ Q_PROPERTY(bool barSpacingRelative READ isBarSpacingRelative WRITE setBarSpacingRelative NOTIFY barSpacingRelativeChanged)
+ Q_PROPERTY(QSizeF barSeriesMargin READ barSeriesMargin WRITE setBarSeriesMargin NOTIFY barSeriesMarginChanged)
+ Q_PROPERTY(QQmlListProperty<QBar3DSeries> seriesList READ seriesList CONSTANT)
+ Q_PROPERTY(QBar3DSeries *selectedSeries READ selectedSeries NOTIFY selectedSeriesChanged)
+ Q_PROPERTY(QBar3DSeries *primarySeries READ primarySeries WRITE setPrimarySeries NOTIFY primarySeriesChanged)
+ Q_PROPERTY(float floorLevel READ floorLevel WRITE setFloorLevel NOTIFY floorLevelChanged)
+ Q_CLASSINFO("DefaultProperty", "seriesList")
+
+ QML_NAMED_ELEMENT(Bars3D)
+ QML_ADDED_IN_VERSION(6, 6)
+
+public:
+ explicit QQuickGraphsBars(QQuickItem *parent = 0);
+ ~QQuickGraphsBars();
+
+ QCategory3DAxis *rowAxis() const;
+ void setRowAxis(QCategory3DAxis *axis);
+ QValue3DAxis *valueAxis() const;
+ void setValueAxis(QValue3DAxis *axis);
+ QCategory3DAxis *columnAxis() const;
+ void setColumnAxis(QCategory3DAxis *axis);
+
+ void setMultiSeriesUniform(bool uniform);
+ bool isMultiSeriesUniform() const;
+
+ void setBarThickness(float thicknessRatio);
+ float barThickness() const;
+
+ void setBarSpacing(const QSizeF &spacing);
+ QSizeF barSpacing() const;
+
+ void setBarSpacingRelative(bool relative);
+ bool isBarSpacingRelative() const;
+
+ void setBarSeriesMargin(const QSizeF &margin);
+ QSizeF barSeriesMargin() const;
+
+ QQmlListProperty<QBar3DSeries> seriesList();
+ static void appendSeriesFunc(QQmlListProperty<QBar3DSeries> *list, QBar3DSeries *series);
+ static qsizetype countSeriesFunc(QQmlListProperty<QBar3DSeries> *list);
+ static QBar3DSeries *atSeriesFunc(QQmlListProperty<QBar3DSeries> *list, qsizetype index);
+ static void clearSeriesFunc(QQmlListProperty<QBar3DSeries> *list);
+ Q_INVOKABLE void addSeries(QBar3DSeries *series);
+ Q_INVOKABLE void removeSeries(QBar3DSeries *series);
+ Q_INVOKABLE void insertSeries(int index, QBar3DSeries *series);
+
+ void setPrimarySeries(QBar3DSeries *series);
+ QBar3DSeries *primarySeries() const;
+ QBar3DSeries *selectedSeries() const;
+ static inline QPoint invalidSelectionPosition(){ return QPoint(-1, -1); }
+
+ void setFloorLevel(float level);
+ float floorLevel() const;
+
+protected:
+ void componentComplete() override;
+ void synchData() override;
+ void updateParameters();
+ void updateFloorLevel(float level);
+ void updateGraph() override;
+ void updateAxisRange(float min, float max) override;
+ void updateAxisReversed(bool enable) override;
+ QVector3D calculateCategoryLabelPosition(QAbstract3DAxis *axis, QVector3D labelPosition,
+ int index) override;
+ float calculateCategoryGridLinePosition(QAbstract3DAxis *axis, int index) override;
+ bool handleMousePressedEvent(QMouseEvent *event) override;
+ void updateSliceGraph() override;
+
+public Q_SLOTS:
+ void handleAxisXChanged(QAbstract3DAxis *axis) override;
+ void handleAxisYChanged(QAbstract3DAxis *axis) override;
+ void handleAxisZChanged(QAbstract3DAxis *axis) override;
+ void handleSeriesMeshChanged(QAbstract3DSeries::Mesh mesh);
+ void handleOptimizationHintsChanged(QAbstract3DGraph::OptimizationHints hints);
+ void handleMeshSmoothChanged(bool enable);
+ void handleRowCountChanged();
+ void handleColCountChanged();
+
+Q_SIGNALS:
+ void rowAxisChanged(QCategory3DAxis *axis);
+ void valueAxisChanged(QValue3DAxis *axis);
+ void columnAxisChanged(QCategory3DAxis *axis);
+ void multiSeriesUniformChanged(bool uniform);
+ void barThicknessChanged(float thicknessRatio);
+ void barSpacingChanged(const QSizeF &spacing);
+ void barSpacingRelativeChanged(bool relative);
+ void barSeriesMarginChanged(const QSizeF &margin);
+ void meshFileNameChanged(const QString &filename);
+ void primarySeriesChanged(QBar3DSeries *series);
+ void selectedSeriesChanged(QBar3DSeries *series);
+ void floorLevelChanged(float level);
+
+private:
+ Bars3DController *m_barsController;
+
+ int m_cachedRowCount;
+ int m_cachedColumnCount;
+ int m_minRow;
+ int m_maxRow;
+ int m_minCol;
+ int m_maxCol;
+ int m_newRows;
+ int m_newCols;
+
+ float m_maxSceneSize;
+ float m_rowWidth;
+ float m_columnDepth;
+ float m_maxDimension;
+ float m_scaleFactor;
+ float m_xScaleFactor;
+ float m_zScaleFactor;
+
+ QSizeF m_cachedBarSeriesMargin;
+ QSizeF m_cachedBarThickness;
+ QSizeF m_cachedBarSpacing;
+
+ // Testing sketching
+ AxisHelper m_helperAxisX;
+ AxisHelper m_helperAxisY;
+ AxisHelper m_helperAxisZ;
+
+ float m_scaleXWithBackground = scaleWithBackground().x();
+ float m_scaleYWithBackground = scaleWithBackground().y();
+ float m_scaleZWithBackground = scaleWithBackground().z();
+ float m_xScale = scale().x();
+ float m_yScale = scale().y();
+ float m_zScale = scale().z();
+
+ float m_requestedMargin = -1.0f;
+ float m_vBackgroundMargin = 0.1f;
+ float m_hBackgroundMargin = 0.1f;
+
+ bool m_hasNegativeValues;
+ bool m_noZeroInRange;
+ float m_actualFloorLevel;
+ float m_heightNormalizer;
+ float m_gradientFraction;
+ float m_backgroundAdjustment;
+
+ float m_minHeight;
+ float m_maxHeight;
+
+ bool m_axisRangeChanged = false;
+
+ QQuick3DModel *m_floorBackground = nullptr;
+ QQuick3DNode *m_floorBackgroundScale = nullptr;
+ QQuick3DNode *m_floorBackgroundRotation = nullptr;
+
+ // Selected bar
+ QBar3DSeries *m_selectedBarSeries;
+ QPoint m_selectedBarCoord;
+ QVector3D m_selectedBarPos;
+
+ //Generate bars
+ struct BarModel
+ {
+ QQuick3DModel *model;
+ QBarDataItem *barItem;
+ QPoint coord;
+ int visualIndex;
+ float heightValue;
+ QQuick3DTexture *texture;
+ };
+ QHash<QBar3DSeries *, QVector<BarModel *> *> m_barModelsMap;
+ QAbstract3DSeries::Mesh m_meshType = QAbstract3DSeries::MeshSphere;
+ bool m_smooth = false;
+ bool m_keepSeriesUniform;
+ bool m_hasHighlightTexture = false;
+ bool m_selectionActive = false;
+ float m_seriesScaleX;
+ float m_seriesScaleZ;
+ float m_seriesStep;
+ float m_seriesStart;
+ float m_zeroPosition;
+ int m_visibleSeriesCount;
+ QQuaternion m_meshRotation;
+ QQuick3DTexture *m_highlightTexture = nullptr;
+ QQuick3DModel *m_selectionIndicator = nullptr;
+ QVector<BarModel *> m_sliceViewBars;
+
+ void calculateSceneScalingFactors();
+ void calculateHeightAdjustment();
+ void calculateSeriesStartPosition();
+ void connectSeries(QBar3DSeries *series);
+ void disconnectSeries(QBar3DSeries *series);
+ void generateBars(QList<QBar3DSeries *> &barSeriesList);
+ QQuick3DModel *createDataItem(QQuick3DNode *scene);
+ QString getMeshFileName();
+ void fixMeshFileName(QString &fileName, QAbstract3DSeries::Mesh meshType);
+ void updateBarVisuality(QBar3DSeries *series, int visualIndex);
+ void updateBarPositions(QBar3DSeries *series);
+ void updateBarVisuals(QBar3DSeries *series);
+ void updateItemMaterial(QQuick3DModel *item, bool useGradient, bool rangeGradient);
+ void updateCustomMaterial(QQuick3DModel *item, bool isHighlight = false,
+ QQuick3DTexture *texture = nullptr);
+ void updatePrincipledMaterial(QQuick3DModel *model, const QColor &color, bool useGradient,
+ bool isHighlight, QQuick3DTexture *texture);
+ void removeDataItems(QBar3DSeries *series);
+ QQuick3DTexture *createTexture();
+ void setSelectedBar(QBar3DSeries *series, const QPoint &coord);
+ void updateSelectedBar();
+ Abstract3DController::SelectionType isSelected(int row, int bar, QBar3DSeries *series);
+ void resetClickedStatus();
+
+ void updateBarSpecs(float thicknessRatio, const QSizeF &spacing, bool relative);
+ void updateBarSeriesMargin(const QSizeF &margin);
+
+ friend class Bars3DController;
+ friend class Q3DBars;
+};
+
+QT_END_NAMESPACE
+#endif // QQUICKGRAPHSBARS_H
diff --git a/src/graphs/qml/qquickgraphsitem.cpp b/src/graphs/qml/qquickgraphsitem.cpp
new file mode 100644
index 0000000..0517d0e
--- /dev/null
+++ b/src/graphs/qml/qquickgraphsitem.cpp
@@ -0,0 +1,2689 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qquickgraphsitem_p.h"
+#include "declarativetheme_p.h"
+#include "declarativescene_p.h"
+#include "qvalue3daxis_p.h"
+#include "qcategory3daxis_p.h"
+#include "q3dscene_p.h"
+#include "abstract3dcontroller_p.h"
+#include "utils_p.h"
+
+#include <QtGui/QGuiApplication>
+
+#include <QtQuick3D/private/qquick3dperspectivecamera_p.h>
+#include <QtQuick3D/private/qquick3dorthographiccamera_p.h>
+#include <QtQuick3D/private/qquick3dcustommaterial_p.h>
+#include <QtQuick3D/private/qquick3dprincipledmaterial_p.h>
+#include <QtQuick3D/private/qquick3ddirectionallight_p.h>
+#include <QtQuick3D/private/qquick3drepeater_p.h>
+#include <QtQuick/private/qquickitem_p.h>
+
+#if defined(Q_OS_IOS)
+#include <QtCore/QTimer>
+#endif
+
+#if defined(Q_OS_MACOS)
+#include <qpa/qplatformnativeinterface.h>
+#endif
+
+QT_BEGIN_NAMESPACE
+
+QQuickGraphsItem::QQuickGraphsItem(QQuickItem *parent) :
+ QQuick3DViewport(parent),
+ m_controller(0),
+ m_renderMode(RenderIndirect),
+ m_samples(0),
+ m_windowSamples(0),
+ m_initialisedSize(0, 0)
+{
+ m_nodeMutex = QSharedPointer<QMutex>::create();
+
+ auto sceneManager = QQuick3DObjectPrivate::get(rootNode())->sceneManager;
+ connect(sceneManager.data(), &QQuick3DSceneManager::windowChanged, this, &QQuickGraphsItem::handleWindowChanged);
+ // Set contents to false in case we are in qml designer to make component look nice
+ m_runningInDesigner = QGuiApplication::applicationDisplayName() == QLatin1String("Qml2Puppet");
+ setFlag(ItemHasContents/*, !m_runningInDesigner*/); // Is this relevant anymore?
+
+ // Accept touchevents
+ setAcceptTouchEvents(true);
+}
+
+QQuickGraphsItem::~QQuickGraphsItem()
+{
+ disconnect(this, 0, this, 0);
+ checkWindowList(0);
+
+ // Make sure not deleting locked mutex
+ QMutexLocker locker(&m_mutex);
+ locker.unlock();
+
+ m_nodeMutex.clear();
+}
+
+void QQuickGraphsItem::setRenderingMode(QQuickGraphsItem::RenderingMode mode)
+{
+ if (mode == m_renderMode)
+ return;
+
+ RenderingMode previousMode = m_renderMode;
+
+ m_renderMode = mode;
+
+ m_initialisedSize = QSize(0, 0);
+ setFlag(ItemHasContents/*, !m_runningInDesigner*/);
+
+ // TODO - Need to check if the mode is set properly
+ switch (mode) {
+ case RenderDirectToBackground:
+ // Intentional flowthrough
+ case RenderDirectToBackground_NoClear:
+ update();
+ setRenderMode(QQuick3DViewport::Underlay);
+ if (previousMode == RenderIndirect) {
+ checkWindowList(window());
+ setAntialiasing(m_windowSamples > 0);
+ if (m_windowSamples != m_samples)
+ emit msaaSamplesChanged(m_windowSamples);
+ }
+ break;
+ case RenderIndirect:
+ update();
+ setRenderMode(QQuick3DViewport::Offscreen);
+ break;
+ }
+
+ updateWindowParameters();
+
+ emit renderingModeChanged(mode);
+}
+
+QQuickGraphsItem::RenderingMode QQuickGraphsItem::renderingMode() const
+{
+ return m_renderMode;
+}
+
+void QQuickGraphsItem::keyPressEvent(QKeyEvent *ev)
+{
+ ev->ignore();
+ setFlag(ItemHasContents);
+ update();
+}
+
+bool QQuickGraphsItem::handleMousePressedEvent(QMouseEvent *event)
+{
+ if (Qt::LeftButton == event->button()) {
+ if (m_sliceEnabled && m_controller->isSlicingActive()) {
+ m_sliceEnabled = false;
+ m_sliceActivatedChanged = true;
+ return false;
+ }
+ auto selectionMode = m_controller->selectionMode();
+ if (selectionMode.testFlag(QAbstract3DGraph::SelectionSlice)
+ && (selectionMode.testFlag(QAbstract3DGraph::SelectionColumn)
+ != selectionMode.testFlag(QAbstract3DGraph::SelectionRow))) {
+ m_sliceEnabled = true;
+ }
+ }
+ return true;
+}
+
+void QQuickGraphsItem::handleThemeTypeChange()
+{
+}
+
+void QQuickGraphsItem::handleFpsChanged()
+{
+ int fps = renderStats()->fps();
+ emit currentFpsChanged(fps);
+}
+
+void QQuickGraphsItem::componentComplete()
+{
+ QQuick3DViewport::componentComplete();
+
+ auto url = QUrl(QStringLiteral("defaultMeshes/backgroundMesh"));
+ m_background = new QQuick3DModel();
+ m_backgroundScale = new QQuick3DNode();
+ m_backgroundRotation = new QQuick3DNode();
+
+ m_backgroundScale->setParent(rootNode());
+ m_backgroundScale->setParentItem(rootNode());
+
+ m_backgroundRotation->setParent(m_backgroundScale);
+ m_backgroundRotation->setParentItem(m_backgroundScale);
+
+ m_background->setObjectName("Background");
+ m_background->setParent(m_backgroundRotation);
+ m_background->setParentItem(m_backgroundRotation);
+
+ m_background->setSource(url);
+
+ m_backgroundBB = new QQuick3DModel();
+ m_backgroundBB->setObjectName("BackgroundBB");
+ m_backgroundBB->setParent(m_background);
+ m_backgroundBB->setParentItem(m_background);
+ m_backgroundBB->setSource(QUrl(QStringLiteral("defaultMeshes/barMeshFull")));
+ m_backgroundBB->setPickable(true);
+
+ setUpCamera();
+ setUpLight();
+
+ // Create repeaters for each axis X, Y, Z
+ m_repeaterX = createRepeater();
+ m_repeaterY = createRepeater();
+ m_repeaterZ = createRepeater();
+
+ auto delegateModelX = createRepeaterDelegateComponent(QStringLiteral(":/axis/AxisLabel"));
+ auto delegateModelY = createRepeaterDelegateComponent(QStringLiteral(":/axis/AxisLabel"));
+ auto delegateModelZ = createRepeaterDelegateComponent(QStringLiteral(":/axis/AxisLabel"));
+
+ m_repeaterX->setDelegate(delegateModelX);
+ m_repeaterY->setDelegate(delegateModelY);
+ m_repeaterZ->setDelegate(delegateModelZ);
+
+ // title labels for axes
+ m_titleLabelX = createTitleLabel();
+ m_titleLabelY = createTitleLabel();
+ m_titleLabelZ = createTitleLabel();
+
+ // Testing gridline
+
+ // X lines
+ m_segmentLineRepeaterX = createRepeater();
+
+ auto segmentLineDelegate = createRepeaterDelegateComponent(QStringLiteral(":/axis/GridLine"));
+ m_segmentLineRepeaterX->setDelegate(segmentLineDelegate);
+
+ m_subsegmentLineRepeaterX = createRepeater();
+ auto subsegmentLineDelegate = createRepeaterDelegateComponent(QStringLiteral(":/axis/GridLine"));
+ m_subsegmentLineRepeaterX->setDelegate(subsegmentLineDelegate);
+
+ // Y lines
+ m_segmentLineRepeaterY = createRepeater();
+ segmentLineDelegate = createRepeaterDelegateComponent(QStringLiteral(":/axis/GridLine"));
+ m_segmentLineRepeaterY->setDelegate(segmentLineDelegate);
+
+ m_subsegmentLineRepeaterY = createRepeater();
+ subsegmentLineDelegate = createRepeaterDelegateComponent(QStringLiteral(":/axis/GridLine"));
+ m_subsegmentLineRepeaterY->setDelegate(subsegmentLineDelegate);
+
+ // Z lines
+ m_segmentLineRepeaterZ = createRepeater();
+
+ segmentLineDelegate = createRepeaterDelegateComponent(QStringLiteral(":/axis/GridLine"));
+ m_segmentLineRepeaterZ->setDelegate(segmentLineDelegate);
+
+ m_subsegmentLineRepeaterZ = createRepeater();
+ subsegmentLineDelegate = createRepeaterDelegateComponent(QStringLiteral(":/axis/GridLine"));
+ m_subsegmentLineRepeaterZ->setDelegate(subsegmentLineDelegate);
+
+ m_itemLabel = createTitleLabel();
+
+ auto axis = m_controller->axisX();
+ int segmentCount = 0;
+ int subSegmentCount = 0;
+ int gridLineCount = 0;
+ int subGridLineCount = 0;
+ if (axis->type() & QAbstract3DAxis::AxisTypeValue) {
+ QValue3DAxis *valueAxis = static_cast<QValue3DAxis *>(axis);
+ segmentCount = valueAxis->segmentCount();
+ subSegmentCount = valueAxis->subSegmentCount();
+ gridLineCount = 2 * (segmentCount + 1);
+ subGridLineCount = 2 * (segmentCount * (subSegmentCount - 1));
+ } else if (axis->type() & QAbstract3DAxis::AxisTypeCategory) {
+ gridLineCount = axis->labels().size();
+ }
+ m_segmentLineRepeaterX->setModel(gridLineCount);
+ m_subsegmentLineRepeaterX->setModel(subGridLineCount);
+ m_repeaterX->setModel(axis->labels().size());
+ m_controller->handleAxisLabelsChangedBySender(m_controller->axisX());
+
+ axis = m_controller->axisY();
+ if (axis->type() & QAbstract3DAxis::AxisTypeValue) {
+ QValue3DAxis *valueAxis = static_cast<QValue3DAxis *>(axis);
+ segmentCount = valueAxis->segmentCount();
+ subSegmentCount = valueAxis->subSegmentCount();
+ gridLineCount = 2 * (segmentCount + 1);
+ subGridLineCount = 2 * (segmentCount * (subSegmentCount - 1));
+ } else if (axis->type() & QAbstract3DAxis::AxisTypeCategory) {
+ gridLineCount = axis->labels().size();
+ }
+ m_segmentLineRepeaterY->setModel(gridLineCount);
+ m_subsegmentLineRepeaterY->setModel(subGridLineCount);
+ m_repeaterY->setModel(2 * axis->labels().size());
+ m_controller->handleAxisLabelsChangedBySender(m_controller->axisY());
+
+ axis = m_controller->axisZ();
+ if (axis->type() & QAbstract3DAxis::AxisTypeValue) {
+ QValue3DAxis *valueAxis = static_cast<QValue3DAxis *>(axis);
+ segmentCount = valueAxis->segmentCount();
+ subSegmentCount = valueAxis->subSegmentCount();
+ gridLineCount = 2 * (segmentCount + 1);
+ subGridLineCount = 2 * (segmentCount * (subSegmentCount - 1));
+ } else if (axis->type() & QAbstract3DAxis::AxisTypeCategory) {
+ gridLineCount = axis->labels().size();
+ }
+ m_segmentLineRepeaterZ->setModel(gridLineCount);
+ m_subsegmentLineRepeaterZ->setModel(subGridLineCount);
+ m_repeaterZ->setModel(axis->labels().size());
+ m_controller->handleAxisLabelsChangedBySender(m_controller->axisZ());
+}
+
+QQuick3DDirectionalLight *QQuickGraphsItem::light() const
+{
+ return m_light;
+}
+
+void QQuickGraphsItem::setTheme(Q3DTheme *theme)
+{
+ m_controller->setActiveTheme(theme, isComponentComplete());
+}
+
+Q3DTheme *QQuickGraphsItem::theme() const
+{
+ return m_controller->activeTheme();
+}
+
+void QQuickGraphsItem::clearSelection()
+{
+ m_controller->clearSelection();
+}
+
+bool QQuickGraphsItem::hasSeries(QAbstract3DSeries *series)
+{
+ return m_controller->hasSeries(series);
+}
+
+void QQuickGraphsItem::setSelectionMode(SelectionFlags mode)
+{
+ int intmode = int(mode);
+ m_controller->setSelectionMode(QAbstract3DGraph::SelectionFlags(intmode));
+}
+
+QQuickGraphsItem::SelectionFlags QQuickGraphsItem::selectionMode() const
+{
+ int intmode = int(m_controller->selectionMode());
+ return SelectionFlags(intmode);
+}
+
+void QQuickGraphsItem::setShadowQuality(ShadowQuality quality)
+{
+ m_controller->setShadowQuality(QAbstract3DGraph::ShadowQuality(quality));
+}
+
+QQuickGraphsItem::ShadowQuality QQuickGraphsItem::shadowQuality() const
+{
+ return ShadowQuality(m_controller->shadowQuality());
+}
+
+bool QQuickGraphsItem::shadowsSupported() const
+{
+ return m_controller->shadowsSupported();
+}
+
+int QQuickGraphsItem::addCustomItem(QCustom3DItem *item)
+{
+ return m_controller->addCustomItem(item);
+}
+
+void QQuickGraphsItem::removeCustomItems()
+{
+ m_controller->deleteCustomItems();
+}
+
+void QQuickGraphsItem::removeCustomItem(QCustom3DItem *item)
+{
+ m_controller->deleteCustomItem(item);
+}
+
+void QQuickGraphsItem::removeCustomItemAt(const QVector3D &position)
+{
+ m_controller->deleteCustomItem(position);
+}
+
+void QQuickGraphsItem::releaseCustomItem(QCustom3DItem *item)
+{
+ m_controller->releaseCustomItem(item);
+}
+
+int QQuickGraphsItem::selectedLabelIndex() const
+{
+ return m_controller->selectedLabelIndex();
+}
+
+QAbstract3DAxis *QQuickGraphsItem::selectedAxis() const
+{
+ return m_controller->selectedAxis();
+}
+
+int QQuickGraphsItem::selectedCustomItemIndex() const
+{
+ return m_controller->selectedCustomItemIndex();
+}
+
+QCustom3DItem *QQuickGraphsItem::selectedCustomItem() const
+{
+ return m_controller->selectedCustomItem();
+}
+
+QQmlListProperty<QCustom3DItem> QQuickGraphsItem::customItemList()
+{
+ return QQmlListProperty<QCustom3DItem>(this, this,
+ &QQuickGraphsItem::appendCustomItemFunc,
+ &QQuickGraphsItem::countCustomItemFunc,
+ &QQuickGraphsItem::atCustomItemFunc,
+ &QQuickGraphsItem::clearCustomItemFunc);
+}
+
+void QQuickGraphsItem::appendCustomItemFunc(QQmlListProperty<QCustom3DItem> *list,
+ QCustom3DItem *item)
+{
+ QQuickGraphsItem *decl = reinterpret_cast<QQuickGraphsItem *>(list->data);
+ decl->addCustomItem(item);
+}
+
+qsizetype QQuickGraphsItem::countCustomItemFunc(QQmlListProperty<QCustom3DItem> *list)
+{
+ Q_UNUSED(list);
+ return 0;
+// return reinterpret_cast<QQuickGraphsItem *>(list->data)->m_controller->m_customItems.size();
+}
+
+QCustom3DItem *QQuickGraphsItem::atCustomItemFunc(QQmlListProperty<QCustom3DItem> *list,
+ qsizetype index)
+{
+ Q_UNUSED(list);
+ Q_UNUSED(index);
+ return new QCustom3DItem();
+// return reinterpret_cast<QQuickGraphsItem *>(list->data)->m_controller->m_customItems.at(index);
+}
+
+void QQuickGraphsItem::clearCustomItemFunc(QQmlListProperty<QCustom3DItem> *list)
+{
+ QQuickGraphsItem *decl = reinterpret_cast<QQuickGraphsItem *>(list->data);
+ decl->removeCustomItems();
+}
+
+void QQuickGraphsItem::setSharedController(Abstract3DController *controller)
+{
+ Q_ASSERT(controller);
+ m_controller = controller;
+ m_controller->m_qml = this;
+
+ if (!m_controller->isOpenGLES())
+ setMsaaSamples(4);
+
+ // Reset default theme, as the default C++ theme is Q3DTheme, not DeclarativeTheme3D.
+ DeclarativeTheme3D *defaultTheme = new DeclarativeTheme3D;
+ defaultTheme->d_ptr->setDefaultTheme(true);
+ defaultTheme->setType(Q3DTheme::ThemeQt);
+ m_controller->setActiveTheme(defaultTheme);
+
+ QObject::connect(m_controller.data(), &Abstract3DController::shadowQualityChanged, this,
+ &QQuickGraphsItem::handleShadowQualityChange);
+ QObject::connect(m_controller.data(), &Abstract3DController::activeInputHandlerChanged, this,
+ &QQuickGraphsItem::inputHandlerChanged);
+ QObject::connect(m_controller.data(), &Abstract3DController::activeThemeChanged, this,
+ &QQuickGraphsItem::themeChanged);
+ QObject::connect(m_controller.data(), &Abstract3DController::themeTypeChanged, this,
+ &QQuickGraphsItem::handleThemeTypeChange);
+ QObject::connect(m_controller.data(), &Abstract3DController::selectionModeChanged, this,
+ &QQuickGraphsItem::handleSelectionModeChange);
+ QObject::connect(m_controller.data(), &Abstract3DController::elementSelected, this,
+ &QQuickGraphsItem::handleSelectedElementChange);
+
+ QObject::connect(m_controller.data(), &Abstract3DController::axisXChanged, this,
+ &QQuickGraphsItem::handleAxisXChanged);
+ QObject::connect(m_controller.data(), &Abstract3DController::axisYChanged, this,
+ &QQuickGraphsItem::handleAxisYChanged);
+ QObject::connect(m_controller.data(), &Abstract3DController::axisZChanged, this,
+ &QQuickGraphsItem::handleAxisZChanged);
+
+ QObject::connect(m_controller.data(), &Abstract3DController::measureFpsChanged, this,
+ &QQuickGraphsItem::measureFpsChanged);
+// QObject::connect(m_controller.data(), &Abstract3DController::currentFpsChanged, this,
+// &QQuickGraphsItem::currentFpsChanged);
+
+ QObject::connect(m_controller.data(), &Abstract3DController::orthoProjectionChanged, this,
+ &QQuickGraphsItem::orthoProjectionChanged);
+
+ QObject::connect(m_controller.data(), &Abstract3DController::aspectRatioChanged, this,
+ &QQuickGraphsItem::aspectRatioChanged);
+ QObject::connect(m_controller.data(), &Abstract3DController::optimizationHintsChanged, this,
+ &QQuickGraphsItem::handleOptimizationHintChange);
+ QObject::connect(m_controller.data(), &Abstract3DController::polarChanged, this,
+ &QQuickGraphsItem::polarChanged);
+ QObject::connect(m_controller.data(), &Abstract3DController::radialLabelOffsetChanged, this,
+ &QQuickGraphsItem::radialLabelOffsetChanged);
+ QObject::connect(m_controller.data(), &Abstract3DController::horizontalAspectRatioChanged, this,
+ &QQuickGraphsItem::horizontalAspectRatioChanged);
+ QObject::connect(m_controller.data(), &Abstract3DController::reflectionChanged, this,
+ &QQuickGraphsItem::reflectionChanged);
+ QObject::connect(m_controller.data(), &Abstract3DController::reflectivityChanged, this,
+ &QQuickGraphsItem::reflectivityChanged);
+ QObject::connect(m_controller.data(), &Abstract3DController::localeChanged, this,
+ &QQuickGraphsItem::localeChanged);
+ QObject::connect(m_controller.data(), &Abstract3DController::queriedGraphPositionChanged, this,
+ &QQuickGraphsItem::queriedGraphPositionChanged);
+ QObject::connect(m_controller.data(), &Abstract3DController::marginChanged, this,
+ &QQuickGraphsItem::marginChanged);
+}
+
+void QQuickGraphsItem::synchData()
+{
+ m_controller->m_renderPending = false;
+
+ bool axisFormatterChanged = false;
+ if (m_controller->m_changeTracker.axisXFormatterChanged) {
+ m_controller->m_changeTracker.axisXFormatterChanged = false;
+ QAbstract3DAxis *axisX = m_controller->axisX();
+ if (axisX->type() & QAbstract3DAxis::AxisTypeValue) {
+ QValue3DAxis *valueAxisX = static_cast<QValue3DAxis *>(axisX);
+ valueAxisX->recalculate();
+ }
+ axisFormatterChanged = true;
+ }
+
+ if (m_controller->m_changeTracker.axisYFormatterChanged) {
+ m_controller->m_changeTracker.axisYFormatterChanged = false;
+ QAbstract3DAxis *axisY = m_controller->axisY();
+ if (axisY->type() & QAbstract3DAxis::AxisTypeValue) {
+ QValue3DAxis *valueAxisY = static_cast<QValue3DAxis *>(axisY);
+ valueAxisY->recalculate();
+ }
+ axisFormatterChanged = true;
+ }
+
+ if (m_controller->m_changeTracker.axisZFormatterChanged) {
+ m_controller->m_changeTracker.axisZFormatterChanged = false;
+ QAbstract3DAxis *axisZ = m_controller->axisZ();
+ if (axisZ->type() & QAbstract3DAxis::AxisTypeValue) {
+ QValue3DAxis *valueAxisZ = static_cast<QValue3DAxis *>(axisZ);
+ valueAxisZ->recalculate();
+ }
+ axisFormatterChanged = true;
+ }
+
+ if (m_controller->m_changeTracker.shadowQualityChanged) {
+ updateShadowQuality(shadowQuality());
+ m_controller->m_changeTracker.shadowQualityChanged = false;
+ }
+
+ if (m_controller->m_changeTracker.axisYRangeChanged) {
+ updateAxisRange(m_controller->m_axisY->min(), m_controller->m_axisY->max());
+ m_controller->m_changeTracker.axisYRangeChanged = false;
+ }
+
+ if (m_controller->m_changeTracker.axisYReversedChanged) {
+ m_controller->m_changeTracker.axisYReversedChanged = false;
+ if (m_controller->m_axisY->type() & QAbstract3DAxis::AxisTypeValue) {
+ QValue3DAxis *valueAxisY = static_cast<QValue3DAxis *>(m_controller->m_axisY);
+ updateAxisReversed(valueAxisY->reversed());
+ }
+ }
+
+ QVector3D forward = camera()->forward();
+ auto targetRotation = cameraTarget()->rotation();
+ bool viewFlipped = false;
+ if (m_yFlipped != (targetRotation.x() > 0)) {
+ m_yFlipped = (targetRotation.x() > 0);
+ viewFlipped = true;
+ }
+ if (m_xFlipped != (forward.x() > 0)) {
+ m_xFlipped = (forward.x() > 0);
+ viewFlipped = true;
+ }
+ if (m_zFlipped != (forward.z() >= 0)) {
+ m_zFlipped = (forward.z() >= 0);
+ viewFlipped = true;
+ }
+
+ if (axisFormatterChanged || viewFlipped) {
+ updateGrid();
+ updateLabels();
+ }
+
+ QMatrix4x4 modelMatrix;
+ m_backgroundScale->setScale(m_scaleWithBackground + m_backgroundScaleMargin);
+
+ QVector3D rotVec;
+ if (!m_yFlipped) {
+ rotVec = QVector3D(0, 270, 0);
+ if (m_xFlipped && m_zFlipped)
+ rotVec.setY(90);
+ else if (!m_xFlipped && m_zFlipped)
+ rotVec.setY(0);
+ else if (m_xFlipped && !m_zFlipped)
+ rotVec.setY(180);
+ } else {
+ rotVec = QVector3D(0, 180, 180);
+ if (m_xFlipped && m_zFlipped)
+ rotVec.setY(0);
+ else if (!m_xFlipped && m_zFlipped)
+ rotVec.setY(270);
+ else if (m_xFlipped && !m_zFlipped)
+ rotVec.setY(90);
+ }
+
+ auto rotation = Utils::calculateRotation(rotVec);
+ if (m_yFlipped) {
+ m_backgroundRotation->setRotation(rotation);
+ } else {
+ modelMatrix.rotate(rotation);
+ m_backgroundRotation->setRotation(rotation);
+ }
+
+ if (m_controller->graphPositionQueryPending())
+ graphPositionAt(m_controller->scene()->graphPositionQuery());
+
+ updateCamera();
+
+ Q3DTheme *theme = m_controller->activeTheme();
+
+ if (m_controller->m_changeTracker.themeChanged) {
+ environment()->setClearColor(theme->windowColor());
+
+ m_controller->m_changeTracker.themeChanged = false;
+ }
+
+ if (theme->d_ptr->m_dirtyBits.lightStrengthDirty) {
+ light()->setBrightness(theme->lightStrength() * .2f);
+ theme->d_ptr->m_dirtyBits.lightStrengthDirty = false;
+ }
+
+ if (theme->d_ptr->m_dirtyBits.ambientLightStrengthDirty) {
+ float ambientStrength = theme->ambientLightStrength();
+ QColor ambientColor = QColor::fromRgbF(ambientStrength, ambientStrength, ambientStrength);
+ light()->setAmbientColor(ambientColor);
+ theme->d_ptr->m_dirtyBits.ambientLightStrengthDirty = false;
+ }
+
+ if (theme->d_ptr->m_dirtyBits.lightColorDirty) {
+ light()->setColor(theme->lightColor());
+ theme->d_ptr->m_dirtyBits.lightColorDirty = false;
+ }
+
+ // label Adjustments
+ if (m_repeaterX->count() && m_repeaterZ->count()) {
+ if (theme->d_ptr->m_dirtyBits.labelBackgroundColorDirty) {
+ QColor labelBackgroundColor = theme->labelBackgroundColor();
+ changeLabelBackgroundColor(m_repeaterX, labelBackgroundColor);
+ changeLabelBackgroundColor(m_repeaterY, labelBackgroundColor);
+ changeLabelBackgroundColor(m_repeaterZ, labelBackgroundColor);
+ m_titleLabelX->setProperty("backgroundColor", labelBackgroundColor);
+ m_titleLabelY->setProperty("backgroundColor", labelBackgroundColor);
+ m_titleLabelZ->setProperty("backgroundColor", labelBackgroundColor);
+ m_itemLabel->setProperty("backgroundColor", labelBackgroundColor);
+
+ if (m_sliceView) {
+ changeLabelBackgroundColor(m_sliceHorizontalLabelRepeater, labelBackgroundColor);
+ changeLabelBackgroundColor(m_sliceVerticalLabelRepeater, labelBackgroundColor);
+ m_sliceItemLabel->setProperty("backgroundColor", labelBackgroundColor);
+ m_sliceHorizontalTitleLabel->setProperty("backgroundColor", labelBackgroundColor);
+ m_sliceVerticalTitleLabel->setProperty("backgroundColor", labelBackgroundColor);
+ }
+ theme->d_ptr->m_dirtyBits.labelBackgroundColorDirty = false;
+ }
+
+ if (theme->d_ptr->m_dirtyBits.labelBackgroundEnabledDirty) {
+ bool enabled = theme->isLabelBackgroundEnabled();
+ changeLabelBackgroundEnabled(m_repeaterX, enabled);
+ changeLabelBackgroundEnabled(m_repeaterY, enabled);
+ changeLabelBackgroundEnabled(m_repeaterZ, enabled);
+ m_titleLabelX->setProperty("backgroundEnabled", enabled);
+ m_titleLabelY->setProperty("backgroundEnabled", enabled);
+ m_titleLabelZ->setProperty("backgroundEnabled", enabled);
+ m_itemLabel->setProperty("backgroundEnabled", enabled);
+
+ if (m_sliceView) {
+ changeLabelBackgroundEnabled(m_sliceHorizontalLabelRepeater, enabled);
+ changeLabelBackgroundEnabled(m_sliceVerticalLabelRepeater, enabled);
+ m_sliceItemLabel->setProperty("backgroundEnabled", enabled);
+ m_sliceHorizontalTitleLabel->setProperty("backgroundEnabled", enabled);
+ m_sliceVerticalTitleLabel->setProperty("backgroundEnabled", enabled);
+ }
+ theme->d_ptr->m_dirtyBits.labelBackgroundEnabledDirty = false;
+ }
+
+ if (theme->d_ptr->m_dirtyBits.labelBorderEnabledDirty) {
+ bool enabled = theme->isLabelBorderEnabled();
+ changeLabelBorderEnabled(m_repeaterX, enabled);
+ changeLabelBorderEnabled(m_repeaterY, enabled);
+ changeLabelBorderEnabled(m_repeaterZ, enabled);
+ m_titleLabelX->setProperty("borderEnabled", enabled);
+ m_titleLabelY->setProperty("borderEnabled", enabled);
+ m_titleLabelZ->setProperty("borderEnabled", enabled);
+ m_itemLabel->setProperty("borderEnabled", enabled);
+
+ if (m_sliceView) {
+ changeLabelBorderEnabled(m_sliceHorizontalLabelRepeater, enabled);
+ changeLabelBorderEnabled(m_sliceVerticalLabelRepeater, enabled);
+ m_sliceItemLabel->setProperty("borderEnabled", enabled);
+ m_sliceHorizontalTitleLabel->setProperty("borderEnabled", enabled);
+ m_sliceVerticalTitleLabel->setProperty("borderEnabled", enabled);
+ }
+ theme->d_ptr->m_dirtyBits.labelBorderEnabledDirty = false;
+ }
+
+ if (theme->d_ptr->m_dirtyBits.labelTextColorDirty) {
+ QColor labelTextColor = theme->labelTextColor();
+ changeLabelTextColor(m_repeaterX, labelTextColor);
+ changeLabelTextColor(m_repeaterY, labelTextColor);
+ changeLabelTextColor(m_repeaterZ, labelTextColor);
+ m_titleLabelX->setProperty("labelTextColor", labelTextColor);
+ m_titleLabelY->setProperty("labelTextColor", labelTextColor);
+ m_titleLabelZ->setProperty("labelTextColor", labelTextColor);
+ m_itemLabel->setProperty("labelTextColor", labelTextColor);
+
+ if (m_sliceView) {
+ changeLabelTextColor(m_sliceHorizontalLabelRepeater, labelTextColor);
+ changeLabelTextColor(m_sliceVerticalLabelRepeater, labelTextColor);
+ m_sliceItemLabel->setProperty("labelTextColor", labelTextColor);
+ m_sliceHorizontalTitleLabel->setProperty("labelTextColor", labelTextColor);
+ m_sliceVerticalTitleLabel->setProperty("labelTextColor", labelTextColor);
+ }
+ theme->d_ptr->m_dirtyBits.labelTextColorDirty = false;
+ }
+
+ if (theme->d_ptr->m_dirtyBits.fontDirty) {
+ auto font = theme->font();
+ changeLabelFont(m_repeaterX, font);
+ changeLabelFont(m_repeaterY, font);
+ changeLabelFont(m_repeaterZ, font);
+ m_titleLabelX->setProperty("labelFont", font);
+ m_titleLabelY->setProperty("labelFont", font);
+ m_titleLabelZ->setProperty("labelFont", font);
+ m_itemLabel->setProperty("labelFont", font);
+ updateLabels();
+
+ if (m_sliceView) {
+ changeLabelFont(m_sliceHorizontalLabelRepeater, font);
+ changeLabelFont(m_sliceVerticalLabelRepeater, font);
+ m_sliceItemLabel->setProperty("labelFont", font);
+ m_sliceHorizontalTitleLabel->setProperty("labelFont", font);
+ m_sliceVerticalTitleLabel->setProperty("labelFont", font);
+ updateSliceLabels();
+ }
+ theme->d_ptr->m_dirtyBits.fontDirty = false;
+ }
+
+ // Grid and background adjustments
+ if (theme->d_ptr->m_dirtyBits.backgroundColorDirty) {
+ QQmlListReference materialsRef(m_background, "materials");
+ QQuick3DPrincipledMaterial *bgMat;
+ if (!materialsRef.size()) {
+ bgMat = new QQuick3DPrincipledMaterial();
+ bgMat->setParent(this);
+ bgMat->setRoughness(.3f);
+ bgMat->setEmissiveFactor(QVector3D(.075f, .075f, .075f));
+ materialsRef.append(bgMat);
+ } else {
+ bgMat = static_cast<QQuick3DPrincipledMaterial *>(materialsRef.at(0));
+ }
+ bgMat->setBaseColor(theme->backgroundColor());
+ theme->d_ptr->m_dirtyBits.backgroundColorDirty = false;
+ }
+
+ if (theme->d_ptr->m_dirtyBits.backgroundEnabledDirty) {
+ m_background->setVisible(theme->isBackgroundEnabled());
+ theme->d_ptr->m_dirtyBits.backgroundEnabledDirty = false;
+ }
+
+ if (theme->d_ptr->m_dirtyBits.gridEnabledDirty) {
+ bool enabled = theme->isGridEnabled();
+ m_segmentLineRepeaterX->setVisible(enabled);
+ m_segmentLineRepeaterY->setVisible(enabled);
+ m_segmentLineRepeaterZ->setVisible(enabled);
+
+ m_subsegmentLineRepeaterX->setVisible(enabled);
+ m_subsegmentLineRepeaterY->setVisible(enabled);
+ m_subsegmentLineRepeaterZ->setVisible(enabled);
+
+ if (m_sliceEnabled) {
+ m_sliceHorizontalGridRepeater->setVisible(enabled);
+ m_sliceVerticalGridRepeater->setVisible(enabled);
+ }
+ theme->d_ptr->m_dirtyBits.gridEnabledDirty = false;
+ }
+
+ if (theme->d_ptr->m_dirtyBits.gridLineColorDirty) {
+ QColor gridLineColor = theme->gridLineColor();
+ changeGridLineColor(m_segmentLineRepeaterX, gridLineColor);
+ changeGridLineColor(m_subsegmentLineRepeaterX, gridLineColor);
+ changeGridLineColor(m_segmentLineRepeaterY, gridLineColor);
+ changeGridLineColor(m_subsegmentLineRepeaterY, gridLineColor);
+ changeGridLineColor(m_segmentLineRepeaterZ, gridLineColor);
+ changeGridLineColor(m_subsegmentLineRepeaterZ, gridLineColor);
+ theme->d_ptr->m_dirtyBits.gridLineColorDirty = false;
+ }
+
+ if (theme->d_ptr->m_dirtyBits.singleHighlightColorDirty) {
+ updateSingleHighlightColor();
+ theme->d_ptr->m_dirtyBits.singleHighlightColorDirty = false;
+ }
+ }
+
+ // Other adjustments
+ if (theme->d_ptr->m_dirtyBits.windowColorDirty) {
+ environment()->setClearColor(theme->windowColor());
+ theme->d_ptr->m_dirtyBits.windowColorDirty = false;
+ }
+
+ if (m_controller->m_changedSeriesList.size()) {
+ updateGraph();
+ m_controller->m_changedSeriesList.clear();
+ }
+
+ if (m_controller->m_isSeriesVisualsDirty) {
+ updateGraph();
+ m_controller->m_isSeriesVisualsDirty = false;
+ }
+
+ if (m_controller->m_isDataDirty) {
+ updateGraph();
+ m_controller->m_isDataDirty = false;
+ }
+
+ if (m_sliceActivatedChanged) {
+ updateSliceGraph();
+ m_sliceActivatedChanged = false;
+ }
+}
+
+void QQuickGraphsItem::updateGrid()
+{
+ int gridLineCountX = segmentLineRepeaterX()->count();
+ int subGridLineCountX = subsegmentLineRepeaterX()->count();
+ int gridLineCountY = segmentLineRepeaterY()->count() / 2;
+ int subGridLineCountY = subsegmentLineRepeaterY()->count() / 2;
+ int gridLineCountZ = segmentLineRepeaterZ()->count();
+ int subGridLineCountZ = subsegmentLineRepeaterZ()->count();
+
+ if (!m_isFloorGridInRange) {
+ gridLineCountX /= 2;
+ subGridLineCountX /= 2;
+ gridLineCountZ /= 2;
+ subGridLineCountZ /= 2;
+ }
+
+ auto backgroundScale = m_scaleWithBackground + m_backgroundScaleMargin;
+ QVector3D scaleX(backgroundScale.x() * lineLengthScaleFactor(), lineWidthScaleFactor(), lineWidthScaleFactor());
+ QVector3D scaleY(lineWidthScaleFactor(), backgroundScale.y() * lineLengthScaleFactor(), lineWidthScaleFactor());
+ QVector3D scaleZ(backgroundScale.z() * lineLengthScaleFactor(), lineWidthScaleFactor(), lineWidthScaleFactor());
+
+ auto axisX = m_controller->axisX();
+ auto axisY = m_controller->axisY();
+ auto axisZ = m_controller->axisZ();
+
+ const bool xFlipped = isXFlipped();
+ const bool yFlipped = isYFlipped();
+ const bool zFlipped = isZFlipped();
+
+ QQuaternion lineRotation(.0f, .0f, .0f, .0f);
+ QVector3D rotation(90.0f, 0.0f, 0.0f);
+
+ // Floor horizontal line
+ float linePosX = 0.0f;
+ float linePosY = backgroundScale.y();
+ float linePosZ = 0.0f;
+ float scale = m_scaleWithBackground.z();
+ float translate = m_scaleWithBackground.z();
+
+ if (!yFlipped) {
+ linePosY *= -1.0f;
+ rotation.setZ(180.0f);
+ }
+ lineRotation = Utils::calculateRotation(rotation);
+ for (int i = 0; i < subGridLineCountZ; i++) {
+ QQuick3DNode *lineNode = static_cast<QQuick3DNode *>(subsegmentLineRepeaterZ()->objectAt(i));
+ if (axisZ->type() == QAbstract3DAxis::AxisTypeValue) {
+ linePosZ = static_cast<QValue3DAxis *>(axisZ)->subGridPositionAt(i) * -scale * 2.0f + translate;
+ } else if (axisZ->type() == QAbstract3DAxis::AxisTypeCategory) {
+ linePosZ = calculateCategoryGridLinePosition(axisZ, i);
+ linePosY = calculateCategoryGridLinePosition(axisY, i);
+ }
+ positionAndScaleLine(lineNode, scaleX, QVector3D(linePosX, linePosY, linePosZ));
+ lineNode->setRotation(lineRotation);
+ }
+
+ for (int i = 0; i < gridLineCountZ; i++) {
+ QQuick3DNode *lineNode = static_cast<QQuick3DNode *>(segmentLineRepeaterZ()->objectAt(i));
+ if (axisZ->type() == QAbstract3DAxis::AxisTypeValue) {
+ linePosZ = static_cast<QValue3DAxis *>(axisZ)->gridPositionAt(i) * -scale * 2.0f + translate;
+ } else if (axisZ->type() == QAbstract3DAxis::AxisTypeCategory) {
+ linePosZ = calculateCategoryGridLinePosition(axisZ, i);
+ linePosY = calculateCategoryGridLinePosition(axisY, i);
+ }
+ positionAndScaleLine(lineNode, scaleX, QVector3D(linePosX, linePosY, linePosZ));
+ lineNode->setRotation(lineRotation);
+ }
+
+ // Side vertical line
+ linePosX = -backgroundScale.x();
+ linePosY = 0.0f;
+ rotation = QVector3D(0.0f, 90.0f, 0.0f);
+ if (xFlipped) {
+ linePosX *= -1.0f;
+ rotation.setY(-90.0f);
+ }
+ lineRotation = Utils::calculateRotation(rotation);
+ if (m_hasVerticalSegmentLine) {
+ for (int i = 0; i < subGridLineCountZ; i++) {
+ QQuick3DNode *lineNode = static_cast<QQuick3DNode *>(subsegmentLineRepeaterZ()->objectAt(i + subGridLineCountZ));
+ if (axisZ->type() == QAbstract3DAxis::AxisTypeValue) {
+ linePosZ = static_cast<QValue3DAxis *>(axisZ)->subGridPositionAt(i) * scale * 2.0f - translate;
+ } else if (axisZ->type() == QAbstract3DAxis::AxisTypeCategory) {
+ linePosX = calculateCategoryGridLinePosition(axisZ, i);
+ linePosY = calculateCategoryGridLinePosition(axisY, i);
+ }
+ positionAndScaleLine(lineNode, scaleY, QVector3D(linePosX, linePosY, linePosZ));
+ lineNode->setRotation(lineRotation);
+ }
+ for (int i = 0; i < gridLineCountZ; i++) {
+ QQuick3DNode *lineNode = static_cast<QQuick3DNode *>(segmentLineRepeaterZ()->objectAt(i + gridLineCountZ));
+ if (axisZ->type() == QAbstract3DAxis::AxisTypeValue) {
+ linePosZ = static_cast<QValue3DAxis *>(axisZ)->gridPositionAt(i) * scale * 2.0f - translate;
+ } else if (axisZ->type() == QAbstract3DAxis::AxisTypeCategory) {
+ linePosX = calculateCategoryGridLinePosition(axisZ, i);
+ linePosY = calculateCategoryGridLinePosition(axisY, i);
+ }
+ positionAndScaleLine(lineNode, scaleY, QVector3D(linePosX, linePosY, linePosZ));
+ lineNode->setRotation(lineRotation);
+ }
+ }
+
+ // Side horizontal line
+ linePosZ = 0.0f;
+ scale = translate = m_scaleWithBackground.y();
+ rotation = QVector3D(180.0f, -90.0f, 0.0f);
+ if (xFlipped)
+ rotation.setY(90.0f);
+ lineRotation = Utils::calculateRotation(rotation);
+ for (int i = 0; i < gridLineCountY; i++) {
+ QQuick3DNode *lineNode = static_cast<QQuick3DNode *>(segmentLineRepeaterY()->objectAt(i));
+ if (axisY->type() == QAbstract3DAxis::AxisTypeValue)
+ linePosY = static_cast<QValue3DAxis *>(axisY)->gridPositionAt(i) * scale * 2.0f - translate;
+ else if (axisY->type() == QAbstract3DAxis::AxisTypeCategory)
+ linePosY = calculateCategoryGridLinePosition(axisY, i);
+ positionAndScaleLine(lineNode, scaleZ, QVector3D(linePosX, linePosY, linePosZ));
+ lineNode->setRotation(lineRotation);
+ }
+
+ for (int i = 0; i < subGridLineCountY; i++) {
+ QQuick3DNode *lineNode = static_cast<QQuick3DNode *>(subsegmentLineRepeaterY()->objectAt(i));
+ if (axisY->type() == QAbstract3DAxis::AxisTypeValue)
+ linePosY = static_cast<QValue3DAxis *>(axisY)->subGridPositionAt(i) * scale * 2.0f - translate;
+ else if (axisY->type() == QAbstract3DAxis::AxisTypeCategory)
+ linePosY = calculateCategoryGridLinePosition(axisY, i);
+ positionAndScaleLine(lineNode, scaleZ, QVector3D(linePosX, linePosY, linePosZ));
+ lineNode->setRotation(lineRotation);
+ }
+
+ // Floor vertical line
+ linePosZ = 0.0f;
+ linePosY = -backgroundScale.y();
+ rotation = QVector3D(-90.0f, 90.0f, 0.0f);
+ if (yFlipped) {
+ linePosY *= -1.0f;
+ rotation.setZ(180.0f);
+ }
+ scale = translate = m_scaleWithBackground.x();
+ lineRotation = Utils::calculateRotation(rotation);
+ for (int i = 0; i < subGridLineCountX; i++) {
+ QQuick3DNode *lineNode = static_cast<QQuick3DNode *>(subsegmentLineRepeaterX()->objectAt(i));
+ if (axisX->type() == QAbstract3DAxis::AxisTypeValue) {
+ linePosX = static_cast<QValue3DAxis *>(axisX)->subGridPositionAt(i) * scale * 2.0f - translate;
+ } else if (axisX->type() == QAbstract3DAxis::AxisTypeCategory) {
+ linePosX = calculateCategoryGridLinePosition(axisX, i);
+ linePosY = calculateCategoryGridLinePosition(axisY, i);
+ }
+ positionAndScaleLine(lineNode, scaleZ, QVector3D(linePosX, linePosY, linePosZ));
+ lineNode->setRotation(lineRotation);
+ }
+ for (int i = 0; i < gridLineCountX; i++) {
+ QQuick3DNode *lineNode = static_cast<QQuick3DNode *>(segmentLineRepeaterX()->objectAt(i));
+ if (axisX->type() == QAbstract3DAxis::AxisTypeValue) {
+ linePosX = static_cast<QValue3DAxis *>(axisX)->gridPositionAt(i) * scale * 2.0f - translate;
+ } else if (axisX->type() == QAbstract3DAxis::AxisTypeCategory) {
+ linePosX = calculateCategoryGridLinePosition(axisX, i);
+ linePosY = calculateCategoryGridLinePosition(axisY, i);
+ }
+ positionAndScaleLine(lineNode, scaleZ, QVector3D(linePosX, linePosY, linePosZ));
+ lineNode->setRotation(lineRotation);
+ }
+
+ // Back horizontal line
+ linePosX = 0.0f;
+ linePosZ = -backgroundScale.z();
+ rotation = QVector3D(0.0f, 0.0f, 0.0f);
+ if (zFlipped) {
+ linePosZ *= -1.0f;
+ rotation.setX(180.0f);
+ }
+ lineRotation = Utils::calculateRotation(rotation);
+ scale = translate = m_scaleWithBackground.y();
+ for (int i = 0; i < subGridLineCountY; i++) {
+ QQuick3DNode *lineNode = static_cast<QQuick3DNode *>(subsegmentLineRepeaterY()->objectAt(i + subGridLineCountY));
+ if (axisY->type() == QAbstract3DAxis::AxisTypeValue)
+ linePosY = static_cast<QValue3DAxis *>(axisY)->subGridPositionAt(i) * scale * 2.0f - translate;
+ else if (axisY->type() == QAbstract3DAxis::AxisTypeCategory)
+ linePosY = calculateCategoryGridLinePosition(axisY, i);
+ positionAndScaleLine(lineNode, scaleX, QVector3D(linePosX, linePosY, linePosZ));
+ lineNode->setRotation(lineRotation);
+ }
+
+ for (int i = 0; i < gridLineCountY; i++) {
+ QQuick3DNode *lineNode = static_cast<QQuick3DNode *>(segmentLineRepeaterY()->objectAt(i + gridLineCountY));
+ if (axisY->type() == QAbstract3DAxis::AxisTypeValue)
+ linePosY = static_cast<QValue3DAxis *>(axisY)->gridPositionAt(i) * scale * 2.0f - translate;
+ else if (axisY->type() == QAbstract3DAxis::AxisTypeCategory)
+ linePosY = calculateCategoryGridLinePosition(axisY, i);
+ positionAndScaleLine(lineNode, scaleX, QVector3D(linePosX, linePosY, linePosZ));
+ lineNode->setRotation(lineRotation);
+ }
+
+ // Back vertical line
+ linePosY = 0.0f;
+ scale = translate = m_scaleWithBackground.x();
+ rotation = QVector3D(0.0f, 0.0f, 0.0f);
+ if (zFlipped)
+ rotation.setY(180.0f);
+ lineRotation = Utils::calculateRotation(rotation);
+ if (m_hasVerticalSegmentLine) {
+ for (int i = 0; i < gridLineCountX; i++) {
+ QQuick3DNode *lineNode = static_cast<QQuick3DNode *>(segmentLineRepeaterX()->objectAt(i + gridLineCountX));
+ if (axisX->type() == QAbstract3DAxis::AxisTypeValue) {
+ linePosX = static_cast<QValue3DAxis *>(axisX)->gridPositionAt(i) * scale * 2.0f - translate;
+ } else if (axisX->type() == QAbstract3DAxis::AxisTypeCategory) {
+ linePosX = calculateCategoryGridLinePosition(axisX, i);
+ linePosY = calculateCategoryGridLinePosition(axisY, i);
+ }
+ positionAndScaleLine(lineNode, scaleY, QVector3D(linePosX, linePosY, linePosZ));
+ lineNode->setRotation(lineRotation);
+ }
+
+ for (int i = 0; i < subGridLineCountX; i++) {
+ QQuick3DNode *lineNode = static_cast<QQuick3DNode *>(subsegmentLineRepeaterX()->objectAt(i + subGridLineCountX));
+ if (axisX->type() == QAbstract3DAxis::AxisTypeValue) {
+ linePosX = static_cast<QValue3DAxis *>(axisX)->subGridPositionAt(i) * scale * 2.0f - translate;
+ } else if (axisX->type() == QAbstract3DAxis::AxisTypeCategory) {
+ linePosX = calculateCategoryGridLinePosition(axisX, i);
+ linePosY = calculateCategoryGridLinePosition(axisY, i);
+ }
+ positionAndScaleLine(lineNode, scaleY, QVector3D(linePosX, linePosY, linePosZ));
+ lineNode->setRotation(lineRotation);
+ }
+ }
+}
+
+void QQuickGraphsItem::updateLabels()
+{
+ auto axisX = m_controller->axisX();
+
+ auto labels = axisX->labels();
+ float labelAutoAngle = axisX->labelAutoRotation();
+ float labelAngleFraction = labelAutoAngle / 90.0f;
+ float fractionCamX = m_controller->scene()->activeCamera()->xRotation() * labelAngleFraction;
+ float fractionCamY = m_controller->scene()->activeCamera()->yRotation() * labelAngleFraction;
+
+ QVector3D labelRotation = QVector3D(0.0f, 0.0f, 0.0f);
+
+ float xPos = 0.0f;
+ float yPos = 0.0f;
+ float zPos = 0.0f;
+
+ const bool xFlipped = isXFlipped();
+ const bool yFlipped = isYFlipped();
+ const bool zFlipped = isZFlipped();
+
+ auto backgroundScale = m_scaleWithBackground + m_backgroundScaleMargin;
+
+ if (labelAutoAngle == 0.0f) {
+ labelRotation = QVector3D(-90.0f, 90.0f, 0.0f);
+ if (xFlipped)
+ labelRotation.setY(-90.0f);
+ if (yFlipped) {
+ if (xFlipped)
+ labelRotation.setY(-90.0f);
+ else
+ labelRotation.setY(90.0f);
+ labelRotation.setX(90.0f);
+ }
+ } else {
+ if (xFlipped)
+ labelRotation.setY(-90.0f);
+ else
+ labelRotation.setY(90.0f);
+ if (yFlipped) {
+ if (zFlipped) {
+ if (xFlipped) {
+ labelRotation.setX(90.0f - (2.0f * labelAutoAngle - fractionCamX)
+ * (labelAutoAngle + fractionCamY) / labelAutoAngle);
+ labelRotation.setZ(-labelAutoAngle - fractionCamY);
+ } else {
+ labelRotation.setX(90.0f - (2.0f * labelAutoAngle + fractionCamX)
+ * (labelAutoAngle + fractionCamY) / labelAutoAngle);
+ labelRotation.setZ(labelAutoAngle + fractionCamY);
+ }
+ } else {
+ if (xFlipped) {
+ labelRotation.setX(90.0f + fractionCamX
+ * -(labelAutoAngle + fractionCamY) / labelAutoAngle);
+ labelRotation.setZ(labelAutoAngle + fractionCamY);
+ } else {
+ labelRotation.setX(90.0f - fractionCamX
+ * (-labelAutoAngle - fractionCamY) / labelAutoAngle);
+ labelRotation.setZ(-labelAutoAngle - fractionCamY);
+ }
+ }
+ } else {
+ if (zFlipped) {
+ if (xFlipped) {
+ labelRotation.setX(-90.0f + (2.0f * labelAutoAngle - fractionCamX)
+ * (labelAutoAngle - fractionCamY) / labelAutoAngle);
+ labelRotation.setZ(labelAutoAngle - fractionCamY);
+ } else {
+ labelRotation.setX(-90.0f + (2.0f * labelAutoAngle + fractionCamX)
+ * (labelAutoAngle - fractionCamY) / labelAutoAngle);
+ labelRotation.setZ(-labelAutoAngle + fractionCamY);
+ }
+ } else {
+ if (xFlipped) {
+ labelRotation.setX(-90.0f - fractionCamX
+ * (-labelAutoAngle + fractionCamY) / labelAutoAngle);
+ labelRotation.setZ(-labelAutoAngle + fractionCamY);
+ } else {
+ labelRotation.setX(-90.0f + fractionCamX
+ * -(labelAutoAngle - fractionCamY) / labelAutoAngle);
+ labelRotation.setZ(labelAutoAngle - fractionCamY);
+ }
+ }
+ }
+ }
+ auto totalRotation = Utils::calculateRotation(labelRotation);
+
+ float scale = backgroundScale.x() - m_backgroundScaleMargin.x();;
+ float translate = backgroundScale.x() - m_backgroundScaleMargin.x();
+ float textPadding = 12.0f;
+
+ auto pointSize = m_controller->activeTheme()->font().pointSizeF();
+ auto scaleFactor = m_labelScale.x() * m_labelFontScaleFactor / pointSize
+ + m_labelScale.x() * m_fontScaleFactor;
+ QVector3D fontScaled = QVector3D(scaleFactor, scaleFactor, 0.0f);
+
+ m_itemLabel->setScale(fontScaled);
+
+ float labelsMaxWidth = 0.0f;
+ labelsMaxWidth = qMax(labelsMaxWidth, float(findLabelsMaxWidth(axisX->labels()))) + textPadding;
+ QFontMetrics fm(m_controller->activeTheme()->font());
+ float labelHeight = fm.height() + textPadding;
+
+ auto adjustment = labelsMaxWidth * scaleFactor * .5f;
+ zPos = backgroundScale.z() + adjustment + m_labelMargin;
+ adjustment *= qAbs(qSin(qDegreesToRadians(labelRotation.z())));
+ yPos = backgroundScale.y() + adjustment;
+
+ if (!yFlipped)
+ yPos *= -1.0f;
+
+ if (zFlipped)
+ zPos *= -1.0f;
+
+ auto labelTrans = QVector3D(0.0f, yPos, zPos);
+
+ if (axisX->type() == QAbstract3DAxis::AxisTypeValue) {
+ auto valueAxisX = static_cast<QValue3DAxis *>(axisX);
+ for (int i = 0; i < repeaterX()->count(); i++) {
+ auto obj = static_cast<QQuick3DNode *>(repeaterX()->objectAt(i));
+ labelTrans.setX(valueAxisX->labelPositionAt(i) * scale * 2.0f - translate);
+ obj->setScale(fontScaled);
+ obj->setPosition(labelTrans);
+ obj->setRotation(totalRotation);
+ obj->setProperty("labelText", labels[i]);
+ obj->setProperty("labelWidth", labelsMaxWidth);
+ obj->setProperty("labelHeight", labelHeight);
+ }
+ } else if (axisX->type() == QAbstract3DAxis::AxisTypeCategory) {
+ for (int i = 0; i < repeaterX()->count(); i++) {
+ labelTrans = calculateCategoryLabelPosition(axisX, labelTrans, i);
+ auto obj = static_cast<QQuick3DNode *>(repeaterX()->objectAt(i));
+ obj->setScale(fontScaled);
+ obj->setPosition(labelTrans);
+ obj->setRotation(totalRotation);
+ obj->setProperty("labelText", labels[i]);
+ obj->setProperty("labelWidth", labelsMaxWidth);
+ obj->setProperty("labelHeight", labelHeight);
+ }
+ }
+
+ if (titleLabelX()->visible()) {
+ float x = labelTrans.x();
+ labelTrans.setX(0.0f);
+ updateXTitle(labelRotation, labelTrans, totalRotation, labelsMaxWidth, labelHeight, fontScaled);
+ labelTrans.setX(x);
+ }
+
+ auto axisY = m_controller->axisY();
+ labels = axisY->labels();
+ labelAutoAngle = axisY->labelAutoRotation();
+ labelAngleFraction = labelAutoAngle / 90.0f;
+ fractionCamX = m_controller->scene()->activeCamera()->xRotation() * labelAngleFraction;
+ fractionCamY = m_controller->scene()->activeCamera()->yRotation() * labelAngleFraction;
+
+ QVector3D sideLabelRotation(0.0f, -90.0f, 0.0f);
+ QVector3D backLabelRotation(0.0f, 0.0f, 0.0f);
+
+ if (labelAutoAngle == 0.0f) {
+ if (!xFlipped)
+ sideLabelRotation.setY(90.0f);
+ if (zFlipped)
+ backLabelRotation.setY(180.f);
+ } else {
+ // Orient side labels somewhat towards the camera
+ if (xFlipped) {
+ if (zFlipped)
+ backLabelRotation.setY(180.0f + (2.0f * labelAutoAngle) - fractionCamX);
+ else
+ backLabelRotation.setY(-fractionCamX);
+ sideLabelRotation.setY(-90.0f + labelAutoAngle - fractionCamX);
+ } else {
+ if (zFlipped)
+ backLabelRotation.setY(180.0f - (2.0f * labelAutoAngle) - fractionCamX);
+ else
+ backLabelRotation.setY(-fractionCamX);
+ sideLabelRotation.setY(90.0f - labelAutoAngle - fractionCamX);
+ }
+ }
+
+ backLabelRotation.setX(-fractionCamY);
+ sideLabelRotation.setX(-fractionCamY);
+
+ totalRotation = Utils::calculateRotation(sideLabelRotation);
+ scale = translate = backgroundScale.y() - m_backgroundScaleMargin.y();
+
+ labelsMaxWidth = 0.0f;
+ labelsMaxWidth = qMax(labelsMaxWidth, float(findLabelsMaxWidth(axisY->labels()))) + textPadding;
+
+ adjustment = labelsMaxWidth * scaleFactor * .5f + m_labelMargin;
+ xPos = backgroundScale.x();
+ if (!xFlipped)
+ xPos *= -1.0f;
+ labelTrans.setX(xPos);
+ zPos = backgroundScale.z() + adjustment;
+ if (zFlipped)
+ zPos *= -1.0f;
+ labelTrans.setZ(zPos);
+
+ for (int i = 0; i < repeaterY()->count() / 2; i++) {
+ auto obj = static_cast<QQuick3DNode *>(repeaterY()->objectAt(i));
+ labelTrans.setY(static_cast<QValue3DAxis *>(axisY)->labelPositionAt(i) * scale * 2.0f - translate);
+ obj->setScale(fontScaled);
+ obj->setPosition(labelTrans);
+ obj->setRotation(totalRotation);
+ obj->setProperty("labelText", labels[i]);
+ obj->setProperty("labelWidth", labelsMaxWidth);
+ obj->setProperty("labelHeight", labelHeight);
+ }
+
+ auto sideLabelTrans = labelTrans;
+ auto totalSideLabelRotation = totalRotation;
+
+ auto axisZ = m_controller->axisZ();
+ labels = axisZ->labels();
+ labelAutoAngle = axisZ->labelAutoRotation();
+ labelAngleFraction = labelAutoAngle / 90.0f;
+ fractionCamX = m_controller->scene()->activeCamera()->xRotation() * labelAngleFraction;
+ fractionCamY = m_controller->scene()->activeCamera()->yRotation() * labelAngleFraction;
+
+ if (labelAutoAngle == 0.0f) {
+ labelRotation = QVector3D(90.0f, 0.0f, 0.0f);
+ if (zFlipped)
+ labelRotation.setY(180.0f);
+ if (yFlipped) {
+ if (zFlipped)
+ labelRotation.setY(180.0f);
+ else
+ labelRotation.setY(0.0f);
+ labelRotation.setX(90.0f);
+ } else {
+ labelRotation.setX(-90.0f);
+ }
+ } else {
+ if (zFlipped)
+ labelRotation.setY(180.0f);
+ else
+ labelRotation.setY(0.0f);
+ if (yFlipped) {
+ if (zFlipped) {
+ if (xFlipped) {
+ labelRotation.setX(90.0f - (labelAutoAngle - fractionCamX)
+ * (-labelAutoAngle - fractionCamY) / labelAutoAngle);
+ labelRotation.setZ(labelAutoAngle + fractionCamY);
+ } else {
+ labelRotation.setX(90.0f + (labelAutoAngle + fractionCamX)
+ * (labelAutoAngle + fractionCamY) / labelAutoAngle);
+ labelRotation.setZ(-labelAutoAngle - fractionCamY);
+ }
+ } else {
+ if (xFlipped) {
+ labelRotation.setX(90.0f + (labelAutoAngle - fractionCamX)
+ * -(labelAutoAngle + fractionCamY) / labelAutoAngle);
+ labelRotation.setZ(-labelAutoAngle - fractionCamY);
+ } else {
+ labelRotation.setX(90.0f - (labelAutoAngle + fractionCamX)
+ * (labelAutoAngle + fractionCamY) / labelAutoAngle);
+ labelRotation.setZ(labelAutoAngle + fractionCamY);
+ }
+ }
+ } else {
+ if (zFlipped) {
+ if (xFlipped) {
+ labelRotation.setX(-90.0f + (labelAutoAngle - fractionCamX)
+ * (-labelAutoAngle + fractionCamY) / labelAutoAngle);
+ labelRotation.setZ(-labelAutoAngle + fractionCamY);
+ } else {
+ labelRotation.setX(-90.0f - (labelAutoAngle + fractionCamX)
+ * (labelAutoAngle - fractionCamY) / labelAutoAngle);
+ labelRotation.setZ(labelAutoAngle - fractionCamY);
+ }
+ } else {
+ if (xFlipped) {
+ labelRotation.setX(-90.0f - (labelAutoAngle - fractionCamX)
+ * (-labelAutoAngle + fractionCamY) / labelAutoAngle);
+ labelRotation.setZ(labelAutoAngle - fractionCamY);
+ } else {
+ labelRotation.setX(-90.0f + (labelAutoAngle + fractionCamX)
+ * (labelAutoAngle - fractionCamY) / labelAutoAngle);
+ labelRotation.setZ(-labelAutoAngle + fractionCamY);
+ }
+ }
+ }
+ }
+
+ totalRotation = Utils::calculateRotation(labelRotation);
+
+ scale = translate = backgroundScale.z() - m_backgroundScaleMargin.z();
+ labelsMaxWidth = 0.0f;
+ labelsMaxWidth = qMax(labelsMaxWidth, float(findLabelsMaxWidth(axisZ->labels()))) + textPadding ;
+ adjustment = labelsMaxWidth * scaleFactor * .5f;
+
+ xPos = backgroundScale.x() + adjustment + m_labelMargin;
+ if (xFlipped)
+ xPos *= -1.0f;
+
+ adjustment *= qAbs(qSin(qDegreesToRadians(labelRotation.z())));
+ yPos = backgroundScale.y() + adjustment;
+ if (!yFlipped)
+ yPos *= -1.0f;
+
+ labelTrans = QVector3D(xPos, yPos, 0.0f);
+
+ if (axisZ->type() == QAbstract3DAxis::AxisTypeValue) {
+ auto valueAxisZ = static_cast<QValue3DAxis *>(axisZ);
+ for (int i = 0; i < repeaterZ()->count(); i++) {
+ auto obj = static_cast<QQuick3DNode *>(repeaterZ()->objectAt(i));
+ labelTrans.setZ(valueAxisZ->labelPositionAt(i) * scale * -2.0f + translate);
+ obj->setScale(fontScaled);
+ obj->setPosition(labelTrans);
+ obj->setRotation(totalRotation);
+ obj->setProperty("labelText", labels[i]);
+ obj->setProperty("labelWidth", labelsMaxWidth);
+ obj->setProperty("labelHeight", labelHeight);
+ }
+ } else if (axisZ->type() == QAbstract3DAxis::AxisTypeCategory) {
+ for (int i = 0; i < repeaterZ()->count(); i++) {
+ labelTrans = calculateCategoryLabelPosition(axisZ, labelTrans, i);
+ auto obj = static_cast<QQuick3DNode *>(repeaterZ()->objectAt(i));
+ obj->setScale(fontScaled);
+ obj->setPosition(labelTrans);
+ obj->setRotation(totalRotation);
+ obj->setProperty("labelText", labels[i]);
+ obj->setProperty("labelWidth", labelsMaxWidth);
+ obj->setProperty("labelHeight", labelHeight);
+ }
+ }
+
+ if (titleLabelZ()->visible()) {
+ float z = labelTrans.z();
+ labelTrans.setZ(0.0f);
+ updateZTitle(labelRotation, labelTrans, totalRotation, labelsMaxWidth, labelHeight, fontScaled);
+ labelTrans.setZ(z);
+ }
+
+ labels = axisY->labels();
+ totalRotation = Utils::calculateRotation(backLabelRotation);
+ scale = translate = backgroundScale.y() - m_backgroundScaleMargin.y();
+ labelsMaxWidth = 0.0f;
+ labelsMaxWidth = qMax(labelsMaxWidth, float(findLabelsMaxWidth(axisY->labels()))) + textPadding;
+ adjustment = labelsMaxWidth * scaleFactor * .5f + m_labelMargin;
+
+ xPos = backgroundScale.x() + adjustment;
+ if (xFlipped)
+ xPos *= -1.0f;
+ labelTrans.setX(xPos);
+
+ zPos = -backgroundScale.z();
+ if (zFlipped)
+ zPos *= -1.0f;
+ labelTrans.setZ(zPos);
+
+ for (int i = 0; i < repeaterY()->count() / 2; i++) {
+ auto obj = static_cast<QQuick3DNode *>(repeaterY()->objectAt(i + (repeaterY()->count() / 2)));
+ labelTrans.setY(static_cast<QValue3DAxis *>(axisY)->labelPositionAt(i) * scale * 2.0f - translate);
+ obj->setScale(fontScaled);
+ obj->setPosition(labelTrans);
+ obj->setRotation(totalRotation);
+ obj->setProperty("labelText", labels[i]);
+ obj->setProperty("labelWidth", labelsMaxWidth);
+ obj->setProperty("labelHeight", labelHeight);
+ }
+
+ auto backLabelTrans = labelTrans;
+ auto totalBackLabelRotation = totalRotation;
+ if (titleLabelY()->visible()) {
+ updateYTitle(sideLabelRotation, backLabelRotation,
+ sideLabelTrans, backLabelTrans,
+ totalSideLabelRotation, totalBackLabelRotation, labelsMaxWidth, labelHeight, fontScaled);
+ }
+
+}
+
+void QQuickGraphsItem::positionAndScaleLine(QQuick3DNode *lineNode, QVector3D scale, QVector3D position)
+{
+ lineNode->setScale(scale);
+ lineNode->setPosition(position);
+}
+
+void QQuickGraphsItem::graphPositionAt(const QPoint &point)
+{
+ bool isHitted = false;
+ auto results = pickAll(point.x(), point.y());
+ for (auto &result : results) {
+ if (auto hit = result.objectHit()) {
+ isHitted = true;
+ m_controller->setQueriedGraphPosition(QVector3D(
+ result.scenePosition().x(),
+ result.scenePosition().y(),
+ result.scenePosition().z()
+ ));
+ if (m_backgroundBB != hit) {
+ m_controller->setQueriedGraphPosition(hit->position());
+ break;
+ }
+ }
+ }
+
+ if (!isHitted)
+ m_controller->setQueriedGraphPosition(QVector3D(0,0,0));
+
+ emit queriedGraphPositionChanged(m_controller->queriedGraphPosition());
+ emit m_controller->queriedGraphPositionChanged(m_controller->queriedGraphPosition());
+ m_controller->setGraphPositionQueryPending(false);
+ scene()->setGraphPositionQuery(Q3DScene::invalidSelectionPoint());
+}
+
+void QQuickGraphsItem::updateShadowQuality(ShadowQuality quality)
+{
+ if (quality != QQuickGraphsItem::ShadowQualityNone) {
+ light()->setCastsShadow(true);
+
+ QQuick3DAbstractLight::QSSGShadowMapQuality shadowMapQuality;
+ switch (quality) {
+ case QQuickGraphsItem::ShadowQualityLow:
+ case QQuickGraphsItem::ShadowQualitySoftLow:
+ shadowMapQuality = QQuick3DAbstractLight::QSSGShadowMapQuality::ShadowMapQualityLow;
+ break;
+ case QQuickGraphsItem::ShadowQualityMedium:
+ case QQuickGraphsItem::ShadowQualitySoftMedium:
+ shadowMapQuality = QQuick3DAbstractLight::QSSGShadowMapQuality::ShadowMapQualityMedium;
+ break;
+ case QQuickGraphsItem::ShadowQualityHigh:
+ case QQuickGraphsItem::ShadowQualitySoftHigh:
+ shadowMapQuality = QQuick3DAbstractLight::QSSGShadowMapQuality::ShadowMapQualityHigh;
+ break;
+ default:
+ shadowMapQuality = QQuick3DAbstractLight::QSSGShadowMapQuality::ShadowMapQualityMedium;
+ break;
+ }
+ light()->setShadowMapQuality(shadowMapQuality);
+ } else {
+ light()->setCastsShadow(false);
+ }
+}
+
+void QQuickGraphsItem::updateAxisRange(float min, float max)
+{
+ Q_UNUSED(min);
+ Q_UNUSED(max);
+}
+
+void QQuickGraphsItem::updateAxisReversed(bool enable)
+{
+ Q_UNUSED(enable);
+}
+
+int QQuickGraphsItem::findLabelsMaxWidth(const QStringList &labels)
+{
+ int labelWidth = 0;
+ QFontMetrics labelFM(m_controller->activeTheme()->font());
+
+ for (const auto &label : std::as_const(labels)) {
+ auto width = labelFM.horizontalAdvance(label);
+ if (labelWidth < width)
+ labelWidth = width;
+ }
+ return labelWidth;
+}
+
+QVector3D QQuickGraphsItem::calculateCategoryLabelPosition(QAbstract3DAxis *axis, QVector3D labelPosition, int index)
+{
+ Q_UNUSED(axis);
+ Q_UNUSED(index);
+ return labelPosition;
+}
+
+float QQuickGraphsItem::calculateCategoryGridLinePosition(QAbstract3DAxis *axis, int index)
+{
+ Q_UNUSED(axis);
+ Q_UNUSED(index);
+ return 0.0f;
+}
+
+void QQuickGraphsItem::updateXTitle(const QVector3D &labelRotation, const QVector3D &labelTrans,
+ const QQuaternion &totalRotation, float labelsMaxWidth,
+ float labelHeight, const QVector3D &scale)
+{
+ float scaledFontSize = (0.05 + m_controller->activeTheme()->font().pointSizeF()) / 500.0f;
+ float scaleFactor = scaledFontSize / 115.0f;
+ float titleOffset;
+
+ bool radial = false;
+ if (radial)
+ titleOffset = -2.0f * (m_labelMargin + scaledFontSize);
+ else
+ titleOffset = 2.0f * (m_labelMargin + (labelsMaxWidth * scaleFactor));
+
+ float zRotation = 0.0f;
+ float yRotation = 0.0f;
+ float xRotation = -90.0f + labelRotation.z();
+ float offsetRotation = labelRotation.z();
+ float extraRotation = -90.0f;
+ if (m_yFlipped) {
+ zRotation = 180.0f;
+ if (m_zFlipped) {
+ titleOffset = -titleOffset;
+ if (m_xFlipped) {
+ offsetRotation = -offsetRotation;
+ extraRotation = -extraRotation;
+ } else {
+ xRotation = -90.0f - labelRotation.z();
+ }
+ } else {
+ yRotation = 180.0f;
+ if (m_xFlipped) {
+ offsetRotation = -offsetRotation;
+ xRotation = -90.0f - labelRotation.z();
+ } else {
+ extraRotation = -extraRotation;
+ }
+ }
+ } else {
+ if (m_zFlipped) {
+ titleOffset = -titleOffset;
+ if (m_xFlipped) {
+ yRotation = 180.0f;
+ offsetRotation = -offsetRotation;
+ } else {
+ yRotation = 180.0f;
+ xRotation = -90.0f - labelRotation.z();
+ extraRotation = -extraRotation;
+ }
+ if (m_yFlipped) {
+ extraRotation = -extraRotation;
+ if (m_xFlipped)
+ xRotation = 90.0f + labelRotation.z();
+ else
+ xRotation = 90.0f - labelRotation.z();
+ }
+ } else {
+ if (m_xFlipped) {
+ offsetRotation = -offsetRotation;
+ xRotation = -90.0f - labelRotation.z();
+ extraRotation = -extraRotation;
+ }
+ if (m_yFlipped) {
+ xRotation = 90.0f + labelRotation.z();
+ extraRotation = -extraRotation;
+ if (m_xFlipped)
+ xRotation = 90.0f - labelRotation.z();
+ }
+ }
+ }
+
+ if (offsetRotation == 180.0f || offsetRotation == -180.0f)
+ offsetRotation = 0.0f;
+
+ QQuaternion offsetRotator = QQuaternion::fromAxisAndAngle(1.0f, 0.0f, 0.0f, offsetRotation);
+ QVector3D titleOffsetVector =
+ offsetRotator.rotatedVector(QVector3D(0.0f, 0.0f, titleOffset));
+
+ QQuaternion titleRotation;
+ if (m_controller->axisX()->isTitleFixed()) {
+ titleRotation = QQuaternion::fromAxisAndAngle(0.0f, 0.0f, 1.0f, zRotation)
+ * QQuaternion::fromAxisAndAngle(0.0f, 1.0f, 0.0f, yRotation)
+ * QQuaternion::fromAxisAndAngle(1.0f, 0.0f, 0.0f, xRotation);
+ } else {
+ titleRotation = totalRotation
+ * QQuaternion::fromAxisAndAngle(0.0f, 0.0f, 1.0f, extraRotation);
+ }
+
+ m_titleLabelX->setScale(scale);
+ m_titleLabelX->setPosition(labelTrans + titleOffsetVector);
+ m_titleLabelX->setRotation(titleRotation);
+ m_titleLabelX->setProperty("labelWidth", labelsMaxWidth);
+ m_titleLabelX->setProperty("LabelHeight", labelHeight);
+}
+
+void QQuickGraphsItem::updateYTitle(const QVector3D &sideLabelRotation,
+ const QVector3D &backLabelRotation,
+ const QVector3D &sideLabelTrans,
+ const QVector3D &backLabelTrans,
+ const QQuaternion &totalSideRotation,
+ const QQuaternion &totalBackRotation,
+ float labelsMaxWidth,
+ float labelHeight,
+ const QVector3D &scale)
+{
+ float scaledFontSize = (0.05 + m_controller->activeTheme()->font().pointSizeF()) / 500.0f;
+ float scaleFactor = scaledFontSize / 115.0f;
+ float titleOffset = 2.0f * (m_labelMargin + (labelsMaxWidth * scaleFactor));
+
+ QQuaternion zRightAngleRotation = QQuaternion::fromAxisAndAngle(0.0f, 0.0f, 1.0f, 90.0f);
+ float yRotation;
+ QVector3D titleTrans;
+ QQuaternion totalRotation;
+ if (m_xFlipped == m_zFlipped) {
+ yRotation = backLabelRotation.y();
+ titleTrans = backLabelTrans;
+ totalRotation = totalBackRotation;
+ } else {
+ yRotation = sideLabelRotation.y();
+ titleTrans = sideLabelTrans;
+ totalRotation = totalSideRotation;
+ }
+
+ QQuaternion offsetRotator = QQuaternion::fromAxisAndAngle(0.0f, 1.0f, 0.0f, yRotation);
+ QVector3D titleOffsetVector =
+ offsetRotator.rotatedVector(QVector3D(-titleOffset, 0.0f, 0.0f));
+
+ QQuaternion titleRotation;
+ if (m_controller->axisY()->isTitleFixed()) {
+ titleRotation = QQuaternion::fromAxisAndAngle(0.0f, 1.0f, 0.0f, yRotation)
+ * zRightAngleRotation;
+ } else {
+ titleRotation = totalRotation * zRightAngleRotation;
+ }
+
+ m_titleLabelY->setScale(scale);
+ m_titleLabelY->setPosition(titleTrans + titleOffsetVector);
+ m_titleLabelY->setRotation(titleRotation);
+ m_titleLabelY->setProperty("labelWidth", labelsMaxWidth);
+ m_titleLabelY->setProperty("LabelHeight", labelHeight);
+}
+
+void QQuickGraphsItem::updateZTitle(const QVector3D &labelRotation, const QVector3D &labelTrans,
+ const QQuaternion &totalRotation, float labelsMaxWidth,
+ float labelHeight, const QVector3D &scale)
+{
+ float scaledFontSize = (0.05 + m_controller->activeTheme()->font().pointSizeF()) / 500.0f;
+ float scaleFactor = scaledFontSize / 115.0f;
+ float titleOffset = 2.0f * (m_labelMargin + (labelsMaxWidth * scaleFactor));
+ float zRotation = labelRotation.z();
+ float yRotation = -90.0f;
+ float xRotation = -90.0f;
+ float extraRotation = 90.0f;
+
+ if (m_yFlipped) {
+
+ xRotation = -xRotation;
+ if (m_zFlipped) {
+ if (m_xFlipped) {
+ titleOffset = -titleOffset;
+ zRotation = -zRotation;
+ extraRotation = -extraRotation;
+ } else {
+ zRotation = -zRotation;
+ yRotation = -yRotation;
+ }
+ } else {
+ if (m_xFlipped) {
+ titleOffset = -titleOffset;
+ } else {
+ extraRotation = -extraRotation;
+ yRotation = -yRotation;
+ }
+ }
+ } else {
+ if (m_zFlipped) {
+ zRotation = -zRotation;
+ if (m_xFlipped) {
+ titleOffset = -titleOffset;
+ } else {
+ extraRotation = -extraRotation;
+ yRotation = -yRotation;
+ }
+ } else {
+ if (m_xFlipped) {
+ titleOffset = -titleOffset;
+ extraRotation = -extraRotation;
+ } else {
+ yRotation = -yRotation;
+ }
+ }
+ if (m_yFlipped) {
+ xRotation = -xRotation;
+ extraRotation = -extraRotation;
+ }
+ }
+
+ float offsetRotation = zRotation;
+ if (offsetRotation == 180.0f || offsetRotation == -180.0f)
+ offsetRotation = 0.0f;
+
+ QQuaternion offsetRotator = QQuaternion::fromAxisAndAngle(0.0f, 0.0f, 1.0f, offsetRotation);
+ QVector3D titleOffsetVector =
+ offsetRotator.rotatedVector(QVector3D(titleOffset, 0.0f, 0.0f));
+
+ QQuaternion titleRotation;
+ if (m_controller->axisZ()->isTitleFixed()) {
+ titleRotation = QQuaternion::fromAxisAndAngle(0.0f, 0.0f, 1.0f, zRotation)
+ * QQuaternion::fromAxisAndAngle(0.0f, 1.0f, 0.0f, yRotation)
+ * QQuaternion::fromAxisAndAngle(1.0f, 0.0f, 0.0f, xRotation);
+ } else {
+ titleRotation = totalRotation
+ * QQuaternion::fromAxisAndAngle(0.0f, 0.0f, 1.0f, extraRotation);
+ }
+
+ m_titleLabelZ->setScale(scale);
+ m_titleLabelZ->setPosition(labelTrans + titleOffsetVector);
+ m_titleLabelZ->setRotation(titleRotation);
+ m_titleLabelZ->setProperty("labelWidth", labelsMaxWidth);
+ m_titleLabelZ->setProperty("LabelHeight", labelHeight);
+}
+
+void QQuickGraphsItem::updateCamera()
+{
+ float zoom = 720.f / m_controller->scene()->activeCamera()->zoomLevel();
+ camera()->setZ(zoom);
+ cameraTarget()->setPosition(m_controller->scene()->activeCamera()->target());
+ auto rotation = QVector3D(
+ -m_controller->scene()->activeCamera()->yRotation(),
+ -m_controller->scene()->activeCamera()->xRotation(),
+ 0);
+ cameraTarget()->setEulerRotation(rotation);
+
+ if (m_itemLabel->visible())
+ m_itemLabel->setEulerRotation(rotation);
+}
+
+int QQuickGraphsItem::msaaSamples() const
+{
+ if (m_renderMode == RenderIndirect)
+ return m_samples;
+ else
+ return m_windowSamples;
+}
+
+void QQuickGraphsItem::setMsaaSamples(int samples)
+{
+ if (m_renderMode != RenderIndirect) {
+ qWarning("Multisampling cannot be adjusted in this render mode");
+ } else {
+ if (m_controller->isOpenGLES()) {
+ if (samples > 0)
+ qWarning("Multisampling is not supported in OpenGL ES2");
+ } else if (m_samples != samples) {
+ m_samples = samples;
+ setAntialiasing(m_samples > 0);
+ auto sceneEnv = environment();
+ sceneEnv->setAntialiasingMode(m_samples > 0
+ ? QQuick3DSceneEnvironment::QQuick3DEnvironmentAAModeValues::MSAA
+ : QQuick3DSceneEnvironment::QQuick3DEnvironmentAAModeValues::NoAA);
+ switch (m_samples) {
+ case 0:
+ // no-op
+ break;
+ case 2:
+ sceneEnv->setAntialiasingQuality(
+ QQuick3DSceneEnvironment::QQuick3DEnvironmentAAQualityValues::Medium);
+ break;
+ case 4:
+ sceneEnv->setAntialiasingQuality(
+ QQuick3DSceneEnvironment::QQuick3DEnvironmentAAQualityValues::High);
+ break;
+ case 8:
+ sceneEnv->setAntialiasingQuality(
+ QQuick3DSceneEnvironment::QQuick3DEnvironmentAAQualityValues::VeryHigh);
+ break;
+ default:
+ qWarning("Invalid multisampling sample number, using 4x instead");
+ sceneEnv->setAntialiasingQuality(
+ QQuick3DSceneEnvironment::QQuick3DEnvironmentAAQualityValues::High);
+ m_samples = 4;
+ break;
+ }
+ emit msaaSamplesChanged(m_samples);
+ update();
+ }
+ }
+}
+
+Declarative3DScene *QQuickGraphsItem::scene() const
+{
+ return static_cast<Declarative3DScene *>(m_controller->scene());
+}
+
+void QQuickGraphsItem::handleWindowChanged(/*QQuickWindow *window*/)
+{
+ auto window = QQuick3DObjectPrivate::get(rootNode())->sceneManager->window();
+ checkWindowList(window);
+ if (!window)
+ return;
+
+#if defined(Q_OS_MACOS)
+ bool previousVisibility = window->isVisible();
+ // Enable touch events for Mac touchpads
+ window->setVisible(true);
+ typedef void * (*EnableTouch)(QWindow*, bool);
+ EnableTouch enableTouch =
+ (EnableTouch)QGuiApplication::platformNativeInterface()->nativeResourceFunctionForIntegration("registertouchwindow");
+ if (enableTouch)
+ enableTouch(window, true);
+ window->setVisible(previousVisibility);
+#endif
+
+ connect(window, &QObject::destroyed, this, &QQuickGraphsItem::windowDestroyed);
+
+ int oldWindowSamples = m_windowSamples;
+ m_windowSamples = window->format().samples();
+ if (m_windowSamples < 0)
+ m_windowSamples = 0;
+
+ connect(window, &QQuickWindow::beforeSynchronizing,
+ this, &QQuickGraphsItem::synchData);
+
+ if (m_renderMode == RenderDirectToBackground_NoClear
+ || m_renderMode == RenderDirectToBackground) {
+ setAntialiasing(m_windowSamples > 0);
+ if (m_windowSamples != oldWindowSamples)
+ emit msaaSamplesChanged(m_windowSamples);
+ }
+
+ connect(m_controller.data(), &Abstract3DController::needRender, window, &QQuickWindow::update);
+ updateWindowParameters();
+
+ if (sliceView()) {
+ float pixelRatio = window->devicePixelRatio();
+ float magnification = 100.0f * pixelRatio + 50.0f;
+ QQuick3DOrthographicCamera *camera = static_cast<QQuick3DOrthographicCamera *>(sliceView()->camera());
+ camera->setHorizontalMagnification(magnification);
+ camera->setVerticalMagnification(magnification);
+ }
+
+#if defined(Q_OS_IOS)
+ // Scenegraph render cycle in iOS sometimes misses update after beforeSynchronizing signal.
+ // This ensures we don't end up displaying the graph without any data, in case update is
+ // skipped after synchDataToRenderer.
+ QTimer::singleShot(0, window, SLOT(update()));
+#endif
+}
+
+void QQuickGraphsItem::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
+{
+ QQuickItem::geometryChange(newGeometry, oldGeometry);
+
+ m_cachedGeometry = newGeometry;
+
+ updateWindowParameters();
+}
+
+void QQuickGraphsItem::itemChange(ItemChange change, const ItemChangeData &value)
+{
+ QQuick3DViewport::itemChange(change, value);
+ updateWindowParameters();
+}
+
+void QQuickGraphsItem::updateWindowParameters()
+{
+ const QMutexLocker locker(&m_mutex);
+
+ // Update the device pixel ratio, window size and bounding box
+ QQuickWindow *win = window();
+ if (win && !m_controller.isNull()) {
+ Q3DScene *scene = m_controller->scene();
+ if (win->devicePixelRatio() != scene->devicePixelRatio()) {
+ scene->setDevicePixelRatio(win->devicePixelRatio());
+ win->update();
+ }
+
+ bool directRender = m_renderMode == RenderDirectToBackground
+ || m_renderMode == RenderDirectToBackground_NoClear;
+ QSize windowSize;
+
+ if (directRender)
+ windowSize = win->size();
+ else
+ windowSize = m_cachedGeometry.size().toSize();
+
+ if (windowSize != scene->d_ptr->windowSize()) {
+ scene->d_ptr->setWindowSize(windowSize);
+ win->update();
+ }
+
+ if (directRender) {
+ // Origin mapping is needed when rendering directly to background
+ QPointF point = QQuickItem::mapToScene(QPointF(0.0, 0.0));
+ scene->d_ptr->setViewport(QRect(point.x() + 0.5f, point.y() + 0.5f,
+ m_cachedGeometry.width() + 0.5f,
+ m_cachedGeometry.height() + 0.5f));
+ } else {
+ // No translation needed when rendering to FBO
+ scene->d_ptr->setViewport(QRect(0.0, 0.0, m_cachedGeometry.width() + 0.5f,
+ m_cachedGeometry.height() + 0.5f));
+ }
+ }
+}
+
+void QQuickGraphsItem::handleSelectionModeChange(QAbstract3DGraph::SelectionFlags mode)
+{
+ int intmode = int(mode);
+ emit selectionModeChanged(SelectionFlags(intmode));
+}
+
+void QQuickGraphsItem::handleShadowQualityChange(QAbstract3DGraph::ShadowQuality quality)
+{
+ emit shadowQualityChanged(ShadowQuality(quality));
+}
+
+void QQuickGraphsItem::handleSelectedElementChange(QAbstract3DGraph::ElementType type)
+{
+ emit selectedElementChanged(ElementType(type));
+}
+
+void QQuickGraphsItem::handleOptimizationHintChange(QAbstract3DGraph::OptimizationHints hints)
+{
+ int intHints = int(hints);
+ emit optimizationHintsChanged(OptimizationHints(intHints));
+}
+
+QAbstract3DInputHandler *QQuickGraphsItem::inputHandler() const
+{
+ return m_controller->activeInputHandler();
+}
+
+void QQuickGraphsItem::setInputHandler(QAbstract3DInputHandler *inputHandler)
+{
+ m_controller->setActiveInputHandler(inputHandler);
+}
+
+void QQuickGraphsItem::mouseDoubleClickEvent(QMouseEvent *event)
+{
+ m_controller->mouseDoubleClickEvent(event);
+}
+
+void QQuickGraphsItem::touchEvent(QTouchEvent *event)
+{
+ m_controller->touchEvent(event);
+ window()->update();
+}
+
+void QQuickGraphsItem::mousePressEvent(QMouseEvent *event)
+{
+ QPoint mousePos = event->pos();
+ handleMousePressedEvent(event);
+ m_controller->mousePressEvent(event, mousePos);
+}
+
+void QQuickGraphsItem::mouseReleaseEvent(QMouseEvent *event)
+{
+ QPoint mousePos = event->pos();
+ m_controller->mouseReleaseEvent(event, mousePos);
+}
+
+void QQuickGraphsItem::mouseMoveEvent(QMouseEvent *event)
+{
+ QPoint mousePos = event->pos();
+ m_controller->mouseMoveEvent(event, mousePos);
+}
+
+#if QT_CONFIG(wheelevent)
+void QQuickGraphsItem::wheelEvent(QWheelEvent *event)
+{
+ m_controller->wheelEvent(event);
+}
+#endif
+
+void QQuickGraphsItem::checkWindowList(QQuickWindow *window)
+{
+ QQuickWindow *oldWindow = m_graphWindowList.value(this);
+ m_graphWindowList[this] = window;
+
+ if (oldWindow != window && oldWindow) {
+ QObject::disconnect(oldWindow, &QObject::destroyed, this,
+ &QQuickGraphsItem::windowDestroyed);
+ QObject::disconnect(oldWindow, &QQuickWindow::beforeSynchronizing, this,
+ &QQuickGraphsItem::synchData);
+ if (!m_controller.isNull()) {
+ QObject::disconnect(m_controller.data(), &Abstract3DController::needRender,
+ oldWindow, &QQuickWindow::update);
+ }
+ }
+
+ QList<QQuickWindow *> windowList;
+
+ foreach (QQuickGraphsItem *graph, m_graphWindowList.keys()) {
+ if (graph->m_renderMode == RenderDirectToBackground
+ || graph->m_renderMode == RenderDirectToBackground_NoClear) {
+ windowList.append(m_graphWindowList.value(graph));
+ }
+ }
+
+ if (!window) {
+ m_graphWindowList.remove(this);
+ return;
+ }
+}
+
+void QQuickGraphsItem::setMeasureFps(bool enable)
+{
+ m_controller->setMeasureFps(enable);
+ if (enable)
+ QObject::connect(renderStats(), &QQuick3DRenderStats::fpsChanged, this, &QQuickGraphsItem::handleFpsChanged);
+ else
+ QObject::disconnect(renderStats(), 0, this, 0);
+
+}
+
+bool QQuickGraphsItem::measureFps() const
+{
+ return m_controller->measureFps();
+}
+
+int QQuickGraphsItem::currentFps() const
+{
+ return m_controller->currentFps();
+}
+
+void QQuickGraphsItem::setOrthoProjection(bool enable)
+{
+ m_controller->setOrthoProjection(enable);
+}
+
+bool QQuickGraphsItem::isOrthoProjection() const
+{
+ return m_controller->isOrthoProjection();
+}
+
+QQuickGraphsItem::ElementType QQuickGraphsItem::selectedElement() const
+{
+ return ElementType(m_controller->selectedElement());
+}
+
+void QQuickGraphsItem::setAspectRatio(qreal ratio)
+{
+ m_controller->setAspectRatio(ratio);
+}
+
+qreal QQuickGraphsItem::aspectRatio() const
+{
+ return m_controller->aspectRatio();
+}
+
+void QQuickGraphsItem::setOptimizationHints(OptimizationHints hints)
+{
+ int intmode = int(hints);
+ m_controller->setOptimizationHints(QAbstract3DGraph::OptimizationHints(intmode));
+}
+
+QQuickGraphsItem::OptimizationHints QQuickGraphsItem::optimizationHints() const
+{
+ int intmode = int(m_controller->optimizationHints());
+ return OptimizationHints(intmode);
+}
+
+void QQuickGraphsItem::setPolar(bool enable)
+{
+ m_controller->setPolar(enable);
+}
+
+bool QQuickGraphsItem::isPolar() const
+{
+ return m_controller->isPolar();
+}
+
+void QQuickGraphsItem::setRadialLabelOffset(float offset)
+{
+ m_controller->setRadialLabelOffset(offset);
+}
+
+float QQuickGraphsItem::radialLabelOffset() const
+{
+ return m_controller->radialLabelOffset();
+}
+
+void QQuickGraphsItem::setHorizontalAspectRatio(qreal ratio)
+{
+ m_controller->setHorizontalAspectRatio(ratio);
+}
+
+qreal QQuickGraphsItem::horizontalAspectRatio() const
+{
+ return m_controller->horizontalAspectRatio();
+}
+
+void QQuickGraphsItem::setReflection(bool enable)
+{
+ m_controller->setReflection(enable);
+}
+
+bool QQuickGraphsItem::isReflection() const
+{
+ return m_controller->reflection();
+}
+
+void QQuickGraphsItem::setReflectivity(qreal reflectivity)
+{
+ m_controller->setReflectivity(reflectivity);
+}
+
+qreal QQuickGraphsItem::reflectivity() const
+{
+ return m_controller->reflectivity();
+}
+
+void QQuickGraphsItem::setLocale(const QLocale &locale)
+{
+ m_controller->setLocale(locale);
+}
+
+QLocale QQuickGraphsItem::locale() const
+{
+ return m_controller->locale();
+}
+
+QVector3D QQuickGraphsItem::queriedGraphPosition() const
+{
+ return m_controller->queriedGraphPosition();
+}
+
+void QQuickGraphsItem::setMargin(qreal margin)
+{
+ m_controller->setMargin(margin);
+}
+
+qreal QQuickGraphsItem::margin() const
+{
+ return m_controller->margin();
+}
+
+QQuick3DNode *QQuickGraphsItem::rootNode() const
+{
+ return QQuick3DViewport::scene();
+}
+
+void QQuickGraphsItem::changeLabelBackgroundColor(QQuick3DRepeater *repeater, const QColor &color)
+{
+ int count = repeater->count();
+ for (int i = 0; i < count; i++) {
+ auto label = static_cast<QQuick3DNode *>(repeater->objectAt(i));
+ label->setProperty("backgroundColor", color);
+ }
+}
+
+void QQuickGraphsItem::changeLabelBackgroundEnabled(QQuick3DRepeater *repeater, const bool &enabled)
+{
+ int count = repeater->count();
+ for (int i = 0; i < count; i++) {
+ auto label = static_cast<QQuick3DNode *>(repeater->objectAt(i));
+ label->setProperty("backgroundEnabled", enabled);
+ }
+}
+
+void QQuickGraphsItem::changeLabelBorderEnabled(QQuick3DRepeater *repeater, const bool &enabled)
+{
+ int count = repeater->count();
+ for (int i = 0; i < count; i++) {
+ auto label = static_cast<QQuick3DNode *>(repeater->objectAt(i));
+ label->setProperty("borderEnabled", enabled);
+ }
+}
+
+void QQuickGraphsItem::changeLabelTextColor(QQuick3DRepeater *repeater, const QColor &color)
+{
+ int count = repeater->count();
+ for (int i = 0; i < count; i++) {
+ auto label = static_cast<QQuick3DNode *>(repeater->objectAt(i));
+ label->setProperty("labelTextColor", color);
+ }
+}
+
+void QQuickGraphsItem::changeLabelFont(QQuick3DRepeater *repeater, const QFont &font)
+{
+ int count = repeater->count();
+ for (int i = 0; i < count; i++) {
+ auto label = static_cast<QQuick3DNode *>(repeater->objectAt(i));
+ label->setProperty("labelFont", font);
+ }
+}
+
+void QQuickGraphsItem::changeGridLineColor(QQuick3DRepeater *repeater, const QColor &color)
+{
+ for (int i = 0; i < repeater->count(); i++) {
+ auto lineNode = static_cast<QQuick3DNode *>(repeater->objectAt(i));
+ lineNode->setProperty("lineColor", color);
+ }
+}
+
+void QQuickGraphsItem::updateTitleLabels()
+{
+ if (m_controller->m_changeTracker.axisXTitleVisibilityChanged) {
+ m_titleLabelX->setVisible(m_controller->axisX()->isTitleVisible());
+ m_controller->m_changeTracker.axisXTitleVisibilityChanged = false;
+ }
+
+ if (m_controller->m_changeTracker.axisYTitleVisibilityChanged) {
+ m_titleLabelY->setVisible(m_controller->axisY()->isTitleVisible());
+ m_controller->m_changeTracker.axisYTitleVisibilityChanged = false;
+ }
+
+ if (m_controller->m_changeTracker.axisZTitleVisibilityChanged) {
+ m_titleLabelZ->setVisible(m_controller->axisZ()->isTitleVisible());
+ m_controller->m_changeTracker.axisZTitleVisibilityChanged = false;
+ }
+
+ if (m_controller->m_changeTracker.axisXTitleChanged) {
+ m_titleLabelX->setProperty("labelText", m_controller->axisX()->title());
+ m_controller->m_changeTracker.axisXTitleChanged = false;
+ }
+
+ if (m_controller->m_changeTracker.axisYTitleChanged) {
+ m_titleLabelY->setProperty("labelText", m_controller->axisY()->title());
+ m_controller->m_changeTracker.axisYTitleChanged = false;
+ }
+
+ if (m_controller->m_changeTracker.axisZTitleChanged) {
+ m_titleLabelZ->setProperty("labelText", m_controller->axisZ()->title());
+ m_controller->m_changeTracker.axisZTitleChanged = false;
+ }
+}
+
+void QQuickGraphsItem::updateSliceGraph()
+{
+ if (!m_sliceEnabled) {
+ m_controller->setSlicingActive(false);
+ setWidth(width() * 5.f);
+ setHeight(height() * 5.f);
+ m_sliceView->setVisible(false);
+ } else {
+ float pixelRatio = QQuick3DObjectPrivate::get(rootNode())->sceneManager->window()->devicePixelRatio();
+ float magnification = 100.0f * pixelRatio + 50.0f;
+ QQuick3DOrthographicCamera *camera = static_cast<QQuick3DOrthographicCamera *>(sliceView()->camera());
+ camera->setHorizontalMagnification(magnification);
+ camera->setVerticalMagnification(magnification);
+ QQuickItem *anchor = QQuickItemPrivate::get(this)->anchors()->fill();
+ if (anchor)
+ QQuickItemPrivate::get(this)->anchors()->resetFill();
+ setWidth(width() * .2f);
+ setHeight(height() * .2f);
+ m_sliceView->setVisible(true);
+ updateSliceGrid();
+ updateSliceLabels();
+ }
+}
+
+void QQuickGraphsItem::windowDestroyed(QObject *obj)
+{
+ // Remove destroyed window from window lists
+ QQuickWindow *win = static_cast<QQuickWindow *>(obj);
+ QQuickWindow *oldWindow = m_graphWindowList.value(this);
+
+ if (win == oldWindow)
+ m_graphWindowList.remove(this);
+}
+
+QQmlComponent *QQuickGraphsItem::createRepeaterDelegateComponent(const QString &fileName)
+{
+ QQmlComponent component(qmlEngine(this), fileName);
+ return qobject_cast<QQmlComponent *>(component.create());
+}
+
+QQuick3DRepeater *QQuickGraphsItem::createRepeater()
+{
+ auto engine = qmlEngine(this);
+ QQmlComponent repeaterComponent(engine);
+ repeaterComponent.setData("import QtQuick3D; Repeater3D{}",QUrl());
+ auto repeater = qobject_cast<QQuick3DRepeater *>(repeaterComponent.create());
+ repeater->setParent(rootNode());
+ repeater->setParentItem(rootNode());
+ return repeater;
+}
+
+QQuick3DNode *QQuickGraphsItem::createTitleLabel()
+{
+ QQmlComponent comp(qmlEngine(this), QStringLiteral(":/axis/ItemLabel"));
+ auto titleLabel = qobject_cast<QQuick3DNode *>(comp.create());
+ titleLabel->setParent(rootNode());
+ titleLabel->setParentItem(rootNode());
+ titleLabel->setVisible(false);
+ titleLabel->setScale(m_labelScale);
+ return titleLabel;
+}
+
+QQuick3DCustomMaterial *QQuickGraphsItem::createQmlCustomMaterial(const QString &fileName)
+{
+ QQmlComponent component(qmlEngine(this), fileName);
+ QQuick3DCustomMaterial *material = qobject_cast<QQuick3DCustomMaterial *>(component.create());
+ return material;
+}
+
+QQuick3DPrincipledMaterial *QQuickGraphsItem::createPrincipledMaterial()
+{
+ QQmlComponent component(qmlEngine(this));
+ component.setData("import QtQuick3D; PrincipledMaterial{}", QUrl());
+ return qobject_cast<QQuick3DPrincipledMaterial *>(component.create());
+}
+
+bool QQuickGraphsItem::event(QEvent *event)
+{
+ return QQuickItem::event(event);
+}
+
+void QQuickGraphsItem::createSliceView()
+{
+ if (m_sliceView)
+ return;
+
+ m_sliceView = new QQuick3DViewport();
+ m_sliceView->setParent(parent());
+ m_sliceView->setParentItem(parentItem());
+ m_sliceView->setVisible(false);
+ m_sliceView->setWidth(width());
+ m_sliceView->setHeight(height());
+
+ auto scene = m_sliceView->scene();
+
+ auto camera = new QQuick3DOrthographicCamera(scene);
+ camera->setPosition(QVector3D(.0f, .0f, 20.0f));
+ m_sliceView->setCamera(camera);
+
+ auto light = new QQuick3DDirectionalLight(scene);
+ light->setParent(camera);
+ light->setParentItem(camera);
+
+ m_sliceHorizontalGridRepeater = createRepeater();
+ m_sliceHorizontalGridRepeater->setParent(scene);
+ m_sliceHorizontalGridRepeater->setParentItem(scene);
+ auto gridDelegate = createRepeaterDelegateComponent(QStringLiteral(":/axis/GridLine"));
+ m_sliceHorizontalGridRepeater->setDelegate(gridDelegate);
+
+ m_sliceVerticalGridRepeater = createRepeater();
+ m_sliceVerticalGridRepeater->setParent(scene);
+ m_sliceVerticalGridRepeater->setParentItem(scene);
+ m_sliceVerticalGridRepeater->setDelegate(gridDelegate);
+
+ m_sliceHorizontalLabelRepeater = createRepeater();
+ m_sliceHorizontalLabelRepeater->setParent(scene);
+ m_sliceHorizontalLabelRepeater->setParentItem(scene);
+ auto labelDelegate = createRepeaterDelegateComponent(QStringLiteral(":/axis/AxisLabel"));
+ m_sliceHorizontalLabelRepeater->setDelegate(labelDelegate);
+
+ m_sliceVerticalLabelRepeater = createRepeater();
+ m_sliceVerticalLabelRepeater->setParent(scene);
+ m_sliceVerticalLabelRepeater->setParentItem(scene);
+ m_sliceVerticalLabelRepeater->setDelegate(labelDelegate);
+
+ m_sliceHorizontalTitleLabel = createTitleLabel();
+ m_sliceHorizontalTitleLabel->setParent(scene);
+ m_sliceHorizontalTitleLabel->setParentItem(scene);
+ m_sliceHorizontalTitleLabel->setVisible(true);
+
+ m_sliceVerticalTitleLabel = createTitleLabel();
+ m_sliceVerticalTitleLabel->setParent(scene);
+ m_sliceVerticalTitleLabel->setParentItem(scene);
+ m_sliceVerticalTitleLabel->setVisible(true);
+
+ m_sliceItemLabel = createTitleLabel();
+ m_sliceItemLabel->setParent(scene);
+ m_sliceItemLabel->setParentItem(scene);
+ m_sliceItemLabel->setVisible(false);
+}
+
+void QQuickGraphsItem::updateSliceGrid()
+{
+ QAbstract3DAxis *horizontalAxis = nullptr;
+ QAbstract3DAxis *verticalAxis = m_controller->axisY();
+ auto backgroundScale = m_scaleWithBackground + m_backgroundScaleMargin;
+ float scale;
+ float translate;
+
+ QVector3D horizontalScale = QVector3D(.0f, .0f, .0f);
+ QVector3D verticalScale = QVector3D(lineWidthScaleFactor(),
+ backgroundScale.y() * lineLengthScaleFactor(),
+ lineWidthScaleFactor());
+ auto selectionMode = m_controller->selectionMode();
+ if (selectionMode.testFlag(QAbstract3DGraph::SelectionRow)) {
+ horizontalAxis = m_controller->axisX();
+ horizontalScale = QVector3D(backgroundScale.x() * lineLengthScaleFactor(),
+ lineWidthScaleFactor(),
+ lineWidthScaleFactor());
+ scale = m_scaleWithBackground.x();
+ translate = m_scaleWithBackground.x();
+ } else if (selectionMode.testFlag(QAbstract3DGraph::SelectionColumn)) {
+ horizontalAxis = m_controller->axisZ();
+ horizontalScale = QVector3D(backgroundScale.z() * lineLengthScaleFactor(),
+ lineWidthScaleFactor(),
+ lineWidthScaleFactor());
+ scale = m_scaleWithBackground.z();
+ translate = m_scaleWithBackground.z();
+ }
+
+ if (horizontalAxis == nullptr) {
+ qWarning("Invalid axis type");
+ return;
+ }
+
+ if (horizontalAxis->type() & QAbstract3DAxis::AxisTypeValue) {
+ QValue3DAxis *valueAxis = static_cast<QValue3DAxis *>(horizontalAxis);
+ m_sliceVerticalGridRepeater->setModel(valueAxis->gridSize()
+ + valueAxis->subGridSize());
+ } else if (horizontalAxis->type() & QAbstract3DAxis::AxisTypeCategory) {
+ m_sliceVerticalGridRepeater->setModel(horizontalAxis->labels().size());
+ }
+
+ if (verticalAxis->type() & QAbstract3DAxis::AxisTypeValue) {
+ QValue3DAxis *valueAxis = static_cast<QValue3DAxis *>(verticalAxis);
+ m_sliceHorizontalGridRepeater->setModel(valueAxis->gridSize()
+ + valueAxis->subGridSize());
+ } else if (horizontalAxis->type() & QAbstract3DAxis::AxisTypeCategory) {
+ m_sliceHorizontalGridRepeater->setModel(verticalAxis->labels().size());
+ }
+
+ float linePosX = .0f;
+ float linePosY = .0f;
+ float linePosZ = .0f;
+
+ if (horizontalAxis->type() == QAbstract3DAxis::AxisTypeCategory) {
+ m_sliceVerticalGridRepeater->setVisible(false);
+ } else if (horizontalAxis->type() == QAbstract3DAxis::AxisTypeValue) {
+ for (int i = 0; i < m_sliceVerticalGridRepeater->count(); i++) {
+ QQuick3DNode *lineNode = static_cast<QQuick3DNode *>(m_sliceVerticalGridRepeater->objectAt(i));
+ auto axis = static_cast<QValue3DAxis *>(horizontalAxis);
+ if (i % 2 == 0)
+ linePosX = axis->gridPositionAt(i / 2) * scale * 2.0f - translate;
+ else
+ linePosX = axis->subGridPositionAt(i / 2) * scale * 2.0f - translate;
+ lineNode->setProperty("lineColor", QColor(0, 0, 0));
+ positionAndScaleLine(lineNode, verticalScale, QVector3D(linePosX, linePosY, linePosZ));
+ }
+ }
+
+ linePosX = 0;
+ scale = m_scaleWithBackground.y();
+ translate = m_scaleWithBackground.y();
+
+ for (int i = 0; i < m_sliceHorizontalGridRepeater->count(); i++) {
+ QQuick3DNode *lineNode = static_cast<QQuick3DNode *>(m_sliceHorizontalGridRepeater->objectAt(i));
+ if (verticalAxis->type() == QAbstract3DAxis::AxisTypeValue) {
+ auto axis = static_cast<QValue3DAxis *>(verticalAxis);
+ if (axis->subGridSize() > 0) {
+ if (i % 2 == 0)
+ linePosY = axis->gridPositionAt(i / 2) * scale * 2.0f - translate;
+ else
+ linePosY = axis->subGridPositionAt(i / 2) * scale * 2.0f - translate;
+ } else {
+ linePosY = axis->gridPositionAt(i) * scale * 2.0f - translate;
+ }
+ } else if (verticalAxis->type() == QAbstract3DAxis::AxisTypeCategory) {
+ linePosY = calculateCategoryGridLinePosition(verticalAxis, i);
+ }
+ lineNode->setProperty("lineColor", QColor(0, 0, 0));
+ positionAndScaleLine(lineNode, horizontalScale, QVector3D(linePosX, linePosY, linePosZ));
+ }
+}
+
+void QQuickGraphsItem::updateSliceLabels()
+{
+ QAbstract3DAxis *horizontalAxis = nullptr;
+ QAbstract3DAxis *verticalAxis = m_controller->axisY();
+ auto backgroundScale = m_scaleWithBackground + m_backgroundScaleMargin;
+ float scale;
+ float translate;
+ float scaleFactor;
+ auto pointSize = m_controller->activeTheme()->font().pointSizeF();
+ auto selectionMode = m_controller->selectionMode();
+
+ if (selectionMode.testFlag(QAbstract3DGraph::SelectionRow))
+ horizontalAxis = m_controller->axisX();
+ else if (selectionMode.testFlag(QAbstract3DGraph::SelectionColumn))
+ horizontalAxis = m_controller->axisZ();
+
+ scale = backgroundScale.x() - m_backgroundScaleMargin.x();
+ translate = backgroundScale.x() - m_backgroundScaleMargin.x();
+ scaleFactor = m_labelScale.x() * m_labelFontScaleFactor / pointSize
+ + m_labelScale.x() * m_fontScaleFactor;
+
+ if (horizontalAxis == nullptr) {
+ qWarning("Invalid selection mode");
+ return;
+ }
+
+ if (horizontalAxis->type() & QAbstract3DAxis::AxisTypeValue) {
+ QValue3DAxis *valueAxis = static_cast<QValue3DAxis *>(horizontalAxis);
+ m_sliceHorizontalLabelRepeater->setModel(valueAxis->labels().size());
+ } else if (horizontalAxis->type() & QAbstract3DAxis::AxisTypeCategory) {
+ m_sliceHorizontalLabelRepeater->setModel(horizontalAxis->labels().size());
+ }
+
+ if (verticalAxis->type() & QAbstract3DAxis::AxisTypeValue) {
+ QValue3DAxis *valueAxis = static_cast<QValue3DAxis *>(verticalAxis);
+ m_sliceVerticalLabelRepeater->setModel(valueAxis->labels().size());
+ } else if (horizontalAxis->type() & QAbstract3DAxis::AxisTypeCategory) {
+ m_sliceVerticalLabelRepeater->setModel(verticalAxis->labels().size());
+ }
+
+ float textPadding = 12.0f;
+ scaleFactor *= .8f;
+
+ QVector3D fontScaled = QVector3D(scaleFactor, scaleFactor, 0.0f);
+
+ float labelsMaxWidth = 0.0f;
+ labelsMaxWidth = qMax(labelsMaxWidth, float(findLabelsMaxWidth(horizontalAxis->labels()))) + textPadding;
+ QFontMetrics fm(m_controller->activeTheme()->font());
+ float labelHeight = fm.height() + textPadding;
+
+ float adjustment = labelsMaxWidth * scaleFactor;
+ float yPos = backgroundScale.y() + adjustment;
+
+ QVector3D labelTrans = QVector3D(0.0f, -yPos, 0.0f);
+ QStringList labels = horizontalAxis->labels();
+ Q3DTheme *theme = m_controller->activeTheme();
+ QFont font = theme->font();
+ bool borderEnabled = theme->isLabelBorderEnabled();
+ QColor labelTextColor = theme->labelTextColor();
+ bool backgroundEnabled = theme->isLabelBackgroundEnabled();
+ QColor backgroundColor = theme->labelBackgroundColor();
+
+ if (horizontalAxis->type() == QAbstract3DAxis::AxisTypeValue) {
+ auto valueAxis = static_cast<QValue3DAxis *>(horizontalAxis);
+ for (int i = 0; i < m_sliceHorizontalLabelRepeater->count(); i++) {
+ auto obj = static_cast<QQuick3DNode *>(m_sliceHorizontalLabelRepeater->objectAt(i));
+ labelTrans.setX(valueAxis->labelPositionAt(i) * scale * 2.0f - translate);
+ obj->setScale(fontScaled);
+ obj->setPosition(labelTrans);
+ obj->setProperty("labelText", labels[i]);
+ obj->setProperty("labelWidth", labelsMaxWidth);
+ obj->setProperty("labelHeight", labelHeight);
+ obj->setProperty("labelFont", font);
+ obj->setProperty("borderEnabled", borderEnabled);
+ obj->setProperty("labelTextColor", labelTextColor);
+ obj->setProperty("backgroundEnabled", backgroundEnabled);
+ obj->setProperty("backgroundColor", backgroundColor);
+ obj->setEulerRotation(QVector3D(.0f, .0f, -45.0f));
+ }
+ } else if (horizontalAxis->type() == QAbstract3DAxis::AxisTypeCategory) {
+ for (int i = 0; i < m_sliceHorizontalLabelRepeater->count(); i++) {
+ labelTrans = calculateCategoryLabelPosition(horizontalAxis, labelTrans, i);
+ labelTrans.setY(labelTrans.y() - (adjustment / 1.5f));
+ auto obj = static_cast<QQuick3DNode *>(m_sliceHorizontalLabelRepeater->objectAt(i));
+ obj->setScale(fontScaled);
+ obj->setPosition(labelTrans);
+ obj->setProperty("labelText", labels[i]);
+ obj->setProperty("labelWidth", labelsMaxWidth);
+ obj->setProperty("labelHeight", labelHeight);
+ obj->setProperty("labelFont", font);
+ obj->setProperty("borderEnabled", borderEnabled);
+ obj->setProperty("labelTextColor", labelTextColor);
+ obj->setProperty("backgroundEnabled", backgroundEnabled);
+ obj->setProperty("backgroundColor", backgroundColor);
+ obj->setEulerRotation(QVector3D(0.0f, 0.0f, -60.0f));
+ }
+ }
+
+ scale = backgroundScale.y() - m_backgroundScaleMargin.y();
+ translate = backgroundScale.y() - m_backgroundScaleMargin.y();
+ labels = verticalAxis->labels();
+ labelsMaxWidth = qMax(labelsMaxWidth, float(findLabelsMaxWidth(labels))) + textPadding;
+ adjustment = labelsMaxWidth * scaleFactor;
+ float xPos = backgroundScale.x() + adjustment;
+ labelTrans = QVector3D(xPos, 0.0f, 0.0f);
+
+ if (verticalAxis->type() == QAbstract3DAxis::AxisTypeValue) {
+ auto valueAxis = static_cast<QValue3DAxis *>(verticalAxis);
+ for (int i = 0; i < m_sliceVerticalLabelRepeater->count(); i++) {
+ auto obj = static_cast<QQuick3DNode *>(m_sliceVerticalLabelRepeater->objectAt(i));
+ labelTrans.setY(valueAxis->labelPositionAt(i) * scale * 2.0f - translate);
+ obj->setScale(fontScaled);
+ obj->setPosition(labelTrans);
+ obj->setProperty("labelText", labels[i]);
+ obj->setProperty("labelWidth", labelsMaxWidth);
+ obj->setProperty("labelHeight", labelHeight);
+ obj->setProperty("labelFont", font);
+ obj->setProperty("borderEnabled", borderEnabled);
+ obj->setProperty("labelTextColor", labelTextColor);
+ obj->setProperty("backgroundEnabled", backgroundEnabled);
+ obj->setProperty("backgroundColor", backgroundColor);
+ }
+ } else if (verticalAxis->type() == QAbstract3DAxis::AxisTypeCategory) {
+ for (int i = 0; i < m_sliceVerticalLabelRepeater->count(); i++) {
+ labelTrans = calculateCategoryLabelPosition(verticalAxis, labelTrans, i);
+ auto obj = static_cast<QQuick3DNode *>(m_sliceVerticalLabelRepeater->objectAt(i));
+ obj->setScale(fontScaled);
+ obj->setPosition(labelTrans);
+ obj->setProperty("labelText", labels[i]);
+ obj->setProperty("labelWidth", labelsMaxWidth);
+ obj->setProperty("labelHeight", labelHeight);
+ obj->setProperty("labelFont", font);
+ obj->setProperty("borderEnabled", borderEnabled);
+ obj->setProperty("labelTextColor", labelTextColor);
+ obj->setProperty("backgroundEnabled", backgroundEnabled);
+ obj->setProperty("backgroundColor", backgroundColor);
+ }
+ }
+
+ labelHeight = fm.height() + textPadding;
+ float labelWidth = fm.horizontalAdvance(verticalAxis->title()) + textPadding;
+ adjustment = labelHeight * scaleFactor;
+ xPos = backgroundScale.x() + adjustment;
+ labelTrans = QVector3D(-xPos, 0.0f, 0.0f);
+
+ if (!verticalAxis->title().isEmpty()) {
+ m_sliceVerticalTitleLabel->setScale(fontScaled);
+ m_sliceVerticalTitleLabel->setPosition(labelTrans);
+ m_sliceVerticalTitleLabel->setProperty("labelWidth", labelWidth);
+ m_sliceVerticalTitleLabel->setProperty("labelHeight", labelHeight);
+ m_sliceVerticalTitleLabel->setProperty("labelText", verticalAxis->title());
+ m_sliceVerticalTitleLabel->setProperty("labelFont", font);
+ m_sliceVerticalTitleLabel->setProperty("borderEnabled", borderEnabled);
+ m_sliceVerticalTitleLabel->setProperty("labelTextColor", labelTextColor);
+ m_sliceVerticalTitleLabel->setProperty("backgroundEnabled", backgroundEnabled);
+ m_sliceVerticalTitleLabel->setProperty("backgroundColor", backgroundColor);
+ m_sliceVerticalTitleLabel->setEulerRotation(QVector3D(.0f, .0f, 90.0f));
+ } else {
+ m_sliceVerticalTitleLabel->setVisible(false);
+ }
+
+ labelHeight = fm.height() + textPadding;
+ labelWidth = fm.horizontalAdvance(horizontalAxis->title()) + textPadding;
+ adjustment = labelHeight * scaleFactor;
+ yPos = backgroundScale.y() * 1.5f + adjustment;
+ labelTrans = QVector3D(0.0f, -yPos, 0.0f);
+
+ if (!horizontalAxis->title().isEmpty()) {
+ m_sliceHorizontalTitleLabel->setScale(fontScaled);
+ m_sliceHorizontalTitleLabel->setPosition(labelTrans);
+ m_sliceHorizontalTitleLabel->setProperty("labelWidth", labelWidth);
+ m_sliceHorizontalTitleLabel->setProperty("labelHeight", labelHeight);
+ m_sliceHorizontalTitleLabel->setProperty("labelText", horizontalAxis->title());
+ m_sliceHorizontalTitleLabel->setProperty("labelFont", font);
+ m_sliceHorizontalTitleLabel->setProperty("borderEnabled", borderEnabled);
+ m_sliceHorizontalTitleLabel->setProperty("labelTextColor", labelTextColor);
+ m_sliceHorizontalTitleLabel->setProperty("backgroundEnabled", backgroundEnabled);
+ m_sliceHorizontalTitleLabel->setProperty("backgroundColor", backgroundColor);
+ } else {
+ m_sliceHorizontalTitleLabel->setVisible(false);
+ }
+
+ m_sliceItemLabel->setScale(fontScaled);
+ m_sliceItemLabel->setProperty("labelWidth", labelWidth);
+ m_sliceItemLabel->setProperty("labelHeight", labelHeight);
+ m_sliceItemLabel->setProperty("labelFont", font);
+ m_sliceItemLabel->setProperty("borderEnabled", borderEnabled);
+ m_sliceItemLabel->setProperty("labelTextColor", labelTextColor);
+ m_sliceItemLabel->setProperty("backgroundEnabled", backgroundEnabled);
+ m_sliceItemLabel->setProperty("backgroundColor", backgroundColor);
+}
+
+void QQuickGraphsItem::setUpCamera()
+{
+ auto useOrtho = m_controller->isOrthoProjection();
+ QQuick3DCamera *camera;
+ if (!useOrtho) {
+ auto persCamera = new QQuick3DPerspectiveCamera(rootNode());
+ persCamera->setClipNear(0.001f);
+ persCamera->setFieldOfView(45.0f);
+ camera = persCamera;
+ } else {
+ auto orthCamera = new QQuick3DOrthographicCamera(rootNode());
+ camera = orthCamera;
+ }
+
+ QQuick3DObjectPrivate::get(camera)->refSceneManager(
+ *QQuick3DObjectPrivate::get(rootNode())->sceneManager);
+ auto cameraTarget = new QQuick3DNode(rootNode());
+
+ setCameraTarget(cameraTarget);
+ cameraTarget->setPosition(QVector3D(0, 0, 0));
+ QQuick3DObjectPrivate::get(cameraTarget)->refSceneManager(
+ *QQuick3DObjectPrivate::get(rootNode())->sceneManager);
+
+ camera->setParent(cameraTarget);
+ camera->setParentItem(cameraTarget);
+
+ camera->setPosition(QVector3D(0, 0, 5));
+ camera->lookAt(cameraTarget);
+ setCamera(camera);
+}
+
+void QQuickGraphsItem::setUpLight()
+{
+ auto light = new QQuick3DDirectionalLight(rootNode());
+ QQuick3DObjectPrivate::get(light)->refSceneManager(
+ *QQuick3DObjectPrivate::get(rootNode())->sceneManager);
+ light->setParent(camera());
+ light->setParentItem(camera());
+ m_light = light;
+}
+
+QT_END_NAMESPACE
diff --git a/src/graphs/qml/qquickgraphsitem_p.h b/src/graphs/qml/qquickgraphsitem_p.h
new file mode 100644
index 0000000..3e21883
--- /dev/null
+++ b/src/graphs/qml/qquickgraphsitem_p.h
@@ -0,0 +1,471 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QQUICKGRAPHSITEM_H
+#define QQUICKGRAPHSITEM_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QtGraphs API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#include "qabstract3dgraph.h"
+
+#include <QtQuick3D/private/qquick3dviewport_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class Abstract3DController;
+class Declarative3DScene;
+class Q3DTheme;
+class QAbstract3DAxis;
+class QAbstract3DInputHandler;
+class QAbstract3DSeries;
+class QCustom3DItem;
+class QQuick3DCustomMaterial;
+class QQuick3DDirectionalLight;
+class QQuick3DPrincipledMaterial;
+class QQuick3DRepeater;
+
+class QQuickGraphsItem : public QQuick3DViewport
+{
+ Q_OBJECT
+ Q_PROPERTY(SelectionFlags selectionMode READ selectionMode WRITE setSelectionMode NOTIFY selectionModeChanged)
+ Q_PROPERTY(ShadowQuality shadowQuality READ shadowQuality WRITE setShadowQuality NOTIFY shadowQualityChanged)
+ Q_PROPERTY(bool shadowsSupported READ shadowsSupported NOTIFY shadowsSupportedChanged)
+ Q_PROPERTY(int msaaSamples READ msaaSamples WRITE setMsaaSamples NOTIFY msaaSamplesChanged)
+ Q_PROPERTY(Declarative3DScene *scene READ scene NOTIFY sceneChanged)
+ Q_PROPERTY(QAbstract3DInputHandler *inputHandler READ inputHandler WRITE setInputHandler NOTIFY inputHandlerChanged)
+ Q_PROPERTY(Q3DTheme *theme READ theme WRITE setTheme NOTIFY themeChanged)
+ Q_PROPERTY(RenderingMode renderingMode READ renderingMode WRITE setRenderingMode NOTIFY renderingModeChanged)
+ Q_PROPERTY(bool measureFps READ measureFps WRITE setMeasureFps NOTIFY measureFpsChanged)
+ Q_PROPERTY(int currentFps READ currentFps NOTIFY currentFpsChanged)
+ Q_PROPERTY(QQmlListProperty<QCustom3DItem> customItemList READ customItemList CONSTANT)
+ Q_PROPERTY(bool orthoProjection READ isOrthoProjection WRITE setOrthoProjection NOTIFY orthoProjectionChanged)
+ Q_PROPERTY(ElementType selectedElement READ selectedElement NOTIFY selectedElementChanged)
+ Q_PROPERTY(qreal aspectRatio READ aspectRatio WRITE setAspectRatio NOTIFY aspectRatioChanged)
+ Q_PROPERTY(OptimizationHints optimizationHints READ optimizationHints WRITE setOptimizationHints NOTIFY optimizationHintsChanged)
+ Q_PROPERTY(bool polar READ isPolar WRITE setPolar NOTIFY polarChanged)
+ Q_PROPERTY(float radialLabelOffset READ radialLabelOffset WRITE setRadialLabelOffset NOTIFY radialLabelOffsetChanged)
+ Q_PROPERTY(qreal horizontalAspectRatio READ horizontalAspectRatio WRITE setHorizontalAspectRatio NOTIFY horizontalAspectRatioChanged)
+ Q_PROPERTY(bool reflection READ isReflection WRITE setReflection NOTIFY reflectionChanged)
+ Q_PROPERTY(qreal reflectivity READ reflectivity WRITE setReflectivity NOTIFY reflectivityChanged)
+ Q_PROPERTY(QLocale locale READ locale WRITE setLocale NOTIFY localeChanged)
+ Q_PROPERTY(QVector3D queriedGraphPosition READ queriedGraphPosition NOTIFY queriedGraphPositionChanged)
+ Q_PROPERTY(qreal margin READ margin WRITE setMargin NOTIFY marginChanged)
+
+ QML_NAMED_ELEMENT(AbstractGraph3D)
+ QML_ADDED_IN_VERSION(6, 6)
+ QML_UNCREATABLE("Trying to create uncreatable: AbstractGraph3D.")
+
+public:
+ enum SelectionFlag {
+ SelectionNone = 0,
+ SelectionItem = 1,
+ SelectionRow = 2,
+ SelectionItemAndRow = SelectionItem | SelectionRow,
+ SelectionColumn = 4,
+ SelectionItemAndColumn = SelectionItem | SelectionColumn,
+ SelectionRowAndColumn = SelectionRow | SelectionColumn,
+ SelectionItemRowAndColumn = SelectionItem | SelectionRow | SelectionColumn,
+ SelectionSlice = 8,
+ SelectionMultiSeries = 16
+ };
+ Q_DECLARE_FLAGS(SelectionFlags, SelectionFlag)
+
+ enum ShadowQuality {
+ ShadowQualityNone = 0,
+ ShadowQualityLow,
+ ShadowQualityMedium,
+ ShadowQualityHigh,
+ ShadowQualitySoftLow,
+ ShadowQualitySoftMedium,
+ ShadowQualitySoftHigh
+ };
+
+ enum ElementType {
+ ElementNone = 0,
+ ElementSeries,
+ ElementAxisXLabel,
+ ElementAxisYLabel,
+ ElementAxisZLabel,
+ ElementCustomItem
+ };
+
+ enum RenderingMode {
+ RenderDirectToBackground = 0,
+ RenderDirectToBackground_NoClear,
+ RenderIndirect
+ };
+
+ enum OptimizationHint {
+ OptimizationDefault = 0,
+ OptimizationStatic = 1
+ };
+ Q_DECLARE_FLAGS(OptimizationHints, OptimizationHint)
+
+ Q_ENUM(ShadowQuality)
+ Q_ENUM(RenderingMode)
+ Q_ENUM(ElementType)
+ Q_ENUM(SelectionFlag)
+ Q_ENUM(OptimizationHint)
+ Q_FLAGS(SelectionFlag SelectionFlags)
+ Q_FLAGS(OptimizationHint OptimizationHints)
+
+public:
+ explicit QQuickGraphsItem(QQuickItem *parent = 0);
+ virtual ~QQuickGraphsItem();
+
+ virtual void setRenderingMode(RenderingMode mode);
+ virtual QQuickGraphsItem::RenderingMode renderingMode() const;
+
+ virtual void setSelectionMode(SelectionFlags mode);
+ virtual QQuickGraphsItem::SelectionFlags selectionMode() const;
+
+ virtual void setShadowQuality(ShadowQuality quality);
+ virtual QQuickGraphsItem::ShadowQuality shadowQuality() const;
+
+ virtual QQuickGraphsItem::ElementType selectedElement() const;
+
+ virtual bool shadowsSupported() const;
+
+ virtual void setMsaaSamples(int samples);
+ virtual int msaaSamples() const;
+
+ virtual Declarative3DScene *scene() const;
+
+ virtual QAbstract3DInputHandler *inputHandler() const;
+ virtual void setInputHandler(QAbstract3DInputHandler *inputHandler);
+
+ virtual void setTheme(Q3DTheme *theme);
+ virtual Q3DTheme *theme() const;
+
+ Q_INVOKABLE virtual void clearSelection();
+
+ Q_INVOKABLE virtual bool hasSeries(QAbstract3DSeries *series);
+
+ Q_INVOKABLE virtual int addCustomItem(QCustom3DItem *item);
+ Q_INVOKABLE virtual void removeCustomItems();
+ Q_INVOKABLE virtual void removeCustomItem(QCustom3DItem *item);
+ Q_INVOKABLE virtual void removeCustomItemAt(const QVector3D &position);
+ Q_INVOKABLE virtual void releaseCustomItem(QCustom3DItem *item);
+
+ Q_INVOKABLE virtual int selectedLabelIndex() const;
+ Q_INVOKABLE virtual QAbstract3DAxis *selectedAxis() const;
+
+ Q_INVOKABLE virtual int selectedCustomItemIndex() const;
+ Q_INVOKABLE virtual QCustom3DItem *selectedCustomItem() const;
+
+ QQmlListProperty<QCustom3DItem> customItemList();
+ static void appendCustomItemFunc(QQmlListProperty<QCustom3DItem> *list,
+ QCustom3DItem *item);
+ static qsizetype countCustomItemFunc(QQmlListProperty<QCustom3DItem> *list);
+ static QCustom3DItem *atCustomItemFunc(QQmlListProperty<QCustom3DItem> *list, qsizetype index);
+ static void clearCustomItemFunc(QQmlListProperty<QCustom3DItem> *list);
+
+ void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override;
+
+ void setSharedController(Abstract3DController *controller);
+
+ void checkWindowList(QQuickWindow *window);
+
+ void setMeasureFps(bool enable);
+ bool measureFps() const;
+ int currentFps() const;
+
+ void setOrthoProjection(bool enable);
+ bool isOrthoProjection() const;
+
+ void setAspectRatio(qreal ratio);
+ qreal aspectRatio() const;
+
+ void setOptimizationHints(OptimizationHints hints);
+ OptimizationHints optimizationHints() const;
+
+ void setPolar(bool enable);
+ bool isPolar() const;
+
+ void setRadialLabelOffset(float offset);
+ float radialLabelOffset() const;
+
+ void setHorizontalAspectRatio(qreal ratio);
+ qreal horizontalAspectRatio() const;
+
+ void setReflection(bool enable);
+ bool isReflection() const;
+
+ void setReflectivity(qreal reflectivity);
+ qreal reflectivity() const;
+
+ void setLocale(const QLocale &locale);
+ QLocale locale() const;
+
+ QVector3D queriedGraphPosition() const;
+
+ void setMargin(qreal margin);
+ qreal margin() const;
+
+ QMutex *mutex() { return &m_mutex; }
+
+ bool isReady() { return isComponentComplete(); }
+ QQuick3DNode *rootNode() const;
+
+ QQuick3DNode *cameraTarget() { return m_cameraTarget; }
+ void setCameraTarget(QQuick3DNode *target) { m_cameraTarget = target; }
+
+ QQuick3DModel *background() const { return m_background; }
+ void setBackground(QQuick3DModel *newBackground) { m_background = newBackground; }
+ inline QQuick3DModel *backgroundBB() const { return m_backgroundBB; }
+
+ QQuick3DDirectionalLight *light() const;
+ QQuick3DCustomMaterial *createQmlCustomMaterial(const QString &fileName);
+ QQuick3DPrincipledMaterial *createPrincipledMaterial();
+
+ QQuick3DNode *itemLabel() { return m_itemLabel; }
+ QQuick3DNode *sliceItemLabel() { return m_sliceItemLabel; }
+
+ QQuick3DModel *m_targetVisualizer;
+
+ QQuick3DRepeater *repeaterX() const { return m_repeaterX; }
+ QQuick3DRepeater *repeaterY() const { return m_repeaterY; }
+ QQuick3DRepeater *repeaterZ() const { return m_repeaterZ; }
+
+ QQuick3DNode *titleLabelX() const { return m_titleLabelX; }
+ QQuick3DNode *titleLabelY() const { return m_titleLabelY; }
+ QQuick3DNode *titleLabelZ() const { return m_titleLabelZ; }
+
+ QQuick3DRepeater *segmentLineRepeaterX() const { return m_segmentLineRepeaterX; }
+ QQuick3DRepeater *segmentLineRepeaterY() const { return m_segmentLineRepeaterY; }
+ QQuick3DRepeater *segmentLineRepeaterZ() const { return m_segmentLineRepeaterZ; }
+
+ QQuick3DRepeater *subsegmentLineRepeaterX() const { return m_subsegmentLineRepeaterX; }
+ QQuick3DRepeater *subsegmentLineRepeaterY() const { return m_subsegmentLineRepeaterY; }
+ QQuick3DRepeater *subsegmentLineRepeaterZ() const { return m_subsegmentLineRepeaterZ; }
+
+ bool isXFlipped() const { return m_xFlipped; }
+ void setXFlipped(bool xFlipped) { m_xFlipped = xFlipped; }
+ bool isYFlipped() const { return m_yFlipped; }
+ void setYFlipped(bool yFlipped) { m_yFlipped = yFlipped; }
+ bool isZFlipped() const { return m_zFlipped; }
+ void setZFlipped(bool zFlipped) { m_zFlipped = zFlipped; }
+ QVector3D scaleWithBackground() const { return m_scaleWithBackground; }
+ void setScaleWithBackground(const QVector3D &scale) { m_scaleWithBackground = scale; }
+ void setBackgroundScaleMargin(const QVector3D &margin) { m_backgroundScaleMargin = margin; }
+ QVector3D rotation() const { return m_rot; }
+ void setRotation(const QVector3D &rotation) { m_rot = rotation; }
+ QVector3D scale() const { return m_scale; }
+ void setScale(const QVector3D &scale) { m_scale = scale; }
+ QVector3D translate() const { return m_translate; }
+ void setTranslate(const QVector3D &translate) { m_translate = translate;}
+
+ float lineLengthScaleFactor() const { return m_lineLengthScaleFactor; }
+ void setLineLengthScaleFactor(float scaleFactor) { m_lineLengthScaleFactor = scaleFactor; }
+ float lineWidthScaleFactor() const { return m_lineWidthScaleFactor; }
+ void setLineWidthScaleFactor(float scaleFactor) { m_lineWidthScaleFactor = scaleFactor; }
+ float gridOffset() const { return m_gridOffset; }
+ void setLabelMargin(float margin) { m_labelMargin = margin; }
+ float labelMargin() const { return m_labelMargin; }
+
+ void changeLabelBackgroundColor(QQuick3DRepeater *repeater, const QColor &color);
+ void changeLabelBackgroundEnabled(QQuick3DRepeater *repeater, const bool &enabled);
+ void changeLabelBorderEnabled(QQuick3DRepeater *repeater, const bool &enabled);
+ void changeLabelTextColor(QQuick3DRepeater *repeater, const QColor &color);
+ void changeLabelFont(QQuick3DRepeater *repeater, const QFont &font);
+ void changeGridLineColor(QQuick3DRepeater *repeater, const QColor &color);
+ void updateTitleLabels();
+
+public Q_SLOTS:
+ virtual void handleAxisXChanged(QAbstract3DAxis *axis) = 0;
+ virtual void handleAxisYChanged(QAbstract3DAxis *axis) = 0;
+ virtual void handleAxisZChanged(QAbstract3DAxis *axis) = 0;
+ void handleFpsChanged();
+ void windowDestroyed(QObject *obj);
+
+Q_SIGNALS:
+ void selectionModeChanged(QQuickGraphsItem::SelectionFlags mode);
+ void shadowQualityChanged(QQuickGraphsItem::ShadowQuality quality);
+ void shadowsSupportedChanged(bool supported);
+ void msaaSamplesChanged(int samples);
+ void inputHandlerChanged(QAbstract3DInputHandler *inputHandler);
+ void themeChanged(Q3DTheme *theme);
+ void renderingModeChanged(QQuickGraphsItem::RenderingMode mode);
+ void measureFpsChanged(bool enabled);
+ void currentFpsChanged(int fps);
+ void selectedElementChanged(QQuickGraphsItem::ElementType type);
+ void orthoProjectionChanged(bool enabled);
+ void aspectRatioChanged(qreal ratio);
+ void optimizationHintsChanged(QQuickGraphsItem::OptimizationHints hints);
+ void polarChanged(bool enabled);
+ void radialLabelOffsetChanged(float offset);
+ void horizontalAspectRatioChanged(qreal ratio);
+ void reflectionChanged(bool enabled);
+ void reflectivityChanged(qreal reflectivity);
+ void localeChanged(const QLocale &locale);
+ void queriedGraphPositionChanged(const QVector3D &data);
+ void marginChanged(qreal margin);
+
+protected:
+ bool event(QEvent *event) override;
+ void mouseDoubleClickEvent(QMouseEvent *event) override;
+ void touchEvent(QTouchEvent *event) override;
+ void mousePressEvent(QMouseEvent *event) override;
+ void mouseReleaseEvent(QMouseEvent *event) override;
+ void mouseMoveEvent(QMouseEvent *event) override;
+#if QT_CONFIG(wheelevent)
+ void wheelEvent(QWheelEvent *event) override;
+#endif
+ virtual void handleWindowChanged(/*QQuickWindow *win*/);
+ void itemChange(ItemChange change, const ItemChangeData &value) override;
+ virtual void updateWindowParameters();
+ virtual void handleSelectionModeChange(QAbstract3DGraph::SelectionFlags mode);
+ virtual void handleShadowQualityChange(QAbstract3DGraph::ShadowQuality quality);
+ virtual void handleSelectedElementChange(QAbstract3DGraph::ElementType type);
+ virtual void handleOptimizationHintChange(QAbstract3DGraph::OptimizationHints hints);
+ virtual void keyPressEvent(QKeyEvent *ev) override;
+ virtual bool handleMousePressedEvent(QMouseEvent *event);
+ virtual void handleThemeTypeChange();
+
+ void componentComplete() override;
+
+ void createSliceView();
+
+ QQuick3DNode *graphNode() { return m_graphNode; }
+ QQuick3DViewport *sliceView() { return m_sliceView; }
+
+ QQmlComponent *createRepeaterDelegateComponent(const QString &fileName);
+ QQuick3DRepeater *createRepeater();
+ QQuick3DNode *createTitleLabel();
+
+ void updateXTitle(const QVector3D &labelRotation, const QVector3D &labelTrans,
+ const QQuaternion &totalRotation, float labelsMaxWidth, float labelHeight, const QVector3D &scale);
+ void updateYTitle(const QVector3D &sideLabelRotation, const QVector3D &backLabelRotation,
+ const QVector3D &sideLabelTrans, const QVector3D &backLabelTrans,
+ const QQuaternion &totalSideRotation, const QQuaternion &totalBackRotation,
+ float labelsMaxWidth, float labelHeight, const QVector3D &scale);
+ void updateZTitle(const QVector3D &labelRotation, const QVector3D &labelTrans,
+ const QQuaternion &totalRotation, float labelsMaxWidth, float labelHeight, const QVector3D &scale);
+
+ void positionAndScaleLine(QQuick3DNode *lineNode, QVector3D scale, QVector3D position);
+ int findLabelsMaxWidth(const QStringList &labels);
+ virtual QVector3D calculateCategoryLabelPosition(QAbstract3DAxis *axis, QVector3D labelPosition, int index);
+ virtual float calculateCategoryGridLinePosition(QAbstract3DAxis *axis, int index);
+ void setFloorGridInRange(bool inRange) { m_isFloorGridInRange = inRange; }
+ void setVerticalSegmentLine(bool hasVerticalLine) { m_hasVerticalSegmentLine = hasVerticalLine; }
+ void updateGrid();
+ void updateLabels();
+ void updateSliceGrid();
+ void updateSliceLabels();
+
+ virtual void synchData();
+ virtual void updateGraph() {}
+
+ bool isSliceEnabled() const { return m_sliceEnabled; }
+ void setSliceEnabled(bool enabled) { m_sliceEnabled = enabled; }
+ void setSliceActivatedChanged(bool changed) { m_sliceActivatedChanged = changed; }
+ virtual void updateSliceGraph();
+
+ virtual void updateShadowQuality(QQuickGraphsItem::ShadowQuality quality);
+ virtual void updateAxisRange(float min, float max);
+ virtual void updateAxisReversed(bool enable);
+ virtual void updateSingleHighlightColor() {}
+
+ QSharedPointer<QMutex> m_nodeMutex;
+
+private:
+ QQuick3DNode *m_graphNode = nullptr;
+ QQuick3DModel *m_background = nullptr;
+ QQuick3DModel *m_backgroundBB = nullptr;
+ QQuick3DNode *m_backgroundScale = nullptr;
+ QQuick3DNode *m_backgroundRotation = nullptr;
+
+ QQuick3DRepeater *m_repeaterX = nullptr;
+ QQuick3DRepeater *m_repeaterY = nullptr;
+ QQuick3DRepeater *m_repeaterZ = nullptr;
+
+ QQuick3DNode *m_titleLabelX = nullptr;
+ QQuick3DNode *m_titleLabelY = nullptr;
+ QQuick3DNode *m_titleLabelZ = nullptr;
+
+ QQuick3DNode *m_itemLabel = nullptr;
+ QQuick3DNode *m_sliceItemLabel = nullptr;
+
+ QQuick3DRepeater *m_segmentLineRepeaterX = nullptr;
+ QQuick3DRepeater *m_subsegmentLineRepeaterX = nullptr;
+ QQuick3DRepeater *m_segmentLineRepeaterY = nullptr;
+ QQuick3DRepeater *m_subsegmentLineRepeaterY = nullptr;
+ QQuick3DRepeater *m_segmentLineRepeaterZ = nullptr;
+ QQuick3DRepeater *m_subsegmentLineRepeaterZ = nullptr;
+
+ QQuick3DViewport *m_sliceView = nullptr;
+ QQuick3DRepeater *m_sliceHorizontalGridRepeater = nullptr;
+ QQuick3DRepeater *m_sliceVerticalGridRepeater = nullptr;
+ QQuick3DRepeater *m_sliceHorizontalLabelRepeater = nullptr;
+ QQuick3DRepeater *m_sliceVerticalLabelRepeater = nullptr;
+
+ QQuick3DNode *m_sliceHorizontalTitleLabel = nullptr;
+ QQuick3DNode *m_sliceVerticalTitleLabel = nullptr;
+
+ QPointer<Abstract3DController> m_controller;
+ QQuick3DNode *m_cameraTarget = nullptr;
+ QQuick3DDirectionalLight *m_light = nullptr;
+ QRectF m_cachedGeometry;
+ QQuickGraphsItem::RenderingMode m_renderMode;
+ int m_samples;
+ int m_windowSamples;
+ QSize m_initialisedSize;
+ bool m_runningInDesigner;
+ QMutex m_mutex;
+
+ bool m_xFlipped = false;
+ bool m_yFlipped = false;
+ bool m_zFlipped = false;
+
+ bool m_flipScales;
+
+ bool m_isFloorGridInRange = false;
+ bool m_hasVerticalSegmentLine = true;
+
+ QVector3D m_scaleWithBackground = QVector3D(1.0f, 1.0f, 1.0f);
+ QVector3D m_backgroundScaleMargin = QVector3D(0.0f, 0.0f, 0.0f);
+
+ QVector3D m_rot = QVector3D(1.0f, 1.0f, 1.0f);
+
+ QVector3D m_scale = QVector3D(1.0f, 1.0f, 1.0f);
+
+ QVector3D m_translate = QVector3D(1.0f, 1.0f, 1.0f);
+
+ QVector3D m_labelScale = QVector3D(0.01f, 0.01f, 0.0f);
+
+ float m_gridOffset = 0.002f;
+ float m_lineWidthScaleFactor = 0.0001f;
+ float m_lineLengthScaleFactor = 0.02f;
+
+ float m_labelFontScaleFactor = 4.0f;
+ float m_fontScaleFactor = .3f;
+
+ float m_labelMargin = 0.1f;
+
+ bool m_sliceEnabled = false;
+ bool m_sliceActivatedChanged = false;
+
+ void setUpCamera();
+ void setUpLight();
+ void graphPositionAt(const QPoint& point);
+ void updateCamera();
+ QVector3D calculateLabelRotation(float labelAutoAngle);
+
+ QHash<QQuickGraphsItem *, QQuickWindow *> m_graphWindowList = {};
+
+ friend class Scatter3DController;
+};
+Q_DECLARE_OPERATORS_FOR_FLAGS(QQuickGraphsItem::SelectionFlags)
+Q_DECLARE_OPERATORS_FOR_FLAGS(QQuickGraphsItem::OptimizationHints)
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/qml/qquickgraphsscatter.cpp b/src/graphs/qml/qquickgraphsscatter.cpp
new file mode 100644
index 0000000..6476923
--- /dev/null
+++ b/src/graphs/qml/qquickgraphsscatter.cpp
@@ -0,0 +1,987 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qml/qquickgraphsscatter_p.h"
+#include "qml/declarativescene_p.h"
+#include "data/qscatter3dseries_p.h"
+#include "qvalue3daxis_p.h"
+#include "qcategory3daxis_p.h"
+#include "axis/qvalue3daxisformatter_p.h"
+#include "engine/q3dcamera_p.h"
+#include "quickgraphstexturedata_p.h"
+
+#include <QtCore/QMutexLocker>
+#include <QColor>
+#include <QtQuick3D/private/qquick3drepeater_p.h>
+#include <QtQuick3D/private/qquick3dprincipledmaterial_p.h>
+#include <QtQuick3D/private/qquick3dperspectivecamera_p.h>
+#include <QtQuick3D/private/qquick3dmodel_p.h>
+#include <QtQuick3D/private/qquick3dcustommaterial_p.h>
+#include <QtQuick3D/private/qquick3ddirectionallight_p.h>
+#include <QtQuick3D/private/qquick3dpointlight_p.h>
+
+QT_BEGIN_NAMESPACE
+
+QQuickGraphsScatter::QQuickGraphsScatter(QQuickItem *parent)
+ : QQuickGraphsItem(parent),
+ m_scatterController(0)
+{
+ setAcceptedMouseButtons(Qt::AllButtons);
+ setFlag(ItemHasContents);
+ // Create the shared component on the main GUI thread.
+ m_scatterController = new Scatter3DController(boundingRect().toRect(), new Declarative3DScene);
+
+ setSharedController(m_scatterController);
+
+ QQuick3DSceneEnvironment *scene = environment();
+ scene->setBackgroundMode(QQuick3DSceneEnvironment::QQuick3DEnvironmentBackgroundTypes::Color);
+ scene->setClearColor(Qt::blue);
+
+ QObject::connect(m_scatterController, &Scatter3DController::selectedSeriesChanged,
+ this, &QQuickGraphsScatter::selectedSeriesChanged);
+}
+
+QQuickGraphsScatter::~QQuickGraphsScatter()
+{
+ QMutexLocker locker(m_nodeMutex.data());
+ const QMutexLocker locker2(mutex());
+ delete m_scatterController;
+
+ if (m_instancing)
+ delete m_instancing;
+}
+
+QValue3DAxis *QQuickGraphsScatter::axisX() const
+{
+ return static_cast<QValue3DAxis *>(m_scatterController->axisX());
+}
+
+void QQuickGraphsScatter::setAxisX(QValue3DAxis *axis)
+{
+ m_scatterController->setAxisX(axis);
+}
+
+QValue3DAxis *QQuickGraphsScatter::axisY() const
+{
+ return static_cast<QValue3DAxis *>(m_scatterController->axisY());
+}
+
+void QQuickGraphsScatter::setAxisY(QValue3DAxis *axis)
+{
+ m_scatterController->setAxisY(axis);
+}
+
+QValue3DAxis *QQuickGraphsScatter::axisZ() const
+{
+ return static_cast<QValue3DAxis *>(m_scatterController->axisZ());
+}
+
+void QQuickGraphsScatter::setAxisZ(QValue3DAxis *axis)
+{
+ m_scatterController->setAxisZ(axis);
+}
+
+// From seriesvisualizer
+void QQuickGraphsScatter::disconnectSeries(QScatter3DSeries *series)
+{
+ QObject::disconnect(series, 0, this, 0);
+}
+
+void QQuickGraphsScatter::generatePointsForScatterModel(ScatterModel *graphModel)
+{
+ QList<QQuick3DModel *> itemList;
+ if (m_scatterController->optimizationHints() == QAbstract3DGraph::OptimizationDefault) {
+ int itemCount = graphModel->series->dataProxy()->itemCount();
+ if (graphModel->series->dataProxy()->itemCount() > 0)
+ itemList.resize(itemCount);
+
+ QAbstract3DSeries::Mesh meshType = graphModel->series->mesh();
+ for (int i = 0; i < itemCount; i++) {
+ QQuick3DModel *item = createDataItem(meshType);
+ item->setPickable(true);
+ item->setParent(graphModel->series);
+ itemList[i] = item;
+ }
+ graphModel->dataItems = itemList;
+ m_scatterController->markDataDirty();
+ } else if (m_scatterController->optimizationHints() == QAbstract3DGraph::OptimizationStatic) {
+ if (m_scatterController->selectionMode() != QAbstract3DGraph::SelectionNone)
+ m_instancingRootItem->setPickable(true);
+ }
+ m_scatterController->markSeriesVisualsDirty();
+}
+
+qsizetype QQuickGraphsScatter::getItemIndex(QQuick3DModel *item)
+{
+ Q_UNUSED(item);
+ if (m_scatterController->optimizationHints() == QAbstract3DGraph::OptimizationDefault)
+ return 0;
+
+ return -1;
+}
+
+void QQuickGraphsScatter::resetSelection()
+{
+ if (m_selectedIndex != -1) {
+ if (m_scatterController->optimizationHints() == QAbstract3DGraph::OptimizationStatic) {
+ m_selectionActive = false;
+ m_selectionIndicator->setVisible(false);
+ }
+ }
+}
+
+void QQuickGraphsScatter::updateScatterGraphItemPositions(ScatterModel *graphModel)
+{
+ float itemSize = graphModel->series->itemSize() / m_itemScaler;
+ QQuaternion meshRotation = graphModel->series->meshRotation();
+ QScatterDataProxy *dataProxy = graphModel->series->dataProxy();
+ QList<QQuick3DModel *> itemList = graphModel->dataItems;
+
+ if (itemSize == 0.0f)
+ itemSize = m_pointScale;
+
+ if (m_scatterController->optimizationHints() == QAbstract3DGraph::OptimizationDefault) {
+ if (dataProxy->itemCount() != itemList.size())
+ qWarning() << __func__ << "Item count differs from itemList count";
+
+ for (int i = 0; i < dataProxy->itemCount(); ++i) {
+ const QScatterDataItem *item = dataProxy->itemAt(i);
+ QQuick3DModel *dataPoint = itemList.at(i);
+
+ QVector3D dotPos = item->position();
+ QQuaternion dotRot = item->rotation();
+ float posX = axisX()->positionAt(dotPos.x()) * scale().x() + translate().x();
+ float posY = axisY()->positionAt(dotPos.y()) * scale().y() + translate().y();
+ float posZ = axisZ()->positionAt(dotPos.z()) * scale().z() + translate().z();
+
+ dataPoint->setPosition(QVector3D(posX, posY, posZ));
+
+ dataPoint->setRotation(dotRot * meshRotation);
+ dataPoint->setScale(QVector3D(itemSize, itemSize, itemSize));
+ }
+ } else if (m_scatterController->optimizationHints() == QAbstract3DGraph::OptimizationStatic) {
+ int count = dataProxy->itemCount();
+ QList<DataItemHolder> positions;
+ positions.resize(count);
+
+ for (int i = 0; i < count; i++) {
+ auto item = dataProxy->itemAt(i);
+ auto dotPos = item->position();
+
+ auto posX = axisX()->positionAt(dotPos.x()) * scale().x() + translate().x();
+ auto posY = axisY()->positionAt(dotPos.y()) * scale().y() + translate().y();
+ auto posZ = axisZ()->positionAt(dotPos.z()) * scale().z() + translate().z();
+
+ DataItemHolder dih;
+ dih.position = {posX, posY, posZ};
+ dih.rotation = item->rotation() * meshRotation;
+ dih.scale = {itemSize, itemSize, itemSize};
+
+ positions[i] = dih;
+ }
+ m_instancing->setDataArray(positions);
+ }
+}
+
+void QQuickGraphsScatter::updateScatterGraphItemVisuals(ScatterModel *graphModel)
+{
+ bool useGradient = graphModel->series->d_ptr->isUsingGradient();
+ int itemCount = graphModel->series->dataProxy()->itemCount();
+
+ if (useGradient) {
+ if (!graphModel->seriesTexture) {
+ graphModel->seriesTexture = createTexture();
+ graphModel->seriesTexture->setParent(graphModel->series);
+ }
+
+ QLinearGradient gradient = graphModel->series->baseGradient();
+ auto textureData = static_cast<QuickGraphsTextureData *>(
+ graphModel->seriesTexture->textureData());
+ textureData->createGradient(gradient);
+
+ if (!graphModel->highlightTexture) {
+ graphModel->highlightTexture = createTexture();
+ graphModel->highlightTexture->setParent(graphModel->series);
+ }
+
+ QLinearGradient highlightGradient = graphModel->series->singleHighlightGradient();
+ auto highlightTextureData = static_cast<QuickGraphsTextureData *>(
+ graphModel->highlightTexture->textureData());
+ highlightTextureData->createGradient(highlightGradient);
+ } else {
+ if (graphModel->seriesTexture) {
+ graphModel->seriesTexture->deleteLater();
+ graphModel->seriesTexture = nullptr;
+ }
+
+ if (graphModel->highlightTexture) {
+ graphModel->highlightTexture->deleteLater();
+ graphModel->highlightTexture = nullptr;
+ }
+ }
+
+ bool rangeGradient = (useGradient && graphModel->series->d_ptr->m_colorStyle
+ == Q3DTheme::ColorStyleRangeGradient) ? true : false;
+
+ if (m_scatterController->optimizationHints() == QAbstract3DGraph::OptimizationDefault) {
+ QList<QQuick3DModel *> itemList = graphModel->dataItems;
+
+ if (itemCount != itemList.size())
+ qWarning() << __func__ << "Item count differs from itemList count";
+
+ if (!rangeGradient) {
+ for (int i = 0; i < itemCount; i++) {
+ auto obj = static_cast<QQuick3DModel *>(itemList.at(i));
+
+ updateItemMaterial(obj, useGradient, rangeGradient);
+
+ updatePrincipledMaterial(obj, graphModel->series->baseColor(),
+ useGradient, graphModel->seriesTexture);
+ }
+ if (m_scatterController->m_selectedItem != invalidSelectionIndex()
+ && graphModel->series == m_scatterController->m_selectedItemSeries) {
+ QQuick3DModel *selectedItem = itemList.at(m_scatterController->m_selectedItem);
+ updatePrincipledMaterial(selectedItem, graphModel->series->singleHighlightColor(),
+ useGradient, graphModel->highlightTexture);
+ }
+ } else {
+ for (int i = 0; i < itemCount; i++) {
+ auto obj = static_cast<QQuick3DModel *>(itemList.at(i));
+ updateItemMaterial(obj, useGradient, rangeGradient);
+ updateCustomMaterial(obj, graphModel->seriesTexture);
+ }
+
+ if (m_selectedIndex != -1) {
+ QQuick3DModel *obj = itemList.at(m_selectedIndex);
+
+ updateCustomMaterial(obj, graphModel->highlightTexture);
+ }
+ }
+ } else if (m_scatterController->optimizationHints() == QAbstract3DGraph::OptimizationStatic) {
+ updateItemInstancedMaterial(m_instancingRootItem, useGradient, rangeGradient);
+ m_instancing->setRangeGradient(rangeGradient);
+ if (!rangeGradient) {
+ updatePrincipledMaterial(m_instancingRootItem, graphModel->series->baseColor(), useGradient);
+ } else {
+ float rangeGradientYScaler = m_rangeGradientYHelper / m_scaleY;
+
+ updateInstancedCustomMaterial(m_instancingRootItem);
+
+ QList<float> customData;
+ customData.resize(itemCount);
+
+ QList<DataItemHolder> instancingData = m_instancing->dataArray();
+ for (int i = 0; i < instancingData.size(); i++) {
+ auto dih = instancingData.at(i);
+ float value = (dih.position.y() + m_scaleY) * rangeGradientYScaler;
+ customData[i] = value;
+ }
+ m_instancing->setCustomData(customData);
+ }
+
+ updateSelectionIndicatorMaterial(useGradient, rangeGradient);
+
+ if (!rangeGradient) {
+ updatePrincipledMaterial(m_selectionIndicator, graphModel->series->singleHighlightColor(),
+ useGradient, graphModel->highlightTexture);
+ } else {
+ // Rangegradient
+ updateInstancedCustomMaterial(m_selectionIndicator, true);
+ }
+
+ if (m_selectedIndex != -1 && !m_selectionActive) {
+ auto dih = m_instancing->dataArray().at(m_selectedIndex);
+ m_selectionIndicator->setPosition(dih.position);
+ m_selectionIndicator->setRotation(dih.rotation);
+ m_selectionIndicator->setScale(dih.scale * m_indicatorScaleAdjustment);
+ m_selectionIndicator->setVisible(true);
+ itemLabel()->setPosition(m_selectionIndicator->position());
+ m_selectionActive = true;
+ m_instancing->markDataDirty();
+ }
+ }
+}
+
+void QQuickGraphsScatter::updateItemMaterial(QQuick3DModel *item, bool useGradient, bool rangeGradient)
+{
+ Q_UNUSED(useGradient);
+ QQmlListReference materialsRef(item, "materials");
+ if (!rangeGradient) {
+ if (materialsRef.size()) {
+ if (!qobject_cast<QQuick3DPrincipledMaterial *>(materialsRef.at(0))) {
+ auto principledMaterial = new QQuick3DPrincipledMaterial();
+ QObject *oldCustomMaterial = materialsRef.at(0);
+ materialsRef.replace(0, principledMaterial);
+ delete oldCustomMaterial;
+ }
+ } else {
+ auto principledMaterial = new QQuick3DPrincipledMaterial();
+ materialsRef.append(principledMaterial);
+ }
+ } else {
+ if (materialsRef.size()) {
+ if (!qobject_cast<QQuick3DCustomMaterial *>(materialsRef.at(0))) {
+ QQuick3DCustomMaterial *customMaterial = createQmlCustomMaterial(
+ QStringLiteral(":/materials/RangeGradientMaterial"));
+ QObject *oldPrincipledMaterial = materialsRef.at(0);
+ materialsRef.replace(0, customMaterial);
+ delete oldPrincipledMaterial;
+ }
+ } else {
+ QQuick3DCustomMaterial *customMaterial = createQmlCustomMaterial(QStringLiteral(":/materials/RangeGradientMaterial"));
+ materialsRef.append(customMaterial);
+ }
+ }
+}
+
+void QQuickGraphsScatter::updateItemInstancedMaterial(QQuick3DModel *item, bool useGradient, bool rangeGradient)
+{
+ Q_UNUSED(useGradient);
+ QQmlListReference materialsRef(item, "materials");
+ if (!rangeGradient) {
+ if (materialsRef.size()) {
+ if (!qobject_cast<QQuick3DPrincipledMaterial *>(materialsRef.at(0))) {
+ auto principledMaterial = new QQuick3DPrincipledMaterial();
+ auto oldCustomMaterial = materialsRef.at(0);
+ materialsRef.replace(0, principledMaterial);
+ delete oldCustomMaterial;
+ }
+ } else {
+ auto principledMaterial = new QQuick3DPrincipledMaterial();
+ materialsRef.append(principledMaterial);
+ }
+ } else {
+ if (materialsRef.size()) {
+ if (!qobject_cast<QQuick3DCustomMaterial *>(materialsRef.at(0))) {
+ auto customMaterial = createQmlCustomMaterial(QStringLiteral(":/materials/RangeGradientMaterialInstancing"));
+ auto oldPrincipledMaterial = materialsRef.at(0);
+ materialsRef.replace(0, customMaterial);
+ delete oldPrincipledMaterial;
+ }
+ } else {
+ auto customMaterial = createQmlCustomMaterial(QStringLiteral(":/materials/RangeGradientMaterialInstancing"));
+ materialsRef.append(customMaterial);
+ }
+ }
+}
+
+void QQuickGraphsScatter::updateSelectionIndicatorMaterial(bool useGradient, bool rangeGradient)
+{
+ Q_UNUSED(useGradient);
+ QQmlListReference materialsRef(m_selectionIndicator, "materials");
+ if (!rangeGradient) {
+ if (materialsRef.size()) {
+ if (!qobject_cast<QQuick3DPrincipledMaterial *>(materialsRef.at(0))) {
+ auto principledMaterial = new QQuick3DPrincipledMaterial();
+ auto old = qobject_cast<QQuick3DCustomMaterial *>(materialsRef.at(0));
+ materialsRef.replace(0, principledMaterial);
+ delete old;
+ }
+ } else {
+ auto material = new QQuick3DPrincipledMaterial();
+ materialsRef.append(material);
+ }
+ } else {
+ // Rangegradient
+ if (materialsRef.size()) {
+ if (!qobject_cast<QQuick3DCustomMaterial *>(materialsRef.at(0))) {
+ auto old = materialsRef.at(0);
+ auto customMaterial = createQmlCustomMaterial(QStringLiteral(":/materials/RangeGradientMaterial"));
+ materialsRef.replace(0, customMaterial);
+ delete old;
+ }
+ } else {
+ auto customMaterial = createQmlCustomMaterial(QStringLiteral(":/materials/RangeGradientMaterial"));
+ materialsRef.append(customMaterial);
+ }
+ }
+}
+
+void QQuickGraphsScatter::updateInstancedCustomMaterial(QQuick3DModel *model, bool isHighlight,
+ QQuick3DTexture *seriesTexture,
+ QQuick3DTexture *highlightTexture)
+{
+ QQmlListReference materialsRef(model, "materials");
+
+ // Rangegradient
+ auto customMaterial = static_cast<QQuick3DCustomMaterial *>(materialsRef.at(0));
+
+ QVariant textureInputAsVariant = customMaterial->property("custex");
+ QQuick3DShaderUtilsTextureInput *textureInput = textureInputAsVariant.value<QQuick3DShaderUtilsTextureInput *>();
+
+ if (isHighlight) {
+ textureInput->setTexture(highlightTexture);
+
+ if (m_selectedIndex != -1 && !m_selectionActive)
+ m_selectedGradientPos = m_instancing->customData().takeAt(m_selectedIndex);
+
+ customMaterial->setProperty("gradientPos", m_selectedGradientPos);
+ } else {
+ textureInput->setTexture(seriesTexture);
+ }
+}
+
+void QQuickGraphsScatter::updateCustomMaterial(QQuick3DModel *item, QQuick3DTexture *texture)
+{
+ QQmlListReference materialsRef(item, "materials");
+ auto customMaterial = static_cast<QQuick3DCustomMaterial *>(materialsRef.at(0));
+ QVariant textureInputAsVariant = customMaterial->property("custex");
+ QQuick3DShaderUtilsTextureInput *textureInput = textureInputAsVariant.value<QQuick3DShaderUtilsTextureInput *>();
+
+ textureInput->setTexture(texture);
+
+ float rangeGradientYScaler = m_rangeGradientYHelper / m_scaleY;
+ float value = (item->y() + m_scaleY) * rangeGradientYScaler;
+ customMaterial->setProperty("gradientPos", value);
+}
+
+void QQuickGraphsScatter::updatePrincipledMaterial(QQuick3DModel *model, const QColor &color,
+ bool useGradient, QQuick3DTexture *texture)
+{
+ QQmlListReference materialsRef(model, "materials");
+ auto principledMaterial = static_cast<QQuick3DPrincipledMaterial *>(materialsRef.at(0));
+
+ if (useGradient) {
+ principledMaterial->setBaseColor(QColor(Qt::white));
+ principledMaterial->setBaseColorMap(texture);
+ } else {
+ principledMaterial->setBaseColor(color);
+ }
+}
+
+QQuick3DTexture *QQuickGraphsScatter::createTexture()
+{
+ QQuick3DTexture *texture = new QQuick3DTexture();
+ texture->setParent(this);
+ texture->setRotationUV(-90.0f);
+ texture->setHorizontalTiling(QQuick3DTexture::ClampToEdge);
+ texture->setVerticalTiling(QQuick3DTexture::ClampToEdge);
+ QuickGraphsTextureData *textureData = new QuickGraphsTextureData();
+ textureData->setParent(texture);
+ textureData->setParentItem(texture);
+ texture->setTextureData(textureData);
+
+ return texture;
+}
+
+QQuick3DNode *QQuickGraphsScatter::createSeriesRoot()
+{
+ auto model = new QQuick3DNode();
+
+ model->setParentItem(QQuick3DViewport::scene());
+ return model;
+}
+
+QQuick3DModel *QQuickGraphsScatter::createDataItem(const QAbstract3DSeries::Mesh meshType)
+{
+ auto model = new QQuick3DModel();
+ model->setParent(this);
+ model->setParentItem(QQuick3DViewport::scene());
+ QString fileName = getMeshFileName(meshType);
+
+ model->setSource(QUrl(fileName));
+ return model;
+}
+
+void QQuickGraphsScatter::createInstancingRootItem(QAbstract3DSeries::Mesh meshType)
+{
+ m_instancingRootItem = createDataItem(meshType);
+ m_instancingRootItem->setInstancing(m_instancing);
+}
+
+void QQuickGraphsScatter::createSelectionIndicator(QAbstract3DSeries::Mesh meshType)
+{
+ m_selectionIndicator = createDataItem(meshType);
+
+ m_selectionIndicator->setVisible(false);
+}
+
+void QQuickGraphsScatter::removeDataItems(QList<QQuick3DModel *> &items)
+{
+ removeDataItems(items, items.count());
+}
+
+void QQuickGraphsScatter::removeDataItems(QList<QQuick3DModel *> &items, qsizetype count)
+{
+ for (int i = 0; i < count; ++i) {
+ QQuick3DModel *item = items.takeLast();
+ QQmlListReference materialsRef(item, "materials");
+ if (materialsRef.size()) {
+ QObject *material = materialsRef.at(0);
+ delete material;
+ }
+ delete item;
+ }
+}
+
+void QQuickGraphsScatter::recreateDataItems()
+{
+ QList<QScatter3DSeries *> seriesList = m_scatterController->scatterSeriesList();
+ for (auto series : seriesList) {
+ for (const auto &model : std::as_const(m_scatterGraphs)) {
+ if (model->series == series) {
+ removeDataItems(model->dataItems);
+ generatePointsForScatterModel(model);
+ }
+ }
+ }
+}
+
+void QQuickGraphsScatter::addPointsToScatterModel(ScatterModel *graphModel, qsizetype count)
+{
+ QAbstract3DSeries::Mesh meshType = graphModel->series->mesh();
+ for (int i = 0; i < count; i++) {
+ QQuick3DModel *item = createDataItem(meshType);
+ item->setPickable(true);
+ item->setParent(graphModel->series);
+ graphModel->dataItems.push_back(item);
+ }
+ m_scatterController->setSeriesVisualsDirty();
+}
+
+int QQuickGraphsScatter::sizeDifference(qsizetype size1, qsizetype size2)
+{
+ return size2 - size1;
+}
+
+QVector3D QQuickGraphsScatter::selectedItemPosition()
+{
+ if (m_selectedIndex == -1)
+ return QVector3D();
+
+ QVector3D position;
+ if (m_scatterController->optimizationHints() == QAbstract3DGraph::OptimizationDefault)
+ position = {0.0f, 0.0f, 0.0f};
+ else if (m_scatterController->optimizationHints() == QAbstract3DGraph::OptimizationStatic)
+ position = {0.0f, 0.0f, 0.0f};
+
+ return position;
+}
+
+void QQuickGraphsScatter::fixMeshFileName(QString &fileName, QAbstract3DSeries::Mesh meshType)
+{
+ if (meshType != QAbstract3DSeries::MeshSphere && meshType != QAbstract3DSeries::MeshArrow
+ && meshType != QAbstract3DSeries::MeshMinimal && meshType != QAbstract3DSeries::MeshPoint) {
+ fileName.append(QStringLiteral("Full"));
+ }
+}
+
+QString QQuickGraphsScatter::getMeshFileName(QAbstract3DSeries::Mesh meshType)
+{
+ QString fileName;
+ QString smoothString = QStringLiteral("Smooth");
+ switch (meshType) {
+ case QAbstract3DSeries::MeshSphere:
+ fileName = QStringLiteral("defaultMeshes/sphereMesh");
+ break;
+ case QAbstract3DSeries::MeshBar:
+ case QAbstract3DSeries::MeshCube:
+ fileName = QStringLiteral("defaultMeshes/barMesh");
+ break;
+ case QAbstract3DSeries::MeshPyramid:
+ fileName = QStringLiteral("defaultMeshes/pyramidMesh");
+ break;
+ case QAbstract3DSeries::MeshCone:
+ fileName = QStringLiteral("defaultMeshes/coneMesh");
+ break;
+ case QAbstract3DSeries::MeshCylinder:
+ fileName = QStringLiteral("defaultMeshes/cylinderMesh");
+ break;
+ case QAbstract3DSeries::MeshBevelBar:
+ case QAbstract3DSeries::MeshBevelCube:
+ fileName = QStringLiteral("defaultMeshes/bevelBarMesh");
+ break;
+ case QAbstract3DSeries::MeshMinimal:
+ fileName = QStringLiteral("defaultMeshes/minimalMesh");
+ break;
+ case QAbstract3DSeries::MeshArrow:
+ fileName = QStringLiteral("defaultMeshes/arrowMesh");
+ break;
+ case QAbstract3DSeries::MeshPoint:
+ qWarning("Points not supported yet");
+ break;
+ default:
+ fileName = QStringLiteral("defaultMeshes/sphereMesh");
+ }
+ if (m_smooth && meshType != QAbstract3DSeries::MeshPoint)
+ fileName += smoothString;
+
+ fixMeshFileName(fileName, meshType);
+
+ return fileName;
+}
+
+void QQuickGraphsScatter::handleSeriesChanged(QList<QAbstract3DSeries *> changedSeries)
+{
+ Q_UNUSED(changedSeries)
+ // TODO: generate items and remove old items
+}
+
+// ------------------------------------------------------------------------------
+QScatter3DSeries *QQuickGraphsScatter::selectedSeries() const
+{
+ return m_scatterController->selectedSeries();
+}
+
+void QQuickGraphsScatter::setSelectedItem(int index, QScatter3DSeries *series)
+{
+ m_scatterController->setSelectedItem(index, series);
+
+ if (index != invalidSelectionIndex())
+ itemLabel()->setVisible(true);
+}
+
+QQmlListProperty<QScatter3DSeries> QQuickGraphsScatter::seriesList()
+{
+ return QQmlListProperty<QScatter3DSeries>(this, this,
+ &QQuickGraphsScatter::appendSeriesFunc,
+ &QQuickGraphsScatter::countSeriesFunc,
+ &QQuickGraphsScatter::atSeriesFunc,
+ &QQuickGraphsScatter::clearSeriesFunc);
+}
+
+void QQuickGraphsScatter::appendSeriesFunc(QQmlListProperty<QScatter3DSeries> *list,
+ QScatter3DSeries *series)
+{
+ reinterpret_cast<QQuickGraphsScatter *>(list->data)->addSeries(series);
+}
+
+qsizetype QQuickGraphsScatter::countSeriesFunc(QQmlListProperty<QScatter3DSeries> *list)
+{
+ return reinterpret_cast<QQuickGraphsScatter *>(list->data)->m_scatterController->scatterSeriesList().size();
+}
+
+QScatter3DSeries *QQuickGraphsScatter::atSeriesFunc(QQmlListProperty<QScatter3DSeries> *list,
+ qsizetype index)
+{
+ return reinterpret_cast<QQuickGraphsScatter *>(list->data)->m_scatterController->scatterSeriesList().at(index);
+}
+
+void QQuickGraphsScatter::clearSeriesFunc(QQmlListProperty<QScatter3DSeries> *list)
+{
+ QQuickGraphsScatter *declScatter = reinterpret_cast<QQuickGraphsScatter *>(list->data);
+ QList<QScatter3DSeries *> realList = declScatter->m_scatterController->scatterSeriesList();
+ int count = realList.size();
+ for (int i = 0; i < count; i++)
+ declScatter->removeSeries(realList.at(i));
+}
+
+void QQuickGraphsScatter::addSeries(QScatter3DSeries *series)
+{
+ m_scatterController->addSeries(series);
+
+ auto graphModel = new ScatterModel;
+ graphModel->series = series;
+ graphModel->seriesTexture = nullptr;
+ graphModel->highlightTexture = nullptr;
+ m_scatterGraphs.push_back(graphModel);
+
+ connectSeries(series);
+
+ if (series->selectedItem() != invalidSelectionIndex())
+ setSelectedItem(series->selectedItem(), series);
+}
+
+void QQuickGraphsScatter::removeSeries(QScatter3DSeries *series)
+{
+ m_scatterController->removeSeries(series);
+ series->setParent(this); // Reparent as removing will leave series parentless
+
+ // Find scattergraph model
+ for (QList<ScatterModel *>::const_iterator it = m_scatterGraphs.constBegin();
+ it != m_scatterGraphs.constEnd(); ++it) {
+ if ((*it)->series == series) {
+ removeDataItems((*it)->dataItems);
+
+ if ((*it)->seriesTexture)
+ delete (*it)->seriesTexture;
+ if ((*it)->highlightTexture)
+ delete (*it)->highlightTexture;
+
+ delete *it;
+ m_scatterGraphs.erase(it);
+ }
+ }
+
+ disconnectSeries(series);
+}
+
+void QQuickGraphsScatter::handleAxisXChanged(QAbstract3DAxis *axis)
+{
+ emit axisXChanged(static_cast<QValue3DAxis *>(axis));
+}
+
+void QQuickGraphsScatter::handleAxisYChanged(QAbstract3DAxis *axis)
+{
+ emit axisYChanged(static_cast<QValue3DAxis *>(axis));
+}
+
+void QQuickGraphsScatter::handleAxisZChanged(QAbstract3DAxis *axis)
+{
+ emit axisZChanged(static_cast<QValue3DAxis *>(axis));
+}
+
+void QQuickGraphsScatter::handleSeriesMeshChanged()
+{
+ recreateDataItems();
+}
+
+void QQuickGraphsScatter::handleMeshSmoothChanged(bool enable)
+{
+ m_smooth = enable;
+ recreateDataItems();
+}
+
+bool QQuickGraphsScatter::handleMousePressedEvent(QMouseEvent *event)
+{
+ if (Qt::LeftButton == event->button()) {
+ if (selectionMode() == QAbstract3DGraph::SelectionItem) {
+ const auto clickPosition = event->pos();
+ QList<QQuick3DPickResult> results = pickAll(clickPosition.x(), clickPosition.y());
+ if (!results.empty()) {
+ for (const auto &result : std::as_const(results)) {
+ if (const auto &hit = result.objectHit()) {
+ if (hit == backgroundBB() || hit == background()) {
+ clearSelectionModel();
+ continue;
+ }
+ if (optimizationHints() == QAbstract3DGraph::OptimizationDefault) {
+ setSelected(hit);
+ break;
+ } else if (optimizationHints() == QAbstract3DGraph::OptimizationStatic) {
+ setSelected(hit, result.instanceIndex());
+ break;
+ }
+ }
+ }
+ } else {
+ clearSelectionModel();
+ }
+ }
+ }
+
+ return true;
+}
+
+void QQuickGraphsScatter::connectSeries(QScatter3DSeries *series)
+{
+ m_smooth = series->isMeshSmooth();
+
+ QObject::connect(series, &QScatter3DSeries::meshChanged, this,
+ &QQuickGraphsScatter::handleSeriesMeshChanged);
+ QObject::connect(series, &QScatter3DSeries::meshSmoothChanged, this,
+ &QQuickGraphsScatter::handleMeshSmoothChanged);
+}
+
+void QQuickGraphsScatter::calculateSceneScalingFactors()
+{
+ if (m_requestedMargin < 0.0f) {
+ if (m_maxItemSize > m_defaultMaxSize)
+ m_hBackgroundMargin = m_maxItemSize / m_itemScaler;
+ else
+ m_hBackgroundMargin = m_defaultMaxSize;
+ m_vBackgroundMargin = m_hBackgroundMargin;
+ } else {
+ m_hBackgroundMargin = m_requestedMargin;
+ m_vBackgroundMargin = m_requestedMargin;
+ }
+
+ float hAspectRatio = horizontalAspectRatio();
+
+ QSizeF areaSize;
+ auto *axisX = static_cast<QValue3DAxis *>(m_scatterController->axisX());
+ auto *axisZ = static_cast<QValue3DAxis *>(m_scatterController->axisZ());
+
+ if (hAspectRatio == 0.0f) {
+ areaSize.setHeight(axisZ->max() - axisZ->min());
+ areaSize.setWidth(axisX->max() - axisX->min());
+ } else {
+ areaSize.setHeight(1.0f);
+ areaSize.setWidth(hAspectRatio);
+ }
+
+ float horizontalMaxDimension;
+ float graphAspectRatio = aspectRatio();
+
+ if (graphAspectRatio > 2.0f) {
+ horizontalMaxDimension = 2.0f;
+ m_scaleY = 2.0f / graphAspectRatio;
+ } else {
+ horizontalMaxDimension = graphAspectRatio;
+ m_scaleY = 1.0f;
+ }
+ float scaleFactor = qMax(areaSize.width(), areaSize.height());
+ m_scaleX = horizontalMaxDimension * areaSize.width() / scaleFactor;
+ m_scaleZ = horizontalMaxDimension * areaSize.height() / scaleFactor;
+
+ setBackgroundScaleMargin({m_hBackgroundMargin, m_vBackgroundMargin, m_hBackgroundMargin});
+
+ setLineLengthScaleFactor(0.02f);
+ setScaleWithBackground({m_scaleX, m_scaleY, m_scaleZ});
+ setScale({m_scaleX * 2.0f, m_scaleY * 2.0f, m_scaleZ * -2.0f});
+ setTranslate({-m_scaleX, -m_scaleY, m_scaleZ});
+}
+
+float QQuickGraphsScatter::calculatePointScaleSize()
+{
+ QList<QScatter3DSeries *> series = m_scatterController->scatterSeriesList();
+ int totalDataSize = 0;
+ for (const auto &scatterSeries : std::as_const(series)) {
+ if (scatterSeries->isVisible())
+ totalDataSize += scatterSeries->dataProxy()->array()->size();
+ }
+
+ return qBound(m_defaultMinSize, 2.0f / float(qSqrt(qreal(totalDataSize))), m_defaultMaxSize);
+}
+
+void QQuickGraphsScatter::updatePointScaleSize()
+{
+ m_pointScale = calculatePointScaleSize();
+}
+
+void QQuickGraphsScatter::updateShadowQuality(ShadowQuality quality)
+{
+ QQuickGraphsItem::updateShadowQuality(quality);
+}
+
+QQuick3DModel *QQuickGraphsScatter::selected() const
+{
+ return m_selected;
+}
+
+void QQuickGraphsScatter::setSelected(QQuick3DModel *newSelected)
+{
+ if (newSelected != m_selected) {
+ m_previousSelected = m_selected;
+ m_selected = newSelected;
+
+ auto series = static_cast<QScatter3DSeries *>(m_selected->parent());
+
+ // Find scattermodel
+ ScatterModel *graphModel = nullptr;
+
+ for (const auto &model : std::as_const(m_scatterGraphs)) {
+ if (model->series == series) {
+ graphModel = model;
+ break;
+ }
+ }
+
+ if (graphModel) {
+ qsizetype index = graphModel->dataItems.indexOf(m_selected);
+ setSelectedItem(index, series);
+
+ m_scatterController->setSeriesVisualsDirty();
+ m_scatterController->setSelectedItemChanged(true);
+ }
+ }
+}
+
+void QQuickGraphsScatter::setSelected(QQuick3DModel *root, qsizetype index)
+{
+ if (index != m_scatterController->m_selectedItem) {
+ QQuick3DObject *seriesRoot = root->parentItem();
+ auto series = static_cast<QScatter3DSeries *>(seriesRoot->parent());
+
+ m_scatterController->setSeriesVisualsDirty();
+ setSelectedItem(index, series);
+ m_scatterController->setSelectedItemChanged(true);
+ }
+}
+
+void QQuickGraphsScatter::setSelected(qsizetype index)
+{
+ m_selectedIndex = index;
+}
+
+void QQuickGraphsScatter::clearSelectionModel()
+{
+ setSelectedItem(invalidSelectionIndex(), nullptr);
+
+ itemLabel()->setVisible(false);
+ m_scatterController->setSeriesVisualsDirty();
+ m_selected = nullptr;
+ m_previousSelected = nullptr;
+}
+
+void QQuickGraphsScatter::updateGraph()
+{
+ updatePointScaleSize();
+ for (auto graphModel : std::as_const(m_scatterGraphs)) {
+ if (m_scatterController->isDataDirty()) {
+ if (graphModel->dataItems.count() != graphModel->series->dataProxy()->itemCount()) {
+ int sizeDiff = sizeDifference(graphModel->dataItems.count(),
+ graphModel->series->dataProxy()->itemCount());
+
+ if (sizeDiff > 0)
+ addPointsToScatterModel(graphModel, sizeDiff);
+ else
+ removeDataItems(graphModel->dataItems, qAbs(sizeDiff));
+ }
+
+ updateScatterGraphItemPositions(graphModel);
+ }
+
+ if (m_scatterController->isSeriesVisualsDirty())
+ updateScatterGraphItemVisuals(graphModel);
+
+ if (m_scatterController->m_selectedItemSeries == graphModel->series
+ && m_scatterController->m_selectedItem != invalidSelectionIndex()) {
+ QQuick3DModel *selectedModel = graphModel->dataItems.at(m_scatterController->m_selectedItem);
+ itemLabel()->setPosition(selectedModel->position());
+ QString label = m_scatterController->m_selectedItemSeries->itemLabel();
+ itemLabel()->setProperty("labelText", label);
+ }
+ }
+
+ if (m_scatterController->m_selectedItem == invalidSelectionIndex()) {
+ itemLabel()->setVisible(false);
+ }
+}
+
+void QQuickGraphsScatter::synchData()
+{
+ QList<QScatter3DSeries *> seriesList = m_scatterController->scatterSeriesList();
+
+ float maxItemSize = 0.0f;
+
+ for (const auto &series : std::as_const(seriesList)) {
+ if (series->isVisible()) {
+ float itemSize = series->itemSize();
+ if (itemSize > maxItemSize)
+ maxItemSize = itemSize;
+ }
+ }
+
+ m_maxItemSize = maxItemSize;
+
+ calculateSceneScalingFactors();
+ updatePointScaleSize();
+ QQuickGraphsItem::synchData();
+ scene()->activeCamera()->d_ptr->setMinYRotation(-90.0f);
+
+ QValue3DAxis *aX = axisX();
+ QValue3DAxis *aY = axisY();
+ QValue3DAxis *aZ = axisZ();
+
+ aX->formatter()->d_ptr->recalculate();
+ aY->formatter()->d_ptr->recalculate();
+ aZ->formatter()->d_ptr->recalculate();
+
+ m_pointScale = calculatePointScaleSize();
+
+ // Notify changes to renderer
+ if (m_scatterController->hasItemChanged()) {
+ m_scatterController->setItemChanged(false);
+ m_scatterController->clearChangedItems();
+ }
+
+ if (m_scatterController->hasSelectedItemChanged()) {
+ if (m_scatterController->m_selectedItem != m_scatterController->invalidSelectionIndex()) {
+ QString itemLabelText = m_scatterController->m_selectedItemSeries->itemLabel();
+ itemLabel()->setProperty("labelText", itemLabelText);
+ }
+ m_scatterController->setSelectedItemChanged(false);
+ }
+}
+QT_END_NAMESPACE
diff --git a/src/graphs/qml/qquickgraphsscatter_p.h b/src/graphs/qml/qquickgraphsscatter_p.h
new file mode 100644
index 0000000..9fe587e
--- /dev/null
+++ b/src/graphs/qml/qquickgraphsscatter_p.h
@@ -0,0 +1,192 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QtGraphs API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+#ifndef QQUICKGRAPHSSCATTER_P_H
+#define QQUICKGRAPHSSCATTER_P_H
+
+#include "qscatter3dseries.h"
+#include "qquickgraphsitem_p.h"
+#include "scatter3dcontroller_p.h"
+#include "scatterinstancing_p.h"
+
+#include <private/graphsglobal_p.h>
+#include <private/qqmldelegatemodel_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class Q3DScatter;
+
+class QQuickGraphsScatter : public QQuickGraphsItem
+{
+ Q_OBJECT
+ Q_PROPERTY(QValue3DAxis *axisX READ axisX WRITE setAxisX NOTIFY axisXChanged)
+ Q_PROPERTY(QValue3DAxis *axisY READ axisY WRITE setAxisY NOTIFY axisYChanged)
+ Q_PROPERTY(QValue3DAxis *axisZ READ axisZ WRITE setAxisZ NOTIFY axisZChanged)
+ Q_PROPERTY(QScatter3DSeries *selectedSeries READ selectedSeries NOTIFY selectedSeriesChanged)
+ Q_PROPERTY(QQmlListProperty<QScatter3DSeries> seriesList READ seriesList CONSTANT)
+ Q_CLASSINFO("DefaultProperty", "seriesList")
+
+ QML_NAMED_ELEMENT(Scatter3D)
+ QML_ADDED_IN_VERSION(6, 6)
+
+public:
+ explicit QQuickGraphsScatter(QQuickItem *parent = 0);
+ ~QQuickGraphsScatter();
+
+ QValue3DAxis *axisX() const;
+ void setAxisX(QValue3DAxis *axis);
+ QValue3DAxis *axisY() const;
+ void setAxisY(QValue3DAxis *axis);
+ QValue3DAxis *axisZ() const;
+ void setAxisZ(QValue3DAxis *axis);
+
+ QQmlListProperty<QScatter3DSeries> seriesList();
+ static void appendSeriesFunc(QQmlListProperty<QScatter3DSeries> *list, QScatter3DSeries *series);
+ static qsizetype countSeriesFunc(QQmlListProperty<QScatter3DSeries> *list);
+ static QScatter3DSeries *atSeriesFunc(QQmlListProperty<QScatter3DSeries> *list, qsizetype index);
+ static void clearSeriesFunc(QQmlListProperty<QScatter3DSeries> *list);
+ Q_INVOKABLE void addSeries(QScatter3DSeries *series);
+ Q_INVOKABLE void removeSeries(QScatter3DSeries *series);
+
+ QScatter3DSeries *selectedSeries() const;
+ void setSelectedItem(int index, QScatter3DSeries *series);
+ static inline int invalidSelectionIndex() { return -1; }
+
+public Q_SLOTS:
+ void handleAxisXChanged(QAbstract3DAxis *axis) override;
+ void handleAxisYChanged(QAbstract3DAxis *axis) override;
+ void handleAxisZChanged(QAbstract3DAxis *axis) override;
+ void handleSeriesMeshChanged();
+ void handleMeshSmoothChanged(bool enable);
+
+Q_SIGNALS:
+ void axisXChanged(QValue3DAxis *axis);
+ void axisYChanged(QValue3DAxis *axis);
+ void axisZChanged(QValue3DAxis *axis);
+ void selectedSeriesChanged(QScatter3DSeries *series);
+
+protected:
+ bool handleMousePressedEvent(QMouseEvent *event) override;
+
+private:
+
+ struct ScatterModel {
+ QList <QQuick3DModel *> dataItems;
+ QQuick3DTexture *seriesTexture;
+ QQuick3DTexture *highlightTexture;
+ QScatter3DSeries *series;
+ };
+
+ Scatter3DController *m_scatterController;
+ float m_maxItemSize = 0.0f;
+
+ // For Quick3D renderer integration
+ const float m_defaultMinSize = 0.01f;
+ const float m_defaultMaxSize = 0.1f;
+ const float m_itemScaler = 3.0f;
+ float m_pointScale = 0;
+
+ const float m_indicatorScaleAdjustment = 1.1f;
+ const float m_rangeGradientYHelper = 0.5f;
+
+ // These were in renderer
+ float m_scaleX = 1.0f;
+ float m_scaleY = 1.0f;
+ float m_scaleZ = 1.0f;
+
+ float m_requestedMargin = -1.0f;
+ float m_vBackgroundMargin = 0.1f;
+ float m_hBackgroundMargin = 0.1f;
+
+ bool m_polarGraph = false;
+
+ int m_selectedItem;
+ QScatter3DSeries *m_selectedItemSeries; // Points to the series for which the bar is selected
+ // in single series selection cases.
+ QQuick3DModel *m_selected = nullptr;
+ QQuick3DModel *m_previousSelected = nullptr;
+
+ QList<ScatterModel *> m_scatterGraphs;
+
+ // From seriesvisualizer
+ void connectSeries(QScatter3DSeries *series);
+ void disconnectSeries(QScatter3DSeries *series);
+ qsizetype getItemIndex(QQuick3DModel *item);
+ void setSelected(qsizetype index);
+// void clearSelection();
+ QVector3D selectedItemPosition();
+
+ ScatterInstancing *m_instancing = nullptr;
+ QQuick3DModel *m_instancingRootItem = nullptr;
+ QQuick3DNode *m_seriesRootItem = nullptr;
+
+ bool m_smooth = false;
+
+ float m_dotSizedScale = 1.0f;
+ qsizetype m_selectedIndex = -1;
+ QQuick3DModel *m_selectionIndicator = nullptr;
+ bool m_selectionActive = false;
+ float m_selectedGradientPos = 0.0f;
+
+ void resetSelection();
+ void updateItemInstancedMaterial(QQuick3DModel *item, bool useGradient, bool rangeGradient);
+ void updateInstancedCustomMaterial(QQuick3DModel *model, bool isHighlight = false,
+ QQuick3DTexture *seriesTexture = nullptr,
+ QQuick3DTexture *highlightTexture = nullptr);
+ void updateSelectionIndicatorMaterial(bool useGradient, bool rangeGradient);
+
+ void updateItemMaterial(QQuick3DModel *item, bool useGradient, bool rangeGradient);
+ void updateCustomMaterial(QQuick3DModel *item, QQuick3DTexture *texture);
+ void updatePrincipledMaterial(QQuick3DModel *model, const QColor &color,
+ bool useGradient, QQuick3DTexture *texture = nullptr);
+
+ QQuick3DTexture *createTexture();
+ QQuick3DModel *createDataItemModel(QAbstract3DSeries::Mesh meshType);
+ QQuick3DNode *createSeriesRoot();
+ QQuick3DModel *createDataItem(const QAbstract3DSeries::Mesh meshType);
+ void createInstancingRootItem(QAbstract3DSeries::Mesh meshType);
+ void createSelectionIndicator(QAbstract3DSeries::Mesh meshType);
+ void removeDataItems(QList<QQuick3DModel *> &items);
+ void fixMeshFileName(QString &fileName, QAbstract3DSeries::Mesh meshType);
+ QString getMeshFileName(QAbstract3DSeries::Mesh meshType);
+
+ void removeDataItems(QList<QQuick3DModel *> &items, qsizetype count);
+ void recreateDataItems();
+ void addPointsToScatterModel(ScatterModel *graphModel, qsizetype count);
+ int sizeDifference(qsizetype size1, qsizetype size2);
+ void handleSeriesChanged(QList<QAbstract3DSeries *> changedSeries);
+
+ QColor m_selectedSeriesColor;
+
+ QQmlComponent *createRepeaterDelegate(QAbstract3DSeries::Mesh MeshType);
+ void calculateSceneScalingFactors();
+ float calculatePointScaleSize();
+ void updatePointScaleSize();
+
+ void updateShadowQuality(QQuickGraphsItem::ShadowQuality quality) override;
+
+ void generatePointsForScatterModel(ScatterModel *series);
+ void updateScatterGraphItemPositions(ScatterModel *graphModel);
+ void updateScatterGraphItemVisuals(ScatterModel *graphModel);
+
+ QQuick3DModel *selected() const;
+ void setSelected(QQuick3DModel *newSelected);
+ void setSelected(QQuick3DModel *root, qsizetype index);
+ void clearSelectionModel();
+
+ void updateGraph() override;
+ void synchData() override;
+
+ friend class Q3DScatter;
+};
+
+QT_END_NAMESPACE
+#endif // QQUICKGRAPHSSCATTER_P_H
diff --git a/src/graphs/qml/qquickgraphssurface.cpp b/src/graphs/qml/qquickgraphssurface.cpp
new file mode 100644
index 0000000..30cfe1f
--- /dev/null
+++ b/src/graphs/qml/qquickgraphssurface.cpp
@@ -0,0 +1,1345 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qquickgraphssurface_p.h"
+#include <QtCore/QMutexLocker>
+
+#include "declarativescene_p.h"
+#include "surface3dcontroller_p.h"
+#include "surfaceselectioninstancing_p.h"
+#include "qvalue3daxis_p.h"
+#include "qcategory3daxis_p.h"
+
+#include <QtQuick3D/private/qquick3dprincipledmaterial_p.h>
+#include <QtQuick3D/private/qquick3ddefaultmaterial_p.h>
+
+QT_BEGIN_NAMESPACE
+
+QQuickGraphsSurface::QQuickGraphsSurface(QQuickItem *parent)
+ : QQuickGraphsItem(parent),
+ m_surfaceController(0)
+{
+ setAcceptedMouseButtons(Qt::AllButtons);
+
+ // Create the shared component on the main GUI thread.
+ m_surfaceController = new Surface3DController(boundingRect().toRect(), new Declarative3DScene);
+ setSharedController(m_surfaceController);
+
+ QObject::connect(m_surfaceController, &Surface3DController::selectedSeriesChanged,
+ this, &QQuickGraphsSurface::selectedSeriesChanged);
+ QObject::connect(m_surfaceController, &Surface3DController::flipHorizontalGridChanged,
+ this, &QQuickGraphsSurface::flipHorizontalGridChanged);
+}
+
+QQuickGraphsSurface::~QQuickGraphsSurface()
+{
+ QMutexLocker locker(m_nodeMutex.data());
+ const QMutexLocker locker2(mutex());
+ delete m_surfaceController;
+ for (auto model : m_model)
+ delete model;
+}
+
+QValue3DAxis *QQuickGraphsSurface::axisX() const
+{
+ return static_cast<QValue3DAxis *>(m_surfaceController->axisX());
+}
+
+void QQuickGraphsSurface::setAxisX(QValue3DAxis *axis)
+{
+ m_surfaceController->setAxisX(axis);
+}
+
+QValue3DAxis *QQuickGraphsSurface::axisY() const
+{
+ return static_cast<QValue3DAxis *>(m_surfaceController->axisY());
+}
+
+void QQuickGraphsSurface::setAxisY(QValue3DAxis *axis)
+{
+ m_surfaceController->setAxisY(axis);
+}
+
+QValue3DAxis *QQuickGraphsSurface::axisZ() const
+{
+ return static_cast<QValue3DAxis *>(m_surfaceController->axisZ());
+}
+
+void QQuickGraphsSurface::setAxisZ(QValue3DAxis *axis)
+{
+ m_surfaceController->setAxisZ(axis);
+}
+
+void QQuickGraphsSurface::handleFlatShadingEnabledChanged()
+{
+ auto series = static_cast<QSurface3DSeries *>(sender());
+ for (auto model : m_model) {
+ if (model->series == series) {
+ updateModel(model);
+ break;
+ }
+ }
+}
+
+void QQuickGraphsSurface::handleWireframeColorChanged()
+{
+ for (auto model : m_model) {
+ QQmlListReference gridMaterialRef(model->gridModel, "materials");
+ auto gridMaterial = static_cast<QQuick3DPrincipledMaterial *>(gridMaterialRef.at(0));
+ QColor gridColor = model->series->wireframeColor();
+ gridMaterial->setBaseColor(gridColor);
+
+ if (sliceView()) {
+ QQmlListReference gridMaterialRef(model->sliceGridModel, "materials");
+ auto gridMaterial = static_cast<QQuick3DPrincipledMaterial *>(gridMaterialRef.at(0));
+ gridMaterial->setBaseColor(gridColor);
+ }
+ }
+}
+
+QSurface3DSeries *QQuickGraphsSurface::selectedSeries() const
+{
+ return m_surfaceController->selectedSeries();
+}
+
+void QQuickGraphsSurface::setFlipHorizontalGrid(bool flip)
+{
+ m_surfaceController->setFlipHorizontalGrid(flip);
+}
+
+bool QQuickGraphsSurface::flipHorizontalGrid() const
+{
+ return m_surfaceController->flipHorizontalGrid();
+}
+
+QQmlListProperty<QSurface3DSeries> QQuickGraphsSurface::seriesList()
+{
+ return QQmlListProperty<QSurface3DSeries>(this, this,
+ &QQuickGraphsSurface::appendSeriesFunc,
+ &QQuickGraphsSurface::countSeriesFunc,
+ &QQuickGraphsSurface::atSeriesFunc,
+ &QQuickGraphsSurface::clearSeriesFunc);
+}
+
+void QQuickGraphsSurface::appendSeriesFunc(QQmlListProperty<QSurface3DSeries> *list,
+ QSurface3DSeries *series)
+{
+ reinterpret_cast<QQuickGraphsSurface *>(list->data)->addSeries(series);
+}
+
+qsizetype QQuickGraphsSurface::countSeriesFunc(QQmlListProperty<QSurface3DSeries> *list)
+{
+ return reinterpret_cast<QQuickGraphsSurface *>(list->data)->m_surfaceController->surfaceSeriesList().size();
+}
+
+QSurface3DSeries *QQuickGraphsSurface::atSeriesFunc(QQmlListProperty<QSurface3DSeries> *list,
+ qsizetype index)
+{
+ return reinterpret_cast<QQuickGraphsSurface *>(list->data)->m_surfaceController->surfaceSeriesList().at(index);
+}
+
+void QQuickGraphsSurface::clearSeriesFunc(QQmlListProperty<QSurface3DSeries> *list)
+{
+ QQuickGraphsSurface *declSurface = reinterpret_cast<QQuickGraphsSurface *>(list->data);
+ QList<QSurface3DSeries *> realList = declSurface->m_surfaceController->surfaceSeriesList();
+ int count = realList.size();
+ for (int i = 0; i < count; i++)
+ declSurface->removeSeries(realList.at(i));
+}
+
+void QQuickGraphsSurface::addSeries(QSurface3DSeries *series)
+{
+ m_surfaceController->addSeries(series);
+ if (isReady())
+ addModel(series);
+}
+
+void QQuickGraphsSurface::removeSeries(QSurface3DSeries *series)
+{
+ m_surfaceController->removeSeries(series);
+ series->setParent(this); // Reparent as removing will leave series parentless
+ for (int i = 0; i < m_model.size();) {
+ if (m_model[i]->series == series) {
+ m_model[i]->model->deleteLater();
+ m_model[i]->gridModel->deleteLater();
+ m_model.removeAt(i);
+ } else {
+ ++i;
+ }
+ }
+}
+
+void QQuickGraphsSurface::handleAxisXChanged(QAbstract3DAxis *axis)
+{
+ emit axisXChanged(static_cast<QValue3DAxis *>(axis));
+}
+
+void QQuickGraphsSurface::handleAxisYChanged(QAbstract3DAxis *axis)
+{
+ emit axisYChanged(static_cast<QValue3DAxis *>(axis));
+}
+
+void QQuickGraphsSurface::handleAxisZChanged(QAbstract3DAxis *axis)
+{
+ emit axisZChanged(static_cast<QValue3DAxis *>(axis));
+}
+
+void QQuickGraphsSurface::componentComplete()
+{
+ QQuickGraphsItem::componentComplete();
+
+ createSliceView();
+
+ for (auto series : m_surfaceController->surfaceSeriesList())
+ addModel(series);
+
+ QQuick3DNode *parent = rootNode();
+ QQuick3DNode *sliceParent = sliceView()->scene();
+
+ m_selectionPointer = new QQuick3DModel();
+ m_selectionPointer->setParent(parent);
+ m_selectionPointer->setParentItem(parent);
+ m_selectionPointer->setSource(QUrl(QStringLiteral("#Sphere")));
+ auto pointerMaterial = new QQuick3DPrincipledMaterial();
+ pointerMaterial->setParent(this);
+ pointerMaterial->setBaseColor(m_surfaceController->activeTheme()->singleHighlightColor());
+ QQmlListReference materialRef(m_selectionPointer, "materials");
+ materialRef.append(pointerMaterial);
+ m_instancing = new SurfaceSelectionInstancing();
+ m_instancing->setScale(QVector3D(0.001f, 0.001f, 0.001f));
+ m_selectionPointer->setInstancing(m_instancing);
+
+ m_sliceSelectionPointer = new QQuick3DModel();
+ m_sliceSelectionPointer->setParent(sliceParent);
+ m_sliceSelectionPointer->setParentItem(sliceParent);
+ m_sliceSelectionPointer->setSource(QUrl(QStringLiteral("#Sphere")));
+ pointerMaterial = new QQuick3DPrincipledMaterial();
+ pointerMaterial->setParent(m_sliceSelectionPointer);
+ pointerMaterial->setBaseColor(m_surfaceController->activeTheme()->singleHighlightColor());
+ QQmlListReference sliceMaterialRef(m_sliceSelectionPointer, "materials");
+ sliceMaterialRef.append(pointerMaterial);
+ m_sliceInstancing = new SurfaceSelectionInstancing();
+ m_sliceInstancing->setScale(QVector3D(0.001f, 0.001f, 0.001f));
+ m_sliceSelectionPointer->setInstancing(m_sliceInstancing);
+
+ setScaleWithBackground({2.0f, 1.0f, 2.0f});
+ setBackgroundScaleMargin({0.1f, 0.1f, 0.1f});
+ setScale({2.0f, 1.0f, 2.0f});
+}
+
+void QQuickGraphsSurface::synchData()
+{
+ QQuickGraphsItem::synchData();
+
+ if (m_surfaceController->isSelectedPointChanged()) {
+ if (m_surfaceController->selectionMode().testFlag(QAbstract3DGraph::SelectionItem))
+ updateSelectedPoint();
+ m_surfaceController->setSelectedPointChanged(false);
+ }
+}
+
+inline static float getDataValue(const QSurfaceDataArray &array, bool searchRow, int index)
+{
+ if (searchRow)
+ return array.at(0)->at(index).x();
+ else
+ return array.at(index)->at(0).z();
+}
+
+inline static int binarySearchArray(const QSurfaceDataArray &array, int maxIndex, float limitValue, bool searchRow, bool lowBound, bool ascending)
+{
+ int min = 0;
+ int max = maxIndex;
+ int mid = 0;
+ int retVal;
+
+ while (max >= min) {
+ mid = (min + max) / 2;
+ float arrayValue = getDataValue(array, searchRow, mid);
+ if (arrayValue == limitValue)
+ return mid;
+ if (ascending) {
+ if (arrayValue < limitValue)
+ min = mid + 1;
+ else
+ max = mid -1;
+ } else {
+ if (arrayValue > limitValue)
+ min = mid + 1;
+ else
+ max = mid - 1;
+ }
+ }
+
+ if (lowBound == ascending) {
+ if (mid > max)
+ retVal = mid;
+ else
+ retVal = min;
+ } else {
+ if (mid > max)
+ retVal = max;
+ else
+ retVal = mid;
+ }
+
+ if (retVal < 0 || retVal > maxIndex) {
+ retVal = -1;
+ } else if (lowBound) {
+ if (getDataValue(array, searchRow, retVal) < limitValue)
+ retVal = -1;
+ } else {
+ if (getDataValue(array, searchRow, retVal) > limitValue)
+ retVal = -1;
+ }
+ return retVal;
+}
+
+void QQuickGraphsSurface::updateGraph()
+{
+ if (m_surfaceController->hasChangedSeriesList())
+ handleChangedSeries();
+
+ if (m_surfaceController->isSeriesVisibilityDirty()) {
+ for (auto model : m_model) {
+ bool visible = model->series->isVisible();
+ if (visible != model->model->visible() && isSliceEnabled()) {
+ setSliceEnabled(false);
+ setSliceActivatedChanged(true);
+ }
+ if (!visible) {
+ model->model->setVisible(visible);
+ model->gridModel->setVisible(visible);
+ if (sliceView()) {
+ model->sliceModel->setVisible(visible);
+ model->sliceGridModel->setVisible(visible);
+ }
+ continue;
+ }
+ model->gridModel->setVisible(model->series->drawMode().testFlag(QSurface3DSeries::DrawWireframe));
+ model->model->setVisible(model->series->drawMode().testFlag(QSurface3DSeries::DrawSurface));
+ if (isSliceEnabled()) {
+ model->sliceGridModel->setVisible(model->series->drawMode().testFlag(QSurface3DSeries::DrawWireframe));
+ model->sliceModel->setVisible(model->series->drawMode().testFlag(QSurface3DSeries::DrawSurface));
+ }
+ }
+
+ if (m_surfaceController->selectionMode().testFlag(QAbstract3DGraph::SelectionItem))
+ updateSelectedPoint();
+ }
+
+ if (m_surfaceController->isDataDirty()) {
+ for (auto model : m_model) {
+ bool visible = model->series->isVisible();
+ if (visible)
+ updateModel(model);
+ }
+ }
+}
+
+void QQuickGraphsSurface::handleChangedSeries()
+{
+ auto changedSeries = m_surfaceController->changedSeriesList();
+
+ for (auto series : changedSeries) {
+ for (auto model : m_model) {
+ if (model->series == series) {
+ updateModel(model);
+ }
+ }
+ }
+}
+
+void QQuickGraphsSurface::updateModel(SurfaceModel *model)
+{
+ const QSurfaceDataArray &array = *(model->series->dataProxy())->array();
+
+ // calculateSampleRect
+ QRect sampleSpace;
+ if (array.size() > 0) {
+ if (array.size() >= 2 && array.at(0)->size() >= 2) {
+ const int maxRow = array.size() - 1;
+ const int maxColumn = array.at(0)->size() - 1;
+
+ const bool ascendingX = array.at(0)->at(0).x() < array.at(0)->at(maxColumn).x();
+ const bool ascendingZ = array.at(0)->at(0).z() < array.at(maxRow)->at(0).z();
+
+ int idx = binarySearchArray(array, maxColumn, m_surfaceController->axisX()->min(), true, true, ascendingX);
+ if (idx != -1) {
+ if (ascendingX)
+ sampleSpace.setLeft(idx);
+ else
+ sampleSpace.setRight(idx);
+ } else {
+ sampleSpace.setWidth(-1);
+ }
+
+ idx = binarySearchArray(array, maxColumn, m_surfaceController->axisX()->max(), true, false, ascendingX);
+ if (idx != -1) {
+ if (ascendingX)
+ sampleSpace.setRight(idx);
+ else
+ sampleSpace.setLeft(idx);
+ } else {
+ sampleSpace.setWidth(-1); // to indicate nothing needs to be shown
+ }
+
+ idx = binarySearchArray(array, maxRow, m_surfaceController->axisZ()->min(), false, true, ascendingZ);
+ if (idx != -1) {
+ if (ascendingZ)
+ sampleSpace.setTop(idx);
+ else
+ sampleSpace.setBottom(idx);
+ } else {
+ sampleSpace.setWidth(-1); // to indicate nothing needs to be shown
+ }
+
+ idx = binarySearchArray(array, maxRow, m_surfaceController->axisZ()->max(), false, false, ascendingZ);
+ if (idx != -1) {
+ if (ascendingZ)
+ sampleSpace.setBottom(idx);
+ else
+ sampleSpace.setTop(idx);
+ } else {
+ sampleSpace.setWidth(-1); // to indicate nothing needs to be shown
+ }
+ }
+
+ int rowCount = sampleSpace.height();
+ int columnCount = sampleSpace.width();
+ model->rowCount = rowCount;
+ model->columnCount = columnCount;
+
+ int totalSize = rowCount * columnCount * 2;
+ float uvX = 1.0f / float(columnCount - 1);
+ float uvY = 1.0f / float(rowCount - 1);
+
+ // checkDirection
+ int dataDimensions = Surface3DController::BothAscending;
+ if (array.at(0)->at(0).x() > array.at(0)->at(array.at(0)->size() - 1).x())
+ dataDimensions |= Surface3DController::XDescending;
+ if (static_cast<QValue3DAxis *>(m_surfaceController->axisX())->reversed())
+ dataDimensions ^= Surface3DController::XDescending;
+
+ if (array.at(0)->at(0).z() > array.at(array.size() - 1)-> at(0).z())
+ dataDimensions |= Surface3DController::ZDescending;
+ if (static_cast<QValue3DAxis *>(m_surfaceController->axisZ())->reversed())
+ dataDimensions ^= Surface3DController::ZDescending;
+
+ m_surfaceController->setDataDimensions(static_cast<Surface3DController::DataDimensions>(dataDimensions));
+
+ model->vertices.reserve(totalSize);
+
+ bool isFlatShadingEnabled = model->series->isFlatShadingEnabled();
+
+ QVector3D boundsMin(0.0f, 0.0f, 0.0f);
+ QVector3D boundsMax(0.0f, 0.0f, 0.0f);
+
+ model->vertices.clear();
+ model->height.clear();
+ for (int i = 0 ; i < rowCount ; i++) {
+ const QSurfaceDataRow &row = *array.at(i);
+ for (int j = 0 ; j < columnCount ; j++) {
+ // getNormalizedVertex
+ SurfaceVertex vertex;
+ QVector3D pos = getNormalizedVertex(model, row.at(j), false, false);
+ vertex.position = pos;
+ vertex.normal = QVector3D(0, 0, 0);
+ vertex.uv = QVector2D(j * uvX, i * uvY);
+ vertex.coord = QPoint(i, j);
+ model->vertices.push_back(vertex);
+ if (boundsMin.isNull())
+ boundsMin = pos;
+ else
+ boundsMin = QVector3D(qMin(boundsMin.x(), pos.x()), qMin(boundsMin.y(), pos.y()), qMin(boundsMin.z(), pos.z()));
+ if (boundsMax.isNull())
+ boundsMax = pos;
+ else
+ boundsMax = QVector3D(qMax(boundsMax.x(), pos.x()), qMax(boundsMax.y(), pos.y()), qMax(boundsMax.z(), pos.z()));
+ }
+ }
+
+ //create normals
+ int rowLimit = rowCount - 1;
+ int colLimit = columnCount - 1;
+
+ int totalIndex = 0;
+
+ model->indices.clear();
+
+ if (isFlatShadingEnabled) {
+ model->coarceVertices.clear();
+
+ createCoarseVertices(model, 0, 0, colLimit, rowLimit);
+ } else {
+ if (dataDimensions == Surface3DController::BothAscending || dataDimensions == Surface3DController::XDescending) {
+ for (int row = 0 ; row < rowLimit ; row++)
+ createSmoothNormalBodyLine(model, totalIndex, row * columnCount);
+ createSmoothNormalUpperLine(model, totalIndex);
+ }
+ else {
+ createSmoothNormalUpperLine(model, totalIndex);
+ for (int row = 1 ; row < rowCount ; row++)
+ createSmoothNormalBodyLine(model, totalIndex, row * columnCount);
+ }
+
+ createSmoothIndices(model, 0, 0, colLimit, rowLimit);
+ }
+
+ auto geometry = model->model->geometry();
+ QByteArray vertexBuffer;
+ if (isFlatShadingEnabled)
+ vertexBuffer.setRawData(reinterpret_cast<char *>(model->coarceVertices.data()),
+ model->coarceVertices.size() * sizeof(SurfaceVertex));
+ else
+ vertexBuffer.setRawData(reinterpret_cast<char *>(model->vertices.data()),
+ model->vertices.size() * sizeof(SurfaceVertex));
+ geometry->setVertexData(vertexBuffer);
+ QByteArray indexBuffer(reinterpret_cast<char *>(model->indices.data()),
+ model->indices.size() * sizeof(quint32));
+ geometry->setIndexData(indexBuffer);
+ geometry->setBounds(boundsMin, boundsMax);
+ geometry->update();
+
+ updateMaterial(model);
+
+ createGridlineIndices(model, 0, 0, colLimit, rowLimit);
+
+ auto gridGeometry = model->gridModel->geometry();
+
+ if (isFlatShadingEnabled)
+ vertexBuffer.setRawData(reinterpret_cast<char *>(model->vertices.data()),
+ model->vertices.size() * sizeof(SurfaceVertex));
+ gridGeometry->setVertexData(vertexBuffer);
+ QByteArray gridIndexBuffer(reinterpret_cast<char *>(model->gridIndices.data()),
+ model->gridIndices.size() * sizeof(quint32));
+ gridGeometry->setIndexData(gridIndexBuffer);
+ gridGeometry->setBounds(boundsMin, boundsMax);
+ gridGeometry->update();
+
+ QQmlListReference gridMaterialRef(model->gridModel, "materials");
+ auto gridMaterial = static_cast<QQuick3DPrincipledMaterial *>(gridMaterialRef.at(0));
+ QColor gridColor = model->series->wireframeColor();
+ gridMaterial->setBaseColor(gridColor);
+ }
+}
+
+void QQuickGraphsSurface::updateMaterial(SurfaceModel *model)
+{
+ auto axisY = m_surfaceController->axisY();
+ float maxY = axisY->max();
+ float minY = axisY->min();
+ QQmlListReference materialRef(model->model, "materials");
+ auto material = static_cast<QQuick3DDefaultMaterial *>(materialRef.at(0));
+ auto textureData = material->diffuseMap()->textureData();
+ textureData->setSize(QSize(model->rowCount, model->columnCount));
+ textureData->setFormat(QQuick3DTextureData::RGBA8);
+ QByteArray imageData;
+ imageData.resize(model->height.size() * 4);
+ QLinearGradient gradient = model->series->baseGradient();
+ auto stops = gradient.stops();
+ for (int i = 0; i < model->height.size(); i++) {
+ float height = model->height.at(i);
+ float normalizedHeight = (height - minY) / (maxY - minY);
+ for (int j = 0; j < stops.size(); j++) {
+ if (normalizedHeight < stops.at(j).first ||
+ (normalizedHeight >= (float)stops.at(j).first && j == stops.size() - 1)) {
+ QColor color;
+ if (j == 0 || normalizedHeight >= (float)stops.at(j).first) {
+ color = stops.at(j).second;
+ } else {
+ float normalLowerBound = stops.at(j - 1).first;
+ float normalUpperBound = stops.at(j).first;
+ normalizedHeight = (normalizedHeight - normalLowerBound) / (normalUpperBound - normalLowerBound);
+ QColor start = stops.at(j - 1).second;
+ QColor end = stops.at(j).second;
+ float red = start.redF() + ((end.redF() - start.redF()) * normalizedHeight);
+ float green = start.greenF() + ((end.greenF() - start.greenF()) * normalizedHeight);
+ float blue = start.blueF() + ((end.blueF() - start.blueF()) * normalizedHeight);
+ color.setRedF(red);
+ color.setGreenF(green);
+ color.setBlueF(blue);
+ }
+ imageData.data()[i * 4 + 0] = char(color.red());
+ imageData.data()[i * 4 + 1] = char(color.green());
+ imageData.data()[i * 4 + 2] = char(color.blue());
+ imageData.data()[i * 4 + 3] = char(color.alpha());
+ break;
+ }
+ }
+ }
+ textureData->setTextureData(imageData);
+
+ QQmlListReference sliceMaterialRef(model->sliceModel, "materials");
+ material = static_cast<QQuick3DDefaultMaterial *>(sliceMaterialRef.at(0));
+ textureData = material->diffuseMap()->textureData();
+ textureData->setSize(QSize(model->rowCount, model->columnCount));
+ textureData->setFormat(QQuick3DTextureData::RGBA8);
+ textureData->setTextureData(imageData);
+}
+
+QVector3D QQuickGraphsSurface::getNormalizedVertex(SurfaceModel *model, const QSurfaceDataItem &data, bool polar, bool flipXZ)
+{
+ Q_UNUSED(polar);
+ Q_UNUSED(flipXZ);
+
+ float normalizedX;
+ float normalizedY;
+ float normalizedZ;
+ QValue3DAxis* axisX = static_cast<QValue3DAxis *>(m_surfaceController->axisX());
+ QValue3DAxis* axisY = static_cast<QValue3DAxis *>(m_surfaceController->axisY());
+ QValue3DAxis* axisZ = static_cast<QValue3DAxis *>(m_surfaceController->axisZ());
+ // TODO : Need to handle polar, flipXZ
+ float scale, translate;
+ scale = translate = this->scale().x();
+ normalizedX = axisX->positionAt(data.x()) * scale * 2.0f - translate;
+ scale = translate = this->scale().y();
+ model->height.push_back(data.y());
+ normalizedY = axisY->positionAt(data.y()) * scale * 2.0f - translate;
+ scale = translate = this->scale().z();
+ normalizedZ = axisZ->positionAt(data.z()) * -scale * 2.0f + translate;
+ return QVector3D(normalizedX, normalizedY, normalizedZ);
+}
+
+inline static QVector3D normal(const QVector3D &a, const QVector3D &b, const QVector3D &c)
+{
+ QVector3D v1 = b - a;
+ QVector3D v2 = c - a;
+ QVector3D normal = QVector3D::crossProduct(v1, v2);
+
+ return normal;
+}
+
+void QQuickGraphsSurface::updateSliceGraph()
+{
+ QQuickGraphsItem::updateSliceGraph();
+
+ if (!sliceView()->isVisible())
+ return;
+
+ auto selectionMode = m_surfaceController->selectionMode();
+
+ for (auto model : m_model) {
+ if (!model->series->isVisible())
+ continue;
+
+ QVector<SurfaceVertex> selectedSeries;
+
+ if (selectionMode.testFlag(QAbstract3DGraph::SelectionRow)) {
+ int selectedRow = model->selectedVertex.coord.x() * model->columnCount;
+ selectedSeries.reserve(model->rowCount * 2);
+ QVector<SurfaceVertex> list;
+ for (int i = 0; i < model->rowCount; i++) {
+ SurfaceVertex vertex = model->vertices.at(selectedRow + i);
+ vertex.normal = QVector3D(.0f, .0f, 1.f);
+ vertex.position.setY(vertex.position.y() - .025f);
+ vertex.position.setZ(.0f);
+ selectedSeries.append(vertex);
+ vertex.position.setY(vertex.position.y() + .05f);
+ list.append(vertex);
+ }
+ selectedSeries.append(list);
+ }
+
+ if (selectionMode.testFlag(QAbstract3DGraph::SelectionColumn)) {
+ int selectedColumn = model->selectedVertex.coord.y();
+ selectedSeries.reserve(model->columnCount * 2);
+ QVector<SurfaceVertex> list;
+ for (int i = 0; i < model->columnCount; i++) {
+ SurfaceVertex vertex = model->vertices.at((i * model->rowCount) + selectedColumn);
+ vertex.normal = QVector3D(.0f, .0f, -1.0f);
+ vertex.position.setX(vertex.position.z());
+ vertex.position.setY(vertex.position.y() - .025f);
+ vertex.position.setZ(0);
+ selectedSeries.append(vertex);
+ vertex.position.setY(vertex.position.y() + .05f);
+ list.append(vertex);
+ }
+ selectedSeries.append(list);
+ }
+
+ int cnt = model->rowCount - 1;
+ QVector<quint32> indices;
+ indices.reserve(cnt * 6);
+ for (int i = 0; i < cnt; i++) {
+ indices.push_back(i + 1);
+ indices.push_back(i + cnt + 1);
+ indices.push_back(i);
+
+ indices.push_back(i + cnt + 2);
+ indices.push_back(i + cnt + 1);
+ indices.push_back(i + 1);
+ }
+
+ auto geometry = model->sliceModel->geometry();
+ QByteArray vertexBuffer(reinterpret_cast<char *>(selectedSeries.data()),
+ selectedSeries.size() * sizeof(SurfaceVertex));
+ geometry->setVertexData(vertexBuffer);
+ QByteArray indexBuffer(reinterpret_cast<char *>(indices.data()),
+ indices.size() * sizeof(quint32));
+ geometry->setIndexData(indexBuffer);
+ geometry->update();
+
+ geometry = model->sliceGridModel->geometry();
+ geometry->setVertexData(vertexBuffer);
+
+ QVector<quint32> gridIndices;
+ gridIndices.reserve(cnt * 4);
+ for (int i = 0; i < cnt; i++) {
+ gridIndices.push_back(i);
+ gridIndices.push_back(i + cnt + 1);
+
+ gridIndices.push_back(i);
+ gridIndices.push_back(i + 1);
+ }
+ QByteArray gridIndexBuffer(reinterpret_cast<char *>(gridIndices.data()),
+ gridIndices.size() * sizeof(quint32));
+ geometry->setIndexData(gridIndexBuffer);
+ geometry->update();
+
+ QQmlListReference gridMaterialRef(model->sliceGridModel, "materials");
+ auto gridMaterial = static_cast<QQuick3DPrincipledMaterial *>(gridMaterialRef.at(0));
+ QColor gridColor = model->series->wireframeColor();
+ gridMaterial->setBaseColor(gridColor);
+ }
+}
+
+void QQuickGraphsSurface::createSmoothNormalBodyLine(SurfaceModel *model, int &totalIndex, int column)
+{
+ int columnCount = model->columnCount;
+ int colLimit = columnCount - 1;
+ Surface3DController::DataDimensions dataDimensions = m_surfaceController->dataDimensions();
+ if (dataDimensions == Surface3DController::BothAscending) {
+ int end = colLimit + column;
+ for (int j = column ; j < end ; j++) {
+ SurfaceVertex vertex = model->vertices.at(totalIndex);
+ vertex.normal = normal(model->vertices.at(j).position,
+ model->vertices.at(j + 1).position,
+ model->vertices.at(j + columnCount).position);
+ model->vertices.replace(totalIndex++, vertex);
+ }
+ SurfaceVertex vertex = model->vertices.at(totalIndex);
+ vertex.normal = normal(model->vertices.at(end).position,
+ model->vertices.at(end + columnCount).position,
+ model->vertices.at(end - 1).position);
+ model->vertices.replace(totalIndex++, vertex);
+ } else if (dataDimensions == Surface3DController::XDescending) {
+ SurfaceVertex vertex = model->vertices.at(totalIndex);
+ vertex.normal = normal(model->vertices.at(column).position,
+ model->vertices.at(column + columnCount).position,
+ model->vertices.at(column + 1).position);
+ model->vertices.replace(totalIndex++, vertex);
+ int end = column + columnCount;
+ for (int j = column + 1 ; j < end ; j++) {
+ SurfaceVertex vertex = model->vertices.at(totalIndex);
+ vertex.normal = normal(model->vertices.at(j).position,
+ model->vertices.at(j - 1).position,
+ model->vertices.at(j + columnCount).position);
+ model->vertices.replace(totalIndex++, vertex);
+ }
+ } else if (dataDimensions == Surface3DController::ZDescending) {
+ int end = colLimit + column;
+ for (int j = column; j < end ; j++) {
+ SurfaceVertex vertex = model->vertices.at(totalIndex);
+ vertex.normal = normal(model->vertices.at(j).position,
+ model->vertices.at(j + 1).position,
+ model->vertices.at(j - columnCount).position);
+ model->vertices.replace(totalIndex++, vertex);
+ }
+ SurfaceVertex vertex = model->vertices.at(totalIndex);
+ vertex.normal = normal(model->vertices.at(end).position,
+ model->vertices.at(end - columnCount).position,
+ model->vertices.at(end - 1).position);
+ model->vertices.replace(totalIndex++, vertex);
+ } else {
+ SurfaceVertex vertex = model->vertices.at(totalIndex);
+ vertex.normal = normal(model->vertices.at(column).position,
+ model->vertices.at(column - columnCount).position,
+ model->vertices.at(column + 1).position);
+ model->vertices.replace(totalIndex++, vertex);
+ int end = column + columnCount;
+ for (int j = 0 ; j < end ; j++) {
+ SurfaceVertex vertex = model->vertices.at(totalIndex);
+ vertex.normal = normal(model->vertices.at(j).position,
+ model->vertices.at(j-1).position,
+ model->vertices.at(j - columnCount).position);
+ model->vertices.replace(totalIndex++, vertex);
+ }
+ }
+}
+
+void QQuickGraphsSurface::createSmoothNormalUpperLine(SurfaceModel *model, int &totalIndex)
+{
+ int columnCount = model->columnCount;
+ int rowCount = model->rowCount;
+ Surface3DController::DataDimensions dataDimensions = m_surfaceController->dataDimensions();
+
+ if (dataDimensions == Surface3DController::BothAscending) {
+ int lineEnd = rowCount * columnCount - 1;
+ for (int j = (rowCount - 1) * columnCount; j < lineEnd; j++) {
+ SurfaceVertex vertex = model->vertices.at(totalIndex);
+ vertex.normal = normal(model->vertices.at(j).position,
+ model->vertices.at(j - columnCount).position,
+ model->vertices.at(j + 1).position);
+ model->vertices.replace(totalIndex++, vertex);
+ }
+ SurfaceVertex vertex = model->vertices.at(totalIndex);
+ vertex.normal = normal(model->vertices.at(lineEnd).position,
+ model->vertices.at(lineEnd - 1).position,
+ model->vertices.at(lineEnd - columnCount).position);
+ model->vertices.replace(totalIndex++, vertex);
+ } else if (dataDimensions == Surface3DController::XDescending) {
+ int lineStart = (rowCount - 1) * columnCount;
+ int lineEnd = rowCount * columnCount;
+ SurfaceVertex vertex = model->vertices.at(totalIndex);
+ vertex.normal = normal(model->vertices.at(lineStart).position,
+ model->vertices.at(lineStart + 1).position,
+ model->vertices.at(lineStart - columnCount).position);
+ model->vertices.replace(totalIndex++, vertex);
+ for (int j = lineStart + 1; j < lineEnd; j++) {
+ SurfaceVertex vertex = model->vertices.at(totalIndex);
+ vertex.normal = normal(model->vertices.at(j).position,
+ model->vertices.at(j - columnCount).position,
+ model->vertices.at(j - 1).position);
+ model->vertices.replace(totalIndex++, vertex);
+ }
+ } else if (dataDimensions == Surface3DController::ZDescending) {
+ int colLimit = columnCount - 1;
+ for (int j = 0; j < colLimit; j++) {
+ SurfaceVertex vertex = model->vertices.at(totalIndex);
+ vertex.normal = normal(model->vertices.at(j).position,
+ model->vertices.at(j + columnCount).position,
+ model->vertices.at(j + 1).position);
+ model->vertices.replace(totalIndex++, vertex);
+ }
+ SurfaceVertex vertex = model->vertices.at(totalIndex);
+ vertex.normal = normal(model->vertices.at(colLimit).position,
+ model->vertices.at(colLimit - 1).position,
+ model->vertices.at(colLimit + columnCount).position);
+ model->vertices.replace(totalIndex++, vertex);
+ } else { // BothDescending
+ SurfaceVertex vertex = model->vertices.at(totalIndex);
+ vertex.normal = normal(model->vertices.at(0).position,
+ model->vertices.at(1).position,
+ model->vertices.at(columnCount).position);
+ model->vertices.replace(totalIndex++, vertex);
+ for (int j = 1; j < columnCount; j++) {
+ SurfaceVertex vertex = model->vertices.at(totalIndex);
+ vertex.normal = normal(model->vertices.at(j).position,
+ model->vertices.at(j + columnCount).position,
+ model->vertices.at(j - 1).position);
+ model->vertices.replace(totalIndex++, vertex);
+ }
+ }
+}
+
+void QQuickGraphsSurface::createSmoothIndices(SurfaceModel *model, int x, int y, int endX, int endY)
+{
+ int columnCount = model->columnCount;
+ int rowCount = model->rowCount;
+ Surface3DController::DataDimensions dataDimensions = m_surfaceController->dataDimensions();
+
+ if (endX >= columnCount)
+ endX = columnCount - 1;
+ if (endY >= rowCount)
+ endY = rowCount - 1;
+ if (x > endX)
+ x = endX - 1;
+ if (y > endY)
+ y = endY - 1;
+
+ int indexCount = 6 * (endX - x) * (endY - y);
+
+ QVector<quint32> *indices = &model->indices;
+
+ indices->clear();
+ indices->resize(indexCount);
+
+ int rowEnd = endY * columnCount;
+ for (int row = y * columnCount ; row < rowEnd ; row += columnCount) {
+ for (int j = x ; j < endX ; j++) {
+ if (dataDimensions == Surface3DController::BothAscending
+ || dataDimensions == Surface3DController::BothDescending) {
+ indices->push_back(row + j + 1);
+ indices->push_back(row + columnCount + j);
+ indices->push_back(row + j);
+
+ indices->push_back(row + columnCount + j + 1);
+ indices->push_back(row + columnCount + j);
+ indices->push_back(row + j + 1);
+ } else if (dataDimensions == Surface3DController::XDescending) {
+ indices->push_back(row + columnCount + j);
+ indices->push_back(row + columnCount + j + 1);
+ indices->push_back(row + j);
+
+ indices->push_back(row + j);
+ indices->push_back(row + columnCount + j + 1);
+ indices->push_back(row + j + 1);
+ } else {
+ indices->push_back(row + columnCount + j);
+ indices->push_back(row + columnCount + j + 1);
+ indices->push_back(row + j + 1);
+
+ indices->push_back(row + j);
+ indices->push_back(row + columnCount + j + 1);
+ indices->push_back(row + j + 1);
+ }
+ }
+ }
+}
+
+void QQuickGraphsSurface::createCoarseVertices(SurfaceModel *model, int x, int y, int endX, int endY)
+{
+ int columnCount = model->columnCount;
+ int rowCount = model->rowCount;
+ Surface3DController::DataDimensions dataDimensions = m_surfaceController->dataDimensions();
+
+ if (endX >= columnCount)
+ endX = columnCount - 1;
+ if (endY >= rowCount)
+ endY = rowCount - 1;
+ if (x > endX)
+ x = endX - 1;
+ if (y > endY)
+ y = endY - 1;
+
+ int indexCount = 6 * (endX - x) * (endY - y);
+ model->indices.clear();
+ model->indices.resize(indexCount);
+
+ int index = 0;
+ int rowEnd = endY * columnCount;
+
+ int i1, i2, i3;
+ SurfaceVertex v1, v2, v3;
+ QVector3D normalVector;
+
+ for (int row = y * columnCount; row < rowEnd; row += columnCount) {
+ for (int j = x; j < endX; j++) {
+ if (dataDimensions == Surface3DController::BothAscending
+ || dataDimensions == Surface3DController::BothDescending) {
+ i1 = row + j + 1, i2 = row + columnCount + j, i3 = row + j;
+ v1 = model->vertices.at(i1);
+ v2 = model->vertices.at(i2);
+ v3 = model->vertices.at(i3);
+ normalVector = normal(v1.position, v2.position, v3.position);
+ v1.normal = normalVector;
+ v2.normal = normalVector;
+ v3.normal = normalVector;
+ model->coarceVertices.push_back(v1);
+ model->coarceVertices.push_back(v2);
+ model->coarceVertices.push_back(v3);
+ model->indices.push_back(index++);
+ model->indices.push_back(index++);
+ model->indices.push_back(index++);
+
+ i1 = row + columnCount + j + 1, i2 = row + columnCount + j, i3 = row + j + 1;
+ v1 = model->vertices.at(i1);
+ v2 = model->vertices.at(i2);
+ v3 = model->vertices.at(i3);
+ normalVector = normal(v1.position, v2.position, v3.position);
+ v1.normal = normalVector;
+ v2.normal = normalVector;
+ v3.normal = normalVector;
+ model->coarceVertices.push_back(v1);
+ model->coarceVertices.push_back(v2);
+ model->coarceVertices.push_back(v3);
+ model->indices.push_back(index++);
+ model->indices.push_back(index++);
+ model->indices.push_back(index++);
+ } else if (dataDimensions == Surface3DController::XDescending) {
+ i1 = row + columnCount + j, i2 = row + columnCount + j + 1, i3 = row + j;
+ v1 = model->vertices.at(i1);
+ v2 = model->vertices.at(i2);
+ v3 = model->vertices.at(i3);
+ normalVector = normal(v1.position, v2.position, v3.position);
+ v1.normal = normalVector;
+ v2.normal = normalVector;
+ v3.normal = normalVector;
+ model->coarceVertices.push_back(v1);
+ model->coarceVertices.push_back(v2);
+ model->coarceVertices.push_back(v3);
+ model->indices.push_back(index++);
+ model->indices.push_back(index++);
+ model->indices.push_back(index++);
+
+ i1 = row + j, i2 = row + columnCount + j + 1, i3 = row + j + 1;
+ v1 = model->vertices.at(i1);
+ v2 = model->vertices.at(i2);
+ v3 = model->vertices.at(i3);
+ normalVector = normal(v1.position, v2.position, v3.position);
+ v1.normal = normalVector;
+ v2.normal = normalVector;
+ v3.normal = normalVector;
+ model->coarceVertices.push_back(v1);
+ model->coarceVertices.push_back(v2);
+ model->coarceVertices.push_back(v3);
+ model->indices.push_back(index++);
+ model->indices.push_back(index++);
+ model->indices.push_back(index++);
+ } else {
+ i1 = row + columnCount + j, i2 = row + columnCount + j + 1, i3 = row + j + 1;
+ v1 = model->vertices.at(i1);
+ v2 = model->vertices.at(i2);
+ v3 = model->vertices.at(i3);
+ normalVector = normal(v1.position, v2.position, v3.position);
+ v1.normal = normalVector;
+ v2.normal = normalVector;
+ v3.normal = normalVector;
+ model->coarceVertices.push_back(v1);
+ model->coarceVertices.push_back(v2);
+ model->coarceVertices.push_back(v3);
+ model->indices.push_back(index++);
+ model->indices.push_back(index++);
+ model->indices.push_back(index++);
+
+ i1 = row + j, i2 = row + columnCount + j + 1, i3 = row + j + 1;
+ v1 = model->vertices.at(i1);
+ v2 = model->vertices.at(i2);
+ v3 = model->vertices.at(i3);
+ normalVector = normal(v1.position, v2.position, v3.position);
+ v1.normal = normalVector;
+ v2.normal = normalVector;
+ v3.normal = normalVector;
+ model->coarceVertices.push_back(v1);
+ model->coarceVertices.push_back(v2);
+ model->coarceVertices.push_back(v3);
+ model->indices.push_back(index++);
+ model->indices.push_back(index++);
+ model->indices.push_back(index++);
+ }
+ }
+ }
+}
+
+void QQuickGraphsSurface::createGridlineIndices(SurfaceModel *model, int x, int y, int endX, int endY)
+{
+ int columnCount = model->columnCount;
+ int rowCount = model->rowCount;
+
+ if (endX >= columnCount)
+ endX = columnCount - 1;
+ if (endY >= rowCount)
+ endY = rowCount - 1;
+ if (x > endX)
+ x = endX - 1;
+ if (y > endY)
+ y = endY - 1;
+
+ int nColumns = endX - x + 1;
+ int nRows = endY - y + 1;
+
+ int gridIndexCount = 2 * nColumns * (nRows - 1) + 2 * nRows * (nColumns - 1);
+ model->gridIndices.resize(gridIndexCount);
+ model->gridIndices.clear();
+
+ for (int i = y, row = columnCount * y ; i <= endY ; i++, row += columnCount) {
+ for (int j = x ; j < endX ; j++) {
+ model->gridIndices.push_back(row + j);
+ model->gridIndices.push_back(row + j + 1);
+ }
+ }
+ for (int i = y, row = columnCount * y ; i < endY ; i++, row += columnCount) {
+ for (int j = x ; j <= endX ; j++) {
+ model->gridIndices.push_back(row + j);
+ model->gridIndices.push_back(row + j + columnCount);
+ }
+ }
+}
+
+bool QQuickGraphsSurface::handleMousePressedEvent(QMouseEvent *event)
+{
+ if (!QQuickGraphsItem::handleMousePressedEvent(event))
+ return true;
+
+ if (Qt::LeftButton == event->button()) {
+ auto mousePos = event->pos();
+ auto pickResult = pickAll(mousePos.x(), mousePos.y());
+ QVector3D pickedPos(0.0f, 0.0f, 0.0f);
+ QQuick3DModel *pickedModel = nullptr;
+
+ auto selectionMode = m_surfaceController->selectionMode();
+ if (!selectionMode.testFlag(QAbstract3DGraph::SelectionNone)) {
+ for (auto picked : pickResult) {
+ if (picked.objectHit()->objectName().contains(QStringLiteral("SurfaceModel"))) {
+ pickedPos = picked.position();
+ pickedModel = picked.objectHit();
+ break;
+ }
+ }
+
+ if (!pickedPos.isNull()) {
+ float min = -1.0f;
+ SurfaceVertex selectedVertex;
+
+ for (auto model : m_model) {
+ if (model->model == pickedModel) {
+ model->picked = true;
+ for (auto vertex : model->vertices) {
+ QVector3D pos = vertex.position;
+ float dist = pickedPos.distanceToPoint(pos);
+ if (selectedVertex.position.isNull() || dist < min) {
+ min = dist;
+ selectedVertex = vertex;
+ }
+ }
+ } else {
+ model->picked = false;
+ }
+ }
+
+ for (auto model : m_model) {
+ if (model->picked)
+ model->selectedVertex = selectedVertex;
+ else
+ model->selectedVertex = SurfaceVertex();
+
+ if (selectionMode.testFlag(QAbstract3DGraph::SelectionMultiSeries)) {
+ if (model->picked) {
+ model->selectedVertex = selectedVertex;
+ } else {
+ QPoint coord = selectedVertex.coord;
+ int index = coord.x() * model->rowCount + coord.y();
+ auto vertex = model->vertices.at(index);
+ model->selectedVertex = vertex;
+ }
+ }
+
+ if (!selectedVertex.position.isNull()
+ && model->picked) {
+ model->series->setSelectedPoint(selectedVertex.coord);
+ if (isSliceEnabled()) {
+ m_surfaceController->setSlicingActive(true);
+ setSliceActivatedChanged(true);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+void QQuickGraphsSurface::updateSelectedPoint()
+{
+ bool labelVisible = false;
+ m_instancing->resetPositions();
+ if (isSliceEnabled())
+ m_sliceInstancing->resetPositions();
+ for (auto model : m_model) {
+ SurfaceVertex selectedVertex = model->selectedVertex;
+ if (model->series->isVisible() &&
+ !selectedVertex.position.isNull()) {
+ m_instancing->addPosition(selectedVertex.position);
+ if (isSliceEnabled()) {
+ QVector3D slicePosition = selectedVertex.position;
+ if (m_surfaceController->selectionMode().testFlag(QAbstract3DGraph::SelectionColumn))
+ slicePosition.setX(slicePosition.z());
+ slicePosition.setZ(.0f);
+ m_sliceInstancing->addPosition(slicePosition);
+ }
+ if (model->picked) {
+ const QSurfaceDataArray &array = *(model->series->dataProxy())->array();
+ const QSurfaceDataRow &rowArray = *array.at(selectedVertex.coord.x());
+ QVector3D value = rowArray.at(selectedVertex.coord.y()).position();
+ QVector3D labelPosition = selectedVertex.position;
+ QString x = static_cast<QValue3DAxis *>(m_surfaceController->axisX())->stringForValue(value.x());
+ QString y = static_cast<QValue3DAxis *>(m_surfaceController->axisY())->stringForValue(value.y());
+ QString z = static_cast<QValue3DAxis *>(m_surfaceController->axisZ())->stringForValue(value.z());
+ QString label = x + QStringLiteral(", ") +
+ y + QStringLiteral(", ") +
+ z;
+ itemLabel()->setPosition(labelPosition);
+ itemLabel()->setProperty("labelText", label);
+ itemLabel()->setEulerRotation(QVector3D(
+ -m_surfaceController->scene()->activeCamera()->yRotation(),
+ -m_surfaceController->scene()->activeCamera()->xRotation(),
+ 0));
+ labelVisible = true;
+
+ if (isSliceEnabled()) {
+ labelPosition.setZ(.1f);
+ labelPosition.setY(labelPosition.y() + .05f);
+ sliceItemLabel()->setPosition(labelPosition);
+ sliceItemLabel()->setProperty("labelText", label);
+ }
+ }
+ }
+ }
+ itemLabel()->setVisible(labelVisible);
+ if (isSliceEnabled())
+ sliceItemLabel()->setVisible(labelVisible);
+}
+
+void QQuickGraphsSurface::addModel(QSurface3DSeries *series)
+{
+ auto scene = QQuick3DViewport::scene();
+ QQuick3DViewport *sliceParent = sliceView();
+ bool visible = series->isVisible();
+
+ auto model = new QQuick3DModel();
+ model->setParent(scene);
+ model->setParentItem(scene);
+ model->setObjectName(QStringLiteral("SurfaceModel"));
+ model->setVisible(visible);
+ if (m_surfaceController->selectionMode().testFlag(QAbstract3DGraph::SelectionNone))
+ model->setPickable(false);
+ else
+ model->setPickable(true);
+
+ auto geometry = new QQuick3DGeometry();
+ geometry->setParent(this);
+ geometry->setStride(sizeof(SurfaceVertex));
+ geometry->setPrimitiveType(QQuick3DGeometry::PrimitiveType::Triangles);
+ geometry->addAttribute(QQuick3DGeometry::Attribute::PositionSemantic,
+ 0,
+ QQuick3DGeometry::Attribute::F32Type);
+ geometry->addAttribute(QQuick3DGeometry::Attribute::TexCoord0Semantic,
+ sizeof(QVector3D) * 2,
+ QQuick3DGeometry::Attribute::F32Type);
+ geometry->addAttribute(QQuick3DGeometry::Attribute::NormalSemantic,
+ sizeof(QVector3D),
+ QQuick3DGeometry::Attribute::F32Type);
+ geometry->addAttribute(QQuick3DGeometry::Attribute::IndexSemantic,
+ 0,
+ QQuick3DGeometry::Attribute::U32Type);
+ model->setGeometry(geometry);
+
+ QQmlListReference materialRef(model, "materials");
+ auto material = new QQuick3DDefaultMaterial();
+ material->setParent(this);
+ QQuick3DTexture *texture = new QQuick3DTexture();
+ texture->setParent(this);
+ QQuick3DTextureData *textureData = new QQuick3DTextureData();
+ textureData->setParent(this);
+ texture->setTextureData(textureData);
+ material->setDiffuseMap(texture);
+ material->setSpecularAmount(7.0f);
+ material->setSpecularRoughness(0.025f);
+ material->setCullMode(QQuick3DMaterial::NoCulling);
+ materialRef.append(material);
+
+ auto gridModel = new QQuick3DModel();
+ gridModel->setParent(scene);
+ gridModel->setParentItem(scene);
+ gridModel->setVisible(visible);
+ gridModel->setDepthBias(1.0f);
+ auto gridGeometry = new QQuick3DGeometry();
+ gridGeometry->setParent(this);
+ gridGeometry->setStride(sizeof(SurfaceVertex));
+ gridGeometry->setPrimitiveType(QQuick3DGeometry::PrimitiveType::Lines);
+ gridGeometry->addAttribute(QQuick3DGeometry::Attribute::PositionSemantic,
+ 0,
+ QQuick3DGeometry::Attribute::F32Type);
+ gridGeometry->addAttribute(QQuick3DGeometry::Attribute::IndexSemantic,
+ 0,
+ QQuick3DGeometry::Attribute::U32Type);
+ gridModel->setGeometry(gridGeometry);
+ QQmlListReference gridMaterialRef(gridModel, "materials");
+ auto gridMaterial = new QQuick3DPrincipledMaterial();
+ gridMaterial->setParent(this);
+ gridMaterial->setLighting(QQuick3DPrincipledMaterial::NoLighting);
+ gridMaterial->setParent(this);
+ gridMaterialRef.append(gridMaterial);
+
+ SurfaceModel *surfaceModel = new SurfaceModel();
+ surfaceModel->model = model;
+ surfaceModel->gridModel = gridModel;
+ surfaceModel->series = series;
+
+ model = new QQuick3DModel();
+ model->setParent(sliceParent->scene());
+ model->setParentItem(sliceParent->scene());
+ model->setVisible(visible);
+ model->setDepthBias(1.f);
+
+ geometry = new QQuick3DGeometry();
+ geometry->setParent(model);
+ geometry->setParentItem(model);
+ geometry->setStride(sizeof(SurfaceVertex));
+ geometry->setPrimitiveType(QQuick3DGeometry::PrimitiveType::Triangles);
+ geometry->addAttribute(QQuick3DGeometry::Attribute::PositionSemantic,
+ 0,
+ QQuick3DGeometry::Attribute::F32Type);
+ geometry->addAttribute(QQuick3DGeometry::Attribute::TexCoord0Semantic,
+ sizeof(QVector3D) * 2,
+ QQuick3DGeometry::Attribute::F32Type);
+ geometry->addAttribute(QQuick3DGeometry::Attribute::NormalSemantic,
+ sizeof(QVector3D),
+ QQuick3DGeometry::Attribute::F32Type);
+ geometry->addAttribute(QQuick3DGeometry::Attribute::IndexSemantic,
+ 0,
+ QQuick3DGeometry::Attribute::U32Type);
+ model->setGeometry(geometry);
+
+ materialRef = QQmlListReference(model, "materials");
+ material = new QQuick3DDefaultMaterial();
+ material->setParent(model);
+ material->setParentItem(model);
+ texture = new QQuick3DTexture();
+ texture->setParent(model);
+ texture->setParent(model);
+ textureData = new QQuick3DTextureData();
+ textureData->setParent(model);
+ textureData->setParentItem(model);
+ texture->setTextureData(textureData);
+ material->setDiffuseMap(texture);
+ material->setSpecularAmount(.1f);
+ material->setSpecularRoughness(0.025f);
+ material->setCullMode(QQuick3DMaterial::NoCulling);
+ materialRef.append(material);
+
+ surfaceModel->sliceModel = model;
+
+ gridModel = new QQuick3DModel();
+ gridModel->setParent(sliceParent->scene());
+ gridModel->setParentItem(sliceParent->scene());
+ gridModel->setVisible(visible);
+ gridModel->setDepthBias(1.0f);
+ gridGeometry = new QQuick3DGeometry();
+ gridGeometry->setParent(gridModel);
+ gridGeometry->setStride(sizeof(SurfaceVertex));
+ gridGeometry->setPrimitiveType(QQuick3DGeometry::PrimitiveType::Lines);
+ gridGeometry->addAttribute(QQuick3DGeometry::Attribute::PositionSemantic,
+ 0,
+ QQuick3DGeometry::Attribute::F32Type);
+ gridGeometry->addAttribute(QQuick3DGeometry::Attribute::IndexSemantic,
+ 0,
+ QQuick3DGeometry::Attribute::U32Type);
+ gridModel->setGeometry(gridGeometry);
+ gridMaterialRef = QQmlListReference(gridModel, "materials");
+ gridMaterial = new QQuick3DPrincipledMaterial();
+ gridMaterial->setParent(gridModel);
+ gridMaterial->setLighting(QQuick3DPrincipledMaterial::NoLighting);
+ gridMaterial->setParent(gridModel);
+ gridMaterialRef.append(gridMaterial);
+
+ surfaceModel->sliceGridModel = gridModel;
+
+ m_model.push_back(surfaceModel);
+
+ connect(series,
+ &QSurface3DSeries::flatShadingEnabledChanged,
+ this,
+ &QQuickGraphsSurface::handleFlatShadingEnabledChanged);
+ connect(series,
+ &QSurface3DSeries::wireframeColorChanged,
+ this,
+ &QQuickGraphsSurface::handleWireframeColorChanged);
+}
+
+void QQuickGraphsSurface::updateSingleHighlightColor()
+{
+ m_instancing->setColor(m_surfaceController->activeTheme()->singleHighlightColor());
+ if (sliceView())
+ m_sliceInstancing->setColor(m_surfaceController->activeTheme()->singleHighlightColor());
+}
+
+void QQuickGraphsSurface::handleThemeTypeChange()
+{
+ for (auto model : m_model)
+ updateMaterial(model);
+}
+
+QT_END_NAMESPACE
diff --git a/src/graphs/qml/qquickgraphssurface_p.h b/src/graphs/qml/qquickgraphssurface_p.h
new file mode 100644
index 0000000..d26d850
--- /dev/null
+++ b/src/graphs/qml/qquickgraphssurface_p.h
@@ -0,0 +1,142 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QtGraphs API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#ifndef QQUICKGRAPHSSURFACE_P_H
+#define QQUICKGRAPHSSURFACE_P_H
+
+#include "qquickgraphsitem_p.h"
+#include "qsurface3dseries.h"
+
+#include <private/graphsglobal_p.h>
+#include <private/surface3dcontroller_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QValue3DAxis;
+class QSurface3DSeries;
+class Surface3DController;
+class SurfaceSelectionInstancing;
+class Q3DSurface;
+
+class QQuickGraphsSurface : public QQuickGraphsItem
+{
+ Q_OBJECT
+ Q_PROPERTY(QValue3DAxis *axisX READ axisX WRITE setAxisX NOTIFY axisXChanged)
+ Q_PROPERTY(QValue3DAxis *axisY READ axisY WRITE setAxisY NOTIFY axisYChanged)
+ Q_PROPERTY(QValue3DAxis *axisZ READ axisZ WRITE setAxisZ NOTIFY axisZChanged)
+ Q_PROPERTY(QSurface3DSeries *selectedSeries READ selectedSeries NOTIFY selectedSeriesChanged)
+ Q_PROPERTY(QQmlListProperty<QSurface3DSeries> seriesList READ seriesList CONSTANT)
+ Q_PROPERTY(bool flipHorizontalGrid READ flipHorizontalGrid WRITE setFlipHorizontalGrid NOTIFY flipHorizontalGridChanged)
+ Q_CLASSINFO("DefaultProperty", "seriesList")
+
+ QML_NAMED_ELEMENT(Surface3D)
+ QML_ADDED_IN_VERSION(6, 6)
+
+public:
+ explicit QQuickGraphsSurface(QQuickItem *parent = 0);
+ ~QQuickGraphsSurface();
+
+ QValue3DAxis *axisX() const;
+ void setAxisX(QValue3DAxis *axis);
+ QValue3DAxis *axisY() const;
+ void setAxisY(QValue3DAxis *axis);
+ QValue3DAxis *axisZ() const;
+ void setAxisZ(QValue3DAxis *axis);
+
+ QQmlListProperty<QSurface3DSeries> seriesList();
+ static void appendSeriesFunc(QQmlListProperty<QSurface3DSeries> *list, QSurface3DSeries *series);
+ static qsizetype countSeriesFunc(QQmlListProperty<QSurface3DSeries> *list);
+ static QSurface3DSeries *atSeriesFunc(QQmlListProperty<QSurface3DSeries> *list, qsizetype index);
+ static void clearSeriesFunc(QQmlListProperty<QSurface3DSeries> *list);
+ Q_INVOKABLE void addSeries(QSurface3DSeries *series);
+ Q_INVOKABLE void removeSeries(QSurface3DSeries *series);
+
+ QSurface3DSeries *selectedSeries() const;
+ void setFlipHorizontalGrid(bool flip);
+ bool flipHorizontalGrid() const;
+
+protected:
+ void componentComplete() override;
+ void synchData() override;
+ void updateGraph() override;
+ void updateSliceGraph() override;
+ bool handleMousePressedEvent(QMouseEvent *event) override;
+ void updateSingleHighlightColor() override;
+ void handleThemeTypeChange() override;
+
+public Q_SLOTS:
+ void handleAxisXChanged(QAbstract3DAxis *axis) override;
+ void handleAxisYChanged(QAbstract3DAxis *axis) override;
+ void handleAxisZChanged(QAbstract3DAxis *axis) override;
+
+ void handleFlatShadingEnabledChanged();
+ void handleWireframeColorChanged();
+
+Q_SIGNALS:
+ void axisXChanged(QValue3DAxis *axis);
+ void axisYChanged(QValue3DAxis *axis);
+ void axisZChanged(QValue3DAxis *axis);
+ void selectedSeriesChanged(QSurface3DSeries *series);
+ void flipHorizontalGridChanged(bool flip);
+
+private:
+ struct SurfaceVertex {
+ QVector3D position;
+ QVector3D normal;
+ QVector2D uv;
+ QPoint coord;
+ };
+
+ struct SurfaceModel {
+ QQuick3DModel *model;
+ QQuick3DModel *gridModel;
+ QQuick3DModel *sliceModel;
+ QQuick3DModel *sliceGridModel;
+ QVector<SurfaceVertex> vertices;
+ QVector<SurfaceVertex> coarceVertices;
+ QVector<quint32> indices;
+ QVector<quint32> gridIndices;
+ QVector<float> height;
+ QSurface3DSeries *series;
+ int columnCount;
+ int rowCount;
+ SurfaceVertex selectedVertex;
+ bool picked = false;
+ };
+
+ QVector3D getNormalizedVertex(SurfaceModel *model, const QSurfaceDataItem &data, bool polar, bool flipXZ);
+ void createSmoothNormalBodyLine(SurfaceModel *model, int &totalIndex, int column);
+ void createSmoothNormalUpperLine(SurfaceModel *model, int &totalIndex);
+ void createSmoothIndices(SurfaceModel *model, int x, int y, int endX, int endY);
+ void createCoarseVertices(SurfaceModel *model, int x, int y, int endX, int endY);
+ void createGridlineIndices(SurfaceModel *model, int x, int y, int endX, int endY);
+ void handleChangedSeries();
+ void updateModel(SurfaceModel *model);
+ void updateMaterial(SurfaceModel *model);
+ void updateSelectedPoint();
+ void addModel(QSurface3DSeries *series);
+
+ QVector<SurfaceModel *> m_model;
+ Surface3DController *m_surfaceController;
+ QQuick3DModel *m_selectionPointer = nullptr;
+ SurfaceSelectionInstancing *m_instancing = nullptr;
+ QQuick3DModel *m_sliceSelectionPointer = nullptr;
+ SurfaceSelectionInstancing *m_sliceInstancing = nullptr;
+
+
+ friend class Q3DSurface;
+};
+
+QT_END_NAMESPACE
+
+#endif // QQUICKGRAPHSSURFACE_P_H
diff --git a/src/graphs/qml/quickgraphstexturedata.cpp b/src/graphs/qml/quickgraphstexturedata.cpp
new file mode 100644
index 0000000..08981a1
--- /dev/null
+++ b/src/graphs/qml/quickgraphstexturedata.cpp
@@ -0,0 +1,56 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "quickgraphstexturedata_p.h"
+
+QuickGraphsTextureData::QuickGraphsTextureData()
+{
+}
+
+QuickGraphsTextureData::~QuickGraphsTextureData()
+{
+}
+
+void QuickGraphsTextureData::createGradient(const QLinearGradient &gradient)
+{
+ setSize(QSize(m_width, m_height));
+ setFormat(QQuick3DTextureData::RGBA8);
+ setHasTransparency(false);
+
+ QByteArray imageData;
+
+ QByteArray gradientScanline;
+ gradientScanline.resize(m_width * 4); // RGBA8
+ auto stops = gradient.stops();
+
+ int x = 0;
+ for (int i = 1; i < stops.size(); i++) {
+ QColor startColor = stops.at(i - 1).second;
+ QColor endColor = stops.at(i).second;
+ int w = 0;
+ w = (stops.at(i).first - stops.at(i - 1).first) * m_width;
+ for (int t = 0; t <= w; t++) {
+ QColor color = linearInterpolate(startColor, endColor, t / float(w));
+ int offset = x * 4;
+ gradientScanline.data()[offset + 0] = char(color.red());
+ gradientScanline.data()[offset + 1] = char(color.green());
+ gradientScanline.data()[offset + 2] = char(color.blue());
+ gradientScanline.data()[offset + 3] = char(255);
+ x++;
+ }
+ }
+ for (int y = 0; y < m_height; y++)
+ imageData += gradientScanline;
+ setTextureData(imageData);
+}
+
+QColor QuickGraphsTextureData::linearInterpolate(QColor startColor, QColor endColor, float value)
+{
+ QColor output;
+
+ output.setRedF(startColor.redF() + (value * (endColor.redF() - startColor.redF())));
+ output.setGreenF(startColor.greenF() + (value * (endColor.greenF() - startColor.greenF())));
+ output.setBlueF(startColor.blueF() + (value * (endColor.blueF() - startColor.blueF())));
+
+ return output;
+}
diff --git a/src/graphs/qml/quickgraphstexturedata_p.h b/src/graphs/qml/quickgraphstexturedata_p.h
new file mode 100644
index 0000000..aa50fec
--- /dev/null
+++ b/src/graphs/qml/quickgraphstexturedata_p.h
@@ -0,0 +1,39 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QtGraphs API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#ifndef QUICKGRAPHSTEXTUREDATA_P_H
+#define QUICKGRAPHSTEXTUREDATA_P_H
+#include <QtQuick3D/qquick3dtexturedata.h>
+#include <QList>
+#include <QLinearGradient>
+
+class QuickGraphsTextureData : public QQuick3DTextureData
+{
+ Q_OBJECT
+
+public:
+ QuickGraphsTextureData();
+ ~QuickGraphsTextureData();
+
+ void createGradient(const QLinearGradient &gradient);
+
+private:
+ int m_width = 256;
+ int m_height = 256;
+
+ QColor linearInterpolate(QColor startColor, QColor endColor, float value);
+
+};
+
+#endif // QUICKGRAPHSTEXTUREDATA_P_H
diff --git a/src/graphs/qml/resources/AxisLabel.qml b/src/graphs/qml/resources/AxisLabel.qml
new file mode 100644
index 0000000..36d50c3
--- /dev/null
+++ b/src/graphs/qml/resources/AxisLabel.qml
@@ -0,0 +1,42 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+import QtQuick3D
+
+Component {
+ Node {
+ property string labelText: "Bar"
+ property color backgroundColor: "gray"
+ property bool backgroundEnabled: false
+ property color labelTextColor: "red"
+ property bool borderEnabled : false
+ property font labelFont
+ property real labelWidth: -1
+ property real labelHeight: -1
+
+ Item {
+ anchors.centerIn: parent
+ width: labelWidth
+ height: labelHeight
+
+ Rectangle {
+ id: labelBackground
+ anchors.fill: parent
+ color: backgroundColor
+ visible: backgroundEnabled
+ border.color: labelTextColor
+ border.width: borderEnabled ? 1 : 0
+ radius: 4
+ }
+
+ Text {
+ id: text0
+ anchors.centerIn: parent
+ color: labelTextColor
+ text: labelText
+ font: labelFont
+ }
+ }
+ }
+}
diff --git a/src/graphs/qml/resources/DatapointCube.qml b/src/graphs/qml/resources/DatapointCube.qml
new file mode 100644
index 0000000..070fc9e
--- /dev/null
+++ b/src/graphs/qml/resources/DatapointCube.qml
@@ -0,0 +1,15 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+import QtQuick3D
+
+Component {
+ Model {
+ property color seriesColor: "white"
+ property int seriesItem: index
+ source: "#Cube"
+ pickable: true
+ scale: Qt.vector3d(0.001, 0.001, 0.001)
+ }
+}
diff --git a/src/graphs/qml/resources/DatapointSphere.qml b/src/graphs/qml/resources/DatapointSphere.qml
new file mode 100644
index 0000000..92dcbfe
--- /dev/null
+++ b/src/graphs/qml/resources/DatapointSphere.qml
@@ -0,0 +1,15 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+import QtQuick3D
+
+Component {
+ Model {
+ property color seriesColor: "white"
+ property int seriesItem: index
+ source: "#Sphere"
+ pickable: true
+ scale: Qt.vector3d(0.001, 0.001, 0.001)
+ }
+}
diff --git a/src/graphs/qml/resources/GridLine.qml b/src/graphs/qml/resources/GridLine.qml
new file mode 100644
index 0000000..3fc9cba
--- /dev/null
+++ b/src/graphs/qml/resources/GridLine.qml
@@ -0,0 +1,18 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+import QtQuick3D
+
+Component {
+ Model {
+ property color lineColor: "green"
+ source: "#Rectangle"
+ materials: [ PrincipledMaterial {
+ baseColor: lineColor
+ lighting: PrincipledMaterial.NoLighting
+ }
+ ]
+ scale: Qt.vector3d(0.0001, 0.0001, 0.0001)
+ }
+}
diff --git a/src/graphs/qml/resources/ItemLabel.qml b/src/graphs/qml/resources/ItemLabel.qml
new file mode 100644
index 0000000..3c9de62
--- /dev/null
+++ b/src/graphs/qml/resources/ItemLabel.qml
@@ -0,0 +1,42 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+import QtQuick3D
+
+Node {
+ property string labelText: "Bar"
+ property color backgroundColor: "gray"
+ property bool backgroundEnabled: true
+ property color labelTextColor: "red"
+ property bool borderEnabled : false
+ property font labelFont
+ property real labelWidth: -1
+
+ Item {
+ anchors.centerIn: parent
+ anchors.verticalCenterOffset: -25
+ width: Math.max(labelWidth / 2, text0.implicitWidth)
+ height: text0.implicitHeight
+ enabled: false
+
+ Rectangle {
+ id: labelBackground
+ anchors.fill: parent
+ color: backgroundColor
+ visible: backgroundEnabled
+ border.color: labelTextColor
+ border.width: borderEnabled ? 1 : 0
+ radius: 3
+ }
+
+ Text {
+ id: text0
+ anchors.centerIn: parent
+ color: labelTextColor
+ text: labelText
+ font: labelFont
+ padding: 4
+ }
+ }
+}
diff --git a/src/graphs/qml/resources/RangeGradientMaterial.qml b/src/graphs/qml/resources/RangeGradientMaterial.qml
new file mode 100644
index 0000000..3bc0fff
--- /dev/null
+++ b/src/graphs/qml/resources/RangeGradientMaterial.qml
@@ -0,0 +1,14 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick3D
+import QtQuick
+
+CustomMaterial {
+ property color uColor: "purple"
+ property real gradientPos: 0.0
+ property TextureInput custex: TextureInput {}
+
+ shadingMode: CustomMaterial.Unshaded
+ fragmentShader: "qrc:/shaders/fragmentrangegradient"
+}
diff --git a/src/graphs/qml/resources/RangeGradientMaterialInstancing.qml b/src/graphs/qml/resources/RangeGradientMaterialInstancing.qml
new file mode 100644
index 0000000..c74f84c
--- /dev/null
+++ b/src/graphs/qml/resources/RangeGradientMaterialInstancing.qml
@@ -0,0 +1,13 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick3D
+import QtQuick
+
+CustomMaterial {
+ property TextureInput custex: TextureInput {}
+
+ shadingMode: CustomMaterial.Unshaded
+ fragmentShader: "qrc:/shaders/fragmentrangegradientInstancing"
+ vertexShader: "qrc:/shaders/vertexrangegradientInstancing"
+}
diff --git a/src/graphs/qml/resources/SurfaceShadowNoTex.qml b/src/graphs/qml/resources/SurfaceShadowNoTex.qml
new file mode 100644
index 0000000..1ddb0a2
--- /dev/null
+++ b/src/graphs/qml/resources/SurfaceShadowNoTex.qml
@@ -0,0 +1,12 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick3D
+import QtQuick
+
+CustomMaterial {
+ property vector3d lightPosition_wrld
+
+ shadingMode: CustomMaterial.Unshaded
+ fragmentShader: "qrc:/shaders/fragmentSurfaceShadowNoTex"
+}
diff --git a/src/graphs/theme/q3dtheme.cpp b/src/graphs/theme/q3dtheme.cpp
new file mode 100644
index 0000000..403c64c
--- /dev/null
+++ b/src/graphs/theme/q3dtheme.cpp
@@ -0,0 +1,1178 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "q3dtheme_p.h"
+#include "thememanager_p.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ * \class Q3DTheme
+ * \inmodule QtGraphs
+ * \brief Q3DTheme class provides a visual style for graphs.
+ *
+ * Specifies visual properties that affect the whole graph. There are several
+ * built-in themes that can be used as is or modified freely.
+ *
+ * The following properties can be overridden by using QAbstract3DSeries
+ * properties to set them explicitly in the series: baseColors, baseGradients,
+ * and colorStyle.
+ *
+ * Themes can be created from scratch using the ThemeUserDefined enum value.
+ * Creating a theme using the default constructor produces a new user-defined
+ * theme.
+ *
+ * \section1 Default Theme
+ *
+ * The following table lists the properties controlled by themes and the
+ * default values for ThemeUserDefined.
+ *
+ * \table
+ * \header
+ * \li Property
+ * \li Default Value
+ * \row
+ * \li ambientLightStrength
+ * \li 0.25
+ * \row
+ * \li backgroundColor
+ * \li Qt::black
+ * \row
+ * \li backgroundEnabled
+ * \li \c true
+ * \row
+ * \li baseColors
+ * \li Qt::black
+ * \row
+ * \li baseGradients
+ * \li QLinearGradient. Essentially fully black.
+ * \row
+ * \li colorStyle
+ * \li ColorStyleUniform
+ * \row
+ * \li \l font
+ * \li QFont
+ * \row
+ * \li gridEnabled
+ * \li \c true
+ * \row
+ * \li gridLineColor
+ * \li Qt::white
+ * \row
+ * \li highlightLightStrength
+ * \li 7.5
+ * \row
+ * \li labelBackgroundColor
+ * \li Qt::gray
+ * \row
+ * \li labelBackgroundEnabled
+ * \li \c true
+ * \row
+ * \li labelBorderEnabled
+ * \li \c true
+ * \row
+ * \li labelTextColor
+ * \li Qt::white
+ * \row
+ * \li lightColor
+ * \li Qt::white
+ * \row
+ * \li lightStrength
+ * \li 5.0
+ * \row
+ * \li multiHighlightColor
+ * \li Qt::blue
+ * \row
+ * \li multiHighlightGradient
+ * \li QLinearGradient. Essentially fully black.
+ * \row
+ * \li singleHighlightColor
+ * \li Qt::red
+ * \row
+ * \li singleHighlightGradient
+ * \li QLinearGradient. Essentially fully black.
+ * \row
+ * \li windowColor
+ * \li Qt::black
+ * \endtable
+ *
+ * \section1 Usage Examples
+ *
+ * Creating a built-in theme without any modifications:
+ *
+ * \snippet doc_src_q3dtheme.cpp 0
+ *
+ * Creating a built-in theme and modifying some properties:
+ *
+ * \snippet doc_src_q3dtheme.cpp 1
+ *
+ * Creating a user-defined theme:
+ *
+ * \snippet doc_src_q3dtheme.cpp 2
+ *
+ * Creating a built-in theme and modifying some properties after it has been set:
+ *
+ * \snippet doc_src_q3dtheme.cpp 3
+ *
+ */
+
+/*!
+ * \enum Q3DTheme::ColorStyle
+ *
+ * Color styles.
+ *
+ * \value ColorStyleUniform
+ * Objects are rendered in a single color. The color used is specified in baseColors,
+ * singleHighlightColor and multiHighlightColor properties.
+ * \value ColorStyleObjectGradient
+ * Objects are colored using a full gradient for each object regardless of object height. The
+ * gradient used is specified in baseGradients, singleHighlightGradient and
+ * multiHighlightGradient properties.
+ * \value ColorStyleRangeGradient
+ * Objects are colored using a portion of the full gradient determined by the object's
+ * height and its position on the Y-axis. The gradient used is specified in baseGradients,
+ * singleHighlightGradient and multiHighlightGradient properties.
+ */
+
+/*!
+ * \enum Q3DTheme::Theme
+ *
+ * Built-in themes.
+ *
+ * \value ThemeQt
+ * A light theme with green as the base color.
+ * \value ThemePrimaryColors
+ * A light theme with yellow as the base color.
+ * \value ThemeDigia
+ * A light theme with gray as the base color.
+ * \value ThemeStoneMoss
+ * A medium dark theme with yellow as the base color.
+ * \value ThemeArmyBlue
+ * A medium light theme with blue as the base color.
+ * \value ThemeRetro
+ * A medium light theme with brown as the base color.
+ * \value ThemeEbony
+ * A dark theme with white as the base color.
+ * \value ThemeIsabelle
+ * A dark theme with yellow as the base color.
+ * \value ThemeUserDefined
+ * A user-defined theme. For more information, see \l {Default Theme}.
+ */
+
+/*!
+ * \qmltype Theme3D
+ * \inqmlmodule QtGraphs
+ * \ingroup graphs_qml
+ * \instantiates Q3DTheme
+ * \brief A visual style for graphs.
+ *
+ * This type is used to specify visual properties that affect the whole graph. There are several
+ * built-in themes that can be used as is or modified freely.
+ *
+ * The following properties can be overridden by using Abstract3DSeries
+ * properties to set them explicitly in the series:
+ * baseColors, baseGradients, and colorStyle.
+ *
+ * Themes can be created from scratch by using the
+ * \l{Q3DTheme::ThemeUserDefined}{Theme3D.ThemeUserDefined} enum value.
+ *
+ * \section1 Default Theme
+ *
+ * The following table lists the properties controlled by themes and the
+ * default values for \l{Q3DTheme::ThemeUserDefined}
+ * {Theme3D.ThemeUserDefined}.
+ *
+ * \table
+ * \header
+ * \li Property
+ * \li Default Value
+ * \row
+ * \li ambientLightStrength
+ * \li 0.25
+ * \row
+ * \li backgroundColor
+ * \li "black". For more information, see \l [QtQuick] color.
+ * \row
+ * \li backgroundEnabled
+ * \li \c true
+ * \row
+ * \li baseColors
+ * \li "black"
+ * \row
+ * \li baseGradients
+ * \li QLinearGradient. Essentially fully black.
+ * \row
+ * \li colorStyle
+ * \li ColorStyleUniform
+ * \row
+ * \li \l font
+ * \li \l [QtQuick] font
+ * \row
+ * \li gridEnabled
+ * \li \c true
+ * \row
+ * \li gridLineColor
+ * \li "white"
+ * \row
+ * \li highlightLightStrength
+ * \li 7.5
+ * \row
+ * \li labelBackgroundColor
+ * \li "gray"
+ * \row
+ * \li labelBackgroundEnabled
+ * \li \c true
+ * \row
+ * \li labelBorderEnabled
+ * \li \c true
+ * \row
+ * \li labelTextColor
+ * \li "white"
+ * \row
+ * \li lightColor
+ * \li "white"
+ * \row
+ * \li lightStrength
+ * \li 5.0
+ * \row
+ * \li multiHighlightColor
+ * \li "blue"
+ * \row
+ * \li multiHighlightGradient
+ * \li QLinearGradient. Essentially fully black.
+ * \row
+ * \li singleHighlightColor
+ * \li "red"
+ * \row
+ * \li singleHighlightGradient
+ * \li QLinearGradient. Essentially fully black.
+ * \row
+ * \li windowColor
+ * \li "black"
+ * \endtable
+ *
+ * \section1 Usage examples
+ *
+ * Using a built-in theme without any modifications:
+ *
+ * \snippet doc_src_q3dtheme.cpp 4
+ *
+ * Using a built-in theme and modifying some properties:
+ *
+ * \snippet doc_src_q3dtheme.cpp 5
+ *
+ * Using a user-defined theme:
+ *
+ * \snippet doc_src_q3dtheme.cpp 6
+ *
+ * For Theme3D enums, see \l Q3DTheme::ColorStyle and \l{Q3DTheme::Theme}.
+ */
+
+/*!
+ * \qmlproperty list<ThemeColor> Theme3D::baseColors
+ *
+ * The list of base colors to be used for all the objects in the graph, series by series. If there
+ * are more series than colors, color list wraps and starts again with the first color in the list.
+ * Has no immediate effect if colorStyle is not \c Theme3D.ColorStyleUniform.
+ *
+ * This can be overridden by setting \l{Abstract3DSeries::baseColor}
+ * {Abstract3DSeries.baseColor} explicitly in the series.
+ */
+
+/*!
+ * \qmlproperty color Theme3D::backgroundColor
+ *
+ * The color of the graph background.
+ */
+
+/*!
+ * \qmlproperty color Theme3D::windowColor
+ *
+ * The color of the application window the graph is drawn into.
+ */
+
+/*!
+ * \qmlproperty color Theme3D::labelTextColor
+ *
+ * The color of the font used for labels.
+ */
+
+/*!
+ * \qmlproperty color Theme3D::labelBackgroundColor
+ *
+ * The color of the label backgrounds. Has no effect if labelBackgroundEnabled is \c false.
+ */
+
+/*!
+ * \qmlproperty color Theme3D::gridLineColor
+ *
+ * The color of the grid lines.
+ */
+
+/*!
+ * \qmlproperty color Theme3D::singleHighlightColor
+ *
+ * The highlight color for a selected object. Used if
+ * \l{AbstractGraph3D::selectionMode}{selectionMode}
+ * has the \c AbstractGraph3D.SelectionItem flag set.
+ */
+
+/*!
+ * \qmlproperty color Theme3D::multiHighlightColor
+ *
+ * The highlight color for selected objects. Used if
+ * \l{AbstractGraph3D::selectionMode}{selectionMode}
+ * has the \c AbstractGraph3D.SelectionRow or \c AbstractGraph3D.SelectionColumn
+ * flag set.
+ */
+
+/*!
+ * \qmlproperty color Theme3D::lightColor
+ *
+ * The color of the ambient and specular light defined in Scene3D.
+ */
+
+/*!
+ * \qmlproperty list<ColorGradient> Theme3D::baseGradients
+ *
+ * The list of base gradients to be used for all the objects in the graph,
+ * series by series. If there are more series than gradients, the gradient list
+ * wraps and starts again with the first gradient in the list.
+ *
+ * Has no immediate effect if colorStyle is \l{Q3DTheme::ColorStyleUniform}
+ * {Theme3D.ColorStyleUniform}.
+ *
+ * This value can be overridden by setting \l{Abstract3DSeries::baseGradient}
+ *{Abstract3DSeries.baseGradient} explicitly in the series.
+ */
+
+/*!
+ * \qmlproperty ColorGradient Theme3D::singleHighlightGradient
+ *
+ * The highlight gradient for a selected object. Used if
+ * \l{AbstractGraph3D::selectionMode}{selectionMode}
+ * has the \c AbstractGraph3D.SelectionItem flag set.
+ */
+
+/*!
+ * \qmlproperty ColorGradient Theme3D::multiHighlightGradient
+ *
+ * The highlight gradient for selected objects. Used if
+ * \l{AbstractGraph3D::selectionMode}{selectionMode}
+ * has the \c AbstractGraph3D.SelectionRow or \c AbstractGraph3D.SelectionColumn
+ * flag set.
+ */
+
+/*!
+ * \qmlproperty real Theme3D::lightStrength
+ *
+ * The specular light strength for the whole graph. The value must be between
+ * \c 0.0 and \c 10.0.
+ *
+ * This value affects the light specified in Scene3D.
+ */
+
+/*!
+ * \qmlproperty real Theme3D::ambientLightStrength
+ *
+ * The ambient light strength for the whole graph. This value determines how
+ * evenly and brightly the colors are shown throughout the graph regardless of
+ * the light position. The value must be between \c 0.0 and \c 1.0.
+ */
+
+/*!
+ * \qmlproperty real Theme3D::highlightLightStrength
+ *
+ * The specular light strength for selected objects. The value must be
+ * between \c 0.0 and \c 10.0.
+ */
+
+/*!
+ * \qmlproperty bool Theme3D::labelBorderEnabled
+ *
+ * Defines whether label borders are drawn for labels that have a background.
+ * Has no effect if labelBackgroundEnabled is \c false.
+ */
+
+/*!
+ * \qmlproperty font Theme3D::font
+ *
+ * Sets the font to be used for labels.
+ */
+
+/*!
+ * \qmlproperty bool Theme3D::backgroundEnabled
+ *
+ * Defines whether the background is drawn by using the value of
+ * backgroundColor.
+ */
+
+/*!
+ * \qmlproperty bool Theme3D::gridEnabled
+ *
+ * Defines whether the grid lines are drawn. This value affects all grid lines.
+ */
+
+/*!
+ * \qmlproperty bool Theme3D::labelBackgroundEnabled
+ *
+ * Defines whether the label is drawn with a background that uses
+ * labelBackgroundColor (including alpha), or with a fully transparent
+ * background. Labels with a background are drawn to equal sizes per axis based
+ * on the longest label, and the text is centered in them. Labels without
+ * a background are drawn as is and are left or right aligned based on their
+ * position in the graph.
+ */
+
+/*!
+ * \qmlproperty Theme3D.ColorStyle Theme3D::colorStyle
+ *
+ * The style of the graph colors. One of Q3DTheme::ColorStyle enum values.
+ *
+ * This value can be overridden by setting \l{Abstract3DSeries::colorStyle}
+ * {Abstract3DSeries.colorStyle} explicitly in the series.
+ *
+ * \sa Q3DTheme::ColorStyle
+ */
+
+/*!
+ * \qmlproperty Theme3D.Theme Theme3D::type
+ *
+ * The type of the theme. If no type is set, the type is
+ * \l{Q3DTheme::ThemeUserDefined}{Theme3D.ThemeUserDefined}.
+ * Changing the theme type after the item has been constructed will change all other properties
+ * of the theme to what the predefined theme specifies. Changing the theme type of the active theme
+ * of the graph will also reset all attached series to use the new theme.
+ */
+
+/*!
+ * Constructs a new theme of type ThemeUserDefined. An optional \a parent parameter
+ * can be given and is then passed to QObject constructor.
+ */
+Q3DTheme::Q3DTheme(QObject *parent)
+ : QObject(parent),
+ d_ptr(new Q3DThemePrivate(this))
+{
+}
+
+/*!
+ * Constructs a new theme with \a themeType, which can be one of the built-in themes from
+ * \l Theme. An optional \a parent parameter can be given and is then passed to QObject
+ * constructor.
+ */
+Q3DTheme::Q3DTheme(Theme themeType, QObject *parent)
+ : QObject(parent),
+ d_ptr(new Q3DThemePrivate(this))
+{
+ setType(themeType);
+}
+
+/*!
+ * \internal
+ */
+Q3DTheme::Q3DTheme(Q3DThemePrivate *d, Theme themeType,
+ QObject *parent) :
+ QObject(parent),
+ d_ptr(d)
+{
+ setType(themeType);
+}
+
+/*!
+ * Destroys the theme.
+ */
+Q3DTheme::~Q3DTheme()
+{
+}
+
+/*!
+ * \property Q3DTheme::baseColors
+ *
+ * \brief The list of base colors to be used for all the objects in the graph,
+ * series by series.
+ *
+ * If there are more series than colors, the color list wraps and starts again
+ * with the first color in the list.
+ *
+ * Has no immediate effect if colorStyle is not ColorStyleUniform.
+ *
+ * This value can be overridden by setting the \l{QAbstract3DSeries::baseColor}
+ * {baseColor} explicitly in the series.
+ */
+void Q3DTheme::setBaseColors(const QList<QColor> &colors)
+{
+ if (colors.size()) {
+ d_ptr->m_dirtyBits.baseColorDirty = true;
+ if (d_ptr->m_baseColors != colors) {
+ d_ptr->m_baseColors.clear();
+ d_ptr->m_baseColors = colors;
+ emit baseColorsChanged(colors);
+ }
+ } else {
+ d_ptr->m_baseColors.clear();
+ }
+}
+
+QList<QColor> Q3DTheme::baseColors() const
+{
+ return d_ptr->m_baseColors;
+}
+
+/*!
+ * \property Q3DTheme::backgroundColor
+ *
+ * \brief The color of the graph background.
+ */
+void Q3DTheme::setBackgroundColor(const QColor &color)
+{
+ d_ptr->m_dirtyBits.backgroundColorDirty = true;
+ if (d_ptr->m_backgroundColor != color) {
+ d_ptr->m_backgroundColor = color;
+ emit backgroundColorChanged(color);
+ emit d_ptr->needRender();
+ }
+}
+
+QColor Q3DTheme::backgroundColor() const
+{
+ return d_ptr->m_backgroundColor;
+}
+
+/*!
+ * \property Q3DTheme::windowColor
+ *
+ * \brief The color of the application window the graph is drawn into.
+ */
+void Q3DTheme::setWindowColor(const QColor &color)
+{
+ d_ptr->m_dirtyBits.windowColorDirty = true;
+ if (d_ptr->m_windowColor != color) {
+ d_ptr->m_windowColor = color;
+ emit windowColorChanged(color);
+ emit d_ptr->needRender();
+ }
+}
+
+QColor Q3DTheme::windowColor() const
+{
+ return d_ptr->m_windowColor;
+}
+
+/*!
+ * \property Q3DTheme::labelTextColor
+ *
+ * \brief The color of the font used for labels.
+ */
+void Q3DTheme::setLabelTextColor(const QColor &color)
+{
+ d_ptr->m_dirtyBits.labelTextColorDirty = true;
+ if (d_ptr->m_textColor != color) {
+ d_ptr->m_textColor = color;
+ emit labelTextColorChanged(color);
+ emit d_ptr->needRender();
+ }
+}
+
+QColor Q3DTheme::labelTextColor() const
+{
+ return d_ptr->m_textColor;
+}
+
+/*!
+ * \property Q3DTheme::labelBackgroundColor
+ *
+ * \brief The color of the label backgrounds.
+ *
+ * Has no effect if labelBackgroundEnabled is \c false.
+ */
+void Q3DTheme::setLabelBackgroundColor(const QColor &color)
+{
+ d_ptr->m_dirtyBits.labelBackgroundColorDirty = true;
+ if (d_ptr->m_textBackgroundColor != color) {
+ d_ptr->m_textBackgroundColor = color;
+ emit labelBackgroundColorChanged(color);
+ emit d_ptr->needRender();
+ }
+}
+
+QColor Q3DTheme::labelBackgroundColor() const
+{
+ return d_ptr->m_textBackgroundColor;
+}
+
+/*!
+ * \property Q3DTheme::gridLineColor
+ *
+ * \brief The color of the grid lines.
+ */
+void Q3DTheme::setGridLineColor(const QColor &color)
+{
+ d_ptr->m_dirtyBits.gridLineColorDirty = true;
+ if (d_ptr->m_gridLineColor != color) {
+ d_ptr->m_gridLineColor = color;
+ emit gridLineColorChanged(color);
+ emit d_ptr->needRender();
+ }
+}
+
+QColor Q3DTheme::gridLineColor() const
+{
+ return d_ptr->m_gridLineColor;
+}
+
+/*!
+ * \property Q3DTheme::singleHighlightColor
+ *
+ * \brief The highlight color for a selected object.
+ *
+ * Used if \l{QAbstract3DGraph::selectionMode}{selectionMode} has the
+ * \c QAbstract3DGraph::SelectionItem flag set.
+ */
+void Q3DTheme::setSingleHighlightColor(const QColor &color)
+{
+ d_ptr->m_dirtyBits.singleHighlightColorDirty = true;
+ if (d_ptr->m_singleHighlightColor != color) {
+ d_ptr->m_singleHighlightColor = color;
+ emit singleHighlightColorChanged(color);
+ }
+}
+
+QColor Q3DTheme::singleHighlightColor() const
+{
+ return d_ptr->m_singleHighlightColor;
+}
+
+/*!
+ * \property Q3DTheme::multiHighlightColor
+ *
+ * \brief The highlight color for selected objects.
+ *
+ * Used if \l{QAbstract3DGraph::selectionMode}{selectionMode} has the
+ * \c QAbstract3DGraph::SelectionRow or \c QAbstract3DGraph::SelectionColumn
+ * flag set.
+ */
+void Q3DTheme::setMultiHighlightColor(const QColor &color)
+{
+ d_ptr->m_dirtyBits.multiHighlightColorDirty = true;
+ if (d_ptr->m_multiHighlightColor != color) {
+ d_ptr->m_multiHighlightColor = color;
+ emit multiHighlightColorChanged(color);
+ }
+}
+
+QColor Q3DTheme::multiHighlightColor() const
+{
+ return d_ptr->m_multiHighlightColor;
+}
+
+/*!
+ * \property Q3DTheme::lightColor
+ *
+ * \brief The color for the ambient and specular light.
+ *
+ * This value affects the light specified in Q3DScene.
+ */
+void Q3DTheme::setLightColor(const QColor &color)
+{
+ d_ptr->m_dirtyBits.lightColorDirty = true;
+ if (d_ptr->m_lightColor != color) {
+ d_ptr->m_lightColor = color;
+ emit lightColorChanged(color);
+ emit d_ptr->needRender();
+ }
+}
+
+QColor Q3DTheme::lightColor() const
+{
+ return d_ptr->m_lightColor;
+}
+
+/*!
+ * \property Q3DTheme::baseGradients
+ *
+ * \brief The list of base gradients to be used for all the objects in the
+ * graph, series by series.
+ *
+ * If there are more series than gradients, the gradient list wraps and starts
+ * again with the first gradient in the list
+ *
+ * Has no immediate effect if colorStyle is ColorStyleUniform.
+ *
+ * This value can be overridden by setting the
+ * \l{QAbstract3DSeries::baseGradient}{baseGradient} explicitly in the series.
+ */
+void Q3DTheme::setBaseGradients(const QList<QLinearGradient> &gradients)
+{
+ if (gradients.size()) {
+ d_ptr->m_dirtyBits.baseGradientDirty = true;
+ if (d_ptr->m_baseGradients != gradients) {
+ d_ptr->m_baseGradients.clear();
+ d_ptr->m_baseGradients = gradients;
+ emit baseGradientsChanged(gradients);
+ }
+ } else {
+ d_ptr->m_baseGradients.clear();
+ }
+}
+
+QList<QLinearGradient> Q3DTheme::baseGradients() const
+{
+ return d_ptr->m_baseGradients;
+}
+
+/*!
+ * \property Q3DTheme::singleHighlightGradient
+ *
+ * \brief The highlight gradient for a selected object.
+ *
+ * Used if \l{QAbstract3DGraph::selectionMode}{selectionMode}
+ * has the \c QAbstract3DGraph::SelectionItem flag set.
+ */
+void Q3DTheme::setSingleHighlightGradient(const QLinearGradient &gradient)
+{
+ d_ptr->m_dirtyBits.singleHighlightGradientDirty = true;
+ if (d_ptr->m_singleHighlightGradient != gradient) {
+ d_ptr->m_singleHighlightGradient = gradient;
+ emit singleHighlightGradientChanged(gradient);
+ }
+}
+
+QLinearGradient Q3DTheme::singleHighlightGradient() const
+{
+ return d_ptr->m_singleHighlightGradient;
+}
+
+/*!
+ * \property Q3DTheme::multiHighlightGradient
+ *
+ * \brief The highlight gradient for selected objects.
+ *
+ * Used if \l{QAbstract3DGraph::selectionMode}{selectionMode}
+ * has the \c QAbstract3DGraph::SelectionRow or
+ * \c QAbstract3DGraph::SelectionColumn flag set.
+ */
+void Q3DTheme::setMultiHighlightGradient(const QLinearGradient &gradient)
+{
+ d_ptr->m_dirtyBits.multiHighlightGradientDirty = true;
+ if (d_ptr->m_multiHighlightGradient != gradient) {
+ d_ptr->m_multiHighlightGradient = gradient;
+ emit multiHighlightGradientChanged(gradient);
+ }
+}
+
+QLinearGradient Q3DTheme::multiHighlightGradient() const
+{
+ return d_ptr->m_multiHighlightGradient;
+}
+
+/*!
+ * \property Q3DTheme::lightStrength
+ *
+ * \brief The specular light strength for the whole graph.
+ *
+ * The value must be between \c 0.0f and \c 10.0f.
+ *
+ * This value affects the light specified in Q3DScene.
+ */
+void Q3DTheme::setLightStrength(float strength)
+{
+ d_ptr->m_dirtyBits.lightStrengthDirty = true;
+ if (strength < 0.0f || strength > 10.0f) {
+ qWarning("Invalid value. Valid range for lightStrength is between 0.0f and 10.0f");
+ } else if (d_ptr->m_lightStrength != strength) {
+ d_ptr->m_lightStrength = strength;
+ emit lightStrengthChanged(strength);
+ emit d_ptr->needRender();
+ }
+}
+
+float Q3DTheme::lightStrength() const
+{
+ return d_ptr->m_lightStrength;
+}
+
+/*!
+ * \property Q3DTheme::ambientLightStrength
+ *
+ * \brief The ambient light strength for the whole graph.
+ *
+ * This value determines how evenly and brightly the colors are shown throughout
+ * the graph regardless of the light position.
+ *
+ * The value must be between \c 0.0f and \c 1.0f.
+ */
+void Q3DTheme::setAmbientLightStrength(float strength)
+{
+ d_ptr->m_dirtyBits.ambientLightStrengthDirty = true;
+ if (strength < 0.0f || strength > 1.0f) {
+ qWarning("Invalid value. Valid range for ambientLightStrength is between 0.0f and 1.0f");
+ } else if (d_ptr->m_ambientLightStrength != strength) {
+ d_ptr->m_ambientLightStrength = strength;
+ emit ambientLightStrengthChanged(strength);
+ emit d_ptr->needRender();
+ }
+}
+
+float Q3DTheme::ambientLightStrength() const
+{
+ return d_ptr->m_ambientLightStrength;
+}
+
+/*!
+ * \property Q3DTheme::highlightLightStrength
+ *
+ * \brief The specular light strength for selected objects.
+ *
+ * The value must be between \c 0.0f and \c 10.0f.
+ */
+void Q3DTheme::setHighlightLightStrength(float strength)
+{
+ d_ptr->m_dirtyBits.highlightLightStrengthDirty = true;
+ if (strength < 0.0f || strength > 10.0f) {
+ qWarning("Invalid value. Valid range for highlightLightStrength is between 0.0f and 10.0f");
+ } else if (d_ptr->m_highlightLightStrength != strength) {
+ d_ptr->m_highlightLightStrength = strength;
+ emit highlightLightStrengthChanged(strength);
+ emit d_ptr->needRender();
+ }
+}
+
+float Q3DTheme::highlightLightStrength() const
+{
+ return d_ptr->m_highlightLightStrength;
+}
+
+/*!
+ * \property Q3DTheme::labelBorderEnabled
+ *
+ * \brief Whether label borders are drawn for labels that have a background.
+ *
+ * Has no effect if labelBackgroundEnabled is \c false.
+ */
+void Q3DTheme::setLabelBorderEnabled(bool enabled)
+{
+ d_ptr->m_dirtyBits.labelBorderEnabledDirty = true;
+ if (d_ptr->m_labelBorders != enabled) {
+ d_ptr->m_labelBorders = enabled;
+ emit labelBorderEnabledChanged(enabled);
+ emit d_ptr->needRender();
+ }
+}
+
+bool Q3DTheme::isLabelBorderEnabled() const
+{
+ return d_ptr->m_labelBorders;
+}
+
+/*!
+ * \property Q3DTheme::font
+ *
+ * \brief The font to be used for labels.
+ */
+void Q3DTheme::setFont(const QFont &font)
+{
+ d_ptr->m_dirtyBits.fontDirty = true;
+ if (d_ptr->m_font != font) {
+ d_ptr->m_font = font;
+ emit fontChanged(font);
+ emit d_ptr->needRender();
+ }
+}
+
+QFont Q3DTheme::font() const
+{
+ return d_ptr->m_font;
+}
+
+/*!
+ * \property Q3DTheme::backgroundEnabled
+ *
+ * \brief Whether the background is visible.
+ *
+ * The background is drawn by using the value of backgroundColor.
+ */
+void Q3DTheme::setBackgroundEnabled(bool enabled)
+{
+ d_ptr->m_dirtyBits.backgroundEnabledDirty = true;
+ if (d_ptr->m_backgoundEnabled != enabled) {
+ d_ptr->m_backgoundEnabled = enabled;
+ emit backgroundEnabledChanged(enabled);
+ emit d_ptr->needRender();
+ }
+}
+
+bool Q3DTheme::isBackgroundEnabled() const
+{
+ return d_ptr->m_backgoundEnabled;
+}
+
+/*!
+ * \property Q3DTheme::gridEnabled
+ *
+ * \brief Whether the grid lines are drawn.
+ *
+ * This value affects all grid lines.
+ */
+void Q3DTheme::setGridEnabled(bool enabled)
+{
+ d_ptr->m_dirtyBits.gridEnabledDirty = true;
+ if (d_ptr->m_gridEnabled != enabled) {
+ d_ptr->m_gridEnabled = enabled;
+ emit gridEnabledChanged(enabled);
+ emit d_ptr->needRender();
+ }
+}
+
+bool Q3DTheme::isGridEnabled() const
+{
+ return d_ptr->m_gridEnabled;
+}
+
+/*!
+ * \property Q3DTheme::labelBackgroundEnabled
+ *
+ *\brief Whether the label is drawn with a color background or with a fully
+ * transparent background.
+ *
+ * The labelBackgroundColor value (including alpha) is used for drawing the
+ * background.
+ *
+ * Labels with a background are drawn to equal sizes per axis based
+ * on the longest label, and the text is centered in them. Labels without a
+ * background are drawn as is and are left or right aligned based on their
+ * position in the graph.
+ */
+void Q3DTheme::setLabelBackgroundEnabled(bool enabled)
+{
+ d_ptr->m_dirtyBits.labelBackgroundEnabledDirty = true;
+ if (d_ptr->m_labelBackground != enabled) {
+ d_ptr->m_labelBackground = enabled;
+ emit labelBackgroundEnabledChanged(enabled);
+ emit d_ptr->needRender();
+ }
+}
+
+bool Q3DTheme::isLabelBackgroundEnabled() const
+{
+ return d_ptr->m_labelBackground;
+}
+
+/*!
+ * \property Q3DTheme::colorStyle
+ *
+ * \brief The style of the graph colors.
+ *
+ * One of the ColorStyle enum values.
+ *
+ * This value can be overridden by setting \l{Abstract3DSeries::colorStyle}
+ * {colorStyle} explicitly in the series.
+ */
+void Q3DTheme::setColorStyle(ColorStyle style)
+{
+ d_ptr->m_dirtyBits.colorStyleDirty = true;
+ if (d_ptr->m_colorStyle != style) {
+ d_ptr->m_colorStyle = style;
+ emit colorStyleChanged(style);
+ }
+}
+
+Q3DTheme::ColorStyle Q3DTheme::colorStyle() const
+{
+ return d_ptr->m_colorStyle;
+}
+
+/*!
+ * \property Q3DTheme::type
+ *
+ * \brief The type of the theme.
+ *
+ * The type is automatically set when constructing a theme,
+ * but can also be changed later. Changing the theme type will change all other
+ * properties of the theme to what the predefined theme specifies.
+ * Changing the theme type of the active theme of the graph will also reset all
+ * attached series to use the new theme.
+ */
+void Q3DTheme::setType(Theme themeType)
+{
+ d_ptr->m_dirtyBits.themeIdDirty = true;
+ if (d_ptr->m_themeId != themeType) {
+ d_ptr->m_themeId = themeType;
+ ThemeManager::setPredefinedPropertiesToTheme(this, themeType);
+ emit typeChanged(themeType);
+ }
+}
+
+Q3DTheme::Theme Q3DTheme::type() const
+{
+ return d_ptr->m_themeId;
+}
+
+// Q3DThemePrivate
+
+Q3DThemePrivate::Q3DThemePrivate(Q3DTheme *q)
+ : QObject(0),
+ m_themeId(Q3DTheme::ThemeUserDefined),
+ m_backgroundColor(Qt::black),
+ m_windowColor(Qt::black),
+ m_textColor(Qt::white),
+ m_textBackgroundColor(Qt::gray),
+ m_gridLineColor(Qt::white),
+ m_singleHighlightColor(Qt::red),
+ m_multiHighlightColor(Qt::blue),
+ m_lightColor(Qt::white),
+ m_singleHighlightGradient(QLinearGradient(qreal(gradientTextureWidth),
+ qreal(gradientTextureHeight),
+ 0.0, 0.0)),
+ m_multiHighlightGradient(QLinearGradient(qreal(gradientTextureWidth),
+ qreal(gradientTextureHeight),
+ 0.0, 0.0)),
+ m_lightStrength(5.0f),
+ m_ambientLightStrength(0.25f),
+ m_highlightLightStrength(7.5f),
+ m_labelBorders(true),
+ m_colorStyle(Q3DTheme::ColorStyleUniform),
+ m_font(QFont()),
+ m_backgoundEnabled(true),
+ m_gridEnabled(true),
+ m_labelBackground(true),
+ m_isDefaultTheme(false),
+ m_forcePredefinedType(true),
+ q_ptr(q)
+{
+ m_baseColors.append(QColor(Qt::black));
+ m_baseGradients.append(QLinearGradient(qreal(gradientTextureWidth),
+ qreal(gradientTextureHeight),
+ 0.0, 0.0));
+}
+
+Q3DThemePrivate::~Q3DThemePrivate()
+{
+}
+
+void Q3DThemePrivate::resetDirtyBits()
+{
+ m_dirtyBits.ambientLightStrengthDirty = true;
+ m_dirtyBits.backgroundColorDirty = true;
+ m_dirtyBits.backgroundEnabledDirty = true;
+ m_dirtyBits.baseColorDirty = true;
+ m_dirtyBits.baseGradientDirty = true;
+ m_dirtyBits.colorStyleDirty = true;
+ m_dirtyBits.fontDirty = true;
+ m_dirtyBits.gridEnabledDirty = true;
+ m_dirtyBits.gridLineColorDirty = true;
+ m_dirtyBits.highlightLightStrengthDirty = true;
+ m_dirtyBits.labelBackgroundColorDirty = true;
+ m_dirtyBits.labelBackgroundEnabledDirty = true;
+ m_dirtyBits.labelBorderEnabledDirty = true;
+ m_dirtyBits.labelTextColorDirty = true;
+ m_dirtyBits.lightColorDirty = true;
+ m_dirtyBits.lightStrengthDirty = true;
+ m_dirtyBits.multiHighlightColorDirty = true;
+ m_dirtyBits.multiHighlightGradientDirty = true;
+ m_dirtyBits.singleHighlightColorDirty = true;
+ m_dirtyBits.singleHighlightGradientDirty = true;
+ m_dirtyBits.themeIdDirty = true;
+ m_dirtyBits.windowColorDirty = true;
+}
+
+bool Q3DThemePrivate::sync(Q3DThemePrivate &other)
+{
+ bool updateDrawer = false;
+ if (m_dirtyBits.ambientLightStrengthDirty) {
+ other.q_ptr->setAmbientLightStrength(m_ambientLightStrength);
+ m_dirtyBits.ambientLightStrengthDirty = false;
+ }
+ if (m_dirtyBits.backgroundColorDirty) {
+ other.q_ptr->setBackgroundColor(m_backgroundColor);
+ m_dirtyBits.backgroundColorDirty = false;
+ }
+ if (m_dirtyBits.backgroundEnabledDirty) {
+ other.q_ptr->setBackgroundEnabled(m_backgoundEnabled);
+ m_dirtyBits.backgroundEnabledDirty = false;
+ }
+ if (m_dirtyBits.baseColorDirty) {
+ other.q_ptr->setBaseColors(m_baseColors);
+ m_dirtyBits.baseColorDirty = false;
+ }
+ if (m_dirtyBits.baseGradientDirty) {
+ other.q_ptr->setBaseGradients(m_baseGradients);
+ m_dirtyBits.baseGradientDirty = false;
+ }
+ if (m_dirtyBits.colorStyleDirty) {
+ other.q_ptr->setColorStyle(m_colorStyle);
+ m_dirtyBits.colorStyleDirty = false;
+ }
+ if (m_dirtyBits.fontDirty) {
+ other.q_ptr->setFont(m_font);
+ m_dirtyBits.fontDirty = false;
+ updateDrawer = true;
+ }
+ if (m_dirtyBits.gridEnabledDirty) {
+ other.q_ptr->setGridEnabled(m_gridEnabled);
+ m_dirtyBits.gridEnabledDirty = false;
+ }
+ if (m_dirtyBits.gridLineColorDirty) {
+ other.q_ptr->setGridLineColor(m_gridLineColor);
+ m_dirtyBits.gridLineColorDirty = false;
+ }
+ if (m_dirtyBits.highlightLightStrengthDirty) {
+ other.q_ptr->setHighlightLightStrength(m_highlightLightStrength);
+ m_dirtyBits.highlightLightStrengthDirty = false;
+ }
+ if (m_dirtyBits.labelBackgroundColorDirty) {
+ other.q_ptr->setLabelBackgroundColor(m_textBackgroundColor);
+ m_dirtyBits.labelBackgroundColorDirty = false;
+ updateDrawer = true;
+ }
+ if (m_dirtyBits.labelBackgroundEnabledDirty) {
+ other.q_ptr->setLabelBackgroundEnabled(m_labelBackground);
+ m_dirtyBits.labelBackgroundEnabledDirty = false;
+ updateDrawer = true;
+ }
+ if (m_dirtyBits.labelBorderEnabledDirty) {
+ other.q_ptr->setLabelBorderEnabled(m_labelBorders);
+ m_dirtyBits.labelBorderEnabledDirty = false;
+ updateDrawer = true;
+ }
+ if (m_dirtyBits.labelTextColorDirty) {
+ other.q_ptr->setLabelTextColor(m_textColor);
+ m_dirtyBits.labelTextColorDirty = false;
+ updateDrawer = true;
+ }
+ if (m_dirtyBits.lightColorDirty) {
+ other.q_ptr->setLightColor(m_lightColor);
+ m_dirtyBits.lightColorDirty = false;
+ }
+ if (m_dirtyBits.lightStrengthDirty) {
+ other.q_ptr->setLightStrength(m_lightStrength);
+ m_dirtyBits.lightStrengthDirty = false;
+ }
+ if (m_dirtyBits.multiHighlightColorDirty) {
+ other.q_ptr->setMultiHighlightColor(m_multiHighlightColor);
+ m_dirtyBits.multiHighlightColorDirty = false;
+ }
+ if (m_dirtyBits.multiHighlightGradientDirty) {
+ other.q_ptr->setMultiHighlightGradient(m_multiHighlightGradient);
+ m_dirtyBits.multiHighlightGradientDirty = false;
+ }
+ if (m_dirtyBits.singleHighlightColorDirty) {
+ other.q_ptr->setSingleHighlightColor(m_singleHighlightColor);
+ m_dirtyBits.singleHighlightColorDirty = false;
+ }
+ if (m_dirtyBits.singleHighlightGradientDirty) {
+ other.q_ptr->setSingleHighlightGradient(m_singleHighlightGradient);
+ m_dirtyBits.singleHighlightGradientDirty = false;
+ }
+ if (m_dirtyBits.themeIdDirty) {
+ other.m_themeId = m_themeId; // Set directly to avoid a call to ThemeManager's useTheme()
+ m_dirtyBits.themeIdDirty = false;
+ }
+ if (m_dirtyBits.windowColorDirty) {
+ other.q_ptr->setWindowColor(m_windowColor);
+ m_dirtyBits.windowColorDirty = false;
+ }
+
+ return updateDrawer;
+}
+
+QT_END_NAMESPACE
diff --git a/src/graphs/theme/q3dtheme.h b/src/graphs/theme/q3dtheme.h
new file mode 100644
index 0000000..6b2a2c7
--- /dev/null
+++ b/src/graphs/theme/q3dtheme.h
@@ -0,0 +1,176 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef Q3DTHEME_H
+#define Q3DTHEME_H
+
+#include <QtGraphs/qgraphsglobal.h>
+#include <QtCore/QObject>
+#include <QtGui/QLinearGradient>
+#include <QtGui/QFont>
+#include <QtGui/QColor>
+
+QT_BEGIN_NAMESPACE
+
+class Q3DThemePrivate;
+
+class Q_GRAPHS_EXPORT Q3DTheme : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(Theme type READ type WRITE setType NOTIFY typeChanged)
+ Q_PROPERTY(QList<QColor> baseColors READ baseColors WRITE setBaseColors NOTIFY baseColorsChanged)
+ Q_PROPERTY(QColor backgroundColor READ backgroundColor WRITE setBackgroundColor NOTIFY backgroundColorChanged)
+ Q_PROPERTY(QColor windowColor READ windowColor WRITE setWindowColor NOTIFY windowColorChanged)
+ Q_PROPERTY(QColor labelTextColor READ labelTextColor WRITE setLabelTextColor NOTIFY labelTextColorChanged)
+ Q_PROPERTY(QColor labelBackgroundColor READ labelBackgroundColor WRITE setLabelBackgroundColor NOTIFY labelBackgroundColorChanged)
+ Q_PROPERTY(QColor gridLineColor READ gridLineColor WRITE setGridLineColor NOTIFY gridLineColorChanged)
+ Q_PROPERTY(QColor singleHighlightColor READ singleHighlightColor WRITE setSingleHighlightColor NOTIFY singleHighlightColorChanged)
+ Q_PROPERTY(QColor multiHighlightColor READ multiHighlightColor WRITE setMultiHighlightColor NOTIFY multiHighlightColorChanged)
+ Q_PROPERTY(QColor lightColor READ lightColor WRITE setLightColor NOTIFY lightColorChanged)
+ Q_PROPERTY(QList<QLinearGradient> baseGradients READ baseGradients WRITE setBaseGradients NOTIFY baseGradientsChanged)
+ Q_PROPERTY(QLinearGradient singleHighlightGradient READ singleHighlightGradient WRITE setSingleHighlightGradient NOTIFY singleHighlightGradientChanged)
+ Q_PROPERTY(QLinearGradient multiHighlightGradient READ multiHighlightGradient WRITE setMultiHighlightGradient NOTIFY multiHighlightGradientChanged)
+ Q_PROPERTY(float lightStrength READ lightStrength WRITE setLightStrength NOTIFY lightStrengthChanged)
+ Q_PROPERTY(float ambientLightStrength READ ambientLightStrength WRITE setAmbientLightStrength NOTIFY ambientLightStrengthChanged)
+ Q_PROPERTY(float highlightLightStrength READ highlightLightStrength WRITE setHighlightLightStrength NOTIFY highlightLightStrengthChanged)
+ Q_PROPERTY(bool labelBorderEnabled READ isLabelBorderEnabled WRITE setLabelBorderEnabled NOTIFY labelBorderEnabledChanged)
+ Q_PROPERTY(QFont font READ font WRITE setFont NOTIFY fontChanged)
+ Q_PROPERTY(bool backgroundEnabled READ isBackgroundEnabled WRITE setBackgroundEnabled NOTIFY backgroundEnabledChanged)
+ Q_PROPERTY(bool gridEnabled READ isGridEnabled WRITE setGridEnabled NOTIFY gridEnabledChanged)
+ Q_PROPERTY(bool labelBackgroundEnabled READ isLabelBackgroundEnabled WRITE setLabelBackgroundEnabled NOTIFY labelBackgroundEnabledChanged)
+ Q_PROPERTY(ColorStyle colorStyle READ colorStyle WRITE setColorStyle NOTIFY colorStyleChanged)
+
+public:
+ enum ColorStyle {
+ ColorStyleUniform = 0,
+ ColorStyleObjectGradient,
+ ColorStyleRangeGradient
+ };
+ Q_ENUM(ColorStyle)
+
+ enum Theme {
+ ThemeQt,
+ ThemePrimaryColors,
+ ThemeDigia,
+ ThemeStoneMoss,
+ ThemeArmyBlue,
+ ThemeRetro,
+ ThemeEbony,
+ ThemeIsabelle,
+ ThemeUserDefined
+ };
+ Q_ENUM(Theme)
+
+public:
+ explicit Q3DTheme(QObject *parent = nullptr);
+ explicit Q3DTheme(Theme themeType, QObject *parent = nullptr);
+ virtual ~Q3DTheme();
+
+ void setType(Theme themeType);
+ Theme type() const;
+
+ void setBaseColors(const QList<QColor> &colors);
+ QList<QColor> baseColors() const;
+
+ void setBackgroundColor(const QColor &color);
+ QColor backgroundColor() const;
+
+ void setWindowColor(const QColor &color);
+ QColor windowColor() const;
+
+ void setLabelTextColor(const QColor &color);
+ QColor labelTextColor() const;
+
+ void setLabelBackgroundColor(const QColor &color);
+ QColor labelBackgroundColor() const;
+
+ void setGridLineColor(const QColor &color);
+ QColor gridLineColor() const;
+
+ void setSingleHighlightColor(const QColor &color);
+ QColor singleHighlightColor() const;
+
+ void setMultiHighlightColor(const QColor &color);
+ QColor multiHighlightColor() const;
+
+ void setLightColor(const QColor &color);
+ QColor lightColor() const;
+
+ void setBaseGradients(const QList<QLinearGradient> &gradients);
+ QList<QLinearGradient> baseGradients() const;
+
+ void setSingleHighlightGradient(const QLinearGradient &gradient);
+ QLinearGradient singleHighlightGradient() const;
+
+ void setMultiHighlightGradient(const QLinearGradient &gradient);
+ QLinearGradient multiHighlightGradient() const;
+
+ void setLightStrength(float strength);
+ float lightStrength() const;
+
+ void setAmbientLightStrength(float strength);
+ float ambientLightStrength() const;
+
+ void setHighlightLightStrength(float strength);
+ float highlightLightStrength() const;
+
+ void setLabelBorderEnabled(bool enabled);
+ bool isLabelBorderEnabled() const;
+
+ void setFont(const QFont &font);
+ QFont font() const;
+
+ void setBackgroundEnabled(bool enabled);
+ bool isBackgroundEnabled() const;
+
+ void setGridEnabled(bool enabled);
+ bool isGridEnabled() const;
+
+ void setLabelBackgroundEnabled(bool enabled);
+ bool isLabelBackgroundEnabled() const;
+
+ void setColorStyle(ColorStyle style);
+ ColorStyle colorStyle() const;
+
+Q_SIGNALS:
+ void typeChanged(Q3DTheme::Theme themeType);
+ void baseColorsChanged(const QList<QColor> &colors);
+ void backgroundColorChanged(const QColor &color);
+ void windowColorChanged(const QColor &color);
+ void labelTextColorChanged(const QColor &color);
+ void labelBackgroundColorChanged(const QColor &color);
+ void gridLineColorChanged(const QColor &color);
+ void singleHighlightColorChanged(const QColor &color);
+ void multiHighlightColorChanged(const QColor &color);
+ void lightColorChanged(const QColor &color);
+ void baseGradientsChanged(const QList<QLinearGradient> &gradients);
+ void singleHighlightGradientChanged(const QLinearGradient &gradient);
+ void multiHighlightGradientChanged(const QLinearGradient &gradient);
+ void lightStrengthChanged(float strength);
+ void ambientLightStrengthChanged(float strength);
+ void highlightLightStrengthChanged(float strength);
+ void labelBorderEnabledChanged(bool enabled);
+ void fontChanged(const QFont &font);
+ void backgroundEnabledChanged(bool enabled);
+ void gridEnabledChanged(bool enabled);
+ void labelBackgroundEnabledChanged(bool enabled);
+ void colorStyleChanged(Q3DTheme::ColorStyle style);
+
+protected:
+ explicit Q3DTheme(Q3DThemePrivate *d, Theme themeType, QObject *parent = nullptr);
+
+ QScopedPointer<Q3DThemePrivate> d_ptr;
+
+private:
+ Q_DISABLE_COPY(Q3DTheme)
+
+ friend class ThemeManager;
+ friend class Bars3DController;
+ friend class AbstractDeclarative;
+ friend class Abstract3DController;
+ friend class QQuickGraphsItem;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/theme/q3dtheme_p.h b/src/graphs/theme/q3dtheme_p.h
new file mode 100644
index 0000000..c223b58
--- /dev/null
+++ b/src/graphs/theme/q3dtheme_p.h
@@ -0,0 +1,134 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QtGraphs API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#ifndef Q3DTHEME_P_H
+#define Q3DTHEME_P_H
+
+#include <private/graphsglobal_p.h>
+#include "q3dtheme.h"
+
+QT_BEGIN_NAMESPACE
+
+struct Q3DThemeDirtyBitField {
+ bool baseColorDirty : 1;
+ bool backgroundColorDirty : 1;
+ bool windowColorDirty : 1;
+ bool labelTextColorDirty : 1;
+ bool labelBackgroundColorDirty : 1;
+ bool gridLineColorDirty : 1;
+ bool singleHighlightColorDirty : 1;
+ bool multiHighlightColorDirty : 1;
+ bool lightColorDirty : 1;
+ bool baseGradientDirty : 1;
+ bool singleHighlightGradientDirty : 1;
+ bool multiHighlightGradientDirty : 1;
+ bool lightStrengthDirty : 1;
+ bool ambientLightStrengthDirty : 1;
+ bool highlightLightStrengthDirty : 1;
+ bool labelBorderEnabledDirty : 1;
+ bool colorStyleDirty : 1;
+ bool fontDirty : 1;
+ bool backgroundEnabledDirty : 1;
+ bool gridEnabledDirty : 1;
+ bool labelBackgroundEnabledDirty : 1;
+ bool themeIdDirty : 1;
+
+ Q3DThemeDirtyBitField()
+ : baseColorDirty(false),
+ backgroundColorDirty(false),
+ windowColorDirty(false),
+ labelTextColorDirty(false),
+ labelBackgroundColorDirty(false),
+ gridLineColorDirty(false),
+ singleHighlightColorDirty(false),
+ multiHighlightColorDirty(false),
+ lightColorDirty(false),
+ baseGradientDirty(false),
+ singleHighlightGradientDirty(false),
+ multiHighlightGradientDirty(false),
+ lightStrengthDirty(false),
+ ambientLightStrengthDirty(false),
+ highlightLightStrengthDirty(false),
+ labelBorderEnabledDirty(false),
+ colorStyleDirty(false),
+ fontDirty(false),
+ backgroundEnabledDirty(false),
+ gridEnabledDirty(false),
+ labelBackgroundEnabledDirty(false),
+ themeIdDirty(false)
+ {
+ }
+};
+
+class Q_GRAPHS_EXPORT Q3DThemePrivate : public QObject
+{
+ Q_OBJECT
+public:
+ Q3DThemePrivate(Q3DTheme *q);
+ virtual ~Q3DThemePrivate();
+
+ void resetDirtyBits();
+
+ bool sync(Q3DThemePrivate &other);
+
+ inline bool isDefaultTheme() { return m_isDefaultTheme; }
+ inline void setDefaultTheme(bool isDefault) { m_isDefaultTheme = isDefault; }
+
+ // If m_forcePredefinedType is true, it means we should forcibly update all properties
+ // of the theme to those of the predefined theme, when setting the theme type. Otherwise
+ // we only change the properties that haven't been explicitly changed since last render cycle.
+ // Defaults to true, and is only ever set to false by DeclarativeTheme3D to enable using
+ // predefined themes as base for custom themes, since the order of initial property sets cannot
+ // be easily controlled in QML.
+ inline bool isForcePredefinedType() { return m_forcePredefinedType; }
+ inline void setForcePredefinedType(bool enable) { m_forcePredefinedType = enable; }
+
+Q_SIGNALS:
+ void needRender();
+
+public:
+ Q3DTheme::Theme m_themeId;
+
+ Q3DThemeDirtyBitField m_dirtyBits;
+
+ QList<QColor> m_baseColors;
+ QColor m_backgroundColor;
+ QColor m_windowColor;
+ QColor m_textColor;
+ QColor m_textBackgroundColor;
+ QColor m_gridLineColor;
+ QColor m_singleHighlightColor;
+ QColor m_multiHighlightColor;
+ QColor m_lightColor;
+ QList<QLinearGradient> m_baseGradients;
+ QLinearGradient m_singleHighlightGradient;
+ QLinearGradient m_multiHighlightGradient;
+ float m_lightStrength;
+ float m_ambientLightStrength;
+ float m_highlightLightStrength;
+ bool m_labelBorders;
+ Q3DTheme::ColorStyle m_colorStyle;
+ QFont m_font;
+ bool m_backgoundEnabled;
+ bool m_gridEnabled;
+ bool m_labelBackground;
+ bool m_isDefaultTheme;
+ bool m_forcePredefinedType;
+
+protected:
+ Q3DTheme *q_ptr;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/theme/thememanager.cpp b/src/graphs/theme/thememanager.cpp
new file mode 100644
index 0000000..ad6d46f
--- /dev/null
+++ b/src/graphs/theme/thememanager.cpp
@@ -0,0 +1,579 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "thememanager_p.h"
+#include "q3dtheme_p.h"
+
+QT_BEGIN_NAMESPACE
+
+const float defaultBuiltInColorLevel = 0.7f; // for built-in gradient themes
+const float defaultColorLevel = 0.5f; // for built-in uniform themes
+
+ThemeManager::ThemeManager(Abstract3DController *controller)
+ : m_activeTheme(0),
+ m_controller(controller)
+{
+}
+
+ThemeManager::~ThemeManager()
+{
+}
+
+void ThemeManager::addTheme(Q3DTheme *theme)
+{
+ Q_ASSERT(theme);
+ ThemeManager *owner = qobject_cast<ThemeManager *>(theme->parent());
+ if (owner != this) {
+ Q_ASSERT_X(!owner, "addTheme", "Theme already attached to a graph.");
+ theme->setParent(this);
+ }
+ if (!m_themes.contains(theme))
+ m_themes.append(theme);
+}
+
+void ThemeManager::releaseTheme(Q3DTheme *theme)
+{
+ if (theme && m_themes.contains(theme)) {
+ // Clear the default status from released default theme
+ if (theme->d_ptr->isDefaultTheme())
+ theme->d_ptr->setDefaultTheme(false);
+
+ // If the theme is in use, replace it with a temporary one
+ if (theme == m_activeTheme)
+ setActiveTheme(0);
+
+ m_themes.removeAll(theme);
+ theme->setParent(0);
+ }
+}
+
+void ThemeManager::setActiveTheme(Q3DTheme *theme)
+{
+ // Setting null theme indicates using default theme
+ if (!theme) {
+ theme = new Q3DTheme;
+ theme->d_ptr->setDefaultTheme(true);
+ }
+
+ // If the old theme is default theme, delete it
+ Q3DTheme *oldTheme = m_activeTheme;
+ if (oldTheme) {
+ if (oldTheme->d_ptr->isDefaultTheme()) {
+ m_themes.removeAll(oldTheme);
+ delete oldTheme;
+ oldTheme = 0;
+ } else {
+ // Disconnect the old theme from use
+ disconnect(m_activeTheme->d_ptr.data(), 0, m_controller, 0);
+ disconnect(m_activeTheme, 0, m_controller, 0);
+ }
+ }
+
+ // Assume ownership
+ addTheme(theme);
+
+ m_activeTheme = theme;
+
+ // Reset all bits to dirty for sync
+ if (theme->d_ptr->isForcePredefinedType())
+ m_activeTheme->d_ptr->resetDirtyBits();
+
+ // Connect signals from new one
+ connectThemeSignals();
+}
+
+Q3DTheme *ThemeManager::activeTheme() const
+{
+ return m_activeTheme;
+}
+
+QList<Q3DTheme *> ThemeManager::themes() const
+{
+ return m_themes;
+}
+
+void ThemeManager::connectThemeSignals()
+{
+ connect(m_activeTheme, &Q3DTheme::colorStyleChanged,
+ m_controller, &Abstract3DController::handleThemeColorStyleChanged);
+ connect(m_activeTheme, &Q3DTheme::baseColorsChanged,
+ m_controller, &Abstract3DController::handleThemeBaseColorsChanged);
+ connect(m_activeTheme, &Q3DTheme::singleHighlightColorChanged,
+ m_controller, &Abstract3DController::handleThemeSingleHighlightColorChanged);
+ connect(m_activeTheme, &Q3DTheme::multiHighlightColorChanged,
+ m_controller, &Abstract3DController::handleThemeMultiHighlightColorChanged);
+ connect(m_activeTheme, &Q3DTheme::baseGradientsChanged,
+ m_controller, &Abstract3DController::handleThemeBaseGradientsChanged);
+ connect(m_activeTheme, &Q3DTheme::singleHighlightGradientChanged,
+ m_controller, &Abstract3DController::handleThemeSingleHighlightGradientChanged);
+ connect(m_activeTheme, &Q3DTheme::multiHighlightGradientChanged,
+ m_controller, &Abstract3DController::handleThemeMultiHighlightGradientChanged);
+ connect(m_activeTheme, &Q3DTheme::typeChanged,
+ m_controller, &Abstract3DController::handleThemeTypeChanged);
+
+ connect(m_activeTheme->d_ptr.data(), &Q3DThemePrivate::needRender,
+ m_controller, &Abstract3DController::needRender);
+}
+
+void ThemeManager::setPredefinedPropertiesToTheme(Q3DTheme *theme, Q3DTheme::Theme type)
+{
+ QList<QColor> baseColors;
+ QList<QLinearGradient> baseGradients;
+ switch (type) {
+ case Q3DTheme::ThemeQt: {
+ baseColors.append(QColor(QRgb(0x80c342)));
+ baseColors.append(QColor(QRgb(0x469835)));
+ baseColors.append(QColor(QRgb(0x006325)));
+ baseColors.append(QColor(QRgb(0x5caa15)));
+ baseColors.append(QColor(QRgb(0x328930)));
+
+ baseGradients.append(createGradient(baseColors.at(0), defaultColorLevel));
+ baseGradients.append(createGradient(baseColors.at(1), defaultColorLevel));
+ baseGradients.append(createGradient(baseColors.at(2), defaultColorLevel));
+ baseGradients.append(createGradient(baseColors.at(3), defaultColorLevel));
+ baseGradients.append(createGradient(baseColors.at(4), defaultColorLevel));
+
+ setBackgroundEnabled(theme, true);
+ setGridEnabled(theme, true);
+ setFont(theme, QFont(QStringLiteral("Arial")));
+ setLabelBackgroundEnabled(theme, true);
+ setLightColor(theme, Qt::white);
+ setBaseColors(theme, baseColors);
+ setBackgroundColor(theme, QColor(QRgb(0xffffff)));
+ setWindowColor(theme, QColor(QRgb(0xffffff)));
+ setTextColor(theme, QColor(QRgb(0x35322f)));
+ setTextBackgroundColor(theme, QColor(0xff, 0xff, 0xff, 0x99));
+ setGridLineColor(theme, QColor(QRgb(0xd7d6d5)));
+ setSingleHighlightColor(theme, QColor(QRgb(0x14aaff)));
+ setMultiHighlightColor(theme, QColor(QRgb(0x6400aa)));
+ setLightStrength(theme, 5.0f);
+ setAmbientLightStrength(theme, 0.5f);
+ setHighlightLightStrength(theme, 5.0f);
+ setLabelBorderEnabled(theme, true);
+ setColorStyle(theme, Q3DTheme::ColorStyleUniform);
+ setBaseGradients(theme, baseGradients);
+ setSingleHighlightGradient(theme, createGradient(QColor(QRgb(0x14aaff)),
+ defaultColorLevel));
+ setMultiHighlightGradient(theme, createGradient(QColor(QRgb(0x6400aa)),
+ defaultColorLevel));
+ break;
+ }
+
+ case Q3DTheme::ThemePrimaryColors: {
+ baseColors.append(QColor(QRgb(0xffe400)));
+ baseColors.append(QColor(QRgb(0xfaa106)));
+ baseColors.append(QColor(QRgb(0xf45f0d)));
+ baseColors.append(QColor(QRgb(0xfcba04)));
+ baseColors.append(QColor(QRgb(0xf7800a)));
+
+ baseGradients.append(createGradient(baseColors.at(0), defaultColorLevel));
+ baseGradients.append(createGradient(baseColors.at(1), defaultColorLevel));
+ baseGradients.append(createGradient(baseColors.at(2), defaultColorLevel));
+ baseGradients.append(createGradient(baseColors.at(3), defaultColorLevel));
+ baseGradients.append(createGradient(baseColors.at(4), defaultColorLevel));
+
+ setBackgroundEnabled(theme, true);
+ setGridEnabled(theme, true);
+ setFont(theme, QFont(QStringLiteral("Arial")));
+ setLabelBackgroundEnabled(theme, true);
+ setLightColor(theme, Qt::white);
+ setBaseColors(theme, baseColors);
+ setBackgroundColor(theme, QColor(QRgb(0xffffff)));
+ setWindowColor(theme, QColor(QRgb(0xffffff)));
+ setTextColor(theme, QColor(QRgb(0x000000)));
+ setTextBackgroundColor(theme, QColor(0xff, 0xff, 0xff, 0x99));
+ setGridLineColor(theme, QColor(QRgb(0xd7d6d5)));
+ setSingleHighlightColor(theme, QColor(QRgb(0x27beee)));
+ setMultiHighlightColor(theme, QColor(QRgb(0xee1414)));
+ setLightStrength(theme, 5.0f);
+ setAmbientLightStrength(theme, 0.5f);
+ setHighlightLightStrength(theme, 5.0f);
+ setLabelBorderEnabled(theme, false);
+ setColorStyle(theme, Q3DTheme::ColorStyleUniform);
+ setBaseGradients(theme, baseGradients);
+ setSingleHighlightGradient(theme, createGradient(QColor(QRgb(0x27beee)),
+ defaultColorLevel));
+ setMultiHighlightGradient(theme, createGradient(QColor(QRgb(0xee1414)),
+ defaultColorLevel));
+ break;
+ }
+
+ case Q3DTheme::ThemeDigia: {
+ baseColors.append(QColor(QRgb(0xeaeaea)));
+ baseColors.append(QColor(QRgb(0xa0a0a0)));
+ baseColors.append(QColor(QRgb(0x626262)));
+ baseColors.append(QColor(QRgb(0xbebebe)));
+ baseColors.append(QColor(QRgb(0x818181)));
+
+ baseGradients.append(createGradient(baseColors.at(0), defaultBuiltInColorLevel));
+ baseGradients.append(createGradient(baseColors.at(1), defaultBuiltInColorLevel));
+ baseGradients.append(createGradient(baseColors.at(2), defaultBuiltInColorLevel));
+ baseGradients.append(createGradient(baseColors.at(3), defaultBuiltInColorLevel));
+ baseGradients.append(createGradient(baseColors.at(4), defaultBuiltInColorLevel));
+
+ setBackgroundEnabled(theme, true);
+ setGridEnabled(theme, true);
+ setFont(theme, QFont(QStringLiteral("Arial")));
+ setLabelBackgroundEnabled(theme, true);
+ setLightColor(theme, Qt::white);
+ setBaseColors(theme, baseColors);
+ setBackgroundColor(theme, QColor(QRgb(0xffffff)));
+ setWindowColor(theme, QColor(QRgb(0xffffff)));
+ setTextColor(theme, QColor(QRgb(0x000000)));
+ setTextBackgroundColor(theme, QColor(0xff, 0xff, 0xff, 0x80));
+ setGridLineColor(theme, QColor(QRgb(0xd7d6d5)));
+ setSingleHighlightColor(theme, QColor(QRgb(0xfa0000)));
+ setMultiHighlightColor(theme, QColor(QRgb(0x333333)));
+ setLightStrength(theme, 5.0f);
+ setAmbientLightStrength(theme, 0.5f);
+ setHighlightLightStrength(theme, 5.0f);
+ setLabelBorderEnabled(theme, false);
+ setColorStyle(theme, Q3DTheme::ColorStyleObjectGradient);
+ setBaseGradients(theme, baseGradients);
+ setSingleHighlightGradient(theme, createGradient(QColor(QRgb(0xfa0000)),
+ defaultBuiltInColorLevel));
+ setMultiHighlightGradient(theme, createGradient(QColor(QRgb(0x333333)),
+ defaultBuiltInColorLevel));
+ break;
+ }
+
+ case Q3DTheme::ThemeStoneMoss: {
+ baseColors.append(QColor(QRgb(0xbeb32b)));
+ baseColors.append(QColor(QRgb(0x928327)));
+ baseColors.append(QColor(QRgb(0x665423)));
+ baseColors.append(QColor(QRgb(0xa69929)));
+ baseColors.append(QColor(QRgb(0x7c6c25)));
+
+ baseGradients.append(createGradient(baseColors.at(0), defaultColorLevel));
+ baseGradients.append(createGradient(baseColors.at(1), defaultColorLevel));
+ baseGradients.append(createGradient(baseColors.at(2), defaultColorLevel));
+ baseGradients.append(createGradient(baseColors.at(3), defaultColorLevel));
+ baseGradients.append(createGradient(baseColors.at(4), defaultColorLevel));
+
+ setBackgroundEnabled(theme, true);
+ setGridEnabled(theme, true);
+ setFont(theme, QFont(QStringLiteral("Arial")));
+ setLabelBackgroundEnabled(theme, true);
+ setLightColor(theme, Qt::white);
+ setBaseColors(theme, baseColors);
+ setBackgroundColor(theme, QColor(QRgb(0x4d4d4f)));
+ setWindowColor(theme, QColor(QRgb(0x4d4d4f)));
+ setTextColor(theme, QColor(QRgb(0xffffff)));
+ setTextBackgroundColor(theme, QColor(0x4d, 0x4d, 0x4f, 0xcd));
+ setGridLineColor(theme, QColor(QRgb(0x3e3e40)));
+ setSingleHighlightColor(theme, QColor(QRgb(0xfbf6d6)));
+ setMultiHighlightColor(theme, QColor(QRgb(0x442f20)));
+ setLightStrength(theme, 5.0f);
+ setAmbientLightStrength(theme, 0.5f);
+ setHighlightLightStrength(theme, 5.0f);
+ setLabelBorderEnabled(theme, true);
+ setColorStyle(theme, Q3DTheme::ColorStyleUniform);
+ setBaseGradients(theme, baseGradients);
+ setSingleHighlightGradient(theme, createGradient(QColor(QRgb(0xfbf6d6)),
+ defaultColorLevel));
+ setMultiHighlightGradient(theme, createGradient(QColor(QRgb(0x442f20)),
+ defaultColorLevel));
+ break;
+ }
+
+ case Q3DTheme::ThemeArmyBlue: {
+ baseColors.append(QColor(QRgb(0x495f76)));
+ baseColors.append(QColor(QRgb(0x81909f)));
+ baseColors.append(QColor(QRgb(0xbec5cd)));
+ baseColors.append(QColor(QRgb(0x687a8d)));
+ baseColors.append(QColor(QRgb(0xa3aeb9)));
+
+ baseGradients.append(createGradient(baseColors.at(0), defaultBuiltInColorLevel));
+ baseGradients.append(createGradient(baseColors.at(1), defaultBuiltInColorLevel));
+ baseGradients.append(createGradient(baseColors.at(2), defaultBuiltInColorLevel));
+ baseGradients.append(createGradient(baseColors.at(3), defaultBuiltInColorLevel));
+ baseGradients.append(createGradient(baseColors.at(4), defaultBuiltInColorLevel));
+
+ setBackgroundEnabled(theme, true);
+ setGridEnabled(theme, true);
+ setFont(theme, QFont(QStringLiteral("Arial")));
+ setLabelBackgroundEnabled(theme, true);
+ setLightColor(theme, Qt::white);
+ setBaseColors(theme, baseColors);
+ setBackgroundColor(theme, QColor(QRgb(0xd5d6d7)));
+ setWindowColor(theme, QColor(QRgb(0xd5d6d7)));
+ setTextColor(theme, QColor(QRgb(0x000000)));
+ setTextBackgroundColor(theme, QColor(0xd5, 0xd6, 0xd7, 0xcd));
+ setGridLineColor(theme, QColor(QRgb(0xaeadac)));
+ setSingleHighlightColor(theme, QColor(QRgb(0x2aa2f9)));
+ setMultiHighlightColor(theme, QColor(QRgb(0x103753)));
+ setLightStrength(theme, 5.0f);
+ setAmbientLightStrength(theme, 0.5f);
+ setHighlightLightStrength(theme, 5.0f);
+ setLabelBorderEnabled(theme, false);
+ setColorStyle(theme, Q3DTheme::ColorStyleObjectGradient);
+ setBaseGradients(theme, baseGradients);
+ setSingleHighlightGradient(theme, createGradient(QColor(QRgb(0x2aa2f9)),
+ defaultBuiltInColorLevel));
+ setMultiHighlightGradient(theme, createGradient(QColor(QRgb(0x103753)),
+ defaultBuiltInColorLevel));
+ break;
+ }
+
+ case Q3DTheme::ThemeRetro: {
+ baseColors.append(QColor(QRgb(0x533b23)));
+ baseColors.append(QColor(QRgb(0x83715a)));
+ baseColors.append(QColor(QRgb(0xb3a690)));
+ baseColors.append(QColor(QRgb(0x6b563e)));
+ baseColors.append(QColor(QRgb(0x9b8b75)));
+
+ baseGradients.append(createGradient(baseColors.at(0), defaultBuiltInColorLevel));
+ baseGradients.append(createGradient(baseColors.at(1), defaultBuiltInColorLevel));
+ baseGradients.append(createGradient(baseColors.at(2), defaultBuiltInColorLevel));
+ baseGradients.append(createGradient(baseColors.at(3), defaultBuiltInColorLevel));
+ baseGradients.append(createGradient(baseColors.at(4), defaultBuiltInColorLevel));
+
+ setBackgroundEnabled(theme, true);
+ setGridEnabled(theme, true);
+ setFont(theme, QFont(QStringLiteral("Arial")));
+ setLabelBackgroundEnabled(theme, true);
+ setLightColor(theme, Qt::white);
+ setBaseColors(theme, baseColors);
+ setBackgroundColor(theme, QColor(QRgb(0xe9e2ce)));
+ setWindowColor(theme, QColor(QRgb(0xe9e2ce)));
+ setTextColor(theme, QColor(QRgb(0x000000)));
+ setTextBackgroundColor(theme, QColor(0xe9, 0xe2, 0xce, 0xc0));
+ setGridLineColor(theme, QColor(QRgb(0xd0c0b0)));
+ setSingleHighlightColor(theme, QColor(QRgb(0x8ea317)));
+ setMultiHighlightColor(theme, QColor(QRgb(0xc25708)));
+ setLightStrength(theme, 5.0f);
+ setAmbientLightStrength(theme, 0.5f);
+ setHighlightLightStrength(theme, 5.0f);
+ setLabelBorderEnabled(theme, false);
+ setColorStyle(theme, Q3DTheme::ColorStyleObjectGradient);
+ setBaseGradients(theme, baseGradients);
+ setSingleHighlightGradient(theme, createGradient(QColor(QRgb(0x8ea317)),
+ defaultBuiltInColorLevel));
+ setMultiHighlightGradient(theme, createGradient(QColor(QRgb(0xc25708)),
+ defaultBuiltInColorLevel));
+ break;
+ }
+
+ case Q3DTheme::ThemeEbony: {
+ baseColors.append(QColor(QRgb(0xffffff)));
+ baseColors.append(QColor(QRgb(0x999999)));
+ baseColors.append(QColor(QRgb(0x474747)));
+ baseColors.append(QColor(QRgb(0xc7c7c7)));
+ baseColors.append(QColor(QRgb(0x6b6b6b)));
+
+ baseGradients.append(createGradient(baseColors.at(0), defaultColorLevel));
+ baseGradients.append(createGradient(baseColors.at(1), defaultColorLevel));
+ baseGradients.append(createGradient(baseColors.at(2), defaultColorLevel));
+ baseGradients.append(createGradient(baseColors.at(3), defaultColorLevel));
+ baseGradients.append(createGradient(baseColors.at(4), defaultColorLevel));
+
+ setBackgroundEnabled(theme, true);
+ setGridEnabled(theme, true);
+ setFont(theme, QFont(QStringLiteral("Arial")));
+ setLabelBackgroundEnabled(theme, true);
+ setLightColor(theme, Qt::white);
+ setBaseColors(theme, baseColors);
+ setBackgroundColor(theme, QColor(QRgb(0x000000)));
+ setWindowColor(theme, QColor(QRgb(0x000000)));
+ setTextColor(theme, QColor(QRgb(0xaeadac)));
+ setTextBackgroundColor(theme, QColor(0x00, 0x00, 0x00, 0xcd));
+ setGridLineColor(theme, QColor(QRgb(0x35322f)));
+ setSingleHighlightColor(theme, QColor(QRgb(0xf5dc0d)));
+ setMultiHighlightColor(theme, QColor(QRgb(0xd72222)));
+ setLightStrength(theme, 5.0f);
+ setAmbientLightStrength(theme, 0.5f);
+ setHighlightLightStrength(theme, 5.0f);
+ setLabelBorderEnabled(theme, false);
+ setColorStyle(theme, Q3DTheme::ColorStyleUniform);
+ setBaseGradients(theme, baseGradients);
+ setSingleHighlightGradient(theme, createGradient(QColor(QRgb(0xf5dc0d)),
+ defaultColorLevel));
+ setMultiHighlightGradient(theme, createGradient(QColor(QRgb(0xd72222)),
+ defaultColorLevel));
+ break;
+ }
+
+ case Q3DTheme::ThemeIsabelle: {
+ baseColors.append(QColor(QRgb(0xf9d900)));
+ baseColors.append(QColor(QRgb(0xf09603)));
+ baseColors.append(QColor(QRgb(0xe85506)));
+ baseColors.append(QColor(QRgb(0xf5b802)));
+ baseColors.append(QColor(QRgb(0xec7605)));
+
+ baseGradients.append(createGradient(baseColors.at(0), defaultColorLevel));
+ baseGradients.append(createGradient(baseColors.at(1), defaultColorLevel));
+ baseGradients.append(createGradient(baseColors.at(2), defaultColorLevel));
+ baseGradients.append(createGradient(baseColors.at(3), defaultColorLevel));
+ baseGradients.append(createGradient(baseColors.at(4), defaultColorLevel));
+
+ setBackgroundEnabled(theme, true);
+ setGridEnabled(theme, true);
+ setFont(theme, QFont(QStringLiteral("Arial")));
+ setLabelBackgroundEnabled(theme, true);
+ setLightColor(theme, Qt::white);
+ setBaseColors(theme, baseColors);
+ setBackgroundColor(theme, QColor(QRgb(0x000000)));
+ setWindowColor(theme, QColor(QRgb(0x000000)));
+ setTextColor(theme, QColor(QRgb(0xaeadac)));
+ setTextBackgroundColor(theme, QColor(0x00, 0x00, 0x00, 0xc0));
+ setGridLineColor(theme, QColor(QRgb(0x35322f)));
+ setSingleHighlightColor(theme, QColor(QRgb(0xfff7cc)));
+ setMultiHighlightColor(theme, QColor(QRgb(0xde0a0a)));
+ setLightStrength(theme, 5.0f);
+ setAmbientLightStrength(theme, 0.5f);
+ setHighlightLightStrength(theme, 5.0f);
+ setLabelBorderEnabled(theme, false);
+ setColorStyle(theme, Q3DTheme::ColorStyleUniform);
+ setBaseGradients(theme, baseGradients);
+ setSingleHighlightGradient(theme, createGradient(QColor(QRgb(0xfff7cc)),
+ defaultColorLevel));
+ setMultiHighlightGradient(theme, createGradient(QColor(QRgb(0xde0a0a)),
+ defaultColorLevel));
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+QLinearGradient ThemeManager::createGradient(const QColor &color, float colorLevel)
+{
+ QColor startColor;
+ QLinearGradient gradient = QLinearGradient(qreal(gradientTextureWidth),
+ qreal(gradientTextureHeight),
+ 0.0, 0.0);;
+ startColor.setRed(color.red() * colorLevel);
+ startColor.setGreen(color.green() * colorLevel);
+ startColor.setBlue(color.blue() * colorLevel);
+ gradient.setColorAt(0.0, startColor);
+ gradient.setColorAt(1.0, color);
+ return gradient;
+}
+
+void ThemeManager::setBaseColors(Q3DTheme *theme, const QList<QColor> &colors)
+{
+ if (theme->d_ptr->isForcePredefinedType() || !theme->d_ptr->m_dirtyBits.baseColorDirty)
+ theme->setBaseColors(colors);
+}
+
+void ThemeManager::setBackgroundColor(Q3DTheme *theme, const QColor &color)
+{
+ if (theme->d_ptr->isForcePredefinedType() || !theme->d_ptr->m_dirtyBits.backgroundColorDirty)
+ theme->setBackgroundColor(color);
+}
+
+void ThemeManager::setWindowColor(Q3DTheme *theme, const QColor &color)
+{
+ if (theme->d_ptr->isForcePredefinedType() || !theme->d_ptr->m_dirtyBits.windowColorDirty)
+ theme->setWindowColor(color);
+}
+
+void ThemeManager::setTextColor(Q3DTheme *theme, const QColor &color)
+{
+ if (theme->d_ptr->isForcePredefinedType() || !theme->d_ptr->m_dirtyBits.labelTextColorDirty)
+ theme->setLabelTextColor(color);
+}
+
+void ThemeManager::setTextBackgroundColor(Q3DTheme *theme, const QColor &color)
+{
+ if (theme->d_ptr->isForcePredefinedType() || !theme->d_ptr->m_dirtyBits.labelBackgroundColorDirty)
+ theme->setLabelBackgroundColor(color);
+}
+
+void ThemeManager::setGridLineColor(Q3DTheme *theme, const QColor &color)
+{
+ if (theme->d_ptr->isForcePredefinedType() || !theme->d_ptr->m_dirtyBits.gridLineColorDirty)
+ theme->setGridLineColor(color);
+}
+
+void ThemeManager::setSingleHighlightColor(Q3DTheme *theme, const QColor &color)
+{
+ if (theme->d_ptr->isForcePredefinedType() || !theme->d_ptr->m_dirtyBits.singleHighlightColorDirty)
+ theme->setSingleHighlightColor(color);
+}
+
+void ThemeManager::setMultiHighlightColor(Q3DTheme *theme, const QColor &color)
+{
+ if (theme->d_ptr->isForcePredefinedType() || !theme->d_ptr->m_dirtyBits.multiHighlightColorDirty)
+ theme->setMultiHighlightColor(color);
+}
+
+void ThemeManager::setLightColor(Q3DTheme *theme, const QColor &color)
+{
+ if (theme->d_ptr->isForcePredefinedType() || !theme->d_ptr->m_dirtyBits.lightColorDirty)
+ theme->setLightColor(color);
+}
+
+void ThemeManager::setBaseGradients(Q3DTheme *theme, const QList<QLinearGradient> &gradients)
+{
+ if (theme->d_ptr->isForcePredefinedType() || !theme->d_ptr->m_dirtyBits.baseGradientDirty)
+ theme->setBaseGradients(gradients);
+}
+
+void ThemeManager::setSingleHighlightGradient(Q3DTheme *theme, const QLinearGradient &gradient)
+{
+ if (theme->d_ptr->isForcePredefinedType() || !theme->d_ptr->m_dirtyBits.singleHighlightGradientDirty)
+ theme->setSingleHighlightGradient(gradient);
+}
+
+void ThemeManager::setMultiHighlightGradient(Q3DTheme *theme, const QLinearGradient &gradient)
+{
+ if (theme->d_ptr->isForcePredefinedType() || !theme->d_ptr->m_dirtyBits.multiHighlightGradientDirty)
+ theme->setMultiHighlightGradient(gradient);
+}
+
+void ThemeManager::setLightStrength(Q3DTheme *theme, float strength)
+{
+ if (theme->d_ptr->isForcePredefinedType() || !theme->d_ptr->m_dirtyBits.lightStrengthDirty)
+ theme->setLightStrength(strength);
+}
+
+void ThemeManager::setAmbientLightStrength(Q3DTheme *theme, float strength)
+{
+ if (theme->d_ptr->isForcePredefinedType() || !theme->d_ptr->m_dirtyBits.ambientLightStrengthDirty)
+ theme->setAmbientLightStrength(strength);
+}
+
+void ThemeManager::setHighlightLightStrength(Q3DTheme *theme, float strength)
+{
+ if (theme->d_ptr->isForcePredefinedType() || !theme->d_ptr->m_dirtyBits.highlightLightStrengthDirty)
+ theme->setHighlightLightStrength(strength);
+}
+
+void ThemeManager::setLabelBorderEnabled(Q3DTheme *theme, bool enabled)
+{
+ if (theme->d_ptr->isForcePredefinedType() || !theme->d_ptr->m_dirtyBits.labelBorderEnabledDirty)
+ theme->setLabelBorderEnabled(enabled);
+}
+
+void ThemeManager::setFont(Q3DTheme *theme, const QFont &font)
+{
+ if (theme->d_ptr->isForcePredefinedType() || !theme->d_ptr->m_dirtyBits.fontDirty)
+ theme->setFont(font);
+}
+
+void ThemeManager::setBackgroundEnabled(Q3DTheme *theme, bool enabled)
+{
+ if (theme->d_ptr->isForcePredefinedType() || !theme->d_ptr->m_dirtyBits.backgroundEnabledDirty)
+ theme->setBackgroundEnabled(enabled);
+}
+
+void ThemeManager::setGridEnabled(Q3DTheme *theme, bool enabled)
+{
+ if (theme->d_ptr->isForcePredefinedType() || !theme->d_ptr->m_dirtyBits.gridEnabledDirty)
+ theme->setGridEnabled(enabled);
+}
+
+void ThemeManager::setLabelBackgroundEnabled(Q3DTheme *theme, bool enabled)
+{
+ if (theme->d_ptr->isForcePredefinedType() || !theme->d_ptr->m_dirtyBits.labelBackgroundEnabledDirty)
+ theme->setLabelBackgroundEnabled(enabled);
+}
+
+void ThemeManager::setColorStyle(Q3DTheme *theme, Q3DTheme::ColorStyle style)
+{
+ if (theme->d_ptr->isForcePredefinedType() || !theme->d_ptr->m_dirtyBits.colorStyleDirty)
+ theme->setColorStyle(style);
+}
+
+QT_END_NAMESPACE
diff --git a/src/graphs/theme/thememanager_p.h b/src/graphs/theme/thememanager_p.h
new file mode 100644
index 0000000..f1fe19a
--- /dev/null
+++ b/src/graphs/theme/thememanager_p.h
@@ -0,0 +1,71 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QtGraphs API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#ifndef THEMEMANAGER_P_H
+#define THEMEMANAGER_P_H
+
+#include "graphsglobal_p.h"
+#include "abstract3dcontroller_p.h"
+#include "q3dtheme.h"
+
+QT_BEGIN_NAMESPACE
+
+class ThemeManager : public QObject
+{
+ Q_OBJECT
+public:
+ ThemeManager(Abstract3DController *controller);
+ ~ThemeManager();
+
+ void addTheme(Q3DTheme *theme);
+ void releaseTheme(Q3DTheme *theme);
+ void setActiveTheme(Q3DTheme *theme);
+ Q3DTheme *activeTheme() const;
+ QList<Q3DTheme *> themes() const;
+
+ static void setPredefinedPropertiesToTheme(Q3DTheme *theme, Q3DTheme::Theme type);
+
+protected:
+ void connectThemeSignals();
+ static QLinearGradient createGradient(const QColor &color, float colorLevel);
+ static void setBaseColors(Q3DTheme *theme, const QList<QColor> &colors);
+ static void setBackgroundColor(Q3DTheme *theme, const QColor &color);
+ static void setWindowColor(Q3DTheme *theme, const QColor &color);
+ static void setTextColor(Q3DTheme *theme, const QColor &color);
+ static void setTextBackgroundColor(Q3DTheme *theme, const QColor &color);
+ static void setGridLineColor(Q3DTheme *theme, const QColor &color);
+ static void setSingleHighlightColor(Q3DTheme *theme, const QColor &color);
+ static void setMultiHighlightColor(Q3DTheme *theme, const QColor &color);
+ static void setLightColor(Q3DTheme *theme, const QColor &color);
+ static void setBaseGradients(Q3DTheme *theme, const QList<QLinearGradient> &gradients);
+ static void setSingleHighlightGradient(Q3DTheme *theme, const QLinearGradient &gradient);
+ static void setMultiHighlightGradient(Q3DTheme *theme, const QLinearGradient &gradient);
+ static void setLightStrength(Q3DTheme *theme, float strength);
+ static void setAmbientLightStrength(Q3DTheme *theme, float strength);
+ static void setHighlightLightStrength(Q3DTheme *theme, float strength);
+ static void setLabelBorderEnabled(Q3DTheme *theme, bool enabled);
+ static void setFont(Q3DTheme *theme, const QFont &font);
+ static void setBackgroundEnabled(Q3DTheme *theme, bool enabled);
+ static void setGridEnabled(Q3DTheme *theme, bool enabled);
+ static void setLabelBackgroundEnabled(Q3DTheme *theme, bool enabled);
+ static void setColorStyle(Q3DTheme *theme, Q3DTheme::ColorStyle style);
+
+private:
+ Q3DTheme *m_activeTheme;
+ QList<Q3DTheme *> m_themes; // List of all added themes
+ Abstract3DController *m_controller;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/graphs/utils/qutils.h b/src/graphs/utils/qutils.h
new file mode 100644
index 0000000..d44a0fe
--- /dev/null
+++ b/src/graphs/utils/qutils.h
@@ -0,0 +1,89 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QUTILS_H
+#define QUTILS_H
+
+#include <QtGui/QSurfaceFormat>
+#include <QtGui/QOpenGLContext>
+#include <QtGui/QOpenGLFunctions>
+#include <QtGui/QOffscreenSurface>
+#include <QtCore/QCoreApplication>
+
+QT_BEGIN_NAMESPACE
+
+[[maybe_unused]]
+static inline QSurfaceFormat qDefaultSurfaceFormat(bool antialias)
+{
+ bool isES = false;
+
+ QSurfaceFormat surfaceFormat;
+
+ // Common attributes
+ surfaceFormat.setDepthBufferSize(24);
+ surfaceFormat.setStencilBufferSize(8);
+ surfaceFormat.setSwapBehavior(QSurfaceFormat::DoubleBuffer);
+ surfaceFormat.setRenderableType(QSurfaceFormat::DefaultRenderableType);
+
+ QOpenGLContext *ctx = QOpenGLContext::currentContext();
+ QOffscreenSurface *dummySurface = nullptr;
+ if (!ctx) {
+ dummySurface = new QOffscreenSurface();
+ dummySurface->setFormat(surfaceFormat);
+ dummySurface->create();
+ ctx = new QOpenGLContext;
+ ctx->setFormat(surfaceFormat);
+ ctx->create();
+ ctx->makeCurrent(dummySurface);
+ }
+
+#if QT_CONFIG(opengles2)
+ isES = true;
+#elif (QT_VERSION < QT_VERSION_CHECK(5, 3, 0))
+ isES = false;
+#else
+ isES = ctx->isOpenGLES();
+#endif
+
+#if (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0))
+ // We support only ES2 emulation with software renderer for now
+ QString versionStr;
+#ifdef Q_OS_WIN
+ const GLubyte *openGLVersion = ctx->functions()->glGetString(GL_VERSION);
+ versionStr = QString::fromLatin1(reinterpret_cast<const char *>(openGLVersion)).toLower();
+#endif
+ if (versionStr.contains(QStringLiteral("mesa"))
+ || QCoreApplication::testAttribute(Qt::AA_UseSoftwareOpenGL)) {
+ qWarning("Only OpenGL ES2 emulation is available for software rendering.");
+ isES = true;
+ }
+#endif
+
+ if (dummySurface) {
+ ctx->doneCurrent();
+ delete ctx;
+ delete dummySurface;
+ }
+
+ if (isES) {
+ // For ES2 only attributes
+ surfaceFormat.setRedBufferSize(8);
+ surfaceFormat.setBlueBufferSize(8);
+ surfaceFormat.setGreenBufferSize(8);
+ } else {
+ surfaceFormat.setVersion(2, 1);
+ surfaceFormat.setProfile(QSurfaceFormat::CoreProfile);
+ // For OpenGL only attributes
+ if (antialias)
+ surfaceFormat.setSamples(8);
+ else
+ surfaceFormat.setSamples(0);
+ }
+
+ return surfaceFormat;
+}
+
+QT_END_NAMESPACE
+
+#endif
+
diff --git a/src/graphs/utils/utils.cpp b/src/graphs/utils/utils.cpp
new file mode 100644
index 0000000..ba72aea
--- /dev/null
+++ b/src/graphs/utils/utils.cpp
@@ -0,0 +1,366 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "utils_p.h"
+
+#include <QtGui/QPainter>
+#include <QtGui/QOpenGLContext>
+#include <QtGui/QOffscreenSurface>
+#include <QtCore/QCoreApplication>
+#include <QtCore/QRegularExpression>
+#include <QLocale>
+
+QT_BEGIN_NAMESPACE
+
+#define NUM_IN_POWER(y, x) for (;y<x;y<<=1)
+#define MIN_POWER 2
+
+// Some values that only need to be resolved once
+static bool staticsResolved = false;
+static GLint maxTextureSize = 0;
+static bool isES = false;
+
+GLuint Utils::getNearestPowerOfTwo(GLuint value)
+{
+ GLuint powOfTwoValue = MIN_POWER;
+ NUM_IN_POWER(powOfTwoValue, value);
+ return powOfTwoValue;
+}
+
+QVector4D Utils::vectorFromColor(const QColor &color)
+{
+ return QVector4D(color.redF(), color.greenF(), color.blueF(), color.alphaF());
+}
+
+QColor Utils::colorFromVector(const QVector3D &colorVector)
+{
+ return QColor(colorVector.x() * 255.0f, colorVector.y() * 255.0f,
+ colorVector.z() * 255.0f, 255.0f);
+}
+
+QColor Utils::colorFromVector(const QVector4D &colorVector)
+{
+ return QColor(colorVector.x() * 255.0f, colorVector.y() * 255.0f,
+ colorVector.z() * 255.0f, colorVector.w() * 255.0f);
+}
+
+QImage Utils::printTextToImage(const QFont &font, const QString &text, const QColor &bgrColor,
+ const QColor &txtColor, bool labelBackground,
+ bool borders, int maxLabelWidth)
+{
+ if (!staticsResolved)
+ resolveStatics();
+
+ GLuint paddingWidth = 20;
+ GLuint paddingHeight = 20;
+ GLuint prePadding = 20;
+ GLint targetWidth = maxTextureSize;
+
+ // Calculate text dimensions
+ QFont valueFont = font;
+ valueFont.setPointSize(textureFontSize);
+ QFontMetrics valueFM(valueFont);
+ int valueStrWidth = valueFM.horizontalAdvance(text);
+
+ // ES2 needs to use maxLabelWidth always (when given) because of the power of 2 -issue.
+ if (maxLabelWidth && (labelBackground || Utils::isOpenGLES()))
+ valueStrWidth = maxLabelWidth;
+ int valueStrHeight = valueFM.height();
+ valueStrWidth += paddingWidth / 2; // Fix clipping problem with skewed fonts (italic or italic-style)
+ QSize labelSize;
+ qreal fontRatio = 1.0;
+
+ if (Utils::isOpenGLES()) {
+ // Test if text with slighly smaller font would fit into one step smaller texture
+ // ie. if the text is just exceeded the smaller texture boundary, it would
+ // make a label with large empty space
+ uint testWidth = getNearestPowerOfTwo(valueStrWidth + prePadding) >> 1;
+ int diffToFit = (valueStrWidth + prePadding) - testWidth;
+ int maxSqueeze = int((valueStrWidth + prePadding) * 0.25f);
+ if (diffToFit < maxSqueeze && maxTextureSize > GLint(testWidth))
+ targetWidth = testWidth;
+ }
+
+ bool sizeOk = false;
+ int currentFontSize = textureFontSize;
+ do {
+ if (Utils::isOpenGLES()) {
+ // ES2 can't handle textures with dimensions not in power of 2. Resize labels accordingly.
+ // Add some padding before converting to power of two to avoid too tight fit
+ labelSize = QSize(valueStrWidth + prePadding, valueStrHeight + prePadding);
+ labelSize.setWidth(getNearestPowerOfTwo(labelSize.width()));
+ labelSize.setHeight(getNearestPowerOfTwo(labelSize.height()));
+ } else {
+ if (!labelBackground)
+ labelSize = QSize(valueStrWidth, valueStrHeight);
+ else
+ labelSize = QSize(valueStrWidth + paddingWidth * 2, valueStrHeight + paddingHeight * 2);
+ }
+
+ if (!maxTextureSize || (labelSize.width() <= maxTextureSize
+ && (labelSize.width() <= targetWidth || !Utils::isOpenGLES()))) {
+ // Make sure the label is not too wide
+ sizeOk = true;
+ } else if (--currentFontSize == 4) {
+ qCritical() << "Label" << text << "is too long to be generated.";
+ return QImage();
+ } else {
+ fontRatio = (qreal)currentFontSize / (qreal)textureFontSize;
+ // Reduce font size and try again
+ valueFont.setPointSize(currentFontSize);
+ QFontMetrics currentValueFM(valueFont);
+ if (maxLabelWidth && (labelBackground || Utils::isOpenGLES()))
+ valueStrWidth = maxLabelWidth * fontRatio;
+ else
+ valueStrWidth = currentValueFM.horizontalAdvance(text);
+ valueStrHeight = currentValueFM.height();
+ valueStrWidth += paddingWidth / 2;
+ }
+ } while (!sizeOk);
+
+ // Create image
+ QImage image = QImage(labelSize, QImage::Format_ARGB32);
+ image.fill(Qt::transparent);
+
+ // Init painter
+ QPainter painter(&image);
+ // Paint text
+ painter.setRenderHint(QPainter::Antialiasing, true);
+ painter.setCompositionMode(QPainter::CompositionMode_Source);
+ painter.setFont(valueFont);
+ if (!labelBackground) {
+ painter.setPen(txtColor);
+ if (Utils::isOpenGLES()) {
+ painter.drawText((labelSize.width() - valueStrWidth) / 2.0f,
+ (labelSize.height() - valueStrHeight) / 2.0f,
+ valueStrWidth, valueStrHeight,
+ Qt::AlignCenter | Qt::AlignVCenter,
+ text);
+ } else {
+ painter.drawText(0, 0,
+ valueStrWidth, valueStrHeight,
+ Qt::AlignCenter | Qt::AlignVCenter,
+ text);
+ }
+ } else {
+ painter.setBrush(QBrush(bgrColor));
+ qreal radius = 10.0 * fontRatio;
+ if (borders) {
+ painter.setPen(QPen(QBrush(txtColor), 5.0 * fontRatio,
+ Qt::SolidLine, Qt::SquareCap, Qt::RoundJoin));
+ painter.drawRoundedRect(5, 5,
+ labelSize.width() - 10, labelSize.height() - 10,
+ radius, radius);
+ } else {
+ painter.setPen(bgrColor);
+ painter.drawRoundedRect(0, 0, labelSize.width(), labelSize.height(), radius, radius);
+ }
+ painter.setPen(txtColor);
+ painter.drawText((labelSize.width() - valueStrWidth) / 2.0f,
+ (labelSize.height() - valueStrHeight) / 2.0f,
+ valueStrWidth, valueStrHeight,
+ Qt::AlignCenter | Qt::AlignVCenter,
+ text);
+ }
+ return image;
+}
+
+QVector4D Utils::getSelection(QPoint mousepos, int height)
+{
+ // This is the only one that works with OpenGL ES 2.0, so we're forced to use it
+ // Item count will be limited to 256*256*256
+ GLubyte pixel[4] = {255, 255, 255, 255};
+ QOpenGLContext::currentContext()->functions()->glReadPixels(mousepos.x(), height - mousepos.y(),
+ 1, 1, GL_RGBA, GL_UNSIGNED_BYTE,
+ (void *)pixel);
+ QVector4D selectedColor(pixel[0], pixel[1], pixel[2], pixel[3]);
+ return selectedColor;
+}
+
+QImage Utils::getGradientImage(QLinearGradient &gradient)
+{
+ QImage image(QSize(gradientTextureWidth, gradientTextureHeight), QImage::Format_RGB32);
+ gradient.setFinalStop(qreal(gradientTextureWidth), qreal(gradientTextureHeight));
+ gradient.setStart(0.0, 0.0);
+
+ QPainter pmp(&image);
+ pmp.setBrush(QBrush(gradient));
+ pmp.setPen(Qt::NoPen);
+ pmp.drawRect(0, 0, int(gradientTextureWidth), int(gradientTextureHeight));
+ return image;
+}
+
+Utils::ParamType Utils::preParseFormat(const QString &format, QString &preStr, QString &postStr,
+ int &precision, char &formatSpec)
+{
+ static QRegularExpression formatMatcher(QStringLiteral("^([^%]*)%([\\-\\+#\\s\\d\\.lhjztL]*)([dicuoxfegXFEG])(.*)$"));
+ static QRegularExpression precisionMatcher(QStringLiteral("\\.(\\d+)"));
+
+ Utils::ParamType retVal;
+
+ QRegularExpressionMatch formatMatch = formatMatcher.match(format, 0);
+
+ if (formatMatch.hasMatch()) {
+ preStr = formatMatch.captured(1);
+ // Six and 'g' are defaults in Qt API
+ precision = 6;
+ if (!formatMatch.captured(2).isEmpty()) {
+ QRegularExpressionMatch precisionMatch = precisionMatcher.match(formatMatch.captured(2),
+ 0);
+ if (precisionMatch.hasMatch())
+ precision = precisionMatch.captured(1).toInt();
+ }
+ if (formatMatch.captured(3).isEmpty())
+ formatSpec = 'g';
+ else
+ formatSpec = formatMatch.captured(3).at(0).toLatin1();
+ postStr = formatMatch.captured(4);
+ retVal = mapFormatCharToParamType(formatSpec);
+ } else {
+ retVal = ParamTypeUnknown;
+ // The out parameters are irrelevant in unknown case
+ }
+
+ return retVal;
+}
+
+Utils::ParamType Utils::mapFormatCharToParamType(char formatSpec)
+{
+ ParamType retVal = ParamTypeUnknown;
+ if (formatSpec == 'd' || formatSpec == 'i' || formatSpec == 'c') {
+ retVal = ParamTypeInt;
+ } else if (formatSpec == 'u' || formatSpec == 'o'
+ || formatSpec == 'x'|| formatSpec == 'X') {
+ retVal = ParamTypeUInt;
+ } else if (formatSpec == 'f' || formatSpec == 'F'
+ || formatSpec == 'e' || formatSpec == 'E'
+ || formatSpec == 'g' || formatSpec == 'G') {
+ retVal = ParamTypeReal;
+ }
+
+ return retVal;
+}
+
+QString Utils::formatLabelSprintf(const QByteArray &format, Utils::ParamType paramType, qreal value)
+{
+ switch (paramType) {
+ case ParamTypeInt:
+ return QString::asprintf(format.constData(), qint64(value));
+ case ParamTypeUInt:
+ return QString::asprintf(format.constData(), quint64(value));
+ case ParamTypeReal:
+ return QString::asprintf(format.constData(), value);
+ default:
+ // Return format string to detect errors. Bars selection label logic also depends on this.
+ return QString::fromUtf8(format);
+ }
+}
+
+QString Utils::formatLabelLocalized(Utils::ParamType paramType, qreal value,
+ const QLocale &locale, const QString &preStr, const QString &postStr,
+ int precision, char formatSpec, const QByteArray &format)
+{
+ switch (paramType) {
+ case ParamTypeInt:
+ case ParamTypeUInt:
+ return preStr + locale.toString(qint64(value)) + postStr;
+ case ParamTypeReal:
+ return preStr + locale.toString(value, formatSpec, precision) + postStr;
+ default:
+ // Return format string to detect errors. Bars selection label logic also depends on this.
+ return QString::fromUtf8(format);
+ }
+}
+
+QString Utils::defaultLabelFormat()
+{
+ static const QString defaultFormat(QStringLiteral("%.2f"));
+ return defaultFormat;
+}
+
+float Utils::wrapValue(float value, float min, float max)
+{
+ if (value > max) {
+ value = min + (value - max);
+
+ // In case single wrap fails, jump to opposite end.
+ if (value > max)
+ value = min;
+ }
+
+ if (value < min) {
+ value = max + (value - min);
+
+ // In case single wrap fails, jump to opposite end.
+ if (value < min)
+ value = max;
+ }
+
+ return value;
+}
+
+QQuaternion Utils::calculateRotation(const QVector3D &xyzRotations)
+{
+ QQuaternion rotQuatX = QQuaternion::fromAxisAndAngle(1.0f, 0.0f, 0.0f, xyzRotations.x());
+ QQuaternion rotQuatY = QQuaternion::fromAxisAndAngle(0.0f, 1.0f, 0.0f, xyzRotations.y());
+ QQuaternion rotQuatZ = QQuaternion::fromAxisAndAngle(0.0f, 0.0f, 1.0f, xyzRotations.z());
+ QQuaternion totalRotation = rotQuatY * rotQuatZ * rotQuatX;
+ return totalRotation;
+}
+
+bool Utils::isOpenGLES()
+{
+ if (!staticsResolved)
+ resolveStatics();
+ return isES;
+}
+
+void Utils::resolveStatics()
+{
+ QOpenGLContext *ctx = QOpenGLContext::currentContext();
+ QOffscreenSurface *dummySurface = 0;
+ if (!ctx) {
+ QSurfaceFormat surfaceFormat;
+ dummySurface = new QOffscreenSurface();
+ dummySurface->setFormat(surfaceFormat);
+ dummySurface->create();
+ ctx = new QOpenGLContext;
+ ctx->setFormat(surfaceFormat);
+ ctx->create();
+ ctx->makeCurrent(dummySurface);
+ }
+
+#if QT_CONFIG(opengles2)
+ isES = true;
+#elif (QT_VERSION < QT_VERSION_CHECK(5, 3, 0))
+ isES = false;
+#else
+ isES = ctx->isOpenGLES();
+#endif
+
+ ctx->functions()->glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);
+
+#if (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0))
+ // We support only ES2 emulation with software renderer for now
+ QString versionStr;
+#ifdef Q_OS_WIN
+ const GLubyte *openGLVersion = ctx->functions()->glGetString(GL_VERSION);
+ versionStr = QString::fromLatin1(reinterpret_cast<const char *>(openGLVersion)).toLower();
+#endif
+ if (versionStr.contains(QStringLiteral("mesa"), Qt::CaseInsensitive)
+ || QCoreApplication::testAttribute(Qt::AA_UseSoftwareOpenGL)) {
+ qWarning("Only OpenGL ES2 emulation is available for software rendering.");
+ isES = true;
+ }
+#endif
+
+ if (dummySurface) {
+ ctx->doneCurrent();
+ delete ctx;
+ delete dummySurface;
+ }
+
+ staticsResolved = true;
+}
+
+QT_END_NAMESPACE
diff --git a/src/graphs/utils/utils_p.h b/src/graphs/utils/utils_p.h
new file mode 100644
index 0000000..918212e
--- /dev/null
+++ b/src/graphs/utils/utils_p.h
@@ -0,0 +1,66 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the QtGraphs API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#ifndef UTILS_P_H
+#define UTILS_P_H
+
+#include <private/graphsglobal_p.h>
+
+QT_FORWARD_DECLARE_CLASS(QLinearGradient)
+
+QT_BEGIN_NAMESPACE
+
+class Utils
+{
+public:
+ enum ParamType {
+ ParamTypeUnknown = 0,
+ ParamTypeInt,
+ ParamTypeUInt,
+ ParamTypeReal
+ };
+
+ static GLuint getNearestPowerOfTwo(GLuint value);
+ static QVector4D vectorFromColor(const QColor &color);
+ static QColor colorFromVector(const QVector3D &colorVector);
+ static QColor colorFromVector(const QVector4D &colorVector);
+ static QImage printTextToImage(const QFont &font,
+ const QString &text,
+ const QColor &bgrColor,
+ const QColor &txtColor,
+ bool labelBackground,
+ bool borders = false,
+ int maxLabelWidth = 0);
+ static QVector4D getSelection(QPoint mousepos, int height);
+ static QImage getGradientImage(QLinearGradient &gradient);
+
+ static ParamType preParseFormat(const QString &format, QString &preStr, QString &postStr,
+ int &precision, char &formatSpec);
+ static QString formatLabelSprintf(const QByteArray &format, ParamType paramType, qreal value);
+ static QString formatLabelLocalized(ParamType paramType, qreal value,
+ const QLocale &locale, const QString &preStr, const QString &postStr,
+ int precision, char formatSpec, const QByteArray &format);
+ static QString defaultLabelFormat();
+
+ static float wrapValue(float value, float min, float max);
+ static QQuaternion calculateRotation(const QVector3D &xyzRotations);
+ static bool isOpenGLES();
+ static void resolveStatics();
+
+private:
+ static ParamType mapFormatCharToParamType(char formatSpec);
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/sync.profile b/sync.profile
new file mode 100644
index 0000000..b660c66
--- /dev/null
+++ b/sync.profile
@@ -0,0 +1,15 @@
+%modules = ( # path to module name map
+ "QtGraphs" => "$basedir/src/datavisualization"
+);
+%moduleheaders = ( # restrict the module headers to those found in relative path
+);
+# Module dependencies.
+# Every module that is required to build this module should have one entry.
+# Each of the module version specifiers can take one of the following values:
+# - A specific Git revision.
+# - any git symbolic ref resolvable from the module's repository (e.g. "refs/heads/master" to track master branch)
+#
+%dependencies = (
+ "qtbase" => "",
+ "qtdeclarative" => "",
+);
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
new file mode 100644
index 0000000..3576b4c
--- /dev/null
+++ b/tests/CMakeLists.txt
@@ -0,0 +1,6 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+if(QT_BUILD_STANDALONE_TESTS)
+endif()
+qt_build_tests()
diff --git a/tests/auto/CMakeLists.txt b/tests/auto/CMakeLists.txt
new file mode 100644
index 0000000..fbb1ea2
--- /dev/null
+++ b/tests/auto/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+if(NOT ANDROID)
+ add_subdirectory(cpptest)
+endif()
+if(TARGET Qt::Quick AND NOT boot2qt)
+ add_subdirectory(qmltest)
+endif()
diff --git a/tests/auto/cpptest/CMakeLists.txt b/tests/auto/cpptest/CMakeLists.txt
new file mode 100644
index 0000000..8ade247
--- /dev/null
+++ b/tests/auto/cpptest/CMakeLists.txt
@@ -0,0 +1,29 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#add_subdirectory(q3dbars) # TODO: Needs either redoing, or fixing the code; see QTBUG-110000 & QTBUG-110001
+add_subdirectory(q3dbars-proxy)
+add_subdirectory(q3dbars-modelproxy)
+add_subdirectory(q3dbars-series)
+#add_subdirectory(q3dscatter) # TODO: Needs either redoing, or fixing the code; see QTBUG-110000 & QTBUG-110001
+add_subdirectory(q3dscatter-proxy)
+add_subdirectory(q3dscatter-modelproxy)
+add_subdirectory(q3dscatter-series)
+#add_subdirectory(q3dsurface) # TODO: Needs either redoing, or fixing the code; see QTBUG-110000 & QTBUG-110001
+add_subdirectory(q3dsurface-proxy)
+add_subdirectory(q3dsurface-modelproxy)
+add_subdirectory(q3dsurface-modelproxy-nan)
+add_subdirectory(q3dsurface-heightproxy)
+add_subdirectory(q3dsurface-series)
+add_subdirectory(q3daxis-category)
+add_subdirectory(q3daxis-logvalue)
+add_subdirectory(q3daxis-value)
+#add_subdirectory(q3dscene) # TODO: Needs either redoing, or fixing the code; see QTBUG-110000 & QTBUG-110001
+add_subdirectory(q3dscene-camera)
+add_subdirectory(q3dscene-light)
+add_subdirectory(q3dtheme)
+add_subdirectory(q3dinput)
+add_subdirectory(q3dinput-touch)
+add_subdirectory(q3dcustom)
+add_subdirectory(q3dcustom-label)
+add_subdirectory(q3dcustom-volume)
diff --git a/tests/auto/cpptest/common/cpptestutil.h b/tests/auto/cpptest/common/cpptestutil.h
new file mode 100644
index 0000000..150ec07
--- /dev/null
+++ b/tests/auto/cpptest/common/cpptestutil.h
@@ -0,0 +1,24 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef CPPTESTUTIL_H
+#define CPPTESTUTIL_H
+
+#include <QtGui/private/qguiapplication_p.h>
+#include <QtGui/qpa/qplatformintegration.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace CpptestUtil {
+
+static bool isOpenGLSupported()
+{
+ return QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::OpenGL);
+}
+
+} // CpptestUtil namespace
+
+QT_END_NAMESPACE
+
+#endif
+
diff --git a/tests/auto/cpptest/q3daxis-category/CMakeLists.txt b/tests/auto/cpptest/q3daxis-category/CMakeLists.txt
new file mode 100644
index 0000000..d8c13a9
--- /dev/null
+++ b/tests/auto/cpptest/q3daxis-category/CMakeLists.txt
@@ -0,0 +1,10 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+qt_internal_add_test(q3daxis-category
+ SOURCES
+ tst_axis.cpp
+ LIBRARIES
+ Qt::Gui
+ Qt::Graphs
+)
diff --git a/tests/auto/cpptest/q3daxis-category/tst_axis.cpp b/tests/auto/cpptest/q3daxis-category/tst_axis.cpp
new file mode 100644
index 0000000..39a3578
--- /dev/null
+++ b/tests/auto/cpptest/q3daxis-category/tst_axis.cpp
@@ -0,0 +1,116 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtTest/QtTest>
+
+#include <QtGraphs/QCategory3DAxis>
+
+class tst_axis: public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void initTestCase();
+ void cleanupTestCase();
+ void init();
+ void cleanup();
+
+ void construct();
+
+ void initialProperties();
+ void initializeProperties();
+ void invalidProperties();
+
+private:
+ QCategory3DAxis *m_axis;
+};
+
+void tst_axis::initTestCase()
+{
+}
+
+void tst_axis::cleanupTestCase()
+{
+}
+
+void tst_axis::init()
+{
+ m_axis = new QCategory3DAxis();
+}
+
+void tst_axis::cleanup()
+{
+ delete m_axis;
+}
+
+void tst_axis::construct()
+{
+ QCategory3DAxis *axis = new QCategory3DAxis();
+ QVERIFY(axis);
+ delete axis;
+}
+
+void tst_axis::initialProperties()
+{
+ QVERIFY(m_axis);
+
+ QCOMPARE(m_axis->labels().size(), 0);
+
+ // Common (from QAbstract3DAxis)
+ QCOMPARE(m_axis->isAutoAdjustRange(), true);
+ QCOMPARE(m_axis->labelAutoRotation(), 0.0f);
+ QCOMPARE(m_axis->max(), 10.0f);
+ QCOMPARE(m_axis->min(), 0.0f);
+ QCOMPARE(m_axis->orientation(), QAbstract3DAxis::AxisOrientationNone);
+ QCOMPARE(m_axis->title(), QString(""));
+ QCOMPARE(m_axis->isTitleFixed(), true);
+ QCOMPARE(m_axis->isTitleVisible(), false);
+ QCOMPARE(m_axis->type(), QAbstract3DAxis::AxisTypeCategory);
+}
+
+void tst_axis::initializeProperties()
+{
+ QVERIFY(m_axis);
+
+ m_axis->setLabels(QStringList() << "first" << "second");
+
+ QCOMPARE(m_axis->labels().size(), 2);
+ QCOMPARE(m_axis->labels().at(1), QString("second"));
+
+ // Common (from QAbstract3DAxis)
+ m_axis->setAutoAdjustRange(false);
+ m_axis->setLabelAutoRotation(15.0f);
+ m_axis->setMax(25.0f);
+ m_axis->setMin(5.0f);
+ m_axis->setTitle("title");
+ m_axis->setTitleFixed(false);
+ m_axis->setTitleVisible(true);
+
+ QCOMPARE(m_axis->isAutoAdjustRange(), false);
+ QCOMPARE(m_axis->labelAutoRotation(), 15.0f);
+ QCOMPARE(m_axis->max(), 25.0f);
+ QCOMPARE(m_axis->min(), 5.0f);
+ QCOMPARE(m_axis->title(), QString("title"));
+ QCOMPARE(m_axis->isTitleFixed(), false);
+ QCOMPARE(m_axis->isTitleVisible(), true);
+}
+
+void tst_axis::invalidProperties()
+{
+ m_axis->setLabelAutoRotation(-15.0f);
+ QCOMPARE(m_axis->labelAutoRotation(), 0.0f);
+
+ m_axis->setLabelAutoRotation(100.0f);
+ QCOMPARE(m_axis->labelAutoRotation(), 90.0f);
+
+ m_axis->setMax(-10.0f);
+ QCOMPARE(m_axis->max(), 0.0f);
+ QCOMPARE(m_axis->min(), 0.0f);
+
+ m_axis->setMin(10.0f);
+ QCOMPARE(m_axis->max(), 11.0f);
+ QCOMPARE(m_axis->min(), 10.0f);
+}
+
+QTEST_MAIN(tst_axis)
+#include "tst_axis.moc"
diff --git a/tests/auto/cpptest/q3daxis-logvalue/CMakeLists.txt b/tests/auto/cpptest/q3daxis-logvalue/CMakeLists.txt
new file mode 100644
index 0000000..589b91e
--- /dev/null
+++ b/tests/auto/cpptest/q3daxis-logvalue/CMakeLists.txt
@@ -0,0 +1,10 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+qt_internal_add_test(q3daxis-logvalue
+ SOURCES
+ tst_axis.cpp
+ LIBRARIES
+ Qt::Gui
+ Qt::Graphs
+)
diff --git a/tests/auto/cpptest/q3daxis-logvalue/tst_axis.cpp b/tests/auto/cpptest/q3daxis-logvalue/tst_axis.cpp
new file mode 100644
index 0000000..a7061a2
--- /dev/null
+++ b/tests/auto/cpptest/q3daxis-logvalue/tst_axis.cpp
@@ -0,0 +1,85 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtTest/QtTest>
+
+#include <QtGraphs/QLogValue3DAxisFormatter>
+
+class tst_axis: public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void initTestCase();
+ void cleanupTestCase();
+ void init();
+ void cleanup();
+
+ void construct();
+
+ void initialProperties();
+ void initializeProperties();
+ void invalidProperties();
+
+private:
+ QLogValue3DAxisFormatter *m_formatter;
+};
+
+void tst_axis::initTestCase()
+{
+}
+
+void tst_axis::cleanupTestCase()
+{
+}
+
+void tst_axis::init()
+{
+ m_formatter = new QLogValue3DAxisFormatter();
+}
+
+void tst_axis::cleanup()
+{
+ delete m_formatter;
+}
+
+void tst_axis::construct()
+{
+ QLogValue3DAxisFormatter *formatter = new QLogValue3DAxisFormatter();
+ QVERIFY(formatter);
+ delete formatter;
+}
+
+void tst_axis::initialProperties()
+{
+ QVERIFY(m_formatter);
+
+ QCOMPARE(m_formatter->autoSubGrid(), true);
+ QCOMPARE(m_formatter->base(), 10.0);
+ QCOMPARE(m_formatter->showEdgeLabels(), true);
+}
+
+void tst_axis::initializeProperties()
+{
+ QVERIFY(m_formatter);
+
+ m_formatter->setAutoSubGrid(false);
+ m_formatter->setBase(0.1);
+ m_formatter->setShowEdgeLabels(false);
+
+ QCOMPARE(m_formatter->autoSubGrid(), false);
+ QCOMPARE(m_formatter->base(), 0.1);
+ QCOMPARE(m_formatter->showEdgeLabels(), false);
+}
+
+void tst_axis::invalidProperties()
+{
+ m_formatter->setBase(-1.0);
+ QCOMPARE(m_formatter->base(), 10.0);
+
+ m_formatter->setBase(1.0);
+ QCOMPARE(m_formatter->base(), 10.0);
+}
+
+QTEST_MAIN(tst_axis)
+#include "tst_axis.moc"
diff --git a/tests/auto/cpptest/q3daxis-value/CMakeLists.txt b/tests/auto/cpptest/q3daxis-value/CMakeLists.txt
new file mode 100644
index 0000000..f8acdde
--- /dev/null
+++ b/tests/auto/cpptest/q3daxis-value/CMakeLists.txt
@@ -0,0 +1,10 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+qt_internal_add_test(q3daxis-value
+ SOURCES
+ tst_axis.cpp
+ LIBRARIES
+ Qt::Gui
+ Qt::Graphs
+)
diff --git a/tests/auto/cpptest/q3daxis-value/tst_axis.cpp b/tests/auto/cpptest/q3daxis-value/tst_axis.cpp
new file mode 100644
index 0000000..7b5cd96
--- /dev/null
+++ b/tests/auto/cpptest/q3daxis-value/tst_axis.cpp
@@ -0,0 +1,141 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtTest/QtTest>
+
+#include <QtGraphs/QValue3DAxis>
+
+class tst_axis: public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void initTestCase();
+ void cleanupTestCase();
+ void init();
+ void cleanup();
+
+ void construct();
+
+ void initialProperties();
+ void initializeProperties();
+ void invalidProperties();
+
+private:
+ QValue3DAxis *m_axis;
+};
+
+void tst_axis::initTestCase()
+{
+}
+
+void tst_axis::cleanupTestCase()
+{
+}
+
+void tst_axis::init()
+{
+ m_axis = new QValue3DAxis();
+}
+
+void tst_axis::cleanup()
+{
+ delete m_axis;
+}
+
+void tst_axis::construct()
+{
+ QValue3DAxis *axis = new QValue3DAxis();
+ QVERIFY(axis);
+ delete axis;
+}
+
+void tst_axis::initialProperties()
+{
+ QVERIFY(m_axis);
+
+ QCOMPARE(m_axis->labelFormat(), QString("%.2f"));
+ QCOMPARE(m_axis->reversed(), false);
+ QCOMPARE(m_axis->segmentCount(), 5);
+ QCOMPARE(m_axis->subSegmentCount(), 1);
+
+ // Common (from QAbstract3DAxis)
+ QCOMPARE(m_axis->isAutoAdjustRange(), true);
+ QCOMPARE(m_axis->labelAutoRotation(), 0.0f);
+ QCOMPARE(m_axis->labels().size(), 6);
+ QCOMPARE(m_axis->labels().at(0), QString("0.00"));
+ QCOMPARE(m_axis->labels().at(1), QString("2.00"));
+ QCOMPARE(m_axis->labels().at(2), QString("4.00"));
+ QCOMPARE(m_axis->labels().at(3), QString("6.00"));
+ QCOMPARE(m_axis->labels().at(4), QString("8.00"));
+ QCOMPARE(m_axis->labels().at(5), QString("10.00"));
+ QCOMPARE(m_axis->max(), 10.0f);
+ QCOMPARE(m_axis->min(), 0.0f);
+ QCOMPARE(m_axis->orientation(), QAbstract3DAxis::AxisOrientationNone);
+ QCOMPARE(m_axis->title(), QString(""));
+ QCOMPARE(m_axis->isTitleFixed(), true);
+ QCOMPARE(m_axis->isTitleVisible(), false);
+ QCOMPARE(m_axis->type(), QAbstract3DAxis::AxisTypeValue);
+}
+
+void tst_axis::initializeProperties()
+{
+ QVERIFY(m_axis);
+
+ m_axis->setLabelFormat("%.0fm");
+ m_axis->setReversed(true);
+ m_axis->setSegmentCount(2);
+ m_axis->setSubSegmentCount(5);
+
+ QCOMPARE(m_axis->labelFormat(), QString("%.0fm"));
+ QCOMPARE(m_axis->reversed(), true);
+ QCOMPARE(m_axis->segmentCount(), 2);
+ QCOMPARE(m_axis->subSegmentCount(), 5);
+
+ // Common (from QAbstract3DAxis)
+ m_axis->setAutoAdjustRange(false);
+ m_axis->setLabelAutoRotation(15.0f);
+ m_axis->setMax(25.0f);
+ m_axis->setMin(5.0f);
+ m_axis->setTitle("title");
+ m_axis->setTitleFixed(false);
+ m_axis->setTitleVisible(true);
+
+ QCOMPARE(m_axis->isAutoAdjustRange(), false);
+ QCOMPARE(m_axis->labelAutoRotation(), 15.0f);
+ QCOMPARE(m_axis->labels().size(), 3);
+ QCOMPARE(m_axis->labels().at(0), QString("5m"));
+ QCOMPARE(m_axis->labels().at(1), QString("15m"));
+ QCOMPARE(m_axis->labels().at(2), QString("25m"));
+ QCOMPARE(m_axis->max(), 25.0f);
+ QCOMPARE(m_axis->min(), 5.0f);
+ QCOMPARE(m_axis->title(), QString("title"));
+ QCOMPARE(m_axis->isTitleFixed(), false);
+ QCOMPARE(m_axis->isTitleVisible(), true);
+}
+
+void tst_axis::invalidProperties()
+{
+ m_axis->setSegmentCount(-1);
+ QCOMPARE(m_axis->segmentCount(), 1);
+
+ m_axis->setSubSegmentCount(-1);
+ QCOMPARE(m_axis->subSegmentCount(), 1);
+
+ m_axis->setLabelAutoRotation(-15.0f);
+ QCOMPARE(m_axis->labelAutoRotation(), 0.0f);
+
+ m_axis->setLabelAutoRotation(100.0f);
+ QCOMPARE(m_axis->labelAutoRotation(), 90.0f);
+
+ m_axis->setMax(-10.0f);
+ QCOMPARE(m_axis->max(), -10.0f);
+ QCOMPARE(m_axis->min(), -11.0f);
+
+ m_axis->setMin(10.0f);
+ QCOMPARE(m_axis->max(), 11.0f);
+ QCOMPARE(m_axis->min(), 10.0f);
+}
+
+QTEST_MAIN(tst_axis)
+#include "tst_axis.moc"
diff --git a/tests/auto/cpptest/q3dbars-modelproxy/CMakeLists.txt b/tests/auto/cpptest/q3dbars-modelproxy/CMakeLists.txt
new file mode 100644
index 0000000..7e9caab
--- /dev/null
+++ b/tests/auto/cpptest/q3dbars-modelproxy/CMakeLists.txt
@@ -0,0 +1,14 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+qt_internal_add_test(q3dbars-modelproxy
+ SOURCES
+ tst_proxy.cpp
+ INCLUDE_DIRECTORIES
+ ../common
+ LIBRARIES
+ Qt::Gui
+ Qt::GuiPrivate
+ Qt::Widgets
+ Qt::Graphs
+)
diff --git a/tests/auto/cpptest/q3dbars-modelproxy/tst_proxy.cpp b/tests/auto/cpptest/q3dbars-modelproxy/tst_proxy.cpp
new file mode 100644
index 0000000..dc69a6b
--- /dev/null
+++ b/tests/auto/cpptest/q3dbars-modelproxy/tst_proxy.cpp
@@ -0,0 +1,258 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtTest/QtTest>
+
+#include <QtGraphs/QItemModelBarDataProxy>
+#include <QtGraphs/Q3DBars>
+#include <QtWidgets/QTableWidget>
+
+#include "cpptestutil.h"
+
+class tst_proxy: public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void initTestCase();
+ void cleanupTestCase();
+ void init();
+ void cleanup();
+
+ void construct();
+
+ void initialProperties();
+ void initializeProperties();
+
+ void multiMatch();
+
+private:
+ QItemModelBarDataProxy *m_proxy;
+};
+
+void tst_proxy::initTestCase()
+{
+}
+
+void tst_proxy::cleanupTestCase()
+{
+}
+
+void tst_proxy::init()
+{
+ m_proxy = new QItemModelBarDataProxy();
+}
+
+void tst_proxy::cleanup()
+{
+ delete m_proxy;
+}
+
+void tst_proxy::construct()
+{
+ QItemModelBarDataProxy *proxy = new QItemModelBarDataProxy();
+ QVERIFY(proxy);
+ delete proxy;
+
+ QTableWidget *table = new QTableWidget();
+
+ proxy = new QItemModelBarDataProxy(table->model());
+ QVERIFY(proxy);
+ delete proxy;
+
+ proxy = new QItemModelBarDataProxy(table->model(), "val");
+ QVERIFY(proxy);
+ QCOMPARE(proxy->rowRole(), QString(""));
+ QCOMPARE(proxy->columnRole(), QString(""));
+ QCOMPARE(proxy->valueRole(), QString("val"));
+ QCOMPARE(proxy->rotationRole(), QString(""));
+ QCOMPARE(proxy->rowCategories().size(), 0);
+ QCOMPARE(proxy->columnCategories().size(), 0);
+ delete proxy;
+
+ proxy = new QItemModelBarDataProxy(table->model(), "row", "col", "val");
+ QVERIFY(proxy);
+ QCOMPARE(proxy->rowRole(), QString("row"));
+ QCOMPARE(proxy->columnRole(), QString("col"));
+ QCOMPARE(proxy->valueRole(), QString("val"));
+ QCOMPARE(proxy->rotationRole(), QString(""));
+ QCOMPARE(proxy->rowCategories().size(), 0);
+ QCOMPARE(proxy->columnCategories().size(), 0);
+ delete proxy;
+
+ proxy = new QItemModelBarDataProxy(table->model(), "row", "col", "val", "rot");
+ QVERIFY(proxy);
+ QCOMPARE(proxy->rowRole(), QString("row"));
+ QCOMPARE(proxy->columnRole(), QString("col"));
+ QCOMPARE(proxy->valueRole(), QString("val"));
+ QCOMPARE(proxy->rotationRole(), QString("rot"));
+ QCOMPARE(proxy->rowCategories().size(), 0);
+ QCOMPARE(proxy->columnCategories().size(), 0);
+ delete proxy;
+
+ proxy = new QItemModelBarDataProxy(table->model(), "row", "col", "val",
+ QStringList() << "rowCat", QStringList() << "colCat");
+ QVERIFY(proxy);
+ QCOMPARE(proxy->rowRole(), QString("row"));
+ QCOMPARE(proxy->columnRole(), QString("col"));
+ QCOMPARE(proxy->valueRole(), QString("val"));
+ QCOMPARE(proxy->rotationRole(), QString(""));
+ QCOMPARE(proxy->rowCategories().size(), 1);
+ QCOMPARE(proxy->columnCategories().size(), 1);
+ delete proxy;
+
+ proxy = new QItemModelBarDataProxy(table->model(), "row", "col", "val", "rot",
+ QStringList() << "rowCat", QStringList() << "colCat");
+ QVERIFY(proxy);
+ QCOMPARE(proxy->rowRole(), QString("row"));
+ QCOMPARE(proxy->columnRole(), QString("col"));
+ QCOMPARE(proxy->valueRole(), QString("val"));
+ QCOMPARE(proxy->rotationRole(), QString("rot"));
+ QCOMPARE(proxy->rowCategories().size(), 1);
+ QCOMPARE(proxy->columnCategories().size(), 1);
+ delete proxy;
+}
+
+void tst_proxy::initialProperties()
+{
+ QVERIFY(m_proxy);
+
+ QCOMPARE(m_proxy->autoColumnCategories(), true);
+ QCOMPARE(m_proxy->autoRowCategories(), true);
+ QCOMPARE(m_proxy->columnCategories(), QStringList());
+ QCOMPARE(m_proxy->columnRole(), QString());
+ QCOMPARE(m_proxy->columnRolePattern(), QRegularExpression());
+ QCOMPARE(m_proxy->columnRoleReplace(), QString());
+ QVERIFY(!m_proxy->itemModel());
+ QCOMPARE(m_proxy->multiMatchBehavior(), QItemModelBarDataProxy::MMBLast);
+ QCOMPARE(m_proxy->rotationRole(), QString());
+ QCOMPARE(m_proxy->rotationRolePattern(), QRegularExpression());
+ QCOMPARE(m_proxy->rotationRoleReplace(), QString());
+ QCOMPARE(m_proxy->rowCategories(), QStringList());
+ QCOMPARE(m_proxy->rowRole(), QString());
+ QCOMPARE(m_proxy->rowRolePattern(), QRegularExpression());
+ QCOMPARE(m_proxy->rowRoleReplace(), QString());
+ QCOMPARE(m_proxy->useModelCategories(), false);
+ QCOMPARE(m_proxy->valueRole(), QString());
+ QCOMPARE(m_proxy->valueRolePattern(), QRegularExpression());
+ QCOMPARE(m_proxy->valueRoleReplace(), QString());
+
+ QCOMPARE(m_proxy->columnLabels().size(), 0);
+ QCOMPARE(m_proxy->rowCount(), 0);
+ QCOMPARE(m_proxy->rowLabels().size(), 0);
+ QVERIFY(!m_proxy->series());
+
+ QCOMPARE(m_proxy->type(), QAbstractDataProxy::DataTypeBar);
+}
+
+void tst_proxy::initializeProperties()
+{
+ QVERIFY(m_proxy);
+
+ QTableWidget table;
+
+ m_proxy->setAutoColumnCategories(false);
+ m_proxy->setAutoRowCategories(false);
+ m_proxy->setColumnCategories(QStringList() << "col1" << "col2");
+ m_proxy->setColumnRole("column");
+ m_proxy->setColumnRolePattern(QRegularExpression("/^.*-(\\d\\d)$/"));
+ m_proxy->setColumnRoleReplace("\\\\1");
+ m_proxy->setItemModel(table.model());
+ m_proxy->setMultiMatchBehavior(QItemModelBarDataProxy::MMBAverage);
+ m_proxy->setRotationRole("rotation");
+ m_proxy->setRotationRolePattern(QRegularExpression("/-/"));
+ m_proxy->setRotationRoleReplace("\\\\1");
+ m_proxy->setRowCategories(QStringList() << "row1" << "row2");
+ m_proxy->setRowRole("row");
+ m_proxy->setRowRolePattern(QRegularExpression("/^(\\d\\d\\d\\d).*$/"));
+ m_proxy->setRowRoleReplace("\\\\1");
+ m_proxy->setUseModelCategories(true);
+ m_proxy->setValueRole("value");
+ m_proxy->setValueRolePattern(QRegularExpression("/-/"));
+ m_proxy->setValueRoleReplace("\\\\1");
+
+ QCOMPARE(m_proxy->autoColumnCategories(), false);
+ QCOMPARE(m_proxy->autoRowCategories(), false);
+ QCOMPARE(m_proxy->columnCategories().size(), 2);
+ QCOMPARE(m_proxy->columnRole(), QString("column"));
+ QCOMPARE(m_proxy->columnRolePattern(), QRegularExpression("/^.*-(\\d\\d)$/"));
+ QCOMPARE(m_proxy->columnRoleReplace(), QString("\\\\1"));
+ QVERIFY(m_proxy->itemModel());
+ QCOMPARE(m_proxy->multiMatchBehavior(), QItemModelBarDataProxy::MMBAverage);
+ QCOMPARE(m_proxy->rotationRole(), QString("rotation"));
+ QCOMPARE(m_proxy->rotationRolePattern(), QRegularExpression("/-/"));
+ QCOMPARE(m_proxy->rotationRoleReplace(), QString("\\\\1"));
+ QCOMPARE(m_proxy->rowCategories().size(), 2);
+ QCOMPARE(m_proxy->rowRole(), QString("row"));
+ QCOMPARE(m_proxy->rowRolePattern(), QRegularExpression("/^(\\d\\d\\d\\d).*$/"));
+ QCOMPARE(m_proxy->rowRoleReplace(), QString("\\\\1"));
+ QCOMPARE(m_proxy->useModelCategories(), true);
+ QCOMPARE(m_proxy->valueRole(), QString("value"));
+ QCOMPARE(m_proxy->valueRolePattern(), QRegularExpression("/-/"));
+ QCOMPARE(m_proxy->valueRoleReplace(), QString("\\\\1"));
+}
+
+void tst_proxy::multiMatch()
+{
+ if (!CpptestUtil::isOpenGLSupported())
+ QSKIP("OpenGL not supported on this platform");
+
+ Q3DBars graph;
+
+ QTableWidget table;
+ QStringList rows;
+ rows << "row 1" << "row 2" << "row 3";
+ QStringList columns;
+ columns << "col 1";
+ const char *values[1][3] = {{"0/0/3.5/30", "0/0/5.0/30", "0/0/6.5/30"}};
+
+ table.setRowCount(1);
+ table.setColumnCount(3);
+
+ for (int col = 0; col < columns.size(); col++) {
+ for (int row = 0; row < rows.size(); row++) {
+ QModelIndex index = table.model()->index(col, row);
+ table.model()->setData(index, values[col][row]);
+ }
+ }
+
+ m_proxy->setItemModel(table.model());
+ m_proxy->setRowRole(table.model()->roleNames().value(Qt::DisplayRole));
+ m_proxy->setColumnRole(table.model()->roleNames().value(Qt::DisplayRole));
+ m_proxy->setRowRolePattern(QRegularExpression(QStringLiteral("^(\\d*)\\/(\\d*)\\/\\d*[\\.\\,]?\\d*\\/\\d*[\\.\\,]?\\d*$")));
+ m_proxy->setRowRoleReplace(QStringLiteral("\\2"));
+ m_proxy->setValueRolePattern(QRegularExpression(QStringLiteral("^\\d*(\\/)(\\d*)\\/(\\d*[\\.\\,]?\\d*)\\/\\d*[\\.\\,]?\\d*$")));
+ m_proxy->setValueRoleReplace(QStringLiteral("\\3"));
+ m_proxy->setColumnRolePattern(QRegularExpression(QStringLiteral("^(\\d*)(\\/)(\\d*)\\/\\d*[\\.\\,]?\\d*\\/\\d*[\\.\\,]?\\d*$")));
+ m_proxy->setColumnRoleReplace(QStringLiteral("\\1"));
+ QCoreApplication::processEvents();
+
+ QBar3DSeries *series = new QBar3DSeries(m_proxy);
+
+ graph.addSeries(series);
+
+ QCoreApplication::processEvents();
+ QCOMPARE(graph.valueAxis()->max(), 6.5f);
+ m_proxy->setMultiMatchBehavior(QItemModelBarDataProxy::MMBFirst);
+ QCoreApplication::processEvents();
+ QCOMPARE(graph.valueAxis()->max(), 3.5f);
+ m_proxy->setMultiMatchBehavior(QItemModelBarDataProxy::MMBLast);
+ QCoreApplication::processEvents();
+ QCOMPARE(graph.valueAxis()->max(), 6.5f);
+ m_proxy->setMultiMatchBehavior(QItemModelBarDataProxy::MMBAverage);
+ QCoreApplication::processEvents();
+ QCOMPARE(graph.valueAxis()->max(), 5.0f);
+ m_proxy->setMultiMatchBehavior(QItemModelBarDataProxy::MMBCumulative);
+ QCoreApplication::processEvents();
+ QCOMPARE(graph.valueAxis()->max(), 15.0f);
+
+ QCOMPARE(m_proxy->columnLabels().size(), 1);
+ QCOMPARE(m_proxy->rowCount(), 1);
+ QCOMPARE(m_proxy->rowLabels().size(), 1);
+ QVERIFY(m_proxy->series());
+
+ m_proxy = 0; // Proxy gets deleted as graph gets deleted
+}
+
+QTEST_MAIN(tst_proxy)
+#include "tst_proxy.moc"
diff --git a/tests/auto/cpptest/q3dbars-proxy/CMakeLists.txt b/tests/auto/cpptest/q3dbars-proxy/CMakeLists.txt
new file mode 100644
index 0000000..874ae77
--- /dev/null
+++ b/tests/auto/cpptest/q3dbars-proxy/CMakeLists.txt
@@ -0,0 +1,10 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+qt_internal_add_test(q3dbars-proxy
+ SOURCES
+ tst_proxy.cpp
+ LIBRARIES
+ Qt::Gui
+ Qt::Graphs
+)
diff --git a/tests/auto/cpptest/q3dbars-proxy/tst_proxy.cpp b/tests/auto/cpptest/q3dbars-proxy/tst_proxy.cpp
new file mode 100644
index 0000000..af6928f
--- /dev/null
+++ b/tests/auto/cpptest/q3dbars-proxy/tst_proxy.cpp
@@ -0,0 +1,80 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtTest/QtTest>
+
+#include <QtGraphs/QBarDataProxy>
+
+class tst_proxy: public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void initTestCase();
+ void cleanupTestCase();
+ void init();
+ void cleanup();
+
+ void construct();
+
+ void initialProperties();
+ void initializeProperties();
+
+private:
+ QBarDataProxy *m_proxy;
+};
+
+void tst_proxy::initTestCase()
+{
+}
+
+void tst_proxy::cleanupTestCase()
+{
+}
+
+void tst_proxy::init()
+{
+ m_proxy = new QBarDataProxy();
+}
+
+void tst_proxy::cleanup()
+{
+ delete m_proxy;
+}
+
+void tst_proxy::construct()
+{
+ QBarDataProxy *proxy = new QBarDataProxy();
+ QVERIFY(proxy);
+ delete proxy;
+}
+
+void tst_proxy::initialProperties()
+{
+ QVERIFY(m_proxy);
+
+ QCOMPARE(m_proxy->columnLabels().size(), 0);
+ QCOMPARE(m_proxy->rowCount(), 0);
+ QCOMPARE(m_proxy->rowLabels().size(), 0);
+ QVERIFY(!m_proxy->series());
+
+ QCOMPARE(m_proxy->type(), QAbstractDataProxy::DataTypeBar);
+}
+
+void tst_proxy::initializeProperties()
+{
+ QVERIFY(m_proxy);
+
+ m_proxy->setColumnLabels(QStringList() << "1" << "2" << "3");
+ QBarDataRow *data = new QBarDataRow;
+ *data << 1.0f << 3.0f << 7.5f;
+ m_proxy->addRow(data);
+ m_proxy->setRowLabels(QStringList() << "1");
+
+ QCOMPARE(m_proxy->columnLabels().size(), 3);
+ QCOMPARE(m_proxy->rowCount(), 1);
+ QCOMPARE(m_proxy->rowLabels().size(), 1);
+}
+
+QTEST_MAIN(tst_proxy)
+#include "tst_proxy.moc"
diff --git a/tests/auto/cpptest/q3dbars-series/CMakeLists.txt b/tests/auto/cpptest/q3dbars-series/CMakeLists.txt
new file mode 100644
index 0000000..60dd226
--- /dev/null
+++ b/tests/auto/cpptest/q3dbars-series/CMakeLists.txt
@@ -0,0 +1,10 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+qt_internal_add_test(q3dbars-series
+ SOURCES
+ tst_series.cpp
+ LIBRARIES
+ Qt::Gui
+ Qt::Graphs
+)
diff --git a/tests/auto/cpptest/q3dbars-series/tst_series.cpp b/tests/auto/cpptest/q3dbars-series/tst_series.cpp
new file mode 100644
index 0000000..caa684e
--- /dev/null
+++ b/tests/auto/cpptest/q3dbars-series/tst_series.cpp
@@ -0,0 +1,165 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtTest/QtTest>
+
+#include <QtGraphs/QBar3DSeries>
+
+class tst_series: public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void initTestCase();
+ void cleanupTestCase();
+ void init();
+ void cleanup();
+
+ void construct();
+
+ void initialProperties();
+ void initializeProperties();
+ void invalidProperties();
+
+private:
+ QBar3DSeries *m_series;
+};
+
+void tst_series::initTestCase()
+{
+}
+
+void tst_series::cleanupTestCase()
+{
+}
+
+void tst_series::init()
+{
+ m_series = new QBar3DSeries();
+}
+
+void tst_series::cleanup()
+{
+ delete m_series;
+}
+
+void tst_series::construct()
+{
+ QBar3DSeries *series = new QBar3DSeries();
+ QVERIFY(series);
+ delete series;
+
+ QBarDataProxy *proxy = new QBarDataProxy();
+
+ series = new QBar3DSeries(proxy);
+ QVERIFY(series);
+ QCOMPARE(series->dataProxy(), proxy);
+ delete series;
+}
+
+void tst_series::initialProperties()
+{
+ QVERIFY(m_series);
+
+ QVERIFY(m_series->dataProxy());
+ QCOMPARE(m_series->meshAngle(), 0.0f);
+ QCOMPARE(m_series->selectedBar(), m_series->invalidSelectionPosition());
+ QCOMPARE(m_series->rowColors().size(), 0);
+
+ // Common properties
+ QCOMPARE(m_series->baseColor(), QColor(Qt::black));
+ QCOMPARE(m_series->baseGradient(), QLinearGradient());
+ QCOMPARE(m_series->colorStyle(), Q3DTheme::ColorStyleUniform);
+ QCOMPARE(m_series->itemLabel(), QString(""));
+ QCOMPARE(m_series->itemLabelFormat(), QString("@valueLabel"));
+ QCOMPARE(m_series->isItemLabelVisible(), true);
+ QCOMPARE(m_series->mesh(), QAbstract3DSeries::MeshBevelBar);
+ QCOMPARE(m_series->meshRotation(), QQuaternion(1, 0, 0, 0));
+ QCOMPARE(m_series->isMeshSmooth(), false);
+ QCOMPARE(m_series->multiHighlightColor(), QColor(Qt::black));
+ QCOMPARE(m_series->multiHighlightGradient(), QLinearGradient());
+ QCOMPARE(m_series->name(), QString(""));
+ QCOMPARE(m_series->singleHighlightColor(), QColor(Qt::black));
+ QCOMPARE(m_series->singleHighlightGradient(), QLinearGradient());
+ QCOMPARE(m_series->type(), QAbstract3DSeries::SeriesTypeBar);
+ QCOMPARE(m_series->userDefinedMesh(), QString(""));
+ QCOMPARE(m_series->isVisible(), true);
+}
+
+void tst_series::initializeProperties()
+{
+ QVERIFY(m_series);
+
+ m_series->setDataProxy(new QBarDataProxy());
+ m_series->setMeshAngle(15.0f);
+ m_series->setSelectedBar(QPoint(0, 0));
+
+ QCOMPARE(m_series->meshAngle(), 15.0f);
+ QCOMPARE(m_series->selectedBar(), QPoint(0, 0));
+
+ QLinearGradient gradient1;
+ gradient1.setColorAt(0.0, Qt::red);
+ gradient1.setColorAt(1.0, Qt::blue);
+ QLinearGradient gradient2;
+ gradient2.setColorAt(0.0, Qt::yellow);
+ gradient2.setColorAt(1.0, Qt::green);
+ QLinearGradient gradient3;
+ gradient3.setColorAt(0.0, Qt::white);
+ gradient3.setColorAt(1.0, Qt::gray);
+
+ QList<QColor> rowColors;
+ rowColors.append(QColor(Qt::green));
+ rowColors.append(QColor(Qt::blue));
+ rowColors.append(QColor(Qt::red));
+
+ m_series->setRowColors(rowColors);
+
+ // Common properties
+ m_series->setBaseColor(QColor(Qt::blue));
+ m_series->setBaseGradient(gradient1);
+ m_series->setColorStyle(Q3DTheme::ColorStyleRangeGradient);
+ m_series->setItemLabelFormat("%f");
+ m_series->setItemLabelVisible(false);
+ m_series->setMesh(QAbstract3DSeries::MeshCone);
+ m_series->setMeshSmooth(true);
+ m_series->setMultiHighlightColor(QColor(Qt::green));
+ m_series->setMultiHighlightGradient(gradient2);
+ m_series->setName("name");
+ m_series->setSingleHighlightColor(QColor(Qt::red));
+ m_series->setSingleHighlightGradient(gradient3);
+ m_series->setUserDefinedMesh(":/customitem.obj");
+ m_series->setVisible(false);
+
+ QCOMPARE(m_series->baseColor(), QColor(Qt::blue));
+ QCOMPARE(m_series->baseGradient(), gradient1);
+ QCOMPARE(m_series->baseGradient().stops().at(0).second, QColor(Qt::red));
+ QCOMPARE(m_series->colorStyle(), Q3DTheme::ColorStyleRangeGradient);
+ QCOMPARE(m_series->itemLabelFormat(), QString("%f"));
+ QCOMPARE(m_series->isItemLabelVisible(), false);
+ QCOMPARE(m_series->mesh(), QAbstract3DSeries::MeshCone);
+ QCOMPARE(m_series->isMeshSmooth(), true);
+ QCOMPARE(m_series->multiHighlightColor(), QColor(Qt::green));
+ QCOMPARE(m_series->multiHighlightGradient(), gradient2);
+ QCOMPARE(m_series->multiHighlightGradient().stops().at(0).second, QColor(Qt::yellow));
+ QCOMPARE(m_series->name(), QString("name"));
+ QCOMPARE(m_series->singleHighlightColor(), QColor(Qt::red));
+ QCOMPARE(m_series->singleHighlightGradient(), gradient3);
+ QCOMPARE(m_series->singleHighlightGradient().stops().at(0).second, QColor(Qt::white));
+ QCOMPARE(m_series->userDefinedMesh(), QString(":/customitem.obj"));
+ QCOMPARE(m_series->isVisible(), false);
+
+ QCOMPARE(m_series->rowColors().size(), 3);
+ QCOMPARE(m_series->rowColors().at(0), QColor(Qt::green));
+ QCOMPARE(m_series->rowColors().at(1), QColor(Qt::blue));
+ QCOMPARE(m_series->rowColors().at(2), QColor(Qt::red));
+}
+
+void tst_series::invalidProperties()
+{
+ m_series->setMesh(QAbstract3DSeries::MeshMinimal);
+
+ QCOMPARE(m_series->mesh(), QAbstract3DSeries::MeshBevelBar);
+}
+
+QTEST_MAIN(tst_series)
+#include "tst_series.moc"
diff --git a/tests/auto/cpptest/q3dbars/CMakeLists.txt b/tests/auto/cpptest/q3dbars/CMakeLists.txt
new file mode 100644
index 0000000..9dd0e6b
--- /dev/null
+++ b/tests/auto/cpptest/q3dbars/CMakeLists.txt
@@ -0,0 +1,13 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+qt_internal_add_test(q3dbars
+ SOURCES
+ tst_bars.cpp
+ INCLUDE_DIRECTORIES
+ ../common
+ LIBRARIES
+ Qt::Gui
+ Qt::GuiPrivate
+ Qt::Graphs
+)
diff --git a/tests/auto/cpptest/q3dbars/tst_bars.cpp b/tests/auto/cpptest/q3dbars/tst_bars.cpp
new file mode 100644
index 0000000..c3d7ab9
--- /dev/null
+++ b/tests/auto/cpptest/q3dbars/tst_bars.cpp
@@ -0,0 +1,409 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtTest/QtTest>
+
+#include <QtGraphs/Q3DBars>
+#include <QtGraphs/QCustom3DItem>
+#include <QtGraphs/Q3DInputHandler>
+#include <QtGraphs/QTouch3DInputHandler>
+
+#include "cpptestutil.h"
+
+class tst_bars: public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void initTestCase();
+ void cleanupTestCase();
+ void init();
+ void cleanup();
+
+ void construct();
+
+ void initialProperties();
+ void initializeProperties();
+ void invalidProperties();
+
+ void addSeries();
+ void addMultipleSeries();
+ void selectSeries();
+ void removeSeries();
+ void removeMultipleSeries();
+ void hasSeries();
+
+ // The following tests are not required for scatter or surface, as they are handled identically
+ void addInputHandler();
+ void removeInputHandler();
+
+ void addTheme();
+ void removeTheme();
+
+ void addCustomItem();
+ void removeCustomItem();
+
+ void renderToImage();
+
+private:
+ Q3DBars *m_graph;
+};
+
+QBar3DSeries *newSeries()
+{
+ QBar3DSeries *series = new QBar3DSeries;
+ QBarDataRow *data = new QBarDataRow;
+ *data << -1.0f << 3.0f << 7.5f << 5.0f << 2.2f;
+ series->dataProxy()->addRow(data);
+ return series;
+}
+
+void tst_bars::initTestCase()
+{
+ if (!CpptestUtil::isOpenGLSupported())
+ QSKIP("OpenGL not supported on this platform");
+}
+
+void tst_bars::cleanupTestCase()
+{
+}
+
+void tst_bars::init()
+{
+ m_graph = new Q3DBars();
+}
+
+void tst_bars::cleanup()
+{
+ delete m_graph;
+}
+
+void tst_bars::construct()
+{
+ Q3DBars *graph = new Q3DBars();
+ QVERIFY(graph);
+ delete graph;
+
+ QSurfaceFormat format;
+ graph = new Q3DBars(&format);
+ QVERIFY(graph);
+ delete graph;
+}
+
+void tst_bars::initialProperties()
+{
+ QVERIFY(m_graph);
+ QCOMPARE(m_graph->isMultiSeriesUniform(), false);
+ QCOMPARE(m_graph->barThickness(), 1.0);
+ QCOMPARE(m_graph->barSpacing(), QSizeF(1.0f, 1.0f));
+ QCOMPARE(m_graph->barSeriesMargin(), QSizeF(0.0f, 0.0f));
+ QCOMPARE(m_graph->isBarSpacingRelative(), true);
+ QCOMPARE(m_graph->seriesList().size(), 0);
+ QVERIFY(!m_graph->selectedSeries());
+ QVERIFY(!m_graph->primarySeries());
+ QCOMPARE(m_graph->floorLevel(), 0.0);
+ QCOMPARE(m_graph->columnAxis()->orientation(), QAbstract3DAxis::AxisOrientationX);
+ QCOMPARE(m_graph->valueAxis()->orientation(), QAbstract3DAxis::AxisOrientationY);
+ QCOMPARE(m_graph->rowAxis()->orientation(), QAbstract3DAxis::AxisOrientationZ);
+
+ // Common properties
+ QCOMPARE(m_graph->activeTheme()->type(), Q3DTheme::ThemeQt);
+ QCOMPARE(m_graph->selectionMode(), QAbstract3DGraph::SelectionItem);
+ QCOMPARE(m_graph->shadowQuality(), QAbstract3DGraph::ShadowQualityMedium);
+ QVERIFY(m_graph->scene());
+ QCOMPARE(m_graph->measureFps(), false);
+ QCOMPARE(m_graph->isOrthoProjection(), false);
+ QCOMPARE(m_graph->selectedElement(), QAbstract3DGraph::ElementNone);
+ QCOMPARE(m_graph->aspectRatio(), 2.0);
+ QCOMPARE(m_graph->optimizationHints(), QAbstract3DGraph::OptimizationDefault);
+ QCOMPARE(m_graph->isPolar(), false);
+ QCOMPARE(m_graph->radialLabelOffset(), 1.0);
+ QCOMPARE(m_graph->horizontalAspectRatio(), 0.0);
+ QCOMPARE(m_graph->isReflection(), false);
+ QCOMPARE(m_graph->reflectivity(), 0.5);
+ QCOMPARE(m_graph->locale(), QLocale("C"));
+ QCOMPARE(m_graph->queriedGraphPosition(), QVector3D(0, 0, 0));
+ QCOMPARE(m_graph->margin(), -1.0);
+}
+
+void tst_bars::initializeProperties()
+{
+ QVERIFY(m_graph);
+
+ m_graph->setMultiSeriesUniform(true);
+ m_graph->setBarThickness(0.2f);
+ m_graph->setBarSpacing(QSizeF(0.1f, 0.1f));
+ m_graph->setBarSeriesMargin(QSizeF(0.3f, 0.3f));
+ m_graph->setBarSpacingRelative(false);
+ m_graph->setFloorLevel(1.0f);
+
+ QCOMPARE(m_graph->isMultiSeriesUniform(), true);
+ QCOMPARE(m_graph->barThickness(), 0.2f);
+ QCOMPARE(m_graph->barSpacing(), QSizeF(0.1f, 0.1f));
+ QCOMPARE(m_graph->barSeriesMargin(), QSizeF(0.3f, 0.3f));
+ QCOMPARE(m_graph->isBarSpacingRelative(), false);
+ QCOMPARE(m_graph->floorLevel(), 1.0f);
+
+ Q3DTheme *theme = new Q3DTheme(Q3DTheme::ThemeDigia);
+ m_graph->setActiveTheme(theme);
+ m_graph->setSelectionMode(QAbstract3DGraph::SelectionItem | QAbstract3DGraph::SelectionRow | QAbstract3DGraph::SelectionSlice);
+ m_graph->setShadowQuality(QAbstract3DGraph::ShadowQualitySoftHigh);
+ QCOMPARE(m_graph->shadowQuality(), QAbstract3DGraph::ShadowQualitySoftHigh);
+ m_graph->setMeasureFps(true);
+ m_graph->setOrthoProjection(true);
+ m_graph->setAspectRatio(1.0);
+ m_graph->setOptimizationHints(QAbstract3DGraph::OptimizationStatic);
+ m_graph->setPolar(true);
+ m_graph->setRadialLabelOffset(0.1f);
+ m_graph->setHorizontalAspectRatio(1.0);
+ m_graph->setReflection(true);
+ m_graph->setReflectivity(0.1);
+ m_graph->setLocale(QLocale("FI"));
+ m_graph->setMargin(1.0);
+
+ QCOMPARE(m_graph->activeTheme()->type(), Q3DTheme::ThemeDigia);
+ QCOMPARE(m_graph->selectionMode(), QAbstract3DGraph::SelectionItem | QAbstract3DGraph::SelectionRow | QAbstract3DGraph::SelectionSlice);
+ QCOMPARE(m_graph->shadowQuality(), QAbstract3DGraph::ShadowQualityNone); // Ortho disables shadows
+ QCOMPARE(m_graph->measureFps(), true);
+ QCOMPARE(m_graph->isOrthoProjection(), true);
+ QCOMPARE(m_graph->aspectRatio(), 1.0);
+ QCOMPARE(m_graph->optimizationHints(), QAbstract3DGraph::OptimizationStatic);
+ QCOMPARE(m_graph->isPolar(), true);
+ QCOMPARE(m_graph->radialLabelOffset(), 0.1f);
+ QCOMPARE(m_graph->horizontalAspectRatio(), 1.0);
+ QCOMPARE(m_graph->isReflection(), true);
+ QCOMPARE(m_graph->reflectivity(), 0.1);
+ QCOMPARE(m_graph->locale(), QLocale("FI"));
+ QCOMPARE(m_graph->margin(), 1.0);
+}
+
+void tst_bars::invalidProperties()
+{
+ m_graph->setSelectionMode(QAbstract3DGraph::SelectionColumn | QAbstract3DGraph::SelectionRow | QAbstract3DGraph::SelectionSlice);
+ m_graph->setAspectRatio(-1.0);
+ m_graph->setHorizontalAspectRatio(-1.0);
+ m_graph->setReflectivity(-1.0);
+ m_graph->setLocale(QLocale("XX"));
+
+ QCOMPARE(m_graph->selectionMode(), QAbstract3DGraph::SelectionItem);
+ QCOMPARE(m_graph->aspectRatio(), -1.0/*2.0*/); // TODO: Fix once QTRD-3367 is done
+ QCOMPARE(m_graph->horizontalAspectRatio(), -1.0/*0.0*/); // TODO: Fix once QTRD-3367 is done
+ QCOMPARE(m_graph->reflectivity(), -1.0/*0.5*/); // TODO: Fix once QTRD-3367 is done
+ QCOMPARE(m_graph->locale(), QLocale("C"));
+}
+
+void tst_bars::addSeries()
+{
+ QBar3DSeries *series = newSeries();
+
+ m_graph->addSeries(series);
+
+ QCOMPARE(m_graph->seriesList().size(), 1);
+ QVERIFY(!m_graph->selectedSeries());
+ QCOMPARE(m_graph->primarySeries(), series);
+}
+
+void tst_bars::addMultipleSeries()
+{
+ QBar3DSeries *series = newSeries();
+ QBar3DSeries *series2 = newSeries();
+ QBar3DSeries *series3 = newSeries();
+
+ m_graph->addSeries(series);
+ m_graph->addSeries(series2);
+ m_graph->addSeries(series3);
+
+ QCOMPARE(m_graph->seriesList().size(), 3);
+ QCOMPARE(m_graph->primarySeries(), series);
+
+ m_graph->setPrimarySeries(series2);
+
+ QCOMPARE(m_graph->primarySeries(), series2);
+}
+
+void tst_bars::selectSeries()
+{
+ QBar3DSeries *series = newSeries();
+
+ m_graph->addSeries(series);
+ m_graph->primarySeries()->setSelectedBar(QPoint(0, 0));
+
+ QCOMPARE(m_graph->seriesList().size(), 1);
+ QCOMPARE(m_graph->selectedSeries(), series);
+
+ m_graph->clearSelection();
+ QVERIFY(!m_graph->selectedSeries());
+}
+
+void tst_bars::removeSeries()
+{
+ QBar3DSeries *series = newSeries();
+
+ m_graph->addSeries(series);
+ m_graph->removeSeries(series);
+ QCOMPARE(m_graph->seriesList().size(), 0);
+ delete series;
+}
+
+void tst_bars::removeMultipleSeries()
+{
+ QBar3DSeries *series = newSeries();
+ QBar3DSeries *series2 = newSeries();
+ QBar3DSeries *series3 = newSeries();
+
+ m_graph->addSeries(series);
+ m_graph->addSeries(series2);
+ m_graph->addSeries(series3);
+
+ m_graph->primarySeries()->setSelectedBar(QPoint(0, 0));
+ QCOMPARE(m_graph->selectedSeries(), series);
+
+ m_graph->removeSeries(series);
+ QCOMPARE(m_graph->seriesList().size(), 2);
+ QCOMPARE(m_graph->primarySeries(), series2);
+ QVERIFY(!m_graph->selectedSeries());
+
+ m_graph->removeSeries(series2);
+ QCOMPARE(m_graph->seriesList().size(), 1);
+ QCOMPARE(m_graph->primarySeries(), series3);
+
+ m_graph->removeSeries(series3);
+ QCOMPARE(m_graph->seriesList().size(), 0);
+
+ delete series;
+ delete series2;
+ delete series3;
+}
+
+void tst_bars::hasSeries()
+{
+ QBar3DSeries *series1 = newSeries();
+ m_graph->addSeries(series1);
+ QCOMPARE(m_graph->hasSeries(series1), true);
+ QBar3DSeries *series2 = newSeries();
+ QCOMPARE(m_graph->hasSeries(series2), false);
+}
+
+// The following tests are not required for scatter or surface, as they are handled identically
+void tst_bars::addInputHandler()
+{
+ Q3DInputHandler *handler = new Q3DInputHandler();
+ QTouch3DInputHandler *handler2 = new QTouch3DInputHandler();
+ QAbstract3DInputHandler *initialHandler = m_graph->activeInputHandler();
+
+ m_graph->addInputHandler(handler);
+ m_graph->addInputHandler(handler2);
+
+ QCOMPARE(m_graph->inputHandlers().size(), 3); // Default, as it is still active, plus added ones
+ QCOMPARE(m_graph->activeInputHandler(), initialHandler);
+ m_graph->setActiveInputHandler(handler2);
+ QCOMPARE(m_graph->activeInputHandler(), handler2);
+
+ m_graph->setActiveInputHandler(NULL);
+ QVERIFY(!m_graph->activeInputHandler());
+ QCOMPARE(m_graph->inputHandlers().size(), 2);
+}
+
+void tst_bars::removeInputHandler()
+{
+ Q3DInputHandler *handler = new Q3DInputHandler();
+ QTouch3DInputHandler *handler2 = new QTouch3DInputHandler();
+
+ m_graph->addInputHandler(handler);
+ m_graph->addInputHandler(handler2);
+
+ m_graph->setActiveInputHandler(handler2);
+ QCOMPARE(m_graph->inputHandlers().size(), 2); // Default handler removed by previous call
+ QCOMPARE(m_graph->activeInputHandler(), handler2);
+ m_graph->releaseInputHandler(handler2);
+ QCOMPARE(m_graph->inputHandlers().size(), 1);
+ m_graph->releaseInputHandler(handler);
+ QCOMPARE(m_graph->inputHandlers().size(), 0);
+
+ delete handler2;
+ delete handler;
+}
+
+void tst_bars::addTheme()
+{
+ Q3DTheme *theme = new Q3DTheme(Q3DTheme::ThemeDigia);
+ Q3DTheme *theme2 = new Q3DTheme();
+ Q3DTheme *initialTheme = m_graph->activeTheme();
+ m_graph->addTheme(theme);
+ m_graph->addTheme(theme2);
+
+ QCOMPARE(m_graph->themes().size(), 3); // Default, plus added ones
+ QCOMPARE(m_graph->activeTheme(), initialTheme);
+ m_graph->setActiveTheme(theme2);
+ QCOMPARE(m_graph->activeTheme(), theme2);
+}
+
+void tst_bars::removeTheme()
+{
+ Q3DTheme *theme = new Q3DTheme(Q3DTheme::ThemeDigia);
+ Q3DTheme *theme2 = new Q3DTheme();
+ m_graph->addTheme(theme);
+ m_graph->addTheme(theme2);
+
+ m_graph->setActiveTheme(theme2);
+ QCOMPARE(m_graph->activeTheme(), theme2);
+ m_graph->releaseTheme(theme2);
+ QCOMPARE(m_graph->themes().size(), 2);
+ m_graph->releaseTheme(theme);
+ QCOMPARE(m_graph->themes().size(), 1); // Default theme remains
+
+ delete theme2;
+ delete theme;
+}
+
+void tst_bars::addCustomItem()
+{
+ QCustom3DItem *item = new QCustom3DItem();
+ QCustom3DItem *item2 = new QCustom3DItem();
+
+ m_graph->addCustomItem(item);
+ QCOMPARE(m_graph->customItems().size(), 1);
+ m_graph->addCustomItem(item2);
+ QCOMPARE(m_graph->customItems().size(), 2);
+}
+
+void tst_bars::removeCustomItem()
+{
+ QCustom3DItem *item = new QCustom3DItem();
+ QCustom3DItem *item2 = new QCustom3DItem();
+ QCustom3DItem *item3 = new QCustom3DItem();
+ item3->setPosition(QVector3D(1, 1, 1));
+
+ m_graph->addCustomItem(item);
+ m_graph->addCustomItem(item2);
+ m_graph->addCustomItem(item3);
+
+ m_graph->releaseCustomItem(item);
+ QCOMPARE(m_graph->customItems().size(), 2);
+ m_graph->removeCustomItem(item2);
+ QCOMPARE(m_graph->customItems().size(), 1);
+ m_graph->addCustomItem(item);
+ m_graph->removeCustomItemAt(QVector3D(1, 1, 1));
+ QCOMPARE(m_graph->customItems().size(), 1);
+ m_graph->removeCustomItems();
+ QCOMPARE(m_graph->customItems().size(), 0);
+}
+
+void tst_bars::renderToImage()
+{
+ /* Crashes on some CI machines using Mesa, but can't repro locally, so commented out for now.
+ m_graph->addSeries(newSeries());
+
+ QImage image = m_graph->renderToImage();
+ QCOMPARE(image.size(), m_graph->size());
+
+ image = m_graph->renderToImage(8);
+ QCOMPARE(image.size(), m_graph->size());
+
+ image = m_graph->renderToImage(4, QSize(300, 300));
+ QCOMPARE(image.size(), QSize(300, 300));
+ */
+}
+
+QTEST_MAIN(tst_bars)
+#include "tst_bars.moc"
diff --git a/tests/auto/cpptest/q3dcustom-label/CMakeLists.txt b/tests/auto/cpptest/q3dcustom-label/CMakeLists.txt
new file mode 100644
index 0000000..609a2ea
--- /dev/null
+++ b/tests/auto/cpptest/q3dcustom-label/CMakeLists.txt
@@ -0,0 +1,13 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+qt_internal_add_test(q3dcustom-label
+ SOURCES
+ tst_custom.cpp
+ INCLUDE_DIRECTORIES
+ ../common
+ LIBRARIES
+ Qt::Gui
+ Qt::GuiPrivate
+ Qt::Graphs
+)
diff --git a/tests/auto/cpptest/q3dcustom-label/tst_custom.cpp b/tests/auto/cpptest/q3dcustom-label/tst_custom.cpp
new file mode 100644
index 0000000..63c8a7f
--- /dev/null
+++ b/tests/auto/cpptest/q3dcustom-label/tst_custom.cpp
@@ -0,0 +1,145 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtTest/QtTest>
+
+#include <QtGraphs/QCustom3DLabel>
+
+#include "cpptestutil.h"
+
+class tst_custom: public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void initTestCase();
+ void cleanupTestCase();
+ void init();
+ void cleanup();
+
+ void construct();
+
+ void initialProperties();
+ void initializeProperties();
+ void invalidProperties();
+
+private:
+ QCustom3DLabel *m_custom;
+};
+
+void tst_custom::initTestCase()
+{
+ if (!CpptestUtil::isOpenGLSupported())
+ QSKIP("OpenGL not supported on this platform");
+}
+
+void tst_custom::cleanupTestCase()
+{
+}
+
+void tst_custom::init()
+{
+ m_custom = new QCustom3DLabel();
+}
+
+void tst_custom::cleanup()
+{
+ delete m_custom;
+}
+
+void tst_custom::construct()
+{
+ QCustom3DLabel *custom = new QCustom3DLabel();
+ QVERIFY(custom);
+ delete custom;
+
+ custom = new QCustom3DLabel("label", QFont("Times New Roman", 10.0), QVector3D(1.0f, 1.0f, 1.0f),
+ QVector3D(1.0f, 1.0f, 1.0f), QQuaternion(1.0f, 1.0f, 10.0f, 100.0f));
+ QVERIFY(custom);
+ QCOMPARE(custom->backgroundColor(), QColor(Qt::gray));
+ QCOMPARE(custom->isBackgroundEnabled(), true);
+ QCOMPARE(custom->isBorderEnabled(), true);
+ QCOMPARE(custom->isFacingCamera(), false);
+ QCOMPARE(custom->font(), QFont("Times New Roman", 10));
+ QCOMPARE(custom->text(), QString("label"));
+ QCOMPARE(custom->textColor(), QColor(Qt::white));
+ QCOMPARE(custom->meshFile(), QString(":/defaultMeshes/plane"));
+ QCOMPARE(custom->position(), QVector3D(1.0f, 1.0f, 1.0f));
+ QCOMPARE(custom->isPositionAbsolute(), false);
+ QCOMPARE(custom->rotation(), QQuaternion(1.0f, 1.0f, 10.0f, 100.0f));
+ QCOMPARE(custom->scaling(), QVector3D(1.0f, 1.0f, 1.0f));
+ QCOMPARE(custom->isScalingAbsolute(), true);
+ QCOMPARE(custom->isShadowCasting(), false);
+ QCOMPARE(custom->textureFile(), QString());
+ QCOMPARE(custom->isVisible(), true);
+ delete custom;
+}
+
+void tst_custom::initialProperties()
+{
+ QVERIFY(m_custom);
+
+ QCOMPARE(m_custom->backgroundColor(), QColor(Qt::gray));
+ QCOMPARE(m_custom->isBackgroundEnabled(), true);
+ QCOMPARE(m_custom->isBorderEnabled(), true);
+ QCOMPARE(m_custom->isFacingCamera(), false);
+ QCOMPARE(m_custom->font(), QFont("Arial", 20));
+ QCOMPARE(m_custom->text(), QString());
+ QCOMPARE(m_custom->textColor(), QColor(Qt::white));
+
+ // Common (from QCustom3DItem)
+ QCOMPARE(m_custom->meshFile(), QString(":/defaultMeshes/plane"));
+ QCOMPARE(m_custom->position(), QVector3D());
+ QCOMPARE(m_custom->isPositionAbsolute(), false);
+ QCOMPARE(m_custom->rotation(), QQuaternion());
+ QCOMPARE(m_custom->scaling(), QVector3D(0.1f, 0.1f, 0.1f));
+ QCOMPARE(m_custom->isScalingAbsolute(), true);
+ QCOMPARE(m_custom->isShadowCasting(), false);
+ QCOMPARE(m_custom->textureFile(), QString());
+ QCOMPARE(m_custom->isVisible(), true);
+}
+
+void tst_custom::initializeProperties()
+{
+ QVERIFY(m_custom);
+
+ m_custom->setBackgroundColor(QColor(Qt::red));
+ m_custom->setBackgroundEnabled(false);
+ m_custom->setBorderEnabled(false);
+ m_custom->setFacingCamera(true);
+ m_custom->setFont(QFont("Times New Roman", 10));
+ m_custom->setText(QString("This is a Custom Label"));
+ m_custom->setTextColor(QColor(Qt::blue));
+
+ QCOMPARE(m_custom->backgroundColor(), QColor(Qt::red));
+ QCOMPARE(m_custom->isBackgroundEnabled(), false);
+ QCOMPARE(m_custom->isBorderEnabled(), false);
+ QCOMPARE(m_custom->isFacingCamera(), true);
+ QCOMPARE(m_custom->font(), QFont("Times New Roman", 10));
+ QCOMPARE(m_custom->text(), QString("This is a Custom Label"));
+ QCOMPARE(m_custom->textColor(), QColor(Qt::blue));
+
+ // Common (from QCustom3DItem)
+ m_custom->setPosition(QVector3D(1.0f, 1.0f, 1.0f));
+ m_custom->setPositionAbsolute(true);
+ m_custom->setRotation(QQuaternion(1.0f, 1.0f, 10.0f, 100.0f));
+ m_custom->setScaling(QVector3D(1.0f, 1.0f, 1.0f));
+ m_custom->setShadowCasting(true);
+ m_custom->setVisible(false);
+
+ QCOMPARE(m_custom->position(), QVector3D(1.0f, 1.0f, 1.0f));
+ QCOMPARE(m_custom->isPositionAbsolute(), true);
+ QCOMPARE(m_custom->rotation(), QQuaternion(1.0f, 1.0f, 10.0f, 100.0f));
+ QCOMPARE(m_custom->scaling(), QVector3D(1.0f, 1.0f, 1.0f));
+ QCOMPARE(m_custom->isShadowCasting(), true);
+ QCOMPARE(m_custom->isVisible(), false);
+}
+
+void tst_custom::invalidProperties()
+{
+ m_custom->setScalingAbsolute(false);
+ QCOMPARE(m_custom->isScalingAbsolute(), true);
+}
+
+QTEST_MAIN(tst_custom)
+#include "tst_custom.moc"
diff --git a/tests/auto/cpptest/q3dcustom-volume/CMakeLists.txt b/tests/auto/cpptest/q3dcustom-volume/CMakeLists.txt
new file mode 100644
index 0000000..70c7b3c
--- /dev/null
+++ b/tests/auto/cpptest/q3dcustom-volume/CMakeLists.txt
@@ -0,0 +1,10 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+qt_internal_add_test(q3dcustom-volume
+ SOURCES
+ tst_custom.cpp
+ LIBRARIES
+ Qt::Gui
+ Qt::Graphs
+)
diff --git a/tests/auto/cpptest/q3dcustom-volume/tst_custom.cpp b/tests/auto/cpptest/q3dcustom-volume/tst_custom.cpp
new file mode 100644
index 0000000..6b5fd0e
--- /dev/null
+++ b/tests/auto/cpptest/q3dcustom-volume/tst_custom.cpp
@@ -0,0 +1,187 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtTest/QtTest>
+
+#include <QtGraphs/QCustom3DVolume>
+
+class tst_custom: public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void initTestCase();
+ void cleanupTestCase();
+ void init();
+ void cleanup();
+
+ void construct();
+
+ void initialProperties();
+ void initializeProperties();
+ void invalidProperties();
+
+private:
+ QCustom3DVolume *m_custom;
+};
+
+void tst_custom::initTestCase()
+{
+}
+
+void tst_custom::cleanupTestCase()
+{
+}
+
+void tst_custom::init()
+{
+ m_custom = new QCustom3DVolume();
+}
+
+void tst_custom::cleanup()
+{
+ delete m_custom;
+}
+
+void tst_custom::construct()
+{
+ QCustom3DVolume *custom = new QCustom3DVolume();
+ QVERIFY(custom);
+ delete custom;
+
+ QList<uchar> *tdata = new QList<uchar>(1000);
+
+ QList<QRgb> table;
+ table << QRgb(0xff00ff) << QRgb(0x00ff00);
+
+ custom = new QCustom3DVolume(QVector3D(1.0f, 1.0f, 1.0f), QVector3D(1.0f, 1.0f, 1.0f),
+ QQuaternion(1.0f, 1.0f, 10.0f, 100.0f), 10, 10, 10,
+ tdata, QImage::Format_ARGB32, table);
+ QVERIFY(custom);
+ QCOMPARE(custom->alphaMultiplier(), 1.0f);
+ QCOMPARE(custom->drawSliceFrames(), false);
+ QCOMPARE(custom->drawSliceFrames(), false);
+ QCOMPARE(custom->preserveOpacity(), true);
+ QCOMPARE(custom->sliceFrameColor(), QColor(Qt::black));
+ QCOMPARE(custom->sliceFrameGaps(), QVector3D(0.01f, 0.01f, 0.01f));
+ QCOMPARE(custom->sliceFrameThicknesses(), QVector3D(0.01f, 0.01f, 0.01f));
+ QCOMPARE(custom->sliceFrameWidths(), QVector3D(0.01f, 0.01f, 0.01f));
+ QCOMPARE(custom->sliceIndexX(), -1);
+ QCOMPARE(custom->sliceIndexY(), -1);
+ QCOMPARE(custom->sliceIndexZ(), -1);
+ QCOMPARE(custom->useHighDefShader(), true);
+ QCOMPARE(custom->textureData()->size(), 1000);
+ QCOMPARE(custom->textureDataWidth(), 40);
+ QCOMPARE(custom->textureFormat(), QImage::Format_ARGB32);
+ QCOMPARE(custom->textureHeight(), 10);
+ QCOMPARE(custom->textureWidth(), 10);
+ QCOMPARE(custom->textureDepth(), 10);
+ QCOMPARE(custom->meshFile(), QString(":/defaultMeshes/barFull"));
+ QCOMPARE(custom->position(), QVector3D(1.0f, 1.0f, 1.0f));
+ QCOMPARE(custom->isPositionAbsolute(), false);
+ QCOMPARE(custom->rotation(), QQuaternion(1.0f, 1.0f, 10.0f, 100.0f));
+ QCOMPARE(custom->scaling(), QVector3D(1.0f, 1.0f, 1.0f));
+ QCOMPARE(custom->isScalingAbsolute(), true);
+ QCOMPARE(custom->isShadowCasting(), false);
+ QCOMPARE(custom->textureFile(), QString());
+ QCOMPARE(custom->isVisible(), true);
+ delete custom;
+}
+
+void tst_custom::initialProperties()
+{
+ QVERIFY(m_custom);
+
+ QCOMPARE(m_custom->alphaMultiplier(), 1.0f);
+ QCOMPARE(m_custom->drawSliceFrames(), false);
+ QCOMPARE(m_custom->drawSliceFrames(), false);
+ QCOMPARE(m_custom->preserveOpacity(), true);
+ QCOMPARE(m_custom->sliceFrameColor(), QColor(Qt::black));
+ QCOMPARE(m_custom->sliceFrameGaps(), QVector3D(0.01f, 0.01f, 0.01f));
+ QCOMPARE(m_custom->sliceFrameThicknesses(), QVector3D(0.01f, 0.01f, 0.01f));
+ QCOMPARE(m_custom->sliceFrameWidths(), QVector3D(0.01f, 0.01f, 0.01f));
+ QCOMPARE(m_custom->sliceIndexX(), -1);
+ QCOMPARE(m_custom->sliceIndexY(), -1);
+ QCOMPARE(m_custom->sliceIndexZ(), -1);
+ QCOMPARE(m_custom->useHighDefShader(), true);
+
+ // Common (from QCustom3DVolume)
+ QCOMPARE(m_custom->meshFile(), QString(":/defaultMeshes/barFull"));
+ QCOMPARE(m_custom->position(), QVector3D());
+ QCOMPARE(m_custom->isPositionAbsolute(), false);
+ QCOMPARE(m_custom->rotation(), QQuaternion());
+ QCOMPARE(m_custom->scaling(), QVector3D(0.1f, 0.1f, 0.1f));
+ QCOMPARE(m_custom->isScalingAbsolute(), true);
+ QCOMPARE(m_custom->isShadowCasting(), true);
+ QCOMPARE(m_custom->textureFile(), QString());
+ QCOMPARE(m_custom->isVisible(), true);
+}
+
+void tst_custom::initializeProperties()
+{
+ QVERIFY(m_custom);
+
+ m_custom->setAlphaMultiplier(0.1f);
+ m_custom->setDrawSliceFrames(true);
+ m_custom->setDrawSliceFrames(true);
+ m_custom->setPreserveOpacity(false);
+ m_custom->setSliceFrameColor(QColor(Qt::red));
+ m_custom->setSliceFrameGaps(QVector3D(2.0f, 2.0f, 2.0f));
+ m_custom->setSliceFrameThicknesses(QVector3D(2.0f, 2.0f, 2.0f));
+ m_custom->setSliceFrameWidths(QVector3D(2.0f, 2.0f, 2.0f));
+ m_custom->setSliceIndexX(0);
+ m_custom->setSliceIndexY(0);
+ m_custom->setSliceIndexZ(0);
+ m_custom->setUseHighDefShader(false);
+
+ QCOMPARE(m_custom->alphaMultiplier(), 0.1f);
+ QCOMPARE(m_custom->drawSliceFrames(), true);
+ QCOMPARE(m_custom->drawSliceFrames(), true);
+ QCOMPARE(m_custom->preserveOpacity(), false);
+ QCOMPARE(m_custom->sliceFrameColor(), QColor(Qt::red));
+ QCOMPARE(m_custom->sliceFrameGaps(), QVector3D(2.0f, 2.0f, 2.0f));
+ QCOMPARE(m_custom->sliceFrameThicknesses(), QVector3D(2.0f, 2.0f, 2.0f));
+ QCOMPARE(m_custom->sliceFrameWidths(), QVector3D(2.0f, 2.0f, 2.0f));
+ QCOMPARE(m_custom->sliceIndexX(), 0);
+ QCOMPARE(m_custom->sliceIndexY(), 0);
+ QCOMPARE(m_custom->sliceIndexZ(), 0);
+ QCOMPARE(m_custom->useHighDefShader(), false);
+
+ // Common (from QCustom3DVolume)
+ m_custom->setPosition(QVector3D(1.0f, 1.0f, 1.0f));
+ m_custom->setPositionAbsolute(true);
+ m_custom->setRotation(QQuaternion(1.0f, 1.0f, 10.0f, 100.0f));
+ m_custom->setScaling(QVector3D(1.0f, 1.0f, 1.0f));
+ m_custom->setScalingAbsolute(false);
+ m_custom->setShadowCasting(false);
+ m_custom->setVisible(false);
+
+ QCOMPARE(m_custom->position(), QVector3D(1.0f, 1.0f, 1.0f));
+ QCOMPARE(m_custom->isPositionAbsolute(), true);
+ QCOMPARE(m_custom->rotation(), QQuaternion(1.0f, 1.0f, 10.0f, 100.0f));
+ QCOMPARE(m_custom->scaling(), QVector3D(1.0f, 1.0f, 1.0f));
+ QCOMPARE(m_custom->isScalingAbsolute(), false);
+ QCOMPARE(m_custom->isShadowCasting(), false);
+ QCOMPARE(m_custom->isVisible(), false);
+}
+
+void tst_custom::invalidProperties()
+{
+ m_custom->setAlphaMultiplier(-1.0f);
+ QCOMPARE(m_custom->alphaMultiplier(), 1.0f);
+
+ m_custom->setSliceFrameGaps(QVector3D(-0.1f, -0.1f, -0.1f));
+ QCOMPARE(m_custom->sliceFrameGaps(), QVector3D(0.01f, 0.01f, 0.01f));
+
+ m_custom->setSliceFrameThicknesses(QVector3D(-0.1f, -0.1f, -0.1f));
+ QCOMPARE(m_custom->sliceFrameThicknesses(), QVector3D(0.01f, 0.01f, 0.01f));
+
+ m_custom->setSliceFrameWidths(QVector3D(-0.1f, -0.1f, -0.1f));
+ QCOMPARE(m_custom->sliceFrameWidths(), QVector3D(0.01f, 0.01f, 0.01f));
+
+ m_custom->setTextureFormat(QImage::Format_ARGB8555_Premultiplied);
+ QCOMPARE(m_custom->textureFormat(), QImage::Format_ARGB32);
+}
+
+QTEST_MAIN(tst_custom)
+#include "tst_custom.moc"
diff --git a/tests/auto/cpptest/q3dcustom/CMakeLists.txt b/tests/auto/cpptest/q3dcustom/CMakeLists.txt
new file mode 100644
index 0000000..176ae03
--- /dev/null
+++ b/tests/auto/cpptest/q3dcustom/CMakeLists.txt
@@ -0,0 +1,10 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+qt_internal_add_test(q3dcustom
+ SOURCES
+ tst_custom.cpp
+ LIBRARIES
+ Qt::Gui
+ Qt::Graphs
+)
diff --git a/tests/auto/cpptest/q3dcustom/tst_custom.cpp b/tests/auto/cpptest/q3dcustom/tst_custom.cpp
new file mode 100644
index 0000000..d238f38
--- /dev/null
+++ b/tests/auto/cpptest/q3dcustom/tst_custom.cpp
@@ -0,0 +1,111 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtTest/QtTest>
+
+#include <QtGraphs/QCustom3DItem>
+
+class tst_custom: public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void initTestCase();
+ void cleanupTestCase();
+ void init();
+ void cleanup();
+
+ void construct();
+
+ void initialProperties();
+ void initializeProperties();
+
+private:
+ QCustom3DItem *m_custom;
+};
+
+void tst_custom::initTestCase()
+{
+}
+
+void tst_custom::cleanupTestCase()
+{
+}
+
+void tst_custom::init()
+{
+ m_custom = new QCustom3DItem();
+}
+
+void tst_custom::cleanup()
+{
+ delete m_custom;
+}
+
+void tst_custom::construct()
+{
+ QCustom3DItem *custom = new QCustom3DItem();
+ QVERIFY(custom);
+ delete custom;
+
+ custom = new QCustom3DItem(":/customitem.obj", QVector3D(1.0f, 1.0f, 1.0f),
+ QVector3D(1.0f, 1.0f, 1.0f), QQuaternion(1.0f, 1.0f, 10.0f, 100.0f),
+ QImage(":/customtexture.jpg"));
+ QVERIFY(custom);
+ QCOMPARE(custom->meshFile(), QString(":/customitem.obj"));
+ QCOMPARE(custom->position(), QVector3D(1.0f, 1.0f, 1.0f));
+ QCOMPARE(custom->isPositionAbsolute(), false);
+ QCOMPARE(custom->rotation(), QQuaternion(1.0f, 1.0f, 10.0f, 100.0f));
+ QCOMPARE(custom->scaling(), QVector3D(1.0f, 1.0f, 1.0f));
+ QCOMPARE(custom->isScalingAbsolute(), true);
+ QCOMPARE(custom->isShadowCasting(), true);
+ QCOMPARE(custom->textureFile(), QString());
+ QCOMPARE(custom->isVisible(), true);
+ delete custom;
+}
+
+void tst_custom::initialProperties()
+{
+ QVERIFY(m_custom);
+
+ QCOMPARE(m_custom->meshFile(), QString());
+ QCOMPARE(m_custom->position(), QVector3D());
+ QCOMPARE(m_custom->isPositionAbsolute(), false);
+ QCOMPARE(m_custom->rotation(), QQuaternion());
+ QCOMPARE(m_custom->scaling(), QVector3D(0.1f, 0.1f, 0.1f));
+ QCOMPARE(m_custom->isScalingAbsolute(), true);
+ QCOMPARE(m_custom->isShadowCasting(), true);
+ QCOMPARE(m_custom->textureFile(), QString());
+ QCOMPARE(m_custom->isVisible(), true);
+}
+
+void tst_custom::initializeProperties()
+{
+ QVERIFY(m_custom);
+
+ m_custom->setMeshFile(":/customitem.obj");
+ m_custom->setPosition(QVector3D(1.0f, 1.0f, 1.0f));
+ m_custom->setPositionAbsolute(true);
+ m_custom->setRotation(QQuaternion(1.0f, 1.0f, 10.0f, 100.0f));
+ m_custom->setScaling(QVector3D(1.0f, 1.0f, 1.0f));
+ m_custom->setScalingAbsolute(false);
+ m_custom->setShadowCasting(false);
+ m_custom->setTextureFile(":/customtexture.jpg");
+ m_custom->setVisible(false);
+
+ QCOMPARE(m_custom->meshFile(), QString(":/customitem.obj"));
+ QCOMPARE(m_custom->position(), QVector3D(1.0f, 1.0f, 1.0f));
+ QCOMPARE(m_custom->isPositionAbsolute(), true);
+ QCOMPARE(m_custom->rotation(), QQuaternion(1.0f, 1.0f, 10.0f, 100.0f));
+ QCOMPARE(m_custom->scaling(), QVector3D(1.0f, 1.0f, 1.0f));
+ QCOMPARE(m_custom->isScalingAbsolute(), false);
+ QCOMPARE(m_custom->isShadowCasting(), false);
+ QCOMPARE(m_custom->textureFile(), QString(":/customtexture.jpg"));
+ QCOMPARE(m_custom->isVisible(), false);
+
+ m_custom->setTextureImage(QImage(QSize(10, 10), QImage::Format_ARGB32));
+ QCOMPARE(m_custom->textureFile(), QString());
+}
+
+QTEST_MAIN(tst_custom)
+#include "tst_custom.moc"
diff --git a/tests/auto/cpptest/q3dinput-touch/CMakeLists.txt b/tests/auto/cpptest/q3dinput-touch/CMakeLists.txt
new file mode 100644
index 0000000..227c821
--- /dev/null
+++ b/tests/auto/cpptest/q3dinput-touch/CMakeLists.txt
@@ -0,0 +1,10 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+qt_internal_add_test(q3dinput-touch
+ SOURCES
+ tst_input.cpp
+ LIBRARIES
+ Qt::Gui
+ Qt::Graphs
+)
diff --git a/tests/auto/cpptest/q3dinput-touch/tst_input.cpp b/tests/auto/cpptest/q3dinput-touch/tst_input.cpp
new file mode 100644
index 0000000..23f88c9
--- /dev/null
+++ b/tests/auto/cpptest/q3dinput-touch/tst_input.cpp
@@ -0,0 +1,89 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtTest/QtTest>
+
+#include <QtGraphs/QTouch3DInputHandler>
+
+class tst_input: public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void initTestCase();
+ void cleanupTestCase();
+ void init();
+ void cleanup();
+
+ void construct();
+
+ void initialProperties();
+ void initializeProperties();
+
+private:
+ QTouch3DInputHandler *m_input;
+};
+
+void tst_input::initTestCase()
+{
+}
+
+void tst_input::cleanupTestCase()
+{
+}
+
+void tst_input::init()
+{
+ m_input = new QTouch3DInputHandler();
+}
+
+void tst_input::cleanup()
+{
+ delete m_input;
+}
+
+void tst_input::construct()
+{
+ QTouch3DInputHandler *input = new QTouch3DInputHandler();
+ QVERIFY(input);
+ delete input;
+}
+
+void tst_input::initialProperties()
+{
+ QVERIFY(m_input);
+
+ // Common (from Q3DInputHandler and QAbstract3DInputHandler)
+ QCOMPARE(m_input->isRotationEnabled(), true);
+ QCOMPARE(m_input->isSelectionEnabled(), true);
+ QCOMPARE(m_input->isZoomAtTargetEnabled(), true);
+ QCOMPARE(m_input->isZoomEnabled(), true);
+ QCOMPARE(m_input->inputPosition(), QPoint(0, 0));
+ QCOMPARE(m_input->inputView(), QAbstract3DInputHandler::InputViewNone);
+ QVERIFY(!m_input->scene());
+}
+
+void tst_input::initializeProperties()
+{
+ QVERIFY(m_input);
+
+ // Common (from Q3DInputHandler and QAbstract3DInputHandler)
+ m_input->setRotationEnabled(false);
+ m_input->setSelectionEnabled(false);
+ m_input->setZoomAtTargetEnabled(false);
+ m_input->setZoomEnabled(false);
+ m_input->setInputPosition(QPoint(100, 100));
+ m_input->setInputView(QAbstract3DInputHandler::InputViewOnPrimary);
+
+ QCOMPARE(m_input->isRotationEnabled(), false);
+ QCOMPARE(m_input->isSelectionEnabled(), false);
+ QCOMPARE(m_input->isZoomAtTargetEnabled(), false);
+ QCOMPARE(m_input->isZoomEnabled(), false);
+ QCOMPARE(m_input->inputPosition(), QPoint(100, 100));
+ QCOMPARE(m_input->inputView(), QAbstract3DInputHandler::InputViewOnPrimary);
+}
+
+// TODO: QTRD-3380 (mouse/touch events)
+
+QTEST_MAIN(tst_input)
+#include "tst_input.moc"
diff --git a/tests/auto/cpptest/q3dinput/CMakeLists.txt b/tests/auto/cpptest/q3dinput/CMakeLists.txt
new file mode 100644
index 0000000..63a07c7
--- /dev/null
+++ b/tests/auto/cpptest/q3dinput/CMakeLists.txt
@@ -0,0 +1,10 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+qt_internal_add_test(q3dinput
+ SOURCES
+ tst_input.cpp
+ LIBRARIES
+ Qt::Gui
+ Qt::Graphs
+)
diff --git a/tests/auto/cpptest/q3dinput/tst_input.cpp b/tests/auto/cpptest/q3dinput/tst_input.cpp
new file mode 100644
index 0000000..b0bfb5e
--- /dev/null
+++ b/tests/auto/cpptest/q3dinput/tst_input.cpp
@@ -0,0 +1,92 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtTest/QtTest>
+
+#include <QtGraphs/Q3DInputHandler>
+
+class tst_input: public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void initTestCase();
+ void cleanupTestCase();
+ void init();
+ void cleanup();
+
+ void construct();
+
+ void initialProperties();
+ void initializeProperties();
+
+private:
+ Q3DInputHandler *m_input;
+};
+
+void tst_input::initTestCase()
+{
+}
+
+void tst_input::cleanupTestCase()
+{
+}
+
+void tst_input::init()
+{
+ m_input = new Q3DInputHandler();
+}
+
+void tst_input::cleanup()
+{
+ delete m_input;
+}
+
+void tst_input::construct()
+{
+ Q3DInputHandler *input = new Q3DInputHandler();
+ QVERIFY(input);
+ delete input;
+}
+
+void tst_input::initialProperties()
+{
+ QVERIFY(m_input);
+
+ QCOMPARE(m_input->isRotationEnabled(), true);
+ QCOMPARE(m_input->isSelectionEnabled(), true);
+ QCOMPARE(m_input->isZoomAtTargetEnabled(), true);
+ QCOMPARE(m_input->isZoomEnabled(), true);
+
+ // Common (from QAbstract3DInputHandler)
+ QCOMPARE(m_input->inputPosition(), QPoint(0, 0));
+ QCOMPARE(m_input->inputView(), QAbstract3DInputHandler::InputViewNone);
+ QVERIFY(!m_input->scene());
+}
+
+void tst_input::initializeProperties()
+{
+ QVERIFY(m_input);
+
+ m_input->setRotationEnabled(false);
+ m_input->setSelectionEnabled(false);
+ m_input->setZoomAtTargetEnabled(false);
+ m_input->setZoomEnabled(false);
+
+ QCOMPARE(m_input->isRotationEnabled(), false);
+ QCOMPARE(m_input->isSelectionEnabled(), false);
+ QCOMPARE(m_input->isZoomAtTargetEnabled(), false);
+ QCOMPARE(m_input->isZoomEnabled(), false);
+
+ // Common (from QAbstract3DInputHandler)
+ m_input->setInputPosition(QPoint(100, 100));
+ m_input->setInputView(QAbstract3DInputHandler::InputViewOnPrimary);
+
+ QCOMPARE(m_input->inputPosition(), QPoint(100, 100));
+ QCOMPARE(m_input->inputView(), QAbstract3DInputHandler::InputViewOnPrimary);
+}
+
+// TODO: QTRD-3380 (mouse events)
+
+QTEST_MAIN(tst_input)
+#include "tst_input.moc"
diff --git a/tests/auto/cpptest/q3dscatter-modelproxy/CMakeLists.txt b/tests/auto/cpptest/q3dscatter-modelproxy/CMakeLists.txt
new file mode 100644
index 0000000..6c9cee1
--- /dev/null
+++ b/tests/auto/cpptest/q3dscatter-modelproxy/CMakeLists.txt
@@ -0,0 +1,11 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+qt_internal_add_test(q3dscatter-modelproxy
+ SOURCES
+ tst_proxy.cpp
+ LIBRARIES
+ Qt::Gui
+ Qt::Widgets
+ Qt::Graphs
+)
diff --git a/tests/auto/cpptest/q3dscatter-modelproxy/tst_proxy.cpp b/tests/auto/cpptest/q3dscatter-modelproxy/tst_proxy.cpp
new file mode 100644
index 0000000..f47b486
--- /dev/null
+++ b/tests/auto/cpptest/q3dscatter-modelproxy/tst_proxy.cpp
@@ -0,0 +1,180 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtTest/QtTest>
+
+#include <QtGraphs/QItemModelScatterDataProxy>
+#include <QtGraphs/Q3DScatter>
+#include <QtWidgets/QTableWidget>
+
+class tst_proxy: public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void initTestCase();
+ void cleanupTestCase();
+ void init();
+ void cleanup();
+
+ void construct();
+
+ void initialProperties();
+ void initializeProperties();
+
+ void addModel();
+
+private:
+ QItemModelScatterDataProxy *m_proxy;
+};
+
+void tst_proxy::initTestCase()
+{
+}
+
+void tst_proxy::cleanupTestCase()
+{
+}
+
+void tst_proxy::init()
+{
+ m_proxy = new QItemModelScatterDataProxy();
+}
+
+void tst_proxy::cleanup()
+{
+ delete m_proxy;
+}
+
+void tst_proxy::construct()
+{
+ QItemModelScatterDataProxy *proxy = new QItemModelScatterDataProxy();
+ QVERIFY(proxy);
+ delete proxy;
+
+ QTableWidget *table = new QTableWidget();
+
+ proxy = new QItemModelScatterDataProxy(table->model());
+ QVERIFY(proxy);
+ delete proxy;
+
+ proxy = new QItemModelScatterDataProxy(table->model(), "x", "y", "z");
+ QVERIFY(proxy);
+ QCOMPARE(proxy->xPosRole(), QString("x"));
+ QCOMPARE(proxy->yPosRole(), QString("y"));
+ QCOMPARE(proxy->zPosRole(), QString("z"));
+ QCOMPARE(proxy->rotationRole(), QString(""));
+ delete proxy;
+
+ proxy = new QItemModelScatterDataProxy(table->model(), "x", "y", "z", "rot");
+ QVERIFY(proxy);
+ QCOMPARE(proxy->xPosRole(), QString("x"));
+ QCOMPARE(proxy->yPosRole(), QString("y"));
+ QCOMPARE(proxy->zPosRole(), QString("z"));
+ QCOMPARE(proxy->rotationRole(), QString("rot"));
+ delete proxy;
+}
+
+void tst_proxy::initialProperties()
+{
+ QVERIFY(m_proxy);
+
+ QVERIFY(!m_proxy->itemModel());
+ QCOMPARE(m_proxy->rotationRole(), QString());
+ QCOMPARE(m_proxy->rotationRolePattern(), QRegularExpression());
+ QCOMPARE(m_proxy->rotationRoleReplace(), QString());
+ QCOMPARE(m_proxy->xPosRole(), QString());
+ QCOMPARE(m_proxy->xPosRolePattern(), QRegularExpression());
+ QCOMPARE(m_proxy->xPosRoleReplace(), QString());
+ QCOMPARE(m_proxy->yPosRole(), QString());
+ QCOMPARE(m_proxy->yPosRolePattern(), QRegularExpression());
+ QCOMPARE(m_proxy->yPosRoleReplace(), QString());
+ QCOMPARE(m_proxy->zPosRole(), QString());
+ QCOMPARE(m_proxy->zPosRolePattern(), QRegularExpression());
+ QCOMPARE(m_proxy->zPosRoleReplace(), QString());
+
+ QCOMPARE(m_proxy->itemCount(), 0);
+ QVERIFY(!m_proxy->series());
+
+ QCOMPARE(m_proxy->type(), QAbstractDataProxy::DataTypeScatter);
+}
+
+void tst_proxy::initializeProperties()
+{
+ QVERIFY(m_proxy);
+
+ QTableWidget table;
+
+ m_proxy->setItemModel(table.model());
+ m_proxy->setRotationRole("rotation");
+ m_proxy->setRotationRolePattern(QRegularExpression("/-/"));
+ m_proxy->setRotationRoleReplace("\\\\1");
+ m_proxy->setXPosRole("X");
+ m_proxy->setXPosRolePattern(QRegularExpression("/-/"));
+ m_proxy->setXPosRoleReplace("\\\\1");
+ m_proxy->setYPosRole("Y");
+ m_proxy->setYPosRolePattern(QRegularExpression("/-/"));
+ m_proxy->setYPosRoleReplace("\\\\1");
+ m_proxy->setZPosRole("Z");
+ m_proxy->setZPosRolePattern(QRegularExpression("/-/"));
+ m_proxy->setZPosRoleReplace("\\\\1");
+
+ QVERIFY(m_proxy->itemModel());
+ QCOMPARE(m_proxy->rotationRole(), QString("rotation"));
+ QCOMPARE(m_proxy->rotationRolePattern(), QRegularExpression("/-/"));
+ QCOMPARE(m_proxy->rotationRoleReplace(), QString("\\\\1"));
+ QCOMPARE(m_proxy->xPosRole(), QString("X"));
+ QCOMPARE(m_proxy->xPosRolePattern(), QRegularExpression("/-/"));
+ QCOMPARE(m_proxy->xPosRoleReplace(), QString("\\\\1"));
+ QCOMPARE(m_proxy->yPosRole(), QString("Y"));
+ QCOMPARE(m_proxy->yPosRolePattern(), QRegularExpression("/-/"));
+ QCOMPARE(m_proxy->yPosRoleReplace(), QString("\\\\1"));
+ QCOMPARE(m_proxy->zPosRole(), QString("Z"));
+ QCOMPARE(m_proxy->zPosRolePattern(), QRegularExpression("/-/"));
+ QCOMPARE(m_proxy->zPosRoleReplace(), QString("\\\\1"));
+}
+
+void tst_proxy::addModel()
+{
+ QTableWidget table;
+ QStringList rows;
+ rows << "row 1";
+ QStringList columns;
+ columns << "col 1";
+ const char *values[1][2] = {{"0/0/5.5/30", "0/0/10.5/30"}};
+
+ table.setRowCount(2);
+ table.setColumnCount(1);
+
+ for (int col = 0; col < columns.size(); col++) {
+ for (int row = 0; row < rows.size(); row++) {
+ QModelIndex index = table.model()->index(col, row);
+ table.model()->setData(index, values[col][row]);
+ }
+ }
+
+ m_proxy->setItemModel(table.model());
+ m_proxy->setXPosRole(table.model()->roleNames().value(Qt::DisplayRole));
+ m_proxy->setZPosRole(table.model()->roleNames().value(Qt::DisplayRole));
+ m_proxy->setXPosRolePattern(QRegularExpression(QStringLiteral("^(\\d*)\\/(\\d*)\\/\\d*[\\.\\,]?\\d*\\/\\d*[\\.\\,]?\\d*$")));
+ m_proxy->setXPosRoleReplace(QStringLiteral("\\2"));
+ m_proxy->setYPosRolePattern(QRegularExpression(QStringLiteral("^\\d*(\\/)(\\d*)\\/(\\d*[\\.\\,]?\\d*)\\/\\d*[\\.\\,]?\\d*$")));
+ m_proxy->setYPosRoleReplace(QStringLiteral("\\3"));
+ m_proxy->setZPosRolePattern(QRegularExpression(QStringLiteral("^(\\d*)(\\/)(\\d*)\\/\\d*[\\.\\,]?\\d*\\/\\d*[\\.\\,]?\\d*$")));
+ m_proxy->setZPosRoleReplace(QStringLiteral("\\1"));
+ QCoreApplication::processEvents();
+
+ QScatter3DSeries *series = new QScatter3DSeries(m_proxy);
+ Q_UNUSED(series);
+
+ QCoreApplication::processEvents();
+
+ QCOMPARE(m_proxy->itemCount(), 2);
+ QVERIFY(m_proxy->series());
+
+ delete series;
+ m_proxy = 0; // proxy gets deleted with series
+}
+
+QTEST_MAIN(tst_proxy)
+#include "tst_proxy.moc"
diff --git a/tests/auto/cpptest/q3dscatter-proxy/CMakeLists.txt b/tests/auto/cpptest/q3dscatter-proxy/CMakeLists.txt
new file mode 100644
index 0000000..be27921
--- /dev/null
+++ b/tests/auto/cpptest/q3dscatter-proxy/CMakeLists.txt
@@ -0,0 +1,10 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+qt_internal_add_test(q3dscatter-proxy
+ SOURCES
+ tst_proxy.cpp
+ LIBRARIES
+ Qt::Gui
+ Qt::Graphs
+)
diff --git a/tests/auto/cpptest/q3dscatter-proxy/tst_proxy.cpp b/tests/auto/cpptest/q3dscatter-proxy/tst_proxy.cpp
new file mode 100644
index 0000000..1af5b21
--- /dev/null
+++ b/tests/auto/cpptest/q3dscatter-proxy/tst_proxy.cpp
@@ -0,0 +1,74 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtTest/QtTest>
+
+#include <QtGraphs/QScatterDataProxy>
+
+class tst_proxy: public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void initTestCase();
+ void cleanupTestCase();
+ void init();
+ void cleanup();
+
+ void construct();
+
+ void initialProperties();
+ void initializeProperties();
+
+private:
+ QScatterDataProxy *m_proxy;
+};
+
+void tst_proxy::initTestCase()
+{
+}
+
+void tst_proxy::cleanupTestCase()
+{
+}
+
+void tst_proxy::init()
+{
+ m_proxy = new QScatterDataProxy();
+}
+
+void tst_proxy::cleanup()
+{
+ delete m_proxy;
+}
+
+void tst_proxy::construct()
+{
+ QScatterDataProxy *proxy = new QScatterDataProxy();
+ QVERIFY(proxy);
+ delete proxy;
+}
+
+void tst_proxy::initialProperties()
+{
+ QVERIFY(m_proxy);
+
+ QCOMPARE(m_proxy->itemCount(), 0);
+ QVERIFY(!m_proxy->series());
+
+ QCOMPARE(m_proxy->type(), QAbstractDataProxy::DataTypeScatter);
+}
+
+void tst_proxy::initializeProperties()
+{
+ QVERIFY(m_proxy);
+
+ QScatterDataArray data;
+ data << QVector3D(0.5f, 0.5f, 0.5f) << QVector3D(-0.3f, -0.5f, -0.4f);
+ m_proxy->addItems(data);
+
+ QCOMPARE(m_proxy->itemCount(), 2);
+}
+
+QTEST_MAIN(tst_proxy)
+#include "tst_proxy.moc"
diff --git a/tests/auto/cpptest/q3dscatter-series/CMakeLists.txt b/tests/auto/cpptest/q3dscatter-series/CMakeLists.txt
new file mode 100644
index 0000000..ee1b4bb
--- /dev/null
+++ b/tests/auto/cpptest/q3dscatter-series/CMakeLists.txt
@@ -0,0 +1,10 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+qt_internal_add_test(q3dscatter-series
+ SOURCES
+ tst_series.cpp
+ LIBRARIES
+ Qt::Gui
+ Qt::Graphs
+)
diff --git a/tests/auto/cpptest/q3dscatter-series/tst_series.cpp b/tests/auto/cpptest/q3dscatter-series/tst_series.cpp
new file mode 100644
index 0000000..0f7e925
--- /dev/null
+++ b/tests/auto/cpptest/q3dscatter-series/tst_series.cpp
@@ -0,0 +1,93 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtTest/QtTest>
+
+#include <QtGraphs/QScatter3DSeries>
+
+class tst_series: public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void initTestCase();
+ void cleanupTestCase();
+ void init();
+ void cleanup();
+
+ void construct();
+
+ void initialProperties();
+ void initializeProperties();
+
+private:
+ QScatter3DSeries *m_series;
+};
+
+void tst_series::initTestCase()
+{
+}
+
+void tst_series::cleanupTestCase()
+{
+}
+
+void tst_series::init()
+{
+ m_series = new QScatter3DSeries();
+}
+
+void tst_series::cleanup()
+{
+ delete m_series;
+}
+
+void tst_series::construct()
+{
+ QScatter3DSeries *series = new QScatter3DSeries();
+ QVERIFY(series);
+ delete series;
+
+ QScatterDataProxy *proxy = new QScatterDataProxy();
+
+ series = new QScatter3DSeries(proxy);
+ QVERIFY(series);
+ QCOMPARE(series->dataProxy(), proxy);
+ delete series;
+}
+
+void tst_series::initialProperties()
+{
+ QVERIFY(m_series);
+
+ QVERIFY(m_series->dataProxy());
+ QCOMPARE(m_series->itemSize(), 0.0f);
+ QCOMPARE(m_series->selectedItem(), m_series->invalidSelectionIndex());
+
+ // Common properties. The ones identical between different series are tested in QBar3DSeries tests
+ QCOMPARE(m_series->itemLabelFormat(), QString("@xLabel, @yLabel, @zLabel"));
+ QCOMPARE(m_series->mesh(), QAbstract3DSeries::MeshSphere);
+ QCOMPARE(m_series->type(), QAbstract3DSeries::SeriesTypeScatter);
+}
+
+void tst_series::initializeProperties()
+{
+ QVERIFY(m_series);
+
+ m_series->setDataProxy(new QScatterDataProxy());
+ m_series->setItemSize(0.5f);
+ m_series->setSelectedItem(0);
+
+ QCOMPARE(m_series->itemSize(), 0.5f);
+ QCOMPARE(m_series->selectedItem(), 0);
+
+ // Common properties. The ones identical between different series are tested in QBar3DSeries tests
+ m_series->setMesh(QAbstract3DSeries::MeshPoint);
+ m_series->setMeshRotation(QQuaternion(1, 1, 10, 20));
+
+ QCOMPARE(m_series->mesh(), QAbstract3DSeries::MeshPoint);
+ QCOMPARE(m_series->meshRotation(), QQuaternion(1, 1, 10, 20));
+}
+
+QTEST_MAIN(tst_series)
+#include "tst_series.moc"
diff --git a/tests/auto/cpptest/q3dscatter/CMakeLists.txt b/tests/auto/cpptest/q3dscatter/CMakeLists.txt
new file mode 100644
index 0000000..6ca0947
--- /dev/null
+++ b/tests/auto/cpptest/q3dscatter/CMakeLists.txt
@@ -0,0 +1,13 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+qt_internal_add_test(q3dscatter
+ SOURCES
+ tst_scatter.cpp
+ INCLUDE_DIRECTORIES
+ ../common
+ LIBRARIES
+ Qt::Gui
+ Qt::GuiPrivate
+ Qt::Graphs
+)
diff --git a/tests/auto/cpptest/q3dscatter/tst_scatter.cpp b/tests/auto/cpptest/q3dscatter/tst_scatter.cpp
new file mode 100644
index 0000000..9ed7ebf
--- /dev/null
+++ b/tests/auto/cpptest/q3dscatter/tst_scatter.cpp
@@ -0,0 +1,241 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtTest/QtTest>
+
+#include <QtGraphs/Q3DScatter>
+
+#include "cpptestutil.h"
+
+class tst_scatter: public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void initTestCase();
+ void cleanupTestCase();
+ void init();
+ void cleanup();
+
+ void construct();
+
+ void initialProperties();
+ void initializeProperties();
+ void invalidProperties();
+
+ void addSeries();
+ void addMultipleSeries();
+ void selectSeries();
+ void removeSeries();
+ void removeMultipleSeries();
+ void hasSeries();
+
+private:
+ Q3DScatter *m_graph;
+};
+
+QScatter3DSeries *newSeries()
+{
+ QScatter3DSeries *series = new QScatter3DSeries;
+ QScatterDataArray data;
+ data << QVector3D(0.5f, 0.5f, 0.5f) << QVector3D(-0.3f, -0.5f, -0.4f) << QVector3D(0.0f, -0.3f, 0.2f);
+ series->dataProxy()->addItems(data);
+ return series;
+}
+
+void tst_scatter::initTestCase()
+{
+ if (!CpptestUtil::isOpenGLSupported())
+ QSKIP("OpenGL not supported on this platform");
+}
+
+void tst_scatter::cleanupTestCase()
+{
+}
+
+void tst_scatter::init()
+{
+ m_graph = new Q3DScatter();
+}
+
+void tst_scatter::cleanup()
+{
+ delete m_graph;
+}
+
+void tst_scatter::construct()
+{
+ Q3DScatter *graph = new Q3DScatter();
+ QVERIFY(graph);
+ delete graph;
+
+ QSurfaceFormat format;
+ graph = new Q3DScatter(&format);
+ QVERIFY(graph);
+ delete graph;
+}
+
+void tst_scatter::initialProperties()
+{
+ QVERIFY(m_graph);
+ QCOMPARE(m_graph->seriesList().size(), 0);
+ QVERIFY(!m_graph->selectedSeries());
+ QCOMPARE(m_graph->axisX()->orientation(), QAbstract3DAxis::AxisOrientationX);
+ QCOMPARE(m_graph->axisY()->orientation(), QAbstract3DAxis::AxisOrientationY);
+ QCOMPARE(m_graph->axisZ()->orientation(), QAbstract3DAxis::AxisOrientationZ);
+
+ // Common properties
+ QCOMPARE(m_graph->activeTheme()->type(), Q3DTheme::ThemeQt);
+ QCOMPARE(m_graph->selectionMode(), QAbstract3DGraph::SelectionItem);
+ QCOMPARE(m_graph->shadowQuality(), QAbstract3DGraph::ShadowQualityMedium);
+ QVERIFY(m_graph->scene());
+ QCOMPARE(m_graph->measureFps(), false);
+ QCOMPARE(m_graph->isOrthoProjection(), false);
+ QCOMPARE(m_graph->selectedElement(), QAbstract3DGraph::ElementNone);
+ QCOMPARE(m_graph->aspectRatio(), 2.0);
+ QCOMPARE(m_graph->optimizationHints(), QAbstract3DGraph::OptimizationDefault);
+ QCOMPARE(m_graph->isPolar(), false);
+ QCOMPARE(m_graph->radialLabelOffset(), 1.0);
+ QCOMPARE(m_graph->horizontalAspectRatio(), 0.0);
+ QCOMPARE(m_graph->isReflection(), false);
+ QCOMPARE(m_graph->reflectivity(), 0.5);
+ QCOMPARE(m_graph->locale(), QLocale("C"));
+ QCOMPARE(m_graph->queriedGraphPosition(), QVector3D(0, 0, 0));
+ QCOMPARE(m_graph->margin(), -1.0);
+}
+
+void tst_scatter::initializeProperties()
+{
+ Q3DTheme *theme = new Q3DTheme(Q3DTheme::ThemeDigia);
+ m_graph->setActiveTheme(theme);
+ m_graph->setSelectionMode(QAbstract3DGraph::SelectionNone);
+ m_graph->setShadowQuality(QAbstract3DGraph::ShadowQualitySoftHigh);
+ QCOMPARE(m_graph->shadowQuality(), QAbstract3DGraph::ShadowQualitySoftHigh);
+ m_graph->setMeasureFps(true);
+ m_graph->setOrthoProjection(true);
+ m_graph->setAspectRatio(1.0);
+ m_graph->setOptimizationHints(QAbstract3DGraph::OptimizationStatic);
+ m_graph->setPolar(true);
+ m_graph->setRadialLabelOffset(0.1f);
+ m_graph->setHorizontalAspectRatio(1.0);
+ m_graph->setReflection(true);
+ m_graph->setReflectivity(0.1);
+ m_graph->setLocale(QLocale("FI"));
+ m_graph->setMargin(1.0);
+
+ QCOMPARE(m_graph->activeTheme()->type(), Q3DTheme::ThemeDigia);
+ QCOMPARE(m_graph->selectionMode(), QAbstract3DGraph::SelectionNone);
+ QCOMPARE(m_graph->shadowQuality(), QAbstract3DGraph::ShadowQualityNone); // Ortho disables shadows
+ QCOMPARE(m_graph->measureFps(), true);
+ QCOMPARE(m_graph->isOrthoProjection(), true);
+ QCOMPARE(m_graph->aspectRatio(), 1.0);
+ QCOMPARE(m_graph->optimizationHints(), QAbstract3DGraph::OptimizationStatic);
+ QCOMPARE(m_graph->isPolar(), true);
+ QCOMPARE(m_graph->radialLabelOffset(), 0.1f);
+ QCOMPARE(m_graph->horizontalAspectRatio(), 1.0);
+ QCOMPARE(m_graph->isReflection(), true);
+ QCOMPARE(m_graph->reflectivity(), 0.1);
+ QCOMPARE(m_graph->locale(), QLocale("FI"));
+ QCOMPARE(m_graph->margin(), 1.0);
+}
+
+void tst_scatter::invalidProperties()
+{
+ m_graph->setSelectionMode(QAbstract3DGraph::SelectionColumn | QAbstract3DGraph::SelectionRow | QAbstract3DGraph::SelectionSlice);
+ m_graph->setAspectRatio(-1.0);
+ m_graph->setHorizontalAspectRatio(-1.0);
+ m_graph->setReflectivity(-1.0);
+ m_graph->setLocale(QLocale("XX"));
+
+ QCOMPARE(m_graph->selectionMode(), QAbstract3DGraph::SelectionItem);
+ QCOMPARE(m_graph->aspectRatio(), -1.0/*2.0*/); // TODO: Fix once QTRD-3367 is done
+ QCOMPARE(m_graph->horizontalAspectRatio(), -1.0/*0.0*/); // TODO: Fix once QTRD-3367 is done
+ QCOMPARE(m_graph->reflectivity(), -1.0/*0.5*/); // TODO: Fix once QTRD-3367 is done
+ QCOMPARE(m_graph->locale(), QLocale("C"));
+}
+
+void tst_scatter::addSeries()
+{
+ m_graph->addSeries(newSeries());
+
+ QCOMPARE(m_graph->seriesList().size(), 1);
+ QVERIFY(!m_graph->selectedSeries());
+}
+
+void tst_scatter::addMultipleSeries()
+{
+ QScatter3DSeries *series = newSeries();
+ QScatter3DSeries *series2 = newSeries();
+ QScatter3DSeries *series3 = newSeries();
+
+ m_graph->addSeries(series);
+ m_graph->addSeries(series2);
+ m_graph->addSeries(series3);
+
+ QCOMPARE(m_graph->seriesList().size(), 3);
+}
+
+void tst_scatter::selectSeries()
+{
+ QScatter3DSeries *series = newSeries();
+
+ m_graph->addSeries(series);
+ m_graph->seriesList()[0]->setSelectedItem(1);
+
+ QCOMPARE(m_graph->seriesList().size(), 1);
+ QCOMPARE(m_graph->selectedSeries(), series);
+
+ m_graph->clearSelection();
+ QVERIFY(!m_graph->selectedSeries());
+}
+
+void tst_scatter::removeSeries()
+{
+ QScatter3DSeries *series = newSeries();
+
+ m_graph->addSeries(series);
+ m_graph->removeSeries(series);
+ QCOMPARE(m_graph->seriesList().size(), 0);
+
+ delete series;
+}
+
+void tst_scatter::removeMultipleSeries()
+{
+ QScatter3DSeries *series = newSeries();
+ QScatter3DSeries *series2 = newSeries();
+ QScatter3DSeries *series3 = newSeries();
+
+ m_graph->addSeries(series);
+ m_graph->addSeries(series2);
+ m_graph->addSeries(series3);
+
+ m_graph->seriesList()[0]->setSelectedItem(1);
+ QCOMPARE(m_graph->selectedSeries(), series);
+
+ m_graph->removeSeries(series);
+ QCOMPARE(m_graph->seriesList().size(), 2);
+ QVERIFY(!m_graph->selectedSeries());
+
+ m_graph->removeSeries(series2);
+ QCOMPARE(m_graph->seriesList().size(), 1);
+
+ m_graph->removeSeries(series3);
+ QCOMPARE(m_graph->seriesList().size(), 0);
+
+ delete series;
+ delete series2;
+ delete series3;
+}
+
+void tst_scatter::hasSeries()
+{
+ QScatter3DSeries *series1 = newSeries();
+ m_graph->addSeries(series1);
+ QCOMPARE(m_graph->hasSeries(series1), true);
+ QScatter3DSeries *series2 = newSeries();
+ QCOMPARE(m_graph->hasSeries(series2), false);
+}
+
+QTEST_MAIN(tst_scatter)
+#include "tst_scatter.moc"
diff --git a/tests/auto/cpptest/q3dscene-camera/CMakeLists.txt b/tests/auto/cpptest/q3dscene-camera/CMakeLists.txt
new file mode 100644
index 0000000..2cbec39
--- /dev/null
+++ b/tests/auto/cpptest/q3dscene-camera/CMakeLists.txt
@@ -0,0 +1,10 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+qt_internal_add_test(q3dscene-camera
+ SOURCES
+ tst_camera.cpp
+ LIBRARIES
+ Qt::Gui
+ Qt::Graphs
+)
diff --git a/tests/auto/cpptest/q3dscene-camera/tst_camera.cpp b/tests/auto/cpptest/q3dscene-camera/tst_camera.cpp
new file mode 100644
index 0000000..96ad6e4
--- /dev/null
+++ b/tests/auto/cpptest/q3dscene-camera/tst_camera.cpp
@@ -0,0 +1,162 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtTest/QtTest>
+
+#include <QtGraphs/Q3DCamera>
+
+class tst_camera: public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void initTestCase();
+ void cleanupTestCase();
+ void init();
+ void cleanup();
+
+ void construct();
+
+ void initialProperties();
+ void initializeProperties();
+ void invalidProperties();
+
+ void changePresets();
+
+private:
+ Q3DCamera *m_camera;
+};
+
+void tst_camera::initTestCase()
+{
+}
+
+void tst_camera::cleanupTestCase()
+{
+}
+
+void tst_camera::init()
+{
+ m_camera = new Q3DCamera();
+}
+
+void tst_camera::cleanup()
+{
+ delete m_camera;
+}
+
+void tst_camera::construct()
+{
+ Q3DCamera *camera = new Q3DCamera();
+ QVERIFY(camera);
+ delete camera;
+}
+
+void tst_camera::initialProperties()
+{
+ QVERIFY(m_camera);
+
+ QCOMPARE(m_camera->cameraPreset(), Q3DCamera::CameraPresetNone);
+ QCOMPARE(m_camera->maxZoomLevel(), 500.0f);
+ QCOMPARE(m_camera->minZoomLevel(), 10.0f);
+ QCOMPARE(m_camera->target(), QVector3D(0.0f, 0.0f, 0.0f));
+ QCOMPARE(m_camera->wrapXRotation(), true);
+ QCOMPARE(m_camera->wrapYRotation(), false);
+ QCOMPARE(m_camera->xRotation(), 0.0f);
+ QCOMPARE(m_camera->yRotation(), 0.0f);
+ QCOMPARE(m_camera->zoomLevel(), 100.0f);
+
+ // Common (from Q3DObject)
+ QVERIFY(!m_camera->parentScene());
+ QCOMPARE(m_camera->position(), QVector3D(0, 0, 0));
+}
+
+void tst_camera::initializeProperties()
+{
+ QVERIFY(m_camera);
+
+ m_camera->setMaxZoomLevel(1000.0f);
+ m_camera->setMinZoomLevel(100.0f);
+ m_camera->setTarget(QVector3D(1.0f, -1.0f, 1.0f));
+ m_camera->setWrapXRotation(false);
+ m_camera->setWrapYRotation(true);
+ m_camera->setXRotation(30.0f);
+ m_camera->setYRotation(30.0f);
+ m_camera->setZoomLevel(500.0f);
+
+ QCOMPARE(m_camera->maxZoomLevel(), 1000.0f);
+ QCOMPARE(m_camera->minZoomLevel(), 100.0f);
+ QCOMPARE(m_camera->target(), QVector3D(1.0f, -1.0f, 1.0f));
+ QCOMPARE(m_camera->wrapXRotation(), false);
+ QCOMPARE(m_camera->wrapYRotation(), true);
+ QCOMPARE(m_camera->xRotation(), 30.0f);
+ QCOMPARE(m_camera->yRotation(), 30.0f);
+ QCOMPARE(m_camera->zoomLevel(), 500.0f);
+
+ m_camera->setPosition(QVector3D(1.0f, 1.0f, 1.0f));
+
+ // Common (from Q3DObject)
+ QCOMPARE(m_camera->position(), QVector3D(1.0f, 1.0f, 1.0f));
+}
+
+void tst_camera::invalidProperties()
+{
+ m_camera->setTarget(QVector3D(-1.5f, -1.5f, -1.5f));
+ QCOMPARE(m_camera->target(), QVector3D(-1.0f, -1.0f, -1.0f));
+
+ m_camera->setTarget(QVector3D(1.5f, 1.5f, 1.5f));
+ QCOMPARE(m_camera->target(), QVector3D(1.0f, 1.0f, 1.0f));
+
+ m_camera->setMinZoomLevel(0.1f);
+ QCOMPARE(m_camera->minZoomLevel(), 1.0f);
+}
+
+void tst_camera::changePresets()
+{
+ m_camera->setCameraPreset(Q3DCamera::CameraPresetBehind); // Will be overridden by the the following sets
+ m_camera->setMaxZoomLevel(1000.0f);
+ m_camera->setMinZoomLevel(100.0f);
+ m_camera->setTarget(QVector3D(1.0f, -1.0f, 1.0f));
+ m_camera->setWrapXRotation(false);
+ m_camera->setWrapYRotation(true);
+ m_camera->setXRotation(30.0f);
+ m_camera->setYRotation(30.0f);
+ m_camera->setZoomLevel(500.0f);
+
+ QCOMPARE(m_camera->cameraPreset(), Q3DCamera::CameraPresetNone);
+ QCOMPARE(m_camera->maxZoomLevel(), 1000.0f);
+ QCOMPARE(m_camera->minZoomLevel(), 100.0f);
+ QCOMPARE(m_camera->target(), QVector3D(1.0f, -1.0f, 1.0f));
+ QCOMPARE(m_camera->wrapXRotation(), false);
+ QCOMPARE(m_camera->wrapYRotation(), true);
+ QCOMPARE(m_camera->xRotation(), 30.0f);
+ QCOMPARE(m_camera->yRotation(), 30.0f);
+ QCOMPARE(m_camera->zoomLevel(), 500.0f);
+
+ m_camera->setCameraPreset(Q3DCamera::CameraPresetBehind); // Sets target and rotations
+
+ QCOMPARE(m_camera->cameraPreset(), Q3DCamera::CameraPresetBehind);
+ QCOMPARE(m_camera->maxZoomLevel(), 1000.0f);
+ QCOMPARE(m_camera->minZoomLevel(), 100.0f);
+ QCOMPARE(m_camera->target(), QVector3D(0.0f, 0.0f, 0.0f));
+ QCOMPARE(m_camera->wrapXRotation(), false);
+ QCOMPARE(m_camera->wrapYRotation(), true);
+ QCOMPARE(m_camera->xRotation(), 180.0f);
+ QCOMPARE(m_camera->yRotation(), 22.5f);
+ QCOMPARE(m_camera->zoomLevel(), 500.0f);
+
+ m_camera->setCameraPosition(10.0f, 15.0f, 125.0f); // Overrides preset
+
+ QCOMPARE(m_camera->cameraPreset(), Q3DCamera::CameraPresetNone);
+ QCOMPARE(m_camera->maxZoomLevel(), 1000.0f);
+ QCOMPARE(m_camera->minZoomLevel(), 100.0f);
+ QCOMPARE(m_camera->target(), QVector3D(0.0f, 0.0f, 0.0f));
+ QCOMPARE(m_camera->wrapXRotation(), false);
+ QCOMPARE(m_camera->wrapYRotation(), true);
+ QCOMPARE(m_camera->xRotation(), 10.0f);
+ QCOMPARE(m_camera->yRotation(), 15.0f);
+ QCOMPARE(m_camera->zoomLevel(), 125.0f);
+}
+
+QTEST_MAIN(tst_camera)
+#include "tst_camera.moc"
diff --git a/tests/auto/cpptest/q3dscene-light/CMakeLists.txt b/tests/auto/cpptest/q3dscene-light/CMakeLists.txt
new file mode 100644
index 0000000..4c46f66
--- /dev/null
+++ b/tests/auto/cpptest/q3dscene-light/CMakeLists.txt
@@ -0,0 +1,10 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+qt_internal_add_test(q3dscene-light
+ SOURCES
+ tst_light.cpp
+ LIBRARIES
+ Qt::Gui
+ Qt::Graphs
+)
diff --git a/tests/auto/cpptest/q3dscene-light/tst_light.cpp b/tests/auto/cpptest/q3dscene-light/tst_light.cpp
new file mode 100644
index 0000000..03b158d
--- /dev/null
+++ b/tests/auto/cpptest/q3dscene-light/tst_light.cpp
@@ -0,0 +1,76 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtTest/QtTest>
+
+#include <QtGraphs/Q3DLight>
+
+class tst_light: public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void initTestCase();
+ void cleanupTestCase();
+ void init();
+ void cleanup();
+
+ void construct();
+
+ void initialProperties();
+ void initializeProperties();
+
+private:
+ Q3DLight *m_light;
+};
+
+void tst_light::initTestCase()
+{
+}
+
+void tst_light::cleanupTestCase()
+{
+}
+
+void tst_light::init()
+{
+ m_light = new Q3DLight();
+}
+
+void tst_light::cleanup()
+{
+ delete m_light;
+}
+
+void tst_light::construct()
+{
+ Q3DLight *light = new Q3DLight();
+ QVERIFY(light);
+ delete light;
+}
+
+void tst_light::initialProperties()
+{
+ QVERIFY(m_light);
+
+ QCOMPARE(m_light->isAutoPosition(), false);
+
+ // Common (from Q3DObject)
+ QVERIFY(!m_light->parentScene());
+ QCOMPARE(m_light->position(), QVector3D(0, 0, 0));
+}
+
+void tst_light::initializeProperties()
+{
+ QVERIFY(m_light);
+
+ m_light->setAutoPosition(true);
+ QCOMPARE(m_light->isAutoPosition(), true);
+
+ // Common (from Q3DObject)
+ m_light->setPosition(QVector3D(1.0f, 1.0f, 1.0f));
+ QCOMPARE(m_light->position(), QVector3D(1.0f, 1.0f, 1.0f));
+}
+
+QTEST_MAIN(tst_light)
+#include "tst_light.moc"
diff --git a/tests/auto/cpptest/q3dscene/CMakeLists.txt b/tests/auto/cpptest/q3dscene/CMakeLists.txt
new file mode 100644
index 0000000..67a9666
--- /dev/null
+++ b/tests/auto/cpptest/q3dscene/CMakeLists.txt
@@ -0,0 +1,13 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+qt_internal_add_test(q3dscene
+ SOURCES
+ tst_scene.cpp
+ INCLUDE_DIRECTORIES
+ ../common
+ LIBRARIES
+ Qt::Gui
+ Qt::GuiPrivate
+ Qt::Graphs
+)
diff --git a/tests/auto/cpptest/q3dscene/tst_scene.cpp b/tests/auto/cpptest/q3dscene/tst_scene.cpp
new file mode 100644
index 0000000..5cc12b9
--- /dev/null
+++ b/tests/auto/cpptest/q3dscene/tst_scene.cpp
@@ -0,0 +1,162 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtTest/QtTest>
+
+#include <QtGraphs/Q3DScene>
+#include <QtGraphs/Q3DBars>
+
+#include "cpptestutil.h"
+
+class tst_scene: public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void initTestCase();
+ void cleanupTestCase();
+ void init();
+ void cleanup();
+
+ void construct();
+
+ void initialProperties();
+ void initializeProperties();
+ void invalidProperties();
+
+ void subViews();
+
+private:
+ Q3DScene *m_scene;
+};
+
+void tst_scene::initTestCase()
+{
+}
+
+void tst_scene::cleanupTestCase()
+{
+}
+
+void tst_scene::init()
+{
+ m_scene = new Q3DScene();
+}
+
+void tst_scene::cleanup()
+{
+ delete m_scene;
+}
+
+void tst_scene::construct()
+{
+ Q3DScene *scene = new Q3DScene();
+ QVERIFY(scene);
+ delete scene;
+}
+
+void tst_scene::initialProperties()
+{
+ QVERIFY(m_scene);
+
+ QVERIFY(m_scene->activeCamera());
+ QVERIFY(m_scene->activeLight());
+ QCOMPARE(m_scene->devicePixelRatio(), 1.0f);
+ QCOMPARE(m_scene->graphPositionQuery(), m_scene->invalidSelectionPoint());
+ QCOMPARE(m_scene->primarySubViewport(), QRect(0, 0, 0, 0));
+ QCOMPARE(m_scene->secondarySubViewport(), QRect(0, 0, 0, 0));
+ QCOMPARE(m_scene->isSecondarySubviewOnTop(), true);
+ QCOMPARE(m_scene->selectionQueryPosition(), m_scene->invalidSelectionPoint());
+ QCOMPARE(m_scene->isSlicingActive(), false);
+ QCOMPARE(m_scene->viewport(), QRect(0, 0, 0, 0));
+}
+
+void tst_scene::initializeProperties()
+{
+ QVERIFY(m_scene);
+
+ Q3DCamera *camera1 = new Q3DCamera();
+ Q3DLight *light1 = new Q3DLight();
+
+ m_scene->setActiveCamera(camera1);
+ m_scene->setActiveLight(light1);
+ m_scene->setDevicePixelRatio(2.0f);
+ m_scene->setGraphPositionQuery(QPoint(0, 0));
+ m_scene->setPrimarySubViewport(QRect(0, 0, 50, 50));
+ m_scene->setSecondarySubViewport(QRect(50, 50, 100, 100));
+ m_scene->setSecondarySubviewOnTop(false);
+ m_scene->setSlicingActive(true);
+ m_scene->setSelectionQueryPosition(QPoint(0, 0));
+
+ QCOMPARE(m_scene->activeCamera(), camera1);
+ QCOMPARE(m_scene->activeLight(), light1);
+ QCOMPARE(m_scene->devicePixelRatio(), 2.0f);
+ QCOMPARE(m_scene->graphPositionQuery(), QPoint(0, 0)); // TODO: When doing signal checks, add tests to check that queries return something (asynchronously)
+ QCOMPARE(m_scene->primarySubViewport(), QRect(0, 0, 50, 50));
+ QCOMPARE(m_scene->secondarySubViewport(), QRect(50, 50, 100, 100));
+ QCOMPARE(m_scene->isSecondarySubviewOnTop(), false);
+ QCOMPARE(m_scene->selectionQueryPosition(), QPoint(0, 0)); // TODO: When doing signal checks, add tests to check that queries return something (asynchronously)
+ QCOMPARE(m_scene->isSlicingActive(), true);
+ QCOMPARE(m_scene->viewport(), QRect(0, 0, 150, 150));
+
+ m_scene->setPrimarySubViewport(QRect());
+ m_scene->setSecondarySubViewport(QRect());
+
+ QCOMPARE(m_scene->primarySubViewport(), QRect(0, 0, 30, 30));
+ QCOMPARE(m_scene->secondarySubViewport(), QRect(0, 0, 150, 150));
+}
+
+void tst_scene::invalidProperties()
+{
+ m_scene->setPrimarySubViewport(QRect(0, 0, -50, -50));
+ m_scene->setSecondarySubViewport(QRect(-50, -50, -100, -100));
+ QCOMPARE(m_scene->primarySubViewport(), QRect(0, 0, 0, 0));
+ QCOMPARE(m_scene->secondarySubViewport(), QRect(0, 0, 0, 0));
+}
+
+void tst_scene::subViews()
+{
+ if (!CpptestUtil::isOpenGLSupported())
+ QSKIP("OpenGL not supported on this platform");
+
+ Q3DBars graph;
+ graph.setPosition(QPoint(0, 0));
+ graph.setWidth(200);
+ graph.setHeight(200);
+
+ Q3DScene *scene = graph.scene();
+
+ QCoreApplication::processEvents();
+
+ QTRY_COMPARE(scene->viewport(), QRect(0, 0, 200, 200));
+ QCOMPARE(scene->primarySubViewport(), QRect(0, 0, 200, 200));
+ QCOMPARE(scene->secondarySubViewport(), QRect(0, 0, 0, 0));
+
+ QCOMPARE(scene->isSecondarySubviewOnTop(), true);
+ QCOMPARE(scene->isPointInPrimarySubView(QPoint(100, 100)), true);
+ QCOMPARE(scene->isPointInPrimarySubView(QPoint(201, 201)), false);
+ QCOMPARE(scene->isPointInSecondarySubView(QPoint(100, 100)), false);
+
+ scene->setSlicingActive(true);
+
+ QCOMPARE(scene->isSecondarySubviewOnTop(), false);
+ QCOMPARE(scene->primarySubViewport(), QRect(0, 0, 40, 40));
+ QCOMPARE(scene->secondarySubViewport(), QRect(0, 0, 200, 200));
+ QCOMPARE(scene->isPointInPrimarySubView(QPoint(100, 100)), false);
+ QCOMPARE(scene->isPointInPrimarySubView(QPoint(30, 30)), true);
+ QCOMPARE(scene->isPointInSecondarySubView(QPoint(100, 100)), true);
+ QCOMPARE(scene->isPointInSecondarySubView(QPoint(30, 30)), false);
+
+ scene->setSecondarySubviewOnTop(true);
+
+ QCOMPARE(scene->isSecondarySubviewOnTop(), true);
+ QCOMPARE(scene->primarySubViewport(), QRect(0, 0, 40, 40));
+ QCOMPARE(scene->secondarySubViewport(), QRect(0, 0, 200, 200));
+ QCOMPARE(scene->isPointInPrimarySubView(QPoint(100, 100)), false);
+ QCOMPARE(scene->isPointInPrimarySubView(QPoint(30, 30)), false);
+ QCOMPARE(scene->isPointInSecondarySubView(QPoint(100, 100)), true);
+ QCOMPARE(scene->isPointInSecondarySubView(QPoint(30, 30)), true);
+}
+
+QTEST_MAIN(tst_scene)
+#include "tst_scene.moc"
diff --git a/tests/auto/cpptest/q3dsurface-heightproxy/CMakeLists.txt b/tests/auto/cpptest/q3dsurface-heightproxy/CMakeLists.txt
new file mode 100644
index 0000000..8c9f222
--- /dev/null
+++ b/tests/auto/cpptest/q3dsurface-heightproxy/CMakeLists.txt
@@ -0,0 +1,21 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+qt_internal_add_test(q3dsurface-heightproxy
+ SOURCES
+ tst_proxy.cpp
+ LIBRARIES
+ Qt::Gui
+ Qt::Graphs
+)
+
+set(q3dsurface-heightproxy_resource_files
+ "customtexture.jpg"
+)
+
+qt_internal_add_resource(q3dsurface-heightproxy "q3dsurface-heightproxy"
+ PREFIX
+ "/"
+ FILES
+ ${q3dsurface-heightproxy_resource_files}
+)
diff --git a/tests/auto/cpptest/q3dsurface-heightproxy/customtexture.jpg b/tests/auto/cpptest/q3dsurface-heightproxy/customtexture.jpg
new file mode 100644
index 0000000..2580f5b
--- /dev/null
+++ b/tests/auto/cpptest/q3dsurface-heightproxy/customtexture.jpg
Binary files differ
diff --git a/tests/auto/cpptest/q3dsurface-heightproxy/tst_proxy.cpp b/tests/auto/cpptest/q3dsurface-heightproxy/tst_proxy.cpp
new file mode 100644
index 0000000..ed547b7
--- /dev/null
+++ b/tests/auto/cpptest/q3dsurface-heightproxy/tst_proxy.cpp
@@ -0,0 +1,143 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtTest/QtTest>
+
+#include <QtGraphs/QHeightMapSurfaceDataProxy>
+
+class tst_proxy: public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void initTestCase();
+ void cleanupTestCase();
+ void init();
+ void cleanup();
+
+ void construct();
+
+ void initialProperties();
+ void initializeProperties();
+ void invalidProperties();
+
+private:
+ QHeightMapSurfaceDataProxy *m_proxy;
+};
+
+void tst_proxy::initTestCase()
+{
+}
+
+void tst_proxy::cleanupTestCase()
+{
+}
+
+void tst_proxy::init()
+{
+ m_proxy = new QHeightMapSurfaceDataProxy();
+}
+
+void tst_proxy::cleanup()
+{
+ delete m_proxy;
+}
+
+void tst_proxy::construct()
+{
+ QHeightMapSurfaceDataProxy *proxy = new QHeightMapSurfaceDataProxy();
+ QVERIFY(proxy);
+ delete proxy;
+
+ QImage image(QSize(10, 10), QImage::Format_ARGB32);
+ image.fill(0);
+ proxy = new QHeightMapSurfaceDataProxy(image);
+ QCoreApplication::processEvents();
+ QVERIFY(proxy);
+ QCoreApplication::processEvents();
+ QCOMPARE(proxy->columnCount(), 10);
+ QCOMPARE(proxy->rowCount(), 10);
+ delete proxy;
+
+ proxy = new QHeightMapSurfaceDataProxy(":/customtexture.jpg");
+ QCoreApplication::processEvents();
+ QVERIFY(proxy);
+ QCoreApplication::processEvents();
+ QCOMPARE(proxy->columnCount(), 24);
+ QCOMPARE(proxy->rowCount(), 24);
+ delete proxy;
+}
+
+void tst_proxy::initialProperties()
+{
+ QVERIFY(m_proxy);
+
+ QCOMPARE(m_proxy->heightMap(), QImage());
+ QCOMPARE(m_proxy->heightMapFile(), QString(""));
+ QCOMPARE(m_proxy->maxXValue(), 10.0f);
+ QCOMPARE(m_proxy->maxZValue(), 10.0f);
+ QCOMPARE(m_proxy->minXValue(), 0.0f);
+ QCOMPARE(m_proxy->minZValue(), 0.0f);
+
+ QCOMPARE(m_proxy->columnCount(), 0);
+ QCOMPARE(m_proxy->rowCount(), 0);
+ QVERIFY(!m_proxy->series());
+
+ QCOMPARE(m_proxy->type(), QAbstractDataProxy::DataTypeSurface);
+}
+
+void tst_proxy::initializeProperties()
+{
+ QVERIFY(m_proxy);
+
+ m_proxy->setHeightMapFile(":/customtexture.jpg");
+ m_proxy->setMaxXValue(11.0f);
+ m_proxy->setMaxZValue(11.0f);
+ m_proxy->setMinXValue(-10.0f);
+ m_proxy->setMinZValue(-10.0f);
+
+ QCoreApplication::processEvents();
+
+ QCOMPARE(m_proxy->heightMapFile(), QString(":/customtexture.jpg"));
+ QCOMPARE(m_proxy->maxXValue(), 11.0f);
+ QCOMPARE(m_proxy->maxZValue(), 11.0f);
+ QCOMPARE(m_proxy->minXValue(), -10.0f);
+ QCOMPARE(m_proxy->minZValue(), -10.0f);
+
+ QCOMPARE(m_proxy->columnCount(), 24);
+ QCOMPARE(m_proxy->rowCount(), 24);
+
+ m_proxy->setHeightMapFile("");
+
+ QCoreApplication::processEvents();
+
+ QCOMPARE(m_proxy->columnCount(), 0);
+ QCOMPARE(m_proxy->rowCount(), 0);
+
+ m_proxy->setHeightMap(QImage(":/customtexture.jpg"));
+
+ QCoreApplication::processEvents();
+
+ QCOMPARE(m_proxy->columnCount(), 24);
+ QCOMPARE(m_proxy->rowCount(), 24);
+}
+
+void tst_proxy::invalidProperties()
+{
+ m_proxy->setMaxXValue(-10.0f);
+ m_proxy->setMaxZValue(-10.0f);
+ QCOMPARE(m_proxy->maxXValue(), -10.0f);
+ QCOMPARE(m_proxy->maxZValue(), -10.0f);
+ QCOMPARE(m_proxy->minXValue(), -11.0f);
+ QCOMPARE(m_proxy->minZValue(), -11.0f);
+
+ m_proxy->setMinXValue(10.0f);
+ m_proxy->setMinZValue(10.0f);
+ QCOMPARE(m_proxy->maxXValue(), 11.0f);
+ QCOMPARE(m_proxy->maxZValue(), 11.0f);
+ QCOMPARE(m_proxy->minXValue(), 10.0f);
+ QCOMPARE(m_proxy->minZValue(), 10.0f);
+}
+
+QTEST_MAIN(tst_proxy)
+#include "tst_proxy.moc"
diff --git a/tests/auto/cpptest/q3dsurface-modelproxy-nan/CMakeLists.txt b/tests/auto/cpptest/q3dsurface-modelproxy-nan/CMakeLists.txt
new file mode 100644
index 0000000..d7333d9
--- /dev/null
+++ b/tests/auto/cpptest/q3dsurface-modelproxy-nan/CMakeLists.txt
@@ -0,0 +1,14 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+qt_internal_add_test(q3dsurface-modelproxy-nan
+ SOURCES
+ tst_proxy.cpp
+ INCLUDE_DIRECTORIES
+ ../common
+ LIBRARIES
+ Qt::Gui
+ Qt::GuiPrivate
+ Qt::Widgets
+ Qt::Graphs
+)
diff --git a/tests/auto/cpptest/q3dsurface-modelproxy-nan/tst_proxy.cpp b/tests/auto/cpptest/q3dsurface-modelproxy-nan/tst_proxy.cpp
new file mode 100644
index 0000000..12539ca
--- /dev/null
+++ b/tests/auto/cpptest/q3dsurface-modelproxy-nan/tst_proxy.cpp
@@ -0,0 +1,266 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtTest/QtTest>
+
+#include <QtGraphs/QItemModelSurfaceDataProxy>
+#include <QtGraphs/Q3DSurface>
+
+#include "cpptestutil.h"
+
+class tst_proxy: public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void initTestCase();
+ void cleanupTestCase();
+ void init();
+ void cleanup();
+
+ void dataContainingNaNFirstRow();
+ void dataContainingNaNLastRow();
+ void dataContainingNaNFirstLastRow();
+};
+
+void tst_proxy::initTestCase()
+{
+}
+
+void tst_proxy::cleanupTestCase()
+{
+}
+
+void tst_proxy::init()
+{
+}
+
+void tst_proxy::cleanup()
+{
+}
+
+void tst_proxy::dataContainingNaNFirstRow()
+{
+ if (!CpptestUtil::isOpenGLSupported())
+ QSKIP("OpenGL not supported on this platform");
+
+ const int size = 10;
+ const int missingRow = 0;
+
+ QItemModelSurfaceDataProxy *proxy = new QItemModelSurfaceDataProxy();
+ QSurface3DSeries *series = new QSurface3DSeries(proxy);
+ Q3DSurface *graph = new Q3DSurface();
+ graph->addSeries(series);
+
+ // X
+ QSurfaceDataArray *array = new QSurfaceDataArray();
+ array->reserve(size);
+ for (int i = 0; i < size; i++) {
+ QSurfaceDataRow *row = new QSurfaceDataRow(size);
+ for (int j = 0; j < size; j++) {
+ (*row)[j].setPosition(QVector3D((i == missingRow) ? std::numeric_limits<float>::quiet_NaN()
+ : static_cast<float>(i),
+ qSin(static_cast<float>(i)), static_cast<float>(j)));
+ }
+ *array << row;
+ }
+ proxy->resetArray(array);
+ QVERIFY(!qIsNaN(graph->axisX()->min()));
+ QVERIFY(!qIsNaN(graph->axisX()->max()));
+ QVERIFY(!qIsNaN(graph->axisY()->min()));
+ QVERIFY(!qIsNaN(graph->axisY()->max()));
+ QVERIFY(!qIsNaN(graph->axisZ()->min()));
+ QVERIFY(!qIsNaN(graph->axisZ()->max()));
+
+ // Y
+ for (int i = 0; i < size; i++) {
+ QSurfaceDataRow *row = new QSurfaceDataRow(size);
+ for (int j = 0; j < size; j++) {
+ (*row)[j].setPosition(QVector3D(static_cast<float>(i),
+ (i == missingRow) ? std::numeric_limits<float>::quiet_NaN()
+ : qSin(static_cast<float>(i)),
+ static_cast<float>(j)));
+ }
+ *array << row;
+ }
+ proxy->resetArray(array);
+ QVERIFY(!qIsNaN(graph->axisX()->min()));
+ QVERIFY(!qIsNaN(graph->axisX()->max()));
+ QVERIFY(!qIsNaN(graph->axisY()->min()));
+ QVERIFY(!qIsNaN(graph->axisY()->max()));
+ QVERIFY(!qIsNaN(graph->axisZ()->min()));
+ QVERIFY(!qIsNaN(graph->axisZ()->max()));
+
+ // Z
+ for (int i = 0; i < size; i++) {
+ QSurfaceDataRow *row = new QSurfaceDataRow(size);
+ for (int j = 0; j < size; j++) {
+ (*row)[j].setPosition(QVector3D(static_cast<float>(i),
+ qSin(static_cast<float>(i)),
+ (i == missingRow) ? std::numeric_limits<float>::quiet_NaN()
+ : static_cast<float>(j)));
+ }
+ *array << row;
+ }
+ proxy->resetArray(array);
+ QVERIFY(!qIsNaN(graph->axisX()->min()));
+ QVERIFY(!qIsNaN(graph->axisX()->max()));
+ QVERIFY(!qIsNaN(graph->axisY()->min()));
+ QVERIFY(!qIsNaN(graph->axisY()->max()));
+ QVERIFY(!qIsNaN(graph->axisZ()->min()));
+ QVERIFY(!qIsNaN(graph->axisZ()->max()));
+
+ delete graph;
+
+}
+
+void tst_proxy::dataContainingNaNLastRow()
+{
+ if (!CpptestUtil::isOpenGLSupported())
+ QSKIP("OpenGL not supported on this platform");
+
+ const int size = 10;
+ const int missingRow = size - 1;
+ QItemModelSurfaceDataProxy *proxy = new QItemModelSurfaceDataProxy();
+ QSurface3DSeries *series = new QSurface3DSeries(proxy);
+ Q3DSurface *graph = new Q3DSurface();
+ graph->addSeries(series);
+
+ // X
+ QSurfaceDataArray *array = new QSurfaceDataArray();
+ array->reserve(size);
+ for (int i = 0; i < size; i++) {
+ QSurfaceDataRow *row = new QSurfaceDataRow(size);
+ for (int j = 0; j < size; j++) {
+ (*row)[j].setPosition(QVector3D((i == missingRow) ? std::numeric_limits<float>::quiet_NaN()
+ : static_cast<float>(i),
+ qSin(static_cast<float>(i)), static_cast<float>(j)));
+ }
+ *array << row;
+ }
+ proxy->resetArray(array);
+ QVERIFY(!qIsNaN(graph->axisX()->min()));
+ QVERIFY(!qIsNaN(graph->axisX()->max()));
+ QVERIFY(!qIsNaN(graph->axisY()->min()));
+ QVERIFY(!qIsNaN(graph->axisY()->max()));
+ QVERIFY(!qIsNaN(graph->axisZ()->min()));
+ QVERIFY(!qIsNaN(graph->axisZ()->max()));
+
+ // Y
+ for (int i = 0; i < size; i++) {
+ QSurfaceDataRow *row = new QSurfaceDataRow(size);
+ for (int j = 0; j < size; j++) {
+ (*row)[j].setPosition(QVector3D(static_cast<float>(i),
+ (i == missingRow) ? std::numeric_limits<float>::quiet_NaN()
+ : qSin(static_cast<float>(i)),
+ static_cast<float>(j)));
+ }
+ *array << row;
+ }
+ proxy->resetArray(array);
+ QVERIFY(!qIsNaN(graph->axisX()->min()));
+ QVERIFY(!qIsNaN(graph->axisX()->max()));
+ QVERIFY(!qIsNaN(graph->axisY()->min()));
+ QVERIFY(!qIsNaN(graph->axisY()->max()));
+ QVERIFY(!qIsNaN(graph->axisZ()->min()));
+ QVERIFY(!qIsNaN(graph->axisZ()->max()));
+
+ // Z
+ for (int i = 0; i < size; i++) {
+ QSurfaceDataRow *row = new QSurfaceDataRow(size);
+ for (int j = 0; j < size; j++) {
+ (*row)[j].setPosition(QVector3D(static_cast<float>(i),
+ qSin(static_cast<float>(i)),
+ (i == missingRow) ? std::numeric_limits<float>::quiet_NaN()
+ : static_cast<float>(j)));
+ }
+ *array << row;
+ }
+ proxy->resetArray(array);
+ QVERIFY(!qIsNaN(graph->axisX()->min()));
+ QVERIFY(!qIsNaN(graph->axisX()->max()));
+ QVERIFY(!qIsNaN(graph->axisY()->min()));
+ QVERIFY(!qIsNaN(graph->axisY()->max()));
+ QVERIFY(!qIsNaN(graph->axisZ()->min()));
+ QVERIFY(!qIsNaN(graph->axisZ()->max()));
+ delete graph;
+}
+
+void tst_proxy::dataContainingNaNFirstLastRow()
+{
+ if (!CpptestUtil::isOpenGLSupported())
+ QSKIP("OpenGL not supported on this platform");
+
+ const int size = 10;
+ const int rowFirst = 0;
+ const int rowLast = size - 1;
+ QItemModelSurfaceDataProxy *proxy = new QItemModelSurfaceDataProxy();
+ QSurface3DSeries *series = new QSurface3DSeries(proxy);
+ Q3DSurface *graph = new Q3DSurface();
+ graph->addSeries(series);
+
+ // X
+ QSurfaceDataArray *array = new QSurfaceDataArray();
+ array->reserve(size);
+ for (int i = 0; i < size; i++) {
+ bool missingRow = (i == rowFirst || i == rowLast);
+ QSurfaceDataRow *row = new QSurfaceDataRow(size);
+ for (int j = 0; j < size; j++) {
+ (*row)[j].setPosition(QVector3D(missingRow ? std::numeric_limits<float>::quiet_NaN()
+ : static_cast<float>(i),
+ qSin(static_cast<float>(i)), static_cast<float>(j)));
+ }
+ *array << row;
+ }
+ proxy->resetArray(array);
+ QVERIFY(!qIsNaN(graph->axisX()->min()));
+ QVERIFY(!qIsNaN(graph->axisX()->max()));
+ QVERIFY(!qIsNaN(graph->axisY()->min()));
+ QVERIFY(!qIsNaN(graph->axisY()->max()));
+ QVERIFY(!qIsNaN(graph->axisZ()->min()));
+ QVERIFY(!qIsNaN(graph->axisZ()->max()));
+
+ // Y
+ for (int i = 0; i < size; i++) {
+ bool missingRow = (i == rowFirst || i == rowLast);
+ QSurfaceDataRow *row = new QSurfaceDataRow(size);
+ for (int j = 0; j < size; j++) {
+ (*row)[j].setPosition(QVector3D(static_cast<float>(i),
+ missingRow ? std::numeric_limits<float>::quiet_NaN()
+ : qSin(static_cast<float>(i)),
+ static_cast<float>(j)));
+ }
+ *array << row;
+ }
+ proxy->resetArray(array);
+ QVERIFY(!qIsNaN(graph->axisX()->min()));
+ QVERIFY(!qIsNaN(graph->axisX()->max()));
+ QVERIFY(!qIsNaN(graph->axisY()->min()));
+ QVERIFY(!qIsNaN(graph->axisY()->max()));
+ QVERIFY(!qIsNaN(graph->axisZ()->min()));
+ QVERIFY(!qIsNaN(graph->axisZ()->max()));
+
+ // Z
+ for (int i = 0; i < size; i++) {
+ bool missingRow = (i == rowFirst || i == rowLast);
+ QSurfaceDataRow *row = new QSurfaceDataRow(size);
+ for (int j = 0; j < size; j++) {
+ (*row)[j].setPosition(QVector3D(static_cast<float>(i),
+ qSin(static_cast<float>(i)),
+ missingRow ? std::numeric_limits<float>::quiet_NaN()
+ : static_cast<float>(j)));
+ }
+ *array << row;
+ }
+ proxy->resetArray(array);
+ QVERIFY(!qIsNaN(graph->axisX()->min()));
+ QVERIFY(!qIsNaN(graph->axisX()->max()));
+ QVERIFY(!qIsNaN(graph->axisY()->min()));
+ QVERIFY(!qIsNaN(graph->axisY()->max()));
+ QVERIFY(!qIsNaN(graph->axisZ()->min()));
+ QVERIFY(!qIsNaN(graph->axisZ()->max()));
+ delete graph;
+}
+
+QTEST_MAIN(tst_proxy)
+#include "tst_proxy.moc"
diff --git a/tests/auto/cpptest/q3dsurface-modelproxy/CMakeLists.txt b/tests/auto/cpptest/q3dsurface-modelproxy/CMakeLists.txt
new file mode 100644
index 0000000..1cdadf8
--- /dev/null
+++ b/tests/auto/cpptest/q3dsurface-modelproxy/CMakeLists.txt
@@ -0,0 +1,14 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+qt_internal_add_test(q3dsurface-modelproxy
+ SOURCES
+ tst_proxy.cpp
+ INCLUDE_DIRECTORIES
+ ../common
+ LIBRARIES
+ Qt::Gui
+ Qt::GuiPrivate
+ Qt::Widgets
+ Qt::Graphs
+)
diff --git a/tests/auto/cpptest/q3dsurface-modelproxy/tst_proxy.cpp b/tests/auto/cpptest/q3dsurface-modelproxy/tst_proxy.cpp
new file mode 100644
index 0000000..bea7994
--- /dev/null
+++ b/tests/auto/cpptest/q3dsurface-modelproxy/tst_proxy.cpp
@@ -0,0 +1,274 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtTest/QtTest>
+
+#include <QtGraphs/QItemModelSurfaceDataProxy>
+#include <QtGraphs/Q3DSurface>
+#include <QtWidgets/QTableWidget>
+
+#include "cpptestutil.h"
+
+class tst_proxy: public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void initTestCase();
+ void cleanupTestCase();
+ void init();
+ void cleanup();
+
+ void construct();
+
+ void initialProperties();
+ void initializeProperties();
+
+ void multiMatch();
+
+private:
+ QItemModelSurfaceDataProxy *m_proxy;
+};
+
+void tst_proxy::initTestCase()
+{
+}
+
+void tst_proxy::cleanupTestCase()
+{
+}
+
+void tst_proxy::init()
+{
+ m_proxy = new QItemModelSurfaceDataProxy();
+}
+
+void tst_proxy::cleanup()
+{
+ delete m_proxy;
+}
+
+
+void tst_proxy::construct()
+{
+ QItemModelSurfaceDataProxy *proxy = new QItemModelSurfaceDataProxy();
+ QVERIFY(proxy);
+ delete proxy;
+
+ QTableWidget table;
+
+ proxy = new QItemModelSurfaceDataProxy(table.model());
+ QVERIFY(proxy);
+ delete proxy;
+
+ proxy = new QItemModelSurfaceDataProxy(table.model(), "y");
+ QVERIFY(proxy);
+ QCOMPARE(proxy->rowRole(), QString(""));
+ QCOMPARE(proxy->columnRole(), QString(""));
+ QCOMPARE(proxy->xPosRole(), QString(""));
+ QCOMPARE(proxy->yPosRole(), QString("y"));
+ QCOMPARE(proxy->zPosRole(), QString(""));
+ QCOMPARE(proxy->rowCategories().size(), 0);
+ QCOMPARE(proxy->columnCategories().size(), 0);
+ delete proxy;
+
+ proxy = new QItemModelSurfaceDataProxy(table.model(), "row", "column", "y");
+ QVERIFY(proxy);
+ QCOMPARE(proxy->rowRole(), QString("row"));
+ QCOMPARE(proxy->columnRole(), QString("column"));
+ QCOMPARE(proxy->xPosRole(), QString("column"));
+ QCOMPARE(proxy->yPosRole(), QString("y"));
+ QCOMPARE(proxy->zPosRole(), QString("row"));
+ QCOMPARE(proxy->rowCategories().size(), 0);
+ QCOMPARE(proxy->columnCategories().size(), 0);
+ delete proxy;
+
+ proxy = new QItemModelSurfaceDataProxy(table.model(), "row", "column", "x", "y", "z");
+ QVERIFY(proxy);
+ QCOMPARE(proxy->rowRole(), QString("row"));
+ QCOMPARE(proxy->columnRole(), QString("column"));
+ QCOMPARE(proxy->xPosRole(), QString("x"));
+ QCOMPARE(proxy->yPosRole(), QString("y"));
+ QCOMPARE(proxy->zPosRole(), QString("z"));
+ QCOMPARE(proxy->rowCategories().size(), 0);
+ QCOMPARE(proxy->columnCategories().size(), 0);
+ delete proxy;
+
+ proxy = new QItemModelSurfaceDataProxy(table.model(), "row", "column", "y",
+ QStringList() << "rowCat", QStringList() << "colCat");
+ QVERIFY(proxy);
+ QCOMPARE(proxy->rowRole(), QString("row"));
+ QCOMPARE(proxy->columnRole(), QString("column"));
+ QCOMPARE(proxy->xPosRole(), QString("column"));
+ QCOMPARE(proxy->yPosRole(), QString("y"));
+ QCOMPARE(proxy->zPosRole(), QString("row"));
+ QCOMPARE(proxy->rowCategories().size(), 1);
+ QCOMPARE(proxy->columnCategories().size(), 1);
+ delete proxy;
+
+ proxy = new QItemModelSurfaceDataProxy(table.model(), "row", "column", "x", "y", "z",
+ QStringList() << "rowCat", QStringList() << "colCat");
+ QVERIFY(proxy);
+ QCOMPARE(proxy->rowRole(), QString("row"));
+ QCOMPARE(proxy->columnRole(), QString("column"));
+ QCOMPARE(proxy->xPosRole(), QString("x"));
+ QCOMPARE(proxy->yPosRole(), QString("y"));
+ QCOMPARE(proxy->zPosRole(), QString("z"));
+ QCOMPARE(proxy->rowCategories().size(), 1);
+ QCOMPARE(proxy->columnCategories().size(), 1);
+ delete proxy;
+}
+
+void tst_proxy::initialProperties()
+{
+ QVERIFY(m_proxy);
+
+ QCOMPARE(m_proxy->autoColumnCategories(), true);
+ QCOMPARE(m_proxy->autoRowCategories(), true);
+ QCOMPARE(m_proxy->columnCategories(), QStringList());
+ QCOMPARE(m_proxy->columnRole(), QString());
+ QCOMPARE(m_proxy->columnRolePattern(), QRegularExpression());
+ QCOMPARE(m_proxy->columnRoleReplace(), QString());
+ QVERIFY(!m_proxy->itemModel());
+ QCOMPARE(m_proxy->multiMatchBehavior(), QItemModelSurfaceDataProxy::MMBLast);
+ QCOMPARE(m_proxy->rowCategories(), QStringList());
+ QCOMPARE(m_proxy->rowRole(), QString());
+ QCOMPARE(m_proxy->rowRolePattern(), QRegularExpression());
+ QCOMPARE(m_proxy->rowRoleReplace(), QString());
+ QCOMPARE(m_proxy->useModelCategories(), false);
+ QCOMPARE(m_proxy->xPosRole(), QString());
+ QCOMPARE(m_proxy->xPosRolePattern(), QRegularExpression());
+ QCOMPARE(m_proxy->xPosRoleReplace(), QString());
+ QCOMPARE(m_proxy->yPosRole(), QString());
+ QCOMPARE(m_proxy->yPosRolePattern(), QRegularExpression());
+ QCOMPARE(m_proxy->yPosRoleReplace(), QString());
+ QCOMPARE(m_proxy->zPosRole(), QString());
+ QCOMPARE(m_proxy->zPosRolePattern(), QRegularExpression());
+ QCOMPARE(m_proxy->zPosRoleReplace(), QString());
+
+ QCOMPARE(m_proxy->columnCount(), 0);
+ QCOMPARE(m_proxy->rowCount(), 0);
+ QVERIFY(!m_proxy->series());
+
+ QCOMPARE(m_proxy->type(), QAbstractDataProxy::DataTypeSurface);
+}
+
+void tst_proxy::initializeProperties()
+{
+ QVERIFY(m_proxy);
+
+ QTableWidget table;
+
+ m_proxy->setAutoColumnCategories(false);
+ m_proxy->setAutoRowCategories(false);
+ m_proxy->setColumnCategories(QStringList() << "col1" << "col2");
+ m_proxy->setColumnRole("column");
+ m_proxy->setColumnRolePattern(QRegularExpression("/^.*-(\\d\\d)$/"));
+ m_proxy->setColumnRoleReplace("\\\\1");
+ m_proxy->setItemModel(table.model());
+ m_proxy->setMultiMatchBehavior(QItemModelSurfaceDataProxy::MMBAverage);
+ m_proxy->setRowCategories(QStringList() << "row1" << "row2");
+ m_proxy->setRowRole("row");
+ m_proxy->setRowRolePattern(QRegularExpression("/^(\\d\\d\\d\\d).*$/"));
+ m_proxy->setRowRoleReplace("\\\\1");
+ m_proxy->setUseModelCategories(true);
+ m_proxy->setXPosRole("X");
+ m_proxy->setXPosRolePattern(QRegularExpression("/-/"));
+ m_proxy->setXPosRoleReplace("\\\\1");
+ m_proxy->setYPosRole("Y");
+ m_proxy->setYPosRolePattern(QRegularExpression("/-/"));
+ m_proxy->setYPosRoleReplace("\\\\1");
+ m_proxy->setZPosRole("Z");
+ m_proxy->setZPosRolePattern(QRegularExpression("/-/"));
+ m_proxy->setZPosRoleReplace("\\\\1");
+
+ QCOMPARE(m_proxy->autoColumnCategories(), false);
+ QCOMPARE(m_proxy->autoRowCategories(), false);
+ QCOMPARE(m_proxy->columnCategories().size(), 2);
+ QCOMPARE(m_proxy->columnRole(), QString("column"));
+ QCOMPARE(m_proxy->columnRolePattern(), QRegularExpression("/^.*-(\\d\\d)$/"));
+ QCOMPARE(m_proxy->columnRoleReplace(), QString("\\\\1"));
+ QVERIFY(m_proxy->itemModel());
+ QCOMPARE(m_proxy->multiMatchBehavior(), QItemModelSurfaceDataProxy::MMBAverage);
+ QCOMPARE(m_proxy->rowCategories().size(), 2);
+ QCOMPARE(m_proxy->rowRole(), QString("row"));
+ QCOMPARE(m_proxy->rowRolePattern(), QRegularExpression("/^(\\d\\d\\d\\d).*$/"));
+ QCOMPARE(m_proxy->rowRoleReplace(), QString("\\\\1"));
+ QCOMPARE(m_proxy->useModelCategories(), true);
+ QCOMPARE(m_proxy->xPosRole(), QString("X"));
+ QCOMPARE(m_proxy->xPosRolePattern(), QRegularExpression("/-/"));
+ QCOMPARE(m_proxy->xPosRoleReplace(), QString("\\\\1"));
+ QCOMPARE(m_proxy->yPosRole(), QString("Y"));
+ QCOMPARE(m_proxy->yPosRolePattern(), QRegularExpression("/-/"));
+ QCOMPARE(m_proxy->yPosRoleReplace(), QString("\\\\1"));
+ QCOMPARE(m_proxy->zPosRole(), QString("Z"));
+ QCOMPARE(m_proxy->zPosRolePattern(), QRegularExpression("/-/"));
+ QCOMPARE(m_proxy->zPosRoleReplace(), QString("\\\\1"));
+}
+
+void tst_proxy::multiMatch()
+{
+ if (!CpptestUtil::isOpenGLSupported())
+ QSKIP("OpenGL not supported on this platform");
+
+ Q3DSurface graph;
+
+ QTableWidget table;
+ QStringList rows;
+ rows << "row 1" << "row 2";
+ QStringList columns;
+ columns << "col 1" << "col 2" << "col 3" << "col 4";
+ const char *values[4][2] = {{"0/0/5.5/30", "0/0/10.5/30"},
+ {"0/1/5.5/30", "0/1/0.5/30"},
+ {"1/0/5.5/30", "1/0/0.5/30"},
+ {"1/1/0.0/30", "1/1/0.0/30"}};
+
+ table.setRowCount(2);
+ table.setColumnCount(4);
+
+ for (int col = 0; col < columns.size(); col++) {
+ for (int row = 0; row < rows.size(); row++) {
+ QModelIndex index = table.model()->index(col, row);
+ table.model()->setData(index, values[col][row]);
+ }
+ }
+
+ m_proxy->setItemModel(table.model());
+ m_proxy->setRowRole(table.model()->roleNames().value(Qt::DisplayRole));
+ m_proxy->setColumnRole(table.model()->roleNames().value(Qt::DisplayRole));
+ m_proxy->setRowRolePattern(QRegularExpression(QStringLiteral("^(\\d*)\\/(\\d*)\\/\\d*[\\.\\,]?\\d*\\/\\d*[\\.\\,]?\\d*$")));
+ m_proxy->setRowRoleReplace(QStringLiteral("\\2"));
+ m_proxy->setYPosRolePattern(QRegularExpression(QStringLiteral("^\\d*(\\/)(\\d*)\\/(\\d*[\\.\\,]?\\d*)\\/\\d*[\\.\\,]?\\d*$")));
+ m_proxy->setYPosRoleReplace(QStringLiteral("\\3"));
+ m_proxy->setColumnRolePattern(QRegularExpression(QStringLiteral("^(\\d*)(\\/)(\\d*)\\/\\d*[\\.\\,]?\\d*\\/\\d*[\\.\\,]?\\d*$")));
+ m_proxy->setColumnRoleReplace(QStringLiteral("\\1"));
+ QCoreApplication::processEvents();
+
+ QSurface3DSeries *series = new QSurface3DSeries(m_proxy);
+
+ graph.addSeries(series);
+
+ QCoreApplication::processEvents();
+ QCOMPARE(graph.axisY()->max(), 10.5f);
+ m_proxy->setMultiMatchBehavior(QItemModelSurfaceDataProxy::MMBFirst);
+ QCoreApplication::processEvents();
+ QCOMPARE(graph.axisY()->max(), 5.5f);
+ m_proxy->setMultiMatchBehavior(QItemModelSurfaceDataProxy::MMBLast);
+ QCoreApplication::processEvents();
+ QCOMPARE(graph.axisY()->max(), 10.5f);
+ m_proxy->setMultiMatchBehavior(QItemModelSurfaceDataProxy::MMBAverage);
+ QCoreApplication::processEvents();
+ QCOMPARE(graph.axisY()->max(), 8.0f);
+ m_proxy->setMultiMatchBehavior(QItemModelSurfaceDataProxy::MMBCumulativeY);
+ QCoreApplication::processEvents();
+ QCOMPARE(graph.axisY()->max(), 16.0f);
+
+ QCOMPARE(m_proxy->columnCount(), 2);
+ QCOMPARE(m_proxy->rowCount(), 3);
+ QVERIFY(m_proxy->series());
+
+ m_proxy = 0; // Graph deletes proxy
+}
+
+QTEST_MAIN(tst_proxy)
+#include "tst_proxy.moc"
diff --git a/tests/auto/cpptest/q3dsurface-proxy/CMakeLists.txt b/tests/auto/cpptest/q3dsurface-proxy/CMakeLists.txt
new file mode 100644
index 0000000..5fea581
--- /dev/null
+++ b/tests/auto/cpptest/q3dsurface-proxy/CMakeLists.txt
@@ -0,0 +1,10 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+qt_internal_add_test(q3dsurface-proxy
+ SOURCES
+ tst_proxy.cpp
+ LIBRARIES
+ Qt::Gui
+ Qt::Graphs
+)
diff --git a/tests/auto/cpptest/q3dsurface-proxy/tst_proxy.cpp b/tests/auto/cpptest/q3dsurface-proxy/tst_proxy.cpp
new file mode 100644
index 0000000..fd210a1
--- /dev/null
+++ b/tests/auto/cpptest/q3dsurface-proxy/tst_proxy.cpp
@@ -0,0 +1,91 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtTest/QtTest>
+
+#include <QtGraphs/QSurfaceDataProxy>
+
+class tst_proxy: public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void initTestCase();
+ void cleanupTestCase();
+ void init();
+ void cleanup();
+
+ void construct();
+
+ void initialProperties();
+ void initializeProperties();
+ void initialRow();
+
+private:
+ QSurfaceDataProxy *m_proxy;
+};
+
+void tst_proxy::initTestCase()
+{
+}
+
+void tst_proxy::cleanupTestCase()
+{
+}
+
+void tst_proxy::init()
+{
+ m_proxy = new QSurfaceDataProxy();
+}
+
+void tst_proxy::cleanup()
+{
+ delete m_proxy;
+}
+
+void tst_proxy::construct()
+{
+ QSurfaceDataProxy *proxy = new QSurfaceDataProxy();
+ QVERIFY(proxy);
+ delete proxy;
+}
+
+void tst_proxy::initialProperties()
+{
+ QVERIFY(m_proxy);
+
+ QCOMPARE(m_proxy->columnCount(), 0);
+ QCOMPARE(m_proxy->rowCount(), 0);
+ QVERIFY(!m_proxy->series());
+
+ QCOMPARE(m_proxy->type(), QAbstractDataProxy::DataTypeSurface);
+}
+
+void tst_proxy::initializeProperties()
+{
+ QVERIFY(m_proxy);
+
+ QSurfaceDataArray *data = new QSurfaceDataArray;
+ QSurfaceDataRow *dataRow1 = new QSurfaceDataRow;
+ QSurfaceDataRow *dataRow2 = new QSurfaceDataRow;
+ *dataRow1 << QVector3D(0.0f, 0.1f, 0.5f) << QVector3D(1.0f, 0.5f, 0.5f);
+ *dataRow2 << QVector3D(0.0f, 1.8f, 1.0f) << QVector3D(1.0f, 1.2f, 1.0f);
+ *data << dataRow1 << dataRow2;
+
+ m_proxy->resetArray(data);
+
+ QCOMPARE(m_proxy->columnCount(), 2);
+ QCOMPARE(m_proxy->rowCount(), 2);
+}
+
+void tst_proxy::initialRow()
+{
+ QSurfaceDataProxy proxy;
+ QSurfaceDataRow row{QSurfaceDataItem{QVector3D{0, 0, 0}},
+ QSurfaceDataItem{QVector3D{1, 1, 1}}};
+ proxy.addRow(new QSurfaceDataRow(row));
+ proxy.addRow(new QSurfaceDataRow(row));
+}
+
+QTEST_MAIN(tst_proxy)
+#include "tst_proxy.moc"
diff --git a/tests/auto/cpptest/q3dsurface-series/CMakeLists.txt b/tests/auto/cpptest/q3dsurface-series/CMakeLists.txt
new file mode 100644
index 0000000..ec80338
--- /dev/null
+++ b/tests/auto/cpptest/q3dsurface-series/CMakeLists.txt
@@ -0,0 +1,10 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+qt_internal_add_test(q3dsurface-series
+ SOURCES
+ tst_series.cpp
+ LIBRARIES
+ Qt::Gui
+ Qt::Graphs
+)
diff --git a/tests/auto/cpptest/q3dsurface-series/tst_series.cpp b/tests/auto/cpptest/q3dsurface-series/tst_series.cpp
new file mode 100644
index 0000000..7840094
--- /dev/null
+++ b/tests/auto/cpptest/q3dsurface-series/tst_series.cpp
@@ -0,0 +1,105 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtTest/QtTest>
+
+#include <QtGraphs/QSurface3DSeries>
+
+class tst_series: public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void initTestCase();
+ void cleanupTestCase();
+ void init();
+ void cleanup();
+
+ void construct();
+
+ void initialProperties();
+ void initializeProperties();
+ void invalidProperties();
+
+private:
+ QSurface3DSeries *m_series;
+};
+
+void tst_series::initTestCase()
+{
+}
+
+void tst_series::cleanupTestCase()
+{
+}
+
+void tst_series::init()
+{
+ m_series = new QSurface3DSeries();
+}
+
+void tst_series::cleanup()
+{
+ delete m_series;
+}
+
+void tst_series::construct()
+{
+ QSurface3DSeries *series = new QSurface3DSeries();
+ QVERIFY(series);
+ delete series;
+
+ QSurfaceDataProxy *proxy = new QSurfaceDataProxy();
+
+ series = new QSurface3DSeries(proxy);
+ QVERIFY(series);
+ QCOMPARE(series->dataProxy(), proxy);
+ delete series;
+}
+
+void tst_series::initialProperties()
+{
+ QVERIFY(m_series);
+
+ QVERIFY(m_series->dataProxy());
+ QCOMPARE(m_series->drawMode(), QSurface3DSeries::DrawSurfaceAndWireframe);
+ QCOMPARE(m_series->isFlatShadingEnabled(), true);
+ QCOMPARE(m_series->isFlatShadingSupported(), true);
+ QCOMPARE(m_series->selectedPoint(), m_series->invalidSelectionPosition());
+ QCOMPARE(m_series->wireframeColor(), QColor(Qt::black));
+ // Common properties. The ones identical between different series are tested in QBar3DSeries tests
+ QCOMPARE(m_series->itemLabelFormat(), QString("@xLabel, @yLabel, @zLabel"));
+ QCOMPARE(m_series->mesh(), QAbstract3DSeries::MeshSphere);
+ QCOMPARE(m_series->type(), QAbstract3DSeries::SeriesTypeSurface);
+}
+
+void tst_series::initializeProperties()
+{
+ QVERIFY(m_series);
+
+ m_series->setDataProxy(new QSurfaceDataProxy());
+ m_series->setDrawMode(QSurface3DSeries::DrawWireframe);
+ m_series->setFlatShadingEnabled(false);
+ m_series->setSelectedPoint(QPoint(0, 0));
+ m_series->setWireframeColor(QColor(Qt::red));
+
+ QCOMPARE(m_series->drawMode(), QSurface3DSeries::DrawWireframe);
+ QCOMPARE(m_series->isFlatShadingEnabled(), false);
+ QCOMPARE(m_series->selectedPoint(), QPoint(0, 0));
+ QCOMPARE(m_series->wireframeColor(), QColor(Qt::red));
+
+ // Common properties. The ones identical between different series are tested in QBar3DSeries tests
+ m_series->setMesh(QAbstract3DSeries::MeshPyramid);
+
+ QCOMPARE(m_series->mesh(), QAbstract3DSeries::MeshPyramid);
+}
+
+void tst_series::invalidProperties()
+{
+ m_series->setMesh(QAbstract3DSeries::MeshPoint);
+
+ QCOMPARE(m_series->mesh(), QAbstract3DSeries::MeshSphere);
+}
+
+QTEST_MAIN(tst_series)
+#include "tst_series.moc"
diff --git a/tests/auto/cpptest/q3dsurface/CMakeLists.txt b/tests/auto/cpptest/q3dsurface/CMakeLists.txt
new file mode 100644
index 0000000..a21c688
--- /dev/null
+++ b/tests/auto/cpptest/q3dsurface/CMakeLists.txt
@@ -0,0 +1,13 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+qt_internal_add_test(q3dsurface
+ SOURCES
+ tst_surface.cpp
+ INCLUDE_DIRECTORIES
+ ../common
+ LIBRARIES
+ Qt::Gui
+ Qt::GuiPrivate
+ Qt::Graphs
+)
diff --git a/tests/auto/cpptest/q3dsurface/tst_surface.cpp b/tests/auto/cpptest/q3dsurface/tst_surface.cpp
new file mode 100644
index 0000000..268e474
--- /dev/null
+++ b/tests/auto/cpptest/q3dsurface/tst_surface.cpp
@@ -0,0 +1,251 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtTest/QtTest>
+
+#include <QtGraphs/Q3DSurface>
+
+#include "cpptestutil.h"
+
+class tst_surface: public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void initTestCase();
+ void cleanupTestCase();
+ void init();
+ void cleanup();
+
+ void construct();
+
+ void initialProperties();
+ void initializeProperties();
+ void invalidProperties();
+
+ void addSeries();
+ void addMultipleSeries();
+ void selectSeries();
+ void removeSeries();
+ void removeMultipleSeries();
+ void hasSeries();
+
+private:
+ Q3DSurface *m_graph;
+};
+
+QSurface3DSeries *newSeries()
+{
+ QSurface3DSeries *series = new QSurface3DSeries;
+ QSurfaceDataArray *data = new QSurfaceDataArray;
+ QSurfaceDataRow *dataRow1 = new QSurfaceDataRow;
+ QSurfaceDataRow *dataRow2 = new QSurfaceDataRow;
+ *dataRow1 << QVector3D(0.0f, 0.1f, 0.5f) << QVector3D(1.0f, 0.5f, 0.5f);
+ *dataRow2 << QVector3D(0.0f, 1.8f, 1.0f) << QVector3D(1.0f, 1.2f, 1.0f);
+ *data << dataRow1 << dataRow2;
+ series->dataProxy()->resetArray(data);
+
+ return series;
+}
+
+void tst_surface::initTestCase()
+{
+ if (!CpptestUtil::isOpenGLSupported())
+ QSKIP("OpenGL not supported on this platform");
+}
+
+void tst_surface::cleanupTestCase()
+{
+}
+
+void tst_surface::init()
+{
+ m_graph = new Q3DSurface();
+}
+
+void tst_surface::cleanup()
+{
+ delete m_graph;
+}
+
+void tst_surface::construct()
+{
+ Q3DSurface *graph = new Q3DSurface();
+ QVERIFY(graph);
+ delete graph;
+
+ QSurfaceFormat format;
+ graph = new Q3DSurface(&format);
+ QVERIFY(graph);
+ delete graph;
+}
+
+void tst_surface::initialProperties()
+{
+ QVERIFY(m_graph);
+ QCOMPARE(m_graph->seriesList().size(), 0);
+ QVERIFY(!m_graph->selectedSeries());
+ QCOMPARE(m_graph->flipHorizontalGrid(), false);
+ QCOMPARE(m_graph->axisX()->orientation(), QAbstract3DAxis::AxisOrientationX);
+ QCOMPARE(m_graph->axisY()->orientation(), QAbstract3DAxis::AxisOrientationY);
+ QCOMPARE(m_graph->axisZ()->orientation(), QAbstract3DAxis::AxisOrientationZ);
+
+ // Common properties
+ QCOMPARE(m_graph->activeTheme()->type(), Q3DTheme::ThemeQt);
+ QCOMPARE(m_graph->selectionMode(), QAbstract3DGraph::SelectionItem);
+ QCOMPARE(m_graph->shadowQuality(), QAbstract3DGraph::ShadowQualityMedium);
+ QVERIFY(m_graph->scene());
+ QCOMPARE(m_graph->measureFps(), false);
+ QCOMPARE(m_graph->isOrthoProjection(), false);
+ QCOMPARE(m_graph->selectedElement(), QAbstract3DGraph::ElementNone);
+ QCOMPARE(m_graph->aspectRatio(), 2.0);
+ QCOMPARE(m_graph->optimizationHints(), QAbstract3DGraph::OptimizationDefault);
+ QCOMPARE(m_graph->isPolar(), false);
+ QCOMPARE(m_graph->radialLabelOffset(), 1.0);
+ QCOMPARE(m_graph->horizontalAspectRatio(), 0.0);
+ QCOMPARE(m_graph->isReflection(), false);
+ QCOMPARE(m_graph->reflectivity(), 0.5);
+ QCOMPARE(m_graph->locale(), QLocale("C"));
+ QCOMPARE(m_graph->queriedGraphPosition(), QVector3D(0, 0, 0));
+ QCOMPARE(m_graph->margin(), -1.0);
+}
+
+void tst_surface::initializeProperties()
+{
+ m_graph->setFlipHorizontalGrid(true);
+
+ QCOMPARE(m_graph->flipHorizontalGrid(), true);
+
+ Q3DTheme *theme = new Q3DTheme(Q3DTheme::ThemeDigia);
+ m_graph->setActiveTheme(theme);
+ m_graph->setSelectionMode(QAbstract3DGraph::SelectionItem | QAbstract3DGraph::SelectionRow | QAbstract3DGraph::SelectionSlice);
+ m_graph->setShadowQuality(QAbstract3DGraph::ShadowQualitySoftHigh);
+ QCOMPARE(m_graph->shadowQuality(), QAbstract3DGraph::ShadowQualitySoftHigh);
+ m_graph->setMeasureFps(true);
+ m_graph->setOrthoProjection(true);
+ m_graph->setAspectRatio(1.0);
+ m_graph->setOptimizationHints(QAbstract3DGraph::OptimizationStatic);
+ m_graph->setPolar(true);
+ m_graph->setRadialLabelOffset(0.1f);
+ m_graph->setHorizontalAspectRatio(1.0);
+ m_graph->setReflection(true);
+ m_graph->setReflectivity(0.1);
+ m_graph->setLocale(QLocale("FI"));
+ m_graph->setMargin(1.0);
+
+ QCOMPARE(m_graph->activeTheme()->type(), Q3DTheme::ThemeDigia);
+ QCOMPARE(m_graph->selectionMode(), QAbstract3DGraph::SelectionItem | QAbstract3DGraph::SelectionRow | QAbstract3DGraph::SelectionSlice);
+ QCOMPARE(m_graph->shadowQuality(), QAbstract3DGraph::ShadowQualityNone); // Ortho disables shadows
+ QCOMPARE(m_graph->measureFps(), true);
+ QCOMPARE(m_graph->isOrthoProjection(), true);
+ QCOMPARE(m_graph->aspectRatio(), 1.0);
+ QCOMPARE(m_graph->optimizationHints(), QAbstract3DGraph::OptimizationStatic);
+ QCOMPARE(m_graph->isPolar(), true);
+ QCOMPARE(m_graph->radialLabelOffset(), 0.1f);
+ QCOMPARE(m_graph->horizontalAspectRatio(), 1.0);
+ QCOMPARE(m_graph->isReflection(), true);
+ QCOMPARE(m_graph->reflectivity(), 0.1);
+ QCOMPARE(m_graph->locale(), QLocale("FI"));
+ QCOMPARE(m_graph->margin(), 1.0);
+}
+
+void tst_surface::invalidProperties()
+{
+ m_graph->setSelectionMode(QAbstract3DGraph::SelectionColumn | QAbstract3DGraph::SelectionRow | QAbstract3DGraph::SelectionSlice);
+ m_graph->setAspectRatio(-1.0);
+ m_graph->setHorizontalAspectRatio(-1.0);
+ m_graph->setReflectivity(-1.0);
+ m_graph->setLocale(QLocale("XX"));
+
+ QCOMPARE(m_graph->selectionMode(), QAbstract3DGraph::SelectionItem);
+ QCOMPARE(m_graph->aspectRatio(), -1.0/*2.0*/); // TODO: Fix once QTRD-3367 is done
+ QCOMPARE(m_graph->horizontalAspectRatio(), -1.0/*0.0*/); // TODO: Fix once QTRD-3367 is done
+ QCOMPARE(m_graph->reflectivity(), -1.0/*0.5*/); // TODO: Fix once QTRD-3367 is done
+ QCOMPARE(m_graph->locale(), QLocale("C"));
+}
+
+void tst_surface::addSeries()
+{
+ m_graph->addSeries(newSeries());
+
+ QCOMPARE(m_graph->seriesList().size(), 1);
+ QVERIFY(!m_graph->selectedSeries());
+}
+
+void tst_surface::addMultipleSeries()
+{
+ QSurface3DSeries *series = newSeries();
+ QSurface3DSeries *series2 = newSeries();
+ QSurface3DSeries *series3 = newSeries();
+
+ m_graph->addSeries(series);
+ m_graph->addSeries(series2);
+ m_graph->addSeries(series3);
+
+ QCOMPARE(m_graph->seriesList().size(), 3);
+}
+
+void tst_surface::selectSeries()
+{
+ QSurface3DSeries *series = newSeries();
+
+ m_graph->addSeries(series);
+ m_graph->seriesList()[0]->setSelectedPoint(QPoint(0, 0));
+
+ QCOMPARE(m_graph->seriesList().size(), 1);
+ QCOMPARE(m_graph->selectedSeries(), series);
+
+ m_graph->clearSelection();
+ QVERIFY(!m_graph->selectedSeries());
+}
+
+void tst_surface::removeSeries()
+{
+ QSurface3DSeries *series = newSeries();
+
+ m_graph->addSeries(series);
+ m_graph->removeSeries(series);
+ QCOMPARE(m_graph->seriesList().size(), 0);
+
+ delete series;
+}
+
+void tst_surface::removeMultipleSeries()
+{
+ QSurface3DSeries *series = newSeries();
+ QSurface3DSeries *series2 = newSeries();
+ QSurface3DSeries *series3 = newSeries();
+
+ m_graph->addSeries(series);
+ m_graph->addSeries(series2);
+ m_graph->addSeries(series3);
+
+ m_graph->seriesList()[0]->setSelectedPoint(QPoint(0, 0));
+ QCOMPARE(m_graph->selectedSeries(), series);
+
+ m_graph->removeSeries(series);
+ QCOMPARE(m_graph->seriesList().size(), 2);
+ QVERIFY(!m_graph->selectedSeries());
+
+ m_graph->removeSeries(series2);
+ QCOMPARE(m_graph->seriesList().size(), 1);
+
+ m_graph->removeSeries(series3);
+ QCOMPARE(m_graph->seriesList().size(), 0);
+
+ delete series;
+ delete series2;
+ delete series3;
+}
+
+void tst_surface::hasSeries()
+{
+ QSurface3DSeries *series1 = newSeries();
+ m_graph->addSeries(series1);
+ QCOMPARE(m_graph->hasSeries(series1), true);
+ QSurface3DSeries *series2 = newSeries();
+ QCOMPARE(m_graph->hasSeries(series2), false);
+}
+
+QTEST_MAIN(tst_surface)
+#include "tst_surface.moc"
diff --git a/tests/auto/cpptest/q3dtheme/CMakeLists.txt b/tests/auto/cpptest/q3dtheme/CMakeLists.txt
new file mode 100644
index 0000000..056d700
--- /dev/null
+++ b/tests/auto/cpptest/q3dtheme/CMakeLists.txt
@@ -0,0 +1,10 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+qt_internal_add_test(q3dtheme
+ SOURCES
+ tst_theme.cpp
+ LIBRARIES
+ Qt::Gui
+ Qt::Graphs
+)
diff --git a/tests/auto/cpptest/q3dtheme/tst_theme.cpp b/tests/auto/cpptest/q3dtheme/tst_theme.cpp
new file mode 100644
index 0000000..ed8f324
--- /dev/null
+++ b/tests/auto/cpptest/q3dtheme/tst_theme.cpp
@@ -0,0 +1,199 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtTest/QtTest>
+
+#include <QtGraphs/Q3DTheme>
+
+class tst_theme: public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void initTestCase();
+ void cleanupTestCase();
+ void init();
+ void cleanup();
+
+ void construct();
+
+ void initialProperties();
+ void initializeProperties();
+ void invalidProperties();
+
+private:
+ Q3DTheme *m_theme;
+};
+
+void tst_theme::initTestCase()
+{
+}
+
+void tst_theme::cleanupTestCase()
+{
+}
+
+void tst_theme::init()
+{
+ m_theme = new Q3DTheme();
+}
+
+void tst_theme::cleanup()
+{
+ delete m_theme;
+}
+
+void tst_theme::construct()
+{
+ Q3DTheme *theme = new Q3DTheme();
+ QVERIFY(theme);
+ delete theme;
+
+ theme = new Q3DTheme(Q3DTheme::ThemeEbony);
+ QVERIFY(theme);
+ QCOMPARE(theme->ambientLightStrength(), 0.5f);
+ QCOMPARE(theme->backgroundColor(), QColor(Qt::black));
+ QCOMPARE(theme->isBackgroundEnabled(), true);
+ QCOMPARE(theme->baseColors().size(), 5);
+ QCOMPARE(theme->baseColors().at(0), QColor(Qt::white));
+ QCOMPARE(theme->baseColors().at(4), QColor(QRgb(0x6b6b6b)));
+ QCOMPARE(theme->baseGradients().size(), 5);
+ QCOMPARE(theme->baseGradients().at(0).stops().at(1).second, QColor(Qt::white));
+ QCOMPARE(theme->baseGradients().at(4).stops().at(1).second, QColor(QRgb(0x6b6b6b)));
+ QCOMPARE(theme->colorStyle(), Q3DTheme::ColorStyleUniform);
+ QCOMPARE(theme->font(), QFont("Arial"));
+ QCOMPARE(theme->isGridEnabled(), true);
+ QCOMPARE(theme->gridLineColor(), QColor(QRgb(0x35322f)));
+ QCOMPARE(theme->highlightLightStrength(), 5.0f);
+ QCOMPARE(theme->labelBackgroundColor(), QColor(0x00, 0x00, 0x00, 0xcd));
+ QCOMPARE(theme->isLabelBackgroundEnabled(), true);
+ QCOMPARE(theme->isLabelBorderEnabled(), false);
+ QCOMPARE(theme->labelTextColor(), QColor(QRgb(0xaeadac)));
+ QCOMPARE(theme->lightColor(), QColor(Qt::white));
+ QCOMPARE(theme->lightStrength(), 5.0f);
+ QCOMPARE(theme->multiHighlightColor(), QColor(QRgb(0xd72222)));
+ QCOMPARE(theme->multiHighlightGradient().stops().at(1).second, QColor(QRgb(0xd72222)));
+ QCOMPARE(theme->singleHighlightColor(), QColor(QRgb(0xf5dc0d)));
+ QCOMPARE(theme->singleHighlightGradient().stops().at(1).second, QColor(QRgb(0xf5dc0d)));
+ QCOMPARE(theme->type(), Q3DTheme::ThemeEbony);
+ QCOMPARE(theme->windowColor(), QColor(Qt::black));
+ delete theme;
+}
+
+void tst_theme::initialProperties()
+{
+ QVERIFY(m_theme);
+
+ QCOMPARE(m_theme->ambientLightStrength(), 0.25f);
+ QCOMPARE(m_theme->backgroundColor(), QColor(Qt::black));
+ QCOMPARE(m_theme->isBackgroundEnabled(), true);
+ QCOMPARE(m_theme->baseColors().size(), 1);
+ QCOMPARE(m_theme->baseColors().at(0), QColor(Qt::black));
+ QCOMPARE(m_theme->baseGradients().size(), 1);
+ QCOMPARE(m_theme->baseGradients().at(0).stops().at(0).second, QColor(Qt::black));
+ QCOMPARE(m_theme->baseGradients().at(0).stops().at(1).second, QColor(Qt::white));
+ QCOMPARE(m_theme->colorStyle(), Q3DTheme::ColorStyleUniform);
+ QCOMPARE(m_theme->font(), QFont());
+ QCOMPARE(m_theme->isGridEnabled(), true);
+ QCOMPARE(m_theme->gridLineColor(), QColor(Qt::white));
+ QCOMPARE(m_theme->highlightLightStrength(), 7.5f);
+ QCOMPARE(m_theme->labelBackgroundColor(), QColor(Qt::gray));
+ QCOMPARE(m_theme->isLabelBackgroundEnabled(), true);
+ QCOMPARE(m_theme->isLabelBorderEnabled(), true);
+ QCOMPARE(m_theme->labelTextColor(), QColor(Qt::white));
+ QCOMPARE(m_theme->lightColor(), QColor(Qt::white));
+ QCOMPARE(m_theme->lightStrength(), 5.0f);
+ QCOMPARE(m_theme->multiHighlightColor(), QColor(Qt::blue));
+ QCOMPARE(m_theme->multiHighlightGradient().stops(), QLinearGradient().stops());
+ QCOMPARE(m_theme->singleHighlightColor(), QColor(Qt::red));
+ QCOMPARE(m_theme->singleHighlightGradient().stops(), QLinearGradient().stops());
+ QCOMPARE(m_theme->type(), Q3DTheme::ThemeUserDefined);
+ QCOMPARE(m_theme->windowColor(), QColor(Qt::black));
+}
+
+void tst_theme::initializeProperties()
+{
+ QVERIFY(m_theme);
+
+ QLinearGradient gradient1;
+ QLinearGradient gradient2;
+ QLinearGradient gradient3;
+ QLinearGradient gradient4;
+
+ QList<QColor> basecolors;
+ basecolors << QColor(Qt::red) << QColor(Qt::blue);
+
+ QList<QLinearGradient> basegradients;
+ basegradients << gradient1 << gradient2;
+
+ m_theme->setType(Q3DTheme::ThemeQt); // We'll override default values with the following setters
+ m_theme->setAmbientLightStrength(0.3f);
+ m_theme->setBackgroundColor(QColor(Qt::red));
+ m_theme->setBackgroundEnabled(false);
+ m_theme->setBaseColors(basecolors);
+ m_theme->setBaseGradients(basegradients);
+ m_theme->setColorStyle(Q3DTheme::ColorStyleRangeGradient);
+ m_theme->setFont(QFont("Arial"));
+ m_theme->setGridEnabled(false);
+ m_theme->setGridLineColor(QColor(Qt::green));
+ m_theme->setHighlightLightStrength(5.0f);
+ m_theme->setLabelBackgroundColor(QColor(Qt::gray));
+ m_theme->setLabelBackgroundEnabled(false);
+ m_theme->setLabelBorderEnabled(false);
+ m_theme->setLabelTextColor(QColor(Qt::cyan));
+ m_theme->setLightColor(QColor(Qt::yellow));
+ m_theme->setLightStrength(2.5f);
+ m_theme->setMultiHighlightColor(QColor(Qt::darkBlue));
+ m_theme->setMultiHighlightGradient(gradient3);
+ m_theme->setSingleHighlightColor(QColor(Qt::darkRed));
+ m_theme->setSingleHighlightGradient(gradient4);
+ m_theme->setWindowColor(QColor(Qt::darkYellow));
+
+ QCOMPARE(m_theme->ambientLightStrength(), 0.3f);
+ QCOMPARE(m_theme->backgroundColor(), QColor(Qt::red));
+ QCOMPARE(m_theme->isBackgroundEnabled(), false);
+ QCOMPARE(m_theme->baseColors().size(), 2);
+ QCOMPARE(m_theme->baseColors().at(0), QColor(Qt::red));
+ QCOMPARE(m_theme->baseColors().at(1), QColor(Qt::blue));
+ QCOMPARE(m_theme->baseGradients().size(), 2);
+ QCOMPARE(m_theme->baseGradients().at(0), gradient1);
+ QCOMPARE(m_theme->baseGradients().at(0), gradient2);
+ QCOMPARE(m_theme->colorStyle(), Q3DTheme::ColorStyleRangeGradient);
+ QCOMPARE(m_theme->font(), QFont("Arial"));
+ QCOMPARE(m_theme->isGridEnabled(), false);
+ QCOMPARE(m_theme->gridLineColor(), QColor(Qt::green));
+ QCOMPARE(m_theme->highlightLightStrength(), 5.0f);
+ QCOMPARE(m_theme->labelBackgroundColor(), QColor(Qt::gray));
+ QCOMPARE(m_theme->isLabelBackgroundEnabled(), false);
+ QCOMPARE(m_theme->isLabelBorderEnabled(), false);
+ QCOMPARE(m_theme->labelTextColor(), QColor(Qt::cyan));
+ QCOMPARE(m_theme->lightColor(), QColor(Qt::yellow));
+ QCOMPARE(m_theme->lightStrength(), 2.5f);
+ QCOMPARE(m_theme->multiHighlightColor(), QColor(Qt::darkBlue));
+ QCOMPARE(m_theme->multiHighlightGradient(), gradient3);
+ QCOMPARE(m_theme->singleHighlightColor(), QColor(Qt::darkRed));
+ QCOMPARE(m_theme->singleHighlightGradient(), gradient4);
+ QCOMPARE(m_theme->type(), Q3DTheme::ThemeQt);
+ QCOMPARE(m_theme->windowColor(), QColor(Qt::darkYellow));
+}
+
+void tst_theme::invalidProperties()
+{
+ m_theme->setAmbientLightStrength(-1.0f);
+ QCOMPARE(m_theme->ambientLightStrength(), 0.25f);
+ m_theme->setAmbientLightStrength(1.1f);
+ QCOMPARE(m_theme->ambientLightStrength(), 0.25f);
+
+ m_theme->setHighlightLightStrength(-1.0f);
+ QCOMPARE(m_theme->highlightLightStrength(), 7.5f);
+ m_theme->setHighlightLightStrength(10.1f);
+ QCOMPARE(m_theme->highlightLightStrength(), 7.5f);
+
+ m_theme->setLightStrength(-1.0f);
+ QCOMPARE(m_theme->lightStrength(), 5.0f);
+ m_theme->setLightStrength(10.1f);
+ QCOMPARE(m_theme->lightStrength(), 5.0f);
+}
+
+QTEST_MAIN(tst_theme)
+#include "tst_theme.moc"
diff --git a/tests/auto/qmltest/CMakeLists.txt b/tests/auto/qmltest/CMakeLists.txt
new file mode 100644
index 0000000..109fcf0
--- /dev/null
+++ b/tests/auto/qmltest/CMakeLists.txt
@@ -0,0 +1,28 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+file(GLOB_RECURSE test_data_glob
+ RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
+ *)
+list(APPEND test_data ${test_data_glob})
+
+qt_internal_add_test(tst_qmltest
+ QMLTEST
+ SOURCES
+ tst_qmltest.cpp
+ LIBRARIES
+ Qt::Gui
+ TESTDATA ${test_data}
+)
+
+set(qmltest_resource_files
+ "customitem.obj"
+ "customtexture.jpg"
+)
+
+qt_internal_add_resource(tst_qmltest "qmltest"
+ PREFIX
+ "/"
+ FILES
+ ${qmltest_resource_files}
+)
diff --git a/tests/auto/qmltest/axis3d/tst_category.qml b/tests/auto/qmltest/axis3d/tst_category.qml
new file mode 100644
index 0000000..641e6b2
--- /dev/null
+++ b/tests/auto/qmltest/axis3d/tst_category.qml
@@ -0,0 +1,120 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick 2.0
+import QtGraphs
+import QtTest 1.0
+
+Item {
+ id: top
+ height: 150
+ width: 150
+
+ CategoryAxis3D {
+ id: initial
+ }
+
+ CategoryAxis3D {
+ id: initialized
+ labels: ["first", "second"]
+
+ autoAdjustRange: false
+ labelAutoRotation: 10.0
+ max: 20
+ min: 10
+ title: "initialized"
+ titleFixed: false
+ titleVisible: true
+ }
+
+ CategoryAxis3D {
+ id: change
+ }
+
+ CategoryAxis3D {
+ id: invalid
+ }
+
+ TestCase {
+ name: "CategoryAxis3D Initial"
+
+ function test_initial() {
+ compare(initial.labels.length, 0)
+
+ compare(initial.autoAdjustRange, true)
+ compare(initial.labelAutoRotation, 0.0)
+ compare(initial.max, 10)
+ compare(initial.min, 0)
+ compare(initial.orientation, AbstractAxis3D.AxisOrientationNone)
+ compare(initial.title, "")
+ compare(initial.titleFixed, true)
+ compare(initial.titleVisible, false)
+ compare(initial.type, AbstractAxis3D.AxisTypeCategory)
+ }
+ }
+
+ TestCase {
+ name: "CategoryAxis3D Initialized"
+
+ function test_initialized() {
+ compare(initialized.labels.length, 2)
+ compare(initialized.labels[0], "first")
+ compare(initialized.labels[1], "second")
+
+ compare(initialized.autoAdjustRange, false)
+ compare(initialized.labelAutoRotation, 10.0)
+ compare(initialized.max, 20)
+ compare(initialized.min, 10)
+ compare(initialized.title, "initialized")
+ compare(initialized.titleFixed, false)
+ compare(initialized.titleVisible, true)
+ }
+ }
+
+ TestCase {
+ name: "CategoryAxis3D Change"
+
+ function test_change() {
+ change.labels = ["first"]
+ compare(change.labels.length, 1)
+ compare(change.labels[0], "first")
+ change.labels = ["first", "second"]
+ compare(change.labels.length, 2)
+ compare(change.labels[0], "first")
+ compare(change.labels[1], "second")
+ change.labels[1] = "another"
+ compare(change.labels[1], "another")
+
+ change.autoAdjustRange = false
+ change.labelAutoRotation = 10.0
+ change.max = 20
+ change.min = 10
+ change.title = "initialized"
+ change.titleFixed = false
+ change.titleVisible = true
+
+ compare(change.autoAdjustRange, false)
+ compare(change.labelAutoRotation, 10.0)
+ compare(change.max, 20)
+ compare(change.min, 10)
+ compare(change.title, "initialized")
+ compare(change.titleFixed, false)
+ compare(change.titleVisible, true)
+ }
+ }
+
+ TestCase {
+ name: "CategoryAxis3D Invalid"
+
+ function test_invalid() {
+ invalid.labelAutoRotation = -10
+ compare(invalid.labelAutoRotation, 0.0)
+ invalid.labelAutoRotation = 100
+ compare(invalid.labelAutoRotation, 90.0)
+ invalid.max = -10
+ compare(invalid.min, 0)
+ invalid.min = 10
+ compare(invalid.max, 11)
+ }
+ }
+}
diff --git a/tests/auto/qmltest/axis3d/tst_logvalue.qml b/tests/auto/qmltest/axis3d/tst_logvalue.qml
new file mode 100644
index 0000000..64035c6
--- /dev/null
+++ b/tests/auto/qmltest/axis3d/tst_logvalue.qml
@@ -0,0 +1,76 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick 2.0
+import QtGraphs
+import QtTest 1.0
+
+Item {
+ id: top
+ height: 150
+ width: 150
+
+ LogValueAxis3DFormatter {
+ id: initial
+ }
+
+ LogValueAxis3DFormatter {
+ id: initialized
+ autoSubGrid: false
+ base: 0.1
+ showEdgeLabels: false
+ }
+
+ LogValueAxis3DFormatter {
+ id: change
+ }
+
+ LogValueAxis3DFormatter {
+ id: invalid
+ }
+
+ TestCase {
+ name: "LogValueAxis3DFormatter Initial"
+
+ function test_initial() {
+ compare(initial.autoSubGrid, true)
+ compare(initial.base, 10)
+ compare(initial.showEdgeLabels, true)
+ }
+ }
+
+ TestCase {
+ name: "LogValueAxis3DFormatter Initialized"
+
+ function test_initialized() {
+ compare(initialized.autoSubGrid, false)
+ compare(initialized.base, 0.1)
+ compare(initialized.showEdgeLabels, false)
+ }
+ }
+
+ TestCase {
+ name: "LogValueAxis3DFormatter Change"
+
+ function test_change() {
+ change.autoSubGrid = false
+ change.base = 0.1
+ change.showEdgeLabels = false
+
+ compare(change.autoSubGrid, false)
+ compare(change.base, 0.1)
+ compare(change.showEdgeLabels, false)
+ }
+ }
+
+ TestCase {
+ name: "LogValueAxis3DFormatter Invalid"
+
+ function test_invalid() {
+ invalid.base = 1
+ compare(invalid.base, 10)
+ invalid.base = -1
+ compare(invalid.base, 10)
+ }
+ }
+}
diff --git a/tests/auto/qmltest/axis3d/tst_value.qml b/tests/auto/qmltest/axis3d/tst_value.qml
new file mode 100644
index 0000000..ace8fad
--- /dev/null
+++ b/tests/auto/qmltest/axis3d/tst_value.qml
@@ -0,0 +1,139 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick 2.0
+import QtGraphs
+import QtTest 1.0
+
+Item {
+ id: top
+ height: 150
+ width: 150
+
+ ValueAxis3D {
+ id: initial
+ }
+
+ ValueAxis3D {
+ id: initialized
+ formatter: ValueAxis3DFormatter { objectName: "formatter1" }
+ labelFormat: "%f"
+ reversed: true
+ segmentCount: 10
+ subSegmentCount: 5
+
+ autoAdjustRange: false
+ labelAutoRotation: 10.0
+ max: 20
+ min: -10
+ title: "initialized"
+ titleFixed: false
+ titleVisible: true
+ }
+
+ ValueAxis3D {
+ id: change
+ }
+
+ ValueAxis3D {
+ id: invalid
+ }
+
+ TestCase {
+ name: "ValueAxis3D Initial"
+
+ function test_initial() {
+ verify(initial.formatter)
+ compare(initial.labelFormat, "%.2f")
+ compare(initial.reversed, false)
+ compare(initial.segmentCount, 5)
+ compare(initial.subSegmentCount, 1)
+
+ compare(initial.autoAdjustRange, true)
+ compare(initial.labelAutoRotation, 0.0)
+ compare(initial.max, 10)
+ compare(initial.min, 0)
+ compare(initial.orientation, AbstractAxis3D.AxisOrientationNone)
+ compare(initial.title, "")
+ compare(initial.titleFixed, true)
+ compare(initial.titleVisible, false)
+ compare(initial.type, AbstractAxis3D.AxisTypeValue)
+ }
+ }
+
+ TestCase {
+ name: "ValueAxis3D Initialized"
+
+ function test_initialized() {
+ compare(initialized.formatter.objectName, "formatter1")
+ compare(initialized.labelFormat, "%f")
+ compare(initialized.reversed, true)
+ compare(initialized.segmentCount, 10)
+ compare(initialized.subSegmentCount, 5)
+
+ compare(initialized.autoAdjustRange, false)
+ compare(initialized.labelAutoRotation, 10.0)
+ compare(initialized.max, 20)
+ compare(initialized.min, -10)
+ compare(initialized.title, "initialized")
+ compare(initialized.titleFixed, false)
+ compare(initialized.titleVisible, true)
+ }
+ }
+
+ TestCase {
+ name: "ValueAxis3D Change"
+
+ ValueAxis3DFormatter { id: formatter1 }
+
+ function test_change() {
+ change.formatter = formatter1
+ change.labelFormat = "%f"
+ change.reversed = true
+ change.segmentCount = 10
+ change.subSegmentCount = 5
+
+ compare(change.formatter, formatter1)
+ compare(change.labelFormat, "%f")
+ compare(change.reversed, true)
+ compare(change.segmentCount, 10)
+ compare(change.subSegmentCount, 5)
+
+ change.autoAdjustRange = false
+ change.labelAutoRotation = 10.0
+ change.max = 20
+ change.min = -10
+ change.title = "initialized"
+ change.titleFixed = false
+ change.titleVisible = true
+
+ compare(change.autoAdjustRange, false)
+ compare(change.labelAutoRotation, 10.0)
+ compare(change.max, 20)
+ compare(change.min, -10)
+ compare(change.title, "initialized")
+ compare(change.titleFixed, false)
+ compare(change.titleVisible, true)
+ }
+ }
+
+ TestCase {
+ name: "ValueAxis3D Invalid"
+
+ function test_invalid() {
+ invalid.segmentCount = -1
+ compare(invalid.segmentCount, 1)
+ invalid.subSegmentCount = -1
+ compare(invalid.subSegmentCount, 1)
+
+ invalid.labelAutoRotation = -10
+ compare(invalid.labelAutoRotation, 0.0)
+ invalid.labelAutoRotation = 100
+ compare(invalid.labelAutoRotation, 90.0)
+ invalid.max = -10
+ compare(invalid.min, -11)
+ invalid.min = 10
+ compare(invalid.max, 11)
+ }
+ }
+}
diff --git a/tests/auto/qmltest/bars3d/tst_bars.qml b/tests/auto/qmltest/bars3d/tst_bars.qml
new file mode 100644
index 0000000..9a6da6b
--- /dev/null
+++ b/tests/auto/qmltest/bars3d/tst_bars.qml
@@ -0,0 +1,179 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick 2.0
+import QtGraphs
+import QtTest 1.0
+
+Item {
+ id: top
+ height: 150
+ width: 150
+
+ property var bars3d: null
+
+// function constructBars() {
+// bars3d = Qt.createQmlObject("
+// import QtQuick 2.2
+// import QtGraphs
+// Bars3D {
+// anchors.fill: parent
+// }", top)
+// bars3d.anchors.fill = top
+// }
+
+// TODO: Needs either redoing, or fixing the code; see QTBUG-110000 & QTBUG-110001
+// TestCase {
+// name: "Bars3D Series"
+// when: windowShown
+
+// Bar3DSeries { id: series1 }
+// Bar3DSeries { id: series2 }
+
+// function test_1_add_series() {
+// constructBars()
+
+// bars3d.seriesList = [series1, series2]
+// compare(bars3d.seriesList.length, 2)
+// console.log("top:", top)
+// waitForRendering(top)
+// }
+
+// function test_2_remove_series() {
+// bars3d.seriesList = [series1]
+// compare(bars3d.seriesList.length, 1)
+// waitForRendering(top)
+// }
+
+// function test_3_remove_series() {
+// bars3d.seriesList = []
+// compare(bars3d.seriesList.length, 0)
+// waitForRendering(top)
+// }
+
+// function test_4_primary_series() {
+// bars3d.seriesList = [series1, series2]
+// compare(bars3d.primarySeries, series1)
+// bars3d.primarySeries = series2
+// compare(bars3d.primarySeries, series2)
+// waitForRendering(top)
+// }
+
+// function test_5_selected_series() {
+// bars3d.seriesList[0].selectedBar = Qt.point(0, 0)
+// compare(bars3d.selectedSeries, series1)
+
+// waitForRendering(top)
+// }
+
+// function test_6_has_series() {
+// bars3d.seriesList = [series1]
+// compare(bars3d.hasSeries(series1), true)
+// compare(bars3d.hasSeries(series2), false)
+
+// waitForRendering(top)
+// bars3d.destroy()
+// waitForRendering(top)
+// }
+// }
+
+// // The following tests are not required for scatter or surface, as they are handled identically
+
+// Custom3DItem { id: item1; meshFile: ":/customitem.obj" }
+// Custom3DItem { id: item2; meshFile: ":/customitem.obj" }
+// Custom3DItem { id: item3; meshFile: ":/customitem.obj" }
+// Custom3DItem { id: item4; meshFile: ":/customitem.obj"; position: Qt.vector3d(0.0, 1.0, 0.0) }
+
+// function constructBarsWithCustomItemList() {
+// bars3d = Qt.createQmlObject("
+// import QtQuick 2.2
+// import QtGraphs
+// Bars3D {
+// anchors.fill: parent
+// customItemList: [item1, item2]
+// }", top)
+// bars3d.anchors.fill = top
+// }
+
+// TODO: Needs either redoing, or fixing the code; see QTBUG-110000 & QTBUG-110001
+// TestCase {
+// name: "Bars3D Theme"
+// when: windowShown
+
+// Theme3D { id: newTheme }
+
+// function test_1_add_theme() {
+// constructBars()
+
+// bars3d.theme = newTheme
+// compare(bars3d.theme, newTheme)
+// waitForRendering(top)
+// }
+
+// function test_2_change_theme() {
+// newTheme.type = Theme3D.ThemePrimaryColors
+// compare(bars3d.theme.type, Theme3D.ThemePrimaryColors)
+
+// waitForRendering(top)
+// bars3d.destroy()
+// waitForRendering(top)
+// }
+// }
+
+// TODO: Needs either redoing, or fixing the code; see QTBUG-110000 & QTBUG-110001
+// TestCase {
+// name: "Bars3D Input"
+// when: windowShown
+
+// function test_1_remove_input() {
+// constructBars()
+// bars3d.inputHandler = null
+// compare(bars3d.inputHandler, null)
+
+// waitForRendering(top)
+// bars3d.destroy()
+// waitForRendering(top)
+// }
+// }
+
+// TODO: Needs either redoing, or fixing the code; see QTBUG-110000 & QTBUG-110001
+// TestCase {
+// name: "Bars3D Custom"
+// when: windowShown
+
+// function test_1_custom_items() {
+// constructBarsWithCustomItemList()
+// compare(bars3d.customItemList.length, 2)
+// waitForRendering(top)
+// }
+
+// function test_2_add_custom_items() {
+// bars3d.addCustomItem(item3)
+// compare(bars3d.customItemList.length, 3)
+// bars3d.addCustomItem(item4)
+// compare(bars3d.customItemList.length, 4)
+// waitForRendering(top)
+// }
+
+// function test_3_change_custom_items() {
+// item1.position = Qt.vector3d(1.0, 1.0, 1.0)
+// compare(bars3d.customItemList[0].position, Qt.vector3d(1.0, 1.0, 1.0))
+// waitForRendering(top)
+// }
+
+// function test_4_remove_custom_items() {
+// bars3d.removeCustomItemAt(Qt.vector3d(0.0, 1.0, 0.0))
+// compare(bars3d.customItemList.length, 3)
+// bars3d.releaseCustomItem(item1)
+// compare(bars3d.customItemList[0], item2)
+// bars3d.releaseCustomItem(item2)
+// compare(bars3d.customItemList.length, 1)
+// bars3d.removeCustomItems()
+// compare(bars3d.customItemList.length, 0)
+
+// waitForRendering(top)
+// bars3d.destroy()
+// waitForRendering(top)
+// }
+// }
+}
diff --git a/tests/auto/qmltest/bars3d/tst_barseries.qml b/tests/auto/qmltest/bars3d/tst_barseries.qml
new file mode 100644
index 0000000..39dc01f
--- /dev/null
+++ b/tests/auto/qmltest/bars3d/tst_barseries.qml
@@ -0,0 +1,223 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick 2.0
+import QtGraphs
+import QtTest 1.0
+
+Item {
+ id: top
+ height: 150
+ width: 150
+
+ Bar3DSeries {
+ id: initial
+ }
+
+ ColorGradient {
+ id: gradient1;
+ stops: [
+ ColorGradientStop { color: "red"; position: 0 },
+ ColorGradientStop { color: "blue"; position: 1 }
+ ]
+ }
+
+ ColorGradient {
+ id: gradient2;
+ stops: [
+ ColorGradientStop { color: "green"; position: 0 },
+ ColorGradientStop { color: "red"; position: 1 }
+ ]
+ }
+
+ ColorGradient {
+ id: gradient3;
+ stops: [
+ ColorGradientStop { color: "gray"; position: 0 },
+ ColorGradientStop { color: "darkgray"; position: 1 }
+ ]
+ }
+
+ ThemeColor {
+ id: rowColor1
+ color: "green"
+ }
+
+ ThemeColor {
+ id: rowColor2
+ color: "blue"
+ }
+ ThemeColor {
+ id: rowColor3
+ color: "red"
+ }
+
+ Bar3DSeries {
+ id: initialized
+ dataProxy: ItemModelBarDataProxy {
+ itemModel: ListModel {
+ ListElement{ year: "2012"; city: "Oulu"; expenses: "4200"; }
+ ListElement{ year: "2012"; city: "Rauma"; expenses: "2100"; }
+ }
+ rowRole: "city"
+ columnRole: "year"
+ valueRole: "expenses"
+ }
+ meshAngle: 15.0
+ selectedBar: Qt.point(0, 0)
+
+ baseColor: "blue"
+ baseGradient: gradient1
+ colorStyle: Theme3D.ColorStyleObjectGradient
+ itemLabelFormat: "%f"
+ itemLabelVisible: false
+ mesh: Abstract3DSeries.MeshCone
+ meshSmooth: true
+ multiHighlightColor: "green"
+ multiHighlightGradient: gradient2
+ name: "series1"
+ singleHighlightColor: "red"
+ singleHighlightGradient: gradient3
+ userDefinedMesh: ":/customitem.obj"
+ visible: false
+ rowColors: [ rowColor1, rowColor2, rowColor3 ]
+ }
+
+ ItemModelBarDataProxy {
+ id: proxy1
+ itemModel: ListModel {
+ ListElement{ year: "2012"; city: "Oulu"; expenses: "4200"; }
+ ListElement{ year: "2012"; city: "Rauma"; expenses: "2100"; }
+ ListElement{ year: "2012"; city: "Helsinki"; expenses: "7040"; }
+ }
+ rowRole: "city"
+ columnRole: "year"
+ valueRole: "expenses"
+ }
+
+ Bar3DSeries {
+ id: change
+ }
+
+ TestCase {
+ name: "Bar3DSeries Initial"
+
+ function test_1_initial() {
+ compare(initial.dataProxy.rowCount, 0)
+ compare(initial.invalidSelectionPosition, Qt.point(-1, -1))
+ compare(initial.meshAngle, 0)
+ compare(initial.selectedBar, Qt.point(-1, -1))
+ compare(initial.rowColors.length, 0)
+ }
+
+ function test_2_initial_common() {
+ // Common properties
+ compare(initial.baseColor, "#000000")
+ compare(initial.baseGradient, null)
+ compare(initial.colorStyle, Theme3D.ColorStyleUniform)
+ compare(initial.itemLabel, "")
+ compare(initial.itemLabelFormat, "@valueLabel")
+ compare(initial.itemLabelVisible, true)
+ compare(initial.mesh, Abstract3DSeries.MeshBevelBar)
+ compare(initial.meshRotation, Qt.quaternion(1, 0, 0, 0))
+ compare(initial.meshSmooth, false)
+ compare(initial.multiHighlightColor, "#000000")
+ compare(initial.multiHighlightGradient, null)
+ compare(initial.name, "")
+ compare(initial.singleHighlightColor, "#000000")
+ compare(initial.singleHighlightGradient, null)
+ compare(initial.type, Abstract3DSeries.SeriesTypeBar)
+ compare(initial.userDefinedMesh, "")
+ compare(initial.visible, true)
+ }
+ }
+
+ TestCase {
+ name: "Bar3DSeries Initialized"
+
+ function test_1_initialized() {
+ compare(initialized.dataProxy.rowCount, 2)
+ fuzzyCompare(initialized.meshAngle, 15.0, 0.01)
+ compare(initialized.selectedBar, Qt.point(0, 0))
+ compare(initialized.rowColors.length, 3)
+ }
+
+ function test_2_initialized_common() {
+ // Common properties
+ compare(initialized.baseColor, "#0000ff")
+ compare(initialized.baseGradient, gradient1)
+ compare(initialized.colorStyle, Theme3D.ColorStyleObjectGradient)
+ compare(initialized.itemLabelFormat, "%f")
+ compare(initialized.itemLabelVisible, false)
+ compare(initialized.mesh, Abstract3DSeries.MeshCone)
+ compare(initialized.meshSmooth, true)
+ compare(initialized.multiHighlightColor, "#008000")
+ compare(initialized.multiHighlightGradient, gradient2)
+ compare(initialized.name, "series1")
+ compare(initialized.singleHighlightColor, "#ff0000")
+ compare(initialized.singleHighlightGradient, gradient3)
+ compare(initialized.userDefinedMesh, ":/customitem.obj")
+ compare(initialized.visible, false)
+ }
+ }
+
+ TestCase {
+ name: "Bar3DSeries Change"
+
+ function test_1_change() {
+ change.dataProxy = proxy1
+ change.meshAngle = 15.0
+ change.selectedBar = Qt.point(0, 0)
+ change.rowColors = [rowColor1, rowColor2, rowColor3]
+ }
+
+ function test_2_test_change() {
+ // This test has a dependency to the previous one due to asynchronous item model resolving
+ compare(change.dataProxy.rowCount, 3)
+ fuzzyCompare(change.meshAngle, 15.0, 0.01)
+ compare(change.selectedBar, Qt.point(0, 0))
+ }
+
+ function test_3_change_common() {
+ change.baseColor = "blue"
+ change.baseGradient = gradient1
+ change.colorStyle = Theme3D.ColorStyleObjectGradient
+ change.itemLabelFormat = "%f"
+ change.itemLabelVisible = false
+ change.mesh = Abstract3DSeries.MeshCone
+ change.meshSmooth = true
+ change.multiHighlightColor = "green"
+ change.multiHighlightGradient = gradient2
+ change.name = "series1"
+ change.singleHighlightColor = "red"
+ change.singleHighlightGradient = gradient3
+ change.userDefinedMesh = ":/customitem.obj"
+ change.visible = false
+
+ compare(change.baseColor, "#0000ff")
+ compare(change.baseGradient, gradient1)
+ compare(change.colorStyle, Theme3D.ColorStyleObjectGradient)
+ compare(change.itemLabelFormat, "%f")
+ compare(change.itemLabelVisible, false)
+ compare(change.mesh, Abstract3DSeries.MeshCone)
+ compare(change.meshSmooth, true)
+ compare(change.multiHighlightColor, "#008000")
+ compare(change.multiHighlightGradient, gradient2)
+ compare(change.name, "series1")
+ compare(change.singleHighlightColor, "#ff0000")
+ compare(change.singleHighlightGradient, gradient3)
+ compare(change.userDefinedMesh, ":/customitem.obj")
+ compare(change.visible, false)
+ }
+
+ function test_4_change_gradient_stop() {
+ gradient1.stops[0].color = "yellow"
+ compare(change.baseGradient.stops[0].color, "#ffff00")
+ }
+
+ function test_5_change_rowColors() {
+ rowColor2.color = "purple"
+ compare(change.rowColors[1].color, "#800080")
+ }
+ }
+}
diff --git a/tests/auto/qmltest/bars3d/tst_basic.qml b/tests/auto/qmltest/bars3d/tst_basic.qml
new file mode 100644
index 0000000..99ca0ee
--- /dev/null
+++ b/tests/auto/qmltest/bars3d/tst_basic.qml
@@ -0,0 +1,284 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick 2.0
+import QtGraphs
+import QtTest 1.0
+
+Item {
+ id: top
+ height: 150
+ width: 150
+
+ property var empty: null
+ property var basic: null
+ property var common: null
+ property var common_init: null
+
+// function constructEmpty() {
+// empty = Qt.createQmlObject("
+// import QtQuick 2.2
+// import QtGraphs
+// Bars3D {
+// }", top)
+// }
+
+// function constructBasic() {
+// basic = Qt.createQmlObject("
+// import QtQuick 2.2
+// import QtGraphs
+// Bars3D {
+// anchors.fill: parent
+// multiSeriesUniform: true
+// barThickness: 0.1
+// barSpacing.width: 0.1
+// barSpacing.height: 0.1
+// barSeriesMargin.width: 0.3
+// barSeriesMargin.height: 0.3
+// barSpacingRelative: false
+// floorLevel: 1.0
+// }", top)
+// basic.anchors.fill = top
+// }
+
+// function constructCommon() {
+// common = Qt.createQmlObject("
+// import QtQuick 2.2
+// import QtGraphs
+// Bars3D {
+// anchors.fill: parent
+// }", top)
+// common.anchors.fill = top
+// }
+
+// function constructCommonInit() {
+// common_init = Qt.createQmlObject("
+// import QtQuick 2.2
+// import QtGraphs
+// Bars3D {
+// anchors.fill: parent
+// selectionMode: AbstractGraph3D.SelectionNone
+// shadowQuality: AbstractGraph3D.ShadowQualityLow
+// msaaSamples: 2
+// theme: Theme3D { }
+// renderingMode: AbstractGraph3D.RenderIndirect
+// measureFps: true
+// orthoProjection: false
+// aspectRatio: 3.0
+// optimizationHints: AbstractGraph3D.OptimizationStatic
+// polar: false
+// radialLabelOffset: 2
+// horizontalAspectRatio: 0.2
+// reflection: true
+// reflectivity: 0.1
+// locale: Qt.locale(\"UK\")
+// margin: 0.2
+// }", top)
+// common_init.anchors.fill = top
+// }
+
+// TODO: Needs either redoing, or fixing the code; see QTBUG-110000 & QTBUG-110001
+// TestCase {
+// name: "Bars3D Empty"
+// when: windowShown
+
+// function test_empty() {
+// constructEmpty()
+// compare(empty.width, 0, "width")
+// compare(empty.height, 0, "height")
+// compare(empty.multiSeriesUniform, false, "multiSeriesUniform")
+// compare(empty.barThickness, 1.0, "barThickness")
+// compare(empty.barSpacing, Qt.size(1.0, 1.0), "barSpacing")
+// compare(empty.barSeriesMargin, Qt.size(0.0, 0.0), "barSeriesMargin")
+// compare(empty.barSpacingRelative, true, "barSpacingRelative")
+// compare(empty.seriesList.length, 0, "seriesList")
+// compare(empty.selectedSeries, null, "selectedSeries")
+// compare(empty.primarySeries, null, "primarySeries")
+// compare(empty.floorLevel, 0.0, "floorLevel")
+// compare(empty.columnAxis.orientation, AbstractAxis3D.AxisOrientationX)
+// compare(empty.rowAxis.orientation, AbstractAxis3D.AxisOrientationZ)
+// compare(empty.valueAxis.orientation, AbstractAxis3D.AxisOrientationY)
+// compare(empty.columnAxis.type, AbstractAxis3D.AxisTypeCategory)
+// compare(empty.rowAxis.type, AbstractAxis3D.AxisTypeCategory)
+// compare(empty.valueAxis.type, AbstractAxis3D.AxisTypeValue)
+// waitForRendering(top)
+// empty.destroy()
+// waitForRendering(top)
+// }
+// }
+
+// TODO: Needs either redoing, or fixing the code; see QTBUG-110000 & QTBUG-110001
+// TestCase {
+// name: "Bars3D Basic"
+// when: windowShown
+
+// function test_1_basic() {
+// constructBasic()
+// compare(basic.width, 150, "width")
+// compare(basic.height, 150, "height")
+// compare(basic.multiSeriesUniform, true, "multiSeriesUniform")
+// compare(basic.barThickness, 0.1, "barThickness")
+// compare(basic.barSpacing, Qt.size(0.1, 0.1), "barSpacing")
+// compare(basic.barSeriesMargin, Qt.size(0.3, 0.3), "barSeriesMargin")
+// compare(basic.barSpacingRelative, false, "barSpacingRelative")
+// compare(basic.floorLevel, 1.0, "floorLevel")
+// waitForRendering(top)
+// }
+
+// function test_2_basic_change() {
+// basic.multiSeriesUniform = false
+// basic.barThickness = 0.5
+// basic.barSpacing = Qt.size(1.0, 0.0)
+// basic.barSeriesMargin = Qt.size(0.5, 0.0)
+// basic.barSpacingRelative = true
+// basic.floorLevel = 0.2
+// compare(basic.multiSeriesUniform, false, "multiSeriesUniform")
+// compare(basic.barThickness, 0.5, "barThickness")
+// compare(basic.barSpacing, Qt.size(1.0, 0.0), "barSpacing")
+// compare(basic.barSeriesMargin, Qt.size(0.5, 0.0), "barSeriesMargin")
+// compare(basic.barSpacingRelative, true, "barSpacingRelative")
+// compare(basic.floorLevel, 0.2, "floorLevel")
+// waitForRendering(top)
+// }
+
+// function test_3_basic_change_invalid() {
+// basic.barThickness = -1
+// basic.barSpacing = Qt.size(-1.0, -1.0)
+// basic.barSeriesMargin = Qt.size(-1.0, -1.0)
+// compare(basic.barThickness, -1/*0.5*/, "barThickness") // TODO: Fix once QTRD-3367 is done
+// compare(basic.barSpacing, Qt.size(-1.0, -1.0), "barSpacing")
+// compare(basic.barSeriesMargin, Qt.size(-1.0, -1.0), "barSeriesMargin")
+// waitForRendering(top)
+// basic.destroy()
+// waitForRendering(top)
+// }
+// }
+
+// TODO: Needs either redoing, or fixing the code; see QTBUG-110000 & QTBUG-110001
+// TestCase {
+// name: "Bars3D Common"
+// when: windowShown
+
+// function test_1_common() {
+// constructCommon()
+// compare(common.selectionMode, AbstractGraph3D.SelectionItem, "selectionMode")
+// compare(common.shadowQuality, AbstractGraph3D.ShadowQualityMedium, "shadowQuality")
+// if (common.shadowsSupported === true)
+// compare(common.msaaSamples, 4, "msaaSamples")
+// else
+// compare(common.msaaSamples, 0, "msaaSamples")
+// compare(common.theme.type, Theme3D.ThemeQt, "theme")
+// compare(common.renderingMode, AbstractGraph3D.RenderIndirect, "renderingMode")
+// compare(common.measureFps, false, "measureFps")
+// compare(common.customItemList.length, 0, "customItemList")
+// compare(common.orthoProjection, false, "orthoProjection")
+// compare(common.selectedElement, AbstractGraph3D.ElementNone, "selectedElement")
+// compare(common.aspectRatio, 2.0, "aspectRatio")
+// compare(common.optimizationHints, AbstractGraph3D.OptimizationDefault, "optimizationHints")
+// compare(common.polar, false, "polar")
+// compare(common.radialLabelOffset, 1, "radialLabelOffset")
+// compare(common.horizontalAspectRatio, 0, "horizontalAspectRatio")
+// compare(common.reflection, false, "reflection")
+// compare(common.reflectivity, 0.5, "reflectivity")
+// compare(common.locale, Qt.locale("C"), "locale")
+// compare(common.queriedGraphPosition, Qt.vector3d(0, 0, 0), "queriedGraphPosition")
+// compare(common.margin, -1, "margin")
+// waitForRendering(top)
+// }
+
+// function test_2_change_common() {
+// common.selectionMode = AbstractGraph3D.SelectionItem | AbstractGraph3D.SelectionRow | AbstractGraph3D.SelectionSlice
+// common.shadowQuality = AbstractGraph3D.ShadowQualitySoftHigh
+// compare(common.shadowQuality, AbstractGraph3D.ShadowQualitySoftHigh, "shadowQuality")
+// common.msaaSamples = 8
+// if (common.shadowsSupported === true)
+// compare(common.msaaSamples, 8, "msaaSamples")
+// else
+// compare(common.msaaSamples, 0, "msaaSamples")
+// common.theme.type = Theme3D.ThemeRetro
+// common.renderingMode = AbstractGraph3D.RenderDirectToBackground_NoClear
+// common.measureFps = true
+// common.orthoProjection = true
+// common.aspectRatio = 1.0
+// common.optimizationHints = AbstractGraph3D.OptimizationStatic
+// common.polar = true
+// common.radialLabelOffset = 2
+// common.horizontalAspectRatio = 1
+// common.reflection = true
+// common.reflectivity = 1.0
+// common.locale = Qt.locale("FI")
+// common.margin = 1.0
+// compare(common.selectionMode, AbstractGraph3D.SelectionItem | AbstractGraph3D.SelectionRow | AbstractGraph3D.SelectionSlice, "selectionMode")
+// compare(common.shadowQuality, AbstractGraph3D.ShadowQualityNone, "shadowQuality") // Ortho disables shadows
+// compare(common.msaaSamples, 0, "msaaSamples") // Rendering mode changes this to zero
+// compare(common.theme.type, Theme3D.ThemeRetro, "theme")
+// compare(common.renderingMode, AbstractGraph3D.RenderDirectToBackground_NoClear, "renderingMode")
+// compare(common.measureFps, true, "measureFps")
+// compare(common.orthoProjection, true, "orthoProjection")
+// compare(common.aspectRatio, 1.0, "aspectRatio")
+// compare(common.optimizationHints, AbstractGraph3D.OptimizationStatic, "optimizationHints")
+// compare(common.polar, true, "polar")
+// compare(common.radialLabelOffset, 2, "radialLabelOffset")
+// compare(common.horizontalAspectRatio, 1, "horizontalAspectRatio")
+// compare(common.reflection, true, "reflection")
+// compare(common.reflectivity, 1.0, "reflectivity")
+// compare(common.locale, Qt.locale("FI"), "locale")
+// compare(common.margin, 1.0, "margin")
+// waitForRendering(top)
+// }
+
+// function test_3_change_invalid_common() {
+// common.selectionMode = AbstractGraph3D.SelectionRow | AbstractGraph3D.SelectionColumn | AbstractGraph3D.SelectionSlice
+// common.theme.type = -2
+// common.renderingMode = -1
+// common.measureFps = false
+// common.orthoProjection = false
+// common.aspectRatio = -1.0
+// common.polar = false
+// common.horizontalAspectRatio = -2
+// common.reflection = false
+// common.reflectivity = -1.0
+// compare(common.selectionMode, AbstractGraph3D.SelectionItem | AbstractGraph3D.SelectionRow | AbstractGraph3D.SelectionSlice, "selectionMode")
+// compare(common.theme.type, -2/*Theme3D.ThemeRetro*/, "theme") // TODO: Fix once QTRD-3367 is done
+// compare(common.renderingMode, -1/*AbstractGraph3D.RenderDirectToBackground_NoClear*/, "renderingMode") // TODO: Fix once QTRD-3367 is done
+// compare(common.aspectRatio, -1.0/*1.0*/, "aspectRatio") // TODO: Fix once QTRD-3367 is done
+// compare(common.horizontalAspectRatio, -2/*1*/, "horizontalAspectRatio") // TODO: Fix once QTRD-3367 is done
+// compare(common.reflectivity, -1.0/*1.0*/, "reflectivity") // TODO: Fix once QTRD-3367 is done
+
+// waitForRendering(top)
+// common.destroy()
+// waitForRendering(top)
+// }
+
+// function test_4_common_initialized() {
+// constructCommonInit()
+
+// compare(common_init.selectionMode, AbstractGraph3D.SelectionNone, "selectionMode")
+// if (common_init.shadowsSupported === true) {
+// tryCompare(common_init, "shadowQuality", AbstractGraph3D.ShadowQualityLow)
+// compare(common_init.msaaSamples, 2, "msaaSamples")
+// } else {
+// tryCompare(common_init, "shadowQuality", AbstractGraph3D.ShadowQualityNone)
+// compare(common_init.msaaSamples, 0, "msaaSamples")
+// }
+// compare(common_init.theme.type, Theme3D.ThemeUserDefined, "theme")
+// compare(common_init.renderingMode, AbstractGraph3D.RenderIndirect, "renderingMode")
+// compare(common_init.measureFps, true, "measureFps")
+// compare(common_init.customItemList.length, 0, "customItemList")
+// compare(common_init.orthoProjection, false, "orthoProjection")
+// compare(common_init.aspectRatio, 3.0, "aspectRatio")
+// compare(common_init.optimizationHints, AbstractGraph3D.OptimizationStatic, "optimizationHints")
+// compare(common_init.polar, false, "polar")
+// compare(common_init.radialLabelOffset, 2, "radialLabelOffset")
+// compare(common_init.horizontalAspectRatio, 0.2, "horizontalAspectRatio")
+// compare(common_init.reflection, true, "reflection")
+// compare(common_init.reflectivity, 0.1, "reflectivity")
+// compare(common_init.locale, Qt.locale("UK"), "locale")
+// compare(common_init.margin, 0.2, "margin")
+
+// waitForRendering(top)
+// common_init.destroy();
+// waitForRendering(top)
+// }
+// }
+}
diff --git a/tests/auto/qmltest/bars3d/tst_proxy.qml b/tests/auto/qmltest/bars3d/tst_proxy.qml
new file mode 100644
index 0000000..4d2da41
--- /dev/null
+++ b/tests/auto/qmltest/bars3d/tst_proxy.qml
@@ -0,0 +1,239 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick 2.0
+import QtGraphs
+import QtTest 1.0
+
+Item {
+ id: top
+ height: 150
+ width: 150
+
+ ItemModelBarDataProxy {
+ id: initial
+ }
+
+ ItemModelBarDataProxy {
+ id: initialized
+
+ autoColumnCategories: false
+ autoRowCategories: false
+ columnCategories: ["colcat1", "colcat2"]
+ columnRole: "col"
+ columnRolePattern: /^.*-(\d\d)$/
+ columnRoleReplace: "\\1"
+ itemModel: ListModel { objectName: "model1" }
+ multiMatchBehavior: ItemModelBarDataProxy.MMBAverage
+ rotationRole: "rot"
+ rotationRolePattern: /-/
+ rotationRoleReplace: "\\1"
+ rowCategories: ["rowcat1", "rowcat2"]
+ rowRole: "row"
+ rowRolePattern: /^(\d\d\d\d).*$/
+ rowRoleReplace: "\\1"
+ valueRole: "val"
+ valueRolePattern: /-/
+ valueRoleReplace: "\\1"
+
+ columnLabels: ["col1", "col2"]
+ rowLabels: ["row1", "row2"]
+ }
+
+ ItemModelBarDataProxy {
+ id: change
+ }
+
+ TestCase {
+ name: "ItemModelBarDataProxy Initial"
+
+ function test_initial() {
+ compare(initial.autoColumnCategories, true)
+ compare(initial.autoRowCategories, true)
+ compare(initial.columnCategories, [])
+ compare(initial.columnRole, "")
+ verify(initial.columnRolePattern)
+ compare(initial.columnRoleReplace, "")
+ verify(!initial.itemModel)
+ compare(initial.multiMatchBehavior, ItemModelBarDataProxy.MMBLast)
+ compare(initial.rotationRole, "")
+ verify(initial.rotationRolePattern)
+ compare(initial.rotationRoleReplace, "")
+ compare(initial.rowCategories, [])
+ compare(initial.rowRole, "")
+ verify(initial.rowRolePattern)
+ compare(initial.rowRoleReplace, "")
+ compare(initial.useModelCategories, false)
+ compare(initial.valueRole, "")
+ verify(initial.valueRolePattern)
+ compare(initial.valueRoleReplace, "")
+
+ compare(initial.columnLabels.length, 0)
+ compare(initial.rowCount, 0)
+ compare(initial.rowLabels.length, 0)
+ verify(!initial.series)
+
+ compare(initial.type, AbstractDataProxy.DataTypeBar)
+ }
+ }
+
+ TestCase {
+ name: "ItemModelBarDataProxy Initialized"
+
+ function test_initialized() {
+ compare(initialized.autoColumnCategories, false)
+ compare(initialized.autoRowCategories, false)
+ compare(initialized.columnCategories.length, 2)
+ compare(initialized.columnCategories[0], "colcat1")
+ compare(initialized.columnCategories[1], "colcat2")
+ compare(initialized.columnRole, "col")
+ compare(initialized.columnRolePattern, /^.*-(\d\d)$/)
+ compare(initialized.columnRoleReplace, "\\1")
+ compare(initialized.itemModel.objectName, "model1")
+ compare(initialized.multiMatchBehavior, ItemModelBarDataProxy.MMBAverage)
+ compare(initialized.rotationRole, "rot")
+ compare(initialized.rotationRolePattern, /-/)
+ compare(initialized.rotationRoleReplace, "\\1")
+ compare(initialized.rowCategories.length, 2)
+ compare(initialized.rowCategories[0], "rowcat1")
+ compare(initialized.rowCategories[1], "rowcat2")
+ compare(initialized.rowRole, "row")
+ compare(initialized.rowRolePattern, /^(\d\d\d\d).*$/)
+ compare(initialized.rowRoleReplace, "\\1")
+ compare(initialized.valueRole, "val")
+ compare(initialized.valueRolePattern, /-/)
+ compare(initialized.valueRoleReplace, "\\1")
+
+ compare(initialized.columnLabels.length, 2)
+ compare(initialized.rowCount, 2)
+ compare(initialized.rowLabels.length, 2)
+ }
+ }
+
+ TestCase {
+ name: "ItemModelBarDataProxy Change"
+
+ ListModel { id: model1; objectName: "model1" }
+
+ function test_1_change() {
+ change.autoColumnCategories = false
+ change.autoRowCategories = false
+ change.columnCategories = ["colcat1", "colcat2"]
+ change.columnRole = "col"
+ change.columnRolePattern = /^.*-(\d\d)$/
+ change.columnRoleReplace = "\\1"
+ change.itemModel = model1
+ change.multiMatchBehavior = ItemModelBarDataProxy.MMBAverage
+ change.rotationRole = "rot"
+ change.rotationRolePattern = /-/
+ change.rotationRoleReplace = "\\1"
+ change.rowCategories = ["rowcat1", "rowcat2"]
+ change.rowRole = "row"
+ change.rowRolePattern = /^(\d\d\d\d).*$/
+ change.rowRoleReplace = "\\1"
+ change.useModelCategories = true // Overwrites columnLabels and rowLabels
+ change.valueRole = "val"
+ change.valueRolePattern = /-/
+ change.valueRoleReplace = "\\1"
+
+ change.columnLabels = ["col1", "col2"]
+ change.rowLabels = ["row1", "row2"]
+ }
+
+ function test_2_test_change() {
+ // This test has a dependency to the previous one due to asynchronous item model resolving
+ compare(change.autoColumnCategories, false)
+ compare(change.autoRowCategories, false)
+ compare(change.columnCategories.length, 2)
+ compare(change.columnCategories[0], "colcat1")
+ compare(change.columnCategories[1], "colcat2")
+ compare(change.columnRole, "col")
+ compare(change.columnRolePattern, /^.*-(\d\d)$/)
+ compare(change.columnRoleReplace, "\\1")
+ compare(change.itemModel.objectName, "model1")
+ compare(change.multiMatchBehavior, ItemModelBarDataProxy.MMBAverage)
+ compare(change.rotationRole, "rot")
+ compare(change.rotationRolePattern, /-/)
+ compare(change.rotationRoleReplace, "\\1")
+ compare(change.rowCategories.length, 2)
+ compare(change.rowCategories[0], "rowcat1")
+ compare(change.rowCategories[1], "rowcat2")
+ compare(change.rowRole, "row")
+ compare(change.rowRolePattern, /^(\d\d\d\d).*$/)
+ compare(change.rowRoleReplace, "\\1")
+ compare(change.useModelCategories, true)
+ compare(change.valueRole, "val")
+ compare(change.valueRolePattern, /-/)
+ compare(change.valueRoleReplace, "\\1")
+
+ compare(change.columnLabels.length, 1)
+ compare(change.rowCount, 0)
+ compare(change.rowLabels.length, 0)
+ }
+ }
+
+// TODO: Needs either redoing, or fixing the code; see QTBUG-110000 & QTBUG-110001
+// TestCase {
+// name: "ItemModelBarDataProxy MultiMatchBehaviour"
+
+// Bars3D {
+// id: bars1
+
+// Bar3DSeries {
+// ItemModelBarDataProxy {
+// id: barProxy
+// itemModel: ListModel {
+// ListElement{ coords: "0,0"; data: "5"; }
+// ListElement{ coords: "0,0"; data: "15"; }
+// }
+// rowRole: "coords"
+// columnRole: "coords"
+// valueRole: "data"
+// rowRolePattern: /(\d),\d/
+// columnRolePattern: /(\d),(\d)/
+// rowRoleReplace: "\\1"
+// columnRoleReplace: "\\2"
+// }
+// }
+// }
+
+// function test_0_async_dummy() {
+// }
+
+// function test_1_test_multimatch() {
+// compare(bars1.valueAxis.max, 15)
+// }
+
+// function test_2_multimatch() {
+// barProxy.multiMatchBehavior = ItemModelBarDataProxy.MMBFirst
+// }
+
+// function test_3_test_multimatch() {
+// compare(bars1.valueAxis.max, 5)
+// }
+
+// function test_4_multimatch() {
+// barProxy.multiMatchBehavior = ItemModelBarDataProxy.MMBLast
+// }
+
+// function test_5_test_multimatch() {
+// compare(bars1.valueAxis.max, 15)
+// }
+
+// function test_6_multimatch() {
+// barProxy.multiMatchBehavior = ItemModelBarDataProxy.MMBAverage
+// }
+
+// function test_7_test_multimatch() {
+// compare(bars1.valueAxis.max, 10)
+// }
+
+// function test_8_multimatch() {
+// barProxy.multiMatchBehavior = ItemModelBarDataProxy.MMBCumulative
+// }
+
+// function test_9_test_multimatch() {
+// compare(bars1.valueAxis.max, 20)
+// }
+// }
+}
diff --git a/tests/auto/qmltest/custom3d/tst_customitem.qml b/tests/auto/qmltest/custom3d/tst_customitem.qml
new file mode 100644
index 0000000..e1566fe
--- /dev/null
+++ b/tests/auto/qmltest/custom3d/tst_customitem.qml
@@ -0,0 +1,91 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick 2.0
+import QtGraphs
+import QtTest 1.0
+
+Item {
+ id: top
+ width: 150
+ height: 150
+
+ Custom3DItem {
+ id: initial
+ }
+
+ Custom3DItem {
+ id: initialized
+ meshFile: ":\customitem.obj"
+ position: Qt.vector3d(1.0, 0.5, 1.0)
+ positionAbsolute: true
+ rotation: Qt.quaternion(1, 0.5, 0, 0)
+ scaling: Qt.vector3d(0.2, 0.2, 0.2)
+ scalingAbsolute: false
+ shadowCasting: false
+ textureFile: ":\customtexture.jpg"
+ visible: false
+ }
+
+ Custom3DItem {
+ id: change
+ }
+
+ TestCase {
+ name: "Custom3DItem Initial"
+
+ function test_initial() {
+ compare(initial.meshFile, "")
+ compare(initial.position, Qt.vector3d(0.0, 0.0, 0.0))
+ compare(initial.positionAbsolute, false)
+ compare(initial.rotation, Qt.quaternion(0, 0, 0, 0))
+ compare(initial.scaling, Qt.vector3d(0.1, 0.1, 0.1))
+ compare(initial.scalingAbsolute, true)
+ compare(initial.shadowCasting, true)
+ compare(initial.textureFile, "")
+ compare(initial.visible, true)
+ }
+ }
+
+ TestCase {
+ name: "Custom3DItem Initialized"
+
+ function test_initialized() {
+ compare(initialized.meshFile, ":\customitem.obj")
+ compare(initialized.position, Qt.vector3d(1.0, 0.5, 1.0))
+ compare(initialized.positionAbsolute, true)
+ compare(initialized.rotation, Qt.quaternion(1, 0.5, 0, 0))
+ compare(initialized.scaling, Qt.vector3d(0.2, 0.2, 0.2))
+ compare(initialized.scalingAbsolute, false)
+ compare(initialized.shadowCasting, false)
+ compare(initialized.textureFile, ":\customtexture.jpg")
+ compare(initialized.visible, false)
+ }
+ }
+
+ TestCase {
+ name: "Custom3DItem Change"
+
+ function test_change() {
+ change.meshFile = ":\customitem.obj"
+ change.position = Qt.vector3d(1.0, 0.5, 1.0)
+ change.positionAbsolute = true
+ change.rotation = Qt.quaternion(1, 0.5, 0, 0)
+ change.scaling = Qt.vector3d(0.2, 0.2, 0.2)
+ change.scalingAbsolute = false
+ change.shadowCasting = false
+ change.textureFile = ":\customtexture.jpg"
+ change.visible = false
+
+ compare(change.meshFile, ":\customitem.obj")
+ compare(change.position, Qt.vector3d(1.0, 0.5, 1.0))
+ compare(change.positionAbsolute, true)
+ compare(change.rotation, Qt.quaternion(1, 0.5, 0, 0))
+ compare(change.scaling, Qt.vector3d(0.2, 0.2, 0.2))
+ compare(change.scalingAbsolute, false)
+ compare(change.shadowCasting, false)
+ compare(change.textureFile, ":\customtexture.jpg")
+ compare(change.visible, false)
+ }
+ }
+}
diff --git a/tests/auto/qmltest/custom3d/tst_customlabel.qml b/tests/auto/qmltest/custom3d/tst_customlabel.qml
new file mode 100644
index 0000000..fc7a679
--- /dev/null
+++ b/tests/auto/qmltest/custom3d/tst_customlabel.qml
@@ -0,0 +1,119 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick 2.0
+import QtGraphs
+import QtTest 1.0
+
+Item {
+ id: top
+ width: 150
+ height: 150
+
+ Custom3DLabel {
+ id: initial
+ }
+
+ Custom3DLabel {
+ id: initialized
+ backgroundColor: "red"
+ backgroundEnabled: false
+ borderEnabled: false
+ facingCamera: true
+ font.family: "Times New Roman"
+ text: "test label"
+ textColor: "blue"
+
+ position: Qt.vector3d(1.0, 0.5, 1.0)
+ positionAbsolute: true
+ rotation: Qt.quaternion(1, 0.5, 0, 0)
+ scaling: Qt.vector3d(0.2, 0.2, 0.2)
+ shadowCasting: true
+ visible: false
+ }
+
+ Custom3DLabel {
+ id: change
+ }
+
+ TestCase {
+ name: "Custom3DLabel Initial"
+
+ function test_initial() {
+ compare(initial.backgroundColor, "#a0a0a4")
+ compare(initial.backgroundEnabled, true)
+ compare(initial.borderEnabled, true)
+ compare(initial.facingCamera, false)
+ compare(initial.font.family, "Arial")
+ compare(initial.text, "")
+ compare(initial.textColor, "#ffffff")
+
+ compare(initial.meshFile, ":/defaultMeshes/plane")
+ compare(initial.position, Qt.vector3d(0.0, 0.0, 0.0))
+ compare(initial.positionAbsolute, false)
+ compare(initial.rotation, Qt.quaternion(0, 0, 0, 0))
+ compare(initial.scaling, Qt.vector3d(0.1, 0.1, 0.1))
+ compare(initial.scalingAbsolute, true)
+ compare(initial.shadowCasting, false)
+ compare(initial.textureFile, "")
+ compare(initial.visible, true)
+ }
+ }
+
+ TestCase {
+ name: "Custom3DLabel Initialized"
+
+ function test_initialized() {
+ compare(initialized.backgroundColor, "#ff0000")
+ compare(initialized.backgroundEnabled, false)
+ compare(initialized.borderEnabled, false)
+ compare(initialized.facingCamera, true)
+ compare(initialized.font.family, "Times New Roman")
+ compare(initialized.text, "test label")
+ compare(initialized.textColor, "#0000ff")
+
+ compare(initialized.position, Qt.vector3d(1.0, 0.5, 1.0))
+ compare(initialized.positionAbsolute, true)
+ compare(initialized.rotation, Qt.quaternion(1, 0.5, 0, 0))
+ compare(initialized.scaling, Qt.vector3d(0.2, 0.2, 0.2))
+ compare(initialized.shadowCasting, true)
+ compare(initialized.visible, false)
+ }
+ }
+
+ TestCase {
+ name: "Custom3DLabel Change"
+
+ function test_change() {
+ change.backgroundColor = "red"
+ change.backgroundEnabled = false
+ change.borderEnabled = false
+ change.facingCamera = true
+ change.font.family = "Times New Roman"
+ change.text = "test label"
+ change.textColor = "blue"
+
+ change.position = Qt.vector3d(1.0, 0.5, 1.0)
+ change.positionAbsolute = true
+ change.rotation = Qt.quaternion(1, 0.5, 0, 0)
+ change.scaling = Qt.vector3d(0.2, 0.2, 0.2)
+ change.shadowCasting = true
+ change.visible = false
+
+ compare(change.backgroundColor, "#ff0000")
+ compare(change.backgroundEnabled, false)
+ compare(change.borderEnabled, false)
+ compare(change.facingCamera, true)
+ compare(change.font.family, "Times New Roman")
+ compare(change.text, "test label")
+ compare(change.textColor, "#0000ff")
+
+ compare(change.position, Qt.vector3d(1.0, 0.5, 1.0))
+ compare(change.positionAbsolute, true)
+ compare(change.rotation, Qt.quaternion(1, 0.5, 0, 0))
+ compare(change.scaling, Qt.vector3d(0.2, 0.2, 0.2))
+ compare(change.shadowCasting, true)
+ compare(change.visible, false)
+ }
+ }
+}
diff --git a/tests/auto/qmltest/custom3d/tst_customvolume.qml b/tests/auto/qmltest/custom3d/tst_customvolume.qml
new file mode 100644
index 0000000..98d308c
--- /dev/null
+++ b/tests/auto/qmltest/custom3d/tst_customvolume.qml
@@ -0,0 +1,167 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick 2.0
+import QtGraphs
+import QtTest 1.0
+
+Item {
+ id: top
+ width: 150
+ height: 150
+
+ Custom3DVolume {
+ id: initial
+ }
+
+ Custom3DVolume {
+ id: initialized
+ alphaMultiplier: 0.1
+ drawSliceFrames: true
+ drawSlices: true
+ preserveOpacity: false
+ sliceFrameColor: "red"
+ sliceFrameGaps: Qt.vector3d(2.0, 2.0, 2.0)
+ sliceFrameThicknesses: Qt.vector3d(2.0, 2.0, 2.0)
+ sliceFrameWidths: Qt.vector3d(2.0, 2.0, 2.0)
+ sliceIndexX: 0
+ sliceIndexY: 0
+ sliceIndexZ: 0
+ useHighDefShader: false
+
+ position: Qt.vector3d(1.0, 0.5, 1.0)
+ positionAbsolute: true
+ rotation: Qt.quaternion(1, 0.5, 0, 0)
+ scaling: Qt.vector3d(0.2, 0.2, 0.2)
+ scalingAbsolute: false
+ shadowCasting: false
+ visible: false
+ }
+
+ Custom3DVolume {
+ id: change
+ }
+
+ Custom3DVolume {
+ id: invalid
+ }
+
+ TestCase {
+ name: "Custom3DVolume Initial"
+
+ function test_initial() {
+ compare(initial.alphaMultiplier, 1.0)
+ compare(initial.drawSliceFrames, false)
+ compare(initial.drawSlices, false)
+ compare(initial.preserveOpacity, true)
+ compare(initial.sliceFrameColor, "#000000")
+ compare(initial.sliceFrameGaps, Qt.vector3d(0.01, 0.01, 0.01))
+ compare(initial.sliceFrameThicknesses, Qt.vector3d(0.01, 0.01, 0.01))
+ compare(initial.sliceFrameWidths, Qt.vector3d(0.01, 0.01, 0.01))
+ compare(initial.sliceIndexX, -1)
+ compare(initial.sliceIndexY, -1)
+ compare(initial.sliceIndexZ, -1)
+ compare(initial.useHighDefShader, true)
+
+ compare(initial.meshFile, ":/defaultMeshes/barFull")
+ compare(initial.position, Qt.vector3d(0.0, 0.0, 0.0))
+ compare(initial.positionAbsolute, false)
+ compare(initial.rotation, Qt.quaternion(0, 0, 0, 0))
+ compare(initial.scaling, Qt.vector3d(0.1, 0.1, 0.1))
+ compare(initial.scalingAbsolute, true)
+ compare(initial.shadowCasting, true)
+ compare(initial.textureFile, "")
+ compare(initial.visible, true)
+ }
+ }
+
+ TestCase {
+ name: "Custom3DVolume Initialized"
+
+ function test_initialized() {
+ compare(initialized.alphaMultiplier, 0.1)
+ compare(initialized.drawSliceFrames, true)
+ compare(initialized.drawSlices, true)
+ compare(initialized.preserveOpacity, false)
+ compare(initialized.sliceFrameColor, "#ff0000")
+ compare(initialized.sliceFrameGaps, Qt.vector3d(2.0, 2.0, 2.0))
+ compare(initialized.sliceFrameThicknesses, Qt.vector3d(2.0, 2.0, 2.0))
+ compare(initialized.sliceFrameWidths, Qt.vector3d(2.0, 2.0, 2.0))
+ compare(initialized.sliceIndexX, 0)
+ compare(initialized.sliceIndexY, 0)
+ compare(initialized.sliceIndexZ, 0)
+ compare(initialized.useHighDefShader, false)
+
+ compare(initialized.position, Qt.vector3d(1.0, 0.5, 1.0))
+ compare(initialized.positionAbsolute, true)
+ compare(initialized.rotation, Qt.quaternion(1, 0.5, 0, 0))
+ compare(initialized.scaling, Qt.vector3d(0.2, 0.2, 0.2))
+ compare(initialized.scalingAbsolute, false)
+ compare(initialized.shadowCasting, false)
+ compare(initialized.visible, false)
+ }
+ }
+
+ TestCase {
+ name: "Custom3DVolume Change"
+
+ function test_change() {
+ change.alphaMultiplier = 0.1
+ change.drawSliceFrames = true
+ change.drawSlices = true
+ change.preserveOpacity = false
+ change.sliceFrameColor = "red"
+ change.sliceFrameGaps = Qt.vector3d(2.0, 2.0, 2.0)
+ change.sliceFrameThicknesses = Qt.vector3d(2.0, 2.0, 2.0)
+ change.sliceFrameWidths = Qt.vector3d(2.0, 2.0, 2.0)
+ change.sliceIndexX = 0
+ change.sliceIndexY = 0
+ change.sliceIndexZ = 0
+ change.useHighDefShader = false
+
+ change.position = Qt.vector3d(1.0, 0.5, 1.0)
+ change.positionAbsolute = true
+ change.rotation = Qt.quaternion(1, 0.5, 0, 0)
+ change.scaling = Qt.vector3d(0.2, 0.2, 0.2)
+ change.scalingAbsolute = false
+ change.shadowCasting = false
+ change.visible = false
+
+ compare(change.alphaMultiplier, 0.1)
+ compare(change.drawSliceFrames, true)
+ compare(change.drawSlices, true)
+ compare(change.preserveOpacity, false)
+ compare(change.sliceFrameColor, "#ff0000")
+ compare(change.sliceFrameGaps, Qt.vector3d(2.0, 2.0, 2.0))
+ compare(change.sliceFrameThicknesses, Qt.vector3d(2.0, 2.0, 2.0))
+ compare(change.sliceFrameWidths, Qt.vector3d(2.0, 2.0, 2.0))
+ compare(change.sliceIndexX, 0)
+ compare(change.sliceIndexY, 0)
+ compare(change.sliceIndexZ, 0)
+ compare(change.useHighDefShader, false)
+
+ compare(change.position, Qt.vector3d(1.0, 0.5, 1.0))
+ compare(change.positionAbsolute, true)
+ compare(change.rotation, Qt.quaternion(1, 0.5, 0, 0))
+ compare(change.scaling, Qt.vector3d(0.2, 0.2, 0.2))
+ compare(change.scalingAbsolute, false)
+ compare(change.shadowCasting, false)
+ compare(change.visible, false)
+ }
+ }
+
+ TestCase {
+ name: "Custom3DVolume Invalid"
+
+ function test_invalid() {
+ invalid.alphaMultiplier = -1.0
+ compare(invalid.alphaMultiplier, 1.0)
+ invalid.sliceFrameGaps = Qt.vector3d(-0.1, -0.1, -0.1)
+ compare(invalid.sliceFrameGaps, Qt.vector3d(0.01, 0.01, 0.01))
+ invalid.sliceFrameThicknesses = Qt.vector3d(-0.1, -0.1, -0.1)
+ compare(invalid.sliceFrameThicknesses, Qt.vector3d(0.01, 0.01, 0.01))
+ invalid.sliceFrameWidths = Qt.vector3d(-0.1, -0.1, -0.1)
+ compare(invalid.sliceFrameWidths, Qt.vector3d(0.01, 0.01, 0.01))
+ }
+ }
+}
diff --git a/tests/auto/qmltest/customitem.obj b/tests/auto/qmltest/customitem.obj
new file mode 100644
index 0000000..108cf7a
--- /dev/null
+++ b/tests/auto/qmltest/customitem.obj
@@ -0,0 +1,54 @@
+# Blender v2.66 (sub 0) OBJ File: 'cube_filled.blend'
+# www.blender.org
+o Cube
+v -1.000000 -1.000000 1.000000
+v -1.000000 -1.000000 -1.000000
+v 1.000000 -1.000000 -1.000000
+v 1.000000 -1.000000 1.000000
+v -1.000000 1.000000 1.000000
+v -1.000000 1.000000 -1.000000
+v 1.000000 1.000000 -1.000000
+v 1.000000 1.000000 1.000000
+vt 0.666667 0.332314
+vt 0.334353 0.333333
+vt 0.665647 0.000000
+vt 0.001020 0.333333
+vt 0.000000 0.001020
+vt 0.333333 0.332314
+vt 0.333333 0.665647
+vt 0.001019 0.666667
+vt 0.000000 0.334353
+vt 0.334353 0.666667
+vt 0.333333 0.334353
+vt 0.665647 0.333333
+vt 0.333333 0.667686
+vt 0.665647 0.666667
+vt 0.666667 0.998980
+vt 0.667686 0.333333
+vt 0.666667 0.001019
+vt 0.998980 0.000000
+vt 0.333333 0.001019
+vt 0.332314 0.000000
+vt 0.332314 0.333333
+vt 0.666667 0.665647
+vt 0.334353 1.000000
+vt 1.000000 0.332314
+vn -1.000000 0.000000 0.000000
+vn 0.000000 0.000000 -1.000000
+vn 1.000000 -0.000000 0.000000
+vn 0.000000 0.000000 1.000000
+vn 0.000000 1.000000 0.000000
+vn -0.000000 -1.000000 -0.000000
+s off
+f 5/1/1 6/2/1 1/3/1
+f 6/4/2 7/5/2 2/6/2
+f 7/7/3 8/8/3 4/9/3
+f 8/10/4 5/11/4 1/12/4
+f 8/13/5 7/14/5 6/15/5
+f 2/16/6 3/17/6 4/18/6
+f 6/2/1 2/19/1 1/3/1
+f 7/5/2 3/20/2 2/6/2
+f 3/21/3 7/7/3 4/9/3
+f 4/22/4 8/10/4 1/12/4
+f 5/23/5 8/13/5 6/15/5
+f 1/24/6 2/16/6 4/18/6
diff --git a/tests/auto/qmltest/customtexture.jpg b/tests/auto/qmltest/customtexture.jpg
new file mode 100644
index 0000000..2580f5b
--- /dev/null
+++ b/tests/auto/qmltest/customtexture.jpg
Binary files differ
diff --git a/tests/auto/qmltest/input3d/tst_input.qml b/tests/auto/qmltest/input3d/tst_input.qml
new file mode 100644
index 0000000..9f9e906
--- /dev/null
+++ b/tests/auto/qmltest/input3d/tst_input.qml
@@ -0,0 +1,68 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick 2.0
+import QtGraphs
+import QtTest 1.0
+
+Item {
+ id: top
+ width: 150
+ height: 150
+
+ InputHandler3D {
+ id: initial
+ }
+
+ InputHandler3D {
+ id: initialized
+ rotationEnabled: false
+ selectionEnabled: false
+ zoomAtTargetEnabled: false
+ zoomEnabled: false
+ }
+
+ InputHandler3D {
+ id: change
+ }
+
+ TestCase {
+ name: "InputHandler3D Initial"
+
+ function test_initial() {
+ compare(initial.rotationEnabled, true)
+ compare(initial.selectionEnabled, true)
+ compare(initial.zoomAtTargetEnabled, true)
+ compare(initial.zoomEnabled, true)
+ }
+ }
+
+ TestCase {
+ name: "InputHandler3D Initialized"
+
+ function test_initialized() {
+ compare(initialized.rotationEnabled, false)
+ compare(initialized.selectionEnabled, false)
+ compare(initialized.zoomAtTargetEnabled, false)
+ compare(initialized.zoomEnabled, false)
+ }
+ }
+
+ TestCase {
+ name: "InputHandler3D Change"
+
+ function test_change() {
+ change.rotationEnabled = false
+ change.selectionEnabled = false
+ change.zoomAtTargetEnabled = false
+ change.zoomEnabled = false
+
+ compare(change.rotationEnabled, false)
+ compare(change.selectionEnabled, false)
+ compare(change.zoomAtTargetEnabled, false)
+ compare(change.zoomEnabled, false)
+ }
+
+ // TODO: QTRD-3380 (mouse events)
+ }
+}
diff --git a/tests/auto/qmltest/input3d/tst_touch.qml b/tests/auto/qmltest/input3d/tst_touch.qml
new file mode 100644
index 0000000..afc88f1
--- /dev/null
+++ b/tests/auto/qmltest/input3d/tst_touch.qml
@@ -0,0 +1,68 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick 2.0
+import QtGraphs
+import QtTest 1.0
+
+Item {
+ id: top
+ width: 150
+ height: 150
+
+ TouchInputHandler3D {
+ id: initial
+ }
+
+ TouchInputHandler3D {
+ id: initialized
+ rotationEnabled: false
+ selectionEnabled: false
+ zoomAtTargetEnabled: false
+ zoomEnabled: false
+ }
+
+ TouchInputHandler3D {
+ id: change
+ }
+
+ TestCase {
+ name: "TouchInputHandler3D Initial"
+
+ function test_initial() {
+ compare(initial.rotationEnabled, true)
+ compare(initial.selectionEnabled, true)
+ compare(initial.zoomAtTargetEnabled, true)
+ compare(initial.zoomEnabled, true)
+ }
+ }
+
+ TestCase {
+ name: "TouchInputHandler3D Initialized"
+
+ function test_initialized() {
+ compare(initialized.rotationEnabled, false)
+ compare(initialized.selectionEnabled, false)
+ compare(initialized.zoomAtTargetEnabled, false)
+ compare(initialized.zoomEnabled, false)
+ }
+ }
+
+ TestCase {
+ name: "TouchInputHandler3D Change"
+
+ function test_change() {
+ change.rotationEnabled = false
+ change.selectionEnabled = false
+ change.zoomAtTargetEnabled = false
+ change.zoomEnabled = false
+
+ compare(change.rotationEnabled, false)
+ compare(change.selectionEnabled, false)
+ compare(change.zoomAtTargetEnabled, false)
+ compare(change.zoomEnabled, false)
+
+ // TODO: QTRD-3380 (mouse events)
+ }
+ }
+}
diff --git a/tests/auto/qmltest/scatter3d/tst_basic.qml b/tests/auto/qmltest/scatter3d/tst_basic.qml
new file mode 100644
index 0000000..110728c
--- /dev/null
+++ b/tests/auto/qmltest/scatter3d/tst_basic.qml
@@ -0,0 +1,237 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick 2.0
+import QtGraphs
+import QtTest 1.0
+
+Item {
+ id: top
+ height: 150
+ width: 150
+
+ property var empty: null
+ property var basic: null
+ property var common: null
+ property var common_init: null
+
+ function constructEmpty() {
+ empty = Qt.createQmlObject("
+ import QtQuick 2.2
+ import QtGraphs
+ Scatter3D {
+ }", top)
+ }
+
+ function constructBasic() {
+ basic = Qt.createQmlObject("
+ import QtQuick 2.2
+ import QtGraphs
+ Scatter3D {
+ anchors.fill: parent
+ }", top)
+ basic.anchors.fill = top
+ }
+
+ function constructCommon() {
+ common = Qt.createQmlObject("
+ import QtQuick 2.2
+ import QtGraphs
+ Scatter3D {
+ anchors.fill: parent
+ }", top)
+ common.anchors.fill = top
+ }
+
+ function constructCommonInit() {
+ common_init = Qt.createQmlObject("
+ import QtQuick 2.2
+ import QtGraphs
+ Scatter3D {
+ anchors.fill: parent
+ selectionMode: AbstractGraph3D.SelectionNone
+ shadowQuality: AbstractGraph3D.ShadowQualityLow
+ msaaSamples: 2
+ theme: Theme3D { }
+ renderingMode: AbstractGraph3D.RenderIndirect
+ measureFps: true
+ orthoProjection: false
+ aspectRatio: 3.0
+ optimizationHints: AbstractGraph3D.OptimizationStatic
+ polar: false
+ radialLabelOffset: 2
+ horizontalAspectRatio: 0.2
+ reflection: true
+ reflectivity: 0.1
+ locale: Qt.locale(\"UK\")
+ margin: 0.2
+ }", top)
+ common_init.anchors.fill = top
+ }
+
+// TODO: Needs either redoing, or fixing the code; see QTBUG-110000 & QTBUG-110001
+// TestCase {
+// name: "Scatter3D Empty"
+// when: windowShown
+
+// function test_empty() {
+// constructEmpty()
+// compare(empty.width, 0, "width")
+// compare(empty.height, 0, "height")
+// compare(empty.seriesList.length, 0, "seriesList")
+// compare(empty.selectedSeries, null, "selectedSeries")
+// compare(empty.axisX.orientation, AbstractAxis3D.AxisOrientationX)
+// compare(empty.axisZ.orientation, AbstractAxis3D.AxisOrientationZ)
+// compare(empty.axisY.orientation, AbstractAxis3D.AxisOrientationY)
+// compare(empty.axisX.type, AbstractAxis3D.AxisTypeValue)
+// compare(empty.axisZ.type, AbstractAxis3D.AxisTypeValue)
+// compare(empty.axisY.type, AbstractAxis3D.AxisTypeValue)
+// waitForRendering(top)
+// empty.destroy()
+// waitForRendering(top)
+// }
+// }
+
+// TODO: Needs either redoing, or fixing the code; see QTBUG-110000 & QTBUG-110001
+// TestCase {
+// name: "Scatter3D Basic"
+// when: windowShown
+
+// function test_basic() {
+// constructBasic()
+// compare(basic.width, 150, "width")
+// compare(basic.height, 150, "height")
+// waitForRendering(top)
+// basic.destroy()
+// waitForRendering(top)
+// }
+// }
+
+// TODO: Needs either redoing, or fixing the code; see QTBUG-110000 & QTBUG-110001
+// TestCase {
+// name: "Scatter3D Common"
+// when: windowShown
+
+// function test_1_common() {
+// constructCommon()
+// compare(common.selectionMode, AbstractGraph3D.SelectionItem, "selectionMode")
+// compare(common.shadowQuality, AbstractGraph3D.ShadowQualityMedium, "shadowQuality")
+// if (common.shadowsSupported === true)
+// compare(common.msaaSamples, 4, "msaaSamples")
+// else
+// compare(common.msaaSamples, 0, "msaaSamples")
+// compare(common.theme.type, Theme3D.ThemeQt, "theme")
+// compare(common.renderingMode, AbstractGraph3D.RenderIndirect, "renderingMode")
+// compare(common.measureFps, false, "measureFps")
+// compare(common.customItemList.length, 0, "customItemList")
+// compare(common.orthoProjection, false, "orthoProjection")
+// compare(common.selectedElement, AbstractGraph3D.ElementNone, "selectedElement")
+// compare(common.aspectRatio, 2.0, "aspectRatio")
+// compare(common.optimizationHints, AbstractGraph3D.OptimizationDefault, "optimizationHints")
+// compare(common.polar, false, "polar")
+// compare(common.radialLabelOffset, 1, "radialLabelOffset")
+// compare(common.horizontalAspectRatio, 0, "horizontalAspectRatio")
+// compare(common.reflection, false, "reflection")
+// compare(common.reflectivity, 0.5, "reflectivity")
+// compare(common.locale, Qt.locale("C"), "locale")
+// compare(common.queriedGraphPosition, Qt.vector3d(0, 0, 0), "queriedGraphPosition")
+// compare(common.margin, -1, "margin")
+// waitForRendering(top)
+// }
+
+// function test_2_change_common() {
+// common.selectionMode = AbstractGraph3D.SelectionNone
+// common.shadowQuality = AbstractGraph3D.ShadowQualitySoftHigh
+// compare(common.shadowQuality, AbstractGraph3D.ShadowQualitySoftHigh, "shadowQuality")
+// common.msaaSamples = 8
+// if (common.shadowsSupported === true)
+// compare(common.msaaSamples, 8, "msaaSamples")
+// else
+// compare(common.msaaSamples, 0, "msaaSamples")
+// common.theme.type = Theme3D.ThemeRetro
+// common.renderingMode = AbstractGraph3D.RenderDirectToBackground_NoClear
+// common.measureFps = true
+// common.orthoProjection = true
+// common.aspectRatio = 1.0
+// common.optimizationHints = AbstractGraph3D.OptimizationStatic
+// common.polar = true
+// common.radialLabelOffset = 2
+// common.horizontalAspectRatio = 1
+// common.reflection = true
+// common.reflectivity = 1.0
+// common.locale = Qt.locale("FI")
+// common.margin = 1.0
+// compare(common.selectionMode, AbstractGraph3D.SelectionNone, "selectionMode")
+// compare(common.shadowQuality, AbstractGraph3D.ShadowQualityNone, "shadowQuality") // Ortho disables shadows
+// compare(common.msaaSamples, 0, "msaaSamples") // Rendering mode changes this to zero
+// compare(common.theme.type, Theme3D.ThemeRetro, "theme")
+// compare(common.renderingMode, AbstractGraph3D.RenderDirectToBackground_NoClear, "renderingMode")
+// compare(common.measureFps, true, "measureFps")
+// compare(common.orthoProjection, true, "orthoProjection")
+// compare(common.aspectRatio, 1.0, "aspectRatio")
+// compare(common.optimizationHints, AbstractGraph3D.OptimizationStatic, "optimizationHints")
+// compare(common.polar, true, "polar")
+// compare(common.radialLabelOffset, 2, "radialLabelOffset")
+// compare(common.horizontalAspectRatio, 1, "horizontalAspectRatio")
+// compare(common.reflection, true, "reflection")
+// compare(common.reflectivity, 1.0, "reflectivity")
+// compare(common.locale, Qt.locale("FI"), "locale")
+// compare(common.margin, 1.0, "margin")
+// waitForRendering(top)
+// }
+
+// function test_3_change_invalid_common() {
+// common.selectionMode = AbstractGraph3D.SelectionRow | AbstractGraph3D.SelectionColumn | AbstractGraph3D.SelectionSlice
+// common.theme.type = -2
+// common.renderingMode = -1
+// common.measureFps = false
+// common.orthoProjection = false
+// common.aspectRatio = -1.0
+// common.polar = false
+// common.horizontalAspectRatio = -2
+// common.reflection = false
+// common.reflectivity = -1.0
+// compare(common.selectionMode, AbstractGraph3D.SelectionNone, "selectionMode")
+// compare(common.theme.type, -2/*Theme3D.ThemeRetro*/, "theme") // TODO: Fix once QTRD-3367 is done
+// compare(common.renderingMode, -1/*AbstractGraph3D.RenderDirectToBackground_NoClear*/, "renderingMode") // TODO: Fix once QTRD-3367 is done
+// compare(common.aspectRatio, -1.0/*1.0*/, "aspectRatio") // TODO: Fix once QTRD-3367 is done
+// compare(common.horizontalAspectRatio, -2/*1*/, "horizontalAspectRatio") // TODO: Fix once QTRD-3367 is done
+// compare(common.reflectivity, -1.0/*1.0*/, "reflectivity") // TODO: Fix once QTRD-3367 is done
+
+// waitForRendering(top)
+// common.destroy()
+// waitForRendering(top)
+// }
+
+// function test_4_common_initialized() {
+// constructCommonInit()
+
+// compare(common_init.selectionMode, AbstractGraph3D.SelectionNone, "selectionMode")
+// if (common_init.shadowsSupported === true) {
+// tryCompare(common_init, "shadowQuality", AbstractGraph3D.ShadowQualityLow)
+// compare(common_init.msaaSamples, 2, "msaaSamples")
+// } else {
+// tryCompare(common_init, "shadowQuality", AbstractGraph3D.ShadowQualityNone)
+// compare(common_init.msaaSamples, 0, "msaaSamples")
+// }
+// compare(common_init.theme.type, Theme3D.ThemeUserDefined, "theme")
+// compare(common_init.renderingMode, AbstractGraph3D.RenderIndirect, "renderingMode")
+// compare(common_init.measureFps, true, "measureFps")
+// compare(common_init.customItemList.length, 0, "customItemList")
+// compare(common_init.orthoProjection, false, "orthoProjection")
+// compare(common_init.aspectRatio, 3.0, "aspectRatio")
+// compare(common_init.optimizationHints, AbstractGraph3D.OptimizationStatic, "optimizationHints")
+// compare(common_init.polar, false, "polar")
+// compare(common_init.radialLabelOffset, 2, "radialLabelOffset")
+// compare(common_init.horizontalAspectRatio, 0.2, "horizontalAspectRatio")
+// compare(common_init.reflection, true, "reflection")
+// compare(common_init.reflectivity, 0.1, "reflectivity")
+// compare(common_init.locale, Qt.locale("UK"), "locale")
+// compare(common_init.margin, 0.2, "margin")
+
+// waitForRendering(top)
+// common_init.destroy();
+// waitForRendering(top)
+// }
+// }
+}
diff --git a/tests/auto/qmltest/scatter3d/tst_proxy.qml b/tests/auto/qmltest/scatter3d/tst_proxy.qml
new file mode 100644
index 0000000..12de6ed
--- /dev/null
+++ b/tests/auto/qmltest/scatter3d/tst_proxy.qml
@@ -0,0 +1,119 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick 2.0
+import QtGraphs
+import QtTest 1.0
+
+Item {
+ id: top
+ height: 150
+ width: 150
+
+ ItemModelScatterDataProxy {
+ id: initial
+ }
+
+ ItemModelScatterDataProxy {
+ id: initialized
+
+ itemModel: ListModel { objectName: "model1" }
+ rotationRole: "rot"
+ rotationRolePattern: /-/
+ rotationRoleReplace: "\\1"
+ xPosRole: "x"
+ xPosRolePattern: /^.*-(\d\d)$/
+ xPosRoleReplace: "\\1"
+ yPosRole: "y"
+ yPosRolePattern: /^(\d\d\d\d).*$/
+ yPosRoleReplace: "\\1"
+ zPosRole: "z"
+ zPosRolePattern: /-/
+ zPosRoleReplace: "\\1"
+ }
+
+ ItemModelScatterDataProxy {
+ id: change
+ }
+
+ TestCase {
+ name: "ItemModelScatterDataProxy Initial"
+
+ function test_initial() {
+ verify(!initial.itemModel)
+ compare(initial.rotationRole, "")
+ verify(initial.rotationRolePattern)
+ compare(initial.rotationRoleReplace, "")
+ compare(initial.xPosRole, "")
+ verify(initial.xPosRolePattern)
+ compare(initial.xPosRoleReplace, "")
+ compare(initial.yPosRole, "")
+ verify(initial.yPosRolePattern)
+ compare(initial.yPosRoleReplace, "")
+ compare(initial.zPosRole, "")
+ verify(initial.zPosRolePattern)
+ compare(initial.zPosRoleReplace, "")
+
+ compare(initial.itemCount, 0)
+ verify(!initial.series)
+
+ compare(initial.type, AbstractDataProxy.DataTypeScatter)
+ }
+ }
+
+ TestCase {
+ name: "ItemModelScatterDataProxy Initialized"
+
+ function test_initialized() {
+ compare(initialized.itemModel.objectName, "model1")
+ compare(initialized.rotationRole, "rot")
+ compare(initialized.rotationRolePattern, /-/)
+ compare(initialized.rotationRoleReplace, "\\1")
+ compare(initialized.xPosRole, "x")
+ compare(initialized.xPosRolePattern, /^.*-(\d\d)$/)
+ compare(initialized.xPosRoleReplace, "\\1")
+ compare(initialized.yPosRole, "y")
+ compare(initialized.yPosRolePattern, /^(\d\d\d\d).*$/)
+ compare(initialized.yPosRoleReplace, "\\1")
+ compare(initialized.zPosRole, "z")
+ compare(initialized.zPosRolePattern, /-/)
+ compare(initialized.zPosRoleReplace, "\\1")
+ }
+ }
+
+ TestCase {
+ name: "ItemModelScatterDataProxy Change"
+
+ ListModel { id: model1; objectName: "model1" }
+
+ function test_change() {
+ change.itemModel = model1
+ change.rotationRole = "rot"
+ change.rotationRolePattern = /-/
+ change.rotationRoleReplace = "\\1"
+ change.xPosRole = "x"
+ change.xPosRolePattern = /^.*-(\d\d)$/
+ change.xPosRoleReplace = "\\1"
+ change.yPosRole = "y"
+ change.yPosRolePattern = /^(\d\d\d\d).*$/
+ change.yPosRoleReplace = "\\1"
+ change.zPosRole = "z"
+ change.zPosRolePattern = /-/
+ change.zPosRoleReplace = "\\1"
+
+ compare(change.itemModel.objectName, "model1")
+ compare(change.rotationRole, "rot")
+ compare(change.rotationRolePattern, /-/)
+ compare(change.rotationRoleReplace, "\\1")
+ compare(change.xPosRole, "x")
+ compare(change.xPosRolePattern, /^.*-(\d\d)$/)
+ compare(change.xPosRoleReplace, "\\1")
+ compare(change.yPosRole, "y")
+ compare(change.yPosRolePattern, /^(\d\d\d\d).*$/)
+ compare(change.yPosRoleReplace, "\\1")
+ compare(change.zPosRole, "z")
+ compare(change.zPosRolePattern, /-/)
+ compare(change.zPosRoleReplace, "\\1")
+ }
+ }
+}
diff --git a/tests/auto/qmltest/scatter3d/tst_scatter.qml b/tests/auto/qmltest/scatter3d/tst_scatter.qml
new file mode 100644
index 0000000..15b3021
--- /dev/null
+++ b/tests/auto/qmltest/scatter3d/tst_scatter.qml
@@ -0,0 +1,54 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick 2.0
+import QtGraphs
+import QtTest 1.0
+
+Item {
+ id: top
+ height: 150
+ width: 150
+
+// TODO: Needs either redoing, or fixing the code; see QTBUG-110000 & QTBUG-110001
+// Scatter3D {
+// id: series
+// anchors.fill: parent
+// }
+
+// TODO: Needs either redoing, or fixing the code; see QTBUG-110000 & QTBUG-110001
+// TestCase {
+// name: "Scatter3D Series"
+
+// Scatter3DSeries { id: series1 }
+// Scatter3DSeries { id: series2 }
+
+// TODO: Needs either redoing, or fixing the code; see QTBUG-110000 & QTBUG-110001
+// function test_1_add_series() {
+// series.seriesList = [series1, series2]
+// compare(series.seriesList.length, 2)
+// }
+
+// function test_2_remove_series() {
+// series.seriesList = [series1]
+// compare(series.seriesList.length, 1)
+// }
+
+// function test_3_remove_series() {
+// series.seriesList = []
+// compare(series.seriesList.length, 0)
+// }
+
+// function test_4_selected_series() {
+// series.seriesList = [series1, series2]
+// series.seriesList[0].selectedItem = 0
+// compare(series.selectedSeries, series1)
+// }
+
+// function test_5_has_series() {
+// series.seriesList = [series1]
+// compare(series.hasSeries(series1), true)
+// compare(series.hasSeries(series2), false)
+// }
+// }
+}
diff --git a/tests/auto/qmltest/scatter3d/tst_scatterseries.qml b/tests/auto/qmltest/scatter3d/tst_scatterseries.qml
new file mode 100644
index 0000000..71ee8a4
--- /dev/null
+++ b/tests/auto/qmltest/scatter3d/tst_scatterseries.qml
@@ -0,0 +1,218 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick 2.0
+import QtGraphs
+import QtTest 1.0
+
+Item {
+ id: top
+ height: 150
+ width: 150
+
+ Scatter3DSeries {
+ id: initial
+ }
+
+ ColorGradient {
+ id: gradient1;
+ stops: [
+ ColorGradientStop { color: "red"; position: 0 },
+ ColorGradientStop { color: "blue"; position: 1 }
+ ]
+ }
+
+ ColorGradient {
+ id: gradient2;
+ stops: [
+ ColorGradientStop { color: "green"; position: 0 },
+ ColorGradientStop { color: "red"; position: 1 }
+ ]
+ }
+
+ ColorGradient {
+ id: gradient3;
+ stops: [
+ ColorGradientStop { color: "gray"; position: 0 },
+ ColorGradientStop { color: "darkgray"; position: 1 }
+ ]
+ }
+
+ Scatter3DSeries {
+ id: initialized
+ dataProxy: ItemModelScatterDataProxy {
+ itemModel: ListModel {
+ ListElement{ xPos: "2.754"; yPos: "1.455"; zPos: "3.362"; }
+ ListElement{ xPos: "3.164"; yPos: "2.022"; zPos: "4.348"; }
+ }
+ xPosRole: "xPos"
+ yPosRole: "yPos"
+ zPosRole: "zPos"
+ }
+ itemSize: 0.5
+ selectedItem: 0
+
+ baseColor: "blue"
+ baseGradient: gradient1
+ colorStyle: Theme3D.ColorStyleObjectGradient
+ itemLabelFormat: "%f"
+ itemLabelVisible: false
+ mesh: Abstract3DSeries.MeshMinimal
+ meshRotation: Qt.quaternion(1, 1, 1, 1)
+ meshSmooth: true
+ multiHighlightColor: "green"
+ multiHighlightGradient: gradient2
+ name: "series1"
+ singleHighlightColor: "red"
+ singleHighlightGradient: gradient3
+ userDefinedMesh: ":/customitem.obj"
+ visible: false
+ }
+
+ ItemModelScatterDataProxy {
+ id: proxy1
+ itemModel: ListModel {
+ ListElement{ xPos: "2.754"; yPos: "1.455"; zPos: "3.362"; }
+ ListElement{ xPos: "3.164"; yPos: "2.022"; zPos: "4.348"; }
+ ListElement{ xPos: "4.564"; yPos: "1.865"; zPos: "1.346"; }
+ }
+ xPosRole: "xPos"
+ yPosRole: "yPos"
+ zPosRole: "zPos"
+ }
+
+ Scatter3DSeries {
+ id: change
+ }
+
+ Scatter3DSeries {
+ id: invalid
+ }
+
+ TestCase {
+ name: "Scatter3DSeries Initial"
+
+ function test_1_initial() {
+ compare(initial.dataProxy.itemCount, 0)
+ compare(initial.invalidSelectionIndex, -1)
+ compare(initial.itemSize, 0.0)
+ compare(initial.selectedItem, -1)
+ }
+
+ function test_2_initial_common() {
+ // Common properties
+ compare(initial.baseColor, "#000000")
+ compare(initial.baseGradient, null)
+ compare(initial.colorStyle, Theme3D.ColorStyleUniform)
+ compare(initial.itemLabel, "")
+ compare(initial.itemLabelFormat, "@xLabel, @yLabel, @zLabel")
+ compare(initial.itemLabelVisible, true)
+ compare(initial.mesh, Abstract3DSeries.MeshSphere)
+ compare(initial.meshRotation, Qt.quaternion(1, 0, 0, 0))
+ compare(initial.meshSmooth, false)
+ compare(initial.multiHighlightColor, "#000000")
+ compare(initial.multiHighlightGradient, null)
+ compare(initial.name, "")
+ compare(initial.singleHighlightColor, "#000000")
+ compare(initial.singleHighlightGradient, null)
+ compare(initial.type, Abstract3DSeries.SeriesTypeScatter)
+ compare(initial.userDefinedMesh, "")
+ compare(initial.visible, true)
+ }
+ }
+
+ TestCase {
+ name: "Scatter3DSeries Initialized"
+
+ function test_1_initialized() {
+ compare(initialized.dataProxy.itemCount, 2)
+ compare(initialized.itemSize, 0.5)
+ compare(initialized.selectedItem, 0)
+ }
+
+ function test_2_initialized_common() {
+ // Common properties
+ compare(initialized.baseColor, "#0000ff")
+ compare(initialized.baseGradient, gradient1)
+ compare(initialized.colorStyle, Theme3D.ColorStyleObjectGradient)
+ compare(initialized.itemLabelFormat, "%f")
+ compare(initialized.itemLabelVisible, false)
+ compare(initialized.mesh, Abstract3DSeries.MeshMinimal)
+ compare(initialized.meshRotation, Qt.quaternion(1, 1, 1, 1))
+ compare(initialized.meshSmooth, true)
+ compare(initialized.multiHighlightColor, "#008000")
+ compare(initialized.multiHighlightGradient, gradient2)
+ compare(initialized.name, "series1")
+ compare(initialized.singleHighlightColor, "#ff0000")
+ compare(initialized.singleHighlightGradient, gradient3)
+ compare(initialized.userDefinedMesh, ":/customitem.obj")
+ compare(initialized.visible, false)
+ }
+ }
+
+ TestCase {
+ name: "Scatter3DSeries Change"
+
+ function test_1_change() {
+ change.dataProxy = proxy1
+ change.itemSize = 0.5
+ change.selectedItem = 0
+ }
+
+ function test_2_test_change() {
+ // This test has a dependency to the previous one due to asynchronous item model resolving
+ compare(change.dataProxy.itemCount, 3)
+ compare(change.itemSize, 0.5)
+ compare(change.selectedItem, 0)
+ }
+
+ function test_3_change_common() {
+ change.baseColor = "blue"
+ change.baseGradient = gradient1
+ change.colorStyle = Theme3D.ColorStyleObjectGradient
+ change.itemLabelFormat = "%f"
+ change.itemLabelVisible = false
+ change.mesh = Abstract3DSeries.MeshMinimal
+ change.meshRotation = Qt.quaternion(1, 1, 1, 1)
+ change.meshSmooth = true
+ change.multiHighlightColor = "green"
+ change.multiHighlightGradient = gradient2
+ change.name = "series1"
+ change.singleHighlightColor = "red"
+ change.singleHighlightGradient = gradient3
+ change.userDefinedMesh = ":/customitem.obj"
+ change.visible = false
+
+ compare(change.baseColor, "#0000ff")
+ compare(change.baseGradient, gradient1)
+ compare(change.colorStyle, Theme3D.ColorStyleObjectGradient)
+ compare(change.itemLabelFormat, "%f")
+ compare(change.itemLabelVisible, false)
+ compare(change.mesh, Abstract3DSeries.MeshMinimal)
+ compare(change.meshRotation, Qt.quaternion(1, 1, 1, 1))
+ compare(change.meshSmooth, true)
+ compare(change.multiHighlightColor, "#008000")
+ compare(change.multiHighlightGradient, gradient2)
+ compare(change.name, "series1")
+ compare(change.singleHighlightColor, "#ff0000")
+ compare(change.singleHighlightGradient, gradient3)
+ compare(change.userDefinedMesh, ":/customitem.obj")
+ compare(change.visible, false)
+ }
+
+ function test_4_change_gradient_stop() {
+ gradient1.stops[0].color = "yellow"
+ compare(change.baseGradient.stops[0].color, "#ffff00")
+ }
+ }
+ TestCase {
+ name: "Scatter3DSeries Invalid"
+
+ function test_invalid() {
+ invalid.itemSize = -1.0
+ compare(invalid.itemSize, 0.0)
+ invalid.itemSize = 1.1
+ compare(invalid.itemSize, 0.0)
+ }
+ }
+}
diff --git a/tests/auto/qmltest/scene3d/tst_camera.qml b/tests/auto/qmltest/scene3d/tst_camera.qml
new file mode 100644
index 0000000..5d43691
--- /dev/null
+++ b/tests/auto/qmltest/scene3d/tst_camera.qml
@@ -0,0 +1,120 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick 2.0
+import QtGraphs
+import QtTest 1.0
+
+Item {
+ id: top
+ width: 150
+ height: 150
+
+ Camera3D {
+ id: initial
+ }
+
+ Camera3D {
+ id: initialized
+ maxZoomLevel: 1000.0
+ minZoomLevel: 100.0
+ target: Qt.vector3d(1.0, -1.0, 1.0)
+ wrapXRotation: false
+ wrapYRotation: true
+ xRotation: 30.0
+ yRotation: 30.0
+ zoomLevel: 500.0
+ }
+
+ Camera3D {
+ id: change
+ }
+
+ Camera3D {
+ id: invalid
+ }
+
+ TestCase {
+ name: "Camera3D Initial"
+
+ function test_initial() {
+ compare(initial.cameraPreset, Camera3D.CameraPresetNone)
+ compare(initial.maxZoomLevel, 500.0)
+ compare(initial.minZoomLevel, 10.0)
+ compare(initial.target, Qt.vector3d(0.0, 0.0, 0.0))
+ compare(initial.wrapXRotation, true)
+ compare(initial.wrapYRotation, false)
+ compare(initial.xRotation, 0.0)
+ compare(initial.yRotation, 0.0)
+ compare(initial.zoomLevel, 100.0)
+ }
+ }
+
+ TestCase {
+ name: "Camera3D Initialized"
+
+ function test_initialized() {
+ compare(initialized.maxZoomLevel, 1000.0)
+ compare(initialized.minZoomLevel, 100.0)
+ compare(initialized.target, Qt.vector3d(1.0, -1.0, 1.0))
+ compare(initialized.wrapXRotation, false)
+ compare(initialized.wrapYRotation, true)
+ compare(initialized.xRotation, 30.0)
+ compare(initialized.yRotation, 30.0)
+ compare(initialized.zoomLevel, 500.0)
+ }
+ }
+
+ TestCase {
+ name: "Camera3D Change"
+
+ function test_1_change() {
+ change.cameraPreset = Camera3D.CameraPresetBehind // Will be overridden by the the following sets
+ change.maxZoomLevel = 1000.0
+ change.minZoomLevel = 100.0
+ change.target = Qt.vector3d(1.0, -1.0, 1.0)
+ change.wrapXRotation = false
+ change.wrapYRotation = true
+ change.xRotation = 30.0
+ change.yRotation = 30.0
+ change.zoomLevel = 500.0
+
+ compare(change.cameraPreset, Camera3D.CameraPresetNone)
+ compare(change.maxZoomLevel, 1000.0)
+ compare(change.minZoomLevel, 100.0)
+ compare(change.target, Qt.vector3d(1.0, -1.0, 1.0))
+ compare(change.wrapXRotation, false)
+ compare(change.wrapYRotation, true)
+ compare(change.xRotation, 30.0)
+ compare(change.yRotation, 30.0)
+ compare(change.zoomLevel, 500.0)
+ }
+
+ function test_2_change_preset() {
+ change.cameraPreset = Camera3D.CameraPresetBehind // Sets target and rotations
+
+ compare(change.cameraPreset, Camera3D.CameraPresetBehind)
+ compare(change.maxZoomLevel, 1000.0)
+ compare(change.minZoomLevel, 100.0)
+ compare(change.target, Qt.vector3d(0.0, 0.0, 0.0))
+ compare(change.wrapXRotation, false)
+ compare(change.wrapYRotation, true)
+ compare(change.xRotation, 180.0)
+ compare(change.yRotation, 22.5)
+ compare(change.zoomLevel, 500.0)
+ }
+ }
+
+ TestCase {
+ name: "Camera3D Invalid"
+
+ function test_invalid() {
+ invalid.target = Qt.vector3d(-1.5, -1.5, -1.5)
+ compare(invalid.target, Qt.vector3d(-1.0, -1.0, -1.0))
+ invalid.target = Qt.vector3d(1.5, 1.5, 1.5)
+ compare(invalid.target, Qt.vector3d(1.0, 1.0, 1.0))
+ invalid.minZoomLevel = 0.1
+ compare(invalid.minZoomLevel, 1.0)
+ }
+ }
+}
diff --git a/tests/auto/qmltest/scene3d/tst_light.qml b/tests/auto/qmltest/scene3d/tst_light.qml
new file mode 100644
index 0000000..5de0cdf
--- /dev/null
+++ b/tests/auto/qmltest/scene3d/tst_light.qml
@@ -0,0 +1,53 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick 2.0
+import QtGraphs
+import QtTest 1.0
+
+Item {
+ id: top
+ height: 150
+ width: 150
+
+ Light3D {
+ id: initial
+ }
+
+ Light3D {
+ id: initialized
+ autoPosition: true
+ }
+
+
+ Light3D {
+ id: change
+ autoPosition: true
+ }
+
+ TestCase {
+ name: "Light3D Initial"
+
+ function test_initial() {
+ compare(initial.autoPosition, false)
+ }
+ }
+
+ TestCase {
+ name: "Light3D Initialized"
+
+ function test_initialized() {
+ compare(initialized.autoPosition, true)
+ }
+ }
+
+ TestCase {
+ name: "Light3D Change"
+
+ function test_change() {
+ compare(change.autoPosition, true)
+ change.autoPosition = false
+ compare(change.autoPosition, false)
+ }
+ }
+}
diff --git a/tests/auto/qmltest/scene3d/tst_scene.qml b/tests/auto/qmltest/scene3d/tst_scene.qml
new file mode 100644
index 0000000..a810c28
--- /dev/null
+++ b/tests/auto/qmltest/scene3d/tst_scene.qml
@@ -0,0 +1,145 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick 2.0
+import QtGraphs
+import QtTest 1.1
+
+Item {
+ id: top
+ height: 150
+ width: 150
+
+ // Scene3D is uncreatable, so it needs to be accessed via a graph
+// TODO: Needs either redoing, or fixing the code; see QTBUG-110000 & QTBUG-110001
+// Bars3D {
+// id: initial
+// }
+
+// Bars3D {
+// id: initialized
+// scene.activeCamera: Camera3D { zoomLevel: 200 }
+// scene.devicePixelRatio: Screen.devicePixelRatio
+// scene.graphPositionQuery: Qt.point(0, 0)
+// scene.primarySubViewport: Qt.rect(0, 0, 50, 50)
+// scene.secondarySubViewport: Qt.rect(50, 50, 100, 100)
+// scene.secondarySubviewOnTop: false
+// scene.selectionQueryPosition: Qt.point(0, 0)
+// scene.slicingActive: true
+// }
+
+// Bars3D {
+// id: change
+// }
+
+// Bars3D {
+// id: invalid
+// }
+
+// TODO: Needs either redoing, or fixing the code; see QTBUG-110000 & QTBUG-110001
+// TestCase {
+// name: "Scene3D Initial"
+
+// function test_initial() {
+// verify(initial.scene.activeCamera)
+// verify(initial.scene.activeLight)
+// compare(initial.scene.devicePixelRatio, Screen.devicePixelRatio)
+// compare(initial.scene.graphPositionQuery, Qt.point(-1, -1))
+// compare(initial.scene.invalidSelectionPoint, Qt.point(-1, -1))
+// compare(initial.scene.primarySubViewport.x, 0)
+// compare(initial.scene.primarySubViewport.y, 0)
+// compare(initial.scene.primarySubViewport.width, 0)
+// compare(initial.scene.primarySubViewport.height, 0)
+// compare(initial.scene.secondarySubViewport.x, 0)
+// compare(initial.scene.secondarySubViewport.y, 0)
+// compare(initial.scene.secondarySubViewport.width, 0)
+// compare(initial.scene.secondarySubViewport.height, 0)
+// compare(initial.scene.secondarySubviewOnTop, true)
+// compare(initial.scene.selectionQueryPosition, Qt.point(-1, -1))
+// compare(initial.scene.slicingActive, false)
+// compare(initial.scene.viewport.x, 0)
+// compare(initial.scene.viewport.y, 0)
+// compare(initial.scene.viewport.width, 0)
+// compare(initial.scene.viewport.height, 0)
+// }
+// }
+
+// TODO: Needs either redoing, or fixing the code; see QTBUG-110000 & QTBUG-110001
+// TestCase {
+// name: "Scene3D Initialized"
+
+// function test_initialized() {
+// compare(initialized.scene.activeCamera.zoomLevel, 200)
+// compare(initialized.scene.devicePixelRatio, Screen.devicePixelRatio)
+// compare(initialized.scene.graphPositionQuery, Qt.point(0, 0))
+// compare(initialized.scene.primarySubViewport.x, 0)
+// compare(initialized.scene.primarySubViewport.y, 0)
+// compare(initialized.scene.primarySubViewport.width, 50)
+// compare(initialized.scene.primarySubViewport.height, 50)
+// compare(initialized.scene.secondarySubViewport.x, 50)
+// compare(initialized.scene.secondarySubViewport.y, 50)
+// compare(initialized.scene.secondarySubViewport.width, 100)
+// compare(initialized.scene.secondarySubViewport.height, 100)
+// compare(initialized.scene.secondarySubviewOnTop, false)
+// compare(initialized.scene.selectionQueryPosition, Qt.point(0, 0))
+// compare(initialized.scene.slicingActive, true)
+// compare(initialized.scene.viewport.x, 0)
+// compare(initialized.scene.viewport.y, 0)
+// compare(initialized.scene.viewport.width, 150)
+// compare(initialized.scene.viewport.height, 150)
+// }
+// }
+
+// TODO: Needs either redoing, or fixing the code; see QTBUG-110000 & QTBUG-110001
+// TestCase {
+// name: "Scene3D Change"
+
+// Camera3D {
+// id: camera1
+// zoomLevel: 200
+// }
+
+// function test_change() {
+// change.scene.activeCamera = camera1
+// change.scene.devicePixelRatio = 2.0
+// change.scene.graphPositionQuery = Qt.point(0, 0)
+// change.scene.primarySubViewport = Qt.rect(0, 0, 50, 50)
+// change.scene.secondarySubViewport = Qt.rect(50, 50, 100, 100)
+// change.scene.secondarySubviewOnTop = false
+// change.scene.selectionQueryPosition = Qt.point(0, 0) // TODO: When doing signal checks, add tests to check that queries return something (asynchronously)
+// change.scene.slicingActive = true
+
+// compare(change.scene.activeCamera.zoomLevel, 200)
+// compare(change.scene.devicePixelRatio, 2.0)
+// compare(change.scene.graphPositionQuery, Qt.point(0, 0))
+// compare(change.scene.primarySubViewport.x, 0)
+// compare(change.scene.primarySubViewport.y, 0)
+// compare(change.scene.primarySubViewport.width, 50)
+// compare(change.scene.primarySubViewport.height, 50)
+// compare(change.scene.secondarySubViewport.x, 50)
+// compare(change.scene.secondarySubViewport.y, 50)
+// compare(change.scene.secondarySubViewport.width, 100)
+// compare(change.scene.secondarySubViewport.height, 100)
+// compare(change.scene.secondarySubviewOnTop, false)
+// compare(change.scene.selectionQueryPosition, Qt.point(0, 0))
+// compare(change.scene.slicingActive, true)
+// compare(change.scene.viewport.x, 0)
+// compare(change.scene.viewport.y, 0)
+// compare(change.scene.viewport.width, 150)
+// compare(change.scene.viewport.height, 150)
+// }
+// }
+
+// TODO: Needs either redoing, or fixing the code; see QTBUG-110000 & QTBUG-110001
+// TestCase {
+// name: "Scene3D Invalid"
+
+// function test_invalid() {
+// invalid.scene.primarySubViewport = Qt.rect(0, 0, -50, -50)
+// compare(invalid.scene.primarySubViewport.x, 0)
+// compare(invalid.scene.primarySubViewport.y, 0)
+// compare(invalid.scene.primarySubViewport.width, 0)
+// compare(invalid.scene.primarySubViewport.height, 0)
+// }
+// }
+}
diff --git a/tests/auto/qmltest/surface3d/tst_basic.qml b/tests/auto/qmltest/surface3d/tst_basic.qml
new file mode 100644
index 0000000..f6ec389
--- /dev/null
+++ b/tests/auto/qmltest/surface3d/tst_basic.qml
@@ -0,0 +1,245 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick 2.0
+import QtGraphs
+import QtTest 1.0
+
+Item {
+ id: top
+ height: 150
+ width: 150
+
+ property var empty: null
+ property var basic: null
+ property var common: null
+ property var common_init: null
+
+ function constructEmpty() {
+ empty = Qt.createQmlObject("
+ import QtQuick 2.2
+ import QtGraphs
+ Surface3D {
+ }", top)
+ }
+
+ function constructBasic() {
+ basic = Qt.createQmlObject("
+ import QtQuick 2.2
+ import QtGraphs
+ Surface3D {
+ anchors.fill: parent
+ flipHorizontalGrid: true
+ }", top)
+ basic.anchors.fill = top
+ }
+
+ function constructCommon() {
+ common = Qt.createQmlObject("
+ import QtQuick 2.2
+ import QtGraphs
+ Surface3D {
+ anchors.fill: parent
+ }", top)
+ common.anchors.fill = top
+ }
+
+ function constructCommonInit() {
+ common_init = Qt.createQmlObject("
+ import QtQuick 2.2
+ import QtGraphs
+ Surface3D {
+ anchors.fill: parent
+ selectionMode: AbstractGraph3D.SelectionNone
+ shadowQuality: AbstractGraph3D.ShadowQualityLow
+ msaaSamples: 2
+ theme: Theme3D { }
+ renderingMode: AbstractGraph3D.RenderIndirect
+ measureFps: true
+ orthoProjection: false
+ aspectRatio: 3.0
+ optimizationHints: AbstractGraph3D.OptimizationStatic
+ polar: false
+ radialLabelOffset: 2
+ horizontalAspectRatio: 0.2
+ reflection: true
+ reflectivity: 0.1
+ locale: Qt.locale(\"UK\")
+ margin: 0.2
+ }", top)
+ common_init.anchors.fill = top
+ }
+
+// TODO: Needs either redoing, or fixing the code; see QTBUG-110000 & QTBUG-110001
+// TestCase {
+// name: "Surface3D Empty"
+// when: windowShown
+
+// function test_empty() {
+// constructEmpty()
+// compare(empty.width, 0, "width")
+// compare(empty.height, 0, "height")
+// compare(empty.seriesList.length, 0, "seriesList")
+// compare(empty.selectedSeries, null, "selectedSeries")
+// compare(empty.flipHorizontalGrid, false, "flipHorizontalGrid")
+// compare(empty.axisX.orientation, AbstractAxis3D.AxisOrientationX)
+// compare(empty.axisZ.orientation, AbstractAxis3D.AxisOrientationZ)
+// compare(empty.axisY.orientation, AbstractAxis3D.AxisOrientationY)
+// compare(empty.axisX.type, AbstractAxis3D.AxisTypeValue)
+// compare(empty.axisZ.type, AbstractAxis3D.AxisTypeValue)
+// compare(empty.axisY.type, AbstractAxis3D.AxisTypeValue)
+// waitForRendering(top)
+// empty.destroy()
+// waitForRendering(top)
+// }
+// }
+
+// TODO: Needs either redoing, or fixing the code; see QTBUG-110000 & QTBUG-110001
+// TestCase {
+// name: "Surface3D Basic"
+// when: windowShown
+
+// function test_1_basic() {
+// constructBasic()
+// compare(basic.width, 150, "width")
+// compare(basic.height, 150, "height")
+// compare(basic.flipHorizontalGrid, true, "flipHorizontalGrid")
+// }
+
+// function test_2_change_basic() {
+// basic.flipHorizontalGrid = false
+// compare(basic.flipHorizontalGrid, false, "flipHorizontalGrid")
+// waitForRendering(top)
+// basic.destroy()
+// waitForRendering(top)
+// }
+// }
+
+// TODO: Needs either redoing, or fixing the code; see QTBUG-110000 & QTBUG-110001
+// TestCase {
+// name: "Surface3D Common"
+// when: windowShown
+
+// function test_1_common() {
+// constructCommon()
+// compare(common.selectionMode, AbstractGraph3D.SelectionItem, "selectionMode")
+// compare(common.shadowQuality, AbstractGraph3D.ShadowQualityMedium, "shadowQuality")
+// if (common.shadowsSupported === true)
+// compare(common.msaaSamples, 4, "msaaSamples")
+// else
+// compare(common.msaaSamples, 0, "msaaSamples")
+// compare(common.theme.type, Theme3D.ThemeQt, "theme")
+// compare(common.renderingMode, AbstractGraph3D.RenderIndirect, "renderingMode")
+// compare(common.measureFps, false, "measureFps")
+// compare(common.customItemList.length, 0, "customItemList")
+// compare(common.orthoProjection, false, "orthoProjection")
+// compare(common.selectedElement, AbstractGraph3D.ElementNone, "selectedElement")
+// compare(common.aspectRatio, 2.0, "aspectRatio")
+// compare(common.optimizationHints, AbstractGraph3D.OptimizationDefault, "optimizationHints")
+// compare(common.polar, false, "polar")
+// compare(common.radialLabelOffset, 1, "radialLabelOffset")
+// compare(common.horizontalAspectRatio, 0, "horizontalAspectRatio")
+// compare(common.reflection, false, "reflection")
+// compare(common.reflectivity, 0.5, "reflectivity")
+// compare(common.locale, Qt.locale("C"), "locale")
+// compare(common.queriedGraphPosition, Qt.vector3d(0, 0, 0), "queriedGraphPosition")
+// compare(common.margin, -1, "margin")
+// waitForRendering(top)
+// }
+
+// function test_2_change_common() {
+// common.selectionMode = AbstractGraph3D.SelectionItem | AbstractGraph3D.SelectionRow | AbstractGraph3D.SelectionSlice
+// common.shadowQuality = AbstractGraph3D.ShadowQualitySoftHigh
+// compare(common.shadowQuality, AbstractGraph3D.ShadowQualitySoftHigh, "shadowQuality")
+// common.msaaSamples = 8
+// if (common.shadowsSupported === true)
+// compare(common.msaaSamples, 8, "msaaSamples")
+// else
+// compare(common.msaaSamples, 0, "msaaSamples")
+// common.theme.type = Theme3D.ThemeRetro
+// common.renderingMode = AbstractGraph3D.RenderDirectToBackground_NoClear
+// common.measureFps = true
+// common.orthoProjection = true
+// common.aspectRatio = 1.0
+// common.optimizationHints = AbstractGraph3D.OptimizationStatic
+// common.polar = true
+// common.radialLabelOffset = 2
+// common.horizontalAspectRatio = 1
+// common.reflection = true
+// common.reflectivity = 1.0
+// common.locale = Qt.locale("FI")
+// common.margin = 1.0
+// compare(common.selectionMode, AbstractGraph3D.SelectionItem | AbstractGraph3D.SelectionRow | AbstractGraph3D.SelectionSlice, "selectionMode")
+// compare(common.shadowQuality, AbstractGraph3D.ShadowQualityNone, "shadowQuality") // Ortho disables shadows
+// compare(common.msaaSamples, 0, "msaaSamples") // Rendering mode changes this to zero
+// compare(common.theme.type, Theme3D.ThemeRetro, "theme")
+// compare(common.renderingMode, AbstractGraph3D.RenderDirectToBackground_NoClear, "renderingMode")
+// compare(common.measureFps, true, "measureFps")
+// compare(common.orthoProjection, true, "orthoProjection")
+// compare(common.aspectRatio, 1.0, "aspectRatio")
+// compare(common.optimizationHints, AbstractGraph3D.OptimizationStatic, "optimizationHints")
+// compare(common.polar, true, "polar")
+// compare(common.radialLabelOffset, 2, "radialLabelOffset")
+// compare(common.horizontalAspectRatio, 1, "horizontalAspectRatio")
+// compare(common.reflection, true, "reflection")
+// compare(common.reflectivity, 1.0, "reflectivity")
+// compare(common.locale, Qt.locale("FI"), "locale")
+// compare(common.margin, 1.0, "margin")
+// waitForRendering(top)
+// }
+
+// function test_3_change_invalid_common() {
+// common.selectionMode = AbstractGraph3D.SelectionRow | AbstractGraph3D.SelectionColumn | AbstractGraph3D.SelectionSlice
+// common.theme.type = -2
+// common.renderingMode = -1
+// common.measureFps = false
+// common.orthoProjection = false
+// common.aspectRatio = -1.0
+// common.polar = false
+// common.horizontalAspectRatio = -2
+// common.reflection = false
+// common.reflectivity = -1.0
+// compare(common.selectionMode, AbstractGraph3D.SelectionItem | AbstractGraph3D.SelectionRow | AbstractGraph3D.SelectionSlice, "selectionMode")
+// compare(common.theme.type, -2/*Theme3D.ThemeRetro*/, "theme") // TODO: Fix once QTRD-3367 is done
+// compare(common.renderingMode, -1/*AbstractGraph3D.RenderDirectToBackground_NoClear*/, "renderingMode") // TODO: Fix once QTRD-3367 is done
+// compare(common.aspectRatio, -1.0/*1.0*/, "aspectRatio") // TODO: Fix once QTRD-3367 is done
+// compare(common.horizontalAspectRatio, -2/*1*/, "horizontalAspectRatio") // TODO: Fix once QTRD-3367 is done
+// compare(common.reflectivity, -1.0/*1.0*/, "reflectivity") // TODO: Fix once QTRD-3367 is done
+
+// waitForRendering(top)
+// common.destroy()
+// waitForRendering(top)
+// }
+
+// function test_4_common_initialized() {
+// constructCommonInit()
+
+// compare(common_init.selectionMode, AbstractGraph3D.SelectionNone, "selectionMode")
+// if (common_init.shadowsSupported === true) {
+// tryCompare(common_init, "shadowQuality", AbstractGraph3D.ShadowQualityLow)
+// compare(common_init.msaaSamples, 2, "msaaSamples")
+// } else {
+// tryCompare(common_init, "shadowQuality", AbstractGraph3D.ShadowQualityNone)
+// compare(common_init.msaaSamples, 0, "msaaSamples")
+// }
+// compare(common_init.theme.type, Theme3D.ThemeUserDefined, "theme")
+// compare(common_init.renderingMode, AbstractGraph3D.RenderIndirect, "renderingMode")
+// compare(common_init.measureFps, true, "measureFps")
+// compare(common_init.customItemList.length, 0, "customItemList")
+// compare(common_init.orthoProjection, false, "orthoProjection")
+// compare(common_init.aspectRatio, 3.0, "aspectRatio")
+// compare(common_init.optimizationHints, AbstractGraph3D.OptimizationStatic, "optimizationHints")
+// compare(common_init.polar, false, "polar")
+// compare(common_init.radialLabelOffset, 2, "radialLabelOffset")
+// compare(common_init.horizontalAspectRatio, 0.2, "horizontalAspectRatio")
+// compare(common_init.reflection, true, "reflection")
+// compare(common_init.reflectivity, 0.1, "reflectivity")
+// compare(common_init.locale, Qt.locale("UK"), "locale")
+// compare(common_init.margin, 0.2, "margin")
+
+// waitForRendering(top)
+// common_init.destroy();
+// waitForRendering(top)
+// }
+// }
+}
diff --git a/tests/auto/qmltest/surface3d/tst_heightproxy.qml b/tests/auto/qmltest/surface3d/tst_heightproxy.qml
new file mode 100644
index 0000000..d0595ee
--- /dev/null
+++ b/tests/auto/qmltest/surface3d/tst_heightproxy.qml
@@ -0,0 +1,101 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick 2.0
+import QtGraphs
+import QtTest 1.0
+
+Item {
+ id: top
+ height: 150
+ width: 150
+
+ HeightMapSurfaceDataProxy {
+ id: initial
+ }
+
+ HeightMapSurfaceDataProxy {
+ id: initialized
+ heightMapFile: ":/customtexture.jpg"
+ maxXValue: 10.0
+ maxZValue: 10.0
+ minXValue: -10.0
+ minZValue: -10.0
+ }
+
+ HeightMapSurfaceDataProxy {
+ id: change
+ }
+
+ HeightMapSurfaceDataProxy {
+ id: invalid
+ }
+
+ TestCase {
+ name: "HeightMapSurfaceDataProxy Initial"
+
+ function test_initial() {
+ compare(initial.heightMapFile, "")
+ compare(initial.maxXValue, 10.0)
+ compare(initial.maxZValue, 10.0)
+ compare(initial.minXValue, 0)
+ compare(initial.minZValue, 0)
+
+ compare(initial.columnCount, 0)
+ compare(initial.rowCount, 0)
+ verify(!initial.series)
+
+ compare(initial.type, AbstractDataProxy.DataTypeSurface)
+ }
+ }
+
+ TestCase {
+ name: "HeightMapSurfaceDataProxy Initialized"
+
+ function test_initialized() {
+ compare(initialized.heightMapFile, ":/customtexture.jpg")
+ compare(initialized.maxXValue, 10.0)
+ compare(initialized.maxZValue, 10.0)
+ compare(initialized.minXValue, -10.0)
+ compare(initialized.minZValue, -10.0)
+
+ compare(initialized.columnCount, 24)
+ compare(initialized.rowCount, 24)
+ }
+ }
+
+ TestCase {
+ name: "HeightMapSurfaceDataProxy Change"
+
+ function test_1_change() {
+ change.heightMapFile = ":/customtexture.jpg"
+ change.maxXValue = 10.0
+ change.maxZValue = 10.0
+ change.minXValue = -10.0
+ change.minZValue = -10.0
+ }
+
+ function test_2_test_change() {
+ // This test has a dependency to the previous one due to asynchronous item model resolving
+ compare(change.heightMapFile, ":/customtexture.jpg")
+ compare(change.maxXValue, 10.0)
+ compare(change.maxZValue, 10.0)
+ compare(change.minXValue, -10.0)
+ compare(change.minZValue, -10.0)
+
+ compare(change.columnCount, 24)
+ compare(change.rowCount, 24)
+ }
+ }
+
+ TestCase {
+ name: "HeightMapSurfaceDataProxy Invalid"
+
+ function test_invalid() {
+ invalid.maxXValue = -10
+ compare(invalid.minXValue, -11)
+ invalid.minZValue = 20
+ compare(invalid.maxZValue, 21)
+ }
+ }
+}
diff --git a/tests/auto/qmltest/surface3d/tst_proxy.qml b/tests/auto/qmltest/surface3d/tst_proxy.qml
new file mode 100644
index 0000000..9c5addd
--- /dev/null
+++ b/tests/auto/qmltest/surface3d/tst_proxy.qml
@@ -0,0 +1,249 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick 2.0
+import QtGraphs
+import QtTest 1.0
+
+Item {
+ id: top
+ height: 150
+ width: 150
+
+ ItemModelSurfaceDataProxy {
+ id: initial
+ }
+
+ ItemModelSurfaceDataProxy {
+ id: initialized
+
+ autoColumnCategories: false
+ autoRowCategories: false
+ columnCategories: ["colcat1", "colcat2"]
+ columnRole: "col"
+ columnRolePattern: /^.*-(\d\d)$/
+ columnRoleReplace: "\\1"
+ itemModel: ListModel { objectName: "model1" }
+ multiMatchBehavior: ItemModelSurfaceDataProxy.MMBAverage
+ rowCategories: ["rowcat1", "rowcat2"]
+ rowRole: "row"
+ rowRolePattern: /^(\d\d\d\d).*$/
+ rowRoleReplace: "\\1"
+ xPosRole: "x"
+ xPosRolePattern: /^.*-(\d\d)$/
+ xPosRoleReplace: "\\1"
+ yPosRole: "y"
+ yPosRolePattern: /^(\d\d\d\d).*$/
+ yPosRoleReplace: "\\1"
+ zPosRole: "z"
+ zPosRolePattern: /-/
+ zPosRoleReplace: "\\1"
+ }
+
+ ItemModelSurfaceDataProxy {
+ id: change
+ }
+
+ TestCase {
+ name: "ItemModelSurfaceDataProxy Initial"
+
+ function test_initial() {
+ compare(initial.autoColumnCategories, true)
+ compare(initial.autoRowCategories, true)
+ compare(initial.columnCategories, [])
+ compare(initial.columnRole, "")
+ verify(initial.columnRolePattern)
+ compare(initial.columnRoleReplace, "")
+ verify(!initial.itemModel)
+ compare(initial.multiMatchBehavior, ItemModelSurfaceDataProxy.MMBLast)
+ compare(initial.rowCategories, [])
+ compare(initial.rowRole, "")
+ verify(initial.rowRolePattern)
+ compare(initial.rowRoleReplace, "")
+ compare(initial.useModelCategories, false)
+ compare(initial.xPosRole, "")
+ verify(initial.xPosRolePattern)
+ compare(initial.xPosRoleReplace, "")
+ compare(initial.yPosRole, "")
+ verify(initial.yPosRolePattern)
+ compare(initial.yPosRoleReplace, "")
+ compare(initial.zPosRole, "")
+ verify(initial.zPosRolePattern)
+ compare(initial.zPosRoleReplace, "")
+
+ compare(initial.columnCount, 0)
+ compare(initial.rowCount, 0)
+ verify(!initial.series)
+
+ compare(initial.type, AbstractDataProxy.DataTypeSurface)
+ }
+ }
+
+ TestCase {
+ name: "ItemModelSurfaceDataProxy Initialized"
+
+ function test_initialized() {
+ compare(initialized.autoColumnCategories, false)
+ compare(initialized.autoRowCategories, false)
+ compare(initialized.columnCategories.length, 2)
+ compare(initialized.columnCategories[0], "colcat1")
+ compare(initialized.columnCategories[1], "colcat2")
+ compare(initialized.columnRole, "col")
+ compare(initialized.columnRolePattern, /^.*-(\d\d)$/)
+ compare(initialized.columnRoleReplace, "\\1")
+ compare(initialized.itemModel.objectName, "model1")
+ compare(initialized.multiMatchBehavior, ItemModelSurfaceDataProxy.MMBAverage)
+ compare(initialized.rowCategories.length, 2)
+ compare(initialized.rowCategories[0], "rowcat1")
+ compare(initialized.rowCategories[1], "rowcat2")
+ compare(initialized.rowRole, "row")
+ compare(initialized.rowRolePattern, /^(\d\d\d\d).*$/)
+ compare(initialized.rowRoleReplace, "\\1")
+ compare(initialized.xPosRole, "x")
+ compare(initialized.xPosRolePattern, /^.*-(\d\d)$/)
+ compare(initialized.xPosRoleReplace, "\\1")
+ compare(initialized.yPosRole, "y")
+ compare(initialized.yPosRolePattern, /^(\d\d\d\d).*$/)
+ compare(initialized.yPosRoleReplace, "\\1")
+ compare(initialized.zPosRole, "z")
+ compare(initialized.zPosRolePattern, /-/)
+ compare(initialized.zPosRoleReplace, "\\1")
+
+ compare(initialized.columnCount, 2)
+ compare(initialized.rowCount, 2)
+ }
+ }
+
+ TestCase {
+ name: "ItemModelSurfaceDataProxy Change"
+
+ ListModel { id: model1; objectName: "model1" }
+
+ function test_1_change() {
+ change.autoColumnCategories = false
+ change.autoRowCategories = false
+ change.columnCategories = ["colcat1", "colcat2"]
+ change.columnRole = "col"
+ change.columnRolePattern = /^.*-(\d\d)$/
+ change.columnRoleReplace = "\\1"
+ change.itemModel = model1
+ change.multiMatchBehavior = ItemModelSurfaceDataProxy.MMBAverage
+ change.rowCategories = ["rowcat1", "rowcat2"]
+ change.rowRole = "row"
+ change.rowRolePattern = /^(\d\d\d\d).*$/
+ change.rowRoleReplace = "\\1"
+ change.useModelCategories = true // Overwrites columnLabels and rowLabels
+ change.xPosRole = "x"
+ change.xPosRolePattern = /^.*-(\d\d)$/
+ change.xPosRoleReplace = "\\1"
+ change.yPosRole = "y"
+ change.yPosRolePattern = /^(\d\d\d\d).*$/
+ change.yPosRoleReplace = "\\1"
+ change.zPosRole = "z"
+ change.zPosRolePattern = /-/
+ change.zPosRoleReplace = "\\1"
+ }
+
+ function test_2_test_change() {
+ // This test has a dependency to the previous one due to asynchronous item model resolving
+ compare(change.autoColumnCategories, false)
+ compare(change.autoRowCategories, false)
+ compare(change.columnCategories.length, 2)
+ compare(change.columnCategories[0], "colcat1")
+ compare(change.columnCategories[1], "colcat2")
+ compare(change.columnRole, "col")
+ compare(change.columnRolePattern, /^.*-(\d\d)$/)
+ compare(change.columnRoleReplace, "\\1")
+ compare(change.itemModel.objectName, "model1")
+ compare(change.multiMatchBehavior, ItemModelSurfaceDataProxy.MMBAverage)
+ compare(change.rowCategories.length, 2)
+ compare(change.rowCategories[0], "rowcat1")
+ compare(change.rowCategories[1], "rowcat2")
+ compare(change.rowRole, "row")
+ compare(change.rowRolePattern, /^(\d\d\d\d).*$/)
+ compare(change.rowRoleReplace, "\\1")
+ compare(change.useModelCategories, true)
+ compare(change.xPosRole, "x")
+ compare(change.xPosRolePattern, /^.*-(\d\d)$/)
+ compare(change.xPosRoleReplace, "\\1")
+ compare(change.yPosRole, "y")
+ compare(change.yPosRolePattern, /^(\d\d\d\d).*$/)
+ compare(change.yPosRoleReplace, "\\1")
+ compare(change.zPosRole, "z")
+ compare(change.zPosRolePattern, /-/)
+ compare(change.zPosRoleReplace, "\\1")
+
+ compare(change.columnCount, 0)
+ compare(change.rowCount, 0)
+ }
+ }
+
+// TODO: Needs either redoing, or fixing the code; see QTBUG-110000 & QTBUG-110001
+// TestCase {
+// name: "ItemModelSurfaceDataProxy MultiMatchBehaviour"
+
+// Surface3D {
+// id: surface1
+// Surface3DSeries {
+// ItemModelSurfaceDataProxy {
+// id: surfaceProxy
+// itemModel: ListModel {
+// ListElement{ coords: "0,0"; data: "5"; }
+// ListElement{ coords: "0,0"; data: "15"; }
+// ListElement{ coords: "0,1"; data: "5"; }
+// ListElement{ coords: "0,1"; data: "15"; }
+// ListElement{ coords: "1,0"; data: "5"; }
+// ListElement{ coords: "1,0"; data: "15"; }
+// ListElement{ coords: "1,1"; data: "0"; }
+// }
+// rowRole: "coords"
+// columnRole: "coords"
+// yPosRole: "data"
+// rowRolePattern: /(\d),\d/
+// columnRolePattern: /(\d),(\d)/
+// rowRoleReplace: "\\1"
+// columnRoleReplace: "\\2"
+// }
+// }
+// }
+
+// function test_0_async_dummy() {
+// }
+
+// function test_1_test_multimatch() {
+// compare(surface1.axisY.max, 15)
+// }
+
+// function test_2_multimatch() {
+// surfaceProxy.multiMatchBehavior = ItemModelSurfaceDataProxy.MMBFirst
+// }
+
+// function test_3_test_multimatch() {
+// compare(surface1.axisY.max, 5)
+// }
+
+// function test_4_multimatch() {
+// surfaceProxy.multiMatchBehavior = ItemModelSurfaceDataProxy.MMBLast
+// }
+
+// function test_5_test_multimatch() {
+// compare(surface1.axisY.max, 15)
+// }
+
+// function test_6_multimatch() {
+// surfaceProxy.multiMatchBehavior = ItemModelSurfaceDataProxy.MMBAverage
+// }
+
+// function test_7_test_multimatch() {
+// compare(surface1.axisY.max, 10)
+// }
+
+// function test_8_multimatch() {
+// surfaceProxy.multiMatchBehavior = ItemModelSurfaceDataProxy.MMBCumulativeY
+// }
+
+// function test_9_test_multimatch() {
+// compare(surface1.axisY.max, 20)
+// }
+// }
+}
diff --git a/tests/auto/qmltest/surface3d/tst_surface.qml b/tests/auto/qmltest/surface3d/tst_surface.qml
new file mode 100644
index 0000000..933f7e6
--- /dev/null
+++ b/tests/auto/qmltest/surface3d/tst_surface.qml
@@ -0,0 +1,53 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick 2.0
+import QtGraphs
+import QtTest 1.0
+
+Item {
+ id: top
+ height: 150
+ width: 150
+
+// TODO: Needs either redoing, or fixing the code; see QTBUG-110000 & QTBUG-110001
+// Surface3D {
+// id: series
+// anchors.fill: parent
+// }
+
+// TODO: Needs either redoing, or fixing the code; see QTBUG-110000 & QTBUG-110001
+// TestCase {
+// name: "Surface3D Series"
+
+// Surface3DSeries { id: series1 }
+// Surface3DSeries { id: series2 }
+
+// function test_1_add_series() {
+// series.seriesList = [series1, series2]
+// compare(series.seriesList.length, 2)
+// }
+
+// function test_2_remove_series() {
+// series.seriesList = [series1]
+// compare(series.seriesList.length, 1)
+// }
+
+// function test_3_remove_series() {
+// series.seriesList = []
+// compare(series.seriesList.length, 0)
+// }
+
+// function test_4_selected_series() {
+// series.seriesList = [series1, series2]
+// series.seriesList[0].selectedPoint = Qt.point(0, 0)
+// compare(series.selectedSeries, series1)
+// }
+
+// function test_5_has_series() {
+// series.seriesList = [series1]
+// compare(series.hasSeries(series1), true)
+// compare(series.hasSeries(series2), false)
+// }
+// }
+}
diff --git a/tests/auto/qmltest/surface3d/tst_surfaceseries.qml b/tests/auto/qmltest/surface3d/tst_surfaceseries.qml
new file mode 100644
index 0000000..effb6f3
--- /dev/null
+++ b/tests/auto/qmltest/surface3d/tst_surfaceseries.qml
@@ -0,0 +1,219 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick 2.0
+import QtGraphs
+import QtTest 1.0
+
+Item {
+ id: top
+ height: 150
+ width: 150
+
+ Surface3DSeries {
+ id: initial
+ }
+
+ ColorGradient {
+ id: gradient1;
+ stops: [
+ ColorGradientStop { color: "red"; position: 0 },
+ ColorGradientStop { color: "blue"; position: 1 }
+ ]
+ }
+
+ ColorGradient {
+ id: gradient2;
+ stops: [
+ ColorGradientStop { color: "green"; position: 0 },
+ ColorGradientStop { color: "red"; position: 1 }
+ ]
+ }
+
+ ColorGradient {
+ id: gradient3;
+ stops: [
+ ColorGradientStop { color: "gray"; position: 0 },
+ ColorGradientStop { color: "darkgray"; position: 1 }
+ ]
+ }
+
+ Surface3DSeries {
+ id: initialized
+ dataProxy: ItemModelSurfaceDataProxy {
+ itemModel: ListModel {
+ ListElement{ longitude: "20"; latitude: "10"; pop_density: "4.75"; }
+ ListElement{ longitude: "21"; latitude: "10"; pop_density: "3.00"; }
+ }
+ rowRole: "longitude"
+ columnRole: "latitude"
+ yPosRole: "pop_density"
+ }
+ drawMode: Surface3DSeries.DrawSurface
+ flatShadingEnabled: false
+ selectedPoint: Qt.point(0, 0)
+ textureFile: ":\customtexture.jpg"
+ wireframeColor: "red"
+
+ baseColor: "blue"
+ baseGradient: gradient1
+ colorStyle: Theme3D.ColorStyleObjectGradient
+ itemLabelFormat: "%f"
+ itemLabelVisible: false
+ mesh: Abstract3DSeries.MeshCube
+ meshRotation: Qt.quaternion(1, 1, 1, 1)
+ meshSmooth: true
+ multiHighlightColor: "green"
+ multiHighlightGradient: gradient2
+ name: "series1"
+ singleHighlightColor: "red"
+ singleHighlightGradient: gradient3
+ userDefinedMesh: ":/customitem.obj"
+ visible: false
+ }
+
+ ItemModelSurfaceDataProxy {
+ id: proxy1
+ itemModel: ListModel {
+ ListElement{ longitude: "20"; latitude: "10"; pop_density: "4.75"; }
+ ListElement{ longitude: "21"; latitude: "10"; pop_density: "3.00"; }
+ ListElement{ longitude: "22"; latitude: "10"; pop_density: "1.24"; }
+ }
+ rowRole: "longitude"
+ columnRole: "latitude"
+ yPosRole: "pop_density"
+ }
+
+ Surface3DSeries {
+ id: change
+ }
+
+ TestCase {
+ name: "Surface3DSeries Initial"
+
+ function test_1_initial() {
+ compare(initial.dataProxy.rowCount, 0)
+ compare(initial.invalidSelectionPosition, Qt.point(-1, -1))
+ compare(initial.drawMode, Surface3DSeries.DrawSurfaceAndWireframe)
+ compare(initial.flatShadingEnabled, true)
+ compare(initial.flatShadingSupported, true)
+ compare(initial.selectedPoint, Qt.point(-1, -1))
+ compare(initial.wireframeColor, "#000000")
+ }
+
+ function test_2_initial_common() {
+ // Common properties
+ compare(initial.baseColor, "#000000")
+ compare(initial.baseGradient, null)
+ compare(initial.colorStyle, Theme3D.ColorStyleUniform)
+ compare(initial.itemLabel, "")
+ compare(initial.itemLabelFormat, "@xLabel, @yLabel, @zLabel")
+ compare(initial.itemLabelVisible, true)
+ compare(initial.mesh, Abstract3DSeries.MeshSphere)
+ compare(initial.meshRotation, Qt.quaternion(1, 0, 0, 0))
+ compare(initial.meshSmooth, false)
+ compare(initial.multiHighlightColor, "#000000")
+ compare(initial.multiHighlightGradient, null)
+ compare(initial.name, "")
+ compare(initial.singleHighlightColor, "#000000")
+ compare(initial.singleHighlightGradient, null)
+ compare(initial.type, Abstract3DSeries.SeriesTypeSurface)
+ compare(initial.userDefinedMesh, "")
+ compare(initial.visible, true)
+ }
+ }
+
+ TestCase {
+ name: "Surface3DSeries Initialized"
+
+ function test_1_initialized() {
+ compare(initialized.dataProxy.rowCount, 2)
+ compare(initialized.drawMode, Surface3DSeries.DrawSurface)
+ compare(initialized.flatShadingEnabled, false)
+ compare(initialized.selectedPoint, Qt.point(0, 0))
+ compare(initialized.textureFile, ":\customtexture.jpg")
+ compare(initialized.wireframeColor, "#ff0000")
+ }
+
+ function test_2_initialized_common() {
+ // Common properties
+ compare(initialized.baseColor, "#0000ff")
+ compare(initialized.baseGradient, gradient1)
+ compare(initialized.colorStyle, Theme3D.ColorStyleObjectGradient)
+ compare(initialized.itemLabelFormat, "%f")
+ compare(initialized.itemLabelVisible, false)
+ compare(initialized.mesh, Abstract3DSeries.MeshCube)
+ compare(initialized.meshRotation, Qt.quaternion(1, 1, 1, 1))
+ compare(initialized.meshSmooth, true)
+ compare(initialized.multiHighlightColor, "#008000")
+ compare(initialized.multiHighlightGradient, gradient2)
+ compare(initialized.name, "series1")
+ compare(initialized.singleHighlightColor, "#ff0000")
+ compare(initialized.singleHighlightGradient, gradient3)
+ compare(initialized.userDefinedMesh, ":/customitem.obj")
+ compare(initialized.visible, false)
+ }
+ }
+
+ TestCase {
+ name: "Surface3DSeries Change"
+
+ function test_1_change() {
+ change.dataProxy = proxy1
+ change.drawMode = Surface3DSeries.DrawSurface
+ change.flatShadingEnabled = false
+ change.selectedPoint = Qt.point(0, 0)
+ change.textureFile = ":\customtexture.jpg"
+ change.wireframeColor = "green"
+ }
+
+ function test_2_test_change() {
+ // This test has a dependency to the previous one due to asynchronous item model resolving
+ compare(change.dataProxy.rowCount, 3)
+ compare(change.drawMode, Surface3DSeries.DrawSurface)
+ compare(change.flatShadingEnabled, false)
+ compare(change.selectedPoint, Qt.point(0, 0))
+ compare(change.textureFile, ":\customtexture.jpg")
+ compare(change.wireframeColor, "#008000")
+ }
+
+ function test_3_change_common() {
+ change.baseColor = "blue"
+ change.baseGradient = gradient1
+ change.colorStyle = Theme3D.ColorStyleObjectGradient
+ change.itemLabelFormat = "%f"
+ change.itemLabelVisible = false
+ change.mesh = Abstract3DSeries.MeshCube
+ change.meshRotation = Qt.quaternion(1, 1, 1, 1)
+ change.meshSmooth = true
+ change.multiHighlightColor = "green"
+ change.multiHighlightGradient = gradient2
+ change.name = "series1"
+ change.singleHighlightColor = "red"
+ change.singleHighlightGradient = gradient3
+ change.userDefinedMesh = ":/customitem.obj"
+ change.visible = false
+
+ compare(change.baseColor, "#0000ff")
+ compare(change.baseGradient, gradient1)
+ compare(change.colorStyle, Theme3D.ColorStyleObjectGradient)
+ compare(change.itemLabelFormat, "%f")
+ compare(change.itemLabelVisible, false)
+ compare(change.mesh, Abstract3DSeries.MeshCube)
+ compare(change.meshRotation, Qt.quaternion(1, 1, 1, 1))
+ compare(change.meshSmooth, true)
+ compare(change.multiHighlightColor, "#008000")
+ compare(change.multiHighlightGradient, gradient2)
+ compare(change.name, "series1")
+ compare(change.singleHighlightColor, "#ff0000")
+ compare(change.singleHighlightGradient, gradient3)
+ compare(change.userDefinedMesh, ":/customitem.obj")
+ compare(change.visible, false)
+ }
+
+ function test_4_change_gradient_stop() {
+ gradient1.stops[0].color = "yellow"
+ compare(change.baseGradient.stops[0].color, "#ffff00")
+ }
+ }
+}
diff --git a/tests/auto/qmltest/theme3d/tst_colorgradient.qml b/tests/auto/qmltest/theme3d/tst_colorgradient.qml
new file mode 100644
index 0000000..e90a6b1
--- /dev/null
+++ b/tests/auto/qmltest/theme3d/tst_colorgradient.qml
@@ -0,0 +1,74 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick 2.0
+import QtGraphs
+import QtTest 1.0
+
+Item {
+ id: top
+ width: 150
+ height: 150
+
+ ColorGradient {
+ id: initial
+ }
+
+ ColorGradient {
+ id: initialized
+ stops: [
+ ColorGradientStop { color: "blue"; position: 0 },
+ ColorGradientStop { color: "white"; position: 0.5 },
+ ColorGradientStop { color: "red"; position: 1 }
+ ]
+ }
+
+ ColorGradient {
+ id: change
+ }
+
+ TestCase {
+ name: "ColorGradient Initial"
+
+ function test_initial() {
+ compare(initial.stops.length, 0)
+ }
+ }
+
+ TestCase {
+ name: "ColorGradient Initialized"
+
+ function test_initialized() {
+ compare(initialized.stops.length, 3)
+ compare(initialized.stops[0].color, "#0000ff")
+ compare(initialized.stops[1].color, "#ffffff")
+ compare(initialized.stops[2].color, "#ff0000")
+ }
+ }
+
+ TestCase {
+ name: "ColorGradient Change"
+
+ ColorGradientStop { id: stop1; color: "blue"; position: 0 }
+ ColorGradientStop { id: stop2; color: "red"; position: 1.0 }
+ ColorGradientStop { id: stop3; color: "white"; position: 0.5 }
+
+ function test_change() {
+ change.stops = [stop1]
+ compare(change.stops.length, 1)
+ change.stops = [stop1, stop2]
+ compare(change.stops.length, 2)
+ compare(change.stops[0].color, "#0000ff")
+ change.stops[0].color = "red"
+ compare(change.stops[0].color, "#ff0000")
+ compare(change.stops[1].color, "#ff0000")
+ change.stops = [stop1, stop2, stop3]
+ compare(change.stops[2].color, "#ffffff")
+ compare(change.stops.length, 3)
+ stop2.position = 0.25
+ stop3.position = 1.0
+ compare(change.stops[1].position, 0.25)
+ compare(change.stops[2].position, 1.0)
+ }
+ }
+}
diff --git a/tests/auto/qmltest/theme3d/tst_theme.qml b/tests/auto/qmltest/theme3d/tst_theme.qml
new file mode 100644
index 0000000..e24ea81
--- /dev/null
+++ b/tests/auto/qmltest/theme3d/tst_theme.qml
@@ -0,0 +1,251 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick 2.0
+import QtGraphs
+import QtTest 1.0
+
+Item {
+ id: top
+ width: 150
+ height: 150
+
+ Theme3D {
+ id: initial
+ }
+
+ ColorGradient {
+ id: gradient1
+ stops: [
+ ColorGradientStop { color: "red"; position: 0 },
+ ColorGradientStop { color: "blue"; position: 1 }
+ ]
+ }
+
+ ColorGradient {
+ id: gradient2
+ stops: [
+ ColorGradientStop { color: "green"; position: 0 },
+ ColorGradientStop { color: "red"; position: 1 }
+ ]
+ }
+
+ ColorGradient {
+ id: gradient3
+ stops: [
+ ColorGradientStop { color: "gray"; position: 0 },
+ ColorGradientStop { color: "darkgray"; position: 1 }
+ ]
+ }
+
+ ThemeColor {
+ id: color1
+ color: "red"
+ }
+
+ ThemeColor {
+ id: color2
+ color: "blue"
+ }
+
+ Theme3D {
+ id: initialized
+ ambientLightStrength: 0.3
+ backgroundColor: "#ff0000"
+ backgroundEnabled: false
+ baseColors: [color1, color2]
+ baseGradients: [gradient1, gradient2]
+ colorStyle: Theme3D.ColorStyleRangeGradient
+ font.family: "Arial"
+ gridEnabled: false
+ gridLineColor: "#00ff00"
+ highlightLightStrength: 5.0
+ labelBackgroundColor: "#ff00ff"
+ labelBackgroundEnabled: false
+ labelBorderEnabled: false
+ labelTextColor: "#00ffff"
+ lightColor: "#ffff00"
+ lightStrength: 2.5
+ multiHighlightColor: "#ff00ff"
+ multiHighlightGradient: gradient3
+ singleHighlightColor: "#ff0000"
+ singleHighlightGradient: gradient3
+ type: Theme3D.ThemeQt // Default values will be overwritten by initialized values
+ windowColor: "#fff00f"
+ }
+
+ Theme3D {
+ id: change
+ }
+
+ Theme3D {
+ id: invalid
+ }
+
+ TestCase {
+ name: "Theme3D Initial"
+
+ Text { id: dummy }
+
+ function test_initial() {
+ compare(initial.ambientLightStrength, 0.25)
+ compare(initial.backgroundColor, "#000000")
+ compare(initial.backgroundEnabled, true)
+ compare(initial.baseColors.length, 1)
+ compare(initial.baseColors[0].color, "#000000")
+ compare(initial.baseGradients.length, 1)
+ compare(initial.baseGradients[0].stops[0].color, "#000000")
+ compare(initial.baseGradients[0].stops[1].color, "#ffffff")
+ compare(initial.colorStyle, Theme3D.ColorStyleUniform)
+ // Initial font needs to be tested like this, as different platforms have different default font (QFont())
+ compare(initial.font.family, dummy.font.family)
+ compare(initial.gridEnabled, true)
+ compare(initial.gridLineColor, "#ffffff")
+ compare(initial.highlightLightStrength, 7.5)
+ compare(initial.labelBackgroundColor, "#a0a0a4")
+ compare(initial.labelBackgroundEnabled, true)
+ compare(initial.labelBorderEnabled, true)
+ compare(initial.labelTextColor, "#ffffff")
+ compare(initial.lightColor, "#ffffff")
+ compare(initial.lightStrength, 5)
+ compare(initial.multiHighlightColor, "#0000ff")
+ compare(initial.multiHighlightGradient, null)
+ compare(initial.singleHighlightColor, "#ff0000")
+ compare(initial.singleHighlightGradient, null)
+ compare(initial.type, Theme3D.ThemeUserDefined)
+ compare(initial.windowColor, "#000000")
+ }
+ }
+
+ TestCase {
+ name: "Theme3D Initialized"
+
+ function test_initialized() {
+ compare(initialized.ambientLightStrength, 0.3)
+ compare(initialized.backgroundColor, "#ff0000")
+ compare(initialized.backgroundEnabled, false)
+ compare(initialized.baseColors.length, 2)
+ compare(initialized.baseColors[0].color, "#ff0000")
+ compare(initialized.baseColors[1].color, "#0000ff")
+ compare(initialized.baseGradients.length, 2)
+ compare(initialized.baseGradients[0], gradient1)
+ compare(initialized.baseGradients[1], gradient2)
+ compare(initialized.colorStyle, Theme3D.ColorStyleRangeGradient)
+ compare(initialized.font.family, "Arial")
+ compare(initialized.gridEnabled, false)
+ compare(initialized.gridLineColor, "#00ff00")
+ compare(initialized.highlightLightStrength, 5.0)
+ compare(initialized.labelBackgroundColor, "#ff00ff")
+ compare(initialized.labelBackgroundEnabled, false)
+ compare(initialized.labelBorderEnabled, false)
+ compare(initialized.labelTextColor, "#00ffff")
+ compare(initialized.lightColor, "#ffff00")
+ compare(initialized.lightStrength, 2.5)
+ compare(initialized.multiHighlightColor, "#ff00ff")
+ compare(initialized.multiHighlightGradient, gradient3)
+ compare(initialized.singleHighlightColor, "#ff0000")
+ compare(initialized.singleHighlightGradient, gradient3)
+ compare(initialized.type, Theme3D.ThemeQt)
+ compare(initialized.windowColor, "#fff00f")
+ }
+ }
+
+ TestCase {
+ name: "Theme3D Change"
+
+ ThemeColor {
+ id: color3
+ color: "red"
+ }
+
+ ColorGradient {
+ id: gradient4
+ stops: [
+ ColorGradientStop { color: "red"; position: 0 },
+ ColorGradientStop { color: "blue"; position: 1 }
+ ]
+ }
+
+ function test_1_change() {
+ change.type = Theme3D.ThemeStoneMoss // Default values will be overwritten by the following sets
+ change.ambientLightStrength = 0.3
+ change.backgroundColor = "#ff0000"
+ change.backgroundEnabled = false
+ change.baseColors = [color3, color2]
+ change.baseGradients = [gradient4, gradient2]
+ change.colorStyle = Theme3D.ColorStyleObjectGradient
+ change.font.family = "Arial"
+ change.gridEnabled = false
+ change.gridLineColor = "#00ff00"
+ change.highlightLightStrength = 5.0
+ change.labelBackgroundColor = "#ff00ff"
+ change.labelBackgroundEnabled = false
+ change.labelBorderEnabled = false
+ change.labelTextColor = "#00ffff"
+ change.lightColor = "#ffff00"
+ change.lightStrength = 2.5
+ change.multiHighlightColor = "#ff00ff"
+ change.multiHighlightGradient = gradient3
+ change.singleHighlightColor = "#ff0000"
+ change.singleHighlightGradient = gradient3
+ change.windowColor = "#fff00f"
+
+ compare(change.ambientLightStrength, 0.3)
+ compare(change.backgroundColor, "#ff0000")
+ compare(change.backgroundEnabled, false)
+ compare(change.baseColors.length, 2)
+ compare(change.baseColors[0].color, "#ff0000")
+ compare(change.baseColors[1].color, "#0000ff")
+ compare(change.baseGradients.length, 2)
+ compare(change.baseGradients[0], gradient4)
+ compare(change.baseGradients[1], gradient2)
+ compare(change.colorStyle, Theme3D.ColorStyleObjectGradient)
+ compare(change.font.family, "Arial")
+ compare(change.gridEnabled, false)
+ compare(change.gridLineColor, "#00ff00")
+ compare(change.highlightLightStrength, 5.0)
+ compare(change.labelBackgroundColor, "#ff00ff")
+ compare(change.labelBackgroundEnabled, false)
+ compare(change.labelBorderEnabled, false)
+ compare(change.labelTextColor, "#00ffff")
+ compare(change.lightColor, "#ffff00")
+ compare(change.lightStrength, 2.5)
+ compare(change.multiHighlightColor, "#ff00ff")
+ compare(change.multiHighlightGradient, gradient3)
+ compare(change.singleHighlightColor, "#ff0000")
+ compare(change.singleHighlightGradient, gradient3)
+ compare(change.type, Theme3D.ThemeStoneMoss)
+ compare(change.windowColor, "#fff00f")
+ }
+
+ function test_2_change_color() {
+ color3.color = "white"
+ compare(change.baseColors[0].color, "#ffffff")
+ }
+
+ function test_3_change_gradient() {
+ gradient4.stops[0].color = "black"
+ compare(change.baseGradients[0].stops[0].color, "#000000")
+ }
+ }
+
+
+ TestCase {
+ name: "Theme3D Invalid"
+
+ function test_invalid() {
+ invalid.ambientLightStrength = -1.0
+ compare(invalid.ambientLightStrength, 0.25)
+ invalid.ambientLightStrength = 1.1
+ compare(invalid.ambientLightStrength, 0.25)
+ invalid.highlightLightStrength = -1.0
+ compare(invalid.highlightLightStrength, 7.5)
+ invalid.highlightLightStrength = 10.1
+ compare(invalid.highlightLightStrength, 7.5)
+ invalid.lightStrength = -1.0
+ compare(invalid.lightStrength, 5.0)
+ invalid.lightStrength = 10.1
+ compare(invalid.lightStrength, 5.0)
+ }
+ }
+}
diff --git a/tests/auto/qmltest/theme3d/tst_themecolor.qml b/tests/auto/qmltest/theme3d/tst_themecolor.qml
new file mode 100644
index 0000000..9fa1c60
--- /dev/null
+++ b/tests/auto/qmltest/theme3d/tst_themecolor.qml
@@ -0,0 +1,51 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick 2.0
+import QtGraphs
+import QtTest 1.0
+
+Item {
+ id: top
+ width: 150
+ height: 150
+
+ ThemeColor {
+ id: initial
+ }
+
+ ThemeColor {
+ id: initialized
+ color: "red"
+ }
+
+ ThemeColor {
+ id: change
+ }
+
+ TestCase {
+ name: "ThemeColor Initial"
+
+ function test_initial() {
+ compare(initial.color, "#000000")
+ }
+ }
+
+ TestCase {
+ name: "ThemeColor Initialized"
+
+ function test_initialized() {
+ compare(initialized.color, "#ff0000")
+ }
+ }
+
+ TestCase {
+ name: "ThemeColor Change"
+
+ function test_change() {
+ change.color = "blue"
+
+ compare(change.color, "#0000ff")
+ }
+ }
+}
diff --git a/tests/auto/qmltest/tst_qmltest.cpp b/tests/auto/qmltest/tst_qmltest.cpp
new file mode 100644
index 0000000..47a7991
--- /dev/null
+++ b/tests/auto/qmltest/tst_qmltest.cpp
@@ -0,0 +1,33 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtQuickTest/quicktest.h>
+
+class tst_qmltest: public QObject
+{
+ Q_OBJECT
+private slots:
+ void skiptest() { QSKIP("This test will fail, skipping."); };
+};
+
+int main(int argc, char **argv)
+{
+ if (!qEnvironmentVariableIsEmpty("QEMU_LD_PREFIX")) {
+ qWarning("This test would fail due to QEMU emulation shortcomings, so it will be skipped.");
+ tst_qmltest skip;
+ return QTest::qExec(&skip, argc, argv);
+ }
+#ifdef Q_OS_QNX
+ if (qEnvironmentVariable("QTEST_ENVIRONMENT").split(' ').contains("ci") &&
+ qEnvironmentVariable("QT_QPA_PLATFORM").split(' ').contains("offscreen")
+ ) {
+ qWarning("This test would fail on CI QNX QEMU without OpenGL support, so it will be skipped.");
+ tst_qmltest skip;
+ return QTest::qExec(&skip, argc, argv);
+ }
+#endif
+ QTEST_SET_MAIN_SOURCE_PATH
+ return quick_test_main(argc, argv, "qmltest", QUICK_TEST_SOURCE_DIR);
+}
+
+#include "tst_qmltest.moc"
diff --git a/tests/manual/CMakeLists.txt b/tests/manual/CMakeLists.txt
new file mode 100644
index 0000000..bec3164
--- /dev/null
+++ b/tests/manual/CMakeLists.txt
@@ -0,0 +1,30 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+if(TARGET Qt::Quick)
+ add_subdirectory(qmldynamicdata)
+ add_subdirectory(qmlmultitest)
+ add_subdirectory(qmlvolume)
+ add_subdirectory(qmlperf)
+ add_subdirectory(qmlgradient)
+ add_subdirectory(qmlheightmap)
+ add_subdirectory(qmlbarsrowcolors)
+ add_subdirectory(qmlcustominput)
+ add_subdirectory(qmllegend)
+ add_subdirectory(qmlsurfacelayers)
+endif()
+if(NOT ANDROID AND NOT IOS AND NOT WINRT)
+# add_subdirectory(barstest)
+# add_subdirectory(scattertest)
+# add_subdirectory(surfacetest)
+# add_subdirectory(multigraphs)
+# add_subdirectory(directional)
+# add_subdirectory(itemmodeltest)
+# add_subdirectory(volumetrictest)
+# add_subdirectory(minimalbars)
+# add_subdirectory(minimalscatter)
+# add_subdirectory(minimalsurface)
+# add_subdirectory(rotations)
+# add_subdirectory(custominput)
+# add_subdirectory(itemmodel)
+endif()
diff --git a/tests/manual/barstest/CMakeLists.txt b/tests/manual/barstest/CMakeLists.txt
new file mode 100644
index 0000000..9c25803
--- /dev/null
+++ b/tests/manual/barstest/CMakeLists.txt
@@ -0,0 +1,31 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+qt_internal_add_manual_test(barstest
+ GUI
+ SOURCES
+ chart.cpp chart.h
+ sliderwrapper.cpp sliderwrapper.h
+ buttonwrapper.cpp buttonwrapper.h
+ custominputhandler.cpp custominputhandler.h
+ main.cpp
+ )
+target_link_libraries(barstest PUBLIC
+ Qt::Gui
+ Qt::Widgets
+ Qt::Graphs
+ )
+
+set(barstest_resource_files
+ "shuttle.obj"
+ "shuttle.png"
+ )
+
+qt_internal_add_resource(barstest "barstest"
+ PREFIX
+ "/"
+ FILES
+ ${barstest_resource_files}
+ )
diff --git a/tests/manual/barstest/barstest.pro b/tests/manual/barstest/barstest.pro
new file mode 100644
index 0000000..56e24ef
--- /dev/null
+++ b/tests/manual/barstest/barstest.pro
@@ -0,0 +1,10 @@
+!include( ../tests.pri ) {
+ error( "Couldn't find the tests.pri file!" )
+}
+
+SOURCES += main.cpp chart.cpp custominputhandler.cpp
+HEADERS += chart.h custominputhandler.h
+
+RESOURCES += barstest.qrc
+
+QT += widgets
diff --git a/tests/manual/barstest/barstest.qrc b/tests/manual/barstest/barstest.qrc
new file mode 100644
index 0000000..f7237eb
--- /dev/null
+++ b/tests/manual/barstest/barstest.qrc
@@ -0,0 +1,6 @@
+<RCC>
+ <qresource prefix="/">
+ <file>shuttle.obj</file>
+ <file>shuttle.png</file>
+ </qresource>
+</RCC>
diff --git a/tests/manual/barstest/buttonwrapper.cpp b/tests/manual/barstest/buttonwrapper.cpp
new file mode 100644
index 0000000..172b149
--- /dev/null
+++ b/tests/manual/barstest/buttonwrapper.cpp
@@ -0,0 +1,14 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+#include "buttonwrapper.h"
+#include <QPushButton>
+
+ButtonWrapper::ButtonWrapper(QPushButton *button)
+{
+ m_button = button;
+}
+
+void ButtonWrapper::setEnabled(int state)
+{
+ m_button->setEnabled(state);
+}
diff --git a/tests/manual/barstest/buttonwrapper.h b/tests/manual/barstest/buttonwrapper.h
new file mode 100644
index 0000000..64103ac
--- /dev/null
+++ b/tests/manual/barstest/buttonwrapper.h
@@ -0,0 +1,22 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+#ifndef BUTTONWRAPPER_H
+#define BUTTONWRAPPER_H
+
+#include <QObject>
+class QPushButton;
+
+class ButtonWrapper : public QObject
+{
+ Q_OBJECT
+public:
+ ButtonWrapper(QPushButton *button);
+
+public Q_SLOTS:
+ void setEnabled(int state);
+
+private:
+ QPushButton *m_button;
+};
+
+#endif // BUTTONWRAPPER_H
diff --git a/tests/manual/barstest/chart.cpp b/tests/manual/barstest/chart.cpp
new file mode 100644
index 0000000..659296a
--- /dev/null
+++ b/tests/manual/barstest/chart.cpp
@@ -0,0 +1,1786 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "chart.h"
+#include "custominputhandler.h"
+#include <QtGraphs/qcategory3daxis.h>
+#include <QtGraphs/qvalue3daxis.h>
+#include <QtGraphs/qlogvalue3daxisformatter.h>
+#include <QtGraphs/qbardataproxy.h>
+#include <QtGraphs/q3dscene.h>
+#include <QtGraphs/q3dcamera.h>
+#include <QtGraphs/q3dtheme.h>
+#include <QtGraphs/q3dinputhandler.h>
+#include <QtGraphs/qcustom3ditem.h>
+#include <QtCore/QRandomGenerator>
+#include <QtCore/QElapsedTimer>
+#include <QtCore/qmath.h>
+
+const QString celsiusString = QString(QChar(0xB0)) + "C";
+
+GraphModifier::GraphModifier(Q3DBars *barchart, QColorDialog *colorDialog)
+ : m_graph(barchart),
+ m_colorDialog(colorDialog),
+ m_columnCount(21),
+ m_rowCount(21),
+ m_xRotation(0.0f),
+ m_yRotation(0.0f),
+ m_static(true),
+ m_barSpacingX(0.1f),
+ m_barSpacingZ(0.1f),
+ m_barSeriesMarginX(0.0f),
+ m_barSeriesMarginZ(0.0f),
+ m_fontSize(20),
+ m_segments(10),
+ m_subSegments(3),
+ m_minval(-16.0f),
+ m_maxval(20.0f),
+ m_selectedBar(-1, -1),
+ m_selectedSeries(0),
+ m_autoAdjustingAxis(new QValue3DAxis),
+ m_fixedRangeAxis(new QValue3DAxis),
+ m_temperatureAxis(new QValue3DAxis),
+ m_yearAxis(new QCategory3DAxis),
+ m_monthAxis(new QCategory3DAxis),
+ m_genericRowAxis(new QCategory3DAxis),
+ m_genericColumnAxis(new QCategory3DAxis),
+ m_temperatureData(new QBar3DSeries),
+ m_temperatureData2(new QBar3DSeries),
+ m_genericData(new QBar3DSeries),
+ m_dummyData(new QBar3DSeries),
+ m_dummyData2(new QBar3DSeries),
+ m_dummyData3(new QBar3DSeries),
+ m_dummyData4(new QBar3DSeries),
+ m_dummyData5(new QBar3DSeries),
+ m_currentAxis(m_fixedRangeAxis),
+ m_negativeValuesOn(false),
+ m_useNullInputHandler(false),
+ m_defaultInputHandler(0),
+ m_ownTheme(0),
+ m_builtinTheme(new Q3DTheme(Q3DTheme::ThemeStoneMoss)),
+ m_customInputHandler(new CustomInputHandler),
+ m_extraSeries(0)
+{
+ m_temperatureData->setObjectName("m_temperatureData");
+ m_temperatureData2->setObjectName("m_temperatureData2");
+ m_genericData->setObjectName("m_genericData");
+ m_dummyData->setObjectName("m_dummyData");
+ m_dummyData2->setObjectName("m_dummyData2");
+ m_dummyData3->setObjectName("m_dummyData3");
+ m_dummyData4->setObjectName("m_dummyData4");
+ m_dummyData5->setObjectName("m_dummyData5");
+
+ // Generate generic labels
+ QStringList genericColumnLabels;
+ for (int i = 0; i < 400; i++) {
+ if (i % 5)
+ genericColumnLabels << QString();
+ else
+ genericColumnLabels << QStringLiteral("Column %1").arg(i);
+ }
+
+ m_months << "January" << "February" << "March" << "April" << "May" << "June" << "July" << "August" << "September" << "October" << "November" << "December";
+ m_years << "2006" << "2007" << "2008" << "2009" << "2010" << "2011" << "2012";
+
+ QString labelFormat(QStringLiteral("%.3f"));
+ QString axisTitle("Generic Value");
+
+ m_autoAdjustingAxis->setLabelFormat(labelFormat);
+ m_autoAdjustingAxis->setTitle(axisTitle);
+ m_autoAdjustingAxis->setSegmentCount(m_segments * 2);
+ m_autoAdjustingAxis->setSubSegmentCount(1);
+ m_autoAdjustingAxis->setAutoAdjustRange(true);
+
+ m_fixedRangeAxis->setLabelFormat(labelFormat);
+ m_fixedRangeAxis->setTitle(axisTitle);
+ m_fixedRangeAxis->setSegmentCount(m_segments);
+ m_fixedRangeAxis->setSubSegmentCount(m_subSegments);
+ m_fixedRangeAxis->setRange(0.0, 100.0);
+
+ m_genericRowAxis->setTitle("Generic Row");
+ m_genericRowAxis->setRange(0, m_rowCount - 1);
+
+ m_genericColumnAxis->setTitle("Generic Column");
+ m_genericColumnAxis->setRange(0, m_columnCount - 1);
+
+ m_temperatureAxis->setTitle("Average temperature");
+ m_temperatureAxis->setSegmentCount(m_segments);
+ m_temperatureAxis->setSubSegmentCount(m_subSegments);
+ m_temperatureAxis->setRange(m_minval, m_maxval);
+ m_temperatureAxis->setLabelFormat(QString(QStringLiteral("%d ") + celsiusString));
+
+ m_yearAxis->setTitle("Year");
+
+ m_monthAxis->setTitle("Month");
+
+ m_graph->addAxis(m_autoAdjustingAxis);
+ m_graph->addAxis(m_fixedRangeAxis);
+ m_graph->addAxis(m_temperatureAxis);
+ m_graph->addAxis(m_yearAxis);
+ m_graph->addAxis(m_monthAxis);
+ m_graph->addAxis(m_genericRowAxis);
+ m_graph->addAxis(m_genericColumnAxis);
+
+ m_graph->setActiveTheme(m_builtinTheme);
+ m_graph->setShadowQuality(QAbstract3DGraph::ShadowQualitySoftMedium);
+
+ m_temperatureData->setName("Oulu");
+ m_temperatureData2->setName("Helsinki");
+ m_genericData->setName("Generic series");
+ m_dummyData->setName("Dummy 1");
+ m_dummyData2->setName("Dummy 2");
+ m_dummyData3->setName("Dummy 3");
+ m_dummyData4->setName("Dummy 4");
+ m_dummyData5->setName("Dummy 5");
+
+ m_temperatureData->setItemLabelFormat(QStringLiteral("@seriesName: @valueTitle for @colLabel @rowLabel: @valueLabel ~ %.4f"));
+ m_temperatureData2->setItemLabelFormat(QStringLiteral("@seriesName: @valueTitle for @colLabel @rowLabel: @valueLabel"));
+ m_genericData->setItemLabelFormat(QStringLiteral("@seriesName: @valueTitle for (@rowIdx, @colIdx): @valueLabel"));
+
+ m_dummyData->setItemLabelFormat(QStringLiteral("@seriesName: @valueLabel"));
+ m_dummyData2->setItemLabelFormat(QStringLiteral("@seriesName: @valueLabel"));
+ m_dummyData3->setItemLabelFormat(QStringLiteral("@seriesName: @valueLabel"));
+ m_dummyData4->setItemLabelFormat(QStringLiteral("@seriesName: @valueLabel"));
+ m_dummyData5->setItemLabelFormat(QStringLiteral("@seriesName: @valueLabel"));
+
+ m_genericData->dataProxy()->setColumnLabels(genericColumnLabels);
+
+ m_temperatureData->setBaseColor(Qt::red);
+ m_temperatureData->setSingleHighlightColor(Qt::cyan);
+ m_temperatureData->setMultiHighlightColor(Qt::magenta);
+ m_temperatureData2->setBaseColor(Qt::yellow);
+ m_genericData->setBaseColor(Qt::blue);
+
+ QLinearGradient barGradient1(0, 0, 1, 100);
+ barGradient1.setColorAt(1.0, Qt::red);
+ barGradient1.setColorAt(0.75001, Qt::red);
+ barGradient1.setColorAt(0.75, Qt::magenta);
+ barGradient1.setColorAt(0.50001, Qt::magenta);
+ barGradient1.setColorAt(0.50, Qt::blue);
+ barGradient1.setColorAt(0.25001, Qt::blue);
+ barGradient1.setColorAt(0.25, Qt::black);
+ barGradient1.setColorAt(0.0, Qt::black);
+
+ QLinearGradient barGradient2(0, 0, 1, 100);
+ barGradient2.setColorAt(1.0, Qt::red);
+ barGradient2.setColorAt(0.75, Qt::magenta);
+ barGradient2.setColorAt(0.50, Qt::blue);
+ barGradient2.setColorAt(0.25, Qt::black);
+ barGradient2.setColorAt(0.0, Qt::black);
+
+ QLinearGradient singleHighlightGradient(0, 0, 1, 100);
+ singleHighlightGradient.setColorAt(1.0, Qt::white);
+ singleHighlightGradient.setColorAt(0.75, Qt::lightGray);
+ singleHighlightGradient.setColorAt(0.50, Qt::gray);
+ singleHighlightGradient.setColorAt(0.25, Qt::darkGray);
+ singleHighlightGradient.setColorAt(0.0, Qt::black);
+
+ QLinearGradient multiHighlightGradient(0, 0, 1, 100);
+ multiHighlightGradient.setColorAt(1.0, Qt::lightGray);
+ multiHighlightGradient.setColorAt(0.75, Qt::gray);
+ multiHighlightGradient.setColorAt(0.50, Qt::darkGray);
+ multiHighlightGradient.setColorAt(0.25, Qt::black);
+ multiHighlightGradient.setColorAt(0.0, Qt::black);
+
+ m_temperatureData->setBaseGradient(barGradient1);
+ m_temperatureData2->setBaseGradient(barGradient2);
+ m_temperatureData->setSingleHighlightGradient(singleHighlightGradient);
+ m_temperatureData->setMultiHighlightGradient(multiHighlightGradient);
+
+ m_graph->activeTheme()->setFont(QFont("Times Roman", 20));
+
+ // Release and store the default input handler.
+ m_defaultInputHandler = static_cast<Q3DInputHandler *>(m_graph->activeInputHandler());
+ m_graph->releaseInputHandler(m_defaultInputHandler);
+ m_graph->setActiveInputHandler(m_defaultInputHandler);
+
+ QObject::connect(m_graph, &Q3DBars::shadowQualityChanged, this,
+ &GraphModifier::shadowQualityUpdatedByVisual);
+ QObject::connect(m_temperatureData, &QBar3DSeries::selectedBarChanged, this,
+ &GraphModifier::handleSelectionChange);
+ QObject::connect(m_temperatureData2, &QBar3DSeries::selectedBarChanged, this,
+ &GraphModifier::handleSelectionChange);
+ QObject::connect(m_genericData, &QBar3DSeries::selectedBarChanged, this,
+ &GraphModifier::handleSelectionChange);
+
+ QObject::connect(m_graph, &Q3DBars::rowAxisChanged, this,
+ &GraphModifier::handleRowAxisChanged);
+ QObject::connect(m_graph, &Q3DBars::columnAxisChanged, this,
+ &GraphModifier::handleColumnAxisChanged);
+ QObject::connect(m_graph, &Q3DBars::valueAxisChanged, this,
+ &GraphModifier::handleValueAxisChanged);
+ QObject::connect(m_graph, &Q3DBars::primarySeriesChanged, this,
+ &GraphModifier::handlePrimarySeriesChanged);
+ QObject::connect(m_temperatureAxis, &QAbstract3DAxis::labelsChanged, this,
+ &GraphModifier::handleValueAxisLabelsChanged);
+
+ QObject::connect(&m_insertRemoveTimer, &QTimer::timeout, this,
+ &GraphModifier::insertRemoveTimerTimeout);
+
+ m_graph->addSeries(m_temperatureData);
+ m_graph->addSeries(m_temperatureData2);
+
+ QObject::connect(&m_selectionTimer, &QTimer::timeout, this,
+ &GraphModifier::triggerSelection);
+ QObject::connect(&m_rotationTimer, &QTimer::timeout, this,
+ &GraphModifier::triggerRotation);
+
+ QObject::connect(m_graph, &QAbstract3DGraph::currentFpsChanged, this,
+ &GraphModifier::handleFpsChange);
+
+ resetTemperatureData();
+}
+
+GraphModifier::~GraphModifier()
+{
+ delete m_graph;
+ delete m_defaultInputHandler;
+}
+
+void GraphModifier::start()
+{
+ restart(false);
+}
+
+void GraphModifier::restart(int dynamicData)
+{
+ m_static = !dynamicData;
+
+ if (m_static) {
+ m_graph->removeSeries(m_genericData);
+
+ m_graph->setTitle(QStringLiteral("Average temperatures in Oulu, Finland (2006-2012)"));
+
+ m_graph->setValueAxis(m_temperatureAxis);
+ m_graph->setRowAxis(m_yearAxis);
+ m_graph->setColumnAxis(m_monthAxis);
+
+ } else {
+ m_graph->addSeries(m_genericData);
+
+ m_graph->setTitle(QStringLiteral("Generic data"));
+
+ m_minval = m_fixedRangeAxis->min();
+ m_maxval = m_fixedRangeAxis->max();
+ m_graph->setValueAxis(m_currentAxis);
+ m_graph->setRowAxis(m_genericRowAxis);
+ m_graph->setColumnAxis(m_genericColumnAxis);
+ }
+}
+
+void GraphModifier::selectBar()
+{
+ QPoint targetBar(5, 5);
+ QPoint noSelection(-1, -1);
+ if (m_selectedBar != targetBar)
+ m_graph->seriesList().at(0)->setSelectedBar(targetBar);
+ else
+ m_graph->seriesList().at(0)->setSelectedBar(noSelection);
+}
+
+void GraphModifier::swapAxis()
+{
+ static int counter = 0;
+ int state = ++counter % 3;
+
+ if (state == 0) {
+ m_currentAxis = m_fixedRangeAxis;
+ qDebug() << "Fixed range axis";
+ } else if (state == 1) {
+ m_currentAxis = m_autoAdjustingAxis;
+ qDebug() << "Automatic range axis";
+ } else {
+ m_currentAxis = 0;
+ qDebug() << "default axis";
+ }
+
+ m_graph->setValueAxis(m_currentAxis);
+}
+
+void GraphModifier::releaseAxes()
+{
+ // Releases all axes we have created - results in default axes for all dimensions.
+ // Axes reset when the graph is switched as set*Axis calls are made, which
+ // implicitly add axes.
+ m_graph->releaseAxis(m_autoAdjustingAxis);
+ m_graph->releaseAxis(m_fixedRangeAxis);
+ m_graph->releaseAxis(m_temperatureAxis);
+ m_graph->releaseAxis(m_yearAxis);
+ m_graph->releaseAxis(m_monthAxis);
+ m_graph->releaseAxis(m_genericRowAxis);
+ m_graph->releaseAxis(m_genericColumnAxis);
+}
+
+void GraphModifier::releaseSeries()
+{
+ foreach (QBar3DSeries *series, m_graph->seriesList())
+ m_graph->removeSeries(series);
+}
+
+void GraphModifier::flipViews(bool checked)
+{
+ Q_UNUSED(checked)
+ m_graph->scene()->setSecondarySubviewOnTop(!m_graph->scene()->isSecondarySubviewOnTop());
+ qDebug() << "secondary subview on top:" << m_graph->scene()->isSecondarySubviewOnTop();
+ qDebug() << "point (50, 50) in primary subview:" << m_graph->scene()->isPointInPrimarySubView(QPoint(50, 50));
+ qDebug() << "point (50, 50) in secondary subview:" << m_graph->scene()->isPointInSecondarySubView(QPoint(50, 50));
+}
+
+void GraphModifier::createMassiveArray()
+{
+ const int arrayDimension = 1000;
+ QElapsedTimer timer;
+ timer.start();
+
+ QStringList genericColumnLabels;
+ for (int i = 0; i < arrayDimension; i++) {
+ if (i % 5)
+ genericColumnLabels << QString();
+ else
+ genericColumnLabels << QStringLiteral("Column %1").arg(i);
+ }
+
+ QStringList genericRowLabels;
+ for (int i = 0; i < arrayDimension; i++) {
+ if (i % 5)
+ genericRowLabels << QString();
+ else
+ genericRowLabels << QStringLiteral("Row %1").arg(i);
+ }
+
+ QBarDataArray *dataArray = new QBarDataArray;
+ dataArray->reserve(arrayDimension);
+ for (int i = 0; i < arrayDimension; i++) {
+ QBarDataRow *dataRow = new QBarDataRow(arrayDimension);
+ for (int j = 0; j < arrayDimension; j++) {
+ if (!m_negativeValuesOn)
+ (*dataRow)[j].setValue((float(i % 300 + 1) / 300.0)
+ * QRandomGenerator::global()->bounded(m_maxval));
+ else
+ (*dataRow)[j].setValue((float(i % 300 + 1) / 300.0)
+ * QRandomGenerator::global()->bounded(m_maxval) + m_minval);
+ }
+ dataArray->append(dataRow);
+ }
+
+ m_genericData->dataProxy()->resetArray(dataArray, genericRowLabels, genericColumnLabels);
+
+ qDebug() << "Created Massive Array (" << arrayDimension << "), time:" << timer.elapsed();
+}
+
+void GraphModifier::resetTemperatureData()
+{
+ // Set up data
+ static const float temp[7][12] = {
+ {-6.7f, -11.7f, -9.7f, 3.3f, 9.2f, 14.0f, 16.3f, 17.8f, 10.2f, 2.1f, -2.6f, -0.3f}, // 2006
+ {-6.8f, -13.3f, 0.2f, 1.5f, 7.9f, 13.4f, 16.1f, 15.5f, 8.2f, 5.4f, -2.6f, -0.8f}, // 2007
+ {-4.2f, -4.0f, -4.6f, 1.9f, 7.3f, 12.5f, 15.0f, 12.8f, 7.6f, 5.1f, -0.9f, -1.3f}, // 2008
+ {-7.8f, -8.8f, -4.2f, 0.7f, 9.3f, 13.2f, 15.8f, 15.5f, 11.2f, 0.6f, 0.7f, -8.4f}, // 2009
+ {-14.4f, -12.1f, -7.0f, 2.3f, 11.0f, 12.6f, 18.8f, 13.8f, 9.4f, 3.9f, -5.6f, -13.0f}, // 2010
+ {-9.0f, -15.2f, -3.8f, 2.6f, 8.3f, 15.9f, 18.6f, 14.9f, 11.1f, 5.3f, 1.8f, -0.2f}, // 2011
+ {-8.7f, -11.3f, -2.3f, 0.4f, 7.5f, 12.2f, 16.4f, 14.1f, 9.2f, 3.1f, 0.3f, -12.1f} // 2012
+ };
+
+ // Create data rows
+ QBarDataArray *dataSet = new QBarDataArray;
+ QBarDataRow *dataRow;
+
+ dataSet->reserve(m_years.size());
+ for (int year = 0; year < m_years.size(); year++) {
+ dataRow = new QBarDataRow(m_months.size());
+ // Create data items
+ for (int month = 0; month < m_months.size(); month++) {
+ // Add data to rows
+ (*dataRow)[month].setValue(temp[year][month]);
+ }
+ // Add row to set
+ dataSet->append(dataRow);
+ }
+
+ QBarDataArray *dataSet2 = new QBarDataArray;
+
+ dataSet2->reserve(m_years.size());
+ for (int year = m_years.size() - 1; year >= 0; year--) {
+ dataRow = new QBarDataRow(m_months.size());
+ // Create data items
+ for (int month = m_months.size() - 1; month >= 0 ; month--) {
+ // Add data to rows
+ (*dataRow)[month].setValue(temp[year][month]);
+ }
+ // Add row to set
+ dataSet2->append(dataRow);
+ }
+
+ // Add data to chart (chart assumes ownership)
+ m_temperatureData->dataProxy()->resetArray(dataSet, m_years, m_months);
+ m_temperatureData2->dataProxy()->resetArray(dataSet2, m_years, m_months);
+}
+
+
+static int addCounter = 0;
+static int insertCounter = 0;
+static int changeCounter = 0;
+
+void GraphModifier::addRow()
+{
+ QBarDataRow *dataRow = new QBarDataRow(m_columnCount);
+ for (float i = 0; i < m_columnCount; i++) {
+ if (!m_negativeValuesOn)
+ (*dataRow)[i].setValue(((i + 1) / (float)m_columnCount)
+ * QRandomGenerator::global()->bounded(m_maxval));
+ else
+ (*dataRow)[i].setValue(((i + 1) / (float)m_columnCount)
+ * QRandomGenerator::global()->bounded(m_maxval)
+ - QRandomGenerator::global()->bounded(m_minval));
+ }
+
+ // TODO Needs to be changed to account for data window offset once it is implemented.
+ QString label = QStringLiteral("Add %1").arg(addCounter++);
+ m_genericData->dataProxy()->addRow(dataRow, label);
+}
+
+void GraphModifier::addRows()
+{
+ QBarDataArray dataArray;
+ QStringList labels;
+ for (int i = 0; i < m_rowCount; i++) {
+ QBarDataRow *dataRow = new QBarDataRow(m_columnCount);
+ for (int j = 0; j < m_columnCount; j++)
+ (*dataRow)[j].setValue(float(j + i + m_genericData->dataProxy()->rowCount()) + m_minval);
+ dataArray.append(dataRow);
+ labels.append(QStringLiteral("Add %1").arg(addCounter++));
+ }
+
+ // TODO Needs to be changed to account for data window offset once it is implemented.
+ m_genericData->dataProxy()->addRows(dataArray, labels);
+}
+
+void GraphModifier::insertRow()
+{
+ QBarDataRow *dataRow = new QBarDataRow(m_columnCount);
+ for (float i = 0; i < m_columnCount; i++)
+ (*dataRow)[i].setValue(((i + 1) / (float)m_columnCount)
+ * QRandomGenerator::global()->bounded(m_maxval) + m_minval);
+
+ // TODO Needs to be changed to account for data window offset once it is implemented.
+ int row = qMax(m_selectedBar.x(), 0);
+ QString label = QStringLiteral("Insert %1").arg(insertCounter++);
+ m_genericData->dataProxy()->insertRow(row, dataRow, label);
+}
+
+void GraphModifier::insertRows()
+{
+ QElapsedTimer timer;
+ timer.start();
+ QBarDataArray dataArray;
+ QStringList labels;
+ for (int i = 0; i < m_rowCount; i++) {
+ QBarDataRow *dataRow = new QBarDataRow(m_columnCount);
+ for (int j = 0; j < m_columnCount; j++)
+ (*dataRow)[j].setValue(float(j + i + m_genericData->dataProxy()->rowCount()) + m_minval);
+ dataArray.append(dataRow);
+ labels.append(QStringLiteral("Insert %1").arg(insertCounter++));
+ }
+
+ // TODO Needs to be changed to account for data window offset once it is implemented.
+ int row = qMax(m_selectedBar.x(), 0);
+ m_genericData->dataProxy()->insertRows(row, dataArray, labels);
+ qDebug() << "Inserted" << m_rowCount << "rows, time:" << timer.elapsed();
+}
+
+void GraphModifier::changeItem()
+{
+ // TODO Needs to be changed to account for data window offset once it is implemented.
+ int row = m_selectedBar.x();
+ int column = m_selectedBar.y();
+ if (row >= 0 && column >= 0) {
+ QBarDataItem item(float(QRandomGenerator::global()->bounded(100)));
+ m_genericData->dataProxy()->setItem(row, column, item);
+ }
+}
+
+void GraphModifier::changeRow()
+{
+ // TODO Needs to be changed to account for data window offset once it is implemented.
+ int row = m_selectedBar.x();
+ if (row >= 0) {
+ QBarDataRow *newRow = new QBarDataRow(m_genericData->dataProxy()->rowAt(row)->size());
+ for (int i = 0; i < newRow->size(); i++)
+ (*newRow)[i].setValue(QRandomGenerator::global()->bounded(m_maxval) + m_minval);
+ QString label = QStringLiteral("Change %1").arg(changeCounter++);
+ m_genericData->dataProxy()->setRow(row, newRow, label);
+ }
+}
+
+void GraphModifier::changeRows()
+{
+ // TODO Needs to be changed to account for data window offset once it is implemented.
+ int row = m_selectedBar.x();
+ if (row >= 0) {
+ int startRow = qMax(row - 2, 0);
+ QBarDataArray newArray;
+ QStringList labels;
+ for (int i = startRow; i <= row; i++ ) {
+ QBarDataRow *newRow = new QBarDataRow(m_genericData->dataProxy()->rowAt(i)->size());
+ for (int j = 0; j < newRow->size(); j++)
+ (*newRow)[j].setValue(QRandomGenerator::global()->bounded(m_maxval) + m_minval);
+ newArray.append(newRow);
+ labels.append(QStringLiteral("Change %1").arg(changeCounter++));
+ }
+ m_genericData->dataProxy()->setRows(startRow, newArray, labels);
+ }
+}
+
+void GraphModifier::removeRow()
+{
+ // TODO Needs to be changed to account for data window offset once it is implemented.
+ int row = m_selectedBar.x();
+ if (row >= 0)
+ m_genericData->dataProxy()->removeRows(row, 1);
+}
+
+void GraphModifier::removeRows()
+{
+ // TODO Needs to be changed to account for data window offset once it is implemented.
+ int row = m_selectedBar.x();
+ if (row >= 0) {
+ int startRow = qMax(row - 3, 0);
+ m_genericData->dataProxy()->removeRows(startRow, 3);
+ }
+}
+
+void GraphModifier::changeStyle()
+{
+ static int model = 0;
+ switch (model) {
+ case 0:
+ m_graph->seriesList().at(0)->setMesh(QAbstract3DSeries::MeshCylinder);
+ m_graph->seriesList().at(0)->setMeshSmooth(false);
+ break;
+ case 1:
+ m_graph->seriesList().at(0)->setMesh(QAbstract3DSeries::MeshCylinder);
+ m_graph->seriesList().at(0)->setMeshSmooth(true);
+ break;
+ case 2:
+ m_graph->seriesList().at(0)->setMesh(QAbstract3DSeries::MeshCone);
+ m_graph->seriesList().at(0)->setMeshSmooth(false);
+ break;
+ case 3:
+ m_graph->seriesList().at(0)->setMesh(QAbstract3DSeries::MeshCone);
+ m_graph->seriesList().at(0)->setMeshSmooth(true);
+ break;
+ case 4:
+ m_graph->seriesList().at(0)->setMesh(QAbstract3DSeries::MeshBar);
+ m_graph->seriesList().at(0)->setMeshSmooth(false);
+ break;
+ case 5:
+ m_graph->seriesList().at(0)->setMesh(QAbstract3DSeries::MeshBar);
+ m_graph->seriesList().at(0)->setMeshSmooth(true);
+ break;
+ case 6:
+ m_graph->seriesList().at(0)->setMesh(QAbstract3DSeries::MeshPyramid);
+ m_graph->seriesList().at(0)->setMeshSmooth(false);
+ break;
+ case 7:
+ m_graph->seriesList().at(0)->setMesh(QAbstract3DSeries::MeshPyramid);
+ m_graph->seriesList().at(0)->setMeshSmooth(true);
+ break;
+ case 8:
+ m_graph->seriesList().at(0)->setMesh(QAbstract3DSeries::MeshBevelBar);
+ m_graph->seriesList().at(0)->setMeshSmooth(false);
+ break;
+ case 9:
+ m_graph->seriesList().at(0)->setMesh(QAbstract3DSeries::MeshBevelBar);
+ m_graph->seriesList().at(0)->setMeshSmooth(true);
+ break;
+ }
+ model++;
+ if (model > 9)
+ model = 0;
+}
+
+void GraphModifier::changePresetCamera()
+{
+ static int preset = Q3DCamera::CameraPresetFrontLow;
+
+ m_graph->scene()->activeCamera()->setCameraPreset((Q3DCamera::CameraPreset)preset);
+
+ if (++preset > Q3DCamera::CameraPresetDirectlyBelow)
+ preset = Q3DCamera::CameraPresetFrontLow;
+}
+
+void GraphModifier::changeTheme()
+{
+ static int theme = Q3DTheme::ThemeQt;
+
+ Q3DTheme *currentTheme = m_graph->activeTheme();
+ m_builtinTheme->setType(Q3DTheme::Theme(theme));
+ if (currentTheme == m_ownTheme)
+ m_graph->setActiveTheme(m_builtinTheme);
+
+ switch (theme) {
+ case Q3DTheme::ThemeQt:
+ qDebug() << __FUNCTION__ << "ThemeQt";
+ break;
+ case Q3DTheme::ThemePrimaryColors:
+ qDebug() << __FUNCTION__ << "ThemePrimaryColors";
+ break;
+ case Q3DTheme::ThemeDigia:
+ qDebug() << __FUNCTION__ << "ThemeDigia";
+ break;
+ case Q3DTheme::ThemeStoneMoss:
+ qDebug() << __FUNCTION__ << "ThemeStoneMoss";
+ break;
+ case Q3DTheme::ThemeArmyBlue:
+ qDebug() << __FUNCTION__ << "ThemeArmyBlue";
+ break;
+ case Q3DTheme::ThemeRetro:
+ qDebug() << __FUNCTION__ << "ThemeRetro";
+ break;
+ case Q3DTheme::ThemeEbony:
+ qDebug() << __FUNCTION__ << "ThemeEbony";
+ break;
+ case Q3DTheme::ThemeIsabelle:
+ qDebug() << __FUNCTION__ << "ThemeIsabelle";
+ break;
+ default:
+ qDebug() << __FUNCTION__ << "Unknown theme";
+ break;
+ }
+
+ if (++theme > Q3DTheme::ThemeIsabelle)
+ theme = Q3DTheme::ThemeQt;
+}
+
+void GraphModifier::changeLabelStyle()
+{
+ m_graph->activeTheme()->setLabelBackgroundEnabled(!m_graph->activeTheme()->isLabelBackgroundEnabled());
+}
+
+void GraphModifier::changeSelectionMode()
+{
+ static int selectionMode = m_graph->selectionMode();
+
+ if (++selectionMode > (int)(QAbstract3DGraph::SelectionItemAndColumn |
+ QAbstract3DGraph::SelectionSlice |
+ QAbstract3DGraph::SelectionMultiSeries))
+ selectionMode = QAbstract3DGraph::SelectionNone;
+
+ m_graph->setSelectionMode((QAbstract3DGraph::SelectionFlag)selectionMode);
+}
+
+void GraphModifier::changeFont(const QFont &font)
+{
+ QFont newFont = font;
+ newFont.setPointSize(m_fontSize);
+ m_graph->activeTheme()->setFont(newFont);
+}
+
+void GraphModifier::changeFontSize(int fontsize)
+{
+ m_fontSize = fontsize;
+ QFont font = m_graph->activeTheme()->font();
+ font.setPointSize(m_fontSize);
+ m_graph->activeTheme()->setFont(font);
+}
+
+void GraphModifier::shadowQualityUpdatedByVisual(QAbstract3DGraph::ShadowQuality sq)
+{
+ int quality = int(sq);
+ // Updates the UI component to show correct shadow quality
+ emit shadowQualityChanged(quality);
+}
+
+void GraphModifier::handleSelectionChange(const QPoint &position)
+{
+ m_selectedBar = position;
+ int index = 0;
+ foreach (QBar3DSeries *series, m_graph->seriesList()) {
+ if (series == sender()) {
+ if (series->selectedBar() != QBar3DSeries::invalidSelectionPosition())
+ m_selectedSeries = series;
+ break;
+ }
+ index++;
+ }
+
+ if (m_selectedSeries->selectedBar() == QBar3DSeries::invalidSelectionPosition())
+ m_selectedSeries = 0;
+
+ qDebug() << "Selected bar position:" << position << "series:" << index;
+}
+
+void GraphModifier::setUseNullInputHandler(int useNull)
+{
+ qDebug() << "setUseNullInputHandler" << useNull;
+ if (m_useNullInputHandler == useNull)
+ return;
+
+ m_useNullInputHandler = useNull;
+
+ if (useNull)
+ m_graph->setActiveInputHandler(0);
+ else
+ m_graph->setActiveInputHandler(m_defaultInputHandler);
+}
+
+void GraphModifier::handleRowAxisChanged(QCategory3DAxis *axis)
+{
+ qDebug() << __FUNCTION__ << axis << axis->orientation() << (axis == m_graph->rowAxis());
+}
+
+void GraphModifier::handleColumnAxisChanged(QCategory3DAxis *axis)
+{
+ qDebug() << __FUNCTION__ << axis << axis->orientation() << (axis == m_graph->columnAxis());
+}
+
+void GraphModifier::handleValueAxisChanged(QValue3DAxis *axis)
+{
+ qDebug() << __FUNCTION__ << axis << axis->orientation() << (axis == m_graph->valueAxis());
+}
+
+void GraphModifier::handlePrimarySeriesChanged(QBar3DSeries *series)
+{
+ qDebug() << __FUNCTION__ << series;
+}
+
+void GraphModifier::changeShadowQuality(int quality)
+{
+ QAbstract3DGraph::ShadowQuality sq = QAbstract3DGraph::ShadowQuality(quality);
+ m_graph->setShadowQuality(sq);
+ emit shadowQualityChanged(quality);
+}
+
+void GraphModifier::showFiveSeries()
+{
+ releaseSeries();
+ releaseAxes();
+ m_graph->setSelectionMode(QAbstract3DGraph::SelectionItemRowAndColumn | QAbstract3DGraph::SelectionMultiSeries);
+
+ m_dummyData->dataProxy()->resetArray(makeDummyData(), QStringList(), QStringList());
+ m_dummyData2->dataProxy()->resetArray(makeDummyData(), QStringList(), QStringList());
+ m_dummyData3->dataProxy()->resetArray(makeDummyData(), QStringList(), QStringList());
+ m_dummyData4->dataProxy()->resetArray(makeDummyData(), QStringList(), QStringList());
+ m_dummyData5->dataProxy()->resetArray(makeDummyData(), QStringList(), QStringList());
+
+ m_graph->addSeries(m_dummyData);
+ m_graph->addSeries(m_dummyData2);
+ m_graph->addSeries(m_dummyData3);
+ m_graph->addSeries(m_dummyData4);
+
+ // Toggle between four and five series
+ static int count = 0;
+ if (++count % 2)
+ m_graph->addSeries(m_dummyData5);
+}
+
+QBarDataArray *GraphModifier::makeDummyData()
+{
+ // Set up data
+ static const float temp[4][4] = {
+ {10.0f, 5.0f, 10.0f, 5.0f},
+ {5.0f, 10.0f, 5.0f, 10.0f},
+ {10.0f, 5.0f, 10.0f, 5.0f},
+ {5.0f, 10.0f, 5.0f, 10.0f}
+ };
+
+ // Create data rows
+ QBarDataArray *dataSet = new QBarDataArray;
+ QBarDataRow *dataRow;
+
+ dataSet->reserve(4);
+ for (int i = 0; i < 4; i++) {
+ dataRow = new QBarDataRow(4);
+ // Create data items
+ for (int j = 0; j < 4; j++) {
+ // Add data to rows
+ (*dataRow)[j].setValue(temp[i][j]);
+ }
+ // Add row to set
+ dataSet->append(dataRow);
+ }
+ return dataSet;
+}
+
+// Executes one step of the primary series test
+void GraphModifier::primarySeriesTest(bool checked)
+{
+ Q_UNUSED(checked)
+ static int nextStep = 0;
+
+ QStringList testLabels;
+ QStringList testLabels2;
+ QStringList testLabels3;
+ QStringList testLabels5;
+ testLabels << "1" << "2" << "3" << "4";
+ testLabels2 << "11" << "22" << "33" << "44";
+ testLabels3 << "111" << "222" << "333" << "444";
+ testLabels5 << "11111" << "22222" << "33333" << "44444";
+
+ switch (nextStep++) {
+ case 0: {
+ qDebug() << "Step 0 - Init:";
+ m_graph->addSeries(m_dummyData); // Add one series to enforce release in releaseProxies()
+ releaseSeries();
+ releaseAxes();
+ m_dummyData->dataProxy()->resetArray(makeDummyData(),
+ testLabels,
+ QStringList() << "A" << "B" << "C" << "D");
+ m_dummyData2->dataProxy()->resetArray(makeDummyData(),
+ testLabels2,
+ QStringList() << "AA" << "BB" << "CC" << "DD");
+ m_dummyData3->dataProxy()->resetArray(makeDummyData(),
+ testLabels3,
+ QStringList() << "AAA" << "BBB" << "CCC" << "DDD");
+ m_dummyData4->dataProxy()->resetArray(makeDummyData(),
+ QStringList() << "1111" << "2222" << "3333" << "4444",
+ QStringList() << "AAAA" << "BBBB" << "CCCC" << "DDDD");
+ m_dummyData5->dataProxy()->resetArray(makeDummyData(),
+ testLabels5,
+ QStringList() << "AAAAA" << "BBBBB" << "CCCCC" << "DDDDD");
+
+ m_graph->addSeries(m_dummyData);
+ m_graph->addSeries(m_dummyData2);
+ m_graph->addSeries(m_dummyData3);
+
+ m_dummyData->setBaseColor(Qt::black);
+ m_dummyData2->setBaseColor(Qt::white);
+ m_dummyData3->setBaseColor(Qt::red);
+ m_dummyData4->setBaseColor(Qt::blue);
+ m_dummyData5->setBaseColor(Qt::green);
+
+ if (m_graph->primarySeries() == m_dummyData)
+ if (m_graph->rowAxis()->labels() == testLabels)
+ qDebug() << "--> SUCCESS";
+ else
+ qDebug() << "--> FAIL!!! Row labels incorrect: " << m_graph->rowAxis()->labels();
+ else
+ qDebug() << "--> FAIL!!! Primary should be m_dummyData, actual: " << m_graph->primarySeries();
+ break;
+ }
+ case 1: {
+ qDebug() << "Step 1 - Set another series as primary:";
+ m_graph->setPrimarySeries(m_dummyData3);
+ if (m_graph->primarySeries() == m_dummyData3) {
+ if (m_graph->rowAxis()->labels() == testLabels3)
+ qDebug() << "--> SUCCESS";
+ else
+ qDebug() << "--> FAIL!!! Row labels incorrect: " << m_graph->rowAxis()->labels();
+ } else {
+ qDebug() << "--> FAIL!!! Primary should be m_dummyData3, actual: " << m_graph->primarySeries();
+ }
+ break;
+ }
+ case 2: {
+ qDebug() << "Step 2 - Add new series:";
+ m_graph->addSeries(m_dummyData4);
+ if (m_graph->primarySeries() == m_dummyData3)
+ if (m_graph->rowAxis()->labels() == testLabels3)
+ qDebug() << "--> SUCCESS";
+ else
+ qDebug() << "--> FAIL!!! Row labels incorrect: " << m_graph->rowAxis()->labels();
+ else
+ qDebug() << "--> FAIL!!! Primary should be m_dummyData3, actual: " << m_graph->primarySeries();
+ break;
+ }
+ case 3: {
+ qDebug() << "Step 3 - Reset primary series:";
+ m_graph->setPrimarySeries(0);
+ if (m_graph->primarySeries() == m_dummyData)
+ if (m_graph->rowAxis()->labels() == testLabels)
+ qDebug() << "--> SUCCESS";
+ else
+ qDebug() << "--> FAIL!!! Row labels incorrect: " << m_graph->rowAxis()->labels();
+ else
+ qDebug() << "--> FAIL!!! Primary should be m_dummyData, actual: " << m_graph->primarySeries();
+ break;
+ }
+ case 4: {
+ qDebug() << "Step 4 - Set new series at primary:";
+ m_graph->setPrimarySeries(m_dummyData5);
+ if (m_graph->primarySeries() == m_dummyData5)
+ if (m_graph->rowAxis()->labels() == testLabels5)
+ qDebug() << "--> SUCCESS";
+ else
+ qDebug() << "--> FAIL!!! Row labels incorrect: " << m_graph->rowAxis()->labels();
+ else
+ qDebug() << "--> FAIL!!! Primary should be m_dummyData5, actual: " << m_graph->primarySeries();
+ break;
+ }
+ case 5: {
+ qDebug() << "Step 5 - Remove nonexistent series:";
+ m_graph->removeSeries(0);
+ if (m_graph->primarySeries() == m_dummyData5)
+ if (m_graph->rowAxis()->labels() == testLabels5)
+ qDebug() << "--> SUCCESS";
+ else
+ qDebug() << "--> FAIL!!! Row labels incorrect: " << m_graph->rowAxis()->labels();
+ else
+ qDebug() << "--> FAIL!!! Primary should be m_dummyData5, actual: " << m_graph->primarySeries();
+ break;
+ }
+ case 6: {
+ qDebug() << "Step 6 - Remove non-primary series:";
+ m_graph->removeSeries(m_dummyData);
+ if (m_graph->primarySeries() == m_dummyData5)
+ if (m_graph->rowAxis()->labels() == testLabels5)
+ qDebug() << "--> SUCCESS";
+ else
+ qDebug() << "--> FAIL!!! Row labels incorrect: " << m_graph->rowAxis()->labels();
+ else
+ qDebug() << "--> FAIL!!! Primary should be m_dummyData5, actual: " << m_graph->primarySeries();
+ break;
+ }
+ case 7: {
+ qDebug() << "Step 7 - Remove primary series:";
+ m_graph->removeSeries(m_dummyData5);
+ if (m_graph->primarySeries() == m_dummyData2) // first series removed, second should be first now
+ if (m_graph->rowAxis()->labels() == testLabels2)
+ qDebug() << "--> SUCCESS";
+ else
+ qDebug() << "--> FAIL!!! Row labels incorrect: " << m_graph->rowAxis()->labels();
+ else
+ qDebug() << "--> FAIL!!! Primary should be m_dummyData3, actual: " << m_graph->primarySeries();
+ break;
+ }
+ case 8: {
+ qDebug() << "Step 8 - move a series (m_dummyData2) forward to a different position";
+ m_graph->insertSeries(3, m_dummyData2);
+ if (m_graph->primarySeries() == m_dummyData2)
+ if (m_graph->seriesList().at(2) == m_dummyData2) // moving series forward, index decrements
+ qDebug() << "--> SUCCESS";
+ else
+ qDebug() << "--> FAIL!!! Moved to incorrect index, index 2 has:" << m_graph->seriesList().at(2);
+ else
+ qDebug() << "--> FAIL!!! Primary should be m_dummyData3, actual: " << m_graph->primarySeries();
+ break;
+ }
+ case 9: {
+ qDebug() << "Step 9 - move a series (m_dummyData4) backward to a different position";
+ m_graph->insertSeries(0, m_dummyData4);
+ if (m_graph->primarySeries() == m_dummyData2)
+ if (m_graph->seriesList().at(0) == m_dummyData4)
+ qDebug() << "--> SUCCESS";
+ else
+ qDebug() << "--> FAIL!!! Moved to incorrect index, index 0 has:" << m_graph->seriesList().at(0);
+ else
+ qDebug() << "--> FAIL!!! Primary should be m_dummyData3, actual: " << m_graph->primarySeries();
+ break;
+ }
+ case 10: {
+ qDebug() << "Step 10 - Insert a series (m_dummyData) series to position 2";
+ m_graph->insertSeries(2, m_dummyData);
+ if (m_graph->primarySeries() == m_dummyData2)
+ if (m_graph->seriesList().at(2) == m_dummyData)
+ qDebug() << "--> SUCCESS";
+ else
+ qDebug() << "--> FAIL!!! Moved to incorrect index, index 2 has:" << m_graph->seriesList().at(2);
+ else
+ qDebug() << "--> FAIL!!! Primary should be m_dummyData3, actual: " << m_graph->primarySeries();
+ break;
+ }
+ case 11: {
+ qDebug() << "Step 11 - Remove everything";
+ m_graph->removeSeries(m_dummyData);
+ m_graph->removeSeries(m_dummyData2);
+ m_graph->removeSeries(m_dummyData3);
+ m_graph->removeSeries(m_dummyData4);
+ m_graph->removeSeries(m_dummyData5);
+ if (m_graph->primarySeries() == 0)
+ if (m_graph->rowAxis()->labels() == QStringList())
+ qDebug() << "--> SUCCESS";
+ else
+ qDebug() << "--> FAIL!!! Row labels incorrect: " << m_graph->rowAxis()->labels();
+ else
+ qDebug() << "--> FAIL!!! Primary should be null, actual: " << m_graph->primarySeries();
+ break;
+ }
+ default:
+ qDebug() << "-- Restarting test sequence --";
+ nextStep = 0;
+ break;
+ }
+}
+
+void GraphModifier::insertRemoveTestToggle()
+{
+ if (m_insertRemoveTimer.isActive()) {
+ m_insertRemoveTimer.stop();
+ m_selectionTimer.stop();
+ m_graph->removeSeries(m_dummyData);
+ m_graph->removeSeries(m_dummyData2);
+ releaseSeries();
+ releaseAxes();
+ m_graph->setActiveInputHandler(m_defaultInputHandler);
+ } else {
+ releaseSeries();
+ releaseAxes();
+ m_graph->rowAxis()->setRange(0, 32);
+ m_graph->columnAxis()->setRange(0, 10);
+ m_graph->setActiveInputHandler(m_customInputHandler);
+ m_graph->addSeries(m_dummyData);
+ m_graph->addSeries(m_dummyData2);
+ m_insertRemoveStep = 0;
+ m_insertRemoveTimer.start(100);
+ m_selectionTimer.start(10);
+ }
+}
+
+void GraphModifier::toggleRotation(bool checked)
+{
+ Q_UNUSED(checked)
+ if (m_rotationTimer.isActive())
+ m_rotationTimer.stop();
+ else
+ m_rotationTimer.start(20);
+}
+
+void GraphModifier::useLogAxis(bool checked)
+{
+ Q_UNUSED(checked)
+ static int counter = -1;
+ static QLogValue3DAxisFormatter *logFormatter = 0;
+ static float minRange = 1.0f;
+ counter++;
+
+ switch (counter) {
+ case 0: {
+ qDebug() << "Case" << counter << ": Default log axis";
+ logFormatter = new QLogValue3DAxisFormatter;
+ m_graph->valueAxis()->setFormatter(logFormatter);
+ m_graph->valueAxis()->setRange(minRange, 1200.0f);
+ m_graph->valueAxis()->setLabelFormat(QStringLiteral("%.3f"));
+ break;
+ }
+ case 1: {
+ qDebug() << "Case" << counter << ": Hide max label";
+ logFormatter->setShowEdgeLabels(false);
+ break;
+ }
+ case 2: {
+ qDebug() << "Case" << counter << ": Try to hide subgrid unsuccessfully";
+ m_graph->valueAxis()->setSubSegmentCount(1);
+ break;
+ }
+ case 3: {
+ qDebug() << "Case" << counter << ": Hide subgrid property";
+ logFormatter->setAutoSubGrid(false);
+ m_graph->valueAxis()->setSubSegmentCount(1);
+ break;
+ }
+ case 4: {
+ qDebug() << "Case" << counter << ": Different base: 2";
+ logFormatter->setBase(2.0f);
+ logFormatter->setAutoSubGrid(true);
+ break;
+ }
+ case 5: {
+ qDebug() << "Case" << counter << ": Different base: 16";
+ logFormatter->setBase(16.0f);
+ break;
+ }
+ case 6: {
+ qDebug() << "Case" << counter << ": Invalid bases";
+ logFormatter->setBase(-1.0f);
+ logFormatter->setBase(1.0f);
+ break;
+ }
+ case 7: {
+ qDebug() << "Case" << counter << ": Zero base";
+ logFormatter->setBase(0.0f);
+ break;
+ }
+ case 8: {
+ qDebug() << "Case" << counter << ": Explicit ranges and segments";
+ int segmentCount = 6;
+ int base = 4;
+ m_graph->valueAxis()->setSegmentCount(segmentCount);
+ m_graph->valueAxis()->setSubSegmentCount(base - 1);
+ m_graph->valueAxis()->setRange(1.0f / float(base * base), qPow(base, segmentCount - 2));
+ break;
+ }
+ case 9: {
+ qDebug() << "Case" << counter << ": Negative range";
+ m_graph->valueAxis()->setRange(-20.0f, 50.0f);
+ break;
+ }
+ case 10: {
+ qDebug() << "Case" << counter << ": Non-integer base";
+ logFormatter->setBase(3.3f);
+ logFormatter->setAutoSubGrid(true);
+ break;
+ }
+ default:
+ qDebug() << "Resetting logaxis test";
+ minRange++;
+ counter = -1;
+ break;
+ }
+}
+
+void GraphModifier::changeValueAxisFormat(const QString & text)
+{
+ m_graph->valueAxis()->setLabelFormat(text);
+}
+
+void GraphModifier::changeLogBase(const QString &text)
+{
+ QLogValue3DAxisFormatter *formatter =
+ qobject_cast<QLogValue3DAxisFormatter *>(m_graph->valueAxis()->formatter());
+ if (formatter)
+ formatter->setBase(qreal(text.toDouble()));
+}
+
+void GraphModifier::addRemoveSeries()
+{
+ static int counter = 0;
+
+ switch (counter) {
+ case 0: {
+ qDebug() << __FUNCTION__ << counter << "New series";
+ m_extraSeries = new QBar3DSeries;
+ m_graph->addSeries(m_extraSeries);
+ QObject::connect(m_extraSeries, &QBar3DSeries::selectedBarChanged, this,
+ &GraphModifier::handleSelectionChange);
+ }
+ break;
+ case 1: {
+ qDebug() << __FUNCTION__ << counter << "Add data to series";
+ QBarDataArray *array = new QBarDataArray;
+ array->reserve(5);
+ for (int i = 0; i < 5; i++) {
+ array->append(new QBarDataRow(10));
+ for (int j = 0; j < 10; j++)
+ (*array->at(i))[j].setValue(i * j);
+ }
+ m_extraSeries->dataProxy()->resetArray(array);
+ }
+ break;
+ case 2: {
+ qDebug() << __FUNCTION__ << counter << "Hide series";
+ m_extraSeries->setVisible(false);
+ }
+ break;
+ case 3: {
+ qDebug() << __FUNCTION__ << counter << "Modify data when hidden";
+ QBarDataArray array;
+ array.reserve(5);
+ for (int i = 0; i < 5; i++) {
+ array.append(new QBarDataRow(10));
+ for (int j = 0; j < 10; j++)
+ (*array.at(i))[j].setValue(2 * i * j);
+ }
+ m_extraSeries->dataProxy()->addRows(array);
+ }
+ break;
+ case 4: {
+ qDebug() << __FUNCTION__ << counter << "Show series again";
+ m_extraSeries->setVisible(true);
+ }
+ break;
+ case 5: {
+ qDebug() << __FUNCTION__ << counter << "Remove series";
+ m_graph->removeSeries(m_extraSeries);
+ delete m_extraSeries;
+ m_extraSeries = 0;
+ }
+ break;
+ default:
+ qDebug() << __FUNCTION__ << "Resetting test";
+ counter = -1;
+ }
+ counter++;
+}
+
+void GraphModifier::testItemAndRowChanges(bool checked)
+{
+ Q_UNUSED(checked)
+ static int counter = 0;
+ const int rowCount = 12;
+ const int colCount = 10;
+ const float flatValue = 10.0f;
+ static QBar3DSeries *series0 = 0;
+ static QBar3DSeries *series1 = 0;
+ static QBar3DSeries *series2 = 0;
+ QBarDataItem item25;
+ QBarDataItem item50;
+ QBarDataItem item75;
+ item25.setValue(25);
+ item50.setValue(50);
+ item75.setValue(75);
+
+ switch (counter) {
+ case 0: {
+ qDebug() << __FUNCTION__ << counter << "Setup test";
+ releaseSeries();
+ releaseAxes();
+ delete series0;
+ delete series1;
+ delete series2;
+ series0 = new QBar3DSeries;
+ series1 = new QBar3DSeries;
+ series2 = new QBar3DSeries;
+ populateFlatSeries(series0, rowCount, colCount, flatValue);
+ populateFlatSeries(series1, rowCount, colCount, flatValue);
+ populateFlatSeries(series2, rowCount, colCount, flatValue);
+ m_graph->rowAxis()->setRange(4.0f, 8.0f);
+ m_graph->columnAxis()->setRange(3.0f, 6.0f);
+ m_graph->valueAxis()->setRange(0.0f, 100.0f);
+ m_graph->addSeries(series0);
+ m_graph->addSeries(series1);
+ m_graph->addSeries(series2);
+ //counter = 11; // skip single item tests
+ }
+ break;
+ case 1: {
+ qDebug() << __FUNCTION__ << counter << "Change single item, unselected";
+ series0->dataProxy()->setItem(4, 3, item50);
+ }
+ break;
+ case 2: {
+ qDebug() << __FUNCTION__ << counter << "Change single item, selected";
+ series1->setSelectedBar(QPoint(4, 5));
+ series1->dataProxy()->setItem(4, 5, item25);
+ }
+ break;
+ case 3: {
+ qDebug() << __FUNCTION__ << counter << "Change item outside visible area";
+ series1->dataProxy()->setItem(0, 3, item25);
+ }
+ break;
+ case 4: {
+ qDebug() << __FUNCTION__ << counter << "Change single item from two series, unselected";
+ series0->dataProxy()->setItem(5, 3, item25);
+ series1->dataProxy()->setItem(5, 3, item75);
+ }
+ break;
+ case 5: {
+ qDebug() << __FUNCTION__ << counter << "Change single item from two series, one selected";
+ series0->dataProxy()->setItem(5, 4, item25);
+ series1->dataProxy()->setItem(4, 5, item75);
+ }
+ break;
+ case 6: {
+ qDebug() << __FUNCTION__ << counter << "Change single item from two series, one outside range";
+ series0->dataProxy()->setItem(1, 2, item25);
+ series1->dataProxy()->setItem(6, 6, item75);
+ }
+ break;
+ case 7: {
+ qDebug() << __FUNCTION__ << counter << "Change single item from two series, both outside range";
+ series0->dataProxy()->setItem(1, 2, item25);
+ series1->dataProxy()->setItem(8, 8, item75);
+ }
+ break;
+ case 8: {
+ qDebug() << __FUNCTION__ << counter << "Change item to same value";
+ series1->dataProxy()->setItem(6, 6, item75);
+ }
+ break;
+ case 9: {
+ qDebug() << __FUNCTION__ << counter << "Change 3 items on each series";
+ series0->dataProxy()->setItem(7, 3, item25);
+ series0->dataProxy()->setItem(7, 4, item50);
+ series0->dataProxy()->setItem(7, 5, item75);
+ series1->dataProxy()->setItem(6, 3, item25);
+ series1->dataProxy()->setItem(6, 4, item50);
+ series1->dataProxy()->setItem(6, 5, item75);
+ }
+ break;
+ case 10: {
+ qDebug() << __FUNCTION__ << counter << "Level the field single item at a time";
+ QBarDataItem item;
+ item.setValue(15.0f);
+ for (int i = 0; i < rowCount; i++) {
+ for (int j = 0; j < colCount; j++) {
+ series0->dataProxy()->setItem(i, j, item);
+ series1->dataProxy()->setItem(i, j, item);
+ series2->dataProxy()->setItem(i, j, item);
+ }
+ }
+ }
+ break;
+ case 11: {
+ qDebug() << __FUNCTION__ << counter << "Change same items multiple times";
+ series0->dataProxy()->setItem(7, 3, item25);
+ series1->dataProxy()->setItem(7, 3, item25);
+ series0->dataProxy()->setItem(7, 3, item50);
+ series1->dataProxy()->setItem(7, 3, item50);
+ series0->dataProxy()->setItem(7, 3, item75);
+ series1->dataProxy()->setItem(7, 3, item75);
+ }
+ break;
+ case 12: {
+ qDebug() << __FUNCTION__ << counter << "Change row";
+ series0->dataProxy()->setRow(5, createFlatRow(colCount, 50.0f));
+ }
+ break;
+ case 13: {
+ qDebug() << __FUNCTION__ << counter << "Change row with selected item";
+ series1->setSelectedBar(QPoint(6, 6));
+ series1->dataProxy()->setRow(6, createFlatRow(colCount, 40.0f));
+ }
+ break;
+ case 14: {
+ qDebug() << __FUNCTION__ << counter << "Change hidden row";
+ series1->dataProxy()->setRow(9, createFlatRow(colCount, 50.0f));
+ }
+ break;
+ case 15: {
+ qDebug() << __FUNCTION__ << counter << "Change multiple rows singly";
+ series0->dataProxy()->setRow(6, createFlatRow(colCount, 70.0f));
+ series1->dataProxy()->setRow(6, createFlatRow(colCount, 80.0f));
+ series2->dataProxy()->setRow(6, createFlatRow(colCount, 90.0f));
+ }
+ break;
+ case 16: {
+ qDebug() << __FUNCTION__ << counter << "Change multiple rows many at a time";
+ QBarDataArray newRows;
+ newRows.reserve(4);
+ newRows.append(createFlatRow(colCount, 26.0f));
+ newRows.append(createFlatRow(colCount, 30.0f));
+ newRows.append(createFlatRow(colCount, 34.0f));
+ newRows.append(createFlatRow(colCount, 38.0f));
+ series0->dataProxy()->setRows(2, newRows);
+ newRows[0] = createFlatRow(colCount, 26.0f);
+ newRows[1] = createFlatRow(colCount, 30.0f);
+ newRows[2] = createFlatRow(colCount, 34.0f);
+ newRows[3] = createFlatRow(colCount, 38.0f);
+ series1->dataProxy()->setRows(3, newRows);
+ newRows[0] = createFlatRow(colCount, 26.0f);
+ newRows[1] = createFlatRow(colCount, 30.0f);
+ newRows[2] = createFlatRow(colCount, 34.0f);
+ newRows[3] = createFlatRow(colCount, 38.0f);
+ series2->dataProxy()->setRows(4, newRows);
+ }
+ break;
+ case 17: {
+ qDebug() << __FUNCTION__ << counter << "Change same rows multiple times";
+ QBarDataArray newRows;
+ newRows.reserve(4);
+ newRows.append(createFlatRow(colCount, 65.0f));
+ newRows.append(createFlatRow(colCount, 65.0f));
+ newRows.append(createFlatRow(colCount, 65.0f));
+ newRows.append(createFlatRow(colCount, 65.0f));
+ series0->dataProxy()->setRows(4, newRows);
+ newRows[0] = createFlatRow(colCount, 65.0f);
+ newRows[1] = createFlatRow(colCount, 65.0f);
+ newRows[2] = createFlatRow(colCount, 65.0f);
+ newRows[3] = createFlatRow(colCount, 65.0f);
+ series1->dataProxy()->setRows(4, newRows);
+ newRows[0] = createFlatRow(colCount, 65.0f);
+ newRows[1] = createFlatRow(colCount, 65.0f);
+ newRows[2] = createFlatRow(colCount, 65.0f);
+ newRows[3] = createFlatRow(colCount, 65.0f);
+ series2->dataProxy()->setRows(4, newRows);
+ series0->dataProxy()->setRow(6, createFlatRow(colCount, 20.0f));
+ series1->dataProxy()->setRow(6, createFlatRow(colCount, 20.0f));
+ series2->dataProxy()->setRow(6, createFlatRow(colCount, 20.0f));
+ series0->dataProxy()->setRow(6, createFlatRow(colCount, 90.0f));
+ series1->dataProxy()->setRow(6, createFlatRow(colCount, 90.0f));
+ series2->dataProxy()->setRow(6, createFlatRow(colCount, 90.0f));
+ }
+ break;
+ case 18: {
+ qDebug() << __FUNCTION__ << counter << "Change row to different length";
+ series0->dataProxy()->setRow(4, createFlatRow(5, 20.0f));
+ series1->dataProxy()->setRow(4, createFlatRow(0, 20.0f));
+ series2->dataProxy()->setRow(4, 0);
+ }
+ break;
+ case 19: {
+ qDebug() << __FUNCTION__ << counter << "Change selected row shorter so that selected item is no longer valid";
+ series1->dataProxy()->setRow(6, createFlatRow(6, 20.0f));
+ }
+ break;
+ default:
+ qDebug() << __FUNCTION__ << "Resetting test";
+ counter = -1;
+ }
+ counter++;
+}
+
+void GraphModifier::reverseValueAxis(int enabled)
+{
+ m_graph->valueAxis()->setReversed(enabled);
+}
+
+void GraphModifier::setInputHandlerRotationEnabled(int enabled)
+{
+ m_defaultInputHandler->setRotationEnabled(enabled);
+}
+
+void GraphModifier::setInputHandlerZoomEnabled(int enabled)
+{
+ m_defaultInputHandler->setZoomEnabled(enabled);
+}
+
+void GraphModifier::setInputHandlerSelectionEnabled(int enabled)
+{
+ m_defaultInputHandler->setSelectionEnabled(enabled);
+}
+
+void GraphModifier::setInputHandlerZoomAtTargetEnabled(int enabled)
+{
+ m_defaultInputHandler->setZoomAtTargetEnabled(enabled);
+}
+
+void GraphModifier::changeValueAxisSegments(int value)
+{
+ qDebug() << __FUNCTION__ << value;
+ m_segments = value;
+ m_graph->valueAxis()->setSegmentCount(m_segments);
+}
+
+void GraphModifier::insertRemoveTimerTimeout()
+{
+ if (m_insertRemoveStep < 32) {
+ for (int k = 0; k < 1; k++) {
+ QBarDataRow *dataRow = new QBarDataRow(10);
+ for (float i = 0; i < 10; i++)
+ (*dataRow)[i].setValue(((i + 1) / 10.0f) * (float)(QRandomGenerator::global()->bounded(100)));
+
+ QString label = QStringLiteral("Insert %1").arg(insertCounter++);
+ m_dummyData->dataProxy()->insertRow(0, dataRow, label);
+ }
+ } else {
+ for (int k = 0; k < 1; k++)
+ m_dummyData->dataProxy()->removeRows(0, 1);
+ }
+
+ if (m_insertRemoveStep < 16 || (m_insertRemoveStep > 31 && m_insertRemoveStep < 48)) {
+ for (int k = 0; k < 2; k++) {
+ QBarDataRow *dataRow = new QBarDataRow(10);
+ for (float i = 0; i < 10; i++)
+ (*dataRow)[i].setValue(((i + 1) / 10.0f) * (float)(QRandomGenerator::global()->bounded(100)));
+
+ QString label = QStringLiteral("Insert %1").arg(insertCounter++);
+ m_dummyData2->dataProxy()->insertRow(0, dataRow, label);
+ }
+ } else {
+ for (int k = 0; k < 2; k++)
+ m_dummyData2->dataProxy()->removeRows(0, 1);
+ }
+
+ if (m_insertRemoveStep++ > 63)
+ m_insertRemoveStep = 0;
+}
+
+void GraphModifier::triggerSelection()
+{
+ m_graph->scene()->setSelectionQueryPosition(m_customInputHandler->inputPosition());
+}
+
+void GraphModifier::triggerRotation()
+{
+ if (m_selectedSeries) {
+ QPoint selectedBar = m_selectedSeries->selectedBar();
+ if (selectedBar != QBar3DSeries::invalidSelectionPosition()) {
+ QBarDataItem item(*(m_selectedSeries->dataProxy()->itemAt(selectedBar.x(), selectedBar.y())));
+ item.setRotation(item.rotation() + 1.0f);
+ m_selectedSeries->dataProxy()->setItem(selectedBar.x(), selectedBar.y(), item);
+ }
+ } else {
+ // Rotate the first series instead
+ static float seriesAngle = 0.0f;
+ if (m_graph->seriesList().size())
+ m_graph->seriesList().at(0)->setMeshAngle(seriesAngle++);
+ }
+}
+
+void GraphModifier::handleValueAxisLabelsChanged()
+{
+ qDebug() << __FUNCTION__;
+}
+
+void GraphModifier::handleFpsChange(qreal fps)
+{
+ static const QString fpsPrefix(QStringLiteral("FPS: "));
+ m_fpsLabel->setText(fpsPrefix + QString::number(qRound(fps)));
+}
+
+void GraphModifier::setCameraTargetX(int value)
+{
+ // Value is (-100, 100), normalize
+ m_cameraTarget.setX(float(value) / 100.0f);
+ m_graph->scene()->activeCamera()->setTarget(m_cameraTarget);
+ qDebug() << "m_cameraTarget:" << m_cameraTarget;
+}
+
+void GraphModifier::setCameraTargetY(int value)
+{
+ // Value is (-100, 100), normalize
+ m_cameraTarget.setY(float(value) / 100.0f);
+ m_graph->scene()->activeCamera()->setTarget(m_cameraTarget);
+ qDebug() << "m_cameraTarget:" << m_cameraTarget;
+}
+
+void GraphModifier::setCameraTargetZ(int value)
+{
+ // Value is (-100, 100), normalize
+ m_cameraTarget.setZ(float(value) / 100.0f);
+ m_graph->scene()->activeCamera()->setTarget(m_cameraTarget);
+ qDebug() << "m_cameraTarget:" << m_cameraTarget;
+}
+
+void GraphModifier::setFloorLevel(int value)
+{
+ m_graph->setFloorLevel(float(value));
+ qDebug() << "Floor level:" << value;
+}
+
+void GraphModifier::setGraphMargin(int value)
+{
+ m_graph->setMargin(qreal(value) / 100.0);
+ qDebug() << "Setting margin:" << m_graph->margin() << value;
+}
+
+void GraphModifier::populateFlatSeries(QBar3DSeries *series, int rows, int columns, float value)
+{
+ QBarDataArray *dataArray = new QBarDataArray;
+ dataArray->reserve(rows);
+ for (int i = 0; i < rows; i++) {
+ QBarDataRow *dataRow = new QBarDataRow(columns);
+ for (int j = 0; j < columns; j++)
+ (*dataRow)[j].setValue(value);
+ dataArray->append(dataRow);
+ }
+ QStringList axisLabels;
+ int count = qMax(rows, columns);
+ for (int i = 0; i < count; i++)
+ axisLabels << QString::number(i);
+
+ series->dataProxy()->resetArray(dataArray, axisLabels, axisLabels);
+}
+
+QBarDataRow *GraphModifier::createFlatRow(int columns, float value)
+{
+ QBarDataRow *dataRow = new QBarDataRow(columns);
+ for (int j = 0; j < columns; j++)
+ (*dataRow)[j].setValue(value);
+ return dataRow;
+}
+
+void GraphModifier::setBackgroundEnabled(int enabled)
+{
+ m_graph->activeTheme()->setBackgroundEnabled(bool(enabled));
+}
+
+void GraphModifier::setGridEnabled(int enabled)
+{
+ m_graph->activeTheme()->setGridEnabled(bool(enabled));
+}
+
+void GraphModifier::rotateX(int rotation)
+{
+ m_xRotation = rotation;
+ m_graph->scene()->activeCamera()->setCameraPosition(m_xRotation, m_yRotation);
+}
+
+void GraphModifier::rotateY(int rotation)
+{
+ m_yRotation = rotation;
+ m_graph->scene()->activeCamera()->setCameraPosition(m_xRotation, m_yRotation);
+}
+
+void GraphModifier::setFpsMeasurement(int enable)
+{
+ m_graph->setMeasureFps(enable);
+}
+
+void GraphModifier::setSpecsRatio(int barwidth)
+{
+ m_graph->setBarThickness((float)barwidth / 30.0f);
+}
+
+void GraphModifier::setSpacingSpecsX(int spacing)
+{
+ m_barSpacingX = (float)spacing / 100.0f;
+ m_graph->setBarSpacing(QSizeF(m_barSpacingX, m_barSpacingZ));
+}
+
+void GraphModifier::setSpacingSpecsZ(int spacing)
+{
+ m_barSpacingZ = (float)spacing / 100.0f;
+ m_graph->setBarSpacing(QSizeF(m_barSpacingX, m_barSpacingZ));
+}
+
+void GraphModifier::setMarginX(int margin)
+{
+ m_barSeriesMarginX = (float)margin / 100.0f;
+ m_graph->setBarSeriesMargin(QSizeF(m_barSeriesMarginX, m_barSeriesMarginZ));
+}
+
+void GraphModifier::setMarginZ(int margin)
+{
+ m_barSeriesMarginZ = (float)margin / 100.0f;
+ m_graph->setBarSeriesMargin(QSizeF(m_barSeriesMarginX, m_barSeriesMarginZ));
+}
+
+void GraphModifier::setSampleCountX(int samples)
+{
+ m_columnCount = samples;
+ m_genericColumnAxis->setRange(m_genericRowAxis->min(), m_genericRowAxis->min() + samples - 1);
+}
+
+void GraphModifier::setSampleCountZ(int samples)
+{
+ m_rowCount = samples;
+ m_genericRowAxis->setRange(m_genericColumnAxis->min(), m_genericColumnAxis->min() + samples - 1);
+}
+
+void GraphModifier::setMinX(int min)
+{
+ m_genericRowAxis->setRange(min, min + m_rowCount - 1);
+}
+
+void GraphModifier::setMinZ(int min)
+{
+ m_genericColumnAxis->setRange(min, min + m_rowCount - 1);
+}
+
+void GraphModifier::setMinY(int min)
+{
+ m_fixedRangeAxis->setMin(min);
+ m_negativeValuesOn = (min < 0) ? true : false;
+ m_minval = min;
+}
+
+void GraphModifier::setMaxY(int max)
+{
+ m_fixedRangeAxis->setMax(max);
+ m_maxval = max;
+}
+
+void GraphModifier::changeColorStyle(bool checked)
+{
+ Q_UNUSED(checked)
+ int style = m_graph->activeTheme()->colorStyle();
+
+ if (++style > Q3DTheme::ColorStyleRangeGradient)
+ style = Q3DTheme::ColorStyleUniform;
+
+ m_graph->activeTheme()->setColorStyle(Q3DTheme::ColorStyle(style));
+}
+
+void GraphModifier::useOwnTheme(bool checked)
+{
+ Q_UNUSED(checked)
+ // Own theme is persistent, any changes to it via UI will be remembered
+ if (!m_ownTheme) {
+ m_ownTheme = new Q3DTheme();
+ m_ownTheme->setBackgroundEnabled(true);
+ m_ownTheme->setGridEnabled(true);
+ m_ownTheme->setAmbientLightStrength(0.3f);
+ m_ownTheme->setBackgroundColor(QColor(QRgb(0x99ca53)));
+ QList<QColor> colors;
+ colors.append(QColor(QRgb(0x209fdf)));
+ m_ownTheme->setBaseColors(colors);
+ m_ownTheme->setColorStyle(Q3DTheme::ColorStyleUniform);
+ m_ownTheme->setGridLineColor(QColor(QRgb(0x99ca53)));
+ m_ownTheme->setHighlightLightStrength(7.0f);
+ m_ownTheme->setLabelBackgroundEnabled(true);
+ m_ownTheme->setLabelBorderEnabled(true);
+ m_ownTheme->setLightColor(Qt::white);
+ m_ownTheme->setLightStrength(6.0f);
+ m_ownTheme->setMultiHighlightColor(QColor(QRgb(0x6d5fd5)));
+ m_ownTheme->setSingleHighlightColor(QColor(QRgb(0xf6a625)));
+ m_ownTheme->setLabelBackgroundColor(QColor(0xf6, 0xa6, 0x25, 0xa0));
+ m_ownTheme->setLabelTextColor(QColor(QRgb(0x404044)));
+ m_ownTheme->setWindowColor(QColor(QRgb(0xffffff)));
+ }
+
+ m_graph->setActiveTheme(m_ownTheme);
+
+ m_colorDialog->open();
+}
+
+void GraphModifier::changeBaseColor(const QColor &color)
+{
+ qDebug() << "base color changed to" << color;
+ QList<QColor> colors;
+ colors.append(color);
+ m_graph->activeTheme()->setBaseColors(colors);
+}
+
+void GraphModifier::setGradient(bool checked)
+{
+ Q_UNUSED(checked)
+ QLinearGradient barGradient(0, 0, 1, 100);
+ barGradient.setColorAt(1.0, Qt::lightGray);
+ barGradient.setColorAt(0.75001, Qt::lightGray);
+ barGradient.setColorAt(0.75, Qt::blue);
+ barGradient.setColorAt(0.50001, Qt::blue);
+ barGradient.setColorAt(0.50, Qt::red);
+ barGradient.setColorAt(0.25001, Qt::red);
+ barGradient.setColorAt(0.25, Qt::yellow);
+ barGradient.setColorAt(0.0, Qt::yellow);
+
+ QLinearGradient singleHighlightGradient(0, 0, 1, 100);
+ singleHighlightGradient.setColorAt(1.0, Qt::white);
+ singleHighlightGradient.setColorAt(0.75, Qt::lightGray);
+ singleHighlightGradient.setColorAt(0.50, Qt::gray);
+ singleHighlightGradient.setColorAt(0.25, Qt::darkGray);
+ singleHighlightGradient.setColorAt(0.0, Qt::black);
+
+ QLinearGradient multiHighlightGradient(0, 0, 1, 100);
+ multiHighlightGradient.setColorAt(1.0, Qt::black);
+ multiHighlightGradient.setColorAt(0.75, Qt::darkBlue);
+ multiHighlightGradient.setColorAt(0.50, Qt::darkRed);
+ multiHighlightGradient.setColorAt(0.25, Qt::darkYellow);
+ multiHighlightGradient.setColorAt(0.0, Qt::darkGray);
+
+ QList<QLinearGradient> barGradients;
+ barGradients.append(barGradient);
+ m_graph->activeTheme()->setBaseGradients(barGradients);
+ m_graph->activeTheme()->setSingleHighlightGradient(singleHighlightGradient);
+ m_graph->activeTheme()->setMultiHighlightGradient(multiHighlightGradient);
+
+ m_graph->activeTheme()->setColorStyle(Q3DTheme::ColorStyleObjectGradient);
+}
+
+void GraphModifier::toggleMultiseriesScaling()
+{
+ m_graph->setMultiSeriesUniform(!m_graph->isMultiSeriesUniform());
+}
+
+void GraphModifier::setReflection(int enabled)
+{
+ m_graph->setReflection(enabled);
+}
+
+void GraphModifier::setReflectivity(int value)
+{
+ qreal reflectivity = (qreal)value / 100.0;
+ m_graph->setReflectivity(reflectivity);
+}
+
+void GraphModifier::toggleCustomItem()
+{
+ static int counter = 0;
+ int state = ++counter % 3;
+
+ QVector3D positionOne = QVector3D(6.0f, -15.0f, 3.0f);
+ QVector3D positionTwo = QVector3D(2.0f, 18.0f, 3.0f);
+
+ if (state == 0) {
+ m_graph->removeCustomItemAt(positionTwo);
+ } else if (state == 1) {
+ QCustom3DItem *item = new QCustom3DItem();
+ item->setMeshFile(":/shuttle.obj");
+ item->setPosition(positionOne);
+ item->setScaling(QVector3D(0.1f, 0.1f, 0.1f));
+ item->setRotation(QQuaternion::fromAxisAndAngle(0.0f, 1.0f, 0.0f, QRandomGenerator::global()->generate()));
+ item->setTextureImage(QImage(":/shuttle.png"));
+ m_graph->addCustomItem(item);
+ } else {
+ m_graph->removeCustomItemAt(positionOne);
+ QCustom3DItem *item = new QCustom3DItem();
+ item->setMeshFile(":/shuttle.obj");
+ item->setPosition(positionTwo);
+ item->setScaling(QVector3D(0.1f, 0.1f, 0.1f));
+ item->setRotation(QQuaternion::fromAxisAndAngle(0.0f, 1.0f, 0.0f, QRandomGenerator::global()->generate()));
+ item->setTextureImage(QImage(":/shuttle.png"));
+ m_graph->addCustomItem(item);
+ }
+}
diff --git a/tests/manual/barstest/chart.h b/tests/manual/barstest/chart.h
new file mode 100644
index 0000000..98e5a7f
--- /dev/null
+++ b/tests/manual/barstest/chart.h
@@ -0,0 +1,174 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef CHARTMODIFIER_H
+#define CHARTMODIFIER_H
+
+#include <QtGraphs/q3dbars.h>
+#include <QtGraphs/q3dinputhandler.h>
+#include <QtGraphs/qbar3dseries.h>
+#include <QtGraphs/q3dtheme.h>
+#include <QFont>
+#include <QDebug>
+#include <QStringList>
+#include <QPointer>
+#include <QColorDialog>
+#include <QTimer>
+#include <QLabel>
+
+class GraphModifier : public QObject
+{
+ Q_OBJECT
+public:
+ explicit GraphModifier(Q3DBars *barchart, QColorDialog *colorDialog);
+ ~GraphModifier();
+
+ void resetTemperatureData();
+ void addRow();
+ void addRows();
+ void insertRow();
+ void insertRows();
+ void changeItem();
+ void changeRow();
+ void changeRows();
+ void removeRow();
+ void removeRows();
+ void changeStyle();
+ void changePresetCamera();
+ void changeTheme();
+ void changeLabelStyle();
+ void changeSelectionMode();
+ void changeFont(const QFont &font);
+ void changeFontSize(int fontsize);
+ void rotateX(int rotation);
+ void rotateY(int rotation);
+ void setFpsMeasurement(int state);
+ void setBackgroundEnabled(int enabled);
+ void setGridEnabled(int enabled);
+ void setSpecsRatio(int barwidth);
+ void setSpecsZ(int bardepth);
+ void setSpacingSpecsX(int spacing);
+ void setSpacingSpecsZ(int spacing);
+ void setMarginX(int margin);
+ void setMarginZ(int margin);
+ void setSampleCountX(int samples);
+ void setSampleCountZ(int samples);
+ void setMinX(int min);
+ void setMinZ(int min);
+ void setMinY(int min);
+ void setMaxY(int max);
+ void start();
+ void restart(int dynamicData);
+ void selectBar();
+ void swapAxis();
+ void releaseAxes();
+ void releaseSeries();
+ void createMassiveArray();
+ void useOwnTheme(bool checked);
+ void changeBaseColor(const QColor &color);
+ void changeColorStyle(bool checked);
+ void showFiveSeries();
+ QBarDataArray *makeDummyData();
+ void primarySeriesTest(bool checked);
+ void insertRemoveTestToggle();
+ void toggleRotation(bool checked);
+ void useLogAxis(bool checked);
+ void changeValueAxisFormat(const QString & text);
+ void changeLogBase(const QString & text);
+ void setFpsLabel(QLabel *fpsLabel) { m_fpsLabel = fpsLabel; }
+ void addRemoveSeries();
+ void testItemAndRowChanges(bool checked);
+ void reverseValueAxis(int enabled);
+ void setInputHandlerRotationEnabled(int enabled);
+ void setInputHandlerZoomEnabled(int enabled);
+ void setInputHandlerSelectionEnabled(int enabled);
+ void setInputHandlerZoomAtTargetEnabled(int enabled);
+ void setReflection(int enabled);
+ void setReflectivity(int value);
+ void toggleCustomItem();
+
+public Q_SLOTS:
+ void flipViews(bool checked);
+ void setGradient(bool checked);
+ void toggleMultiseriesScaling();
+ void changeShadowQuality(int quality);
+ void shadowQualityUpdatedByVisual(QAbstract3DGraph::ShadowQuality shadowQuality);
+ void handleSelectionChange(const QPoint &position);
+ void setUseNullInputHandler(int useNull);
+ void changeValueAxisSegments(int value);
+
+ void handleRowAxisChanged(QCategory3DAxis *axis);
+ void handleColumnAxisChanged(QCategory3DAxis *axis);
+ void handleValueAxisChanged(QValue3DAxis *axis);
+ void handlePrimarySeriesChanged(QBar3DSeries *series);
+
+ void insertRemoveTimerTimeout();
+ void triggerSelection();
+ void triggerRotation();
+ void handleValueAxisLabelsChanged();
+ void handleFpsChange(qreal fps);
+ void setCameraTargetX(int value);
+ void setCameraTargetY(int value);
+ void setCameraTargetZ(int value);
+ void setFloorLevel(int value);
+ void setGraphMargin(int value);
+
+Q_SIGNALS:
+ void shadowQualityChanged(int quality);
+
+private:
+ void populateFlatSeries(QBar3DSeries *series, int rows, int columns, float value);
+ QBarDataRow *createFlatRow(int columns, float value);
+
+ Q3DBars *m_graph;
+ QColorDialog *m_colorDialog;
+ int m_columnCount;
+ int m_rowCount;
+ float m_xRotation;
+ float m_yRotation;
+ int m_static;
+ float m_barSpacingX;
+ float m_barSpacingZ;
+ float m_barSeriesMarginX;
+ float m_barSeriesMarginZ;
+ int m_fontSize;
+ int m_segments;
+ int m_subSegments;
+ float m_minval;
+ float m_maxval;
+ QStringList m_months;
+ QStringList m_years;
+ QPoint m_selectedBar;
+ QBar3DSeries *m_selectedSeries;
+ QValue3DAxis *m_autoAdjustingAxis;
+ QValue3DAxis *m_fixedRangeAxis;
+ QValue3DAxis *m_temperatureAxis;
+ QCategory3DAxis *m_yearAxis;
+ QCategory3DAxis *m_monthAxis;
+ QCategory3DAxis *m_genericRowAxis;
+ QCategory3DAxis *m_genericColumnAxis;
+ QBar3DSeries *m_temperatureData;
+ QBar3DSeries *m_temperatureData2;
+ QBar3DSeries *m_genericData;
+ QBar3DSeries *m_dummyData;
+ QBar3DSeries *m_dummyData2;
+ QBar3DSeries *m_dummyData3;
+ QBar3DSeries *m_dummyData4;
+ QBar3DSeries *m_dummyData5;
+ QValue3DAxis *m_currentAxis;
+ bool m_negativeValuesOn;
+ bool m_useNullInputHandler;
+ Q3DInputHandler *m_defaultInputHandler;
+ Q3DTheme *m_ownTheme;
+ Q3DTheme *m_builtinTheme;
+ QTimer m_insertRemoveTimer;
+ int m_insertRemoveStep;
+ QAbstract3DInputHandler *m_customInputHandler;
+ QTimer m_selectionTimer;
+ QTimer m_rotationTimer;
+ QLabel *m_fpsLabel;
+ QBar3DSeries *m_extraSeries;
+ QVector3D m_cameraTarget;
+};
+
+#endif
diff --git a/tests/manual/barstest/custominputhandler.cpp b/tests/manual/barstest/custominputhandler.cpp
new file mode 100644
index 0000000..4f9d1f9
--- /dev/null
+++ b/tests/manual/barstest/custominputhandler.cpp
@@ -0,0 +1,39 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "custominputhandler.h"
+
+#include <QtGraphs/Q3DCamera>
+
+CustomInputHandler::CustomInputHandler(QObject *parent) :
+ QAbstract3DInputHandler(parent)
+{
+}
+
+//! [0]
+void CustomInputHandler::mouseMoveEvent(QMouseEvent *event, const QPoint &mousePos)
+{
+ Q_UNUSED(event);
+ setInputPosition(mousePos);
+}
+//! [0]
+
+//! [1]
+void CustomInputHandler::wheelEvent(QWheelEvent *event)
+{
+ // Adjust zoom level based on what zoom range we're in.
+ int zoomLevel = scene()->activeCamera()->zoomLevel();
+ if (zoomLevel > 100)
+ zoomLevel += event->angleDelta().y() / 12;
+ else if (zoomLevel > 50)
+ zoomLevel += event->angleDelta().y() / 60;
+ else
+ zoomLevel += event->angleDelta().y() / 120;
+ if (zoomLevel > 500)
+ zoomLevel = 500;
+ else if (zoomLevel < 10)
+ zoomLevel = 10;
+
+ scene()->activeCamera()->setZoomLevel(zoomLevel);
+}
+//! [1]
diff --git a/tests/manual/barstest/custominputhandler.h b/tests/manual/barstest/custominputhandler.h
new file mode 100644
index 0000000..bc3e319
--- /dev/null
+++ b/tests/manual/barstest/custominputhandler.h
@@ -0,0 +1,19 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef CUSTOMINPUTHANDLER_H
+#define CUSTOMINPUTHANDLER_H
+
+#include <QtGraphs/QAbstract3DInputHandler>
+
+class CustomInputHandler : public QAbstract3DInputHandler
+{
+ Q_OBJECT
+public:
+ explicit CustomInputHandler(QObject *parent = 0);
+
+ virtual void mouseMoveEvent(QMouseEvent *event, const QPoint &mousePos) override;
+ virtual void wheelEvent(QWheelEvent *event) override;
+};
+
+#endif
diff --git a/tests/manual/barstest/main.cpp b/tests/manual/barstest/main.cpp
new file mode 100644
index 0000000..f22fdd9
--- /dev/null
+++ b/tests/manual/barstest/main.cpp
@@ -0,0 +1,674 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "chart.h"
+#include "sliderwrapper.h"
+#include "buttonwrapper.h"
+
+#include <QApplication>
+#include <QWidget>
+#include <QHBoxLayout>
+#include <QVBoxLayout>
+#include <QPushButton>
+#include <QCheckBox>
+#include <QSlider>
+#include <QFontComboBox>
+#include <QLabel>
+#include <QScreen>
+#include <QFontDatabase>
+#include <QLinearGradient>
+#include <QPainter>
+#include <QColorDialog>
+#include <QLineEdit>
+#include <QSpinBox>
+#include <QtGui/QOpenGLContext>
+#include <QtGraphs/QCustom3DItem>
+#include <QtGraphs/QCustom3DLabel>
+#include <QtGraphs/QCustom3DVolume>
+
+int main(int argc, char **argv)
+{
+ qputenv("QSG_RHI_BACKEND", "opengl");
+ QApplication app(argc, argv);
+
+ // Test creating custom items before graph is created
+ QCustom3DItem customItem;
+ QCustom3DLabel customLabel;
+ QCustom3DVolume customVolume;
+
+ QWidget *widget = new QWidget;
+ QHBoxLayout *hLayout = new QHBoxLayout(widget);
+ QVBoxLayout *vLayout = new QVBoxLayout();
+ QVBoxLayout *vLayout2 = new QVBoxLayout();
+ QVBoxLayout *vLayout3 = new QVBoxLayout();
+
+ // For testing custom surface format
+ QSurfaceFormat surfaceFormat;
+ surfaceFormat.setDepthBufferSize(24);
+ surfaceFormat.setSamples(8);
+
+ Q3DBars *widgetchart = new Q3DBars(&surfaceFormat);
+ QSize screenSize = widgetchart->screen()->size();
+
+ QWidget *container = QWidget::createWindowContainer(widgetchart);
+ container->setMinimumSize(QSize(screenSize.width() / 3, screenSize.height() / 3));
+ container->setMaximumSize(screenSize);
+ container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+ container->setFocusPolicy(Qt::StrongFocus);
+
+ widget->setWindowTitle(QStringLiteral("Average temperatures in Oulu, Finland (2006-2012)"));
+
+ hLayout->addWidget(container, 1);
+ hLayout->addLayout(vLayout);
+ hLayout->addLayout(vLayout2);
+ hLayout->addLayout(vLayout3);
+
+ QPushButton *addSeriesButton = new QPushButton(widget);
+ addSeriesButton->setText(QStringLiteral("Add / Remove a series"));
+ addSeriesButton->setEnabled(true);
+
+ QPushButton *addDataButton = new QPushButton(widget);
+ addDataButton->setText(QStringLiteral("Add a row of data"));
+ addDataButton->setEnabled(false);
+
+ QPushButton *addMultiDataButton = new QPushButton(widget);
+ addMultiDataButton->setText(QStringLiteral("Add many rows of data"));
+ addMultiDataButton->setEnabled(false);
+
+ QPushButton *insertDataButton = new QPushButton(widget);
+ insertDataButton->setText(QStringLiteral("Insert a row of data"));
+ insertDataButton->setEnabled(false);
+
+ QPushButton *insertMultiDataButton = new QPushButton(widget);
+ insertMultiDataButton->setText(QStringLiteral("Insert many rows of data"));
+ insertMultiDataButton->setEnabled(false);
+
+ QPushButton *changeSingleDataButton = new QPushButton(widget);
+ changeSingleDataButton->setText(QStringLiteral("Change selected bar value"));
+ changeSingleDataButton->setEnabled(false);
+
+ QPushButton *changeRowButton = new QPushButton(widget);
+ changeRowButton->setText(QStringLiteral("Change selected row values"));
+ changeRowButton->setEnabled(false);
+
+ QPushButton *changeRowsButton = new QPushButton(widget);
+ changeRowsButton->setText(QStringLiteral("Change three rows from selected"));
+ changeRowsButton->setEnabled(false);
+
+ QPushButton *removeRowButton = new QPushButton(widget);
+ removeRowButton->setText(QStringLiteral("Remove selected row"));
+ removeRowButton->setEnabled(false);
+
+ QPushButton *removeRowsButton = new QPushButton(widget);
+ removeRowsButton->setText(QStringLiteral("Remove three rows before selected"));
+ removeRowsButton->setEnabled(false);
+
+ QPushButton *massiveArrayButton = new QPushButton(widget);
+ massiveArrayButton->setText(QStringLiteral("Create massive array"));
+ massiveArrayButton->setEnabled(false);
+
+ QPushButton *themeButton = new QPushButton(widget);
+ themeButton->setText(QStringLiteral("Change theme"));
+
+ QPushButton *labelButton = new QPushButton(widget);
+ labelButton->setText(QStringLiteral("Change label style"));
+
+ QPushButton *multiScaleButton = new QPushButton(widget);
+ multiScaleButton->setText(QStringLiteral("Change multiseries scaling"));
+
+ QPushButton *styleButton = new QPushButton(widget);
+ styleButton->setText(QStringLiteral("Change bar style"));
+
+ QPushButton *cameraButton = new QPushButton(widget);
+ cameraButton->setText(QStringLiteral("Change camera preset"));
+
+ QPushButton *selectionButton = new QPushButton(widget);
+ selectionButton->setText(QStringLiteral("Change selection mode"));
+
+ QPushButton *setSelectedBarButton = new QPushButton(widget);
+ setSelectedBarButton->setText(QStringLiteral("Select/deselect bar at (5,5)"));
+
+ QPushButton *swapAxisButton = new QPushButton(widget);
+ swapAxisButton->setText(QStringLiteral("Swap value axis"));
+ swapAxisButton->setEnabled(false);
+
+ QPushButton *insertRemoveTestButton = new QPushButton(widget);
+ insertRemoveTestButton->setText(QStringLiteral("Toggle insert/remove cycle"));
+ insertRemoveTestButton->setEnabled(true);
+
+ QPushButton *releaseAxesButton = new QPushButton(widget);
+ releaseAxesButton->setText(QStringLiteral("Release all axes"));
+ releaseAxesButton->setEnabled(true);
+
+ QPushButton *releaseProxiesButton = new QPushButton(widget);
+ releaseProxiesButton->setText(QStringLiteral("Release all proxies"));
+ releaseProxiesButton->setEnabled(true);
+
+ QPushButton *flipViewsButton = new QPushButton(widget);
+ flipViewsButton->setText(QStringLiteral("Flip views"));
+ flipViewsButton->setEnabled(true);
+
+ QPushButton *showFiveSeriesButton = new QPushButton(widget);
+ showFiveSeriesButton->setText(QStringLiteral("Try 5 series"));
+ showFiveSeriesButton->setEnabled(true);
+
+ QPushButton *changeColorStyleButton = new QPushButton(widget);
+ changeColorStyleButton->setText(QStringLiteral("Change color style"));
+ changeColorStyleButton->setEnabled(true);
+
+ QPushButton *ownThemeButton = new QPushButton(widget);
+ ownThemeButton->setText(QStringLiteral("Use own theme"));
+ ownThemeButton->setEnabled(true);
+
+ QPushButton *primarySeriesTestsButton = new QPushButton(widget);
+ primarySeriesTestsButton->setText(QStringLiteral("Test primary series"));
+ primarySeriesTestsButton->setEnabled(true);
+
+ QPushButton *toggleRotationButton = new QPushButton(widget);
+ toggleRotationButton->setText(QStringLiteral("Toggle rotation"));
+ toggleRotationButton->setEnabled(true);
+
+ QPushButton *logAxisButton = new QPushButton(widget);
+ logAxisButton->setText(QStringLiteral("Use Log Axis"));
+ logAxisButton->setEnabled(true);
+
+ QPushButton *testItemAndRowChangesButton = new QPushButton(widget);
+ testItemAndRowChangesButton->setText(QStringLiteral("Test Item/Row changing"));
+ testItemAndRowChangesButton->setEnabled(true);
+
+ QColorDialog *colorDialog = new QColorDialog(widget);
+
+ QLinearGradient grBtoY(0, 0, 100, 0);
+ grBtoY.setColorAt(1.0, Qt::black);
+ grBtoY.setColorAt(0.67, Qt::blue);
+ grBtoY.setColorAt(0.33, Qt::red);
+ grBtoY.setColorAt(0.0, Qt::yellow);
+ QPixmap pm(100, 24);
+ QPainter pmp(&pm);
+ pmp.setBrush(QBrush(grBtoY));
+ pmp.setPen(Qt::NoPen);
+ pmp.drawRect(0, 0, 100, 24);
+ QPushButton *gradientBtoYPB = new QPushButton(widget);
+ gradientBtoYPB->setIcon(QIcon(pm));
+ gradientBtoYPB->setIconSize(QSize(100, 24));
+
+ QLabel *fpsLabel = new QLabel(QStringLiteral(""));
+
+ QCheckBox *fpsCheckBox = new QCheckBox(widget);
+ fpsCheckBox->setText(QStringLiteral("Measure Fps"));
+ fpsCheckBox->setChecked(false);
+
+ QCheckBox *reverseValueAxisCheckBox = new QCheckBox(widget);
+ reverseValueAxisCheckBox->setText(QStringLiteral("Reverse value axis"));
+ reverseValueAxisCheckBox->setChecked(false);
+
+ QCheckBox *backgroundCheckBox = new QCheckBox(widget);
+ backgroundCheckBox->setText(QStringLiteral("Show background"));
+ backgroundCheckBox->setChecked(true);
+
+ QCheckBox *gridCheckBox = new QCheckBox(widget);
+ gridCheckBox->setText(QStringLiteral("Show grid"));
+ gridCheckBox->setChecked(true);
+
+ QCheckBox *rotationCheckBox = new QCheckBox(widget);
+ rotationCheckBox->setText("Rotate with slider");
+
+ QCheckBox *staticCheckBox = new QCheckBox(widget);
+ staticCheckBox->setText("Use dynamic data");
+ staticCheckBox->setChecked(false);
+
+ QCheckBox *inputHandlerRotationCheckBox = new QCheckBox(widget);
+ inputHandlerRotationCheckBox->setText("IH: Allow rotation");
+ inputHandlerRotationCheckBox->setChecked(true);
+
+ QCheckBox *inputHandlerZoomCheckBox = new QCheckBox(widget);
+ inputHandlerZoomCheckBox->setText("IH: Allow zoom");
+ inputHandlerZoomCheckBox->setChecked(true);
+
+ QCheckBox *inputHandlerSelectionCheckBox = new QCheckBox(widget);
+ inputHandlerSelectionCheckBox->setText("IH: Allow selection");
+ inputHandlerSelectionCheckBox->setChecked(true);
+
+ QCheckBox *inputHandlerZoomAtTargetCheckBox = new QCheckBox(widget);
+ inputHandlerZoomAtTargetCheckBox->setText("IH: setZoomAtTarget");
+ inputHandlerZoomAtTargetCheckBox->setChecked(true);
+
+ QSlider *rotationSliderX = new QSlider(Qt::Horizontal, widget);
+ rotationSliderX->setTickInterval(1);
+ rotationSliderX->setMinimum(-180);
+ rotationSliderX->setValue(0);
+ rotationSliderX->setMaximum(180);
+ rotationSliderX->setEnabled(false);
+ QSlider *rotationSliderY = new QSlider(Qt::Horizontal, widget);
+ rotationSliderY->setTickInterval(1);
+ rotationSliderY->setMinimum(0);
+ rotationSliderY->setValue(0);
+ rotationSliderY->setMaximum(90);
+ rotationSliderY->setEnabled(false);
+
+ QSlider *ratioSlider = new QSlider(Qt::Horizontal, widget);
+ ratioSlider->setTickInterval(1);
+ ratioSlider->setMinimum(10);
+ ratioSlider->setValue(30);
+ ratioSlider->setMaximum(100);
+
+ QCheckBox *reflectionCheckBox = new QCheckBox(widget);
+ reflectionCheckBox->setText(QStringLiteral("Show reflections"));
+ reflectionCheckBox->setChecked(false);
+
+ QSlider *reflectivitySlider = new QSlider(Qt::Horizontal, widget);
+ reflectivitySlider->setMinimum(0);
+ reflectivitySlider->setValue(50);
+ reflectivitySlider->setMaximum(100);
+
+ QSlider *floorLevelSlider = new QSlider(Qt::Horizontal, widget);
+ floorLevelSlider->setMinimum(-50);
+ floorLevelSlider->setValue(0);
+ floorLevelSlider->setMaximum(50);
+
+ QPushButton *toggleCustomItemButton = new QPushButton(widget);
+ toggleCustomItemButton->setText(QStringLiteral("Toggle Custom Item"));
+
+ QSlider *spacingSliderX = new QSlider(Qt::Horizontal, widget);
+ spacingSliderX->setTickInterval(1);
+ spacingSliderX->setMinimum(0);
+ spacingSliderX->setValue(10);
+ spacingSliderX->setMaximum(200);
+ QSlider *spacingSliderZ = new QSlider(Qt::Horizontal, widget);
+ spacingSliderZ->setTickInterval(1);
+ spacingSliderZ->setMinimum(0);
+ spacingSliderZ->setValue(10);
+ spacingSliderZ->setMaximum(200);
+
+ QSlider *marginSliderX = new QSlider(Qt::Horizontal, widget);
+ marginSliderX->setTickInterval(1);
+ marginSliderX->setMinimum(0);
+ marginSliderX->setValue(0);
+ marginSliderX->setMaximum(100);
+
+ QSlider *marginSliderZ = new QSlider(Qt::Horizontal, widget);
+ marginSliderZ->setTickInterval(1);
+ marginSliderZ->setMinimum(0);
+ marginSliderZ->setValue(0);
+ marginSliderZ->setMaximum(100);
+
+ QSlider *sampleSliderX = new QSlider(Qt::Horizontal, widget);
+ sampleSliderX->setTickInterval(1);
+ sampleSliderX->setMinimum(1);
+ sampleSliderX->setValue(21);
+ sampleSliderX->setMaximum(200);
+ sampleSliderX->setEnabled(false);
+ QSlider *sampleSliderZ = new QSlider(Qt::Horizontal, widget);
+ sampleSliderZ->setTickInterval(1);
+ sampleSliderZ->setMinimum(1);
+ sampleSliderZ->setValue(21);
+ sampleSliderZ->setMaximum(200);
+ sampleSliderZ->setEnabled(false);
+
+ QSlider *minSliderX = new QSlider(Qt::Horizontal, widget);
+ minSliderX->setTickInterval(10);
+ minSliderX->setTickPosition(QSlider::TicksBelow);
+ minSliderX->setMinimum(0);
+ minSliderX->setValue(0);
+ minSliderX->setMaximum(200);
+ minSliderX->setEnabled(false);
+ QSlider *minSliderZ = new QSlider(Qt::Horizontal, widget);
+ minSliderZ->setTickInterval(10);
+ minSliderZ->setTickPosition(QSlider::TicksAbove);
+ minSliderZ->setMinimum(0);
+ minSliderZ->setValue(0);
+ minSliderZ->setMaximum(200);
+ minSliderZ->setEnabled(false);
+ QSlider *minSliderY = new QSlider(Qt::Horizontal, widget);
+ minSliderY->setTickInterval(10);
+ minSliderY->setTickPosition(QSlider::TicksBelow);
+ minSliderY->setMinimum(-100);
+ minSliderY->setValue(0);
+ minSliderY->setMaximum(100);
+ minSliderY->setEnabled(false);
+ QSlider *maxSliderY = new QSlider(Qt::Horizontal, widget);
+ maxSliderY->setTickInterval(10);
+ maxSliderY->setTickPosition(QSlider::TicksAbove);
+ maxSliderY->setMinimum(-50);
+ maxSliderY->setValue(100);
+ maxSliderY->setMaximum(200);
+ maxSliderY->setEnabled(false);
+
+ QSlider *fontSizeSlider = new QSlider(Qt::Horizontal, widget);
+ fontSizeSlider->setTickInterval(1);
+ fontSizeSlider->setMinimum(1);
+ fontSizeSlider->setValue(20);
+ fontSizeSlider->setMaximum(100);
+
+ QFontComboBox *fontList = new QFontComboBox(widget);
+
+ QComboBox *shadowQuality = new QComboBox(widget);
+ shadowQuality->addItem(QStringLiteral("None"));
+ shadowQuality->addItem(QStringLiteral("Low"));
+ shadowQuality->addItem(QStringLiteral("Medium"));
+ shadowQuality->addItem(QStringLiteral("High"));
+ shadowQuality->addItem(QStringLiteral("Low Soft"));
+ shadowQuality->addItem(QStringLiteral("Medium Soft"));
+ shadowQuality->addItem(QStringLiteral("High Soft"));
+ shadowQuality->setCurrentIndex(5);
+
+ QLineEdit *valueAxisFormatEdit = new QLineEdit(widget);
+ QLineEdit *logBaseEdit = new QLineEdit(widget);
+ QSpinBox *valueAxisSegmentsSpin = new QSpinBox(widget);
+ valueAxisSegmentsSpin->setMinimum(1);
+ valueAxisSegmentsSpin->setMaximum(100);
+ valueAxisSegmentsSpin->setValue(10);
+
+ QSlider *cameraTargetSliderX = new QSlider(Qt::Horizontal, widget);
+ cameraTargetSliderX->setTickInterval(1);
+ cameraTargetSliderX->setMinimum(-100);
+ cameraTargetSliderX->setValue(0);
+ cameraTargetSliderX->setMaximum(100);
+ QSlider *cameraTargetSliderY = new QSlider(Qt::Horizontal, widget);
+ cameraTargetSliderY->setTickInterval(1);
+ cameraTargetSliderY->setMinimum(-100);
+ cameraTargetSliderY->setValue(0);
+ cameraTargetSliderY->setMaximum(100);
+ QSlider *cameraTargetSliderZ = new QSlider(Qt::Horizontal, widget);
+ cameraTargetSliderZ->setTickInterval(1);
+ cameraTargetSliderZ->setMinimum(-100);
+ cameraTargetSliderZ->setValue(0);
+ cameraTargetSliderZ->setMaximum(100);
+
+ QSlider *marginSlider = new QSlider(Qt::Horizontal, widget);
+ marginSlider->setMinimum(-1);
+ marginSlider->setValue(-1);
+ marginSlider->setMaximum(100);
+
+ vLayout->addWidget(addSeriesButton, 0, Qt::AlignTop);
+ vLayout->addWidget(addDataButton, 0, Qt::AlignTop);
+ vLayout->addWidget(addMultiDataButton, 0, Qt::AlignTop);
+ vLayout->addWidget(insertDataButton, 0, Qt::AlignTop);
+ vLayout->addWidget(insertMultiDataButton, 0, Qt::AlignTop);
+ vLayout->addWidget(changeSingleDataButton, 0, Qt::AlignTop);
+ vLayout->addWidget(changeRowButton, 0, Qt::AlignTop);
+ vLayout->addWidget(changeRowsButton, 0, Qt::AlignTop);
+ vLayout->addWidget(removeRowButton, 0, Qt::AlignTop);
+ vLayout->addWidget(removeRowsButton, 0, Qt::AlignTop);
+ vLayout->addWidget(massiveArrayButton, 0, Qt::AlignTop);
+ vLayout->addWidget(showFiveSeriesButton, 0, Qt::AlignTop);
+ vLayout->addWidget(themeButton, 0, Qt::AlignTop);
+ vLayout->addWidget(labelButton, 0, Qt::AlignTop);
+ vLayout->addWidget(multiScaleButton, 0, Qt::AlignTop);
+ vLayout->addWidget(styleButton, 0, Qt::AlignTop);
+ vLayout->addWidget(cameraButton, 0, Qt::AlignTop);
+ vLayout->addWidget(selectionButton, 0, Qt::AlignTop);
+ vLayout->addWidget(setSelectedBarButton, 0, Qt::AlignTop);
+ vLayout->addWidget(swapAxisButton, 0, Qt::AlignTop);
+ vLayout->addWidget(insertRemoveTestButton, 0, Qt::AlignTop);
+ vLayout->addWidget(releaseAxesButton, 0, Qt::AlignTop);
+ vLayout->addWidget(releaseProxiesButton, 1, Qt::AlignTop);
+
+ vLayout2->addWidget(flipViewsButton, 0, Qt::AlignTop);
+ vLayout2->addWidget(changeColorStyleButton, 0, Qt::AlignTop);
+ vLayout2->addWidget(ownThemeButton, 0, Qt::AlignTop);
+ vLayout2->addWidget(primarySeriesTestsButton, 0, Qt::AlignTop);
+ vLayout2->addWidget(toggleRotationButton, 0, Qt::AlignTop);
+ vLayout2->addWidget(gradientBtoYPB, 0, Qt::AlignTop);
+ vLayout2->addWidget(logAxisButton, 0, Qt::AlignTop);
+ vLayout2->addWidget(testItemAndRowChangesButton, 0, Qt::AlignTop);
+ vLayout2->addWidget(staticCheckBox, 0, Qt::AlignTop);
+ vLayout2->addWidget(rotationCheckBox, 0, Qt::AlignTop);
+ vLayout2->addWidget(rotationSliderX, 0, Qt::AlignTop);
+ vLayout2->addWidget(rotationSliderY, 0, Qt::AlignTop);
+ vLayout2->addWidget(new QLabel(QStringLiteral("Adjust relative bar size")), 0, Qt::AlignTop);
+ vLayout2->addWidget(ratioSlider, 0, Qt::AlignTop);
+ vLayout2->addWidget(new QLabel(QStringLiteral("Adjust relative bar spacing")), 0, Qt::AlignTop);
+ vLayout2->addWidget(spacingSliderX, 0, Qt::AlignTop);
+ vLayout2->addWidget(spacingSliderZ, 0, Qt::AlignTop);
+ vLayout2->addWidget(new QLabel(QStringLiteral("Adjust margin")), 0, Qt::AlignTop);
+ vLayout2->addWidget(marginSliderX, 0, Qt::AlignTop);
+ vLayout2->addWidget(marginSliderZ, 0, Qt::AlignTop);
+ vLayout2->addWidget(new QLabel(QStringLiteral("Adjust sample count")), 0, Qt::AlignTop);
+ vLayout2->addWidget(sampleSliderX, 0, Qt::AlignTop);
+ vLayout2->addWidget(sampleSliderZ, 0, Qt::AlignTop);
+ vLayout2->addWidget(new QLabel(QStringLiteral("Adjust data window minimums")), 0, Qt::AlignTop);
+ vLayout2->addWidget(minSliderX, 0, Qt::AlignTop);
+ vLayout2->addWidget(minSliderZ, 0, Qt::AlignTop);
+ vLayout2->addWidget(minSliderY, 0, Qt::AlignTop);
+ vLayout2->addWidget(maxSliderY, 1, Qt::AlignTop);
+
+ vLayout3->addWidget(fpsLabel, 0, Qt::AlignTop);
+ vLayout3->addWidget(fpsCheckBox, 0, Qt::AlignTop);
+ vLayout3->addWidget(reverseValueAxisCheckBox, 0, Qt::AlignTop);
+ vLayout3->addWidget(backgroundCheckBox, 0, Qt::AlignTop);
+ vLayout3->addWidget(gridCheckBox, 0, Qt::AlignTop);
+ vLayout3->addWidget(inputHandlerRotationCheckBox, 0, Qt::AlignTop);
+ vLayout3->addWidget(inputHandlerZoomCheckBox, 0, Qt::AlignTop);
+ vLayout3->addWidget(inputHandlerSelectionCheckBox, 0, Qt::AlignTop);
+ vLayout3->addWidget(inputHandlerZoomAtTargetCheckBox, 0, Qt::AlignTop);
+ vLayout3->addWidget(new QLabel(QStringLiteral("Adjust shadow quality")), 0, Qt::AlignTop);
+ vLayout3->addWidget(shadowQuality, 0, Qt::AlignTop);
+ vLayout3->addWidget(new QLabel(QStringLiteral("Change font")), 0, Qt::AlignTop);
+ vLayout3->addWidget(fontList, 0, Qt::AlignTop);
+ vLayout3->addWidget(new QLabel(QStringLiteral("Adjust font size")), 0, Qt::AlignTop);
+ vLayout3->addWidget(fontSizeSlider, 0, Qt::AlignTop);
+ vLayout3->addWidget(new QLabel(QStringLiteral("Value axis format")), 0, Qt::AlignTop);
+ vLayout3->addWidget(valueAxisFormatEdit, 0, Qt::AlignTop);
+ vLayout3->addWidget(new QLabel(QStringLiteral("Log axis base")), 0, Qt::AlignTop);
+ vLayout3->addWidget(logBaseEdit, 0, Qt::AlignTop);
+ vLayout3->addWidget(new QLabel(QStringLiteral("Value axis segments")), 0, Qt::AlignTop);
+ vLayout3->addWidget(valueAxisSegmentsSpin, 0, Qt::AlignTop);
+ vLayout3->addWidget(new QLabel(QStringLiteral("Camera target")), 0, Qt::AlignTop);
+ vLayout3->addWidget(cameraTargetSliderX, 0, Qt::AlignTop);
+ vLayout3->addWidget(cameraTargetSliderY, 0, Qt::AlignTop);
+ vLayout3->addWidget(cameraTargetSliderZ, 0, Qt::AlignTop);
+ vLayout3->addWidget(reflectionCheckBox, 0, Qt::AlignTop);
+ vLayout3->addWidget(reflectivitySlider, 0, Qt::AlignTop);
+ vLayout3->addWidget(toggleCustomItemButton, 0, Qt::AlignTop);
+ vLayout3->addWidget(new QLabel(QStringLiteral("Adjust floor level")), 0, Qt::AlignTop);
+ vLayout3->addWidget(floorLevelSlider, 0, Qt::AlignTop);
+ vLayout3->addWidget(new QLabel(QStringLiteral("Adjust bar series margin")), 0, Qt::AlignTop);
+ vLayout3->addWidget(marginSlider, 1, Qt::AlignTop);
+
+ widget->show();
+
+ GraphModifier *modifier = new GraphModifier(widgetchart, colorDialog);
+
+ QObject::connect(rotationSliderX, &QSlider::valueChanged, modifier, &GraphModifier::rotateX);
+ QObject::connect(rotationSliderY, &QSlider::valueChanged, modifier, &GraphModifier::rotateY);
+
+ QObject::connect(ratioSlider, &QSlider::valueChanged, modifier, &GraphModifier::setSpecsRatio);
+
+ QObject::connect(spacingSliderX, &QSlider::valueChanged, modifier,
+ &GraphModifier::setSpacingSpecsX);
+ QObject::connect(spacingSliderZ, &QSlider::valueChanged, modifier,
+ &GraphModifier::setSpacingSpecsZ);
+
+ QObject::connect(marginSliderX, &QSlider::valueChanged, modifier, &GraphModifier::setMarginX);
+ QObject::connect(marginSliderZ, &QSlider::valueChanged, modifier, &GraphModifier::setMarginZ);
+
+ QObject::connect(sampleSliderX, &QSlider::valueChanged, modifier,
+ &GraphModifier::setSampleCountX);
+ QObject::connect(sampleSliderZ, &QSlider::valueChanged, modifier,
+ &GraphModifier::setSampleCountZ);
+ QObject::connect(minSliderX, &QSlider::valueChanged, modifier,
+ &GraphModifier::setMinX);
+ QObject::connect(minSliderZ, &QSlider::valueChanged, modifier,
+ &GraphModifier::setMinZ);
+ QObject::connect(minSliderY, &QSlider::valueChanged, modifier,
+ &GraphModifier::setMinY);
+ QObject::connect(maxSliderY, &QSlider::valueChanged, modifier,
+ &GraphModifier::setMaxY);
+ QObject::connect(cameraTargetSliderX, &QSlider::valueChanged, modifier,
+ &GraphModifier::setCameraTargetX);
+ QObject::connect(cameraTargetSliderY, &QSlider::valueChanged, modifier,
+ &GraphModifier::setCameraTargetY);
+ QObject::connect(cameraTargetSliderZ, &QSlider::valueChanged, modifier,
+ &GraphModifier::setCameraTargetZ);
+
+ QObject::connect(shadowQuality, SIGNAL(currentIndexChanged(int)), modifier,
+ SLOT(changeShadowQuality(int)));
+ QObject::connect(modifier, &GraphModifier::shadowQualityChanged, shadowQuality,
+ &QComboBox::setCurrentIndex);
+ QObject::connect(fontSizeSlider, &QSlider::valueChanged, modifier,
+ &GraphModifier::changeFontSize);
+ QObject::connect(valueAxisFormatEdit, &QLineEdit::textEdited, modifier,
+ &GraphModifier::changeValueAxisFormat);
+ QObject::connect(logBaseEdit, &QLineEdit::textEdited, modifier,
+ &GraphModifier::changeLogBase);
+ QObject::connect(valueAxisSegmentsSpin, SIGNAL(valueChanged(int)), modifier,
+ SLOT(changeValueAxisSegments(int)));
+
+ QObject::connect(multiScaleButton, &QPushButton::clicked, modifier,
+ &GraphModifier::toggleMultiseriesScaling);
+ QObject::connect(styleButton, &QPushButton::clicked, modifier, &GraphModifier::changeStyle);
+ QObject::connect(cameraButton, &QPushButton::clicked, modifier,
+ &GraphModifier::changePresetCamera);
+ QObject::connect(themeButton, &QPushButton::clicked, modifier, &GraphModifier::changeTheme);
+ QObject::connect(labelButton, &QPushButton::clicked, modifier,
+ &GraphModifier::changeLabelStyle);
+ QObject::connect(addDataButton, &QPushButton::clicked, modifier, &GraphModifier::addRow);
+ QObject::connect(addSeriesButton, &QPushButton::clicked, modifier, &GraphModifier::addRemoveSeries);
+ QObject::connect(addMultiDataButton, &QPushButton::clicked, modifier, &GraphModifier::addRows);
+ QObject::connect(insertDataButton, &QPushButton::clicked, modifier, &GraphModifier::insertRow);
+ QObject::connect(insertMultiDataButton, &QPushButton::clicked, modifier, &GraphModifier::insertRows);
+ QObject::connect(changeSingleDataButton, &QPushButton::clicked, modifier, &GraphModifier::changeItem);
+ QObject::connect(changeRowButton, &QPushButton::clicked, modifier, &GraphModifier::changeRow);
+ QObject::connect(changeRowsButton, &QPushButton::clicked, modifier, &GraphModifier::changeRows);
+ QObject::connect(removeRowButton, &QPushButton::clicked, modifier, &GraphModifier::removeRow);
+ QObject::connect(removeRowsButton, &QPushButton::clicked, modifier, &GraphModifier::removeRows);
+ QObject::connect(massiveArrayButton, &QPushButton::clicked, modifier, &GraphModifier::createMassiveArray);
+ QObject::connect(showFiveSeriesButton, &QPushButton::clicked, modifier, &GraphModifier::showFiveSeries);
+ QObject::connect(selectionButton, &QPushButton::clicked, modifier,
+ &GraphModifier::changeSelectionMode);
+ QObject::connect(setSelectedBarButton, &QPushButton::clicked, modifier,
+ &GraphModifier::selectBar);
+ QObject::connect(swapAxisButton, &QPushButton::clicked, modifier,
+ &GraphModifier::swapAxis);
+ QObject::connect(insertRemoveTestButton, &QPushButton::clicked, modifier,
+ &GraphModifier::insertRemoveTestToggle);
+ QObject::connect(releaseAxesButton, &QPushButton::clicked, modifier,
+ &GraphModifier::releaseAxes);
+ QObject::connect(releaseProxiesButton, &QPushButton::clicked, modifier,
+ &GraphModifier::releaseSeries);
+
+ QObject::connect(flipViewsButton, &QPushButton::clicked, modifier,
+ &GraphModifier::flipViews);
+ QObject::connect(changeColorStyleButton, &QPushButton::clicked, modifier,
+ &GraphModifier::changeColorStyle);
+ QObject::connect(ownThemeButton, &QPushButton::clicked, modifier,
+ &GraphModifier::useOwnTheme);
+ QObject::connect(primarySeriesTestsButton, &QPushButton::clicked, modifier,
+ &GraphModifier::primarySeriesTest);
+ QObject::connect(toggleRotationButton, &QPushButton::clicked, modifier,
+ &GraphModifier::toggleRotation);
+ QObject::connect(logAxisButton, &QPushButton::clicked, modifier,
+ &GraphModifier::useLogAxis);
+ QObject::connect(testItemAndRowChangesButton, &QPushButton::clicked, modifier,
+ &GraphModifier::testItemAndRowChanges);
+ QObject::connect(colorDialog, &QColorDialog::currentColorChanged, modifier,
+ &GraphModifier::changeBaseColor);
+ QObject::connect(gradientBtoYPB, &QPushButton::clicked, modifier,
+ &GraphModifier::setGradient);
+
+ QObject::connect(fontList, &QFontComboBox::currentFontChanged, modifier,
+ &GraphModifier::changeFont);
+
+ QObject::connect(fpsCheckBox, &QCheckBox::stateChanged, modifier,
+ &GraphModifier::setFpsMeasurement);
+ QObject::connect(reverseValueAxisCheckBox, &QCheckBox::stateChanged, modifier,
+ &GraphModifier::reverseValueAxis);
+ QObject::connect(backgroundCheckBox, &QCheckBox::stateChanged, modifier,
+ &GraphModifier::setBackgroundEnabled);
+ QObject::connect(gridCheckBox, &QCheckBox::stateChanged, modifier,
+ &GraphModifier::setGridEnabled);
+ QObject::connect(inputHandlerRotationCheckBox, &QCheckBox::stateChanged, modifier,
+ &GraphModifier::setInputHandlerRotationEnabled);
+ QObject::connect(inputHandlerZoomCheckBox, &QCheckBox::stateChanged, modifier,
+ &GraphModifier::setInputHandlerZoomEnabled);
+ QObject::connect(inputHandlerSelectionCheckBox, &QCheckBox::stateChanged, modifier,
+ &GraphModifier::setInputHandlerSelectionEnabled);
+ QObject::connect(inputHandlerZoomAtTargetCheckBox, &QCheckBox::stateChanged, modifier,
+ &GraphModifier::setInputHandlerZoomAtTargetEnabled);
+ QObject::connect(rotationCheckBox, &QCheckBox::stateChanged, modifier,
+ &GraphModifier::setUseNullInputHandler);
+
+ SliderWrapper *rotationSliderWrapperX = new SliderWrapper(rotationSliderX);
+ SliderWrapper *rotationSliderWrapperY = new SliderWrapper(rotationSliderY);
+ QObject::connect(rotationCheckBox, &QCheckBox::stateChanged, rotationSliderWrapperX,
+ &SliderWrapper::setEnabled);
+ QObject::connect(rotationCheckBox, &QCheckBox::stateChanged, rotationSliderX,
+ &QSlider::setValue);
+ QObject::connect(rotationCheckBox, &QCheckBox::stateChanged, rotationSliderWrapperY,
+ &SliderWrapper::setEnabled);
+ QObject::connect(rotationCheckBox, &QCheckBox::stateChanged, rotationSliderY,
+ &QSlider::setValue);
+
+ QObject::connect(reflectionCheckBox, &QCheckBox::stateChanged, modifier,
+ &GraphModifier::setReflection);
+ QObject::connect(reflectivitySlider, &QSlider::valueChanged, modifier,
+ &GraphModifier::setReflectivity);
+ QObject::connect(floorLevelSlider, &QSlider::valueChanged, modifier,
+ &GraphModifier::setFloorLevel);
+ QObject::connect(marginSlider, &QSlider::valueChanged, modifier,
+ &GraphModifier::setGraphMargin);
+ QObject::connect(toggleCustomItemButton, &QPushButton::clicked, modifier,
+ &GraphModifier::toggleCustomItem);
+
+ ButtonWrapper *addDataButtonWrapper = new ButtonWrapper(addDataButton);
+ ButtonWrapper *addMultiDataButtonWrapper = new ButtonWrapper(addMultiDataButton);
+ ButtonWrapper *insertDataButtonWrapper = new ButtonWrapper(insertDataButton);
+ ButtonWrapper *insertMultiDataButtonWrapper = new ButtonWrapper(insertMultiDataButton);
+ ButtonWrapper *changeSingleDataButtonWrapper = new ButtonWrapper(changeSingleDataButton);
+ ButtonWrapper *changeRowButtonWrapper = new ButtonWrapper(changeRowButton);
+ ButtonWrapper *changeRowsButtonWrapper = new ButtonWrapper(changeRowsButton);
+ ButtonWrapper *massiveArrayButtonWrapper = new ButtonWrapper(massiveArrayButton);
+ ButtonWrapper *removeRowButtonWrapper = new ButtonWrapper(removeRowButton);
+ ButtonWrapper *removeRowsButtonWrapper = new ButtonWrapper(removeRowsButton);
+
+ SliderWrapper *sampleSliderWrapperX = new SliderWrapper(sampleSliderX);
+ SliderWrapper *sampleSliderWrapperZ = new SliderWrapper(sampleSliderZ);
+ SliderWrapper *minSliderWrapperX = new SliderWrapper(minSliderX);
+ SliderWrapper *minSliderWrapperZ = new SliderWrapper(minSliderZ);
+ SliderWrapper *minSliderWrapperY = new SliderWrapper(minSliderY);
+ SliderWrapper *maxSliderWrapperY = new SliderWrapper(maxSliderY);
+ ButtonWrapper *swapAxisButtonWrapper = new ButtonWrapper(swapAxisButton);
+
+ QObject::connect(staticCheckBox, &QCheckBox::stateChanged, addDataButtonWrapper,
+ &ButtonWrapper::setEnabled);
+ QObject::connect(staticCheckBox, &QCheckBox::stateChanged, addMultiDataButtonWrapper,
+ &ButtonWrapper::setEnabled);
+ QObject::connect(staticCheckBox, &QCheckBox::stateChanged, insertDataButtonWrapper,
+ &ButtonWrapper::setEnabled);
+ QObject::connect(staticCheckBox, &QCheckBox::stateChanged, insertMultiDataButtonWrapper,
+ &ButtonWrapper::setEnabled);
+ QObject::connect(staticCheckBox, &QCheckBox::stateChanged, changeSingleDataButtonWrapper,
+ &ButtonWrapper::setEnabled);
+ QObject::connect(staticCheckBox, &QCheckBox::stateChanged, changeRowButtonWrapper,
+ &ButtonWrapper::setEnabled);
+ QObject::connect(staticCheckBox, &QCheckBox::stateChanged, changeRowsButtonWrapper,
+ &ButtonWrapper::setEnabled);
+ QObject::connect(staticCheckBox, &QCheckBox::stateChanged, removeRowButtonWrapper,
+ &ButtonWrapper::setEnabled);
+ QObject::connect(staticCheckBox, &QCheckBox::stateChanged, removeRowsButtonWrapper,
+ &ButtonWrapper::setEnabled);
+ QObject::connect(staticCheckBox, &QCheckBox::stateChanged, massiveArrayButtonWrapper,
+ &ButtonWrapper::setEnabled);
+ QObject::connect(staticCheckBox, &QCheckBox::stateChanged, sampleSliderWrapperX,
+ &SliderWrapper::setEnabled);
+ QObject::connect(staticCheckBox, &QCheckBox::stateChanged, sampleSliderWrapperZ,
+ &SliderWrapper::setEnabled);
+ QObject::connect(staticCheckBox, &QCheckBox::stateChanged, minSliderWrapperX,
+ &SliderWrapper::setEnabled);
+ QObject::connect(staticCheckBox, &QCheckBox::stateChanged, minSliderWrapperZ,
+ &SliderWrapper::setEnabled);
+ QObject::connect(staticCheckBox, &QCheckBox::stateChanged, minSliderWrapperY,
+ &SliderWrapper::setEnabled);
+ QObject::connect(staticCheckBox, &QCheckBox::stateChanged, maxSliderWrapperY,
+ &SliderWrapper::setEnabled);
+ QObject::connect(staticCheckBox, &QCheckBox::stateChanged, swapAxisButtonWrapper,
+ &ButtonWrapper::setEnabled);
+ QObject::connect(staticCheckBox, &QCheckBox::stateChanged, modifier, &GraphModifier::restart);
+
+ modifier->setFpsLabel(fpsLabel);
+
+ modifier->start();
+
+ return app.exec();
+}
diff --git a/tests/manual/barstest/shuttle.obj b/tests/manual/barstest/shuttle.obj
new file mode 100644
index 0000000..e872228
--- /dev/null
+++ b/tests/manual/barstest/shuttle.obj
@@ -0,0 +1,6349 @@
+# Blender v2.66 (sub 0) OBJ File: ''
+# www.blender.org
+v -0.290924 -0.416888 -3.733797
+v -0.033253 -0.399965 -3.916942
+v 0.098892 -0.335648 -4.014968
+v 0.110144 -0.419894 -4.016005
+v -0.014627 -0.351984 -3.919619
+v -0.003375 -0.436229 -3.920655
+v 0.077510 -0.338845 -3.987319
+v 0.088762 -0.423090 -3.988356
+v 0.268419 -0.311615 -4.127978
+v 0.279672 -0.395861 -4.129015
+v -0.216945 -0.357152 -3.636585
+v -0.442480 -0.421494 -3.440589
+v 0.040557 -0.350366 -3.819523
+v -0.366883 -0.356351 -3.352865
+v -0.059636 -0.414591 -3.440213
+v 0.195164 -0.388461 -3.624492
+v -0.508658 -0.419445 -3.066802
+v -0.205255 -0.426666 -3.173799
+v -0.446409 -0.327120 -2.985436
+v -0.301084 -0.444031 -2.817294
+v 0.151449 -0.262081 -3.165398
+v 0.405315 -0.264481 -3.348743
+v 0.818570 -0.244023 -3.649532
+v 0.829822 -0.328268 -3.650568
+v 0.438945 -0.298561 -3.337861
+v 0.450197 -0.382806 -3.338897
+v 0.010751 -0.257630 -2.926187
+v 0.476024 -0.294278 -3.283418
+v 0.487276 -0.378524 -3.284455
+v 0.893196 -0.234640 -3.602006
+v 0.904448 -0.318886 -3.603042
+v -0.575146 -0.405155 -2.358083
+v -0.526846 -0.291701 -2.296914
+v -0.123567 -0.205514 -2.586394
+v -0.401501 -0.443667 -2.166967
+v 0.438519 -0.375468 -2.805563
+v 0.688118 -0.342410 -2.991890
+v 0.304996 -0.393955 -2.597466
+v -0.651696 -0.393273 -1.762192
+v -0.615966 -0.265853 -1.716287
+v -0.263638 -0.153966 -1.992210
+v 0.143186 -0.430595 -2.279088
+v -0.512839 -0.438185 -1.613670
+v -0.710028 -0.388098 -1.448538
+v -0.679965 -0.261349 -1.410043
+v -0.813314 -0.388657 -1.206769
+v 0.751287 -0.156495 -2.401505
+v 0.998622 -0.165467 -2.585768
+v -0.031714 -0.447371 -1.755604
+v -0.583894 -0.434779 -1.325088
+v -0.408965 -0.117462 -1.482493
+v -0.790573 -0.268054 -1.169135
+v 0.625851 -0.150158 -2.234187
+v -1.051885 -0.397310 -0.885646
+v -1.031232 -0.284379 -0.851625
+v -0.707410 -0.437077 -1.088477
+v -1.334166 -0.408930 -0.570468
+v 0.404917 -0.087146 -1.938298
+v -0.498689 -0.116097 -1.212783
+v -1.314745 -0.307881 -0.538558
+v -0.959224 -0.446984 -0.774490
+v 1.798825 0.967755 -3.015114
+v 2.046406 0.986555 -3.209870
+v 1.795686 1.006585 -3.015357
+v 2.043267 1.025386 -3.210112
+v 1.806945 0.929656 -3.008470
+v 2.054526 0.948456 -3.203225
+v 1.797665 1.044451 -3.009186
+v 2.045245 1.063251 -3.203941
+v -0.218636 -0.452499 -1.294639
+v -0.644958 -0.132219 -0.977335
+v 1.804675 1.079696 -2.996872
+v 2.052256 1.098496 -3.191627
+v -1.248682 -0.457011 -0.466167
+v 1.840314 0.837931 -2.974904
+v 2.087895 0.856731 -3.169660
+v 1.646588 -0.144702 -2.733368
+v 1.657840 -0.228948 -2.734405
+v 1.737599 -0.131659 -2.805524
+v 1.748852 -0.215905 -2.806561
+v 1.819258 1.148577 -2.971684
+v 1.033279 0.836881 -2.319959
+v 1.031777 0.970096 -2.298396
+v 1.060381 0.758531 -2.296534
+v -0.899523 -0.158764 -0.676483
+v 0.945538 0.814187 -2.200939
+v 0.170080 -0.031614 -1.496071
+v 1.043881 1.032762 -2.270612
+v 0.966443 0.743790 -2.180778
+v 0.952423 0.936921 -2.181507
+v 1.858652 1.239977 -2.912781
+v -0.317238 -0.452926 -1.060344
+v -1.192492 -0.196285 -0.374663
+v 1.856681 0.583228 -2.822182
+v 2.018811 -0.094129 -2.886386
+v 0.967854 0.995533 -2.156932
+v 1.272977 -0.200327 -2.268122
+v 1.284228 -0.284573 -2.269158
+v 1.076506 1.117039 -2.211863
+v -0.484562 -0.458987 -0.834904
+v 1.848687 0.788896 -2.757349
+v 2.078178 -0.164712 -2.848704
+v 1.967059 0.229003 -2.794663
+v 1.967832 0.643840 -2.831535
+v 2.215413 0.662641 -3.026290
+v 0.936685 0.816251 -2.042840
+v 2.039912 0.250063 -2.839813
+v 1.003257 1.074974 -2.104949
+v 1.164321 0.598239 -2.181823
+v 0.950019 0.768391 -2.028775
+v 0.943810 0.900014 -2.031005
+v 2.521226 0.390336 -3.213021
+v 1.936351 1.129229 -2.824698
+v 2.183932 1.148030 -3.019454
+v 2.568967 0.404915 -3.250601
+v 1.055366 0.603082 -2.081103
+v 0.869869 0.465058 -1.923758
+v 0.936491 -0.173930 -1.917023
+v 2.705161 0.067033 -3.318110
+v 2.096944 0.264363 -2.850901
+v 1.957182 0.386704 -2.747152
+v 2.029313 0.077472 -2.774992
+v 0.955724 0.940103 -2.015164
+v 1.928438 1.372002 -2.811322
+v -0.765050 -0.471609 -0.536370
+v 2.030367 0.402476 -2.793896
+v 2.100080 0.103611 -2.820803
+v 2.514476 0.522913 -3.173254
+v 2.574949 0.263657 -3.196595
+v 2.562289 0.536088 -3.211256
+v 2.622122 0.279579 -3.234349
+v 2.598488 0.409527 -3.214962
+v -0.064938 0.006053 -1.100592
+v 2.088244 0.403277 -2.809050
+v 2.151782 0.130884 -2.833575
+v 2.199769 1.159790 -2.964559
+v 2.193588 1.233527 -2.965299
+v 2.221596 1.161935 -2.979915
+v 2.215481 1.234891 -2.980646
+v 2.136538 0.271664 -2.829406
+v 1.137830 1.220917 -2.137315
+v 0.872108 0.654248 -1.875077
+v 0.961190 -0.247342 -1.861737
+v 2.739614 0.051854 -3.275776
+v -2.850674 0.163164 1.082772
+v -2.774223 -0.444295 1.078949
+v -2.508742 -0.433973 0.871016
+v -2.566129 0.205717 0.857016
+v 0.982043 0.994775 -1.981001
+v -1.069794 -0.481435 -0.242789
+v 2.592936 0.518564 -3.182256
+v 2.642673 0.305340 -3.201453
+v -2.926099 0.145051 1.155134
+v -2.869277 -0.451273 1.165592
+v 1.009718 0.673075 -1.961110
+v 2.235091 1.170649 -2.961918
+v 1.045386 1.162465 -2.033586
+v 2.230007 1.231294 -2.962527
+v 2.325998 0.322675 -2.951759
+v 1.972627 1.185781 -2.753756
+v 2.129131 0.389939 -2.793774
+v 2.183229 0.158016 -2.814654
+v 2.227766 1.099078 -2.934829
+v 1.117407 -0.306283 -1.939579
+v 1.981589 1.144577 -2.746340
+v 1.973910 1.228192 -2.748031
+v 2.249297 1.101867 -2.950499
+v 2.210880 1.300532 -2.936848
+v 1.886303 0.951739 -2.651532
+v 2.165588 -0.207364 -2.762840
+v 1.360371 -0.269775 -2.128519
+v 2.232590 1.301186 -2.952497
+v 2.160973 0.274542 -2.798066
+v 0.937019 0.410104 -1.852872
+v 0.960798 0.265444 -1.858128
+v 2.041237 1.191461 -2.784518
+v 2.048705 1.157124 -2.778339
+v 2.042305 1.226803 -2.779748
+v 2.258118 1.120717 -2.937466
+v 2.320933 0.422137 -2.921925
+v 2.366302 0.227636 -2.939436
+v 1.976683 1.431691 -2.744228
+v 2.244230 1.286403 -2.939127
+v -3.086442 -0.474834 1.379359
+v 1.999431 1.110853 -2.726914
+v 1.985242 1.265353 -2.730038
+v -3.023720 -0.458462 1.334241
+v -3.024019 0.109883 1.282283
+v -2.844489 0.344693 1.122684
+v -2.737176 -0.511595 1.117650
+v -2.469550 -0.506414 0.908517
+v -2.551327 0.395732 0.889431
+v 1.000046 -0.329019 -1.811242
+v 2.154800 0.373104 -2.768372
+v 2.199881 0.179835 -2.785773
+v -2.914064 0.322285 1.189905
+v -2.834171 -0.518740 1.204847
+v 1.179604 1.267508 -2.087104
+v 2.051748 1.257770 -2.764754
+v 2.063573 1.129020 -2.762150
+v 2.080589 1.195258 -2.775463
+v -0.196284 0.002337 -0.889996
+v 1.013546 1.060481 -1.927888
+v -3.072142 -0.445996 1.396327
+v 2.603875 0.637948 -3.126857
+v 2.707508 0.193661 -3.166857
+v 2.085482 1.172761 -2.771414
+v 2.081289 1.218414 -2.772337
+v 1.075555 1.201415 -1.986324
+v 2.002330 0.508317 -2.645192
+v 2.127263 -0.027287 -2.693413
+v 2.556508 0.625865 -3.087951
+v 2.661251 0.176820 -3.128379
+v 2.074001 0.520013 -2.695353
+v 2.194747 0.002364 -2.741958
+v 1.000951 0.830916 -1.880131
+v 2.095224 1.154349 -2.760807
+v 2.087476 1.238703 -2.762513
+v 2.023438 1.089743 -2.698434
+v 2.004898 1.291606 -2.702516
+v -3.122983 -0.461461 1.459483
+v -3.158215 -0.041663 1.448402
+v -2.497997 0.004146 0.929450
+v -2.509563 0.149734 0.925099
+v 1.003036 0.858653 -1.877204
+v 2.270079 1.067661 -2.884072
+v 1.005703 0.814744 -1.874867
+v 2.291162 1.070783 -2.900280
+v 2.128013 0.510403 -2.719236
+v 2.238064 0.038604 -2.761713
+v 2.068128 1.279648 -2.741818
+v 2.083579 1.111428 -2.738417
+v 2.247011 1.342852 -2.886831
+v 2.268338 1.343057 -2.903010
+v -2.993610 -0.524459 1.373357
+v -2.994892 0.277606 1.300703
+v 2.627505 0.603237 -3.112099
+v 2.713651 0.233920 -3.145348
+v 2.292918 1.094878 -2.895721
+v 1.006938 0.871768 -1.872413
+v 2.105035 1.197866 -2.757530
+v 2.093582 1.187722 -2.747088
+v 2.092232 1.203828 -2.747250
+v 2.273946 1.321208 -2.897990
+v 0.912103 0.803812 -1.788658
+v 1.019079 -0.292614 -1.771378
+v 2.804159 0.046327 -3.194257
+v 2.107537 1.186366 -2.755460
+v 2.105394 1.209702 -2.755932
+v 2.245164 0.292374 -2.778011
+v 0.747334 -0.381459 -1.545246
+v 2.108331 1.142823 -2.745258
+v 2.098208 1.253036 -2.747486
+v 2.099697 1.174463 -2.740594
+v 2.096009 1.218463 -2.741035
+v 2.112516 1.176954 -2.750038
+v 2.108556 1.220074 -2.750911
+v 1.015678 0.889476 -1.861661
+v 2.162992 0.481149 -2.717304
+v 2.256692 0.079446 -2.753470
+v 1.035607 1.090416 -1.893256
+v 2.119216 1.171063 -2.742090
+v 2.114042 1.227400 -2.743229
+v -3.036469 -0.488219 1.432582
+v 1.026486 0.781917 -1.851175
+v 2.241119 0.356951 -2.758555
+v 2.270656 0.230325 -2.769956
+v -0.400720 -0.022989 -0.663568
+v 2.108939 1.167601 -2.729508
+v 2.352467 0.499374 -2.857929
+v 2.431048 0.162488 -2.888259
+v 2.103901 1.227706 -2.730110
+v 2.078094 0.869479 -2.669583
+v 2.325675 0.888279 -2.864338
+v 2.049954 1.084459 -2.665236
+v 2.029886 1.302955 -2.669655
+v 2.105674 1.107026 -2.710752
+v 2.088951 1.289105 -2.714434
+v 2.107442 1.198835 -2.720560
+v 2.122808 1.139938 -2.727132
+v 2.111851 1.259233 -2.729544
+v 2.126617 1.169588 -2.732825
+v 2.121016 1.230568 -2.734058
+v 2.126010 1.200245 -2.735167
+v 2.126854 1.200309 -2.735831
+v -3.255541 -0.463337 1.614298
+v -3.256979 -0.068785 1.579187
+v -3.090151 -0.525402 1.492390
+v -3.140838 0.071873 1.477060
+v 2.183017 0.449113 -2.704647
+v 2.261101 0.114361 -2.734786
+v 1.025626 0.910940 -1.843900
+v 2.118830 1.168976 -2.716800
+v 2.113792 1.229081 -2.717403
+v 2.133590 1.172756 -2.723654
+v 2.128416 1.229093 -2.724793
+v -2.405642 -0.551479 0.981773
+v -2.505313 0.545503 0.958747
+v 1.032741 0.920593 -1.832253
+v 2.139077 1.180082 -2.715972
+v 2.135116 1.223202 -2.716844
+v 2.126722 1.178219 -2.705876
+v 2.123034 1.222219 -2.706317
+v 2.136451 1.146135 -2.709190
+v 2.126328 1.256348 -2.711419
+v 2.142239 1.190453 -2.710951
+v 2.140096 1.213790 -2.711423
+v 2.330166 1.100055 -2.847870
+v 2.130499 1.192853 -2.699662
+v 2.129149 1.208959 -2.699824
+v 2.142597 1.202290 -2.709353
+v 2.311194 1.326385 -2.850139
+v -0.678415 -0.058034 -0.389918
+v 2.126498 1.116483 -2.683367
+v 2.111047 1.284703 -2.686769
+v 2.335972 1.077011 -2.842714
+v 2.315368 1.073955 -2.825890
+v 2.297923 0.301154 -2.738834
+v 2.313149 1.349285 -2.845444
+v -3.224716 -0.523658 1.644234
+v -3.228977 0.037748 1.596002
+v 2.259607 0.406750 -2.716804
+v 2.310765 0.187427 -2.736550
+v 2.292301 1.349146 -2.828649
+v 1.051190 0.836263 -1.810548
+v 2.074942 1.095809 -2.632374
+v 2.056402 1.297672 -2.636457
+v 1.051411 0.836718 -1.810222
+v 1.051764 0.833314 -1.810103
+v 2.147183 1.160469 -2.694164
+v 2.139435 1.244822 -2.695870
+v 1.051937 0.833724 -1.809843
+v 1.051832 0.835286 -1.809826
+v 1.052011 0.834070 -1.809716
+v 1.052027 0.833906 -1.809712
+v 1.051999 0.834825 -1.809658
+v 1.051961 0.835391 -1.809651
+v 1.052122 0.834297 -1.809553
+v 2.289008 0.296555 -2.724424
+v 1.946327 1.084103 -2.529010
+v 2.264997 -0.237532 -2.656099
+v -3.146048 -0.468059 1.586396
+v 2.295856 0.334163 -2.728890
+v 2.310954 0.269436 -2.734717
+v 2.287534 0.325511 -2.715739
+v 2.300741 0.268886 -2.720837
+v 2.153370 1.180757 -2.684340
+v 2.149177 1.226410 -2.685263
+v -2.995660 -0.411552 1.480706
+v 1.055648 0.923143 -1.800017
+v 2.154070 1.203913 -2.681214
+v -0.987628 -0.106173 -0.106669
+v -3.134648 -0.466717 1.601017
+v 2.142878 1.138361 -2.660432
+v 2.131053 1.267111 -2.663036
+v 2.305306 0.359619 -2.707547
+v 2.331457 0.247507 -2.717641
+v 2.296714 0.347997 -2.697108
+v 2.319591 0.249920 -2.705938
+v 2.094598 1.122062 -2.604852
+v 2.080408 1.276562 -2.607976
+v 0.470274 -0.415629 -1.195124
+v 2.682583 0.683203 -3.020018
+v 2.802250 0.170184 -3.066206
+v 2.692932 0.640855 -3.023288
+v 2.792405 0.214405 -3.061682
+v 2.636060 0.671604 -2.979968
+v 2.757006 0.153092 -3.026651
+v 2.359883 1.134861 -2.806733
+v 2.152321 1.169328 -2.645438
+v 2.145921 1.239007 -2.646847
+v 2.345995 1.300547 -2.808394
+v 2.153390 1.204670 -2.640668
+v 2.412149 0.533689 -2.776916
+v 2.502886 0.144687 -2.811939
+v 0.970224 0.925229 -1.686331
+v 1.091354 -0.325301 -1.665935
+v 2.881699 0.045075 -3.095805
+v 2.371721 1.118882 -2.793227
+v 2.351501 1.116275 -2.775872
+v -3.112347 -0.464090 1.629621
+v 2.105930 1.159222 -2.586859
+v 2.098250 1.242837 -2.588550
+v 2.355013 1.318201 -2.795225
+v 2.334614 1.317729 -2.777893
+v 2.314089 0.357987 -2.673523
+v 2.340505 0.244738 -2.683719
+v 1.104303 1.101391 -1.798523
+v 2.090404 0.561258 -2.516102
+v 2.159124 0.571179 -2.570591
+v 2.234664 -0.057203 -2.571783
+v 2.205596 0.557038 -2.605525
+v 2.229047 0.520855 -2.620486
+v 2.238063 0.482200 -2.623966
+v 2.298549 -0.026551 -2.624406
+v 2.332671 0.012252 -2.654572
+v 2.295672 0.428428 -2.663943
+v 2.328226 0.095662 -2.658767
+v 2.337243 0.057009 -2.662247
+v 2.319328 0.300003 -2.670594
+v 2.323741 0.370700 -2.680527
+v 2.354745 0.175176 -2.686744
+v 2.343450 0.307384 -2.690079
+v 2.353938 0.241245 -2.692182
+v 1.085896 0.905799 -1.761274
+v 2.107213 1.201633 -2.581135
+v 1.172914 1.208536 -1.853034
+v -3.524472 -0.463065 1.962955
+v -3.542270 -0.167984 1.949735
+v 2.374104 1.189970 -2.783334
+v 2.369021 1.250615 -2.783942
+v 1.303689 1.278820 -1.940955
+v -2.749923 0.604098 1.287890
+v -2.601352 -0.583194 1.281085
+v -2.331104 -0.584296 1.070484
+v -2.444929 0.666788 1.044342
+v 2.335002 0.352805 -2.651304
+v 2.357880 0.254727 -2.660134
+v -2.812832 0.575628 1.346300
+v -2.702155 -0.590712 1.367115
+v 2.388829 1.185178 -2.765078
+v 2.382714 1.258133 -2.765809
+v 2.346222 0.364438 -2.655067
+v 2.372374 0.252327 -2.665161
+v 2.368793 1.183281 -2.747422
+v 2.362612 1.257019 -2.748161
+v -3.502416 -0.514638 1.987533
+v -3.528153 -0.092777 1.968860
+v -2.930290 -0.491180 1.547024
+v 2.066685 0.851305 -2.470213
+v -3.080119 -0.460294 1.670958
+v 2.353852 0.333838 -2.636405
+v 2.367060 0.277213 -2.641503
+v 2.140192 0.869385 -2.515599
+v -2.873451 -0.595312 1.528083
+v -2.875654 0.517249 1.427634
+v 2.123721 1.449962 -2.555544
+v 2.366726 0.342510 -2.637991
+v 2.381824 0.277783 -2.643818
+v 2.365586 0.306170 -2.632818
+v 0.180044 -0.433739 -0.860263
+v 2.390810 0.196854 -2.633883
+v 2.339653 0.416178 -2.614137
+v 2.379757 0.310792 -2.633873
+v -2.972414 -0.594417 1.632153
+v -3.043199 0.236465 1.611041
+v 2.197619 0.881876 -2.526829
+v 1.155619 0.140929 -1.644002
+v 2.061041 1.010198 -2.426111
+v 2.124709 0.698580 -2.447134
+v 2.629037 1.066507 -2.869623
+v 2.771686 0.621339 -2.939622
+v 2.857832 0.252023 -2.972872
+v 2.676305 1.087418 -2.904726
+v 2.483987 0.515887 -2.700598
+v 2.562568 0.179001 -2.730927
+v 2.134736 1.022953 -2.472975
+v 2.196270 0.721780 -2.493294
+v -3.115630 -0.588979 1.772415
+v -3.122642 0.192029 1.706162
+v 2.305190 0.463503 -2.547947
+v 2.383274 0.128751 -2.578085
+v 2.026136 1.180204 -2.395136
+v 2.372061 -0.253901 -2.533144
+v 0.043832 -0.439397 -0.700086
+v -2.331525 -0.123197 1.132486
+v 2.610039 1.166097 -2.837585
+v 2.673477 0.969915 -2.869029
+v 2.777325 0.659725 -2.919367
+v 2.880958 0.215439 -2.959367
+v 2.657509 1.185952 -2.873027
+v 2.720273 0.991850 -2.904138
+v 2.379762 0.373280 -2.580731
+v 2.409298 0.246654 -2.592131
+v 2.731814 0.647876 -2.878239
+v 2.836558 0.198831 -2.918667
+v -3.039373 -0.455495 1.723220
+v 2.192646 1.021841 -2.487980
+v 2.248729 0.747345 -2.506499
+v 2.309599 0.498417 -2.529263
+v 2.403300 0.096714 -2.565429
+v 2.237351 0.888547 -2.505383
+v -0.183302 -0.450597 -0.489896
+v 2.405254 0.311230 -2.572676
+v 1.043932 1.013192 -1.572568
+v 1.174857 -0.343976 -1.550015
+v 2.968846 0.048153 -2.984724
+v 2.704206 1.090705 -2.868940
+v 1.193313 1.053879 -1.685613
+v 2.300203 0.530684 -2.498384
+v 2.410253 0.058885 -2.540860
+v -2.870028 -0.379944 1.617982
+v -0.502937 -0.466927 -0.212460
+v 2.262925 0.542265 -2.453038
+v 2.383671 0.024616 -2.499642
+v 2.688581 1.172611 -2.842590
+v 2.740755 1.011263 -2.868450
+v 2.233117 1.007717 -2.472307
+v 2.280868 0.774003 -2.488075
+v -2.681424 0.688036 1.389168
+v -2.372814 0.754289 1.142473
+v -2.249194 -0.603433 1.170773
+v 2.433319 0.963376 -2.621027
+v -2.742923 0.657642 1.444079
+v 2.197806 0.531341 -2.394472
+v 2.322739 -0.004263 -2.442693
+v -3.420844 -0.571247 2.087621
+v -3.456852 0.016609 2.061712
+v 2.261785 0.891425 -2.474043
+v -0.828435 -0.477984 0.061043
+v 2.548733 0.450740 -2.649420
+v 2.594102 0.256238 -2.666930
+v 2.619376 1.253520 -2.777794
+v 2.736592 0.891025 -2.835893
+v 2.666746 1.272449 -2.813868
+v 2.782720 0.913795 -2.871353
+v -2.802757 0.594778 1.513184
+v 2.842664 0.549920 -2.883518
+v 2.892401 0.336695 -2.902714
+v 2.419066 1.038090 -2.596991
+v 2.466659 0.890910 -2.620581
+v 2.366409 0.398028 -2.496960
+v 2.411491 0.204759 -2.514360
+v -3.768498 -0.285858 2.351028
+v -3.754395 -0.436849 2.353898
+v -2.979417 0.290761 1.695520
+v 2.258257 0.990733 -2.446479
+v 2.298049 0.795972 -2.459619
+v -3.744207 -0.468465 2.368351
+v -3.763825 -0.250991 2.363676
+v -2.991889 -0.449902 1.784124
+v 2.589037 0.355701 -2.637096
+v -3.058914 0.242857 1.779885
+v 2.405318 0.303321 -2.484666
+v 2.886850 0.445733 -2.870009
+v 2.109286 1.132686 -2.326646
+v 2.219562 0.592947 -2.363060
+v 2.696261 1.244513 -2.793414
+v 2.792664 0.946380 -2.841198
+v 1.301631 1.132067 -1.690738
+v 2.383062 0.419848 -2.468079
+v 2.437160 0.187925 -2.488959
+v 2.181364 1.141334 -2.376844
+v 2.287943 0.619687 -2.412037
+v 2.862712 0.573808 -2.851874
+v 2.922544 0.317299 -2.874968
+v 2.818116 0.561038 -2.810024
+v 2.878590 0.301782 -2.833364
+v 2.426071 1.103677 -2.552133
+v 2.514010 0.831724 -2.595722
+v 2.235144 1.129736 -2.400364
+v 2.332283 0.654296 -2.432440
+v 2.346390 0.907367 -2.454136
+v -3.411176 0.052851 2.120724
+v 2.429752 0.306199 -2.453326
+v 2.269301 1.099582 -2.397708
+v 2.352008 0.694778 -2.425019
+v 2.386485 0.438404 -2.426523
+v 2.450023 0.166012 -2.451047
+v 2.915866 0.448472 -2.835623
+v 2.344078 0.972432 -2.436077
+v 2.370150 0.844828 -2.444686
+v 2.871840 0.434359 -2.793598
+v -2.786263 -0.483209 1.702896
+v 1.459973 1.193063 -1.759240
+v -3.703011 -0.503725 2.422886
+v -3.730082 -0.199896 2.416091
+v 2.655625 1.315469 -2.699349
+v 2.808775 0.841848 -2.775261
+v 2.702611 1.333742 -2.736255
+v 2.854139 0.865139 -2.811362
+v 2.288410 1.067288 -2.384314
+v 2.357333 0.729951 -2.407073
+v 2.357592 0.441017 -2.374193
+v 2.427305 0.142152 -2.401100
+v 1.807099 1.188967 -2.008747
+v -3.783568 -0.334437 2.490638
+v -3.776477 -0.407414 2.491810
+v 2.122243 1.235844 -2.255761
+v 2.482100 -0.255753 -2.399353
+v 2.726073 1.295463 -2.728897
+v 2.852031 0.905933 -2.791331
+v 2.295756 0.426582 -2.312892
+v 2.367887 0.117350 -2.340733
+v -2.939741 -0.443760 1.851008
+v -3.781106 -0.318610 2.496646
+v -3.771103 -0.424172 2.498541
+v 2.441322 0.304925 -2.409196
+v 2.453266 1.150154 -2.493283
+v 2.568163 0.794829 -2.550234
+v 1.130007 1.063856 -1.452342
+v 1.265938 -0.347821 -1.428686
+v 3.061789 0.055427 -2.865868
+v -2.602579 0.736106 1.497157
+v -2.292120 0.804178 1.248855
+v -2.434953 -0.604050 1.489534
+v -2.163494 -0.608053 1.278256
+v -2.664054 0.704644 1.549387
+v -2.539160 -0.611934 1.572914
+v 2.399283 0.915534 -2.415008
+v -3.707198 -0.182800 2.448233
+v 2.363834 1.022590 -2.395347
+v 2.408991 0.801573 -2.410258
+v -2.722515 -0.616791 1.721958
+v -2.725142 0.639161 1.608672
+v -2.829179 -0.615824 1.810674
+v -2.909245 0.322942 1.786891
+v 2.392228 0.914706 -2.400276
+v 2.398101 0.948794 -2.405777
+v 2.411428 0.883567 -2.410178
+v -2.983486 -0.609514 1.936485
+v -2.991769 0.272910 1.861910
+v 2.388079 0.936458 -2.393279
+v 2.401934 0.893610 -2.400146
+v 1.266405 1.148829 -1.537915
+v 1.276485 1.330618 -1.562468
+v 1.301959 1.399716 -1.588675
+v 1.829089 1.191557 -1.980541
+v 2.385908 3.043540 -2.584738
+v 2.432531 3.084452 -2.624845
+v 2.484531 3.138930 -2.670390
+v 2.555058 3.180480 -2.729192
+v 2.587869 1.699016 -2.618729
+v 2.897153 3.208163 -2.998451
+v 2.950947 3.055532 -3.026375
+v 2.946595 3.189529 -3.035288
+v 2.959600 3.134547 -3.040378
+v 2.417759 0.294566 -2.355182
+v -3.764574 -0.295328 2.521762
+v -3.750702 -0.443027 2.524510
+v 2.358011 0.275051 -2.293223
+v -3.320640 -0.590025 2.214050
+v -3.361390 0.074490 2.184798
+v 2.390117 0.955552 -2.380219
+v 2.415719 0.876379 -2.392909
+v 2.408200 0.974432 -2.384957
+v 2.431282 0.861455 -2.392579
+v -3.753472 -0.287501 2.537197
+v 2.398036 0.969083 -2.363085
+v 2.431485 0.865638 -2.379665
+v -3.651074 -0.516133 2.490283
+v -3.681587 -0.172421 2.482510
+v 2.311849 1.347830 -2.326247
+v -3.741101 -0.282712 2.553676
+v -3.725441 -0.449869 2.556817
+v 2.175858 1.240742 -2.187508
+v 1.176857 1.068079 -1.392566
+v 1.312589 -0.342230 -1.368881
+v -2.560177 0.739860 1.551322
+v -2.121265 -0.602981 1.332435
+v -2.249778 0.807875 1.303072
+v -2.622366 0.708349 1.602724
+v -2.686267 0.642614 1.658664
+v -2.872984 0.326449 1.833357
+v -2.958486 0.276123 1.904730
+v -3.335886 0.076990 2.217503
+v -3.668144 -0.171074 2.499706
+v -3.734634 -0.282056 2.561952
+v -3.734566 -0.369723 2.570150
+v -2.150712 -0.249451 1.324240
+v 1.267079 0.010084 -1.364303
+v 1.704978 -0.191159 -1.687236
+v 2.198494 1.185947 -2.198470
+v 2.267581 1.192809 -2.252964
+v 2.325830 0.562710 -2.240516
+v 2.313725 1.176653 -2.287457
+v 2.336207 1.139528 -2.301576
+v 2.434129 0.098705 -2.282343
+v 2.390649 0.590464 -2.293602
+v 2.344165 1.100576 -2.304204
+v 2.425891 0.627661 -2.324495
+v 2.423750 0.711053 -2.330483
+v 2.431708 0.672101 -2.333111
+v 2.400364 1.044399 -2.342861
+v 2.410625 0.974989 -2.344487
+v 2.420496 0.915260 -2.346699
+v 2.426872 0.985581 -2.358127
+v 2.452506 0.789191 -2.360079
+v 2.446831 0.863022 -2.362433
+v 2.444856 0.921557 -2.366269
+v 2.453526 0.855127 -2.366929
+v 2.496511 1.170443 -2.429398
+v 2.620876 0.785842 -2.491042
+v 2.713268 1.342514 -2.614195
+v 2.759644 1.360499 -2.652004
+v 2.773482 1.317705 -2.658864
+v 2.879037 0.829869 -2.696361
+v 2.909818 0.896081 -2.726441
+v 2.923656 0.853286 -2.733300
+v 2.434469 0.098745 -2.281906
+v -2.120000 -0.602832 1.334057
+v 1.313986 -0.342065 -1.367090
+v 2.435135 0.098824 -2.281052
+v 2.435757 0.098897 -2.280254
+v 2.436307 0.098962 -2.279548
+v 2.436763 0.099016 -2.278964
+v 2.436999 0.099043 -2.278662
+v -2.885212 -0.437337 1.920949
+v -2.717870 -0.356682 1.785125
+v -3.728000 -0.281169 2.570479
+v -3.712341 -0.448326 2.573620
+v -0.735546 -0.053125 0.219066
+v -0.406216 0.000394 -0.041418
+v -0.100273 0.040739 -0.281944
+v 0.172417 0.073341 -0.495491
+v 2.335518 1.350617 -2.295889
+v 0.354290 0.083209 -0.634777
+v -3.623852 -0.512926 2.525198
+v -3.654365 -0.169215 2.517424
+v 2.425971 0.972373 -2.327255
+v 2.459421 0.868929 -2.343834
+v 0.696977 0.054076 -0.892441
+v 2.948120 3.214165 -2.933081
+v 2.606057 3.186486 -2.663780
+v 2.997989 3.195582 -2.969370
+v 2.536222 3.145018 -2.604090
+v 3.012064 3.140727 -2.973088
+v 2.485188 3.090654 -2.557307
+v 1.046458 0.007453 -1.152257
+v -3.714744 -0.282939 2.586870
+v 2.439279 3.049827 -2.516283
+v 3.004908 3.061887 -2.957164
+v 2.449116 0.979252 -2.332477
+v 2.472199 0.866275 -2.340099
+v 1.368491 -0.052711 -1.393705
+v 1.717894 -0.070445 -1.662515
+v 1.478573 -0.058668 -1.476762
+v 2.441737 0.961632 -2.314012
+v 2.467338 0.882459 -2.326701
+v -3.309670 0.080582 2.251135
+v -3.268919 -0.583933 2.280387
+v 2.410137 0.281190 -2.226363
+v -3.701911 -0.287948 2.602134
+v -3.688039 -0.435647 2.604882
+v 2.482991 0.302250 -2.271514
+v 2.455522 0.944402 -2.306774
+v 2.469378 0.901553 -2.313642
+v -2.915858 -0.601549 2.023225
+v -2.924141 0.280876 1.948650
+v 2.468971 0.957141 -2.314878
+v 2.482299 0.891914 -2.319279
+v 2.465229 0.923305 -2.306645
+v -2.755682 -0.607167 1.904943
+v -2.835748 0.331599 1.881160
+v -2.646201 0.648459 1.709923
+v -2.643573 -0.607493 1.823210
+v 2.443879 1.032018 -2.292681
+v 2.489036 0.811001 -2.307592
+v -3.626724 -0.173321 2.551450
+v 2.481117 0.925173 -2.310048
+v -2.579668 0.714584 1.657621
+v -2.454774 -0.601995 1.681149
+v -2.516832 0.746206 1.607137
+v -2.349206 -0.593950 1.599513
+v -2.206373 0.814278 1.358835
+v -2.077747 -0.597953 1.388236
+v 2.667233 1.708364 -2.516935
+v 1.224686 1.075007 -1.330905
+v 1.360617 -0.336670 -1.307250
+v 3.156468 0.066579 -2.744432
+v 2.549224 1.161456 -2.370206
+v 2.664121 0.806131 -2.427157
+v 2.540023 0.316550 -2.282602
+v 1.385180 1.409518 -1.481935
+v -3.679715 -0.306668 2.626691
+v -3.669712 -0.412230 2.628586
+v 1.360989 1.340571 -1.454082
+v 2.400261 0.438891 -2.178853
+v 2.472391 0.129659 -2.206693
+v 2.831269 1.307854 -2.593973
+v 2.957226 0.918323 -2.656406
+v 2.230447 1.248588 -2.116977
+v 2.590304 -0.243009 -2.260568
+v 1.354370 1.159190 -1.425091
+v 1.917054 1.201918 -1.867717
+v -3.673127 -0.321429 2.632291
+v -3.666035 -0.394406 2.633464
+v -2.830682 -0.430915 1.990889
+v 2.473447 0.454663 -2.225597
+v 2.543159 0.155798 -2.252504
+v 2.410583 1.081677 -2.227614
+v 2.479506 0.744341 -2.250373
+v 3.001074 0.449623 -2.628313
+v 2.829160 1.348647 -2.573941
+v 2.980689 0.880044 -2.649048
+v 2.783530 1.330534 -2.535297
+v 2.936681 0.856913 -2.611208
+v 3.051332 0.464469 -2.662340
+v -3.572802 -0.488389 2.589894
+v -3.599872 -0.184558 2.583099
+v 1.591093 1.208507 -1.591064
+v 2.482720 0.988763 -2.258252
+v 2.508792 0.861158 -2.266861
+v 2.531323 0.455464 -2.240752
+v 2.594861 0.183071 -2.265276
+v 2.415908 1.116850 -2.209669
+v 2.498616 0.712046 -2.236978
+v 2.579618 0.323851 -2.261107
+v -2.645182 -0.466535 1.885712
+v 1.939045 1.204508 -1.839511
+v -3.258276 0.070860 2.316835
+v 2.506480 0.926224 -2.248802
+v 2.992383 0.581606 -2.586979
+v 3.052857 0.322350 -2.610320
+v 2.407334 1.150018 -2.179512
+v 2.504473 0.674577 -2.211588
+v 3.042733 0.595054 -2.621444
+v 3.102565 0.338545 -2.644537
+v 2.603378 1.124561 -2.324717
+v 2.691316 0.852608 -2.368305
+v 3.078396 0.468329 -2.624718
+v 2.370287 1.163586 -2.134528
+v 2.476867 0.641940 -2.169721
+v 2.572210 0.442126 -2.225475
+v 2.626308 0.210203 -2.246356
+v 1.491168 1.154391 -1.447635
+v 2.890636 1.267406 -2.544106
+v 2.987040 0.969274 -2.591890
+v 2.788228 0.379194 -2.381967
+v 2.304762 1.155709 -2.075926
+v 2.415038 0.615971 -2.112340
+v 2.604052 0.326729 -2.229767
+v -2.858987 0.266405 2.036315
+v -3.533523 -0.443650 2.638577
+v -3.553141 -0.226176 2.633901
+v 2.469866 1.015657 -2.175067
+v 2.509659 0.820896 -2.188207
+v -2.762138 0.316353 1.974206
+v -2.778535 -0.424773 2.057773
+v 3.071249 0.576878 -2.590723
+v 3.120985 0.363654 -2.609920
+v 2.781708 0.478212 -2.350957
+v 2.827077 0.283710 -2.368468
+v -3.539008 -0.258828 2.645375
+v -3.524905 -0.409819 2.648245
+v 2.597879 0.425291 -2.200073
+v 2.642961 0.232022 -2.217474
+v 2.650729 1.065375 -2.299858
+v 2.698322 0.918196 -2.323448
+v -2.569382 0.622266 1.812512
+v 2.900579 1.299991 -2.513951
+v 3.016554 0.941337 -2.571435
+v 2.855713 1.281357 -2.474664
+v 2.972929 0.918862 -2.532765
+v 2.506130 0.920204 -2.160643
+v -3.173446 -0.542107 2.404937
+v -3.209453 0.045748 2.379028
+v 2.445409 0.560504 -2.076893
+v 2.570342 0.024900 -2.125114
+v -2.493455 0.687025 1.764050
+v 2.684069 0.992910 -2.299412
+v -2.427931 0.717893 1.714301
+v -2.119321 0.784146 1.467606
+v -1.995701 -0.573576 1.495906
+v 2.487048 1.037626 -2.146613
+v 2.534799 0.803912 -2.162379
+v 2.942544 1.202524 -2.516853
+v 2.994718 1.041175 -2.542714
+v 2.517081 0.572200 -2.127054
+v 2.637826 0.054551 -2.173658
+v 2.571093 0.562590 -2.150938
+v 2.681143 0.090791 -2.193414
+v 1.466306 1.086034 -1.335468
+v 2.979094 1.123082 -2.516365
+v -0.568604 -0.447372 0.394570
+v 1.323830 1.046159 -1.213567
+v 1.454755 -0.311009 -1.191014
+v 3.248744 0.081120 -2.625723
+v 2.688243 0.344561 -2.209711
+v -2.593169 -0.347277 1.974947
+v 3.029114 0.682935 -2.497393
+v 3.133857 0.233890 -2.537821
+v 2.530565 0.923082 -2.129303
+v -0.224107 -0.434065 0.145862
+v 3.079074 0.695309 -2.532807
+v 3.182707 0.251022 -2.572807
+v 2.606071 0.533336 -2.149005
+v 2.699770 0.131633 -2.185170
+v 2.490887 1.056969 -2.105453
+v 2.546971 0.782472 -2.123972
+v 2.684199 0.409138 -2.190257
+v 2.713735 0.282512 -2.201657
+v 2.963027 1.221936 -2.481166
+v 3.025790 1.027834 -2.512276
+v 2.918829 1.202466 -2.441528
+v 2.982266 1.006286 -2.472972
+v -2.731051 -0.419180 2.118678
+v 2.346020 1.217882 -1.984849
+v 2.691945 -0.216224 -2.122858
+v 2.626097 0.501300 -2.136348
+v 2.704180 0.166548 -2.166486
+v 2.809263 0.554231 -2.283747
+v 2.887846 0.217345 -2.314077
+v -2.799153 0.230131 2.121073
+v -2.792140 -0.550877 2.187326
+v 0.122037 -0.414552 -0.095656
+v 3.101457 0.660216 -2.517043
+v 3.187603 0.290900 -2.550293
+v 2.461960 1.061494 -2.053272
+v 2.523494 0.760321 -2.073591
+v 3.006995 1.126368 -2.480578
+v 2.963268 1.105875 -2.440934
+v 2.399616 1.050076 -1.991851
+v 2.463283 0.738458 -2.012874
+v 2.541997 0.922437 -2.085123
+v -2.620849 -0.553009 2.083075
+v -2.691633 0.277873 2.061963
+v 0.380138 -0.399580 -0.262083
+v 2.741003 0.353341 -2.170536
+v 2.702686 0.458937 -2.148505
+v 2.753845 0.239614 -2.168251
+v 2.735783 0.349782 -2.158101
+v 2.738935 0.386350 -2.160591
+v 2.754033 0.321623 -2.166418
+v 2.501059 1.494406 -2.071566
+v -2.498046 0.561724 1.911958
+v -2.495843 -0.550836 2.012409
+v 2.518039 0.913888 -2.030968
+v 2.733885 0.378609 -2.149074
+v 2.747093 0.321983 -2.154171
+v 2.457638 0.897352 -1.968773
+v -2.690304 -0.414381 2.170940
+v -3.127855 -0.045628 2.482288
+v -3.102117 -0.467490 2.500962
+v 2.770798 1.227046 -2.232150
+v 2.748385 0.411806 -2.139249
+v 2.774536 0.299695 -2.149342
+v 2.764617 1.300784 -2.232889
+v 2.792626 1.229192 -2.247504
+v -2.409183 0.623172 1.864025
+v -2.298506 -0.543169 1.884841
+v 2.786510 1.302148 -2.248235
+v 2.741907 0.400740 -2.129507
+v 2.764784 0.302663 -2.138337
+v -2.529794 -0.443952 2.062569
+v -2.339763 0.652407 1.813967
+v -2.191192 -0.534884 1.807161
+v -1.920944 -0.535987 1.596561
+v -2.034769 0.715099 1.570418
+v -1.970078 -0.080625 1.596081
+v 1.720073 1.327862 -1.406896
+v 0.572047 -0.387414 -0.352526
+v 2.806120 1.237906 -2.229508
+v 2.801037 1.298551 -2.230116
+v -3.088440 -0.411708 2.522214
+v -3.106239 -0.116627 2.508994
+v 1.609069 1.259907 -1.293617
+v 2.543657 1.253039 -2.021345
+v 1.525799 0.957611 -1.197050
+v 2.757700 0.410246 -2.104644
+v 2.784117 0.296996 -2.114841
+v 2.533483 0.613445 -1.947803
+v 2.602202 0.623366 -2.002292
+v 2.677744 -0.005017 -2.003484
+v 2.648675 0.609224 -2.037226
+v 2.672127 0.573042 -2.052187
+v 2.681143 0.534388 -2.055668
+v 2.741627 0.025636 -2.056106
+v 2.775750 0.064439 -2.086273
+v 2.738751 0.480616 -2.095645
+v 2.771306 0.147849 -2.090468
+v 2.780322 0.109196 -2.093948
+v 2.762407 0.352190 -2.102295
+v 2.766821 0.422887 -2.112228
+v 2.797824 0.227363 -2.118445
+v 2.786530 0.359571 -2.121780
+v 2.797017 0.293432 -2.123883
+v 1.548968 1.153765 -1.228190
+v 2.798795 1.166335 -2.202419
+v 2.552618 1.211834 -2.013930
+v 2.544939 1.295449 -2.015621
+v 2.863513 0.586883 -2.198347
+v 2.954250 0.197881 -2.233369
+v 2.820327 1.169125 -2.218088
+v 2.781909 1.367789 -2.204438
+v 2.803619 1.368443 -2.220086
+v 1.423109 0.978571 -1.105456
+v 1.544240 -0.271959 -1.085059
+v 3.334585 0.098417 -2.514929
+v 1.554716 0.187936 -1.132116
+v 2.612265 1.258718 -2.052108
+v 3.101424 0.726458 -2.383560
+v 3.222370 0.207946 -2.430242
+v -2.658075 -0.410585 2.212276
+v 3.150617 0.738370 -2.420182
+v 3.270283 0.225352 -2.466369
+v 3.160928 0.696012 -2.423422
+v 3.260400 0.269561 -2.461816
+v 2.619734 1.224381 -2.045928
+v 2.613334 1.294060 -2.047338
+v 2.829147 1.187974 -2.205056
+v 2.815259 1.353660 -2.206717
+v 2.570461 1.178110 -1.994503
+v 2.556271 1.332609 -1.997628
+v 2.777033 0.404579 -2.081148
+v 2.799910 0.306502 -2.089978
+v 2.789302 0.416625 -2.086768
+v 2.815452 0.304514 -2.096862
+v 2.634603 1.196277 -2.029740
+v 2.622778 1.325027 -2.032343
+v 2.651619 1.262515 -2.043053
+v 1.554142 0.981857 -1.160642
+v 2.656512 1.240018 -2.039004
+v 2.652319 1.285671 -2.039927
+v -2.635774 -0.407958 2.240880
+v 2.794724 0.385259 -2.065313
+v 2.807933 0.328634 -2.070411
+v 2.809805 0.394697 -2.069692
+v 2.824903 0.329970 -2.075520
+v 2.806035 0.357461 -2.061383
+v -2.489068 -0.351828 2.132329
+v 2.463909 1.145066 -1.865152
+v 2.782581 -0.176570 -1.992240
+v 1.570340 0.895334 -1.144881
+v 1.570297 0.896443 -1.144827
+v 1.570363 0.895879 -1.144798
+v 1.570504 0.894974 -1.144707
+v 1.570489 0.895138 -1.144709
+v 1.570444 0.896371 -1.144649
+v 1.570624 0.894816 -1.144569
+v 2.666254 1.221606 -2.028398
+v 2.658506 1.305960 -2.030103
+v 1.570869 0.894455 -1.144293
+v 1.570594 0.897869 -1.144312
+v 2.594467 1.157000 -1.966023
+v 2.575927 1.358863 -1.970106
+v 1.570894 0.897475 -1.143969
+v 2.833890 0.249041 -2.065584
+v 2.782731 0.468365 -2.045837
+v -2.701300 -0.462008 2.315575
+v -2.705561 0.099398 2.267342
+v 2.841108 1.134918 -2.151662
+v -0.479044 -0.046272 0.545593
+v 2.862191 1.138039 -2.167869
+v 2.822836 0.362979 -2.065575
+v 2.654608 1.178685 -2.006007
+v 2.639158 1.346905 -2.009409
+v -2.624374 -0.406615 2.255502
+v 2.818040 1.410109 -2.154421
+v 2.839368 1.410315 -2.170599
+v 2.863947 1.162135 -2.163311
+v 2.676065 1.265123 -2.025119
+v 2.664612 1.254980 -2.014677
+v 2.663261 1.271085 -2.014839
+v 0.971816 -0.356390 -0.546463
+v 2.844975 1.388465 -2.165580
+v 2.678566 1.253623 -2.023050
+v 2.676423 1.276959 -2.023522
+v 2.679361 1.210080 -2.012848
+v 2.669238 1.320294 -2.015076
+v 2.670727 1.241720 -2.008183
+v 2.667038 1.285720 -2.008625
+v 2.683546 1.244211 -2.017628
+v 2.679586 1.287331 -2.018500
+v 1.579791 0.985026 -1.130600
+v -1.956412 0.610154 1.662774
+v -1.856740 -0.486828 1.685802
+v 2.690246 1.238319 -2.009680
+v 2.685071 1.294657 -2.010819
+v 2.679968 1.234858 -1.997098
+v 2.674930 1.294963 -1.997700
+v -0.132362 0.006287 0.310615
+v 1.590120 0.977428 -1.119874
+v 2.748270 0.515690 -1.979648
+v 2.826352 0.180938 -2.009786
+v -2.571993 0.138873 2.206668
+v -2.521306 -0.458402 2.221998
+v -2.685403 -0.396185 2.345565
+v -2.686840 -0.001633 2.310453
+v 2.620983 1.151716 -1.932826
+v 2.600915 1.370211 -1.937245
+v 2.676704 1.174282 -1.978342
+v 2.659981 1.356362 -1.982024
+v 2.678470 1.266092 -1.988150
+v 2.693838 1.207195 -1.994723
+v 2.682881 1.326489 -1.997135
+v 2.697646 1.236845 -2.000415
+v 2.692045 1.297825 -2.001648
+v 2.697040 1.267502 -2.002758
+v 2.651196 0.936979 -1.934514
+v 2.898778 0.955779 -2.129270
+v 2.929918 0.567420 -2.117638
+v 3.008498 0.230534 -2.147968
+v 2.689859 1.236233 -1.984390
+v 2.684821 1.296338 -1.984993
+v 2.822841 0.425467 -2.012432
+v 2.852378 0.298841 -2.023833
+v 1.608781 0.850501 -1.104316
+v 2.704620 1.240012 -1.991243
+v 2.699446 1.296350 -1.992383
+v 1.624309 1.159754 -1.138181
+v 2.752678 0.550604 -1.960964
+v 2.846378 0.148901 -1.997130
+v 1.606690 0.959087 -1.103622
+v 2.710106 1.247339 -1.983562
+v 2.706145 1.290459 -1.984434
+v 2.697752 1.245476 -1.973465
+v 2.694063 1.289476 -1.973907
+v 2.707481 1.213392 -1.976781
+v 2.697358 1.323606 -1.979010
+v -2.445962 -0.418610 2.191836
+v 3.233725 0.674674 -2.334944
+v 3.319870 0.305357 -2.368193
+v 2.848333 0.363417 -2.004377
+v 2.713269 1.257710 -1.978540
+v 2.711125 1.281046 -1.979012
+v 1.518181 0.875199 -1.011295
+v 1.625158 -0.221229 -0.994015
+v 3.410238 0.117713 -2.416893
+v 2.901195 1.167312 -2.115460
+v 2.701529 1.260111 -1.967251
+v 2.700178 1.276216 -1.967413
+v 2.713626 1.269547 -1.976943
+v 1.616466 0.943560 -1.090626
+v 2.882224 1.393643 -2.117728
+v -2.383910 0.349570 2.084356
+v -2.382627 -0.452496 2.157011
+v 2.697528 1.183740 -1.950957
+v 2.682078 1.351960 -1.954359
+v 2.907001 1.144267 -2.110304
+v 2.886398 1.141212 -2.093480
+v 2.743282 0.582871 -1.930085
+v 2.853333 0.111072 -1.972562
+v 1.623579 0.887518 -1.082372
+v 0.198902 0.047648 0.105888
+v 1.621282 0.931471 -1.084234
+v 2.884178 1.416542 -2.113034
+v 2.863331 1.416404 -2.096239
+v -2.503361 -0.388481 2.254219
+v -2.538594 0.031317 2.243138
+v -1.878374 0.077127 1.724185
+v -1.889940 0.222714 1.719833
+v 2.645971 1.163066 -1.899965
+v 2.627431 1.364929 -1.904047
+v 2.718213 1.227726 -1.961754
+v 2.710466 1.312080 -1.963460
+v 1.626047 0.904541 -1.078374
+v 3.189935 0.700514 -2.275981
+v 3.294679 0.251469 -2.316409
+v 3.238191 0.712702 -2.313742
+v 3.341825 0.268415 -2.353742
+v 2.706004 0.594452 -1.884739
+v 2.826750 0.076803 -1.931343
+v 2.640884 0.583528 -1.826173
+v 2.765818 0.047924 -1.874394
+v 1.714162 1.276631 -1.167239
+v 2.724400 1.248014 -1.951930
+v 2.720207 1.293667 -1.952853
+v 1.654904 1.136021 -1.105274
+v 2.725100 1.271170 -1.948805
+v 2.713907 1.205618 -1.928023
+v 2.702083 1.334368 -1.930626
+v 1.831320 1.344269 -1.251206
+v -2.260945 0.399211 2.027604
+v -2.181051 -0.441814 2.042546
+v 2.809488 0.450216 -1.928661
+v 2.854570 0.256946 -1.946061
+v -2.423567 -0.369548 2.230060
+v -2.180836 0.422861 1.973894
+v -2.073524 -0.433428 1.968859
+v -1.805897 -0.428247 1.759726
+v -1.887675 0.473899 1.740641
+v -2.358199 -0.380076 2.187846
+v -2.358499 0.188271 2.135889
+v 2.990686 0.501054 -2.063247
+v 3.036054 0.306554 -2.080758
+v 2.665627 1.189319 -1.872442
+v 2.651437 1.343818 -1.875567
+v 2.648270 1.510792 -1.882843
+v 2.930912 1.202118 -2.074323
+v 2.723351 1.236585 -1.913028
+v 2.716951 1.306264 -1.914438
+v 2.917024 1.367804 -2.075984
+v 1.384653 -0.306314 -0.725204
+v 2.724420 1.271928 -1.908258
+v 1.621186 0.490687 -0.975352
+v 1.644965 0.346027 -0.980608
+v 0.465380 0.080433 -0.036070
+v 2.848397 0.355508 -1.916368
+v -2.408185 -0.394890 2.251162
+v 2.942750 1.186138 -2.060817
+v 2.578963 1.033323 -1.763117
+v 2.858250 -0.125781 -1.874424
+v 2.922529 1.183532 -2.043463
+v 2.676959 1.226479 -1.854450
+v 2.669279 1.310094 -1.856141
+v 2.926042 1.385458 -2.062815
+v 2.826142 0.472035 -1.899780
+v 2.880239 0.240111 -1.920660
+v 2.905643 1.384987 -2.045482
+v 3.029535 0.405571 -2.049749
+v 3.300344 0.601920 -2.275317
+v 3.350079 0.388695 -2.294513
+v 2.678242 1.268890 -1.848725
+v 2.945134 1.257227 -2.050923
+v 1.755731 1.246131 -1.122488
+v 1.720327 0.756772 -1.049674
+v 2.940050 1.317871 -2.051532
+v -2.214680 0.228844 2.067608
+v -2.157859 -0.367480 2.078067
+v 1.704158 1.079827 -1.054807
+v -2.127781 0.248308 2.009962
+v -2.051331 -0.359151 2.006140
+v -1.843236 0.290860 1.784206
+v -1.785848 -0.348829 1.798208
+v 1.604892 0.740558 -0.935201
+v 1.693974 -0.161033 -0.921860
+v 3.472397 0.138163 -2.335899
+v 1.873536 1.307570 -1.193691
+v 2.872832 0.358386 -1.885027
+v 2.959859 1.252435 -2.032667
+v 2.953743 1.325390 -2.033399
+v 3.342932 0.497244 -2.260519
+v 2.939822 1.250538 -2.015012
+v 2.829564 0.490591 -1.858224
+v 2.893102 0.218199 -1.882748
+v 2.933641 1.324275 -2.015751
+v 1.701154 -0.246228 -0.905127
+v -0.334995 -0.394882 0.699940
+v 3.318333 0.625178 -2.242012
+v 3.378166 0.368670 -2.265106
+v 3.270936 0.612055 -2.203483
+v 3.331410 0.352798 -2.226824
+v 2.870384 0.194339 -1.832802
+v 2.800672 0.493204 -1.805894
+v 2.706330 1.463624 -1.813588
+v 1.735438 1.031940 -1.015094
+v 2.096574 -0.183066 -1.184359
+v 2.738836 0.478769 -1.744593
+v 2.810966 0.169537 -1.772433
+v 1.857375 -0.219119 -0.990222
+v 2.884402 0.357112 -1.840897
+v 3.369567 0.499255 -2.224210
+v 0.704277 0.096803 -0.109143
+v 3.322719 0.484781 -2.185490
+v 1.668063 0.559071 -0.899985
+v 1.734685 -0.079917 -0.893249
+v 3.503355 0.161047 -2.294336
+v 1.853604 0.697100 -1.057274
+v 0.023839 -0.378671 0.476160
+v 2.742703 1.224204 -1.790460
+v 2.990285 1.243004 -1.985215
+v 1.751320 0.995124 -0.995284
+v 1.762367 0.864072 -0.986848
+v 1.978204 0.694100 -1.137927
+v 1.821889 1.171394 -1.054962
+v 2.860838 0.346753 -1.786883
+v 1.768391 0.914212 -0.976082
+v 2.800102 0.741867 -1.764054
+v 3.047683 0.760667 -1.958810
+v 2.801089 0.327237 -1.724923
+v 2.686153 0.887537 -1.683203
+v 2.915644 -0.066074 -1.774558
+v 1.939461 1.218681 -1.105028
+v 0.380965 -0.356962 0.277840
+v 2.157984 -0.096145 -1.130609
+v 2.171539 -0.180007 -1.133476
+v 1.870303 1.101825 -0.999439
+v 2.768902 0.690672 -1.652155
+v 2.931033 0.013315 -1.716360
+v 2.798614 1.350688 -1.707175
+v 1.895380 1.047986 -0.972059
+v -0.263286 -0.086843 0.817096
+v 1.915261 0.855545 -0.963812
+v 1.995518 1.144847 -1.050030
+v 1.923295 0.929349 -0.946858
+v 0.639411 -0.340042 0.173318
+v 2.048910 0.874962 -1.028633
+v 2.024876 1.087065 -1.024636
+v 0.098227 -0.041243 0.603403
+v 1.153992 0.084424 -0.229250
+v 2.057853 0.957558 -1.005829
+v 2.853774 1.270425 -1.644800
+v 2.691514 -0.001160 -1.386453
+v 2.705070 -0.085024 -1.389320
+v 2.781930 0.015918 -1.458515
+v 2.795485 -0.067945 -1.461382
+v 2.889620 0.961520 -1.629051
+v 3.137201 0.980321 -1.823807
+v 2.880562 1.206416 -1.616926
+v 3.128144 1.225217 -1.811681
+v 2.893827 1.173559 -1.603234
+v 3.141408 1.192360 -1.797990
+v -0.165990 -0.329480 0.922774
+v 2.905606 1.059058 -1.599313
+v 3.153188 1.077859 -1.794069
+v 2.902644 1.136966 -1.595558
+v 3.150226 1.155766 -1.790314
+v 2.906628 1.098234 -1.594232
+v 3.154210 1.117035 -1.788988
+v 0.450756 -0.003152 0.428413
+v 0.893967 -0.321301 0.137348
+v 0.203261 -0.310043 0.717218
+v -0.125580 -0.167821 0.986626
+v 0.712336 0.026704 0.345760
+v -0.090811 -0.262476 1.024542
+v 1.652963 0.059929 -0.335038
+v 0.245682 -0.133976 0.786321
+v 0.568430 -0.286725 0.550539
+v 0.283133 -0.240046 0.827356
+v 2.000346 0.011962 -0.463845
+v 0.611741 -0.102875 0.629863
+v 0.998399 0.048451 0.327453
+v 0.827128 -0.268380 0.491359
+v 1.390944 -0.279641 0.074489
+v 2.438757 0.004155 -0.738633
+v 0.652016 -0.215986 0.675288
+v 2.198852 0.014020 -0.544254
+v 0.870584 -0.078559 0.583979
+v 0.910831 -0.196982 0.637049
+v 1.127571 -0.244822 0.495289
+v 1.185795 -0.053488 0.599517
+v 1.535993 0.058149 0.320863
+v 1.947252 -0.218027 0.037437
+v 1.232465 -0.171199 0.659405
+v 2.293159 -0.159573 -0.040558
+v 2.769338 -0.097283 -0.322594
+v 1.695832 -0.196471 0.528473
+v 2.530709 -0.129037 -0.121828
+v 2.157723 0.063260 0.342121
+v 1.776927 -0.020207 0.662777
+v 1.833764 -0.121263 0.736990
+v 3.351091 0.073201 -0.443243
+v 3.364646 -0.010661 -0.446110
+v 2.948324 -0.003291 -0.110055
+v 2.961879 -0.087154 -0.112922
+v 2.992015 0.002089 -0.060870
+v 3.005570 -0.081773 -0.063737
+v 2.525032 0.038738 0.306061
+v 3.379430 0.074900 -0.358922
+v 3.392986 -0.008964 -0.361790
+v 3.036570 0.045434 0.026135
+v 2.796513 0.049478 0.227779
+v 2.357571 -0.130808 0.595337
+v 2.473462 0.016867 0.762129
+v 2.725617 -0.081249 0.592239
+v 2.544662 -0.059737 0.852031
+v 3.261550 -0.027300 0.308389
+v 3.022995 -0.051504 0.513873
+v 2.851826 0.022984 0.782891
+v 3.407963 0.046255 0.499554
+v 2.923753 -0.024800 0.883847
+v 3.168214 0.041578 0.705848
+v 3.711371 0.106264 0.292980
+v 3.724926 0.022401 0.290113
+v 3.531985 0.072015 0.446671
+v 3.545540 -0.011849 0.443804
+v 3.445588 0.055512 0.520889
+v 3.459143 -0.028351 0.518022
+v 3.563503 0.077601 0.432271
+v 3.577058 -0.006262 0.429404
+v 3.488101 0.014784 0.599486
+v 3.249111 0.000073 0.806963
+vt 0.592295 0.226200
+vt 0.597451 0.733993
+vt 0.582117 0.735152
+vt 0.578489 0.226600
+vt 0.583926 0.737917
+vt 0.580317 0.227579
+vt 0.553525 0.740642
+vt 0.440791 0.740508
+vt 0.450875 0.228401
+vt 0.552974 0.228523
+vt 0.390668 0.737686
+vt 0.405291 0.227370
+vt 0.388859 0.734922
+vt 0.403463 0.226391
+vt 0.371983 0.733724
+vt 0.388098 0.225956
+vt 0.619772 0.907170
+vt 0.592331 0.999817
+vt 0.601330 0.999666
+vt 0.610146 0.909999
+vt 0.574598 0.999907
+vt 0.590330 0.911915
+vt 0.553091 0.999969
+vt 0.566105 0.913419
+vt 0.528748 1.000000
+vt 0.538531 0.914447
+vt 0.502634 1.000000
+vt 0.478247 0.914918
+vt 0.475889 0.999968
+vt 0.508812 0.914955
+vt 0.449684 0.999906
+vt 0.448173 0.914340
+vt 0.425163 0.999816
+vt 0.419902 0.913245
+vt 0.403398 0.999703
+vt 0.394672 0.911681
+vt 0.385339 0.999570
+vt 0.373584 0.909717
+vt 0.375862 0.999397
+vt 0.362094 0.906863
+vt 0.605465 0.740602
+vt 0.597205 0.739049
+vt 0.491046 0.731144
+vt 0.490016 0.901745
+vt 0.489949 0.901745
+vt 0.579978 0.737974
+vt 0.489821 0.901745
+vt 0.558870 0.737116
+vt 0.489665 0.901745
+vt 0.534804 0.736510
+vt 0.489489 0.901745
+vt 0.508831 0.736184
+vt 0.489301 0.901744
+vt 0.482087 0.736152
+vt 0.489205 0.901744
+vt 0.455740 0.736416
+vt 0.430942 0.736963
+vt 0.408777 0.737770
+vt 0.390214 0.738801
+vt 0.379997 0.740333
+vt 0.353656 0.886514
+vt 0.378904 0.730193
+vt 0.361705 0.880889
+vt 0.395023 0.727499
+vt 0.380234 0.876591
+vt 0.415254 0.725377
+vt 0.403440 0.873201
+vt 0.438713 0.723920
+vt 0.430309 0.870869
+vt 0.464376 0.723192
+vt 0.459668 0.869695
+vt 0.477566 0.723232
+vt 0.474742 0.869751
+vt 0.491120 0.723224
+vt 0.490232 0.869731
+vt 0.517777 0.724015
+vt 0.520667 0.870977
+vt 0.543181 0.725530
+vt 0.549643 0.873375
+vt 0.566223 0.727704
+vt 0.575892 0.876824
+vt 0.585895 0.730440
+vt 0.598267 0.881171
+vt 0.611334 0.886821
+vt 0.514919 0.001808
+vt 0.498936 0.000000
+vt 0.513858 0.001846
+vt 0.529721 0.013570
+vt 0.531965 0.013618
+vt 0.556734 0.062512
+vt 0.561107 0.062168
+vt 0.573935 0.111992
+vt 0.579743 0.111218
+vt 0.580242 0.134938
+vt 0.586574 0.134186
+vt 0.586077 0.152110
+vt 0.592909 0.152041
+vt 0.591813 0.178477
+vt 0.599157 0.177963
+vt 0.593139 0.193428
+vt 0.600618 0.192699
+vt 0.592908 0.232476
+vt 0.600324 0.231459
+vt 0.496437 0.231529
+vt 0.508654 0.001888
+vt 0.518859 0.013502
+vt 0.535963 0.062914
+vt 0.546664 0.112914
+vt 0.550579 0.135830
+vt 0.554180 0.152166
+vt 0.557666 0.179076
+vt 0.558422 0.194288
+vt 0.558268 0.233688
+vt 0.501777 0.001902
+vt 0.504546 0.013460
+vt 0.508707 0.063079
+vt 0.510973 0.113312
+vt 0.511780 0.136212
+vt 0.512489 0.152160
+vt 0.513077 0.179318
+vt 0.513104 0.194651
+vt 0.512986 0.234213
+vt 0.498076 0.001898
+vt 0.496856 0.013450
+vt 0.494097 0.063062
+vt 0.491870 0.113289
+vt 0.491019 0.136187
+vt 0.490190 0.152133
+vt 0.489240 0.179289
+vt 0.488883 0.194622
+vt 0.488765 0.234184
+vt 0.490954 0.001867
+vt 0.482078 0.013458
+vt 0.466079 0.062831
+vt 0.455287 0.112805
+vt 0.451272 0.135711
+vt 0.447516 0.152039
+vt 0.443646 0.178940
+vt 0.442563 0.194150
+vt 0.442409 0.233550
+vt 0.485218 0.001812
+vt 0.470208 0.013499
+vt 0.443661 0.062377
+vt 0.426084 0.111816
+vt 0.419558 0.134746
+vt 0.413491 0.151904
+vt 0.407324 0.178257
+vt 0.405675 0.193204
+vt 0.405444 0.232252
+vt 0.483722 0.001771
+vt 0.467140 0.013541
+vt 0.437940 0.062021
+vt 0.418694 0.111026
+vt 0.411548 0.133977
+vt 0.404917 0.151817
+vt 0.398200 0.177723
+vt 0.396421 0.192455
+vt 0.396126 0.231215
+vt 0.482807 0.001091
+vt 0.465246 0.012218
+vt 0.434231 0.060860
+vt 0.413722 0.112734
+vt 0.406278 0.130880
+vt 0.397753 0.154535
+vt 0.390718 0.172336
+vt 0.388811 0.184259
+vt 0.483894 0.000866
+vt 0.467479 0.011696
+vt 0.438359 0.060674
+vt 0.419010 0.113922
+vt 0.412060 0.130257
+vt 0.403381 0.155615
+vt 0.396772 0.170695
+vt 0.394947 0.181718
+vt 0.394122 0.224712
+vt 0.489100 0.000562
+vt 0.478265 0.010987
+vt 0.458692 0.060437
+vt 0.445446 0.115578
+vt 0.440842 0.129428
+vt 0.433493 0.157124
+vt 0.429012 0.168469
+vt 0.427688 0.178256
+vt 0.426709 0.223032
+vt 0.492401 0.000471
+vt 0.485112 0.010775
+vt 0.471652 0.060373
+vt 0.462347 0.116094
+vt 0.459225 0.129185
+vt 0.452993 0.157597
+vt 0.449873 0.167802
+vt 0.448880 0.177211
+vt 0.447853 0.222532
+vt 0.495979 0.000427
+vt 0.492542 0.010672
+vt 0.485745 0.060349
+vt 0.480752 0.116370
+vt 0.479235 0.129074
+vt 0.474359 0.157853
+vt 0.472721 0.167476
+vt 0.472093 0.176692
+vt 0.471042 0.222291
+vt 0.497803 0.000430
+vt 0.496333 0.010680
+vt 0.492949 0.060359
+vt 0.490171 0.116373
+vt 0.489471 0.129091
+vt 0.485358 0.157859
+vt 0.484479 0.167501
+vt 0.484040 0.176724
+vt 0.482990 0.222314
+vt 0.499680 0.000431
+vt 0.500232 0.010681
+vt 0.500355 0.060366
+vt 0.499854 0.116392
+vt 0.499996 0.129099
+vt 0.496658 0.157879
+vt 0.496558 0.167505
+vt 0.496314 0.176721
+vt 0.495264 0.222320
+vt 0.503340 0.000484
+vt 0.507844 0.010802
+vt 0.514842 0.060424
+vt 0.518821 0.116161
+vt 0.520601 0.129259
+vt 0.518915 0.157676
+vt 0.520341 0.167887
+vt 0.520484 0.177297
+vt 0.519458 0.222618
+vt 0.506801 0.000583
+vt 0.515045 0.011031
+vt 0.528575 0.060520
+vt 0.536823 0.115687
+vt 0.540149 0.129546
+vt 0.540157 0.157251
+vt 0.543032 0.168605
+vt 0.543547 0.178394
+vt 0.542568 0.223170
+vt 0.512534 0.000900
+vt 0.526992 0.011767
+vt 0.551432 0.060809
+vt 0.566860 0.114099
+vt 0.572744 0.130449
+vt 0.575967 0.155821
+vt 0.581260 0.170915
+vt 0.582411 0.181941
+vt 0.581586 0.224936
+vt 0.514004 0.001128
+vt 0.530070 0.012295
+vt 0.557398 0.061007
+vt 0.574770 0.112926
+vt 0.581304 0.131089
+vt 0.585745 0.154759
+vt 0.591675 0.172575
+vt 0.593008 0.184503
+vt 0.500194 0.123038
+vt 0.538682 0.145632
+vt 0.499355 0.147686
+vt 0.567425 0.437987
+vt 0.495309 0.437689
+vt 0.572058 0.485965
+vt 0.494594 0.485885
+vt 0.579203 0.530428
+vt 0.494130 0.530571
+vt 0.588071 0.564595
+vt 0.493832 0.570346
+vt 0.603460 0.591429
+vt 0.494064 0.596672
+vt 0.634825 0.640783
+vt 0.495158 0.646017
+vt 0.673396 0.693624
+vt 0.496679 0.696159
+vt 0.692678 0.734348
+vt 0.497801 0.742533
+vt 0.702762 0.758051
+vt 0.498113 0.758399
+vt 0.701606 0.793564
+vt 0.498079 0.793255
+vt 0.493311 0.794017
+vt 0.530328 0.123074
+vt 0.571363 0.144013
+vt 0.627653 0.438535
+vt 0.636786 0.486372
+vt 0.650275 0.530678
+vt 0.666763 0.560227
+vt 0.694688 0.587480
+vt 0.750987 0.636789
+vt 0.820060 0.691789
+vt 0.854194 0.727752
+vt 0.872335 0.757923
+vt 0.870198 0.793953
+vt 0.555251 0.123104
+vt 0.591748 0.143109
+vt 0.665577 0.439237
+vt 0.677585 0.487036
+vt 0.695058 0.531278
+vt 0.716302 0.557996
+vt 0.751975 0.585509
+vt 0.823559 0.634725
+vt 0.911312 0.690971
+vt 0.954422 0.723886
+vt 0.977512 0.758039
+vt 0.974704 0.794354
+vt 0.570653 0.123123
+vt 0.596282 0.143062
+vt 0.674482 0.439878
+vt 0.687203 0.487720
+vt 0.705580 0.531920
+vt 0.727870 0.558106
+vt 0.765162 0.585722
+vt 0.839794 0.634825
+vt 0.931229 0.691232
+vt 0.975934 0.723388
+vt 1.000000 0.758335
+vt 0.996949 0.794648
+vt 0.404437 0.142964
+vt 0.426515 0.122951
+vt 0.573874 0.123126
+vt 0.323231 0.439478
+vt 0.310002 0.487319
+vt 0.291306 0.531610
+vt 0.269108 0.558029
+vt 0.232259 0.585436
+vt 0.158605 0.634392
+vt 0.068392 0.690386
+vt 0.024123 0.722737
+vt 0.000000 0.757160
+vt 0.002277 0.793454
+vt 0.408288 0.143022
+vt 0.329678 0.438833
+vt 0.316870 0.486617
+vt 0.298892 0.530832
+vt 0.277591 0.557845
+vt 0.242362 0.585244
+vt 0.172141 0.634290
+vt 0.086185 0.690163
+vt 0.044209 0.723323
+vt 0.021216 0.756939
+vt 0.023503 0.793219
+vt 0.429735 0.122954
+vt 0.428009 0.143973
+vt 0.365185 0.438218
+vt 0.354927 0.486047
+vt 0.340715 0.530335
+vt 0.323958 0.560190
+vt 0.296482 0.587348
+vt 0.241975 0.636524
+vt 0.175315 0.691197
+vt 0.142962 0.727426
+vt 0.125096 0.757073
+vt 0.126941 0.793066
+vt 0.445138 0.122973
+vt 0.460221 0.145670
+vt 0.423771 0.437812
+vt 0.417791 0.485792
+vt 0.409775 0.530252
+vt 0.400446 0.564744
+vt 0.385513 0.591511
+vt 0.356231 0.640793
+vt 0.320514 0.693380
+vt 0.303405 0.734406
+vt 0.293784 0.757604
+vt 0.294807 0.793079
+vt 0.470061 0.123002
+vt 0.584118 0.143846
+vt 0.652512 0.440323
+vt 0.663585 0.488333
+vt 0.679561 0.533226
+vt 0.698964 0.560878
+vt 0.731472 0.587869
+vt 0.796494 0.637100
+vt 0.876085 0.692426
+vt 0.914822 0.726015
+vt 0.935707 0.758515
+vt 0.932883 0.794633
+vt 0.564354 0.123115
+vt 0.557453 0.145374
+vt 0.603941 0.440767
+vt 0.611404 0.489119
+vt 0.622180 0.535513
+vt 0.635332 0.566338
+vt 0.657480 0.591977
+vt 0.701732 0.641506
+vt 0.755840 0.694578
+vt 0.781940 0.731416
+vt 0.796061 0.758682
+vt 0.793893 0.794463
+vt 0.543740 0.123090
+vt 0.520898 0.147383
+vt 0.537164 0.441133
+vt 0.539680 0.489942
+vt 0.543358 0.538385
+vt 0.547975 0.573543
+vt 0.555980 0.597337
+vt 0.571895 0.647283
+vt 0.591287 0.697315
+vt 0.600266 0.738659
+vt 0.605208 0.758809
+vt 0.604010 0.794167
+vt 0.515597 0.123057
+vt 0.480790 0.147467
+vt 0.463733 0.441064
+vt 0.460823 0.489897
+vt 0.456750 0.538466
+vt 0.452065 0.573899
+vt 0.444570 0.597554
+vt 0.429485 0.647493
+vt 0.410904 0.697284
+vt 0.401280 0.738907
+vt 0.396151 0.758578
+vt 0.396067 0.793912
+vt 0.484791 0.123020
+vt 0.444068 0.145371
+vt 0.396343 0.440538
+vt 0.388469 0.488901
+vt 0.377334 0.535405
+vt 0.364191 0.566484
+vt 0.342521 0.591951
+vt 0.299133 0.641406
+vt 0.245883 0.694154
+vt 0.219396 0.731230
+vt 0.205038 0.757996
+vt 0.206019 0.793754
+vt 0.456648 0.122986
+vt 0.417060 0.143778
+vt 0.346645 0.439977
+vt 0.335120 0.487990
+vt 0.318813 0.532980
+vt 0.299476 0.560871
+vt 0.267422 0.587665
+vt 0.203316 0.636772
+vt 0.124729 0.691714
+vt 0.085988 0.725511
+vt 0.064911 0.757495
+vt 0.066727 0.793592
+vt 0.436035 0.122962
+vt 0.495260 0.736170
+vt 0.500696 0.234197
+vt 0.536496 0.234045
+vt 0.577350 0.233157
+vt 0.495655 0.736171
+vt 0.501053 0.234197
+vt 0.464891 0.233960
+vt 0.422300 0.232972
+vt 0.408534 0.223769
+vt 0.563583 0.223955
+vt 0.367604 0.795692
+vt 0.344548 0.863566
+vt 0.343493 0.862334
+vt 0.368659 0.796923
+vt 0.344327 0.876900
+vt 0.343272 0.875668
+vt 0.145143 0.856433
+vt 0.144088 0.855201
+vt 0.144487 0.796170
+vt 0.143432 0.794939
+vt 0.132013 0.795411
+vt 0.130679 0.853014
+vt 0.129623 0.851782
+vt 0.133068 0.796642
+vt 0.006462 0.836828
+vt 0.005407 0.835597
+vt 0.003936 0.813497
+vt 0.002882 0.812266
+vt 0.005457 0.809612
+vt 0.004402 0.808380
+vt 0.004946 0.796489
+vt 0.003891 0.795258
+vt 0.854563 0.797672
+vt 0.853823 0.852194
+vt 0.854872 0.853763
+vt 0.853514 0.796103
+vt 0.978631 0.836386
+vt 0.979680 0.837955
+vt 0.982018 0.813180
+vt 0.983067 0.814749
+vt 0.980641 0.809310
+vt 0.981690 0.810879
+vt 0.981636 0.796256
+vt 0.982685 0.797825
+vt 0.618972 0.797391
+vt 0.639573 0.862183
+vt 0.640622 0.863752
+vt 0.617923 0.795822
+vt 0.639302 0.875450
+vt 0.640350 0.877019
+vt 0.839233 0.855561
+vt 0.840281 0.857130
+vt 0.842114 0.795606
+vt 0.843162 0.797175
+vt 0.341429 0.892018
+vt 0.354422 0.771751
+vt 0.371180 0.771906
+vt 0.361304 0.757630
+vt 0.375583 0.756290
+vt 0.376061 0.747099
+vt 0.386704 0.746114
+vt 0.396022 0.741918
+vt 0.399519 0.741536
+vt 0.406939 0.741656
+vt 0.407021 0.741651
+vt 0.362783 0.891001
+vt 0.382518 0.772090
+vt 0.385281 0.755640
+vt 0.393806 0.745632
+vt 0.401875 0.741347
+vt 0.407053 0.741648
+vt 0.377126 0.890562
+vt 0.415783 0.773053
+vt 0.413937 0.755087
+vt 0.414119 0.745184
+vt 0.408737 0.741153
+vt 0.407022 0.741639
+vt 0.418673 0.890604
+vt 0.457357 0.774594
+vt 0.449912 0.755475
+vt 0.439090 0.745395
+vt 0.417274 0.741201
+vt 0.406885 0.741627
+vt 0.367889 0.778452
+vt 0.364200 0.897177
+vt 0.470167 0.891713
+vt 0.370122 0.761446
+vt 0.381736 0.748731
+vt 0.398612 0.742335
+vt 0.406817 0.741630
+vt 0.341083 0.776828
+vt 0.346977 0.760159
+vt 0.366096 0.747919
+vt 0.393148 0.742062
+vt 0.406985 0.741639
+vt 0.330950 0.895375
+vt 0.334983 0.775889
+vt 0.341985 0.759566
+vt 0.362746 0.747620
+vt 0.391920 0.741975
+vt 0.407026 0.741643
+vt 0.322792 0.894560
+vt 0.337783 0.774019
+vt 0.345386 0.758625
+vt 0.365129 0.747299
+vt 0.392543 0.741914
+vt 0.407019 0.741650
+vt 0.324143 0.893295
+vt 0.342886 0.773061
+vt 0.350401 0.758202
+vt 0.368571 0.747202
+vt 0.393616 0.741910
+vt 0.406994 0.741653
+vt 0.329152 0.892735
+vt 0.606943 0.892335
+vt 0.578997 0.772154
+vt 0.598183 0.772042
+vt 0.576236 0.756530
+vt 0.592545 0.757906
+vt 0.567870 0.746331
+vt 0.580039 0.747342
+vt 0.558973 0.741727
+vt 0.562967 0.742117
+vt 0.553477 0.741826
+vt 0.553572 0.741831
+vt 0.582516 0.891263
+vt 0.566609 0.772310
+vt 0.565670 0.755855
+vt 0.560098 0.745830
+vt 0.556401 0.741531
+vt 0.553436 0.741823
+vt 0.566831 0.890789
+vt 0.533400 0.773194
+vt 0.537139 0.755234
+vt 0.539725 0.745333
+vt 0.549548 0.741322
+vt 0.553438 0.741813
+vt 0.525261 0.890731
+vt 0.494395 0.774638
+vt 0.503451 0.755539
+vt 0.516203 0.745487
+vt 0.541534 0.741350
+vt 0.553540 0.741802
+vt 0.599293 0.897457
+vt 0.597789 0.778726
+vt 0.476853 0.891721
+vt 0.595602 0.761715
+vt 0.582464 0.748970
+vt 0.563094 0.742531
+vt 0.553620 0.741805
+vt 0.620315 0.777161
+vt 0.614992 0.760479
+vt 0.595561 0.748192
+vt 0.567681 0.742270
+vt 0.553478 0.741814
+vt 0.627351 0.895729
+vt 0.624397 0.776234
+vt 0.618175 0.759896
+vt 0.597681 0.747900
+vt 0.568493 0.742185
+vt 0.553449 0.741818
+vt 0.633134 0.894930
+vt 0.618307 0.774354
+vt 0.611745 0.758943
+vt 0.593228 0.747572
+vt 0.567181 0.742122
+vt 0.553475 0.741825
+vt 0.628051 0.893657
+vt 0.611697 0.773381
+vt 0.605318 0.758506
+vt 0.588819 0.747464
+vt 0.565791 0.742115
+vt 0.553508 0.741827
+vt 0.621374 0.893083
+vt 0.467142 0.741480
+vt 0.460043 0.823727
+vt 0.466255 0.823734
+vt 0.491990 0.741510
+vt 0.491102 0.823764
+vt 0.497314 0.823771
+vt 0.459387 0.936839
+vt 0.481806 0.936865
+vt 0.442062 0.996371
+vt 0.457305 0.996390
+vt 0.441061 0.998017
+vt 0.455880 0.998035
+vt 0.440376 0.996388
+vt 0.454893 0.996405
+vt 0.440171 0.989270
+vt 0.454568 0.989287
+vt 0.440732 0.939254
+vt 0.455138 0.939271
+vt 0.441300 0.928768
+vt 0.455901 0.928785
+vt 0.442019 0.920921
+vt 0.456893 0.920939
+vt 0.442564 0.913924
+vt 0.457640 0.913942
+vt 0.463957 0.747884
+vt 0.487465 0.747912
+vt 0.464844 0.743833
+vt 0.488714 0.743862
+vt 0.549439 0.921434
+vt 0.548851 0.918488
+vt 0.544157 0.921323
+vt 0.551271 0.945406
+vt 0.533127 0.945025
+vt 0.552776 0.983628
+vt 0.528590 0.983120
+vt 0.552915 0.990499
+vt 0.528985 0.989997
+vt 0.552488 0.989209
+vt 0.532597 0.988792
+vt 0.540100 0.920799
+vt 0.519194 0.943227
+vt 0.510019 0.980723
+vt 0.510611 0.987626
+vt 0.517323 0.986820
+vt 0.538358 0.920003
+vt 0.513207 0.940494
+vt 0.502039 0.977080
+vt 0.502715 0.984021
+vt 0.510760 0.983824
+vt 0.539395 0.919149
+vt 0.516770 0.937557
+vt 0.506788 0.973165
+vt 0.507415 0.980148
+vt 0.514666 0.980605
+vt 0.542934 0.918463
+vt 0.528929 0.935204
+vt 0.522994 0.970029
+vt 0.523449 0.977045
+vt 0.527995 0.978025
+vt 0.548028 0.918132
+vt 0.546424 0.934066
+vt 0.546314 0.968512
+vt 0.546522 0.975543
+vt 0.547174 0.976777
+vt 0.553310 0.918243
+vt 0.564569 0.934446
+vt 0.570500 0.969019
+vt 0.570451 0.976045
+vt 0.567066 0.977194
+vt 0.557366 0.918766
+vt 0.578501 0.936245
+vt 0.589071 0.971416
+vt 0.588825 0.978417
+vt 0.582340 0.979166
+vt 0.559110 0.919562
+vt 0.584488 0.938978
+vt 0.597051 0.975059
+vt 0.596721 0.982022
+vt 0.588903 0.982162
+vt 0.558072 0.920417
+vt 0.580925 0.941915
+vt 0.592302 0.978974
+vt 0.592022 0.985895
+vt 0.584996 0.985382
+vt 0.554533 0.921102
+vt 0.568767 0.944267
+vt 0.576096 0.982110
+vt 0.575988 0.988998
+vt 0.571668 0.987961
+vt 0.424145 0.921277
+vt 0.423693 0.918338
+vt 0.418870 0.920933
+vt 0.423822 0.945229
+vt 0.405702 0.944048
+vt 0.421388 0.983437
+vt 0.397235 0.981863
+vt 0.420772 0.990308
+vt 0.396875 0.988751
+vt 0.420346 0.989024
+vt 0.400481 0.987729
+vt 0.414819 0.920239
+vt 0.391787 0.941664
+vt 0.378687 0.978685
+vt 0.378524 0.985607
+vt 0.385227 0.985116
+vt 0.413078 0.919381
+vt 0.385807 0.938716
+vt 0.370716 0.974756
+vt 0.370638 0.981719
+vt 0.378671 0.981884
+vt 0.414113 0.918588
+vt 0.389363 0.935994
+vt 0.375457 0.971128
+vt 0.375328 0.978129
+vt 0.382570 0.978900
+vt 0.417648 0.918074
+vt 0.401504 0.934227
+vt 0.391639 0.968773
+vt 0.391339 0.975799
+vt 0.395879 0.976963
+vt 0.422734 0.917975
+vt 0.418975 0.933889
+vt 0.414927 0.968321
+vt 0.414380 0.975353
+vt 0.415032 0.976592
+vt 0.428009 0.918319
+vt 0.437095 0.935070
+vt 0.439080 0.969896
+vt 0.438277 0.976910
+vt 0.434897 0.977887
+vt 0.432060 0.919013
+vt 0.451010 0.937454
+vt 0.457627 0.973073
+vt 0.456628 0.980054
+vt 0.450151 0.980500
+vt 0.433801 0.919872
+vt 0.456990 0.940402
+vt 0.465599 0.977003
+vt 0.464515 0.983942
+vt 0.456707 0.983732
+vt 0.432766 0.920664
+vt 0.453434 0.943124
+vt 0.460858 0.980631
+vt 0.459824 0.987532
+vt 0.452808 0.986716
+vt 0.429231 0.921178
+vt 0.441293 0.944891
+vt 0.444676 0.982987
+vt 0.443813 0.989862
+vt 0.439499 0.988653
+vt 0.489178 0.904955
+vt 0.478920 0.903724
+vt 0.488659 0.905860
+vt 0.513457 0.924223
+vt 0.511674 0.927332
+vt 0.523738 0.959386
+vt 0.521362 0.963531
+vt 0.522949 0.966121
+vt 0.520598 0.970220
+vt 0.515067 0.966111
+vt 0.513113 0.969519
+vt 0.486649 0.906626
+vt 0.504771 0.929961
+vt 0.512160 0.967035
+vt 0.511494 0.973688
+vt 0.505545 0.972402
+vt 0.483455 0.907135
+vt 0.493798 0.931710
+vt 0.497535 0.969367
+vt 0.497023 0.975995
+vt 0.493517 0.974319
+vt 0.479562 0.907310
+vt 0.480427 0.932313
+vt 0.479712 0.970170
+vt 0.479389 0.976790
+vt 0.478859 0.974980
+vt 0.475564 0.907126
+vt 0.466693 0.931678
+vt 0.461405 0.969323
+vt 0.461277 0.975952
+vt 0.463802 0.974284
+vt 0.472068 0.906608
+vt 0.454687 0.929901
+vt 0.445402 0.966955
+vt 0.445442 0.973609
+vt 0.450640 0.972336
+vt 0.469608 0.905838
+vt 0.446236 0.927254
+vt 0.434137 0.963426
+vt 0.434297 0.970118
+vt 0.441375 0.969434
+vt 0.468558 0.904931
+vt 0.442627 0.924138
+vt 0.429327 0.959274
+vt 0.429538 0.966009
+vt 0.437419 0.966018
+vt 0.469077 0.904026
+vt 0.444410 0.921030
+vt 0.431703 0.955130
+vt 0.431889 0.961909
+vt 0.439374 0.962610
+vt 0.471086 0.903260
+vt 0.451313 0.918400
+vt 0.440904 0.951625
+vt 0.440993 0.958442
+vt 0.446941 0.959728
+vt 0.474281 0.902751
+vt 0.462285 0.916651
+vt 0.455530 0.949294
+vt 0.455463 0.956135
+vt 0.458970 0.957810
+vt 0.478174 0.902575
+vt 0.475656 0.916048
+vt 0.473353 0.948490
+vt 0.473097 0.955340
+vt 0.473628 0.957150
+vt 0.482172 0.902760
+vt 0.489391 0.916684
+vt 0.491660 0.949337
+vt 0.491210 0.956177
+vt 0.488685 0.957846
+vt 0.485667 0.903278
+vt 0.501397 0.918460
+vt 0.507663 0.951705
+vt 0.507044 0.958521
+vt 0.501847 0.959794
+vt 0.488128 0.904048
+vt 0.509848 0.921108
+vt 0.518928 0.955234
+vt 0.518190 0.962012
+vt 0.511111 0.962696
+vt 0.490402 0.906636
+vt 0.478826 0.907307
+vt 0.488446 0.905821
+vt 0.501508 0.904625
+vt 0.497682 0.903031
+vt 0.513572 0.898392
+vt 0.507734 0.895959
+vt 0.520474 0.898400
+vt 0.513468 0.895480
+vt 0.527756 0.896328
+vt 0.519527 0.892898
+vt 0.532641 0.890349
+vt 0.523612 0.886586
+vt 0.534751 0.880504
+vt 0.525409 0.876611
+vt 0.483918 0.905221
+vt 0.488823 0.901856
+vt 0.494212 0.894166
+vt 0.497242 0.893329
+vt 0.500470 0.890372
+vt 0.502703 0.883814
+vt 0.503775 0.873743
+vt 0.478030 0.904996
+vt 0.477305 0.901416
+vt 0.476632 0.893495
+vt 0.476145 0.892524
+vt 0.475691 0.889426
+vt 0.475516 0.882776
+vt 0.475646 0.872669
+vt 0.472360 0.905207
+vt 0.466213 0.901829
+vt 0.459702 0.894125
+vt 0.455830 0.893280
+vt 0.451831 0.890314
+vt 0.449337 0.883750
+vt 0.448558 0.873677
+vt 0.468427 0.905797
+vt 0.458520 0.902984
+vt 0.447960 0.895887
+vt 0.441739 0.895394
+vt 0.435282 0.892798
+vt 0.431179 0.886476
+vt 0.429771 0.876497
+vt 0.467286 0.906609
+vt 0.456287 0.904571
+vt 0.444552 0.898310
+vt 0.437650 0.898301
+vt 0.430478 0.896212
+vt 0.425909 0.890222
+vt 0.424318 0.880373
+vt 0.469241 0.907423
+vt 0.460112 0.906165
+vt 0.450390 0.900743
+vt 0.444656 0.901221
+vt 0.438707 0.899641
+vt 0.434938 0.893984
+vt 0.433660 0.884266
+vt 0.473770 0.908024
+vt 0.468971 0.907340
+vt 0.463912 0.902535
+vt 0.460882 0.903372
+vt 0.457764 0.902168
+vt 0.455847 0.896756
+vt 0.455294 0.887134
+vt 0.479658 0.908248
+vt 0.480490 0.907779
+vt 0.481492 0.903207
+vt 0.481979 0.904178
+vt 0.482542 0.903114
+vt 0.483033 0.897794
+vt 0.483423 0.888208
+vt 0.485328 0.908037
+vt 0.491581 0.907367
+vt 0.498422 0.902577
+vt 0.502294 0.903422
+vt 0.506403 0.902226
+vt 0.509212 0.896820
+vt 0.510510 0.887200
+vt 0.489260 0.907447
+vt 0.499275 0.906212
+vt 0.510164 0.900814
+vt 0.516385 0.901307
+vt 0.522952 0.899742
+vt 0.527370 0.894095
+vt 0.529298 0.884380
+vt 0.435164 0.921226
+vt 0.423586 0.921892
+vt 0.433212 0.920281
+vt 0.446278 0.919232
+vt 0.442459 0.917382
+vt 0.458366 0.913050
+vt 0.452538 0.910227
+vt 0.465268 0.913058
+vt 0.458275 0.909671
+vt 0.472557 0.911003
+vt 0.464343 0.907025
+vt 0.477464 0.905073
+vt 0.468453 0.900708
+vt 0.479612 0.895309
+vt 0.470288 0.890792
+vt 0.428687 0.919585
+vt 0.433606 0.916021
+vt 0.439025 0.908150
+vt 0.442059 0.907178
+vt 0.445298 0.904097
+vt 0.447556 0.897496
+vt 0.448667 0.887469
+vt 0.422799 0.919325
+vt 0.422089 0.915513
+vt 0.421447 0.907374
+vt 0.420965 0.906248
+vt 0.420524 0.903004
+vt 0.420375 0.896296
+vt 0.420542 0.886228
+vt 0.417129 0.919572
+vt 0.410995 0.915994
+vt 0.404515 0.908109
+vt 0.400647 0.907129
+vt 0.396659 0.904039
+vt 0.394191 0.897432
+vt 0.393450 0.887403
+vt 0.413194 0.920257
+vt 0.403297 0.917335
+vt 0.392764 0.910156
+vt 0.386546 0.909585
+vt 0.380099 0.906924
+vt 0.376020 0.900598
+vt 0.374650 0.890678
+vt 0.412048 0.921199
+vt 0.401056 0.919178
+vt 0.389345 0.912967
+vt 0.382443 0.912959
+vt 0.375279 0.910887
+vt 0.370733 0.904945
+vt 0.369179 0.895177
+vt 0.414000 0.922144
+vt 0.404875 0.921027
+vt 0.395173 0.915790
+vt 0.389436 0.916346
+vt 0.383493 0.914865
+vt 0.379745 0.909310
+vt 0.378503 0.899693
+vt 0.418526 0.922840
+vt 0.413728 0.922388
+vt 0.408686 0.917867
+vt 0.405652 0.918839
+vt 0.402538 0.917793
+vt 0.400641 0.912523
+vt 0.400124 0.903017
+vt 0.424413 0.923099
+vt 0.425245 0.922896
+vt 0.426264 0.918643
+vt 0.426745 0.919770
+vt 0.427312 0.918886
+vt 0.427823 0.913722
+vt 0.428249 0.904258
+vt 0.430084 0.922854
+vt 0.436339 0.922415
+vt 0.443196 0.917909
+vt 0.447065 0.918889
+vt 0.451177 0.917851
+vt 0.454007 0.912586
+vt 0.455341 0.903083
+vt 0.434019 0.922168
+vt 0.444037 0.921074
+vt 0.454947 0.915862
+vt 0.461165 0.916432
+vt 0.467738 0.914966
+vt 0.472177 0.909421
+vt 0.474141 0.899807
+vt 0.560322 0.921376
+vt 0.548743 0.922041
+vt 0.558370 0.920430
+vt 0.571436 0.919381
+vt 0.567617 0.917532
+vt 0.583524 0.913199
+vt 0.577696 0.910376
+vt 0.590426 0.913208
+vt 0.583432 0.909820
+vt 0.597715 0.911152
+vt 0.589501 0.907174
+vt 0.602622 0.905222
+vt 0.593610 0.900857
+vt 0.604770 0.895458
+vt 0.595446 0.890942
+vt 0.553844 0.919735
+vt 0.558764 0.916170
+vt 0.564183 0.908299
+vt 0.567217 0.907327
+vt 0.570456 0.904246
+vt 0.572714 0.897645
+vt 0.573824 0.887618
+vt 0.547957 0.919475
+vt 0.547247 0.915662
+vt 0.546605 0.907524
+vt 0.546123 0.906397
+vt 0.545682 0.903153
+vt 0.545532 0.896446
+vt 0.545700 0.886378
+vt 0.542286 0.919721
+vt 0.536153 0.916144
+vt 0.529672 0.908258
+vt 0.525804 0.907278
+vt 0.521817 0.904188
+vt 0.519348 0.897581
+vt 0.518608 0.887552
+vt 0.538351 0.920406
+vt 0.528455 0.917485
+vt 0.517922 0.910305
+vt 0.511704 0.909735
+vt 0.505256 0.907073
+vt 0.501178 0.900747
+vt 0.499808 0.890828
+vt 0.537206 0.921348
+vt 0.526215 0.919327
+vt 0.514503 0.913117
+vt 0.507601 0.913109
+vt 0.500437 0.911036
+vt 0.495891 0.905095
+vt 0.494337 0.895326
+vt 0.539158 0.922293
+vt 0.530033 0.921176
+vt 0.520331 0.915940
+vt 0.514594 0.916496
+vt 0.508651 0.915014
+vt 0.504902 0.909460
+vt 0.503661 0.899843
+vt 0.543684 0.922989
+vt 0.538886 0.922537
+vt 0.533844 0.918017
+vt 0.530810 0.918989
+vt 0.527696 0.917942
+vt 0.525799 0.912672
+vt 0.525282 0.903166
+vt 0.549571 0.923249
+vt 0.550403 0.923045
+vt 0.551422 0.918792
+vt 0.551903 0.919919
+vt 0.552470 0.919035
+vt 0.552980 0.913871
+vt 0.553407 0.904407
+vt 0.555241 0.923003
+vt 0.561497 0.922564
+vt 0.568354 0.918058
+vt 0.572222 0.919038
+vt 0.576335 0.918000
+vt 0.579165 0.912735
+vt 0.580499 0.903232
+vt 0.559177 0.922317
+vt 0.569195 0.921223
+vt 0.580104 0.916011
+vt 0.586323 0.916581
+vt 0.592896 0.915115
+vt 0.597334 0.909570
+vt 0.599299 0.899957
+vt 0.559044 0.903560
+vt 0.555560 0.904061
+vt 0.557240 0.903502
+vt 0.562383 0.902441
+vt 0.558853 0.902327
+vt 0.565998 0.899330
+vt 0.560611 0.899155
+vt 0.568167 0.890833
+vt 0.561703 0.890623
+vt 0.555180 0.903480
+vt 0.554825 0.902283
+vt 0.554462 0.899089
+vt 0.554324 0.890544
+vt 0.553180 0.903497
+vt 0.550910 0.902317
+vt 0.548487 0.899141
+vt 0.547155 0.890606
+vt 0.551541 0.903552
+vt 0.547705 0.902423
+vt 0.543596 0.899303
+vt 0.541285 0.890801
+vt 0.550515 0.903635
+vt 0.545699 0.902586
+vt 0.540533 0.899551
+vt 0.537609 0.891098
+vt 0.550258 0.903734
+vt 0.545195 0.902780
+vt 0.539764 0.899847
+vt 0.536687 0.891453
+vt 0.550808 0.903834
+vt 0.546272 0.902975
+vt 0.541408 0.900146
+vt 0.538658 0.891812
+vt 0.552082 0.903920
+vt 0.548764 0.903143
+vt 0.545212 0.900402
+vt 0.543224 0.892119
+vt 0.553886 0.903978
+vt 0.552293 0.903257
+vt 0.550598 0.900576
+vt 0.549688 0.892329
+vt 0.555946 0.904000
+vt 0.556322 0.903301
+vt 0.556747 0.900643
+vt 0.557066 0.892408
+vt 0.557947 0.903983
+vt 0.560237 0.903267
+vt 0.562722 0.900591
+vt 0.564236 0.892346
+vt 0.559585 0.903929
+vt 0.563441 0.903161
+vt 0.567613 0.900428
+vt 0.570106 0.892151
+vt 0.560611 0.903845
+vt 0.565448 0.902998
+vt 0.570676 0.900181
+vt 0.573781 0.891854
+vt 0.560868 0.903746
+vt 0.565951 0.902804
+vt 0.571445 0.899885
+vt 0.574704 0.891499
+vt 0.560318 0.903646
+vt 0.564875 0.902609
+vt 0.569802 0.899586
+vt 0.572732 0.891140
+vt 0.397743 0.903368
+vt 0.394259 0.903868
+vt 0.395939 0.903310
+vt 0.401082 0.902248
+vt 0.397553 0.902134
+vt 0.404697 0.899137
+vt 0.399311 0.898963
+vt 0.406867 0.890641
+vt 0.400403 0.890431
+vt 0.393880 0.903288
+vt 0.393524 0.902090
+vt 0.393162 0.898897
+vt 0.393024 0.890351
+vt 0.391879 0.903305
+vt 0.389610 0.902124
+vt 0.387187 0.898948
+vt 0.385854 0.890414
+vt 0.390241 0.903359
+vt 0.386405 0.902231
+vt 0.382296 0.899111
+vt 0.379985 0.890608
+vt 0.389215 0.903442
+vt 0.384398 0.902393
+vt 0.379233 0.899359
+vt 0.376309 0.890906
+vt 0.388958 0.903541
+vt 0.383895 0.902587
+vt 0.378464 0.899655
+vt 0.375387 0.891261
+vt 0.389508 0.903641
+vt 0.384971 0.902783
+vt 0.380107 0.899953
+vt 0.377358 0.891619
+vt 0.390782 0.903727
+vt 0.387464 0.902951
+vt 0.383912 0.900209
+vt 0.381923 0.891927
+vt 0.392586 0.903786
+vt 0.390993 0.903065
+vt 0.389298 0.900384
+vt 0.388387 0.892136
+vt 0.394645 0.903808
+vt 0.395022 0.903108
+vt 0.395447 0.900450
+vt 0.395766 0.892216
+vt 0.396646 0.903790
+vt 0.398936 0.903074
+vt 0.401422 0.900398
+vt 0.402936 0.892153
+vt 0.398285 0.903736
+vt 0.402141 0.902968
+vt 0.406313 0.900236
+vt 0.408806 0.891959
+vt 0.399310 0.903653
+vt 0.404148 0.902806
+vt 0.409376 0.899988
+vt 0.412481 0.891661
+vt 0.399568 0.903554
+vt 0.404651 0.902612
+vt 0.410145 0.899692
+vt 0.413404 0.891306
+vt 0.399017 0.903454
+vt 0.403575 0.902416
+vt 0.408502 0.899393
+vt 0.411432 0.890948
+vt 0.560050 0.895619
+vt 0.598903 0.933649
+vt 0.559659 0.931810
+vt 0.626960 0.931921
+vt 0.632743 0.931121
+vt 0.633550 0.930793
+vt 0.633940 0.894602
+vt 0.632944 0.930462
+vt 0.633334 0.894271
+vt 0.630952 0.930143
+vt 0.631342 0.893951
+vt 0.627661 0.929849
+vt 0.589170 0.929516
+vt 0.589561 0.893325
+vt 0.398164 0.895426
+vt 0.363809 0.933369
+vt 0.397773 0.931617
+vt 0.330560 0.931567
+vt 0.322401 0.930751
+vt 0.321016 0.894228
+vt 0.320625 0.930420
+vt 0.320649 0.893898
+vt 0.320258 0.930089
+vt 0.321706 0.893582
+vt 0.321316 0.929773
+vt 0.323752 0.929486
+vt 0.361788 0.893053
+vt 0.361398 0.929244
+vt 0.559656 0.902097
+vt 0.555589 0.901346
+vt 0.557317 0.902165
+vt 0.573965 0.927557
+vt 0.563255 0.927865
+vt 0.573734 0.930613
+vt 0.563137 0.930918
+vt 0.570621 0.930463
+vt 0.561812 0.930717
+vt 0.554520 0.902163
+vt 0.550449 0.927858
+vt 0.550467 0.930911
+vt 0.551280 0.930711
+vt 0.552015 0.902093
+vt 0.538979 0.927538
+vt 0.539119 0.930594
+vt 0.541846 0.930447
+vt 0.550473 0.901973
+vt 0.531919 0.926990
+vt 0.532133 0.930052
+vt 0.536040 0.929997
+vt 0.550307 0.901836
+vt 0.531159 0.926362
+vt 0.531382 0.929430
+vt 0.535415 0.929480
+vt 0.551562 0.901718
+vt 0.536905 0.925821
+vt 0.537067 0.928895
+vt 0.540141 0.929035
+vt 0.553901 0.901651
+vt 0.547615 0.925513
+vt 0.547664 0.928591
+vt 0.548949 0.928782
+vt 0.556698 0.901652
+vt 0.560422 0.925520
+vt 0.560334 0.928598
+vt 0.559482 0.928788
+vt 0.559203 0.901723
+vt 0.571891 0.925841
+vt 0.571682 0.928915
+vt 0.568915 0.929052
+vt 0.560745 0.901842
+vt 0.578951 0.926388
+vt 0.578668 0.929457
+vt 0.574722 0.929502
+vt 0.560911 0.901979
+vt 0.579711 0.927017
+vt 0.579419 0.930079
+vt 0.575346 0.930019
+vt 0.398356 0.901905
+vt 0.394289 0.901154
+vt 0.396017 0.901972
+vt 0.412665 0.927365
+vt 0.401954 0.927673
+vt 0.412434 0.930421
+vt 0.401837 0.930726
+vt 0.409321 0.930271
+vt 0.400512 0.930524
+vt 0.394258 0.903992
+vt 0.393220 0.901971
+vt 0.389148 0.927666
+vt 0.389167 0.930719
+vt 0.389979 0.930518
+vt 0.390714 0.901901
+vt 0.377679 0.927345
+vt 0.377819 0.930402
+vt 0.380546 0.930255
+vt 0.389172 0.901781
+vt 0.370618 0.926798
+vt 0.370833 0.929860
+vt 0.374739 0.929804
+vt 0.389006 0.901644
+vt 0.369859 0.926169
+vt 0.370082 0.929238
+vt 0.374115 0.929287
+vt 0.390261 0.901526
+vt 0.375605 0.925629
+vt 0.375767 0.928703
+vt 0.378840 0.928843
+vt 0.392601 0.901458
+vt 0.386315 0.925321
+vt 0.386364 0.928398
+vt 0.387649 0.928590
+vt 0.395398 0.901460
+vt 0.399121 0.925328
+vt 0.399034 0.928406
+vt 0.398181 0.928596
+vt 0.397903 0.901530
+vt 0.410591 0.925648
+vt 0.410382 0.928723
+vt 0.407615 0.928859
+vt 0.399445 0.901650
+vt 0.417651 0.926196
+vt 0.417368 0.929265
+vt 0.413422 0.929310
+vt 0.399611 0.901787
+vt 0.418410 0.926824
+vt 0.418119 0.929886
+vt 0.414046 0.929826
+vn 0.011353 0.401349 0.915830
+vn 0.382275 0.411969 -0.827082
+vn -0.579608 0.385968 -0.717673
+vn -0.581500 0.379101 -0.719810
+vn -0.578936 0.391400 -0.715262
+vn -0.587512 0.353526 -0.727866
+vn -0.281625 0.919126 -0.275399
+vn 0.057253 0.984527 0.165563
+vn 0.071383 0.980926 0.180639
+vn -0.296457 0.908780 -0.293558
+vn 0.474380 0.596789 0.647114
+vn 0.491165 0.565630 0.662404
+vn 0.513932 0.515061 0.685965
+vn 0.517106 0.508774 0.688223
+vn 0.275857 0.401013 -0.873531
+vn -0.191260 0.288827 -0.938047
+vn -0.394971 -0.383221 0.834925
+vn 0.387310 -0.917112 0.094089
+vn 0.623280 0.340129 0.704123
+vn 0.167821 -0.764855 0.621906
+vn 0.346049 -0.937437 -0.037507
+vn 0.028108 -0.899045 0.436933
+vn 0.351848 -0.931425 -0.092898
+vn -0.021699 -0.935636 0.352214
+vn 0.354595 -0.925047 -0.135960
+vn -0.066134 -0.957091 0.282022
+vn 0.350780 -0.921049 -0.169012
+vn 0.518418 -0.764580 -0.382855
+vn 0.338420 -0.920835 -0.193701
+vn 0.202460 -0.979156 -0.015046
+vn 0.316080 -0.924802 -0.211646
+vn 0.010743 -0.996918 -0.077364
+vn 0.282449 -0.932432 -0.225257
+vn -0.263527 -0.963652 0.043672
+vn 0.235603 -0.942106 -0.238563
+vn -0.338664 -0.940489 -0.026948
+vn 0.130589 -0.939299 -0.317209
+vn -0.497787 -0.838588 -0.221198
+vn -0.419324 0.243233 -0.874599
+vn -0.876553 -0.425245 0.225349
+vn 0.628407 0.047426 -0.776421
+vn 0.780297 -0.115146 -0.614673
+vn 0.783563 0.069918 -0.617328
+vn -0.748405 -0.260353 0.609973
+vn -0.749504 -0.261116 0.608264
+vn 0.753014 -0.055422 -0.655629
+vn -0.749474 -0.261025 0.608386
+vn 0.758538 -0.024628 -0.651143
+vn -0.749535 -0.261055 0.608264
+vn 0.767571 -0.000092 -0.640919
+vn -0.749565 -0.261055 0.608234
+vn 0.611438 -0.665090 -0.428663
+vn -0.751213 -0.254250 0.609088
+vn 0.407697 -0.867550 -0.284768
+vn -0.752129 -0.255257 0.607532
+vn 0.356670 -0.868801 -0.343394
+vn 0.296243 -0.863887 -0.407300
+vn 0.220618 -0.847133 -0.483383
+vn 0.021393 -0.758263 -0.651570
+vn -0.141331 -0.391461 -0.909268
+vn -0.187475 -0.241737 0.952055
+vn 0.837397 0.084780 -0.539933
+vn -0.023103 -0.413434 0.910215
+vn 0.824702 0.075686 -0.560442
+vn -0.095370 -0.585253 0.805200
+vn 0.812677 0.068239 -0.578692
+vn -0.154118 -0.691580 0.705618
+vn 0.800623 0.065004 -0.595599
+vn -0.233772 -0.776574 0.584979
+vn 0.788965 0.064425 -0.611011
+vn -0.333689 -0.831782 0.443556
+vn 0.785150 0.052644 -0.617023
+vn -0.415937 -0.817103 0.399091
+vn 0.779382 0.063265 -0.623310
+vn -0.437086 -0.843745 0.311441
+vn 0.767327 0.061159 -0.638295
+vn -0.553789 -0.813898 0.175604
+vn 0.753685 0.061495 -0.654317
+vn -0.658071 -0.750481 0.060671
+vn 0.738456 0.065859 -0.671041
+vn -0.748985 -0.661794 -0.031587
+vn 0.720969 0.071505 -0.689230
+vn -0.847560 -0.510117 -0.146214
+vn -0.934355 -0.298257 -0.194861
+vn -0.388806 -0.092868 0.916593
+vn -0.783807 -0.067476 0.617298
+vn -0.417402 -0.324046 0.848964
+vn 0.045289 -0.536576 0.842616
+vn 0.101627 -0.139500 0.984985
+vn 0.268532 -0.648549 0.712180
+vn 0.342967 -0.179662 0.921964
+vn 0.327982 -0.690237 0.644917
+vn 0.425520 -0.197241 0.883145
+vn 0.333811 -0.702963 0.627979
+vn 0.428297 -0.209754 0.878933
+vn 0.353496 -0.704642 0.615192
+vn 0.454268 -0.208472 0.866115
+vn 0.400616 -0.719047 0.567858
+vn 0.518052 -0.221412 0.826167
+vn 0.455519 -0.674062 0.581439
+vn 0.540757 -0.377819 0.751518
+vn -0.056063 -0.389721 0.919187
+vn -0.071047 -0.408887 0.909787
+vn -0.799554 -0.060579 0.597491
+vn -0.499863 -0.502060 0.705710
+vn -0.068178 -0.820673 0.567278
+vn 0.131443 -0.908658 0.396252
+vn 0.175726 -0.927091 0.331004
+vn 0.185553 -0.931791 0.311930
+vn 0.202643 -0.933988 0.294198
+vn 0.229926 -0.937773 0.260079
+vn 0.249245 -0.927580 0.278268
+vn -0.605335 -0.408643 0.683035
+vn -0.580706 -0.562334 0.588610
+vn -0.185675 -0.914670 0.358959
+vn 0.015992 -0.980255 0.196997
+vn 0.065188 -0.986755 0.148350
+vn 0.079196 -0.987762 0.134129
+vn 0.098361 -0.988250 0.116916
+vn 0.122410 -0.988006 0.093783
+vn 0.182073 -0.973327 0.139500
+vn -0.653920 -0.466628 0.595477
+vn -0.661611 -0.570849 0.486160
+vn -0.310678 -0.929106 0.200537
+vn -0.097995 -0.993866 0.051149
+vn -0.037843 -0.999146 0.014679
+vn -0.021607 -0.999725 0.005341
+vn 0.000305 -0.999939 -0.008972
+vn 0.045381 -0.998901 0.010651
+vn 0.056917 0.996734 0.057009
+vn -0.761834 -0.063967 0.644581
+vn -0.760979 -0.529954 0.374187
+vn -0.492935 -0.869564 0.028474
+vn -0.268410 -0.956328 -0.115513
+vn -0.190405 -0.971160 -0.143376
+vn -0.172277 -0.974181 -0.145756
+vn -0.149602 -0.976074 -0.157720
+vn -0.080416 -0.988525 -0.127750
+vn 0.157567 0.963073 0.218177
+vn -0.774438 -0.080844 0.627430
+vn -0.895108 -0.376080 0.239387
+vn -0.758873 -0.626179 -0.178747
+vn -0.565905 -0.744591 -0.353954
+vn -0.478896 -0.784600 -0.393689
+vn -0.462539 -0.795160 -0.392071
+vn -0.446303 -0.797143 -0.406629
+vn -0.383801 -0.811182 -0.441176
+vn -0.569842 -0.402722 -0.716269
+vn -0.867214 -0.482040 -0.124546
+vn -0.972625 -0.160802 0.167699
+vn -0.913907 -0.254952 -0.315836
+vn -0.789666 -0.307993 -0.530595
+vn -0.731681 -0.333262 -0.594592
+vn -0.719108 -0.343425 -0.604053
+vn -0.711142 -0.341990 -0.614215
+vn -0.645924 -0.361064 -0.672597
+vn -0.589465 -0.359386 -0.723411
+vn -0.558916 -0.355724 -0.749016
+vn -0.984649 -0.017029 0.173559
+vn -0.950407 0.063387 -0.304422
+vn -0.841395 0.103824 -0.530290
+vn -0.806238 0.117710 -0.579699
+vn -0.796869 0.065188 -0.600574
+vn -0.815058 0.082797 -0.573412
+vn -0.747459 0.052217 -0.662221
+vn -0.662618 0.054933 -0.746910
+vn -0.946440 0.184942 0.264534
+vn -0.901151 0.405866 -0.152165
+vn -0.791284 0.484329 -0.373119
+vn -0.758507 0.497940 -0.420301
+vn -0.832942 0.412763 -0.368511
+vn -0.853053 0.386303 -0.350780
+vn -0.759667 0.372906 -0.532762
+vn -0.660024 0.386639 -0.644063
+vn -0.246498 0.354472 -0.901975
+vn -0.869778 0.307108 0.386151
+vn -0.768059 0.638356 0.050264
+vn -0.649892 0.745659 -0.146855
+vn -0.625477 0.757378 -0.187414
+vn -0.755150 0.653676 -0.049348
+vn -0.807489 0.588000 -0.046693
+vn -0.708548 0.652181 -0.269387
+vn -0.584002 0.694327 -0.420484
+vn -0.127415 0.687521 -0.714866
+vn -0.815638 0.347392 0.462600
+vn -0.668355 0.719535 0.188513
+vn -0.535874 0.844081 0.017426
+vn -0.510453 0.859676 -0.017945
+vn -0.661184 0.738060 0.134465
+vn -0.726676 0.668844 0.156682
+vn -0.613208 0.788659 -0.044343
+vn -0.469069 0.859127 -0.204505
+vn -0.005982 0.836024 -0.548601
+vn -0.769311 0.368267 0.521989
+vn -0.581652 0.755638 0.301035
+vn -0.433180 0.887845 0.155126
+vn -0.404614 0.905606 0.127018
+vn -0.570086 0.773553 0.276742
+vn -0.631184 0.706412 0.320200
+vn -0.491775 0.855464 0.162236
+vn -0.328074 0.944578 0.010163
+vn 0.136052 0.927732 -0.347453
+vn -0.751610 0.359294 0.553117
+vn -0.543962 0.760521 0.354472
+vn -0.385083 0.896725 0.218116
+vn -0.352306 0.916196 0.190802
+vn -0.517808 0.788629 0.331462
+vn -0.585437 0.710044 0.391247
+vn -0.428938 0.866573 0.255043
+vn -0.254402 0.960753 0.110477
+vn 0.239692 0.931913 -0.272164
+vn -0.726463 0.373699 0.576678
+vn -0.501083 0.765160 0.404187
+vn -0.335337 0.899319 0.280557
+vn -0.301431 0.917936 0.257851
+vn -0.474563 0.785058 0.398083
+vn -0.523942 0.719474 0.455824
+vn -0.352763 0.870785 0.342387
+vn -0.173925 0.962493 0.208197
+vn -0.035707 0.993988 0.103244
+vn -0.678487 0.364696 0.637684
+vn -0.410749 0.749992 0.518418
+vn -0.224158 0.880764 0.417066
+vn -0.182409 0.898740 0.398663
+vn -0.354961 0.774987 0.522843
+vn -0.385052 0.709708 0.589892
+vn -0.180914 0.837123 0.516190
+vn -0.016968 0.917447 0.397443
+vn -0.462996 0.684591 0.562975
+vn -0.612934 0.339244 0.713553
+vn -0.294046 0.693960 0.657216
+vn -0.084872 0.811884 0.577593
+vn -0.034639 0.826319 0.562120
+vn -0.180486 0.720573 0.669454
+vn -0.199011 0.655660 0.728324
+vn 0.022217 0.734397 0.678335
+vn 0.145665 0.812983 0.563738
+vn -0.714560 0.223823 0.662770
+vn -0.504288 0.240455 0.829341
+vn -0.113254 0.500290 0.858394
+vn 0.119724 0.592853 0.796319
+vn 0.180761 0.608966 0.772301
+vn 0.118778 0.528459 0.840571
+vn 0.082675 0.500137 0.861965
+vn 0.284463 0.498032 0.819147
+vn 0.422620 0.516312 0.744835
+vn 0.035096 0.318186 0.947356
+vn -0.410077 0.052370 0.910520
+vn 0.049562 0.183081 0.981842
+vn 0.291269 0.239875 0.926054
+vn 0.358043 0.252937 0.898770
+vn 0.362987 0.210150 0.907773
+vn 0.343822 0.221992 0.912381
+vn 0.442213 0.193243 0.875820
+vn 0.550554 0.199133 0.810663
+vn -0.318186 0.933988 0.162420
+vn -0.200354 0.971496 0.126591
+vn -0.223548 0.970855 0.086062
+vn -0.066347 0.990966 0.116398
+vn -0.139744 0.989990 0.019105
+vn -0.061495 0.991150 0.117496
+vn -0.134465 0.990814 0.013977
+vn -0.061037 0.991882 0.111362
+vn -0.123630 0.992309 0.003357
+vn -0.057009 0.994201 0.090915
+vn -0.100101 0.994812 -0.016938
+vn -0.012268 0.998962 0.043367
+vn -0.037477 0.996979 -0.067965
+vn 0.029817 0.999237 -0.024323
+vn 0.018677 0.993408 -0.112918
+vn 0.065371 0.994415 -0.082797
+vn 0.053926 0.989196 -0.136113
+vn 0.033235 0.996521 -0.076144
+vn 0.028626 0.992889 -0.115299
+vn -0.016511 0.998688 -0.048128
+vn -0.013733 0.996429 -0.083041
+vn 0.529496 0.712210 -0.460829
+vn 0.523026 0.708579 -0.473617
+vn 0.789056 -0.011536 -0.614185
+vn -0.302988 0.938353 0.166265
+vn -0.265389 0.938994 0.218726
+vn 0.008972 0.974334 0.224860
+vn 0.017914 0.971709 0.235389
+vn 0.009888 0.967406 0.252937
+vn -0.025452 0.968108 0.249153
+vn -0.026399 0.977172 0.210669
+vn -0.000336 0.993500 0.113742
+vn 0.030061 0.999298 0.021210
+vn 0.023957 0.999512 -0.018830
+vn -0.017853 0.999817 0.001221
+vn 0.536332 0.721305 -0.438215
+vn -0.298685 0.932981 0.200690
+vn -0.114658 -0.798791 -0.590564
+vn -0.370708 -0.526780 -0.764885
+vn -0.291025 -0.616688 -0.731407
+vn -0.143254 -0.625172 -0.767205
+vn 0.027223 -0.640553 -0.767418
+vn 0.111332 -0.662038 -0.741111
+vn 0.135563 -0.740410 -0.658315
+vn 0.095309 -0.862575 -0.496811
+vn 0.031892 -0.950316 -0.309610
+vn -0.117801 -0.895688 -0.428724
+vn 0.703726 0.085910 -0.705222
+vn 0.140812 -0.988342 -0.057833
+vn -0.176916 0.112217 -0.977783
+vn -0.463820 -0.000244 -0.885922
+vn -0.396466 0.495773 -0.772637
+vn -0.251259 0.629933 -0.734855
+vn -0.082247 0.613941 -0.785028
+vn 0.062685 0.432813 -0.899289
+vn 0.114200 0.360729 -0.925626
+vn 0.067263 0.509629 -0.857723
+vn -0.147404 0.666036 -0.731162
+vn -0.333659 0.761101 -0.556200
+vn -0.153478 0.913327 -0.377148
+vn -0.370403 -0.745903 -0.553514
+vn -0.117374 0.990661 0.069308
+vn -0.045228 -0.997101 0.061098
+vn -0.457808 -0.775109 -0.435408
+vn -0.716849 -0.364360 -0.594409
+vn -0.842647 0.257424 -0.472915
+vn -0.742363 -0.635609 -0.211829
+vn -0.978973 -0.126621 -0.159734
+vn -0.920560 0.370373 -0.124027
+vn -0.685263 -0.714591 -0.140477
+vn -0.822535 0.452986 -0.343791
+vn -0.424726 0.841395 -0.334086
+vn 0.104862 -0.938810 0.328043
+vn -0.146855 0.961974 -0.230232
+vn -0.592578 0.640126 -0.488907
+vn -0.615009 0.631336 -0.472365
+vn -0.672750 0.632954 -0.383038
+vn -0.732994 0.635304 -0.243019
+vn -0.733726 0.657796 -0.169988
+vn -0.650349 0.747856 -0.133000
+vn -0.509659 0.849849 -0.133976
+vn -0.374187 0.917051 -0.137638
+vn -0.261879 0.943449 -0.203101
+vn -0.750603 0.280526 0.598193
+vn -0.264351 0.961669 0.072787
+vn -0.292032 0.955870 -0.031159
+vn -0.329691 0.920103 -0.211402
+vn -0.339793 0.915555 -0.215125
+vn -0.354900 0.915159 -0.190985
+vn -0.364147 0.919675 -0.146916
+vn -0.327921 0.934049 -0.141392
+vn -0.236305 0.962859 -0.130497
+vn -0.132908 0.981079 -0.140751
+vn -0.095737 0.987732 -0.123142
+vn -0.095767 0.990448 -0.099002
+vn 0.488937 0.717917 -0.495499
+vn -0.306192 0.943205 0.128666
+vn -0.249184 0.966887 0.054659
+vn -0.214362 0.973754 -0.076144
+vn -0.213904 0.973327 -0.082614
+vn -0.208258 0.974456 -0.083956
+vn -0.190619 0.978057 -0.083712
+vn -0.134281 0.983673 -0.119694
+vn -0.065127 0.987915 -0.140446
+vn -0.005676 0.987548 -0.157170
+vn -0.010773 0.991577 -0.128971
+vn -0.047334 0.995025 -0.087466
+vn 0.511277 0.710990 -0.482742
+vn -0.317698 0.934355 0.161168
+vn 0.115726 -0.988556 0.096622
+vn 0.137333 -0.978088 0.156377
+vn 0.149754 -0.969787 0.192450
+vn 0.113804 -0.975402 0.188665
+vn 0.085177 -0.975860 0.201056
+vn 0.090030 -0.983673 0.155797
+vn 0.118778 -0.991150 0.058962
+vn 0.183233 -0.982940 -0.013886
+vn 0.138249 -0.988586 0.059420
+vn 0.134892 -0.989990 0.041383
+vn 0.729453 -0.456862 -0.509049
+vn -0.047090 -0.987243 0.151891
+vn 0.017121 -0.989441 0.143895
+vn 0.119480 -0.983428 0.136174
+vn 0.126865 -0.983886 0.125919
+vn 0.118809 -0.985748 0.119053
+vn 0.100009 -0.988037 0.117252
+vn 0.092471 -0.989349 0.112217
+vn 0.103183 -0.991180 0.082858
+vn 0.137211 -0.989776 0.038453
+vn 0.148564 -0.988800 0.012665
+vn 0.133518 -0.990966 0.011872
+vn 0.619343 -0.669668 -0.409772
+vn -0.082308 -0.983306 0.162206
+vn -0.013428 -0.993072 0.116581
+vn 0.076205 -0.994476 0.071780
+vn 0.089877 -0.994110 0.060427
+vn 0.092868 -0.994140 0.054903
+vn 0.089145 -0.994568 0.053194
+vn 0.092318 -0.994537 0.048616
+vn 0.110874 -0.993408 0.028413
+vn 0.147404 -0.989044 -0.005463
+vn 0.164617 -0.986053 -0.024293
+vn 0.148503 -0.988769 -0.015656
+vn 0.615223 -0.670400 -0.414716
+vn -0.095187 -0.981842 0.163976
+vn -0.030366 -0.994903 0.096072
+vn 0.037080 -0.999023 0.022736
+vn 0.052431 -0.998535 0.012757
+vn 0.059267 -0.998169 0.011414
+vn 0.060518 -0.998016 0.015534
+vn 0.066469 -0.997681 0.014222
+vn 0.091372 -0.995788 0.001312
+vn 0.133305 -0.990692 -0.027131
+vn 0.154118 -0.987274 -0.039003
+vn 0.143284 -0.989319 -0.026276
+vn 0.612751 -0.670492 -0.418226
+vn -0.094028 -0.983001 0.157659
+vn -0.057375 -0.996307 0.063570
+vn -0.016144 -0.999237 -0.034730
+vn -0.002991 -0.999207 -0.039277
+vn 0.003479 -0.999542 -0.030030
+vn 0.002014 -0.999908 -0.012177
+vn 0.004822 -0.999969 -0.002655
+vn 0.037202 -0.999268 -0.006897
+vn 0.092563 -0.995239 -0.029878
+vn 0.117161 -0.992523 -0.033509
+vn 0.112613 -0.993439 -0.018281
+vn 0.609699 -0.669759 -0.423841
+vn -0.078555 -0.987610 0.135685
+vn -0.137303 -0.990387 -0.015900
+vn -0.142460 -0.977447 -0.155675
+vn -0.135044 -0.979888 -0.146733
+vn -0.135533 -0.984924 -0.107364
+vn -0.147191 -0.987518 -0.055879
+vn -0.153020 -0.987945 -0.022248
+vn -0.097934 -0.995117 -0.011658
+vn -0.015992 -0.999634 -0.021455
+vn 0.047945 -0.998474 -0.026307
+vn 0.066897 -0.997559 -0.018983
+vn 0.600665 -0.671346 -0.434065
+vn -0.027772 -0.995697 0.088137
+vn 0.090670 -0.995666 0.019135
+vn 0.090579 -0.995697 0.019044
+vn 0.188879 -0.970733 0.148198
+vn 0.334513 -0.875454 0.348735
+vn 0.092196 -0.995514 0.021088
+vn 0.092257 -0.995483 0.021180
+vn -0.010163 -0.994171 -0.107089
+vn -0.175970 -0.935575 -0.306040
+vn -0.539750 0.570238 -0.619221
+vn 0.411664 0.682363 0.604022
+vn -0.004852 -0.012940 0.999878
+vn 0.696249 0.084201 0.712821
+vn -0.004883 -0.012940 0.999878
+vn 0.990326 0.131718 0.043519
+vn 0.050478 0.019013 -0.998535
+vn -0.983367 -0.129765 -0.126957
+vn -0.108615 -0.026734 0.993713
+vn 0.991089 0.132176 0.014832
+vn 0.062136 0.020569 -0.997833
+vn -0.673421 -0.080905 -0.734764
+vn -0.673421 -0.080905 -0.734794
+vn -0.689810 -0.083254 -0.719169
+vn -0.689810 -0.083285 -0.719169
+vn -0.981964 -0.129429 -0.137730
+vn -0.979644 -0.153935 -0.128636
+vn 0.215613 0.068148 -0.974090
+vn 0.972839 0.151250 0.175115
+vn 0.549547 0.060335 0.833247
+vn 0.530747 0.056856 0.845607
+vn -0.095462 -0.049409 0.994201
+vn -0.961516 -0.147496 -0.231666
+vn -0.523148 -0.055483 -0.850398
+vn 0.187872 0.063875 -0.980102
+vn 0.970794 0.150517 0.186682
+vn -0.106113 -0.051088 0.993011
+vn -0.567309 0.638081 -0.520554
+vn -0.672536 0.613269 -0.414167
+vn -0.600604 0.726829 -0.333079
+vn -0.865627 0.485733 -0.121220
+vn -0.821436 0.567125 -0.059572
+vn -0.932096 0.276009 0.234443
+vn -0.919126 0.294443 0.261635
+vn -0.874447 0.076205 0.479049
+vn -0.883908 0.053224 0.464583
+vn -0.603076 0.791742 0.096896
+vn -0.825739 -0.022523 0.563585
+vn -0.512680 0.740440 -0.434584
+vn -0.416974 0.906156 -0.070406
+vn -0.663503 0.732475 0.152409
+vn -0.827845 0.397473 0.395764
+vn -0.848079 0.094943 0.521256
+vn -0.826167 -0.037660 0.562120
+vn -0.333415 0.927396 -0.169500
+vn -0.134648 0.947172 0.290994
+vn -0.400525 0.795190 0.455214
+vn -0.672384 0.449507 0.588031
+vn -0.789911 0.101962 0.604633
+vn -0.920225 -0.098300 0.378765
+vn -0.052156 0.977294 0.205359
+vn 0.416059 0.251076 0.873959
+vn 0.175665 0.154698 0.972198
+vn -0.109897 -0.090121 0.989837
+vn -0.221870 -0.384716 0.895932
+vn 0.825556 -0.543962 0.150029
+vn 0.010590 -0.999908 0.005737
+vn 0.058046 -0.998260 -0.008972
+vn 0.495163 0.283273 0.821284
+vn -0.225318 -0.960570 0.162877
+vn -0.348491 -0.769860 0.534623
+vn -0.290658 -0.581988 0.759453
+vn 0.808496 -0.539659 0.234657
+vn -0.564837 -0.555742 -0.609973
+vn -0.793847 -0.511612 -0.328593
+vn -0.928495 -0.333506 0.163152
+vn -0.862911 -0.147649 0.483230
+vn 0.801141 0.079134 -0.593188
+vn -0.487472 -0.583026 -0.649922
+vn -0.719382 -0.151708 -0.677816
+vn -0.918424 -0.140019 -0.369945
+vn -0.987701 -0.086550 0.129978
+vn -0.884121 -0.044404 0.465102
+vn 0.804407 0.067415 -0.590197
+vn -0.601794 -0.283303 -0.746666
+vn -0.756035 0.248848 -0.605335
+vn -0.935301 0.212775 -0.282632
+vn -0.976867 0.133824 0.166753
+vn -0.877590 0.049898 0.476791
+vn 0.810999 0.054262 -0.582507
+vn -0.590533 0.500595 -0.632954
+vn -0.717277 0.486373 -0.498917
+vn -0.895657 0.404645 -0.184393
+vn -0.943510 0.251595 0.215583
+vn -0.863338 0.100070 0.494583
+vn 0.816828 0.033052 -0.575884
+vn -0.622181 0.495407 -0.606128
+vn 0.305063 0.740837 0.598376
+vn 0.108097 0.810297 0.575915
+vn 0.178533 0.713523 0.677450
+vn -0.196326 0.640767 0.742180
+vn -0.140599 0.571123 0.808710
+vn -0.506272 0.343059 0.791162
+vn -0.481643 0.329081 0.812220
+vn -0.671957 0.078188 0.736412
+vn -0.685629 0.098453 0.721244
+vn -0.746391 -0.013153 0.665365
+vn -0.312754 0.825800 0.469253
+vn 0.226600 0.827509 0.513627
+vn -0.115696 0.941649 0.316019
+vn -0.376385 0.766289 0.520676
+vn -0.622211 0.421674 0.659536
+vn -0.721458 0.109836 0.683645
+vn -0.743767 -0.027955 0.667837
+vn -0.001251 0.966521 0.256508
+vn -0.398022 0.916166 -0.046754
+vn -0.608875 0.770653 0.187964
+vn -0.773766 0.437574 0.458022
+vn -0.788141 0.102176 0.606922
+vn -0.584918 -0.058779 0.808954
+vn -0.297617 0.948363 -0.109470
+vn -0.762291 0.112308 -0.637410
+vn -0.908078 0.027039 -0.417859
+vn -0.974181 -0.191931 -0.118686
+vn -0.885250 -0.462874 0.045076
+vn 0.108097 -0.628437 -0.770287
+vn 0.111484 -0.991974 0.059542
+vn 0.085665 -0.991058 0.102023
+vn -0.694693 0.143132 -0.704886
+vn -0.127873 -0.949065 0.287851
+vn -0.533952 -0.791681 0.296762
+vn -0.753136 -0.636464 0.166173
+vn -0.002838 -0.644154 -0.764885
+vn 0.497726 -0.430586 0.752861
+vn 0.165593 -0.398602 0.902036
+vn -0.357982 -0.266305 0.894925
+vn -0.666982 -0.124607 0.734550
+vn 0.763817 0.074709 -0.641072
+vn 0.557848 -0.459914 0.690817
+vn 0.489090 -0.009369 0.872158
+vn 0.141697 -0.015137 0.989776
+vn -0.362529 -0.012940 0.931852
+vn -0.663869 -0.018494 0.747581
+vn 0.762780 0.062502 -0.643605
+vn 0.596301 -0.142186 0.790033
+vn 0.374584 0.382000 0.844813
+vn 0.022004 0.325541 0.945250
+vn -0.414838 0.200018 0.887631
+vn -0.681906 0.072970 0.727775
+vn 0.758141 0.048006 -0.650288
+vn 0.419935 0.619617 0.663076
+vn 0.260475 0.601550 0.755150
+vn -0.079928 0.500717 0.861873
+vn -0.464095 0.308054 0.830470
+vn -0.699973 0.119327 0.704093
+vn 0.755120 0.025727 -0.655049
+vn 0.386670 0.614246 0.687857
+vn -0.797174 -0.603107 -0.026521
+vn -0.244301 -0.755242 -0.608173
+vn 0.250954 -0.962004 -0.107303
+vn -0.112033 -0.515458 0.849513
+vn 0.600024 -0.644642 0.473647
+vn 0.108371 -0.565691 -0.817438
+vn 0.871578 -0.463698 0.159001
+vn 0.153172 -0.253609 -0.955077
+vn 0.984649 -0.136814 0.108341
+vn 0.192694 -0.028748 -0.980804
+vn 0.995270 0.083193 0.049776
+vn 0.059358 0.501267 -0.863247
+vn 0.796503 0.598743 0.084140
+vn -0.371776 0.692984 -0.617664
+vn 0.433210 0.798486 0.417951
+vn -0.580859 0.677633 -0.450972
+vn 0.224006 0.783013 0.580218
+vn -0.743370 0.583178 -0.327494
+vn 0.075320 0.691855 0.718070
+vn -0.787133 0.522355 -0.327952
+vn 0.071963 0.637623 0.766930
+vn -0.818659 0.513230 -0.257607
+vn -0.003662 0.622639 0.782464
+vn -0.911527 0.376415 -0.165441
+vn -0.102359 0.487228 0.867244
+vn -0.980560 0.178625 -0.081088
+vn -0.182897 0.289376 0.939573
+vn -0.523942 -0.729637 0.439375
+vn -0.776849 -0.229896 0.586200
+vn -0.699820 -0.676443 0.229469
+vn -0.117740 -0.979553 0.162999
+vn -0.408979 -0.893521 -0.185186
+vn 0.164525 -0.985473 -0.041627
+vn -0.152776 -0.891842 -0.425672
+vn 0.689962 -0.561754 -0.456435
+vn 0.454360 -0.492691 -0.742149
+vn 0.777703 0.233924 -0.583453
+vn -0.864254 -0.495743 0.085330
+vn -0.681845 -0.595752 -0.424390
+vn -0.450972 -0.565142 -0.690786
+vn 0.231727 -0.250313 -0.940001
+vn -0.972015 -0.229987 0.047243
+vn -0.859523 -0.156987 -0.486312
+vn -0.646535 -0.082919 -0.758324
+vn 0.085025 0.109104 -0.990356
+vn -0.990478 0.048311 0.128666
+vn -0.887539 0.300302 -0.349345
+vn -0.679220 0.417493 -0.603565
+vn 0.060488 0.482131 -0.873989
+vn -0.915403 0.260872 0.306497
+vn -0.761162 0.646199 -0.054811
+vn -0.543809 0.792535 -0.275918
+vn 0.161473 0.760338 -0.629109
+vn -0.769860 0.352367 0.532090
+vn -0.521287 0.792932 0.315378
+vn -0.284402 0.949736 0.130772
+vn 0.353923 0.876370 -0.326548
+vn -0.592029 0.299966 0.747978
+vn -0.230628 0.706900 0.668630
+vn 0.032563 0.856044 0.515854
+vn 0.588946 0.807154 -0.040101
+vn -0.427168 0.113987 0.896939
+vn 0.038881 0.404797 0.913541
+vn 0.329691 0.528428 0.782311
+vn 0.810205 0.564165 0.158971
+vn 0.777703 0.233955 -0.583422
+vn -0.320933 -0.157964 0.933805
+vn 0.213782 -0.038301 0.976104
+vn 0.524552 0.045381 0.850154
+vn 0.956145 0.204260 0.209815
+vn -0.304483 -0.437086 0.846278
+vn 0.242317 -0.495590 0.834040
+vn 0.557573 -0.454939 0.694327
+vn 0.981292 -0.168584 0.092654
+vn -0.379955 -0.644246 0.663717
+vn 0.119327 -0.837092 0.533830
+vn 0.423261 -0.829066 0.365337
+vn 0.881680 -0.446150 -0.153417
+vn -0.488205 -0.725425 0.485153
+vn -0.733879 -0.224799 0.640980
+vn -0.678182 -0.676565 0.286843
+vn -0.099948 -0.977447 0.185858
+vn -0.414655 -0.898648 -0.143132
+vn 0.167852 -0.985076 -0.037324
+vn -0.175298 -0.899380 -0.400403
+vn 0.661214 -0.565142 -0.493301
+vn 0.406384 -0.501968 -0.763451
+vn 0.734886 0.228889 -0.638356
+vn -0.853175 -0.499100 0.151402
+vn -0.705008 -0.606250 -0.367931
+vn -0.492904 -0.578600 -0.649770
+vn 0.169286 -0.264016 -0.949522
+vn -0.965056 -0.234626 0.116550
+vn -0.889401 -0.169530 -0.424482
+vn -0.695791 -0.098636 -0.711417
+vn 0.017121 0.093722 -0.995422
+vn -0.979827 0.044832 0.194708
+vn -0.911100 0.289743 -0.293100
+vn -0.721244 0.404004 -0.562609
+vn -0.001923 0.468429 -0.883480
+vn 0.734916 0.228889 -0.638356
+vn -0.894284 0.260659 0.363689
+vn -0.767388 0.641011 -0.013215
+vn -0.566454 0.784967 -0.250771
+vn 0.113529 0.751061 -0.650380
+vn -0.734428 0.356548 0.577441
+vn -0.504013 0.794977 0.337565
+vn -0.281198 0.950102 0.134922
+vn 0.325205 0.873012 -0.363384
+vn -0.542070 0.308603 0.781579
+vn -0.189550 0.716208 0.671590
+vn 0.061708 0.864345 0.499069
+vn 0.579547 0.809656 -0.092471
+vn 0.734886 0.228858 -0.638356
+vn -0.366283 0.125919 0.921934
+vn 0.097873 0.419568 0.902402
+vn 0.378338 0.542711 0.749840
+vn 0.815332 0.571123 0.094913
+vn -0.255989 -0.144780 0.955748
+vn 0.279550 -0.021516 0.959868
+vn 0.580523 0.061892 0.811853
+vn 0.966765 0.212897 0.141392
+vn -0.243629 -0.425153 0.871670
+vn 0.301462 -0.480850 0.823328
+vn 0.606250 -0.440687 0.661977
+vn 0.986419 -0.161626 0.028596
+vn -0.329936 -0.635670 0.697867
+vn 0.160710 -0.827784 0.537492
+vn 0.452467 -0.820734 0.348704
+vn 0.872219 -0.443617 -0.205817
+vn -0.279611 -0.239448 0.929746
+vn -0.728965 -0.331675 0.598804
+vn -0.241798 -0.437696 0.865963
+vn 0.251595 -0.073305 0.965026
+vn 0.314890 -0.405164 0.858272
+vn 0.554064 0.045930 0.831172
+vn 0.623554 -0.318369 0.713981
+vn 0.945769 0.279214 0.165960
+vn 0.996704 0.012146 0.080050
+vn 0.728965 0.331675 -0.598804
+vn 0.728965 0.331675 -0.598773
+vn -0.260384 -0.611774 0.746940
+vn 0.283761 -0.696493 0.659017
+vn 0.589404 -0.638203 0.495224
+vn 0.971679 -0.222236 -0.080233
+vn -0.332560 -0.735099 0.590747
+vn 0.162969 -0.902921 0.397626
+vn 0.456801 -0.864834 0.208258
+vn 0.874447 -0.388348 -0.290597
+vn -0.447310 -0.788965 0.421186
+vn -0.029084 -0.993042 0.113865
+vn 0.245918 -0.963744 -0.103214
+vn 0.719901 -0.460891 -0.518937
+vn -0.587207 -0.765099 0.264107
+vn -0.263222 -0.953124 -0.149022
+vn -0.011078 -0.919919 -0.391858
+vn 0.531480 -0.428755 -0.730522
+vn 0.728965 0.331706 -0.598804
+vn -0.730918 -0.667196 0.143406
+vn -0.503769 -0.789239 -0.351085
+vn -0.275155 -0.740013 -0.613666
+vn 0.337901 -0.296884 -0.893094
+vn -0.856594 -0.510117 0.077425
+vn -0.714072 -0.526353 -0.461501
+vn -0.506058 -0.451430 -0.734886
+vn 0.168676 -0.085330 -0.981964
+vn -0.945067 -0.317820 0.076235
+vn -0.862178 -0.204505 -0.463485
+vn -0.668630 -0.098056 -0.737083
+vn 0.049501 0.173650 -0.983551
+vn -0.982879 -0.119541 0.140019
+vn -0.925474 0.127354 -0.356700
+vn -0.738121 0.266243 -0.619861
+vn -0.001404 0.440687 -0.897641
+vn -0.964293 0.054506 0.259072
+vn -0.894345 0.418653 -0.157476
+vn -0.703970 0.586077 -0.401135
+vn 0.023591 0.675130 -0.737297
+vn 0.728935 0.331706 -0.598804
+vn -0.892117 0.177862 0.415265
+vn -0.773553 0.625111 0.103916
+vn -0.571368 0.812708 -0.114170
+vn 0.120792 0.841243 -0.526933
+vn -0.777367 0.231697 0.584796
+vn -0.581469 0.715232 0.387677
+vn -0.360485 0.911618 0.197333
+vn 0.275369 0.913755 -0.298593
+vn -0.637471 0.207831 0.741874
+vn -0.347331 0.675314 0.650594
+vn -0.103458 0.867794 0.485977
+vn 0.463790 0.881649 -0.087039
+vn -0.493759 0.109928 0.862606
+vn -0.106815 0.511429 0.852626
+vn 0.160588 0.687887 0.707785
+vn 0.657338 0.749779 0.075533
+vn -0.368084 -0.047121 0.928587
+vn 0.103488 0.248543 0.963042
+vn 0.391491 0.399304 0.829005
+vn 0.826594 0.538224 0.164373
+vn 0.869930 0.207892 -0.447157
+vn 0.764183 0.197546 -0.613941
+vn 0.834101 0.302194 -0.461409
+vn 0.965789 0.207770 -0.155034
+vn 0.877438 0.440321 -0.190191
+vn 0.906308 0.209906 -0.366710
+vn 0.854885 0.345256 -0.387158
+vn 0.889004 0.209113 -0.407300
+vn 0.845332 0.324046 -0.424696
+vn 0.972869 0.189856 0.131993
+vn 0.842891 0.532029 0.080264
+vn 0.805780 0.125584 0.578692
+vn 0.631306 0.584796 0.509323
+vn 0.701254 0.095614 0.706442
+vn 0.520554 0.571215 0.634571
+vn 0.775536 0.367412 -0.513291
+vn 0.733024 0.601154 -0.318217
+vn 0.770806 0.438887 -0.461684
+vn 0.773949 0.403546 -0.487960
+vn 0.630360 0.768731 -0.108066
+vn 0.346110 0.902432 0.256508
+vn 0.225166 0.900174 0.372753
+vn 0.709952 0.386090 -0.588977
+vn 0.571215 0.647175 -0.504776
+vn 0.676626 0.465712 -0.570299
+vn 0.693960 0.426313 -0.580187
+vn 0.392254 0.836482 -0.382641
+vn 0.026582 0.993347 -0.111942
+vn -0.105747 0.994324 -0.008850
+vn 0.654866 0.353191 -0.668111
+vn 0.435377 0.566088 -0.699942
+vn 0.597552 0.418500 -0.683920
+vn 0.626820 0.386212 -0.676656
+vn 0.192389 0.717124 -0.669820
+vn -0.241646 0.833186 -0.497330
+vn -0.383557 0.828486 -0.408002
+vn 0.625080 0.277566 -0.729514
+vn 0.361919 0.379589 -0.851405
+vn 0.554796 0.309915 -0.772057
+vn 0.590503 0.294046 -0.751518
+vn 0.084262 0.442701 -0.892666
+vn -0.386700 0.464888 -0.796411
+vn -0.533799 0.447035 -0.717765
+vn 0.628559 0.179479 -0.756737
+vn 0.370525 0.137669 -0.918546
+vn 0.559801 0.169073 -0.811151
+vn 0.594775 0.174444 -0.784722
+vn 0.096927 0.086673 -0.991485
+vn -0.369732 -0.012848 -0.929014
+vn -0.516221 -0.047761 -0.855098
+vn 0.664388 0.085177 -0.742485
+vn 0.458876 -0.094852 -0.883389
+vn 0.611225 0.033723 -0.790704
+vn 0.638417 0.059511 -0.767357
+vn 0.226936 -0.255470 -0.939787
+vn -0.195257 -0.472060 -0.859645
+vn -0.335521 -0.523392 -0.783227
+vn 0.722953 0.019959 -0.690573
+vn 0.603290 -0.255684 -0.755394
+vn 0.695303 -0.059877 -0.716178
+vn 0.709830 -0.019990 -0.704062
+vn 0.439467 -0.492141 -0.751396
+vn 0.089908 -0.789697 -0.606830
+vn -0.040132 -0.852351 -0.521409
+vn 0.788568 0.001282 -0.614917
+vn 0.765099 -0.301706 -0.568804
+vn 0.789483 -0.086673 -0.607562
+vn 0.789788 -0.042726 -0.611835
+vn 0.677572 -0.559893 -0.476852
+vn 0.409467 -0.880612 -0.238350
+vn 0.290780 -0.946501 -0.139775
+vn 0.843623 0.034150 -0.535783
+vn 0.900937 -0.220618 -0.373638
+vn 0.868557 -0.039460 -0.493973
+vn 0.856929 -0.002655 -0.515366
+vn 0.877438 -0.440565 -0.189642
+vn 0.677694 -0.720481 0.147008
+vn 0.568590 -0.780633 0.259346
+vn 0.873409 0.109806 -0.474380
+vn 0.974395 -0.034120 -0.222205
+vn 0.911313 0.069063 -0.405805
+vn 0.893246 0.089511 -0.440504
+vn 0.985534 -0.166112 0.033174
+vn 0.822748 -0.352153 0.446089
+vn 0.718833 -0.399182 0.569109
+vn 0.862636 0.241218 -0.444533
+vn 0.756767 0.231544 -0.611286
+vn 0.824213 0.334788 -0.456679
+vn 0.959288 0.237495 -0.152715
+vn 0.864498 0.468215 -0.182653
+vn 0.899197 0.242531 -0.364147
+vn 0.844020 0.376812 -0.381573
+vn 0.881771 0.242103 -0.404706
+vn 0.834925 0.356151 -0.419507
+vn 0.967681 0.213660 0.133854
+vn 0.828181 0.553178 0.089785
+vn 0.803674 0.135289 0.579455
+vn 0.616474 0.590899 0.520310
+vn 0.700339 0.099704 0.706778
+vn 0.506485 0.571612 0.645497
+vn 0.763726 0.399457 -0.507035
+vn 0.715354 0.627735 -0.306864
+vn 0.757195 0.469680 -0.453902
+vn 0.761193 0.435011 -0.480911
+vn 0.608722 0.787896 -0.092959
+vn 0.321940 0.905911 0.275033
+vn 0.201453 0.897855 0.391461
+vn 0.697409 0.417951 -0.582141
+vn 0.551836 0.673269 -0.492080
+vn 0.662008 0.496200 -0.561693
+vn 0.680380 0.457503 -0.572466
+vn 0.368084 0.854915 -0.365490
+vn -0.000977 0.995849 -0.090670
+vn -0.133000 0.991028 0.012665
+vn 0.643055 0.385266 -0.661824
+vn 0.417737 0.592669 -0.688620
+vn 0.583941 0.449263 -0.676107
+vn 0.614093 0.417676 -0.669637
+vn 0.170751 0.736290 -0.654714
+vn -0.265786 0.836665 -0.478835
+vn -0.407270 0.826136 -0.389294
+vn 0.615162 0.310160 -0.724784
+vn 0.348979 0.407514 -0.843867
+vn 0.543901 0.341472 -0.766472
+vn 0.580096 0.326151 -0.746361
+vn 0.069582 0.463820 -0.883175
+vn -0.401532 0.470992 -0.785394
+vn -0.547868 0.447432 -0.706809
+vn 0.621265 0.212806 -0.754112
+vn 0.364025 0.167394 -0.916196
+vn 0.552660 0.201697 -0.808588
+vn 0.587542 0.207465 -0.782128
+vn 0.091739 0.110477 -0.989624
+vn -0.371838 -0.003143 -0.928251
+vn -0.517106 -0.043641 -0.854762
+vn 0.659688 0.119236 -0.741966
+vn 0.458815 -0.063295 -0.886258
+vn 0.607837 0.067385 -0.791162
+vn 0.634388 0.093417 -0.767327
+vn 0.231208 -0.229011 -0.945555
+vn -0.184667 -0.458785 -0.869106
+vn -0.323252 -0.515549 -0.793512
+vn 0.720176 0.054567 -0.691610
+vn 0.607959 -0.222816 -0.762047
+vn 0.694662 -0.025422 -0.718863
+vn 0.708121 0.014557 -0.705924
+vn 0.450667 -0.463729 -0.762749
+vn 0.109836 -0.773766 -0.623829
+vn -0.018220 -0.841792 -0.539476
+vn 0.786493 0.036073 -0.616504
+vn 0.771477 -0.268349 -0.576830
+vn 0.789850 -0.051943 -0.611042
+vn 0.788934 -0.007935 -0.614368
+vn 0.691305 -0.530747 -0.490249
+vn 0.432783 -0.863735 -0.258095
+vn 0.316233 -0.934965 -0.160680
+vn 0.840877 0.068789 -0.536790
+vn 0.905576 -0.187750 -0.380291
+vn 0.867916 -0.005036 -0.496628
+vn 0.855251 0.031892 -0.517228
+vn 0.888638 -0.412152 -0.200995
+vn 0.697592 -0.704550 0.130039
+vn 0.590503 -0.770074 0.241279
+vn 0.868740 0.143864 -0.473861
+vn 0.974334 -0.002594 -0.225043
+vn 0.907956 0.102756 -0.406262
+vn 0.889218 0.123417 -0.440474
+vn 0.989807 -0.139653 0.027406
+vn 0.833369 -0.338878 0.436598
+vn 0.731101 -0.391369 0.558794
+vn 0.862636 0.241218 -0.444502
+vn 0.899197 0.242500 -0.364147
+vn 0.506485 0.571612 0.645527
+vn 0.763726 0.399487 -0.507035
+vn 0.757195 0.469680 -0.453871
+vn 0.697439 0.417951 -0.582141
+vn -0.133000 0.991028 0.012696
+vn 0.583911 0.449263 -0.676107
+vn 0.348979 0.407483 -0.843867
+vn 0.543931 0.341472 -0.766472
+vn -0.401532 0.470992 -0.785424
+vn 0.091708 0.110477 -0.989624
+vn 0.659719 0.119236 -0.741966
+vn 0.694662 -0.025452 -0.718863
+vn 0.109836 -0.773797 -0.623829
+vn 0.786493 0.036103 -0.616504
+vn 0.771477 -0.268349 -0.576861
+vn 0.840877 0.068789 -0.536821
+vn 0.855220 0.031892 -0.517228
+vn 0.907956 0.102725 -0.406262
+vn 0.731101 -0.391369 0.558824
+vn 0.841060 0.204260 -0.500870
+vn 0.784539 0.059572 -0.617145
+vn 0.798791 0.241432 -0.550981
+vn 0.865413 0.414045 -0.282052
+vn 0.760613 0.506211 -0.406415
+vn 0.723441 0.675710 0.141453
+vn 0.536637 0.839961 -0.080142
+vn 0.548235 0.746971 0.376080
+vn 0.335978 0.933622 0.124271
+vn 0.752129 0.250740 -0.609424
+vn 0.644826 0.529283 -0.551347
+vn 0.330332 0.881069 -0.338450
+vn 0.101566 0.980316 -0.169195
+vn 0.708152 0.230750 -0.667257
+vn 0.535752 0.479720 -0.694845
+vn 0.135899 0.792749 -0.594165
+vn -0.119327 0.879971 -0.459731
+vn 0.673544 0.184545 -0.715720
+vn 0.449904 0.365093 -0.814997
+vn -0.017029 0.588488 -0.808313
+vn -0.293130 0.647877 -0.703055
+vn 0.653584 0.119144 -0.747368
+vn 0.400433 0.202857 -0.893582
+vn -0.105197 0.299326 -0.948302
+vn -0.393292 0.319315 -0.862148
+vn 0.651326 0.044496 -0.757439
+vn 0.394848 0.017670 -0.918546
+vn -0.115177 -0.030641 -0.992859
+vn -0.404645 -0.055605 -0.912748
+vn 0.667104 -0.028016 -0.744407
+vn 0.433973 -0.162206 -0.886196
+vn -0.045442 -0.351268 -0.935148
+vn -0.325419 -0.419904 -0.847194
+vn 0.698508 -0.087374 -0.710196
+vn 0.511856 -0.309458 -0.801355
+vn 0.093356 -0.613666 -0.783990
+vn -0.167669 -0.718070 -0.675436
+vn 0.740776 -0.124516 -0.660085
+vn 0.616688 -0.401624 -0.677023
+vn 0.280160 -0.777917 -0.562395
+vn 0.044557 -0.904721 -0.423658
+vn 0.787439 -0.133824 -0.601642
+vn 0.732444 -0.424696 -0.532060
+vn 0.486465 -0.819056 -0.304086
+vn 0.278970 -0.951415 -0.130131
+vn 0.831416 -0.113834 -0.543809
+vn 0.841578 -0.375134 -0.388592
+vn 0.680929 -0.730735 -0.048372
+vn 0.499893 -0.851070 0.160375
+vn 0.866024 -0.067629 -0.495376
+vn 0.927396 -0.260506 -0.268410
+vn 0.833857 -0.526444 0.165746
+vn 0.673696 -0.618976 0.403699
+vn 0.885983 -0.002228 -0.463698
+vn 0.976867 -0.098239 -0.189856
+vn 0.922025 -0.237312 0.305765
+vn 0.773888 -0.290414 0.562761
+vn 0.888241 0.072390 -0.453627
+vn 0.982452 0.086886 -0.164861
+vn 0.932005 0.092654 0.350291
+vn 0.785211 0.084506 0.613392
+vn 0.872463 0.144902 -0.466659
+vn 0.943327 0.266793 -0.197241
+vn 0.862270 0.413282 0.292611
+vn 0.705985 0.448805 0.547838
+vn 0.841060 0.204291 -0.500870
+vn 0.784570 0.059572 -0.617145
+vn 0.760613 0.506211 -0.406384
+vn 0.723441 0.675680 0.141423
+vn 0.336009 0.933622 0.124271
+vn 0.535722 0.479720 -0.694845
+vn 0.135899 0.792749 -0.594134
+vn -0.016999 0.588488 -0.808313
+vn -0.293100 0.647877 -0.703055
+vn 0.400433 0.202857 -0.893551
+vn 0.651326 0.044496 -0.757469
+vn 0.394818 0.017670 -0.918577
+vn -0.045442 -0.351238 -0.935148
+vn 0.093387 -0.613666 -0.783990
+vn 0.280160 -0.777947 -0.562395
+vn 0.486496 -0.819056 -0.304056
+vn 0.279000 -0.951415 -0.130131
+vn 0.841578 -0.375134 -0.388562
+vn 0.499924 -0.851070 0.160375
+vn 0.866024 -0.067629 -0.495346
+vn 0.976867 -0.098270 -0.189856
+vn 0.922056 -0.237281 0.305765
+vn 0.982452 0.086886 -0.164830
+vn 0.932035 0.092685 0.350291
+vn 0.785241 0.084506 0.613392
+vn 0.872463 0.144932 -0.466659
+vn 0.943327 0.266793 -0.197211
+vn 0.862300 0.413282 0.292611
+vn 0.705985 0.448805 0.547807
+vn -0.602557 -0.161321 -0.781579
+vn 0.115879 -0.991913 0.051546
+vn 0.569872 -0.461409 0.679922
+vn 0.613330 -0.220466 0.758415
+vn 0.618824 -0.013581 0.785363
+vn 0.597156 0.195227 0.777978
+vn 0.547441 0.400739 0.734611
+vn 0.291025 0.843501 0.451399
+vn -0.326334 0.885983 -0.329356
+vn 0.618976 -0.017457 0.785180
+vn 0.066836 -0.997681 -0.011322
+vn -0.473830 -0.584338 -0.658773
+vn -0.560137 -0.358684 -0.746696
+vn -0.603107 -0.157506 -0.781915
+vn -0.619861 0.051881 -0.782983
+vn -0.608539 0.264595 -0.748070
+vn -0.438063 0.757622 -0.483779
+vn 0.158361 0.943083 0.292367
+vn -0.298898 -0.455184 0.838710
+vn -0.785272 -0.055574 0.616627
+vn -0.454390 -0.633290 0.626423
+vn 0.329417 -0.651906 0.682974
+vn 0.086978 -0.932493 0.350475
+vn 0.897397 -0.441145 0.003815
+vn 0.716025 -0.653005 -0.246620
+vn -0.653432 -0.658773 0.372845
+vn -0.224281 -0.973388 -0.046602
+vn 0.481399 -0.685232 -0.546495
+vn -0.843684 -0.517258 0.143559
+vn -0.521531 -0.751701 -0.403577
+vn 0.255623 -0.517869 -0.816340
+vn -0.969604 -0.244484 0.005127
+vn -0.717399 -0.325297 -0.616016
+vn 0.106540 -0.194739 -0.975036
+vn -0.996765 0.080172 -0.004120
+vn -0.758629 0.180090 -0.626087
+vn 0.074801 0.186163 -0.979644
+vn -0.922208 0.369335 0.114292
+vn -0.642232 0.628254 -0.439070
+vn 0.161504 0.522233 -0.837336
+vn -0.766472 0.551256 0.329508
+vn -0.400708 0.910184 -0.104678
+vn 0.342265 0.734306 -0.586200
+vn -0.566881 0.576373 0.588549
+vn -0.091281 0.950774 0.296030
+vn 0.575640 0.766411 -0.284951
+vn 0.784570 0.059542 -0.617145
+vn -0.376293 0.430647 0.820307
+vn 0.205054 0.727439 0.654775
+vn 0.800806 0.598712 -0.014435
+vn -0.250587 0.154088 0.955718
+vn 0.401807 0.299661 0.865291
+vn 0.950530 0.275399 0.143498
+vn -0.224036 -0.170202 0.959593
+vn 0.444899 -0.205390 0.871700
+vn 0.983520 -0.105411 0.146764
+vn -0.298898 -0.455153 0.838710
+vn -0.454360 -0.633290 0.626453
+vn 0.897427 -0.441115 0.003815
+vn 0.716056 -0.653005 -0.246651
+vn 0.478042 0.346324 -0.807154
+vn 0.590289 0.472304 -0.654561
+vn 0.786279 0.049104 -0.615894
+vn -0.653401 -0.658773 0.372845
+vn 0.732505 0.487350 -0.475265
+vn 0.866543 0.386975 -0.315104
+vn 0.106510 -0.194739 -0.975036
+vn 0.956420 0.196387 -0.216041
+vn -0.996765 0.080203 -0.004120
+vn -0.758660 0.180090 -0.626057
+vn 0.074770 0.186193 -0.979644
+vn 0.977935 -0.034547 -0.205969
+vn -0.922208 0.369366 0.114322
+vn 0.161473 0.522233 -0.837367
+vn 0.925565 -0.243690 -0.289712
+vn -0.766472 0.551256 0.329539
+vn 0.342235 0.734306 -0.586200
+vn 0.813379 -0.373730 -0.445723
+vn -0.566851 0.576373 0.588549
+vn 0.671163 -0.388348 -0.631397
+vn -0.376263 0.430647 0.820307
+vn 0.205023 0.727439 0.654775
+vn 0.800806 0.598743 -0.014435
+vn 0.537034 -0.283303 -0.794519
+vn 0.950530 0.275369 0.143498
+vn 0.447127 -0.088626 -0.890042
+vn 0.444899 -0.205390 0.871670
+vn 0.983520 -0.105411 0.146733
+vn 0.425611 0.141789 -0.893704
+s 1
+f 1204/1/1 1236/2/2 1176/3/3
+f 1132/4/4 1176/3/3 1177/5/5
+f 1131/6/6 1177/5/5 980/7/7
+f 980/7/7 448/8/8 466/9/9
+f 940/10/10 980/7/7 466/9/9
+f 466/9/9 448/8/8 175/11/11
+f 223/12/12 175/11/11 174/13/13
+f 224/14/14 174/13/13 117/15/15
+f 1204/1/1 1176/3/3 1132/4/4
+f 1132/4/4 1177/5/5 1131/6/6
+f 1131/6/6 980/7/7 940/10/10
+f 466/9/9 175/11/11 223/12/12
+f 223/12/12 174/13/13 224/14/14
+f 224/14/14 117/15/15 148/16/16
+f 1260/17/17 1208/18/18 1238/19/19
+f 1253/20/20 1109/21/21 1208/18/18
+f 1183/22/22 979/23/23 1109/21/21
+f 1013/24/24 868/25/25 979/23/23
+f 889/26/26 760/27/27 868/25/25
+f 760/27/27 580/28/28 593/29/29
+f 773/30/30 580/28/28 760/27/27
+f 580/28/28 487/31/31 593/29/29
+f 464/32/32 378/33/33 487/31/31
+f 341/34/34 247/35/35 378/33/33
+f 170/36/36 144/37/37 247/35/35
+f 102/38/38 119/39/39 144/37/37
+f 119/39/39 1260/17/17 1238/19/19
+f 95/40/40 1260/17/17 119/39/39
+f 1260/17/17 1253/20/20 1208/18/18
+f 1253/20/20 1183/22/22 1109/21/21
+f 1183/22/22 1013/24/24 979/23/23
+f 1013/24/24 889/26/26 868/25/25
+f 889/26/26 773/30/30 760/27/27
+f 580/28/28 464/32/32 487/31/31
+f 464/32/32 341/34/34 378/33/33
+f 341/34/34 170/36/36 247/35/35
+f 170/36/36 102/38/38 144/37/37
+f 102/38/38 95/40/40 119/39/39
+f 1237/41/41 1207/42/42 661/43/43
+f 1237/41/41 1253/20/20 1260/17/17
+f 697/44/44 696/45/45 1253/20/20
+f 1260/17/17 697/44/44 1253/20/20
+f 1207/42/42 1108/46/46 661/43/43
+f 1207/42/42 1183/22/22 1253/20/20
+f 696/45/45 695/47/47 1183/22/22
+f 1253/20/20 696/45/45 1183/22/22
+f 1108/46/46 978/48/48 661/43/43
+f 1108/46/46 1013/24/24 1183/22/22
+f 695/47/47 694/49/49 1013/24/24
+f 1183/22/22 695/47/47 1013/24/24
+f 978/48/48 867/50/50 661/43/43
+f 978/48/48 889/26/26 1013/24/24
+f 694/49/49 693/51/51 889/26/26
+f 1013/24/24 694/49/49 889/26/26
+f 867/50/50 759/52/52 661/43/43
+f 867/50/50 773/30/30 889/26/26
+f 693/51/51 690/53/53 773/30/30
+f 889/26/26 693/51/51 773/30/30
+f 759/52/52 592/54/54 661/43/43
+f 773/30/30 592/54/54 580/28/28
+f 759/52/52 592/54/54 773/30/30
+f 690/53/53 668/55/55 580/28/28
+f 773/30/30 580/28/28 690/53/53
+f 592/54/54 486/56/56 661/43/43
+f 592/54/54 464/32/32 580/28/28
+f 580/28/28 464/32/32 668/55/55
+f 486/56/56 377/57/57 661/43/43
+f 486/56/56 341/34/34 464/32/32
+f 464/32/32 668/55/55 341/34/34
+f 377/57/57 246/58/58 661/43/43
+f 377/57/57 170/36/36 341/34/34
+f 341/34/34 668/55/55 170/36/36
+f 246/58/58 143/59/59 661/43/43
+f 246/58/58 102/38/38 170/36/36
+f 170/36/36 668/55/55 102/38/38
+f 143/59/59 118/60/60 661/43/43
+f 143/59/59 95/40/40 102/38/38
+f 102/38/38 668/55/55 95/40/40
+f 118/60/60 117/15/15 661/43/43
+f 118/60/60 95/40/40 94/61/61
+f 95/40/40 668/55/55 94/61/61
+f 117/15/15 142/62/62 661/43/43
+f 117/15/15 94/61/61 101/63/63
+f 94/61/61 668/55/55 101/63/63
+f 142/62/62 245/64/64 661/43/43
+f 142/62/62 101/63/63 169/65/65
+f 101/63/63 668/55/55 169/65/65
+f 245/64/64 376/66/66 661/43/43
+f 245/64/64 169/65/65 340/67/67
+f 169/65/65 668/55/55 340/67/67
+f 376/66/66 485/68/68 661/43/43
+f 376/66/66 340/67/67 463/69/69
+f 340/67/67 668/55/55 463/69/69
+f 485/68/68 591/70/70 661/43/43
+f 485/68/68 463/69/69 579/71/71
+f 463/69/69 668/55/55 579/71/71
+f 591/70/70 647/72/72 661/43/43
+f 591/70/70 579/71/71 646/73/73
+f 579/71/71 668/55/55 646/73/73
+f 647/72/72 758/74/74 661/43/43
+f 647/72/72 646/73/73 772/75/75
+f 668/55/55 690/53/53 772/75/75
+f 646/73/73 668/55/55 772/75/75
+f 758/74/74 866/76/76 661/43/43
+f 758/74/74 772/75/75 888/77/77
+f 690/53/53 693/51/51 888/77/77
+f 772/75/75 690/53/53 888/77/77
+f 866/76/76 977/78/78 661/43/43
+f 866/76/76 888/77/77 1012/79/79
+f 693/51/51 694/49/49 1012/79/79
+f 888/77/77 693/51/51 1012/79/79
+f 977/78/78 1107/80/80 661/43/43
+f 977/78/78 1012/79/79 1182/81/81
+f 694/49/49 695/47/47 1182/81/81
+f 1012/79/79 694/49/49 1182/81/81
+f 1107/80/80 1206/82/82 661/43/43
+f 1107/80/80 1182/81/81 1252/83/83
+f 695/47/47 696/45/45 1252/83/83
+f 1182/81/81 695/47/47 1252/83/83
+f 1206/82/82 1236/2/2 661/43/43
+f 1206/82/82 1252/83/83 1259/84/84
+f 696/45/45 697/44/44 1259/84/84
+f 1252/83/83 696/45/45 1259/84/84
+f 1236/2/2 1237/41/41 661/43/43
+f 1236/2/2 1259/84/84 1260/17/17
+f 1259/84/84 697/44/44 1260/17/17
+f 1237/41/41 1253/20/20 1207/42/42
+f 1207/42/42 1183/22/22 1108/46/46
+f 1108/46/46 1013/24/24 978/48/48
+f 978/48/48 889/26/26 867/50/50
+f 867/50/50 773/30/30 759/52/52
+f 592/54/54 486/56/56 464/32/32
+f 486/56/56 377/57/57 341/34/34
+f 377/57/57 246/58/58 170/36/36
+f 246/58/58 143/59/59 102/38/38
+f 143/59/59 118/60/60 95/40/40
+f 118/60/60 117/15/15 94/61/61
+f 117/15/15 101/63/63 142/62/62
+f 142/62/62 169/65/65 245/64/64
+f 245/64/64 340/67/67 376/66/66
+f 376/66/66 463/69/69 485/68/68
+f 485/68/68 579/71/71 591/70/70
+f 591/70/70 646/73/73 647/72/72
+f 647/72/72 772/75/75 758/74/74
+f 758/74/74 888/77/77 866/76/76
+f 866/76/76 1012/79/79 977/78/78
+f 977/78/78 1182/81/81 1107/80/80
+f 1107/80/80 1252/83/83 1206/82/82
+f 1206/82/82 1259/84/84 1236/2/2
+f 1236/2/2 1260/17/17 1237/41/41
+f 777/85/85 659/86/86 766/87/87
+f 777/85/85 824/88/88 835/89/89
+f 835/89/89 924/90/90 945/91/91
+f 945/91/91 1030/92/92 1068/93/93
+f 1068/93/93 1067/94/94 1129/95/95
+f 1129/95/95 1117/96/96 1163/97/97
+f 1163/97/97 1155/98/98 1200/99/99
+f 1200/99/99 1160/100/100 1203/101/101
+f 1203/101/101 1161/102/102 1205/103/103
+f 1205/103/103 660/104/104 1161/102/102
+f 766/87/87 659/86/86 734/105/105
+f 766/87/87 789/106/106 824/88/88
+f 824/88/88 846/107/107 924/90/90
+f 924/90/90 895/108/108 1030/92/92
+f 1030/92/92 906/109/109 1067/94/94
+f 1067/94/94 917/110/110 1117/96/96
+f 1117/96/96 931/111/111 1155/98/98
+f 1155/98/98 937/112/112 1160/100/100
+f 1160/100/100 1161/102/102 938/113/113
+f 1161/102/102 660/104/104 938/113/113
+f 734/105/105 659/86/86 701/114/114
+f 734/105/105 708/115/115 789/106/106
+f 789/106/106 731/116/116 846/107/107
+f 846/107/107 738/117/117 895/108/108
+f 895/108/108 743/118/118 906/109/109
+f 906/109/109 746/119/119 917/110/110
+f 917/110/110 752/120/120 931/111/111
+f 931/111/111 754/121/121 937/112/112
+f 937/112/112 938/113/113 756/122/122
+f 938/113/113 660/104/104 756/122/122
+f 701/114/114 659/86/86 645/123/123
+f 708/115/115 645/123/123 641/124/124
+f 701/114/114 645/123/123 708/115/115
+f 731/116/116 641/124/124 632/125/125
+f 708/115/115 641/124/124 731/116/116
+f 738/117/117 632/125/125 611/126/126
+f 731/116/116 632/125/125 738/117/117
+f 743/118/118 611/126/126 606/127/127
+f 738/117/117 611/126/126 743/118/118
+f 746/119/119 606/127/127 604/128/128
+f 743/118/118 606/127/127 746/119/119
+f 752/120/120 604/128/128 599/129/129
+f 746/119/119 604/128/128 752/120/120
+f 754/121/121 599/129/129 596/130/130
+f 752/120/120 599/129/129 754/121/121
+f 756/122/122 597/131/131 596/130/130
+f 754/121/121 756/122/122 596/130/130
+f 756/122/122 660/104/104 597/131/131
+f 645/123/123 659/86/86 630/132/132
+f 645/123/123 566/133/133 641/124/124
+f 641/124/124 507/134/134 632/125/125
+f 632/125/125 459/135/135 611/126/126
+f 611/126/126 445/136/136 606/127/127
+f 606/127/127 435/137/137 604/128/128
+f 604/128/128 420/138/138 599/129/129
+f 599/129/129 596/130/130 414/139/139
+f 596/130/130 597/131/131 415/140/140
+f 597/131/131 660/104/104 415/140/140
+f 630/132/132 659/86/86 587/141/141
+f 630/132/132 529/142/142 566/133/133
+f 566/133/133 427/143/143 507/134/134
+f 507/134/134 320/144/144 459/135/135
+f 459/135/135 288/145/145 445/136/136
+f 445/136/136 235/146/146 435/137/137
+f 435/137/137 197/147/147 420/138/138
+f 420/138/138 414/139/139 190/148/148
+f 414/139/139 415/140/140 191/149/149
+f 415/140/140 660/104/104 191/149/149
+f 587/141/141 659/86/86 578/150/150
+f 587/141/141 525/151/151 529/142/142
+f 529/142/142 408/152/152 427/143/143
+f 427/143/143 286/153/153 320/144/144
+f 320/144/144 221/154/154 288/145/145
+f 288/145/145 187/155/155 235/146/146
+f 235/146/146 154/156/156 197/147/147
+f 197/147/147 146/157/157 190/148/148
+f 190/148/148 147/158/158 191/149/149
+f 191/149/149 660/104/104 147/158/158
+f 578/150/150 659/86/86 577/159/159
+f 578/150/150 524/160/160 525/151/151
+f 525/151/151 409/161/161 408/152/152
+f 408/152/152 287/162/162 286/153/153
+f 286/153/153 222/163/163 221/154/154
+f 221/154/154 188/164/164 187/155/155
+f 187/155/155 153/165/165 154/156/156
+f 154/156/156 145/166/166 146/157/157
+f 147/158/158 145/166/166 148/16/16
+f 146/157/157 145/166/166 147/158/158
+f 147/158/158 148/16/16 660/104/104
+f 577/159/159 659/86/86 586/167/167
+f 577/159/159 530/168/168 524/160/160
+f 524/160/160 428/169/169 409/161/161
+f 409/161/161 321/170/170 287/162/162
+f 287/162/162 289/171/171 222/163/163
+f 222/163/163 236/172/172 188/164/164
+f 188/164/164 196/173/173 153/165/165
+f 153/165/165 189/174/174 145/166/166
+f 145/166/166 192/175/175 148/16/16
+f 148/16/16 192/175/175 660/104/104
+f 586/167/167 659/86/86 629/176/176
+f 586/167/167 567/177/177 530/168/168
+f 530/168/168 508/178/178 428/169/169
+f 428/169/169 460/179/179 321/170/170
+f 321/170/170 446/180/180 289/171/171
+f 289/171/171 436/181/181 236/172/172
+f 236/172/172 419/182/182 196/173/173
+f 196/173/173 413/183/183 189/174/174
+f 189/174/174 416/184/184 192/175/175
+f 192/175/175 416/184/184 660/104/104
+f 629/176/176 659/86/86 638/185/185
+f 629/176/176 601/186/186 567/177/177
+f 567/177/177 554/187/187 508/178/178
+f 508/178/178 533/188/188 460/179/179
+f 460/179/179 526/189/189 446/180/180
+f 446/180/180 517/190/190 436/181/181
+f 436/181/181 504/191/191 419/182/182
+f 419/182/182 500/192/192 413/183/183
+f 413/183/183 501/193/193 416/184/184
+f 416/184/184 501/193/193 660/104/104
+f 638/185/185 659/86/86 644/194/194
+f 638/185/185 642/195/195 601/186/186
+f 601/186/186 633/196/196 554/187/187
+f 554/187/187 612/197/197 533/188/188
+f 533/188/188 607/198/198 526/189/189
+f 526/189/189 605/199/199 517/190/190
+f 517/190/190 598/200/200 504/191/191
+f 504/191/191 594/201/201 500/192/192
+f 500/192/192 595/202/202 501/193/193
+f 501/193/193 595/202/202 660/104/104
+f 644/194/194 659/86/86 658/203/203
+f 642/195/195 658/203/203 657/204/204
+f 644/194/194 658/203/203 642/195/195
+f 633/196/196 657/204/204 656/205/205
+f 642/195/195 657/204/204 633/196/196
+f 612/197/197 656/205/205 655/206/206
+f 633/196/196 656/205/205 612/197/197
+f 607/198/198 655/206/206 654/207/207
+f 612/197/197 655/206/206 607/198/198
+f 607/198/198 653/208/208 605/199/199
+f 605/199/199 652/209/209 598/200/200
+f 594/201/201 652/209/209 649/210/210
+f 598/200/200 652/209/209 594/201/201
+f 594/201/201 651/211/211 595/202/202
+f 595/202/202 651/211/211 660/104/104
+f 658/203/203 659/86/86 700/212/212
+f 657/204/204 700/212/212 709/213/213
+f 658/203/203 700/212/212 657/204/204
+f 656/205/205 709/213/213 730/214/214
+f 657/204/204 709/213/213 656/205/205
+f 655/206/206 730/214/214 739/215/215
+f 656/205/205 730/214/214 655/206/206
+f 654/207/207 739/215/215 744/216/216
+f 655/206/206 739/215/215 654/207/207
+f 654/207/207 745/217/217 653/208/208
+f 653/208/208 751/218/218 652/209/209
+f 649/210/210 751/218/218 753/219/219
+f 652/209/209 751/218/218 649/210/210
+f 649/210/210 755/220/220 651/211/211
+f 651/211/211 755/220/220 660/104/104
+f 700/212/212 659/86/86 720/221/221
+f 700/212/212 749/222/222 709/213/213
+f 709/213/213 801/223/223 730/214/214
+f 730/214/214 823/224/224 739/215/215
+f 739/215/215 828/225/225 744/216/216
+f 744/216/216 840/226/226 745/217/217
+f 745/217/217 850/227/227 751/218/218
+f 751/218/218 852/228/228 753/219/219
+f 753/219/219 853/229/229 755/220/220
+f 755/220/220 660/104/104 853/229/229
+f 720/221/221 659/86/86 733/230/230
+f 720/221/221 790/231/231 749/222/222
+f 749/222/222 847/232/232 801/223/223
+f 801/223/223 894/233/233 823/224/224
+f 823/224/224 907/234/234 828/225/225
+f 828/225/225 916/235/235 840/226/226
+f 840/226/226 930/236/236 850/227/227
+f 850/227/227 936/237/237 852/228/228
+f 852/228/228 853/229/229 939/238/238
+f 853/229/229 660/104/104 939/238/238
+f 733/230/230 659/86/86 765/239/239
+f 733/230/230 825/240/240 790/231/231
+f 790/231/231 923/241/241 847/232/232
+f 847/232/232 1031/242/242 894/233/233
+f 894/233/233 1066/243/243 907/234/234
+f 907/234/234 1116/244/244 916/235/235
+f 916/235/235 1154/245/245 930/236/236
+f 930/236/236 1159/246/246 936/237/237
+f 936/237/237 939/238/238 1162/247/247
+f 939/238/238 660/104/104 1162/247/247
+f 765/239/239 659/86/86 776/248/248
+f 765/239/239 834/249/249 825/240/240
+f 825/240/240 946/250/250 923/241/241
+f 923/241/241 1069/251/251 1031/242/242
+f 1031/242/242 1130/252/252 1066/243/243
+f 1066/243/243 1164/253/253 1116/244/244
+f 1116/244/244 1199/254/254 1154/245/245
+f 1154/245/245 1202/255/255 1159/246/246
+f 1159/246/246 1204/1/1 1162/247/247
+f 1162/247/247 660/104/104 1204/1/1
+f 776/248/248 659/86/86 777/85/85
+f 776/248/248 835/89/89 834/249/249
+f 834/249/249 945/91/91 946/250/250
+f 946/250/250 1068/93/93 1069/251/251
+f 1069/251/251 1129/95/95 1130/252/252
+f 1130/252/252 1163/97/97 1164/253/253
+f 1164/253/253 1200/99/99 1199/254/254
+f 1199/254/254 1203/101/101 1202/255/255
+f 1204/1/1 1205/103/103 1203/101/101
+f 1202/255/255 1203/101/101 1204/1/1
+f 1204/1/1 660/104/104 1205/103/103
+f 777/85/85 766/87/87 824/88/88
+f 835/89/89 824/88/88 924/90/90
+f 945/91/91 924/90/90 1030/92/92
+f 1068/93/93 1030/92/92 1067/94/94
+f 1129/95/95 1067/94/94 1117/96/96
+f 1163/97/97 1117/96/96 1155/98/98
+f 1200/99/99 1155/98/98 1160/100/100
+f 1203/101/101 1160/100/100 1161/102/102
+f 766/87/87 734/105/105 789/106/106
+f 824/88/88 789/106/106 846/107/107
+f 924/90/90 846/107/107 895/108/108
+f 1030/92/92 895/108/108 906/109/109
+f 1067/94/94 906/109/109 917/110/110
+f 1117/96/96 917/110/110 931/111/111
+f 1155/98/98 931/111/111 937/112/112
+f 1160/100/100 937/112/112 938/113/113
+f 734/105/105 701/114/114 708/115/115
+f 789/106/106 708/115/115 731/116/116
+f 846/107/107 731/116/116 738/117/117
+f 895/108/108 738/117/117 743/118/118
+f 906/109/109 743/118/118 746/119/119
+f 917/110/110 746/119/119 752/120/120
+f 931/111/111 752/120/120 754/121/121
+f 937/112/112 754/121/121 756/122/122
+f 645/123/123 630/132/132 566/133/133
+f 641/124/124 566/133/133 507/134/134
+f 632/125/125 507/134/134 459/135/135
+f 611/126/126 459/135/135 445/136/136
+f 606/127/127 445/136/136 435/137/137
+f 604/128/128 435/137/137 420/138/138
+f 599/129/129 420/138/138 414/139/139
+f 596/130/130 415/140/140 414/139/139
+f 630/132/132 587/141/141 529/142/142
+f 566/133/133 529/142/142 427/143/143
+f 507/134/134 427/143/143 320/144/144
+f 459/135/135 320/144/144 288/145/145
+f 445/136/136 288/145/145 235/146/146
+f 435/137/137 235/146/146 197/147/147
+f 420/138/138 197/147/147 190/148/148
+f 414/139/139 191/149/149 190/148/148
+f 587/141/141 578/150/150 525/151/151
+f 529/142/142 525/151/151 408/152/152
+f 427/143/143 408/152/152 286/153/153
+f 320/144/144 286/153/153 221/154/154
+f 288/145/145 221/154/154 187/155/155
+f 235/146/146 187/155/155 154/156/156
+f 197/147/147 154/156/156 146/157/157
+f 190/148/148 146/157/157 147/158/158
+f 578/150/150 577/159/159 524/160/160
+f 525/151/151 524/160/160 409/161/161
+f 408/152/152 409/161/161 287/162/162
+f 286/153/153 287/162/162 222/163/163
+f 221/154/154 222/163/163 188/164/164
+f 187/155/155 188/164/164 153/165/165
+f 154/156/156 153/165/165 145/166/166
+f 577/159/159 586/167/167 530/168/168
+f 524/160/160 530/168/168 428/169/169
+f 409/161/161 428/169/169 321/170/170
+f 287/162/162 321/170/170 289/171/171
+f 222/163/163 289/171/171 236/172/172
+f 188/164/164 236/172/172 196/173/173
+f 153/165/165 196/173/173 189/174/174
+f 145/166/166 189/174/174 192/175/175
+f 586/167/167 629/176/176 567/177/177
+f 530/168/168 567/177/177 508/178/178
+f 428/169/169 508/178/178 460/179/179
+f 321/170/170 460/179/179 446/180/180
+f 289/171/171 446/180/180 436/181/181
+f 236/172/172 436/181/181 419/182/182
+f 196/173/173 419/182/182 413/183/183
+f 189/174/174 413/183/183 416/184/184
+f 629/176/176 638/185/185 601/186/186
+f 567/177/177 601/186/186 554/187/187
+f 508/178/178 554/187/187 533/188/188
+f 460/179/179 533/188/188 526/189/189
+f 446/180/180 526/189/189 517/190/190
+f 436/181/181 517/190/190 504/191/191
+f 419/182/182 504/191/191 500/192/192
+f 413/183/183 500/192/192 501/193/193
+f 638/185/185 644/194/194 642/195/195
+f 601/186/186 642/195/195 633/196/196
+f 554/187/187 633/196/196 612/197/197
+f 533/188/188 612/197/197 607/198/198
+f 526/189/189 607/198/198 605/199/199
+f 517/190/190 605/199/199 598/200/200
+f 504/191/191 598/200/200 594/201/201
+f 500/192/192 594/201/201 595/202/202
+f 607/198/198 654/207/207 653/208/208
+f 605/199/199 653/208/208 652/209/209
+f 594/201/201 649/210/210 651/211/211
+f 654/207/207 744/216/216 745/217/217
+f 653/208/208 745/217/217 751/218/218
+f 649/210/210 753/219/219 755/220/220
+f 700/212/212 720/221/221 749/222/222
+f 709/213/213 749/222/222 801/223/223
+f 730/214/214 801/223/223 823/224/224
+f 739/215/215 823/224/224 828/225/225
+f 744/216/216 828/225/225 840/226/226
+f 745/217/217 840/226/226 850/227/227
+f 751/218/218 850/227/227 852/228/228
+f 753/219/219 852/228/228 853/229/229
+f 720/221/221 733/230/230 790/231/231
+f 749/222/222 790/231/231 847/232/232
+f 801/223/223 847/232/232 894/233/233
+f 823/224/224 894/233/233 907/234/234
+f 828/225/225 907/234/234 916/235/235
+f 840/226/226 916/235/235 930/236/236
+f 850/227/227 930/236/236 936/237/237
+f 852/228/228 936/237/237 939/238/238
+f 733/230/230 765/239/239 825/240/240
+f 790/231/231 825/240/240 923/241/241
+f 847/232/232 923/241/241 1031/242/242
+f 894/233/233 1031/242/242 1066/243/243
+f 907/234/234 1066/243/243 1116/244/244
+f 916/235/235 1116/244/244 1154/245/245
+f 930/236/236 1154/245/245 1159/246/246
+f 936/237/237 1159/246/246 1162/247/247
+f 765/239/239 776/248/248 834/249/249
+f 825/240/240 834/249/249 946/250/250
+f 923/241/241 946/250/250 1069/251/251
+f 1031/242/242 1069/251/251 1130/252/252
+f 1066/243/243 1130/252/252 1164/253/253
+f 1116/244/244 1164/253/253 1199/254/254
+f 1154/245/245 1199/254/254 1202/255/255
+f 1159/246/246 1202/255/255 1204/1/1
+f 776/248/248 777/85/85 835/89/89
+f 834/249/249 835/89/89 945/91/91
+f 946/250/250 945/91/91 1068/93/93
+f 1069/251/251 1068/93/93 1129/95/95
+f 1130/252/252 1129/95/95 1163/97/97
+f 1164/253/253 1163/97/97 1200/99/99
+f 1199/254/254 1200/99/99 1203/101/101
+f 698/256/256 870/257/257 699/258/258
+f 699/258/258 1033/259/259 702/260/260
+f 702/260/260 1062/261/261 703/262/262
+f 703/262/262 1125/263/263 704/264/264
+f 704/264/264 1178/265/265 705/266/266
+f 705/266/266 1234/267/267 707/268/268
+f 707/268/268 1271/269/269 712/270/270
+f 712/270/270 1297/271/271 719/272/272
+f 719/272/272 1301/273/273 725/274/274
+f 725/274/274 1308/275/275 727/276/276
+f 727/276/276 1306/277/277 726/278/278
+f 726/278/278 1306/277/277 662/279/279
+f 829/280/280 1011/281/281 870/257/257
+f 870/257/257 1263/282/282 1033/259/259
+f 1033/259/259 1270/283/283 1062/261/261
+f 1062/261/261 1291/284/284 1125/263/263
+f 1125/263/263 1295/285/285 1178/265/265
+f 1178/265/265 1303/286/286 1234/267/267
+f 1234/267/267 1313/287/287 1271/269/269
+f 1271/269/269 1320/288/288 1297/271/271
+f 1297/271/271 1329/289/289 1301/273/273
+f 1301/273/273 1333/290/290 1308/275/275
+f 1308/275/275 1332/291/291 1306/277/277
+f 1306/277/277 1332/291/291 662/279/279
+f 922/292/292 1158/293/293 1011/281/281
+f 1011/281/281 1294/294/294 1263/282/282
+f 1263/282/282 1298/295/295 1270/283/283
+f 1270/283/283 1302/296/296 1291/284/284
+f 1291/284/284 1309/297/297 1295/285/285
+f 1295/285/285 1312/298/298 1303/286/286
+f 1303/286/286 1321/299/299 1313/287/287
+f 1313/287/287 1335/300/300 1320/288/288
+f 1320/288/288 1340/301/301 1329/289/289
+f 1329/289/289 1343/302/302 1333/290/290
+f 1333/290/290 1341/303/303 1332/291/291
+f 1332/291/291 1341/303/303 662/279/279
+f 1005/304/304 1158/293/293 1180/305/305
+f 1158/293/293 1294/294/294 1296/306/306
+f 1294/294/294 1298/295/295 1300/307/307
+f 1298/295/295 1302/296/296 1307/308/308
+f 1302/296/296 1309/297/297 1310/309/309
+f 1309/297/297 1312/298/298 1315/310/310
+f 1312/298/298 1321/299/299 1322/311/311
+f 1321/299/299 1335/300/300 1337/312/312
+f 1335/300/300 1340/301/301 1342/313/313
+f 1340/301/301 1343/302/302 1353/314/314
+f 1343/302/302 1341/303/303 1352/315/315
+f 1341/303/303 1352/315/315 662/279/279
+f 1180/305/305 184/316/316 342/317/317
+f 1038/318/318 1180/305/305 342/317/317
+f 1296/306/306 184/316/316 57/319/319
+f 1180/305/305 1296/306/306 184/316/316
+f 1300/307/307 57/319/319 54/320/320
+f 1296/306/306 1300/307/307 57/319/319
+f 1307/308/308 46/321/321 54/320/320
+f 1300/307/307 1307/308/308 54/320/320
+f 1310/309/309 44/322/322 46/321/321
+f 1307/308/308 1310/309/309 46/321/321
+f 1315/310/310 44/322/322 39/323/323
+f 1310/309/309 1315/310/310 44/322/322
+f 1322/311/311 32/324/324 39/323/323
+f 1315/310/310 1322/311/311 39/323/323
+f 1337/312/312 17/325/325 32/324/324
+f 1322/311/311 1337/312/312 32/324/324
+f 1337/312/312 1342/313/313 12/326/326
+f 1353/314/314 1/327/327 12/326/326
+f 1342/313/313 1353/314/314 12/326/326
+f 1/327/327 1352/315/315 2/328/328
+f 1/327/327 1353/314/314 1352/315/315
+f 1352/315/315 662/279/279 2/328/328
+f 342/317/317 184/316/316 204/329/329
+f 184/316/316 60/330/330 57/319/319
+f 57/319/319 55/331/331 54/320/320
+f 54/320/320 52/332/332 46/321/321
+f 46/321/321 45/333/333 44/322/322
+f 44/322/322 40/334/334 39/323/323
+f 39/323/323 33/335/335 32/324/324
+f 32/324/324 19/336/336 17/325/325
+f 17/325/325 14/337/337 12/326/326
+f 12/326/326 11/338/338 1/327/327
+f 1/327/327 2/328/328 13/339/339
+f 2/328/328 662/279/279 13/339/339
+f 353/340/340 349/341/341 204/329/329
+f 204/329/329 93/342/342 60/330/330
+f 60/330/330 85/343/343 55/331/331
+f 55/331/331 71/344/344 52/332/332
+f 52/332/332 59/345/345 45/333/333
+f 45/333/333 51/346/346 40/334/334
+f 40/334/334 41/347/347 33/335/335
+f 33/335/335 34/348/348 19/336/336
+f 19/336/336 27/349/349 14/337/337
+f 14/337/337 21/350/350 11/338/338
+f 11/338/338 22/351/351 13/339/339
+f 13/339/339 22/351/351 662/279/279
+f 431/352/352 492/353/353 349/341/341
+f 349/341/341 352/354/354 93/342/342
+f 93/342/342 313/355/355 85/343/343
+f 85/343/343 268/356/356 71/344/344
+f 71/344/344 202/357/357 59/345/345
+f 59/345/345 133/358/358 51/346/346
+f 51/346/346 87/359/359 41/347/347
+f 41/347/347 58/360/360 34/348/348
+f 34/348/348 53/361/361 27/349/349
+f 27/349/349 47/362/362 21/350/350
+f 21/350/350 48/363/363 22/351/351
+f 22/351/351 48/363/363 662/279/279
+f 531/364/364 699/258/258 492/353/353
+f 492/353/353 702/260/260 352/354/354
+f 352/354/354 703/262/262 313/355/355
+f 313/355/355 704/264/264 268/356/356
+f 268/356/356 705/266/266 202/357/357
+f 202/357/357 707/268/268 133/358/358
+f 133/358/358 712/270/270 87/359/359
+f 87/359/359 719/272/272 58/360/360
+f 58/360/360 725/274/274 53/361/361
+f 53/361/361 727/276/276 47/362/362
+f 47/362/362 726/278/278 48/363/363
+f 48/363/363 726/278/278 662/279/279
+f 698/256/256 829/280/280 870/257/257
+f 699/258/258 870/257/257 1033/259/259
+f 702/260/260 1033/259/259 1062/261/261
+f 703/262/262 1062/261/261 1125/263/263
+f 704/264/264 1125/263/263 1178/265/265
+f 705/266/266 1178/265/265 1234/267/267
+f 707/268/268 1234/267/267 1271/269/269
+f 712/270/270 1271/269/269 1297/271/271
+f 719/272/272 1297/271/271 1301/273/273
+f 725/274/274 1301/273/273 1308/275/275
+f 727/276/276 1308/275/275 1306/277/277
+f 829/280/280 922/292/292 1011/281/281
+f 870/257/257 1011/281/281 1263/282/282
+f 1033/259/259 1263/282/282 1270/283/283
+f 1062/261/261 1270/283/283 1291/284/284
+f 1125/263/263 1291/284/284 1295/285/285
+f 1178/265/265 1295/285/285 1303/286/286
+f 1234/267/267 1303/286/286 1313/287/287
+f 1271/269/269 1313/287/287 1320/288/288
+f 1297/271/271 1320/288/288 1329/289/289
+f 1301/273/273 1329/289/289 1333/290/290
+f 1308/275/275 1333/290/290 1332/291/291
+f 922/292/292 1158/293/293 1005/304/304
+f 1011/281/281 1294/294/294 1158/293/293
+f 1263/282/282 1298/295/295 1294/294/294
+f 1270/283/283 1302/296/296 1298/295/295
+f 1291/284/284 1309/297/297 1302/296/296
+f 1295/285/285 1312/298/298 1309/297/297
+f 1303/286/286 1321/299/299 1312/298/298
+f 1313/287/287 1335/300/300 1321/299/299
+f 1320/288/288 1340/301/301 1335/300/300
+f 1329/289/289 1343/302/302 1340/301/301
+f 1333/290/290 1343/302/302 1341/303/303
+f 1005/304/304 1180/305/305 1038/318/318
+f 1158/293/293 1296/306/306 1180/305/305
+f 1294/294/294 1300/307/307 1296/306/306
+f 1298/295/295 1307/308/308 1300/307/307
+f 1302/296/296 1310/309/309 1307/308/308
+f 1309/297/297 1315/310/310 1310/309/309
+f 1312/298/298 1322/311/311 1315/310/310
+f 1321/299/299 1337/312/312 1322/311/311
+f 1335/300/300 1342/313/313 1337/312/312
+f 1340/301/301 1353/314/314 1342/313/313
+f 1343/302/302 1352/315/315 1353/314/314
+f 1337/312/312 17/325/325 12/326/326
+f 342/317/317 353/340/340 204/329/329
+f 184/316/316 204/329/329 60/330/330
+f 57/319/319 60/330/330 55/331/331
+f 54/320/320 55/331/331 52/332/332
+f 46/321/321 52/332/332 45/333/333
+f 44/322/322 45/333/333 40/334/334
+f 39/323/323 40/334/334 33/335/335
+f 32/324/324 33/335/335 19/336/336
+f 17/325/325 19/336/336 14/337/337
+f 12/326/326 14/337/337 11/338/338
+f 1/327/327 11/338/338 13/339/339
+f 353/340/340 431/352/352 349/341/341
+f 204/329/329 349/341/341 93/342/342
+f 60/330/330 93/342/342 85/343/343
+f 55/331/331 85/343/343 71/344/344
+f 52/332/332 71/344/344 59/345/345
+f 45/333/333 59/345/345 51/346/346
+f 40/334/334 51/346/346 41/347/347
+f 33/335/335 41/347/347 34/348/348
+f 19/336/336 34/348/348 27/349/349
+f 14/337/337 27/349/349 21/350/350
+f 11/338/338 21/350/350 22/351/351
+f 431/352/352 531/364/364 492/353/353
+f 349/341/341 492/353/353 352/354/354
+f 93/342/342 352/354/354 313/355/355
+f 85/343/343 313/355/355 268/356/356
+f 71/344/344 268/356/356 202/357/357
+f 59/345/345 202/357/357 133/358/358
+f 51/346/346 133/358/358 87/359/359
+f 41/347/347 87/359/359 58/360/360
+f 34/348/348 58/360/360 53/361/361
+f 27/349/349 53/361/361 47/362/362
+f 21/350/350 47/362/362 48/363/363
+f 531/364/364 698/256/256 699/258/258
+f 492/353/353 699/258/258 702/260/260
+f 352/354/354 702/260/260 703/262/262
+f 313/355/355 703/262/262 704/264/264
+f 268/356/356 704/264/264 705/266/266
+f 202/357/357 705/266/266 707/268/268
+f 133/358/358 707/268/268 712/270/270
+f 87/359/359 712/270/270 719/272/272
+f 58/360/360 719/272/272 725/274/274
+f 53/361/361 725/274/274 727/276/276
+f 47/362/362 727/276/276 726/278/278
+f 1038/318/318 1180/305/305 1101/365/365
+f 1180/305/305 1296/306/306 1284/366/366
+f 1296/306/306 1300/307/307 1293/367/367
+f 1300/307/307 1307/308/308 1299/368/368
+f 1307/308/308 1310/309/309 1304/369/369
+f 1310/309/309 1315/310/310 1311/370/370
+f 1315/310/310 1322/311/311 1318/371/371
+f 1322/311/311 1337/312/312 1334/372/372
+f 1337/312/312 1342/313/313 1336/373/373
+f 1342/313/313 1353/314/314 1339/374/374
+f 1353/314/314 1352/315/315 1338/375/375
+f 1352/315/315 1338/375/375 662/279/279
+f 984/376/376 935/377/377 1101/365/365
+f 1101/365/365 1219/378/378 1284/366/366
+f 1284/366/366 1240/379/379 1293/367/367
+f 1293/367/367 1255/380/380 1299/368/368
+f 1299/368/368 1267/381/381 1304/369/369
+f 1304/369/369 1292/382/382 1311/370/370
+f 1311/370/370 1305/383/383 1318/371/371
+f 1318/371/371 1314/384/384 1334/372/372
+f 1334/372/372 1316/385/385 1336/373/373
+f 1336/373/373 1319/386/386 1339/374/374
+f 1339/374/374 1317/387/387 1338/375/375
+f 1338/375/375 1317/387/387 662/279/279
+f 887/388/388 799/389/389 935/377/377
+f 935/377/377 865/390/390 1219/378/378
+f 1219/378/378 874/391/391 1240/379/379
+f 1240/379/379 896/392/392 1255/380/380
+f 1255/380/380 908/393/393 1267/381/381
+f 1267/381/381 942/394/394 1292/382/382
+f 1292/382/382 1045/395/395 1305/383/383
+f 1305/383/383 1174/396/396 1314/384/384
+f 1314/384/384 1218/397/397 1316/385/385
+f 1316/385/385 1231/398/398 1319/386/386
+f 1319/386/386 1228/399/399 1317/387/387
+f 1317/387/387 1228/399/399 662/279/279
+f 778/400/400 564/401/401 799/389/389
+f 799/389/389 510/402/402 865/390/390
+f 874/391/391 510/402/402 493/403/403
+f 865/390/390 510/402/402 874/391/391
+f 896/392/392 493/403/403 483/404/404
+f 874/391/391 493/403/403 896/392/392
+f 908/393/393 483/404/404 465/405/405
+f 896/392/392 483/404/404 908/393/393
+f 942/394/394 465/405/405 441/406/406
+f 908/393/393 465/405/405 942/394/394
+f 1045/395/395 441/406/406 362/407/407
+f 942/394/394 441/406/406 1045/395/395
+f 1045/395/395 251/408/408 1174/396/396
+f 1174/396/396 193/409/409 1218/397/397
+f 1218/397/397 164/410/410 1231/398/398
+f 1228/399/399 164/410/410 171/411/411
+f 1231/398/398 164/410/410 1228/399/399
+f 1228/399/399 171/411/411 662/279/279
+f 585/412/412 429/413/413 564/401/401
+f 564/401/401 150/414/414 510/402/402
+f 510/402/402 125/415/415 493/403/403
+f 493/403/403 100/416/416 483/404/404
+f 483/404/404 92/417/417 465/405/405
+f 465/405/405 70/418/418 441/406/406
+f 441/406/406 49/419/419 362/407/407
+f 362/407/407 42/420/420 251/408/408
+f 251/408/408 38/421/421 193/409/409
+f 193/409/409 36/422/422 164/410/410
+f 164/410/410 37/423/423 171/411/411
+f 171/411/411 37/423/423 662/279/279
+f 477/424/424 264/425/425 429/413/413
+f 429/413/413 74/426/426 150/414/414
+f 150/414/414 61/427/427 125/415/415
+f 125/415/415 56/428/428 100/416/416
+f 100/416/416 50/429/429 92/417/417
+f 92/417/417 43/430/430 70/418/418
+f 70/418/418 35/431/431 49/419/419
+f 49/419/419 20/432/432 42/420/420
+f 42/420/420 18/433/433 38/421/421
+f 38/421/421 15/434/434 36/422/422
+f 36/422/422 16/435/435 37/423/423
+f 37/423/423 16/435/435 662/279/279
+f 381/436/436 184/316/316 264/425/425
+f 264/425/425 57/319/319 74/426/426
+f 74/426/426 54/320/320 61/427/427
+f 61/427/427 46/321/321 56/428/428
+f 56/428/428 44/322/322 50/429/429
+f 50/429/429 39/323/323 43/430/430
+f 43/430/430 32/324/324 35/431/431
+f 35/431/431 17/325/325 20/432/432
+f 20/432/432 12/326/326 18/433/433
+f 1/327/327 15/434/434 18/433/433
+f 15/434/434 2/328/328 16/435/435
+f 16/435/435 2/328/328 662/279/279
+f 1038/318/318 984/376/376 1101/365/365
+f 1180/305/305 1101/365/365 1284/366/366
+f 1296/306/306 1284/366/366 1293/367/367
+f 1300/307/307 1293/367/367 1299/368/368
+f 1307/308/308 1299/368/368 1304/369/369
+f 1310/309/309 1304/369/369 1311/370/370
+f 1315/310/310 1311/370/370 1318/371/371
+f 1322/311/311 1318/371/371 1334/372/372
+f 1337/312/312 1334/372/372 1336/373/373
+f 1342/313/313 1336/373/373 1339/374/374
+f 1353/314/314 1339/374/374 1338/375/375
+f 984/376/376 887/388/388 935/377/377
+f 1101/365/365 935/377/377 1219/378/378
+f 1284/366/366 1219/378/378 1240/379/379
+f 1293/367/367 1240/379/379 1255/380/380
+f 1299/368/368 1255/380/380 1267/381/381
+f 1304/369/369 1267/381/381 1292/382/382
+f 1311/370/370 1292/382/382 1305/383/383
+f 1318/371/371 1305/383/383 1314/384/384
+f 1334/372/372 1314/384/384 1316/385/385
+f 1336/373/373 1316/385/385 1319/386/386
+f 1339/374/374 1319/386/386 1317/387/387
+f 887/388/388 778/400/400 799/389/389
+f 935/377/377 799/389/389 865/390/390
+f 1219/378/378 865/390/390 874/391/391
+f 1240/379/379 874/391/391 896/392/392
+f 1255/380/380 896/392/392 908/393/393
+f 1267/381/381 908/393/393 942/394/394
+f 1292/382/382 942/394/394 1045/395/395
+f 1305/383/383 1045/395/395 1174/396/396
+f 1314/384/384 1174/396/396 1218/397/397
+f 1316/385/385 1218/397/397 1231/398/398
+f 1319/386/386 1231/398/398 1228/399/399
+f 778/400/400 585/412/412 564/401/401
+f 799/389/389 564/401/401 510/402/402
+f 1045/395/395 362/407/407 251/408/408
+f 1174/396/396 251/408/408 193/409/409
+f 1218/397/397 193/409/409 164/410/410
+f 585/412/412 477/424/424 429/413/413
+f 564/401/401 429/413/413 150/414/414
+f 510/402/402 150/414/414 125/415/415
+f 493/403/403 125/415/415 100/416/416
+f 483/404/404 100/416/416 92/417/417
+f 465/405/405 92/417/417 70/418/418
+f 441/406/406 70/418/418 49/419/419
+f 362/407/407 49/419/419 42/420/420
+f 251/408/408 42/420/420 38/421/421
+f 193/409/409 38/421/421 36/422/422
+f 164/410/410 36/422/422 37/423/423
+f 477/424/424 381/436/436 264/425/425
+f 429/413/413 264/425/425 74/426/426
+f 150/414/414 74/426/426 61/427/427
+f 125/415/415 61/427/427 56/428/428
+f 100/416/416 56/428/428 50/429/429
+f 92/417/417 50/429/429 43/430/430
+f 70/418/418 43/430/430 35/431/431
+f 49/419/419 35/431/431 20/432/432
+f 42/420/420 20/432/432 18/433/433
+f 38/421/421 18/433/433 15/434/434
+f 36/422/422 15/434/434 16/435/435
+f 381/436/436 342/317/317 184/316/316
+f 264/425/425 184/316/316 57/319/319
+f 74/426/426 57/319/319 54/320/320
+f 61/427/427 54/320/320 46/321/321
+f 56/428/428 46/321/321 44/322/322
+f 50/429/429 44/322/322 39/323/323
+f 43/430/430 39/323/323 32/324/324
+f 35/431/431 32/324/324 17/325/325
+f 20/432/432 17/325/325 12/326/326
+f 18/433/433 12/326/326 1/327/327
+f 1/327/327 2/328/328 15/434/434
+f 648/437/437 759/52/52 756/122/122
+f 650/438/438 648/437/437 756/122/122
+f 756/122/122 759/52/52 867/50/50
+f 854/439/439 867/50/50 978/48/48
+f 938/113/113 978/48/48 1108/46/46
+f 1057/440/440 1108/46/46 1207/42/42
+f 1161/102/102 1207/42/42 1237/41/41
+f 1205/103/103 1237/41/41 1236/2/2
+f 756/122/122 867/50/50 854/439/439
+f 854/439/439 978/48/48 938/113/113
+f 938/113/113 1108/46/46 1057/440/440
+f 1057/440/440 1207/42/42 1161/102/102
+f 1161/102/102 1237/41/41 1205/103/103
+f 1205/103/103 1236/2/2 1204/1/1
+f 692/441/441 597/131/131 592/54/54
+f 691/442/442 597/131/131 692/441/441
+f 597/131/131 486/56/56 592/54/54
+f 502/443/443 377/57/57 486/56/56
+f 415/140/140 246/58/58 377/57/57
+f 297/444/444 143/59/59 246/58/58
+f 191/149/149 118/60/60 143/59/59
+f 147/158/158 117/15/15 118/60/60
+f 597/131/131 502/443/443 486/56/56
+f 502/443/443 415/140/140 377/57/57
+f 415/140/140 297/444/444 246/58/58
+f 297/444/444 191/149/149 143/59/59
+f 191/149/149 147/158/158 118/60/60
+f 147/158/158 148/16/16 117/15/15
+f 148/16/16 142/62/62 117/15/15
+f 192/175/175 245/64/64 142/62/62
+f 298/445/445 376/66/66 245/64/64
+f 416/184/184 485/68/68 376/66/66
+f 501/193/193 591/70/70 485/68/68
+f 595/202/202 647/72/72 591/70/70
+f 148/16/16 192/175/175 142/62/62
+f 192/175/175 298/445/445 245/64/64
+f 298/445/445 416/184/184 376/66/66
+f 416/184/184 501/193/193 485/68/68
+f 501/193/193 595/202/202 591/70/70
+f 595/202/202 651/211/211 647/72/72
+f 651/211/211 758/74/74 647/72/72
+f 755/220/220 866/76/76 758/74/74
+f 853/229/229 977/78/78 866/76/76
+f 939/238/238 1107/80/80 977/78/78
+f 1056/446/446 1206/82/82 1107/80/80
+f 1162/247/247 1236/2/2 1206/82/82
+f 651/211/211 755/220/220 758/74/74
+f 755/220/220 853/229/229 866/76/76
+f 853/229/229 939/238/238 977/78/78
+f 939/238/238 1056/446/446 1107/80/80
+f 1056/446/446 1162/247/247 1206/82/82
+f 1162/247/247 1204/1/1 1236/2/2
+f 97/447/447 78/448/448 77/449/448
+f 98/450/449 78/448/448 97/447/447
+f 77/449/448 80/451/450 79/452/450
+f 78/448/448 80/451/450 77/449/448
+f 79/452/450 31/453/451 30/454/451
+f 80/451/450 31/453/451 79/452/450
+f 30/454/451 29/455/452 28/456/452
+f 31/453/451 29/455/452 30/454/451
+f 28/456/452 98/450/449 97/447/447
+f 29/455/452 98/450/449 28/456/452
+f 25/457/453 24/458/454 23/459/454
+f 26/460/453 24/458/454 25/457/453
+f 23/459/454 10/461/455 9/462/455
+f 24/458/454 10/461/455 23/459/454
+f 9/462/455 4/463/456 3/464/457
+f 10/461/455 4/463/456 9/462/455
+f 3/464/457 8/465/458 7/466/459
+f 4/463/456 8/465/458 3/464/457
+f 7/466/459 6/467/460 5/468/460
+f 8/465/458 6/467/460 7/466/459
+f 5/468/460 26/460/453 25/457/453
+f 6/467/460 26/460/453 5/468/460
+f 1328/469/461 1330/470/462 1331/471/462
+f 1327/472/461 1330/470/462 1328/469/461
+f 1331/471/462 1344/473/463 1345/474/463
+f 1330/470/462 1344/473/463 1331/471/462
+f 1345/474/463 1350/475/464 1351/476/464
+f 1344/473/463 1350/475/464 1345/474/463
+f 1351/476/464 1346/477/465 1347/478/465
+f 1350/475/464 1346/477/465 1351/476/464
+f 1347/478/465 1348/479/466 1349/480/466
+f 1346/477/465 1348/479/466 1347/478/465
+f 1349/480/466 1327/472/461 1328/469/461
+f 1348/479/466 1327/472/461 1349/480/466
+f 1257/481/467 1274/482/468 1275/483/468
+f 1256/484/467 1274/482/468 1257/481/467
+f 1275/483/468 1276/485/469 1277/486/469
+f 1274/482/468 1276/485/469 1275/483/468
+f 1277/486/469 1323/487/470 1324/488/470
+f 1276/485/469 1323/487/470 1277/486/469
+f 1324/488/470 1325/489/471 1326/490/471
+f 1323/487/470 1325/489/471 1324/488/470
+f 1326/490/471 1256/484/467 1257/481/467
+f 1325/489/471 1256/484/467 1326/490/471
+f 91/491/472 99/492/473 141/493/474
+f 99/492/473 108/494/475 157/495/476
+f 108/494/475 149/496/477 203/497/478
+f 149/496/477 258/498/479 292/499/480
+f 258/498/479 329/500/481 335/501/482
+f 124/502/483 141/493/474 198/503/484
+f 141/493/474 157/495/476 209/504/485
+f 157/495/476 203/497/478 261/505/486
+f 203/497/478 292/499/480 299/506/487
+f 335/501/482 338/507/488 299/506/487
+f 292/499/480 335/501/482 299/506/487
+f 182/508/489 198/503/484 412/509/490
+f 198/503/484 209/504/485 407/510/491
+f 209/504/485 261/505/486 388/511/492
+f 261/505/486 299/506/487 350/512/493
+f 299/506/487 338/507/488 337/513/494
+f 437/514/495 412/509/490 565/515/496
+f 412/509/490 407/510/491 540/516/497
+f 407/510/491 388/511/492 489/517/498
+f 350/512/493 405/518/499 489/517/498
+f 388/511/492 350/512/493 489/517/498
+f 350/512/493 337/513/494 328/519/500
+f 565/515/496 109/520/501 104/521/502
+f 643/522/503 565/515/496 104/521/502
+f 540/516/497 116/523/504 109/520/501
+f 565/515/496 540/516/497 109/520/501
+f 489/517/498 155/524/505 116/523/504
+f 540/516/497 489/517/498 116/523/504
+f 405/518/499 265/525/506 155/524/505
+f 489/517/498 405/518/499 155/524/505
+f 328/519/500 325/526/507 265/525/506
+f 405/518/499 328/519/500 265/525/506
+f 104/521/502 109/520/501 84/527/508
+f 109/520/501 116/523/504 89/528/509
+f 116/523/504 155/524/505 110/529/510
+f 155/524/505 265/525/506 227/530/511
+f 325/526/507 333/531/512 227/530/511
+f 265/525/506 325/526/507 227/530/511
+f 75/532/513 84/527/508 82/533/514
+f 84/527/508 89/528/509 86/534/515
+f 89/528/509 110/529/510 106/535/516
+f 110/529/510 227/530/511 216/536/517
+f 227/530/511 333/531/512 336/537/518
+f 66/538/519 82/533/514 83/539/520
+f 82/533/514 86/534/515 90/540/521
+f 86/534/515 106/535/516 111/541/522
+f 106/535/516 216/536/517 225/542/523
+f 216/536/517 336/537/518 334/543/524
+f 72/544/525 83/539/520 88/545/526
+f 83/539/520 90/540/521 96/546/527
+f 90/540/521 111/541/522 123/547/528
+f 111/541/522 225/542/523 240/548/529
+f 225/542/523 334/543/524 332/549/530
+f 81/550/531 88/545/526 99/492/473
+f 88/545/526 96/546/527 108/494/475
+f 96/546/527 123/547/528 149/496/477
+f 123/547/528 240/548/529 258/498/479
+f 240/548/529 332/549/530 329/500/481
+f 91/491/472 141/493/474 124/502/483
+f 99/492/473 157/495/476 141/493/474
+f 108/494/475 203/497/478 157/495/476
+f 149/496/477 292/499/480 203/497/478
+f 258/498/479 335/501/482 292/499/480
+f 124/502/483 198/503/484 182/508/489
+f 141/493/474 209/504/485 198/503/484
+f 157/495/476 261/505/486 209/504/485
+f 203/497/478 299/506/487 261/505/486
+f 182/508/489 412/509/490 437/514/495
+f 198/503/484 407/510/491 412/509/490
+f 209/504/485 388/511/492 407/510/491
+f 261/505/486 350/512/493 388/511/492
+f 299/506/487 337/513/494 350/512/493
+f 437/514/495 565/515/496 643/522/503
+f 412/509/490 540/516/497 565/515/496
+f 407/510/491 489/517/498 540/516/497
+f 350/512/493 328/519/500 405/518/499
+f 104/521/502 84/527/508 75/532/513
+f 109/520/501 89/528/509 84/527/508
+f 116/523/504 110/529/510 89/528/509
+f 155/524/505 227/530/511 110/529/510
+f 75/532/513 82/533/514 66/538/519
+f 84/527/508 86/534/515 82/533/514
+f 89/528/509 106/535/516 86/534/515
+f 110/529/510 216/536/517 106/535/516
+f 227/530/511 336/537/518 216/536/517
+f 66/538/519 83/539/520 72/544/525
+f 82/533/514 90/540/521 83/539/520
+f 86/534/515 111/541/522 90/540/521
+f 106/535/516 225/542/523 111/541/522
+f 216/536/517 334/543/524 225/542/523
+f 72/544/525 88/545/526 81/550/531
+f 83/539/520 96/546/527 88/545/526
+f 90/540/521 123/547/528 96/546/527
+f 111/541/522 240/548/529 123/547/528
+f 225/542/523 332/549/530 240/548/529
+f 81/550/531 99/492/473 91/491/472
+f 88/545/526 108/494/475 99/492/473
+f 96/546/527 149/496/477 108/494/475
+f 123/547/528 258/498/479 149/496/477
+f 240/548/529 329/500/481 258/498/479
+f 1261/551/532 1209/552/533 1254/553/534
+f 1254/553/534 1196/554/535 1246/555/536
+f 1246/555/536 1149/556/537 1201/557/538
+f 1201/557/538 1063/558/539 1094/559/540
+f 1094/559/540 1017/560/541 1023/561/542
+f 1226/562/543 1153/563/544 1209/552/533
+f 1209/552/533 1146/564/545 1196/554/535
+f 1196/554/535 1091/565/546 1149/556/537
+f 1149/556/537 1055/566/547 1063/558/539
+f 1017/560/541 1055/566/547 1014/567/548
+f 1063/558/539 1055/566/547 1017/560/541
+f 1169/568/549 941/569/550 1153/563/544
+f 1153/563/544 947/570/551 1146/564/545
+f 1146/564/545 968/571/552 1091/565/546
+f 1091/565/546 1002/572/553 1055/566/547
+f 1055/566/547 1015/573/554 1014/567/548
+f 915/574/555 791/575/556 941/569/550
+f 941/569/550 816/576/557 947/570/551
+f 947/570/551 863/577/558 968/571/552
+f 1002/572/553 863/577/558 949/578/559
+f 968/571/552 863/577/558 1002/572/553
+f 1002/572/553 1024/579/560 1015/573/554
+f 791/575/556 1249/580/561 1245/581/562
+f 706/582/563 1249/580/561 791/575/556
+f 816/576/557 1245/581/562 1239/583/564
+f 791/575/556 1245/581/562 816/576/557
+f 863/577/558 1239/583/564 1197/584/565
+f 816/576/557 1239/583/564 863/577/558
+f 949/578/559 1197/584/565 1088/585/566
+f 863/577/558 1197/584/565 949/578/559
+f 1024/579/560 1088/585/566 1027/586/567
+f 949/578/559 1088/585/566 1024/579/560
+f 1249/580/561 1268/587/568 1245/581/562
+f 1245/581/562 1264/588/569 1239/583/564
+f 1239/583/564 1244/589/570 1197/584/565
+f 1197/584/565 1124/590/571 1088/585/566
+f 1027/586/567 1124/590/571 1019/591/572
+f 1088/585/566 1124/590/571 1027/586/567
+f 1278/592/573 1272/593/574 1268/587/568
+f 1268/587/568 1266/594/575 1264/588/569
+f 1264/588/569 1248/595/576 1244/589/570
+f 1244/589/570 1137/596/577 1124/590/571
+f 1124/590/571 1016/597/578 1019/591/572
+f 1285/598/579 1269/599/580 1272/593/574
+f 1272/593/574 1262/600/581 1266/594/575
+f 1266/594/575 1243/601/582 1248/595/576
+f 1248/595/576 1126/602/583 1137/596/577
+f 1137/596/577 1018/603/584 1016/597/578
+f 1280/604/585 1265/605/586 1269/599/580
+f 1269/599/580 1258/606/587 1262/600/581
+f 1262/600/581 1227/607/588 1243/601/582
+f 1243/601/582 1114/608/589 1126/602/583
+f 1126/602/583 1020/609/590 1018/603/584
+f 1273/610/591 1254/553/534 1265/605/586
+f 1265/605/586 1246/555/536 1258/606/587
+f 1258/606/587 1201/557/538 1227/607/588
+f 1227/607/588 1094/559/540 1114/608/589
+f 1114/608/589 1023/561/542 1020/609/590
+f 1261/551/532 1226/562/543 1209/552/533
+f 1254/553/534 1209/552/533 1196/554/535
+f 1246/555/536 1196/554/535 1149/556/537
+f 1201/557/538 1149/556/537 1063/558/539
+f 1094/559/540 1063/558/539 1017/560/541
+f 1226/562/543 1169/568/549 1153/563/544
+f 1209/552/533 1153/563/544 1146/564/545
+f 1196/554/535 1146/564/545 1091/565/546
+f 1149/556/537 1091/565/546 1055/566/547
+f 1169/568/549 915/574/555 941/569/550
+f 1153/563/544 941/569/550 947/570/551
+f 1146/564/545 947/570/551 968/571/552
+f 1091/565/546 968/571/552 1002/572/553
+f 1055/566/547 1002/572/553 1015/573/554
+f 915/574/555 706/582/563 791/575/556
+f 941/569/550 791/575/556 816/576/557
+f 947/570/551 816/576/557 863/577/558
+f 1002/572/553 949/578/559 1024/579/560
+f 1249/580/561 1278/592/573 1268/587/568
+f 1245/581/562 1268/587/568 1264/588/569
+f 1239/583/564 1264/588/569 1244/589/570
+f 1197/584/565 1244/589/570 1124/590/571
+f 1278/592/573 1285/598/579 1272/593/574
+f 1268/587/568 1272/593/574 1266/594/575
+f 1264/588/569 1266/594/575 1248/595/576
+f 1244/589/570 1248/595/576 1137/596/577
+f 1124/590/571 1137/596/577 1016/597/578
+f 1285/598/579 1280/604/585 1269/599/580
+f 1272/593/574 1269/599/580 1262/600/581
+f 1266/594/575 1262/600/581 1243/601/582
+f 1248/595/576 1243/601/582 1126/602/583
+f 1137/596/577 1126/602/583 1018/603/584
+f 1280/604/585 1273/610/591 1265/605/586
+f 1269/599/580 1265/605/586 1258/606/587
+f 1262/600/581 1258/606/587 1227/607/588
+f 1243/601/582 1227/607/588 1114/608/589
+f 1126/602/583 1114/608/589 1020/609/590
+f 1273/610/591 1261/551/532 1254/553/534
+f 1265/605/586 1254/553/534 1246/555/536
+f 1258/606/587 1246/555/536 1201/557/538
+f 1227/607/588 1201/557/538 1094/559/540
+f 1114/608/589 1094/559/540 1023/561/542
+f 615/611/592 576/612/593 618/613/594
+f 774/614/595 618/613/594 775/615/594
+f 615/611/592 618/613/594 774/614/595
+f 774/614/595 775/615/594 800/616/596
+f 618/613/594 576/612/593 623/617/597
+f 775/615/594 623/617/597 757/618/598
+f 618/613/594 623/617/597 775/615/594
+f 775/615/594 757/618/598 800/616/596
+f 623/617/597 576/612/593 625/619/599
+f 757/618/598 625/619/599 722/620/600
+f 623/617/597 625/619/599 757/618/598
+f 757/618/598 722/620/600 800/616/596
+f 625/619/599 576/612/593 627/621/601
+f 722/620/600 627/621/601 717/622/602
+f 625/619/599 627/621/601 722/620/600
+f 722/620/600 717/622/602 800/616/596
+f 627/621/601 576/612/593 626/623/603
+f 717/622/602 626/623/603 715/624/604
+f 627/621/601 626/623/603 717/622/602
+f 717/622/602 715/624/604 800/616/596
+f 626/623/603 576/612/593 624/625/605
+f 715/624/604 624/625/605 713/626/606
+f 626/623/603 624/625/605 715/624/604
+f 715/624/604 713/626/606 800/616/596
+f 624/625/605 576/612/593 622/627/607
+f 713/626/606 622/627/607 714/628/608
+f 624/625/605 622/627/607 713/626/606
+f 713/626/606 714/628/608 800/616/596
+f 622/627/607 576/612/593 621/629/609
+f 714/628/608 621/629/609 716/630/610
+f 622/627/607 621/629/609 714/628/608
+f 714/628/608 716/630/610 800/616/596
+f 621/629/609 576/612/593 620/631/611
+f 716/630/610 620/631/611 718/632/612
+f 621/629/609 620/631/611 716/630/610
+f 716/630/610 718/632/612 800/616/596
+f 620/631/611 576/612/593 619/633/613
+f 718/632/612 619/633/613 721/634/614
+f 620/631/611 619/633/613 718/632/612
+f 718/632/612 721/634/614 800/616/596
+f 619/633/613 576/612/593 617/635/615
+f 721/634/614 617/635/615 764/636/616
+f 619/633/613 617/635/615 721/634/614
+f 721/634/614 764/636/616 800/616/596
+f 617/635/615 576/612/593 616/637/617
+f 764/636/616 616/637/617 767/638/618
+f 617/635/615 616/637/617 764/636/616
+f 764/636/616 767/638/618 800/616/596
+f 616/637/617 576/612/593 615/611/592
+f 767/638/618 615/611/592 774/614/595
+f 616/637/617 615/611/592 767/638/618
+f 767/638/618 774/614/595 800/616/596
+f 951/639/619 963/640/620 934/641/621
+f 973/642/622 934/641/621 893/643/623
+f 951/639/619 934/641/621 973/642/622
+f 983/644/624 893/643/623 872/645/625
+f 973/642/622 893/643/623 983/644/624
+f 986/646/626 872/645/625 876/647/627
+f 983/644/624 872/645/625 986/646/626
+f 988/648/628 876/647/627 898/649/628
+f 986/646/626 876/647/627 988/648/628
+f 934/641/621 963/640/620 920/650/629
+f 893/643/623 920/650/629 833/651/630
+f 934/641/621 920/650/629 893/643/623
+f 872/645/625 833/651/630 804/652/631
+f 893/643/623 833/651/630 872/645/625
+f 876/647/627 804/652/631 808/653/632
+f 872/645/625 804/652/631 876/647/627
+f 898/649/628 808/653/632 831/654/628
+f 876/647/627 808/653/632 898/649/628
+f 920/650/629 963/640/620 912/655/633
+f 833/651/630 912/655/633 819/656/634
+f 920/650/629 912/655/633 833/651/630
+f 804/652/631 819/656/634 783/657/635
+f 833/651/630 819/656/634 804/652/631
+f 808/653/632 783/657/635 788/658/636
+f 804/652/631 783/657/635 808/653/632
+f 831/654/628 788/658/636 811/659/628
+f 808/653/632 788/658/636 831/654/628
+f 912/655/633 963/640/620 919/660/637
+f 819/656/634 919/660/637 832/661/638
+f 912/655/633 919/660/637 819/656/634
+f 783/657/635 832/661/638 803/662/639
+f 819/656/634 832/661/638 783/657/635
+f 788/658/636 803/662/639 807/663/640
+f 783/657/635 803/662/639 788/658/636
+f 811/659/628 807/663/640 830/664/628
+f 788/658/636 807/663/640 811/659/628
+f 919/660/637 963/640/620 933/665/641
+f 832/661/638 933/665/641 892/666/642
+f 919/660/637 933/665/641 832/661/638
+f 803/662/639 892/666/642 871/667/643
+f 832/661/638 892/666/642 803/662/639
+f 807/663/640 871/667/643 875/668/644
+f 803/662/639 871/667/643 807/663/640
+f 830/664/628 875/668/644 897/669/628
+f 807/663/640 875/668/644 830/664/628
+f 933/665/641 963/640/620 950/670/645
+f 892/666/642 950/670/645 972/671/646
+f 933/665/641 950/670/645 892/666/642
+f 871/667/643 972/671/646 982/672/647
+f 892/666/642 972/671/646 871/667/643
+f 875/668/644 982/672/647 985/673/648
+f 871/667/643 982/672/647 875/668/644
+f 897/669/628 985/673/648 987/674/628
+f 875/668/644 985/673/648 897/669/628
+f 950/670/645 963/640/620 995/675/649
+f 972/671/646 995/675/649 1082/676/650
+f 950/670/645 995/675/649 972/671/646
+f 982/672/647 1082/676/650 1138/677/651
+f 972/671/646 1082/676/650 982/672/647
+f 985/673/648 1138/677/651 1140/678/652
+f 982/672/647 1138/677/651 985/673/648
+f 987/674/628 1140/678/652 1102/679/628
+f 985/673/648 1140/678/652 987/674/628
+f 995/675/649 963/640/620 1006/680/653
+f 1082/676/650 1006/680/653 1165/681/654
+f 995/675/649 1006/680/653 1082/676/650
+f 1138/677/651 1165/681/654 1222/682/655
+f 1082/676/650 1165/681/654 1138/677/651
+f 1140/678/652 1222/682/655 1220/683/656
+f 1138/677/651 1222/682/655 1140/678/652
+f 1102/679/628 1220/683/656 1192/684/657
+f 1140/678/652 1220/683/656 1102/679/628
+f 1006/680/653 963/640/620 1010/685/658
+f 1165/681/654 1010/685/658 1191/686/659
+f 1006/680/653 1010/685/658 1165/681/654
+f 1222/682/655 1191/686/659 1235/687/660
+f 1165/681/654 1191/686/659 1222/682/655
+f 1220/683/656 1235/687/660 1233/688/661
+f 1222/682/655 1235/687/660 1220/683/656
+f 1192/684/657 1233/688/661 1213/689/628
+f 1220/683/656 1233/688/661 1192/684/657
+f 1010/685/658 963/640/620 1007/690/662
+f 1191/686/659 1007/690/662 1166/691/663
+f 1010/685/658 1007/690/662 1191/686/659
+f 1235/687/660 1166/691/663 1223/692/664
+f 1191/686/659 1166/691/663 1235/687/660
+f 1233/688/661 1223/692/664 1221/693/665
+f 1235/687/660 1223/692/664 1233/688/661
+f 1213/689/628 1221/693/665 1193/694/628
+f 1233/688/661 1221/693/665 1213/689/628
+f 1007/690/662 963/640/620 996/695/666
+f 1166/691/663 996/695/666 1083/696/667
+f 1007/690/662 996/695/666 1166/691/663
+f 1223/692/664 1083/696/667 1139/697/668
+f 1166/691/663 1083/696/667 1223/692/664
+f 1221/693/665 1139/697/668 1141/698/669
+f 1223/692/664 1139/697/668 1221/693/665
+f 1193/694/628 1141/698/669 1103/699/628
+f 1221/693/665 1141/698/669 1193/694/628
+f 996/695/666 963/640/620 951/639/619
+f 1083/696/667 951/639/619 973/642/622
+f 996/695/666 951/639/619 1083/696/667
+f 1139/697/668 973/642/622 983/644/624
+f 1083/696/667 973/642/622 1139/697/668
+f 1141/698/669 983/644/624 986/646/626
+f 1139/697/668 983/644/624 1141/698/669
+f 1103/699/628 986/646/626 988/648/628
+f 1141/698/669 986/646/626 1103/699/628
+f 387/700/670 400/701/671 359/702/672
+f 375/703/673 359/702/672 271/704/674
+f 387/700/670 359/702/672 375/703/673
+f 368/705/675 271/704/674 213/706/676
+f 375/703/673 271/704/674 368/705/675
+f 364/707/677 213/706/676 206/708/678
+f 368/705/675 213/706/676 364/707/677
+f 366/709/679 206/708/678 238/710/679
+f 364/707/677 206/708/678 366/709/679
+f 359/702/672 400/701/671 346/711/680
+f 271/704/674 346/711/680 181/712/681
+f 359/702/672 346/711/680 271/704/674
+f 213/706/676 181/712/681 129/713/682
+f 271/704/674 181/712/681 213/706/676
+f 206/708/678 129/713/682 131/714/683
+f 213/706/676 129/713/682 206/708/678
+f 238/710/679 131/714/683 152/715/679
+f 206/708/678 131/714/683 238/710/679
+f 346/711/680 400/701/671 339/716/684
+f 181/712/681 339/716/684 159/717/685
+f 346/711/680 339/716/684 181/712/681
+f 129/713/682 159/717/685 112/718/686
+f 181/712/681 159/717/685 129/713/682
+f 131/714/683 112/718/686 115/719/687
+f 129/713/682 112/718/686 131/714/683
+f 152/715/679 115/719/687 132/720/679
+f 131/714/683 115/719/687 152/715/679
+f 339/716/684 400/701/671 345/721/688
+f 159/717/685 345/721/688 180/722/689
+f 339/716/684 345/721/688 159/717/685
+f 112/718/686 180/722/689 128/723/690
+f 159/717/685 180/722/689 112/718/686
+f 115/719/687 128/723/690 130/724/691
+f 112/718/686 128/723/690 115/719/687
+f 132/720/679 130/724/691 151/725/692
+f 115/719/687 130/724/691 132/720/679
+f 345/721/688 400/701/671 358/726/693
+f 180/722/689 358/726/693 270/727/694
+f 345/721/688 358/726/693 180/722/689
+f 128/723/690 270/727/694 212/728/695
+f 180/722/689 270/727/694 128/723/690
+f 130/724/691 212/728/695 205/729/696
+f 128/723/690 212/728/695 130/724/691
+f 151/725/692 205/729/696 237/730/679
+f 130/724/691 205/729/696 151/725/692
+f 358/726/693 400/701/671 386/731/697
+f 270/727/694 386/731/697 374/732/698
+f 358/726/693 386/731/697 270/727/694
+f 212/728/695 374/732/698 367/733/699
+f 270/727/694 374/732/698 212/728/695
+f 205/729/696 367/733/699 363/734/700
+f 212/728/695 367/733/699 205/729/696
+f 237/730/679 363/734/700 365/735/679
+f 205/729/696 363/734/700 237/730/679
+f 386/731/697 400/701/671 417/736/701
+f 374/732/698 417/736/701 455/737/702
+f 386/731/697 417/736/701 374/732/698
+f 367/733/699 455/737/702 475/738/703
+f 374/732/698 455/737/702 367/733/699
+f 363/734/700 475/738/703 469/739/704
+f 367/733/699 475/738/703 363/734/700
+f 365/735/679 469/739/704 452/740/705
+f 363/734/700 469/739/704 365/735/679
+f 417/736/701 400/701/671 432/741/706
+f 455/737/702 432/741/706 511/742/707
+f 417/736/701 432/741/706 455/737/702
+f 475/738/703 511/742/707 547/743/708
+f 455/737/702 511/742/707 475/738/703
+f 469/739/704 547/743/708 545/744/709
+f 475/738/703 547/743/708 469/739/704
+f 452/740/705 545/744/709 518/745/679
+f 469/739/704 545/744/709 452/740/705
+f 432/741/706 400/701/671 440/746/710
+f 511/742/707 440/746/710 532/747/711
+f 432/741/706 440/746/710 511/742/707
+f 547/743/708 532/747/711 563/748/712
+f 511/742/707 532/747/711 547/743/708
+f 545/744/709 563/748/712 560/749/713
+f 547/743/708 563/748/712 545/744/709
+f 518/745/679 560/749/713 535/750/679
+f 545/744/709 560/749/713 518/745/679
+f 440/746/710 400/701/671 433/751/714
+f 532/747/711 433/751/714 512/752/715
+f 440/746/710 433/751/714 532/747/711
+f 563/748/712 512/752/715 548/753/716
+f 532/747/711 512/752/715 563/748/712
+f 560/749/713 548/753/716 546/754/717
+f 563/748/712 548/753/716 560/749/713
+f 535/750/679 546/754/717 519/755/679
+f 560/749/713 546/754/717 535/750/679
+f 433/751/714 400/701/671 418/756/718
+f 512/752/715 418/756/718 456/757/719
+f 433/751/714 418/756/718 512/752/715
+f 548/753/716 456/757/719 476/758/720
+f 512/752/715 456/757/719 548/753/716
+f 546/754/717 476/758/720 470/759/721
+f 548/753/716 476/758/720 546/754/717
+f 519/755/679 470/759/721 453/760/679
+f 546/754/717 470/759/721 519/755/679
+f 418/756/718 400/701/671 387/700/670
+f 456/757/719 387/700/670 375/703/673
+f 418/756/718 387/700/670 456/757/719
+f 476/758/720 375/703/673 368/705/675
+f 456/757/719 375/703/673 476/758/720
+f 470/759/721 368/705/675 364/707/677
+f 476/758/720 368/705/675 470/759/721
+f 453/760/679 364/707/677 366/709/679
+f 470/759/721 364/707/677 453/760/679
+f 742/761/722 676/762/723 737/763/724
+f 851/764/725 737/763/724 839/765/726
+f 742/761/722 737/763/724 851/764/725
+f 902/766/727 839/765/726 886/767/728
+f 851/764/725 839/765/726 902/766/727
+f 901/768/729 886/767/728 884/769/730
+f 902/766/727 886/767/728 901/768/729
+f 864/770/731 884/769/730 858/771/732
+f 901/768/729 884/769/730 864/770/731
+f 737/763/724 676/762/723 729/772/733
+f 839/765/726 729/772/733 810/773/734
+f 737/763/724 729/772/733 839/765/726
+f 886/767/728 810/773/734 844/774/735
+f 839/765/726 810/773/734 886/767/728
+f 884/769/730 844/774/735 842/775/736
+f 886/767/728 844/774/735 884/769/730
+f 858/771/732 842/775/736 818/776/731
+f 884/769/730 842/775/736 858/771/732
+f 729/772/733 676/762/723 711/777/737
+f 810/773/734 711/777/737 762/778/738
+f 729/772/733 711/777/737 810/773/734
+f 844/774/735 762/778/738 787/779/739
+f 810/773/734 762/778/738 844/774/735
+f 842/775/736 787/779/739 785/780/740
+f 844/774/735 787/779/739 842/775/736
+f 818/776/731 785/780/740 771/781/731
+f 842/775/736 785/780/740 818/776/731
+f 711/777/737 676/762/723 679/782/741
+f 762/778/738 679/782/741 683/783/742
+f 711/777/737 679/782/741 762/778/738
+f 787/779/739 683/783/742 687/784/743
+f 762/778/738 683/783/742 787/779/739
+f 785/780/740 687/784/743 689/785/744
+f 787/779/739 687/784/743 785/780/740
+f 771/781/731 689/785/744 688/786/731
+f 785/780/740 689/785/744 771/781/731
+f 679/782/741 676/762/723 640/787/745
+f 683/783/742 640/787/745 590/788/746
+f 679/782/741 640/787/745 683/783/742
+f 687/784/743 590/788/746 569/789/747
+f 683/783/742 590/788/746 687/784/743
+f 689/785/744 569/789/747 571/790/748
+f 687/784/743 569/789/747 689/785/744
+f 688/786/731 571/790/748 582/791/749
+f 689/785/744 571/790/748 688/786/731
+f 640/787/745 676/762/723 635/792/750
+f 590/788/746 635/792/750 550/793/751
+f 640/787/745 635/792/750 590/788/746
+f 569/789/747 550/793/751 514/794/752
+f 590/788/746 550/793/751 569/789/747
+f 571/790/748 514/794/752 516/795/753
+f 569/789/747 514/794/752 571/790/748
+f 582/791/749 516/795/753 539/796/731
+f 571/790/748 516/795/753 582/791/749
+f 635/792/750 676/762/723 614/797/754
+f 550/793/751 614/797/754 521/798/755
+f 635/792/750 614/797/754 550/793/751
+f 514/794/752 521/798/755 468/799/756
+f 550/793/751 521/798/755 514/794/752
+f 516/795/753 468/799/756 472/800/757
+f 514/794/752 468/799/756 516/795/753
+f 539/796/731 472/800/757 497/801/731
+f 516/795/753 472/800/757 539/796/731
+f 614/797/754 676/762/723 608/802/758
+f 521/798/755 608/802/758 503/803/759
+f 614/797/754 608/802/758 521/798/755
+f 468/799/756 503/803/759 451/804/760
+f 521/798/755 503/803/759 468/799/756
+f 472/800/757 451/804/760 454/805/761
+f 468/799/756 451/804/760 472/800/757
+f 497/801/731 454/805/761 488/806/731
+f 472/800/757 454/805/761 497/801/731
+f 608/802/758 676/762/723 613/807/762
+f 503/803/759 613/807/762 520/808/763
+f 608/802/758 613/807/762 503/803/759
+f 451/804/760 520/808/763 467/809/764
+f 503/803/759 520/808/763 451/804/760
+f 454/805/761 467/809/764 471/810/765
+f 451/804/760 467/809/764 454/805/761
+f 488/806/731 471/810/765 496/811/731
+f 454/805/761 471/810/765 488/806/731
+f 613/807/762 676/762/723 634/812/766
+f 520/808/763 634/812/766 549/813/767
+f 613/807/762 634/812/766 520/808/763
+f 467/809/764 549/813/767 513/814/768
+f 520/808/763 549/813/767 467/809/764
+f 471/810/765 513/814/768 515/815/769
+f 467/809/764 513/814/768 471/810/765
+f 496/811/731 515/815/769 538/816/770
+f 471/810/765 515/815/769 496/811/731
+f 634/812/766 676/762/723 639/817/771
+f 549/813/767 639/817/771 589/818/772
+f 634/812/766 639/817/771 549/813/767
+f 513/814/768 589/818/772 568/819/773
+f 549/813/767 589/818/772 513/814/768
+f 515/815/769 568/819/773 570/820/774
+f 513/814/768 568/819/773 515/815/769
+f 538/816/770 570/820/774 581/821/749
+f 515/815/769 570/820/774 538/816/770
+f 639/817/771 676/762/723 675/822/775
+f 589/818/772 675/822/775 682/823/776
+f 639/817/771 675/822/775 589/818/772
+f 568/819/773 682/823/776 684/824/777
+f 589/818/772 682/823/776 568/819/773
+f 570/820/774 684/824/777 685/825/778
+f 568/819/773 684/824/777 570/820/774
+f 581/821/749 685/825/778 686/826/749
+f 570/820/774 685/825/778 581/821/749
+f 675/822/775 676/762/723 710/827/779
+f 682/823/776 710/827/779 761/828/780
+f 675/822/775 710/827/779 682/823/776
+f 684/824/777 761/828/780 786/829/781
+f 682/823/776 761/828/780 684/824/777
+f 685/825/778 786/829/781 784/830/782
+f 684/824/777 786/829/781 685/825/778
+f 686/826/749 784/830/782 770/831/749
+f 685/825/778 784/830/782 686/826/749
+f 710/827/779 676/762/723 728/832/783
+f 761/828/780 728/832/783 809/833/784
+f 710/827/779 728/832/783 761/828/780
+f 786/829/781 809/833/784 843/834/785
+f 761/828/780 809/833/784 786/829/781
+f 784/830/782 843/834/785 841/835/786
+f 786/829/781 843/834/785 784/830/782
+f 770/831/749 841/835/786 817/836/749
+f 784/830/782 841/835/786 770/831/749
+f 728/832/783 676/762/723 736/837/787
+f 809/833/784 736/837/787 838/838/788
+f 728/832/783 736/837/787 809/833/784
+f 843/834/785 838/838/788 885/839/789
+f 809/833/784 838/838/788 843/834/785
+f 841/835/786 885/839/789 883/840/790
+f 843/834/785 885/839/789 841/835/786
+f 817/836/749 883/840/790 857/841/731
+f 841/835/786 883/840/790 817/836/749
+f 736/837/787 676/762/723 742/761/722
+f 838/838/788 742/761/722 851/764/725
+f 736/837/787 742/761/722 838/838/788
+f 885/839/789 851/764/725 902/766/727
+f 838/838/788 851/764/725 885/839/789
+f 883/840/790 902/766/727 901/768/729
+f 885/839/789 902/766/727 883/840/790
+f 857/841/731 901/768/729 864/770/731
+f 883/840/790 901/768/729 857/841/731
+f 750/842/791 680/843/792 740/844/793
+f 802/845/794 740/844/793 792/846/795
+f 750/842/791 740/844/793 802/845/794
+f 845/847/796 792/846/795 826/848/797
+f 802/845/794 792/846/795 845/847/796
+f 873/849/798 826/848/797 855/850/799
+f 845/847/796 826/848/797 873/849/798
+f 905/851/800 855/850/799 879/852/801
+f 873/849/798 855/850/799 905/851/800
+f 918/853/802 879/852/801 899/854/803
+f 905/851/800 879/852/801 918/853/802
+f 921/855/804 899/854/803 903/856/805
+f 918/853/802 899/854/803 921/855/804
+f 740/844/793 680/843/792 723/857/806
+f 792/846/795 723/857/806 747/858/807
+f 740/844/793 723/857/806 792/846/795
+f 826/848/797 747/858/807 781/859/808
+f 792/846/795 747/858/807 826/848/797
+f 855/850/799 781/859/808 796/860/809
+f 826/848/797 781/859/808 855/850/799
+f 879/852/801 796/860/809 805/861/810
+f 855/850/799 796/860/809 879/852/801
+f 899/854/803 805/861/810 812/862/811
+f 879/852/801 805/861/810 899/854/803
+f 903/856/805 812/862/811 820/863/812
+f 899/854/803 812/862/811 903/856/805
+f 723/857/806 680/843/792 677/864/813
+f 747/858/807 677/864/813 674/865/814
+f 723/857/806 677/864/813 747/858/807
+f 781/859/808 674/865/814 670/866/815
+f 747/858/807 674/865/814 781/859/808
+f 796/860/809 670/866/815 667/867/816
+f 781/859/808 670/866/815 796/860/809
+f 805/861/810 667/867/816 666/868/817
+f 796/860/809 667/867/816 805/861/810
+f 812/862/811 666/868/817 664/869/818
+f 805/861/810 666/868/817 812/862/811
+f 820/863/812 664/869/818 663/870/819
+f 812/862/811 664/869/818 820/863/812
+f 677/864/813 680/843/792 636/871/820
+f 674/865/814 636/871/820 602/872/821
+f 677/864/813 636/871/820 674/865/814
+f 670/866/815 602/872/821 572/873/822
+f 674/865/814 602/872/821 670/866/815
+f 667/867/816 572/873/822 556/874/823
+f 670/866/815 572/873/822 667/867/816
+f 666/868/817 556/874/823 551/875/824
+f 667/867/816 556/874/823 666/868/817
+f 664/869/818 551/875/824 543/876/825
+f 666/868/817 551/875/824 664/869/818
+f 663/870/819 543/876/825 536/877/826
+f 664/869/818 543/876/825 663/870/819
+f 636/871/820 680/843/792 609/878/827
+f 602/872/821 609/878/827 561/879/828
+f 636/871/820 609/878/827 602/872/821
+f 572/873/822 561/879/828 527/880/829
+f 602/872/821 561/879/828 572/873/822
+f 556/874/823 527/880/829 498/881/830
+f 572/873/822 527/880/829 556/874/823
+f 551/875/824 498/881/830 478/882/831
+f 556/874/823 498/881/830 551/875/824
+f 543/876/825 478/882/831 457/883/832
+f 551/875/824 478/882/831 543/876/825
+f 536/877/826 457/883/832 449/884/833
+f 543/876/825 457/883/832 536/877/826
+f 609/878/827 680/843/792 600/885/834
+f 561/879/828 600/885/834 553/886/835
+f 609/878/827 600/885/834 561/879/828
+f 527/880/829 553/886/835 509/887/836
+f 561/879/828 553/886/835 527/880/829
+f 498/881/830 509/887/836 482/888/837
+f 527/880/829 509/887/836 498/881/830
+f 478/882/831 482/888/837 447/889/838
+f 498/881/830 482/888/837 478/882/831
+f 457/883/832 447/889/838 434/890/839
+f 478/882/831 447/889/838 457/883/832
+f 449/884/833 434/890/839 430/891/840
+f 457/883/832 434/890/839 449/884/833
+f 600/885/834 680/843/792 610/892/841
+f 553/886/835 610/892/841 562/893/842
+f 600/885/834 610/892/841 553/886/835
+f 509/887/836 562/893/842 528/894/843
+f 553/886/835 562/893/842 509/887/836
+f 482/888/837 528/894/843 499/895/844
+f 509/887/836 528/894/843 482/888/837
+f 447/889/838 499/895/844 479/896/845
+f 482/888/837 499/895/844 447/889/838
+f 434/890/839 479/896/845 458/897/846
+f 447/889/838 479/896/845 434/890/839
+f 430/891/840 458/897/846 450/898/847
+f 434/890/839 458/897/846 430/891/840
+f 610/892/841 680/843/792 637/899/848
+f 562/893/842 637/899/848 603/900/849
+f 610/892/841 637/899/848 562/893/842
+f 528/894/843 603/900/849 573/901/850
+f 562/893/842 603/900/849 528/894/843
+f 499/895/844 573/901/850 557/902/851
+f 528/894/843 573/901/850 499/895/844
+f 479/896/845 557/902/851 552/903/852
+f 499/895/844 557/902/851 479/896/845
+f 458/897/846 552/903/852 544/904/853
+f 479/896/845 552/903/852 458/897/846
+f 450/898/847 544/904/853 537/905/854
+f 458/897/846 544/904/853 450/898/847
+f 637/899/848 680/843/792 681/906/855
+f 603/900/849 681/906/855 678/907/856
+f 637/899/848 681/906/855 603/900/849
+f 573/901/850 678/907/856 672/908/857
+f 603/900/849 678/907/856 573/901/850
+f 557/902/851 672/908/857 673/909/858
+f 573/901/850 672/908/857 557/902/851
+f 552/903/852 673/909/858 671/910/859
+f 557/902/851 673/909/858 552/903/852
+f 544/904/853 671/910/859 669/911/860
+f 552/903/852 671/910/859 544/904/853
+f 537/905/854 669/911/860 665/912/861
+f 544/904/853 669/911/860 537/905/854
+f 681/906/855 680/843/792 724/913/862
+f 678/907/856 724/913/862 748/914/863
+f 681/906/855 724/913/862 678/907/856
+f 672/908/857 748/914/863 782/915/864
+f 678/907/856 748/914/863 672/908/857
+f 673/909/858 782/915/864 797/916/865
+f 672/908/857 782/915/864 673/909/858
+f 671/910/859 797/916/865 806/917/866
+f 673/909/858 797/916/865 671/910/859
+f 669/911/860 806/917/866 813/918/867
+f 671/910/859 806/917/866 669/911/860
+f 665/912/861 813/918/867 821/919/868
+f 669/911/860 813/918/867 665/912/861
+f 724/913/862 680/843/792 741/920/869
+f 748/914/863 741/920/869 793/921/870
+f 724/913/862 741/920/869 748/914/863
+f 782/915/864 793/921/870 827/922/871
+f 748/914/863 793/921/870 782/915/864
+f 797/916/865 827/922/871 856/923/872
+f 782/915/864 827/922/871 797/916/865
+f 806/917/866 856/923/872 880/924/873
+f 797/916/865 856/923/872 806/917/866
+f 813/918/867 880/924/873 900/925/874
+f 806/917/866 880/924/873 813/918/867
+f 821/919/868 900/925/874 904/926/875
+f 813/918/867 900/925/874 821/919/868
+f 741/920/869 680/843/792 750/842/791
+f 793/921/870 750/842/791 802/845/794
+f 741/920/869 750/842/791 793/921/870
+f 827/922/871 802/845/794 845/847/796
+f 793/921/870 802/845/794 827/922/871
+f 856/923/872 845/847/796 873/849/798
+f 827/922/871 845/847/796 856/923/872
+f 880/924/873 873/849/798 905/851/800
+f 856/923/872 873/849/798 880/924/873
+f 900/925/874 905/851/800 918/853/802
+f 880/924/873 905/851/800 900/925/874
+f 904/926/875 918/853/802 921/855/804
+f 900/925/874 918/853/802 904/926/875
+f 444/927/876 403/928/877 438/929/878
+f 484/930/879 438/929/878 473/931/880
+f 444/927/876 438/929/878 484/930/879
+f 534/932/881 473/931/880 522/933/882
+f 484/930/879 473/931/880 534/932/881
+f 555/934/883 522/933/882 541/935/884
+f 534/932/881 522/933/882 555/934/883
+f 588/936/885 541/935/884 558/937/886
+f 555/934/883 541/935/884 588/936/885
+f 628/938/887 558/937/886 574/939/888
+f 588/936/885 558/937/886 628/938/887
+f 631/940/889 574/939/888 583/941/890
+f 628/938/887 574/939/888 631/940/889
+f 438/929/878 403/928/877 423/942/891
+f 473/931/880 423/942/891 443/943/892
+f 438/929/878 423/942/891 473/931/880
+f 522/933/882 443/943/892 461/944/893
+f 473/931/880 443/943/892 522/933/882
+f 541/935/884 461/944/893 480/945/894
+f 522/933/882 461/944/893 541/935/884
+f 558/937/886 480/945/894 490/946/895
+f 541/935/884 480/945/894 558/937/886
+f 574/939/888 490/946/895 494/947/896
+f 558/937/886 490/946/895 574/939/888
+f 583/941/890 494/947/896 505/948/897
+f 574/939/888 494/947/896 583/941/890
+f 423/942/891 403/928/877 401/949/898
+f 443/943/892 401/949/898 397/950/899
+f 423/942/891 401/949/898 443/943/892
+f 461/944/893 397/950/899 394/951/900
+f 443/943/892 397/950/899 461/944/893
+f 480/945/894 394/951/900 393/952/901
+f 461/944/893 394/951/900 480/945/894
+f 490/946/895 393/952/901 392/953/902
+f 480/945/894 393/952/901 490/946/895
+f 494/947/896 392/953/902 390/954/903
+f 490/946/895 392/953/902 494/947/896
+f 505/948/897 390/954/903 389/955/904
+f 494/947/896 390/954/903 505/948/897
+f 401/949/898 403/928/877 356/956/905
+f 397/950/899 356/956/905 322/957/906
+f 401/949/898 356/956/905 397/950/899
+f 394/951/900 322/957/906 290/958/907
+f 397/950/899 322/957/906 394/951/900
+f 393/952/901 290/958/907 259/959/908
+f 394/951/900 290/958/907 393/952/901
+f 392/953/902 259/959/908 229/960/909
+f 393/952/901 259/959/908 392/953/902
+f 390/954/903 229/960/909 214/961/910
+f 392/953/902 229/960/909 390/954/903
+f 389/955/904 214/961/910 210/962/911
+f 390/954/903 214/961/910 389/955/904
+f 356/956/905 403/928/877 343/963/912
+f 322/957/906 343/963/912 266/964/913
+f 356/956/905 343/963/912 322/957/906
+f 290/958/907 266/964/913 194/965/914
+f 322/957/906 266/964/913 290/958/907
+f 259/959/908 194/965/914 161/966/915
+f 290/958/907 194/965/914 259/959/908
+f 229/960/909 161/966/915 134/967/916
+f 259/959/908 161/966/915 229/960/909
+f 214/961/910 134/967/916 126/968/917
+f 229/960/909 134/967/916 214/961/910
+f 210/962/911 126/968/917 121/969/918
+f 214/961/910 126/968/917 210/962/911
+f 343/963/912 403/928/877 318/970/919
+f 266/964/913 318/970/919 250/971/920
+f 343/963/912 318/970/919 266/964/913
+f 194/965/914 250/971/920 173/972/921
+f 266/964/913 250/971/920 194/965/914
+f 161/966/915 173/972/921 140/973/922
+f 194/965/914 173/972/921 161/966/915
+f 134/967/916 140/973/922 120/974/923
+f 161/966/915 140/973/922 134/967/916
+f 126/968/917 120/974/923 107/975/924
+f 134/967/916 120/974/923 126/968/917
+f 121/969/918 107/975/924 103/976/925
+f 126/968/917 107/975/924 121/969/918
+f 318/970/919 403/928/877 344/977/926
+f 250/971/920 344/977/926 267/978/927
+f 318/970/919 344/977/926 250/971/920
+f 173/972/921 267/978/927 195/979/928
+f 250/971/920 267/978/927 173/972/921
+f 140/973/922 195/979/928 162/980/929
+f 173/972/921 195/979/928 140/973/922
+f 120/974/923 162/980/929 135/981/930
+f 140/973/922 162/980/929 120/974/923
+f 107/975/924 135/981/930 127/982/931
+f 120/974/923 135/981/930 107/975/924
+f 103/976/925 127/982/931 122/983/932
+f 107/975/924 127/982/931 103/976/925
+f 344/977/926 403/928/877 357/984/933
+f 267/978/927 357/984/933 323/985/934
+f 344/977/926 357/984/933 267/978/927
+f 195/979/928 323/985/934 291/986/935
+f 267/978/927 323/985/934 195/979/928
+f 162/980/929 291/986/935 260/987/936
+f 195/979/928 291/986/935 162/980/929
+f 135/981/930 260/987/936 230/988/937
+f 162/980/929 260/987/936 135/981/930
+f 127/982/931 230/988/937 215/989/938
+f 135/981/930 230/988/937 127/982/931
+f 122/983/932 215/989/938 211/990/939
+f 127/982/931 215/989/938 122/983/932
+f 357/984/933 403/928/877 404/991/940
+f 323/985/934 404/991/940 402/992/941
+f 357/984/933 404/991/940 323/985/934
+f 291/986/935 402/992/941 398/993/942
+f 323/985/934 402/992/941 291/986/935
+f 260/987/936 398/993/942 399/994/943
+f 291/986/935 398/993/942 260/987/936
+f 230/988/937 399/994/943 396/995/944
+f 260/987/936 399/994/943 230/988/937
+f 215/989/938 396/995/944 395/996/945
+f 230/988/937 396/995/944 215/989/938
+f 211/990/939 395/996/945 391/997/946
+f 215/989/938 395/996/945 211/990/939
+f 404/991/940 403/928/877 424/998/947
+f 402/992/941 424/998/947 442/999/948
+f 404/991/940 424/998/947 402/992/941
+f 398/993/942 442/999/948 462/1000/949
+f 402/992/941 442/999/948 398/993/942
+f 399/994/943 462/1000/949 481/1001/950
+f 398/993/942 462/1000/949 399/994/943
+f 396/995/944 481/1001/950 491/1002/951
+f 399/994/943 481/1001/950 396/995/944
+f 395/996/945 491/1002/951 495/1003/952
+f 396/995/944 491/1002/951 395/996/945
+f 391/997/946 495/1003/952 506/1004/953
+f 395/996/945 495/1003/952 391/997/946
+f 424/998/947 403/928/877 439/1005/954
+f 442/999/948 439/1005/954 474/1006/955
+f 424/998/947 439/1005/954 442/999/948
+f 462/1000/949 474/1006/955 523/1007/956
+f 442/999/948 474/1006/955 462/1000/949
+f 481/1001/950 523/1007/956 542/1008/957
+f 462/1000/949 523/1007/956 481/1001/950
+f 491/1002/951 542/1008/957 559/1009/958
+f 481/1001/950 542/1008/957 491/1002/951
+f 495/1003/952 559/1009/958 575/1010/959
+f 491/1002/951 559/1009/958 495/1003/952
+f 506/1004/953 575/1010/959 584/1011/960
+f 495/1003/952 575/1010/959 506/1004/953
+f 439/1005/954 403/928/877 444/927/876
+f 474/1006/955 444/927/876 484/930/879
+f 439/1005/954 444/927/876 474/1006/955
+f 523/1007/956 484/930/879 534/932/881
+f 474/1006/955 484/930/879 523/1007/956
+f 542/1008/957 534/932/881 555/934/883
+f 523/1007/956 534/932/881 542/1008/957
+f 559/1009/958 555/934/883 588/936/885
+f 542/1008/957 555/934/883 559/1009/958
+f 575/1010/959 588/936/885 628/938/887
+f 559/1009/958 588/936/885 575/1010/959
+f 584/1011/960 628/938/887 631/940/889
+f 575/1010/959 628/938/887 584/1011/960
+f 1035/1012/961 966/1013/877 1008/1014/878
+f 1104/1015/879 1008/1014/878 1086/1016/880
+f 1035/1012/961 1008/1014/878 1104/1015/879
+f 1179/1017/962 1086/1016/880 1156/1018/882
+f 1104/1015/879 1086/1016/880 1179/1017/962
+f 1210/1019/883 1156/1018/882 1188/1020/884
+f 1179/1017/962 1156/1018/882 1210/1019/883
+f 1232/1021/885 1188/1020/884 1215/1022/886
+f 1210/1019/883 1188/1020/884 1232/1021/885
+f 1247/1023/887 1215/1022/886 1225/1024/888
+f 1232/1021/885 1215/1022/886 1247/1023/887
+f 1251/1025/889 1225/1024/888 1229/1026/963
+f 1247/1023/887 1225/1024/888 1251/1025/889
+f 1008/1014/878 966/1013/877 997/1027/964
+f 1086/1016/880 997/1027/964 1029/1028/892
+f 1008/1014/878 997/1027/964 1086/1016/880
+f 1156/1018/882 1029/1028/892 1064/1029/965
+f 1086/1016/880 1029/1028/892 1156/1018/882
+f 1188/1020/884 1064/1029/965 1092/1030/894
+f 1156/1018/882 1064/1029/965 1188/1020/884
+f 1215/1022/886 1092/1030/894 1122/1031/895
+f 1188/1020/884 1092/1030/894 1215/1022/886
+f 1225/1024/888 1122/1031/895 1142/1032/896
+f 1215/1022/886 1122/1031/895 1225/1024/888
+f 1229/1026/963 1142/1032/896 1144/1033/897
+f 1225/1024/888 1142/1032/896 1229/1026/963
+f 997/1027/964 966/1013/877 964/1034/966
+f 1029/1028/892 964/1034/966 960/1035/899
+f 997/1027/964 964/1034/966 1029/1028/892
+f 1064/1029/965 960/1035/899 957/1036/900
+f 1029/1028/892 960/1035/899 1064/1029/965
+f 1092/1030/894 957/1036/900 956/1037/901
+f 1064/1029/965 957/1036/900 1092/1030/894
+f 1122/1031/895 956/1037/901 955/1038/902
+f 1092/1030/894 956/1037/901 1122/1031/895
+f 1142/1032/896 955/1038/902 953/1039/903
+f 1122/1031/895 955/1038/902 1142/1032/896
+f 1144/1033/897 953/1039/903 952/1040/967
+f 1142/1032/896 953/1039/903 1144/1033/897
+f 964/1034/966 966/1013/877 926/1041/905
+f 960/1035/899 926/1041/905 910/1042/906
+f 964/1034/966 926/1041/905 960/1035/899
+f 957/1036/900 910/1042/906 890/1043/968
+f 960/1035/899 910/1042/906 957/1036/900
+f 956/1037/901 890/1043/968 877/1044/908
+f 957/1036/900 890/1043/968 956/1037/901
+f 955/1038/902 877/1044/908 861/1045/909
+f 956/1037/901 877/1044/908 955/1038/902
+f 953/1039/903 861/1045/909 859/1046/910
+f 955/1038/902 861/1045/909 953/1039/903
+f 952/1040/967 859/1046/910 848/1047/911
+f 953/1039/903 859/1046/910 952/1040/967
+f 926/1041/905 966/1013/877 913/1048/912
+f 910/1042/906 913/1048/912 881/1049/969
+f 926/1041/905 913/1048/912 910/1042/906
+f 890/1043/968 881/1049/969 836/1050/970
+f 910/1042/906 881/1049/969 890/1043/968
+f 877/1044/908 836/1050/970 814/1051/915
+f 890/1043/968 836/1050/970 877/1044/908
+f 861/1045/909 814/1051/915 794/1052/916
+f 877/1044/908 814/1051/915 861/1045/909
+f 859/1046/910 794/1052/916 779/1053/971
+f 861/1045/909 794/1052/916 859/1046/910
+f 848/1047/911 779/1053/971 768/1054/918
+f 859/1046/910 779/1053/971 848/1047/911
+f 913/1048/912 966/1013/877 909/1055/919
+f 881/1049/969 909/1055/919 869/1056/920
+f 913/1048/912 909/1055/919 881/1049/969
+f 836/1050/970 869/1056/920 822/1057/921
+f 881/1049/969 869/1056/920 836/1050/970
+f 814/1051/915 822/1057/921 798/1058/922
+f 836/1050/970 822/1057/921 814/1051/915
+f 794/1052/916 798/1058/922 763/1059/972
+f 814/1051/915 798/1058/922 794/1052/916
+f 779/1053/971 763/1059/972 735/1060/924
+f 794/1052/916 763/1059/972 779/1053/971
+f 768/1054/918 735/1060/924 732/1061/925
+f 779/1053/971 735/1060/924 768/1054/918
+f 909/1055/919 966/1013/877 914/1062/973
+f 869/1056/920 914/1062/973 882/1063/927
+f 909/1055/919 914/1062/973 869/1056/920
+f 822/1057/921 882/1063/927 837/1064/928
+f 869/1056/920 882/1063/927 822/1057/921
+f 798/1058/922 837/1064/928 815/1065/929
+f 822/1057/921 837/1064/928 798/1058/922
+f 763/1059/972 815/1065/929 795/1066/930
+f 798/1058/922 815/1065/929 763/1059/972
+f 735/1060/924 795/1066/930 780/1067/931
+f 763/1059/972 795/1066/930 735/1060/924
+f 732/1061/925 780/1067/931 769/1068/932
+f 735/1060/924 780/1067/931 732/1061/925
+f 914/1062/973 966/1013/877 927/1069/933
+f 882/1063/927 927/1069/933 911/1070/934
+f 914/1062/973 927/1069/933 882/1063/927
+f 837/1064/928 911/1070/934 891/1071/974
+f 882/1063/927 911/1070/934 837/1064/928
+f 815/1065/929 891/1071/974 878/1072/936
+f 837/1064/928 891/1071/974 815/1065/929
+f 795/1066/930 878/1072/936 862/1073/937
+f 815/1065/929 878/1072/936 795/1066/930
+f 780/1067/931 862/1073/937 860/1074/975
+f 795/1066/930 862/1073/937 780/1067/931
+f 769/1068/932 860/1074/975 849/1075/939
+f 780/1067/931 860/1074/975 769/1068/932
+f 927/1069/933 966/1013/877 967/1076/976
+f 911/1070/934 967/1076/976 965/1077/977
+f 927/1069/933 967/1076/976 911/1070/934
+f 891/1071/974 965/1077/977 961/1078/942
+f 911/1070/934 965/1077/977 891/1071/974
+f 878/1072/936 961/1078/942 962/1079/943
+f 891/1071/974 961/1078/942 878/1072/936
+f 862/1073/937 962/1079/943 959/1080/944
+f 878/1072/936 962/1079/943 862/1073/937
+f 860/1074/975 959/1080/944 958/1081/945
+f 862/1073/937 959/1080/944 860/1074/975
+f 849/1075/939 958/1081/945 954/1082/946
+f 860/1074/975 958/1081/945 849/1075/939
+f 967/1076/976 966/1013/877 998/1083/978
+f 965/1077/977 998/1083/978 1028/1084/948
+f 967/1076/976 998/1083/978 965/1077/977
+f 961/1078/942 1028/1084/948 1065/1085/949
+f 965/1077/977 1028/1084/948 961/1078/942
+f 962/1079/943 1065/1085/949 1093/1086/979
+f 961/1078/942 1065/1085/949 962/1079/943
+f 959/1080/944 1093/1086/979 1123/1087/951
+f 962/1079/943 1093/1086/979 959/1080/944
+f 958/1081/945 1123/1087/951 1143/1088/952
+f 959/1080/944 1123/1087/951 958/1081/945
+f 954/1082/946 1143/1088/952 1145/1089/953
+f 958/1081/945 1143/1088/952 954/1082/946
+f 998/1083/978 966/1013/877 1009/1090/954
+f 1028/1084/948 1009/1090/954 1087/1091/955
+f 998/1083/978 1009/1090/954 1028/1084/948
+f 1065/1085/949 1087/1091/955 1157/1092/980
+f 1028/1084/948 1087/1091/955 1065/1085/949
+f 1093/1086/979 1157/1092/980 1189/1093/957
+f 1065/1085/949 1157/1092/980 1093/1086/979
+f 1123/1087/951 1189/1093/957 1216/1094/958
+f 1093/1086/979 1189/1093/957 1123/1087/951
+f 1143/1088/952 1216/1094/958 1224/1095/959
+f 1123/1087/951 1216/1094/958 1143/1088/952
+f 1145/1089/953 1224/1095/959 1230/1096/981
+f 1143/1088/952 1224/1095/959 1145/1089/953
+f 1009/1090/954 966/1013/877 1035/1012/961
+f 1087/1091/955 1035/1012/961 1104/1015/879
+f 1009/1090/954 1035/1012/961 1087/1091/955
+f 1157/1092/980 1104/1015/879 1179/1017/962
+f 1087/1091/955 1104/1015/879 1157/1092/980
+f 1189/1093/957 1179/1017/962 1210/1019/883
+f 1157/1092/980 1179/1017/962 1189/1093/957
+f 1216/1094/958 1210/1019/883 1232/1021/885
+f 1189/1093/957 1210/1019/883 1216/1094/958
+f 1224/1095/959 1232/1021/885 1247/1023/887
+f 1216/1094/958 1232/1021/885 1224/1095/959
+f 1230/1096/981 1247/1023/887 1251/1025/889
+f 1224/1095/959 1247/1023/887 1230/1096/981
+f 1096/1097/982 1079/1098/983 1090/1099/984
+f 1136/1100/985 1090/1099/984 1100/1101/986
+f 1096/1097/982 1090/1099/984 1136/1100/985
+f 1152/1102/987 1100/1101/986 1119/1103/988
+f 1136/1100/985 1100/1101/986 1152/1102/987
+f 1168/1104/989 1119/1103/988 1134/1105/990
+f 1152/1102/987 1119/1103/988 1168/1104/989
+f 1090/1099/984 1079/1098/983 1078/1106/991
+f 1100/1101/986 1078/1106/991 1076/1107/992
+f 1090/1099/984 1078/1106/991 1100/1101/986
+f 1119/1103/988 1076/1107/992 1073/1108/993
+f 1100/1101/986 1076/1107/992 1119/1103/988
+f 1134/1105/990 1073/1108/993 1071/1109/994
+f 1119/1103/988 1073/1108/993 1134/1105/990
+f 1078/1106/991 1079/1098/983 1059/1110/995
+f 1076/1107/992 1059/1110/995 1050/1111/996
+f 1078/1106/991 1059/1110/995 1076/1107/992
+f 1073/1108/993 1050/1111/996 1037/1112/997
+f 1076/1107/992 1050/1111/996 1073/1108/993
+f 1071/1109/994 1037/1112/997 1026/1113/998
+f 1073/1108/993 1037/1112/997 1071/1109/994
+f 1059/1110/995 1079/1098/983 1054/1114/999
+f 1050/1111/996 1054/1114/999 1022/1115/1000
+f 1059/1110/995 1054/1114/999 1050/1111/996
+f 1037/1112/997 1022/1115/1000 1000/1116/1001
+f 1050/1111/996 1022/1115/1000 1037/1112/997
+f 1026/1113/998 1000/1116/1001 994/1117/1002
+f 1037/1112/997 1000/1116/1001 1026/1113/998
+f 1054/1114/999 1079/1098/983 1048/1118/1003
+f 1022/1115/1000 1048/1118/1003 1004/1119/1004
+f 1054/1114/999 1048/1118/1003 1022/1115/1000
+f 1000/1116/1001 1004/1119/1004 990/1120/1005
+f 1022/1115/1000 1004/1119/1004 1000/1116/1001
+f 994/1117/1002 990/1120/1005 971/1121/1006
+f 1000/1116/1001 990/1120/1005 994/1117/1002
+f 1048/1118/1003 1079/1098/983 1042/1122/1007
+f 1004/1119/1004 1042/1122/1007 1001/1123/1008
+f 1048/1118/1003 1042/1122/1007 1004/1119/1004
+f 990/1120/1005 1001/1123/1008 981/1124/1009
+f 1004/1119/1004 1001/1123/1008 990/1120/1005
+f 971/1121/1006 981/1124/1009 948/1125/1010
+f 990/1120/1005 981/1124/1009 971/1121/1006
+f 1042/1122/1007 1079/1098/983 1047/1126/1011
+f 1001/1123/1008 1047/1126/1011 1003/1127/1012
+f 1042/1122/1007 1047/1126/1011 1001/1123/1008
+f 981/1124/1009 1003/1127/1012 989/1128/1013
+f 1001/1123/1008 1003/1127/1012 981/1124/1009
+f 948/1125/1010 989/1128/1013 970/1129/1014
+f 981/1124/1009 989/1128/1013 948/1125/1010
+f 1047/1126/1011 1079/1098/983 1053/1130/1015
+f 1003/1127/1012 1053/1130/1015 1021/1131/1016
+f 1047/1126/1011 1053/1130/1015 1003/1127/1012
+f 989/1128/1013 1021/1131/1016 999/1132/1017
+f 1003/1127/1012 1021/1131/1016 989/1128/1013
+f 970/1129/1014 999/1132/1017 993/1133/1018
+f 989/1128/1013 999/1132/1017 970/1129/1014
+f 1053/1130/1015 1079/1098/983 1058/1134/1019
+f 1021/1131/1016 1058/1134/1019 1049/1135/1020
+f 1053/1130/1015 1058/1134/1019 1021/1131/1016
+f 999/1132/1017 1049/1135/1020 1036/1136/1021
+f 1021/1131/1016 1049/1135/1020 999/1132/1017
+f 993/1133/1018 1036/1136/1021 1025/1137/1022
+f 999/1132/1017 1036/1136/1021 993/1133/1018
+f 1058/1134/1019 1079/1098/983 1077/1138/1023
+f 1049/1135/1020 1077/1138/1023 1075/1139/1024
+f 1058/1134/1019 1077/1138/1023 1049/1135/1020
+f 1036/1136/1021 1075/1139/1024 1072/1140/1025
+f 1049/1135/1020 1075/1139/1024 1036/1136/1021
+f 1025/1137/1022 1072/1140/1025 1070/1141/1026
+f 1036/1136/1021 1072/1140/1025 1025/1137/1022
+f 1077/1138/1023 1079/1098/983 1089/1142/1027
+f 1075/1139/1024 1089/1142/1027 1099/1143/1028
+f 1077/1138/1023 1089/1142/1027 1075/1139/1024
+f 1072/1140/1025 1099/1143/1028 1118/1144/1029
+f 1075/1139/1024 1099/1143/1028 1072/1140/1025
+f 1070/1141/1026 1118/1144/1029 1133/1145/1030
+f 1072/1140/1025 1118/1144/1029 1070/1141/1026
+f 1089/1142/1027 1079/1098/983 1095/1146/1031
+f 1099/1143/1028 1095/1146/1031 1135/1147/1032
+f 1089/1142/1027 1095/1146/1031 1099/1143/1028
+f 1118/1144/1029 1135/1147/1032 1151/1148/1033
+f 1099/1143/1028 1135/1147/1032 1118/1144/1029
+f 1133/1145/1030 1151/1148/1033 1167/1149/1034
+f 1118/1144/1029 1151/1148/1033 1133/1145/1030
+f 1095/1146/1031 1079/1098/983 1105/1150/1035
+f 1135/1147/1032 1105/1150/1035 1147/1151/1036
+f 1095/1146/1031 1105/1150/1035 1135/1147/1032
+f 1151/1148/1033 1147/1151/1036 1171/1152/1037
+f 1135/1147/1032 1147/1151/1036 1151/1148/1033
+f 1167/1149/1034 1171/1152/1037 1185/1153/1038
+f 1151/1148/1033 1171/1152/1037 1167/1149/1034
+f 1105/1150/1035 1079/1098/983 1113/1154/1039
+f 1147/1151/1036 1113/1154/1039 1150/1155/1040
+f 1105/1150/1035 1113/1154/1039 1147/1151/1036
+f 1171/1152/1037 1150/1155/1040 1175/1156/1041
+f 1147/1151/1036 1150/1155/1040 1171/1152/1037
+f 1185/1153/1038 1175/1156/1041 1194/1157/1042
+f 1171/1152/1037 1175/1156/1041 1185/1153/1038
+f 1113/1154/1039 1079/1098/983 1106/1158/1043
+f 1150/1155/1040 1106/1158/1043 1148/1159/1044
+f 1113/1154/1039 1106/1158/1043 1150/1155/1040
+f 1175/1156/1041 1148/1159/1044 1172/1160/1045
+f 1150/1155/1040 1148/1159/1044 1175/1156/1041
+f 1194/1157/1042 1172/1160/1045 1186/1161/1046
+f 1175/1156/1041 1172/1160/1045 1194/1157/1042
+f 1106/1158/1043 1079/1098/983 1096/1097/982
+f 1148/1159/1044 1096/1097/982 1136/1100/985
+f 1106/1158/1043 1096/1097/982 1148/1159/1044
+f 1172/1160/1045 1136/1100/985 1152/1102/987
+f 1148/1159/1044 1136/1100/985 1172/1160/1045
+f 1186/1161/1046 1152/1102/987 1168/1104/989
+f 1172/1160/1045 1152/1102/987 1186/1161/1046
+f 301/1162/1047 284/1163/1048 296/1164/984
+f 331/1165/985 296/1164/984 305/1166/1049
+f 301/1162/1047 296/1164/984 331/1165/985
+f 355/1167/1050 305/1166/1049 315/1168/988
+f 331/1165/985 305/1166/1049 355/1167/1050
+f 361/1169/989 315/1168/988 327/1170/1051
+f 355/1167/1050 315/1168/988 361/1169/989
+f 296/1164/984 284/1163/1048 283/1171/991
+f 305/1166/1049 283/1171/991 281/1172/992
+f 296/1164/984 283/1171/991 305/1166/1049
+f 315/1168/988 281/1172/992 278/1173/993
+f 305/1166/1049 281/1172/992 315/1168/988
+f 327/1170/1051 278/1173/993 276/1174/994
+f 315/1168/988 278/1173/993 327/1170/1051
+f 283/1171/991 284/1163/1048 263/1175/995
+f 281/1172/992 263/1175/995 253/1176/1052
+f 283/1171/991 263/1175/995 281/1172/992
+f 278/1173/993 253/1176/1052 231/1177/1053
+f 281/1172/992 253/1176/1052 278/1173/993
+f 276/1174/994 231/1177/1053 220/1178/998
+f 278/1173/993 231/1177/1053 276/1174/994
+f 263/1175/995 284/1163/1048 257/1179/999
+f 253/1176/1052 257/1179/999 218/1180/1000
+f 263/1175/995 257/1179/999 253/1176/1052
+f 231/1177/1053 218/1180/1000 199/1181/1054
+f 253/1176/1052 218/1180/1000 231/1177/1053
+f 220/1178/998 199/1181/1054 186/1182/1055
+f 231/1177/1053 199/1181/1054 220/1178/998
+f 257/1179/999 284/1163/1048 249/1183/1003
+f 218/1180/1000 249/1183/1003 208/1184/1056
+f 257/1179/999 249/1183/1003 218/1180/1000
+f 199/1181/1054 208/1184/1056 178/1185/1005
+f 218/1180/1000 208/1184/1056 199/1181/1054
+f 186/1182/1055 178/1185/1005 166/1186/1006
+f 199/1181/1054 178/1185/1005 186/1182/1055
+f 249/1183/1003 284/1163/1048 241/1187/1057
+f 208/1184/1056 241/1187/1057 201/1188/1058
+f 249/1183/1003 241/1187/1057 208/1184/1056
+f 178/1185/1005 201/1188/1058 176/1189/1009
+f 208/1184/1056 201/1188/1058 178/1185/1005
+f 166/1186/1006 176/1189/1009 160/1190/1010
+f 178/1185/1005 176/1189/1009 166/1186/1006
+f 241/1187/1057 284/1163/1048 248/1191/1011
+f 201/1188/1058 248/1191/1011 207/1192/1012
+f 241/1187/1057 248/1191/1011 201/1188/1058
+f 176/1189/1009 207/1192/1012 177/1193/1059
+f 201/1188/1058 207/1192/1012 176/1189/1009
+f 160/1190/1010 177/1193/1059 165/1194/1014
+f 176/1189/1009 177/1193/1059 160/1190/1010
+f 248/1191/1011 284/1163/1048 256/1195/1015
+f 207/1192/1012 256/1195/1015 217/1196/1016
+f 248/1191/1011 256/1195/1015 207/1192/1012
+f 177/1193/1059 217/1196/1016 200/1197/1060
+f 207/1192/1012 217/1196/1016 177/1193/1059
+f 165/1194/1014 200/1197/1060 185/1198/1018
+f 177/1193/1059 200/1197/1060 165/1194/1014
+f 256/1195/1015 284/1163/1048 262/1199/1019
+f 217/1196/1016 262/1199/1019 252/1200/1020
+f 256/1195/1015 262/1199/1019 217/1196/1016
+f 200/1197/1060 252/1200/1020 232/1201/1061
+f 217/1196/1016 252/1200/1020 200/1197/1060
+f 185/1198/1018 232/1201/1061 219/1202/1022
+f 200/1197/1060 232/1201/1061 185/1198/1018
+f 262/1199/1019 284/1163/1048 282/1203/1023
+f 252/1200/1020 282/1203/1023 280/1204/1024
+f 262/1199/1019 282/1203/1023 252/1200/1020
+f 232/1201/1061 280/1204/1024 277/1205/1062
+f 252/1200/1020 280/1204/1024 232/1201/1061
+f 219/1202/1022 277/1205/1062 275/1206/1063
+f 232/1201/1061 277/1205/1062 219/1202/1022
+f 282/1203/1023 284/1163/1048 295/1207/1027
+f 280/1204/1024 295/1207/1027 304/1208/1064
+f 282/1203/1023 295/1207/1027 280/1204/1024
+f 277/1205/1062 304/1208/1064 314/1209/1029
+f 280/1204/1024 304/1208/1064 277/1205/1062
+f 275/1206/1063 314/1209/1029 326/1210/1065
+f 277/1205/1062 314/1209/1029 275/1206/1063
+f 295/1207/1027 284/1163/1048 300/1211/1066
+f 304/1208/1064 300/1211/1066 330/1212/1032
+f 295/1207/1027 300/1211/1066 304/1208/1064
+f 314/1209/1029 330/1212/1032 354/1213/1033
+f 304/1208/1064 330/1212/1032 314/1209/1029
+f 326/1210/1065 354/1213/1033 360/1214/1034
+f 314/1209/1029 354/1213/1033 326/1210/1065
+f 300/1211/1066 284/1163/1048 306/1215/1035
+f 330/1212/1032 306/1215/1035 347/1216/1067
+f 300/1211/1066 306/1215/1035 330/1212/1032
+f 354/1213/1033 347/1216/1067 370/1217/1068
+f 330/1212/1032 347/1216/1067 354/1213/1033
+f 360/1214/1034 370/1217/1068 382/1218/1038
+f 354/1213/1033 370/1217/1068 360/1214/1034
+f 306/1215/1035 284/1163/1048 311/1219/1039
+f 347/1216/1067 311/1219/1039 351/1220/1069
+f 306/1215/1035 311/1219/1039 347/1216/1067
+f 370/1217/1068 351/1220/1069 373/1221/1070
+f 347/1216/1067 351/1220/1069 370/1217/1068
+f 382/1218/1038 373/1221/1070 406/1222/1071
+f 370/1217/1068 373/1221/1070 382/1218/1038
+f 311/1219/1039 284/1163/1048 307/1223/1072
+f 351/1220/1069 307/1223/1072 348/1224/1073
+f 311/1219/1039 307/1223/1072 351/1220/1069
+f 373/1221/1070 348/1224/1073 371/1225/1074
+f 351/1220/1069 348/1224/1073 373/1221/1070
+f 406/1222/1071 371/1225/1074 383/1226/1075
+f 373/1221/1070 371/1225/1074 406/1222/1071
+f 307/1223/1072 284/1163/1048 301/1162/1047
+f 348/1224/1073 301/1162/1047 331/1165/985
+f 307/1223/1072 301/1162/1047 348/1224/1073
+f 371/1225/1074 331/1165/985 355/1167/1050
+f 348/1224/1073 331/1165/985 371/1225/1074
+f 383/1226/1075 355/1167/1050 361/1169/989
+f 371/1225/1074 355/1167/1050 383/1226/1075
+f 1080/1227/1076 1250/1228/1077 1249/580/561
+f 1081/1229/1076 1250/1228/1077 1080/1227/1076
+f 1249/580/561 1279/1230/1078 1278/592/573
+f 1250/1228/1077 1279/1230/1078 1249/580/561
+f 1278/592/573 1286/1231/1079 1285/598/579
+f 1279/1230/1078 1286/1231/1079 1278/592/573
+f 1285/598/579 1290/1232/1080 1289/1233/1080
+f 1286/1231/1079 1290/1232/1080 1285/598/579
+f 1289/1233/1080 1288/1234/1081 1287/1235/1081
+f 1290/1232/1080 1288/1234/1081 1289/1233/1080
+f 1287/1235/1081 1283/1236/1082 1282/1237/1082
+f 1288/1234/1081 1283/1236/1082 1287/1235/1081
+f 1282/1237/1082 1281/1238/1083 1280/604/585
+f 1283/1236/1082 1281/1238/1083 1282/1237/1082
+f 1280/604/585 1242/1239/1084 1241/1240/1084
+f 1281/1238/1083 1242/1239/1084 1280/604/585
+f 1241/1240/1084 1081/1229/1076 1080/1227/1076
+f 1242/1239/1084 1081/1229/1076 1241/1240/1084
+f 273/1241/1085 104/521/502 105/1242/1086
+f 274/1243/1085 273/1241/1085 105/1242/1086
+f 104/521/502 75/532/513 76/1244/1087
+f 105/1242/1086 104/521/502 76/1244/1087
+f 75/532/513 66/538/519 67/1245/1088
+f 76/1244/1087 75/532/513 67/1245/1088
+f 66/538/519 62/1246/1089 63/1247/1089
+f 67/1245/1088 66/538/519 63/1247/1089
+f 62/1246/1089 64/1248/1090 65/1249/1090
+f 63/1247/1089 62/1246/1089 65/1249/1090
+f 64/1248/1090 68/1250/1091 69/1251/1091
+f 65/1249/1090 64/1248/1090 69/1251/1091
+f 68/1250/1091 72/544/525 73/1252/1092
+f 69/1251/1091 68/1250/1091 73/1252/1092
+f 72/544/525 113/1253/1093 114/1254/1093
+f 73/1252/1092 72/544/525 114/1254/1093
+f 113/1253/1093 273/1241/1085 274/1243/1085
+f 114/1254/1093 113/1253/1093 274/1243/1085
+f 1097/1255/1094 1074/1256/1095 1084/1257/1096
+f 1184/1258/1097 1084/1257/1096 1121/1259/1098
+f 1097/1255/1094 1084/1257/1096 1184/1258/1097
+f 1181/1260/1099 1121/1259/1098 1120/1261/1100
+f 1184/1258/1097 1121/1259/1098 1181/1260/1099
+f 1170/1262/1048 1120/1261/1100 1110/1263/1048
+f 1181/1260/1099 1120/1261/1100 1170/1262/1048
+f 1084/1257/1096 1074/1256/1095 1060/1264/1101
+f 1121/1259/1098 1060/1264/1101 1032/1265/1102
+f 1084/1257/1096 1060/1264/1101 1121/1259/1098
+f 1120/1261/1100 1032/1265/1102 1034/1266/1103
+f 1121/1259/1098 1032/1265/1102 1120/1261/1100
+f 1110/1263/1048 1034/1266/1103 1041/1267/1048
+f 1120/1261/1100 1034/1266/1103 1110/1263/1048
+f 1060/1264/1101 1074/1256/1095 1051/1268/1104
+f 1032/1265/1102 1051/1268/1104 969/1269/1105
+f 1060/1264/1101 1051/1268/1104 1032/1265/1102
+f 1034/1266/1103 969/1269/1105 974/1270/1106
+f 1032/1265/1102 969/1269/1105 1034/1266/1103
+f 1041/1267/1048 974/1270/1106 991/1271/1048
+f 1034/1266/1103 974/1270/1106 1041/1267/1048
+f 1051/1268/1104 1074/1256/1095 1043/1272/1107
+f 969/1269/1105 1043/1272/1107 925/1273/1108
+f 1051/1268/1104 1043/1272/1107 969/1269/1105
+f 974/1270/1106 925/1273/1108 929/1274/1109
+f 969/1269/1105 925/1273/1108 974/1270/1106
+f 991/1271/1048 929/1274/1109 943/1275/1048
+f 974/1270/1106 929/1274/1109 991/1271/1048
+f 1043/1272/1107 1074/1256/1095 1044/1276/1110
+f 925/1273/1108 1044/1276/1110 928/1277/1111
+f 1043/1272/1107 1044/1276/1110 925/1273/1108
+f 929/1274/1109 928/1277/1111 932/1278/1112
+f 925/1273/1108 928/1277/1111 929/1274/1109
+f 943/1275/1048 932/1278/1112 944/1279/983
+f 929/1274/1109 932/1278/1112 943/1275/1048
+f 1044/1276/1110 1074/1256/1095 1052/1280/1113
+f 928/1277/1111 1052/1280/1113 975/1281/1114
+f 1044/1276/1110 1052/1280/1113 928/1277/1111
+f 932/1278/1112 975/1281/1114 976/1282/1115
+f 928/1277/1111 975/1281/1114 932/1278/1112
+f 944/1279/983 976/1282/1115 992/1283/983
+f 932/1278/1112 976/1282/1115 944/1279/983
+f 1052/1280/1113 1074/1256/1095 1061/1284/1116
+f 975/1281/1114 1061/1284/1116 1039/1285/1117
+f 1052/1280/1113 1061/1284/1116 975/1281/1114
+f 976/1282/1115 1039/1285/1117 1040/1286/1118
+f 975/1281/1114 1039/1285/1117 976/1282/1115
+f 992/1283/983 1040/1286/1118 1046/1287/1048
+f 976/1282/1115 1040/1286/1118 992/1283/983
+f 1061/1284/1116 1074/1256/1095 1085/1288/1119
+f 1039/1285/1117 1085/1288/1119 1128/1289/1120
+f 1061/1284/1116 1085/1288/1119 1039/1285/1117
+f 1040/1286/1118 1128/1289/1120 1127/1290/1121
+f 1039/1285/1117 1128/1289/1120 1040/1286/1118
+f 1046/1287/1048 1127/1290/1121 1115/1291/1122
+f 1040/1286/1118 1127/1290/1121 1046/1287/1048
+f 1085/1288/1119 1074/1256/1095 1098/1292/1123
+f 1128/1289/1120 1098/1292/1123 1190/1293/1124
+f 1085/1288/1119 1098/1292/1123 1128/1289/1120
+f 1127/1290/1121 1190/1293/1124 1187/1294/1125
+f 1128/1289/1120 1190/1293/1124 1127/1290/1121
+f 1115/1291/1122 1187/1294/1125 1173/1295/1048
+f 1127/1290/1121 1187/1294/1125 1115/1291/1122
+f 1098/1292/1123 1074/1256/1095 1112/1296/1126
+f 1190/1293/1124 1112/1296/1126 1217/1297/1127
+f 1098/1292/1123 1112/1296/1126 1190/1293/1124
+f 1187/1294/1125 1217/1297/1127 1212/1298/1128
+f 1190/1293/1124 1217/1297/1127 1187/1294/1125
+f 1173/1295/1048 1212/1298/1128 1198/1299/1048
+f 1187/1294/1125 1212/1298/1128 1173/1295/1048
+f 1112/1296/1126 1074/1256/1095 1111/1300/1129
+f 1217/1297/1127 1111/1300/1129 1214/1301/1130
+f 1112/1296/1126 1111/1300/1129 1217/1297/1127
+f 1212/1298/1128 1214/1301/1130 1211/1302/1131
+f 1217/1297/1127 1214/1301/1130 1212/1298/1128
+f 1198/1299/1048 1211/1302/1131 1195/1303/1048
+f 1212/1298/1128 1211/1302/1131 1198/1299/1048
+f 1111/1300/1129 1074/1256/1095 1097/1255/1094
+f 1214/1301/1130 1097/1255/1094 1184/1258/1097
+f 1111/1300/1129 1097/1255/1094 1214/1301/1130
+f 1211/1302/1131 1184/1258/1097 1181/1260/1099
+f 1214/1301/1130 1184/1258/1097 1211/1302/1131
+f 1195/1303/1048 1181/1260/1099 1170/1262/1048
+f 1211/1302/1131 1181/1260/1099 1195/1303/1048
+f 302/1304/1132 279/1305/1095 293/1306/1133
+f 380/1307/1097 293/1306/1133 317/1308/1098
+f 302/1304/1132 293/1306/1133 380/1307/1097
+f 379/1309/1134 317/1308/1098 316/1310/1135
+f 380/1307/1097 317/1308/1098 379/1309/1134
+f 369/1311/1136 316/1310/1135 308/1312/1137
+f 379/1309/1134 316/1310/1135 369/1311/1136
+f 369/1311/1136 308/1312/1137 285/1313/1138
+f 293/1306/1133 279/1305/1095 269/1314/1139
+f 317/1308/1098 269/1314/1139 226/1315/1102
+f 293/1306/1133 269/1314/1139 317/1308/1098
+f 316/1310/1135 226/1315/1102 228/1316/1103
+f 317/1308/1098 226/1315/1102 316/1310/1135
+f 308/1312/1137 228/1316/1103 239/1317/1140
+f 316/1310/1135 228/1316/1103 308/1312/1137
+f 308/1312/1137 239/1317/1140 285/1313/1138
+f 269/1314/1139 279/1305/1095 254/1318/1104
+f 226/1315/1102 254/1318/1104 163/1319/1105
+f 269/1314/1139 254/1318/1104 226/1315/1102
+f 228/1316/1103 163/1319/1105 167/1320/1106
+f 226/1315/1102 163/1319/1105 228/1316/1103
+f 239/1317/1140 167/1320/1106 179/1321/1141
+f 228/1316/1103 167/1320/1106 239/1317/1140
+f 239/1317/1140 179/1321/1141 285/1313/1138
+f 254/1318/1104 279/1305/1095 242/1322/1107
+f 163/1319/1105 242/1322/1107 136/1323/1108
+f 254/1318/1104 242/1322/1107 163/1319/1105
+f 167/1320/1106 136/1323/1108 138/1324/1142
+f 163/1319/1105 136/1323/1108 167/1320/1106
+f 179/1321/1141 138/1324/1142 156/1325/1143
+f 167/1320/1106 138/1324/1142 179/1321/1141
+f 179/1321/1141 156/1325/1143 285/1313/1138
+f 242/1322/1107 279/1305/1095 243/1326/1144
+f 136/1323/1108 243/1326/1144 137/1327/1145
+f 242/1322/1107 243/1326/1144 136/1323/1108
+f 138/1324/1142 137/1327/1145 139/1328/1146
+f 136/1323/1108 137/1327/1145 138/1324/1142
+f 156/1325/1143 139/1328/1146 158/1329/1147
+f 138/1324/1142 139/1328/1146 156/1325/1143
+f 156/1325/1143 158/1329/1147 285/1313/1138
+f 243/1326/1144 279/1305/1095 255/1330/1148
+f 137/1327/1145 255/1330/1148 168/1331/1114
+f 243/1326/1144 255/1330/1148 137/1327/1145
+f 139/1328/1146 168/1331/1114 172/1332/1149
+f 137/1327/1145 168/1331/1114 139/1328/1146
+f 158/1329/1147 172/1332/1149 183/1333/1150
+f 139/1328/1146 172/1332/1149 158/1329/1147
+f 158/1329/1147 183/1333/1150 285/1313/1138
+f 255/1330/1148 279/1305/1095 272/1334/1151
+f 168/1331/1114 272/1334/1151 233/1335/1117
+f 255/1330/1148 272/1334/1151 168/1331/1114
+f 172/1332/1149 233/1335/1117 234/1336/1152
+f 168/1331/1114 233/1335/1117 172/1332/1149
+f 183/1333/1150 234/1336/1152 244/1337/1153
+f 172/1332/1149 234/1336/1152 183/1333/1150
+f 183/1333/1150 244/1337/1153 285/1313/1138
+f 272/1334/1151 279/1305/1095 294/1338/1154
+f 233/1335/1117 294/1338/1154 324/1339/1120
+f 272/1334/1151 294/1338/1154 233/1335/1117
+f 234/1336/1152 324/1339/1120 319/1340/1121
+f 233/1335/1117 324/1339/1120 234/1336/1152
+f 244/1337/1153 319/1340/1121 312/1341/1155
+f 234/1336/1152 319/1340/1121 244/1337/1153
+f 244/1337/1153 312/1341/1155 285/1313/1138
+f 294/1338/1154 279/1305/1095 303/1342/1156
+f 324/1339/1120 303/1342/1156 385/1343/1157
+f 294/1338/1154 303/1342/1156 324/1339/1120
+f 319/1340/1121 385/1343/1157 384/1344/1158
+f 324/1339/1120 385/1343/1157 319/1340/1121
+f 312/1341/1155 384/1344/1158 372/1345/1159
+f 319/1340/1121 384/1344/1158 312/1341/1155
+f 312/1341/1155 372/1345/1159 285/1313/1138
+f 303/1342/1156 279/1305/1095 310/1346/1126
+f 385/1343/1157 310/1346/1126 426/1347/1127
+f 303/1342/1156 310/1346/1126 385/1343/1157
+f 384/1344/1158 426/1347/1127 422/1348/1160
+f 385/1343/1157 426/1347/1127 384/1344/1158
+f 372/1345/1159 422/1348/1160 411/1349/1161
+f 384/1344/1158 422/1348/1160 372/1345/1159
+f 372/1345/1159 411/1349/1161 285/1313/1138
+f 310/1346/1126 279/1305/1095 309/1350/1129
+f 426/1347/1127 309/1350/1129 425/1351/1162
+f 310/1346/1126 309/1350/1129 426/1347/1127
+f 422/1348/1160 425/1351/1162 421/1352/1163
+f 426/1347/1127 425/1351/1162 422/1348/1160
+f 411/1349/1161 421/1352/1163 410/1353/1164
+f 422/1348/1160 421/1352/1163 411/1349/1161
+f 411/1349/1161 410/1353/1164 285/1313/1138
+f 309/1350/1129 279/1305/1095 302/1304/1132
+f 425/1351/1162 302/1304/1132 380/1307/1097
+f 309/1350/1129 302/1304/1132 425/1351/1162
+f 421/1352/1163 380/1307/1097 379/1309/1134
+f 425/1351/1162 380/1307/1097 421/1352/1163
+f 410/1353/1164 379/1309/1134 369/1311/1136
+f 421/1352/1163 379/1309/1134 410/1353/1164
+f 410/1353/1164 369/1311/1136 285/1313/1138
diff --git a/tests/manual/barstest/shuttle.png b/tests/manual/barstest/shuttle.png
new file mode 100644
index 0000000..52d6244
--- /dev/null
+++ b/tests/manual/barstest/shuttle.png
Binary files differ
diff --git a/tests/manual/barstest/sliderwrapper.cpp b/tests/manual/barstest/sliderwrapper.cpp
new file mode 100644
index 0000000..108e160
--- /dev/null
+++ b/tests/manual/barstest/sliderwrapper.cpp
@@ -0,0 +1,13 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+#include "sliderwrapper.h"
+
+SliderWrapper::SliderWrapper(QSlider *slider)
+{
+ m_slider = slider;
+}
+
+void SliderWrapper::setEnabled(int enabled)
+{
+ m_slider->setEnabled(enabled);
+}
diff --git a/tests/manual/barstest/sliderwrapper.h b/tests/manual/barstest/sliderwrapper.h
new file mode 100644
index 0000000..80d4e54
--- /dev/null
+++ b/tests/manual/barstest/sliderwrapper.h
@@ -0,0 +1,23 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+#ifndef SLIDERWRAPPER_H
+#define SLIDERWRAPPER_H
+#include <QObject>
+#include <QSlider>
+
+class SliderWrapper : public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit SliderWrapper(QSlider *slider);
+
+public Q_SLOTS:
+ void setEnabled(int enabled);
+
+private:
+ QSlider *m_slider = nullptr;
+
+};
+
+#endif // SLIDERWRAPPER_H
diff --git a/tests/manual/custominput/CMakeLists.txt b/tests/manual/custominput/CMakeLists.txt
new file mode 100644
index 0000000..84cb343
--- /dev/null
+++ b/tests/manual/custominput/CMakeLists.txt
@@ -0,0 +1,36 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+qt_internal_add_manual_test(custominput
+ GUI
+ SOURCES
+ custominputhandler.cpp custominputhandler.h
+ main.cpp
+ scatterdatamodifier.cpp scatterdatamodifier.h
+ )
+
+qt_add_executable(custominput
+ custominputhandler.cpp custominputhandler.h
+ main.cpp
+ scatterdatamodifier.cpp scatterdatamodifier.h
+)
+
+target_link_libraries(custominput PUBLIC
+ Qt::Core
+ Qt::Gui
+ Qt::Widgets
+ Qt::Graphs
+)
+
+set(custominput_resource_files
+ "data/data.txt"
+)
+
+qt6_add_resources(custominput "custominput"
+ PREFIX
+ "/"
+ FILES
+ ${custominput_resource_files}
+)
diff --git a/tests/manual/custominput/custominput.pro b/tests/manual/custominput/custominput.pro
new file mode 100644
index 0000000..1fe1c57
--- /dev/null
+++ b/tests/manual/custominput/custominput.pro
@@ -0,0 +1,20 @@
+android|ios|winrt {
+ error( "This example is not supported for android, ios, or winrt." )
+}
+
+!include( ../examples.pri ) {
+ error( "Couldn't find the examples.pri file!" )
+}
+
+SOURCES += main.cpp scatterdatamodifier.cpp \
+ custominputhandler.cpp
+HEADERS += scatterdatamodifier.h \
+ custominputhandler.h
+
+QT += widgets
+requires(qtConfig(combobox))
+
+RESOURCES += custominput.qrc
+
+OTHER_FILES += doc/src/* \
+ doc/images/*
diff --git a/tests/manual/custominput/custominput.qrc b/tests/manual/custominput/custominput.qrc
new file mode 100644
index 0000000..8ced2b9
--- /dev/null
+++ b/tests/manual/custominput/custominput.qrc
@@ -0,0 +1,5 @@
+<RCC>
+ <qresource prefix="/">
+ <file>data/data.txt</file>
+ </qresource>
+</RCC>
diff --git a/tests/manual/custominput/custominputhandler.cpp b/tests/manual/custominput/custominputhandler.cpp
new file mode 100644
index 0000000..4f9d1f9
--- /dev/null
+++ b/tests/manual/custominput/custominputhandler.cpp
@@ -0,0 +1,39 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "custominputhandler.h"
+
+#include <QtGraphs/Q3DCamera>
+
+CustomInputHandler::CustomInputHandler(QObject *parent) :
+ QAbstract3DInputHandler(parent)
+{
+}
+
+//! [0]
+void CustomInputHandler::mouseMoveEvent(QMouseEvent *event, const QPoint &mousePos)
+{
+ Q_UNUSED(event);
+ setInputPosition(mousePos);
+}
+//! [0]
+
+//! [1]
+void CustomInputHandler::wheelEvent(QWheelEvent *event)
+{
+ // Adjust zoom level based on what zoom range we're in.
+ int zoomLevel = scene()->activeCamera()->zoomLevel();
+ if (zoomLevel > 100)
+ zoomLevel += event->angleDelta().y() / 12;
+ else if (zoomLevel > 50)
+ zoomLevel += event->angleDelta().y() / 60;
+ else
+ zoomLevel += event->angleDelta().y() / 120;
+ if (zoomLevel > 500)
+ zoomLevel = 500;
+ else if (zoomLevel < 10)
+ zoomLevel = 10;
+
+ scene()->activeCamera()->setZoomLevel(zoomLevel);
+}
+//! [1]
diff --git a/tests/manual/custominput/custominputhandler.h b/tests/manual/custominput/custominputhandler.h
new file mode 100644
index 0000000..90b8e5b
--- /dev/null
+++ b/tests/manual/custominput/custominputhandler.h
@@ -0,0 +1,19 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef CUSTOMINPUTHANDLER_H
+#define CUSTOMINPUTHANDLER_H
+
+#include <QtGraphs/QAbstract3DInputHandler>
+
+class CustomInputHandler : public QAbstract3DInputHandler
+{
+ Q_OBJECT
+public:
+ explicit CustomInputHandler(QObject *parent = 0);
+
+ virtual void mouseMoveEvent(QMouseEvent *event, const QPoint &mousePos);
+ virtual void wheelEvent(QWheelEvent *event);
+};
+
+#endif
diff --git a/tests/manual/custominput/data/data.txt b/tests/manual/custominput/data/data.txt
new file mode 100644
index 0000000..d0689b1
--- /dev/null
+++ b/tests/manual/custominput/data/data.txt
@@ -0,0 +1,1060 @@
+# Each line contains 3 floating point values
+# for x, y and z values in the scatter chart
+-10.0084,5.01799,-5.04425,
+-9.06229,3.07132,-4.54268,
+-8.55132,4.19424,-4.03318,
+-8.03806,4.75162,-3.99583,
+-9.50337,4.90684,-4.21947,
+-9.93819,3.42724,-3.58955,
+-7.84971,3.15272,-4.90367,
+-7.30477,2.91062,-4.11078,
+-7.11201,3.68863,-4.52683,
+-8.83267,2.96504,-3.61108,
+-6.94874,2.49808,-2.92883,
+-9.02606,4.7496,-4.18193,
+-9.5434,3.15534,-3.83789,
+-6.8679,3.66922,-3.58288,
+-8.16487,1.82227,-4.64523,
+-7.42165,3.18192,-4.22791,
+-7.99257,3.06559,-4.33262,
+-8.98851,2.64924,-4.44595,
+-6.36774,3.96697,-4.38998,
+-7.18413,3.32417,-4.04636,
+-7.91649,3.46826,-2.78126,
+-7.49495,3.12306,-3.14539,
+-7.5445,2.85744,-3.68421,
+2.25354,1.36828,-1.32025,
+-2.35524,-0.081203,1.23267,
+2.6517,-1.20549,2.73606,
+-2.55382,3.48814,-0.454971,
+-3.85468,0.263955,0.578276,
+2.85275,1.32315,1.0565,
+-0.404099,-2.36811,-1.60324,
+1.58908,0.363782,-0.554303,
+0.251507,0.124637,-0.752568,
+-2.45626,-0.722719,-1.11764,
+4.15342,1.92247,-0.954975,
+2.05845,0.643191,-0.121564,
+0.253468,0.814651,3.05732,
+1.51724,0.244303,2.25864,
+1.15,-0.487518,0.815931,
+-0.0538979,0.124927,0.251571,
+0.941523,-0.483498,-3.2731,
+3.55074,-1.04714,-0.954301,
+-2.43125,-0.964099,-0.658537,
+4.25459,0.163296,2.05563,
+-0.612031,1.03234,-0.227175,
+7.8338,-0.847922,-0.959189,
+-4.20076,1.44907,0.853836,
+-1.59466,-1.27511,-1.5686,
+-3.3567,-1.96864,1.83224,
+-2.75169,-0.964221,-1.26465,
+-2.45624,0.287046,-4.55032,
+2.73649,-1.48789,-0.653082,
+2.73284,2.2912,-2.4933,
+-0.854321,-2.04288,3.7516,
+-1.35708,-1.84915,2.35985,
+-3.25001,-1.64456,-4.45419,
+-0.356834,-0.569139,-1.75308,
+-0.813569,-0.287899,-0.0535036,
+4.75975,-1.48817,-2.45957,
+4.35294,1.46154,0.814214,
+-3.22467,2.76903,0.510435,
+2.49494,1.9286,0.552287,
+-0.456521,0.688277,-0.82788,
+-2.72568,2.80278,-2.45782,
+-1.65023,1.32005,-2.05754,
+-1.63551,1.88519,-3.65544,
+-1.20008,-0.723785,0.853563,
+1.45448,1.08701,2.17385,
+-3.9042,-1.16916,-0.85395,
+3.15645,0.123932,-0.950988,
+-1.35924,-2.64015,-0.54254,
+-4.15753,1.28294,-4.47502,
+-2.7885,2.48535,-0.159651,
+-3.44364,0.627232,6.11881,
+-1.55639,-0.967484,2.35497,
+-0.752853,1.16736,-0.757871,
+-0.859974,0.640213,-1.75453,
+0.85744,0.480175,2.97204,
+4.0571,3.24083,-0.183622,
+0.658087,0.841418,0.357839,
+-2.13027,0.920836,-2.758,
+-0.65825,0.164257,1.69478,
+-1.88335,-1.4811,-2.15408,
+-1.67331,2.96982,1.85521,
+-0.750937,0.282914,-0.758707,
+2.29891,1.16949,2.65927,
+2.69132,2.92632,-0.206126,
+-1.65771,3.28846,2.50201,
+-0.568799,2.5289,-1.15875,
+-1.85383,0.528953,-2.32418,
+0.159422,-2.36165,-0.569393,
+-3.96506,0.282374,-0.254519,
+0.150933,-3.88058,-0.759422,
+-1.17917,-1.96176,0.95589,
+-0.340817,-3.52333,-1.45715,
+3.21784,-2.56593,2.55468,
+0.609650,-2.44153,1.35813,
+1.4594,-1.96511,0.170228,
+3.55017,-2.16882,1.75539,
+2.89487,-0.727481,-0.39588,
+-1.65151,-0.603877,0.250508,
+-4.35232,-1.32155,-2.31877,
+2.75852,-1.88931,1.77874,
+2.75452,-0.64123,2.45546,
+0.151914,-0.888395,-0.260935,
+0.150593,0.0461652,-0.158206,
+-1.22087,-2.92034,-3.78604,
+-0.761622,0.161856,3.5586,
+-1.88456,-2.48094,0.287091,
+1.25293,2.64374,1.6532,
+-0.657609,1.32547,-0.557301,
+3.85995,2.32568,-1.38265,
+1.65572,-2.28977,0.957488,
+-2.45312,-2.96071,3.45147,
+-1.75259,0.365259,1.60113,
+1.14045,-0.844805,0.359343,
+-1.55951,1.65687,-3.10398,
+0.441652,-1.36623,-1.55494,
+-3.95762,0.288753,3.7791,
+-1.80101,-0.241499,2.29693,
+-0.456931,1.64399,1.35559,
+-0.691421,-0.723378,2.51839,
+-1.20658,-3.04063,-1.552,
+-0.958574,1.48688,0.950152,
+1.76319,-1.36072,2.15866,
+-3.91301,-0.328932,-2.45524,
+2.9553,3.12703,-2.43321,
+3.55939,-1.48028,0.152252,
+-1.41545,3.247,0.779314,
+-3.34482,-0.894538,2.09302,
+1.15338,0.362332,-2.7924,
+0.468852,0.120872,1.35593,
+1.98778,-3.32292,-0.758591,
+0.420718,-0.225014,-2.44806,
+0.550207,1.56432,2.25317,
+4.35156,-0.966178,-0.791032,
+3.35179,-1.04981,-0.854089,
+1.70857,-0.281369,-0.254686,
+-3.05855,-0.443693,1.30251,
+-3.9509,-2.72179,-3.83489,
+2.19632,1.32915,1.15345,
+1.42782,-0.449435,-2.85005,
+-2.19053,0.049866,2.87493,
+2.45196,-2.44705,-2.85302,
+4.35263,0.245956,1.12886,
+1.8167,1.85407,-0.714159,
+1.88577,2.04227,-0.959396,
+1.6522,-2.48289,0.355373,
+-3.39965,0.286834,-1.68171,
+1.85639,1.47419,2.25749,
+-2.5216,-0.88573,-2.69594,
+-0.143043,0.28453,-1.75898,
+-2.52745,-2.76741,-0.257011,
+0.381448,-1.64793,-0.756889,
+2.30469,-1.28844,-2.79271,
+-1.72491,-1.48634,-2.61686,
+0.503342,0.248352,-2.27162,
+0.457491,-1.88183,-0.951124,
+-1.23123,0.963519,2.3569,
+-0.55709,-0.364372,-1.82528,
+2.73963,-0.567024,-0.496936,
+1.17979,2.76252,-2.35641,
+-1.20723,-2.1692,3.14368,
+-0.0504301,1.76714,1.64265,
+1.45714,-0.725448,0.739217,
+0.246123,-3.27811,-0.251218,
+-0.894632,1.08758,-0.17184,
+-2.45217,0.565077,-0.557015,
+2.51301,1.1271,2.25057,
+-1.05099,-2.1688,-1.88996,
+1.26945,-2.1225,-1.55031,
+-4.30129,-0.760298,0.259868,
+-0.157336,-0.237532,-2.69483,
+-2.95778,1.36212,-3.65524,
+2.74003,1.16234,2.05928,
+0.955294,-1.52182,-1.05684,
+-1.87004,2.72729,-0.550608,
+0.459439,2.88101,-4.4135,
+1.35069,1.08445,-0.808625,
+-2.70034,-1.36291,-2.65126,
+0.353099,2.32354,-1.61885,
+-0.453359,1.67524,0.122888,
+3.7506,1.12855,-3.86338,
+0.548626,2.63101,0.552261,
+-0.753935,-3.52241,-3.21122,
+0.258935,-1.32453,2.25745,
+-2.8797,-1.60832,-2.45626,
+0.151838,-0.565264,3.98539,
+2.1508,-1.68734,-1.75076,
+-4.23947,-1.92531,2.35438,
+1.95273,-1.32736,0.313398,
+0.533647,0.646686,0.75712,
+0.856619,-0.843249,-0.577773,
+-2.15649,-1.64285,-2.31055,
+-0.851309,-2.84717,-0.324933,
+-3.17772,-0.324817,-0.858394,
+3.45303,5.29449,0.818094,
+1.42912,0.238088,0.385617,
+0.459586,1.7661,-0.153761,
+0.855577,3.16845,-2.19548,
+2.15181,0.729021,-0.955922,
+-2.40113,0.665756,-0.521366,
+1.45469,-0.462177,-1.95869,
+0.629115,-1.84769,1.86755,
+3.47576,-0.209875,-0.555502,
+0.295075,-0.840772,4.68895,
+-0.253301,-2.49078,2.11749,
+-1.15923,-3.163,-3.05671,
+1.45484,0.963654,-0.734942,
+0.354307,0.520772,-1.32278,
+2.65725,0.284589,-0.856856,
+-1.2596,0.765493,-1.66469,
+-0.656057,-2.16906,3.72144,
+-0.251559,-2.36406,-1.89709,
+0.35608,-0.80463,1.85674,
+0.0508692,0.615674,0.856785,
+-2.50726,2.28743,-2.05697,
+1.65272,1.29604,2.11481,
+-3.2878,-0.244516,0.799732,
+-2.18989,-0.847222,-0.264559,
+0.452832,0.960993,2.53691,
+-2.43913,1.28957,2.75427,
+-1.72889,-3.29414,-2.31426,
+0.952615,-0.0844651,0.346607,
+1.41175,0.889643,0.450356,
+2.13145,1.08697,0.223055,
+-2.16002,-0.225505,-0.602641,
+0.54028,1.24765,-0.456129,
+2.55086,-0.56734,-2.65051,
+-4.53921,-0.483588,-1.25013,
+3.45413,-0.44258,2.29687,
+-0.257456,0.64624,1.65041,
+1.25559,-0.65493,-0.358872,
+1.9599,-1.56965,-4.17044,
+2.75996,-1.98665,3.31794,
+3.05837,1.04847,-0.975536,
+-2.95407,1.40294,-2.25825,
+1.38718,0.360709,-2.98211,
+0.481728,-2.48564,3.25864,
+-1.15089,0.363522,0.458662,
+-2.25551,0.0421839,0.650008,
+-1.85862,-0.969237,4.25313,
+1.55797,0.0465051,-3.85709,
+0.0555338,0.682957,-2.45556,
+-0.186868,-0.482811,1.96957,
+2.197,-1.5248,-4.20912,
+3.40636,6.26268,-2.05757,
+0.780426,2.68048,-0.852693,
+1.65184,1.68951,-0.892089,
+2.11929,-2.44406,1.21168,
+-0.153348,-1.88112,-0.357374,
+-0.359393,1.76654,1.63063,
+-2.15954,0.0819277,0.757621,
+-0.159898,-3.36316,0.359582,
+2.74125,-2.84148,0.355785,
+-1.3558,0.27827,-0.588162,
+-0.354346,-2.56747,-0.984403,
+-2.75082,-3.56807,1.5599,
+-2.54708,-0.686147,2.75649,
+1.35138,0.692978,-2.24969,
+-1.39826,-0.246682,-1.65876,
+-1.46629,-1.44446,4.45293,
+-1.89442,3.64549,2.05732,
+-0.658093,0.0815129,3.95269,
+2.25603,2.96329,-2.35993,
+1.36323,1.64488,-0.0538547,
+1.75659,2.24227,-2.8522,
+-0.0566584,-1.56465,-0.0503143,
+-3.8532,-0.822258,-0.345406,
+0.951328,0.329296,-2.52211,
+-2.48659,0.410856,-3.55401,
+3.72676,2.36324,3.65589,
+0.559972,0.884984,3.15283,
+-1.37624,-1.36007,-2.16578,
+2.05543,0.4472,-0.82911,
+-4.75258,1.8779,-1.75376,
+0.15648,2.64039,-2.21467,
+-2.0593,-2.56775,1.15037,
+-3.70217,2.12375,1.3652,
+1.05566,6.8299,4.02985,
+-0.766882,-0.88359,1.35525,
+0.951335,-1.84689,0.178337,
+0.751608,1.1691,4.25273,
+-1.36367,0.728904,0.655858,
+3.17581,-0.0844758,-1.75811,
+2.85546,-0.683618,0.653701,
+-0.471118,3.04176,-2.35393,
+3.0574,-0.601996,-0.611932,
+-0.854024,-0.44532,0.355575,
+0.504700,-1.92888,6.4818,
+-1.25515,-1.44466,1.90429,
+-1.67201,0.0461708,-0.796655,
+1.45345,-1.66159,-3.48143,
+-2.84514,-1.24586,-3.47945,
+0.287402,-0.688276,-3.75664,
+3.35908,0.687828,-1.94406,
+-2.39167,1.08322,-1.73508,
+1.52152,1.86032,-1.25351,
+1.55931,3.64414,1.35944,
+1.15954,-1.36058,0.758814,
+-1.95325,0.0851092,-0.854106,
+-2.25254,-0.523024,1.05486,
+-2.68036,-1.32901,1.05877,
+-0.485956,-1.52164,2.45303,
+-0.0546215,0.640683,-2.85953,
+4.45295,-0.246051,-0.159626,
+3.4523,1.7215,-1.10587,
+3.35142,-1.72053,-0.252105,
+1.74251,2.76108,2.51524,
+2.15054,-2.88101,-1.7527,
+-3.70517,-0.0470951,0.258921,
+-0.45593,-3.47184,-2.95345,
+3.15988,-2.32107,0.105299,
+0.751449,-2.88762,-3.45245,
+2.9794,0.493172,-0.654683,
+-1.87713,-2.48632,-3.534,
+1.65266,3.16008,2.1579,
+-1.25239,-0.763119,2.15776,
+3.5572,0.282681,2.44174,
+0.251145,0.520256,2.3184,
+-1.78596,-1.36913,-2.50818,
+3.82122,1.04473,0.456159,
+2.45979,-0.722759,-4.05123,
+-0.855594,0.163792,-0.553702,
+0.656895,0.529982,3.35129,
+0.857952,-0.0885677,0.695128,
+-0.143269,1.12972,-3.155,
+-2.95923,0.241767,0.832165,
+0.253329,-0.622952,-0.459799,
+0.151499,2.5297,1.53059,
+0.655464,-1.49902,-1.51071,
+4.7585,1.76425,1.15164,
+-1.75063,-1.44645,-3.65525,
+0.850392,0.0417223,-0.340588,
+-0.557015,-0.282305,-1.85291,
+-4.05639,0.522959,-2.3507,
+-0.358137,-0.967852,0.807832,
+-1.82056,-0.0483894,0.0541359,
+-2.16623,0.129809,-2.52513,
+-1.85591,-1.04417,-1.28501,
+-1.79647,-3.44045,-1.73399,
+2.25342,0.161308,-0.0517495,
+-1.37877,0.243596,-1.52931,
+-0.059299,-0.480825,1.7137,
+-2.54357,0.286685,-2.11495,
+3.92334,-0.442936,-0.852895,
+-0.390023,-1.96437,1.38718,
+1.35263,2.92968,-1.1545,
+-2.25892,-2.27429,-0.451533,
+-0.2215,-0.126727,0.155541,
+0.715932,1.47509,-3.52895,
+-0.382939,3.16461,2.65165,
+-1.14437,-1.44682,0.456601,
+0.251892,-1.0431,4.31548,
+-2.23281,-2.48698,0.46995,
+0.954231,-3.4323,-1.20233,
+2.75569,-1.66383,-1.95486,
+0.750644,-1.84163,-0.159206,
+-0.757387,-1.84192,0.354209,
+-2.85509,-2.12151,-0.954754,
+-0.888427,-2.8403,-0.157387,
+-1.95265,-0.445753,2.17956,
+-1.05845,-2.48694,-1.25315,
+-2.66497,-1.48251,0.873192,
+2.50491,-1.0833,1.99667,
+0.453931,2.52158,0.456875,
+1.55859,-0.161924,0.256619,
+-2.92585,0.368018,0.35908,
+2.95509,-1.56248,-2.74469,
+3.46082,-1.76026,3.05835,
+1.75644,-2.84241,0.507631,
+-0.959006,0.649579,1.10824,
+2.63856,2.0417,0.187281,
+-0.150004,-0.0838836,-0.949314,
+2.26402,-1.28916,2.85284,
+-0.821239,2.68795,-0.317185,
+3.47124,0.840813,-2.65322,
+-0.253209,-0.244177,0.457348,
+2.76181,1.64033,-1.95329,
+1.35105,-2.96027,0.659952,
+-1.45423,-0.24358,0.832696,
+1.45109,1.64958,-3.45448,
+-1.15659,3.08225,1.11445,
+0.806359,1.88298,-2.13001,
+1.15538,3.04545,-0.759437,
+-0.450074,1.36121,-0.155042,
+-2.80924,-1.24207,2.55513,
+4.48859,2.04394,1.25324,
+-0.958741,1.24575,3.65169,
+3.45143,1.3276,2.5144,
+-3.25232,1.12514,-1.21425,
+-2.45327,0.681109,4.35764,
+0.55395,-0.128353,-3.95705,
+-0.352458,3.08882,-0.340631,
+1.35213,-2.92251,-3.31166,
+0.52621,-0.279201,0.959619,
+1.2243,-0.240093,-0.75247,
+-1.29854,2.16477,3.3507,
+-3.35677,2.36713,3.4585,
+0.957717,-0.885793,1.25827,
+0.150983,1.24269,5.39106,
+1.84986,1.56932,-1.05811,
+-1.35563,3.82103,-1.45287,
+1.4544,-2.6453,0.58082,
+2.05908,1.16496,1.44075,
+2.9507,-2.4957,0.153512,
+-1.10289,0.763085,-1.65351,
+-2.50708,-2.467,1.05892,
+3.55284,-1.8509,-2.55732,
+-0.848034,1.24305,-3.7516,
+-1.35051,-2.48178,2.85326,
+-2.18554,1.48771,-0.155205,
+-0.459278,2.68404,2.85727,
+0.854722,1.47322,-3.35951,
+-2.23505,-2.24254,0.353203,
+1.42395,-2.32169,0.558188,
+3.65106,1.12201,-4.58409,
+0.11,-0.68782,1.85804,
+-1.9551,-0.560204,-2.0577,
+1.85964,1.32737,-4.40673,
+-0.616311,0.649737,-0.30189,
+-1.55375,1.52043,-3.75629,
+1.85417,0.486964,0.654806,
+3.35638,2.52599,1.94343,
+0.491389,1.40427,-1.31935,
+-2.72939,0.844341,1.62621,
+-0.525985,1.31415,0.484015,
+2.25998,-1.719,1.25143,
+1.79268,2.46378,-0.951188,
+-3.55105,1.04122,3.9501,
+0.954251,1.28296,2.05486,
+-3.15267,1.96498,-0.361707,
+1.35358,-0.921098,-1.71743,
+-3.16896,-3.08548,0.903411,
+1.25127,-2.44903,2.25616,
+-3.88899,0.761334,-1.05751,
+-1.05163,-3.89783,-0.883668,
+-4.189,1.24176,-2.63816,
+2.45671,-1.84859,0.352808,
+-1.59618,2.16255,4.33699,
+2.10913,-1.88673,-0.952497,
+-1.85571,-0.287392,0.277176,
+0.751967,1.04568,-1.35427,
+2.85792,1.60982,-2.05454,
+-2.13152,-0.260207,-2.15124,
+1.75432,2.28936,0.756254,
+1.95711,-0.681098,-2.38037,
+-0.826273,1.08361,1.6515,
+-1.85118,1.89481,-0.756754,
+-3.0571,2.44795,0.355341,
+-0.455122,-3.58071,-2.85209,
+-0.95805,0.641282,2.15189,
+-1.35515,-0.234803,-0.825819,
+2.12472,-0.0444431,0.651227,
+2.67573,0.223987,2.75712,
+1.65871,-0.163059,1.6513,
+-1.85429,0.68862,1.9576,
+-3.05186,-2.2804,0.100919,
+3.41813,-1.88775,3.67075,
+-0.753778,0.36467,1.55333,
+2.97628,-1.36502,-1.85135,
+0.477128,1.08862,0.858931,
+-1.0531,1.0488,2.15218,
+2.66911,-2.08876,-0.182397,
+5.117,-2.84097,-0.953684,
+0.468302,1.88616,2.05369,
+-3.16099,-2.76085,-2.75679,
+-2.6593,3.52373,-1.24072,
+-4.37957,-0.286903,3.63863,
+-2.85958,-2.56921,-2.85723,
+-0.159735,2.72758,-2.80575,
+-0.951849,-0.607465,1.05633,
+1.93077,2.56422,1.25446,
+-0.859754,0.248106,0.0584456,
+2.4023,2.56659,-1.2588,
+2.35295,-1.08729,2.7851,
+-2.1537,-0.765032,2.83652,
+1.40185,1.29804,2.3588,
+-0.991566,1.72049,4.17146,
+3.76736,-1.48837,2.05329,
+-0.251896,0.765367,-1.4087,
+-1.6228,0.328693,0.0528287,
+2.56735,-3.08103,0.853144,
+0.0531812,-1.96216,1.55734,
+-3.77052,0.8421,-0.258953,
+2.35523,0.676643,-1.55789,
+1.16702,2.64474,-1.45533,
+2.55709,-1.56013,2.05351,
+-2.15518,3.56253,3.257,
+-0.553936,-1.24935,2.65224,
+-0.355931,1.32374,0.859863,
+-1.92974,1.2482,1.15936,
+0.350652,-2.44371,-1.35611,
+2.98996,-1.08527,-4.30641,
+1.82765,-0.440236,1.25528,
+-0.689231,-1.08813,-0.668663,
+-0.326426,-0.881857,-1.45371,
+-1.0655,2.12466,2.34146,
+3.1563,0.523166,-2.8572,
+0.455505,2.48775,-1.33482,
+0.53939,-0.847333,0.732877,
+-0.683025,-0.448889,-1.35747,
+-1.7711,-0.125587,-2.55083,
+-0.512871,0.520964,1.40731,
+4.93857,-1.6805,-0.127298,
+1.46098,-1.64073,1.35833,
+0.0518058,0.285151,-2.2437,
+1.5587,-1.23067,0.458753,
+3.13089,3.64132,1.45181,
+-1.55648,2.167,0.153491,
+3.94451,-2.56372,-1.25276,
+4.15866,0.646921,2.65542,
+-2.88189,0.562407,-1.35379,
+1.31686,1.2808,0.804375,
+-2.36912,-3.08775,1.28335,
+0.575203,-0.36483,-2.43958,
+0.613108,0.526892,2.75368,
+3.96027,-0.525425,-4.25746,
+-0.510821,1.28578,-0.058488,
+-0.254704,-3.847,3.15258,
+-0.925874,-1.72014,-3.15341,
+0.85704,0.84788,-1.75947,
+-3.35712,0.722104,2.15645,
+-1.67305,0.681216,1.65726,
+-3.64682,0.867926,2.63525,
+-0.715921,1.96081,-0.939934,
+-2.45646,1.3249,-2.75733,
+-1.75798,0.725382,-0.851921,
+-2.78528,0.679275,-2.59212,
+-2.24551,-3.4597,-1.85735,
+0.85142,2.28058,-3.75328,
+-3.85054,2.44519,4.35081,
+-3.6553,0.521917,0.293354,
+-4.35959,-0.528198,1.55557,
+1.45186,-0.0891161,-0.468118,
+1.85594,-0.761461,-4.68083,
+0.950642,0.526239,-1.30614,
+-2.50526,-0.885606,-0.362569,
+-2.96569,1.68519,-1.15965,
+-3.212,0.260715,1.18472,
+0.950556,-0.282806,0.776252,
+3.66678,1.08585,-2.15646,
+-0.806289,1.72784,2.85906,
+0.363827,1.76644,0.931866,
+-1.34204,-0.563686,-2.34091,
+-2.81333,0.415358,4.28363,
+1.52053,-0.327359,0.35052,
+-0.633441,-0.240518,4.05745,
+-2.38947,-1.84662,-2.29572,
+-1.95744,-0.863705,1.85889,
+-0.0509082,-0.164164,3.8571,
+0.156438,-2.64188,1.75836,
+-3.85642,1.48025,0.171659,
+0.253545,1.0852,-2.45243,
+-4.11318,4.1655,-0.120976,
+-1.44928,-0.328222,-0.871279,
+-1.90972,-1.8495,-3.16966,
+0.359433,1.3236,-3.95045,
+-2.60974,-1.04138,4.25836,
+-3.16336,-0.961581,-1.65161,
+-0.552909,-1.16942,4.05164,
+-3.10918,-1.2402,-0.555073,
+2.25494,1.24432,3.44063,
+-1.24998,-1.24928,-4.05493,
+3.05441,1.92762,1.43329,
+0.557032,-2.7688,-3.25463,
+-2.05665,1.6357,0.656665,
+-0.459042,0.122664,-0.152961,
+2.17715,2.96833,1.1332,
+0.0536573,-2.08635,-0.736471,
+-3.15658,1.0818,-0.172166,
+-1.95784,0.44402,0.612685,
+0.15168,-0.323951,2.85563,
+0.559356,1.63101,0.558005,
+-2.05643,1.84615,-1.87964,
+-0.254098,0.368208,1.23061,
+4.6438,-0.209283,0.695869,
+-1.95087,-0.859788,0.0539467,
+-0.351737,-1.04291,0.869198,
+0.776319,3.04922,-3.55278,
+-0.158751,1.12538,-2.59681,
+-0.560997,-1.68381,1.56935,
+1.71385,-0.446223,-1.56843,
+4.05142,-1.3243,2.85052,
+1.68685,-1.56305,2.159,
+0.359977,-1.16029,1.25037,
+-2.65045,-1.28532,0.459338,
+5.42068,1.52601,-1.35562,
+1.34386,1.68231,-2.75114,
+1.25123,0.43108,0.383897,
+-0.952936,3.6889,1.75759,
+-3.55855,0.484241,1.15503,
+-1.71394,-2.64568,-3.30684,
+-2.23513,-2.46812,1.05321,
+0.160986,0.442362,-1.05077,
+2.05433,0.473204,0.823968,
+0.482077,0.0477338,4.21712,
+-1.25348,0.767612,0.455813,
+1.61308,2.04125,-2.95433,
+-2.05862,-0.444052,1.35177,
+-3.2582,0.44354,2.15898,
+-1.75935,0.0459283,2.35061,
+-4.15501,0.68674,0.802439,
+-1.66005,1.12067,0.552901,
+-0.150828,3.4151,3.05446,
+-0.274381,0.0405946,-1.45463,
+2.94543,-0.360918,1.45895,
+-1.42494,1.56503,1.31012,
+3.45402,-1.27808,-4.29049,
+-1.14819,0.674339,1.55807,
+-0.356159,1.52295,-0.351445,
+0.387809,0.965119,1.84271,
+2.56165,-1.84639,1.65056,
+-0.619108,2.24004,-2.92134,
+-0.254288,-2.32744,-0.720931,
+3.3508,-2.24501,1.58268,
+1.69434,1.96814,3.97439,
+1.80785,0.685502,-1.55595,
+2.71587,1.76415,-2.98124,
+-2.35151,-0.697338,-3.96269,
+-1.85759,1.96899,-1.25212,
+-4.05922,1.12838,-2.7597,
+-2.85434,-1.36487,0.659987,
+-1.75616,2.12969,-1.35032,
+-2.95985,-0.0854955,3.65545,
+-2.46049,2.45378,4.51969,
+-0.0577358,0.0427911,0.359068,
+-3.25346,0.161829,1.12956,
+3.55498,2.32482,1.33302,
+4.99905,-1.88748,0.450165,
+-2.35406,-1.44715,-0.745307,
+2.44217,-0.642981,0.126924,
+1.73283,1.67362,1.91136,
+1.34239,-0.434386,-0.449795,
+-0.9813,-2.72962,-3.6889,
+1.29807,-0.448566,2.13911,
+0.654017,3.54591,-1.55982,
+-1.55508,-2.64564,0.555,
+1.92722,-0.322513,-2.13691,
+1.35913,-2.84853,-0.470788,
+0.257868,-2.68874,-0.268328,
+-2.05043,-1.68405,-3.05075,
+1.65173,-0.446801,-0.75339,
+-1.25655,0.965275,-4.15374,
+-4.27942,-0.564403,1.45826,
+0.950669,-2.4416,-1.68235,
+-0.0541107,0.882274,3.15308,
+-1.65016,-0.886156,-3.85588,
+0.355613,0.217671,4.25412,
+0.800472,0.847259,3.11114,
+1.8599,-2.16981,-2.22314,
+-2.05061,0.164561,-0.452181,
+0.854144,1.84334,-1.45981,
+1.68547,1.56536,-1.45874,
+1.25244,-0.844991,-2.95475,
+-1.92348,2.32119,-1.80444,
+-0.645646,2.45193,-3.65332,
+4.45803,0.527732,3.02031,
+2.74572,0.287964,-1.80485,
+0.35961,-2.56437,3.97194,
+-0.956828,-1.28915,1.64424,
+0.414971,-0.235061,-2.15757,
+-0.0530542,-1.08464,-4.11853,
+3.17203,-1.21013,-3.85806,
+0.758948,0.124698,-3.10188,
+-1.65404,-1.16204,-1.65357,
+-1.65985,3.84433,4.65101,
+2.83444,-2.69529,-1.65021,
+1.29814,-2.76926,-2.27139,
+0.462382,3.04217,3.45153,
+-3.61944,1.04723,-0.638308,
+2.25235,1.64048,2.95175,
+-3.05826,-0.76526,-2.38243,
+-2.8506,2.12102,-0.659444,
+-0.10046,0.0887098,-1.63621,
+-1.55585,-1.36073,2.2076,
+-0.474968,1.56568,-0.302349,
+0.36584,-2.36102,1.35289,
+0.224784,-0.637694,1.62444,
+-0.658172,1.9689,-3.13712,
+0.646334,-1.04672,2.59285,
+-2.63054,-1.08263,-0.851087,
+1.45804,-2.56159,-2.66388,
+-2.45748,0.0409116,-2.85428,
+-0.0561462,-1.24229,-1.25145,
+-2.95391,-1.80896,-2.05036,
+4.36778,-0.969951,3.53686,
+2.15582,-1.67173,-0.831609,
+-1.27059,-1.14919,1.7569,
+-1.57398,-1.28091,-0.251735,
+2.59506,-2.6408,-0.345589,
+-1.64147,-0.360324,3.1562,
+-0.125427,-0.641484,-3.66095,
+-1.9148,1.8885,2.12972,
+-2.85768,-2.28782,-1.55719,
+-1.37239,-0.485964,-2.22291,
+0.516294,0.281078,0.652612,
+-1.13028,0.246659,-2.25636,
+1.7555,-1.41602,0.348449,
+1.5355,-1.32646,-2.82417,
+-0.95426,2.0824,-0.383507,
+2.75739,0.241779,0.755701,
+0.752655,0.56204,-1.55738,
+1.1271,-3.76145,4.3701,
+0.326656,-1.24467,2.62222,
+-0.259702,2.68152,-3.18542,
+1.88504,-0.760623,4.42592,
+2.51673,4.72252,-1.99239,
+-1.66934,-0.129677,-2.49048,
+-1.25499,2.36151,-2.98447,
+2.65331,0.0443886,0.108819,
+-3.35439,0.0872109,-1.33429,
+3.35597,0.0403626,2.8203,
+4.45191,1.24824,-0.95821,
+-0.171441,-1.52622,-4.68253,
+1.19986,1.72992,-3.29566,
+-0.451339,1.92976,1.21534,
+-0.743119,-0.160688,0.805688,
+2.38678,0.272829,2.75457,
+1.72464,2.08232,-0.950389,
+2.11167,1.56576,1.21969,
+3.05157,-1.56838,-0.450535,
+0.132417,-3.08279,-1.34127,
+1.65441,-0.325204,-0.825145,
+2.05515,-1.8417,1.8561,
+-0.555858,1.52007,-0.80122,
+-2.05026,1.52773,2.33529,
+-2.35231,-3.28716,3.53598,
+-2.25771,2.5667,-1.92243,
+0.386884,-3.28674,3.49336,
+0.957272,-1.68124,-1.89095,
+2.99881,2.24992,-0.0535837,
+3.19604,2.92893,-2.16276,
+2.55903,3.12413,2.95022,
+-0.570758,-2.85326,-0.339255,
+0.356627,0.641074,0.355538,
+-1.15777,-0.162227,-0.455885,
+-2.4871,-0.885492,-0.374875,
+1.55464,-1.48929,-0.593706,
+-0.852655,-2.08736,-1.18281,
+0.504087,-0.879247,1.35148,
+-2.15261,1.04511,-3.25543,
+-0.653745,-1.32873,-3.18964,
+0.35973,-0.844255,-1.72034,
+-2.11112,0.962572,2.59386,
+1.47531,-0.693626,-3.15249,
+-1.35814,0.767202,3.45094,
+-3.05812,-1.26298,1.45287,
+-1.63927,0.893568,-1.94978,
+0.751992,1.68559,2.3618,
+5.8205,-0.769076,-0.958994,
+-2.38512,-0.166005,-1.25855,
+-0.556071,-2.28164,1.57032,
+5.89172,-0.244834,0.553728,
+-0.468648,-0.927756,-0.737048,
+-2.66659,-1.64374,-0.995568,
+0.251499,-1.24569,0.95028,
+0.737721,2.88575,0.448918,
+-0.634421,-1.84377,-2.47094,
+0.556349,2.86774,1.62621,
+-3.29644,3.28642,-3.45459,
+-1.45045,2.52138,2.75329,
+1.87449,-2.68651,3.60371,
+-0.656424,-2.56828,1.35509,
+1.95771,-1.48325,-3.85265,
+3.85074,0.0825779,-1.65283,
+-1.17278,-1.69258,-1.05875,
+1.25754,-0.0439433,1.3571,
+2.91459,2.08715,0.259842,
+0.748196,2.52132,0.659051,
+-1.85745,-1.88355,1.05259,
+1.69311,1.12299,-3.05453,
+-2.86301,0.123274,0.559289,
+-1.43146,1.64425,-1.95053,
+2.35287,0.414621,-0.950807,
+0.952585,-0.280452,-0.844588,
+-1.53836,-1.76396,2.51742,
+-3.05431,2.45,-0.26571,
+-0.637412,1.12028,-0.587387,
+-1.71017,-0.728497,-1.05087,
+0.454027,0.849208,-0.151716,
+-3.54846,0.367137,1.1484,
+0.200816,-2.08832,0.255433,
+-2.15817,0.725338,0.852676,
+-2.45708,-0.725538,-0.859435,
+4.25339,1.84177,-0.555145,
+0.119319,-1.32041,-1.45864,
+-3.65177,0.361323,0.351554,
+0.928598,0.321024,-3.64759,
+-0.250062,1.84912,7.49887,
+3.59593,0.929451,-2.15224,
+0.658599,2.36796,4.11632,
+0.55841,-1.08657,0.258326,
+1.9419,0.0488641,-3.75375,
+0.0589998,0.521351,-3.85723,
+3.45108,0.562199,4.20946,
+-0.255073,2.36504,-3.556,
+1.95848,1.28698,4.25014,
+1.85411,3.12131,0.652769,
+-3.3589,0.801817,-1.5591,
+2.48128,-0.163406,1.25243,
+2.52135,0.960467,1.45432,
+0.827496,-0.163021,-0.558886,
+-3.45032,-0.0831453,0.851212,
+-3.45334,0.855944,1.35596,
+-0.834644,0.245598,-1.23878,
+3.64558,1.16831,-0.18559,
+-1.45919,-3.08196,-0.329805,
+-3.65715,-1.04485,0.159208,
+1.35463,-0.766382,0.558632,
+-2.75899,-1.67431,-1.55069,
+1.67539,2.04337,0.912884,
+3.51677,-3.24285,0.170272,
+-4.25189,0.56589,-4.35936,
+-2.68545,-0.448243,0.540342,
+0.279844,-0.641466,-0.353986,
+-3.27626,-3.52006,1.77644,
+-0.724096,-1.84647,-2.45271,
+-4.18603,0.123376,3.85066,
+-4.05156,0.0499386,-0.332945,
+2.69508,2.32777,1.26256,
+-0.353726,1.07252,-0.738837,
+-0.947178,0.36459,1.55593,
+-0.058346,2.44781,-4.35023,
+1.51586,-0.961109,-3.43483,
+-2.8852,2.08863,-1.75468,
+-0.297867,0.722757,0.91355,
+-1.12917,-1.68328,0.175315,
+-1.24248,-0.323519,-0.854841,
+0.751943,0.564075,3.95073,
+1.15436,2.81813,0.653114,
+-2.55058,-2.56486,0.756618,
+0.959794,0.845224,-0.854001,
+-2.15033,0.248556,-3.16151,
+0.353224,-1.68637,0.457949,
+-0.753237,-2.48313,-0.355373,
+0.296585,2.04822,0.198473,
+-2.4737,0.682952,1.85349,
+1.9027,0.882796,1.45908,
+0.254799,1.92572,-1.70848,
+-0.951602,-0.698987,-2.22682,
+0.262582,-2.44093,1.05636,
+0.385415,-0.685667,-3.35928,
+0.055981,0.523585,-3.36093,
+-0.0518635,-0.889068,-0.840648,
+0.455171,2.84624,-1.98276,
+2.85475,-0.685697,-2.45695,
+-1.05047,-1.92121,0.931666,
+-2.75962,-0.164458,-1.55261,
+1.91811,1.65767,3.15004,
+-2.25653,0.856735,-4.351,
+1.89178,-0.728669,-3.8803,
+-3.25958,-1.16223,-1.85148,
+-1.6291,-2.32967,-0.874786,
+3.17524,0.327351,2.15337,
+-1.05094,-0.560694,-3.4581,
+0.759785,-1.0801,-0.257876,
+-0.4597,-2.2404,2.54341,
+0.242266,0.121832,7.80878,
+-1.6573,1.56049,-1.61749,
+1.39912,-2.84395,-0.25965,
+0.552342,1.36924,2.95278,
+-1.92932,-0.883981,0.0589583,
+-1.9026,0.56297,-0.723523,
+-1.25867,-0.529617,2.3503,
+2.16527,-0.723364,-1.45609,
+-0.984608,-0.720375,-2.05144,
+0.946803,-0.969188,-3.14625,
+2.7986,-2.36069,1.25827,
+3.37979,-1.11699,-3.55116,
+-0.753992,-1.9649,2.98548,
+-0.456321,0.525303,-0.109174,
+0.8642,1.53147,-3.34749,
+-0.450507,-0.443265,-2.8325,
+-3.24876,3.12791,0.576143,
+-3.14755,2.84502,-0.45749,
+0.226819,-1.28172,0.939501,
+-0.650725,-1.40317,-1.35211,
+-0.451625,1.84271,-0.950536,
+-1.36693,0.850218,-1.09799,
+2.35374,-0.28759,1.55815,
+2.60577,-2.46765,1.2633,
+1.21562,-1.72153,-0.4034,
+-2.43216,3.04268,1.90109,
+-1.85365,-0.16523,-2.98947,
+-0.953475,-2.76164,2.53396,
+2.55264,-0.645611,3.25479,
+-0.675476,-0.724382,-1.4566,
+0.801062,0.16778,2.21815,
+0.680443,0.0407888,3.27279,
+-3.75426,-0.247624,0.26307,
+0.427609,-0.322312,0.652005,
+-2.17428,-3.68643,0.256619,
+0.456732,-2.44858,3.13051,
+0.524175,-1.88231,-1.93171,
+7.2311,1.8354,-1.2502,
+2.69502,2.25543,-4.25127,
+0.259357,2.28099,-0.476734,
+-0.327316,-1.24908,0.78628,
+0.190362,-3.26019,0.0545844,
+-1.25409,-0.761609,2.65361,
+2.86816,0.443709,0.556137,
+2.60289,-0.680561,0.248414,
+1.75631,-0.162859,-3.62488,
+-0.559754,-0.16222,2.35858,
+0.157552,-1.72639,-0.48056,
+3.69492,-0.848265,-0.256413,
+-1.4264,-1.48589,-2.724,
+1.15372,-0.27228,-2.75499,
+-1.85159,1.76577,-0.858854,
+0.0580466,-0.41133,1.05649,
+-1.25289,0.528142,-0.386138,
+-0.858101,-0.245127,1.95078,
+-1.95302,-0.0862415,-1.95806,
+-2.79133,-0.527094,-0.356931,
+0.110597,0.167534,4.12784,
+-0.637771,-0.526587,-1.25734,
+-2.311,-0.489068,-2.8594,
+-0.352617,-1.23939,-1.85435,
+1.78814,-0.265883,3.35913,
+2.75557,-2.32271,3.15559,
+2.85426,0.443661,0.921828,
+-2.14262,0.400863,1.11614,
+4.45496,-0.488668,-3.7533,
+0.754719,-1.44165,0.853323,
+-0.856506,1.76559,1.05702,
+-0.418565,-0.921031,-2.43699,
+-1.29292,-0.282271,-1.62927,
+-0.759531,0.566692,-0.750991,
+0.559787,1.72479,-0.26667,
+2.75533,-0.245187,-0.543844,
+-2.27924,-1.04154,-4.05156,
+3.35852,-0.561129,-2.98986,
+2.41843,-0.321119,-1.55651,
+0.85431,-0.883719,-2.17826,
+0.417867,0.242995,-0.456326,
+-4.17449,-0.720086,0.355145,
+-0.577386,0.0440364,-0.950268,
+-2.8156,2.92346,0.958713,
+-1.65138,0.963561,1.25265,
+1.45117,0.845424,0.252789,
+2.27848,-1.76777,0.117707,
+-0.754248,1.08381,-2.15345,
+-3.15415,-0.162292,-1.15347,
+2.55396,-1.68912,1.15698,
+0.159561,1.84202,0.428428,
+-1.58079,2.04207,3.29578,
+-1.05711,-0.843112,-1.98122,
+0.138866,-2.96642,-0.212247,
+2.6778,0.686972,-2.6553,
+-0.498291,-0.362846,-1.0538,
+1.19872,0.0476518,-2.42077,
+-2.3972,1.40393,-0.1943,
+0.85034,-1.24222,1.5646,
+0.142174,0.249903,-2.75252,
+1.15197,1.32746,-1.70104,
+0.359387,-1.65115,-2.75243,
+0.357917,-1.36406,2.55102,
+-1.65268,-0.28339,2.75665,
+-3.82681,-3.28984,2.55128,
+-2.61371,1.08247,-0.457068,
+1.67152,-2.25527,2.45819,
+-0.753832,1.1682,-1.95913,
+-2.05131,3.04537,1.91954,
+-0.695378,2.88924,1.41965,
+3.25767,-2.76934,-0.354127,
+-0.746795,1.36034,1.85953,
+0.859135,1.08222,0.959116,
+1.15898,1.32443,3.41981,
+3.67954,0.447177,-0.414029,
+0.907323,2.12852,1.95837,
+-0.683456,-0.247536,1.05621,
+-1.67809,0.641544,-1.31143,
+-1.30192,-0.677394,-1.95159,
+-0.105665,0.365205,-1.2567,
+1.12189,-1.48969,0.957166,
+1.25554,-1.92186,-0.904086,
+0.117786,2.23836,-0.498009,
+-2.85788,0.643364,0.55867,
+-1.66115,2.56146,-2.28632,
+-3.29334,-0.0894367,-0.656519,
+3.15561,-0.769732,1.15695,
+1.25684,0.64652,-2.6002,
+2.65231,-3.52625,1.55617,
+-1.8573,-1.76276,-1.74075,
+-1.55648,1.2592,2.1585,
+-0.555522,2.88068,-2.85423,
+-3.14249,-0.288592,-3.35534,
+2.45413,0.0854903,-2.20507,
+-2.05464,0.887836,-0.658349,
+1.12708,-0.568106,-3.65865,
+-0.59147,-0.685439,0.301612,
+0.473679,0.886411,-1.65386,
+1.65166,0.166741,0.751119,
+-0.299287,1.72233,0.618559,
+1.15181,-0.488026,0.124448,
+0.0561315,1.04877,2.15922,
+0.154258,-0.64401,-1.31179,
+1.75863,-1.88571,-2.8537,
+3.35024,1.28154,-1.05461,
+-3.71738,-2.88631,-1.05314,
+-1.78258,3.08967,0.150476,
+3.47828,-2.524,-2.45502,
+-0.159138,0.160633,-0.338796,
+-2.15885,-0.82959,1.25022,
+-1.95268,-0.841195,-3.19487,
+-0.281381,-0.887435,-3.55807,
+0.415164,0.326482,-1.55411,
+1.92868,-2.84771,-0.556196,
+1.05804,1.32866,0.66596,
+2.46545,1.00747,2.35957,
+-0.77358,0.284677,-1.38805,
+0.851046,0.960742,-2.70934,
+-0.858208,-0.884015,1.16663,
+1.55291,0.282705,-3.18254,
+1.99034,0.286298,-1.75821,
+0.259097,-2.04379,-0.858936,
+-1.5956,2.04693,-1.35234,
+-0.775432,1.24465,1.95935,
+1.65595,-1.04954,-0.954437,
+-2.35417,-1.76095,-0.248306,
+0.855717,-2.92161,3.4496,
+-0.717941,2.52993,1.25007,
+-3.42927,0.673305,0.995742,
+8.03373,-2.05136,4.02113,
+7.88929,-2.2029,5.0036,
+7.61596,-2.44569,4.54892,
+7.41949,-2.64169,3.82339,
+7.21192,-2.87947,4.89423,
+7.08346,-2.38649,4.19212,
+6.90279,-3.33038,4.9273,
+6.78959,-3.55575,3.55127,
+6.57074,-3.75902,3.32517,
+6.30756,-3.40499,3.78366,
+7.90893,-3.3237,2.48012,
+6.24078,-4.04978,3.47459,
+8.0691,-4.6865,4.08057,
+5.98731,-2.26113,2.82166,
+5.46898,-3.99581,3.25024,
+8.1461,-3.34471,3.42168,
+6.8456,-5.00126,2.36145,
+7.62888,-3.82465,2.7879,
+7.18034,-3.94376,2.74348,
+6.02527,-4.36523,2.56247,
+8.64577,-3.04345,2.62974,
+7.82539,-3.6881,2.96164,
+7.10508,-3.6174,4.22416,
+7.64684,-3.88431,3.9336,
+7.46411,-4.21516,3.32001,
+6.98777,-3.94024,3.14694,
+7.36355,-4.24875,1.72061,
+8.42011,-4.52065,2.62078,
+6.04598,-4.48682,2.74494,
+8.7682,-3.9271,3.82815,
+6.76279,-3.58031,2.92525,
+7.14794,-4.42742,2.62365,
+5.32696,-3.28733,3.34916,
+6.43655,-4.60784,2.41548,
+5.66033,-4.98497,3.72282
diff --git a/tests/manual/custominput/main.cpp b/tests/manual/custominput/main.cpp
new file mode 100644
index 0000000..b0a346e
--- /dev/null
+++ b/tests/manual/custominput/main.cpp
@@ -0,0 +1,77 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "scatterdatamodifier.h"
+
+#include <QtWidgets/QApplication>
+#include <QtWidgets/QWidget>
+#include <QtWidgets/QHBoxLayout>
+#include <QtWidgets/QVBoxLayout>
+#include <QtWidgets/QPushButton>
+#include <QtWidgets/QCheckBox>
+#include <QtWidgets/QComboBox>
+#include <QtWidgets/QLabel>
+#include <QtWidgets/QMessageBox>
+#include <QtGui/QScreen>
+#include <QtGui/QFontDatabase>
+
+int main(int argc, char **argv)
+{
+ qputenv("QSG_RHI_BACKEND", "opengl");
+ QApplication app(argc, argv);
+ Q3DScatter *graph = new Q3DScatter();
+ QWidget *container = QWidget::createWindowContainer(graph);
+
+ if (!graph->hasContext()) {
+ QMessageBox msgBox;
+ msgBox.setText("Couldn't initialize the OpenGL context.");
+ msgBox.exec();
+ return -1;
+ }
+
+ QSize screenSize = graph->screen()->size();
+ container->setMinimumSize(QSize(screenSize.width() / 2, screenSize.height() / 1.5));
+ container->setMaximumSize(screenSize);
+ container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+ container->setFocusPolicy(Qt::StrongFocus);
+
+ QWidget *widget = new QWidget;
+ QHBoxLayout *hLayout = new QHBoxLayout(widget);
+ QVBoxLayout *vLayout = new QVBoxLayout();
+ hLayout->addWidget(container, 1);
+ hLayout->addLayout(vLayout);
+
+ widget->setWindowTitle(QStringLiteral("Custom Input Handling"));
+
+ QPushButton *cameraButton = new QPushButton(widget);
+ cameraButton->setText(QStringLiteral("Toggle Camera Animation"));
+
+ QComboBox *shadowQuality = new QComboBox(widget);
+ shadowQuality->addItem(QStringLiteral("None"));
+ shadowQuality->addItem(QStringLiteral("Low"));
+ shadowQuality->addItem(QStringLiteral("Medium"));
+ shadowQuality->addItem(QStringLiteral("High"));
+ shadowQuality->addItem(QStringLiteral("Low Soft"));
+ shadowQuality->addItem(QStringLiteral("Medium Soft"));
+ shadowQuality->addItem(QStringLiteral("High Soft"));
+ shadowQuality->setCurrentIndex(2);
+
+ vLayout->addWidget(cameraButton, 0, Qt::AlignTop);
+ vLayout->addWidget(new QLabel(QStringLiteral("Adjust shadow quality")), 0, Qt::AlignTop);
+ vLayout->addWidget(shadowQuality, 1, Qt::AlignTop);
+
+ ScatterDataModifier *modifier = new ScatterDataModifier(graph);
+
+ QObject::connect(cameraButton, &QPushButton::clicked, modifier,
+ &ScatterDataModifier::toggleCameraAnimation);
+
+ QObject::connect(shadowQuality, SIGNAL(currentIndexChanged(int)), modifier,
+ SLOT(changeShadowQuality(int)));
+
+ QObject::connect(modifier, &ScatterDataModifier::shadowQualityChanged, shadowQuality,
+ &QComboBox::setCurrentIndex);
+
+ widget->show();
+ modifier->start();
+ return app.exec();
+}
diff --git a/tests/manual/custominput/scatterdatamodifier.cpp b/tests/manual/custominput/scatterdatamodifier.cpp
new file mode 100644
index 0000000..c21768b
--- /dev/null
+++ b/tests/manual/custominput/scatterdatamodifier.cpp
@@ -0,0 +1,158 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "scatterdatamodifier.h"
+
+#include <QtGraphs/QScatterDataProxy>
+#include <QtGraphs/QValue3DAxis>
+#include <QtGraphs/Q3DScene>
+#include <QtGraphs/Q3DCamera>
+#include <QtGraphs/QScatter3DSeries>
+#include <QtGraphs/Q3DTheme>
+#include <QtCore/qmath.h>
+#include <QtCore/QFile>
+#include <QtCore/QTextStream>
+#include <QtCore/QDebug>
+
+ScatterDataModifier::ScatterDataModifier(Q3DScatter *scatter)
+ : m_graph(scatter),
+ m_inputHandler(new CustomInputHandler())
+{
+ m_graph->activeTheme()->setType(Q3DTheme::ThemeDigia);
+ m_graph->setShadowQuality(QAbstract3DGraph::ShadowQualityMedium);
+ m_graph->scene()->activeCamera()->setCameraPreset(Q3DCamera::CameraPresetFront);
+
+ m_graph->setAxisX(new QValue3DAxis);
+ m_graph->setAxisY(new QValue3DAxis);
+ m_graph->setAxisZ(new QValue3DAxis);
+
+ m_graph->axisX()->setRange(-10.0f, 10.0f);
+ m_graph->axisY()->setRange(-5.0f, 5.0f);
+ m_graph->axisZ()->setRange(-5.0f, 5.0f);
+
+ QScatter3DSeries *series = new QScatter3DSeries;
+ series->setItemLabelFormat(QStringLiteral("@xLabel, @yLabel, @zLabel"));
+ series->setMesh(QAbstract3DSeries::MeshCube);
+ series->setItemSize(0.15f);
+ m_graph->addSeries(series);
+
+ //! [2]
+ m_animationCameraX = new QPropertyAnimation(m_graph->scene()->activeCamera(), "xRotation");
+ m_animationCameraX->setDuration(20000);
+ m_animationCameraX->setStartValue(QVariant::fromValue(0.0f));
+ m_animationCameraX->setEndValue(QVariant::fromValue(360.0f));
+ m_animationCameraX->setLoopCount(-1);
+ //! [2]
+
+ //! [3]
+ QPropertyAnimation *upAnimation = new QPropertyAnimation(m_graph->scene()->activeCamera(), "yRotation");
+ upAnimation->setDuration(9000);
+ upAnimation->setStartValue(QVariant::fromValue(5.0f));
+ upAnimation->setEndValue(QVariant::fromValue(45.0f));
+
+ QPropertyAnimation *downAnimation = new QPropertyAnimation(m_graph->scene()->activeCamera(), "yRotation");
+ downAnimation->setDuration(9000);
+ downAnimation->setStartValue(QVariant::fromValue(45.0f));
+ downAnimation->setEndValue(QVariant::fromValue(5.0f));
+
+ m_animationCameraY = new QSequentialAnimationGroup();
+ m_animationCameraY->setLoopCount(-1);
+ m_animationCameraY->addAnimation(upAnimation);
+ m_animationCameraY->addAnimation(downAnimation);
+ //! [3]
+
+ m_animationCameraX->start();
+ m_animationCameraY->start();
+
+ // Give ownership of the handler to the graph and make it the active handler
+ //! [0]
+ m_graph->setActiveInputHandler(m_inputHandler);
+ //! [0]
+
+ //! [1]
+ m_selectionTimer = new QTimer(this);
+ m_selectionTimer->setInterval(10);
+ m_selectionTimer->setSingleShot(false);
+ QObject::connect(m_selectionTimer, &QTimer::timeout, this,
+ &ScatterDataModifier::triggerSelection);
+ m_selectionTimer->start();
+ //! [1]
+}
+
+ScatterDataModifier::~ScatterDataModifier()
+{
+ delete m_graph;
+}
+
+void ScatterDataModifier::start()
+{
+ addData();
+}
+
+void ScatterDataModifier::addData()
+{
+ QList<QVector3D> itemList;
+
+ // Read data items from the file to QList
+ QTextStream stream;
+ QFile dataFile(":/data/data.txt");
+ if (dataFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
+ stream.setDevice(&dataFile);
+ while (!stream.atEnd()) {
+ QString line = stream.readLine();
+ if (line.startsWith("#")) // Ignore comments
+ continue;
+ QStringList strList = line.split(",", Qt::SkipEmptyParts);
+ // Each line has three data items: xPos, yPos and zPos value
+ if (strList.size() < 3) {
+ qWarning() << "Invalid row read from data:" << line;
+ continue;
+ }
+ itemList.append(QVector3D(
+ strList.at(0).trimmed().toFloat(),
+ strList.at(1).trimmed().toFloat(),
+ strList.at(2).trimmed().toFloat()));
+ }
+ } else {
+ qWarning() << "Unable to open data file:" << dataFile.fileName();
+ }
+
+ // Add data from the QList to datamodel
+ QScatterDataArray *dataArray = new QScatterDataArray;
+ dataArray->resize(itemList.size());
+ QScatterDataItem *ptrToDataArray = &dataArray->first();
+ for (int i = 0; i < itemList.size(); i++) {
+ ptrToDataArray->setPosition(itemList.at(i));
+ ptrToDataArray++;
+ }
+
+ m_graph->seriesList().at(0)->dataProxy()->resetArray(dataArray);
+}
+
+void ScatterDataModifier::toggleCameraAnimation()
+{
+ if (m_animationCameraX->state() != QAbstractAnimation::Paused) {
+ m_animationCameraX->pause();
+ m_animationCameraY->pause();
+ } else {
+ m_animationCameraX->resume();
+ m_animationCameraY->resume();
+ }
+}
+
+void ScatterDataModifier::triggerSelection()
+{
+ m_graph->scene()->setSelectionQueryPosition(m_inputHandler->inputPosition());
+}
+
+void ScatterDataModifier::shadowQualityUpdatedByVisual(QAbstract3DGraph::ShadowQuality sq)
+{
+ int quality = int(sq);
+ emit shadowQualityChanged(quality); // connected to a checkbox in main.cpp
+}
+
+void ScatterDataModifier::changeShadowQuality(int quality)
+{
+ QAbstract3DGraph::ShadowQuality sq = QAbstract3DGraph::ShadowQuality(quality);
+ m_graph->setShadowQuality(sq);
+}
diff --git a/tests/manual/custominput/scatterdatamodifier.h b/tests/manual/custominput/scatterdatamodifier.h
new file mode 100644
index 0000000..413ccc4
--- /dev/null
+++ b/tests/manual/custominput/scatterdatamodifier.h
@@ -0,0 +1,43 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef SCATTERDATAMODIFIER_H
+#define SCATTERDATAMODIFIER_H
+
+#include "custominputhandler.h"
+
+#include <QtGraphs/q3dscatter.h>
+#include <QtGui/QFont>
+#include <QtCore/QTimer>
+#include <QtCore/QPropertyAnimation>
+#include <QtCore/QSequentialAnimationGroup>
+#include <QtGui/QVector3D>
+
+class ScatterDataModifier : public QObject
+{
+ Q_OBJECT
+public:
+ explicit ScatterDataModifier(Q3DScatter *scatter);
+ ~ScatterDataModifier();
+
+ void addData();
+ void toggleCameraAnimation();
+ void start();
+
+public Q_SLOTS:
+ void changeShadowQuality(int quality);
+ void shadowQualityUpdatedByVisual(QAbstract3DGraph::ShadowQuality shadowQuality);
+ void triggerSelection();
+
+Q_SIGNALS:
+ void shadowQualityChanged(int quality);
+
+private:
+ Q3DScatter *m_graph;
+ QPropertyAnimation *m_animationCameraX;
+ QSequentialAnimationGroup *m_animationCameraY;
+ CustomInputHandler *m_inputHandler;
+ QTimer *m_selectionTimer;
+};
+
+#endif
diff --git a/tests/manual/directional/CMakeLists.txt b/tests/manual/directional/CMakeLists.txt
new file mode 100644
index 0000000..81acc25
--- /dev/null
+++ b/tests/manual/directional/CMakeLists.txt
@@ -0,0 +1,16 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+qt_internal_add_manual_test(directional
+ GUI
+ SOURCES
+ main.cpp
+ scatterdatamodifier.cpp scatterdatamodifier.h
+ )
+target_link_libraries(directional PUBLIC
+ Qt::Gui
+ Qt::Widgets
+ Qt::Graphs
+ )
diff --git a/tests/manual/directional/directional.pro b/tests/manual/directional/directional.pro
new file mode 100644
index 0000000..25b2a1f
--- /dev/null
+++ b/tests/manual/directional/directional.pro
@@ -0,0 +1,8 @@
+!include( ../tests.pri ) {
+ error( "Couldn't find the tests.pri file!" )
+}
+
+SOURCES += main.cpp scatterdatamodifier.cpp
+HEADERS += scatterdatamodifier.h
+
+QT += widgets
diff --git a/tests/manual/directional/main.cpp b/tests/manual/directional/main.cpp
new file mode 100644
index 0000000..c420de2
--- /dev/null
+++ b/tests/manual/directional/main.cpp
@@ -0,0 +1,149 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "scatterdatamodifier.h"
+
+#include <QApplication>
+#include <QWidget>
+#include <QHBoxLayout>
+#include <QVBoxLayout>
+#include <QPushButton>
+#include <QCheckBox>
+#include <QComboBox>
+#include <QFontComboBox>
+#include <QLabel>
+#include <QScreen>
+#include <QFontDatabase>
+
+int main(int argc, char **argv)
+{
+ qputenv("QSG_RHI_BACKEND", "opengl");
+ QApplication app(argc, argv);
+ Q3DScatter *graph = new Q3DScatter();
+ QWidget *container = QWidget::createWindowContainer(graph);
+
+ QSize screenSize = graph->screen()->size();
+ container->setMinimumSize(QSize(screenSize.width() / 2, screenSize.height() / 1.5));
+ container->setMaximumSize(screenSize);
+ container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+ container->setFocusPolicy(Qt::StrongFocus);
+
+ QWidget *widget = new QWidget;
+ QHBoxLayout *hLayout = new QHBoxLayout(widget);
+ QVBoxLayout *vLayout = new QVBoxLayout();
+ hLayout->addWidget(container, 1);
+ hLayout->addLayout(vLayout);
+
+ widget->setWindowTitle(QStringLiteral("Directional scatter"));
+
+ QComboBox *themeList = new QComboBox(widget);
+ themeList->addItem(QStringLiteral("Qt"));
+ themeList->addItem(QStringLiteral("Primary Colors"));
+ themeList->addItem(QStringLiteral("Digia"));
+ themeList->addItem(QStringLiteral("Stone Moss"));
+ themeList->addItem(QStringLiteral("Army Blue"));
+ themeList->addItem(QStringLiteral("Retro"));
+ themeList->addItem(QStringLiteral("Ebony"));
+ themeList->addItem(QStringLiteral("Isabelle"));
+ themeList->setCurrentIndex(6);
+
+ QPushButton *labelButton = new QPushButton(widget);
+ labelButton->setText(QStringLiteral("Change label style"));
+
+ QComboBox *itemStyleList = new QComboBox(widget);
+ itemStyleList->addItem(QStringLiteral("Arrow"), int(QAbstract3DSeries::MeshArrow));
+ itemStyleList->addItem(QStringLiteral("Cube"), int(QAbstract3DSeries::MeshCube));
+ itemStyleList->addItem(QStringLiteral("Minimal"), int(QAbstract3DSeries::MeshMinimal));
+ itemStyleList->setCurrentIndex(-1);
+
+ QPushButton *cameraButton = new QPushButton(widget);
+ cameraButton->setText(QStringLiteral("Change camera preset"));
+
+ QPushButton *toggleRotationButton = new QPushButton(widget);
+ toggleRotationButton->setText(QStringLiteral("Toggle animation"));
+
+ QCheckBox *backgroundCheckBox = new QCheckBox(widget);
+ backgroundCheckBox->setText(QStringLiteral("Show background"));
+ backgroundCheckBox->setChecked(true);
+
+ QCheckBox *optimizationCheckBox = new QCheckBox(widget);
+ optimizationCheckBox->setText(QStringLiteral("Optimization static"));
+
+ QCheckBox *gridCheckBox = new QCheckBox(widget);
+ gridCheckBox->setText(QStringLiteral("Show grid"));
+ gridCheckBox->setChecked(true);
+
+ QComboBox *shadowQuality = new QComboBox(widget);
+ shadowQuality->addItem(QStringLiteral("None"));
+ shadowQuality->addItem(QStringLiteral("Low"));
+ shadowQuality->addItem(QStringLiteral("Medium"));
+ shadowQuality->addItem(QStringLiteral("High"));
+ shadowQuality->addItem(QStringLiteral("Low Soft"));
+ shadowQuality->addItem(QStringLiteral("Medium Soft"));
+ shadowQuality->addItem(QStringLiteral("High Soft"));
+ shadowQuality->setCurrentIndex(4);
+
+ QFontComboBox *fontList = new QFontComboBox(widget);
+ fontList->setCurrentFont(QFont("Arial"));
+
+ vLayout->addWidget(labelButton, 0, Qt::AlignTop);
+ vLayout->addWidget(cameraButton, 0, Qt::AlignTop);
+ vLayout->addWidget(toggleRotationButton, 0, Qt::AlignTop);
+ vLayout->addWidget(optimizationCheckBox);
+ vLayout->addWidget(backgroundCheckBox);
+ vLayout->addWidget(gridCheckBox);
+ vLayout->addWidget(new QLabel(QStringLiteral("Change dot style")));
+ vLayout->addWidget(itemStyleList);
+ vLayout->addWidget(new QLabel(QStringLiteral("Change theme")));
+ vLayout->addWidget(themeList);
+ vLayout->addWidget(new QLabel(QStringLiteral("Adjust shadow quality")));
+ vLayout->addWidget(shadowQuality);
+ vLayout->addWidget(new QLabel(QStringLiteral("Change font")));
+ vLayout->addWidget(fontList, 1, Qt::AlignTop);
+
+ ScatterDataModifier *modifier = new ScatterDataModifier(graph);
+
+ QObject::connect(cameraButton, &QPushButton::clicked, modifier,
+ &ScatterDataModifier::changePresetCamera);
+ QObject::connect(toggleRotationButton, &QPushButton::clicked, modifier,
+ &ScatterDataModifier::toggleRotation);
+ QObject::connect(labelButton, &QPushButton::clicked, modifier,
+ &ScatterDataModifier::changeLabelStyle);
+
+ QObject::connect(backgroundCheckBox, &QCheckBox::stateChanged, modifier,
+ &ScatterDataModifier::setBackgroundEnabled);
+ QObject::connect(gridCheckBox, &QCheckBox::stateChanged, modifier,
+ &ScatterDataModifier::setGridEnabled);
+
+ QObject::connect(modifier, &ScatterDataModifier::backgroundEnabledChanged,
+ backgroundCheckBox, &QCheckBox::setChecked);
+ QObject::connect(optimizationCheckBox, &QCheckBox::stateChanged,
+ modifier, &ScatterDataModifier::enableOptimization);
+ QObject::connect(modifier, &ScatterDataModifier::gridEnabledChanged,
+ gridCheckBox, &QCheckBox::setChecked);
+ QObject::connect(itemStyleList, SIGNAL(currentIndexChanged(int)), modifier,
+ SLOT(changeStyle(int)));
+
+ QObject::connect(themeList, SIGNAL(currentIndexChanged(int)), modifier,
+ SLOT(changeTheme(int)));
+
+ QObject::connect(shadowQuality, SIGNAL(currentIndexChanged(int)), modifier,
+ SLOT(changeShadowQuality(int)));
+
+ QObject::connect(modifier, &ScatterDataModifier::shadowQualityChanged, shadowQuality,
+ &QComboBox::setCurrentIndex);
+ QObject::connect(graph, &Q3DScatter::shadowQualityChanged, modifier,
+ &ScatterDataModifier::shadowQualityUpdatedByVisual);
+
+ QObject::connect(fontList, &QFontComboBox::currentFontChanged, modifier,
+ &ScatterDataModifier::changeFont);
+
+ QObject::connect(modifier, &ScatterDataModifier::fontChanged, fontList,
+ &QFontComboBox::setCurrentFont);
+
+ itemStyleList->setCurrentIndex(0);
+ optimizationCheckBox->setChecked(true);
+
+ widget->show();
+ return app.exec();
+}
diff --git a/tests/manual/directional/scatterdatamodifier.cpp b/tests/manual/directional/scatterdatamodifier.cpp
new file mode 100644
index 0000000..36894a7
--- /dev/null
+++ b/tests/manual/directional/scatterdatamodifier.cpp
@@ -0,0 +1,199 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "scatterdatamodifier.h"
+#include <QtGraphs/qscatterdataproxy.h>
+#include <QtGraphs/qvalue3daxis.h>
+#include <QtGraphs/q3dscene.h>
+#include <QtGraphs/q3dcamera.h>
+#include <QtGraphs/qscatter3dseries.h>
+#include <QtGraphs/q3dtheme.h>
+#include <qmath.h>
+#include <QComboBox>
+
+const int numberOfCols = 8;
+const int numberOfRows = 8;
+const float limit = 8.0f;
+#define HEDGEHOG
+
+ScatterDataModifier::ScatterDataModifier(Q3DScatter *scatter)
+ : m_graph(scatter),
+ m_fontSize(40.0f),
+ m_style(QAbstract3DSeries::MeshUserDefined),
+ m_smooth(true)
+{
+ m_graph->activeTheme()->setType(Q3DTheme::ThemeEbony);
+ QFont font = m_graph->activeTheme()->font();
+ font.setPointSize(m_fontSize);
+ m_graph->activeTheme()->setFont(font);
+ m_graph->setShadowQuality(QAbstract3DGraph::ShadowQualitySoftLow);
+ m_graph->scene()->activeCamera()->setCameraPreset(Q3DCamera::CameraPresetFront);
+
+ m_graph->setAxisX(new QValue3DAxis);
+ m_graph->setAxisY(new QValue3DAxis);
+ m_graph->setAxisZ(new QValue3DAxis);
+
+ QScatterDataProxy *proxy = new QScatterDataProxy;
+ QScatter3DSeries *series = new QScatter3DSeries(proxy);
+ series->setItemLabelFormat("@xTitle: @xLabel @yTitle: @yLabel @zTitle: @zLabel");
+ m_graph->addSeries(series);
+
+ QObject::connect(&m_rotationTimer, &QTimer::timeout, this,
+ &ScatterDataModifier::triggerRotation);
+
+ addData();
+}
+
+ScatterDataModifier::~ScatterDataModifier()
+{
+ delete m_graph;
+}
+
+void ScatterDataModifier::addData()
+{
+ // Configure the axes according to the data
+ m_graph->axisX()->setTitle("X");
+ m_graph->axisY()->setTitle("Y");
+ m_graph->axisZ()->setTitle("Z");
+ m_graph->axisX()->setRange(-limit, limit);
+ m_graph->axisY()->setRange(-1.0f, 1.0f);
+ m_graph->axisZ()->setRange(-limit, limit);
+
+ QScatterDataArray *dataArray = new QScatterDataArray;
+ dataArray->resize(numberOfCols * numberOfRows);
+ QScatterDataItem *ptrToDataArray = &dataArray->first();
+
+ float angleStep = 360.0f / float(numberOfCols);
+ float latAngleStep = 100.0f / float(numberOfRows);
+
+ for (float i = 0; i < numberOfRows; i++) {
+ float latAngle = float(i) * latAngleStep + 40.0f;
+ float radius = qSin(qDegreesToRadians(latAngle)) * limit;
+ float y = qCos(qDegreesToRadians(latAngle)) * 1.0f;
+#ifdef HEDGEHOG
+ float angleZ = qRadiansToDegrees(qAtan((y * limit / 2.0f) / radius));
+ QQuaternion rotationZ = QQuaternion::fromAxisAndAngle(QVector3D(0.0f, 0.0f, 1.0f), angleZ - 90.0f);
+#endif
+ for (float j = 0; j < numberOfCols; j++) {
+ float angle = float(j) * angleStep;
+ float x = qCos(qDegreesToRadians(angle)) * radius;
+ float z = qSin(qDegreesToRadians(angle)) * radius;
+
+ float angleY = qRadiansToDegrees(qAtan(z / x));
+ if (x < 0)
+ angleY = 180.0f + angleY;
+ if (x > 0 && z < 0)
+ angleY = 360.0f + angleY;
+#ifdef HEDGEHOG
+ QQuaternion rotationY = QQuaternion::fromAxisAndAngle(QVector3D(0.0f, 1.0f, 0.0f), angleY);
+ QQuaternion rotation = rotationY * rotationZ;
+#else
+ QQuaternion rotation = QQuaternion::fromAxisAndAngle(QVector3D(0.0f, 1.0f, 0.0f), angleY) *
+ QQuaternion::fromAxisAndAngle(QVector3D(1.0f, 0.0f, 0.0f), -90.0f);
+#endif
+
+ ptrToDataArray->setPosition(QVector3D(x, y, z));
+ ptrToDataArray->setRotation(rotation);
+ ptrToDataArray++;
+ }
+ }
+
+ m_graph->seriesList().at(0)->dataProxy()->resetArray(dataArray);
+}
+
+void ScatterDataModifier::enableOptimization(int enabled)
+{
+ if (enabled)
+ m_graph->setOptimizationHints(QAbstract3DGraph::OptimizationStatic);
+ else
+ m_graph->setOptimizationHints(QAbstract3DGraph::OptimizationDefault);
+}
+
+void ScatterDataModifier::changeStyle(int style)
+{
+ QComboBox *comboBox = qobject_cast<QComboBox *>(sender());
+ if (comboBox) {
+ m_style = QAbstract3DSeries::Mesh(comboBox->itemData(style).toInt());
+ if (m_graph->seriesList().size())
+ m_graph->seriesList().at(0)->setMesh(m_style);
+ }
+}
+
+void ScatterDataModifier::changeTheme(int theme)
+{
+ Q3DTheme *currentTheme = m_graph->activeTheme();
+ currentTheme->setType(Q3DTheme::Theme(theme));
+ emit backgroundEnabledChanged(currentTheme->isBackgroundEnabled());
+ emit gridEnabledChanged(currentTheme->isGridEnabled());
+ emit fontChanged(currentTheme->font());
+}
+
+void ScatterDataModifier::changePresetCamera()
+{
+ static int preset = Q3DCamera::CameraPresetFrontLow;
+
+ m_graph->scene()->activeCamera()->setCameraPreset((Q3DCamera::CameraPreset)preset);
+
+ if (++preset > Q3DCamera::CameraPresetDirectlyBelow)
+ preset = Q3DCamera::CameraPresetFrontLow;
+}
+
+void ScatterDataModifier::changeLabelStyle()
+{
+ m_graph->activeTheme()->setLabelBackgroundEnabled(!m_graph->activeTheme()->isLabelBackgroundEnabled());
+}
+
+void ScatterDataModifier::changeFont(const QFont &font)
+{
+ QFont newFont = font;
+ newFont.setPointSizeF(m_fontSize);
+ m_graph->activeTheme()->setFont(newFont);
+}
+
+void ScatterDataModifier::shadowQualityUpdatedByVisual(QAbstract3DGraph::ShadowQuality sq)
+{
+ int quality = int(sq);
+ emit shadowQualityChanged(quality); // connected to a checkbox in main.cpp
+}
+
+void ScatterDataModifier::triggerRotation()
+{
+ if (m_graph->seriesList().size()) {
+ int selectedIndex = m_graph->seriesList().at(0)->selectedItem();
+ if (selectedIndex != QScatter3DSeries::invalidSelectionIndex()) {
+ static float itemAngle = 0.0f;
+ QScatterDataItem item(*(m_graph->seriesList().at(0)->dataProxy()->itemAt(selectedIndex)));
+ QQuaternion itemRotation = QQuaternion::fromAxisAndAngle(0.0f, 0.0f, 1.0f, itemAngle++);
+ item.setRotation(itemRotation);
+ m_graph->seriesList().at(0)->dataProxy()->setItem(selectedIndex, item);
+ } else {
+ static float seriesAngle = 0.0f;
+ QQuaternion rotation = QQuaternion::fromAxisAndAngle(1.0f, 1.0f, 1.0f, seriesAngle++);
+ m_graph->seriesList().at(0)->setMeshRotation(rotation);
+ }
+ }
+}
+
+void ScatterDataModifier::changeShadowQuality(int quality)
+{
+ QAbstract3DGraph::ShadowQuality sq = QAbstract3DGraph::ShadowQuality(quality);
+ m_graph->setShadowQuality(sq);
+}
+
+void ScatterDataModifier::setBackgroundEnabled(int enabled)
+{
+ m_graph->activeTheme()->setBackgroundEnabled((bool)enabled);
+}
+
+void ScatterDataModifier::setGridEnabled(int enabled)
+{
+ m_graph->activeTheme()->setGridEnabled((bool)enabled);
+}
+
+void ScatterDataModifier::toggleRotation()
+{
+ if (m_rotationTimer.isActive())
+ m_rotationTimer.stop();
+ else
+ m_rotationTimer.start(20);
+}
diff --git a/tests/manual/directional/scatterdatamodifier.h b/tests/manual/directional/scatterdatamodifier.h
new file mode 100644
index 0000000..bad0f53
--- /dev/null
+++ b/tests/manual/directional/scatterdatamodifier.h
@@ -0,0 +1,52 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef SCATTERDATAMODIFIER_H
+#define SCATTERDATAMODIFIER_H
+
+#include <QtGraphs/q3dscatter.h>
+#include <QtGraphs/qabstract3dseries.h>
+#include <QtGui/QFont>
+#include <QtCore/QTimer>
+
+class ScatterDataModifier : public QObject
+{
+ Q_OBJECT
+public:
+ explicit ScatterDataModifier(Q3DScatter *scatter);
+ ~ScatterDataModifier();
+
+ void addData();
+ void changeStyle();
+ void changePresetCamera();
+ void changeLabelStyle();
+ void changeFont(const QFont &font);
+ void changeFontSize(int fontsize);
+ void enableOptimization(int enabled);
+ void setBackgroundEnabled(int enabled);
+ void setGridEnabled(int enabled);
+ void toggleRotation();
+ void start();
+
+public Q_SLOTS:
+ void changeStyle(int style);
+ void changeTheme(int theme);
+ void changeShadowQuality(int quality);
+ void shadowQualityUpdatedByVisual(QAbstract3DGraph::ShadowQuality shadowQuality);
+ void triggerRotation();
+
+Q_SIGNALS:
+ void backgroundEnabledChanged(bool enabled);
+ void gridEnabledChanged(bool enabled);
+ void shadowQualityChanged(int quality);
+ void fontChanged(const QFont &font);
+
+private:
+ Q3DScatter *m_graph;
+ int m_fontSize;
+ QAbstract3DSeries::Mesh m_style;
+ bool m_smooth;
+ QTimer m_rotationTimer;
+};
+
+#endif
diff --git a/tests/manual/galaxy/CMakeLists.txt b/tests/manual/galaxy/CMakeLists.txt
new file mode 100644
index 0000000..8367ea8
--- /dev/null
+++ b/tests/manual/galaxy/CMakeLists.txt
@@ -0,0 +1,14 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+qt_add_executable(galaxy
+ cumulativedistributor.cpp cumulativedistributor.h
+ galaxydata.cpp galaxydata.h
+ main.cpp
+ star.cpp star.h
+ )
+target_link_libraries(galaxy PUBLIC
+ Qt::Gui
+ Qt::Widgets
+ )
diff --git a/tests/manual/galaxy/cumulativedistributor.cpp b/tests/manual/galaxy/cumulativedistributor.cpp
new file mode 100644
index 0000000..2504892
--- /dev/null
+++ b/tests/manual/galaxy/cumulativedistributor.cpp
@@ -0,0 +1,129 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+/*
+ * Galaxy creation code obtained from http://beltoforion.de/galaxy/galaxy_en.html
+ * Thanks to Ingo Berg, great work.
+ * Licensed under a Creative Commons Attribution 3.0 License
+ * http://creativecommons.org/licenses/by/3.0/
+ */
+
+#include "cumulativedistributor.h"
+
+#include <QDebug>
+#include <math.h>
+
+CumulativeDistributor::CumulativeDistributor()
+ : m_pDistFun(NULL),
+ m_vM1(),
+ m_vY1(),
+ m_vX1(),
+ m_vM2(),
+ m_vY2(),
+ m_vX2()
+{
+}
+
+CumulativeDistributor::~CumulativeDistributor()
+{
+}
+
+void CumulativeDistributor::setupRealistic(qreal I0, qreal k, qreal a,
+ qreal RBulge, qreal min, qreal max,
+ int nSteps)
+{
+ m_fMin = min;
+ m_fMax = max;
+ m_nSteps = nSteps;
+
+ m_I0 = I0;
+ m_k = k;
+ m_a = a;
+ m_RBulge = RBulge;
+
+ m_pDistFun = &CumulativeDistributor::Intensity;
+
+ // build the distribution function
+ buildCDF(m_nSteps);
+}
+
+void CumulativeDistributor::buildCDF(int nSteps)
+{
+ qreal h = (m_fMax - m_fMin) / nSteps;
+ qreal x = 0, y = 0;
+
+ m_vX1.clear();
+ m_vY1.clear();
+ m_vX2.clear();
+ m_vM1.clear();
+ m_vM1.clear();
+
+ // Simpson rule for integration of the distribution function
+ m_vY1.push_back(0.0);
+ m_vX1.push_back(0.0);
+ for (int i = 0; i < nSteps; i += 2) {
+ x = (i + 2) * h;
+ y += h/3 * ((this->*m_pDistFun)(m_fMin + i*h) + 4*(this->*m_pDistFun)(m_fMin + (i+1)*h) + (this->*m_pDistFun)(m_fMin + (i+2)*h) );
+
+ m_vM1.push_back((y - m_vY1.back()) / (2*h));
+ m_vX1.push_back(x);
+ m_vY1.push_back(y);
+ }
+
+ // normieren
+ for (int i = 0; i < m_vM1.size(); i++) {
+ m_vY1[i] /= m_vY1.back();
+ m_vM1[i] /= m_vY1.back();
+ }
+
+ m_vY2.clear();
+ m_vM2.clear();
+ m_vY2.push_back(0.0);
+
+ qreal p = 0;
+ h = 1.0 / nSteps;
+ for (int i = 1, k = 0; i < nSteps; ++i) {
+ p = (qreal)i * h;
+
+ for (; m_vY1[k+1] <= p; ++k) {
+ }
+
+ y = m_vX1[k] + (p - m_vY1[k]) / m_vM1[k];
+
+ m_vM2.push_back( (y - m_vY2.back()) / h);
+ m_vX2.push_back(p);
+ m_vY2.push_back(y);
+ }
+}
+
+qreal CumulativeDistributor::valFromProp(qreal fVal)
+{
+ if (fVal < 0.0 || fVal > 1.0)
+ throw std::runtime_error("out of range");
+
+ qreal h = 1.0 / m_vY2.size();
+
+ int i = int(fVal / h);
+ qreal remainder = fVal - i*h;
+
+ int min = qMin(m_vY2.size(), m_vM2.size());
+ if (i >= min)
+ i = min - 1;
+
+ return (m_vY2[i] + m_vM2[i] * remainder);
+}
+
+qreal CumulativeDistributor::IntensityBulge(qreal R, qreal I0, qreal k)
+{
+ return I0 * exp(-k * pow(R, 0.25));
+}
+
+qreal CumulativeDistributor::IntensityDisc(qreal R, qreal I0, qreal a)
+{
+ return I0 * exp(-R/a);
+}
+
+qreal CumulativeDistributor::Intensity(qreal x)
+{
+ return (x < m_RBulge) ? IntensityBulge(x, m_I0, m_k) : IntensityDisc(x - m_RBulge, IntensityBulge(m_RBulge, m_I0, m_k), m_a);
+}
diff --git a/tests/manual/galaxy/cumulativedistributor.h b/tests/manual/galaxy/cumulativedistributor.h
new file mode 100644
index 0000000..5a125c0
--- /dev/null
+++ b/tests/manual/galaxy/cumulativedistributor.h
@@ -0,0 +1,61 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+/*
+ * Galaxy creation code obtained from http://beltoforion.de/galaxy/galaxy_en.html
+ * Thanks to Ingo Berg, great work.
+ * Licensed under a Creative Commons Attribution 3.0 License
+ * http://creativecommons.org/licenses/by/3.0/
+ */
+
+#ifndef CUMULATIVEDISTRIBUTOR_H
+#define CUMULATIVEDISTRIBUTOR_H
+
+#include <QtCore/qglobal.h>
+#include <QtCore/QList>
+#include <QtGui/QVector2D>
+
+class CumulativeDistributor
+{
+public:
+ typedef qreal (CumulativeDistributor::*dist_fun_t)(qreal x);
+
+ CumulativeDistributor();
+ virtual ~CumulativeDistributor();
+
+ qreal PropFromVal(qreal fVal);
+ qreal ValFromProp(qreal fVal);
+
+ void setupRealistic(qreal I0, qreal k, qreal a, qreal RBulge, qreal min,
+ qreal max, int nSteps);
+ qreal valFromProp(qreal fVal);
+
+private:
+ dist_fun_t m_pDistFun;
+ qreal m_fMin;
+ qreal m_fMax;
+ qreal m_fWidth;
+ int m_nSteps;
+
+ // parameters for realistic star distribution
+ qreal m_I0;
+ qreal m_k;
+ qreal m_a;
+ qreal m_RBulge;
+
+ QList<qreal> m_vM1;
+ QList<qreal> m_vY1;
+ QList<qreal> m_vX1;
+
+ QList<qreal> m_vM2;
+ QList<qreal> m_vY2;
+ QList<qreal> m_vX2;
+
+ void buildCDF(int nSteps);
+
+ qreal IntensityBulge(qreal R, qreal I0, qreal k);
+ qreal IntensityDisc(qreal R, qreal I0, qreal a);
+ qreal Intensity(qreal x);
+};
+
+#endif // CUMULATIVEDISTRIBUTOR_H
diff --git a/tests/manual/galaxy/galaxy.pro b/tests/manual/galaxy/galaxy.pro
new file mode 100644
index 0000000..5c35b86
--- /dev/null
+++ b/tests/manual/galaxy/galaxy.pro
@@ -0,0 +1,24 @@
+android|ios|winrt {
+ error( "This example is not supported for android, ios, or winrt." )
+}
+
+!include( ../tests.pri ) {
+ error( "Couldn't find the tests.pri file!" )
+}
+
+SOURCES += main.cpp \
+ galaxydata.cpp \
+ star.cpp \
+ cumulativedistributor.cpp
+
+HEADERS += \
+ cumulativedistributor.h \
+ galaxydata.h \
+ star.h
+
+QT += widgets
+CONFIG += exceptions
+
+OTHER_FILES += doc/src/* \
+ doc/images/*
+
diff --git a/tests/manual/galaxy/galaxydata.cpp b/tests/manual/galaxy/galaxydata.cpp
new file mode 100644
index 0000000..eab12e5
--- /dev/null
+++ b/tests/manual/galaxy/galaxydata.cpp
@@ -0,0 +1,463 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+/*
+ * Galaxy creation code obtained from http://beltoforion.de/galaxy/galaxy_en.html
+ * Thanks to Ingo Berg, great work.
+ * Licensed under a Creative Commons Attribution 3.0 License
+ * http://creativecommons.org/licenses/by/3.0/
+ */
+
+#include "galaxydata.h"
+#include "cumulativedistributor.h"
+#include "star.h"
+#include <QtGraphs/qscatterdataproxy.h>
+#include <QtGraphs/qvalue3daxis.h>
+#include <QtGraphs/q3dscene.h>
+#include <QtGraphs/q3dcamera.h>
+#include <QtGraphs/qscatter3dseries.h>
+#include <QtGraphs/q3dtheme.h>
+#include <QtCore/qmath.h>
+#include <QtCore/qrandom.h>
+#include <QPainter>
+
+#include <QDebug>
+
+using namespace QtGraphs;
+
+static const int numOfStars = 70000;
+static const int numOfDust = numOfStars / 2;
+static const int numOfH2 = 200;
+
+GalaxyData::GalaxyData(Q3DScatter *scatter,
+ qreal rad,
+ qreal radCore,
+ qreal deltaAng,
+ qreal ex1,
+ qreal ex2)
+ : m_graph(scatter),
+ m_pStars(0),
+ m_pDust(0),
+ m_pH2(0),
+ m_radGalaxy(rad),
+ m_radCore(radCore),
+ m_angleOffset(deltaAng),
+ m_elEx1(ex1),
+ m_elEx2(ex2),
+ m_radFarField(m_radGalaxy * 2),
+ m_filtered(false),
+ m_minx(9999.9),
+ m_maxx(-9999.0),
+ m_miny(9999.9),
+ m_maxy(-9999.0)
+{
+ m_graph->activeTheme()->setType(Q3DTheme::ThemeEbony);
+ m_graph->setShadowQuality(QAbstract3DGraph::ShadowQualitySoftLow);
+
+ m_graph->axisX()->setRange(-25000.0f, 25000.0f);
+ m_graph->axisZ()->setRange(-25000.0f, 25000.0f);
+ m_graph->setOptimizationHints(QAbstract3DGraph::OptimizationStatic);
+ m_graph->activeTheme()->setBackgroundColor(QColor(31, 31, 31));
+ m_graph->activeTheme()->setWindowColor(QColor(31, 31, 31));
+ m_graph->activeTheme()->setLabelBackgroundColor(QColor(31, 31, 31));
+ m_graph->activeTheme()->setLabelTextColor(QColor(31, 31, 31));
+
+ QObject::connect(m_graph, &QAbstract3DGraph::currentFpsChanged,
+ this, &GalaxyData::handleFpsChange);
+ m_graph->setMeasureFps(true);
+
+ createSeries();
+ createGalaxy();
+}
+
+GalaxyData::~GalaxyData()
+{
+ delete m_graph;
+ if (m_pStars)
+ delete [] m_pStars;
+ if (m_pDust)
+ delete [] m_pDust;
+ if (m_pH2)
+ delete [] m_pH2;
+}
+
+void GalaxyData::createGalaxy()
+{
+ if (m_pStars)
+ delete [] m_pStars;
+ m_pStars = new Star[numOfStars];
+
+ if (m_pDust)
+ delete [] m_pDust;
+ m_pDust = new Star[numOfDust];
+
+ if (m_pH2)
+ delete [] m_pH2;
+ m_pH2 = new Star[numOfH2 * 2];
+
+
+ m_minx = 9999.9;
+ m_maxx = -9999.0;
+ m_miny = 9999.9;
+ m_maxy = -9999.0;
+
+ // First star is the black hole at the center
+ m_pStars[0].m_a = 0;
+ m_pStars[0].m_b = 0;
+ m_pStars[0].m_angle = 0;
+ m_pStars[0].m_theta = 0;
+ m_pStars[0].m_center = QVector2D(0.0f, 0.0f);
+ m_pStars[0].calcXY();
+
+ // second star is at the edge of the core area
+ m_pStars[1].m_a = m_radCore;
+ m_pStars[1].m_b = m_radCore * getExcentricity(m_radCore);
+ m_pStars[1].m_angle = getAngularOffset(m_radCore);
+ m_pStars[1].m_theta = 0;
+ m_pStars[1].m_center = QVector2D(0.0f, 0.0f);
+ m_pStars[1].calcXY();
+ checkMinMax(m_pStars[1]);
+
+ // third star is at the edge of the disk
+ m_pStars[2].m_a = m_radGalaxy;
+ m_pStars[2].m_b = m_radGalaxy * getExcentricity(m_radGalaxy);
+ m_pStars[2].m_angle = getAngularOffset(m_radGalaxy);
+ m_pStars[2].m_theta = 0;
+ m_pStars[2].m_center = QVector2D(0.0f, 0.0f);
+ m_pStars[2].calcXY();
+ checkMinMax(m_pStars[2]);
+
+ CumulativeDistributor cd;
+ cd.setupRealistic(1.0, // Maximalintensität
+ 0.02, // k (bulge)
+ m_radGalaxy/3.0, // disc skalenlänge
+ m_radCore, // bulge radius
+ 0, // start der intensitätskurve
+ m_radFarField, // ende der intensitätskurve
+ 1000.0); // Anzahl der stützstellen
+
+ for (int i = 3; i < numOfStars; ++i) {
+ qreal rad = cd.valFromProp(QRandomGenerator::global()->generateDouble());
+
+ m_pStars[i].m_a = rad;
+ m_pStars[i].m_b = rad * getExcentricity(rad);
+ m_pStars[i].m_angle = getAngularOffset(rad);
+ m_pStars[i].m_theta = 360.0 * QRandomGenerator::global()->generateDouble();
+ m_pStars[i].m_center = QVector2D(0.0f, 0.0f);
+ m_pStars[i].calcXY();
+
+ checkMinMax(m_pStars[i]);
+ }
+
+ // Initialize Dust
+ qreal x, y, rad;
+ for (int i = 0; i < numOfDust; ++i)
+ {
+ x = 2.0 * m_radGalaxy * QRandomGenerator::global()->generateDouble() - m_radGalaxy;
+ y = 2.0 * m_radGalaxy * QRandomGenerator::global()->generateDouble() - m_radGalaxy;
+ rad = sqrt(x*x + y*y);
+
+ m_pDust[i].m_a = rad;
+ m_pDust[i].m_b = rad * getExcentricity(rad);
+ m_pDust[i].m_angle = getAngularOffset(rad);
+ m_pDust[i].m_theta = 360.0 * QRandomGenerator::global()->generateDouble();
+ m_pDust[i].m_center = QVector2D(0.0f, 0.0f);
+ m_pDust[i].calcXY();
+
+ checkMinMax(m_pDust[i]);
+ }
+
+ // Initialize H2
+ for (int i = 0; i < numOfH2; ++i)
+ {
+ x = 2*(m_radGalaxy) * QRandomGenerator::global()->generateDouble() - (m_radGalaxy);
+ y = 2*(m_radGalaxy) * QRandomGenerator::global()->generateDouble() - (m_radGalaxy);
+ rad = sqrt(x*x + y*y);
+
+ int k1 = 2*i;
+ m_pH2[k1].m_a = rad;
+ m_pH2[k1].m_b = rad * getExcentricity(rad);
+ m_pH2[k1].m_angle = getAngularOffset(rad);
+ m_pH2[k1].m_theta = 360.0 * QRandomGenerator::global()->generateDouble();
+ m_pH2[k1].m_center = QVector2D(0.0f, 0.0f);
+ m_pH2[k1].calcXY();
+
+ int k2 = 2*i + 1;
+ m_pH2[k2].m_a = rad + 1000.0;
+ m_pH2[k2].m_b = rad * getExcentricity(rad);
+ m_pH2[k2].m_angle = m_pH2[k1].m_angle;
+ m_pH2[k2].m_theta = m_pH2[k1].m_theta;
+ m_pH2[k2].m_center = m_pH2[k1].m_center;
+ m_pH2[k2].calcXY();
+ }
+
+ qreal max = qMax(m_maxx, m_maxy);
+ qreal min = -qMin(m_minx, m_miny);
+ max = qMax(min, max);
+ m_range = int((max + 500.0) / 1000.0) * 1000;
+ m_graph->axisX()->setRange(-float(m_range), float(m_range));
+ m_graph->axisZ()->setRange(-float(m_range), float(m_range));
+
+ if (!m_filtered)
+ createNormalDataView();
+ else
+ createFilteredView();
+}
+
+void GalaxyData::createSeries()
+{
+ QScatterDataProxy *proxyNormal = new QScatterDataProxy;
+ m_normalSeries = new QScatter3DSeries(proxyNormal);
+ m_normalSeries->setMesh(QAbstract3DSeries::MeshPoint);
+ m_graph->addSeries(m_normalSeries);
+
+ QScatterDataProxy *proxyDust = new QScatterDataProxy;
+ m_dustSeries = new QScatter3DSeries(proxyDust);
+ m_dustSeries->setMesh(QAbstract3DSeries::MeshPoint);
+ m_graph->addSeries(m_dustSeries);
+
+ QScatterDataProxy *proxyH2 = new QScatterDataProxy;
+ m_H2Series = new QScatter3DSeries(proxyH2);
+ m_H2Series->setMesh(QAbstract3DSeries::MeshPoint);
+ m_graph->addSeries(m_H2Series);
+
+ QScatterDataProxy *proxyFiltered = new QScatterDataProxy;
+ m_filteredSeries = new QScatter3DSeries(proxyFiltered);
+ m_filteredSeries->setMesh(QAbstract3DSeries::MeshCube);
+ m_graph->addSeries(m_filteredSeries);
+
+}
+
+void GalaxyData::createNormalDataView()
+{
+ QScatterDataArray *dataArray = new QScatterDataArray;
+ dataArray->resize(numOfStars);
+ QScatterDataItem *ptrToDataArray = &dataArray->first();
+
+ for (uint i = 0; i < numOfStars; i++) {
+ ptrToDataArray->setPosition(QVector3D(m_pStars[i].m_pos.x(),
+ 0.0f,
+ m_pStars[i].m_pos.y()));
+ ptrToDataArray++;
+ }
+
+ m_normalSeries->dataProxy()->resetArray(dataArray);
+ m_normalSeries->setMesh(QAbstract3DSeries::MeshPoint);
+ m_normalSeries->setBaseColor(Qt::white);
+
+ dataArray = new QScatterDataArray;
+ dataArray->resize(numOfDust);
+ ptrToDataArray = &dataArray->first();
+
+ for (uint i = 0; i < numOfDust; i++) {
+ ptrToDataArray->setPosition(QVector3D(m_pDust[i].m_pos.x(),
+ 0.0f,
+ m_pDust[i].m_pos.y()));
+ ptrToDataArray++;
+ }
+
+ m_dustSeries->dataProxy()->resetArray(dataArray);
+ m_dustSeries->setMesh(QAbstract3DSeries::MeshPoint);
+ m_dustSeries->setBaseColor(QColor(131, 111, 255));
+
+ dataArray = new QScatterDataArray;
+ dataArray->resize(numOfDust);
+ ptrToDataArray = &dataArray->first();
+
+ uint H2Count = numOfH2 * 2;
+ for (uint i = 0; i < H2Count; i++) {
+ ptrToDataArray->setPosition(QVector3D(m_pH2[i].m_pos.x(),
+ 0.0f,
+ m_pH2[i].m_pos.y()));
+ ptrToDataArray++;
+ }
+
+ m_H2Series->dataProxy()->resetArray(dataArray);
+ m_H2Series->setMesh(QAbstract3DSeries::MeshPoint);
+ m_H2Series->setBaseColor(Qt::red);
+}
+
+void GalaxyData::createFilteredView()
+{
+ int steps = (m_range / 1000) * 2;
+ int tableSize = steps * steps;
+ int *table = new int[tableSize];
+ for (int i = 0; i < tableSize; i++)
+ table[i] = 0;
+ qreal add = qreal(m_range);
+ int max = 0;
+
+ for (uint i = 0; i < numOfStars; i++) {
+ int x = int(m_pStars[i].m_pos.x() + add) / 1000;
+ int y = int(m_pStars[i].m_pos.y() + add) / 1000;
+ table[y * steps + x] = table[y * steps + x] + 1;
+
+ if (max < table[y * steps + x])
+ max = table[y * steps + x];
+ }
+
+ // Count how many cells have data
+ int nActiveCell = 0;
+ for (int i = 0; i < tableSize; i++) {
+ if (table[i])
+ nActiveCell++;
+ }
+
+
+ QScatterDataArray *dataArray = new QScatterDataArray;
+ dataArray->resize(nActiveCell);
+ QScatterDataItem *ptrToDataArray = &dataArray->first();
+
+ for (int y = 0; y < steps; y++) {
+ for (int x = 0; x < steps; x++) {
+ if (table[y * steps + x]) {
+ ptrToDataArray->setPosition(QVector3D(float(x) * 1000.0f - add + 500.0f,
+ float(table[y * steps + x]),
+ float(y) * 1000.0f - add + 500.0f));
+ ptrToDataArray++;
+ }
+ }
+ }
+
+ m_filteredSeries->dataProxy()->resetArray(dataArray);
+ m_filteredSeries->setMesh(QAbstract3DSeries::MeshCube);
+ m_filteredSeries->setItemSize(0.1f);
+
+ m_graph->axisY()->setRange(0.0f, float(max + 1));
+
+ qDebug() << "max = " << max;
+}
+
+void GalaxyData::checkMinMax(const Star &star)
+{
+ if (star.m_pos.x() < m_minx)
+ m_minx = star.m_pos.x();
+ if (star.m_pos.x() > m_maxx)
+ m_maxx = star.m_pos.x();
+ if (star.m_pos.y() < m_miny)
+ m_miny = star.m_pos.y();
+ if (star.m_pos.y() > m_maxy)
+ m_maxy = star.m_pos.y();
+}
+
+qreal GalaxyData::getExcentricity(qreal r) const
+{
+ if (r < m_radCore)
+ {
+ // Core region of the galaxy. Innermost part is round
+ // excentricity increasing linear to the border of the core.
+ return 1 + (r / m_radCore) * (m_elEx1-1);
+ } else if (r > m_radCore && r <= m_radGalaxy) {
+ return m_elEx1 + (r - m_radCore) / (m_radGalaxy - m_radCore) * (m_elEx2 - m_elEx1);
+ } else if (r > m_radGalaxy && r < m_radFarField) {
+ // excentricity is slowly reduced to 1.
+ return m_elEx2 + (r - m_radGalaxy) / (m_radFarField - m_radGalaxy) * (1 - m_elEx2);
+ } else {
+ return 1.0;
+ }
+}
+
+qreal GalaxyData::getAngularOffset(qreal rad) const
+{
+ return rad * m_angleOffset;
+}
+
+void GalaxyData::radiusGalaxyChanged(int value)
+{
+ m_radGalaxy = qreal(value);
+ createGalaxy();
+}
+
+void GalaxyData::radiusCoreChanged(int value)
+{
+ m_radCore = qreal(value);
+ createGalaxy();
+}
+
+void GalaxyData::angleOffsetChanged(int value)
+{
+ m_angleOffset = qreal(value) / 100000.0;
+ createGalaxy();
+}
+
+void GalaxyData::eccentricityInnerChanged(int value)
+{
+ m_elEx1 = qreal(value) / 100.0;
+ createGalaxy();
+}
+
+void GalaxyData::eccentricityOuterChanged(int value)
+{
+ m_elEx2 = qreal(value) / 100.0;
+ createGalaxy();
+}
+
+void GalaxyData::setFilteredEnabled(bool enabled)
+{
+ m_filtered = enabled;
+ if (enabled) {
+ QLinearGradient gr(0, 0, 1, 100);
+ gr.setColorAt(0.0, Qt::white);
+ gr.setColorAt(0.04, Qt::green);
+ gr.setColorAt(0.1, Qt::darkGreen);
+ gr.setColorAt(1.0, Qt::red);
+
+ m_filteredSeries->setBaseGradient(gr);
+ m_filteredSeries->setColorStyle(Q3DTheme::ColorStyleRangeGradient);
+
+ m_normalSeries->setVisible(false);
+
+ createFilteredView();
+
+ m_filteredSeries->setVisible(true);
+ } else {
+ m_normalSeries->setColorStyle(Q3DTheme::ColorStyleUniform);
+ m_graph->axisY()->setRange(-1.0f, 1.0f);
+ m_normalSeries->setItemSize(0.0f);
+
+ m_filteredSeries->setVisible(false);
+
+ createNormalDataView();
+
+ m_normalSeries->setVisible(true);
+ }
+}
+
+
+void GalaxyData::setStaticEnabled(bool enabled)
+{
+ if (enabled)
+ m_graph->setOptimizationHints(QAbstract3DGraph::OptimizationStatic);
+ else
+ m_graph->setOptimizationHints(QAbstract3DGraph::OptimizationDefault);
+}
+
+void GalaxyData::setStarsVisible(bool enabled)
+{
+ m_normalSeries->setVisible(enabled);
+}
+
+void GalaxyData::setDustVisible(bool enabled)
+{
+ m_dustSeries->setVisible(enabled);
+}
+
+void GalaxyData::setH2Visible(bool enabled)
+{
+ m_H2Series->setVisible(enabled);
+}
+
+void GalaxyData::resetValues()
+{
+ m_radiusGalaxySlider->setValue(15000);
+ m_radiusCoreSlider->setValue(4000);
+ m_angleOffsetSlider->setValue(40);
+ m_eccentricityInnerSlider->setValue(90);
+ m_eccentricityOuterSlider->setValue(90);
+}
+
+void GalaxyData::handleFpsChange(qreal fps)
+{
+ static const QString fpsPrefix(QStringLiteral("FPS: "));
+ m_fpsLabel->setText(fpsPrefix + QString::number(qRound(fps)));
+}
diff --git a/tests/manual/galaxy/galaxydata.h b/tests/manual/galaxy/galaxydata.h
new file mode 100644
index 0000000..b944936
--- /dev/null
+++ b/tests/manual/galaxy/galaxydata.h
@@ -0,0 +1,99 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef SCATTERDATAMODIFIER_H
+#define SCATTERDATAMODIFIER_H
+
+#include "star.h"
+
+#include <QtGraphs/q3dscatter.h>
+#include <QtGraphs/qabstract3dseries.h>
+#include <QtGui/QFont>
+#include <QtWidgets/QSlider>
+#include <QtWidgets/QLabel>
+
+using namespace QtGraphs;
+
+class GalaxyData : public QObject
+{
+ Q_OBJECT
+public:
+ explicit GalaxyData(Q3DScatter *scatter,
+ qreal rad = 13000,
+ qreal radCore = 4000.0,
+ qreal deltaAng = 0.0004,
+ qreal ex1 = 0.9,
+ qreal ex2 = 0.9);
+ ~GalaxyData();
+
+ qreal getExcentricity(qreal r) const;
+ qreal getAngularOffset(qreal rad) const;
+
+ void radiusGalaxyChanged(int value);
+ void radiusCoreChanged(int value);
+ void angleOffsetChanged(int value);
+ void eccentricityInnerChanged(int value);
+ void eccentricityOuterChanged(int value);
+ void resetValues();
+ void setFilteredEnabled(bool enabled);
+ void setStaticEnabled(bool enabled);
+ void setStarsVisible(bool enabled);
+ void setDustVisible(bool enabled);
+ void setH2Visible(bool enabled);
+ inline void setSliders(QSlider *rg,
+ QSlider *rc,
+ QSlider *ao,
+ QSlider *ei,
+ QSlider *eo) {
+ m_radiusGalaxySlider = rg;
+ m_radiusCoreSlider = rc;
+ m_angleOffsetSlider = ao;
+ m_eccentricityInnerSlider = ei;
+ m_eccentricityOuterSlider = eo;
+ }
+ void setFpsLabel(QLabel *fpsLabel) { m_fpsLabel = fpsLabel; }
+ void handleFpsChange(qreal fps);
+
+private:
+ void createGalaxy();
+ void checkMinMax(const Star &star);
+ void createNormalDataView();
+ void createFilteredView();
+ void createSeries();
+ qreal value;
+
+private:
+ Q3DScatter *m_graph;
+ QScatter3DSeries *m_normalSeries;
+ QScatter3DSeries *m_dustSeries;
+ QScatter3DSeries *m_H2Series;
+ QScatter3DSeries *m_filteredSeries;
+ Star *m_pStars;
+ Star *m_pDust;
+ Star *m_pH2;
+
+ qreal m_elEx1; // Excentricity of the innermost ellipse
+ qreal m_elEx2; // Excentricity of the outermost ellipse
+
+ qreal m_angleOffset; // Angular offset per parsec
+
+ qreal m_radCore; // Radius of the inner core
+ qreal m_radGalaxy; // Radius of the galaxy
+ qreal m_radFarField; // The radius after which all density waves must have circular shape
+
+ QSlider *m_radiusGalaxySlider;
+ QSlider *m_radiusCoreSlider;
+ QSlider *m_angleOffsetSlider;
+ QSlider *m_eccentricityInnerSlider;
+ QSlider *m_eccentricityOuterSlider;
+ QLabel *m_fpsLabel;
+
+ qreal m_minx;
+ qreal m_maxx;
+ qreal m_miny;
+ qreal m_maxy;
+ int m_range;
+ bool m_filtered;
+};
+
+#endif
diff --git a/tests/manual/galaxy/main.cpp b/tests/manual/galaxy/main.cpp
new file mode 100644
index 0000000..7740441
--- /dev/null
+++ b/tests/manual/galaxy/main.cpp
@@ -0,0 +1,145 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "galaxydata.h"
+
+#include <QtWidgets/QApplication>
+#include <QtWidgets/QWidget>
+#include <QtWidgets/QHBoxLayout>
+#include <QtWidgets/QVBoxLayout>
+#include <QtWidgets/QPushButton>
+#include <QtWidgets/QCheckBox>
+#include <QtWidgets/QComboBox>
+#include <QtWidgets/QFontComboBox>
+#include <QtWidgets/QLabel>
+#include <QtGui/QScreen>
+#include <QtGui/QFontDatabase>
+
+int main(int argc, char **argv)
+{
+ //! [0]
+ QApplication app(argc, argv);
+ Q3DScatter *graph = new Q3DScatter();
+ QWidget *container = QWidget::createWindowContainer(graph);
+ //! [0]
+
+ QSize screenSize = graph->screen()->size();
+ container->setMinimumSize(QSize(screenSize.width() / 2, screenSize.height() / 1.5));
+ container->setMaximumSize(screenSize);
+ container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+ container->setFocusPolicy(Qt::StrongFocus);
+
+ QWidget *widget = new QWidget;
+ QHBoxLayout *hLayout = new QHBoxLayout(widget);
+ QVBoxLayout *vLayout = new QVBoxLayout();
+ hLayout->addWidget(container, 1);
+ hLayout->addLayout(vLayout);
+ vLayout->setAlignment(Qt::AlignTop);
+
+ widget->setWindowTitle(QStringLiteral("A Galaxy"));
+
+ QSlider *radiusGalaxySlider = new QSlider(Qt::Horizontal, widget);
+ radiusGalaxySlider->setMinimum(1000);
+ radiusGalaxySlider->setMaximum(40000);
+ radiusGalaxySlider->setValue(15000);
+ radiusGalaxySlider->setEnabled(true);
+
+ QSlider *radiusCoreSlider = new QSlider(Qt::Horizontal, widget);
+ radiusCoreSlider->setMinimum(1000);
+ radiusCoreSlider->setMaximum(30000);
+ radiusCoreSlider->setValue(4000);
+ radiusCoreSlider->setEnabled(true);
+
+ QSlider *angleOffsetSlider = new QSlider(Qt::Horizontal, widget);
+ angleOffsetSlider->setMinimum(10);
+ angleOffsetSlider->setMaximum(70);
+ angleOffsetSlider->setValue(40);
+ angleOffsetSlider->setEnabled(true);
+
+ QSlider *eccentricityInnerSlider = new QSlider(Qt::Horizontal, widget);
+ eccentricityInnerSlider->setMinimum(10);
+ eccentricityInnerSlider->setMaximum(200);
+ eccentricityInnerSlider->setValue(90);
+ eccentricityInnerSlider->setEnabled(true);
+
+ QSlider *eccentricityOuterSlider = new QSlider(Qt::Horizontal, widget);
+ eccentricityOuterSlider->setMinimum(10);
+ eccentricityOuterSlider->setMaximum(200);
+ eccentricityOuterSlider->setValue(90);
+ eccentricityOuterSlider->setEnabled(true);
+
+ QCheckBox *staticCheckBox = new QCheckBox(widget);
+ staticCheckBox->setText(QStringLiteral("Static"));
+ staticCheckBox->setChecked(false);
+
+ QCheckBox *starsCheckBox = new QCheckBox(widget);
+ starsCheckBox->setText(QStringLiteral("Stars"));
+ starsCheckBox->setChecked(true);
+
+ QCheckBox *dustCheckBox = new QCheckBox(widget);
+ dustCheckBox->setText(QStringLiteral("Dust"));
+ dustCheckBox->setChecked(true);
+
+ QCheckBox *H2CheckBox = new QCheckBox(widget);
+ H2CheckBox->setText(QStringLiteral("H2"));
+ H2CheckBox->setChecked(true);
+
+ QPushButton *resetButton = new QPushButton(widget);
+ resetButton->setText(QStringLiteral("Reset values"));
+
+ QCheckBox *filteredCheckBox = new QCheckBox(widget);
+ filteredCheckBox->setText(QStringLiteral("Filtered"));
+ filteredCheckBox->setChecked(false);
+
+ QLabel *fpsLabel = new QLabel(QStringLiteral(""));
+
+ vLayout->addWidget(new QLabel(QStringLiteral("Galaxy radius")));
+ vLayout->addWidget(radiusGalaxySlider);
+ vLayout->addWidget(new QLabel(QStringLiteral("Core radius")));
+ vLayout->addWidget(radiusCoreSlider);
+ vLayout->addWidget(new QLabel(QStringLiteral("Angle offset")));
+ vLayout->addWidget(angleOffsetSlider);
+ vLayout->addWidget(new QLabel(QStringLiteral("Eccentricity inner")));
+ vLayout->addWidget(eccentricityInnerSlider);
+ vLayout->addWidget(new QLabel(QStringLiteral("Eccentricity outer")));
+ vLayout->addWidget(eccentricityOuterSlider);
+ vLayout->addWidget(staticCheckBox);
+ vLayout->addWidget(starsCheckBox);
+ vLayout->addWidget(dustCheckBox);
+ vLayout->addWidget(H2CheckBox);
+ vLayout->addWidget(resetButton);
+ vLayout->addWidget(filteredCheckBox);
+ vLayout->addWidget(fpsLabel);
+
+ GalaxyData *modifier = new GalaxyData(graph);
+
+ QObject::connect(radiusGalaxySlider, &QSlider::valueChanged,
+ modifier, &GalaxyData::radiusGalaxyChanged);
+ QObject::connect(radiusCoreSlider, &QSlider::valueChanged,
+ modifier, &GalaxyData::radiusCoreChanged);
+ QObject::connect(angleOffsetSlider, &QSlider::valueChanged,
+ modifier, &GalaxyData::angleOffsetChanged);
+ QObject::connect(eccentricityInnerSlider, &QSlider::valueChanged,
+ modifier, &GalaxyData::eccentricityInnerChanged);
+ QObject::connect(eccentricityOuterSlider, &QSlider::valueChanged,
+ modifier, &GalaxyData::eccentricityOuterChanged);
+ QObject::connect(resetButton, &QPushButton::clicked,
+ modifier, &GalaxyData::resetValues);
+ QObject::connect(filteredCheckBox, &QCheckBox::stateChanged,
+ modifier, &GalaxyData::setFilteredEnabled);
+ QObject::connect(staticCheckBox, &QCheckBox::stateChanged,
+ modifier, &GalaxyData::setStaticEnabled);
+ QObject::connect(starsCheckBox, &QCheckBox::stateChanged,
+ modifier, &GalaxyData::setStarsVisible);
+ QObject::connect(dustCheckBox, &QCheckBox::stateChanged,
+ modifier, &GalaxyData::setDustVisible);
+ QObject::connect(H2CheckBox, &QCheckBox::stateChanged,
+ modifier, &GalaxyData::setH2Visible);
+
+ modifier->setSliders(radiusGalaxySlider, radiusCoreSlider, angleOffsetSlider,
+ eccentricityInnerSlider, eccentricityOuterSlider);
+ modifier->setFpsLabel(fpsLabel);
+
+ widget->show();
+ return app.exec();
+}
diff --git a/tests/manual/galaxy/star.cpp b/tests/manual/galaxy/star.cpp
new file mode 100644
index 0000000..15831d6
--- /dev/null
+++ b/tests/manual/galaxy/star.cpp
@@ -0,0 +1,34 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "star.h"
+
+#include <QtCore/qmath.h>
+
+Star::Star()
+ : m_theta(0),
+ m_a(0),
+ m_b(0),
+ m_center(QVector2D(0.0f, 0.0f))
+{
+}
+
+const void Star::calcXY()
+{
+ qreal &a = m_a;
+ qreal &b = m_b;
+ qreal &theta = m_theta;
+ const QVector2D &p = m_center;
+
+ qreal beta = -m_angle;
+ qreal alpha = qDegreesToRadians(theta);
+
+ // temporaries to save cpu time
+ qreal cosalpha = qCos(alpha);
+ qreal sinalpha = qSin(alpha);
+ qreal cosbeta = qCos(beta);
+ qreal sinbeta = qSin(beta);
+
+ m_pos = QVector2D(p.x() + (a * cosalpha * cosbeta - b * sinalpha * sinbeta),
+ p.y() + (a * cosalpha * sinbeta + b * sinalpha * cosbeta));
+}
diff --git a/tests/manual/galaxy/star.h b/tests/manual/galaxy/star.h
new file mode 100644
index 0000000..ce25b53
--- /dev/null
+++ b/tests/manual/galaxy/star.h
@@ -0,0 +1,28 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef STAR_H
+#define STAR_H
+
+#include <QtCore/qglobal.h>
+#include <QtGui/QVector2D>
+
+class Star
+{
+public:
+ Star();
+ const void calcXY();
+
+ qreal m_theta; // position auf der ellipse
+// qreal m_velTheta; // angular velocity
+ qreal m_angle; // Schräglage der Ellipse
+ qreal m_a; // kleine halbachse
+ qreal m_b; // große halbachse
+// qreal m_temp; // star temperature
+// qreal m_mag; // brightness;
+ QVector2D m_center; // center of the elliptical orbit
+// QVector2D m_vel; // Current velocity (calculated)
+ QVector2D m_pos; // current position in kartesion koordinates
+};
+
+#endif // STAR_H
diff --git a/tests/manual/itemmodel/CMakeLists.txt b/tests/manual/itemmodel/CMakeLists.txt
new file mode 100644
index 0000000..f8f1573
--- /dev/null
+++ b/tests/manual/itemmodel/CMakeLists.txt
@@ -0,0 +1,17 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+qt_internal_add_manual_test(itemmodel
+ GUI
+ SOURCES
+ main.cpp
+ )
+
+target_link_libraries(itemmodel PUBLIC
+ Qt::Core
+ Qt::Gui
+ Qt::Widgets
+ Qt::Graphs
+)
diff --git a/tests/manual/itemmodel/itemmodel.pro b/tests/manual/itemmodel/itemmodel.pro
new file mode 100644
index 0000000..60b36b9
--- /dev/null
+++ b/tests/manual/itemmodel/itemmodel.pro
@@ -0,0 +1,15 @@
+android|ios|winrt {
+ error( "This example is not supported for android, ios, or winrt." )
+}
+
+!include( ../examples.pri ) {
+ error( "Couldn't find the examples.pri file!" )
+}
+
+SOURCES += main.cpp
+
+QT += widgets
+requires(qtConfig(tablewidget))
+
+OTHER_FILES += doc/src/* \
+ doc/images/*
diff --git a/tests/manual/itemmodel/main.cpp b/tests/manual/itemmodel/main.cpp
new file mode 100644
index 0000000..d3ad4dc
--- /dev/null
+++ b/tests/manual/itemmodel/main.cpp
@@ -0,0 +1,279 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtGraphs/q3dbars.h>
+#include <QtGraphs/qcategory3daxis.h>
+#include <QtGraphs/qitemmodelbardataproxy.h>
+#include <QtGraphs/qvalue3daxis.h>
+#include <QtGraphs/q3dscene.h>
+#include <QtGraphs/q3dcamera.h>
+#include <QtGraphs/qbar3dseries.h>
+#include <QtGraphs/q3dtheme.h>
+
+#include <QtWidgets/QApplication>
+#include <QtWidgets/QVBoxLayout>
+#include <QtWidgets/QTableWidget>
+#include <QtGui/QScreen>
+#include <QtCore/QRandomGenerator>
+#include <QtCore/QTimer>
+#include <QtGui/QFont>
+#include <QtCore/QDebug>
+#include <QtWidgets/QHeaderView>
+#include <QtWidgets/QMessageBox>
+
+#define USE_STATIC_DATA
+
+class GraphDataGenerator : public QObject
+{
+public:
+ explicit GraphDataGenerator(Q3DBars *bargraph, QTableWidget *tableWidget);
+ ~GraphDataGenerator();
+
+ void setupModel();
+ void addRow();
+ void changeStyle();
+ void changePresetCamera();
+ void changeTheme();
+ void start();
+ void selectFromTable(const QPoint &selection);
+ void selectedFromTable(int currentRow, int currentColumn, int previousRow, int previousColumn);
+ void fixTableSize();
+
+private:
+ Q3DBars *m_graph;
+ QTimer *m_dataTimer;
+ int m_columnCount;
+ int m_rowCount;
+ QTableWidget *m_tableWidget; // not owned
+};
+
+GraphDataGenerator::GraphDataGenerator(Q3DBars *bargraph, QTableWidget *tableWidget)
+ : m_graph(bargraph),
+ m_dataTimer(0),
+ m_columnCount(100),
+ m_rowCount(50),
+ m_tableWidget(tableWidget)
+{
+ //! [5]
+ // Set up bar specifications; make the bars as wide as they are deep,
+ // and add a small space between them
+ m_graph->setBarThickness(1.0f);
+ m_graph->setBarSpacing(QSizeF(0.2, 0.2));
+
+ //! [5]
+#ifndef USE_STATIC_DATA
+ // Set up sample space; make it as deep as it's wide
+ m_graph->rowAxis()->setRange(0, m_rowCount);
+ m_graph->columnAxis()->setRange(0, m_columnCount);
+ m_tableWidget->setColumnCount(m_columnCount);
+
+ // Set selection mode to full
+ m_graph->setSelectionMode(QAbstract3DGraph::SelectionItemRowAndColumn);
+
+ // Hide axis labels by explicitly setting one empty string as label list
+ m_graph->rowAxis()->setLabels(QStringList(QString()));
+ m_graph->columnAxis()->setLabels(QStringList(QString()));
+
+ m_graph->seriesList().at(0)->setItemLabelFormat(QStringLiteral("@valueLabel"));
+#else
+ //! [6]
+ // Set selection mode to slice row
+ m_graph->setSelectionMode(QAbstract3DGraph::SelectionItemAndRow | QAbstract3DGraph::SelectionSlice);
+
+ //! [6]
+#endif
+
+ //! [7]
+ // Set theme
+ m_graph->activeTheme()->setType(Q3DTheme::ThemeDigia);
+
+ // Set font
+ QFont font = QFont("Impact", 20);
+ font.setStyleHint(QFont::SansSerif);
+ m_graph->activeTheme()->setFont(font);
+
+ // Set preset camera position
+ m_graph->scene()->activeCamera()->setCameraPreset(Q3DCamera::CameraPresetFront);
+ //! [7]
+}
+
+GraphDataGenerator::~GraphDataGenerator()
+{
+ if (m_dataTimer) {
+ m_dataTimer->stop();
+ delete m_dataTimer;
+ }
+ delete m_graph;
+}
+
+void GraphDataGenerator::start()
+{
+#ifndef USE_STATIC_DATA
+ m_dataTimer = new QTimer();
+ m_dataTimer->setTimerType(Qt::CoarseTimer);
+ QObject::connect(m_dataTimer, &QTimer::timeout, this, &GraphDataGenerator::addRow);
+ m_dataTimer->start(0);
+ m_tableWidget->setFixedWidth(m_graph->width());
+#else
+ //! [8]
+ setupModel();
+
+ // Table needs to be shown before the size of its headers can be accurately obtained,
+ // so we postpone it a bit
+ m_dataTimer = new QTimer();
+ m_dataTimer->setSingleShot(true);
+ QObject::connect(m_dataTimer, &QTimer::timeout, this, &GraphDataGenerator::fixTableSize);
+ m_dataTimer->start(0);
+ //! [8]
+#endif
+}
+
+void GraphDataGenerator::setupModel()
+{
+ //! [9]
+ // Set up row and column names
+ QStringList days;
+ days << "Monday" << "Tuesday" << "Wednesday" << "Thursday" << "Friday" << "Saturday" << "Sunday";
+ QStringList weeks;
+ weeks << "week 1" << "week 2" << "week 3" << "week 4" << "week 5";
+
+ // Set up data Mon Tue Wed Thu Fri Sat Sun
+ float hours[5][7] = {{2.0f, 1.0f, 3.0f, 0.2f, 1.0f, 5.0f, 10.0f}, // week 1
+ {0.5f, 1.0f, 3.0f, 1.0f, 2.0f, 2.0f, 3.0f}, // week 2
+ {1.0f, 1.0f, 2.0f, 1.0f, 4.0f, 4.0f, 4.0f}, // week 3
+ {0.0f, 1.0f, 0.0f, 0.0f, 2.0f, 2.0f, 0.3f}, // week 4
+ {3.0f, 3.0f, 6.0f, 2.0f, 2.0f, 1.0f, 1.0f}}; // week 5
+ //! [9]
+
+ // Add labels
+ //! [10]
+ m_graph->rowAxis()->setTitle("Week of year");
+ m_graph->rowAxis()->setTitleVisible(true);
+ m_graph->columnAxis()->setTitle("Day of week");
+ m_graph->columnAxis()->setTitleVisible(true);
+ m_graph->valueAxis()->setTitle("Hours spent on the Internet");
+ m_graph->valueAxis()->setTitleVisible(true);
+ m_graph->valueAxis()->setLabelFormat("%.1f h");
+ //! [10]
+
+ //! [11]
+ m_tableWidget->setRowCount(5);
+ m_tableWidget->setColumnCount(7);
+ m_tableWidget->setHorizontalHeaderLabels(days);
+ m_tableWidget->setVerticalHeaderLabels(weeks);
+ m_tableWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ m_tableWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ m_tableWidget->setCurrentCell(-1, -1);
+ m_tableWidget->setSelectionMode(QAbstractItemView::SingleSelection);
+ //! [11]
+
+ //! [12]
+ for (int week = 0; week < weeks.size(); week++) {
+ for (int day = 0; day < days.size(); day++) {
+ QModelIndex index = m_tableWidget->model()->index(week, day);
+ m_tableWidget->model()->setData(index, hours[week][day]);
+ }
+ }
+ //! [12]
+}
+
+void GraphDataGenerator::addRow()
+{
+ m_tableWidget->model()->insertRow(0);
+ if (m_tableWidget->model()->rowCount() > m_rowCount)
+ m_tableWidget->model()->removeRow(m_rowCount);
+ for (int i = 0; i < m_columnCount; i++) {
+ QModelIndex index = m_tableWidget->model()->index(0, i);
+ m_tableWidget->model()->setData(index,
+ ((float)i / (float)m_columnCount) / 2.0f +
+ (float)(QRandomGenerator::global()->bounded(30)) / 100.0f);
+ }
+ m_tableWidget->resizeColumnsToContents();
+}
+
+//! [13]
+void GraphDataGenerator::selectFromTable(const QPoint &selection)
+{
+ m_tableWidget->setFocus();
+ m_tableWidget->setCurrentCell(selection.x(), selection.y());
+}
+//! [13]
+
+//! [14]
+void GraphDataGenerator::selectedFromTable(int currentRow, int currentColumn,
+ int previousRow, int previousColumn)
+{
+ Q_UNUSED(previousRow);
+ Q_UNUSED(previousColumn);
+ m_graph->seriesList().at(0)->setSelectedBar(QPoint(currentRow, currentColumn));
+}
+//! [14]
+
+void GraphDataGenerator::fixTableSize()
+{
+ int width = m_tableWidget->horizontalHeader()->length();
+ width += m_tableWidget->verticalHeader()->width();
+ m_tableWidget->setFixedWidth(width + 2);
+ int height = m_tableWidget->verticalHeader()->length();
+ height += m_tableWidget->horizontalHeader()->height();
+ m_tableWidget->setFixedHeight(height + 2);
+}
+
+int main(int argc, char **argv)
+{
+ qputenv("QSG_RHI_BACKEND", "opengl");
+ //! [0]
+ QApplication app(argc, argv);
+ Q3DBars *graph = new Q3DBars();
+ QWidget *container = QWidget::createWindowContainer(graph);
+ //! [0]
+
+ if (!graph->hasContext()) {
+ QMessageBox msgBox;
+ msgBox.setText("Couldn't initialize the OpenGL context.");
+ msgBox.exec();
+ return -1;
+ }
+
+ QSize screenSize = graph->screen()->size();
+ container->setMinimumSize(QSize(screenSize.width() / 2, screenSize.height() / 2));
+ container->setMaximumSize(screenSize);
+ container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+ container->setFocusPolicy(Qt::StrongFocus);
+
+ //! [1]
+ QWidget widget;
+ QVBoxLayout *layout = new QVBoxLayout(&widget);
+ QTableWidget *tableWidget = new QTableWidget(&widget);
+ layout->addWidget(container, 1);
+ layout->addWidget(tableWidget, 1, Qt::AlignHCenter);
+ //! [1]
+
+ tableWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
+ tableWidget->setAlternatingRowColors(true);
+ widget.setWindowTitle(QStringLiteral("Hours spent on the Internet"));
+
+ //! [2]
+ // Since we are dealing with QTableWidget, the model will already have data sorted properly
+ // into rows and columns, so we simply set useModelCategories property to true to utilize this.
+ QItemModelBarDataProxy *proxy = new QItemModelBarDataProxy(tableWidget->model());
+ proxy->setUseModelCategories(true);
+ QBar3DSeries *series = new QBar3DSeries(proxy);
+ series->setMesh(QAbstract3DSeries::MeshPyramid);
+ graph->addSeries(series);
+ //! [2]
+
+ //! [3]
+ GraphDataGenerator generator(graph, tableWidget);
+ QObject::connect(series, &QBar3DSeries::selectedBarChanged, &generator,
+ &GraphDataGenerator::selectFromTable);
+ QObject::connect(tableWidget, &QTableWidget::currentCellChanged, &generator,
+ &GraphDataGenerator::selectedFromTable);
+ //! [3]
+
+ //! [4]
+ widget.show();
+ generator.start();
+ return app.exec();
+ //! [4]
+}
diff --git a/tests/manual/itemmodeltest/CMakeLists.txt b/tests/manual/itemmodeltest/CMakeLists.txt
new file mode 100644
index 0000000..764d788
--- /dev/null
+++ b/tests/manual/itemmodeltest/CMakeLists.txt
@@ -0,0 +1,13 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+qt_internal_add_manual_test(itemmodeltest
+ GUI
+ SOURCES
+ main.cpp
+ )
+target_link_libraries(itemmodeltest PUBLIC
+ Qt::Gui
+ Qt::Widgets
+ Qt::Graphs
+ )
diff --git a/tests/manual/itemmodeltest/itemmodeltest.pro b/tests/manual/itemmodeltest/itemmodeltest.pro
new file mode 100644
index 0000000..47f1291
--- /dev/null
+++ b/tests/manual/itemmodeltest/itemmodeltest.pro
@@ -0,0 +1,7 @@
+!include( ../tests.pri ) {
+ error( "Couldn't find the tests.pri file!" )
+}
+
+SOURCES += main.cpp
+
+QT += widgets
diff --git a/tests/manual/itemmodeltest/main.cpp b/tests/manual/itemmodeltest/main.cpp
new file mode 100644
index 0000000..b3e59ec
--- /dev/null
+++ b/tests/manual/itemmodeltest/main.cpp
@@ -0,0 +1,300 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtGraphs/q3dbars.h>
+#include <QtGraphs/q3dsurface.h>
+#include <QtGraphs/qcategory3daxis.h>
+#include <QtGraphs/qitemmodelbardataproxy.h>
+#include <QtGraphs/qitemmodelsurfacedataproxy.h>
+#include <QtGraphs/qvalue3daxis.h>
+#include <QtGraphs/q3dscene.h>
+#include <QtGraphs/q3dcamera.h>
+#include <QtGraphs/qbar3dseries.h>
+#include <QtGraphs/q3dtheme.h>
+
+#include <QtWidgets/QApplication>
+#include <QtWidgets/QVBoxLayout>
+#include <QtWidgets/QTableWidget>
+#include <QtGui/QScreen>
+#include <QtCore/QTimer>
+#include <QtCore/QRandomGenerator>
+#include <QtCore/QDebug>
+#include <QtWidgets/QHeaderView>
+#include <QtWidgets/QPushButton>
+
+#define USE_STATIC_DATA
+
+class GraphDataGenerator : public QObject
+{
+public:
+ explicit GraphDataGenerator(Q3DBars *bargraph, Q3DSurface * surfaceGraph,
+ QTableWidget *tableWidget);
+ ~GraphDataGenerator();
+
+ void setupModel();
+ void addRow();
+ void start();
+ void selectFromTable(const QPoint &selection);
+ void selectedFromTable(int currentRow, int currentColumn, int previousRow, int previousColumn);
+ void fixTableSize();
+ void changeSelectedButtonClicked();
+
+private:
+ Q3DBars *m_barGraph;
+ Q3DSurface *m_surfaceGraph;
+ QTimer *m_dataTimer;
+ QTimer *m_styleTimer;
+ QTimer *m_presetTimer;
+ QTimer *m_themeTimer;
+ int m_columnCount;
+ int m_rowCount;
+ QTableWidget *m_tableWidget; // not owned
+};
+
+GraphDataGenerator::GraphDataGenerator(Q3DBars *bargraph, Q3DSurface * surfaceGraph,
+ QTableWidget *tableWidget)
+ : m_barGraph(bargraph),
+ m_surfaceGraph(surfaceGraph),
+ m_dataTimer(0),
+ m_styleTimer(0),
+ m_presetTimer(0),
+ m_themeTimer(0),
+ m_columnCount(100),
+ m_rowCount(50),
+ m_tableWidget(tableWidget)
+{
+ // Set up bar specifications; make the bars as wide as they are deep,
+ // and add a small space between them
+ m_barGraph->setBarThickness(1.0f);
+ m_barGraph->setBarSpacing(QSizeF(0.2, 0.2));
+
+#ifndef USE_STATIC_DATA
+ // Set up sample space; make it as deep as it's wide
+ m_barGraph->rowAxis()->setRange(0, m_rowCount);
+ m_barGraph->columnAxis()->setRange(0, m_columnCount);
+ m_tableWidget->setColumnCount(m_columnCount);
+
+ // Set selection mode to full
+ m_barGraph->setSelectionMode(QAbstract3DGraph::SelectionItemRowAndColumn);
+
+ // Hide axis labels by explicitly setting one empty string as label list
+ m_barGraph->rowAxis()->setLabels(QStringList(QString()));
+ m_barGraph->columnAxis()->setLabels(QStringList(QString()));
+
+ m_barGraph->seriesList().at(0)->setItemLabelFormat(QStringLiteral("@valueLabel"));
+#else
+ // Set selection mode to slice row
+ m_barGraph->setSelectionMode(
+ QAbstract3DGraph::SelectionItemAndRow | QAbstract3DGraph::SelectionSlice);
+ m_surfaceGraph->setSelectionMode(
+ QAbstract3DGraph::SelectionItemAndRow | QAbstract3DGraph::SelectionSlice);
+#endif
+}
+
+GraphDataGenerator::~GraphDataGenerator()
+{
+ if (m_dataTimer) {
+ m_dataTimer->stop();
+ delete m_dataTimer;
+ }
+ delete m_barGraph;
+ delete m_surfaceGraph;
+}
+
+void GraphDataGenerator::start()
+{
+#ifndef USE_STATIC_DATA
+ m_dataTimer = new QTimer();
+ m_dataTimer->setTimerType(Qt::CoarseTimer);
+ QObject::connect(m_dataTimer, &QTimer::timeout, this, &GraphDataGenerator::addRow);
+ m_dataTimer->start(0);
+ m_tableWidget->setFixedWidth(m_graph->width());
+#else
+ setupModel();
+
+ // Table needs to be shown before the size of its headers can be accurately obtained,
+ // so we postpone it a bit
+ m_dataTimer = new QTimer();
+ m_dataTimer->setSingleShot(true);
+ QObject::connect(m_dataTimer, &QTimer::timeout, this, &GraphDataGenerator::fixTableSize);
+ m_dataTimer->start(0);
+#endif
+}
+
+void GraphDataGenerator::setupModel()
+{
+ // Set up row and column names
+ QStringList days;
+ days << "Monday" << "Tuesday" << "Wednesday" << "Thursday" << "Friday" << "Saturday" << "Sunday";
+ QStringList weeks;
+ weeks << "week 1" << "week 2" << "week 3" << "week 4" << "week 5";
+
+ // Set up data
+ const char *hours[5][7] =
+ // Mon Tue Wed Thu Fri Sat Sun
+ {{"9/10/2.0/30", "9/11/1.0/30", "9/12/3.0/30", "9/13/0.2/30", "9/14/1.0/30", "9/15/5.0/30", "9/16/10.0/30"}, // week 1
+ {"8/10/0.5/45", "8/11/1.0/45", "8/12/3.0/45", "8/13/1.0/45", "8/14/2.0/45", "8/15/2.0/45", "8/16/3.0/45"}, // week 2
+ {"7/10/1.0/60", "7/11/1.0/60", "7/12/2.0/60", "7/13/1.0/60", "7/14/4.0/60", "7/15/4.0/60", "7/16/4.0/60"}, // week 3
+ {"6/10/0.0/75", "6/11/1.0/75", "6/12/0.0/75", "6/13/0.0/75", "6/14/2.0/75", "6/15/2.0/75", "6/16/0.3/75"}, // week 4
+ {"5/10/3.0/90", "5/11/3.0/90", "5/12/6.0/90", "5/13/2.0/90", "5/14/2.0/90", "5/15/1.0/90", "5/16/1.0/90"}}; // week 5
+
+ // Add labels
+ m_barGraph->rowAxis()->setTitle("Week of year");
+ m_barGraph->columnAxis()->setTitle("Day of week");
+ m_barGraph->valueAxis()->setTitle("Hours spent on the Internet");
+ m_barGraph->valueAxis()->setLabelFormat("%.1f h");
+
+ m_surfaceGraph->axisZ()->setTitle("Week of year");
+ m_surfaceGraph->axisX()->setTitle("Day of week");
+ m_surfaceGraph->axisY()->setTitle("Hours spent on the Internet");
+ m_surfaceGraph->axisY()->setLabelFormat("%.1f h");
+
+ m_tableWidget->setRowCount(5);
+ m_tableWidget->setColumnCount(7);
+ m_tableWidget->setHorizontalHeaderLabels(days);
+ m_tableWidget->setVerticalHeaderLabels(weeks);
+ m_tableWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ m_tableWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ m_tableWidget->setCurrentCell(-1, -1);
+
+ for (int week = 0; week < weeks.size(); week++) {
+ for (int day = 0; day < days.size(); day++) {
+ QModelIndex index = m_tableWidget->model()->index(week, day);
+ m_tableWidget->model()->setData(index, hours[week][day]);
+ }
+ }
+}
+
+void GraphDataGenerator::addRow()
+{
+ m_tableWidget->model()->insertRow(0);
+ if (m_tableWidget->model()->rowCount() > m_rowCount)
+ m_tableWidget->model()->removeRow(m_rowCount);
+ for (int i = 0; i < m_columnCount; i++) {
+ QModelIndex index = m_tableWidget->model()->index(0, i);
+ m_tableWidget->model()->setData(index,
+ ((float)i / (float)m_columnCount) / 2.0f
+ + QRandomGenerator::global()->bounded(30.0 / 100.0f));
+ }
+ m_tableWidget->resizeColumnsToContents();
+}
+
+void GraphDataGenerator::selectFromTable(const QPoint &selection)
+{
+ m_tableWidget->setFocus();
+ m_tableWidget->setCurrentCell(selection.x(), selection.y());
+}
+
+void GraphDataGenerator::selectedFromTable(int currentRow, int currentColumn,
+ int previousRow, int previousColumn)
+{
+ Q_UNUSED(previousRow);
+ Q_UNUSED(previousColumn);
+ m_barGraph->seriesList().at(0)->setSelectedBar(QPoint(currentRow, currentColumn));
+ m_surfaceGraph->seriesList().at(0)->setSelectedPoint(QPoint(currentRow, currentColumn));
+}
+
+void GraphDataGenerator::fixTableSize()
+{
+ int width = m_tableWidget->horizontalHeader()->length();
+ width += m_tableWidget->verticalHeader()->width();
+ m_tableWidget->setFixedWidth(width + 2);
+ int height = m_tableWidget->verticalHeader()->length();
+ height += m_tableWidget->horizontalHeader()->height();
+ m_tableWidget->setFixedHeight(height + 2);
+}
+
+void GraphDataGenerator::changeSelectedButtonClicked()
+{
+ // Change all selected cells to a random value 1-10
+ QVariant value = QVariant::fromValue(QRandomGenerator::global()->bounded(10.0) + 1);
+ QList<QTableWidgetItem *> selectedItems = m_tableWidget->selectedItems();
+ foreach (QTableWidgetItem *item, selectedItems) {
+ QString oldData = item->data(Qt::DisplayRole).toString();
+ item->setData(Qt::DisplayRole,
+ oldData.left(5)
+ .append(QString::number(value.toReal()))
+ .append("/")
+ .append(QString::number(value.toReal() * 10)));
+ }
+}
+
+int main(int argc, char **argv)
+{
+ qputenv("QSG_RHI_BACKEND", "opengl");
+ QApplication app(argc, argv);
+ Q3DBars *barGraph = new Q3DBars();
+ Q3DSurface *surfaceGraph = new Q3DSurface();
+ QWidget *barContainer = QWidget::createWindowContainer(barGraph);
+ QWidget *surfaceContainer = QWidget::createWindowContainer(surfaceGraph);
+
+ barContainer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+ barContainer->setFocusPolicy(Qt::StrongFocus);
+ surfaceContainer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+ surfaceContainer->setFocusPolicy(Qt::StrongFocus);
+
+ QWidget widget;
+ QVBoxLayout *mainLayout = new QVBoxLayout(&widget);
+ QHBoxLayout *graphLayout = new QHBoxLayout();
+ QVBoxLayout *buttonLayout = new QVBoxLayout();
+ QHBoxLayout *bottomLayout = new QHBoxLayout();
+ QTableWidget *tableWidget = new QTableWidget(&widget);
+ QPushButton *changeSelectedButton = new QPushButton(&widget);
+ changeSelectedButton->setText(QStringLiteral("Change Selected"));
+
+ buttonLayout->addWidget(changeSelectedButton);
+ graphLayout->addWidget(barContainer);
+ graphLayout->addWidget(surfaceContainer);
+ bottomLayout->addLayout(buttonLayout);
+ bottomLayout->addWidget(tableWidget);
+ mainLayout->addLayout(graphLayout);
+ mainLayout->addLayout(bottomLayout);
+
+ tableWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
+ tableWidget->setAlternatingRowColors(true);
+ widget.setWindowTitle(QStringLiteral("Hours spent on the Internet"));
+
+ // Since we are dealing with QTableWidget, the model will already have data sorted properly
+ // into rows and columns, so we simply set useModelCategories property to true to utilize this.
+ QItemModelBarDataProxy *barProxy = new QItemModelBarDataProxy(tableWidget->model());
+ QItemModelSurfaceDataProxy *surfaceProxy = new QItemModelSurfaceDataProxy(tableWidget->model());
+ barProxy->setUseModelCategories(true);
+ surfaceProxy->setUseModelCategories(true);
+ barProxy->setRotationRole(tableWidget->model()->roleNames().value(Qt::DisplayRole));
+ barProxy->setValueRolePattern(QRegularExpression(QStringLiteral("^(\\d*)(\\/)(\\d*)\\/(\\d*[\\.\\,]?\\d*)\\/\\d*[\\.\\,]?\\d*$")));
+ barProxy->setRotationRolePattern(QRegularExpression(QStringLiteral("^(\\d*)(\\/)\\d*\\/\\d*([\\.\\,]?)\\d*(\\/)(\\d*[\\.\\,]?\\d*)$")));
+ barProxy->setValueRoleReplace(QStringLiteral("\\4"));
+ barProxy->setRotationRoleReplace(QStringLiteral("\\5"));
+ surfaceProxy->setXPosRole(tableWidget->model()->roleNames().value(Qt::DisplayRole));
+ surfaceProxy->setZPosRole(tableWidget->model()->roleNames().value(Qt::DisplayRole));
+ surfaceProxy->setXPosRolePattern(QRegularExpression(QStringLiteral("^(\\d*)\\/(\\d*)\\/\\d*[\\.\\,]?\\d*\\/\\d*[\\.\\,]?\\d*$")));
+ surfaceProxy->setXPosRoleReplace(QStringLiteral("\\2"));
+ surfaceProxy->setYPosRolePattern(QRegularExpression(QStringLiteral("^\\d*(\\/)(\\d*)\\/(\\d*[\\.\\,]?\\d*)\\/\\d*[\\.\\,]?\\d*$")));
+ surfaceProxy->setYPosRoleReplace(QStringLiteral("\\3"));
+ surfaceProxy->setZPosRolePattern(QRegularExpression(QStringLiteral("^(\\d*)(\\/)(\\d*)\\/\\d*[\\.\\,]?\\d*\\/\\d*[\\.\\,]?\\d*$")));
+ surfaceProxy->setZPosRoleReplace(QStringLiteral("\\1"));
+ QBar3DSeries *barSeries = new QBar3DSeries(barProxy);
+ QSurface3DSeries *surfaceSeries = new QSurface3DSeries(surfaceProxy);
+ barSeries->setMesh(QAbstract3DSeries::MeshPyramid);
+ barGraph->addSeries(barSeries);
+ surfaceGraph->addSeries(surfaceSeries);
+
+ barGraph->scene()->activeCamera()->setCameraPreset(Q3DCamera::CameraPresetBehind);
+ surfaceGraph->scene()->activeCamera()->setCameraPreset(Q3DCamera::CameraPresetFront);
+
+ GraphDataGenerator generator(barGraph, surfaceGraph, tableWidget);
+ QObject::connect(barSeries, &QBar3DSeries::selectedBarChanged, &generator,
+ &GraphDataGenerator::selectFromTable);
+ QObject::connect(surfaceSeries, &QSurface3DSeries::selectedPointChanged, &generator,
+ &GraphDataGenerator::selectFromTable);
+ QObject::connect(tableWidget, &QTableWidget::currentCellChanged, &generator,
+ &GraphDataGenerator::selectedFromTable);
+ QObject::connect(changeSelectedButton, &QPushButton::clicked, &generator,
+ &GraphDataGenerator::changeSelectedButtonClicked);
+
+ QSize screenSize = barGraph->screen()->size();
+ widget.resize(QSize(screenSize.width() / 2, screenSize.height() / 2));
+ widget.show();
+ generator.start();
+ return app.exec();
+}
diff --git a/tests/manual/manual.pro b/tests/manual/manual.pro
new file mode 100644
index 0000000..f39d06e
--- /dev/null
+++ b/tests/manual/manual.pro
@@ -0,0 +1,32 @@
+TEMPLATE = subdirs
+
+qtHaveModule(quick) {
+ SUBDIRS += qmldynamicdata \
+ qmlmultitest \
+ qmlvolume \
+ qmlperf \
+ qmlgradient \
+ qmlheightmap \
+ qmlbarsrowcolors \
+ qmlcustominput \
+ qmllegend \
+ qmlsurfacelayers
+}
+
+!android:!ios:!winrt {
+ SUBDIRS += barstest \
+ scattertest \
+ surfacetest \
+ multigraphs \
+ directional \
+ itemmodeltest \
+ volumetrictest \
+ rotations \
+ custominput \
+ itemmodel
+
+ # For testing code snippets of minimal applications
+ SUBDIRS += minimalbars \
+ minimalscatter \
+ minimalsurface
+}
diff --git a/tests/manual/minimalbars/CMakeLists.txt b/tests/manual/minimalbars/CMakeLists.txt
new file mode 100644
index 0000000..a2420cf
--- /dev/null
+++ b/tests/manual/minimalbars/CMakeLists.txt
@@ -0,0 +1,12 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+qt_internal_add_manual_test(MinimalBars
+ GUI
+ SOURCES
+ ../../../src/graphs/doc/snippets/doc_src_q3dbars_construction.cpp
+ )
+target_link_libraries(MinimalBars PUBLIC
+ Qt::Gui
+ Qt::Graphs
+ )
diff --git a/tests/manual/minimalbars/minimalbars.pro b/tests/manual/minimalbars/minimalbars.pro
new file mode 100644
index 0000000..b9d3639
--- /dev/null
+++ b/tests/manual/minimalbars/minimalbars.pro
@@ -0,0 +1,10 @@
+!include( ../tests.pri ) {
+ error( "Couldn't find the tests.pri file!" )
+}
+
+QT += core gui graphs
+
+TARGET = MinimalBars
+TEMPLATE = app
+
+SOURCES += ../../../src/graphs/doc/snippets/doc_src_q3dbars_construction.cpp
diff --git a/tests/manual/minimalscatter/CMakeLists.txt b/tests/manual/minimalscatter/CMakeLists.txt
new file mode 100644
index 0000000..5ff2026
--- /dev/null
+++ b/tests/manual/minimalscatter/CMakeLists.txt
@@ -0,0 +1,12 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+qt_internal_add_manual_test(MinimalScatter
+ GUI
+ SOURCES
+ ../../../src/graphs/doc/snippets/doc_src_q3dscatter_construction.cpp
+ )
+target_link_libraries(MinimalScatter PUBLIC
+ Qt::Gui
+ Qt::Graphs
+ )
diff --git a/tests/manual/minimalscatter/minimalscatter.pro b/tests/manual/minimalscatter/minimalscatter.pro
new file mode 100644
index 0000000..899c768
--- /dev/null
+++ b/tests/manual/minimalscatter/minimalscatter.pro
@@ -0,0 +1,10 @@
+!include( ../tests.pri ) {
+ error( "Couldn't find the tests.pri file!" )
+}
+
+QT += core gui graphs
+
+TARGET = MinimalScatter
+TEMPLATE = app
+
+SOURCES += ../../../src/graphs/doc/snippets/doc_src_q3dscatter_construction.cpp
diff --git a/tests/manual/minimalsurface/CMakeLists.txt b/tests/manual/minimalsurface/CMakeLists.txt
new file mode 100644
index 0000000..7c9af7b
--- /dev/null
+++ b/tests/manual/minimalsurface/CMakeLists.txt
@@ -0,0 +1,12 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+qt_internal_add_manual_test(MinimalSurface
+ GUI
+ SOURCES
+ ../../../src/graphs/doc/snippets/doc_src_q3dsurface_construction.cpp
+ )
+target_link_libraries(MinimalSurface PUBLIC
+ Qt::Gui
+ Qt::Graphs
+ )
diff --git a/tests/manual/minimalsurface/minimalsurface.pro b/tests/manual/minimalsurface/minimalsurface.pro
new file mode 100644
index 0000000..5ac07a4
--- /dev/null
+++ b/tests/manual/minimalsurface/minimalsurface.pro
@@ -0,0 +1,10 @@
+!include( ../tests.pri ) {
+ error( "Couldn't find the tests.pri file!" )
+}
+
+QT += core gui graphs
+
+TARGET = minimalSurface
+TEMPLATE = app
+
+SOURCES += ../../../src/graphs/doc/snippets/doc_src_q3dsurface_construction.cpp
diff --git a/tests/manual/multigraphs/CMakeLists.txt b/tests/manual/multigraphs/CMakeLists.txt
new file mode 100644
index 0000000..5e514ee
--- /dev/null
+++ b/tests/manual/multigraphs/CMakeLists.txt
@@ -0,0 +1,27 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+qt_internal_add_manual_test(multigraphs
+ GUI
+ SOURCES
+ data.cpp data.h
+ main.cpp
+ )
+target_link_libraries(multigraphs PUBLIC
+ Qt::Gui
+ Qt::Widgets
+ Qt::Graphs
+ )
+
+set(multigraphs_resource_files
+ "australia.png"
+ )
+
+qt_internal_add_resource(multigraphs "multigraphs"
+ PREFIX
+ "/"
+ FILES
+ ${multigraphs_resource_files}
+ )
diff --git a/tests/manual/multigraphs/australia.png b/tests/manual/multigraphs/australia.png
new file mode 100644
index 0000000..a839b6b
--- /dev/null
+++ b/tests/manual/multigraphs/australia.png
Binary files differ
diff --git a/tests/manual/multigraphs/data.cpp b/tests/manual/multigraphs/data.cpp
new file mode 100644
index 0000000..dce8c20
--- /dev/null
+++ b/tests/manual/multigraphs/data.cpp
@@ -0,0 +1,309 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#define NOMINMAX
+
+#include "data.h"
+#include <QtGraphs/QValue3DAxis>
+#include <QtGraphs/Q3DCamera>
+#include <QtGraphs/QBar3DSeries>
+#include <QtGraphs/QScatter3DSeries>
+#include <QtGraphs/QSurface3DSeries>
+#include <QtGraphs/Q3DTheme>
+#include <QScrollBar>
+#include <QSize>
+#include <QImage>
+
+Data::Data(Q3DSurface *surface, Q3DScatter *scatter, Q3DBars *bars,
+ QTextEdit *statusArea, QWidget *widget) :
+ m_surface(surface),
+ m_scatter(scatter),
+ m_bars(bars),
+ m_statusArea(statusArea),
+ m_widget(widget),
+ m_resize(true),
+ m_resolution(QSize(300, 300)),
+ m_resolutionLevel(0),
+ m_mode(Surface),
+ m_scatterDataArray(0),
+ m_barDataArray(0),
+ m_started(false)
+{
+ // Initialize surface
+ m_surface->activeTheme()->setType(Q3DTheme::ThemeIsabelle);
+ QLinearGradient gradient;
+ gradient.setColorAt(0.0, Qt::black);
+ gradient.setColorAt(0.33, Qt::blue);
+ gradient.setColorAt(0.67, Qt::red);
+ gradient.setColorAt(1.0, Qt::yellow);
+ m_surface->setSelectionMode(QAbstract3DGraph::SelectionNone);
+ m_surface->activeTheme()->setGridEnabled(false);
+ m_surface->activeTheme()->setBackgroundEnabled(false);
+ m_surface->scene()->activeCamera()->setCameraPosition(0.0, 90.0, 150.0);
+ QSurface3DSeries *series1 = new QSurface3DSeries(new QHeightMapSurfaceDataProxy());
+ series1->setFlatShadingEnabled(true);
+ series1->setDrawMode(QSurface3DSeries::DrawSurface);
+ series1->setColorStyle(Q3DTheme::ColorStyleRangeGradient);
+ series1->setBaseGradient(gradient);
+ m_surface->addSeries(series1);
+
+ // Initialize scatter
+ m_scatter->activeTheme()->setType(Q3DTheme::ThemeStoneMoss);
+ m_scatter->setSelectionMode(QAbstract3DGraph::SelectionNone);
+ m_scatter->activeTheme()->setGridEnabled(false);
+ m_scatter->setShadowQuality(QAbstract3DGraph::ShadowQualitySoftLow);
+ m_scatter->scene()->activeCamera()->setCameraPosition(0.0, 85.0, 150.0);
+ QScatter3DSeries *series2 = new QScatter3DSeries;
+ series2->setMesh(QAbstract3DSeries::MeshPoint);
+ m_scatter->addSeries(series2);
+
+ // Initialize bars
+ m_bars->activeTheme()->setType(Q3DTheme::ThemeQt);
+ m_bars->setSelectionMode(QAbstract3DGraph::SelectionItemAndRow | QAbstract3DGraph::SelectionSlice);
+ m_bars->activeTheme()->setGridEnabled(false);
+ m_bars->setShadowQuality(QAbstract3DGraph::ShadowQualityLow);
+ m_bars->setBarSpacing(QSizeF(0.0, 0.0));
+ m_bars->scene()->activeCamera()->setCameraPosition(0.0, 75.0, 150.0);
+ QBar3DSeries *series3 = new QBar3DSeries;
+ series3->setMesh(QAbstract3DSeries::MeshBar);
+ m_bars->addSeries(series3);
+
+ // Hide scroll bar
+ m_statusArea->verticalScrollBar()->setVisible(false);
+}
+
+Data::~Data()
+{
+ delete m_bars;
+ delete m_surface;
+ delete m_scatter;
+ delete m_widget;
+ delete m_scatterDataArray; // only portion of this array is set to graph
+}
+
+void Data::updateData()
+{
+ if (!m_started) {
+ m_statusArea->append(QStringLiteral("<i>We are stopped. The changes will take effect once started.</i>"));
+ return;
+ }
+ QImage depthMap = QImage(":/australia.png");
+ if (m_resize) // Resize for better performance
+ depthMap = depthMap.scaled(m_resolution);
+ if (m_mode != Surface)
+ setData(depthMap);
+ else
+ static_cast<QHeightMapSurfaceDataProxy *>(m_surface->seriesList().at(0)->dataProxy())->setHeightMap(
+ depthMap);
+}
+
+void Data::clearData()
+{
+ m_bars->seriesList().at(0)->dataProxy()->resetArray(0);
+ m_scatter->seriesList().at(0)->dataProxy()->resetArray(0);
+ m_surface->seriesList().at(0)->dataProxy()->resetArray(0);
+}
+
+void Data::setResolution(int selection)
+{
+ m_resolutionLevel = selection;
+ switch (selection) {
+ case 0: {
+ m_resize = true;
+ m_resolution = QSize(300, 300);
+ break;
+ }
+ case 1: {
+ m_resize = true;
+ m_resolution = QSize(600, 600);
+ break;
+ }
+ case 2: {
+ m_resize = true;
+ m_resolution = QSize(800, 800);
+ break;
+ }
+ case 3: {
+ m_resize = false;
+ m_resolution = QSize(1020, 1020); // image size
+ break;
+ }
+ };
+ if (m_mode == Scatter) {
+ m_resize = true;
+ m_resolution /= 3;
+ delete m_scatterDataArray;
+ m_scatterDataArray = new QScatterDataArray;
+ m_scatterDataArray->resize(m_resolution.width() * m_resolution.height());
+ } else if (m_mode == Bars) {
+ m_resize = true;
+ m_resolution /= 6;
+ m_barDataArray = new QBarDataArray;
+ m_barDataArray->reserve(m_resolution.height());
+ for (int i = 0; i < m_resolution.height(); i++) {
+ QBarDataRow *newProxyRow = new QBarDataRow(m_resolution.width());
+ m_barDataArray->append(newProxyRow);
+ }
+ }
+
+ m_statusArea->append(QString(QStringLiteral("<b>Resolution:</b> %1 x %2")).arg(
+ m_resolution.width()).arg(m_resolution.height()));
+
+ updateData();
+}
+
+void Data::scrollDown()
+{
+ QScrollBar *scrollbar = m_statusArea->verticalScrollBar();
+ scrollbar->setValue(scrollbar->maximum());
+}
+
+void Data::useGradientOne()
+{
+ m_surface->activeTheme()->setType(Q3DTheme::ThemeIsabelle);
+ QLinearGradient gradient;
+ gradient.setColorAt(0.0, Qt::black);
+ gradient.setColorAt(0.33, Qt::blue);
+ gradient.setColorAt(0.67, Qt::red);
+ gradient.setColorAt(1.0, Qt::yellow);
+ m_surface->seriesList().at(0)->setBaseGradient(gradient);
+ m_surface->seriesList().at(0)->setColorStyle(Q3DTheme::ColorStyleRangeGradient);
+ m_statusArea->append(QStringLiteral("<b>Colors:</b> Thermal image imitation"));
+}
+
+void Data::useGradientTwo()
+{
+ m_surface->activeTheme()->setType(Q3DTheme::ThemeQt);
+ QLinearGradient gradient;
+ gradient.setColorAt(0.0, Qt::white);
+ gradient.setColorAt(0.8, Qt::red);
+ gradient.setColorAt(1.0, Qt::green);
+ m_surface->seriesList().at(0)->setBaseGradient(gradient);
+ m_surface->seriesList().at(0)->setColorStyle(Q3DTheme::ColorStyleRangeGradient);
+ m_statusArea->append(QStringLiteral("<b>Colors:</b> Highlight foreground"));
+}
+
+void Data::setData(const QImage &image)
+{
+ QImage heightImage = image;
+
+ uchar *bits = heightImage.bits();
+
+ int imageHeight = heightImage.height();
+ int imageWidth = heightImage.width();
+ int bitCount = imageWidth * 4 * (imageHeight - 1);
+ int widthBits = imageWidth * 4;
+
+ if (m_mode == Scatter) {
+ QScatterDataItem *ptrToDataArray = &m_scatterDataArray->first();
+
+ int limitsX = imageWidth / 2;
+ int limitsZ = imageHeight / 2;
+ float height = 0;
+ int count = 0;
+
+ for (int i = -limitsZ; i < limitsZ; i++, bitCount -= widthBits) {
+ for (int j = -limitsX; j < limitsX; j++) {
+ height = float(bits[bitCount + ((j + limitsX) * 4)]) - 128.0;
+ if (height > -128) {
+ ptrToDataArray->setPosition(QVector3D(float(j), height, float(i)));
+ ptrToDataArray++;
+ count++;
+ }
+ }
+ }
+
+ QScatterDataArray *dataArray = new QScatterDataArray(m_scatterDataArray->mid(0, count));
+ m_scatter->seriesList().at(0)->dataProxy()->resetArray(dataArray);
+ } else {
+ QBarDataArray *dataArray = m_barDataArray;
+ for (int i = 0; i < imageHeight; i++, bitCount -= widthBits) {
+ QBarDataRow &newRow = *dataArray->at(i);
+ for (int j = 0; j < imageWidth; j++)
+ newRow[j] = float(bits[bitCount + (j * 4)]);
+ }
+
+ m_bars->seriesList().at(0)->dataProxy()->resetArray(dataArray);
+ }
+}
+
+void Data::changeMode(int mode)
+{
+ m_mode = GraphsMode(mode);
+ switch (m_mode) {
+ case Surface: {
+ m_statusArea->append(QStringLiteral("<b>Graphs Type:</b> Surface"));
+ break;
+ }
+ case Scatter: {
+ m_statusArea->append(QStringLiteral("<b>Graphs Type:</b> Scatter"));
+ break;
+ }
+ default: {
+ m_statusArea->append(QStringLiteral("<b>Graphs Type:</b> Bars"));
+ break;
+ }
+ }
+ // Reset resolution after mode change
+ setResolution(m_resolutionLevel);
+}
+
+void Data::start()
+{
+ m_started = true;
+ // Reset resolution before starting (otherwise restart will crash due to empty data)
+ setResolution(m_resolutionLevel);
+ updateData();
+ m_statusArea->append(QStringLiteral("<b>Started<\b>"));
+}
+
+void Data::stop()
+{
+ m_started = false;
+ clearData();
+ m_statusArea->append(QStringLiteral("<b>Stopped<\b>"));
+}
+
+ContainerChanger::ContainerChanger(QWidget *surface, QWidget *scatter, QWidget *bars,
+ QWidget *buttonOne, QWidget *buttonTwo)
+ : m_surface(surface),
+ m_scatter(scatter),
+ m_bars(bars),
+ m_button1(buttonOne),
+ m_button2(buttonTwo)
+{
+}
+
+ContainerChanger::~ContainerChanger()
+{
+}
+
+void ContainerChanger::changeContainer(int container)
+{
+ switch (container) {
+ case 0: {
+ m_scatter->setVisible(false);
+ m_bars->setVisible(false);
+ m_surface->setVisible(true);
+ m_button1->setEnabled(true);
+ m_button2->setEnabled(true);
+ break;
+ }
+ case 1: {
+ m_surface->setVisible(false);
+ m_bars->setVisible(false);
+ m_scatter->setVisible(true);
+ m_button1->setEnabled(false);
+ m_button2->setEnabled(false);
+ break;
+ }
+ case 2: {
+ m_surface->setVisible(false);
+ m_scatter->setVisible(false);
+ m_bars->setVisible(true);
+ m_button1->setEnabled(false);
+ m_button2->setEnabled(false);
+ break;
+ }
+ }
+}
diff --git a/tests/manual/multigraphs/data.h b/tests/manual/multigraphs/data.h
new file mode 100644
index 0000000..bdcc0c3
--- /dev/null
+++ b/tests/manual/multigraphs/data.h
@@ -0,0 +1,81 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef DATA_H
+#define DATA_H
+
+#include <QtGraphs/Q3DScatter>
+#include <QtGraphs/Q3DBars>
+#include <QtGraphs/Q3DSurface>
+#include <QtGraphs/QScatterDataProxy>
+#include <QtGraphs/QBarDataProxy>
+#include <QtGraphs/QHeightMapSurfaceDataProxy>
+#include <QTextEdit>
+
+class Data : public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit Data(Q3DSurface *surface, Q3DScatter *scatter, Q3DBars *bars,
+ QTextEdit *statusLabel, QWidget *widget);
+ ~Data();
+
+ void start();
+ void stop();
+
+ void updateData();
+ void clearData();
+
+ void scrollDown();
+ void setData(const QImage &image);
+ void useGradientOne();
+ void useGradientTwo();
+
+public:
+ enum GraphsMode {
+ Surface = 0,
+ Scatter,
+ Bars
+ };
+
+public Q_SLOTS:
+ void setResolution(int selection);
+ void changeMode(int mode);
+
+private:
+ Q3DSurface *m_surface;
+ Q3DScatter *m_scatter;
+ Q3DBars *m_bars;
+ QTextEdit *m_statusArea;
+ QWidget *m_widget;
+ bool m_resize;
+ QSize m_resolution;
+ int m_resolutionLevel;
+ GraphsMode m_mode;
+ QScatterDataArray *m_scatterDataArray;
+ QBarDataArray *m_barDataArray;
+ bool m_started;
+};
+
+class ContainerChanger : public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit ContainerChanger(QWidget *surface, QWidget *scatter, QWidget *bars,
+ QWidget *buttonOne, QWidget *buttonTwo);
+ ~ContainerChanger();
+
+public Q_SLOTS:
+ void changeContainer(int container);
+
+private:
+ QWidget *m_surface;
+ QWidget *m_scatter;
+ QWidget *m_bars;
+ QWidget *m_button1;
+ QWidget *m_button2;
+};
+
+#endif
diff --git a/tests/manual/multigraphs/main.cpp b/tests/manual/multigraphs/main.cpp
new file mode 100644
index 0000000..05d860e
--- /dev/null
+++ b/tests/manual/multigraphs/main.cpp
@@ -0,0 +1,142 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "data.h"
+
+#include <QApplication>
+#include <QWidget>
+#include <QHBoxLayout>
+#include <QVBoxLayout>
+#include <QPushButton>
+#include <QLabel>
+#include <QComboBox>
+#include <QSlider>
+#include <QTextEdit>
+#include <QScreen>
+#include <QPainter>
+
+int main(int argc, char **argv)
+{
+ qputenv("QSG_RHI_BACKEND", "opengl");
+ QApplication app(argc, argv);
+
+ QWidget *widget = new QWidget();
+ QHBoxLayout *hLayout = new QHBoxLayout(widget);
+ QVBoxLayout *vLayout = new QVBoxLayout();
+
+ Q3DSurface *surface = new Q3DSurface();
+ Q3DScatter *scatter = new Q3DScatter();
+ Q3DBars *bars = new Q3DBars();
+
+ QSize screenSize = surface->screen()->size();
+
+ QWidget *containerSurface = QWidget::createWindowContainer(surface);
+ containerSurface->setMinimumSize(QSize(screenSize.height() / 1.2, screenSize.height() / 1.2));
+ containerSurface->setMaximumSize(screenSize);
+ containerSurface->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+ containerSurface->setFocusPolicy(Qt::StrongFocus);
+
+ QWidget *containerScatter = QWidget::createWindowContainer(scatter);
+ containerScatter->setMinimumSize(QSize(screenSize.height() / 1.2, screenSize.height() / 1.2));
+ containerScatter->setMaximumSize(screenSize);
+ containerScatter->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+ containerScatter->setFocusPolicy(Qt::StrongFocus);
+ containerScatter->setVisible(false);
+
+ QWidget *containerBars = QWidget::createWindowContainer(bars);
+ containerBars->setMinimumSize(QSize(screenSize.height() / 1.2, screenSize.height() / 1.2));
+ containerBars->setMaximumSize(screenSize);
+ containerBars->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+ containerBars->setFocusPolicy(Qt::StrongFocus);
+ containerBars->setVisible(false);
+
+ widget->setWindowTitle(QStringLiteral("Test switching graphs on the fly"));
+
+ hLayout->addWidget(containerSurface, 1);
+ hLayout->addWidget(containerScatter, 1);
+ hLayout->addWidget(containerBars, 1);
+ hLayout->addLayout(vLayout);
+
+ QPushButton *startButton = new QPushButton(widget);
+ startButton->setText(QStringLiteral("Start"));
+
+ QPushButton *stopButton = new QPushButton(widget);
+ stopButton->setText(QStringLiteral("Stop"));
+
+ QComboBox *resolutionBox = new QComboBox(widget);
+ resolutionBox->addItem(QStringLiteral("Low"));
+ resolutionBox->addItem(QStringLiteral("Medium"));
+ resolutionBox->addItem(QStringLiteral("High"));
+ resolutionBox->addItem(QStringLiteral("Max")); // Comment this out if demo machine is low-perf
+ resolutionBox->setCurrentIndex(0);
+
+ QComboBox *modeBox = new QComboBox(widget);
+ modeBox->addItem(QStringLiteral("Surface Plot"));
+ modeBox->addItem(QStringLiteral("Scatter Chart"));
+ modeBox->addItem(QStringLiteral("Bar Chart"));
+ modeBox->setCurrentIndex(0);
+
+ QLinearGradient gradientOne(0, 0, 200, 1);
+ gradientOne.setColorAt(0.0, Qt::black);
+ gradientOne.setColorAt(0.33, Qt::blue);
+ gradientOne.setColorAt(0.67, Qt::red);
+ gradientOne.setColorAt(1.0, Qt::yellow);
+
+ QPixmap pm(200, 24);
+ QPainter pmp(&pm);
+ pmp.setBrush(QBrush(gradientOne));
+ pmp.setPen(Qt::NoPen);
+ pmp.drawRect(0, 0, 200, 24);
+
+ QPushButton *gradientOneButton = new QPushButton(widget);
+ gradientOneButton->setIcon(QIcon(pm));
+ gradientOneButton->setIconSize(QSize(200, 24));
+ gradientOneButton->setToolTip(QStringLiteral("Colors: Thermal Imitation"));
+
+ QLinearGradient gradientTwo(0, 0, 200, 1);
+ gradientTwo.setColorAt(0.0, Qt::white);
+ gradientTwo.setColorAt(0.8, Qt::red);
+ gradientTwo.setColorAt(1.0, Qt::green);
+
+ pmp.setBrush(QBrush(gradientTwo));
+ pmp.setPen(Qt::NoPen);
+ pmp.drawRect(0, 0, 200, 24);
+
+ QPushButton *gradientTwoButton = new QPushButton(widget);
+ gradientTwoButton->setIcon(QIcon(pm));
+ gradientTwoButton->setIconSize(QSize(200, 24));
+ gradientTwoButton->setToolTip(QStringLiteral("Colors: Highlight Foreground"));
+
+ QTextEdit *status = new QTextEdit(QStringLiteral("<b>Ready</b><br>"), widget);
+ status->setReadOnly(true);
+
+ vLayout->addWidget(startButton);
+ vLayout->addWidget(stopButton);
+ vLayout->addWidget(new QLabel(QStringLiteral("Change resolution")));
+ vLayout->addWidget(resolutionBox);
+ vLayout->addWidget(new QLabel(QStringLiteral("Change graphs type")));
+ vLayout->addWidget(modeBox);
+ vLayout->addWidget(new QLabel(QStringLiteral("Change color scheme")));
+ vLayout->addWidget(gradientOneButton);
+ vLayout->addWidget(gradientTwoButton);
+ vLayout->addWidget(status, 1, Qt::AlignBottom);
+
+ widget->show();
+
+ Data datagen(surface, scatter, bars, status, widget);
+ ContainerChanger changer(containerSurface, containerScatter, containerBars,
+ gradientOneButton, gradientTwoButton);
+
+ QObject::connect(startButton, &QPushButton::clicked, &datagen, &Data::start);
+ QObject::connect(stopButton, &QPushButton::clicked, &datagen, &Data::stop);
+ QObject::connect(resolutionBox, SIGNAL(activated(int)), &datagen, SLOT(setResolution(int)));
+ QObject::connect(modeBox, SIGNAL(activated(int)), &changer, SLOT(changeContainer(int)));
+ QObject::connect(modeBox, SIGNAL(activated(int)), &datagen, SLOT(changeMode(int)));
+ QObject::connect(status, &QTextEdit::textChanged, &datagen, &Data::scrollDown);
+ QObject::connect(gradientOneButton, &QPushButton::clicked, &datagen,
+ &Data::useGradientOne);
+ QObject::connect(gradientTwoButton, &QPushButton::clicked, &datagen,
+ &Data::useGradientTwo);
+
+ return app.exec();
+}
diff --git a/tests/manual/multigraphs/multigraphs.pro b/tests/manual/multigraphs/multigraphs.pro
new file mode 100644
index 0000000..f1a7cff
--- /dev/null
+++ b/tests/manual/multigraphs/multigraphs.pro
@@ -0,0 +1,12 @@
+!include( ../tests.pri ) {
+ error( "Couldn't find the tests.pri file!" )
+}
+
+SOURCES += main.cpp \
+ data.cpp
+HEADERS += data.h
+
+QT += widgets
+
+RESOURCES += \
+ multigraphs.qrc
diff --git a/tests/manual/multigraphs/multigraphs.qrc b/tests/manual/multigraphs/multigraphs.qrc
new file mode 100644
index 0000000..bd92425
--- /dev/null
+++ b/tests/manual/multigraphs/multigraphs.qrc
@@ -0,0 +1,5 @@
+<RCC>
+ <qresource prefix="/">
+ <file>australia.png</file>
+ </qresource>
+</RCC>
diff --git a/tests/manual/qmlbarsrowcolors/CMakeLists.txt b/tests/manual/qmlbarsrowcolors/CMakeLists.txt
new file mode 100644
index 0000000..72d9a81
--- /dev/null
+++ b/tests/manual/qmlbarsrowcolors/CMakeLists.txt
@@ -0,0 +1,31 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+qt_internal_add_manual_test(qmlbarsrowcolors
+ GUI
+ SOURCES
+ main.cpp
+ )
+
+target_link_libraries(qmlbarsrowcolors PUBLIC
+ Qt::Core
+ Qt::Gui
+ Qt::Qml
+ Qt::Quick
+ Qt::Graphs
+)
+
+set(qmlbarsrowcolors_resource_files
+ "qml/qmlbarsrowcolors/Axes.qml"
+ "qml/qmlbarsrowcolors/Data.qml"
+ "qml/qmlbarsrowcolors/main.qml"
+)
+
+qt6_add_resources(qmlbarsrowcolors "qmlbarsrowcolors"
+ PREFIX
+ "/"
+ FILES
+ ${qmlbarsrowcolors_resource_files}
+)
diff --git a/tests/manual/qmlbarsrowcolors/main.cpp b/tests/manual/qmlbarsrowcolors/main.cpp
new file mode 100644
index 0000000..7c05a02
--- /dev/null
+++ b/tests/manual/qmlbarsrowcolors/main.cpp
@@ -0,0 +1,32 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtGui/QGuiApplication>
+#include <QtCore/QDir>
+#include <QtQuick/QQuickView>
+#include <QtQml/QQmlEngine>
+
+int main(int argc, char *argv[])
+{
+ QGuiApplication app(argc, argv);
+
+ QQuickView viewer;
+
+ // The following are needed to make examples run without having to install the module
+ // in desktop environments.
+#ifdef Q_OS_WIN
+ QString extraImportPath(QStringLiteral("%1/../../../../%2"));
+#else
+ QString extraImportPath(QStringLiteral("%1/../../../%2"));
+#endif
+ viewer.engine()->addImportPath(extraImportPath.arg(QGuiApplication::applicationDirPath(),
+ QString::fromLatin1("qml")));
+
+ viewer.setTitle(QStringLiteral("Monthly income/expenses"));
+
+ viewer.setSource(QUrl("qrc:/qml/qmlbarsrowcolors/main.qml"));
+ viewer.setResizeMode(QQuickView::SizeRootObjectToView);
+ viewer.show();
+
+ return app.exec();
+}
diff --git a/tests/manual/qmlbarsrowcolors/qml/qmlbarsrowcolors/Axes.qml b/tests/manual/qmlbarsrowcolors/qml/qmlbarsrowcolors/Axes.qml
new file mode 100644
index 0000000..86562ae
--- /dev/null
+++ b/tests/manual/qmlbarsrowcolors/qml/qmlbarsrowcolors/Axes.qml
@@ -0,0 +1,41 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+import QtGraphs
+
+Item {
+ property alias column: columnAxis
+ property alias row: rowAxis
+ property alias value: valueAxis
+ property alias total: totalAxis
+
+ // Custom labels for columns, since the data contains abbreviated month names.
+ //! [0]
+ CategoryAxis3D {
+ id: columnAxis
+ labels: ["January", "February", "March", "April", "May", "June",
+ "July", "August", "September", "October", "November", "December"]
+ labelAutoRotation: 30
+ }
+ //! [0]
+ CategoryAxis3D {
+ id: totalAxis
+ labels: ["Yearly total"]
+ labelAutoRotation: 30
+ }
+ CategoryAxis3D {
+ // For row labels we can use row labels from data proxy, no labels defined for rows.
+ id: rowAxis
+ labelAutoRotation: 30
+ }
+
+ ValueAxis3D {
+ id: valueAxis
+ min: 0
+ max: 35
+ labelFormat: "%.2f M\u20AC"
+ title: "Monthly income"
+ labelAutoRotation: 90
+ }
+}
diff --git a/tests/manual/qmlbarsrowcolors/qml/qmlbarsrowcolors/Data.qml b/tests/manual/qmlbarsrowcolors/qml/qmlbarsrowcolors/Data.qml
new file mode 100644
index 0000000..5d138f8
--- /dev/null
+++ b/tests/manual/qmlbarsrowcolors/qml/qmlbarsrowcolors/Data.qml
@@ -0,0 +1,118 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+import QtQml.Models
+
+Item {
+ property alias model: dataModel
+
+ property var modelAsJsArray: {
+ var arr = []
+ for (var i = 0; i < dataModel.count; i++) {
+ var row = dataModel.get(i)
+ arr.push({
+ timestamp: row.timestamp,
+ expenses: row.expenses,
+ income: row.income
+ })
+ }
+ return arr
+ }
+
+ //! [0]
+ ListModel {
+ id: dataModel
+ ListElement{ timestamp: "2006-01"; expenses: "-4"; income: "5" }
+ ListElement{ timestamp: "2006-02"; expenses: "-5"; income: "6" }
+ ListElement{ timestamp: "2006-03"; expenses: "-7"; income: "4" }
+ //! [0]
+ ListElement{ timestamp: "2006-04"; expenses: "-3"; income: "2" }
+ ListElement{ timestamp: "2006-05"; expenses: "-4"; income: "1" }
+ ListElement{ timestamp: "2006-06"; expenses: "-2"; income: "2" }
+ ListElement{ timestamp: "2006-07"; expenses: "-1"; income: "3" }
+ ListElement{ timestamp: "2006-08"; expenses: "-5"; income: "1" }
+ ListElement{ timestamp: "2006-09"; expenses: "-2"; income: "3" }
+ ListElement{ timestamp: "2006-10"; expenses: "-5"; income: "2" }
+ ListElement{ timestamp: "2006-11"; expenses: "-8"; income: "5" }
+ ListElement{ timestamp: "2006-12"; expenses: "-3"; income: "3" }
+
+ ListElement{ timestamp: "2007-01"; expenses: "-3"; income: "1" }
+ ListElement{ timestamp: "2007-02"; expenses: "-4"; income: "2" }
+ ListElement{ timestamp: "2007-03"; expenses: "-12"; income: "4" }
+ ListElement{ timestamp: "2007-04"; expenses: "-13"; income: "6" }
+ ListElement{ timestamp: "2007-05"; expenses: "-14"; income: "11" }
+ ListElement{ timestamp: "2007-06"; expenses: "-7"; income: "7" }
+ ListElement{ timestamp: "2007-07"; expenses: "-6"; income: "4" }
+ ListElement{ timestamp: "2007-08"; expenses: "-4"; income: "15" }
+ ListElement{ timestamp: "2007-09"; expenses: "-2"; income: "18" }
+ ListElement{ timestamp: "2007-10"; expenses: "-29"; income: "25" }
+ ListElement{ timestamp: "2007-11"; expenses: "-23"; income: "29" }
+ ListElement{ timestamp: "2007-12"; expenses: "-5"; income: "9" }
+
+ ListElement{ timestamp: "2008-01"; expenses: "-3"; income: "8" }
+ ListElement{ timestamp: "2008-02"; expenses: "-8"; income: "14" }
+ ListElement{ timestamp: "2008-03"; expenses: "-10"; income: "20" }
+ ListElement{ timestamp: "2008-04"; expenses: "-12"; income: "24" }
+ ListElement{ timestamp: "2008-05"; expenses: "-10"; income: "19" }
+ ListElement{ timestamp: "2008-06"; expenses: "-5"; income: "8" }
+ ListElement{ timestamp: "2008-07"; expenses: "-1"; income: "4" }
+ ListElement{ timestamp: "2008-08"; expenses: "-7"; income: "12" }
+ ListElement{ timestamp: "2008-09"; expenses: "-4"; income: "16" }
+ ListElement{ timestamp: "2008-10"; expenses: "-22"; income: "33" }
+ ListElement{ timestamp: "2008-11"; expenses: "-16"; income: "25" }
+ ListElement{ timestamp: "2008-12"; expenses: "-2"; income: "7" }
+
+ ListElement{ timestamp: "2009-01"; expenses: "-4"; income: "5" }
+ ListElement{ timestamp: "2009-02"; expenses: "-4"; income: "7" }
+ ListElement{ timestamp: "2009-03"; expenses: "-11"; income: "14" }
+ ListElement{ timestamp: "2009-04"; expenses: "-16"; income: "22" }
+ ListElement{ timestamp: "2009-05"; expenses: "-3"; income: "5" }
+ ListElement{ timestamp: "2009-06"; expenses: "-4"; income: "8" }
+ ListElement{ timestamp: "2009-07"; expenses: "-7"; income: "9" }
+ ListElement{ timestamp: "2009-08"; expenses: "-9"; income: "13" }
+ ListElement{ timestamp: "2009-09"; expenses: "-1"; income: "6" }
+ ListElement{ timestamp: "2009-10"; expenses: "-14"; income: "25" }
+ ListElement{ timestamp: "2009-11"; expenses: "-19"; income: "29" }
+ ListElement{ timestamp: "2009-12"; expenses: "-5"; income: "7" }
+
+ ListElement{ timestamp: "2010-01"; expenses: "-14"; income: "22" }
+ ListElement{ timestamp: "2010-02"; expenses: "-5"; income: "7" }
+ ListElement{ timestamp: "2010-03"; expenses: "-1"; income: "9" }
+ ListElement{ timestamp: "2010-04"; expenses: "-1"; income: "12" }
+ ListElement{ timestamp: "2010-05"; expenses: "-5"; income: "9" }
+ ListElement{ timestamp: "2010-06"; expenses: "-5"; income: "8" }
+ ListElement{ timestamp: "2010-07"; expenses: "-3"; income: "7" }
+ ListElement{ timestamp: "2010-08"; expenses: "-1"; income: "5" }
+ ListElement{ timestamp: "2010-09"; expenses: "-2"; income: "4" }
+ ListElement{ timestamp: "2010-10"; expenses: "-10"; income: "13" }
+ ListElement{ timestamp: "2010-11"; expenses: "-12"; income: "17" }
+ ListElement{ timestamp: "2010-12"; expenses: "-6"; income: "9" }
+
+ ListElement{ timestamp: "2011-01"; expenses: "-2"; income: "6" }
+ ListElement{ timestamp: "2011-02"; expenses: "-4"; income: "8" }
+ ListElement{ timestamp: "2011-03"; expenses: "-7"; income: "12" }
+ ListElement{ timestamp: "2011-04"; expenses: "-9"; income: "15" }
+ ListElement{ timestamp: "2011-05"; expenses: "-7"; income: "19" }
+ ListElement{ timestamp: "2011-06"; expenses: "-9"; income: "18" }
+ ListElement{ timestamp: "2011-07"; expenses: "-13"; income: "17" }
+ ListElement{ timestamp: "2011-08"; expenses: "-5"; income: "9" }
+ ListElement{ timestamp: "2011-09"; expenses: "-3"; income: "8" }
+ ListElement{ timestamp: "2011-10"; expenses: "-13"; income: "15" }
+ ListElement{ timestamp: "2011-11"; expenses: "-8"; income: "17" }
+ ListElement{ timestamp: "2011-12"; expenses: "-7"; income: "10" }
+
+ ListElement{ timestamp: "2012-01"; expenses: "-12"; income: "16" }
+ ListElement{ timestamp: "2012-02"; expenses: "-24"; income: "28" }
+ ListElement{ timestamp: "2012-03"; expenses: "-27"; income: "22" }
+ ListElement{ timestamp: "2012-04"; expenses: "-29"; income: "25" }
+ ListElement{ timestamp: "2012-05"; expenses: "-27"; income: "29" }
+ ListElement{ timestamp: "2012-06"; expenses: "-19"; income: "18" }
+ ListElement{ timestamp: "2012-07"; expenses: "-13"; income: "17" }
+ ListElement{ timestamp: "2012-08"; expenses: "-15"; income: "19" }
+ ListElement{ timestamp: "2012-09"; expenses: "-3"; income: "8" }
+ ListElement{ timestamp: "2012-10"; expenses: "-3"; income: "6" }
+ ListElement{ timestamp: "2012-11"; expenses: "-4"; income: "8" }
+ ListElement{ timestamp: "2012-12"; expenses: "-5"; income: "9" }
+ }
+}
diff --git a/tests/manual/qmlbarsrowcolors/qml/qmlbarsrowcolors/main.qml b/tests/manual/qmlbarsrowcolors/qml/qmlbarsrowcolors/main.qml
new file mode 100644
index 0000000..1b64f52
--- /dev/null
+++ b/tests/manual/qmlbarsrowcolors/qml/qmlbarsrowcolors/main.qml
@@ -0,0 +1,392 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import QtGraphs
+import QtQuick.Window
+import Qt.labs.qmlmodels
+import "."
+
+Item {
+ id: mainview
+
+ property int buttonLayoutHeight: 180;
+ property int currentRow
+ property Bar3DSeries selectedSeries
+
+ function toggleRowColorsForBarSeries(enable) {
+ if (enable)
+ barSeries.rowColors = [color1, color2, color3]
+ else
+ barSeries.rowColors = []
+ }
+
+ function toggleRowColorsForSecondarySeries(enable) {
+ if (enable)
+ secondarySeries.rowColors = [color4, color5, color6]
+ else
+ secondarySeries.rowColors = []
+ }
+
+ function handleSelectionChange(series, position) {
+ if (position !== series.invalidSelectionPosition)
+ selectedSeries = series
+
+ // Set tableView current row to selected bar
+ var rowRole = series.dataProxy.rowLabels[position.x];
+ var colRole
+ if (barGraph.columnAxis === graphAxes.total)
+ colRole = "01";
+ else
+ colRole = series.dataProxy.columnLabels[position.y];
+ var checkTimestamp = rowRole + "-" + colRole
+
+ if (currentRow === -1 || checkTimestamp !== graphData.model.get(currentRow).timestamp) {
+ var totalRows = tableView.rows;
+ for (var i = 0; i < totalRows; i++) {
+ var modelTimestamp = graphData.model.get(i).timestamp
+ if (modelTimestamp === checkTimestamp) {
+ currentRow = i
+ break
+ }
+ }
+ }
+ }
+
+ width: 1280
+ height: 1024
+
+ state: Screen.width < Screen.height ? "portrait" : "landscape"
+ selectedSeries: barSeries
+
+ onCurrentRowChanged: {
+ var timestamp = graphData.model.get(currentRow).timestamp
+ var pattern = /(\d\d\d\d)-(\d\d)/
+ var matches = pattern.exec(timestamp)
+ var rowIndex = modelProxy.rowCategoryIndex(matches[1])
+ var colIndex
+ if (barGraph.columnAxis === graphAxes.total)
+ colIndex = 0 // Just one column when showing yearly totals
+ else
+ colIndex = modelProxy.columnCategoryIndex(matches[2])
+ if (selectedSeries.visible)
+ mainview.selectedSeries.selectedBar = Qt.point(rowIndex, colIndex)
+ else if (barSeries.visible)
+ barSeries.selectedBar = Qt.point(rowIndex, colIndex)
+ else
+ secondarySeries.selectedBar = Qt.point(rowIndex, colIndex)
+ }
+
+ Data {
+ id: graphData
+ }
+
+ Axes {
+ id: graphAxes
+ }
+
+ ThemeColor {
+ id: color1
+ color: "green"
+ }
+
+ ThemeColor {
+ id: color2
+ color: "blue"
+ }
+
+ ThemeColor {
+ id: color3
+ color: "red"
+ }
+
+ ThemeColor {
+ id: color4
+ color: "yellow"
+ }
+
+ ThemeColor {
+ id: color5
+ color: "purple"
+ }
+
+ ThemeColor {
+ id: color6
+ color: "orange"
+ }
+
+ Theme3D {
+ id: theme1
+ type: Theme3D.ThemeRetro
+ labelBorderEnabled: true
+ font.pointSize: 35
+ labelBackgroundEnabled: true
+ colorStyle: Theme3D.ColorStyleUniform
+ }
+
+ Theme3D {
+ id: theme2
+ type: Theme3D.ThemeArmyBlue
+ labelBorderEnabled: true
+ font.pointSize: 35
+ labelBackgroundEnabled: true
+ colorStyle: Theme3D.ColorStyleUniform
+ }
+
+ Item {
+ id: dataView
+ anchors.right: mainview.right;
+ anchors.bottom: mainview.bottom
+
+ Bars3D {
+ id: barGraph
+ width: dataView.width
+ height: dataView.height
+ shadowQuality: AbstractGraph3D.ShadowQualityMedium
+ selectionMode: AbstractGraph3D.SelectionItem
+ theme: theme1
+ barThickness: 0.7
+ barSpacing: Qt.size(0.5, 0.5)
+ barSpacingRelative: false
+ scene.activeCamera.cameraPreset: Camera3D.CameraPresetIsometricLeftHigh
+ columnAxis: graphAxes.column
+ rowAxis: graphAxes.row
+ valueAxis: graphAxes.value
+
+ Bar3DSeries {
+ id: secondarySeries
+ visible: false
+ itemLabelFormat: "Expenses, @colLabel, @rowLabel: -@valueLabel"
+ rowColors: [color4 , color5, color6]
+
+ onSelectedBarChanged: (position)=> handleSelectionChange(secondarySeries, position)
+
+ ItemModelBarDataProxy {
+ id: secondaryProxy
+ itemModel: graphData.model
+ rowRole: "timestamp"
+ columnRole: "timestamp"
+ valueRole: "expenses"
+ rowRolePattern: /^(\d\d\d\d).*$/
+ columnRolePattern: /^.*-(\d\d)$/
+ valueRolePattern: /-/
+ rowRoleReplace: "\\1"
+ columnRoleReplace: "\\1"
+ multiMatchBehavior: ItemModelBarDataProxy.MMBCumulative
+ }
+ }
+
+ Bar3DSeries {
+ id: barSeries
+ itemLabelFormat: "Income, @colLabel, @rowLabel: @valueLabel"
+ rowColors: [color1, color2, color3]
+
+ onSelectedBarChanged: (position)=> handleSelectionChange(barSeries, position)
+
+ ItemModelBarDataProxy {
+ id: modelProxy
+ itemModel: graphData.model
+ rowRole: "timestamp"
+ columnRole: "timestamp"
+ valueRole: "income"
+ rowRolePattern: /^(\d\d\d\d).*$/
+ columnRolePattern: /^.*-(\d\d)$/
+ rowRoleReplace: "\\1"
+ columnRoleReplace: "\\1"
+ multiMatchBehavior: ItemModelBarDataProxy.MMBCumulative
+ }
+ }
+ }
+ }
+
+ ColumnLayout {
+ id: tableViewLayout
+
+ anchors.top: parent.top
+ anchors.left: parent.left
+
+ HorizontalHeaderView {
+ id: header
+ property var columnNames: ["Month", "Expenses", "Income"]
+
+ syncView: tableView
+ Layout.fillWidth: true
+ delegate: Text {
+ padding: 3
+ text: header.columnNames[index]
+ }
+
+ }
+
+ TableView {
+ id: tableView
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+
+ reuseItems: false
+ clip: true
+
+ model: TableModel {
+ id: tableModel
+ TableModelColumn { display: "timestamp" }
+ TableModelColumn { display: "expenses" }
+ TableModelColumn { display: "income" }
+
+ rows: graphData.modelAsJsArray
+ }
+
+ delegate: Rectangle {
+ implicitHeight: 30
+ implicitWidth: tableView.width / 3
+ color: row === currentRow ? "#e0e0e0" : "#ffffff"
+ MouseArea {
+ anchors.fill: parent
+ onClicked: currentRow = row
+ }
+
+ Text {
+ id: delegateText
+ anchors.verticalCenter: parent.verticalCenter
+ width: parent.width
+ anchors.leftMargin: 4
+ anchors.left: parent.left
+ anchors.right: parent.right
+ text: formattedText
+ property string formattedText: {
+ if (column === 0) {
+ if (display !== "") {
+ var pattern = /(\d\d\d\d)-(\d\d)/
+ var matches = pattern.exec(display)
+ var colIndex = parseInt(matches[2], 10) - 1
+ return matches[1] + " - " + graphAxes.column.labels[colIndex]
+ }
+ } else {
+ return display
+ }
+ }
+ }
+ }
+ }
+ }
+
+ ColumnLayout {
+ id: controlLayout
+ spacing: 0
+
+ Button {
+ id: seriesToggle
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ text: "Show Expenses"
+ clip: true
+
+ onClicked: {
+ if (text === "Show Expenses") {
+ barSeries.visible = false
+ secondarySeries.visible = true
+ barGraph.valueAxis.labelFormat = "-%.2f M\u20AC"
+ secondarySeries.itemLabelFormat = "Expenses, @colLabel, @rowLabel: @valueLabel"
+ text = "Show Both"
+ } else if (text === "Show Both") {
+ barSeries.visible = true
+ barGraph.valueAxis.labelFormat = "%.2f M\u20AC"
+ secondarySeries.itemLabelFormat = "Expenses, @colLabel, @rowLabel: -@valueLabel"
+ text = "Show Income"
+ } else { // text === "Show Income"
+ secondarySeries.visible = false
+ text = "Show Expenses"
+ }
+ }
+ }
+
+ Button {
+ id: themeToggle
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ text: "Use theme 2"
+ clip: true
+
+ onClicked: {
+ if (text === "Use theme 2") {
+ barGraph.theme = theme2
+ text = "Use theme 1"
+ } else {
+ barGraph.theme = theme1
+ text = "Use theme 2"
+ }
+ }
+ }
+
+ Button {
+ id: barSeriesRowColorToggle
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ text: "Disable row colors"
+
+ onClicked: {
+ if (text === "Disable row colors") {
+ toggleRowColorsForBarSeries(false)
+ toggleRowColorsForSecondarySeries(false)
+ text = "Enable row colors"
+ } else {
+ toggleRowColorsForBarSeries(true)
+ toggleRowColorsForSecondarySeries(true)
+ text = "Disable row colors"
+ }
+ }
+ }
+ }
+
+ states: [
+ State {
+ name: "landscape"
+ PropertyChanges {
+ target: dataView
+ width: mainview.width / 4 * 3
+ height: mainview.height
+ }
+ PropertyChanges {
+ target: tableViewLayout
+ height: mainview.height - buttonLayoutHeight
+ anchors.right: dataView.left
+ anchors.left: mainview.left
+ anchors.bottom: undefined
+ }
+ PropertyChanges {
+ target: controlLayout
+ width: mainview.width / 4
+ height: buttonLayoutHeight
+ anchors.top: tableViewLayout.bottom
+ anchors.bottom: mainview.bottom
+ anchors.left: mainview.left
+ anchors.right: dataView.left
+ }
+ },
+ State {
+ name: "portrait"
+ PropertyChanges {
+ target: dataView
+ width: mainview.height / 4 * 3
+ height: mainview.width
+ }
+ PropertyChanges {
+ target: tableViewLayout
+ height: mainview.width
+ anchors.right: controlLayout.left
+ anchors.left: mainview.left
+ anchors.bottom: dataView.top
+ }
+ PropertyChanges {
+ target: controlLayout
+ width: mainview.height / 4
+ height: mainview.width / 4
+ anchors.top: mainview.top
+ anchors.bottom: dataView.top
+ anchors.left: undefined
+ anchors.right: mainview.right
+ }
+ }
+ ]
+}
diff --git a/tests/manual/qmlcustominput/CMakeLists.txt b/tests/manual/qmlcustominput/CMakeLists.txt
new file mode 100644
index 0000000..0d2b613
--- /dev/null
+++ b/tests/manual/qmlcustominput/CMakeLists.txt
@@ -0,0 +1,30 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+qt_internal_add_manual_test(qmlcustominput
+ GUI
+ SOURCES
+ main.cpp
+ )
+
+target_link_libraries(qmlcustominput PUBLIC
+ Qt::Core
+ Qt::Gui
+ Qt::Qml
+ Qt::Quick
+ Qt::Graphs
+)
+
+set(qmlcustominput_resource_files
+ "qml/qmlcustominput/Data.qml"
+ "qml/qmlcustominput/main.qml"
+)
+
+qt6_add_resources(qmlcustominput "qmlcustominput"
+ PREFIX
+ "/"
+ FILES
+ ${qmlcustominput_resource_files}
+)
diff --git a/tests/manual/qmlcustominput/main.cpp b/tests/manual/qmlcustominput/main.cpp
new file mode 100644
index 0000000..1f70c48
--- /dev/null
+++ b/tests/manual/qmlcustominput/main.cpp
@@ -0,0 +1,34 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtGui/QGuiApplication>
+#include <QtCore/QDir>
+#include <QtQuick/QQuickView>
+#include <QtQml/QQmlEngine>
+#include <QtQuick3D/qquick3d.h>
+
+int main(int argc, char *argv[])
+{
+ QGuiApplication app(argc, argv);
+ QSurfaceFormat::setDefaultFormat(QQuick3D::idealSurfaceFormat());
+ QQuickView viewer;
+
+ // The following are needed to make examples run without having to install the module
+ // in desktop environments.
+#ifdef Q_OS_WIN
+ QString extraImportPath(QStringLiteral("%1/../../../../%2"));
+#else
+ QString extraImportPath(QStringLiteral("%1/../../../%2"));
+#endif
+ viewer.engine()->addImportPath(extraImportPath.arg(QGuiApplication::applicationDirPath(),
+ QString::fromLatin1("qml")));
+ QObject::connect(viewer.engine(), &QQmlEngine::quit, &viewer, &QWindow::close);
+
+ viewer.setTitle(QStringLiteral("QML Custom Input"));
+
+ viewer.setSource(QUrl("qrc:/qml/qmlcustominput/main.qml"));
+ viewer.setResizeMode(QQuickView::SizeRootObjectToView);
+ viewer.show();
+
+ return app.exec();
+}
diff --git a/tests/manual/qmlcustominput/qml/qmlcustominput/Data.qml b/tests/manual/qmlcustominput/qml/qmlcustominput/Data.qml
new file mode 100644
index 0000000..ac2107e
--- /dev/null
+++ b/tests/manual/qmlcustominput/qml/qmlcustominput/Data.qml
@@ -0,0 +1,1080 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+
+Item {
+ property alias modelOne: dataModelOne
+ property alias modelTwo: dataModelTwo
+ property alias modelThree: dataModelThree
+
+ ListModel {
+ id: dataModelOne
+ ListElement{ xPos: -10.0000; yPos: 5.00000; zPos: -5.00000 }
+ ListElement{ xPos: -9.06229; yPos: 3.07132; zPos: -4.54268 }
+ ListElement{ xPos: -8.55132; yPos: 4.19424; zPos: -4.03318 }
+ ListElement{ xPos: -8.03806; yPos: 4.75162; zPos: -3.99583 }
+ ListElement{ xPos: -9.50337; yPos: 4.90684; zPos: -4.21947 }
+ ListElement{ xPos: -9.93819; yPos: 3.42724; zPos: -3.58955 }
+ ListElement{ xPos: -7.84971; yPos: 3.15272; zPos: -4.90367 }
+ ListElement{ xPos: -7.30477; yPos: 2.91062; zPos: -4.11078 }
+ ListElement{ xPos: -7.11201; yPos: 3.68863; zPos: -4.52683 }
+ ListElement{ xPos: -8.83267; yPos: 2.96504; zPos: -3.61108 }
+ ListElement{ xPos: -6.94874; yPos: 2.49808; zPos: -2.92883 }
+ ListElement{ xPos: -9.02606; yPos: 4.7496; zPos: -4.18193 }
+ ListElement{ xPos: -9.5434; yPos: 3.15534; zPos: -3.83789 }
+ ListElement{ xPos: -6.8679; yPos: 3.66922; zPos: -3.58288 }
+ ListElement{ xPos: -8.16487; yPos: 1.82227; zPos: -4.64523 }
+ ListElement{ xPos: -7.42165; yPos: 3.18192; zPos: -4.22791 }
+ ListElement{ xPos: -7.99257; yPos: 3.06559; zPos: -4.33262 }
+ ListElement{ xPos: -8.98851; yPos: 2.64924; zPos: -4.44595 }
+ ListElement{ xPos: -6.36774; yPos: 3.96697; zPos: -4.38998 }
+ ListElement{ xPos: -7.18413; yPos: 3.32417; zPos: -4.04636 }
+ ListElement{ xPos: -7.91649; yPos: 3.46826; zPos: -2.78126 }
+ ListElement{ xPos: -7.49495; yPos: 3.12306; zPos: -3.14539 }
+ ListElement{ xPos: -7.5445; yPos: 2.85744; zPos: -3.68421 }
+ }
+
+ ListModel {
+ id: dataModelTwo
+ ListElement{ xPos: 2.25354; yPos: 1.36828; zPos: -1.32025 }
+ ListElement{ xPos: -2.35524; yPos: -0.081203; zPos: 1.23267 }
+ ListElement{ xPos: 2.6517; yPos: -1.20549; zPos: 2.73606 }
+ ListElement{ xPos: -2.55382; yPos: 3.48814; zPos: -0.454971 }
+ ListElement{ xPos: -3.85468; yPos: 0.263955; zPos: 0.578276 }
+ ListElement{ xPos: 2.85275; yPos: 1.32315; zPos: 1.0565 }
+ ListElement{ xPos: -0.404099; yPos: -2.36811; zPos: -1.60324 }
+ ListElement{ xPos: 1.58908; yPos: 0.363782; zPos: -0.554303 }
+ ListElement{ xPos: 0.251507; yPos: 0.124637; zPos: -0.752568 }
+ ListElement{ xPos: -2.45626; yPos: -0.722719; zPos: -1.11764 }
+ ListElement{ xPos: 4.15342; yPos: 1.92247; zPos: -0.954975 }
+ ListElement{ xPos: 2.05845; yPos: 0.643191; zPos: -0.121564 }
+ ListElement{ xPos: 0.253468; yPos: 0.814651; zPos: 3.05732 }
+ ListElement{ xPos: 1.51724; yPos: 0.244303; zPos: 2.25864 }
+ ListElement{ xPos: 1.15; yPos: -0.487518; zPos: 0.815931 }
+ ListElement{ xPos: -0.0538979; yPos: 0.124927; zPos: 0.251571 }
+ ListElement{ xPos: 0.941523; yPos: -0.483498; zPos: -3.2731 }
+ ListElement{ xPos: 3.55074; yPos: -1.04714; zPos: -0.954301 }
+ ListElement{ xPos: -2.43125; yPos: -0.964099; zPos: -0.658537 }
+ ListElement{ xPos: 4.25459; yPos: 0.163296; zPos: 2.05563 }
+ ListElement{ xPos: -0.612031; yPos: 1.03234; zPos: -0.227175 }
+ ListElement{ xPos: 0.78338; yPos: -0.847922; zPos: -0.959189 }
+ ListElement{ xPos: -4.20076; yPos: 1.44907; zPos: 0.853836 }
+ ListElement{ xPos: -1.59466; yPos: -1.27511; zPos: -1.5686 }
+ ListElement{ xPos: -3.3567; yPos: -1.96864; zPos: 1.83224 }
+ ListElement{ xPos: -2.75169; yPos: -0.964221; zPos: -1.26465 }
+ ListElement{ xPos: -2.45624; yPos: 0.287046; zPos: -4.55032 }
+ ListElement{ xPos: 2.73649; yPos: -1.48789; zPos: -0.653082 }
+ ListElement{ xPos: 2.73284; yPos: 2.2912; zPos: -2.4933 }
+ ListElement{ xPos: -0.854321; yPos: -2.04288; zPos: 3.7516 }
+ ListElement{ xPos: -1.35708; yPos: -1.84915; zPos: 2.35985 }
+ ListElement{ xPos: -3.25001; yPos: -1.64456; zPos: -4.45419 }
+ ListElement{ xPos: -0.356834; yPos: -0.569139; zPos: -1.75308 }
+ ListElement{ xPos: -0.813569; yPos: -0.287899; zPos: -0.0535036 }
+ ListElement{ xPos: 4.75975; yPos: -1.48817; zPos: -2.45957 }
+ ListElement{ xPos: 4.35294; yPos: 1.46154; zPos: 0.814214 }
+ ListElement{ xPos: -3.22467; yPos: 2.76903; zPos: 0.510435 }
+ ListElement{ xPos: 2.49494; yPos: 1.9286; zPos: 0.552287 }
+ ListElement{ xPos: -0.456521; yPos: 0.688277; zPos: -0.82788 }
+ ListElement{ xPos: -2.72568; yPos: 2.80278; zPos: -2.45782 }
+ ListElement{ xPos: -1.65023; yPos: 1.32005; zPos: -2.05754 }
+ ListElement{ xPos: -1.63551; yPos: 1.88519; zPos: -3.65544 }
+ ListElement{ xPos: -1.20008; yPos: -0.723785; zPos: 0.853563 }
+ ListElement{ xPos: 1.45448; yPos: 1.08701; zPos: 2.17385 }
+ ListElement{ xPos: -3.9042; yPos: -1.16916; zPos: -0.85395 }
+ ListElement{ xPos: 3.15645; yPos: 0.123932; zPos: -0.950988 }
+ ListElement{ xPos: -1.35924; yPos: -2.64015; zPos: -0.54254 }
+ ListElement{ xPos: -4.15753; yPos: 1.28294; zPos: -4.47502 }
+ ListElement{ xPos: -2.7885; yPos: 2.48535; zPos: -0.159651 }
+ ListElement{ xPos: -3.44364; yPos: 0.627232; zPos: 0.611881 }
+ ListElement{ xPos: -1.55639; yPos: -0.967484; zPos: 2.35497 }
+ ListElement{ xPos: -0.752853; yPos: 1.16736; zPos: -0.757871 }
+ ListElement{ xPos: -0.859974; yPos: 0.640213; zPos: -1.75453 }
+ ListElement{ xPos: 0.85744; yPos: 0.480175; zPos: 2.97204 }
+ ListElement{ xPos: 4.0571; yPos: 3.24083; zPos: -0.183622 }
+ ListElement{ xPos: 0.658087; yPos: 0.841418; zPos: 0.357839 }
+ ListElement{ xPos: -2.13027; yPos: 0.920836; zPos: -2.758 }
+ ListElement{ xPos: -0.65825; yPos: 0.164257; zPos: 1.69478 }
+ ListElement{ xPos: -1.88335; yPos: -1.4811; zPos: -2.15408 }
+ ListElement{ xPos: -1.67331; yPos: 2.96982; zPos: 1.85521 }
+ ListElement{ xPos: -0.750937; yPos: 0.282914; zPos: -0.758707 }
+ ListElement{ xPos: 2.29891; yPos: 1.16949; zPos: 2.65927 }
+ ListElement{ xPos: 2.69132; yPos: 2.92632; zPos: -0.206126 }
+ ListElement{ xPos: -1.65771; yPos: 3.28846; zPos: 2.50201 }
+ ListElement{ xPos: -0.568799; yPos: 2.5289; zPos: -1.15875 }
+ ListElement{ xPos: -1.85383; yPos: 0.528953; zPos: -2.32418 }
+ ListElement{ xPos: 0.159422; yPos: -2.36165; zPos: -0.569393 }
+ ListElement{ xPos: -3.96506; yPos: 0.282374; zPos: -0.254519 }
+ ListElement{ xPos: 0.150933; yPos: -3.88058; zPos: -0.759422 }
+ ListElement{ xPos: -1.17917; yPos: -1.96176; zPos: 0.95589 }
+ ListElement{ xPos: -0.340817; yPos: -3.52333; zPos: -1.45715 }
+ ListElement{ xPos: 3.21784; yPos: -2.56593; zPos: 2.55468 }
+ ListElement{ xPos: 0.060965; yPos: -2.44153; zPos: 1.35813 }
+ ListElement{ xPos: 1.4594; yPos: -1.96511; zPos: 0.170228 }
+ ListElement{ xPos: 3.55017; yPos: -2.16882; zPos: 1.75539 }
+ ListElement{ xPos: 2.89487; yPos: -0.727481; zPos: -0.39588 }
+ ListElement{ xPos: -1.65151; yPos: -0.603877; zPos: 0.250508 }
+ ListElement{ xPos: -4.35232; yPos: -1.32155; zPos: -2.31877 }
+ ListElement{ xPos: 2.75852; yPos: -1.88931; zPos: 1.77874 }
+ ListElement{ xPos: 2.75452; yPos: -0.64123; zPos: 2.45546 }
+ ListElement{ xPos: 0.151914; yPos: -0.888395; zPos: -0.260935 }
+ ListElement{ xPos: 0.150593; yPos: 0.0461652; zPos: -0.158206 }
+ ListElement{ xPos: -1.22087; yPos: -2.92034; zPos: -3.78604 }
+ ListElement{ xPos: -0.761622; yPos: 0.161856; zPos: 3.5586 }
+ ListElement{ xPos: -1.88456; yPos: -2.48094; zPos: 0.287091 }
+ ListElement{ xPos: 1.25293; yPos: 2.64374; zPos: 1.6532 }
+ ListElement{ xPos: -0.657609; yPos: 1.32547; zPos: -0.557301 }
+ ListElement{ xPos: 3.85995; yPos: 2.32568; zPos: -1.38265 }
+ ListElement{ xPos: 1.65572; yPos: -2.28977; zPos: 0.957488 }
+ ListElement{ xPos: -2.45312; yPos: -2.96071; zPos: 3.45147 }
+ ListElement{ xPos: -1.75259; yPos: 0.365259; zPos: 1.60113 }
+ ListElement{ xPos: 1.14045; yPos: -0.844805; zPos: 0.359343 }
+ ListElement{ xPos: -1.55951; yPos: 1.65687; zPos: -3.10398 }
+ ListElement{ xPos: 0.441652; yPos: -1.36623; zPos: -1.55494 }
+ ListElement{ xPos: -3.95762; yPos: 0.288753; zPos: 3.7791 }
+ ListElement{ xPos: -1.80101; yPos: -0.241499; zPos: 2.29693 }
+ ListElement{ xPos: -0.456931; yPos: 1.64399; zPos: 1.35559 }
+ ListElement{ xPos: -0.691421; yPos: -0.723378; zPos: 2.51839 }
+ ListElement{ xPos: -1.20658; yPos: -3.04063; zPos: -1.552 }
+ ListElement{ xPos: -0.958574; yPos: 1.48688; zPos: 0.950152 }
+ ListElement{ xPos: 1.76319; yPos: -1.36072; zPos: 2.15866 }
+ ListElement{ xPos: -3.91301; yPos: -0.328932; zPos: -2.45524 }
+ ListElement{ xPos: 2.9553; yPos: 3.12703; zPos: -2.43321 }
+ ListElement{ xPos: 3.55939; yPos: -1.48028; zPos: 0.152252 }
+ ListElement{ xPos: -1.41545; yPos: 3.247; zPos: 0.779314 }
+ ListElement{ xPos: -3.34482; yPos: -0.894538; zPos: 0.209302 }
+ ListElement{ xPos: 1.15338; yPos: 0.362332; zPos: -2.7924 }
+ ListElement{ xPos: 0.468852; yPos: 0.120872; zPos: 1.35593 }
+ ListElement{ xPos: 1.98778; yPos: -3.32292; zPos: -0.758591 }
+ ListElement{ xPos: 0.420718; yPos: -0.225014; zPos: -2.44806 }
+ ListElement{ xPos: 0.550207; yPos: 1.56432; zPos: 2.25317 }
+ ListElement{ xPos: 4.35156; yPos: -0.966178; zPos: -0.791032 }
+ ListElement{ xPos: 3.35179; yPos: -1.04981; zPos: -0.854089 }
+ ListElement{ xPos: 1.70857; yPos: -0.281369; zPos: -0.254686 }
+ ListElement{ xPos: -3.05855; yPos: -0.443693; zPos: 1.30251 }
+ ListElement{ xPos: -3.9509; yPos: -2.72179; zPos: -3.83489 }
+ ListElement{ xPos: 2.19632; yPos: 1.32915; zPos: 1.15345 }
+ ListElement{ xPos: 1.42782; yPos: -0.449435; zPos: -2.85005 }
+ ListElement{ xPos: -2.19053; yPos: 0.049866; zPos: 2.87493 }
+ ListElement{ xPos: 2.45196; yPos: -2.44705; zPos: -2.85302 }
+ ListElement{ xPos: 4.35263; yPos: 0.245956; zPos: 1.12886 }
+ ListElement{ xPos: 1.8167; yPos: 1.85407; zPos: -0.714159 }
+ ListElement{ xPos: 1.88577; yPos: 2.04227; zPos: -0.959396 }
+ ListElement{ xPos: 1.6522; yPos: -2.48289; zPos: 0.355373 }
+ ListElement{ xPos: -3.39965; yPos: 0.286834; zPos: -1.68171 }
+ ListElement{ xPos: 1.85639; yPos: 1.47419; zPos: 2.25749 }
+ ListElement{ xPos: -2.5216; yPos: -0.88573; zPos: -2.69594 }
+ ListElement{ xPos: -0.143043; yPos: 0.28453; zPos: -1.75898 }
+ ListElement{ xPos: -2.52745; yPos: -2.76741; zPos: -0.257011 }
+ ListElement{ xPos: 0.381448; yPos: -1.64793; zPos: -0.756889 }
+ ListElement{ xPos: 2.30469; yPos: -1.28844; zPos: -2.79271 }
+ ListElement{ xPos: -1.72491; yPos: -1.48634; zPos: -2.61686 }
+ ListElement{ xPos: 0.503342; yPos: 0.248352; zPos: -2.27162 }
+ ListElement{ xPos: 0.457491; yPos: -1.88183; zPos: -0.951124 }
+ ListElement{ xPos: -1.23123; yPos: 0.963519; zPos: 2.3569 }
+ ListElement{ xPos: -0.55709; yPos: -0.364372; zPos: -1.82528 }
+ ListElement{ xPos: 2.73963; yPos: -0.567024; zPos: -0.496936 }
+ ListElement{ xPos: 1.17979; yPos: 2.76252; zPos: -2.35641 }
+ ListElement{ xPos: -1.20723; yPos: -2.1692; zPos: 3.14368 }
+ ListElement{ xPos: -0.0504301; yPos: 1.76714; zPos: 1.64265 }
+ ListElement{ xPos: 1.45714; yPos: -0.725448; zPos: 0.739217 }
+ ListElement{ xPos: 0.246123; yPos: -3.27811; zPos: -0.251218 }
+ ListElement{ xPos: -0.894632; yPos: 1.08758; zPos: -0.17184 }
+ ListElement{ xPos: -2.45217; yPos: 0.565077; zPos: -0.557015 }
+ ListElement{ xPos: 2.51301; yPos: 1.1271; zPos: 2.25057 }
+ ListElement{ xPos: -1.05099; yPos: -2.1688; zPos: -1.88996 }
+ ListElement{ xPos: 1.26945; yPos: -2.1225; zPos: -1.55031 }
+ ListElement{ xPos: -4.30129; yPos: -0.760298; zPos: 0.259868 }
+ ListElement{ xPos: -0.157336; yPos: -0.237532; zPos: -2.69483 }
+ ListElement{ xPos: -2.95778; yPos: 1.36212; zPos: -3.65524 }
+ ListElement{ xPos: 2.74003; yPos: 1.16234; zPos: 2.05928 }
+ ListElement{ xPos: 0.955294; yPos: -1.52182; zPos: -1.05684 }
+ ListElement{ xPos: -1.87004; yPos: 2.72729; zPos: -0.550608 }
+ ListElement{ xPos: 0.459439; yPos: 2.88101; zPos: -4.4135 }
+ ListElement{ xPos: 1.35069; yPos: 1.08445; zPos: -0.808625 }
+ ListElement{ xPos: -2.70034; yPos: -1.36291; zPos: -2.65126 }
+ ListElement{ xPos: 0.353099; yPos: 2.32354; zPos: -1.61885 }
+ ListElement{ xPos: -0.453359; yPos: 1.67524; zPos: 0.122888 }
+ ListElement{ xPos: 3.7506; yPos: 1.12855; zPos: -3.86338 }
+ ListElement{ xPos: 0.548626; yPos: 2.63101; zPos: 0.552261 }
+ ListElement{ xPos: -0.753935; yPos: -3.52241; zPos: -3.21122 }
+ ListElement{ xPos: 0.258935; yPos: -1.32453; zPos: 2.25745 }
+ ListElement{ xPos: -2.8797; yPos: -1.60832; zPos: -2.45626 }
+ ListElement{ xPos: 0.151838; yPos: -0.565264; zPos: 3.98539 }
+ ListElement{ xPos: 2.1508; yPos: -1.68734; zPos: -1.75076 }
+ ListElement{ xPos: -4.23947; yPos: -1.92531; zPos: 2.35438 }
+ ListElement{ xPos: 1.95273; yPos: -1.32736; zPos: 0.313398 }
+ ListElement{ xPos: 0.533647; yPos: 0.646686; zPos: 0.75712 }
+ ListElement{ xPos: 0.856619; yPos: -0.843249; zPos: -0.577773 }
+ ListElement{ xPos: -2.15649; yPos: -1.64285; zPos: -2.31055 }
+ ListElement{ xPos: -0.851309; yPos: -2.84717; zPos: -0.324933 }
+ ListElement{ xPos: -3.17772; yPos: -0.324817; zPos: -0.858394 }
+ ListElement{ xPos: 3.45303; yPos: 0.529449; zPos: 0.818094 }
+ ListElement{ xPos: 1.42912; yPos: 0.238088; zPos: 0.385617 }
+ ListElement{ xPos: 0.459586; yPos: 1.7661; zPos: -0.153761 }
+ ListElement{ xPos: 0.855577; yPos: 3.16845; zPos: -2.19548 }
+ ListElement{ xPos: 2.15181; yPos: 0.729021; zPos: -0.955922 }
+ ListElement{ xPos: -2.40113; yPos: 0.665756; zPos: -0.521366 }
+ ListElement{ xPos: 1.45469; yPos: -0.462177; zPos: -1.95869 }
+ ListElement{ xPos: 0.629115; yPos: -1.84769; zPos: 1.86755 }
+ ListElement{ xPos: 3.47576; yPos: -0.209875; zPos: -0.555502 }
+ ListElement{ xPos: 0.295075; yPos: -0.840772; zPos: 4.68895 }
+ ListElement{ xPos: -0.253301; yPos: -2.49078; zPos: 2.11749 }
+ ListElement{ xPos: -1.15923; yPos: -3.163; zPos: -3.05671 }
+ ListElement{ xPos: 1.45484; yPos: 0.963654; zPos: -0.734942 }
+ ListElement{ xPos: 0.354307; yPos: 0.520772; zPos: -1.32278 }
+ ListElement{ xPos: 2.65725; yPos: 0.284589; zPos: -0.856856 }
+ ListElement{ xPos: -1.2596; yPos: 0.765493; zPos: -1.66469 }
+ ListElement{ xPos: -0.656057; yPos: -2.16906; zPos: 3.72144 }
+ ListElement{ xPos: -0.251559; yPos: -2.36406; zPos: -1.89709 }
+ ListElement{ xPos: 0.35608; yPos: -0.80463; zPos: 1.85674 }
+ ListElement{ xPos: 0.0508692; yPos: 0.615674; zPos: 0.856785 }
+ ListElement{ xPos: -2.50726; yPos: 2.28743; zPos: -2.05697 }
+ ListElement{ xPos: 1.65272; yPos: 1.29604; zPos: 2.11481 }
+ ListElement{ xPos: -3.2878; yPos: -0.244516; zPos: 0.799732 }
+ ListElement{ xPos: -2.18989; yPos: -0.847222; zPos: -0.264559 }
+ ListElement{ xPos: 0.452832; yPos: 0.960993; zPos: 2.53691 }
+ ListElement{ xPos: -2.43913; yPos: 1.28957; zPos: 2.75427 }
+ ListElement{ xPos: -1.72889; yPos: -3.29414; zPos: -2.31426 }
+ ListElement{ xPos: 0.952615; yPos: -0.0844651; zPos: 0.346607 }
+ ListElement{ xPos: 1.41175; yPos: 0.889643; zPos: 0.450356 }
+ ListElement{ xPos: 2.13145; yPos: 1.08697; zPos: 0.223055 }
+ ListElement{ xPos: -2.16002; yPos: -0.225505; zPos: -0.602641 }
+ ListElement{ xPos: 0.54028; yPos: 1.24765; zPos: -0.456129 }
+ ListElement{ xPos: 2.55086; yPos: -0.56734; zPos: -2.65051 }
+ ListElement{ xPos: -4.53921; yPos: -0.483588; zPos: -1.25013 }
+ ListElement{ xPos: 3.45413; yPos: -0.44258; zPos: 2.29687 }
+ ListElement{ xPos: -0.257456; yPos: 0.64624; zPos: 1.65041 }
+ ListElement{ xPos: 1.25559; yPos: -0.65493; zPos: -0.358872 }
+ ListElement{ xPos: 1.9599; yPos: -1.56965; zPos: -4.17044 }
+ ListElement{ xPos: 2.75996; yPos: -1.98665; zPos: 3.31794 }
+ ListElement{ xPos: 3.05837; yPos: 1.04847; zPos: -0.975536 }
+ ListElement{ xPos: -2.95407; yPos: 1.40294; zPos: -2.25825 }
+ ListElement{ xPos: 1.38718; yPos: 0.360709; zPos: -2.98211 }
+ ListElement{ xPos: 0.481728; yPos: -2.48564; zPos: 3.25864 }
+ ListElement{ xPos: -1.15089; yPos: 0.363522; zPos: 0.458662 }
+ ListElement{ xPos: -2.25551; yPos: 0.0421839; zPos: 0.650008 }
+ ListElement{ xPos: -1.85862; yPos: -0.969237; zPos: 4.25313 }
+ ListElement{ xPos: 1.55797; yPos: 0.0465051; zPos: -3.85709 }
+ ListElement{ xPos: 0.0555338; yPos: 0.682957; zPos: -2.45556 }
+ ListElement{ xPos: -0.186868; yPos: -0.482811; zPos: 1.96957 }
+ ListElement{ xPos: 2.197; yPos: -1.5248; zPos: -4.20912 }
+ ListElement{ xPos: 3.40636; yPos: 0.626269; zPos: -2.05757 }
+ ListElement{ xPos: 0.780426; yPos: 2.68048; zPos: -0.852693 }
+ ListElement{ xPos: 1.65184; yPos: 1.68951; zPos: -0.892089 }
+ ListElement{ xPos: 2.11929; yPos: -2.44406; zPos: 1.21168 }
+ ListElement{ xPos: -0.153348; yPos: -1.88112; zPos: -0.357374 }
+ ListElement{ xPos: -0.359393; yPos: 1.76654; zPos: 1.63063 }
+ ListElement{ xPos: -2.15954; yPos: 0.0819277; zPos: 0.757621 }
+ ListElement{ xPos: -0.159898; yPos: -3.36316; zPos: 0.359582 }
+ ListElement{ xPos: 2.74125; yPos: -2.84148; zPos: 0.355785 }
+ ListElement{ xPos: -1.3558; yPos: 0.0027827; zPos: -0.588162 }
+ ListElement{ xPos: -0.354346; yPos: -2.56747; zPos: -0.984403 }
+ ListElement{ xPos: -2.75082; yPos: -3.56807; zPos: 1.5599 }
+ ListElement{ xPos: -2.54708; yPos: -0.686147; zPos: 2.75649 }
+ ListElement{ xPos: 1.35138; yPos: 0.692978; zPos: -2.24969 }
+ ListElement{ xPos: -1.39826; yPos: -0.246682; zPos: -1.65876 }
+ ListElement{ xPos: -1.46629; yPos: -1.44446; zPos: 4.45293 }
+ ListElement{ xPos: -1.89442; yPos: 3.64549; zPos: 2.05732 }
+ ListElement{ xPos: -0.658093; yPos: 0.0815129; zPos: 3.95269 }
+ ListElement{ xPos: 2.25603; yPos: 2.96329; zPos: -2.35993 }
+ ListElement{ xPos: 1.36323; yPos: 1.64488; zPos: -0.0538547 }
+ ListElement{ xPos: 1.75659; yPos: 2.24227; zPos: -2.8522 }
+ ListElement{ xPos: -0.0566584; yPos: -1.56465; zPos: -0.0503143 }
+ ListElement{ xPos: -3.8532; yPos: -0.822258; zPos: -0.345406 }
+ ListElement{ xPos: 0.951328; yPos: 0.329296; zPos: -2.52211 }
+ ListElement{ xPos: -2.48659; yPos: 0.410856; zPos: -3.55401 }
+ ListElement{ xPos: 3.72676; yPos: 2.36324; zPos: 3.65589 }
+ ListElement{ xPos: 0.559972; yPos: 0.884984; zPos: 3.15283 }
+ ListElement{ xPos: -1.37624; yPos: -1.36007; zPos: -2.16578 }
+ ListElement{ xPos: 2.05543; yPos: 0.4472; zPos: -0.82911 }
+ ListElement{ xPos: -4.75258; yPos: 1.8779; zPos: -1.75376 }
+ ListElement{ xPos: 0.15648; yPos: 2.64039; zPos: -2.21467 }
+ ListElement{ xPos: -2.0593; yPos: -2.56775; zPos: 1.15037 }
+ ListElement{ xPos: -3.70217; yPos: 2.12375; zPos: 1.3652 }
+ ListElement{ xPos: 1.05566; yPos: 0.98299; zPos: 4.02985 }
+ ListElement{ xPos: -0.766882; yPos: -0.88359; zPos: 1.35525 }
+ ListElement{ xPos: 0.951335; yPos: -1.84689; zPos: 0.178337 }
+ ListElement{ xPos: 0.751608; yPos: 1.1691; zPos: 4.25273 }
+ ListElement{ xPos: -1.36367; yPos: 0.728904; zPos: 0.655858 }
+ ListElement{ xPos: 3.17581; yPos: -0.0844758; zPos: -1.75811 }
+ ListElement{ xPos: 2.85546; yPos: -0.683618; zPos: 0.653701 }
+ ListElement{ xPos: -0.471118; yPos: 3.04176; zPos: -2.35393 }
+ ListElement{ xPos: 3.0574; yPos: -0.601996; zPos: -0.611932 }
+ ListElement{ xPos: -0.854024; yPos: -0.44532; zPos: 0.355575 }
+ ListElement{ xPos: 0.05047; yPos: -1.92888; zPos: 0.64818 }
+ ListElement{ xPos: -1.25515; yPos: -1.44466; zPos: 1.90429 }
+ ListElement{ xPos: -1.67201; yPos: 0.0461708; zPos: -0.796655 }
+ ListElement{ xPos: 1.45345; yPos: -1.66159; zPos: -3.48143 }
+ ListElement{ xPos: -2.84514; yPos: -1.24586; zPos: -3.47945 }
+ ListElement{ xPos: 0.287402; yPos: -0.688276; zPos: -3.75664 }
+ ListElement{ xPos: 3.35908; yPos: 0.687828; zPos: -1.94406 }
+ ListElement{ xPos: -2.39167; yPos: 1.08322; zPos: -1.73508 }
+ ListElement{ xPos: 1.52152; yPos: 1.86032; zPos: -1.25351 }
+ ListElement{ xPos: 1.55931; yPos: 3.64414; zPos: 1.35944 }
+ ListElement{ xPos: 1.15954; yPos: -1.36058; zPos: 0.758814 }
+ ListElement{ xPos: -1.95325; yPos: 0.0851092; zPos: -0.854106 }
+ ListElement{ xPos: -2.25254; yPos: -0.523024; zPos: 1.05486 }
+ ListElement{ xPos: -2.68036; yPos: -1.32901; zPos: 1.05877 }
+ ListElement{ xPos: -0.485956; yPos: -1.52164; zPos: 2.45303 }
+ ListElement{ xPos: -0.0546215; yPos: 0.640683; zPos: -2.85953 }
+ ListElement{ xPos: 4.45295; yPos: -0.246051; zPos: -0.159626 }
+ ListElement{ xPos: 3.4523; yPos: 1.7215; zPos: -1.10587 }
+ ListElement{ xPos: 3.35142; yPos: -1.72053; zPos: -0.252105 }
+ ListElement{ xPos: 1.74251; yPos: 2.76108; zPos: 2.51524 }
+ ListElement{ xPos: 2.15054; yPos: -2.88101; zPos: -1.7527 }
+ ListElement{ xPos: -3.70517; yPos: -0.0470951; zPos: 0.258921 }
+ ListElement{ xPos: -0.45593; yPos: -3.47184; zPos: -2.95345 }
+ ListElement{ xPos: 3.15988; yPos: -2.32107; zPos: 0.105299 }
+ ListElement{ xPos: 0.751449; yPos: -2.88762; zPos: -3.45245 }
+ ListElement{ xPos: 2.9794; yPos: 0.493172; zPos: -0.654683 }
+ ListElement{ xPos: -1.87713; yPos: -2.48632; zPos: -3.534 }
+ ListElement{ xPos: 1.65266; yPos: 3.16008; zPos: 2.1579 }
+ ListElement{ xPos: -1.25239; yPos: -0.763119; zPos: 2.15776 }
+ ListElement{ xPos: 3.5572; yPos: 0.282681; zPos: 2.44174 }
+ ListElement{ xPos: 0.251145; yPos: 0.520256; zPos: 2.3184 }
+ ListElement{ xPos: -1.78596; yPos: -1.36913; zPos: -2.50818 }
+ ListElement{ xPos: 3.82122; yPos: 1.04473; zPos: 0.456159 }
+ ListElement{ xPos: 2.45979; yPos: -0.722759; zPos: -4.05123 }
+ ListElement{ xPos: -0.855594; yPos: 0.163792; zPos: -0.553702 }
+ ListElement{ xPos: 0.656895; yPos: 0.529982; zPos: 3.35129 }
+ ListElement{ xPos: 0.857952; yPos: -0.0885677; zPos: 0.695128 }
+ ListElement{ xPos: -0.143269; yPos: 1.12972; zPos: -3.155 }
+ ListElement{ xPos: -2.95923; yPos: 0.241767; zPos: 0.832165 }
+ ListElement{ xPos: 0.253329; yPos: -0.622952; zPos: -0.459799 }
+ ListElement{ xPos: 0.151499; yPos: 2.5297; zPos: 1.53059 }
+ ListElement{ xPos: 0.655464; yPos: -1.49902; zPos: -1.51071 }
+ ListElement{ xPos: 4.7585; yPos: 1.76425; zPos: 1.15164 }
+ ListElement{ xPos: -1.75063; yPos: -1.44645; zPos: -3.65525 }
+ ListElement{ xPos: 0.850392; yPos: 0.0417223; zPos: -0.340588 }
+ ListElement{ xPos: -0.557015; yPos: -0.282305; zPos: -1.85291 }
+ ListElement{ xPos: -4.05639; yPos: 0.522959; zPos: -2.3507 }
+ ListElement{ xPos: -0.358137; yPos: -0.967852; zPos: 0.807832 }
+ ListElement{ xPos: -1.82056; yPos: -0.0483894; zPos: 0.0541359 }
+ ListElement{ xPos: -2.16623; yPos: 0.129809; zPos: -2.52513 }
+ ListElement{ xPos: -1.85591; yPos: -1.04417; zPos: -1.28501 }
+ ListElement{ xPos: -1.79647; yPos: -3.44045; zPos: -1.73399 }
+ ListElement{ xPos: 2.25342; yPos: 0.161308; zPos: -0.0517495 }
+ ListElement{ xPos: -1.37877; yPos: 0.243596; zPos: -1.52931 }
+ ListElement{ xPos: -0.059299; yPos: -0.480825; zPos: 1.7137 }
+ ListElement{ xPos: -2.54357; yPos: 0.286685; zPos: -2.11495 }
+ ListElement{ xPos: 3.92334; yPos: -0.442936; zPos: -0.852895 }
+ ListElement{ xPos: -0.390023; yPos: -1.96437; zPos: 1.38718 }
+ ListElement{ xPos: 1.35263; yPos: 2.92968; zPos: -1.1545 }
+ ListElement{ xPos: -2.25892; yPos: -2.27429; zPos: -0.451533 }
+ ListElement{ xPos: -0.2215; yPos: -0.126727; zPos: 0.155541 }
+ ListElement{ xPos: 0.715932; yPos: 1.47509; zPos: -3.52895 }
+ ListElement{ xPos: -0.382939; yPos: 3.16461; zPos: 2.65165 }
+ ListElement{ xPos: -1.14437; yPos: -1.44682; zPos: 0.456601 }
+ ListElement{ xPos: 0.251892; yPos: -1.0431; zPos: 4.31548 }
+ ListElement{ xPos: -2.23281; yPos: -2.48698; zPos: 0.46995 }
+ ListElement{ xPos: 0.954231; yPos: -3.4323; zPos: -1.20233 }
+ ListElement{ xPos: 2.75569; yPos: -1.66383; zPos: -1.95486 }
+ ListElement{ xPos: 0.750644; yPos: -1.84163; zPos: -0.159206 }
+ ListElement{ xPos: -0.757387; yPos: -1.84192; zPos: 0.354209 }
+ ListElement{ xPos: -2.85509; yPos: -2.12151; zPos: -0.954754 }
+ ListElement{ xPos: -0.888427; yPos: -2.8403; zPos: -0.157387 }
+ ListElement{ xPos: -1.95265; yPos: -0.445753; zPos: 2.17956 }
+ ListElement{ xPos: -1.05845; yPos: -2.48694; zPos: -1.25315 }
+ ListElement{ xPos: -2.66497; yPos: -1.48251; zPos: 0.873192 }
+ ListElement{ xPos: 2.50491; yPos: -1.0833; zPos: 1.99667 }
+ ListElement{ xPos: 0.453931; yPos: 2.52158; zPos: 0.456875 }
+ ListElement{ xPos: 1.55859; yPos: -0.161924; zPos: 0.256619 }
+ ListElement{ xPos: -2.92585; yPos: 0.368018; zPos: 0.35908 }
+ ListElement{ xPos: 2.95509; yPos: -1.56248; zPos: -2.74469 }
+ ListElement{ xPos: 3.46082; yPos: -1.76026; zPos: 3.05835 }
+ ListElement{ xPos: 1.75644; yPos: -2.84241; zPos: 0.507631 }
+ ListElement{ xPos: -0.959006; yPos: 0.649579; zPos: 1.10824 }
+ ListElement{ xPos: 2.63856; yPos: 2.0417; zPos: 0.187281 }
+ ListElement{ xPos: -0.150004; yPos: -0.0838836; zPos: -0.949314 }
+ ListElement{ xPos: 2.26402; yPos: -1.28916; zPos: 2.85284 }
+ ListElement{ xPos: -0.821239; yPos: 2.68795; zPos: -0.317185 }
+ ListElement{ xPos: 3.47124; yPos: 0.840813; zPos: -2.65322 }
+ ListElement{ xPos: -0.253209; yPos: -0.244177; zPos: 0.457348 }
+ ListElement{ xPos: 2.76181; yPos: 1.64033; zPos: -1.95329 }
+ ListElement{ xPos: 1.35105; yPos: -2.96027; zPos: 0.659952 }
+ ListElement{ xPos: -1.45423; yPos: -0.24358; zPos: 0.832696 }
+ ListElement{ xPos: 1.45109; yPos: 1.64958; zPos: -3.45448 }
+ ListElement{ xPos: -1.15659; yPos: 3.08225; zPos: 1.11445 }
+ ListElement{ xPos: 0.806359; yPos: 1.88298; zPos: -2.13001 }
+ ListElement{ xPos: 1.15538; yPos: 3.04545; zPos: -0.759437 }
+ ListElement{ xPos: -0.450074; yPos: 1.36121; zPos: -0.155042 }
+ ListElement{ xPos: -2.80924; yPos: -1.24207; zPos: 2.55513 }
+ ListElement{ xPos: 4.48859; yPos: 2.04394; zPos: 1.25324 }
+ ListElement{ xPos: -0.958741; yPos: 1.24575; zPos: 3.65169 }
+ ListElement{ xPos: 3.45143; yPos: 1.3276; zPos: 2.5144 }
+ ListElement{ xPos: -3.25232; yPos: 1.12514; zPos: -1.21425 }
+ ListElement{ xPos: -2.45327; yPos: 0.681109; zPos: 4.35764 }
+ ListElement{ xPos: 0.55395; yPos: -0.128353; zPos: -3.95705 }
+ ListElement{ xPos: -0.352458; yPos: 3.08882; zPos: -0.340631 }
+ ListElement{ xPos: 1.35213; yPos: -2.92251; zPos: -3.31166 }
+ ListElement{ xPos: 0.52621; yPos: -0.279201; zPos: 0.959619 }
+ ListElement{ xPos: 1.2243; yPos: -0.240093; zPos: -0.75247 }
+ ListElement{ xPos: -1.29854; yPos: 2.16477; zPos: 3.3507 }
+ ListElement{ xPos: -3.35677; yPos: 2.36713; zPos: 3.4585 }
+ ListElement{ xPos: 0.957717; yPos: -0.885793; zPos: 1.25827 }
+ ListElement{ xPos: 0.150983; yPos: 1.24269; zPos: 0.539106 }
+ ListElement{ xPos: 1.84986; yPos: 1.56932; zPos: -1.05811 }
+ ListElement{ xPos: -1.35563; yPos: 3.82103; zPos: -1.45287 }
+ ListElement{ xPos: 1.4544; yPos: -2.6453; zPos: 0.58082 }
+ ListElement{ xPos: 2.05908; yPos: 1.16496; zPos: 1.44075 }
+ ListElement{ xPos: 2.9507; yPos: -2.4957; zPos: 0.153512 }
+ ListElement{ xPos: -1.10289; yPos: 0.763085; zPos: -1.65351 }
+ ListElement{ xPos: -2.50708; yPos: -2.467; zPos: 1.05892 }
+ ListElement{ xPos: 3.55284; yPos: -1.8509; zPos: -2.55732 }
+ ListElement{ xPos: -0.848034; yPos: 1.24305; zPos: -3.7516 }
+ ListElement{ xPos: -1.35051; yPos: -2.48178; zPos: 2.85326 }
+ ListElement{ xPos: -2.18554; yPos: 1.48771; zPos: -0.155205 }
+ ListElement{ xPos: -0.459278; yPos: 2.68404; zPos: 2.85727 }
+ ListElement{ xPos: 0.854722; yPos: 1.47322; zPos: -3.35951 }
+ ListElement{ xPos: -2.23505; yPos: -2.24254; zPos: 0.353203 }
+ ListElement{ xPos: 1.42395; yPos: -2.32169; zPos: 0.558188 }
+ ListElement{ xPos: 3.65106; yPos: 1.12201; zPos: -4.58409 }
+ ListElement{ xPos: 0.11; yPos: -0.68782; zPos: 1.85804 }
+ ListElement{ xPos: -1.9551; yPos: -0.560204; zPos: -2.0577 }
+ ListElement{ xPos: 1.85964; yPos: 1.32737; zPos: -4.40673 }
+ ListElement{ xPos: -0.616311; yPos: 0.649737; zPos: -0.30189 }
+ ListElement{ xPos: -1.55375; yPos: 1.52043; zPos: -3.75629 }
+ ListElement{ xPos: 1.85417; yPos: 0.486964; zPos: 0.654806 }
+ ListElement{ xPos: 3.35638; yPos: 2.52599; zPos: 1.94343 }
+ ListElement{ xPos: 0.491389; yPos: 1.40427; zPos: -1.31935 }
+ ListElement{ xPos: -2.72939; yPos: 0.844341; zPos: 1.62621 }
+ ListElement{ xPos: -0.525985; yPos: 1.31415; zPos: 0.484015 }
+ ListElement{ xPos: 2.25998; yPos: -1.719; zPos: 1.25143 }
+ ListElement{ xPos: 1.79268; yPos: 2.46378; zPos: -0.951188 }
+ ListElement{ xPos: -3.55105; yPos: 1.04122; zPos: 3.9501 }
+ ListElement{ xPos: 0.954251; yPos: 1.28296; zPos: 2.05486 }
+ ListElement{ xPos: -3.15267; yPos: 1.96498; zPos: -0.361707 }
+ ListElement{ xPos: 1.35358; yPos: -0.921098; zPos: -1.71743 }
+ ListElement{ xPos: -3.16896; yPos: -3.08548; zPos: 0.903411 }
+ ListElement{ xPos: 1.25127; yPos: -2.44903; zPos: 2.25616 }
+ ListElement{ xPos: -3.88899; yPos: 0.761334; zPos: -1.05751 }
+ ListElement{ xPos: -1.05163; yPos: -3.89783; zPos: -0.883668 }
+ ListElement{ xPos: -4.189; yPos: 1.24176; zPos: -2.63816 }
+ ListElement{ xPos: 2.45671; yPos: -1.84859; zPos: 0.352808 }
+ ListElement{ xPos: -1.59618; yPos: 2.16255; zPos: 4.33699 }
+ ListElement{ xPos: 2.10913; yPos: -1.88673; zPos: -0.952497 }
+ ListElement{ xPos: -1.85571; yPos: -0.287392; zPos: 0.277176 }
+ ListElement{ xPos: 0.751967; yPos: 1.04568; zPos: -1.35427 }
+ ListElement{ xPos: 2.85792; yPos: 1.60982; zPos: -2.05454 }
+ ListElement{ xPos: -2.13152; yPos: -0.260207; zPos: -2.15124 }
+ ListElement{ xPos: 1.75432; yPos: 2.28936; zPos: 0.756254 }
+ ListElement{ xPos: 1.95711; yPos: -0.681098; zPos: -2.38037 }
+ ListElement{ xPos: -0.826273; yPos: 1.08361; zPos: 1.6515 }
+ ListElement{ xPos: -1.85118; yPos: 1.89481; zPos: -0.756754 }
+ ListElement{ xPos: -3.0571; yPos: 2.44795; zPos: 0.355341 }
+ ListElement{ xPos: -0.455122; yPos: -3.58071; zPos: -2.85209 }
+ ListElement{ xPos: -0.95805; yPos: 0.641282; zPos: 2.15189 }
+ ListElement{ xPos: -1.35515; yPos: -0.234803; zPos: -0.825819 }
+ ListElement{ xPos: 2.12472; yPos: -0.0444431; zPos: 0.651227 }
+ ListElement{ xPos: 2.67573; yPos: 0.223987; zPos: 2.75712 }
+ ListElement{ xPos: 1.65871; yPos: -0.163059; zPos: 1.6513 }
+ ListElement{ xPos: -1.85429; yPos: 0.68862; zPos: 1.9576 }
+ ListElement{ xPos: -3.05186; yPos: -2.2804; zPos: 0.100919 }
+ ListElement{ xPos: 3.41813; yPos: -1.88775; zPos: 3.67075 }
+ ListElement{ xPos: -0.753778; yPos: 0.36467; zPos: 1.55333 }
+ ListElement{ xPos: 2.97628; yPos: -1.36503; zPos: -1.85135 }
+ ListElement{ xPos: 0.477128; yPos: 1.08862; zPos: 0.858931 }
+ ListElement{ xPos: -1.0531; yPos: 1.0488; zPos: 2.15218 }
+ ListElement{ xPos: 2.66911; yPos: -2.08876; zPos: -0.182397 }
+ ListElement{ xPos: 0.5117; yPos: -2.84097; zPos: -0.953684 }
+ ListElement{ xPos: 0.468302; yPos: 1.88616; zPos: 2.05369 }
+ ListElement{ xPos: -3.16099; yPos: -2.76085; zPos: -2.75679 }
+ ListElement{ xPos: -2.6593; yPos: 3.52373; zPos: -1.24072 }
+ ListElement{ xPos: -4.37957; yPos: -0.286903; zPos: 3.63863 }
+ ListElement{ xPos: -2.85958; yPos: -2.56921; zPos: -2.85723 }
+ ListElement{ xPos: -0.159735; yPos: 2.72758; zPos: -2.80575 }
+ ListElement{ xPos: -0.951849; yPos: -0.607465; zPos: 1.05633 }
+ ListElement{ xPos: 1.93077; yPos: 2.56422; zPos: 1.25446 }
+ ListElement{ xPos: -0.859754; yPos: 0.248106; zPos: 0.0584456 }
+ ListElement{ xPos: 2.4023; yPos: 2.56659; zPos: -1.2588 }
+ ListElement{ xPos: 2.35295; yPos: -1.08729; zPos: 2.7851 }
+ ListElement{ xPos: -2.1537; yPos: -0.765032; zPos: 2.83652 }
+ ListElement{ xPos: 1.40185; yPos: 1.29804; zPos: 2.3588 }
+ ListElement{ xPos: -0.991566; yPos: 1.72049; zPos: 4.17146 }
+ ListElement{ xPos: 3.76736; yPos: -1.48837; zPos: 2.05329 }
+ ListElement{ xPos: -0.251896; yPos: 0.765367; zPos: -1.4087 }
+ ListElement{ xPos: -1.6228; yPos: 0.328693; zPos: 0.0528287 }
+ ListElement{ xPos: 2.56735; yPos: -3.08103; zPos: 0.853144 }
+ ListElement{ xPos: 0.0531812; yPos: -1.96216; zPos: 1.55734 }
+ ListElement{ xPos: -3.77052; yPos: 0.8421; zPos: -0.258953 }
+ ListElement{ xPos: 2.35523; yPos: 0.676643; zPos: -1.55789 }
+ ListElement{ xPos: 1.16702; yPos: 2.64474; zPos: -1.45533 }
+ ListElement{ xPos: 2.55709; yPos: -1.56013; zPos: 2.05351 }
+ ListElement{ xPos: -2.15518; yPos: 3.56253; zPos: 3.257 }
+ ListElement{ xPos: -0.553936; yPos: -1.24935; zPos: 2.65224 }
+ ListElement{ xPos: -0.355931; yPos: 1.32374; zPos: 0.859863 }
+ ListElement{ xPos: -1.92974; yPos: 1.2482; zPos: 1.15936 }
+ ListElement{ xPos: 0.350652; yPos: -2.44371; zPos: -1.35611 }
+ ListElement{ xPos: 2.98996; yPos: -1.08527; zPos: -4.30641 }
+ ListElement{ xPos: 1.82765; yPos: -0.440236; zPos: 1.25528 }
+ ListElement{ xPos: -0.689231; yPos: -1.08813; zPos: -0.668663 }
+ ListElement{ xPos: -0.326426; yPos: -0.881857; zPos: -1.45371 }
+ ListElement{ xPos: -1.0655; yPos: 2.12466; zPos: 2.34146 }
+ ListElement{ xPos: 3.1563; yPos: 0.523166; zPos: -2.8572 }
+ ListElement{ xPos: 0.455505; yPos: 2.48775; zPos: -1.33482 }
+ ListElement{ xPos: 0.53939; yPos: -0.847333; zPos: 0.732877 }
+ ListElement{ xPos: -0.683025; yPos: -0.448889; zPos: -1.35747 }
+ ListElement{ xPos: -1.7711; yPos: -0.125587; zPos: -2.55083 }
+ ListElement{ xPos: -0.512871; yPos: 0.520964; zPos: 1.40731 }
+ ListElement{ xPos: 4.93857; yPos: -1.6805; zPos: -0.127298 }
+ ListElement{ xPos: 1.46098; yPos: -1.64073; zPos: 1.35833 }
+ ListElement{ xPos: 0.0518058; yPos: 0.285151; zPos: -2.2437 }
+ ListElement{ xPos: 1.5587; yPos: -1.23067; zPos: 0.458753 }
+ ListElement{ xPos: 3.13089; yPos: 3.64132; zPos: 1.45181 }
+ ListElement{ xPos: -1.55648; yPos: 2.167; zPos: 0.153491 }
+ ListElement{ xPos: 3.94451; yPos: -2.56372; zPos: -1.25276 }
+ ListElement{ xPos: 4.15866; yPos: 0.646921; zPos: 2.65542 }
+ ListElement{ xPos: -2.88189; yPos: 0.562407; zPos: -1.35379 }
+ ListElement{ xPos: 1.31686; yPos: 1.2808; zPos: 0.804375 }
+ ListElement{ xPos: -2.36912; yPos: -3.08775; zPos: 1.28335 }
+ ListElement{ xPos: 0.575203; yPos: -0.36483; zPos: -2.43958 }
+ ListElement{ xPos: 0.613108; yPos: 0.526892; zPos: 2.75368 }
+ ListElement{ xPos: 3.96027; yPos: -0.525425; zPos: -4.25746 }
+ ListElement{ xPos: -0.510821; yPos: 1.28578; zPos: -0.058488 }
+ ListElement{ xPos: -0.254704; yPos: -3.847; zPos: 3.15258 }
+ ListElement{ xPos: -0.925874; yPos: -1.72014; zPos: -3.15341 }
+ ListElement{ xPos: 0.85704; yPos: 0.84788; zPos: -1.75947 }
+ ListElement{ xPos: -3.35712; yPos: 0.722104; zPos: 2.15645 }
+ ListElement{ xPos: -1.67305; yPos: 0.681216; zPos: 1.65726 }
+ ListElement{ xPos: -3.64682; yPos: 0.867926; zPos: 2.63525 }
+ ListElement{ xPos: -0.715921; yPos: 1.96081; zPos: -0.939934 }
+ ListElement{ xPos: -2.45646; yPos: 1.3249; zPos: -2.75733 }
+ ListElement{ xPos: -1.75798; yPos: 0.725382; zPos: -0.851921 }
+ ListElement{ xPos: -2.78528; yPos: 0.679275; zPos: -2.59212 }
+ ListElement{ xPos: -2.24551; yPos: -3.4597; zPos: -1.85735 }
+ ListElement{ xPos: 0.85142; yPos: 2.28058; zPos: -3.75328 }
+ ListElement{ xPos: -3.85054; yPos: 2.44519; zPos: 4.35081 }
+ ListElement{ xPos: -3.6553; yPos: 0.521917; zPos: 0.293354 }
+ ListElement{ xPos: -4.35959; yPos: -0.528198; zPos: 1.55557 }
+ ListElement{ xPos: 1.45186; yPos: -0.0891161; zPos: -0.468118 }
+ ListElement{ xPos: 1.85594; yPos: -0.761461; zPos: -4.68083 }
+ ListElement{ xPos: 0.950642; yPos: 0.526239; zPos: -1.30614 }
+ ListElement{ xPos: -2.50526; yPos: -0.885606; zPos: -0.362569 }
+ ListElement{ xPos: -2.96569; yPos: 1.68519; zPos: -1.15965 }
+ ListElement{ xPos: -3.212; yPos: 0.260715; zPos: 1.18472 }
+ ListElement{ xPos: 0.950556; yPos: -0.282806; zPos: 0.776252 }
+ ListElement{ xPos: 3.66678; yPos: 1.08585; zPos: -2.15646 }
+ ListElement{ xPos: -0.806289; yPos: 1.72784; zPos: 2.85906 }
+ ListElement{ xPos: 0.363827; yPos: 1.76644; zPos: 0.931866 }
+ ListElement{ xPos: -1.34204; yPos: -0.563686; zPos: -2.34091 }
+ ListElement{ xPos: -2.81333; yPos: 0.415358; zPos: 4.28363 }
+ ListElement{ xPos: 1.52053; yPos: -0.327359; zPos: 0.35052 }
+ ListElement{ xPos: -0.633441; yPos: -0.240518; zPos: 4.05745 }
+ ListElement{ xPos: -2.38947; yPos: -1.84662; zPos: -2.29572 }
+ ListElement{ xPos: -1.95744; yPos: -0.863705; zPos: 1.85889 }
+ ListElement{ xPos: -0.0509082; yPos: -0.164164; zPos: 3.8571 }
+ ListElement{ xPos: 0.156438; yPos: -2.64188; zPos: 1.75836 }
+ ListElement{ xPos: -3.85642; yPos: 1.48025; zPos: 0.171659 }
+ ListElement{ xPos: 0.253545; yPos: 1.0852; zPos: -2.45243 }
+ ListElement{ xPos: -4.11318; yPos: 0.41655; zPos: -0.120976 }
+ ListElement{ xPos: -1.44928; yPos: -0.328222; zPos: -0.871279 }
+ ListElement{ xPos: -1.90972; yPos: -1.8495; zPos: -3.16966 }
+ ListElement{ xPos: 0.359433; yPos: 1.3236; zPos: -3.95045 }
+ ListElement{ xPos: -2.60974; yPos: -1.04138; zPos: 4.25836 }
+ ListElement{ xPos: -3.16336; yPos: -0.961581; zPos: -1.65161 }
+ ListElement{ xPos: -0.552909; yPos: -1.16942; zPos: 4.05164 }
+ ListElement{ xPos: -3.10918; yPos: -1.2402; zPos: -0.555073 }
+ ListElement{ xPos: 2.25494; yPos: 1.24432; zPos: 3.44063 }
+ ListElement{ xPos: -1.24998; yPos: -1.24928; zPos: -4.05493 }
+ ListElement{ xPos: 3.05441; yPos: 1.92762; zPos: 1.43329 }
+ ListElement{ xPos: 0.557032; yPos: -2.7688; zPos: -3.25463 }
+ ListElement{ xPos: -2.05665; yPos: 1.6357; zPos: 0.656665 }
+ ListElement{ xPos: -0.459042; yPos: 0.122664; zPos: -0.152961 }
+ ListElement{ xPos: 2.17715; yPos: 2.96833; zPos: 1.1332 }
+ ListElement{ xPos: 0.0536573; yPos: -2.08635; zPos: -0.736471 }
+ ListElement{ xPos: -3.15658; yPos: 1.0818; zPos: -0.172166 }
+ ListElement{ xPos: -1.95784; yPos: 0.44402; zPos: 0.612685 }
+ ListElement{ xPos: 0.15168; yPos: -0.323951; zPos: 2.85563 }
+ ListElement{ xPos: 0.559356; yPos: 1.63101; zPos: 0.558005 }
+ ListElement{ xPos: -2.05643; yPos: 1.84615; zPos: -1.87964 }
+ ListElement{ xPos: -0.254098; yPos: 0.368208; zPos: 1.23061 }
+ ListElement{ xPos: 4.6438; yPos: -0.209283; zPos: 0.695869 }
+ ListElement{ xPos: -1.95087; yPos: -0.859788; zPos: 0.0539467 }
+ ListElement{ xPos: -0.351737; yPos: -1.04291; zPos: 0.869198 }
+ ListElement{ xPos: 0.776319; yPos: 3.04922; zPos: -3.55278 }
+ ListElement{ xPos: -0.158751; yPos: 1.12538; zPos: -2.59681 }
+ ListElement{ xPos: -0.560997; yPos: -1.68381; zPos: 1.56935 }
+ ListElement{ xPos: 1.71385; yPos: -0.446223; zPos: -1.56843 }
+ ListElement{ xPos: 4.05142; yPos: -1.3243; zPos: 2.85052 }
+ ListElement{ xPos: 1.68685; yPos: -1.56305; zPos: 2.159 }
+ ListElement{ xPos: 0.359977; yPos: -1.16029; zPos: 1.25037 }
+ ListElement{ xPos: -2.65045; yPos: -1.28532; zPos: 0.459338 }
+ ListElement{ xPos: 0.542068; yPos: 1.52601; zPos: -1.35562 }
+ ListElement{ xPos: 1.34386; yPos: 1.68231; zPos: -2.75114 }
+ ListElement{ xPos: 1.25123; yPos: 0.43108; zPos: 0.383897 }
+ ListElement{ xPos: -0.952936; yPos: 3.6889; zPos: 1.75759 }
+ ListElement{ xPos: -3.55855; yPos: 0.484241; zPos: 1.15503 }
+ ListElement{ xPos: -1.71394; yPos: -2.64568; zPos: -3.30684 }
+ ListElement{ xPos: -2.23513; yPos: -2.46812; zPos: 1.05321 }
+ ListElement{ xPos: 0.160986; yPos: 0.442362; zPos: -1.05077 }
+ ListElement{ xPos: 2.05433; yPos: 0.473204; zPos: 0.823968 }
+ ListElement{ xPos: 0.482077; yPos: 0.0477338; zPos: 4.21712 }
+ ListElement{ xPos: -1.25348; yPos: 0.767612; zPos: 0.455813 }
+ ListElement{ xPos: 1.61308; yPos: 2.04125; zPos: -2.95433 }
+ ListElement{ xPos: -2.05862; yPos: -0.444052; zPos: 1.35177 }
+ ListElement{ xPos: -3.2582; yPos: 0.44354; zPos: 2.15898 }
+ ListElement{ xPos: -1.75935; yPos: 0.0459283; zPos: 2.35061 }
+ ListElement{ xPos: -4.15501; yPos: 0.68674; zPos: 0.802439 }
+ ListElement{ xPos: -1.66005; yPos: 1.12067; zPos: 0.552901 }
+ ListElement{ xPos: -0.150828; yPos: 3.4151; zPos: 3.05446 }
+ ListElement{ xPos: -0.274381; yPos: 0.0405946; zPos: -1.45463 }
+ ListElement{ xPos: 2.94543; yPos: -0.360918; zPos: 1.45895 }
+ ListElement{ xPos: -1.42494; yPos: 1.56503; zPos: 1.31012 }
+ ListElement{ xPos: 3.45402; yPos: -1.27808; zPos: -4.29049 }
+ ListElement{ xPos: -1.14819; yPos: 0.674339; zPos: 1.55807 }
+ ListElement{ xPos: -0.356159; yPos: 1.52295; zPos: -0.351445 }
+ ListElement{ xPos: 0.387809; yPos: 0.965119; zPos: 1.84271 }
+ ListElement{ xPos: 2.56165; yPos: -1.84639; zPos: 1.65056 }
+ ListElement{ xPos: -0.619108; yPos: 2.24004; zPos: -2.92134 }
+ ListElement{ xPos: -0.254288; yPos: -2.32744; zPos: -0.720931 }
+ ListElement{ xPos: 3.3508; yPos: -2.24501; zPos: 1.58268 }
+ ListElement{ xPos: 1.69434; yPos: 1.96814; zPos: 3.97439 }
+ ListElement{ xPos: 1.80785; yPos: 0.685502; zPos: -1.55595 }
+ ListElement{ xPos: 2.71587; yPos: 1.76415; zPos: -2.98124 }
+ ListElement{ xPos: -2.35151; yPos: -0.697338; zPos: -3.96269 }
+ ListElement{ xPos: -1.85759; yPos: 1.96899; zPos: -1.25212 }
+ ListElement{ xPos: -4.05922; yPos: 1.12838; zPos: -2.7597 }
+ ListElement{ xPos: -2.85434; yPos: -1.36487; zPos: 0.659987 }
+ ListElement{ xPos: -1.75616; yPos: 2.12969; zPos: -1.35032 }
+ ListElement{ xPos: -2.95985; yPos: -0.0854955; zPos: 3.65545 }
+ ListElement{ xPos: -2.46049; yPos: 2.45378; zPos: 4.51969 }
+ ListElement{ xPos: -0.0577358; yPos: 0.0427911; zPos: 0.359068 }
+ ListElement{ xPos: -3.25346; yPos: 0.161829; zPos: 1.12956 }
+ ListElement{ xPos: 3.55498; yPos: 2.32482; zPos: 1.33302 }
+ ListElement{ xPos: 4.99905; yPos: -1.88748; zPos: 0.450165 }
+ ListElement{ xPos: -2.35406; yPos: -1.44715; zPos: -0.745307 }
+ ListElement{ xPos: 2.44217; yPos: -0.642981; zPos: 0.126924 }
+ ListElement{ xPos: 1.73283; yPos: 1.67362; zPos: 1.91136 }
+ ListElement{ xPos: 1.34239; yPos: -0.434386; zPos: -0.449795 }
+ ListElement{ xPos: -0.9813; yPos: -2.72962; zPos: -3.6889 }
+ ListElement{ xPos: 1.29807; yPos: -0.448566; zPos: 2.13911 }
+ ListElement{ xPos: 0.654017; yPos: 3.54591; zPos: -1.55982 }
+ ListElement{ xPos: -1.55508; yPos: -2.64564; zPos: 0.555 }
+ ListElement{ xPos: 1.92722; yPos: -0.322513; zPos: -2.13691 }
+ ListElement{ xPos: 1.35913; yPos: -2.84853; zPos: -0.470788 }
+ ListElement{ xPos: 0.257868; yPos: -2.68874; zPos: -0.268328 }
+ ListElement{ xPos: -2.05043; yPos: -1.68405; zPos: -3.05075 }
+ ListElement{ xPos: 1.65173; yPos: -0.446801; zPos: -0.75339 }
+ ListElement{ xPos: -1.25655; yPos: 0.965275; zPos: -4.15374 }
+ ListElement{ xPos: -4.27942; yPos: -0.564403; zPos: 1.45826 }
+ ListElement{ xPos: 0.950669; yPos: -2.4416; zPos: -1.68235 }
+ ListElement{ xPos: -0.0541107; yPos: 0.882274; zPos: 3.15308 }
+ ListElement{ xPos: -1.65016; yPos: -0.886156; zPos: -3.85588 }
+ ListElement{ xPos: 0.355613; yPos: 0.217671; zPos: 4.25412 }
+ ListElement{ xPos: 0.800472; yPos: 0.847259; zPos: 3.11114 }
+ ListElement{ xPos: 1.8599; yPos: -2.16981; zPos: -2.22314 }
+ ListElement{ xPos: -2.05061; yPos: 0.164561; zPos: -0.452181 }
+ ListElement{ xPos: 0.854144; yPos: 1.84334; zPos: -1.45981 }
+ ListElement{ xPos: 1.68547; yPos: 1.56536; zPos: -1.45874 }
+ ListElement{ xPos: 1.25244; yPos: -0.844991; zPos: -2.95475 }
+ ListElement{ xPos: -1.92348; yPos: 2.32119; zPos: -1.80444 }
+ ListElement{ xPos: -0.645646; yPos: 2.45193; zPos: -3.65332 }
+ ListElement{ xPos: 4.45803; yPos: 0.527732; zPos: 3.02031 }
+ ListElement{ xPos: 2.74572; yPos: 0.287964; zPos: -1.80485 }
+ ListElement{ xPos: 0.35961; yPos: -2.56437; zPos: 3.97194 }
+ ListElement{ xPos: -0.956828; yPos: -1.28915; zPos: 1.64424 }
+ ListElement{ xPos: 0.414971; yPos: -0.235061; zPos: -2.15757 }
+ ListElement{ xPos: -0.0530542; yPos: -1.08464; zPos: -4.11853 }
+ ListElement{ xPos: 3.17203; yPos: -1.21013; zPos: -3.85806 }
+ ListElement{ xPos: 0.758948; yPos: 0.124698; zPos: -3.10188 }
+ ListElement{ xPos: -1.65404; yPos: -1.16204; zPos: -1.65357 }
+ ListElement{ xPos: -1.65985; yPos: 3.84433; zPos: 4.65101 }
+ ListElement{ xPos: 2.83444; yPos: -2.69529; zPos: -1.65021 }
+ ListElement{ xPos: 1.29814; yPos: -2.76926; zPos: -2.27139 }
+ ListElement{ xPos: 0.462382; yPos: 3.04217; zPos: 3.45153 }
+ ListElement{ xPos: -3.61944; yPos: 1.04723; zPos: -0.638308 }
+ ListElement{ xPos: 2.25235; yPos: 1.64048; zPos: 2.95175 }
+ ListElement{ xPos: -3.05826; yPos: -0.76526; zPos: -2.38243 }
+ ListElement{ xPos: -2.8506; yPos: 2.12102; zPos: -0.659444 }
+ ListElement{ xPos: -0.10046; yPos: 0.0887098; zPos: -1.63621 }
+ ListElement{ xPos: -1.55585; yPos: -1.36073; zPos: 2.2076 }
+ ListElement{ xPos: -0.474968; yPos: 1.56568; zPos: -0.302349 }
+ ListElement{ xPos: 0.36584; yPos: -2.36102; zPos: 1.35289 }
+ ListElement{ xPos: 0.224784; yPos: -0.637694; zPos: 1.62444 }
+ ListElement{ xPos: -0.658172; yPos: 1.9689; zPos: -3.13712 }
+ ListElement{ xPos: 0.646334; yPos: -1.04672; zPos: 2.59285 }
+ ListElement{ xPos: -2.63054; yPos: -1.08263; zPos: -0.851087 }
+ ListElement{ xPos: 1.45804; yPos: -2.56159; zPos: -2.66388 }
+ ListElement{ xPos: -2.45748; yPos: 0.0409116; zPos: -2.85428 }
+ ListElement{ xPos: -0.0561462; yPos: -1.24229; zPos: -1.25145 }
+ ListElement{ xPos: -2.95391; yPos: -1.80896; zPos: -2.05036 }
+ ListElement{ xPos: 4.36778; yPos: -0.969951; zPos: 3.53686 }
+ ListElement{ xPos: 2.15582; yPos: -1.67173; zPos: -0.831609 }
+ ListElement{ xPos: -1.27059; yPos: -1.14919; zPos: 1.7569 }
+ ListElement{ xPos: -1.57398; yPos: -1.28091; zPos: -0.251735 }
+ ListElement{ xPos: 2.59506; yPos: -2.6408; zPos: -0.345589 }
+ ListElement{ xPos: -1.64147; yPos: -0.360324; zPos: 3.1562 }
+ ListElement{ xPos: -0.125427; yPos: -0.641484; zPos: -3.66095 }
+ ListElement{ xPos: -1.9148; yPos: 1.8885; zPos: 2.12972 }
+ ListElement{ xPos: -2.85768; yPos: -2.28782; zPos: -1.55719 }
+ ListElement{ xPos: -1.37239; yPos: -0.485964; zPos: -2.22291 }
+ ListElement{ xPos: 0.516294; yPos: 0.281078; zPos: 0.652612 }
+ ListElement{ xPos: -1.13028; yPos: 0.246659; zPos: -2.25636 }
+ ListElement{ xPos: 1.7555; yPos: -1.41602; zPos: 0.348449 }
+ ListElement{ xPos: 1.5355; yPos: -1.32646; zPos: -2.82417 }
+ ListElement{ xPos: -0.95426; yPos: 2.0824; zPos: -0.383507 }
+ ListElement{ xPos: 2.75739; yPos: 0.241779; zPos: 0.755701 }
+ ListElement{ xPos: 0.752655; yPos: 0.56204; zPos: -1.55738 }
+ ListElement{ xPos: 1.1271; yPos: -3.76145; zPos: 0.43701 }
+ ListElement{ xPos: 0.326656; yPos: -1.24467; zPos: 2.62222 }
+ ListElement{ xPos: -0.259702; yPos: 2.68152; zPos: -3.18542 }
+ ListElement{ xPos: 1.88504; yPos: -0.760623; zPos: 4.42592 }
+ ListElement{ xPos: 0.251673; yPos: 0.472252; zPos: -1.99239 }
+ ListElement{ xPos: -1.66934; yPos: -0.129677; zPos: -2.49048 }
+ ListElement{ xPos: -1.25499; yPos: 2.36151; zPos: -2.98447 }
+ ListElement{ xPos: 2.65331; yPos: 0.0443886; zPos: 0.108819 }
+ ListElement{ xPos: -3.35439; yPos: 0.0872109; zPos: -1.33429 }
+ ListElement{ xPos: 3.35597; yPos: 0.0403626; zPos: 2.8203 }
+ ListElement{ xPos: 4.45191; yPos: 1.24824; zPos: -0.95821 }
+ ListElement{ xPos: -0.171441; yPos: -1.52622; zPos: -4.68253 }
+ ListElement{ xPos: 1.19986; yPos: 1.72992; zPos: -3.29566 }
+ ListElement{ xPos: -0.451339; yPos: 1.92976; zPos: 1.21534 }
+ ListElement{ xPos: -0.743119; yPos: -0.160688; zPos: 0.805688 }
+ ListElement{ xPos: 2.38678; yPos: 0.272829; zPos: 2.75457 }
+ ListElement{ xPos: 1.72464; yPos: 2.08232; zPos: -0.950389 }
+ ListElement{ xPos: 2.11167; yPos: 1.56576; zPos: 1.21969 }
+ ListElement{ xPos: 3.05157; yPos: -1.56838; zPos: -0.450535 }
+ ListElement{ xPos: 0.132417; yPos: -3.08279; zPos: -1.34127 }
+ ListElement{ xPos: 1.65441; yPos: -0.325204; zPos: -0.825145 }
+ ListElement{ xPos: 2.05515; yPos: -1.8417; zPos: 1.8561 }
+ ListElement{ xPos: -0.555858; yPos: 1.52007; zPos: -0.80122 }
+ ListElement{ xPos: -2.05026; yPos: 1.52773; zPos: 2.33529 }
+ ListElement{ xPos: -2.35231; yPos: -3.28716; zPos: 3.53598 }
+ ListElement{ xPos: -2.25771; yPos: 2.5667; zPos: -1.92243 }
+ ListElement{ xPos: 0.386884; yPos: -3.28674; zPos: 3.49336 }
+ ListElement{ xPos: 0.957272; yPos: -1.68124; zPos: -1.89095 }
+ ListElement{ xPos: 2.99881; yPos: 2.24992; zPos: -0.0535837 }
+ ListElement{ xPos: 3.19604; yPos: 2.92893; zPos: -2.16276 }
+ ListElement{ xPos: 2.55903; yPos: 3.12413; zPos: 2.95022 }
+ ListElement{ xPos: -0.570758; yPos: -2.85326; zPos: -0.339255 }
+ ListElement{ xPos: 0.356627; yPos: 0.641074; zPos: 0.355538 }
+ ListElement{ xPos: -1.15777; yPos: -0.162227; zPos: -0.455885 }
+ ListElement{ xPos: -2.4871; yPos: -0.885492; zPos: -0.374875 }
+ ListElement{ xPos: 1.55464; yPos: -1.48929; zPos: -0.593706 }
+ ListElement{ xPos: -0.852655; yPos: -2.08736; zPos: -1.18281 }
+ ListElement{ xPos: 0.504087; yPos: -0.879247; zPos: 1.35148 }
+ ListElement{ xPos: -2.15261; yPos: 1.04511; zPos: -3.25543 }
+ ListElement{ xPos: -0.653745; yPos: -1.32873; zPos: -3.18964 }
+ ListElement{ xPos: 0.35973; yPos: -0.844255; zPos: -1.72034 }
+ ListElement{ xPos: -2.11112; yPos: 0.962572; zPos: 2.59386 }
+ ListElement{ xPos: 1.47531; yPos: -0.693626; zPos: -3.15249 }
+ ListElement{ xPos: -1.35814; yPos: 0.767202; zPos: 3.45094 }
+ ListElement{ xPos: -3.05812; yPos: -1.26298; zPos: 1.45287 }
+ ListElement{ xPos: -1.63927; yPos: 0.893568; zPos: -1.94978 }
+ ListElement{ xPos: 0.751992; yPos: 1.68559; zPos: 2.3618 }
+ ListElement{ xPos: 0.58205; yPos: -0.769076; zPos: -0.958994 }
+ ListElement{ xPos: -2.38512; yPos: -0.166005; zPos: -1.25855 }
+ ListElement{ xPos: -0.556071; yPos: -2.28164; zPos: 1.57032 }
+ ListElement{ xPos: 0.589172; yPos: -0.244834; zPos: 0.553728 }
+ ListElement{ xPos: -0.468648; yPos: -0.927756; zPos: -0.737048 }
+ ListElement{ xPos: -2.66659; yPos: -1.64374; zPos: -0.995568 }
+ ListElement{ xPos: 0.251499; yPos: -1.24569; zPos: 0.95028 }
+ ListElement{ xPos: 0.737721; yPos: 2.88575; zPos: 0.448918 }
+ ListElement{ xPos: -0.634421; yPos: -1.84377; zPos: -2.47094 }
+ ListElement{ xPos: 0.556349; yPos: 2.86774; zPos: 1.62621 }
+ ListElement{ xPos: -3.29644; yPos: 3.28642; zPos: -3.45459 }
+ ListElement{ xPos: -1.45045; yPos: 2.52138; zPos: 2.75329 }
+ ListElement{ xPos: 1.87449; yPos: -2.68651; zPos: 3.60371 }
+ ListElement{ xPos: -0.656424; yPos: -2.56828; zPos: 1.35509 }
+ ListElement{ xPos: 1.95771; yPos: -1.48325; zPos: -3.85265 }
+ ListElement{ xPos: 3.85074; yPos: 0.0825779; zPos: -1.65283 }
+ ListElement{ xPos: -1.17278; yPos: -1.69258; zPos: -1.05875 }
+ ListElement{ xPos: 1.25754; yPos: -0.0439433; zPos: 1.3571 }
+ ListElement{ xPos: 2.91459; yPos: 2.08715; zPos: 0.259842 }
+ ListElement{ xPos: 0.748196; yPos: 2.52132; zPos: 0.659051 }
+ ListElement{ xPos: -1.85745; yPos: -1.88355; zPos: 1.05259 }
+ ListElement{ xPos: 1.69311; yPos: 1.12299; zPos: -3.05453 }
+ ListElement{ xPos: -2.86301; yPos: 0.123274; zPos: 0.559289 }
+ ListElement{ xPos: -1.43146; yPos: 1.64425; zPos: -1.95053 }
+ ListElement{ xPos: 2.35287; yPos: 0.414621; zPos: -0.950807 }
+ ListElement{ xPos: 0.952585; yPos: -0.280452; zPos: -0.844588 }
+ ListElement{ xPos: -1.53836; yPos: -1.76396; zPos: 2.51742 }
+ ListElement{ xPos: -3.05431; yPos: 2.45; zPos: -0.26571 }
+ ListElement{ xPos: -0.637412; yPos: 1.12028; zPos: -0.587387 }
+ ListElement{ xPos: -1.71017; yPos: -0.728497; zPos: -1.05087 }
+ ListElement{ xPos: 0.454027; yPos: 0.849208; zPos: -0.151716 }
+ ListElement{ xPos: -3.54846; yPos: 0.367137; zPos: 1.1484 }
+ ListElement{ xPos: 0.200816; yPos: -2.08832; zPos: 0.255433 }
+ ListElement{ xPos: -2.15817; yPos: 0.725338; zPos: 0.852676 }
+ ListElement{ xPos: -2.45708; yPos: -0.725538; zPos: -0.859435 }
+ ListElement{ xPos: 4.25339; yPos: 1.84177; zPos: -0.555145 }
+ ListElement{ xPos: 0.119319; yPos: -1.32041; zPos: -1.45864 }
+ ListElement{ xPos: -3.65177; yPos: 0.361323; zPos: 0.351554 }
+ ListElement{ xPos: 0.928598; yPos: 0.321024; zPos: -3.64759 }
+ ListElement{ xPos: -0.250062; yPos: 1.84912; zPos: 0.949887 }
+ ListElement{ xPos: 3.59593; yPos: 0.929451; zPos: -2.15224 }
+ ListElement{ xPos: 0.658599; yPos: 2.36796; zPos: 4.11632 }
+ ListElement{ xPos: 0.55841; yPos: -1.08657; zPos: 0.258326 }
+ ListElement{ xPos: 1.9419; yPos: 0.0488641; zPos: -3.75375 }
+ ListElement{ xPos: 0.0589998; yPos: 0.521351; zPos: -3.85723 }
+ ListElement{ xPos: 3.45108; yPos: 0.562199; zPos: 4.20946 }
+ ListElement{ xPos: -0.255073; yPos: 2.36504; zPos: -3.556 }
+ ListElement{ xPos: 1.95848; yPos: 1.28698; zPos: 4.25014 }
+ ListElement{ xPos: 1.85411; yPos: 3.12131; zPos: 0.652769 }
+ ListElement{ xPos: -3.3589; yPos: 0.801817; zPos: -1.5591 }
+ ListElement{ xPos: 2.48128; yPos: -0.163406; zPos: 1.25243 }
+ ListElement{ xPos: 2.52135; yPos: 0.960467; zPos: 1.45432 }
+ ListElement{ xPos: 0.827496; yPos: -0.163021; zPos: -0.558886 }
+ ListElement{ xPos: -3.45032; yPos: -0.0831453; zPos: 0.851212 }
+ ListElement{ xPos: -3.45334; yPos: 0.855944; zPos: 1.35596 }
+ ListElement{ xPos: -0.834644; yPos: 0.245598; zPos: -1.23878 }
+ ListElement{ xPos: 3.64558; yPos: 1.16831; zPos: -0.18559 }
+ ListElement{ xPos: -1.45919; yPos: -3.08196; zPos: -0.329805 }
+ ListElement{ xPos: -3.65715; yPos: -1.04485; zPos: 0.159208 }
+ ListElement{ xPos: 1.35463; yPos: -0.766382; zPos: 0.558632 }
+ ListElement{ xPos: -2.75899; yPos: -1.67431; zPos: -1.55069 }
+ ListElement{ xPos: 1.67539; yPos: 2.04337; zPos: 0.912884 }
+ ListElement{ xPos: 3.51677; yPos: -3.24285; zPos: 0.170272 }
+ ListElement{ xPos: -4.25189; yPos: 0.56589; zPos: -4.35936 }
+ ListElement{ xPos: -2.68545; yPos: -0.448243; zPos: 0.540342 }
+ ListElement{ xPos: 0.279844; yPos: -0.641466; zPos: -0.353986 }
+ ListElement{ xPos: -3.27626; yPos: -3.52006; zPos: 1.77644 }
+ ListElement{ xPos: -0.724096; yPos: -1.84647; zPos: -2.45271 }
+ ListElement{ xPos: -4.18603; yPos: 0.123376; zPos: 3.85066 }
+ ListElement{ xPos: -4.05156; yPos: 0.0499386; zPos: -0.332945 }
+ ListElement{ xPos: 2.69508; yPos: 2.32777; zPos: 1.26256 }
+ ListElement{ xPos: -0.353726; yPos: 1.07252; zPos: -0.738837 }
+ ListElement{ xPos: -0.947178; yPos: 0.36459; zPos: 1.55593 }
+ ListElement{ xPos: -0.058346; yPos: 2.44781; zPos: -4.35023 }
+ ListElement{ xPos: 1.51586; yPos: -0.961109; zPos: -3.43483 }
+ ListElement{ xPos: -2.8852; yPos: 2.08863; zPos: -1.75468 }
+ ListElement{ xPos: -0.297867; yPos: 0.722757; zPos: 0.91355 }
+ ListElement{ xPos: -1.12917; yPos: -1.68328; zPos: 0.175315 }
+ ListElement{ xPos: -1.24248; yPos: -0.323519; zPos: -0.854841 }
+ ListElement{ xPos: 0.751943; yPos: 0.564075; zPos: 3.95073 }
+ ListElement{ xPos: 1.15436; yPos: 2.81813; zPos: 0.653114 }
+ ListElement{ xPos: -2.55058; yPos: -2.56486; zPos: 0.756618 }
+ ListElement{ xPos: 0.959794; yPos: 0.845224; zPos: -0.854001 }
+ ListElement{ xPos: -2.15033; yPos: 0.248556; zPos: -3.16151 }
+ ListElement{ xPos: 0.353224; yPos: -1.68637; zPos: 0.457949 }
+ ListElement{ xPos: -0.753237; yPos: -2.48313; zPos: -0.355373 }
+ ListElement{ xPos: 0.296585; yPos: 2.04822; zPos: 0.198473 }
+ ListElement{ xPos: -2.4737; yPos: 0.682952; zPos: 1.85349 }
+ ListElement{ xPos: 1.9027; yPos: 0.882796; zPos: 1.45908 }
+ ListElement{ xPos: 0.254799; yPos: 1.92572; zPos: -1.70848 }
+ ListElement{ xPos: -0.951602; yPos: -0.698987; zPos: -2.22682 }
+ ListElement{ xPos: 0.262582; yPos: -2.44093; zPos: 1.05636 }
+ ListElement{ xPos: 0.385415; yPos: -0.685667; zPos: -3.35928 }
+ ListElement{ xPos: 0.055981; yPos: 0.523585; zPos: -3.36093 }
+ ListElement{ xPos: -0.0518635; yPos: -0.889068; zPos: -0.840648 }
+ ListElement{ xPos: 0.455171; yPos: 2.84624; zPos: -1.98276 }
+ ListElement{ xPos: 2.85475; yPos: -0.685697; zPos: -2.45695 }
+ ListElement{ xPos: -1.05047; yPos: -1.92121; zPos: 0.931666 }
+ ListElement{ xPos: -2.75962; yPos: -0.164458; zPos: -1.55261 }
+ ListElement{ xPos: 1.91811; yPos: 1.65767; zPos: 3.15004 }
+ ListElement{ xPos: -2.25653; yPos: 0.856735; zPos: -4.351 }
+ ListElement{ xPos: 1.89178; yPos: -0.728669; zPos: -3.8803 }
+ ListElement{ xPos: -3.25958; yPos: -1.16223; zPos: -1.85148 }
+ ListElement{ xPos: -1.6291; yPos: -2.32967; zPos: -0.874786 }
+ ListElement{ xPos: 3.17524; yPos: 0.327351; zPos: 2.15337 }
+ ListElement{ xPos: -1.05094; yPos: -0.560694; zPos: -3.4581 }
+ ListElement{ xPos: 0.759785; yPos: -1.0801; zPos: -0.257876 }
+ ListElement{ xPos: -0.4597; yPos: -2.2404; zPos: 2.54341 }
+ ListElement{ xPos: 0.242266; yPos: 0.121832; zPos: 0.780878 }
+ ListElement{ xPos: -1.6573; yPos: 1.56049; zPos: -1.61749 }
+ ListElement{ xPos: 1.39912; yPos: -2.84395; zPos: -0.25965 }
+ ListElement{ xPos: 0.552342; yPos: 1.36924; zPos: 2.95278 }
+ ListElement{ xPos: -1.92932; yPos: -0.883981; zPos: 0.0589583 }
+ ListElement{ xPos: -1.9026; yPos: 0.56297; zPos: -0.723523 }
+ ListElement{ xPos: -1.25867; yPos: -0.529617; zPos: 2.3503 }
+ ListElement{ xPos: 2.16527; yPos: -0.723364; zPos: -1.45609 }
+ ListElement{ xPos: -0.984608; yPos: -0.720375; zPos: -2.05144 }
+ ListElement{ xPos: 0.946803; yPos: -0.969188; zPos: -3.14625 }
+ ListElement{ xPos: 2.7986; yPos: -2.36069; zPos: 1.25827 }
+ ListElement{ xPos: 3.37979; yPos: -1.11699; zPos: -3.55116 }
+ ListElement{ xPos: -0.753992; yPos: -1.9649; zPos: 2.98548 }
+ ListElement{ xPos: -0.456321; yPos: 0.525303; zPos: -0.109174 }
+ ListElement{ xPos: 0.8642; yPos: 0.153147; zPos: -3.34749 }
+ ListElement{ xPos: -0.450507; yPos: -0.443265; zPos: -2.8325 }
+ ListElement{ xPos: -3.24876; yPos: 3.12791; zPos: 0.576143 }
+ ListElement{ xPos: -3.14755; yPos: 2.84502; zPos: -0.45749 }
+ ListElement{ xPos: 0.226819; yPos: -1.28172; zPos: 0.939501 }
+ ListElement{ xPos: -0.650725; yPos: -1.40317; zPos: -1.35211 }
+ ListElement{ xPos: -0.451625; yPos: 1.84271; zPos: -0.950536 }
+ ListElement{ xPos: -1.36693; yPos: 0.850218; zPos: -1.09799 }
+ ListElement{ xPos: 2.35374; yPos: -0.28759; zPos: 1.55815 }
+ ListElement{ xPos: 2.60577; yPos: -2.46765; zPos: 1.2633 }
+ ListElement{ xPos: 1.21562; yPos: -1.72153; zPos: -0.4034 }
+ ListElement{ xPos: -2.43216; yPos: 3.04268; zPos: 1.90109 }
+ ListElement{ xPos: -1.85365; yPos: -0.16523; zPos: -2.98947 }
+ ListElement{ xPos: -0.953475; yPos: -2.76164; zPos: 2.53396 }
+ ListElement{ xPos: 2.55264; yPos: -0.645611; zPos: 3.25479 }
+ ListElement{ xPos: -0.675476; yPos: -0.724382; zPos: -1.4566 }
+ ListElement{ xPos: 0.801062; yPos: 0.16778; zPos: 2.21815 }
+ ListElement{ xPos: 0.680443; yPos: 0.0407888; zPos: 3.27279 }
+ ListElement{ xPos: -3.75426; yPos: -0.247624; zPos: 0.26307 }
+ ListElement{ xPos: 0.427609; yPos: -0.322312; zPos: 0.652005 }
+ ListElement{ xPos: -2.17428; yPos: -3.68643; zPos: 0.256619 }
+ ListElement{ xPos: 0.456732; yPos: -2.44858; zPos: 3.13051 }
+ ListElement{ xPos: 0.524175; yPos: -1.88231; zPos: -1.93171 }
+ ListElement{ xPos: 0.92311; yPos: 1.8354; zPos: -1.2502 }
+ ListElement{ xPos: 2.69502; yPos: 2.25543; zPos: -4.25127 }
+ ListElement{ xPos: 0.259357; yPos: 2.28099; zPos: -0.476734 }
+ ListElement{ xPos: -0.327316; yPos: -1.24908; zPos: 0.78628 }
+ ListElement{ xPos: 0.190362; yPos: -3.26019; zPos: 0.0545844 }
+ ListElement{ xPos: -1.25409; yPos: -0.761609; zPos: 2.65361 }
+ ListElement{ xPos: 2.86816; yPos: 0.443709; zPos: 0.556137 }
+ ListElement{ xPos: 2.60289; yPos: -0.680561; zPos: 0.248414 }
+ ListElement{ xPos: 1.75631; yPos: -0.162859; zPos: -3.62488 }
+ ListElement{ xPos: -0.559754; yPos: -0.16222; zPos: 2.35858 }
+ ListElement{ xPos: 0.157552; yPos: -1.72639; zPos: -0.48056 }
+ ListElement{ xPos: 3.69492; yPos: -0.848265; zPos: -0.256413 }
+ ListElement{ xPos: -1.4264; yPos: -1.48589; zPos: -2.724 }
+ ListElement{ xPos: 1.15372; yPos: -0.27228; zPos: -2.75499 }
+ ListElement{ xPos: -1.85159; yPos: 1.76577; zPos: -0.858854 }
+ ListElement{ xPos: 0.0580466; yPos: -0.41133; zPos: 1.05649 }
+ ListElement{ xPos: -1.25289; yPos: 0.528142; zPos: -0.386138 }
+ ListElement{ xPos: -0.858101; yPos: -0.245127; zPos: 1.95078 }
+ ListElement{ xPos: -1.95302; yPos: -0.0862415; zPos: -1.95806 }
+ ListElement{ xPos: -2.79133; yPos: -0.527094; zPos: -0.356931 }
+ ListElement{ xPos: 0.110597; yPos: 0.167534; zPos: 4.12784 }
+ ListElement{ xPos: -0.637771; yPos: -0.526587; zPos: -1.25734 }
+ ListElement{ xPos: -2.311; yPos: -0.489068; zPos: -2.8594 }
+ ListElement{ xPos: -0.352617; yPos: -1.23939; zPos: -1.85435 }
+ ListElement{ xPos: 1.78814; yPos: -0.265883; zPos: 3.35913 }
+ ListElement{ xPos: 2.75557; yPos: -2.32271; zPos: 3.15559 }
+ ListElement{ xPos: 2.85426; yPos: 0.443661; zPos: 0.921828 }
+ ListElement{ xPos: -2.14262; yPos: 0.400863; zPos: 1.11614 }
+ ListElement{ xPos: 4.45496; yPos: -0.488668; zPos: -3.7533 }
+ ListElement{ xPos: 0.754719; yPos: -1.44165; zPos: 0.853323 }
+ ListElement{ xPos: -0.856506; yPos: 1.76559; zPos: 1.05702 }
+ ListElement{ xPos: -0.418565; yPos: -0.921031; zPos: -2.43699 }
+ ListElement{ xPos: -1.29292; yPos: -0.282271; zPos: -1.62927 }
+ ListElement{ xPos: -0.759531; yPos: 0.566692; zPos: -0.750991 }
+ ListElement{ xPos: 0.559787; yPos: 1.72479; zPos: -0.26667 }
+ ListElement{ xPos: 2.75533; yPos: -0.245187; zPos: -0.543844 }
+ ListElement{ xPos: -2.27924; yPos: -1.04154; zPos: -4.05156 }
+ ListElement{ xPos: 3.35852; yPos: -0.561129; zPos: -2.98986 }
+ ListElement{ xPos: 2.41843; yPos: -0.321119; zPos: -1.55651 }
+ ListElement{ xPos: 0.85431; yPos: -0.883719; zPos: -2.17826 }
+ ListElement{ xPos: 0.417867; yPos: 0.242995; zPos: -0.456326 }
+ ListElement{ xPos: -4.17449; yPos: -0.720086; zPos: 0.355145 }
+ ListElement{ xPos: -0.577386; yPos: 0.0440364; zPos: -0.950268 }
+ ListElement{ xPos: -2.8156; yPos: 2.92346; zPos: 0.958713 }
+ ListElement{ xPos: -1.65138; yPos: 0.963561; zPos: 1.25265 }
+ ListElement{ xPos: 1.45117; yPos: 0.845424; zPos: 0.252789 }
+ ListElement{ xPos: 2.27848; yPos: -1.76777; zPos: 0.0117707 }
+ ListElement{ xPos: -0.754248; yPos: 1.08381; zPos: -2.15345 }
+ ListElement{ xPos: -3.15415; yPos: -0.162292; zPos: -1.15347 }
+ ListElement{ xPos: 2.55396; yPos: -1.68912; zPos: 1.15698 }
+ ListElement{ xPos: 0.159561; yPos: 1.84202; zPos: 0.428428 }
+ ListElement{ xPos: -1.58079; yPos: 2.04207; zPos: 3.29578 }
+ ListElement{ xPos: -1.05711; yPos: -0.843112; zPos: -1.98122 }
+ ListElement{ xPos: 0.138866; yPos: -2.96642; zPos: -0.212247 }
+ ListElement{ xPos: 2.6778; yPos: 0.686972; zPos: -2.6553 }
+ ListElement{ xPos: -0.498291; yPos: -0.362846; zPos: -1.0538 }
+ ListElement{ xPos: 1.19872; yPos: 0.0476518; zPos: -2.42077 }
+ ListElement{ xPos: -2.3972; yPos: 1.40393; zPos: -0.1943 }
+ ListElement{ xPos: 0.85034; yPos: -1.24222; zPos: 1.5646 }
+ ListElement{ xPos: 0.142174; yPos: 0.249903; zPos: -2.75252 }
+ ListElement{ xPos: 1.15197; yPos: 1.32746; zPos: -1.70104 }
+ ListElement{ xPos: 0.359387; yPos: -1.65115; zPos: -2.75243 }
+ ListElement{ xPos: 0.357917; yPos: -1.36406; zPos: 2.55102 }
+ ListElement{ xPos: -1.65268; yPos: -0.28339; zPos: 2.75665 }
+ ListElement{ xPos: -3.82681; yPos: -3.28984; zPos: 2.55128 }
+ ListElement{ xPos: -2.61371; yPos: 1.08247; zPos: -0.457068 }
+ ListElement{ xPos: 1.67152; yPos: -2.25527; zPos: 2.45819 }
+ ListElement{ xPos: -0.753832; yPos: 1.1682; zPos: -1.95913 }
+ ListElement{ xPos: -2.05131; yPos: 3.04537; zPos: 1.91954 }
+ ListElement{ xPos: -0.695378; yPos: 2.88924; zPos: 1.41965 }
+ ListElement{ xPos: 3.25767; yPos: -2.76934; zPos: -0.354127 }
+ ListElement{ xPos: -0.746795; yPos: 1.36034; zPos: 1.85953 }
+ ListElement{ xPos: 0.859135; yPos: 1.08222; zPos: 0.959116 }
+ ListElement{ xPos: 1.15898; yPos: 1.32443; zPos: 3.41981 }
+ ListElement{ xPos: 3.67954; yPos: 0.447177; zPos: -0.414029 }
+ ListElement{ xPos: 0.907323; yPos: 2.12852; zPos: 1.95837 }
+ ListElement{ xPos: -0.683456; yPos: -0.247536; zPos: 1.05621 }
+ ListElement{ xPos: -1.67809; yPos: 0.641544; zPos: -1.31143 }
+ ListElement{ xPos: -1.30192; yPos: -0.677394; zPos: -1.95159 }
+ ListElement{ xPos: -0.105665; yPos: 0.365205; zPos: -1.2567 }
+ ListElement{ xPos: 1.12189; yPos: -1.48969; zPos: 0.957166 }
+ ListElement{ xPos: 1.25554; yPos: -1.92186; zPos: -0.904086 }
+ ListElement{ xPos: 0.117786; yPos: 2.23836; zPos: -0.498009 }
+ ListElement{ xPos: -2.85788; yPos: 0.643364; zPos: 0.55867 }
+ ListElement{ xPos: -1.66115; yPos: 2.56146; zPos: -2.28632 }
+ ListElement{ xPos: -3.29334; yPos: -0.0894367; zPos: -0.656519 }
+ ListElement{ xPos: 3.15561; yPos: -0.769732; zPos: 1.15695 }
+ ListElement{ xPos: 1.25684; yPos: 0.64652; zPos: -2.6002 }
+ ListElement{ xPos: 2.65231; yPos: -3.52625; zPos: 1.55617 }
+ ListElement{ xPos: -1.8573; yPos: -1.76276; zPos: -1.74075 }
+ ListElement{ xPos: -1.55648; yPos: 1.2592; zPos: 2.1585 }
+ ListElement{ xPos: -0.555522; yPos: 2.88068; zPos: -2.85423 }
+ ListElement{ xPos: -3.14249; yPos: -0.288592; zPos: -3.35534 }
+ ListElement{ xPos: 2.45413; yPos: 0.0854903; zPos: -2.20507 }
+ ListElement{ xPos: -2.05464; yPos: 0.887836; zPos: -0.658349 }
+ ListElement{ xPos: 1.12708; yPos: -0.568106; zPos: -3.65865 }
+ ListElement{ xPos: -0.59147; yPos: -0.685439; zPos: 0.301612 }
+ ListElement{ xPos: 0.473679; yPos: 0.886411; zPos: -1.65386 }
+ ListElement{ xPos: 1.65166; yPos: 0.166741; zPos: 0.751119 }
+ ListElement{ xPos: -0.299287; yPos: 1.72233; zPos: 0.618559 }
+ ListElement{ xPos: 1.15181; yPos: -0.488026; zPos: 0.124448 }
+ ListElement{ xPos: 0.0561315; yPos: 1.04877; zPos: 2.15922 }
+ ListElement{ xPos: 0.154258; yPos: -0.64401; zPos: -1.31179 }
+ ListElement{ xPos: 1.75863; yPos: -1.88571; zPos: -2.8537 }
+ ListElement{ xPos: 3.35024; yPos: 1.28154; zPos: -1.05461 }
+ ListElement{ xPos: -3.71738; yPos: -2.88631; zPos: -1.05314 }
+ ListElement{ xPos: -1.78258; yPos: 3.08967; zPos: 0.150476 }
+ ListElement{ xPos: 3.47828; yPos: -2.524; zPos: -2.45502 }
+ ListElement{ xPos: -0.159138; yPos: 0.160633; zPos: -0.338796 }
+ ListElement{ xPos: -2.15885; yPos: -0.82959; zPos: 1.25022 }
+ ListElement{ xPos: -1.95268; yPos: -0.841195; zPos: -3.19487 }
+ ListElement{ xPos: -0.281381; yPos: -0.887435; zPos: -3.55807 }
+ ListElement{ xPos: 0.415164; yPos: 0.326482; zPos: -1.55411 }
+ ListElement{ xPos: 1.92868; yPos: -2.84771; zPos: -0.556196 }
+ ListElement{ xPos: 1.05804; yPos: 1.32866; zPos: 0.66596 }
+ ListElement{ xPos: 2.46545; yPos: 1.00747; zPos: 2.35957 }
+ ListElement{ xPos: -0.77358; yPos: 0.284677; zPos: -1.38805 }
+ ListElement{ xPos: 0.851046; yPos: 0.960742; zPos: -2.70934 }
+ ListElement{ xPos: -0.858208; yPos: -0.884015; zPos: 1.16663 }
+ ListElement{ xPos: 1.55291; yPos: 0.282705; zPos: -3.18254 }
+ ListElement{ xPos: 1.99034; yPos: 0.286298; zPos: -1.75821 }
+ ListElement{ xPos: 0.259097; yPos: -2.04379; zPos: -0.858936 }
+ ListElement{ xPos: -1.5956; yPos: 2.04693; zPos: -1.35234 }
+ ListElement{ xPos: -0.775432; yPos: 1.24465; zPos: 1.95935 }
+ ListElement{ xPos: 1.65595; yPos: -1.04954; zPos: -0.954437 }
+ ListElement{ xPos: -2.35417; yPos: -1.76095; zPos: -0.248306 }
+ ListElement{ xPos: 0.855717; yPos: -2.92161; zPos: 3.4496 }
+ ListElement{ xPos: -0.717941; yPos: 2.52993; zPos: 1.25007 }
+ ListElement{ xPos: -3.42927; yPos: 0.673305; zPos: 0.995742 }
+ }
+
+ ListModel {
+ id: dataModelThree
+ ListElement{ xPos: 8.00000; yPos: -2.05136; zPos: 4.02113 }
+ ListElement{ xPos: 7.88929; yPos: -2.2029; zPos: 5.0000 }
+ ListElement{ xPos: 7.61596; yPos: -2.44569; zPos: 4.54892 }
+ ListElement{ xPos: 7.41949; yPos: -2.64169; zPos: 3.82339 }
+ ListElement{ xPos: 7.21192; yPos: -2.87947; zPos: 4.89423 }
+ ListElement{ xPos: 7.08346; yPos: -2.38649; zPos: 4.19212 }
+ ListElement{ xPos: 6.90279; yPos: -3.33038; zPos: 4.9273 }
+ ListElement{ xPos: 6.78959; yPos: -3.55575; zPos: 3.55127 }
+ ListElement{ xPos: 6.57074; yPos: -3.75902; zPos: 3.32517 }
+ ListElement{ xPos: 6.30756; yPos: -3.40499; zPos: 3.78366 }
+ ListElement{ xPos: 7.90893; yPos: -3.3237; zPos: 2.48012 }
+ ListElement{ xPos: 6.24078; yPos: -4.04978; zPos: 3.47459 }
+ ListElement{ xPos: 7.93452; yPos: -4.6865; zPos: 4.08057 }
+ ListElement{ xPos: 5.98731; yPos: -2.26113; zPos: 2.82166 }
+ ListElement{ xPos: 5.46898; yPos: -3.99581; zPos: 3.25024 }
+ ListElement{ xPos: 7.92353; yPos: -3.34471; zPos: 3.42168 }
+ ListElement{ xPos: 6.8456; yPos: -5.00000; zPos: 2.36145 }
+ ListElement{ xPos: 7.62888; yPos: -3.82465; zPos: 2.7879 }
+ ListElement{ xPos: 7.18034; yPos: -3.94376; zPos: 2.74348 }
+ ListElement{ xPos: 6.02527; yPos: -4.36523; zPos: 2.56247 }
+ ListElement{ xPos: 7.82353; yPos: -3.04345; zPos: 2.62974 }
+ ListElement{ xPos: 7.82539; yPos: -3.6881; zPos: 2.96164 }
+ ListElement{ xPos: 7.10508; yPos: -3.6174; zPos: 4.22416 }
+ ListElement{ xPos: 7.64684; yPos: -3.88431; zPos: 3.9336 }
+ ListElement{ xPos: 7.46411; yPos: -4.21516; zPos: 3.32001 }
+ ListElement{ xPos: 6.98777; yPos: -3.94024; zPos: 3.14694 }
+ ListElement{ xPos: 7.36355; yPos: -4.24875; zPos: 1.72061 }
+ ListElement{ xPos: 7.92773; yPos: -4.52065; zPos: 2.62078 }
+ ListElement{ xPos: 6.04598; yPos: -4.48682; zPos: 2.74494 }
+ ListElement{ xPos: 7.83353; yPos: -3.9271; zPos: 3.82815 }
+ ListElement{ xPos: 6.76279; yPos: -3.58031; zPos: 2.92525 }
+ ListElement{ xPos: 7.14794; yPos: -4.42742; zPos: 2.62365 }
+ ListElement{ xPos: 5.32696; yPos: -3.28733; zPos: 3.34916 }
+ ListElement{ xPos: 6.43655; yPos: -4.60784; zPos: 2.41548 }
+ ListElement{ xPos: 5.66033; yPos: -4.98497; zPos: 3.72282 }
+ }
+}
diff --git a/tests/manual/qmlcustominput/qml/qmlcustominput/main.qml b/tests/manual/qmlcustominput/qml/qmlcustominput/main.qml
new file mode 100644
index 0000000..1a1709d
--- /dev/null
+++ b/tests/manual/qmlcustominput/qml/qmlcustominput/main.qml
@@ -0,0 +1,218 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.Controls
+import QtGraphs
+import "."
+
+Item {
+ id: mainView
+ width: 1280
+ height: 720
+
+ Data {
+ id: graphData
+ }
+
+ Item {
+ id: dataView
+ anchors.bottom: parent.bottom
+ width: parent.width
+ height: parent.height - buttonLayout.height
+
+ //! [0]
+ Scatter3D {
+ //! [0]
+ id: scatterGraph
+ width: dataView.width
+ height: dataView.height
+ theme: Theme3D { type: Theme3D.ThemeDigia }
+ shadowQuality: AbstractGraph3D.ShadowQualityMedium
+ scene.activeCamera.yRotation: 30.0
+ //! [1]
+ inputHandler: null
+ //! [1]
+
+ Scatter3DSeries {
+ id: scatterSeriesOne
+ itemLabelFormat: "One - X:@xLabel Y:@yLabel Z:@zLabel"
+ mesh: Abstract3DSeries.MeshCube
+
+ ItemModelScatterDataProxy {
+ itemModel: graphData.modelOne
+ xPosRole: "xPos"
+ yPosRole: "yPos"
+ zPosRole: "zPos"
+ }
+ }
+
+ Scatter3DSeries {
+ id: scatterSeriesTwo
+ itemLabelFormat: "Two - X:@xLabel Y:@yLabel Z:@zLabel"
+ mesh: Abstract3DSeries.MeshCube
+
+ ItemModelScatterDataProxy {
+ itemModel: graphData.modelTwo
+ xPosRole: "xPos"
+ yPosRole: "yPos"
+ zPosRole: "zPos"
+ }
+ }
+
+ Scatter3DSeries {
+ id: scatterSeriesThree
+ itemLabelFormat: "Three - X:@xLabel Y:@yLabel Z:@zLabel"
+ mesh: Abstract3DSeries.MeshCube
+
+ ItemModelScatterDataProxy {
+ itemModel: graphData.modelThree
+ xPosRole: "xPos"
+ yPosRole: "yPos"
+ zPosRole: "zPos"
+ }
+ }
+ }
+
+ //! [2]
+ MouseArea {
+ id: inputArea
+ anchors.fill: parent
+ hoverEnabled: true
+ acceptedButtons: Qt.LeftButton | Qt.RightButton
+ property int mouseX: -1
+ property int mouseY: -1
+ //! [2]
+
+ //! [3]
+ onPositionChanged: (mouse)=> {
+ mouseX = mouse.x;
+ mouseY = mouse.y;
+ }
+ //! [3]
+
+ //! [5]
+ onWheel: (wheel)=> {
+ // Adjust zoom level based on what zoom range we're in.
+ var zoomLevel = scatterGraph.scene.activeCamera.zoomLevel;
+ if (zoomLevel > 100)
+ zoomLevel += wheel.angleDelta.y / 12.0;
+ else if (zoomLevel > 50)
+ zoomLevel += wheel.angleDelta.y / 60.0;
+ else
+ zoomLevel += wheel.angleDelta.y / 120.0;
+ if (zoomLevel > 500)
+ zoomLevel = 500;
+ else if (zoomLevel < 10)
+ zoomLevel = 10;
+
+ scatterGraph.scene.activeCamera.zoomLevel = zoomLevel;
+ }
+ //! [5]
+ }
+
+ //! [4]
+ Timer {
+ id: reselectTimer
+ interval: 10
+ running: true
+ repeat: true
+ onTriggered: {
+ scatterGraph.scene.selectionQueryPosition = Qt.point(inputArea.mouseX, inputArea.mouseY);
+ }
+ }
+ //! [4]
+ }
+
+ //! [6]
+ NumberAnimation {
+ id: cameraAnimationX
+ loops: Animation.Infinite
+ running: true
+ target: scatterGraph.scene.activeCamera
+ property:"xRotation"
+ from: 0.0
+ to: 360.0
+ duration: 20000
+ }
+ //! [6]
+
+
+ //! [7]
+ SequentialAnimation {
+ id: cameraAnimationY
+ loops: Animation.Infinite
+ running: true
+
+ NumberAnimation {
+ target: scatterGraph.scene.activeCamera
+ property:"yRotation"
+ from: 5.0
+ to: 45.0
+ duration: 9000
+ easing.type: Easing.InOutSine
+ }
+
+ NumberAnimation {
+ target: scatterGraph.scene.activeCamera
+ property:"yRotation"
+ from: 45.0
+ to: 5.0
+ duration: 9000
+ easing.type: Easing.InOutSine
+ }
+ }
+ //! [7]
+
+ RowLayout {
+ id: buttonLayout
+ Layout.minimumHeight: shadowToggle.height
+ width: parent.width
+ anchors.left: parent.left
+ spacing: 0
+
+ Button {
+ id: shadowToggle
+ Layout.fillHeight: true
+ Layout.minimumWidth: parent.width / 3 // 3 buttons divided equally in the layout
+ text: scatterGraph.shadowsSupported ? "Hide Shadows" : "Shadows not supported"
+ enabled: scatterGraph.shadowsSupported
+
+ onClicked: {
+ if (scatterGraph.shadowQuality === AbstractGraph3D.ShadowQualityNone) {
+ scatterGraph.shadowQuality = AbstractGraph3D.ShadowQualityMedium;
+ text = "Hide Shadows";
+ } else {
+ scatterGraph.shadowQuality = AbstractGraph3D.ShadowQualityNone;
+ text = "Show Shadows";
+ }
+ }
+ }
+
+ Button {
+ id: cameraToggle
+ Layout.fillHeight: true
+ Layout.minimumWidth: parent.width / 3
+ text: "Pause Camera"
+
+ onClicked: {
+ cameraAnimationX.paused = !cameraAnimationX.paused;
+ cameraAnimationY.paused = cameraAnimationX.paused;
+ if (cameraAnimationX.paused) {
+ text = "Animate Camera";
+ } else {
+ text = "Pause Camera";
+ }
+ }
+ }
+
+ Button {
+ id: exitButton
+ Layout.fillHeight: true
+ Layout.minimumWidth: parent.width / 3
+ text: "Quit"
+ onClicked: Qt.quit();
+ }
+ }
+}
diff --git a/tests/manual/qmlcustominput/qmlcustominput.pro b/tests/manual/qmlcustominput/qmlcustominput.pro
new file mode 100644
index 0000000..356bb26
--- /dev/null
+++ b/tests/manual/qmlcustominput/qmlcustominput.pro
@@ -0,0 +1,12 @@
+!include( ../examples.pri ) {
+ error( "Couldn't find the examples.pri file!" )
+}
+
+# The .cpp file which was generated for your project. Feel free to hack it.
+SOURCES += main.cpp
+
+RESOURCES += qmlcustominput.qrc
+
+OTHER_FILES += doc/src/* \
+ doc/images/* \
+ qml/qmlcustominput/*
diff --git a/tests/manual/qmlcustominput/qmlcustominput.qrc b/tests/manual/qmlcustominput/qmlcustominput.qrc
new file mode 100644
index 0000000..d620a3d
--- /dev/null
+++ b/tests/manual/qmlcustominput/qmlcustominput.qrc
@@ -0,0 +1,6 @@
+<RCC>
+ <qresource prefix="/">
+ <file>qml/qmlcustominput/Data.qml</file>
+ <file>qml/qmlcustominput/main.qml</file>
+ </qresource>
+</RCC>
diff --git a/tests/manual/qmldynamicdata/CMakeLists.txt b/tests/manual/qmldynamicdata/CMakeLists.txt
new file mode 100644
index 0000000..9d36391
--- /dev/null
+++ b/tests/manual/qmldynamicdata/CMakeLists.txt
@@ -0,0 +1,23 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+qt_internal_add_manual_test(qmldynamicdata
+ GUI
+ SOURCES
+ main.cpp
+ )
+target_link_libraries(qmldynamicdata PUBLIC
+ Qt::Gui
+ Qt::Graphs
+ )
+
+set(qmldynamicdata_resource_files
+ "qml/qmldynamicdata/main.qml"
+ )
+
+qt_internal_add_resource(qmldynamicdata "qmldynamicdata"
+ PREFIX
+ "/"
+ FILES
+ ${qmldynamicdata_resource_files}
+ )
diff --git a/tests/manual/qmldynamicdata/main.cpp b/tests/manual/qmldynamicdata/main.cpp
new file mode 100644
index 0000000..d74ccc0
--- /dev/null
+++ b/tests/manual/qmldynamicdata/main.cpp
@@ -0,0 +1,33 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtGui/QGuiApplication>
+#include <QtCore/QDir>
+#include <QtQuick/QQuickView>
+#include <QtQml/QQmlEngine>
+
+int main(int argc, char *argv[])
+{
+ QGuiApplication app(argc, argv);
+
+ QQuickView viewer;
+
+ // The following are needed to make examples run without having to install the module
+ // in desktop environments.
+#ifdef Q_OS_WIN
+ QString extraImportPath(QStringLiteral("%1/../../../%2"));
+#else
+ QString extraImportPath(QStringLiteral("%1/../../%2"));
+#endif
+ viewer.engine()->addImportPath(extraImportPath.arg(QGuiApplication::applicationDirPath(),
+ QString::fromLatin1("qml")));
+ QObject::connect(viewer.engine(), &QQmlEngine::quit, &viewer, &QWindow::close);
+
+ viewer.setTitle(QStringLiteral("QML Dynamic Data Test"));
+
+ viewer.setSource(QUrl("qrc:/qml/qmldynamicdata/main.qml"));
+ viewer.setResizeMode(QQuickView::SizeRootObjectToView);
+ viewer.show();
+
+ return app.exec();
+}
diff --git a/tests/manual/qmldynamicdata/qml/qmldynamicdata/main.qml b/tests/manual/qmldynamicdata/qml/qmldynamicdata/main.qml
new file mode 100644
index 0000000..9fe6acf
--- /dev/null
+++ b/tests/manual/qmldynamicdata/qml/qmldynamicdata/main.qml
@@ -0,0 +1,246 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+import QtGraphs
+import QtQuick.Controls
+import "."
+
+Item {
+ id: mainView
+ width: 1280
+ height: 720
+ visible: true
+
+ ListModel {
+ id: graphModel
+ ListElement{ xPos: 0.0; yPos: 0.0; zPos: 0.0; rotation: "0.92388, 0.220942, 0.220942, 0.220942"}
+ ListElement{ xPos: 1.0; yPos: 1.0; zPos: 1.0; rotation: "@45,1.0,1.0,1.0" }
+ }
+
+ Timer {
+ id: dataTimer
+ interval: 1
+ running: true
+ repeat: true
+ property bool isIncreasing: true
+ property real rotationAngle: 0
+
+ function generateQuaternion() {
+ return "@" + Math.random() * 360 + "," + Math.random() + "," + Math.random() + "," + Math.random()
+ }
+
+ function appendRow() {
+ graphModel.append({"xPos": Math.random(),
+ "yPos": Math.random(),
+ "zPos": Math.random(),
+ "rotation": generateQuaternion()
+ });
+ }
+
+ onTriggered: {
+ rotationAngle = rotationAngle + 1
+ scatterSeries.setMeshAxisAndAngle(Qt.vector3d(1,1,1), rotationAngle)
+ if (isIncreasing) {
+ appendRow()
+ appendRow()
+ appendRow()
+ appendRow()
+ appendRow()
+ appendRow()
+ appendRow()
+ appendRow()
+ appendRow()
+ appendRow()
+ if (graphModel.count > 5000) {
+ scatterGraph.theme.type = Theme3D.ThemeIsabelle;
+ isIncreasing = false;
+ }
+ } else {
+ graphModel.remove(Math.random() * (graphModel.count - 1));
+ graphModel.remove(Math.random() * (graphModel.count - 1));
+ graphModel.remove(Math.random() * (graphModel.count - 1));
+ graphModel.remove(Math.random() * (graphModel.count - 1));
+ graphModel.remove(Math.random() * (graphModel.count - 1));
+ graphModel.remove(Math.random() * (graphModel.count - 1));
+ graphModel.remove(Math.random() * (graphModel.count - 1));
+ graphModel.remove(Math.random() * (graphModel.count - 1));
+ graphModel.remove(Math.random() * (graphModel.count - 1));
+ graphModel.remove(Math.random() * (graphModel.count - 1));
+ if (graphModel.count == 2) {
+ scatterGraph.theme.type = Theme3D.ThemeDigia;
+ isIncreasing = true;
+ }
+ }
+ }
+ }
+
+ ThemeColor {
+ id: dynamicColor
+ ColorAnimation on color {
+ from: "red"
+ to: "yellow"
+ duration: 5000
+ loops: Animation.Infinite
+ }
+ }
+
+ Item {
+ id: dataView
+ anchors.bottom: parent.bottom
+ width: parent.width
+ height: parent.height - shadowToggle.height
+
+ Scatter3D {
+ id: scatterGraph
+ width: dataView.width
+ height: dataView.height
+ theme: Theme3D {
+ type: Theme3D.ThemeQt
+ baseColors: [dynamicColor]
+ }
+ shadowQuality: AbstractGraph3D.ShadowQualitySoftMedium
+ scene.activeCamera.yRotation: 30.0
+ inputHandler: null
+ axisX.min: 0
+ axisY.min: 0
+ axisZ.min: 0
+ axisX.max: 1
+ axisY.max: 1
+ axisZ.max: 1
+
+ Scatter3DSeries {
+ id: scatterSeries
+ itemLabelFormat: "X:@xLabel Y:@yLabel Z:@zLabel"
+ mesh: Abstract3DSeries.MeshCube
+
+ ItemModelScatterDataProxy {
+ itemModel: graphModel
+ xPosRole: "xPos"
+ yPosRole: "yPos"
+ zPosRole: "zPos"
+ rotationRole: "rotation"
+ }
+ }
+ }
+
+ MouseArea {
+ id: inputArea
+ anchors.fill: parent
+ hoverEnabled: true
+ acceptedButtons: Qt.LeftButton | Qt.RightButton
+ property int mouseX: -1
+ property int mouseY: -1
+
+ onPositionChanged: (mouse)=> {
+ mouseX = mouse.x;
+ mouseY = mouse.y;
+ }
+
+ onWheel: (wheel)=> {
+ // Adjust zoom level based on what zoom range we're in.
+ var zoomLevel = scatterGraph.scene.activeCamera.zoomLevel;
+ if (zoomLevel > 100)
+ zoomLevel += wheel.angleDelta.y / 12.0;
+ else if (zoomLevel > 50)
+ zoomLevel += wheel.angleDelta.y / 60.0;
+ else
+ zoomLevel += wheel.angleDelta.y / 120.0;
+ if (zoomLevel > 500)
+ zoomLevel = 500;
+ else if (zoomLevel < 10)
+ zoomLevel = 10;
+
+ scatterGraph.scene.activeCamera.zoomLevel = zoomLevel;
+ }
+ }
+
+ Timer {
+ id: reselectTimer
+ interval: 10
+ running: true
+ repeat: true
+ onTriggered: {
+ scatterGraph.scene.selectionQueryPosition = Qt.point(inputArea.mouseX, inputArea.mouseY);
+ }
+ }
+ }
+
+ NumberAnimation {
+ id: cameraAnimationX
+ loops: Animation.Infinite
+ running: true
+ target: scatterGraph.scene.activeCamera
+ property:"xRotation"
+ from: 0.0
+ to: 360.0
+ duration: 20000
+ }
+
+
+ SequentialAnimation {
+ id: cameraAnimationY
+ loops: Animation.Infinite
+ running: true
+
+ NumberAnimation {
+ target: scatterGraph.scene.activeCamera
+ property:"yRotation"
+ from: 5.0
+ to: 45.0
+ duration: 9000
+ easing.type: Easing.InOutSine
+ }
+
+ NumberAnimation {
+ target: scatterGraph.scene.activeCamera
+ property:"yRotation"
+ from: 45.0
+ to: 5.0
+ duration: 9000
+ easing.type: Easing.InOutSine
+ }
+ }
+
+ Button {
+ id: shadowToggle
+ width: parent.width / 3 // We're adding 3 buttons and want to divide them equally
+ text: "Hide Shadows"
+ anchors.left: parent.left
+
+ onClicked: {
+ if (scatterGraph.shadowQuality === AbstractGraph3D.ShadowQualityNone) {
+ scatterGraph.shadowQuality = AbstractGraph3D.ShadowQualitySoftMedium;
+ text = "Hide Shadows";
+ } else {
+ scatterGraph.shadowQuality = AbstractGraph3D.ShadowQualityNone;
+ text = "Show Shadows";
+ }
+ }
+ }
+
+ Button {
+ id: cameraToggle
+ width: parent.width / 3
+ text: "Pause Camera"
+ anchors.left: shadowToggle.right
+
+ onClicked: {
+ cameraAnimationX.paused = !cameraAnimationX.paused;
+ cameraAnimationY.paused = cameraAnimationX.paused;
+ if (cameraAnimationX.paused) {
+ text = "Animate Camera";
+ } else {
+ text = "Pause Camera";
+ }
+ }
+ }
+
+ Button {
+ id: exitButton
+ width: parent.width / 3
+ text: "Quit"
+ anchors.left: cameraToggle.right
+ onClicked: Qt.quit();
+ }
+}
diff --git a/tests/manual/qmldynamicdata/qmldynamicdata.pro b/tests/manual/qmldynamicdata/qmldynamicdata.pro
new file mode 100644
index 0000000..a6ffeb3
--- /dev/null
+++ b/tests/manual/qmldynamicdata/qmldynamicdata.pro
@@ -0,0 +1,9 @@
+!include( ../tests.pri ) {
+ error( "Couldn't find the tests.pri file!" )
+}
+
+SOURCES += main.cpp
+
+RESOURCES += qmldynamicdata.qrc
+
+OTHER_FILES += qml/qmldynamicdata/*
diff --git a/tests/manual/qmldynamicdata/qmldynamicdata.qrc b/tests/manual/qmldynamicdata/qmldynamicdata.qrc
new file mode 100644
index 0000000..e1997ff
--- /dev/null
+++ b/tests/manual/qmldynamicdata/qmldynamicdata.qrc
@@ -0,0 +1,5 @@
+<RCC>
+ <qresource prefix="/">
+ <file>qml/qmldynamicdata/main.qml</file>
+ </qresource>
+</RCC>
diff --git a/tests/manual/qmlgradient/CMakeLists.txt b/tests/manual/qmlgradient/CMakeLists.txt
new file mode 100644
index 0000000..9eb133a
--- /dev/null
+++ b/tests/manual/qmlgradient/CMakeLists.txt
@@ -0,0 +1,28 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+qt_internal_add_manual_test(qmlgradient
+ GUI
+ SOURCES
+ main.cpp
+ )
+target_link_libraries(qmlgradient PUBLIC
+ Qt::Gui
+ Qt::Graphs
+ )
+
+set(qmlgradient_resource_files
+ "qml/qmlgradient/main.qml"
+ "crater.png"
+ )
+
+set_source_files_properties("crater.png"
+ PROPERTIES QT_RESOURCE_ALIAS "map"
+ )
+
+qt_internal_add_resource(qmlgradient "qmlgradient"
+ PREFIX
+ "/"
+ FILES
+ ${qmlgradient_resource_files}
+ )
diff --git a/tests/manual/qmlgradient/crater.png b/tests/manual/qmlgradient/crater.png
new file mode 100644
index 0000000..91bba19
--- /dev/null
+++ b/tests/manual/qmlgradient/crater.png
Binary files differ
diff --git a/tests/manual/qmlgradient/main.cpp b/tests/manual/qmlgradient/main.cpp
new file mode 100644
index 0000000..0550a90
--- /dev/null
+++ b/tests/manual/qmlgradient/main.cpp
@@ -0,0 +1,36 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtGui/QGuiApplication>
+#include <QtCore/QDir>
+#include <QtQuick/QQuickView>
+#include <QtQml/QQmlEngine>
+
+int main(int argc, char *argv[])
+{
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+ QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
+#endif
+
+ QGuiApplication app(argc, argv);
+
+ QQuickView viewer;
+
+ const QUrl url(QStringLiteral("qrc:/qml/qmlgradient/main.qml"));
+
+ // The following are needed to make examples run without having to install the module
+ // in desktop environments.
+#ifdef Q_OS_WIN
+ QString extraImportPath(QStringLiteral("%1/../../../%2"));
+#else
+ QString extraImportPath(QStringLiteral("%1/../../%2"));
+#endif
+
+ viewer.engine()->addImportPath(extraImportPath.arg(QGuiApplication::applicationDirPath(),
+ QString::fromLatin1("qml")));
+ QObject::connect( viewer.engine(), &QQmlEngine::quit, &viewer, &QWindow::close);
+ viewer.setSource(url);
+ viewer.show();
+ viewer.setResizeMode(QQuickView::SizeRootObjectToView);
+ return app.exec();
+}
diff --git a/tests/manual/qmlgradient/qml.qrc b/tests/manual/qmlgradient/qml.qrc
new file mode 100644
index 0000000..cd67689
--- /dev/null
+++ b/tests/manual/qmlgradient/qml.qrc
@@ -0,0 +1,5 @@
+<RCC>
+ <qresource prefix="/">
+ <file>qml/qmlgradient/main.qml</file>
+ </qresource>
+</RCC>
diff --git a/tests/manual/qmlgradient/qml/qmlgradient/main.qml b/tests/manual/qmlgradient/qml/qmlgradient/main.qml
new file mode 100644
index 0000000..2c6fa43
--- /dev/null
+++ b/tests/manual/qmlgradient/qml/qmlgradient/main.qml
@@ -0,0 +1,191 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+import QtQuick.Window
+import QtQuick.Layouts
+import QtQuick.Controls
+import QtGraphs
+import "."
+
+Item {
+ id: mainwindow
+
+ function updateinfoLabels()
+ {
+ if (surfaceGraph.theme.baseGradients[0] === mainGradient)
+ gradientLabel.text = "Main gradient";
+ else if (surfaceGraph.theme.baseGradients[0] === secondaryGradient)
+ gradientLabel.text = "Secondary gradient";
+ }
+
+ width: 1024
+ height: 768
+ visible: true
+
+ Item {
+ id: surfaceview
+ width: mainwindow.width
+ height: mainwindow.height
+
+ anchors.top: mainwindow.top
+ anchors.left: mainwindow.left
+
+ ColorGradient {
+ id: mainGradient
+ ColorGradientStop { position: 0.0; color: "red"}
+ ColorGradientStop { position: 0.5; color: "green"}
+ ColorGradientStop { position: 0.8; color: "blue"}
+ ColorGradientStop { position: 0.6; color: "yellow"}
+ ColorGradientStop { position: 0.8; color: "black"}
+ ColorGradientStop { position: 1.0; color: "peru"}
+ }
+
+ ColorGradient {
+ id: secondaryGradient
+ ColorGradientStop { position: 0.0; color: "crimson"}
+ ColorGradientStop { position: 0.5; color: "chartreuse"}
+ ColorGradientStop { position: 0.8; color: "blueviolet"}
+ ColorGradientStop { position: 0.6; color: "gold"}
+ ColorGradientStop { position: 0.8; color: "darkslategrey"}
+ ColorGradientStop { position: 1.0; color: "seagreen"}
+ }
+
+ ColorGradient {
+ id: seriesGradient
+ ColorGradientStop { position: 0.0; color: "gold" }
+ ColorGradientStop { position: 0.5; color: "crimson" }
+ ColorGradientStop { position: 1.0; color: "blueviolet" }
+ }
+
+ Theme3D {
+ id: mainTheme
+ type: Q3DTheme.ThemeStoneMoss
+
+ colorStyle: Q3DTheme.ColorStyleRangeGradient
+ baseGradients: [mainGradient]
+ }
+
+ Theme3D {
+ id: secondaryTheme
+ type: Q3DTheme.ThemeArmyBlue
+ baseGradients: [secondaryGradient]
+ }
+
+ Surface3D {
+ id: surfaceGraph
+ width: surfaceview.width
+ height: surfaceview.height
+ theme: mainTheme
+
+ shadowQuality: AbstractGraph3D.ShadowQualityMedium
+ selectionMode: AbstractGraph3D.SelectionSlice | AbstractGraph3D.SelectionItemAndRow
+ scene.activeCamera.cameraPreset: Camera3D.CameraPresetIsometricLeft
+ axisY.min: 0.0
+ axisY.max: 500.0
+ axisX.segmentCount: 10
+ axisX.subSegmentCount: 2
+ axisX.labelFormat: "%i"
+ axisZ.segmentCount: 10
+ axisZ.subSegmentCount: 2
+ axisZ.labelFormat: "%i"
+ axisY.segmentCount: 5
+ axisY.subSegmentCount: 2
+ axisY.labelFormat: "%i"
+ axisY.title: "Height"
+ axisX.title: "Latitude"
+ axisZ.title: "Longitude"
+
+ Surface3DSeries {
+ id: heightSeries
+ drawMode: Surface3DSeries.DrawSurface
+ visible: true
+ flatShadingEnabled: false
+
+ HeightMapSurfaceDataProxy {
+ heightMapFile: ":/map"
+ }
+ }
+ }
+
+ RowLayout {
+ id: buttonLayout
+ anchors.top: parent.top
+ anchors.left: parent.left
+ anchors.right: parent.right
+
+ Button {
+ id: toggleTheme
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+
+ text: qsTr("Toggle theme")
+ onClicked: {
+ if (surfaceGraph.theme == mainTheme) {
+ surfaceGraph.theme = secondaryTheme;
+ themeLabel.text = "Secondary theme";
+ updateinfoLabels();
+ } else if (surfaceGraph.theme == secondaryTheme) {
+ surfaceGraph.theme = mainTheme;
+ updateinfoLabels();
+ themeLabel.text = "Main theme";
+ }
+ }
+ }
+
+ Button {
+ id: toggleGradient
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+
+ text: qsTr("Toggle theme gradient")
+ onClicked: {
+ if (surfaceGraph.theme.baseGradients[0] === mainGradient) {
+ surfaceGraph.theme.baseGradients[0] = secondaryGradient;
+ updateinfoLabels();
+ } else if (surfaceGraph.theme.baseGradients[0] === secondaryGradient) {
+ surfaceGraph.theme.baseGradients[0] = mainGradient;
+ updateinfoLabels();
+ }
+ }
+ }
+
+ Button {
+ id: toggleSeriesGradient
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+
+ text: qsTr("Override theme gradient with series gradient")
+
+ onClicked: {
+ heightSeries.baseGradient = seriesGradient;
+ gradientLabel.text = "Series gradient";
+ }
+ }
+ }
+
+ ColumnLayout {
+ id: infoLayout
+ anchors.top: buttonLayout.bottom
+ anchors.left: parent.left
+
+ Rectangle {
+ Layout.minimumHeight: 20
+
+ Label {
+ id: themeLabel
+ text: qsTr("Main theme")
+ }
+ }
+
+ Rectangle {
+ Layout.minimumHeight: 20
+
+ Label {
+ id: gradientLabel
+ text: qsTr("Main gradient")
+ }
+ }
+ }
+ }
+}
diff --git a/tests/manual/qmlheightmap/CMakeLists.txt b/tests/manual/qmlheightmap/CMakeLists.txt
new file mode 100644
index 0000000..3fcba99
--- /dev/null
+++ b/tests/manual/qmlheightmap/CMakeLists.txt
@@ -0,0 +1,41 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+qt_internal_add_manual_test(qmlheightmap
+ GUI
+ SOURCES
+ main.cpp
+ )
+target_link_libraries(qmlheightmap PUBLIC
+ Qt::Gui
+ Qt::Graphs
+ )
+
+set_source_files_properties("gradientGRAY8.png"
+ PROPERTIES QT_RESOURCE_ALIAS "mapGRAY8"
+ )
+set_source_files_properties("gradientGRAY16.png"
+ PROPERTIES QT_RESOURCE_ALIAS "mapGRAY16"
+ )
+set_source_files_properties("gradientRGB8.png"
+ PROPERTIES QT_RESOURCE_ALIAS "mapRGB8"
+ )
+set_source_files_properties("gradientRGB16.png"
+ PROPERTIES QT_RESOURCE_ALIAS "mapRGB16"
+ )
+
+set(qmlheightmap_resource_files
+ "qml/qmlheightmap/main.qml"
+ "gradientGRAY8.png"
+ "gradientGRAY16.png"
+ "gradientRGB8.png"
+ "gradientRGB16.png"
+ )
+qt_internal_add_resource(qmlheightmap "qmlheightmap"
+ PREFIX
+ "/"
+ FILES
+ ${qmlheightmap_resource_files}
+ )
diff --git a/tests/manual/qmlheightmap/gradientGRAY16.png b/tests/manual/qmlheightmap/gradientGRAY16.png
new file mode 100644
index 0000000..28df367
--- /dev/null
+++ b/tests/manual/qmlheightmap/gradientGRAY16.png
Binary files differ
diff --git a/tests/manual/qmlheightmap/gradientGRAY8.png b/tests/manual/qmlheightmap/gradientGRAY8.png
new file mode 100644
index 0000000..6696e57
--- /dev/null
+++ b/tests/manual/qmlheightmap/gradientGRAY8.png
Binary files differ
diff --git a/tests/manual/qmlheightmap/gradientRGB16.png b/tests/manual/qmlheightmap/gradientRGB16.png
new file mode 100644
index 0000000..b62e510
--- /dev/null
+++ b/tests/manual/qmlheightmap/gradientRGB16.png
Binary files differ
diff --git a/tests/manual/qmlheightmap/gradientRGB8.png b/tests/manual/qmlheightmap/gradientRGB8.png
new file mode 100644
index 0000000..79879f3
--- /dev/null
+++ b/tests/manual/qmlheightmap/gradientRGB8.png
Binary files differ
diff --git a/tests/manual/qmlheightmap/main.cpp b/tests/manual/qmlheightmap/main.cpp
new file mode 100644
index 0000000..603e377
--- /dev/null
+++ b/tests/manual/qmlheightmap/main.cpp
@@ -0,0 +1,36 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtGui/QGuiApplication>
+#include <QtCore/QDir>
+#include <QtQuick/QQuickView>
+#include <QtQml/QQmlEngine>
+
+int main(int argc, char *argv[])
+{
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+ QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
+#endif
+
+ QGuiApplication app(argc, argv);
+
+ QQuickView viewer;
+
+ const QUrl url(QStringLiteral("qrc:/qml/qmlheightmap/main.qml"));
+
+ // The following are needed to make examples run without having to install the module
+ // in desktop environments.
+#ifdef Q_OS_WIN
+ QString extraImportPath(QStringLiteral("%1/../../../%2"));
+#else
+ QString extraImportPath(QStringLiteral("%1/../../%2"));
+#endif
+
+ viewer.engine()->addImportPath(extraImportPath.arg(QGuiApplication::applicationDirPath(),
+ QString::fromLatin1("qml")));
+ QObject::connect( viewer.engine(), &QQmlEngine::quit, &viewer, &QWindow::close);
+ viewer.setSource(url);
+ viewer.show();
+ viewer.setResizeMode(QQuickView::SizeRootObjectToView);
+ return app.exec();
+}
diff --git a/tests/manual/qmlheightmap/qml.qrc b/tests/manual/qmlheightmap/qml.qrc
new file mode 100644
index 0000000..b1cbc54
--- /dev/null
+++ b/tests/manual/qmlheightmap/qml.qrc
@@ -0,0 +1,5 @@
+<RCC>
+ <qresource prefix="/">
+ <file>qml/qmlheightmap/qml/main.qml</file>
+ </qresource>
+</RCC>
diff --git a/tests/manual/qmlheightmap/qml/qmlheightmap/main.qml b/tests/manual/qmlheightmap/qml/qmlheightmap/main.qml
new file mode 100644
index 0000000..3f3c11b
--- /dev/null
+++ b/tests/manual/qmlheightmap/qml/qmlheightmap/main.qml
@@ -0,0 +1,181 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+import QtQuick.Window
+import QtQuick.Layouts
+import QtQuick.Controls
+import QtGraphs
+import "."
+
+Item {
+ id: mainwindow
+
+ width: 1024
+ height: 768
+ visible: true
+
+ Item {
+ id: surfaceview
+ width: mainwindow.width
+ height: mainwindow.height
+
+ anchors.top: mainwindow.top
+ anchors.left: mainwindow.left
+
+ ColorGradient {
+ id: surfaceGradient
+ ColorGradientStop { position: 0.0; color: "darkslategray" }
+ ColorGradientStop { id: middleGradient; position: 0.50; color: "peru" }
+ ColorGradientStop { position: 1.0; color: "red" }
+ }
+
+ Theme3D {
+ id: mainTheme
+ type: Q3DTheme.ThemeStoneMoss
+ colorStyle: Q3DTheme.ColorStyleRangeGradient
+ baseGradients: [surfaceGradient]
+ }
+
+ Surface3D {
+ id: surfaceGraph
+ width: surfaceview.width
+ height: surfaceview.height
+ theme: mainTheme
+ shadowQuality: AbstractGraph3D.ShadowQualityMedium
+ selectionMode: AbstractGraph3D.SelectionSlice | AbstractGraph3D.SelectionItemAndRow
+ scene.activeCamera.cameraPreset: Camera3D.CameraPresetIsometricLeft
+ axisY.min: 0.0
+ axisY.max: 500.0
+ axisX.segmentCount: 10
+ axisX.subSegmentCount: 2
+ axisX.labelFormat: "%i"
+ axisZ.segmentCount: 10
+ axisZ.subSegmentCount: 2
+ axisZ.labelFormat: "%i"
+ axisY.segmentCount: 5
+ axisY.subSegmentCount: 2
+ axisY.labelFormat: "%i"
+ axisY.title: "Y"
+ axisX.title: "X"
+ axisZ.title: "Z"
+
+ Surface3DSeries {
+ id: heightSeriesRGB8
+ drawMode: Surface3DSeries.DrawSurface
+ visible: true
+ flatShadingEnabled: false
+
+ HeightMapSurfaceDataProxy {
+ heightMapFile: ":/mapRGB8"
+ minYValue: surfaceGraph.axisY.min
+ maxYValue: surfaceGraph.axisY.max
+ }
+ }
+
+ Surface3DSeries {
+ id: heightSeriesRGB16
+ drawMode: Surface3DSeries.DrawSurface
+ visible: false
+ flatShadingEnabled: false
+
+ HeightMapSurfaceDataProxy {
+ heightMapFile: ":/mapRGB16"
+ minYValue: surfaceGraph.axisY.min
+ maxYValue: surfaceGraph.axisY.max
+ }
+ }
+
+ Surface3DSeries {
+ id: heightSeriesGRAY8
+ drawMode: Surface3DSeries.DrawSurface
+ visible: false
+ flatShadingEnabled: false
+
+ HeightMapSurfaceDataProxy {
+ heightMapFile: ":/mapGRAY8"
+ minYValue: surfaceGraph.axisY.min
+ maxYValue: surfaceGraph.axisY.max
+ }
+ }
+
+ Surface3DSeries {
+ id: heightSeriesGRAY16
+ drawMode: Surface3DSeries.DrawSurface
+ visible: false
+ flatShadingEnabled: false
+
+ HeightMapSurfaceDataProxy {
+ heightMapFile: ":/mapGRAY16"
+ minYValue: surfaceGraph.axisY.min
+ maxYValue: surfaceGraph.axisY.max
+ }
+ }
+ }
+
+ RowLayout {
+ id: buttonLayout
+ anchors.top: parent.top
+ anchors.left: parent.left
+ anchors.right: parent.right
+
+ Button {
+ id: toggleHeightSeries
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+
+ text: qsTr("Use 16-bit rgb map")
+ onClicked: {
+ if (heightSeriesRGB8.visible === true) {
+ heightSeriesRGB8.visible = false
+ heightSeriesRGB16.visible = true
+ heightSeriesGRAY8.visible = false
+ heightSeriesGRAY16.visible = false
+ text = "Use 8-bit grayscale map"
+ } else if (heightSeriesRGB16.visible === true){
+ heightSeriesRGB8.visible = false
+ heightSeriesRGB16.visible = false
+ heightSeriesGRAY8.visible = true
+ heightSeriesGRAY16.visible = false
+ text = "Use 16-bit grayscale map"
+ } else if (heightSeriesGRAY8.visible === true){
+ heightSeriesRGB8.visible = false
+ heightSeriesRGB16.visible = false
+ heightSeriesGRAY8.visible = false
+ heightSeriesGRAY16.visible = true
+ text = "Use 8-bit rgb map"
+ } else if (heightSeriesGRAY16.visible === true){
+ heightSeriesRGB8.visible = true
+ heightSeriesRGB16.visible = false
+ heightSeriesGRAY8.visible = false
+ heightSeriesGRAY16.visible = false
+ text = "Use 16-bit rgb map"
+ }
+ }
+ }
+
+ Button {
+ id: toggleAutoScaleY
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+
+ text: qsTr("Enable autoScaleY")
+ onClicked: {
+ if (text === "Enable autoScaleY") {
+ heightSeriesRGB8.dataProxy.autoScaleY = true
+ heightSeriesRGB16.dataProxy.autoScaleY = true
+ heightSeriesGRAY8.dataProxy.autoScaleY = true
+ heightSeriesGRAY16.dataProxy.autoScaleY = true
+ text = "Disable autoScaleY"
+ } else {
+ heightSeriesRGB8.dataProxy.autoScaleY = false
+ heightSeriesRGB16.dataProxy.autoScaleY = false
+ heightSeriesGRAY8.dataProxy.autoScaleY = false
+ heightSeriesGRAY16.dataProxy.autoScaleY = false
+ text = "Enable autoScaleY"
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/tests/manual/qmllegend/CMakeLists.txt b/tests/manual/qmllegend/CMakeLists.txt
new file mode 100644
index 0000000..f737d1e
--- /dev/null
+++ b/tests/manual/qmllegend/CMakeLists.txt
@@ -0,0 +1,31 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+qt_internal_add_manual_test(qmllegend
+ GUI
+ SOURCES
+ main.cpp
+ )
+
+target_link_libraries(qmllegend PUBLIC
+ Qt::Core
+ Qt::Gui
+ Qt::Qml
+ Qt::Quick
+ Qt::Graphs
+)
+
+set(qmllegend_resource_files
+ "qml/qmllegend/Data.qml"
+ "qml/qmllegend/LegendItem.qml"
+ "qml/qmllegend/main.qml"
+)
+
+qt6_add_resources(qmllegend "qmllegend"
+ PREFIX
+ "/"
+ FILES
+ ${qmllegend_resource_files}
+)
diff --git a/tests/manual/qmllegend/main.cpp b/tests/manual/qmllegend/main.cpp
new file mode 100644
index 0000000..212e011
--- /dev/null
+++ b/tests/manual/qmllegend/main.cpp
@@ -0,0 +1,34 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtGui/QGuiApplication>
+#include <QtCore/QDir>
+#include <QtQuick/QQuickView>
+#include <QtQml/QQmlEngine>
+#include <QtQuick3D/qquick3d.h>
+
+int main(int argc, char *argv[])
+{
+ QGuiApplication app(argc, argv);
+ QSurfaceFormat::setDefaultFormat(QQuick3D::idealSurfaceFormat());
+ QQuickView viewer;
+
+ // The following are needed to make examples run without having to install the module
+ // in desktop environments.
+#ifdef Q_OS_WIN
+ QString extraImportPath(QStringLiteral("%1/../../../../%2"));
+#else
+ QString extraImportPath(QStringLiteral("%1/../../../%2"));
+#endif
+ viewer.engine()->addImportPath(extraImportPath.arg(QGuiApplication::applicationDirPath(),
+ QString::fromLatin1("qml")));
+ QObject::connect(viewer.engine(), &QQmlEngine::quit, &viewer, &QWindow::close);
+
+ viewer.setTitle(QStringLiteral("Legend example"));
+
+ viewer.setSource(QUrl("qrc:/qml/qmllegend/main.qml"));
+ viewer.setResizeMode(QQuickView::SizeRootObjectToView);
+ viewer.show();
+
+ return app.exec();
+}
diff --git a/tests/manual/qmllegend/qml/qmllegend/Data.qml b/tests/manual/qmllegend/qml/qmllegend/Data.qml
new file mode 100644
index 0000000..8efb4be
--- /dev/null
+++ b/tests/manual/qmllegend/qml/qmllegend/Data.qml
@@ -0,0 +1,63 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+
+Item {
+ property alias model: dataModel
+
+ ListModel {
+ id: dataModel
+ ListElement{ year: "2010"; month: "Jan"; s1: "-14"; s2: "-15"; s3: "-15" }
+ ListElement{ year: "2010"; month: "Feb"; s1: "-15"; s2: "-16"; s3: "-9" }
+ ListElement{ year: "2010"; month: "Mar"; s1: "-7"; s2: "-4"; s3: "-2" }
+ ListElement{ year: "2010"; month: "Apr"; s1: "3"; s2: "2"; s3: "2" }
+ ListElement{ year: "2010"; month: "May"; s1: "7"; s2: "9"; s3: "10" }
+ ListElement{ year: "2010"; month: "Jun"; s1: "12"; s2: "13"; s3: "22" }
+ ListElement{ year: "2010"; month: "Jul"; s1: "18"; s2: "19"; s3: "24" }
+ ListElement{ year: "2010"; month: "Aug"; s1: "15"; s2: "13"; s3: "16" }
+ ListElement{ year: "2010"; month: "Sep"; s1: "6"; s2: "3"; s3: "4" }
+ ListElement{ year: "2010"; month: "Oct"; s1: "1"; s2: "2"; s3: "-2" }
+ ListElement{ year: "2010"; month: "Nov"; s1: "-2"; s2: "-5"; s3: "-6" }
+ ListElement{ year: "2010"; month: "Dec"; s1: "-3"; s2: "-3"; s3: "-9" }
+
+ ListElement{ year: "2011"; month: "Jan"; s1: "-12"; s2: "-11"; s3: "-14" }
+ ListElement{ year: "2011"; month: "Feb"; s1: "-13"; s2: "-12"; s3: "-10" }
+ ListElement{ year: "2011"; month: "Mar"; s1: "-6"; s2: "-4"; s3: "-3" }
+ ListElement{ year: "2011"; month: "Apr"; s1: "0"; s2: "1"; s3: "3" }
+ ListElement{ year: "2011"; month: "May"; s1: "4"; s2: "12"; s3: "11" }
+ ListElement{ year: "2011"; month: "Jun"; s1: "9"; s2: "17"; s3: "23" }
+ ListElement{ year: "2011"; month: "Jul"; s1: "15"; s2: "22"; s3: "25" }
+ ListElement{ year: "2011"; month: "Aug"; s1: "12"; s2: "15"; s3: "12" }
+ ListElement{ year: "2011"; month: "Sep"; s1: "2"; s2: "4"; s3: "7" }
+ ListElement{ year: "2011"; month: "Oct"; s1: "-2"; s2: "4"; s3: "-4" }
+ ListElement{ year: "2011"; month: "Nov"; s1: "-4"; s2: "-8"; s3: "-5" }
+ ListElement{ year: "2011"; month: "Dec"; s1: "-6"; s2: "-6"; s3: "-7" }
+
+ ListElement{ year: "2012"; month: "Jan"; s1: "-10"; s2: "-19"; s3: "-11" }
+ ListElement{ year: "2012"; month: "Feb"; s1: "-11"; s2: "-17"; s3: "-4" }
+ ListElement{ year: "2012"; month: "Mar"; s1: "-6"; s2: "-3"; s3: "-1" }
+ ListElement{ year: "2012"; month: "Apr"; s1: "5"; s2: "1"; s3: "2" }
+ ListElement{ year: "2012"; month: "May"; s1: "9"; s2: "12"; s3: "13" }
+ ListElement{ year: "2012"; month: "Jun"; s1: "11"; s2: "16"; s3: "26" }
+ ListElement{ year: "2012"; month: "Jul"; s1: "18"; s2: "20"; s3: "23" }
+ ListElement{ year: "2012"; month: "Aug"; s1: "19"; s2: "12"; s3: "12" }
+ ListElement{ year: "2012"; month: "Sep"; s1: "9"; s2: "1"; s3: "3" }
+ ListElement{ year: "2012"; month: "Oct"; s1: "-3"; s2: "2"; s3: "-1" }
+ ListElement{ year: "2012"; month: "Nov"; s1: "-5"; s2: "-4"; s3: "-3" }
+ ListElement{ year: "2012"; month: "Dec"; s1: "-7"; s2: "-2"; s3: "-4" }
+
+ ListElement{ year: "2013"; month: "Jan"; s1: "-18"; s2: "-19"; s3: "-19" }
+ ListElement{ year: "2013"; month: "Feb"; s1: "-17"; s2: "-19"; s3: "-12" }
+ ListElement{ year: "2013"; month: "Mar"; s1: "-9"; s2: "-6"; s3: "-5" }
+ ListElement{ year: "2013"; month: "Apr"; s1: "0"; s2: "0"; s3: "0" }
+ ListElement{ year: "2013"; month: "May"; s1: "4"; s2: "7"; s3: "9" }
+ ListElement{ year: "2013"; month: "Jun"; s1: "9"; s2: "11"; s3: "18" }
+ ListElement{ year: "2013"; month: "Jul"; s1: "13"; s2: "15"; s3: "20" }
+ ListElement{ year: "2013"; month: "Aug"; s1: "10"; s2: "11"; s3: "13" }
+ ListElement{ year: "2013"; month: "Sep"; s1: "3"; s2: "1"; s3: "2" }
+ ListElement{ year: "2013"; month: "Oct"; s1: "0"; s2: "1"; s3: "-4" }
+ ListElement{ year: "2013"; month: "Nov"; s1: "-5"; s2: "-6"; s3: "-5" }
+ ListElement{ year: "2013"; month: "Dec"; s1: "-6"; s2: "-7"; s3: "-10" }
+ }
+}
diff --git a/tests/manual/qmllegend/qml/qmllegend/LegendItem.qml b/tests/manual/qmllegend/qml/qmllegend/LegendItem.qml
new file mode 100644
index 0000000..23156f0
--- /dev/null
+++ b/tests/manual/qmllegend/qml/qmllegend/LegendItem.qml
@@ -0,0 +1,109 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.Window
+import QtGraphs
+
+Rectangle {
+ //! [0]
+ property Theme3D theme
+ property Bar3DSeries series
+ //! [0]
+ property point previousSelection
+
+ id: legendItem
+ state: "unselected"
+
+ // Workaround for a layout bug that in some situations causes changing from fully opaque color
+ // to a transparent one to use black background instead of what is actually under the items.
+ // Having the control always slighthly transparent forces the background to be refreshed
+ // properly.
+ opacity: 0.999
+
+ //! [1]
+ RowLayout {
+ anchors.fill: parent
+ spacing: 0
+ clip: true
+ Item {
+ id: markerSpace
+ Layout.minimumWidth: 20
+ Layout.minimumHeight: 20
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ Layout.alignment: Qt.AlignVCenter
+ Rectangle {
+ x: parent.x + parent.width / 4
+ y: parent.y + parent.height / 4
+ width: parent.width / 2
+ height: width
+ border.color: "black"
+ color: series.baseColor
+ }
+ }
+ Item {
+ height: markerSpace.height
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ Layout.alignment: Qt.AlignVCenter
+ Layout.minimumWidth: 100
+ Text {
+ anchors.fill: parent
+ text: series.name
+ verticalAlignment: Text.AlignVCenter
+ clip: true
+ color: theme.labelTextColor
+ font: theme.font
+ }
+ }
+ }
+ //! [1]
+
+ //! [2]
+ MouseArea {
+ id: mouseArea
+ anchors.fill: legendItem
+ onClicked: {
+ if (legendItem.state === "selected") {
+ series.selectedBar = series.invalidSelectionPosition
+ } else {
+ series.selectedBar = previousSelection
+ }
+ }
+ }
+ //! [2]
+
+ //! [4]
+ Connections {
+ target: series
+ function onSelectedBarChanged(position) {
+ if (position !== series.invalidSelectionPosition) {
+ previousSelection = position
+ }
+ }
+ }
+ //! [4]
+
+ //! [3]
+ states: [
+ State {
+ name: "selected"
+ when: series.selectedBar != series.invalidSelectionPosition
+ PropertyChanges {
+ target: legendItem
+ color: series.singleHighlightColor
+ }
+ },
+ State {
+ name: "unselected"
+ when: series.selectedBar == series.invalidSelectionPosition
+ PropertyChanges {
+ target: legendItem
+ color: theme.labelBackgroundColor
+ }
+ }
+ ]
+ //! [3]
+}
diff --git a/tests/manual/qmllegend/qml/qmllegend/main.qml b/tests/manual/qmllegend/qml/qmllegend/main.qml
new file mode 100644
index 0000000..6acdde9
--- /dev/null
+++ b/tests/manual/qmllegend/qml/qmllegend/main.qml
@@ -0,0 +1,215 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import QtGraphs
+import "."
+
+Item {
+ id: mainView
+ width: 800
+ height: 600
+
+ property int buttonLayoutHeight: 180;
+
+ Data {
+ id: graphData
+ }
+
+ Theme3D {
+ id: firstTheme
+ type: Theme3D.ThemeQt
+ }
+
+ Theme3D {
+ id: secondTheme
+ type: Theme3D.ThemeEbony
+ }
+
+ Item {
+ id: dataView
+ anchors.fill: parent
+
+ Bars3D {
+ id: barGraph
+ anchors.fill: parent
+ selectionMode: AbstractGraph3D.SelectionItemAndRow
+ scene.activeCamera.cameraPreset: Camera3D.CameraPresetIsometricLeftHigh
+ theme: firstTheme
+ valueAxis.labelFormat: "%d\u00B0C"
+
+ Bar3DSeries {
+ id: station1
+ name: "Station 1"
+ itemLabelFormat: "Temperature at @seriesName for @colLabel, @rowLabel: @valueLabel"
+
+ ItemModelBarDataProxy {
+ itemModel: graphData.model
+ rowRole: "year"
+ columnRole: "month"
+ valueRole: "s1"
+ }
+ }
+ Bar3DSeries {
+ id: station2
+ name: "Station 2"
+ itemLabelFormat: "Temperature at @seriesName for @colLabel, @rowLabel: @valueLabel"
+
+ ItemModelBarDataProxy {
+ itemModel: graphData.model
+ rowRole: "year"
+ columnRole: "month"
+ valueRole: "s2"
+ }
+ }
+ Bar3DSeries {
+ id: station3
+ name: "Station 3"
+ itemLabelFormat: "Temperature at @seriesName for @colLabel, @rowLabel: @valueLabel"
+
+ ItemModelBarDataProxy {
+ itemModel: graphData.model
+ rowRole: "year"
+ columnRole: "month"
+ valueRole: "s2"
+ }
+ }
+ }
+ }
+
+ Rectangle {
+ property int legendLocation: 3
+ // Make the height and width fractional of main view height and width.
+ // Reverse the relation if screen is in portrait - this makes legend look the same
+ // if the orientation is rotated.
+ property int fractionalHeight: mainView.width > mainView.height ? mainView.height / 5 : mainView.width / 5
+ property int fractionalWidth: mainView.width > mainView.height ? mainView.width / 5 : mainView.height / 5
+
+ id: legendPanel
+ width: fractionalWidth > 150 ? fractionalWidth : 150
+ // Adjust legendpanel height to avoid gaps between layouted items.
+ height: fractionalHeight > 99 ? fractionalHeight - fractionalHeight % 3 : 99
+ border.color: barGraph.theme.labelTextColor
+ border.width: 3
+ color: "#00000000" // Transparent
+
+ //! [0]
+ ColumnLayout {
+ anchors.fill: parent
+ anchors.margins: parent.border.width
+ spacing: 0
+ clip: true
+ LegendItem {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ series: station1
+ theme: barGraph.theme
+ }
+ LegendItem {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ series: station2
+ theme: barGraph.theme
+ }
+ LegendItem {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ series: station3
+ theme: barGraph.theme
+ }
+ }
+ //! [0]
+
+ states: [
+ State {
+ name: "topleft"
+ when: legendPanel.legendLocation === 1
+ AnchorChanges {
+ target: legendPanel
+ anchors.top: buttonLayout.bottom
+ anchors.bottom: undefined
+ anchors.left: dataView.left
+ anchors.right: undefined
+ }
+ },
+ State {
+ name: "topright"
+ when: legendPanel.legendLocation === 2
+ AnchorChanges {
+ target: legendPanel
+ anchors.top: buttonLayout.bottom
+ anchors.bottom: undefined
+ anchors.left: undefined
+ anchors.right: dataView.right
+ }
+ },
+ State {
+ name: "bottomleft"
+ when: legendPanel.legendLocation === 3
+ AnchorChanges {
+ target: legendPanel
+ anchors.top: undefined
+ anchors.bottom: dataView.bottom
+ anchors.left: dataView.left
+ anchors.right: undefined
+ }
+ },
+ State {
+ name: "bottomright"
+ when: legendPanel.legendLocation === 4
+ AnchorChanges {
+ target: legendPanel
+ anchors.top: undefined
+ anchors.bottom: dataView.bottom
+ anchors.left: undefined
+ anchors.right: dataView.right
+ }
+ }
+ ]
+ }
+
+ RowLayout {
+ id: buttonLayout
+ Layout.minimumHeight: themeToggle.height
+ width: parent.width
+ anchors.left: parent.left
+ spacing: 0
+
+ Button {
+ id: themeToggle
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ text: "Change Theme"
+ onClicked: {
+ if (barGraph.theme === firstTheme) {
+ barGraph.theme = secondTheme
+ } else {
+ barGraph.theme = firstTheme
+ }
+ }
+ }
+ Button {
+ id: repositionLegend
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ text: "Reposition Legend"
+ onClicked: {
+ if (legendPanel.legendLocation === 4) {
+ legendPanel.legendLocation = 1
+ } else {
+ legendPanel.legendLocation++
+ }
+ }
+ }
+ Button {
+ id: exitButton
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ text: "Quit"
+ onClicked: Qt.quit();
+ }
+ }
+
+}
diff --git a/tests/manual/qmllegend/qmllegend.pro b/tests/manual/qmllegend/qmllegend.pro
new file mode 100644
index 0000000..af449d8
--- /dev/null
+++ b/tests/manual/qmllegend/qmllegend.pro
@@ -0,0 +1,13 @@
+!include( ../examples.pri ) {
+ error( "Couldn't find the examples.pri file!" )
+}
+
+# The .cpp file which was generated for your project. Feel free to hack it.
+SOURCES += main.cpp
+
+RESOURCES += qmllegend.qrc
+
+OTHER_FILES += doc/src/* \
+ doc/images/* \
+ qml/qmllegend/*
+
diff --git a/tests/manual/qmllegend/qmllegend.qrc b/tests/manual/qmllegend/qmllegend.qrc
new file mode 100644
index 0000000..e9b0f4b
--- /dev/null
+++ b/tests/manual/qmllegend/qmllegend.qrc
@@ -0,0 +1,7 @@
+<RCC>
+ <qresource prefix="/">
+ <file>qml/qmllegend/Data.qml</file>
+ <file>qml/qmllegend/LegendItem.qml</file>
+ <file>qml/qmllegend/main.qml</file>
+ </qresource>
+</RCC>
diff --git a/tests/manual/qmlmultitest/CMakeLists.txt b/tests/manual/qmlmultitest/CMakeLists.txt
new file mode 100644
index 0000000..1fa804e
--- /dev/null
+++ b/tests/manual/qmlmultitest/CMakeLists.txt
@@ -0,0 +1,24 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+qt_internal_add_manual_test(qmlmultitest
+ GUI
+ SOURCES
+ main.cpp
+ )
+target_link_libraries(qmlmultitest PUBLIC
+ Qt::Gui
+ Qt::Graphs
+ )
+
+set(qmlmultitest_resource_files
+ "qml/qmlmultitest/Data.qml"
+ "qml/qmlmultitest/main.qml"
+ )
+
+qt_internal_add_resource(qmlmultitest "qmlmultitest"
+ PREFIX
+ "/"
+ FILES
+ ${qmlmultitest_resource_files}
+ )
diff --git a/tests/manual/qmlmultitest/main.cpp b/tests/manual/qmlmultitest/main.cpp
new file mode 100644
index 0000000..312e8a7
--- /dev/null
+++ b/tests/manual/qmlmultitest/main.cpp
@@ -0,0 +1,32 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtGui/QGuiApplication>
+#include <QtCore/QDir>
+#include <QtQuick/QQuickView>
+#include <QtQml/QQmlEngine>
+
+int main(int argc, char *argv[])
+{
+ QGuiApplication app(argc, argv);
+
+ QQuickView viewer;
+
+ // The following are needed to make examples run without having to install the module
+ // in desktop environments.
+#ifdef Q_OS_WIN
+ QString extraImportPath(QStringLiteral("%1/../../../%2"));
+#else
+ QString extraImportPath(QStringLiteral("%1/../../%2"));
+#endif
+ viewer.engine()->addImportPath(extraImportPath.arg(QGuiApplication::applicationDirPath(),
+ QString::fromLatin1("qml")));
+ QObject::connect(viewer.engine(), &QQmlEngine::quit, &viewer, &QWindow::close);
+
+ viewer.setTitle(QStringLiteral("QML multitest"));
+ viewer.setSource(QUrl("qrc:/qml/qmlmultitest/main.qml"));
+ viewer.setResizeMode(QQuickView::SizeRootObjectToView);
+ viewer.show();
+
+ return app.exec();
+}
diff --git a/tests/manual/qmlmultitest/qml/qmlmultitest/Data.qml b/tests/manual/qmlmultitest/qml/qmlmultitest/Data.qml
new file mode 100644
index 0000000..e010f7e
--- /dev/null
+++ b/tests/manual/qmlmultitest/qml/qmlmultitest/Data.qml
@@ -0,0 +1,54 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+
+Item {
+ property alias sharedData: dataModel
+
+ ListModel {
+ id: dataModel
+ ListElement{ coords: "0,0"; data: "20.0/10.0/4.75"; }
+ ListElement{ coords: "1,0"; data: "21.1/10.3/3.00"; }
+ ListElement{ coords: "2,0"; data: "22.5/10.7/1.24"; }
+ ListElement{ coords: "3,0"; data: "24.0/10.5/2.53"; }
+ ListElement{ coords: "0,1"; data: "20.2/11.2/3.55"; }
+ ListElement{ coords: "1,1"; data: "21.3/11.5/3.03"; }
+ ListElement{ coords: "2,1"; data: "22.6/11.7/3.46"; }
+ ListElement{ coords: "3,1"; data: "23.4/11.5/4.12"; }
+ ListElement{ coords: "0,2"; data: "20.2/12.3/3.37"; }
+ ListElement{ coords: "1,2"; data: "21.1/12.4/2.98"; }
+ ListElement{ coords: "2,2"; data: "22.5/12.1/3.33"; }
+ ListElement{ coords: "3,2"; data: "23.3/12.7/3.23"; }
+ ListElement{ coords: "0,3"; data: "20.7/13.3/5.34"; }
+ ListElement{ coords: "1,3"; data: "21.5/13.2/4.54"; }
+ ListElement{ coords: "2,3"; data: "22.4/13.6/4.65"; }
+ ListElement{ coords: "3,3"; data: "23.2/13.4/6.67"; }
+ ListElement{ coords: "0,4"; data: "20.6/15.0/6.01"; }
+ ListElement{ coords: "1,4"; data: "21.3/14.6/5.83"; }
+ ListElement{ coords: "2,4"; data: "22.5/14.8/7.32"; }
+ ListElement{ coords: "3,4"; data: "23.7/14.3/6.90"; }
+
+ ListElement{ coords: "0,0"; data: "40.0/30.0/14.75"; }
+ ListElement{ coords: "1,0"; data: "41.1/30.3/13.00"; }
+ ListElement{ coords: "2,0"; data: "42.5/30.7/11.24"; }
+ ListElement{ coords: "3,0"; data: "44.0/30.5/12.53"; }
+ ListElement{ coords: "0,1"; data: "40.2/31.2/13.55"; }
+ ListElement{ coords: "1,1"; data: "41.3/31.5/13.03"; }
+ ListElement{ coords: "2,1"; data: "42.6/31.7/13.46"; }
+ ListElement{ coords: "3,1"; data: "43.4/31.5/14.12"; }
+ ListElement{ coords: "0,2"; data: "40.2/32.3/13.37"; }
+ ListElement{ coords: "1,2"; data: "41.1/32.4/12.98"; }
+ ListElement{ coords: "2,2"; data: "42.5/32.1/13.33"; }
+ ListElement{ coords: "3,2"; data: "43.3/32.7/13.23"; }
+ ListElement{ coords: "0,3"; data: "40.7/33.3/15.34"; }
+ ListElement{ coords: "1,3"; data: "41.5/33.2/14.54"; }
+ ListElement{ coords: "2,3"; data: "42.4/33.6/14.65"; }
+ ListElement{ coords: "3,3"; data: "43.2/33.4/16.67"; }
+ ListElement{ coords: "0,4"; data: "40.6/35.0/16.01"; }
+ ListElement{ coords: "1,4"; data: "41.3/34.6/15.83"; }
+ ListElement{ coords: "2,4"; data: "42.5/34.8/17.32"; }
+ ListElement{ coords: "3,4"; data: "43.7/34.3/16.90"; }
+ }
+}
+
diff --git a/tests/manual/qmlmultitest/qml/qmlmultitest/main.qml b/tests/manual/qmlmultitest/qml/qmlmultitest/main.qml
new file mode 100644
index 0000000..cf9a412
--- /dev/null
+++ b/tests/manual/qmlmultitest/qml/qmlmultitest/main.qml
@@ -0,0 +1,237 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.Controls
+import QtGraphs
+import "."
+
+Item {
+ id: mainView
+ width: 800
+ height: 600
+
+ Data {
+ id: data
+ }
+
+ GridLayout {
+ id: gridLayout
+ columns: 2
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ anchors.top: mainView.top
+ anchors.bottom: mainView.bottom
+ anchors.left: mainView.left
+ anchors.right: mainView.right
+
+ Rectangle {
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ border.color: surfaceGraph.theme.gridLineColor
+ border.width: 2
+ color: "#00000000"
+
+ Surface3D {
+ id: surfaceGraph
+ anchors.fill: parent
+ anchors.margins: parent.border.width
+ theme: Theme3D {
+ type: Theme3D.ThemePrimaryColors
+ font.pointSize: 60
+ }
+ scene.activeCamera.cameraPreset: Camera3D.CameraPresetIsometricLeftHigh
+
+ Surface3DSeries {
+ itemLabelFormat: "Pop density at (@xLabel N, @zLabel E): @yLabel"
+ ItemModelSurfaceDataProxy {
+ id: surfaceProxy
+ itemModel: data.sharedData
+ // The surface data points are not neatly lined up in rows and columns,
+ // so we define explicit row and column roles.
+ rowRole: "coords"
+ columnRole: "coords"
+ xPosRole: "data"
+ zPosRole: "data"
+ yPosRole: "data"
+ rowRolePattern: /(\d),\d/
+ columnRolePattern: /(\d),(\d)/
+ xPosRolePattern: /^([asd]*)([fgh]*)([jkl]*)[^\/]*\/([^\/]*)\/.*$/
+ yPosRolePattern: /^([^\/]*)\/([^\/]*)\/(.*)$/
+ zPosRolePattern: /^([asd]*)([qwe]*)([tyu]*)([fgj]*)([^\/]*)\/[^\/]*\/.*$/
+ rowRoleReplace: "\\1"
+ columnRoleReplace: "\\2"
+ xPosRoleReplace: "\\4"
+ yPosRoleReplace: "\\3"
+ zPosRoleReplace: "\\5"
+ }
+ }
+ }
+ }
+
+ // We'll use one grid cell for buttons
+ Rectangle {
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+
+ GridLayout {
+ anchors.right: parent.right
+ anchors.left: parent.left
+ anchors.top: parent.top
+ anchors.bottom: parent.bottom
+ columns: 2
+
+ Button {
+ Layout.minimumWidth: parent.width / 2
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ text: "Clear Selections"
+ onClicked: clearSelections() // call a helper function to keep button itself simpler
+ }
+
+ Button {
+ Layout.minimumWidth: parent.width / 2
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ text: "Quit"
+ onClicked: Qt.quit();
+ }
+
+ Button {
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ text: "Reset Cameras"
+ onClicked: resetCameras() // call a helper function to keep button itself simpler
+ }
+
+ Button {
+ id: mmbButton
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ text: "MMB: Last"
+ onClicked: changeMMB() // call a helper function to keep button itself simpler
+ }
+ }
+ }
+
+ Rectangle {
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ border.color: scatterGraph.theme.gridLineColor
+ border.width: 2
+ color: "#00000000"
+
+ Scatter3D {
+ id: scatterGraph
+ anchors.fill: parent
+ anchors.margins: parent.border.width
+ theme: Theme3D {
+ type: Theme3D.ThemeDigia
+ font.pointSize: 60
+ }
+ scene.activeCamera.cameraPreset: Camera3D.CameraPresetIsometricLeftHigh
+
+ Scatter3DSeries {
+ itemLabelFormat: "Pop density at (@xLabel N, @zLabel E): @yLabel"
+ mesh: Abstract3DSeries.MeshCube
+ ItemModelScatterDataProxy {
+ id: scatterProxy
+ itemModel: data.sharedData
+ // Mapping model roles to scatter series item coordinates.
+ xPosRole: "data"
+ zPosRole: "data"
+ yPosRole: "data"
+ rotationRole: "coords"
+ xPosRolePattern: /^([asd]*)([fgh]*)([jkl]*)[^\/]*\/([^\/]*)\/.*$/
+ yPosRolePattern: /^([^\/]*)\/([^\/]*)\/(.*)$/
+ zPosRolePattern: /^([asd]*)([qwe]*)([tyu]*)([fgj]*)([^\/]*)\/[^\/]*\/.*$/
+ rotationRolePattern: /(\d)\,(\d)/
+ xPosRoleReplace: "\\4"
+ yPosRoleReplace: "\\3"
+ zPosRoleReplace: "\\5"
+ rotationRoleReplace: "@\\2\\1,0,1,0"
+ }
+ }
+ }
+ }
+
+ Rectangle {
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ border.color: barGraph.theme.gridLineColor
+ border.width: 2
+ color: "#00000000"
+
+ Bars3D {
+ id: barGraph
+ anchors.fill: parent
+ anchors.margins: parent.border.width
+ theme: Theme3D {
+ type: Theme3D.ThemeQt
+ font.pointSize: 60
+ }
+ selectionMode: AbstractGraph3D.SelectionItemAndRow | AbstractGraph3D.SelectionSlice
+ scene.activeCamera.cameraPreset: Camera3D.CameraPresetIsometricLeftHigh
+
+ Bar3DSeries {
+ itemLabelFormat: "@seriesName: @valueLabel"
+ name: "Population density"
+
+ ItemModelBarDataProxy {
+ id: barProxy
+ itemModel: data.sharedData
+ // Mapping model roles to bar series rows, columns, and values.
+ rowRole: "coords"
+ columnRole: "coords"
+ valueRole: "data"
+ rotationRole: "coords"
+ rowRolePattern: /(\d),\d/
+ columnRolePattern: /(\d),(\d)/
+ valueRolePattern: /^([^\/]*)\/([^\/]*)\/(.*)$/
+ rotationRolePattern: /(\d)\,(\d)/
+ rowRoleReplace: "\\1"
+ columnRoleReplace: "\\2"
+ valueRoleReplace: "\\3"
+ rotationRoleReplace: "\\2\\1"
+ }
+ }
+ }
+ }
+ }
+
+ function clearSelections() {
+ barGraph.clearSelection()
+ scatterGraph.clearSelection()
+ surfaceGraph.clearSelection()
+ }
+
+ function resetCameras() {
+ surfaceGraph.scene.activeCamera.cameraPreset = Camera3D.CameraPresetIsometricLeftHigh
+ scatterGraph.scene.activeCamera.cameraPreset = Camera3D.CameraPresetIsometricLeftHigh
+ barGraph.scene.activeCamera.cameraPreset = Camera3D.CameraPresetIsometricLeftHigh
+ surfaceGraph.scene.activeCamera.zoomLevel = 100.0
+ scatterGraph.scene.activeCamera.zoomLevel = 100.0
+ barGraph.scene.activeCamera.zoomLevel = 100.0
+ }
+
+ function changeMMB() {
+ if (barProxy.multiMatchBehavior === ItemModelBarDataProxy.MMBLast) {
+ barProxy.multiMatchBehavior = ItemModelBarDataProxy.MMBAverage
+ surfaceProxy.multiMatchBehavior = ItemModelSurfaceDataProxy.MMBAverage
+ mmbButton.text = "MMB: Average"
+ } else if (barProxy.multiMatchBehavior === ItemModelBarDataProxy.MMBAverage) {
+ barProxy.multiMatchBehavior = ItemModelBarDataProxy.MMBCumulative
+ surfaceProxy.multiMatchBehavior = ItemModelSurfaceDataProxy.MMBCumulativeY
+ mmbButton.text = "MMB: Cumulative"
+ } else if (barProxy.multiMatchBehavior === ItemModelBarDataProxy.MMBCumulative) {
+ barProxy.multiMatchBehavior = ItemModelBarDataProxy.MMBFirst
+ surfaceProxy.multiMatchBehavior = ItemModelSurfaceDataProxy.MMBFirst
+ mmbButton.text = "MMB: First"
+ } else {
+ barProxy.multiMatchBehavior = ItemModelBarDataProxy.MMBLast
+ surfaceProxy.multiMatchBehavior = ItemModelSurfaceDataProxy.MMBLast
+ mmbButton.text = "MMB: Last"
+ }
+ }
+}
diff --git a/tests/manual/qmlmultitest/qmlmultitest.pro b/tests/manual/qmlmultitest/qmlmultitest.pro
new file mode 100644
index 0000000..6e5e1b9
--- /dev/null
+++ b/tests/manual/qmlmultitest/qmlmultitest.pro
@@ -0,0 +1,12 @@
+!include( ../tests.pri ) {
+ error( "Couldn't find the tests.pri file!" )
+}
+
+# The .cpp file which was generated for your project. Feel free to hack it.
+SOURCES += main.cpp
+
+RESOURCES += qmlmultitest.qrc
+
+OTHER_FILES += doc/src/* \
+ doc/images/* \
+ qml/qmlmultitest/*
diff --git a/tests/manual/qmlmultitest/qmlmultitest.qrc b/tests/manual/qmlmultitest/qmlmultitest.qrc
new file mode 100644
index 0000000..ae52b7e
--- /dev/null
+++ b/tests/manual/qmlmultitest/qmlmultitest.qrc
@@ -0,0 +1,6 @@
+<RCC>
+ <qresource prefix="/">
+ <file>qml/qmlmultitest/Data.qml</file>
+ <file>qml/qmlmultitest/main.qml</file>
+ </qresource>
+</RCC>
diff --git a/tests/manual/qmlperf/CMakeLists.txt b/tests/manual/qmlperf/CMakeLists.txt
new file mode 100644
index 0000000..893b5d1
--- /dev/null
+++ b/tests/manual/qmlperf/CMakeLists.txt
@@ -0,0 +1,26 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+qt_internal_add_manual_test(qmlperf
+ GUI
+ SOURCES
+ datagenerator.cpp datagenerator.h
+ main.cpp
+ )
+target_link_libraries(qmlperf PUBLIC
+ Qt::Gui
+ Qt::Graphs
+ )
+
+set(qmlperf_resource_files
+ "qml/qmlperf/main.qml"
+ )
+
+qt_internal_add_resource(qmlperf "qmlperf"
+ PREFIX
+ "/"
+ FILES
+ ${qmlperf_resource_files}
+ )
diff --git a/tests/manual/qmlperf/datagenerator.cpp b/tests/manual/qmlperf/datagenerator.cpp
new file mode 100644
index 0000000..a58055f
--- /dev/null
+++ b/tests/manual/qmlperf/datagenerator.cpp
@@ -0,0 +1,68 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "datagenerator.h"
+#include <QDebug>
+#include <QRandomGenerator>
+
+Q_DECLARE_METATYPE(QScatter3DSeries *)
+
+DataGenerator::DataGenerator(QObject *parent) :
+ QObject(parent)
+{
+ qRegisterMetaType<QScatter3DSeries *>();
+
+ m_file = new QFile("results.txt");
+ if (!m_file->open(QIODevice::WriteOnly | QIODevice::Text)) {
+ delete m_file;
+ m_file = 0;
+ }
+}
+
+DataGenerator::~DataGenerator()
+{
+ m_file->close();
+ delete m_file;
+}
+
+void DataGenerator::generateData(QScatter3DSeries *series, uint count)
+{
+ QScatterDataArray *dataArray = new QScatterDataArray;
+ dataArray->resize(count);
+ QScatterDataItem *ptrToDataArray = &dataArray->first();
+
+ for (uint i = 0; i < count; i++) {
+ ptrToDataArray->setPosition(QVector3D(QRandomGenerator::global()->generateDouble(),
+ QRandomGenerator::global()->generateDouble(),
+ QRandomGenerator::global()->generateDouble()));
+ ptrToDataArray++;
+ }
+
+ series->dataProxy()->resetArray(dataArray);
+}
+
+void DataGenerator::add(QScatter3DSeries *series, uint count)
+{
+ QScatterDataArray appendArray;
+ appendArray.resize(count);
+
+ for (uint i = 0; i < count; i++) {
+ appendArray[i].setPosition(QVector3D(QRandomGenerator::global()->generateDouble(),
+ QRandomGenerator::global()->generateDouble(),
+ QRandomGenerator::global()->generateDouble()));
+ }
+
+ series->dataProxy()->addItems(appendArray);
+}
+
+void DataGenerator::writeLine(int itemCount, float fps)
+{
+ if (m_file) {
+ QTextStream out(m_file);
+
+ QString fpsFormatString(QStringLiteral("%1 %2\n"));
+ QString fpsString = fpsFormatString.arg(itemCount).arg(fps);
+
+ out << fpsString;
+ }
+}
diff --git a/tests/manual/qmlperf/datagenerator.h b/tests/manual/qmlperf/datagenerator.h
new file mode 100644
index 0000000..2fa6f1f
--- /dev/null
+++ b/tests/manual/qmlperf/datagenerator.h
@@ -0,0 +1,27 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef DATAGENERATOR_H
+#define DATAGENERATOR_H
+
+#include <QtGraphs/QScatter3DSeries>
+#include <QtCore/QFile>
+
+class DataGenerator : public QObject
+{
+ Q_OBJECT
+public:
+ DataGenerator(QObject *parent = 0);
+ virtual ~DataGenerator();
+
+public Q_SLOTS:
+ void generateData(QScatter3DSeries *series, uint count);
+ void add(QScatter3DSeries *series, uint count);
+ void writeLine(int itemCount, float fps);
+
+private:
+ QScatter3DSeries m_series;
+ QFile *m_file;
+};
+
+#endif // DATAGENERATOR_H
diff --git a/tests/manual/qmlperf/main.cpp b/tests/manual/qmlperf/main.cpp
new file mode 100644
index 0000000..9683caf
--- /dev/null
+++ b/tests/manual/qmlperf/main.cpp
@@ -0,0 +1,38 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "datagenerator.h"
+
+#include <QtGui/QGuiApplication>
+#include <QtCore/QDir>
+#include <QtQml/QQmlContext>
+#include <QtQuick/QQuickView>
+#include <QtQml/QQmlEngine>
+
+int main(int argc, char *argv[])
+{
+ QGuiApplication app(argc, argv);
+
+ QQuickView viewer;
+
+ // The following are needed to make examples run without having to install the module
+ // in desktop environments.
+#ifdef Q_OS_WIN
+ QString extraImportPath(QStringLiteral("%1/../../../%2"));
+#else
+ QString extraImportPath(QStringLiteral("%1/../../%2"));
+#endif
+ viewer.engine()->addImportPath(extraImportPath.arg(QGuiApplication::applicationDirPath(),
+ QString::fromLatin1("qml")));
+ QObject::connect(viewer.engine(), &QQmlEngine::quit, &viewer, &QWindow::close);
+
+ DataGenerator dataGenerator;
+ viewer.rootContext()->setContextProperty("dataGenerator", &dataGenerator);
+
+ viewer.setTitle(QStringLiteral("QML Performance"));
+ viewer.setSource(QUrl("qrc:/qml/qmlperf/main.qml"));
+ viewer.setResizeMode(QQuickView::SizeRootObjectToView);
+ viewer.show();
+
+ return app.exec();
+}
diff --git a/tests/manual/qmlperf/qml/qmlperf/main.qml b/tests/manual/qmlperf/qml/qmlperf/main.qml
new file mode 100644
index 0000000..b04cf0c
--- /dev/null
+++ b/tests/manual/qmlperf/qml/qmlperf/main.qml
@@ -0,0 +1,176 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.Controls
+import QtGraphs
+import "."
+
+Item {
+ id: mainview
+ width: 1280
+ height: 1024
+
+ property var itemCount: 1000.0
+ property var addItems: 500.0
+
+ Button {
+ id: changeButton
+ width: parent.width / 7
+ height: 50
+ anchors.left: parent.left
+ enabled: true
+ text: "Change"
+ onClicked: {
+ console.log("changeButton clicked");
+ if (graphView.state == "meshsphere") {
+ graphView.state = "meshcube"
+ } else if (graphView.state == "meshcube") {
+ graphView.state = "meshpyramid"
+ } else if (graphView.state == "meshpyramid") {
+ graphView.state = "meshpoint"
+ } else if (graphView.state == "meshpoint") {
+ graphView.state = "meshsphere"
+ }
+ }
+ }
+
+ Text {
+ id: fpsText
+ text: "Reading"
+ width: (parent.width / 7) * 3
+ height: 50
+ anchors.left: changeButton.right
+ verticalAlignment: Text.AlignVCenter
+ horizontalAlignment: Text.AlignHCenter
+ }
+
+ Button {
+ id: optimization
+ width: parent.width / 7
+ height: 50
+ anchors.left: fpsText.right
+ enabled: true
+ text: scatterPlot.optimizationHints === AbstractGraph3D.OptimizationDefault ? "To Static" : "To Default"
+ onClicked: {
+ console.log("Optimization");
+ if (scatterPlot.optimizationHints === AbstractGraph3D.OptimizationDefault) {
+ scatterPlot.optimizationHints = AbstractGraph3D.OptimizationStatic;
+ optimization.text = "To Default";
+ } else {
+ scatterPlot.optimizationHints = AbstractGraph3D.OptimizationDefault;
+ optimization.text = "To Static";
+ }
+ }
+ }
+
+ Button {
+ id: itemAdd
+ width: parent.width / 7
+ height: 50
+ anchors.left: optimization.right
+ enabled: true
+ text: "Add"
+ onClicked: {
+ itemCount = itemCount + addItems;
+ dataGenerator.add(scatterSeries, addItems);
+ }
+ }
+
+ Button {
+ id: writeLine
+ width: parent.width / 7
+ height: 50
+ anchors.left: itemAdd.right
+ enabled: true
+ text: "Write"
+ onClicked: {
+ dataGenerator.writeLine(itemCount, scatterPlot.currentFps.toFixed(1));
+ }
+ }
+
+ Item {
+ id: graphView
+ width: mainview.width
+ height: mainview.height
+ anchors.top: changeButton.bottom
+ anchors.left: mainview.left
+ state: "meshsphere"
+
+ Scatter3D {
+ id: scatterPlot
+ width: graphView.width
+ height: graphView.height
+ shadowQuality: AbstractGraph3D.ShadowQualityNone
+ optimizationHints: AbstractGraph3D.OptimizationDefault
+ scene.activeCamera.yRotation: 45.0
+ measureFps: true
+ onCurrentFpsChanged: {
+ fpsText.text = itemCount + " : " + scatterPlot.currentFps.toFixed(1);
+ }
+
+ theme: Theme3D {
+ type: Theme3D.ThemeRetro
+ colorStyle: Theme3D.ColorStyleRangeGradient
+ baseGradients: customGradient
+
+ ColorGradient {
+ id: customGradient
+ ColorGradientStop { position: 1.0; color: "red" }
+ ColorGradientStop { position: 0.0; color: "blue" }
+ }
+ }
+
+ Scatter3DSeries {
+ id: scatterSeries
+ mesh: Abstract3DSeries.MeshSphere
+ }
+
+ Component.onCompleted: dataGenerator.generateData(scatterSeries, itemCount);
+ }
+
+ states: [
+ State {
+ name: "meshsphere"
+ StateChangeScript {
+ name: "doSphere"
+ script: {
+ console.log("Do the sphere");
+ scatterSeries.mesh = Abstract3DSeries.MeshSphere;
+ }
+ }
+ },
+ State {
+ name: "meshcube"
+ StateChangeScript {
+ name: "doCube"
+ script: {
+ console.log("Do the cube");
+ scatterSeries.mesh = Abstract3DSeries.MeshCube;
+ }
+ }
+ },
+ State {
+ name: "meshpyramid"
+ StateChangeScript {
+ name: "doPyramid"
+ script: {
+ console.log("Do the pyramid");
+ scatterSeries.mesh = Abstract3DSeries.MeshPyramid;
+ }
+ }
+ },
+ State {
+ name: "meshpoint"
+ StateChangeScript {
+ name: "doPoint"
+ script: {
+ console.log("Do the point");
+ scatterSeries.mesh = Abstract3DSeries.MeshPoint;
+ }
+ }
+ }
+ ]
+ }
+}
diff --git a/tests/manual/qmlperf/qmlperf.pro b/tests/manual/qmlperf/qmlperf.pro
new file mode 100644
index 0000000..e29ef75
--- /dev/null
+++ b/tests/manual/qmlperf/qmlperf.pro
@@ -0,0 +1,16 @@
+!include( ../tests.pri ) {
+ error( "Couldn't find the tests.pri file!" )
+}
+
+# The .cpp file which was generated for your project. Feel free to hack it.
+SOURCES += main.cpp \
+ datagenerator.cpp
+
+RESOURCES += qmlperf.qrc
+
+OTHER_FILES += doc/src/* \
+ doc/images/* \
+ qml/qmlperf/*
+
+HEADERS += \
+ datagenerator.h
diff --git a/tests/manual/qmlperf/qmlperf.qrc b/tests/manual/qmlperf/qmlperf.qrc
new file mode 100644
index 0000000..b52f4bb
--- /dev/null
+++ b/tests/manual/qmlperf/qmlperf.qrc
@@ -0,0 +1,5 @@
+<RCC>
+ <qresource prefix="/">
+ <file>qml/qmlperf/main.qml</file>
+ </qresource>
+</RCC>
diff --git a/tests/manual/qmlsurfacelayers/CMakeLists.txt b/tests/manual/qmlsurfacelayers/CMakeLists.txt
new file mode 100644
index 0000000..ad1fd9e
--- /dev/null
+++ b/tests/manual/qmlsurfacelayers/CMakeLists.txt
@@ -0,0 +1,41 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+qt_internal_add_manual_test(qmlsurfacelayers
+ GUI
+ SOURCES
+ main.cpp
+ )
+
+target_link_libraries(qmlsurfacelayers PUBLIC
+ Qt::Core
+ Qt::Gui
+ Qt::Qml
+ Qt::Quick
+ Qt::Graphs
+)
+
+set(qmlsurfacelayers_resource_files
+ "qml/qmlsurfacelayers/main.qml"
+)
+
+qt6_add_resources(qmlsurfacelayers "qmlsurfacelayers"
+ PREFIX
+ "/"
+ FILES
+ ${qmlsurfacelayers_resource_files}
+)
+set(qmlsurfacelayers1_resource_files
+ "layer_1.png"
+ "layer_2.png"
+ "layer_3.png"
+)
+
+qt6_add_resources(qmlsurfacelayers "qmlsurfacelayers1"
+ PREFIX
+ "/heightmaps"
+ FILES
+ ${qmlsurfacelayers1_resource_files}
+)
diff --git a/tests/manual/qmlsurfacelayers/layer_1.png b/tests/manual/qmlsurfacelayers/layer_1.png
new file mode 100644
index 0000000..9138c71
--- /dev/null
+++ b/tests/manual/qmlsurfacelayers/layer_1.png
Binary files differ
diff --git a/tests/manual/qmlsurfacelayers/layer_2.png b/tests/manual/qmlsurfacelayers/layer_2.png
new file mode 100644
index 0000000..3af154e
--- /dev/null
+++ b/tests/manual/qmlsurfacelayers/layer_2.png
Binary files differ
diff --git a/tests/manual/qmlsurfacelayers/layer_3.png b/tests/manual/qmlsurfacelayers/layer_3.png
new file mode 100644
index 0000000..796df64
--- /dev/null
+++ b/tests/manual/qmlsurfacelayers/layer_3.png
Binary files differ
diff --git a/tests/manual/qmlsurfacelayers/main.cpp b/tests/manual/qmlsurfacelayers/main.cpp
new file mode 100644
index 0000000..fb2c16f
--- /dev/null
+++ b/tests/manual/qmlsurfacelayers/main.cpp
@@ -0,0 +1,38 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtGraphs/qutils.h>
+
+#include <QtGui/QGuiApplication>
+#include <QtCore/QDir>
+#include <QtQuick/QQuickView>
+#include <QtQml/QQmlEngine>
+#include <QtQuick3D/qquick3d.h>
+
+int main(int argc, char *argv[])
+{
+ QGuiApplication app(argc, argv);
+ QSurfaceFormat::setDefaultFormat(QQuick3D::idealSurfaceFormat());
+ QQuickView viewer;
+
+ // Enable antialiasing in direct rendering mode
+ viewer.setFormat(qDefaultSurfaceFormat(true));
+
+ // The following are needed to make examples run without having to install the module
+ // in desktop environments.
+#ifdef Q_OS_WIN
+ QString extraImportPath(QStringLiteral("%1/../../../../%2"));
+#else
+ QString extraImportPath(QStringLiteral("%1/../../../%2"));
+#endif
+ viewer.engine()->addImportPath(extraImportPath.arg(QGuiApplication::applicationDirPath(),
+ QString::fromLatin1("qml")));
+
+ viewer.setSource(QUrl("qrc:/qml/qmlsurfacelayers/main.qml"));
+
+ viewer.setTitle(QStringLiteral("3-layered Terrain"));
+ viewer.setResizeMode(QQuickView::SizeRootObjectToView);
+ viewer.show();
+
+ return app.exec();
+}
diff --git a/tests/manual/qmlsurfacelayers/qml/qmlsurfacelayers/main.qml b/tests/manual/qmlsurfacelayers/qml/qmlsurfacelayers/main.qml
new file mode 100644
index 0000000..12b4039
--- /dev/null
+++ b/tests/manual/qmlsurfacelayers/qml/qmlsurfacelayers/main.qml
@@ -0,0 +1,283 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.Controls
+import QtGraphs
+import "."
+
+Item {
+ id: mainview
+ width: 1280
+ height: 720
+
+ property real fontSize: 12
+
+ Item {
+ id: surfaceView
+ width: mainview.width - buttonLayout.width
+ height: mainview.height
+ anchors.right: mainview.right;
+
+ //! [0]
+ ColorGradient {
+ id: layerOneGradient
+ ColorGradientStop { position: 0.0; color: "black" }
+ ColorGradientStop { position: 0.31; color: "tan" }
+ ColorGradientStop { position: 0.32; color: "green" }
+ ColorGradientStop { position: 0.40; color: "darkslategray" }
+ ColorGradientStop { position: 1.0; color: "white" }
+ }
+
+ ColorGradient {
+ id: layerTwoGradient
+ ColorGradientStop { position: 0.315; color: "blue" }
+ ColorGradientStop { position: 0.33; color: "white" }
+ }
+
+ ColorGradient {
+ id: layerThreeGradient
+ ColorGradientStop { position: 0.0; color: "red" }
+ ColorGradientStop { position: 0.15; color: "black" }
+ }
+ //! [0]
+
+ Surface3D {
+ id: surfaceLayers
+ width: surfaceView.width
+ height: surfaceView.height
+ theme: Theme3D {
+ type: Theme3D.ThemeEbony
+ font.pointSize: 35
+ colorStyle: Theme3D.ColorStyleRangeGradient
+ }
+ shadowQuality: AbstractGraph3D.ShadowQualityNone
+ selectionMode: AbstractGraph3D.SelectionRow | AbstractGraph3D.SelectionSlice
+ scene.activeCamera.cameraPreset: Camera3D.CameraPresetIsometricLeft
+ axisY.min: 20
+ axisY.max: 200
+ axisX.segmentCount: 5
+ axisX.subSegmentCount: 2
+ axisX.labelFormat: "%i"
+ axisZ.segmentCount: 5
+ axisZ.subSegmentCount: 2
+ axisZ.labelFormat: "%i"
+ axisY.segmentCount: 5
+ axisY.subSegmentCount: 2
+ axisY.labelFormat: "%i"
+
+ //! [1]
+ //! [2]
+ Surface3DSeries {
+ id: layerOneSeries
+ baseGradient: layerOneGradient
+ //! [2]
+ HeightMapSurfaceDataProxy {
+ heightMapFile: ":/heightmaps/layer_1.png"
+ }
+ flatShadingEnabled: false
+ drawMode: Surface3DSeries.DrawSurface
+ //! [4]
+ visible: layerOneToggle.checked // bind to checkbox state
+ //! [4]
+ }
+
+ Surface3DSeries {
+ id: layerTwoSeries
+ baseGradient: layerTwoGradient
+ HeightMapSurfaceDataProxy {
+ heightMapFile: ":/heightmaps/layer_2.png"
+ }
+ flatShadingEnabled: false
+ drawMode: Surface3DSeries.DrawSurface
+ visible: layerTwoToggle.checked // bind to checkbox state
+ }
+
+ Surface3DSeries {
+ id: layerThreeSeries
+ baseGradient: layerThreeGradient
+ HeightMapSurfaceDataProxy {
+ heightMapFile: ":/heightmaps/layer_3.png"
+ }
+ flatShadingEnabled: false
+ drawMode: Surface3DSeries.DrawSurface
+ visible: layerThreeToggle.checked // bind to checkbox state
+ }
+ //! [1]
+ }
+ }
+
+ ColumnLayout {
+ id: buttonLayout
+ anchors.top: parent.top
+ anchors.left: parent.left
+ spacing: 0
+
+ //! [3]
+ GroupBox {
+ Layout.fillWidth: true
+ Column {
+ spacing: 10
+
+ Label {
+ font.pointSize: fontSize
+ font.bold: true
+ text: "Layer Selection"
+ }
+
+ CheckBox {
+ id: layerOneToggle
+ checked: true
+ text: "Show Ground Layer"
+ }
+
+ CheckBox {
+ id: layerTwoToggle
+ checked: true
+ text: "Show Sea Layer"
+ }
+
+ CheckBox {
+ id: layerThreeToggle
+ checked: true
+ text: "Show Tectonic Layer"
+ }
+ }
+ }
+ //! [3]
+
+ //! [5]
+ GroupBox {
+ Layout.fillWidth: true
+ Column {
+ spacing: 10
+
+ Label {
+ font.pointSize: fontSize
+ font.bold: true
+ text: "Layer Style"
+ }
+
+ CheckBox {
+ id: layerOneGrid
+ text: "Show Ground as Grid"
+ onCheckedChanged: {
+ if (checked)
+ layerOneSeries.drawMode = Surface3DSeries.DrawWireframe
+ else
+ layerOneSeries.drawMode = Surface3DSeries.DrawSurface
+ }
+ }
+
+ CheckBox {
+ id: layerTwoGrid
+ text: "Show Sea as Grid"
+
+ onCheckedChanged: {
+ if (checked)
+ layerTwoSeries.drawMode = Surface3DSeries.DrawWireframe
+ else
+ layerTwoSeries.drawMode = Surface3DSeries.DrawSurface
+ }
+ }
+
+ CheckBox {
+ id: layerThreeGrid
+ text: "Show Tectonic as Grid"
+ onCheckedChanged: {
+ if (checked)
+ layerThreeSeries.drawMode = Surface3DSeries.DrawWireframe
+ else
+ layerThreeSeries.drawMode = Surface3DSeries.DrawSurface
+ }
+ }
+ }
+ }
+ //! [5]
+
+ //! [6]
+ Button {
+ id: sliceButton
+ text: "Slice All Layers"
+ Layout.fillWidth: true
+ Layout.minimumHeight: 40
+ onClicked: {
+ if (surfaceLayers.selectionMode & AbstractGraph3D.SelectionMultiSeries) {
+ surfaceLayers.selectionMode = AbstractGraph3D.SelectionRow
+ | AbstractGraph3D.SelectionSlice
+ text = "Slice All Layers"
+ } else {
+ surfaceLayers.selectionMode = AbstractGraph3D.SelectionRow
+ | AbstractGraph3D.SelectionSlice
+ | AbstractGraph3D.SelectionMultiSeries
+ text = "Slice One Layer"
+ }
+ }
+ }
+ //! [6]
+
+ Button {
+ id: shadowButton
+ Layout.fillWidth: true
+ Layout.minimumHeight: 40
+ text: surfaceLayers.shadowsSupported ? "Show Shadows" : "Shadows not supported"
+ enabled: surfaceLayers.shadowsSupported
+ onClicked: {
+ if (surfaceLayers.shadowQuality === AbstractGraph3D.ShadowQualityNone) {
+ surfaceLayers.shadowQuality = AbstractGraph3D.ShadowQualityLow
+ text = "Hide Shadows"
+ } else {
+ surfaceLayers.shadowQuality = AbstractGraph3D.ShadowQualityNone
+ text = "Show Shadows"
+ }
+ }
+ }
+
+ Button {
+ id: renderModeButton
+ text: "Switch Render Mode"
+ Layout.fillWidth: true
+ Layout.minimumHeight: 40
+ onClicked: {
+ var modeText = "Indirect "
+ var aaText
+ if (surfaceLayers.renderingMode === AbstractGraph3D.RenderIndirect &&
+ surfaceLayers.msaaSamples === 0) {
+ surfaceLayers.renderingMode = AbstractGraph3D.RenderDirectToBackground
+ modeText = "BackGround "
+ } else if (surfaceLayers.renderingMode === AbstractGraph3D.RenderIndirect &&
+ surfaceLayers.msaaSamples === 4) {
+ surfaceLayers.renderingMode = AbstractGraph3D.RenderIndirect
+ surfaceLayers.msaaSamples = 0
+ } else if (surfaceLayers.renderingMode === AbstractGraph3D.RenderIndirect &&
+ surfaceLayers.msaaSamples === 8) {
+ surfaceLayers.renderingMode = AbstractGraph3D.RenderIndirect
+ surfaceLayers.msaaSamples = 4
+ } else {
+ surfaceLayers.renderingMode = AbstractGraph3D.RenderIndirect
+ surfaceLayers.msaaSamples = 8
+ }
+
+ if (surfaceLayers.msaaSamples <= 0) {
+ aaText = "No AA"
+ } else {
+ aaText = surfaceLayers.msaaSamples + "xMSAA"
+ }
+
+ renderLabel.text = modeText + aaText
+ }
+ }
+
+ TextField {
+ id: renderLabel
+ font.pointSize: fontSize
+ Layout.fillWidth: true
+ Layout.minimumHeight: 40
+ color: "gray"
+ enabled: false
+ horizontalAlignment: TextInput.AlignHCenter
+ text: "Indirect, " + surfaceLayers.msaaSamples + "xMSAA"
+ }
+ }
+}
diff --git a/tests/manual/qmlsurfacelayers/qmlsurfacelayers.pro b/tests/manual/qmlsurfacelayers/qmlsurfacelayers.pro
new file mode 100644
index 0000000..2bfa904
--- /dev/null
+++ b/tests/manual/qmlsurfacelayers/qmlsurfacelayers.pro
@@ -0,0 +1,12 @@
+!include( ../examples.pri ) {
+ error( "Couldn't find the examples.pri file!" )
+}
+
+# The .cpp file which was generated for your project. Feel free to hack it.
+SOURCES += main.cpp
+
+RESOURCES += qmlsurfacelayers.qrc
+
+OTHER_FILES += doc/src/* \
+ doc/images/* \
+ qml/qmlsurfacelayers/*
diff --git a/tests/manual/qmlsurfacelayers/qmlsurfacelayers.qrc b/tests/manual/qmlsurfacelayers/qmlsurfacelayers.qrc
new file mode 100644
index 0000000..71c2ea3
--- /dev/null
+++ b/tests/manual/qmlsurfacelayers/qmlsurfacelayers.qrc
@@ -0,0 +1,10 @@
+<RCC>
+ <qresource prefix="/">
+ <file>qml/qmlsurfacelayers/main.qml</file>
+ </qresource>
+ <qresource prefix="/heightmaps">
+ <file>layer_1.png</file>
+ <file>layer_2.png</file>
+ <file>layer_3.png</file>
+ </qresource>
+</RCC>
diff --git a/tests/manual/qmlvolume/CMakeLists.txt b/tests/manual/qmlvolume/CMakeLists.txt
new file mode 100644
index 0000000..f6457fc
--- /dev/null
+++ b/tests/manual/qmlvolume/CMakeLists.txt
@@ -0,0 +1,26 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+qt_internal_add_manual_test(qmlvolume
+ GUI
+ SOURCES
+ datasource.cpp datasource.h
+ main.cpp
+ )
+target_link_libraries(qmlvolume PUBLIC
+ Qt::Gui
+ Qt::Graphs
+ )
+
+set(qmlvolume_resource_files
+ "qml/qmlvolume/main.qml"
+ )
+
+qt_internal_add_resource(qmlvolume "qmlvolume"
+ PREFIX
+ "/"
+ FILES
+ ${qmlvolume_resource_files}
+ )
diff --git a/tests/manual/qmlvolume/datasource.cpp b/tests/manual/qmlvolume/datasource.cpp
new file mode 100644
index 0000000..f82d291
--- /dev/null
+++ b/tests/manual/qmlvolume/datasource.cpp
@@ -0,0 +1,73 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "datasource.h"
+#include <QtCore/qmath.h>
+#include <QtGui/QRgb>
+#include <QtGui/QVector3D>
+
+Q_DECLARE_METATYPE(QCustom3DVolume *)
+
+DataSource::DataSource(QObject *parent) :
+ QObject(parent)
+{
+ qRegisterMetaType<QCustom3DVolume *>();
+}
+
+DataSource::~DataSource()
+{
+}
+
+void DataSource::fillVolume(QCustom3DVolume *volumeItem)
+{
+ // Generate example texture data for an half-ellipsoid with a section missing.
+ // This can take a while if the dimensions are large, so we support incremental data generation.
+
+ int index = 0;
+ int textureSize = 256;
+ QVector3D midPoint(float(textureSize) / 2.0f,
+ float(textureSize) / 2.0f,
+ float(textureSize) / 2.0f);
+
+ QList<uchar> *textureData = new QList<uchar>(textureSize * textureSize * textureSize / 2);
+ for (int i = 0; i < textureSize; i++) {
+ for (int j = 0; j < textureSize / 2; j++) {
+ for (int k = 0; k < textureSize; k++) {
+ int colorIndex = 0;
+ // Take a section out of the ellipsoid
+ if (i >= textureSize / 2 || j >= textureSize / 4 || k >= textureSize / 2) {
+ QVector3D distVec = QVector3D(float(k), float(j * 2), float(i)) - midPoint;
+ float adjLen = qMin(255.0f, (distVec.length() * 512.0f / float(textureSize)));
+ colorIndex = 255 - int(adjLen);
+ }
+
+ (*textureData)[index] = colorIndex;
+ index++;
+ }
+ }
+ }
+
+ volumeItem->setScaling(QVector3D(2.0f, 1.0f, 2.0f));
+ volumeItem->setTextureWidth(textureSize);
+ volumeItem->setTextureHeight(textureSize / 2);
+ volumeItem->setTextureDepth(textureSize);
+ volumeItem->setTextureFormat(QImage::Format_Indexed8);
+ volumeItem->setTextureData(textureData);
+
+ QList<QRgb> colorTable(256);
+
+ for (int i = 1; i < 256; i++) {
+ if (i < 15)
+ colorTable[i] = qRgba(0, 0, 0, 0);
+ else if (i < 60)
+ colorTable[i] = qRgba((i * 2) + 120, 0, 0, 15);
+ else if (i < 120)
+ colorTable[i] = qRgba(0, ((i - 60) * 2) + 120, 0, 50);
+ else if (i < 180)
+ colorTable[i] = qRgba(0, 0, ((i - 120) * 2) + 120, 255);
+ else
+ colorTable[i] = qRgba(i, i, i, 255);
+ }
+
+ volumeItem->setColorTable(colorTable);
+}
diff --git a/tests/manual/qmlvolume/datasource.h b/tests/manual/qmlvolume/datasource.h
new file mode 100644
index 0000000..65b0923
--- /dev/null
+++ b/tests/manual/qmlvolume/datasource.h
@@ -0,0 +1,21 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef DATASOURCE_H
+#define DATASOURCE_H
+
+#include <QtGraphs/QCustom3DVolume>
+#include <QtCore/QObject>
+
+class DataSource : public QObject
+{
+ Q_OBJECT
+public:
+ explicit DataSource(QObject *parent = 0);
+ virtual ~DataSource();
+
+public:
+ Q_INVOKABLE void fillVolume(QCustom3DVolume *volumeItem);
+};
+
+#endif
diff --git a/tests/manual/qmlvolume/main.cpp b/tests/manual/qmlvolume/main.cpp
new file mode 100644
index 0000000..96c4ed0
--- /dev/null
+++ b/tests/manual/qmlvolume/main.cpp
@@ -0,0 +1,41 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "datasource.h"
+
+#include <QtGraphs/qutils.h>
+
+#include <QtGui/QGuiApplication>
+#include <QtCore/QDir>
+#include <QtQml/QQmlContext>
+#include <QtQuick/QQuickView>
+#include <QtQml/QQmlEngine>
+
+int main(int argc, char *argv[])
+{
+ QGuiApplication app(argc, argv);
+
+ QQuickView viewer;
+
+ // The following are needed to make examples run without having to install the module
+ // in desktop environments.
+#ifdef Q_OS_WIN
+ QString extraImportPath(QStringLiteral("%1/../../../%2"));
+#else
+ QString extraImportPath(QStringLiteral("%1/../../%2"));
+#endif
+ viewer.engine()->addImportPath(extraImportPath.arg(QGuiApplication::applicationDirPath(),
+ QString::fromLatin1("qml")));
+ QObject::connect(viewer.engine(), &QQmlEngine::quit, &viewer, &QWindow::close);
+
+ viewer.setTitle(QStringLiteral("QML volume example"));
+
+ DataSource dataSource;
+ viewer.rootContext()->setContextProperty("dataSource", &dataSource);
+
+ viewer.setSource(QUrl("qrc:/qml/qmlvolume/main.qml"));
+ viewer.setResizeMode(QQuickView::SizeRootObjectToView);
+ viewer.show();
+
+ return app.exec();
+}
diff --git a/tests/manual/qmlvolume/qml/qmlvolume/main.qml b/tests/manual/qmlvolume/qml/qmlvolume/main.qml
new file mode 100644
index 0000000..ca24779
--- /dev/null
+++ b/tests/manual/qmlvolume/qml/qmlvolume/main.qml
@@ -0,0 +1,140 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.Controls
+import QtGraphs
+import "."
+
+Item {
+ id: mainView
+ width: 1280
+ height: 1024
+
+ Item {
+ id: dataView
+ anchors.bottom: parent.bottom
+ width: parent.width
+ height: parent.height - buttonLayout.height
+
+ Surface3D {
+ id: surfaceGraph
+
+ width: dataView.width
+ height: dataView.height
+ orthoProjection: true
+ measureFps : false
+ Surface3DSeries {
+ id: surfaceSeries
+ drawMode: Surface3DSeries.DrawSurface;
+ flatShadingEnabled: false;
+ meshSmooth: true
+ itemLabelFormat: "@xLabel, @zLabel: @yLabel"
+ itemLabelVisible: false
+
+ onItemLabelChanged: {
+ if (surfaceSeries.selectedPoint === surfaceSeries.invalidSelectionPosition)
+ selectionText.text = "No selection"
+ else
+ selectionText.text = surfaceSeries.itemLabel
+ }
+ }
+
+ Component.onCompleted: {
+ mainView.createVolume();
+ }
+ }
+ }
+
+ Rectangle {
+ width: parent.width
+ height: 50
+ anchors.left: parent.left
+ anchors.top: parent.top
+ color: surfaceGraph.theme.backgroundColor
+
+ ColumnLayout {
+ anchors.fill: parent
+ RowLayout {
+ id: sliderLayout
+ Layout.fillHeight: false
+ spacing: 0
+ visible: surfaceGraph.measureFps
+ Rectangle {
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ Layout.minimumWidth: fpsText.implicitWidth + 10
+ Layout.maximumWidth: fpsText.implicitWidth + 10
+ Layout.minimumHeight: 50
+ Layout.maximumHeight: 50
+ Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
+
+ border.color: "gray"
+ border.width: 1
+ radius: 4
+
+ Text {
+ id: fpsText
+ anchors.fill: parent
+ verticalAlignment: Text.AlignVCenter
+ horizontalAlignment: Text.AlignHCenter
+ text: {
+ return "FPS: " + surfaceGraph.currentFps > 10
+ ? Math.round(surfaceGraph.currentFps)
+ : Math.round(surfaceGraph.currentFps * 10.0) / 10.0
+ }
+ }
+ }
+ }
+
+ RowLayout {
+ id: buttonLayout
+ Layout.minimumHeight: 50
+ spacing: 0
+
+ Button {
+ id: sliceButton
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+
+ text: "Slice"
+
+ onClicked: {
+ if (volumeItem.sliceIndexZ == -1) {
+ volumeItem.sliceIndexZ = 128
+ volumeItem.drawSlices = true
+ volumeItem.drawSliceFrames = true
+ } else {
+ volumeItem.sliceIndexZ = -1
+ volumeItem.drawSlices = false
+ volumeItem.drawSliceFrames = false
+ }
+ }
+ }
+ Button {
+ id: exitButton
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+
+ text: "Quit"
+
+ onClicked: Qt.quit();
+ }
+ }
+ }
+
+ }
+
+ Custom3DVolume {
+ id: volumeItem
+ alphaMultiplier: 0.3
+ preserveOpacity: true
+ useHighDefShader: false
+ }
+
+ function createVolume() {
+ surfaceGraph.addCustomItem(volumeItem)
+ dataSource.fillVolume(volumeItem)
+ }
+}
diff --git a/tests/manual/qmlvolume/qmlvolume.pro b/tests/manual/qmlvolume/qmlvolume.pro
new file mode 100644
index 0000000..73c6bb1
--- /dev/null
+++ b/tests/manual/qmlvolume/qmlvolume.pro
@@ -0,0 +1,16 @@
+!include( ../tests.pri ) {
+ error( "Couldn't find the tests.pri file!" )
+}
+
+QT += graphs
+
+# The .cpp file which was generated for your project. Feel free to hack it.
+SOURCES += main.cpp \
+ datasource.cpp
+HEADERS += datasource.h
+
+RESOURCES += qmlvolume.qrc
+
+OTHER_FILES += doc/src/* \
+ doc/images/* \
+ qml/qmlvolume/*
diff --git a/tests/manual/qmlvolume/qmlvolume.qrc b/tests/manual/qmlvolume/qmlvolume.qrc
new file mode 100644
index 0000000..b1cb299
--- /dev/null
+++ b/tests/manual/qmlvolume/qmlvolume.qrc
@@ -0,0 +1,5 @@
+<RCC>
+ <qresource prefix="/">
+ <file>qml/qmlvolume/main.qml</file>
+ </qresource>
+</RCC>
diff --git a/tests/manual/rotations/CMakeLists.txt b/tests/manual/rotations/CMakeLists.txt
new file mode 100644
index 0000000..0b743ce
--- /dev/null
+++ b/tests/manual/rotations/CMakeLists.txt
@@ -0,0 +1,30 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+qt_internal_add_manual_test(rotations
+ GUI
+ SOURCES
+ main.cpp
+ scatterdatamodifier.cpp scatterdatamodifier.h
+ )
+
+target_link_libraries(rotations PUBLIC
+ Qt::Core
+ Qt::Gui
+ Qt::Widgets
+ Qt::Graphs
+)
+
+set(rotations_resource_files
+ "mesh/largesphere.obj"
+ "mesh/narrowarrow.obj"
+)
+
+qt6_add_resources(rotations "rotations"
+ PREFIX
+ "/"
+ FILES
+ ${rotations_resource_files}
+)
diff --git a/tests/manual/rotations/main.cpp b/tests/manual/rotations/main.cpp
new file mode 100644
index 0000000..38b111b
--- /dev/null
+++ b/tests/manual/rotations/main.cpp
@@ -0,0 +1,81 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "scatterdatamodifier.h"
+
+#include <QtWidgets/QApplication>
+#include <QtWidgets/QWidget>
+#include <QtWidgets/QHBoxLayout>
+#include <QtWidgets/QVBoxLayout>
+#include <QtWidgets/QPushButton>
+#include <QtWidgets/QSlider>
+#include <QtWidgets/QLabel>
+#include <QtWidgets/QMessageBox>
+#include <QtGui/QScreen>
+
+int main(int argc, char **argv)
+{
+ qputenv("QSG_RHI_BACKEND", "opengl");
+ QApplication app(argc, argv);
+ Q3DScatter *graph = new Q3DScatter();
+ QWidget *container = QWidget::createWindowContainer(graph);
+
+ if (!graph->hasContext()) {
+ QMessageBox msgBox;
+ msgBox.setText("Couldn't initialize the OpenGL context.");
+ msgBox.exec();
+ return -1;
+ }
+
+ QSize screenSize = graph->screen()->size();
+ container->setMinimumSize(QSize(screenSize.width() / 2, screenSize.height() / 1.5));
+ container->setMaximumSize(screenSize);
+ container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+ container->setFocusPolicy(Qt::StrongFocus);
+
+ QWidget *widget = new QWidget;
+ QHBoxLayout *hLayout = new QHBoxLayout(widget);
+ QVBoxLayout *vLayout = new QVBoxLayout();
+ hLayout->addWidget(container, 1);
+ hLayout->addLayout(vLayout);
+
+ widget->setWindowTitle(QStringLiteral("Item rotations example - Magnetic field of the sun"));
+
+ QPushButton *toggleRotationButton = new QPushButton(widget);
+ toggleRotationButton->setText(QStringLiteral("Toggle animation"));
+ QPushButton *toggleSunButton = new QPushButton(widget);
+ toggleSunButton->setText(QStringLiteral("Toggle Sun"));
+
+ QSlider *fieldLinesSlider = new QSlider(Qt::Horizontal, widget);
+ fieldLinesSlider->setTickInterval(1);
+ fieldLinesSlider->setMinimum(1);
+ fieldLinesSlider->setValue(12);
+ fieldLinesSlider->setMaximum(128);
+
+ QSlider *arrowsSlider = new QSlider(Qt::Horizontal, widget);
+ arrowsSlider->setTickInterval(1);
+ arrowsSlider->setMinimum(8);
+ arrowsSlider->setValue(16);
+ arrowsSlider->setMaximum(32);
+
+ vLayout->addWidget(toggleRotationButton);
+ vLayout->addWidget(toggleSunButton);
+ vLayout->addWidget(new QLabel(QStringLiteral("Field Lines (1 - 128):")));
+ vLayout->addWidget(fieldLinesSlider);
+ vLayout->addWidget(new QLabel(QStringLiteral("Arrows per line (8 - 32):")));
+ vLayout->addWidget(arrowsSlider, 1, Qt::AlignTop);
+
+ ScatterDataModifier *modifier = new ScatterDataModifier(graph);
+
+ QObject::connect(toggleRotationButton, &QPushButton::clicked, modifier,
+ &ScatterDataModifier::toggleRotation);
+ QObject::connect(toggleSunButton, &QPushButton::clicked, modifier,
+ &ScatterDataModifier::toggleSun);
+ QObject::connect(fieldLinesSlider, &QSlider::valueChanged, modifier,
+ &ScatterDataModifier::setFieldLines);
+ QObject::connect(arrowsSlider, &QSlider::valueChanged, modifier,
+ &ScatterDataModifier::setArrowsPerLine);
+
+ widget->show();
+ return app.exec();
+}
diff --git a/tests/manual/rotations/mesh/largesphere.obj b/tests/manual/rotations/mesh/largesphere.obj
new file mode 100644
index 0000000..6310141
--- /dev/null
+++ b/tests/manual/rotations/mesh/largesphere.obj
@@ -0,0 +1,1938 @@
+# Blender v2.66 (sub 0) OBJ File: ''
+# www.blender.org
+o Sphere_Sphere.001
+v -1.251476 7.901507 0.000000
+v -2.472136 7.608452 0.000000
+v -3.631924 7.128052 0.000000
+v -4.702282 6.472136 0.000000
+v -5.656854 5.656854 0.000000
+v -6.472136 4.702282 0.000000
+v -7.128052 3.631924 0.000000
+v -7.608452 2.472136 0.000000
+v -7.901507 1.251475 0.000000
+v -8.000000 -0.000001 0.000000
+v -7.901506 -1.251477 0.000000
+v -7.608451 -2.472138 0.000000
+v -7.128051 -3.631927 0.000000
+v -6.472134 -4.702285 0.000000
+v -5.656851 -5.656857 0.000000
+v -4.702279 -6.472138 0.000000
+v -3.631920 -7.128055 0.000000
+v -2.472131 -7.608454 0.000000
+v -1.251470 -7.901508 0.000000
+v -1.190224 7.901506 -0.386729
+v -2.351141 7.608452 -0.763934
+v -3.454165 7.128052 -1.122328
+v -4.472136 6.472136 -1.453087
+v -5.379988 5.656855 -1.748066
+v -6.155366 4.702282 -2.000002
+v -6.779180 3.631924 -2.202692
+v -7.236068 2.472136 -2.351143
+v -7.514779 1.251475 -2.441702
+v -7.608452 -0.000001 -2.472138
+v -7.514779 -1.251477 -2.441702
+v -7.236067 -2.472138 -2.351143
+v -6.779179 -3.631927 -2.202691
+v -6.155365 -4.702285 -2.000002
+v -5.379985 -5.656857 -1.748065
+v -4.472132 -6.472138 -1.453086
+v -3.454160 -7.128055 -1.122327
+v -2.351136 -7.608454 -0.763933
+v -1.190218 -7.901508 -0.386727
+v 0.000006 -8.000000 0.000000
+v -1.012464 7.901506 -0.735603
+v -1.999999 7.608452 -1.453089
+v -2.938287 7.128052 -2.134795
+v -3.804224 6.472136 -2.763936
+v -4.576490 5.656855 -3.325019
+v -5.236066 4.702282 -3.804230
+v -5.766714 3.631924 -4.189768
+v -6.155365 2.472136 -4.472140
+v -6.392451 1.251475 -4.644393
+v -6.472135 -0.000001 -4.702287
+v -6.392451 -1.251477 -4.644393
+v -6.155365 -2.472138 -4.472139
+v -5.766713 -3.631927 -4.189767
+v -5.236064 -4.702285 -3.804229
+v -4.576487 -5.656857 -3.325018
+v -3.804221 -6.472138 -2.763934
+v -2.938283 -7.128055 -2.134793
+v -1.999994 -7.608454 -1.453086
+v -1.012459 -7.901508 -0.735599
+v -0.735596 7.901506 -1.012470
+v -1.453082 7.608452 -2.000005
+v -2.134788 7.128052 -2.938294
+v -2.763928 6.472136 -3.804231
+v -3.325012 5.656855 -4.576497
+v -3.804222 4.702282 -5.236073
+v -4.189760 3.631924 -5.766721
+v -4.472132 2.472136 -6.155373
+v -4.644385 1.251475 -6.392459
+v -4.702279 -0.000001 -6.472142
+v -4.644385 -1.251477 -6.392458
+v -4.472132 -2.472138 -6.155372
+v -4.189760 -3.631927 -5.766720
+v -3.804221 -4.702285 -5.236072
+v -3.325010 -5.656857 -4.576494
+v -2.763927 -6.472138 -3.804229
+v -2.134786 -7.128055 -2.938290
+v -1.453079 -7.608454 -2.000001
+v -0.735593 -7.901508 -1.012466
+v -0.386723 7.901506 -1.190230
+v -0.763927 7.608452 -2.351147
+v -1.122321 7.128052 -3.454171
+v -1.453079 6.472136 -4.472141
+v -1.748058 5.656855 -5.379994
+v -1.999994 4.702282 -6.155373
+v -2.202683 3.631924 -6.779186
+v -2.351135 2.472136 -7.236074
+v -2.441693 1.251475 -7.514785
+v -2.472130 -0.000001 -7.608459
+v -2.441693 -1.251477 -7.514784
+v -2.351135 -2.472138 -7.236073
+v -2.202683 -3.631927 -6.779185
+v -1.999993 -4.702285 -6.155371
+v -1.748057 -5.656857 -5.379991
+v -1.453079 -6.472138 -4.472138
+v -1.122320 -7.128055 -3.454167
+v -0.763926 -7.608454 -2.351142
+v -0.386721 -7.901508 -1.190225
+v 0.000007 7.901506 -1.251482
+v 0.000007 7.608452 -2.472142
+v 0.000007 7.128052 -3.631930
+v 0.000008 6.472136 -4.702287
+v 0.000008 5.656855 -5.656860
+v 0.000008 4.702282 -6.472141
+v 0.000008 3.631924 -7.128057
+v 0.000008 2.472136 -7.608458
+v 0.000009 1.251475 -7.901512
+v 0.000009 -0.000001 -8.000006
+v 0.000009 -1.251477 -7.901511
+v 0.000008 -2.472138 -7.608456
+v 0.000008 -3.631927 -7.128057
+v 0.000008 -4.702285 -6.472139
+v 0.000008 -5.656857 -5.656857
+v 0.000007 -6.472138 -4.702284
+v 0.000007 -7.128055 -3.631926
+v 0.000007 -7.608454 -2.472137
+v 0.000007 -7.901508 -1.251476
+v 0.386736 7.901506 -1.190230
+v 0.763941 7.608452 -2.351147
+v 1.122336 7.128052 -3.454170
+v 1.453094 6.472136 -4.472140
+v 1.748074 5.656855 -5.379993
+v 2.000010 4.702282 -6.155371
+v 2.202699 3.631924 -6.779185
+v 2.351151 2.472136 -7.236073
+v 2.441710 1.251475 -7.514783
+v 2.472147 -0.000001 -7.608457
+v 2.441710 -1.251477 -7.514783
+v 2.351151 -2.472138 -7.236071
+v 2.202699 -3.631927 -6.779184
+v 2.000009 -4.702285 -6.155369
+v 1.748073 -5.656857 -5.379990
+v 1.453093 -6.472138 -4.472137
+v 1.122334 -7.128055 -3.454166
+v 0.763939 -7.608454 -2.351141
+v 0.386734 -7.901508 -1.190225
+v 0.735610 7.901506 -1.012470
+v 1.453096 7.608452 -2.000005
+v 2.134803 7.128052 -2.938292
+v 2.763942 6.472136 -3.804229
+v 3.325027 5.656855 -4.576495
+v 3.804237 4.702282 -5.236070
+v 4.189775 3.631924 -5.766718
+v 4.472148 2.472136 -6.155370
+v 4.644401 1.251475 -6.392455
+v 4.702294 -0.000001 -6.472139
+v 4.644400 -1.251477 -6.392455
+v 4.472147 -2.472138 -6.155369
+v 4.189774 -3.631927 -5.766717
+v 3.804236 -4.702285 -5.236069
+v 3.325025 -5.656857 -4.576492
+v 2.763941 -6.472138 -3.804227
+v 2.134800 -7.128055 -2.938289
+v 1.453092 -7.608454 -2.000000
+v 0.735606 -7.901508 -1.012465
+v 1.012477 7.901506 -0.735602
+v 2.000012 7.608452 -1.453088
+v 2.938301 7.128052 -2.134794
+v 3.804237 6.472136 -2.763933
+v 4.576503 5.656855 -3.325017
+v 5.236080 4.702282 -3.804226
+v 5.766727 3.631924 -4.189765
+v 6.155380 2.472136 -4.472136
+v 6.392465 1.251475 -4.644389
+v 6.472148 -0.000001 -4.702283
+v 6.392465 -1.251477 -4.644389
+v 6.155378 -2.472138 -4.472136
+v 5.766726 -3.631927 -4.189764
+v 5.236078 -4.702285 -3.804225
+v 4.576500 -5.656857 -3.325016
+v 3.804235 -6.472138 -2.763932
+v 2.938297 -7.128055 -2.134791
+v 2.000007 -7.608454 -1.453085
+v 1.012472 -7.901508 -0.735599
+v 1.190237 7.901506 -0.386729
+v 2.351154 7.608452 -0.763933
+v 3.454178 7.128052 -1.122326
+v 4.472147 6.472136 -1.453085
+v 5.380000 5.656855 -1.748063
+v 6.155379 4.702282 -1.999998
+v 6.779192 3.631924 -2.202688
+v 7.236081 2.472136 -2.351139
+v 7.514791 1.251475 -2.441698
+v 7.608464 -0.000001 -2.472134
+v 7.514791 -1.251477 -2.441698
+v 7.236079 -2.472138 -2.351139
+v 6.779191 -3.631927 -2.202688
+v 6.155377 -4.702285 -1.999998
+v 5.379997 -5.656857 -1.748063
+v 4.472144 -6.472138 -1.453084
+v 3.454173 -7.128055 -1.122325
+v 2.351148 -7.608454 -0.763931
+v 1.190231 -7.901508 -0.386727
+v 1.251488 7.901506 0.000001
+v 2.472148 7.608452 0.000001
+v 3.631936 7.128052 0.000002
+v 4.702293 6.472136 0.000002
+v 5.656866 5.656855 0.000003
+v 6.472147 4.702282 0.000004
+v 7.128063 3.631924 0.000003
+v 7.608464 2.472136 0.000004
+v 7.901517 1.251475 0.000004
+v 8.000011 -0.000001 0.000004
+v 7.901517 -1.251477 0.000004
+v 7.608462 -2.472138 0.000004
+v 7.128062 -3.631927 0.000003
+v 6.472145 -4.702285 0.000004
+v 5.656862 -5.656857 0.000002
+v 4.702290 -6.472138 0.000002
+v 3.631932 -7.128055 0.000002
+v 2.472143 -7.608454 0.000001
+v 1.251483 -7.901508 0.000001
+v 1.190236 7.901506 0.386730
+v 2.351153 7.608452 0.763935
+v 3.454176 7.128052 1.122330
+v 4.472146 6.472136 1.453089
+v 5.379998 5.656855 1.748069
+v 6.155376 4.702282 2.000005
+v 6.779190 3.631924 2.202694
+v 7.236078 2.472136 2.351147
+v 7.514788 1.251475 2.441705
+v 7.608462 -0.000001 2.472141
+v 7.514788 -1.251477 2.441705
+v 7.236076 -2.472138 2.351146
+v 6.779189 -3.631927 2.202693
+v 6.155375 -4.702285 2.000004
+v 5.379995 -5.656857 1.748067
+v 4.472143 -6.472138 1.453088
+v 3.454172 -7.128055 1.122328
+v 2.351147 -7.608454 0.763933
+v 1.190231 -7.901508 0.386728
+v 1.012476 7.901506 0.735603
+v 2.000010 7.608452 1.453090
+v 2.938298 7.128052 2.134796
+v 3.804235 6.472136 2.763937
+v 4.576499 5.656855 3.325021
+v 5.236075 4.702282 3.804232
+v 5.766723 3.631924 4.189770
+v 6.155375 2.472136 4.472143
+v 6.392460 1.251475 4.644395
+v 6.472144 -0.000001 4.702289
+v 6.392460 -1.251477 4.644395
+v 6.155374 -2.472138 4.472141
+v 5.766722 -3.631927 4.189769
+v 5.236074 -4.702285 3.804231
+v 4.576497 -5.656857 3.325018
+v 3.804232 -6.472138 2.763935
+v 2.938295 -7.128055 2.134794
+v 2.000006 -7.608454 1.453086
+v 1.012471 -7.901508 0.735600
+v 0.735608 7.901506 1.012471
+v 1.453094 7.608452 2.000006
+v 2.134799 7.128052 2.938294
+v 2.763939 6.472136 3.804232
+v 3.325022 5.656855 4.576497
+v 3.804231 4.702282 5.236074
+v 4.189770 3.631924 5.766721
+v 4.472141 2.472136 6.155374
+v 4.644393 1.251475 6.392459
+v 4.702287 -0.000001 6.472143
+v 4.644393 -1.251477 6.392459
+v 4.472141 -2.472138 6.155372
+v 4.189768 -3.631927 5.766720
+v 3.804231 -4.702285 5.236073
+v 3.325021 -5.656857 4.576494
+v 2.763937 -6.472138 3.804228
+v 2.134797 -7.128055 2.938291
+v 1.453090 -7.608454 2.000001
+v 0.735605 -7.901508 1.012466
+v 0.386735 7.901506 1.190230
+v 0.763939 7.608452 2.351147
+v 1.122332 7.128052 3.454171
+v 1.453090 6.472136 4.472141
+v 1.748069 5.656855 5.379993
+v 2.000002 4.702282 6.155373
+v 2.202693 3.631924 6.779186
+v 2.351144 2.472136 7.236073
+v 2.441703 1.251475 7.514784
+v 2.472139 -0.000001 7.608459
+v 2.441703 -1.251477 7.514784
+v 2.351144 -2.472138 7.236072
+v 2.202692 -3.631927 6.779184
+v 2.000003 -4.702285 6.155371
+v 1.748068 -5.656857 5.379990
+v 1.453089 -6.472138 4.472137
+v 1.122331 -7.128055 3.454167
+v 0.763937 -7.608454 2.351141
+v 0.386733 -7.901508 1.190225
+v 0.000005 7.901506 1.251482
+v 0.000005 7.608452 2.472142
+v 0.000004 7.128052 3.631929
+v 0.000003 6.472136 4.702286
+v 0.000003 5.656855 5.656859
+v 0.000001 4.702282 6.472140
+v 0.000002 3.631924 7.128057
+v 0.000001 2.472136 7.608456
+v 0.000001 1.251475 7.901510
+v 0.000000 -0.000001 8.000005
+v 0.000001 -1.251477 7.901510
+v 0.000002 -2.472138 7.608455
+v 0.000001 -3.631927 7.128055
+v 0.000002 -4.702285 6.472138
+v 0.000003 -5.656857 5.656855
+v 0.000004 -6.472138 4.702283
+v 0.000004 -7.128055 3.631925
+v 0.000005 -7.608454 2.472136
+v 0.000006 -7.901508 1.251476
+v -0.386724 7.901506 1.190230
+v -0.763929 7.608452 2.351146
+v -1.122324 7.128052 3.454169
+v -1.453083 6.472136 4.472139
+v -1.748063 5.656855 5.379992
+v -2.000000 4.702282 6.155369
+v -2.202688 3.631924 6.779183
+v -2.351142 2.472136 7.236070
+v -2.441700 1.251475 7.514781
+v -2.472137 -0.000001 7.608455
+v -2.441700 -1.251477 7.514781
+v -2.351140 -2.472138 7.236069
+v -2.202689 -3.631927 6.779181
+v -1.999999 -4.702285 6.155367
+v -1.748061 -5.656857 5.379988
+v -1.453082 -6.472138 4.472136
+v -1.122323 -7.128055 3.454165
+v -0.763927 -7.608454 2.351140
+v -0.386722 -7.901508 1.190224
+v -0.735597 7.901506 1.012470
+v -1.453084 7.608452 2.000003
+v -2.134790 7.128052 2.938291
+v -2.763931 6.472136 3.804227
+v -3.325015 5.656855 4.576493
+v -3.804227 4.702282 5.236067
+v -4.189764 3.631924 5.766716
+v -4.472137 2.472136 6.155366
+v -4.644390 1.251475 6.392452
+v -4.702284 -0.000001 6.472136
+v -4.644390 -1.251477 6.392452
+v -4.472136 -2.472138 6.155365
+v -4.189764 -3.631927 5.766714
+v -3.804225 -4.702285 5.236066
+v -3.325012 -5.656857 4.576489
+v -2.763929 -6.472138 3.804224
+v -2.134788 -7.128055 2.938287
+v -1.453080 -7.608454 1.999999
+v -0.735594 -7.901508 1.012465
+v 0.000001 8.000000 0.000004
+v -1.012464 7.901506 0.735602
+v -1.999999 7.608452 1.453087
+v -2.938287 7.128052 2.134792
+v -3.804225 6.472136 2.763931
+v -4.576491 5.656855 3.325015
+v -5.236069 4.702282 3.804223
+v -5.766715 3.631924 4.189762
+v -6.155367 2.472136 4.472132
+v -6.392453 1.251475 4.644385
+v -6.472137 -0.000001 4.702279
+v -6.392453 -1.251477 4.644385
+v -6.155365 -2.472138 4.472132
+v -5.766714 -3.631927 4.189760
+v -5.236066 -4.702285 3.804223
+v -4.576488 -5.656857 3.325012
+v -3.804222 -6.472138 2.763929
+v -2.938284 -7.128055 2.134789
+v -1.999995 -7.608454 1.453083
+v -1.012460 -7.901508 0.735598
+v -1.190224 7.901506 0.386728
+v -2.351140 7.608452 0.763932
+v -3.454164 7.128052 1.122325
+v -4.472135 6.472136 1.453082
+v -5.379988 5.656855 1.748062
+v -6.155366 4.702282 1.999995
+v -6.779179 3.631924 2.202685
+v -7.236067 2.472136 2.351135
+v -7.514778 1.251475 2.441694
+v -7.608452 -0.000001 2.472130
+v -7.514778 -1.251477 2.441694
+v -7.236065 -2.472138 2.351135
+v -6.779178 -3.631927 2.202684
+v -6.155364 -4.702285 1.999995
+v -5.379983 -5.656857 1.748060
+v -4.472131 -6.472138 1.453082
+v -3.454160 -7.128055 1.122324
+v -2.351135 -7.608454 0.763930
+v -1.190218 -7.901508 0.386726
+vt 0.035483 0.289114
+vt 0.042491 0.337738
+vt 0.007397 0.296413
+vt 0.063422 0.533643
+vt 0.068439 0.582662
+vt 0.015908 0.545902
+vt 0.097669 0.776914
+vt 0.111929 0.824070
+vt 0.027296 0.795217
+vt 0.391159 0.929226
+vt 0.461397 0.895775
+vt 0.452242 0.950225
+vt 0.026572 0.240871
+vt 0.004734 0.246573
+vt 0.058567 0.484592
+vt 0.014398 0.495993
+vt 0.087726 0.728878
+vt 0.023823 0.745426
+vt 0.270350 0.936037
+vt 0.231553 0.980467
+vt 0.014254 0.193317
+vt 0.001048 0.196795
+vt 0.053639 0.435556
+vt 0.012882 0.446084
+vt 0.080129 0.680372
+vt 0.021272 0.695580
+vt 0.178869 0.909944
+vt 0.068145 0.942733
+vt 0.048386 0.386583
+vt 0.011281 0.396180
+vt 0.073887 0.631595
+vt 0.019234 0.645702
+vt 0.135028 0.869370
+vt 0.042415 0.894305
+vt 0.009500 0.346287
+vt 0.017489 0.595807
+vt 0.032618 0.844897
+vt 0.484179 0.902186
+vt 0.550590 0.948315
+vt 1.004734 0.246573
+vt 1.007397 0.296413
+vt 0.982704 0.246140
+vt 1.014398 0.495993
+vt 1.015908 0.545902
+vt 0.969883 0.495119
+vt 1.023823 0.745426
+vt 1.027296 0.795217
+vt 0.957447 0.744128
+vt 1.231553 0.980467
+vt 0.750122 0.974207
+vt 1.001048 0.196795
+vt 0.987598 0.196530
+vt 1.012882 0.446084
+vt 0.971892 0.445278
+vt 1.021272 0.695580
+vt 0.960802 0.694395
+vt 1.068145 0.942733
+vt 0.901563 0.939596
+vt 1.011281 0.396180
+vt 0.974014 0.395447
+vt 1.019234 0.645702
+vt 0.963490 0.644610
+vt 1.042415 0.894305
+vt 0.933349 0.892198
+vt 1.009500 0.346287
+vt 0.976377 0.345635
+vt 1.017489 0.595807
+vt 0.965794 0.594793
+vt 1.032618 0.844897
+vt 0.945957 0.843211
+vt 0.979169 0.295858
+vt 0.967884 0.544959
+vt 0.952893 0.793765
+vt 0.960930 0.239587
+vt 0.925822 0.482052
+vt 0.894284 0.725297
+vt 0.716363 0.929856
+vt 0.974473 0.192535
+vt 0.931207 0.433203
+vt 0.902436 0.677050
+vt 0.801451 0.904132
+vt 0.936960 0.384433
+vt 0.909171 0.628493
+vt 0.844888 0.864413
+vt 0.943427 0.335818
+vt 0.915075 0.579756
+vt 0.868695 0.819731
+vt 0.951128 0.287472
+vt 0.920529 0.530922
+vt 0.883697 0.773009
+vt 0.509882 0.901680
+vt 0.603672 0.925341
+vt 0.939868 0.227168
+vt 0.882765 0.457977
+vt 0.837646 0.692981
+vt 0.690972 0.886830
+vt 0.962264 0.185007
+vt 0.891191 0.410771
+vt 0.848554 0.646630
+vt 0.746350 0.860374
+vt 0.900372 0.363822
+vt 0.857974 0.599764
+vt 0.782623 0.823943
+vt 0.910879 0.317332
+vt 0.866529 0.552601
+vt 0.806908 0.782514
+vt 0.923572 0.271605
+vt 0.874667 0.505296
+vt 0.824298 0.738476
+vt 0.531785 0.894399
+vt 0.616896 0.895419
+vt 0.840708 0.424882
+vt 0.787319 0.652419
+vt 0.666434 0.846117
+vt 0.951650 0.174333
+vt 0.851788 0.379552
+vt 0.799275 0.607479
+vt 0.706276 0.816232
+vt 0.864196 0.334814
+vt 0.810089 0.562030
+vt 0.735147 0.779653
+vt 0.878757 0.291055
+vt 0.820304 0.516318
+vt 0.756661 0.739218
+vt 0.896714 0.248885
+vt 0.830369 0.470540
+vt 0.773481 0.696510
+vt 0.545835 0.882162
+vt 0.613251 0.864748
+vt 0.920019 0.209353
+vt 0.601814 0.835743
+vt 0.902011 0.186799
+vt 0.798974 0.385208
+vt 0.741339 0.608286
+vt 0.641659 0.808678
+vt 0.812374 0.341530
+vt 0.753261 0.563866
+vt 0.671741 0.774500
+vt 0.827915 0.298950
+vt 0.764518 0.519117
+vt 0.694750 0.736000
+vt 0.846764 0.258126
+vt 0.775586 0.474273
+vt 0.713019 0.694862
+vt 0.551827 0.867272
+vt 0.551336 0.851697
+vt 0.870653 0.220135
+vt 0.786910 0.429557
+vt 0.728181 0.652086
+vt 0.943465 0.161100
+vt 0.886800 0.160336
+vt 0.756203 0.341667
+vt 0.697515 0.564418
+vt 0.616262 0.775491
+vt 0.771631 0.298973
+vt 0.708699 0.519594
+vt 0.639253 0.736889
+vt 0.790354 0.258010
+vt 0.719689 0.474675
+vt 0.657473 0.695657
+vt 0.814109 0.219846
+vt 0.730930 0.429879
+vt 0.672573 0.652794
+vt 0.546100 0.836912
+vt 0.845356 0.186292
+vt 0.742904 0.385443
+vt 0.685664 0.608914
+vt 0.586129 0.809772
+vt 0.938802 0.146131
+vt 0.876174 0.131001
+vt 0.710309 0.297343
+vt 0.654008 0.524127
+vt 0.590087 0.747535
+vt 0.727302 0.254538
+vt 0.663845 0.478153
+vt 0.607276 0.704820
+vt 0.749262 0.214082
+vt 0.673880 0.432264
+vt 0.621259 0.660654
+vt 0.779077 0.177660
+vt 0.684564 0.386653
+vt 0.643951 0.570007
+vt 0.537514 0.823995
+vt 0.820645 0.148333
+vt 0.696450 0.341564
+vt 0.567816 0.787838
+vt 0.658464 0.256085
+vt 0.609517 0.490486
+vt 0.563122 0.725732
+vt 0.700288 0.169995
+vt 0.617324 0.442954
+vt 0.575103 0.679574
+vt 0.625569 0.395564
+vt 0.633223 0.615598
+vt 0.585045 0.632740
+vt 0.737701 0.133357
+vt 0.634697 0.348462
+vt 0.601779 0.538040
+vt 0.526630 0.813733
+vt 0.796084 0.107238
+vt 0.645335 0.301858
+vt 0.547763 0.770746
+vt 0.939113 0.130553
+vt 0.874144 0.100247
+vt 0.563421 0.466357
+vt 0.535483 0.710885
+vt 0.675759 0.211740
+vt 0.635028 0.130630
+vt 0.568439 0.417338
+vt 0.542491 0.662261
+vt 0.573887 0.368405
+vt 0.593757 0.585502
+vt 0.548386 0.613416
+vt 0.678869 0.090056
+vt 0.580129 0.319628
+vt 0.558567 0.515408
+vt 0.514254 0.806683
+vt 0.770350 0.063964
+vt 0.587726 0.271122
+vt 0.526572 0.759129
+vt 0.946160 0.115905
+vt 0.891158 0.070774
+vt 0.597669 0.223086
+vt 0.611929 0.175930
+vt 0.542415 0.105695
+vt 0.517489 0.404193
+vt 0.509500 0.653713
+vt 0.519234 0.354298
+vt 0.553639 0.564444
+vt 0.511281 0.603820
+vt 0.568145 0.057267
+vt 0.521272 0.304420
+vt 0.514398 0.504007
+vt 0.501048 0.803204
+vt 0.731552 0.019533
+vt 0.523822 0.254574
+vt 0.504734 0.753426
+vt 0.961397 0.104224
+vt 0.952242 0.049775
+vt 0.527296 0.204783
+vt 0.515908 0.454098
+vt 0.507397 0.703586
+vt 0.532618 0.155103
+vt 0.433350 0.107802
+vt 0.465794 0.405207
+vt 0.476377 0.654364
+vt 0.463490 0.355390
+vt 0.512882 0.553916
+vt 0.474014 0.604552
+vt 0.401564 0.060404
+vt 0.460802 0.305605
+vt 0.469884 0.504881
+vt 0.487599 0.803469
+vt 1.250123 0.025793
+vt 0.457447 0.255872
+vt 0.482704 0.753860
+vt 0.984179 0.097813
+vt 1.050589 0.051685
+vt 0.452893 0.206235
+vt 0.467885 0.455040
+vt 0.479169 0.704142
+vt 0.445957 0.156790
+vt 0.344888 0.135587
+vt 0.415075 0.420244
+vt 0.443428 0.664182
+vt 0.409171 0.371507
+vt 0.471892 0.554721
+vt 0.436960 0.615567
+vt 0.250123 0.025793
+vt 0.301452 0.095868
+vt 0.402437 0.322950
+vt 0.425822 0.517948
+vt 0.474473 0.807464
+vt 0.050589 0.051685
+vt 0.216363 0.070144
+vt 0.394284 0.274702
+vt 0.460930 0.760413
+vt 0.009882 0.098319
+vt 0.103671 0.074658
+vt 0.383698 0.226991
+vt 0.420529 0.469078
+vt 0.451128 0.712528
+vt 0.366529 0.447399
+vt 0.410880 0.682668
+vt 0.282623 0.176056
+vt 0.357975 0.400236
+vt 0.431207 0.566797
+vt 0.400372 0.636177
+vt 0.246351 0.139626
+vt 0.348554 0.353370
+vt 0.382765 0.542022
+vt 0.462264 0.814992
+vt 0.190972 0.113170
+vt 0.337646 0.307019
+vt 0.439868 0.772831
+vt 0.031785 0.105600
+vt 0.116896 0.104580
+vt 0.368695 0.180269
+vt 0.324298 0.261524
+vt 0.374667 0.494704
+vt 0.423573 0.728394
+vt 0.306908 0.217486
+vt 0.378757 0.708945
+vt 0.235147 0.220346
+vt 0.310089 0.437969
+vt 0.391191 0.589229
+vt 0.364196 0.665186
+vt 0.206276 0.183767
+vt 0.299275 0.392521
+vt 0.340709 0.575117
+vt 0.451650 0.825666
+vt 0.166434 0.153882
+vt 0.287319 0.347580
+vt 0.420019 0.790647
+vt 0.045835 0.117837
+vt 0.113250 0.135252
+vt 0.273481 0.303490
+vt 0.330370 0.529459
+vt 0.396715 0.751114
+vt 0.256661 0.260781
+vt 0.320304 0.483681
+vt 0.171741 0.225499
+vt 0.253261 0.436134
+vt 0.351788 0.620448
+vt 0.312374 0.658470
+vt 0.141659 0.191322
+vt 0.241339 0.391714
+vt 0.402011 0.813201
+vt 0.101814 0.164257
+vt 0.228181 0.347914
+vt 0.286910 0.570443
+vt 0.370653 0.779864
+vt 0.051826 0.132727
+vt 0.051336 0.148303
+vt 0.213019 0.305137
+vt 0.275586 0.525726
+vt 0.346765 0.741874
+vt 0.194750 0.264000
+vt 0.264518 0.480883
+vt 0.327915 0.701050
+vt 0.139253 0.263111
+vt 0.208699 0.480406
+vt 0.271631 0.701027
+vt 0.116262 0.224508
+vt 0.197515 0.435582
+vt 0.298975 0.614792
+vt 0.443465 0.838899
+vt 0.386800 0.839664
+vt 0.086129 0.190228
+vt 0.185664 0.391086
+vt 0.242904 0.614557
+vt 0.345357 0.813708
+vt 0.046099 0.163088
+vt 0.172573 0.347205
+vt 0.230930 0.570121
+vt 0.314109 0.780154
+vt 0.157473 0.304343
+vt 0.219689 0.525325
+vt 0.290354 0.741990
+vt 0.107276 0.295180
+vt 0.163845 0.521847
+vt 0.227302 0.745462
+vt 0.090087 0.252465
+vt 0.154008 0.475873
+vt 0.256203 0.658333
+vt 0.438803 0.853868
+vt 0.376175 0.868999
+vt 0.067816 0.212161
+vt 0.143951 0.429993
+vt 0.196450 0.658436
+vt 0.320646 0.851667
+vt 0.037514 0.176004
+vt 0.133223 0.384401
+vt 0.184564 0.613347
+vt 0.279077 0.822340
+vt 0.121259 0.339345
+vt 0.173881 0.567736
+vt 0.249262 0.785918
+vt 0.175759 0.788260
+vt 0.063122 0.274267
+vt 0.109517 0.509514
+vt 0.210309 0.702657
+vt 0.439113 0.869447
+vt 0.374144 0.899752
+vt 0.047763 0.229253
+vt 0.101779 0.461959
+vt 0.145335 0.698142
+vt 0.296085 0.892762
+vt 0.026630 0.186267
+vt 0.093757 0.414497
+vt 0.134697 0.651538
+vt 0.237701 0.866643
+vt 0.085045 0.367260
+vt 0.125569 0.604436
+vt 0.200288 0.830006
+vt 0.075103 0.320425
+vt 0.117324 0.557046
+vt 0.995265 0.147143
+vt 1.014254 0.193317
+vt 0.495265 0.852856
+vt 1.009882 0.098319
+vt 1.031785 0.105600
+vt 1.045835 0.117837
+vt 1.051826 0.132727
+vt 1.051336 0.148303
+vt 1.046099 0.163088
+vt 1.037514 0.176004
+vt 0.446160 0.884095
+vt 1.026630 0.186267
+vt 0.158464 0.743915
+vn -0.460158 -0.887814 0.000000
+vn -0.592853 -0.805292 0.000000
+vn -0.437635 -0.887814 -0.142186
+vn -0.951781 -0.306742 0.000000
+vn -0.987854 -0.155217 0.000000
+vn -0.905179 -0.306742 -0.294107
+vn -0.892575 0.450850 0.000000
+vn -0.811670 0.584063 0.000000
+vn -0.848903 0.450850 -0.275826
+vn -0.316050 0.948729 0.000000
+vn -0.164068 0.986419 0.000000
+vn -0.300577 0.948729 -0.097659
+vn -0.316050 -0.948729 0.000000
+vn -0.300577 -0.948729 -0.097659
+vn -0.892575 -0.450850 0.000000
+vn -0.848903 -0.450850 -0.275826
+vn -0.951781 0.306742 0.000000
+vn -0.905179 0.306742 -0.294107
+vn -0.460158 0.887814 0.000000
+vn -0.437635 0.887814 -0.142186
+vn -0.164068 -0.986419 0.000000
+vn -0.156011 -0.986419 -0.050691
+vn -0.811670 -0.584063 0.000000
+vn -0.771935 -0.584063 -0.250801
+vn -0.987854 0.155217 0.000000
+vn -0.939512 0.155217 -0.305246
+vn -0.592853 0.805292 0.000000
+vn -0.563829 0.805292 -0.183203
+vn -0.710959 -0.703207 0.000000
+vn -0.676168 -0.703207 -0.219703
+vn -1.000000 0.000000 0.000000
+vn -0.951048 0.000000 -0.309000
+vn -0.710959 0.703207 0.000000
+vn -0.676168 0.703207 -0.219703
+vn -0.563829 -0.805292 -0.183203
+vn -0.939512 -0.155217 -0.305246
+vn -0.771935 0.584063 -0.250801
+vn -0.156011 0.986419 -0.050691
+vn -0.255684 0.948729 -0.185766
+vn -0.255684 -0.948729 -0.185766
+vn -0.722098 -0.450850 -0.524644
+vn -0.770012 0.306742 -0.559435
+vn -0.372265 0.887814 -0.270455
+vn -0.132725 -0.986419 -0.096408
+vn -0.656667 -0.584063 -0.477096
+vn -0.799188 0.155217 -0.580645
+vn -0.479629 0.805292 -0.348460
+vn -0.575182 -0.703207 -0.417890
+vn -0.809015 0.000000 -0.587756
+vn -0.575182 0.703207 -0.417890
+vn -0.479629 -0.805292 -0.348460
+vn -0.799188 -0.155217 -0.580645
+vn -0.656667 0.584063 -0.477096
+vn -0.372265 -0.887814 -0.270455
+vn -0.770012 -0.306742 -0.559435
+vn -0.722098 0.450850 -0.524644
+vn -0.185766 -0.948729 -0.255684
+vn -0.524644 -0.450850 -0.722098
+vn -0.559435 0.306742 -0.770012
+vn -0.270455 0.887814 -0.372265
+vn -0.096408 -0.986419 -0.132725
+vn -0.477096 -0.584063 -0.656667
+vn -0.580645 0.155217 -0.799188
+vn -0.348460 0.805292 -0.479629
+vn -0.417890 -0.703207 -0.575182
+vn -0.587756 0.000000 -0.809015
+vn -0.417890 0.703207 -0.575182
+vn -0.348460 -0.805292 -0.479629
+vn -0.580645 -0.155217 -0.799188
+vn -0.477096 0.584063 -0.656667
+vn -0.270455 -0.887814 -0.372265
+vn -0.559435 -0.306742 -0.770012
+vn -0.524644 0.450850 -0.722098
+vn -0.132725 0.986419 -0.096408
+vn -0.185766 0.948729 -0.255684
+vn -0.097659 -0.948729 -0.300577
+vn -0.275826 -0.450850 -0.848903
+vn -0.294107 0.306742 -0.905179
+vn -0.142186 0.887814 -0.437635
+vn -0.050691 -0.986419 -0.156011
+vn -0.250801 -0.584063 -0.771935
+vn -0.305246 0.155217 -0.939512
+vn -0.183203 0.805292 -0.563829
+vn -0.219703 -0.703207 -0.676168
+vn -0.309000 0.000000 -0.951048
+vn -0.219703 0.703207 -0.676168
+vn -0.183203 -0.805292 -0.563829
+vn -0.305246 -0.155217 -0.939512
+vn -0.250801 0.584063 -0.771935
+vn -0.142186 -0.887814 -0.437635
+vn -0.294107 -0.306742 -0.905179
+vn -0.275826 0.450850 -0.848903
+vn -0.096408 0.986419 -0.132725
+vn -0.097659 0.948729 -0.300577
+vn 0.000000 -0.450850 -0.892575
+vn 0.000000 0.306742 -0.951781
+vn 0.000000 0.887814 -0.460158
+vn 0.000000 -0.986419 -0.164068
+vn 0.000000 -0.584063 -0.811670
+vn 0.000000 0.155217 -0.987854
+vn 0.000000 0.805292 -0.592853
+vn 0.000000 -0.703207 -0.710959
+vn 0.000000 0.000000 -1.000000
+vn 0.000000 0.703207 -0.710959
+vn 0.000000 -0.805292 -0.592853
+vn 0.000000 -0.155217 -0.987854
+vn 0.000000 0.584063 -0.811670
+vn 0.000000 -0.887814 -0.460158
+vn 0.000000 -0.306742 -0.951781
+vn 0.000000 0.450850 -0.892575
+vn -0.050691 0.986419 -0.156011
+vn 0.000000 0.948729 -0.316050
+vn 0.000000 -0.948729 -0.316050
+vn 0.097659 0.948729 -0.300577
+vn 0.097659 -0.948729 -0.300577
+vn 0.275826 -0.450850 -0.848903
+vn 0.294107 0.306742 -0.905179
+vn 0.142186 0.887814 -0.437635
+vn 0.250801 -0.584063 -0.771935
+vn 0.305246 0.155217 -0.939512
+vn 0.183203 0.805292 -0.563829
+vn 0.219703 -0.703207 -0.676168
+vn 0.309000 0.000000 -0.951048
+vn 0.219703 0.703207 -0.676168
+vn 0.183203 -0.805292 -0.563829
+vn 0.305246 -0.155217 -0.939512
+vn 0.250801 0.584063 -0.771935
+vn 0.000000 0.986419 -0.164068
+vn 0.050691 0.986419 -0.156011
+vn 0.142186 -0.887814 -0.437635
+vn 0.294107 -0.306742 -0.905179
+vn 0.275826 0.450850 -0.848903
+vn 0.050691 -0.986419 -0.156011
+vn 0.185766 -0.948729 -0.255684
+vn 0.524644 -0.450850 -0.722098
+vn 0.559435 0.306742 -0.770012
+vn 0.270455 0.887814 -0.372265
+vn 0.477096 -0.584063 -0.656667
+vn 0.580645 0.155217 -0.799188
+vn 0.348460 0.805292 -0.479629
+vn 0.417890 -0.703207 -0.575182
+vn 0.587756 0.000000 -0.809015
+vn 0.417890 0.703207 -0.575182
+vn 0.348460 -0.805292 -0.479629
+vn 0.580645 -0.155217 -0.799188
+vn 0.477096 0.584063 -0.656667
+vn 0.096408 0.986419 -0.132725
+vn 0.270455 -0.887814 -0.372265
+vn 0.559435 -0.306742 -0.770012
+vn 0.524644 0.450850 -0.722098
+vn 0.185766 0.948729 -0.255684
+vn 0.096408 -0.986419 -0.132725
+vn 0.255684 -0.948729 -0.185766
+vn 0.722098 -0.450850 -0.524644
+vn 0.770012 0.306742 -0.559435
+vn 0.372265 0.887814 -0.270455
+vn 0.656667 -0.584063 -0.477096
+vn 0.799188 0.155217 -0.580645
+vn 0.479629 0.805292 -0.348460
+vn 0.575182 -0.703207 -0.417890
+vn 0.809015 0.000000 -0.587756
+vn 0.575182 0.703207 -0.417890
+vn 0.479629 -0.805292 -0.348460
+vn 0.799188 -0.155217 -0.580645
+vn 0.722098 0.450850 -0.524644
+vn 0.132725 0.986419 -0.096408
+vn 0.372265 -0.887814 -0.270455
+vn 0.770012 -0.306742 -0.559435
+vn 0.255684 0.948729 -0.185766
+vn 0.848903 -0.450850 -0.275826
+vn 0.905179 0.306742 -0.294107
+vn 0.437635 0.887814 -0.142186
+vn 0.676168 -0.703207 -0.219703
+vn 0.939512 0.155217 -0.305246
+vn 0.563829 0.805292 -0.183203
+vn 0.951048 0.000000 -0.309000
+vn 0.656667 0.584063 -0.477096
+vn 0.676168 0.703207 -0.219703
+vn 0.563829 -0.805292 -0.183203
+vn 0.939512 -0.155217 -0.305246
+vn 0.848903 0.450850 -0.275826
+vn 0.156011 0.986419 -0.050691
+vn 0.437635 -0.887814 -0.142186
+vn 0.905179 -0.306742 -0.294107
+vn 0.300577 0.948729 -0.097659
+vn 0.132725 -0.986419 -0.096408
+vn 0.300577 -0.948729 -0.097659
+vn 0.951781 0.306742 0.000000
+vn 0.460158 0.887814 0.000000
+vn 0.771935 -0.584063 -0.250801
+vn 0.710959 -0.703207 0.000000
+vn 0.987854 0.155217 0.000000
+vn 0.592853 0.805292 0.000000
+vn 1.000000 0.000000 0.000000
+vn 0.771935 0.584063 -0.250801
+vn 0.710959 0.703207 0.000000
+vn 0.592853 -0.805292 0.000000
+vn 0.987854 -0.155217 0.000000
+vn 0.892575 0.450850 0.000000
+vn 0.164068 0.986419 0.000000
+vn 0.460158 -0.887814 0.000000
+vn 0.951781 -0.306742 0.000000
+vn 0.316050 0.948729 0.000000
+vn 0.156011 -0.986419 -0.050691
+vn 0.316050 -0.948729 0.000000
+vn 0.892575 -0.450850 0.000000
+vn 0.811670 -0.584063 0.000000
+vn 0.676168 -0.703207 0.219703
+vn 0.939512 0.155217 0.305246
+vn 0.563829 0.805292 0.183203
+vn 0.951048 0.000000 0.309000
+vn 0.811670 0.584063 0.000000
+vn 0.676168 0.703207 0.219703
+vn 0.563829 -0.805292 0.183203
+vn 0.939512 -0.155217 0.305246
+vn 0.848903 0.450850 0.275826
+vn 0.156011 0.986419 0.050691
+vn 0.437635 -0.887814 0.142186
+vn 0.905179 -0.306742 0.294107
+vn 0.300577 0.948729 0.097659
+vn 0.164068 -0.986419 0.000000
+vn 0.300577 -0.948729 0.097659
+vn 0.848903 -0.450850 0.275826
+vn 0.905179 0.306742 0.294107
+vn 0.437635 0.887814 0.142186
+vn 0.771935 -0.584063 0.250801
+vn 0.575182 -0.703207 0.417890
+vn 0.799188 0.155217 0.580645
+vn 0.479629 0.805292 0.348460
+vn 0.809015 0.000000 0.587756
+vn 0.771935 0.584063 0.250801
+vn 0.575182 0.703207 0.417890
+vn 0.479629 -0.805292 0.348460
+vn 0.799188 -0.155217 0.580645
+vn 0.722098 0.450850 0.524644
+vn 0.132725 0.986419 0.096408
+vn 0.372265 -0.887814 0.270455
+vn 0.770012 -0.306742 0.559435
+vn 0.255684 0.948729 0.185766
+vn 0.156011 -0.986419 0.050691
+vn 0.255684 -0.948729 0.185766
+vn 0.722098 -0.450850 0.524644
+vn 0.770012 0.306742 0.559435
+vn 0.372265 0.887814 0.270455
+vn 0.656667 -0.584063 0.477096
+vn 0.417890 -0.703207 0.575182
+vn 0.580645 0.155217 0.799188
+vn 0.348460 0.805292 0.479629
+vn 0.587756 0.000000 0.809015
+vn 0.656667 0.584063 0.477096
+vn 0.417890 0.703207 0.575182
+vn 0.348460 -0.805292 0.479629
+vn 0.580645 -0.155217 0.799188
+vn 0.524644 0.450850 0.722098
+vn 0.096408 0.986419 0.132725
+vn 0.270455 -0.887814 0.372265
+vn 0.559435 -0.306742 0.770012
+vn 0.185766 0.948729 0.255684
+vn 0.132725 -0.986419 0.096408
+vn 0.185766 -0.948729 0.255684
+vn 0.524644 -0.450850 0.722098
+vn 0.559435 0.306742 0.770012
+vn 0.270455 0.887814 0.372265
+vn 0.305246 0.155217 0.939512
+vn 0.183203 0.805292 0.563829
+vn 0.219703 -0.703207 0.676168
+vn 0.309000 0.000000 0.951048
+vn 0.477096 0.584063 0.656667
+vn 0.219703 0.703207 0.676168
+vn 0.183203 -0.805292 0.563829
+vn 0.305246 -0.155217 0.939512
+vn 0.275826 0.450850 0.848903
+vn 0.050691 0.986419 0.156011
+vn 0.142186 -0.887814 0.437635
+vn 0.294107 -0.306742 0.905179
+vn 0.097659 0.948729 0.300577
+vn 0.096408 -0.986419 0.132725
+vn 0.097659 -0.948729 0.300577
+vn 0.477096 -0.584063 0.656667
+vn 0.275826 -0.450850 0.848903
+vn 0.294107 0.306742 0.905179
+vn 0.142186 0.887814 0.437635
+vn 0.250801 -0.584063 0.771935
+vn 0.000000 0.805292 0.592853
+vn 0.000000 -0.703207 0.710959
+vn 0.000000 0.000000 1.000000
+vn 0.250801 0.584063 0.771935
+vn 0.000000 0.703207 0.710959
+vn 0.000000 -0.805292 0.592853
+vn 0.000000 -0.155217 0.987854
+vn 0.000000 0.450850 0.892575
+vn 0.000000 0.986419 0.164068
+vn 0.000000 -0.887814 0.460158
+vn 0.000000 -0.306742 0.951781
+vn 0.000000 0.948729 0.316050
+vn 0.050691 -0.986419 0.156011
+vn 0.000000 -0.948729 0.316050
+vn 0.000000 -0.450850 0.892575
+vn 0.000000 0.306742 0.951781
+vn 0.000000 0.887814 0.460158
+vn 0.000000 -0.584063 0.811670
+vn 0.000000 0.155217 0.987854
+vn -0.183203 -0.805292 0.563829
+vn -0.305246 -0.155217 0.939512
+vn 0.000000 0.584063 0.811670
+vn -0.250801 0.584063 0.771935
+vn -0.142186 -0.887814 0.437635
+vn -0.294107 -0.306742 0.905179
+vn -0.097659 0.948729 0.300577
+vn -0.097659 -0.948729 0.300577
+vn -0.275826 -0.450850 0.848903
+vn -0.294107 0.306742 0.905179
+vn -0.142186 0.887814 0.437635
+vn 0.000000 -0.986419 0.164068
+vn -0.050691 -0.986419 0.156011
+vn -0.250801 -0.584063 0.771935
+vn -0.305246 0.155217 0.939512
+vn -0.183203 0.805292 0.563829
+vn -0.219703 -0.703207 0.676168
+vn -0.309000 0.000000 0.951048
+vn -0.219703 0.703207 0.676168
+vn -0.348460 -0.805292 0.479629
+vn -0.580645 -0.155217 0.799188
+vn -0.477096 0.584063 0.656667
+vn -0.270455 -0.887814 0.372265
+vn -0.559435 -0.306742 0.770012
+vn -0.275826 0.450850 0.848903
+vn -0.050691 0.986419 0.156011
+vn -0.185766 0.948729 0.255684
+vn -0.185766 -0.948729 0.255684
+vn -0.524644 -0.450850 0.722098
+vn -0.559435 0.306742 0.770012
+vn -0.270455 0.887814 0.372265
+vn -0.096408 -0.986419 0.132725
+vn -0.477096 -0.584063 0.656667
+vn -0.580645 0.155217 0.799188
+vn -0.348460 0.805292 0.479629
+vn -0.417890 -0.703207 0.575182
+vn -0.587756 0.000000 0.809015
+vn -0.417890 0.703207 0.575182
+vn -0.479629 -0.805292 0.348460
+vn -0.799188 -0.155217 0.580645
+vn -0.656667 0.584063 0.477096
+vn -0.372265 -0.887814 0.270455
+vn -0.770012 -0.306742 0.559435
+vn -0.524644 0.450850 0.722098
+vn -0.096408 0.986419 0.132725
+vn -0.255684 0.948729 0.185766
+vn -0.255684 -0.948729 0.185766
+vn -0.722098 -0.450850 0.524644
+vn -0.770012 0.306742 0.559435
+vn -0.372265 0.887814 0.270455
+vn -0.132725 -0.986419 0.096408
+vn -0.656667 -0.584063 0.477096
+vn -0.799188 0.155217 0.580645
+vn -0.479629 0.805292 0.348460
+vn -0.575182 -0.703207 0.417890
+vn -0.809015 0.000000 0.587756
+vn -0.575182 0.703207 0.417890
+vn -0.771935 0.584063 0.250801
+vn -0.437635 -0.887814 0.142186
+vn -0.905179 -0.306742 0.294107
+vn -0.722098 0.450850 0.524644
+vn -0.132725 0.986419 0.096408
+vn -0.300577 0.948729 0.097659
+vn -0.300577 -0.948729 0.097659
+vn -0.848903 -0.450850 0.275826
+vn -0.905179 0.306742 0.294107
+vn -0.437635 0.887814 0.142186
+vn -0.156011 -0.986419 0.050691
+vn -0.771935 -0.584063 0.250801
+vn -0.939512 0.155217 0.305246
+vn -0.563829 0.805292 0.183203
+vn -0.676168 -0.703207 0.219703
+vn -0.951048 0.000000 0.309000
+vn -0.676168 0.703207 0.219703
+vn -0.563829 -0.805292 0.183203
+vn -0.939512 -0.155217 0.305246
+vn 0.000000 -0.999969 0.000000
+vn 0.000000 1.000000 0.000000
+vn -0.156011 0.986419 0.050691
+vn -0.848903 0.450850 0.275826
+s 1
+f 17/1/1 16/2/2 36/3/3
+f 12/4/4 11/5/5 31/6/6
+f 7/7/7 6/8/8 26/9/9
+f 2/10/10 1/11/11 21/12/12
+f 18/13/13 17/1/1 37/14/14
+f 13/15/15 12/4/4 32/16/16
+f 8/17/17 7/7/7 27/18/18
+f 3/19/19 2/10/10 22/20/20
+f 19/21/21 18/13/13 38/22/22
+f 14/23/23 13/15/15 33/24/24
+f 9/25/25 8/17/17 28/26/26
+f 4/27/27 3/19/19 23/28/28
+f 15/29/29 14/23/23 34/30/30
+f 10/31/31 9/25/25 29/32/32
+f 5/33/33 4/27/27 24/34/34
+f 16/2/2 15/29/29 35/35/35
+f 11/5/5 10/31/31 30/36/36
+f 6/8/8 5/33/33 25/37/37
+f 21/12/12 20/38/38 41/39/39
+f 37/40/14 36/41/3 57/42/40
+f 32/43/16 31/44/6 52/45/41
+f 27/46/18 26/47/9 47/48/42
+f 22/49/20 21/12/12 42/50/43
+f 38/51/22 37/40/14 58/52/44
+f 33/53/24 32/43/16 53/54/45
+f 28/55/26 27/46/18 48/56/46
+f 23/57/28 22/49/20 43/58/47
+f 34/59/30 33/53/24 54/60/48
+f 29/61/32 28/55/26 49/62/49
+f 24/63/34 23/57/28 44/64/50
+f 35/65/35 34/59/30 55/66/51
+f 30/67/36 29/61/32 50/68/52
+f 25/69/37 24/63/34 45/70/53
+f 36/41/3 35/65/35 56/71/54
+f 31/44/6 30/67/36 51/72/55
+f 26/47/9 25/69/37 46/73/56
+f 57/42/40 56/71/54 76/74/57
+f 52/45/41 51/72/55 71/75/58
+f 47/48/42 46/73/56 66/76/59
+f 42/50/43 41/39/39 61/77/60
+f 58/52/44 57/42/40 77/78/61
+f 53/54/45 52/45/41 72/79/62
+f 48/56/46 47/48/42 67/80/63
+f 43/58/47 42/50/43 62/81/64
+f 54/60/48 53/54/45 73/82/65
+f 49/62/49 48/56/46 68/83/66
+f 44/64/50 43/58/47 63/84/67
+f 55/66/51 54/60/48 74/85/68
+f 50/68/52 49/62/49 69/86/69
+f 45/70/53 44/64/50 64/87/70
+f 56/71/54 55/66/51 75/88/71
+f 51/72/55 50/68/52 70/89/72
+f 46/73/56 45/70/53 65/90/73
+f 41/39/39 40/91/74 60/92/75
+f 76/74/57 75/88/71 95/93/76
+f 71/75/58 70/89/72 90/94/77
+f 66/76/59 65/90/73 85/95/78
+f 61/77/60 60/92/75 80/96/79
+f 77/78/61 76/74/57 96/97/80
+f 72/79/62 71/75/58 91/98/81
+f 67/80/63 66/76/59 86/99/82
+f 62/81/64 61/77/60 81/100/83
+f 73/82/65 72/79/62 92/101/84
+f 68/83/66 67/80/63 87/102/85
+f 63/84/67 62/81/64 82/103/86
+f 74/85/68 73/82/65 93/104/87
+f 69/86/69 68/83/66 88/105/88
+f 64/87/70 63/84/67 83/106/89
+f 75/88/71 74/85/68 94/107/90
+f 70/89/72 69/86/69 89/108/91
+f 65/90/73 64/87/70 84/109/92
+f 60/92/75 59/110/93 79/111/94
+f 90/94/77 89/108/91 109/112/95
+f 85/95/78 84/109/92 104/113/96
+f 80/96/79 79/111/94 99/114/97
+f 96/97/80 95/93/76 115/115/98
+f 91/98/81 90/94/77 110/116/99
+f 86/99/82 85/95/78 105/117/100
+f 81/100/83 80/96/79 100/118/101
+f 92/101/84 91/98/81 111/119/102
+f 87/102/85 86/99/82 106/120/103
+f 82/103/86 81/100/83 101/121/104
+f 93/104/87 92/101/84 112/122/105
+f 88/105/88 87/102/85 107/123/106
+f 83/106/89 82/103/86 102/124/107
+f 94/107/90 93/104/87 113/125/108
+f 89/108/91 88/105/88 108/126/109
+f 84/109/92 83/106/89 103/127/110
+f 79/111/94 78/128/111 98/129/112
+f 95/93/76 94/107/90 114/130/113
+f 99/114/97 98/129/112 117/131/114
+f 115/115/98 114/130/113 133/132/115
+f 110/116/99 109/112/95 128/133/116
+f 105/117/100 104/113/96 123/134/117
+f 100/118/101 99/114/97 118/135/118
+f 111/119/102 110/116/99 129/136/119
+f 106/120/103 105/117/100 124/137/120
+f 101/121/104 100/118/101 119/138/121
+f 112/122/105 111/119/102 130/139/122
+f 107/123/106 106/120/103 125/140/123
+f 102/124/107 101/121/104 120/141/124
+f 113/125/108 112/122/105 131/142/125
+f 108/126/109 107/123/106 126/143/126
+f 103/127/110 102/124/107 121/144/127
+f 98/129/112 97/145/128 116/146/129
+f 114/130/113 113/125/108 132/147/130
+f 109/112/95 108/126/109 127/148/131
+f 104/113/96 103/127/110 122/149/132
+f 134/150/133 133/132/115 152/151/134
+f 129/136/119 128/133/116 147/152/135
+f 124/137/120 123/134/117 142/153/136
+f 119/138/121 118/135/118 137/154/137
+f 130/139/122 129/136/119 148/155/138
+f 125/140/123 124/137/120 143/156/139
+f 120/141/124 119/138/121 138/157/140
+f 131/142/125 130/139/122 149/158/141
+f 126/143/126 125/140/123 144/159/142
+f 121/144/127 120/141/124 139/160/143
+f 132/147/130 131/142/125 150/161/144
+f 127/148/131 126/143/126 145/162/145
+f 122/149/132 121/144/127 140/163/146
+f 117/131/114 116/146/129 135/164/147
+f 133/132/115 132/147/130 151/165/148
+f 128/133/116 127/148/131 146/166/149
+f 123/134/117 122/149/132 141/167/150
+f 118/135/118 117/131/114 136/168/151
+f 153/169/152 152/151/134 171/170/153
+f 148/155/138 147/152/135 166/171/154
+f 143/156/139 142/153/136 161/172/155
+f 138/157/140 137/154/137 156/173/156
+f 149/158/141 148/155/138 167/174/157
+f 144/159/142 143/156/139 162/175/158
+f 139/160/143 138/157/140 157/176/159
+f 150/161/144 149/158/141 168/177/160
+f 145/162/145 144/159/142 163/178/161
+f 140/163/146 139/160/143 158/179/162
+f 151/165/148 150/161/144 169/180/163
+f 146/166/149 145/162/145 164/181/164
+f 141/167/150 140/163/146 160/182/165
+f 136/168/151 135/164/147 154/183/166
+f 152/151/134 151/165/148 170/184/167
+f 147/152/135 146/166/149 165/185/168
+f 142/153/136 141/167/150 160/182/165
+f 137/154/137 136/168/151 155/186/169
+f 167/174/157 166/171/154 185/187/170
+f 162/175/158 161/172/155 180/188/171
+f 157/176/159 156/173/156 175/189/172
+f 168/177/160 167/174/157 187/190/173
+f 163/178/161 162/175/158 181/191/174
+f 158/179/162 157/176/159 176/192/175
+f 169/180/163 168/177/160 187/190/173
+f 164/181/164 163/178/161 182/193/176
+f 159/194/177 158/179/162 177/195/178
+f 170/184/167 169/180/163 188/196/179
+f 165/185/168 164/181/164 183/197/180
+f 160/182/165 159/194/177 179/198/181
+f 155/186/169 154/183/166 173/199/182
+f 171/170/153 170/184/167 189/200/183
+f 166/171/154 165/185/168 184/201/184
+f 161/172/155 160/182/165 179/198/181
+f 156/173/156 155/186/169 174/202/185
+f 172/203/186 171/170/153 190/204/187
+f 181/191/174 180/188/171 199/205/188
+f 176/192/175 175/189/172 194/206/189
+f 187/190/173 186/207/190 206/208/191
+f 182/193/176 181/191/174 200/209/192
+f 177/195/178 176/192/175 195/210/193
+f 188/196/179 187/190/173 206/208/191
+f 183/197/180 182/193/176 201/211/194
+f 178/212/195 177/195/178 196/213/196
+f 189/200/183 188/196/179 207/214/197
+f 184/201/184 183/197/180 202/215/198
+f 179/198/181 178/212/195 198/216/199
+f 174/202/185 173/199/182 192/217/200
+f 190/204/187 189/200/183 208/218/201
+f 185/187/170 184/201/184 203/219/202
+f 180/188/171 179/198/181 198/216/199
+f 175/189/172 174/202/185 193/220/203
+f 191/221/204 190/204/187 209/222/205
+f 186/207/190 185/187/170 204/223/206
+f 206/208/191 205/224/207 225/225/208
+f 201/211/194 200/209/192 219/226/209
+f 196/213/196 195/210/193 214/227/210
+f 207/214/197 206/208/191 225/225/208
+f 202/215/198 201/211/194 220/228/211
+f 197/229/212 196/213/196 215/230/213
+f 208/218/201 207/214/197 226/231/214
+f 203/219/202 202/215/198 221/232/215
+f 198/216/199 197/229/212 217/233/216
+f 193/220/203 192/217/200 211/234/217
+f 209/222/205 208/218/201 227/235/218
+f 204/223/206 203/219/202 222/236/219
+f 199/205/188 198/216/199 217/233/216
+f 194/206/189 193/220/203 212/237/220
+f 210/238/221 209/222/205 228/239/222
+f 205/224/207 204/223/206 223/240/223
+f 200/209/192 199/205/188 218/241/224
+f 195/210/193 194/206/189 213/242/225
+f 225/225/208 224/243/226 244/244/227
+f 220/228/211 219/226/209 238/245/228
+f 215/230/213 214/227/210 233/246/229
+f 226/231/214 225/225/208 244/244/227
+f 221/232/215 220/228/211 239/247/230
+f 216/248/231 215/230/213 234/249/232
+f 227/235/218 226/231/214 245/250/233
+f 222/236/219 221/232/215 240/251/234
+f 217/233/216 216/248/231 236/252/235
+f 212/237/220 211/234/217 230/253/236
+f 228/239/222 227/235/218 246/254/237
+f 223/240/223 222/236/219 241/255/238
+f 218/241/224 217/233/216 236/252/235
+f 213/242/225 212/237/220 231/256/239
+f 229/257/240 228/239/222 247/258/241
+f 224/243/226 223/240/223 242/259/242
+f 219/226/209 218/241/224 237/260/243
+f 214/227/210 213/242/225 232/261/244
+f 244/244/227 243/262/245 263/263/246
+f 239/247/230 238/245/228 257/264/247
+f 234/249/232 233/246/229 252/265/248
+f 245/250/233 244/244/227 263/263/246
+f 240/251/234 239/247/230 258/266/249
+f 235/267/250 234/249/232 253/268/251
+f 246/269/237 245/250/233 264/270/252
+f 241/255/238 240/251/234 259/271/253
+f 236/252/235 235/267/250 255/272/254
+f 231/256/239 230/253/236 249/273/255
+f 247/274/241 246/269/237 265/275/256
+f 242/259/242 241/255/238 260/276/257
+f 237/260/243 236/252/235 255/272/254
+f 232/261/244 231/256/239 250/277/258
+f 248/278/259 247/274/241 266/279/260
+f 243/262/245 242/259/242 261/280/261
+f 238/245/228 237/260/243 256/281/262
+f 233/246/229 232/261/244 251/282/263
+f 258/266/249 257/264/247 276/283/264
+f 253/268/251 252/265/248 271/284/265
+f 264/270/252 263/263/246 282/285/266
+f 259/271/253 258/266/249 277/286/267
+f 254/287/268 253/268/251 272/288/269
+f 265/275/256 264/270/252 283/289/270
+f 260/276/257 259/271/253 278/290/271
+f 255/272/254 254/287/268 274/291/272
+f 250/277/258 249/273/255 268/292/273
+f 266/279/260 265/275/256 284/293/274
+f 261/280/261 260/276/257 279/294/275
+f 256/281/262 255/272/254 274/291/272
+f 251/282/263 250/277/258 269/295/276
+f 267/296/277 266/279/260 285/297/278
+f 262/298/279 261/280/261 280/299/280
+f 257/264/247 256/281/262 275/300/281
+f 252/265/248 251/282/263 270/301/282
+f 263/263/246 262/298/279 281/302/283
+f 272/288/269 271/284/265 290/303/284
+f 283/289/270 282/285/266 301/304/285
+f 278/290/271 277/286/267 296/305/286
+f 273/306/287 272/288/269 291/307/288
+f 284/293/274 283/289/270 302/308/289
+f 279/294/275 278/290/271 297/309/290
+f 274/291/272 273/306/287 293/310/291
+f 269/295/276 268/292/273 287/311/292
+f 285/297/278 284/293/274 303/312/293
+f 280/299/280 279/294/275 298/313/294
+f 275/300/281 274/291/272 293/310/291
+f 270/301/282 269/295/276 288/314/295
+f 286/315/296 285/297/278 304/316/297
+f 281/302/283 280/299/280 299/317/298
+f 276/283/264 275/300/281 294/318/299
+f 271/284/265 270/301/282 289/319/300
+f 282/285/266 281/302/283 300/320/301
+f 277/286/267 276/283/264 295/321/302
+f 302/308/289 301/304/285 321/322/303
+f 297/309/290 296/305/286 316/323/304
+f 292/324/305 291/307/288 311/325/306
+f 303/312/293 302/308/289 322/326/307
+f 298/313/294 297/309/290 317/327/308
+f 293/310/291 292/324/305 311/325/306
+f 288/314/295 287/311/292 307/328/309
+f 304/316/297 303/312/293 323/329/310
+f 299/317/298 298/313/294 318/330/311
+f 294/318/299 293/310/291 313/331/312
+f 289/319/300 288/314/295 308/332/313
+f 305/333/314 304/316/297 324/334/315
+f 300/320/301 299/317/298 319/335/316
+f 295/321/302 294/318/299 314/336/317
+f 290/303/284 289/319/300 309/337/318
+f 301/304/285 300/320/301 320/338/319
+f 296/305/286 295/321/302 315/339/320
+f 291/307/288 290/303/284 310/340/321
+f 321/322/303 320/338/319 340/341/322
+f 316/323/304 315/339/320 335/342/323
+f 311/325/306 310/340/321 330/343/324
+f 322/326/307 321/322/303 341/344/325
+f 317/327/308 316/323/304 336/345/326
+f 312/346/327 311/325/306 330/343/324
+f 307/328/309 306/347/328 326/348/329
+f 323/329/310 322/326/307 342/349/330
+f 318/330/311 317/327/308 337/350/331
+f 313/331/312 312/346/327 332/351/332
+f 308/332/313 307/328/309 327/352/333
+f 324/334/315 323/329/310 343/353/334
+f 319/335/316 318/330/311 338/354/335
+f 314/336/317 313/331/312 333/355/336
+f 309/337/318 308/332/313 328/356/337
+f 320/338/319 319/335/316 339/357/338
+f 315/339/320 314/336/317 334/358/339
+f 310/340/321 309/337/318 329/359/340
+f 340/341/322 339/357/338 360/360/341
+f 335/342/323 334/358/339 355/361/342
+f 330/343/324 329/359/340 350/362/343
+f 341/344/325 340/341/322 361/363/344
+f 336/345/326 335/342/323 356/364/345
+f 331/365/346 330/343/324 350/362/343
+f 326/348/329 325/366/347 346/367/348
+f 342/349/330 341/344/325 362/368/349
+f 337/350/331 336/345/326 357/369/350
+f 332/351/332 331/365/346 352/370/351
+f 327/352/333 326/348/329 347/371/352
+f 343/353/334 342/349/330 363/372/353
+f 338/354/335 337/350/331 358/373/354
+f 333/355/336 332/351/332 353/374/355
+f 328/356/337 327/352/333 348/375/356
+f 339/357/338 338/354/335 359/376/357
+f 334/358/339 333/355/336 354/377/358
+f 329/359/340 328/356/337 349/378/359
+f 350/362/343 349/378/359 369/379/360
+f 361/363/344 360/360/341 380/380/361
+f 356/364/345 355/361/342 375/381/362
+f 351/382/363 350/362/343 369/379/360
+f 346/367/348 345/383/364 365/384/365
+f 362/368/349 361/363/344 381/385/366
+f 357/369/350 356/364/345 376/386/367
+f 352/370/351 351/382/363 371/387/368
+f 347/371/352 346/367/348 366/388/369
+f 363/372/353 362/368/349 382/389/370
+f 358/373/354 357/369/350 377/390/371
+f 353/374/355 352/370/351 372/391/372
+f 348/375/356 347/371/352 367/392/373
+f 359/376/357 358/373/354 378/393/374
+f 354/377/358 353/374/355 373/394/375
+f 349/378/359 348/375/356 368/395/376
+f 360/360/341 359/376/357 379/396/377
+f 355/361/342 354/377/358 374/397/378
+f 39/398/379 19/399/21 38/51/22
+f 1/11/11 344/400/380 20/38/38
+f 39/398/379 38/51/22 58/52/44
+f 20/38/38 344/400/380 40/91/74
+f 39/398/379 58/52/44 77/78/61
+f 40/91/74 344/400/380 59/110/93
+f 39/398/379 77/78/61 96/97/80
+f 59/110/93 344/400/380 78/128/111
+f 39/398/379 96/97/80 115/115/98
+f 78/128/111 344/400/380 97/145/128
+f 39/398/379 115/115/98 134/150/133
+f 97/145/128 344/400/380 116/146/129
+f 39/398/379 134/150/133 153/169/152
+f 116/146/129 344/400/380 135/164/147
+f 39/398/379 153/169/152 172/203/186
+f 135/164/147 344/400/380 154/183/166
+f 39/398/379 172/203/186 191/221/204
+f 154/183/166 344/400/380 173/199/182
+f 39/398/379 191/221/204 210/238/221
+f 173/199/182 344/400/380 192/217/200
+f 39/398/379 210/238/221 229/257/240
+f 192/217/200 344/400/380 211/234/217
+f 39/398/379 229/257/240 248/401/259
+f 211/234/217 344/400/380 230/253/236
+f 39/398/379 248/401/259 267/402/277
+f 230/253/236 344/400/380 249/273/255
+f 249/273/255 344/400/380 268/292/273
+f 39/398/379 267/402/277 286/403/296
+f 268/292/273 344/400/380 287/311/292
+f 39/398/379 286/403/296 305/404/314
+f 287/311/292 344/400/380 306/347/328
+f 39/398/379 305/404/314 324/405/315
+f 306/347/328 344/400/380 325/366/347
+f 39/398/379 324/405/315 343/406/334
+f 325/366/347 344/400/380 345/383/364
+f 39/398/379 343/406/334 363/407/353
+f 345/383/364 344/400/380 364/408/381
+f 39/398/379 363/407/353 382/409/370
+f 364/408/381 344/400/380 1/11/11
+f 380/380/361 379/396/377 16/2/2
+f 375/381/362 374/397/378 12/4/4
+f 370/410/382 369/379/360 7/7/7
+f 365/384/365 364/408/381 2/10/10
+f 381/385/366 380/380/361 17/1/1
+f 376/386/367 375/381/362 13/15/15
+f 371/387/368 370/410/382 8/17/17
+f 366/388/369 365/384/365 3/19/19
+f 382/389/370 381/385/366 18/13/13
+f 377/390/371 376/386/367 13/15/15
+f 372/391/372 371/387/368 9/25/25
+f 367/392/373 366/388/369 4/27/27
+f 39/398/379 382/409/370 19/399/21
+f 378/393/374 377/390/371 14/23/23
+f 373/394/375 372/391/372 10/31/31
+f 368/395/376 367/392/373 5/33/33
+f 379/396/377 378/393/374 15/29/29
+f 374/397/378 373/394/375 11/5/5
+f 369/379/360 368/395/376 6/8/8
+f 16/2/2 35/35/35 36/3/3
+f 11/5/5 30/36/36 31/6/6
+f 6/8/8 25/37/37 26/9/9
+f 1/11/11 20/38/38 21/12/12
+f 17/1/1 36/3/3 37/14/14
+f 12/4/4 31/6/6 32/16/16
+f 7/7/7 26/9/9 27/18/18
+f 2/10/10 21/12/12 22/20/20
+f 18/13/13 37/14/14 38/22/22
+f 13/15/15 32/16/16 33/24/24
+f 8/17/17 27/18/18 28/26/26
+f 3/19/19 22/20/20 23/28/28
+f 14/23/23 33/24/24 34/30/30
+f 9/25/25 28/26/26 29/32/32
+f 4/27/27 23/28/28 24/34/34
+f 15/29/29 34/30/30 35/35/35
+f 10/31/31 29/32/32 30/36/36
+f 5/33/33 24/34/34 25/37/37
+f 20/38/38 40/91/74 41/39/39
+f 36/41/3 56/71/54 57/42/40
+f 31/44/6 51/72/55 52/45/41
+f 26/47/9 46/73/56 47/48/42
+f 21/12/12 41/39/39 42/50/43
+f 37/40/14 57/42/40 58/52/44
+f 32/43/16 52/45/41 53/54/45
+f 27/46/18 47/48/42 48/56/46
+f 22/49/20 42/50/43 43/58/47
+f 33/53/24 53/54/45 54/60/48
+f 28/55/26 48/56/46 49/62/49
+f 23/57/28 43/58/47 44/64/50
+f 34/59/30 54/60/48 55/66/51
+f 29/61/32 49/62/49 50/68/52
+f 24/63/34 44/64/50 45/70/53
+f 35/65/35 55/66/51 56/71/54
+f 30/67/36 50/68/52 51/72/55
+f 25/69/37 45/70/53 46/73/56
+f 56/71/54 75/88/71 76/74/57
+f 51/72/55 70/89/72 71/75/58
+f 46/73/56 65/90/73 66/76/59
+f 41/39/39 60/92/75 61/77/60
+f 57/42/40 76/74/57 77/78/61
+f 52/45/41 71/75/58 72/79/62
+f 47/48/42 66/76/59 67/80/63
+f 42/50/43 61/77/60 62/81/64
+f 53/54/45 72/79/62 73/82/65
+f 48/56/46 67/80/63 68/83/66
+f 43/58/47 62/81/64 63/84/67
+f 54/60/48 73/82/65 74/85/68
+f 49/62/49 68/83/66 69/86/69
+f 44/64/50 63/84/67 64/87/70
+f 55/66/51 74/85/68 75/88/71
+f 50/68/52 69/86/69 70/89/72
+f 45/70/53 64/87/70 65/90/73
+f 40/91/74 59/110/93 60/92/75
+f 75/88/71 94/107/90 95/93/76
+f 70/89/72 89/108/91 90/94/77
+f 65/90/73 84/109/92 85/95/78
+f 60/92/75 79/111/94 80/96/79
+f 76/74/57 95/93/76 96/97/80
+f 71/75/58 90/94/77 91/98/81
+f 66/76/59 85/95/78 86/99/82
+f 61/77/60 80/96/79 81/100/83
+f 72/79/62 91/98/81 92/101/84
+f 67/80/63 86/99/82 87/102/85
+f 62/81/64 81/100/83 82/103/86
+f 73/82/65 92/101/84 93/104/87
+f 68/83/66 87/102/85 88/105/88
+f 63/84/67 82/103/86 83/106/89
+f 74/85/68 93/104/87 94/107/90
+f 69/86/69 88/105/88 89/108/91
+f 64/87/70 83/106/89 84/109/92
+f 59/110/93 78/128/111 79/111/94
+f 89/108/91 108/126/109 109/112/95
+f 84/109/92 103/127/110 104/113/96
+f 79/111/94 98/129/112 99/114/97
+f 95/93/76 114/130/113 115/115/98
+f 90/94/77 109/112/95 110/116/99
+f 85/95/78 104/113/96 105/117/100
+f 80/96/79 99/114/97 100/118/101
+f 91/98/81 110/116/99 111/119/102
+f 86/99/82 105/117/100 106/120/103
+f 81/100/83 100/118/101 101/121/104
+f 92/101/84 111/119/102 112/122/105
+f 87/102/85 106/120/103 107/123/106
+f 82/103/86 101/121/104 102/124/107
+f 93/104/87 112/122/105 113/125/108
+f 88/105/88 107/123/106 108/126/109
+f 83/106/89 102/124/107 103/127/110
+f 78/128/111 97/145/128 98/129/112
+f 94/107/90 113/125/108 114/130/113
+f 118/135/118 99/114/97 117/131/114
+f 134/150/133 115/115/98 133/132/115
+f 129/136/119 110/116/99 128/133/116
+f 124/137/120 105/117/100 123/134/117
+f 119/138/121 100/118/101 118/135/118
+f 130/139/122 111/119/102 129/136/119
+f 125/140/123 106/120/103 124/137/120
+f 120/141/124 101/121/104 119/138/121
+f 131/142/125 112/122/105 130/139/122
+f 126/143/126 107/123/106 125/140/123
+f 121/144/127 102/124/107 120/141/124
+f 132/147/130 113/125/108 131/142/125
+f 127/148/131 108/126/109 126/143/126
+f 122/149/132 103/127/110 121/144/127
+f 117/131/114 98/129/112 116/146/129
+f 133/132/115 114/130/113 132/147/130
+f 128/133/116 109/112/95 127/148/131
+f 123/134/117 104/113/96 122/149/132
+f 153/169/152 134/150/133 152/151/134
+f 148/155/138 129/136/119 147/152/135
+f 143/156/139 124/137/120 142/153/136
+f 138/157/140 119/138/121 137/154/137
+f 149/158/141 130/139/122 148/155/138
+f 144/159/142 125/140/123 143/156/139
+f 139/160/143 120/141/124 138/157/140
+f 150/161/144 131/142/125 149/158/141
+f 145/162/145 126/143/126 144/159/142
+f 140/163/146 121/144/127 139/160/143
+f 151/165/148 132/147/130 150/161/144
+f 146/166/149 127/148/131 145/162/145
+f 141/167/150 122/149/132 140/163/146
+f 136/168/151 117/131/114 135/164/147
+f 152/151/134 133/132/115 151/165/148
+f 147/152/135 128/133/116 146/166/149
+f 142/153/136 123/134/117 141/167/150
+f 137/154/137 118/135/118 136/168/151
+f 172/203/186 153/169/152 171/170/153
+f 167/174/157 148/155/138 166/171/154
+f 162/175/158 143/156/139 161/172/155
+f 157/176/159 138/157/140 156/173/156
+f 168/177/160 149/158/141 167/174/157
+f 163/178/161 144/159/142 162/175/158
+f 158/179/162 139/160/143 157/176/159
+f 169/180/163 150/161/144 168/177/160
+f 164/181/164 145/162/145 163/178/161
+f 159/194/177 140/163/146 158/179/162
+f 170/184/167 151/165/148 169/180/163
+f 165/185/168 146/166/149 164/181/164
+f 140/163/146 159/194/177 160/182/165
+f 155/186/169 136/168/151 154/183/166
+f 171/170/153 152/151/134 170/184/167
+f 166/171/154 147/152/135 165/185/168
+f 161/172/155 142/153/136 160/182/165
+f 156/173/156 137/154/137 155/186/169
+f 186/207/190 167/174/157 185/187/170
+f 181/191/174 162/175/158 180/188/171
+f 176/192/175 157/176/159 175/189/172
+f 167/174/157 186/207/190 187/190/173
+f 182/193/176 163/178/161 181/191/174
+f 177/195/178 158/179/162 176/192/175
+f 188/196/179 169/180/163 187/190/173
+f 183/197/180 164/181/164 182/193/176
+f 178/212/195 159/194/177 177/195/178
+f 189/200/183 170/184/167 188/196/179
+f 184/201/184 165/185/168 183/197/180
+f 159/194/177 178/212/195 179/198/181
+f 174/202/185 155/186/169 173/199/182
+f 190/204/187 171/170/153 189/200/183
+f 185/187/170 166/171/154 184/201/184
+f 180/188/171 161/172/155 179/198/181
+f 175/189/172 156/173/156 174/202/185
+f 191/221/204 172/203/186 190/204/187
+f 200/209/192 181/191/174 199/205/188
+f 195/210/193 176/192/175 194/206/189
+f 186/207/190 205/224/207 206/208/191
+f 201/211/194 182/193/176 200/209/192
+f 196/213/196 177/195/178 195/210/193
+f 207/214/197 188/196/179 206/208/191
+f 202/215/198 183/197/180 201/211/194
+f 197/229/212 178/212/195 196/213/196
+f 208/218/201 189/200/183 207/214/197
+f 203/219/202 184/201/184 202/215/198
+f 178/212/195 197/229/212 198/216/199
+f 193/220/203 174/202/185 192/217/200
+f 209/222/205 190/204/187 208/218/201
+f 204/223/206 185/187/170 203/219/202
+f 199/205/188 180/188/171 198/216/199
+f 194/206/189 175/189/172 193/220/203
+f 210/238/221 191/221/204 209/222/205
+f 205/224/207 186/207/190 204/223/206
+f 205/224/207 224/243/226 225/225/208
+f 220/228/211 201/211/194 219/226/209
+f 215/230/213 196/213/196 214/227/210
+f 226/231/214 207/214/197 225/225/208
+f 221/232/215 202/215/198 220/228/211
+f 216/248/231 197/229/212 215/230/213
+f 227/235/218 208/218/201 226/231/214
+f 222/236/219 203/219/202 221/232/215
+f 197/229/212 216/248/231 217/233/216
+f 212/237/220 193/220/203 211/234/217
+f 228/239/222 209/222/205 227/235/218
+f 223/240/223 204/223/206 222/236/219
+f 218/241/224 199/205/188 217/233/216
+f 213/242/225 194/206/189 212/237/220
+f 229/257/240 210/238/221 228/239/222
+f 224/243/226 205/224/207 223/240/223
+f 219/226/209 200/209/192 218/241/224
+f 214/227/210 195/210/193 213/242/225
+f 224/243/226 243/262/245 244/244/227
+f 239/247/230 220/228/211 238/245/228
+f 234/249/232 215/230/213 233/246/229
+f 245/250/233 226/231/214 244/244/227
+f 240/251/234 221/232/215 239/247/230
+f 235/267/250 216/248/231 234/249/232
+f 246/269/237 227/235/218 245/250/233
+f 241/255/238 222/236/219 240/251/234
+f 216/248/231 235/267/250 236/252/235
+f 231/256/239 212/237/220 230/253/236
+f 247/258/241 228/239/222 246/254/237
+f 242/259/242 223/240/223 241/255/238
+f 237/260/243 218/241/224 236/252/235
+f 232/261/244 213/242/225 231/256/239
+f 248/401/259 229/257/240 247/258/241
+f 243/262/245 224/243/226 242/259/242
+f 238/245/228 219/226/209 237/260/243
+f 233/246/229 214/227/210 232/261/244
+f 243/262/245 262/298/279 263/263/246
+f 258/266/249 239/247/230 257/264/247
+f 253/268/251 234/249/232 252/265/248
+f 264/270/252 245/250/233 263/263/246
+f 259/271/253 240/251/234 258/266/249
+f 254/287/268 235/267/250 253/268/251
+f 265/275/256 246/269/237 264/270/252
+f 260/276/257 241/255/238 259/271/253
+f 235/267/250 254/287/268 255/272/254
+f 250/277/258 231/256/239 249/273/255
+f 266/279/260 247/274/241 265/275/256
+f 261/280/261 242/259/242 260/276/257
+f 256/281/262 237/260/243 255/272/254
+f 251/282/263 232/261/244 250/277/258
+f 267/296/277 248/278/259 266/279/260
+f 262/298/279 243/262/245 261/280/261
+f 257/264/247 238/245/228 256/281/262
+f 252/265/248 233/246/229 251/282/263
+f 277/286/267 258/266/249 276/283/264
+f 272/288/269 253/268/251 271/284/265
+f 283/289/270 264/270/252 282/285/266
+f 278/290/271 259/271/253 277/286/267
+f 273/306/287 254/287/268 272/288/269
+f 284/293/274 265/275/256 283/289/270
+f 279/294/275 260/276/257 278/290/271
+f 254/287/268 273/306/287 274/291/272
+f 269/295/276 250/277/258 268/292/273
+f 285/297/278 266/279/260 284/293/274
+f 280/299/280 261/280/261 279/294/275
+f 275/300/281 256/281/262 274/291/272
+f 270/301/282 251/282/263 269/295/276
+f 286/315/296 267/296/277 285/297/278
+f 281/302/283 262/298/279 280/299/280
+f 276/283/264 257/264/247 275/300/281
+f 271/284/265 252/265/248 270/301/282
+f 282/285/266 263/263/246 281/302/283
+f 291/307/288 272/288/269 290/303/284
+f 302/308/289 283/289/270 301/304/285
+f 297/309/290 278/290/271 296/305/286
+f 292/324/305 273/306/287 291/307/288
+f 303/312/293 284/293/274 302/308/289
+f 298/313/294 279/294/275 297/309/290
+f 273/306/287 292/324/305 293/310/291
+f 288/314/295 269/295/276 287/311/292
+f 304/316/297 285/297/278 303/312/293
+f 299/317/298 280/299/280 298/313/294
+f 294/318/299 275/300/281 293/310/291
+f 289/319/300 270/301/282 288/314/295
+f 305/333/314 286/315/296 304/316/297
+f 300/320/301 281/302/283 299/317/298
+f 295/321/302 276/283/264 294/318/299
+f 290/303/284 271/284/265 289/319/300
+f 301/304/285 282/285/266 300/320/301
+f 296/305/286 277/286/267 295/321/302
+f 301/304/285 320/338/319 321/322/303
+f 296/305/286 315/339/320 316/323/304
+f 291/307/288 310/340/321 311/325/306
+f 302/308/289 321/322/303 322/326/307
+f 297/309/290 316/323/304 317/327/308
+f 312/346/327 293/310/291 311/325/306
+f 287/311/292 306/347/328 307/328/309
+f 303/312/293 322/326/307 323/329/310
+f 298/313/294 317/327/308 318/330/311
+f 293/310/291 312/346/327 313/331/312
+f 288/314/295 307/328/309 308/332/313
+f 304/316/297 323/329/310 324/334/315
+f 299/317/298 318/330/311 319/335/316
+f 294/318/299 313/331/312 314/336/317
+f 289/319/300 308/332/313 309/337/318
+f 300/320/301 319/335/316 320/338/319
+f 295/321/302 314/336/317 315/339/320
+f 290/303/284 309/337/318 310/340/321
+f 320/338/319 339/357/338 340/341/322
+f 315/339/320 334/358/339 335/342/323
+f 310/340/321 329/359/340 330/343/324
+f 321/322/303 340/341/322 341/344/325
+f 316/323/304 335/342/323 336/345/326
+f 331/365/346 312/346/327 330/343/324
+f 306/347/328 325/366/347 326/348/329
+f 322/326/307 341/344/325 342/349/330
+f 317/327/308 336/345/326 337/350/331
+f 312/346/327 331/365/346 332/351/332
+f 307/328/309 326/348/329 327/352/333
+f 323/329/310 342/349/330 343/353/334
+f 318/330/311 337/350/331 338/354/335
+f 313/331/312 332/351/332 333/355/336
+f 308/332/313 327/352/333 328/356/337
+f 319/335/316 338/354/335 339/357/338
+f 314/336/317 333/355/336 334/358/339
+f 309/337/318 328/356/337 329/359/340
+f 339/357/338 359/376/357 360/360/341
+f 334/358/339 354/377/358 355/361/342
+f 329/359/340 349/378/359 350/362/343
+f 340/341/322 360/360/341 361/363/344
+f 335/342/323 355/361/342 356/364/345
+f 351/382/363 331/365/346 350/362/343
+f 325/366/347 345/383/364 346/367/348
+f 341/344/325 361/363/344 362/368/349
+f 336/345/326 356/364/345 357/369/350
+f 331/365/346 351/382/363 352/370/351
+f 326/348/329 346/367/348 347/371/352
+f 342/349/330 362/368/349 363/372/353
+f 337/350/331 357/369/350 358/373/354
+f 332/351/332 352/370/351 353/374/355
+f 327/352/333 347/371/352 348/375/356
+f 338/354/335 358/373/354 359/376/357
+f 333/355/336 353/374/355 354/377/358
+f 328/356/337 348/375/356 349/378/359
+f 349/378/359 368/395/376 369/379/360
+f 360/360/341 379/396/377 380/380/361
+f 355/361/342 374/397/378 375/381/362
+f 370/410/382 351/382/363 369/379/360
+f 345/383/364 364/408/381 365/384/365
+f 361/363/344 380/380/361 381/385/366
+f 356/364/345 375/381/362 376/386/367
+f 351/382/363 370/410/382 371/387/368
+f 346/367/348 365/384/365 366/388/369
+f 362/368/349 381/385/366 382/389/370
+f 357/369/350 376/386/367 377/390/371
+f 352/370/351 371/387/368 372/391/372
+f 347/371/352 366/388/369 367/392/373
+f 358/373/354 377/390/371 378/393/374
+f 353/374/355 372/391/372 373/394/375
+f 348/375/356 367/392/373 368/395/376
+f 359/376/357 378/393/374 379/396/377
+f 354/377/358 373/394/375 374/397/378
+f 17/1/1 380/380/361 16/2/2
+f 374/397/378 11/5/5 12/4/4
+f 369/379/360 6/8/8 7/7/7
+f 364/408/381 1/11/11 2/10/10
+f 18/13/13 381/385/366 17/1/1
+f 375/381/362 12/4/4 13/15/15
+f 370/410/382 7/7/7 8/17/17
+f 365/384/365 2/10/10 3/19/19
+f 19/21/21 382/389/370 18/13/13
+f 14/23/23 377/390/371 13/15/15
+f 371/387/368 8/17/17 9/25/25
+f 366/388/369 3/19/19 4/27/27
+f 15/29/29 378/393/374 14/23/23
+f 372/391/372 9/25/25 10/31/31
+f 367/392/373 4/27/27 5/33/33
+f 16/2/2 379/396/377 15/29/29
+f 373/394/375 10/31/31 11/5/5
+f 368/395/376 5/33/33 6/8/8
diff --git a/tests/manual/rotations/mesh/narrowarrow.obj b/tests/manual/rotations/mesh/narrowarrow.obj
new file mode 100644
index 0000000..481fc1d
--- /dev/null
+++ b/tests/manual/rotations/mesh/narrowarrow.obj
@@ -0,0 +1,413 @@
+# Blender v2.69 (sub 0) OBJ File: ''
+# www.blender.org
+v 0.000000 0.986570 0.000000
+v 0.000000 0.500000 -0.218399
+v -0.042608 0.500000 -0.214202
+v -0.083578 0.500000 -0.201774
+v -0.121336 0.500000 -0.181592
+v -0.154431 0.500000 -0.154431
+v -0.181592 0.500000 -0.121336
+v -0.201774 0.500000 -0.083578
+v -0.214202 0.500000 -0.042608
+v -0.218399 0.500000 -0.000000
+v -0.214202 0.500000 0.042607
+v -0.201774 0.500000 0.083578
+v -0.181592 0.500000 0.121336
+v -0.154431 0.500000 0.154431
+v -0.121336 0.500000 0.181592
+v -0.083578 0.500000 0.201774
+v -0.042607 0.500000 0.214202
+v 0.000000 0.500000 0.218399
+v 0.042608 0.500000 0.214202
+v 0.083578 0.500000 0.201774
+v 0.121336 0.500000 0.181592
+v 0.154431 0.500000 0.154431
+v 0.181592 0.500000 0.121336
+v 0.201774 0.500000 0.083577
+v 0.214202 0.500000 0.042607
+v 0.218399 0.500000 -0.000000
+v 0.214202 0.500000 -0.042608
+v 0.201774 0.500000 -0.083578
+v 0.181592 0.500000 -0.121336
+v 0.154431 0.500000 -0.154432
+v 0.121336 0.500000 -0.181592
+v 0.083577 0.500000 -0.201774
+v 0.042607 0.500000 -0.214202
+v 0.000000 0.000000 0.000000
+v 0.000000 0.000000 0.000000
+v -0.000000 0.500000 -0.126422
+v -0.024664 0.500000 -0.123993
+v -0.048380 0.500000 -0.116799
+v -0.070236 0.500000 -0.105116
+v -0.089394 0.500000 -0.089394
+v -0.105116 0.500000 -0.070236
+v -0.116799 0.500000 -0.048380
+v -0.123993 0.500000 -0.024664
+v -0.126422 0.500000 -0.000000
+v -0.123993 0.500000 0.024664
+v -0.116799 0.500000 0.048380
+v -0.105116 0.500000 0.070236
+v -0.089394 0.500000 0.089394
+v -0.070236 0.500000 0.105116
+v -0.048380 0.500000 0.116799
+v -0.024664 0.500000 0.123993
+v 0.000000 0.500000 0.126422
+v 0.024664 0.500000 0.123993
+v 0.048380 0.500000 0.116799
+v 0.070236 0.500000 0.105116
+v 0.089394 0.500000 0.089394
+v 0.105116 0.500000 0.070236
+v 0.116799 0.500000 0.048380
+v 0.123993 0.500000 0.024664
+v 0.126422 0.500000 -0.000000
+v 0.123993 0.500000 -0.024664
+v 0.116799 0.500000 -0.048380
+v 0.105116 0.500000 -0.070237
+v 0.089394 0.500000 -0.089394
+v 0.070236 0.500000 -0.105116
+v 0.048380 0.500000 -0.116799
+v 0.024664 0.500000 -0.123993
+v 0.126422 -0.983070 -0.000000
+v 0.123993 -0.983070 0.024664
+v -0.048380 -0.983070 -0.116799
+v -0.024664 -0.983070 -0.123993
+v -0.070236 -0.983070 0.105116
+v -0.089394 -0.983070 0.089394
+v 0.116799 -0.983070 0.048380
+v 0.105116 -0.983070 0.070236
+v -0.105116 -0.983070 0.070236
+v -0.116799 -0.983070 0.048380
+v 0.024664 -0.983070 -0.123993
+v 0.048380 -0.983070 -0.116799
+v 0.089394 -0.983070 0.089394
+v 0.070236 -0.983070 0.105116
+v -0.123993 -0.983070 0.024664
+v -0.126422 -0.983070 -0.000000
+v 0.070236 -0.983070 -0.105116
+v 0.089394 -0.983070 -0.089394
+v 0.048380 -0.983070 0.116799
+v 0.024664 -0.983070 0.123993
+v -0.123993 -0.983070 -0.024664
+v -0.116799 -0.983070 -0.048380
+v 0.105116 -0.983070 -0.070237
+v 0.116799 -0.983070 -0.048380
+v -0.105116 -0.983070 -0.070236
+v -0.089394 -0.983070 -0.089394
+v 0.000000 -0.983070 0.126422
+v -0.024664 -0.983070 0.123993
+v 0.123993 -0.983070 -0.024664
+v -0.070236 -0.983070 -0.105116
+v -0.048380 -0.983070 0.116799
+v -0.000000 -0.983070 -0.126422
+vt 0.000000 0.000000
+vt 1.000000 0.000000
+vt 1.000000 1.000000
+vt 0.000000 1.000000
+vt 0.500000 1.000000
+vt 0.597545 0.990393
+vt 0.402456 0.990393
+vt 0.691342 0.961940
+vt 0.777785 0.915735
+vt 0.853553 0.853553
+vt 0.915735 0.777785
+vt 0.961940 0.691342
+vt 0.990393 0.597545
+vt 1.000000 0.500000
+vt 0.990393 0.402455
+vt 0.961940 0.308658
+vt 0.915735 0.222215
+vt 0.853553 0.146447
+vt 0.777785 0.084265
+vt 0.691342 0.038060
+vt 0.597545 0.009607
+vt 0.308659 0.961940
+vt 0.222215 0.915735
+vt 0.146447 0.853554
+vt 0.308658 0.038060
+vt 0.500000 0.000000
+vt 0.402455 0.009607
+vt 0.222215 0.084265
+vt 0.146446 0.146447
+vt 0.084265 0.222215
+vt 0.009607 0.402455
+vt 0.038060 0.308659
+vt 0.009607 0.597546
+vt 0.000000 0.500000
+vt 0.038060 0.691342
+vt 0.084266 0.777786
+vn -0.089495 0.407852 -0.908651
+vn -0.265044 0.407852 -0.873733
+vn -0.430408 0.407852 -0.805236
+vn -0.579231 0.407852 -0.705796
+vn -0.705796 0.407852 -0.579232
+vn -0.805237 0.407852 -0.430408
+vn -0.873733 0.407852 -0.265043
+vn -0.908651 0.407852 -0.089495
+vn -0.908652 0.407852 0.089494
+vn -0.873732 0.407852 0.265044
+vn -0.805237 0.407852 0.430407
+vn -0.705796 0.407852 0.579232
+vn -0.579231 0.407852 0.705796
+vn -0.430408 0.407852 0.805237
+vn -0.265044 0.407852 0.873733
+vn -0.089494 0.407852 0.908652
+vn 0.089495 0.407852 0.908651
+vn 0.265044 0.407852 0.873732
+vn 0.430408 0.407852 0.805237
+vn 0.579232 0.407852 0.705796
+vn 0.705796 0.407852 0.579231
+vn 0.805237 0.407852 0.430406
+vn 0.873733 0.407852 0.265044
+vn 0.908652 0.407852 0.089493
+vn 0.908651 0.407852 -0.089496
+vn 0.873732 0.407852 -0.265045
+vn 0.805236 0.407852 -0.430409
+vn 0.705795 0.407852 -0.579232
+vn 0.579231 0.407852 -0.705796
+vn 0.430407 0.407852 -0.805237
+vn 0.265042 0.407852 -0.873733
+vn 0.089494 0.407852 -0.908652
+vn 0.000000 -1.000000 0.000000
+vn 0.000000 -1.000000 0.000062
+vn -0.290284 0.000000 0.956940
+vn -0.098017 0.000000 0.995185
+vn -0.634393 0.000000 -0.773010
+vn -0.773010 0.000000 -0.634393
+vn 0.956940 0.000000 -0.290286
+vn 0.881921 0.000000 -0.471398
+vn -0.881922 0.000000 -0.471396
+vn -0.956940 0.000000 -0.290285
+vn 0.098017 0.000000 0.995185
+vn 0.290285 0.000000 0.956940
+vn 0.773010 0.000000 -0.634394
+vn 0.634392 0.000000 -0.773011
+vn -0.995185 0.000000 -0.098017
+vn -0.995185 0.000000 0.098017
+vn 0.471397 0.000000 0.881921
+vn 0.634394 0.000000 0.773010
+vn 0.471395 0.000000 -0.881922
+vn 0.290283 0.000000 -0.956941
+vn 0.098016 0.000000 -0.995185
+vn -0.956940 0.000000 0.290285
+vn -0.881922 0.000000 0.471396
+vn 0.773011 0.000000 0.634393
+vn 0.881922 0.000000 0.471396
+vn -0.773010 0.000000 0.634393
+vn -0.634394 0.000000 0.773010
+vn -0.471397 0.000000 0.881921
+vn -0.098016 0.000000 -0.995185
+vn -0.290284 0.000000 -0.956940
+vn -0.471397 0.000000 -0.881921
+vn 0.956941 0.000000 0.290284
+vn 0.995185 0.000000 0.098016
+vn 0.995185 0.000000 -0.098018
+vn 0.000000 -1.000000 0.000001
+vn 0.000000 -1.000000 0.000018
+vn 0.000000 -1.000000 -0.000013
+vn 0.000000 -1.000000 0.000011
+vn 0.000000 -1.000000 0.000002
+vn 0.000000 -1.000000 -0.000031
+vn 0.000000 -1.000000 0.000031
+vn 0.000000 -1.000000 -0.000021
+vn 0.000000 -1.000000 -0.000016
+vn 0.000000 -1.000000 -0.000005
+vn 0.000000 -1.000000 0.000003
+vn -0.098015 0.000000 0.995185
+vn -0.773011 0.000000 -0.634393
+vn -0.881921 0.000000 -0.471396
+vn 0.773009 0.000000 -0.634394
+vn 0.098017 0.000000 -0.995185
+vn -0.634393 0.000000 0.773011
+vn -0.098018 0.000000 -0.995185
+vn -0.290285 0.000000 -0.956940
+s off
+f 1/1/1 2/2/1 3/3/1
+f 1/1/2 3/2/2 4/3/2
+f 1/1/3 4/2/3 5/3/3
+f 1/1/4 5/2/4 6/3/4
+f 1/1/5 6/2/5 7/3/5
+f 1/1/6 7/2/6 8/3/6
+f 1/1/7 8/2/7 9/3/7
+f 1/1/8 9/2/8 10/3/8
+f 1/1/9 10/2/9 11/3/9
+f 1/1/10 11/2/10 12/3/10
+f 1/1/11 12/2/11 13/3/11
+f 1/1/12 13/2/12 14/3/12
+f 1/1/13 14/2/13 15/3/13
+f 1/1/14 15/2/14 16/3/14
+f 1/1/15 16/2/15 17/3/15
+f 1/1/16 17/2/16 18/3/16
+f 1/1/17 18/2/17 19/3/17
+f 1/1/18 19/2/18 20/3/18
+f 1/1/19 20/2/19 21/3/19
+f 1/1/20 21/2/20 22/3/20
+f 1/1/21 22/2/21 23/3/21
+f 1/1/22 23/2/22 24/3/22
+f 1/1/23 24/2/23 25/3/23
+f 1/1/24 25/2/24 26/3/24
+f 1/1/25 26/2/25 27/3/25
+f 1/1/26 27/2/26 28/3/26
+f 1/1/27 28/2/27 29/3/27
+f 1/1/28 29/2/28 30/3/28
+f 1/1/29 30/2/29 31/3/29
+f 1/1/30 31/2/30 32/3/30
+f 1/1/31 32/2/31 33/3/31
+f 1/1/32 33/2/32 2/3/32
+f 24/1/33 23/2/33 58/4/33
+f 13/1/33 12/2/33 47/4/33
+f 2/1/33 33/2/33 36/4/33
+f 23/1/33 22/2/33 57/4/33
+f 12/1/33 11/2/33 46/4/33
+f 33/1/33 32/2/33 67/4/33
+f 22/1/33 21/2/33 56/4/33
+f 11/1/33 10/2/33 45/4/33
+f 32/1/33 31/2/33 66/4/33
+f 21/1/33 20/2/33 55/4/33
+f 10/1/33 9/2/33 44/4/33
+f 31/1/33 30/2/33 65/4/33
+f 20/1/33 19/2/33 54/4/33
+f 9/1/33 8/2/33 43/4/33
+f 30/1/33 29/2/33 64/4/33
+f 19/1/33 18/2/33 53/4/33
+f 3/1/33 2/2/33 37/4/33
+f 8/1/33 7/2/33 42/4/33
+f 29/1/33 28/2/33 63/4/33
+f 7/1/33 6/2/33 41/4/33
+f 18/1/33 17/2/33 52/4/33
+f 28/1/33 27/2/33 62/4/33
+f 6/1/33 5/2/33 40/4/33
+f 17/1/33 16/2/33 51/4/33
+f 27/1/33 26/2/33 61/4/33
+f 5/1/33 4/2/33 39/4/33
+f 16/1/33 15/2/33 50/4/33
+f 26/1/33 25/2/33 60/4/33
+f 4/1/33 3/2/33 38/4/33
+f 15/1/33 14/2/33 49/4/33
+f 25/1/33 24/2/33 59/4/33
+f 14/1/33 13/2/33 48/4/33
+f 81/5/34 86/6/34 80/7/34
+f 51/1/35 50/2/35 95/4/35
+f 52/1/36 51/2/36 94/4/36
+f 40/1/37 39/2/37 93/4/37
+f 41/1/38 40/2/38 92/4/38
+f 62/1/39 61/2/39 91/4/39
+f 63/1/40 62/2/40 90/4/40
+f 42/1/41 41/2/41 89/4/41
+f 43/1/42 42/2/42 88/4/42
+f 53/1/43 52/2/43 87/4/43
+f 54/1/44 53/2/44 86/4/44
+f 64/1/45 63/2/45 85/4/45
+f 65/1/46 64/2/46 84/4/46
+f 44/1/47 43/2/47 83/4/47
+f 45/1/48 44/2/48 82/4/48
+f 55/1/49 54/2/49 81/4/49
+f 56/1/50 55/2/50 80/4/50
+f 66/1/51 65/2/51 79/4/51
+f 67/1/52 66/2/52 78/4/52
+f 36/1/53 67/2/53 99/4/53
+f 46/1/54 45/2/54 77/4/54
+f 47/1/55 46/2/55 76/4/55
+f 57/1/56 56/2/56 75/4/56
+f 58/1/57 57/2/57 74/4/57
+f 48/1/58 47/2/58 73/4/58
+f 49/1/59 48/2/59 72/4/59
+f 50/1/60 49/2/60 98/4/60
+f 37/1/61 36/2/61 71/4/61
+f 38/1/62 37/2/62 70/4/62
+f 39/1/63 38/2/63 97/4/63
+f 59/1/64 58/2/64 69/4/64
+f 60/1/65 59/2/65 68/4/65
+f 61/1/66 60/2/66 96/4/66
+f 23/2/33 57/3/33 58/4/33
+f 12/2/33 46/3/33 47/4/33
+f 33/2/33 67/3/33 36/4/33
+f 22/2/33 56/3/33 57/4/33
+f 11/2/33 45/3/33 46/4/33
+f 32/2/67 66/3/67 67/4/67
+f 21/2/33 55/3/33 56/4/33
+f 10/2/33 44/3/33 45/4/33
+f 31/2/33 65/3/33 66/4/33
+f 20/2/33 54/3/33 55/4/33
+f 9/2/33 43/3/33 44/4/33
+f 30/2/33 64/3/33 65/4/33
+f 19/2/33 53/3/33 54/4/33
+f 8/2/33 42/3/33 43/4/33
+f 29/2/33 63/3/33 64/4/33
+f 18/2/33 52/3/33 53/4/33
+f 2/2/33 36/3/33 37/4/33
+f 7/2/33 41/3/33 42/4/33
+f 28/2/33 62/3/33 63/4/33
+f 6/2/33 40/3/33 41/4/33
+f 17/2/33 51/3/33 52/4/33
+f 27/2/33 61/3/33 62/4/33
+f 5/2/33 39/3/33 40/4/33
+f 16/2/67 50/3/67 51/4/67
+f 26/2/33 60/3/33 61/4/33
+f 4/2/33 38/3/33 39/4/33
+f 15/2/33 49/3/33 50/4/33
+f 25/2/33 59/3/33 60/4/33
+f 3/2/33 37/3/33 38/4/33
+f 14/2/33 48/3/33 49/4/33
+f 24/2/33 58/3/33 59/4/33
+f 13/2/33 47/3/33 48/4/33
+f 86/6/33 87/8/33 80/7/33
+f 87/8/33 94/9/33 80/7/33
+f 94/9/33 95/10/33 80/7/33
+f 95/10/68 98/11/68 80/7/68
+f 98/11/69 72/12/69 80/7/69
+f 72/12/70 73/13/70 80/7/70
+f 73/13/33 76/14/33 80/7/33
+f 76/14/33 77/15/33 80/7/33
+f 77/15/33 82/16/33 80/7/33
+f 82/16/33 83/17/33 80/7/33
+f 83/17/33 88/18/33 80/7/33
+f 88/18/33 89/19/33 80/7/33
+f 89/19/33 92/20/33 93/21/33
+f 80/7/71 89/19/71 93/21/71
+f 75/22/33 80/7/33 93/21/33
+f 74/23/33 75/22/33 93/21/33
+f 69/24/33 74/23/33 93/21/33
+f 71/25/72 97/26/72 70/27/72
+f 71/25/73 93/21/73 97/26/73
+f 99/28/74 93/21/74 71/25/74
+f 78/29/33 93/21/33 99/28/33
+f 79/30/33 93/21/33 78/29/33
+f 85/31/73 79/30/73 84/32/73
+f 85/31/33 93/21/33 79/30/33
+f 91/33/75 85/31/75 90/34/75
+f 91/33/33 93/21/33 85/31/33
+f 96/35/33 68/36/33 91/33/33
+f 68/36/76 69/24/76 93/21/76
+f 91/33/77 68/36/77 93/21/77
+f 50/2/35 98/3/35 95/4/35
+f 51/2/78 95/3/78 94/4/78
+f 39/2/37 97/3/37 93/4/37
+f 40/2/79 93/3/79 92/4/79
+f 61/2/39 96/3/39 91/4/39
+f 62/2/40 91/3/40 90/4/40
+f 41/2/80 92/3/80 89/4/80
+f 42/2/42 89/3/42 88/4/42
+f 52/2/43 94/3/43 87/4/43
+f 53/2/44 87/3/44 86/4/44
+f 63/2/81 90/3/81 85/4/81
+f 64/2/46 85/3/46 84/4/46
+f 43/2/47 88/3/47 83/4/47
+f 44/2/48 83/3/48 82/4/48
+f 54/2/49 86/3/49 81/4/49
+f 55/2/50 81/3/50 80/4/50
+f 65/2/51 84/3/51 79/4/51
+f 66/2/52 79/3/52 78/4/52
+f 67/2/82 78/3/82 99/4/82
+f 45/2/54 82/3/54 77/4/54
+f 46/2/55 77/3/55 76/4/55
+f 56/2/56 80/3/56 75/4/56
+f 57/2/57 75/3/57 74/4/57
+f 47/2/58 76/3/58 73/4/58
+f 48/2/83 73/3/83 72/4/83
+f 49/2/60 72/3/60 98/4/60
+f 36/2/84 99/3/84 71/4/84
+f 37/2/85 71/3/85 70/4/85
+f 38/2/63 70/3/63 97/4/63
+f 58/2/64 74/3/64 69/4/64
+f 59/2/65 69/3/65 68/4/65
+f 60/2/66 68/3/66 96/4/66
diff --git a/tests/manual/rotations/rotations.pro b/tests/manual/rotations/rotations.pro
new file mode 100644
index 0000000..faabc5a
--- /dev/null
+++ b/tests/manual/rotations/rotations.pro
@@ -0,0 +1,17 @@
+android|ios|winrt {
+ error( "This example is not supported for android, ios, or winrt." )
+}
+
+!include( ../examples.pri ) {
+ error( "Couldn't find the examples.pri file!" )
+}
+
+SOURCES += main.cpp scatterdatamodifier.cpp
+HEADERS += scatterdatamodifier.h
+
+QT += widgets
+
+RESOURCES += rotations.qrc
+
+OTHER_FILES += doc/src/* \
+ doc/images/*
diff --git a/tests/manual/rotations/rotations.qrc b/tests/manual/rotations/rotations.qrc
new file mode 100644
index 0000000..bf66ebe
--- /dev/null
+++ b/tests/manual/rotations/rotations.qrc
@@ -0,0 +1,6 @@
+<RCC>
+ <qresource prefix="/">
+ <file>mesh/narrowarrow.obj</file>
+ <file>mesh/largesphere.obj</file>
+ </qresource>
+</RCC>
diff --git a/tests/manual/rotations/scatterdatamodifier.cpp b/tests/manual/rotations/scatterdatamodifier.cpp
new file mode 100644
index 0000000..bd2542c
--- /dev/null
+++ b/tests/manual/rotations/scatterdatamodifier.cpp
@@ -0,0 +1,165 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "scatterdatamodifier.h"
+#include <QtGraphs/qscatterdataproxy.h>
+#include <QtGraphs/qvalue3daxis.h>
+#include <QtGraphs/q3dscene.h>
+#include <QtGraphs/q3dcamera.h>
+#include <QtGraphs/qscatter3dseries.h>
+#include <QtGraphs/q3dtheme.h>
+#include <QtGraphs/QCustom3DItem>
+#include <QtCore/qmath.h>
+
+static const float verticalRange = 8.0f;
+static const float horizontalRange = verticalRange;
+static const float ellipse_a = horizontalRange / 3.0f;
+static const float ellipse_b = verticalRange;
+static const float doublePi = float(M_PI) * 2.0f;
+static const float radiansToDegrees = 360.0f / doublePi;
+static const float animationFrames = 30.0f;
+
+ScatterDataModifier::ScatterDataModifier(Q3DScatter *scatter)
+ : m_graph(scatter),
+ m_fieldLines(12),
+ m_arrowsPerLine(16),
+ m_magneticField(new QScatter3DSeries),
+ m_sun(new QCustom3DItem),
+ m_magneticFieldArray(0),
+ m_angleOffset(0.0f),
+ m_angleStep(doublePi / m_arrowsPerLine / animationFrames)
+{
+ m_graph->setShadowQuality(QAbstract3DGraph::ShadowQualityNone);
+ m_graph->scene()->activeCamera()->setCameraPreset(Q3DCamera::CameraPresetFront);
+
+ // Magnetic field lines use custom narrow arrow
+ m_magneticField->setItemSize(0.2f);
+ //! [3]
+ m_magneticField->setMesh(QAbstract3DSeries::MeshUserDefined);
+ m_magneticField->setUserDefinedMesh(QStringLiteral(":/mesh/narrowarrow.obj"));
+ //! [3]
+ //! [4]
+ QLinearGradient fieldGradient(0, 0, 16, 1024);
+ fieldGradient.setColorAt(0.0, Qt::black);
+ fieldGradient.setColorAt(1.0, Qt::white);
+ m_magneticField->setBaseGradient(fieldGradient);
+ m_magneticField->setColorStyle(Q3DTheme::ColorStyleRangeGradient);
+ //! [4]
+
+ // For 'sun' we use a custom large sphere
+ m_sun->setScaling(QVector3D(0.07f, 0.07f, 0.07f));
+ m_sun->setMeshFile(QStringLiteral(":/mesh/largesphere.obj"));
+ QImage sunColor = QImage(2, 2, QImage::Format_RGB32);
+ sunColor.fill(QColor(0xff, 0xbb, 0x00));
+ m_sun->setTextureImage(sunColor);
+
+ m_graph->addSeries(m_magneticField);
+ m_graph->addCustomItem(m_sun);
+
+ // Configure the axes according to the data
+ m_graph->axisX()->setRange(-horizontalRange, horizontalRange);
+ m_graph->axisY()->setRange(-verticalRange, verticalRange);
+ m_graph->axisZ()->setRange(-horizontalRange, horizontalRange);
+ m_graph->axisX()->setSegmentCount(int(horizontalRange));
+ m_graph->axisZ()->setSegmentCount(int(horizontalRange));
+
+ QObject::connect(&m_rotationTimer, &QTimer::timeout, this,
+ &ScatterDataModifier::triggerRotation);
+
+ toggleRotation();
+ generateData();
+}
+
+ScatterDataModifier::~ScatterDataModifier()
+{
+ delete m_graph;
+}
+
+void ScatterDataModifier::generateData()
+{
+ // Reusing existing array is computationally cheaper than always generating new array, even if
+ // all data items change in the array, if the array size doesn't change.
+ if (!m_magneticFieldArray)
+ m_magneticFieldArray = new QScatterDataArray;
+
+ int arraySize = m_fieldLines * m_arrowsPerLine;
+ if (arraySize != m_magneticFieldArray->size())
+ m_magneticFieldArray->resize(arraySize);
+
+ QScatterDataItem *ptrToDataArray = &m_magneticFieldArray->first();
+
+ for (float i = 0; i < m_fieldLines; i++) {
+ float horizontalAngle = (doublePi * i) / m_fieldLines;
+ float xCenter = ellipse_a * qCos(horizontalAngle);
+ float zCenter = ellipse_a * qSin(horizontalAngle);
+
+ // Rotate - arrow always tangential to origin
+ //! [0]
+ QQuaternion yRotation = QQuaternion::fromAxisAndAngle(0.0f, 1.0f, 0.0f, horizontalAngle * radiansToDegrees);
+ //! [0]
+
+ for (float j = 0; j < m_arrowsPerLine; j++) {
+ // Calculate point on ellipse centered on origin and parallel to x-axis
+ float verticalAngle = ((doublePi * j) / m_arrowsPerLine) + m_angleOffset;
+ float xUnrotated = ellipse_a * qCos(verticalAngle);
+ float y = ellipse_b * qSin(verticalAngle);
+
+ // Rotate the ellipse around y-axis
+ float xRotated = xUnrotated * qCos(horizontalAngle);
+ float zRotated = xUnrotated * qSin(horizontalAngle);
+
+ // Add offset
+ float x = xCenter + xRotated;
+ float z = zCenter + zRotated;
+
+ //! [1]
+ QQuaternion zRotation = QQuaternion::fromAxisAndAngle(0.0f, 0.0f, 1.0f, verticalAngle * radiansToDegrees);
+ QQuaternion totalRotation = yRotation * zRotation;
+ //! [1]
+
+ ptrToDataArray->setPosition(QVector3D(x, y, z));
+ //! [2]
+ ptrToDataArray->setRotation(totalRotation);
+ //! [2]
+ ptrToDataArray++;
+ }
+ }
+
+ if (m_graph->selectedSeries() == m_magneticField)
+ m_graph->clearSelection();
+
+ m_magneticField->dataProxy()->resetArray(m_magneticFieldArray);
+}
+
+void ScatterDataModifier::setFieldLines(int lines)
+{
+ m_fieldLines = lines;
+ generateData();
+}
+
+void ScatterDataModifier::setArrowsPerLine(int arrows)
+{
+ m_angleOffset = 0.0f;
+ m_angleStep = doublePi / m_arrowsPerLine / animationFrames;
+ m_arrowsPerLine = arrows;
+ generateData();
+}
+
+void ScatterDataModifier::triggerRotation()
+{
+ m_angleOffset += m_angleStep;
+ generateData();
+}
+
+void ScatterDataModifier::toggleSun()
+{
+ m_sun->setVisible(!m_sun->isVisible());
+}
+
+void ScatterDataModifier::toggleRotation()
+{
+ if (m_rotationTimer.isActive())
+ m_rotationTimer.stop();
+ else
+ m_rotationTimer.start(15);
+}
diff --git a/tests/manual/rotations/scatterdatamodifier.h b/tests/manual/rotations/scatterdatamodifier.h
new file mode 100644
index 0000000..ae0e3b7
--- /dev/null
+++ b/tests/manual/rotations/scatterdatamodifier.h
@@ -0,0 +1,39 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef SCATTERDATAMODIFIER_H
+#define SCATTERDATAMODIFIER_H
+
+#include <QtGraphs/q3dscatter.h>
+#include <QtGraphs/qscatterdataproxy.h>
+#include <QtCore/QTimer>
+
+class ScatterDataModifier : public QObject
+{
+ Q_OBJECT
+public:
+ explicit ScatterDataModifier(Q3DScatter *scatter);
+ ~ScatterDataModifier();
+
+ void generateData();
+
+public Q_SLOTS:
+ void setFieldLines(int lines);
+ void setArrowsPerLine(int arrows);
+ void toggleRotation();
+ void triggerRotation();
+ void toggleSun();
+
+private:
+ Q3DScatter *m_graph;
+ QTimer m_rotationTimer;
+ int m_fieldLines;
+ int m_arrowsPerLine;
+ QScatter3DSeries *m_magneticField;
+ QCustom3DItem *m_sun;
+ QScatterDataArray *m_magneticFieldArray;
+ float m_angleOffset;
+ float m_angleStep;
+};
+
+#endif
diff --git a/tests/manual/scattertest/CMakeLists.txt b/tests/manual/scattertest/CMakeLists.txt
new file mode 100644
index 0000000..e38c659
--- /dev/null
+++ b/tests/manual/scattertest/CMakeLists.txt
@@ -0,0 +1,16 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+qt_internal_add_manual_test(scattertest
+ GUI
+ SOURCES
+ main.cpp
+ scatterchart.cpp scatterchart.h
+ )
+target_link_libraries(scattertest PUBLIC
+ Qt::Gui
+ Qt::Widgets
+ Qt::Graphs
+ )
diff --git a/tests/manual/scattertest/main.cpp b/tests/manual/scattertest/main.cpp
new file mode 100644
index 0000000..c2458bd
--- /dev/null
+++ b/tests/manual/scattertest/main.cpp
@@ -0,0 +1,479 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "scatterchart.h"
+
+#include <QApplication>
+#include <QWidget>
+#include <QHBoxLayout>
+#include <QVBoxLayout>
+#include <QPushButton>
+#include <QCheckBox>
+#include <QSlider>
+#include <QComboBox>
+#include <QFontComboBox>
+#include <QLabel>
+#include <QScreen>
+#include <QFontDatabase>
+#include <QLinearGradient>
+#include <QPainter>
+
+int main(int argc, char **argv)
+{
+ qputenv("QSG_RHI_BACKEND", "opengl");
+ QApplication app(argc, argv);
+ //QCoreApplication::setAttribute(Qt::AA_UseOpenGLES);
+
+ QWidget *widget = new QWidget;
+ QHBoxLayout *hLayout = new QHBoxLayout(widget);
+ QVBoxLayout *vLayout = new QVBoxLayout();
+ QVBoxLayout *vLayout2 = new QVBoxLayout();
+ QVBoxLayout *vLayout3 = new QVBoxLayout();
+
+ Q3DScatter *chart = new Q3DScatter();
+ QSize screenSize = chart->screen()->size();
+
+ QWidget *container = QWidget::createWindowContainer(chart);
+ container->setMinimumSize(QSize(screenSize.width() / 2, screenSize.height() / 2));
+ container->setMaximumSize(screenSize);
+ container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+ container->setFocusPolicy(Qt::StrongFocus);
+
+ widget->setWindowTitle(QStringLiteral("values of some things in something"));
+
+ hLayout->addWidget(container, 1);
+ hLayout->addLayout(vLayout);
+ hLayout->addLayout(vLayout2);
+ hLayout->addLayout(vLayout3);
+
+ QPushButton *themeButton = new QPushButton(widget);
+ themeButton->setText(QStringLiteral("Change theme"));
+
+ QPushButton *labelButton = new QPushButton(widget);
+ labelButton->setText(QStringLiteral("Change label style"));
+
+ QPushButton *styleButton = new QPushButton(widget);
+ styleButton->setText(QStringLiteral("Change item style"));
+
+ QPushButton *cameraButton = new QPushButton(widget);
+ cameraButton->setText(QStringLiteral("Change camera preset"));
+
+ QPushButton *clearButton = new QPushButton(widget);
+ clearButton->setText(QStringLiteral("Clear chart"));
+
+ QPushButton *resetButton = new QPushButton(widget);
+ resetButton->setText(QStringLiteral("Reset axes"));
+
+ QPushButton *addOneButton = new QPushButton(widget);
+ addOneButton->setText(QStringLiteral("Add item"));
+
+ QPushButton *addBunchButton = new QPushButton(widget);
+ addBunchButton->setText(QStringLiteral("Add bunch of items"));
+
+ QPushButton *insertOneButton = new QPushButton(widget);
+ insertOneButton->setText(QStringLiteral("Insert item"));
+
+ QPushButton *insertBunchButton = new QPushButton(widget);
+ insertBunchButton->setText(QStringLiteral("Insert bunch of items"));
+
+ QPushButton *changeOneButton = new QPushButton(widget);
+ changeOneButton->setText(QStringLiteral("Change selected item"));
+
+ QPushButton *changeBunchButton = new QPushButton(widget);
+ changeBunchButton->setText(QStringLiteral("Change bunch of items"));
+
+ QPushButton *removeOneButton = new QPushButton(widget);
+ removeOneButton->setText(QStringLiteral("Remove selected item"));
+
+ QPushButton *removeBunchButton = new QPushButton(widget);
+ removeBunchButton->setText(QStringLiteral("Remove bunch of items"));
+
+ QPushButton *setSelectedItemButton = new QPushButton(widget);
+ setSelectedItemButton->setText(QStringLiteral("Select/deselect item 3"));
+
+ QPushButton *clearSeriesDataButton = new QPushButton(widget);
+ clearSeriesDataButton->setText(QStringLiteral("Clear series data"));
+
+ QPushButton *addSeriesButton = new QPushButton(widget);
+ addSeriesButton->setText(QStringLiteral("Add Series"));
+
+ QPushButton *removeSeriesButton = new QPushButton(widget);
+ removeSeriesButton->setText(QStringLiteral("Remove Series"));
+
+ QPushButton *toggleSeriesVisibilityButton = new QPushButton(widget);
+ toggleSeriesVisibilityButton->setText(QStringLiteral("Toggle visibility"));
+
+ QPushButton *changeSeriesNameButton = new QPushButton(widget);
+ changeSeriesNameButton->setText(QStringLiteral("Series name"));
+
+ QPushButton *startTimerButton = new QPushButton(widget);
+ startTimerButton->setText(QStringLiteral("Start/stop timer"));
+
+ QPushButton *massiveDataTestButton = new QPushButton(widget);
+ massiveDataTestButton->setText(QStringLiteral("Massive data test"));
+
+ QPushButton *testItemChangesButton = new QPushButton(widget);
+ testItemChangesButton->setText(QStringLiteral("Test Item changing"));
+
+ QPushButton *testReverseButton = new QPushButton(widget);
+ testReverseButton->setText(QStringLiteral("Test Axis Reversing"));
+
+ QPushButton *renderToImageButton = new QPushButton(widget);
+ renderToImageButton->setText(QStringLiteral("Render the graph to an image"));
+
+ QLinearGradient grBtoY(0, 0, 100, 0);
+ grBtoY.setColorAt(1.0, Qt::black);
+ grBtoY.setColorAt(0.67, Qt::blue);
+ grBtoY.setColorAt(0.33, Qt::red);
+ grBtoY.setColorAt(0.0, Qt::yellow);
+ QPixmap pm(100, 24);
+ QPainter pmp(&pm);
+ pmp.setBrush(QBrush(grBtoY));
+ pmp.setPen(Qt::NoPen);
+ pmp.drawRect(0, 0, 100, 24);
+ QPushButton *gradientBtoYPB = new QPushButton(widget);
+ gradientBtoYPB->setIcon(QIcon(pm));
+ gradientBtoYPB->setIconSize(QSize(100, 24));
+
+ QLabel *fpsLabel = new QLabel(QStringLiteral(""));
+
+ QCheckBox *fpsCheckBox = new QCheckBox(widget);
+ fpsCheckBox->setText(QStringLiteral("Measure Fps"));
+ fpsCheckBox->setChecked(false);
+
+ QCheckBox *backgroundCheckBox = new QCheckBox(widget);
+ backgroundCheckBox->setText(QStringLiteral("Show background"));
+ backgroundCheckBox->setChecked(true);
+
+ QCheckBox *gridCheckBox = new QCheckBox(widget);
+ gridCheckBox->setText(QStringLiteral("Show grid"));
+ gridCheckBox->setChecked(true);
+
+ QComboBox *shadowQuality = new QComboBox(widget);
+ shadowQuality->addItem(QStringLiteral("None"));
+ shadowQuality->addItem(QStringLiteral("Low"));
+ shadowQuality->addItem(QStringLiteral("Medium"));
+ shadowQuality->addItem(QStringLiteral("High"));
+ shadowQuality->addItem(QStringLiteral("Low Soft"));
+ shadowQuality->addItem(QStringLiteral("Medium Soft"));
+ shadowQuality->addItem(QStringLiteral("High Soft"));
+ shadowQuality->setCurrentIndex(0);
+
+ QFontComboBox *fontList = new QFontComboBox(widget);
+
+ QSlider *fontSizeSlider = new QSlider(Qt::Horizontal, widget);
+ fontSizeSlider->setTickInterval(15);
+ fontSizeSlider->setTickPosition(QSlider::TicksBelow);
+ fontSizeSlider->setMinimum(1);
+ fontSizeSlider->setValue(30);
+ fontSizeSlider->setMaximum(200);
+
+ QSlider *pointSizeSlider = new QSlider(Qt::Horizontal, widget);
+ pointSizeSlider->setTickInterval(15);
+ pointSizeSlider->setTickPosition(QSlider::TicksBelow);
+ pointSizeSlider->setMinimum(1);
+ pointSizeSlider->setValue(30);
+ pointSizeSlider->setMaximum(100);
+
+ QSlider *minSliderX = new QSlider(Qt::Horizontal, widget);
+ minSliderX->setTickInterval(50);
+ minSliderX->setTickPosition(QSlider::TicksBelow);
+ minSliderX->setMinimum(-100);
+ minSliderX->setValue(-50);
+ minSliderX->setMaximum(100);
+
+ QSlider *minSliderY = new QSlider(Qt::Horizontal, widget);
+ minSliderY->setTickInterval(100);
+ minSliderY->setTickPosition(QSlider::TicksBelow);
+ minSliderY->setMinimum(-200);
+ minSliderY->setValue(-100);
+ minSliderY->setMaximum(200);
+
+ QSlider *minSliderZ = new QSlider(Qt::Horizontal, widget);
+ minSliderZ->setTickInterval(50);
+ minSliderZ->setTickPosition(QSlider::TicksBelow);
+ minSliderZ->setMinimum(-100);
+ minSliderZ->setValue(-50);
+ minSliderZ->setMaximum(100);
+
+ QSlider *maxSliderX = new QSlider(Qt::Horizontal, widget);
+ maxSliderX->setTickInterval(50);
+ maxSliderX->setTickPosition(QSlider::TicksAbove);
+ maxSliderX->setMinimum(-100);
+ maxSliderX->setValue(50);
+ maxSliderX->setMaximum(100);
+
+ QSlider *maxSliderY = new QSlider(Qt::Horizontal, widget);
+ maxSliderY->setTickInterval(100);
+ maxSliderY->setTickPosition(QSlider::TicksAbove);
+ maxSliderY->setMinimum(-200);
+ maxSliderY->setValue(120);
+ maxSliderY->setMaximum(200);
+
+ QSlider *maxSliderZ = new QSlider(Qt::Horizontal, widget);
+ maxSliderZ->setTickInterval(50);
+ maxSliderZ->setTickPosition(QSlider::TicksAbove);
+ maxSliderZ->setMinimum(-100);
+ maxSliderZ->setValue(50);
+ maxSliderZ->setMaximum(100);
+
+ QSlider *aspectRatioSlider = new QSlider(Qt::Horizontal, widget);
+ aspectRatioSlider->setTickInterval(10);
+ aspectRatioSlider->setTickPosition(QSlider::TicksBelow);
+ aspectRatioSlider->setMinimum(1);
+ aspectRatioSlider->setValue(20);
+ aspectRatioSlider->setMaximum(100);
+
+ QSlider *horizontalAspectRatioSlider = new QSlider(Qt::Horizontal, widget);
+ horizontalAspectRatioSlider->setTickInterval(30);
+ horizontalAspectRatioSlider->setTickPosition(QSlider::TicksBelow);
+ horizontalAspectRatioSlider->setMinimum(0);
+ horizontalAspectRatioSlider->setValue(0);
+ horizontalAspectRatioSlider->setMaximum(300);
+
+ QCheckBox *optimizationStaticCB = new QCheckBox(widget);
+ optimizationStaticCB->setText(QStringLiteral("Static optimization"));
+ optimizationStaticCB->setChecked(false);
+
+ QCheckBox *orthoCB = new QCheckBox(widget);
+ orthoCB->setText(QStringLiteral("Orthographic projection"));
+ orthoCB->setChecked(false);
+
+ QCheckBox *polarCB = new QCheckBox(widget);
+ polarCB->setText(QStringLiteral("Polar graph"));
+ polarCB->setChecked(false);
+
+ QCheckBox *axisTitlesVisibleCB = new QCheckBox(widget);
+ axisTitlesVisibleCB->setText(QStringLiteral("Axis titles visible"));
+ axisTitlesVisibleCB->setChecked(false);
+
+ QCheckBox *axisTitlesFixedCB = new QCheckBox(widget);
+ axisTitlesFixedCB->setText(QStringLiteral("Axis titles fixed"));
+ axisTitlesFixedCB->setChecked(true);
+
+ QSlider *axisLabelRotationSlider = new QSlider(Qt::Horizontal, widget);
+ axisLabelRotationSlider->setTickInterval(10);
+ axisLabelRotationSlider->setTickPosition(QSlider::TicksBelow);
+ axisLabelRotationSlider->setMinimum(0);
+ axisLabelRotationSlider->setValue(0);
+ axisLabelRotationSlider->setMaximum(90);
+
+ QSlider *radialLabelSlider = new QSlider(Qt::Horizontal, widget);
+ radialLabelSlider->setTickInterval(10);
+ radialLabelSlider->setTickPosition(QSlider::TicksBelow);
+ radialLabelSlider->setMinimum(0);
+ radialLabelSlider->setValue(100);
+ radialLabelSlider->setMaximum(150);
+
+ QSlider *cameraTargetSliderX = new QSlider(Qt::Horizontal, widget);
+ cameraTargetSliderX->setTickInterval(1);
+ cameraTargetSliderX->setMinimum(-100);
+ cameraTargetSliderX->setValue(0);
+ cameraTargetSliderX->setMaximum(100);
+ QSlider *cameraTargetSliderY = new QSlider(Qt::Horizontal, widget);
+ cameraTargetSliderY->setTickInterval(1);
+ cameraTargetSliderY->setMinimum(-100);
+ cameraTargetSliderY->setValue(0);
+ cameraTargetSliderY->setMaximum(100);
+ QSlider *cameraTargetSliderZ = new QSlider(Qt::Horizontal, widget);
+ cameraTargetSliderZ->setTickInterval(1);
+ cameraTargetSliderZ->setMinimum(-100);
+ cameraTargetSliderZ->setValue(0);
+ cameraTargetSliderZ->setMaximum(100);
+
+ QSlider *marginSlider = new QSlider(Qt::Horizontal, widget);
+ marginSlider->setMinimum(-1);
+ marginSlider->setValue(-1);
+ marginSlider->setMaximum(100);
+
+ vLayout->addWidget(themeButton, 0, Qt::AlignTop);
+ vLayout->addWidget(labelButton, 0, Qt::AlignTop);
+ vLayout->addWidget(styleButton, 0, Qt::AlignTop);
+ vLayout->addWidget(cameraButton, 0, Qt::AlignTop);
+ vLayout->addWidget(clearButton, 0, Qt::AlignTop);
+ vLayout->addWidget(resetButton, 0, Qt::AlignTop);
+ vLayout->addWidget(addOneButton, 0, Qt::AlignTop);
+ vLayout->addWidget(addBunchButton, 0, Qt::AlignTop);
+ vLayout->addWidget(insertOneButton, 0, Qt::AlignTop);
+ vLayout->addWidget(insertBunchButton, 0, Qt::AlignTop);
+ vLayout->addWidget(changeOneButton, 0, Qt::AlignTop);
+ vLayout->addWidget(changeBunchButton, 0, Qt::AlignTop);
+ vLayout->addWidget(removeOneButton, 0, Qt::AlignTop);
+ vLayout->addWidget(removeBunchButton, 0, Qt::AlignTop);
+ vLayout->addWidget(setSelectedItemButton, 0, Qt::AlignTop);
+ vLayout->addWidget(clearSeriesDataButton, 0, Qt::AlignTop);
+ vLayout->addWidget(addSeriesButton, 0, Qt::AlignTop);
+ vLayout->addWidget(removeSeriesButton, 0, Qt::AlignTop);
+ vLayout->addWidget(toggleSeriesVisibilityButton, 0, Qt::AlignTop);
+ vLayout->addWidget(changeSeriesNameButton, 0, Qt::AlignTop);
+ vLayout->addWidget(startTimerButton, 0, Qt::AlignTop);
+ vLayout->addWidget(massiveDataTestButton, 0, Qt::AlignTop);
+ vLayout->addWidget(testItemChangesButton, 0, Qt::AlignTop);
+ vLayout->addWidget(testReverseButton, 0, Qt::AlignTop);
+ vLayout->addWidget(renderToImageButton, 1, Qt::AlignTop);
+
+ vLayout2->addWidget(gradientBtoYPB, 0, Qt::AlignTop);
+ vLayout2->addWidget(fpsLabel, 0, Qt::AlignTop);
+ vLayout2->addWidget(fpsCheckBox, 0, Qt::AlignTop);
+ vLayout2->addWidget(backgroundCheckBox);
+ vLayout2->addWidget(gridCheckBox);
+ vLayout2->addWidget(new QLabel(QStringLiteral("Adjust shadow quality")));
+ vLayout2->addWidget(shadowQuality, 0, Qt::AlignTop);
+ vLayout2->addWidget(new QLabel(QStringLiteral("Adjust point size")));
+ vLayout2->addWidget(pointSizeSlider, 0, Qt::AlignTop);
+ vLayout2->addWidget(new QLabel(QStringLiteral("Adjust data window")));
+ vLayout2->addWidget(minSliderX, 0, Qt::AlignTop);
+ vLayout2->addWidget(maxSliderX, 0, Qt::AlignTop);
+ vLayout2->addWidget(minSliderY, 0, Qt::AlignTop);
+ vLayout2->addWidget(maxSliderY, 0, Qt::AlignTop);
+ vLayout2->addWidget(minSliderZ, 0, Qt::AlignTop);
+ vLayout2->addWidget(maxSliderZ, 0, Qt::AlignTop);
+ vLayout2->addWidget(new QLabel(QStringLiteral("Change font")));
+ vLayout2->addWidget(fontList);
+ vLayout2->addWidget(new QLabel(QStringLiteral("Adjust font size")));
+ vLayout2->addWidget(fontSizeSlider);
+ vLayout2->addWidget(new QLabel(QStringLiteral("Adjust vertical aspect ratio")));
+ vLayout2->addWidget(aspectRatioSlider);
+ vLayout2->addWidget(new QLabel(QStringLiteral("Adjust horizontal aspect ratio")));
+ vLayout2->addWidget(horizontalAspectRatioSlider, 1, Qt::AlignTop);
+
+ vLayout3->addWidget(optimizationStaticCB);
+ vLayout3->addWidget(orthoCB);
+ vLayout3->addWidget(polarCB);
+ vLayout3->addWidget(axisTitlesVisibleCB);
+ vLayout3->addWidget(axisTitlesFixedCB);
+ vLayout3->addWidget(new QLabel(QStringLiteral("Axis label rotation")));
+ vLayout3->addWidget(axisLabelRotationSlider);
+ vLayout3->addWidget(new QLabel(QStringLiteral("Radial label offset")));
+ vLayout3->addWidget(radialLabelSlider, 0, Qt::AlignTop);
+ vLayout3->addWidget(new QLabel(QStringLiteral("Camera target")), 0, Qt::AlignTop);
+ vLayout3->addWidget(cameraTargetSliderX, 0, Qt::AlignTop);
+ vLayout3->addWidget(cameraTargetSliderY, 0, Qt::AlignTop);
+ vLayout3->addWidget(cameraTargetSliderZ, 0, Qt::AlignTop);
+ vLayout3->addWidget(new QLabel(QStringLiteral("Adjust margin")), 0, Qt::AlignTop);
+ vLayout3->addWidget(marginSlider, 1, Qt::AlignTop);
+
+ ScatterDataModifier *modifier = new ScatterDataModifier(chart);
+
+ QObject::connect(fontSizeSlider, &QSlider::valueChanged, modifier,
+ &ScatterDataModifier::changeFontSize);
+ QObject::connect(pointSizeSlider, &QSlider::valueChanged, modifier,
+ &ScatterDataModifier::changePointSize);
+
+ QObject::connect(styleButton, &QPushButton::clicked, modifier,
+ &ScatterDataModifier::changeStyle);
+ QObject::connect(cameraButton, &QPushButton::clicked, modifier,
+ &ScatterDataModifier::changePresetCamera);
+ QObject::connect(clearButton, &QPushButton::clicked, modifier,
+ &ScatterDataModifier::clear);
+ QObject::connect(resetButton, &QPushButton::clicked, modifier,
+ &ScatterDataModifier::resetAxes);
+ QObject::connect(addOneButton, &QPushButton::clicked, modifier,
+ &ScatterDataModifier::addOne);
+ QObject::connect(addBunchButton, &QPushButton::clicked, modifier,
+ &ScatterDataModifier::addBunch);
+ QObject::connect(insertOneButton, &QPushButton::clicked, modifier,
+ &ScatterDataModifier::insertOne);
+ QObject::connect(insertBunchButton, &QPushButton::clicked, modifier,
+ &ScatterDataModifier::insertBunch);
+ QObject::connect(changeOneButton, &QPushButton::clicked, modifier,
+ &ScatterDataModifier::changeOne);
+ QObject::connect(changeBunchButton, &QPushButton::clicked, modifier,
+ &ScatterDataModifier::changeBunch);
+ QObject::connect(removeOneButton, &QPushButton::clicked, modifier,
+ &ScatterDataModifier::removeOne);
+ QObject::connect(removeBunchButton, &QPushButton::clicked, modifier,
+ &ScatterDataModifier::removeBunch);
+ QObject::connect(setSelectedItemButton, &QPushButton::clicked, modifier,
+ &ScatterDataModifier::selectItem);
+ QObject::connect(clearSeriesDataButton, &QPushButton::clicked, modifier,
+ &ScatterDataModifier::clearSeriesData);
+ QObject::connect(addSeriesButton, &QPushButton::clicked, modifier,
+ &ScatterDataModifier::addSeries);
+ QObject::connect(removeSeriesButton, &QPushButton::clicked, modifier,
+ &ScatterDataModifier::removeSeries);
+ QObject::connect(toggleSeriesVisibilityButton, &QPushButton::clicked, modifier,
+ &ScatterDataModifier::toggleSeriesVisibility);
+ QObject::connect(changeSeriesNameButton, &QPushButton::clicked, modifier,
+ &ScatterDataModifier::changeSeriesName);
+ QObject::connect(startTimerButton, &QPushButton::clicked, modifier,
+ &ScatterDataModifier::startStopTimer);
+ QObject::connect(massiveDataTestButton, &QPushButton::clicked, modifier,
+ &ScatterDataModifier::massiveDataTest);
+ QObject::connect(testItemChangesButton, &QPushButton::clicked, modifier,
+ &ScatterDataModifier::testItemChanges);
+ QObject::connect(testReverseButton, &QPushButton::clicked, modifier,
+ &ScatterDataModifier::testAxisReverse);
+ QObject::connect(renderToImageButton, &QPushButton::clicked, modifier,
+ &ScatterDataModifier::renderToImage);
+ QObject::connect(gradientBtoYPB, &QPushButton::clicked, modifier,
+ &ScatterDataModifier::setGradient);
+ QObject::connect(themeButton, &QPushButton::clicked, modifier,
+ &ScatterDataModifier::changeTheme);
+ QObject::connect(labelButton, &QPushButton::clicked, modifier,
+ &ScatterDataModifier::changeLabelStyle);
+
+ QObject::connect(shadowQuality, SIGNAL(currentIndexChanged(int)), modifier,
+ SLOT(changeShadowQuality(int)));
+ QObject::connect(modifier, &ScatterDataModifier::shadowQualityChanged, shadowQuality,
+ &QComboBox::setCurrentIndex);
+ QObject::connect(fontList, &QFontComboBox::currentFontChanged, modifier,
+ &ScatterDataModifier::changeFont);
+
+ QObject::connect(fpsCheckBox, &QCheckBox::stateChanged, modifier,
+ &ScatterDataModifier::setFpsMeasurement);
+ QObject::connect(backgroundCheckBox, &QCheckBox::stateChanged, modifier,
+ &ScatterDataModifier::setBackgroundEnabled);
+ QObject::connect(gridCheckBox, &QCheckBox::stateChanged, modifier,
+ &ScatterDataModifier::setGridEnabled);
+
+ QObject::connect(minSliderX, &QSlider::valueChanged, modifier,
+ &ScatterDataModifier::setMinX);
+ QObject::connect(minSliderY, &QSlider::valueChanged, modifier,
+ &ScatterDataModifier::setMinY);
+ QObject::connect(minSliderZ, &QSlider::valueChanged, modifier,
+ &ScatterDataModifier::setMinZ);
+ QObject::connect(maxSliderX, &QSlider::valueChanged, modifier,
+ &ScatterDataModifier::setMaxX);
+ QObject::connect(maxSliderY, &QSlider::valueChanged, modifier,
+ &ScatterDataModifier::setMaxY);
+ QObject::connect(maxSliderZ, &QSlider::valueChanged, modifier,
+ &ScatterDataModifier::setMaxZ);
+ QObject::connect(optimizationStaticCB, &QCheckBox::stateChanged, modifier,
+ &ScatterDataModifier::toggleStatic);
+ QObject::connect(orthoCB, &QCheckBox::stateChanged, modifier,
+ &ScatterDataModifier::toggleOrtho);
+ QObject::connect(polarCB, &QCheckBox::stateChanged, modifier,
+ &ScatterDataModifier::togglePolar);
+ QObject::connect(axisTitlesVisibleCB, &QCheckBox::stateChanged, modifier,
+ &ScatterDataModifier::toggleAxisTitleVisibility);
+ QObject::connect(axisTitlesFixedCB, &QCheckBox::stateChanged, modifier,
+ &ScatterDataModifier::toggleAxisTitleFixed);
+ QObject::connect(axisLabelRotationSlider, &QSlider::valueChanged, modifier,
+ &ScatterDataModifier::changeLabelRotation);
+ QObject::connect(aspectRatioSlider, &QSlider::valueChanged, modifier,
+ &ScatterDataModifier::setAspectRatio);
+ QObject::connect(horizontalAspectRatioSlider, &QSlider::valueChanged, modifier,
+ &ScatterDataModifier::setHorizontalAspectRatio);
+ QObject::connect(radialLabelSlider, &QSlider::valueChanged, modifier,
+ &ScatterDataModifier::changeRadialLabelOffset);
+ QObject::connect(cameraTargetSliderX, &QSlider::valueChanged, modifier,
+ &ScatterDataModifier::setCameraTargetX);
+ QObject::connect(cameraTargetSliderY, &QSlider::valueChanged, modifier,
+ &ScatterDataModifier::setCameraTargetY);
+ QObject::connect(cameraTargetSliderZ, &QSlider::valueChanged, modifier,
+ &ScatterDataModifier::setCameraTargetZ);
+ QObject::connect(marginSlider, &QSlider::valueChanged, modifier,
+ &ScatterDataModifier::setGraphMargin);
+
+ modifier->setFpsLabel(fpsLabel);
+
+ chart->setGeometry(QRect(0, 0, 800, 800));
+
+ modifier->start();
+ //modifier->renderToImage(); // Initial hidden render
+
+ widget->show();
+
+ return app.exec();
+}
diff --git a/tests/manual/scattertest/scatterchart.cpp b/tests/manual/scattertest/scatterchart.cpp
new file mode 100644
index 0000000..303ead2
--- /dev/null
+++ b/tests/manual/scattertest/scatterchart.cpp
@@ -0,0 +1,1154 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "scatterchart.h"
+#include <QtGraphs/qscatterdataproxy.h>
+#include <QtGraphs/qscatter3dseries.h>
+#include <QtGraphs/qvalue3daxis.h>
+#include <QtGraphs/q3dscene.h>
+#include <QtGraphs/q3dcamera.h>
+#include <QtGraphs/q3dtheme.h>
+#include <QtGraphs/Q3DInputHandler>
+#include <qmath.h>
+#include <qrandom.h>
+
+//#define RANDOM_SCATTER
+
+const int numberOfItems = 10000;
+
+ScatterDataModifier::ScatterDataModifier(Q3DScatter *scatter)
+ : m_chart(scatter),
+ m_fontSize(30.0f),
+ m_loopCounter(0),
+ m_selectedItem(-1),
+ m_targetSeries(0)
+{
+ m_chart->activeTheme()->setType(Q3DTheme::ThemeStoneMoss);
+ QFont font = m_chart->activeTheme()->font();
+ font.setPointSize(m_fontSize);
+ m_chart->activeTheme()->setFont(font);
+ m_chart->setShadowQuality(QAbstract3DGraph::ShadowQualityNone);
+ m_chart->scene()->activeCamera()->setCameraPreset(Q3DCamera::CameraPresetFront);
+ m_chart->setAxisX(new QValue3DAxis);
+ m_chart->setAxisY(new QValue3DAxis);
+ m_chart->setAxisZ(new QValue3DAxis);
+ m_chart->axisY()->setLabelFormat(QStringLiteral("%.7f"));
+ static_cast<Q3DInputHandler *>(m_chart->activeInputHandler())->setZoomAtTargetEnabled(true);
+
+ createAndAddSeries();
+ createAndAddSeries();
+
+ m_chart->setSelectionMode(QAbstract3DGraph::SelectionItem);
+
+ QObject::connect(&m_timer, &QTimer::timeout, this, &ScatterDataModifier::timeout);
+ QObject::connect(m_chart, &Q3DScatter::shadowQualityChanged, this,
+ &ScatterDataModifier::shadowQualityUpdatedByVisual);
+
+ QObject::connect(m_chart, &Q3DScatter::axisXChanged, this,
+ &ScatterDataModifier::handleAxisXChanged);
+ QObject::connect(m_chart, &Q3DScatter::axisYChanged, this,
+ &ScatterDataModifier::handleAxisYChanged);
+ QObject::connect(m_chart, &Q3DScatter::axisZChanged, this,
+ &ScatterDataModifier::handleAxisZChanged);
+ QObject::connect(m_chart, &QAbstract3DGraph::currentFpsChanged, this,
+ &ScatterDataModifier::handleFpsChange);
+}
+
+ScatterDataModifier::~ScatterDataModifier()
+{
+ delete m_chart;
+}
+
+void ScatterDataModifier::start()
+{
+ addData();
+}
+
+static const int itemsPerUnit = 100; // "unit" is one unit range along Z-axis
+
+void ScatterDataModifier::massiveDataTest()
+{
+ static int testPhase = 0;
+ static QTimer *massiveTestTimer = 0;
+
+ if (!massiveTestTimer)
+ massiveTestTimer = new QTimer;
+
+ int items = 1000000;
+ int visibleRange = 200;
+ int unitCount = items / itemsPerUnit;
+ int cacheSize = visibleRange * itemsPerUnit * 5;
+
+ switch (testPhase) {
+ case 0: {
+ float yRangeMin = 0.0f;
+ float yRangeMax = 1.0f;
+ float yRangeMargin = 0.05f;
+ float minY = yRangeMin + yRangeMargin;
+ float maxY = yRangeMax - yRangeMargin;
+ float unitBase = minY;
+ float direction = 1.0f;
+
+ if (!m_massiveTestCacheArray.size()) {
+ m_massiveTestCacheArray.resize(cacheSize);
+ int totalIndex = 0;
+ for (int i = 0; i < unitCount && totalIndex < cacheSize; i++) {
+ unitBase += direction * (QRandomGenerator::global()->bounded(3) / 100.0f);
+ if (unitBase > maxY) {
+ unitBase = maxY;
+ direction = -1.0f;
+ } else if (unitBase < minY) {
+ unitBase = minY;
+ direction = 1.0f;
+ }
+ for (int j = 0; j < itemsPerUnit && totalIndex < cacheSize; j++) {
+ float randFactor = float(QRandomGenerator::global()->bounded(100)) / (100 / yRangeMargin);
+ m_massiveTestCacheArray[totalIndex].setPosition(
+ QVector3D(float(QRandomGenerator::global()->bounded(itemsPerUnit)),
+ unitBase + randFactor, 0.0f));
+ // Z value is irrelevant, we replace it anyway when we take item to use
+ totalIndex++;
+ }
+ }
+ }
+
+ qDebug() << __FUNCTION__ << testPhase << ": Setting the graph up...";
+ QValue3DAxis *xAxis = new QValue3DAxis();
+ QValue3DAxis *yAxis = new QValue3DAxis();
+ QValue3DAxis *zAxis = new QValue3DAxis();
+ xAxis->setRange(0.0f, float(itemsPerUnit - 1));
+ yAxis->setRange(yRangeMin, yRangeMax);
+ zAxis->setRange(0.0f, float(visibleRange - 1));
+ xAxis->setSegmentCount(1);
+ yAxis->setSegmentCount(1);
+ zAxis->setSegmentCount(1);
+ m_chart->setAxisX(xAxis);
+ m_chart->setAxisY(yAxis);
+ m_chart->setAxisZ(zAxis);
+ m_chart->scene()->activeCamera()->setCameraPreset(Q3DCamera::CameraPresetRight);
+ m_chart->setShadowQuality(QAbstract3DGraph::ShadowQualityNone);
+ foreach (QAbstract3DSeries *series, m_chart->seriesList())
+ m_chart->removeSeries(static_cast<QScatter3DSeries *>(series));
+
+ qDebug() << __FUNCTION__ << testPhase << ": Creating massive array..." << items;
+ QScatterDataArray *massiveArray = new QScatterDataArray;
+ massiveArray->resize(items);
+
+ int cacheIndex = 0;
+ for (int i = 0; i < items; i++) {
+ // Use qreals for precicion as the numbers can overflow int
+ float currentZ = float(qreal(i) * qreal(unitCount) / qreal(items));
+ (*massiveArray)[i] = m_massiveTestCacheArray.at(cacheIndex++);
+ (*massiveArray)[i].setZ(currentZ);
+ if (cacheIndex >= cacheSize)
+ cacheIndex = 0;
+ }
+ qDebug() << __FUNCTION__ << testPhase << ": Massive array creation finished!";
+
+ QScatter3DSeries *series = new QScatter3DSeries;
+ series->dataProxy()->resetArray(massiveArray);
+ series->setMesh(QAbstract3DSeries::MeshPoint);
+ m_chart->addSeries(series);
+ break;
+ }
+ case 1: {
+ qDebug() << __FUNCTION__ << testPhase << ": Scroll";
+ QObject::disconnect(massiveTestTimer, 0, this, 0);
+ QObject::connect(massiveTestTimer, &QTimer::timeout, this,
+ &ScatterDataModifier::massiveTestScroll);
+ massiveTestTimer->start(16);
+ break;
+ }
+ case 2: {
+ qDebug() << __FUNCTION__ << testPhase << ": Append and scroll";
+ massiveTestTimer->stop();
+ QObject::disconnect(massiveTestTimer, 0, this, 0);
+ QObject::connect(massiveTestTimer, &QTimer::timeout, this,
+ &ScatterDataModifier::massiveTestAppendAndScroll);
+ m_chart->axisZ()->setRange(unitCount - visibleRange, unitCount);
+ massiveTestTimer->start(16);
+ break;
+ }
+ default:
+ QObject::disconnect(massiveTestTimer, 0, this, 0);
+ massiveTestTimer->stop();
+ qDebug() << __FUNCTION__ << testPhase << ": Resetting the test";
+ testPhase = -1;
+ }
+ testPhase++;
+}
+
+void ScatterDataModifier::massiveTestScroll()
+{
+ const int scrollAmount = 20;
+ int itemCount = m_chart->seriesList().at(0)->dataProxy()->itemCount();
+ int min = m_chart->axisZ()->min() + scrollAmount;
+ int max = m_chart->axisZ()->max() + scrollAmount;
+ if (max >= itemCount / itemsPerUnit) {
+ max = max - min - 1;
+ min = 0;
+ }
+ m_chart->axisZ()->setRange(min, max);
+}
+
+void ScatterDataModifier::massiveTestAppendAndScroll()
+{
+ const int addedUnits = 50;
+ const int addedItems = itemsPerUnit * addedUnits;
+ int cacheSize = m_massiveTestCacheArray.size();
+ int itemCount = m_chart->seriesList().at(0)->dataProxy()->itemCount();
+ static int cacheIndex = 0;
+
+ // Copy items from cache
+ QScatterDataArray appendArray;
+ appendArray.resize(addedItems);
+
+ float zOffset = m_chart->seriesList().at(0)->dataProxy()->itemAt(itemCount - 1)->z();
+ for (int i = 0; i < addedItems; i++) {
+ float currentZ = zOffset + float(qreal(i) * qreal(addedUnits) / qreal(addedItems));
+ appendArray[i] = m_massiveTestCacheArray.at(cacheIndex++);
+ appendArray[i].setZ(currentZ);
+ if (cacheIndex >= cacheSize)
+ cacheIndex = 0;
+ }
+
+ m_chart->seriesList().at(0)->dataProxy()->addItems(appendArray);
+ int min = m_chart->axisZ()->min() + addedUnits;
+ int max = m_chart->axisZ()->max() + addedUnits;
+ m_chart->axisZ()->setRange(min, max);
+}
+
+void ScatterDataModifier::setFpsMeasurement(int enable)
+{
+ m_chart->setMeasureFps(enable);
+}
+
+void ScatterDataModifier::testItemChanges()
+{
+ static int counter = 0;
+ const int rowCount = 12;
+ const int colCount = 10;
+ static QScatter3DSeries *series0 = 0;
+ static QScatter3DSeries *series1 = 0;
+ static QScatter3DSeries *series2 = 0;
+
+ switch (counter) {
+ case 0: {
+ qDebug() << __FUNCTION__ << counter << "Setup test";
+ foreach (QScatter3DSeries *series, m_chart->seriesList())
+ m_chart->removeSeries(series);
+ foreach (QValue3DAxis *axis, m_chart->axes())
+ deleteAxis(axis);
+ delete series0;
+ delete series1;
+ delete series2;
+ series0 = new QScatter3DSeries;
+ series1 = new QScatter3DSeries;
+ series2 = new QScatter3DSeries;
+ populateFlatSeries(series0, rowCount, colCount, 10.0f);
+ populateFlatSeries(series1, rowCount, colCount, 30.0f);
+ populateFlatSeries(series2, rowCount, colCount, 50.0f);
+ m_chart->axisX()->setRange(3.0f, 6.0f);
+ m_chart->axisY()->setRange(0.0f, 100.0f);
+ m_chart->axisZ()->setRange(4.0f, 8.0f);
+ m_chart->addSeries(series0);
+ m_chart->addSeries(series1);
+ m_chart->addSeries(series2);
+ }
+ break;
+ case 1: {
+ qDebug() << __FUNCTION__ << counter << "Change single item, unselected";
+ int itemIndex = 3 * colCount + 5;
+ QScatterDataItem item = *series0->dataProxy()->itemAt(itemIndex);
+ item.setY(75.0f);
+ series0->dataProxy()->setItem(itemIndex, item);
+ }
+ break;
+ case 2: {
+ qDebug() << __FUNCTION__ << counter << "Change single item, selected";
+ int itemIndex = 4 * colCount + 4;
+ series1->setSelectedItem(itemIndex);
+ QScatterDataItem item = *series1->dataProxy()->itemAt(itemIndex);
+ item.setY(75.0f);
+ series1->dataProxy()->setItem(itemIndex, item);
+ }
+ break;
+ case 3: {
+ qDebug() << __FUNCTION__ << counter << "Change item outside visible area";
+ int itemIndex = 2;
+ QScatterDataItem item = *series1->dataProxy()->itemAt(itemIndex);
+ item.setY(75.0f);
+ series1->dataProxy()->setItem(itemIndex, item);
+ }
+ break;
+ case 4: {
+ qDebug() << __FUNCTION__ << counter << "Change single item from two series, unselected";
+ int itemIndex = 4 * colCount + 6;
+ QScatterDataItem item0 = *series0->dataProxy()->itemAt(itemIndex);
+ QScatterDataItem item1 = *series1->dataProxy()->itemAt(itemIndex);
+ item0.setY(65.0f);
+ item1.setY(85.0f);
+ series0->dataProxy()->setItem(itemIndex, item0);
+ series1->dataProxy()->setItem(itemIndex, item1);
+ }
+ break;
+ case 5: {
+ qDebug() << __FUNCTION__ << counter << "Change single item from two series, one selected";
+ int itemIndex0 = 5 * colCount + 5;
+ int itemIndex1 = 4 * colCount + 4;
+ QScatterDataItem item0 = *series0->dataProxy()->itemAt(itemIndex0);
+ QScatterDataItem item1 = *series1->dataProxy()->itemAt(itemIndex1);
+ item0.setY(65.0f);
+ item1.setY(85.0f);
+ series0->dataProxy()->setItem(itemIndex0, item0);
+ series1->dataProxy()->setItem(itemIndex1, item1);
+ }
+ break;
+ case 6: {
+ qDebug() << __FUNCTION__ << counter << "Change single item from two series, one outside range";
+ int itemIndex0 = 6 * colCount + 6;
+ int itemIndex1 = 9 * colCount + 2;
+ QScatterDataItem item0 = *series0->dataProxy()->itemAt(itemIndex0);
+ QScatterDataItem item1 = *series1->dataProxy()->itemAt(itemIndex1);
+ item0.setY(65.0f);
+ item1.setY(85.0f);
+ series0->dataProxy()->setItem(itemIndex0, item0);
+ series1->dataProxy()->setItem(itemIndex1, item1);
+ }
+ break;
+ case 7: {
+ qDebug() << __FUNCTION__ << counter << "Change single item from two series, both outside range";
+ int itemIndex0 = 1 * colCount + 3;
+ int itemIndex1 = 9 * colCount + 2;
+ QScatterDataItem item0 = *series0->dataProxy()->itemAt(itemIndex0);
+ QScatterDataItem item1 = *series1->dataProxy()->itemAt(itemIndex1);
+ item0.setY(65.0f);
+ item1.setY(85.0f);
+ series0->dataProxy()->setItem(itemIndex0, item0);
+ series1->dataProxy()->setItem(itemIndex1, item1);
+ }
+ break;
+ case 8: {
+ qDebug() << __FUNCTION__ << counter << "Change item to same value as previously";
+ int itemIndex0 = 5 * colCount + 7;
+ int itemIndex1 = 4 * colCount + 7;
+ QScatterDataItem item0 = *series0->dataProxy()->itemAt(itemIndex0);
+ QScatterDataItem item1 = *series1->dataProxy()->itemAt(itemIndex1);
+ series0->dataProxy()->setItem(itemIndex0, item0);
+ series1->dataProxy()->setItem(itemIndex1, item1);
+ }
+ break;
+ case 9: {
+ qDebug() << __FUNCTION__ << counter << "Change 3 items on each series";
+ int itemIndex0 = 5 * colCount + 6;
+ int itemIndex1 = 4 * colCount + 6;
+ QScatterDataItem item00 = *series0->dataProxy()->itemAt(itemIndex0);
+ QScatterDataItem item01 = *series0->dataProxy()->itemAt(itemIndex0 + 1);
+ QScatterDataItem item02 = *series0->dataProxy()->itemAt(itemIndex0 + 2);
+ QScatterDataItem item10 = *series1->dataProxy()->itemAt(itemIndex1);
+ QScatterDataItem item11 = *series1->dataProxy()->itemAt(itemIndex1 + 1);
+ QScatterDataItem item12 = *series1->dataProxy()->itemAt(itemIndex1 + 2);
+ item00.setY(65.0f);
+ item01.setY(70.0f);
+ item02.setY(75.0f);
+ item10.setY(80.0f);
+ item11.setY(85.0f);
+ item12.setY(90.0f);
+ series0->dataProxy()->setItem(itemIndex0, item00);
+ series0->dataProxy()->setItem(itemIndex0 + 1, item01);
+ series0->dataProxy()->setItem(itemIndex0 + 2, item02);
+ series1->dataProxy()->setItem(itemIndex1, item10);
+ series1->dataProxy()->setItem(itemIndex1 + 1, item11);
+ series1->dataProxy()->setItem(itemIndex1 + 2, item12);
+ }
+ break;
+ case 10: {
+ qDebug() << __FUNCTION__ << counter << "Level the field single item at a time";
+ QScatterDataItem item;
+ for (int i = 0; i < rowCount; i++) {
+ for (int j = 0; j < colCount; j++) {
+ int itemIndex = i * colCount + j;
+ QScatterDataItem item0 = *series0->dataProxy()->itemAt(itemIndex);
+ QScatterDataItem item1 = *series1->dataProxy()->itemAt(itemIndex);
+ QScatterDataItem item2 = *series2->dataProxy()->itemAt(itemIndex);
+ item0.setY(10.0f);
+ item1.setY(15.0f);
+ item2.setY(20.0f);
+ series0->dataProxy()->setItem(itemIndex, item0);
+ series1->dataProxy()->setItem(itemIndex, item1);
+ series2->dataProxy()->setItem(itemIndex, item2);
+ }
+ }
+ }
+ break;
+ case 11: {
+ qDebug() << __FUNCTION__ << counter << "Change same items multiple times";
+ int itemIndex0 = 6 * colCount + 6;
+ QScatterDataItem item0 = *series0->dataProxy()->itemAt(itemIndex0);
+ item0.setY(90.0f);
+ series0->dataProxy()->setItem(itemIndex0, item0);
+ series0->dataProxy()->setItem(itemIndex0, item0);
+ series0->dataProxy()->setItem(itemIndex0, item0);
+ series0->dataProxy()->setItem(itemIndex0, item0);
+ }
+ break;
+ default:
+ qDebug() << __FUNCTION__ << "Resetting test";
+ counter = -1;
+ }
+ counter++;
+}
+
+void ScatterDataModifier::testAxisReverse()
+{
+ static int counter = 0;
+ const int rowCount = 16;
+ const int colCount = 16;
+ static QScatter3DSeries *series0 = 0;
+ static QScatter3DSeries *series1 = 0;
+
+ switch (counter) {
+ case 0: {
+ qDebug() << __FUNCTION__ << counter << "Setup test";
+ foreach (QScatter3DSeries *series, m_chart->seriesList())
+ m_chart->removeSeries(series);
+ foreach (QValue3DAxis *axis, m_chart->axes())
+ deleteAxis(axis);
+ delete series0;
+ delete series1;
+ series0 = new QScatter3DSeries;
+ series1 = new QScatter3DSeries;
+ populateRisingSeries(series0, rowCount, colCount, 0.0f, 50.0f);
+ populateRisingSeries(series1, rowCount, colCount, -20.0f, 30.0f);
+ m_chart->axisX()->setRange(0.0f, 10.0f);
+ m_chart->axisY()->setRange(-20.0f, 50.0f);
+ m_chart->axisZ()->setRange(5.0f, 15.0f);
+ m_chart->axisX()->setTitle("Axis X");
+ m_chart->axisZ()->setTitle("Axis Z");
+ m_chart->axisX()->setTitleVisible(true);
+ m_chart->axisZ()->setTitleVisible(true);
+ m_chart->addSeries(series0);
+ m_chart->addSeries(series1);
+ }
+ break;
+ case 1: {
+ qDebug() << __FUNCTION__ << counter << "Reverse X axis";
+ m_chart->axisX()->setReversed(true);
+ }
+ break;
+ case 2: {
+ qDebug() << __FUNCTION__ << counter << "Reverse Y axis";
+ m_chart->axisY()->setReversed(true);
+ }
+ break;
+ case 3: {
+ qDebug() << __FUNCTION__ << counter << "Reverse Z axis";
+ m_chart->axisZ()->setReversed(true);
+ }
+ break;
+ case 4: {
+ qDebug() << __FUNCTION__ << counter << "Return all axes to normal";
+ m_chart->axisX()->setReversed(false);
+ m_chart->axisY()->setReversed(false);
+ m_chart->axisZ()->setReversed(false);
+ }
+ break;
+ case 5: {
+ qDebug() << __FUNCTION__ << counter << "Reverse all axes";
+ m_chart->axisX()->setReversed(true);
+ m_chart->axisY()->setReversed(true);
+ m_chart->axisZ()->setReversed(true);
+ }
+ break;
+ default:
+ qDebug() << __FUNCTION__ << "Resetting test";
+ counter = -1;
+ }
+ counter++;
+}
+
+void ScatterDataModifier::addData()
+{
+ // Add labels
+ m_chart->axisX()->setTitle("X - Axis");
+ m_chart->axisY()->setTitle("Y - Axis");
+ m_chart->axisZ()->setTitle("Z - Axis");
+ m_chart->axisX()->setRange(-50.0f, 50.0f);
+ m_chart->axisY()->setRange(-1.0f, 1.2f);
+ m_chart->axisZ()->setRange(-50.0f, 50.0f);
+ m_chart->axisX()->setSegmentCount(5);
+ m_chart->axisY()->setSegmentCount(4);
+ m_chart->axisZ()->setSegmentCount(10);
+ m_chart->axisX()->setSubSegmentCount(2);
+ m_chart->axisY()->setSubSegmentCount(3);
+ m_chart->axisZ()->setSubSegmentCount(1);
+
+ QScatterDataArray *dataArray = new QScatterDataArray;
+ dataArray->resize(numberOfItems);
+ QScatterDataItem *ptrToDataArray = &dataArray->first();
+ QScatterDataArray *dataArray2 = new QScatterDataArray;
+ dataArray2->resize(numberOfItems);
+ QScatterDataItem *ptrToDataArray2 = &dataArray2->first();
+
+#ifdef RANDOM_SCATTER
+ for (int i = 0; i < numberOfItems; i++) {
+ ptrToDataArray->setPosition(randVector());
+ ptrToDataArray++;
+ ptrToDataArray2->setPosition(randVector());
+ ptrToDataArray2++;
+ }
+#else
+ float limit = qSqrt(numberOfItems) / 2.0f;
+ for (float i = -limit; i < limit; i++) {
+ for (float j = -limit; j < limit; j++) {
+ ptrToDataArray->setPosition(QVector3D(i, qCos(qDegreesToRadians((i * j) / 7.5)), j));
+ ptrToDataArray++;
+ ptrToDataArray2->setPosition(QVector3D(i, qCos(qDegreesToRadians((i * j) / 7.5)) + 0.2, j));
+ ptrToDataArray2++;
+ }
+ }
+#endif
+
+ m_chart->seriesList().at(0)->dataProxy()->resetArray(dataArray);
+ m_chart->seriesList().at(1)->dataProxy()->resetArray(dataArray2);
+ m_chart->seriesList().at(0)->setItemSize(0.0f);
+ m_chart->seriesList().at(1)->setItemSize(0.0f);
+}
+
+void ScatterDataModifier::changeStyle()
+{
+ if (!m_targetSeries)
+ createAndAddSeries();
+
+ if (m_targetSeries->isMeshSmooth()) {
+ m_targetSeries->setMeshSmooth(false);
+ switch (m_targetSeries->mesh()) {
+ case QAbstract3DSeries::MeshCube:
+ m_targetSeries->setMesh(QAbstract3DSeries::MeshPyramid);
+ break;
+ case QAbstract3DSeries::MeshPyramid:
+ m_targetSeries->setMesh(QAbstract3DSeries::MeshCone);
+ break;
+ case QAbstract3DSeries::MeshCone:
+ m_targetSeries->setMesh(QAbstract3DSeries::MeshCylinder);
+ break;
+ case QAbstract3DSeries::MeshCylinder:
+ m_targetSeries->setMesh(QAbstract3DSeries::MeshBevelCube);
+ break;
+ case QAbstract3DSeries::MeshBevelCube:
+ m_targetSeries->setMesh(QAbstract3DSeries::MeshSphere);
+ break;
+ case QAbstract3DSeries::MeshSphere:
+ m_targetSeries->setMesh(QAbstract3DSeries::MeshMinimal);
+ break;
+ case QAbstract3DSeries::MeshMinimal:
+ m_targetSeries->setMesh(QAbstract3DSeries::MeshPoint);
+ break;
+ default:
+ m_targetSeries->setMesh(QAbstract3DSeries::MeshCube);
+ break;
+ }
+ } else {
+ m_targetSeries->setMeshSmooth(true);
+ }
+
+ qDebug() << __FUNCTION__ << m_targetSeries->mesh() << m_targetSeries->isMeshSmooth();
+}
+
+void ScatterDataModifier::changePresetCamera()
+{
+ static int preset = Q3DCamera::CameraPresetFrontLow;
+
+ m_chart->scene()->activeCamera()->setCameraPreset((Q3DCamera::CameraPreset)preset);
+
+ if (++preset > Q3DCamera::CameraPresetDirectlyAboveCCW45)
+ preset = Q3DCamera::CameraPresetFrontLow;
+}
+
+void ScatterDataModifier::changeTheme()
+{
+ static int theme = Q3DTheme::ThemeQt;
+
+ m_chart->activeTheme()->setType(Q3DTheme::Theme(theme));
+
+ if (++theme > Q3DTheme::ThemeIsabelle)
+ theme = Q3DTheme::ThemeQt;
+}
+
+void ScatterDataModifier::changeLabelStyle()
+{
+ m_chart->activeTheme()->setLabelBackgroundEnabled(!m_chart->activeTheme()->isLabelBackgroundEnabled());
+}
+
+void ScatterDataModifier::changeFont(const QFont &font)
+{
+ QFont newFont = font;
+ newFont.setPointSizeF(m_fontSize);
+ m_chart->activeTheme()->setFont(newFont);
+}
+
+void ScatterDataModifier::changeFontSize(int fontSize)
+{
+ m_fontSize = fontSize;
+ QFont font = m_chart->activeTheme()->font();
+ font.setPointSize(m_fontSize);
+ m_chart->activeTheme()->setFont(font);
+}
+
+void ScatterDataModifier::changePointSize(int pointSize)
+{
+ m_targetSeries->setItemSize(0.01f * float(pointSize));
+}
+
+void ScatterDataModifier::shadowQualityUpdatedByVisual(QAbstract3DGraph::ShadowQuality sq)
+{
+ int quality = int(sq);
+ // Updates the UI component to show correct shadow quality
+ emit shadowQualityChanged(quality);
+}
+
+void ScatterDataModifier::clear()
+{
+ foreach (QScatter3DSeries *series, m_chart->seriesList()) {
+ m_chart->removeSeries(series);
+ delete series;
+ }
+
+ m_targetSeries = 0;
+
+ qDebug() << m_loopCounter << "Cleared array";
+}
+
+void ScatterDataModifier::deleteAxis(QValue3DAxis *axis)
+{
+ m_chart->releaseAxis(axis);
+ delete axis;
+}
+
+void ScatterDataModifier::resetAxes()
+{
+ deleteAxis(m_chart->axisX());
+ deleteAxis(m_chart->axisY());
+ deleteAxis(m_chart->axisZ());
+
+ m_chart->setAxisX(new QValue3DAxis);
+ m_chart->setAxisY(new QValue3DAxis);
+ m_chart->setAxisZ(new QValue3DAxis);
+ m_chart->axisX()->setSegmentCount(6);
+ m_chart->axisY()->setSegmentCount(4);
+ m_chart->axisZ()->setSegmentCount(9);
+ m_chart->axisX()->setSubSegmentCount(2);
+ m_chart->axisY()->setSubSegmentCount(3);
+ m_chart->axisZ()->setSubSegmentCount(1);
+ m_chart->axisX()->setTitle("X");
+ m_chart->axisY()->setTitle("Y");
+ m_chart->axisZ()->setTitle("Z");
+}
+
+void ScatterDataModifier::addOne()
+{
+ if (!m_targetSeries)
+ createAndAddSeries();
+
+ QScatterDataItem item(randVector());
+ int addIndex = m_targetSeries->dataProxy()->addItem(item);
+ qDebug() << m_loopCounter << "added one to index:" << addIndex << "array size:" << m_targetSeries->dataProxy()->array()->size();
+}
+
+void ScatterDataModifier::addBunch()
+{
+ if (!m_targetSeries)
+ createAndAddSeries();
+
+ QScatterDataArray items(100);
+ for (int i = 0; i < items.size(); i++)
+ items[i].setPosition(randVector());
+ int addIndex = m_targetSeries->dataProxy()->addItems(items);
+ qDebug() << m_loopCounter << "added bunch to index:" << addIndex << "array size:" << m_targetSeries->dataProxy()->array()->size();
+}
+
+void ScatterDataModifier::insertOne()
+{
+ if (!m_targetSeries)
+ createAndAddSeries();
+
+ QScatterDataItem item(randVector());
+ m_targetSeries->dataProxy()->insertItem(0, item);
+ qDebug() << m_loopCounter << "Inserted one, array size:" << m_targetSeries->dataProxy()->array()->size();
+}
+
+void ScatterDataModifier::insertBunch()
+{
+ if (!m_targetSeries)
+ createAndAddSeries();
+
+ QScatterDataArray items(100);
+ for (int i = 0; i < items.size(); i++)
+ items[i].setPosition(randVector());
+ m_targetSeries->dataProxy()->insertItems(0, items);
+ qDebug() << m_loopCounter << "Inserted bunch, array size:" << m_targetSeries->dataProxy()->array()->size();
+}
+
+void ScatterDataModifier::changeOne()
+{
+ if (!m_targetSeries)
+ createAndAddSeries();
+
+ if (m_selectedItem >= 0 && m_selectedItem < m_targetSeries->dataProxy()->itemCount()) {
+ QScatterDataItem item(randVector());
+ m_targetSeries->dataProxy()->setItem(m_selectedItem, item);
+ qDebug() << m_loopCounter << "Changed one, array size:" << m_targetSeries->dataProxy()->array()->size();
+ }
+}
+
+void ScatterDataModifier::changeBunch()
+{
+ if (!m_targetSeries)
+ createAndAddSeries();
+
+ if (m_targetSeries->dataProxy()->array()->size()) {
+ int amount = qMin(m_targetSeries->dataProxy()->array()->size(), 100);
+ QScatterDataArray items(amount);
+ for (int i = 0; i < items.size(); i++) {
+ items[i].setPosition(randVector());
+ // Change the Y-values of first few items to exact gradient boundaries
+ if (i == 0)
+ items[i].setY(0.65f);
+ else if (i == 1)
+ items[i].setY(0.1f);
+ else if (i == 2)
+ items[i].setY(-0.45f);
+ else if (i == 3)
+ items[i].setY(-1.0f);
+ else if (i == 4)
+ items[i].setY(1.2f);
+// else
+// items[i].setY(0.1001f - (0.00001f * float(i)));
+
+ }
+
+ m_targetSeries->dataProxy()->setItems(0, items);
+ qDebug() << m_loopCounter << "Changed bunch, array size:" << m_targetSeries->dataProxy()->array()->size();
+ }
+}
+
+void ScatterDataModifier::removeOne()
+{
+ if (!m_targetSeries)
+ createAndAddSeries();
+
+ if (m_selectedItem >= 0) {
+ m_targetSeries->dataProxy()->removeItems(m_selectedItem, 1);
+ qDebug() << m_loopCounter << "Removed one, array size:" << m_targetSeries->dataProxy()->array()->size();
+ }
+}
+
+void ScatterDataModifier::removeBunch()
+{
+ if (!m_targetSeries)
+ createAndAddSeries();
+
+ m_targetSeries->dataProxy()->removeItems(0, 100);
+ qDebug() << m_loopCounter << "Removed bunch, array size:" << m_targetSeries->dataProxy()->array()->size();
+}
+
+void ScatterDataModifier::timeout()
+{
+ int doWhat = QRandomGenerator::global()->bounded(10);
+ if (!(QRandomGenerator::global()->bounded(100)))
+ doWhat = -1;
+
+ switch (doWhat) {
+ case 0:
+ addOne();
+ break;
+ case 1:
+ addBunch();
+ break;
+ case 2:
+ insertOne();
+ break;
+ case 3:
+ insertBunch();
+ break;
+ case 4:
+ changeOne();
+ break;
+ case 5:
+ changeBunch();
+ break;
+ case 6:
+ removeOne();
+ break;
+ case 7:
+ removeBunch();
+ break;
+ case 8:
+ addSeries();
+ break;
+ case 9:
+ if (m_chart->seriesList().size())
+ m_targetSeries = m_chart->seriesList().at(QRandomGenerator::global()->bounded(m_chart->seriesList().size()));
+ else
+ addSeries();
+ break;
+ default:
+ clear();
+ break;
+ }
+
+ m_loopCounter++;
+}
+
+void ScatterDataModifier::startStopTimer()
+{
+ if (m_timer.isActive()) {
+ m_timer.stop();
+ } else {
+ clear();
+ m_loopCounter = 0;
+ m_timer.start(0);
+ }
+}
+
+void ScatterDataModifier::selectItem()
+{
+ if (!m_targetSeries)
+ createAndAddSeries();
+
+ int targetItem(3);
+ int noSelection(-1);
+ if (m_selectedItem != targetItem || m_targetSeries != m_chart->seriesList().at(0))
+ m_chart->seriesList().at(0)->setSelectedItem(targetItem);
+ else
+ m_chart->seriesList().at(0)->setSelectedItem(noSelection);
+}
+
+void ScatterDataModifier::handleSelectionChange(int index)
+{
+ m_selectedItem = index;
+ m_targetSeries = static_cast<QScatter3DSeries *>(sender());
+ int seriesIndex = 0;
+ foreach (QScatter3DSeries *series, m_chart->seriesList()) {
+ if (series == sender())
+ break;
+ seriesIndex++;
+ }
+
+ qDebug() << "Selected item index:" << index << "series:" << seriesIndex;
+}
+
+void ScatterDataModifier::setGradient()
+{
+ QLinearGradient baseGradient(0, 0, 1, 100);
+ baseGradient.setColorAt(1.0, Qt::lightGray);
+ baseGradient.setColorAt(0.75001, Qt::lightGray);
+ baseGradient.setColorAt(0.75, Qt::blue);
+ baseGradient.setColorAt(0.50001, Qt::blue);
+ baseGradient.setColorAt(0.50, Qt::red);
+ baseGradient.setColorAt(0.25001, Qt::red);
+ baseGradient.setColorAt(0.25, Qt::yellow);
+ baseGradient.setColorAt(0.0, Qt::yellow);
+
+ QLinearGradient singleHighlightGradient(0, 0, 1, 100);
+ singleHighlightGradient.setColorAt(1.0, Qt::lightGray);
+ singleHighlightGradient.setColorAt(0.75, Qt::blue);
+ singleHighlightGradient.setColorAt(0.50, Qt::red);
+ singleHighlightGradient.setColorAt(0.25, Qt::yellow);
+ singleHighlightGradient.setColorAt(0.0, Qt::white);
+
+ if (m_targetSeries) {
+ m_targetSeries->setBaseColor(Qt::green);
+ m_targetSeries->setSingleHighlightColor(Qt::white);
+
+ m_targetSeries->setBaseGradient(baseGradient);
+ m_targetSeries->setSingleHighlightGradient(singleHighlightGradient);
+
+ Q3DTheme::ColorStyle oldStyle = m_targetSeries->colorStyle();
+ if (oldStyle == Q3DTheme::ColorStyleUniform)
+ m_targetSeries->setColorStyle(Q3DTheme::ColorStyleObjectGradient);
+ else if (oldStyle == Q3DTheme::ColorStyleObjectGradient)
+ m_targetSeries->setColorStyle(Q3DTheme::ColorStyleRangeGradient);
+ if (oldStyle == Q3DTheme::ColorStyleRangeGradient)
+ m_targetSeries->setColorStyle(Q3DTheme::ColorStyleUniform);
+ }
+}
+
+void ScatterDataModifier::clearSeriesData()
+{
+ if (m_targetSeries)
+ m_targetSeries->dataProxy()->resetArray(0);
+}
+
+void ScatterDataModifier::addSeries()
+{
+ QScatter3DSeries *series = createAndAddSeries();
+
+ QScatter3DSeries *oldTargetSeries = m_targetSeries;
+ m_targetSeries = series; // adding always adds to target series, so fake it for a bit
+ addOne(); // add one random item to start the new series off
+ m_targetSeries = oldTargetSeries;
+}
+
+void ScatterDataModifier::removeSeries()
+{
+ if (m_targetSeries) {
+ m_chart->removeSeries(m_targetSeries);
+ delete m_targetSeries;
+ if (m_chart->seriesList().size())
+ m_targetSeries = m_chart->seriesList().at(0);
+ else
+ m_targetSeries = 0;
+ }
+}
+
+void ScatterDataModifier::toggleSeriesVisibility()
+{
+ if (m_targetSeries)
+ m_targetSeries->setVisible(!m_targetSeries->isVisible());
+}
+
+void ScatterDataModifier::changeSeriesName()
+{
+ if (m_targetSeries)
+ m_targetSeries->setName(m_targetSeries->name().append("-").append(QString::number(QRandomGenerator::global()->bounded(10))));
+}
+
+void ScatterDataModifier::handleAxisXChanged(QValue3DAxis *axis)
+{
+ qDebug() << __FUNCTION__ << axis << axis->orientation() << (axis == m_chart->axisX());
+}
+
+void ScatterDataModifier::handleAxisYChanged(QValue3DAxis *axis)
+{
+ qDebug() << __FUNCTION__ << axis << axis->orientation() << (axis == m_chart->axisY());
+}
+
+void ScatterDataModifier::handleAxisZChanged(QValue3DAxis *axis)
+{
+ qDebug() << __FUNCTION__ << axis << axis->orientation() << (axis == m_chart->axisZ());
+}
+
+void ScatterDataModifier::handleFpsChange(qreal fps)
+{
+ static const QString fpsPrefix(QStringLiteral("FPS: "));
+ m_fpsLabel->setText(fpsPrefix + QString::number(qRound(fps)));
+}
+
+void ScatterDataModifier::changeLabelRotation(int rotation)
+{
+ m_chart->axisX()->setLabelAutoRotation(float(rotation));
+ m_chart->axisY()->setLabelAutoRotation(float(rotation));
+ m_chart->axisZ()->setLabelAutoRotation(float(rotation));
+}
+
+void ScatterDataModifier::changeRadialLabelOffset(int offset)
+{
+ m_chart->setRadialLabelOffset(float(offset) / 100.0f);
+}
+
+void ScatterDataModifier::toggleAxisTitleVisibility(int enabled)
+{
+ m_chart->axisX()->setTitleVisible(enabled);
+ m_chart->axisY()->setTitleVisible(enabled);
+ m_chart->axisZ()->setTitleVisible(enabled);
+}
+
+void ScatterDataModifier::toggleAxisTitleFixed(int enabled)
+{
+ m_chart->axisX()->setTitleFixed(enabled);
+ m_chart->axisY()->setTitleFixed(enabled);
+ m_chart->axisZ()->setTitleFixed(enabled);
+}
+
+void ScatterDataModifier::renderToImage()
+{
+ QImage renderedImage8AA = m_chart->renderToImage(8);
+ QImage renderedImageNoAA = m_chart->renderToImage(0);
+ QImage renderedImage8AASmall = m_chart->renderToImage(8, QSize(100, 100));
+ QImage renderedImageNoAASmall = m_chart->renderToImage(0, QSize(100, 100));
+
+ if (m_chart->isVisible()) {
+ renderedImage8AA.save(QStringLiteral("./renderedImage8AA_visible.png"));
+ renderedImageNoAA.save(QStringLiteral("./renderedImageNoAA_visible.png"));
+ renderedImage8AASmall.save(QStringLiteral("./renderedImage8AASmall_visible.png"));
+ renderedImageNoAASmall.save(QStringLiteral("./renderedImageNoAASmall_visible.png"));
+ qDebug() << "Visible images rendered!";
+ } else {
+ renderedImage8AA.save(QStringLiteral("./renderedImage8AA_hidden.png"));
+ renderedImageNoAA.save(QStringLiteral("./renderedImageNoAA_hidden.png"));
+ renderedImage8AASmall.save(QStringLiteral("./renderedImage8AASmall_hidden.png"));
+ renderedImageNoAASmall.save(QStringLiteral("./renderedImageNoAASmall_hidden.png"));
+ qDebug() << "Hidden images rendered!";
+ }
+}
+
+void ScatterDataModifier::togglePolar(int enable)
+{
+ m_chart->setPolar(enable);
+}
+
+void ScatterDataModifier::toggleStatic(int enable)
+{
+ if (enable)
+ m_chart->setOptimizationHints(QAbstract3DGraph::OptimizationStatic);
+ else
+ m_chart->setOptimizationHints(QAbstract3DGraph::OptimizationDefault);
+}
+
+void ScatterDataModifier::toggleOrtho(int enable)
+{
+ m_chart->setOrthoProjection(enable);
+}
+
+void ScatterDataModifier::setCameraTargetX(int value)
+{
+ // Value is (-100, 100), normalize
+ m_cameraTarget.setX(float(value) / 100.0f);
+ m_chart->scene()->activeCamera()->setTarget(m_cameraTarget);
+ qDebug() << "m_cameraTarget:" << m_cameraTarget;
+}
+
+void ScatterDataModifier::setCameraTargetY(int value)
+{
+ // Value is (-100, 100), normalize
+ m_cameraTarget.setY(float(value) / 100.0f);
+ m_chart->scene()->activeCamera()->setTarget(m_cameraTarget);
+ qDebug() << "m_cameraTarget:" << m_cameraTarget;
+}
+
+void ScatterDataModifier::setCameraTargetZ(int value)
+{
+ // Value is (-100, 100), normalize
+ m_cameraTarget.setZ(float(value) / 100.0f);
+ m_chart->scene()->activeCamera()->setTarget(m_cameraTarget);
+ qDebug() << "m_cameraTarget:" << m_cameraTarget;
+}
+
+void ScatterDataModifier::setGraphMargin(int value)
+{
+ m_chart->setMargin(qreal(value) / 100.0);
+ qDebug() << "Setting margin:" << m_chart->margin() << value;
+}
+
+void ScatterDataModifier::changeShadowQuality(int quality)
+{
+ QAbstract3DGraph::ShadowQuality sq = QAbstract3DGraph::ShadowQuality(quality);
+ m_chart->setShadowQuality(sq);
+ emit shadowQualityChanged(quality);
+}
+
+void ScatterDataModifier::setBackgroundEnabled(int enabled)
+{
+ m_chart->activeTheme()->setBackgroundEnabled((bool)enabled);
+}
+
+void ScatterDataModifier::setGridEnabled(int enabled)
+{
+ m_chart->activeTheme()->setGridEnabled((bool)enabled);
+}
+
+void ScatterDataModifier::setMinX(int min)
+{
+ m_chart->axisX()->setMin(min);
+}
+
+void ScatterDataModifier::setMinY(int min)
+{
+ m_chart->axisY()->setMin(float(min) / 100.0f);
+}
+
+void ScatterDataModifier::setMinZ(int min)
+{
+ m_chart->axisZ()->setMin(min);
+}
+
+void ScatterDataModifier::setMaxX(int max)
+{
+ m_chart->axisX()->setMax(max);
+}
+
+void ScatterDataModifier::setMaxY(int max)
+{
+ m_chart->axisY()->setMax(float(max) / 100.0f);
+}
+
+void ScatterDataModifier::setMaxZ(int max)
+{
+ m_chart->axisZ()->setMax(max);
+}
+
+void ScatterDataModifier::setAspectRatio(int ratio)
+{
+ qreal aspectRatio = qreal(ratio) / 10.0;
+ m_chart->setAspectRatio(aspectRatio);
+}
+
+void ScatterDataModifier::setHorizontalAspectRatio(int ratio)
+{
+ qreal aspectRatio = qreal(ratio) / 100.0;
+ m_chart->setHorizontalAspectRatio(aspectRatio);
+}
+
+QVector3D ScatterDataModifier::randVector()
+{
+ QVector3D retvec = QVector3D(
+ (float)(QRandomGenerator::global()->bounded(100)) / 2.0f - (float)(QRandomGenerator::global()->bounded(100)) / 2.0f,
+ (float)(QRandomGenerator::global()->bounded(100)) / 100.0f - (float)(QRandomGenerator::global()->bounded(100)) / 100.0f,
+ (float)(QRandomGenerator::global()->bounded(100)) / 2.0f - (float)(QRandomGenerator::global()->bounded(100)) / 2.0f);
+
+ qDebug() << __FUNCTION__ << retvec;
+
+ return retvec;
+}
+
+QScatter3DSeries *ScatterDataModifier::createAndAddSeries()
+{
+ static int counter = 0;
+
+ QScatter3DSeries *series = new QScatter3DSeries;
+
+ if (!m_targetSeries)
+ m_targetSeries = series;
+
+ m_chart->addSeries(series);
+ series->setName(QString("Series %1").arg(counter++));
+ series->setItemLabelFormat(QStringLiteral("@seriesName: (X:@xLabel / Z:@zLabel) Y:@yLabel"));
+ series->setMesh(QAbstract3DSeries::MeshSphere);
+ series->setMeshSmooth(true);
+ series->setBaseColor(QColor(QRandomGenerator::global()->bounded(256), QRandomGenerator::global()->bounded(256), QRandomGenerator::global()->bounded(256)));
+ series->setItemSize(float(QRandomGenerator::global()->bounded(90) + 10) / 100.0f);
+
+ QObject::connect(series, &QScatter3DSeries::selectedItemChanged, this,
+ &ScatterDataModifier::handleSelectionChange);
+
+ return series;
+}
+
+void ScatterDataModifier::populateFlatSeries(QScatter3DSeries *series, int rows, int columns,
+ float value)
+{
+ QScatterDataArray *dataArray = new QScatterDataArray;
+ dataArray->resize(rows * columns);
+ for (int i = 0; i < rows; i++) {
+ for (int j = 0; j < columns; j++)
+ (*dataArray)[i * columns + j].setPosition(QVector3D(float(i), value, float(j)));
+ }
+ series->dataProxy()->resetArray(dataArray);
+}
+
+void ScatterDataModifier::populateRisingSeries(QScatter3DSeries *series, int rows, int columns,
+ float minValue, float maxValue)
+{
+ QScatterDataArray *dataArray = new QScatterDataArray;
+ int arraySize = rows * columns;
+ dataArray->resize(arraySize);
+ float range = maxValue - minValue;
+ for (int i = 0; i < rows; i++) {
+ for (int j = 0; j < columns; j++) {
+ float yValue = minValue + (range * i * j / arraySize);
+ (*dataArray)[i * columns + j].setPosition(QVector3D(float(i), yValue, float(j)));
+ }
+ }
+ series->dataProxy()->resetArray(dataArray);
+}
diff --git a/tests/manual/scattertest/scatterchart.h b/tests/manual/scattertest/scatterchart.h
new file mode 100644
index 0000000..2385635
--- /dev/null
+++ b/tests/manual/scattertest/scatterchart.h
@@ -0,0 +1,112 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef SCATTERDATAMODIFIER_H
+#define SCATTERDATAMODIFIER_H
+
+#include <QtGraphs/q3dscatter.h>
+#include <QtGraphs/qscatter3dseries.h>
+
+#include <QFont>
+#include <QDebug>
+#include <QTimer>
+#include <QLabel>
+
+class ScatterDataModifier : public QObject
+{
+ Q_OBJECT
+public:
+ explicit ScatterDataModifier(Q3DScatter *scatter);
+ ~ScatterDataModifier();
+
+ void addData();
+ void changeStyle();
+ void changePresetCamera();
+ void changeTheme();
+ void changeLabelStyle();
+ void changeFont(const QFont &font);
+ void changeFontSize(int fontSize);
+ void changePointSize(int pointSize);
+ void setBackgroundEnabled(int enabled);
+ void setGridEnabled(int enabled);
+ void setMinX(int min);
+ void setMinY(int min);
+ void setMinZ(int min);
+ void setMaxX(int max);
+ void setMaxY(int max);
+ void setMaxZ(int max);
+ void setAspectRatio(int ratio);
+ void setHorizontalAspectRatio(int ratio);
+ void start();
+ void massiveDataTest();
+ void massiveTestScroll();
+ void massiveTestAppendAndScroll();
+ void setFpsMeasurement(int enable);
+ void setFpsLabel(QLabel *fpsLabel) { m_fpsLabel = fpsLabel; }
+ void testItemChanges();
+ void testAxisReverse();
+
+public Q_SLOTS:
+ void changeShadowQuality(int quality);
+ void shadowQualityUpdatedByVisual(QAbstract3DGraph::ShadowQuality shadowQuality);
+ void clear();
+ void resetAxes();
+ void addOne();
+ void addBunch();
+ void insertOne();
+ void insertBunch();
+ void changeOne();
+ void changeBunch();
+ void removeOne();
+ void removeBunch();
+ void timeout();
+ void startStopTimer();
+ void selectItem();
+ void handleSelectionChange(int index);
+ void setGradient();
+ void clearSeriesData();
+ void addSeries();
+ void removeSeries();
+ void toggleSeriesVisibility();
+ void changeSeriesName();
+ void handleAxisXChanged(QValue3DAxis *axis);
+ void handleAxisYChanged(QValue3DAxis *axis);
+ void handleAxisZChanged(QValue3DAxis *axis);
+ void handleFpsChange(qreal fps);
+ void changeLabelRotation(int rotation);
+ void changeRadialLabelOffset(int offset);
+ void toggleAxisTitleVisibility(int enabled);
+ void toggleAxisTitleFixed(int enabled);
+ void renderToImage();
+ void togglePolar(int enable);
+ void toggleStatic(int enable);
+ void toggleOrtho(int enable);
+ void setCameraTargetX(int value);
+ void setCameraTargetY(int value);
+ void setCameraTargetZ(int value);
+ void setGraphMargin(int value);
+
+Q_SIGNALS:
+ void shadowQualityChanged(int quality);
+
+private:
+ QVector3D randVector();
+ QScatter3DSeries *createAndAddSeries();
+ void populateFlatSeries(QScatter3DSeries *series, int rows, int columns, float value);
+ void populateRisingSeries(QScatter3DSeries *series, int rows, int columns, float minValue,
+ float maxValue);
+ void deleteAxis(QValue3DAxis *axis);
+
+ Q3DScatter *m_chart;
+ int m_fontSize;
+ QTimer m_timer;
+ int m_loopCounter;
+ int m_selectedItem;
+ QScatter3DSeries *m_targetSeries;
+ QScatterDataArray m_massiveTestCacheArray;
+ QLabel *m_fpsLabel;
+ QVector3D m_cameraTarget;
+
+};
+
+#endif
diff --git a/tests/manual/scattertest/scattertest.pro b/tests/manual/scattertest/scattertest.pro
new file mode 100644
index 0000000..4fec5b3
--- /dev/null
+++ b/tests/manual/scattertest/scattertest.pro
@@ -0,0 +1,8 @@
+!include( ../tests.pri ) {
+ error( "Couldn't find the tests.pri file!" )
+}
+
+SOURCES += main.cpp scatterchart.cpp
+HEADERS += scatterchart.h
+
+QT += widgets
diff --git a/tests/manual/surfacetest/CMakeLists.txt b/tests/manual/surfacetest/CMakeLists.txt
new file mode 100644
index 0000000..671c85e
--- /dev/null
+++ b/tests/manual/surfacetest/CMakeLists.txt
@@ -0,0 +1,36 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+qt_internal_add_manual_test(surfacetest
+ GUI
+ SOURCES
+ buttonwrapper.cpp buttonwrapper.h
+ checkboxwrapper.cpp checkboxwrapper.h
+ graphmodifier.cpp graphmodifier.h
+ main.cpp
+ )
+target_link_libraries(surfacetest PUBLIC
+ Qt::Gui
+ Qt::Widgets
+ Qt::Graphs
+ )
+
+set_source_files_properties("Heightmap.png"
+ PROPERTIES QT_RESOURCE_ALIAS "map"
+ )
+set_source_files_properties("mapimage.png"
+ PROPERTIES QT_RESOURCE_ALIAS "mapimage"
+ )
+set(surfacetest_resource_files
+ "Heightmap.png"
+ "mapimage.png"
+ )
+
+qt_internal_add_resource(surfacetest "surfacetest"
+ PREFIX
+ "/maps"
+ FILES
+ ${surfacetest_resource_files}
+ )
diff --git a/tests/manual/surfacetest/Heightmap.png b/tests/manual/surfacetest/Heightmap.png
new file mode 100644
index 0000000..2a86011
--- /dev/null
+++ b/tests/manual/surfacetest/Heightmap.png
Binary files differ
diff --git a/tests/manual/surfacetest/buttonwrapper.cpp b/tests/manual/surfacetest/buttonwrapper.cpp
new file mode 100644
index 0000000..172b149
--- /dev/null
+++ b/tests/manual/surfacetest/buttonwrapper.cpp
@@ -0,0 +1,14 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+#include "buttonwrapper.h"
+#include <QPushButton>
+
+ButtonWrapper::ButtonWrapper(QPushButton *button)
+{
+ m_button = button;
+}
+
+void ButtonWrapper::setEnabled(int state)
+{
+ m_button->setEnabled(state);
+}
diff --git a/tests/manual/surfacetest/buttonwrapper.h b/tests/manual/surfacetest/buttonwrapper.h
new file mode 100644
index 0000000..64103ac
--- /dev/null
+++ b/tests/manual/surfacetest/buttonwrapper.h
@@ -0,0 +1,22 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+#ifndef BUTTONWRAPPER_H
+#define BUTTONWRAPPER_H
+
+#include <QObject>
+class QPushButton;
+
+class ButtonWrapper : public QObject
+{
+ Q_OBJECT
+public:
+ ButtonWrapper(QPushButton *button);
+
+public Q_SLOTS:
+ void setEnabled(int state);
+
+private:
+ QPushButton *m_button;
+};
+
+#endif // BUTTONWRAPPER_H
diff --git a/tests/manual/surfacetest/checkboxwrapper.cpp b/tests/manual/surfacetest/checkboxwrapper.cpp
new file mode 100644
index 0000000..59d432d
--- /dev/null
+++ b/tests/manual/surfacetest/checkboxwrapper.cpp
@@ -0,0 +1,14 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+#include "checkboxwrapper.h"
+#include <QCheckBox>
+
+CheckBoxWrapper::CheckBoxWrapper(QCheckBox *cb)
+{
+ m_checkbox = cb;
+}
+
+void CheckBoxWrapper::setEnabled(int enabled)
+{
+ m_checkbox->setEnabled(enabled);
+}
diff --git a/tests/manual/surfacetest/checkboxwrapper.h b/tests/manual/surfacetest/checkboxwrapper.h
new file mode 100644
index 0000000..d67591b
--- /dev/null
+++ b/tests/manual/surfacetest/checkboxwrapper.h
@@ -0,0 +1,23 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+#ifndef CHECKBOXWRAPPER_H
+#define CHECKBOXWRAPPER_H
+
+#include <QObject>
+
+class QCheckBox;
+
+class CheckBoxWrapper : public QObject
+{
+ Q_OBJECT
+public:
+ explicit CheckBoxWrapper(QCheckBox *cb);
+
+public Q_SLOTS:
+ void setEnabled(int enabled);
+
+private:
+ QCheckBox *m_checkbox;
+};
+
+#endif // CHECKBOXWRAPPER_H
diff --git a/tests/manual/surfacetest/graphmodifier.cpp b/tests/manual/surfacetest/graphmodifier.cpp
new file mode 100644
index 0000000..b95cf27
--- /dev/null
+++ b/tests/manual/surfacetest/graphmodifier.cpp
@@ -0,0 +1,1664 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "graphmodifier.h"
+#include <QtGraphs/QValue3DAxis>
+#include <QtGraphs/QSurfaceDataProxy>
+#include <QtGraphs/QSurface3DSeries>
+#include <QtGraphs/Q3DTheme>
+#include <QtGraphs/Q3DInputHandler>
+
+#include <qmath.h>
+#include <qrandom.h>
+#include <QLinearGradient>
+#include <QDebug>
+#include <QComboBox>
+#ifndef QT_NO_CURSOR
+#include <QtGui/QCursor>
+#endif
+
+//#define JITTER_PLANE
+//#define WONKY_PLANE
+
+GraphModifier::GraphModifier(Q3DSurface *graph, QWidget *parentWidget)
+ : m_graph(graph),
+ m_series1(new QSurface3DSeries),
+ m_series2(new QSurface3DSeries),
+ m_series3(new QSurface3DSeries),
+ m_series4(new QSurface3DSeries),
+ m_gridSliderX(0),
+ m_gridSliderZ(0),
+ m_axisRangeSliderX(0),
+ m_axisRangeSliderZ(0),
+ m_axisMinSliderX(0),
+ m_axisMinSliderZ(0),
+ m_xCount(24),
+ m_zCount(24),
+ m_activeSample(0),
+ m_fontSize(40),
+ m_rangeX(34.0),
+ m_rangeY(16.0),
+ m_rangeZ(34.0),
+ m_minX(-17.0),
+ m_minY(-8.0),
+ m_minZ(-17.0),
+ m_addRowCounter(m_zCount),
+ m_insertTestZPos(0),
+ m_insertTestIndexPos(1),
+ m_planeArray(0),
+ m_theSeries(new QSurface3DSeries),
+ m_drawMode(QSurface3DSeries::DrawSurfaceAndWireframe),
+ m_drawMode2(QSurface3DSeries::DrawSurfaceAndWireframe),
+ m_drawMode3(QSurface3DSeries::DrawSurfaceAndWireframe),
+ m_drawMode4(QSurface3DSeries::DrawSurfaceAndWireframe),
+ m_offset(4.0f),
+ m_parentWidget(parentWidget),
+ m_ascendingX(true),
+ m_ascendingZ(true)
+{
+ m_graph->setAxisX(new QValue3DAxis);
+ m_graph->axisX()->setTitle("X-Axis");
+ m_graph->setAxisY(new QValue3DAxis);
+ m_graph->axisY()->setTitle("Value Axis");
+ m_graph->setAxisZ(new QValue3DAxis);
+ m_graph->axisZ()->setTitle("Z-Axis");
+#ifdef MULTI_SERIES
+ m_limitX = float(m_xCount) / 2.0f;
+ m_limitZ = float(m_zCount) / 2.0f;
+ // Series 1
+ m_multiSampleOffsetX[0] = -m_offset;
+ m_multiSampleOffsetZ[0] = -m_offset;
+ // Series 2
+ m_multiSampleOffsetX[1] = -m_offset;
+ m_multiSampleOffsetZ[1] = m_offset;
+ // Series 3
+ m_multiSampleOffsetX[2] = m_offset;
+ m_multiSampleOffsetZ[2] = -m_offset;
+ // Series 4
+ m_multiSampleOffsetX[3] = m_offset;
+ m_multiSampleOffsetZ[3] = m_offset;
+
+// m_graph->axisX()->setRange(-m_limitX - m_offset, m_limitX + m_offset);
+// m_graph->axisY()->setRange(-1.0f, 4.5f);
+// m_graph->axisZ()->setRange(-m_limitZ - m_offset, m_limitZ + m_offset);
+#else
+ m_graph->addSeries(m_theSeries);
+#endif
+ m_graph->axisX()->setRange(m_minX, m_minX + m_rangeX);
+ m_graph->axisY()->setRange(m_minY, m_minY + m_rangeY);
+ m_graph->axisZ()->setRange(m_minZ, m_minZ + m_rangeZ);
+
+ static_cast<Q3DInputHandler *>(m_graph->activeInputHandler())->setZoomAtTargetEnabled(true);
+
+ for (int i = 0; i < 4; i++) {
+ m_multiseries[i] = new QSurface3DSeries;
+ m_multiseries[i]->setName(QStringLiteral("Series %1").arg(i+1));
+ m_multiseries[i]->setItemLabelFormat(QStringLiteral("@seriesName: (@xLabel, @zLabel): @yLabel"));
+ }
+
+ fillSeries();
+ changeStyle();
+
+ m_theSeries->setItemLabelFormat(QStringLiteral("@seriesName: (@xLabel, @zLabel): @yLabel"));
+
+ connect(&m_timer, &QTimer::timeout, this, &GraphModifier::timeout);
+ connect(&m_graphPositionQueryTimer, &QTimer::timeout, this, &GraphModifier::graphQueryTimeout);
+ connect(m_theSeries, &QSurface3DSeries::selectedPointChanged, this, &GraphModifier::selectedPointChanged);
+
+ QObject::connect(m_graph, &Q3DSurface::axisXChanged, this,
+ &GraphModifier::handleAxisXChanged);
+ QObject::connect(m_graph, &Q3DSurface::axisYChanged, this,
+ &GraphModifier::handleAxisYChanged);
+ QObject::connect(m_graph, &Q3DSurface::axisZChanged, this,
+ &GraphModifier::handleAxisZChanged);
+ QObject::connect(m_graph, &QAbstract3DGraph::currentFpsChanged, this,
+ &GraphModifier::handleFpsChange);
+
+ //m_graphPositionQueryTimer.start(100);
+}
+
+GraphModifier::~GraphModifier()
+{
+ delete m_graph;
+}
+
+void GraphModifier::fillSeries()
+{
+ float full = m_limitX * m_limitZ;
+
+ QSurfaceDataArray *dataArray1 = new QSurfaceDataArray;
+ dataArray1->reserve(m_zCount);
+ QSurfaceDataArray *dataArray2 = new QSurfaceDataArray;
+ dataArray2->reserve(m_zCount);
+ QSurfaceDataArray *dataArray3 = new QSurfaceDataArray;
+ dataArray3->reserve(m_zCount);
+ QSurfaceDataArray *dataArray4 = new QSurfaceDataArray;
+ dataArray4->reserve(m_zCount);
+
+
+ for (int i = 0; i < m_zCount; i++) {
+ QSurfaceDataRow *newRow[4];
+ float zAdjust = 0.0f;
+ if (i == 2)
+ zAdjust = 0.7f;
+
+ for (int s = 0; s < 4; s++) {
+ newRow[s] = new QSurfaceDataRow(m_xCount);
+ float z = float(i) - m_limitZ + 0.5f + m_multiSampleOffsetZ[s] + zAdjust;
+ for (int j = 0; j < m_xCount; j++) {
+ float xAdjust = 0.0f;
+ if (j == 4)
+ xAdjust = 0.7f;
+ float x = float(j) - m_limitX + 0.5f + m_multiSampleOffsetX[s] + xAdjust;
+ float angle = (z * x) / full * 1.57f;
+ float y = (qSin(angle * float(qPow(1.3f, s))) + 1.1f * s) * 3.0f - 5.0f + xAdjust + zAdjust;
+ (*newRow[s])[j].setPosition(QVector3D(x, y, z));
+ }
+ }
+ *dataArray1 << newRow[0];
+ *dataArray2 << newRow[1];
+ *dataArray3 << newRow[2];
+ *dataArray4 << newRow[3];
+ }
+
+ m_multiseries[0]->dataProxy()->resetArray(dataArray1);
+ m_multiseries[1]->dataProxy()->resetArray(dataArray2);
+ m_multiseries[2]->dataProxy()->resetArray(dataArray3);
+ m_multiseries[3]->dataProxy()->resetArray(dataArray4);
+}
+
+void GraphModifier::toggleSeries1(int enabled)
+{
+ qDebug() << __FUNCTION__ << " enabled = " << enabled;
+
+ if (enabled) {
+ m_graph->addSeries(m_multiseries[0]);
+ } else {
+ m_graph->removeSeries(m_multiseries[0]);
+ }
+}
+
+void GraphModifier::toggleSeries2(int enabled)
+{
+ qDebug() << __FUNCTION__ << " enabled = " << enabled;
+
+ if (enabled) {
+ m_graph->addSeries(m_multiseries[1]);
+ } else {
+ m_graph->removeSeries(m_multiseries[1]);
+ }
+}
+
+void GraphModifier::toggleSeries3(int enabled)
+{
+ qDebug() << __FUNCTION__ << " enabled = " << enabled;
+
+ if (enabled) {
+ m_graph->addSeries(m_multiseries[2]);
+ } else {
+ m_graph->removeSeries(m_multiseries[2]);
+ }
+}
+
+void GraphModifier::toggleSeries4(int enabled)
+{
+ qDebug() << __FUNCTION__ << " enabled = " << enabled;
+
+ if (enabled) {
+ m_graph->addSeries(m_multiseries[3]);
+ } else {
+ m_graph->removeSeries(m_multiseries[3]);
+ }
+}
+
+void GraphModifier::toggleSmooth(int enabled)
+{
+ qDebug() << "GraphModifier::toggleSmooth " << enabled;
+ m_theSeries->setFlatShadingEnabled(enabled);
+#ifdef MULTI_SERIES
+ m_multiseries[0]->setFlatShadingEnabled(enabled);
+#endif
+}
+
+void GraphModifier::toggleSurfaceGrid(int enable)
+{
+ qDebug() << "GraphModifier::toggleSurfaceGrid" << enable;
+ if (enable)
+ m_drawMode |= QSurface3DSeries::DrawWireframe;
+ else
+ m_drawMode &= ~QSurface3DSeries::DrawWireframe;
+
+ m_theSeries->setDrawMode(m_drawMode);
+#ifdef MULTI_SERIES
+ m_multiseries[0]->setDrawMode(m_drawMode);
+#endif
+}
+
+void GraphModifier::toggleSurface(int enable)
+{
+ qDebug() << "GraphModifier::toggleSurface" << enable;
+ if (enable)
+ m_drawMode |= QSurface3DSeries::DrawSurface;
+ else
+ m_drawMode &= ~QSurface3DSeries::DrawSurface;
+
+ m_theSeries->setDrawMode(m_drawMode);
+#ifdef MULTI_SERIES
+ m_multiseries[0]->setDrawMode(m_drawMode);
+#endif
+}
+
+void GraphModifier::toggleSeriesVisible(int enable)
+{
+ m_theSeries->setVisible(enable);
+#ifdef MULTI_SERIES
+ m_multiseries[0]->setVisible(enable);
+#endif
+}
+
+void GraphModifier::toggleSmoothS2(int enabled)
+{
+ qDebug() << __FUNCTION__ << enabled;
+ m_multiseries[1]->setFlatShadingEnabled(enabled);
+}
+
+void GraphModifier::toggleSurfaceGridS2(int enable)
+{
+ qDebug() << __FUNCTION__ << enable;
+ if (enable)
+ m_drawMode2 |= QSurface3DSeries::DrawWireframe;
+ else
+ m_drawMode2 &= ~QSurface3DSeries::DrawWireframe;
+
+ m_multiseries[1]->setDrawMode(m_drawMode2);
+}
+
+void GraphModifier::toggleSurfaceS2(int enable)
+{
+ qDebug() << __FUNCTION__ << enable;
+ if (enable)
+ m_drawMode2 |= QSurface3DSeries::DrawSurface;
+ else
+ m_drawMode2 &= ~QSurface3DSeries::DrawSurface;
+
+ m_multiseries[1]->setDrawMode(m_drawMode2);
+}
+
+void GraphModifier::toggleSeries2Visible(int enable)
+{
+ qDebug() << __FUNCTION__ << enable;
+ m_multiseries[1]->setVisible(enable);
+}
+
+void GraphModifier::toggleSmoothS3(int enabled)
+{
+ qDebug() << __FUNCTION__ << enabled;
+ m_multiseries[2]->setFlatShadingEnabled(enabled);
+}
+
+void GraphModifier::toggleSurfaceGridS3(int enable)
+{
+ qDebug() << __FUNCTION__ << enable;
+ if (enable)
+ m_drawMode3 |= QSurface3DSeries::DrawWireframe;
+ else
+ m_drawMode3 &= ~QSurface3DSeries::DrawWireframe;
+
+ m_multiseries[2]->setDrawMode(m_drawMode3);
+}
+
+void GraphModifier::toggleSurfaceS3(int enable)
+{
+ qDebug() << __FUNCTION__ << enable;
+ if (enable)
+ m_drawMode3 |= QSurface3DSeries::DrawSurface;
+ else
+ m_drawMode3 &= ~QSurface3DSeries::DrawSurface;
+
+ m_multiseries[2]->setDrawMode(m_drawMode3);
+}
+
+void GraphModifier::toggleSeries3Visible(int enable)
+{
+ qDebug() << __FUNCTION__ << enable;
+ m_multiseries[2]->setVisible(enable);
+}
+
+void GraphModifier::toggleSmoothS4(int enabled)
+{
+ qDebug() << __FUNCTION__ << enabled;
+ m_multiseries[3]->setFlatShadingEnabled(enabled);
+}
+
+void GraphModifier::toggleSurfaceGridS4(int enable)
+{
+ qDebug() << __FUNCTION__ << enable;
+ if (enable)
+ m_drawMode4 |= QSurface3DSeries::DrawWireframe;
+ else
+ m_drawMode4 &= ~QSurface3DSeries::DrawWireframe;
+
+ m_multiseries[3]->setDrawMode(m_drawMode4);
+}
+
+void GraphModifier::toggleSurfaceS4(int enable)
+{
+ qDebug() << __FUNCTION__ << enable;
+ if (enable)
+ m_drawMode4 |= QSurface3DSeries::DrawSurface;
+ else
+ m_drawMode4 &= ~QSurface3DSeries::DrawSurface;
+
+ m_multiseries[3]->setDrawMode(m_drawMode4);
+}
+
+void GraphModifier::toggleSeries4Visible(int enable)
+{
+ qDebug() << __FUNCTION__ << enable;
+ m_multiseries[3]->setVisible(enable);
+}
+
+void GraphModifier::toggleSqrtSin(int enable)
+{
+ if (enable) {
+ qDebug() << "Create Sqrt&Sin surface, (" << m_xCount << ", " << m_zCount << ")";
+
+ float minX = -10.0f;
+ float maxX = 10.0f;
+ float minZ = -10.0f;
+ float maxZ = 10.0f;
+ float stepX = (maxX - minX) / float(m_xCount - 1);
+ float stepZ = (maxZ - minZ) / float(m_zCount - 1);
+
+ QSurfaceDataArray *dataArray = new QSurfaceDataArray;
+ dataArray->reserve(m_zCount);
+ for (float i = 0; i < m_zCount; i++) {
+ QSurfaceDataRow *newRow = new QSurfaceDataRow(m_xCount);
+ // Keep values within range bounds, since just adding step can cause minor drift due
+ // to the rounding errors.
+ float z = qMin(maxZ, (i * stepZ + minZ));
+ for (float j = 0; j < m_xCount; j++) {
+ float x = qMin(maxX, (j * stepX + minX));
+ float R = qSqrt(x * x + z * z) + 0.01f;
+ float y = (qSin(R) / R + 0.24f) * 1.61f + 1.0f;
+ (*newRow)[j].setPosition(QVector3D(x, y, z));
+ }
+ *dataArray << newRow;
+ }
+
+ m_graph->axisY()->setRange(1.0, 3.0);
+ m_graph->axisX()->setLabelFormat("%.2f");
+ m_graph->axisZ()->setLabelFormat("%.2f");
+
+ m_theSeries->setName("Sqrt & Sin");
+
+ resetArrayAndSliders(dataArray, minZ, maxZ, minX, maxX);
+
+ m_activeSample = GraphModifier::SqrtSin;
+ } else {
+ qDebug() << "Remove surface";
+ }
+}
+
+void GraphModifier::togglePlane(int enable)
+{
+ qDebug() << "GraphModifier::togglePlane " << enable;
+
+ if (enable) {
+ m_planeArray = new QSurfaceDataArray;
+
+#ifdef JITTER_PLANE
+ m_timer.start(0);
+#endif
+ m_graph->axisY()->setRange(0.0, 1.0);
+ m_graph->axisX()->setLabelFormat("%.2f");
+ m_graph->axisZ()->setLabelFormat("%.2f");
+
+ m_planeArray->reserve(m_zCount);
+ float minX = -10.0f;
+ float maxX = 20.0f;
+ float minZ = -10.0f;
+ float maxZ = 10.0f;
+ float stepX = (maxX - minX) / float(m_xCount - 1);
+ float stepZ = (maxZ - minZ) / float(m_zCount - 1);
+#ifdef WONKY_PLANE
+ float halfZ = m_zCount / 2;
+ float wonkyFactor = 0.01f;
+ float maxStepX = 0.0f;
+ float add = 0.0f;
+ for (float i = 0; i < m_zCount; i++) {
+ QSurfaceDataRow *newRow = new QSurfaceDataRow(m_xCount);
+ if (i < halfZ) {
+ stepX += wonkyFactor;
+ maxStepX = stepX;
+ } else {
+ stepX -= wonkyFactor;
+ }
+ add = 0.0f;
+ for (float j = 0; j < m_xCount; j++) {
+ (*newRow)[j].setPosition(QVector3D(j * stepX + minX, -0.04f,
+ i * stepZ + minZ + add));
+ add += 0.5f;
+
+ }
+ *m_planeArray << newRow;
+ }
+
+ m_theSeries->setName("Wonky Plane");
+
+ resetArrayAndSliders(m_planeArray, minZ, maxZ + add, minX, m_xCount * maxStepX + minX);
+#else
+ for (float i = 0; i < m_zCount; i++) {
+ QSurfaceDataRow *newRow = new QSurfaceDataRow(m_xCount);
+ // Keep values within range bounds, since just adding step can cause minor drift due
+ // to the rounding errors.
+ float zVal;
+ if (i == (m_zCount - 1))
+ zVal = maxZ;
+ else
+ zVal = i * stepZ + minZ;
+
+ float j = 0;
+ for (; j < m_xCount - 1; j++)
+ (*newRow)[j].setPosition(QVector3D(j * stepX + minX, -0.04f, zVal));
+ (*newRow)[j].setPosition(QVector3D(maxX, -0.04f, zVal));
+
+ *m_planeArray << newRow;
+ }
+
+ m_theSeries->setName("Plane");
+
+ resetArrayAndSliders(m_planeArray, minZ, maxZ, minX, maxX);
+#endif
+
+ m_activeSample = GraphModifier::Plane;
+ }
+#ifdef JITTER_PLANE
+ else {
+ m_timer.stop();
+ }
+#endif
+}
+
+void GraphModifier::setHeightMapData(bool enable)
+{
+ if (enable) {
+ // Do the height map the hard way.
+ // Easier alternative would be to use the QHeightMapSurfaceDataProxy.
+ QImage image(":/maps/map");
+
+ QSurfaceDataArray *dataArray = new QSurfaceDataArray;
+ uchar *bits = image.bits();
+
+ int p = image.width() * 4 * (image.height() - 1);
+ dataArray->reserve(image.height());
+ float minX = 34.0;
+ float maxX = 40.0;
+ float minZ = 18.0;
+ float maxZ = 24.0;
+ float xMul = (maxX - minX) / float(image.width() - 1);
+ float zMul = (maxZ - minZ) / float(image.height() - 1);
+ for (int i = 0; i < image.height(); i++, p -= image.width() * 4) {
+ QSurfaceDataRow *newRow = new QSurfaceDataRow(image.width());
+ for (int j = 0; j < image.width(); j++) {
+ (*newRow)[j].setPosition(QVector3D((float(j) * xMul) + minX,
+ (float(bits[p + (j * 4)]) + 1.0f) / 1.0f,
+ (float(i) * zMul) + minZ));
+ }
+ *dataArray << newRow;
+ }
+
+ m_graph->axisY()->setAutoAdjustRange(true);
+ m_graph->axisX()->setLabelFormat("%.1f N");
+ m_graph->axisZ()->setLabelFormat("%.1f E");
+
+ m_theSeries->setName("Height Map");
+
+ resetArrayAndSliders(dataArray, minZ, maxZ, minX, maxX);
+
+ m_activeSample = GraphModifier::Map;
+ }
+}
+
+void GraphModifier::toggleGridSliderLock(bool enable)
+{
+ m_gridSlidersLocked = enable;
+ if (m_gridSlidersLocked) {
+ m_gridSliderZ->setEnabled(false);
+ m_gridSliderZ->setValue(m_gridSliderX->value());
+ } else {
+ m_gridSliderZ->setEnabled(true);
+ }
+}
+
+void GraphModifier::adjustXCount(int count)
+{
+ m_xCount = count;
+ if (m_gridSlidersLocked)
+ m_gridSliderZ->setValue(count);
+
+ updateSamples();
+
+ qDebug() << "X count =" << count;
+}
+
+void GraphModifier::adjustZCount(int count)
+{
+ m_zCount = count;
+
+ updateSamples();
+
+ qDebug() << "Z count =" << count;
+}
+
+void GraphModifier::adjustXRange(int range)
+{
+ m_rangeX = range;
+ m_graph->axisX()->setRange(m_minX, m_minX + m_rangeX);
+
+ qDebug() << "X Range =" << range;
+}
+
+void GraphModifier::adjustYRange(int range)
+{
+ m_rangeY = range;
+ m_graph->axisY()->setRange(m_minY, m_minY + m_rangeY);
+
+ qDebug() << "Y Range =" << range;
+}
+
+void GraphModifier::adjustZRange(int range)
+{
+ m_rangeZ = range;
+ m_graph->axisZ()->setRange(m_minZ, m_minZ + m_rangeZ);
+
+ qDebug() << "Z Range =" << range;
+}
+
+void GraphModifier::adjustXMin(int min)
+{
+ m_minX = min;
+ m_graph->axisX()->setRange(m_minX, m_minX + m_rangeX);
+
+ qDebug() << "X Minimum =" << min;
+}
+
+void GraphModifier::adjustYMin(int min)
+{
+ m_minY = min;
+ m_graph->axisY()->setRange(m_minY, m_minY + m_rangeY);
+
+ qDebug() << "Y Minimum =" << min;
+}
+
+void GraphModifier::adjustZMin(int min)
+{
+ m_minZ = min;
+ m_graph->axisZ()->setRange(m_minZ, m_minZ + m_rangeZ);
+
+ qDebug() << "Z Minimum =" << min;
+}
+
+void GraphModifier::gradientPressed()
+{
+ static Q3DTheme::ColorStyle colorStyle = Q3DTheme::ColorStyleUniform;
+
+ if (colorStyle == Q3DTheme::ColorStyleRangeGradient) {
+ colorStyle = Q3DTheme::ColorStyleObjectGradient;
+ qDebug() << "Color style: ColorStyleObjectGradient";
+ } else if (colorStyle == Q3DTheme::ColorStyleObjectGradient) {
+ colorStyle = Q3DTheme::ColorStyleUniform;
+ qDebug() << "Color style: ColorStyleUniform";
+ } else {
+ colorStyle = Q3DTheme::ColorStyleRangeGradient;
+ qDebug() << "Color style: ColorStyleRangeGradient";
+ }
+
+ QLinearGradient gradient;
+ gradient.setColorAt(0.0, Qt::black);
+ gradient.setColorAt(0.33, Qt::blue);
+ gradient.setColorAt(0.67, Qt::red);
+ gradient.setColorAt(1.0, Qt::yellow);
+
+ QList<QLinearGradient> gradients;
+ gradients << gradient;
+ m_graph->activeTheme()->setBaseGradients(gradients);
+ m_graph->activeTheme()->setColorStyle(colorStyle);
+
+}
+
+void GraphModifier::changeFont(const QFont &font)
+{
+ QFont newFont = font;
+ newFont.setPointSizeF(m_fontSize);
+ m_graph->activeTheme()->setFont(newFont);
+}
+
+void GraphModifier::changeStyle()
+{
+ m_graph->activeTheme()->setLabelBackgroundEnabled(!m_graph->activeTheme()->isLabelBackgroundEnabled());
+}
+
+void GraphModifier::selectButtonClicked()
+{
+ QSurfaceDataProxy *proxy = m_theSeries->dataProxy();
+ int row = QRandomGenerator::global()->bounded(proxy->rowCount());
+ int col = QRandomGenerator::global()->bounded(proxy->columnCount());
+
+ m_theSeries->setSelectedPoint(QPoint(row, col));
+}
+
+void GraphModifier::selectedPointChanged(const QPoint &point)
+{
+ QString labelText = QStringLiteral("Selected row: %1, column: %2").arg(point.x()).arg(point.y());
+ m_selectionInfoLabel->setText(labelText);
+}
+
+void GraphModifier::changeTheme(int theme)
+{
+ m_graph->activeTheme()->setType(Q3DTheme::Theme(theme));
+}
+
+
+void GraphModifier::flipViews()
+{
+ m_graph->scene()->setSecondarySubviewOnTop(!m_graph->scene()->isSecondarySubviewOnTop());
+}
+
+void GraphModifier::timeout()
+{
+ int rows = m_planeArray->size();
+ int columns = m_planeArray->at(0)->size();
+
+ // Induce minor random jitter to the existing plane array
+ for (int i = 0; i < rows; i++) {
+ for (int j = 0; j < columns; j++) {
+ (*m_planeArray->at(i))[j].setX(m_planeArray->at(i)->at(j).x()
+ * ((float((QRandomGenerator::global()->bounded(10)) + 5.0f) / 10000.0f) + 0.999f));
+ (*m_planeArray->at(i))[j].setY(m_planeArray->at(i)->at(j).y()
+ * ((float((QRandomGenerator::global()->bounded(10)) + 5.0f) / 1000.0f) + 0.99f) + 0.0001f);
+ (*m_planeArray->at(i))[j].setZ(m_planeArray->at(i)->at(j).z()
+ * ((float((QRandomGenerator::global()->bounded(10)) + 5.0f) / 10000.0f) + 0.999f));
+ }
+ }
+
+ // Reset same array to make it redraw
+ m_theSeries->dataProxy()->resetArray(m_planeArray);
+}
+
+void GraphModifier::graphQueryTimeout()
+{
+#ifndef QT_NO_CURSOR
+ m_graph->scene()->setGraphPositionQuery(m_parentWidget->mapFromGlobal(QCursor::pos()));
+ qDebug() << "pos: " << (m_parentWidget->mapFromGlobal(QCursor::pos()));
+#else
+ m_graph->scene()->setGraphPositionQuery(QPoint(100, 100));
+#endif
+}
+
+void GraphModifier::handleAxisXChanged(QValue3DAxis *axis)
+{
+ qDebug() << __FUNCTION__ << axis << axis->orientation() << (axis == m_graph->axisX());
+}
+
+void GraphModifier::handleAxisYChanged(QValue3DAxis *axis)
+{
+ qDebug() << __FUNCTION__ << axis << axis->orientation() << (axis == m_graph->axisY());
+}
+
+void GraphModifier::handleAxisZChanged(QValue3DAxis *axis)
+{
+ qDebug() << __FUNCTION__ << axis << axis->orientation() << (axis == m_graph->axisZ());
+}
+
+void GraphModifier::handleFpsChange(qreal fps)
+{
+ qDebug() << "FPS:" << fps;
+}
+
+void GraphModifier::changeLabelRotation(int rotation)
+{
+ m_graph->axisX()->setLabelAutoRotation(float(rotation));
+ m_graph->axisY()->setLabelAutoRotation(float(rotation));
+ m_graph->axisZ()->setLabelAutoRotation(float(rotation));
+}
+
+void GraphModifier::toggleAxisTitleVisibility(int enabled)
+{
+ m_graph->axisX()->setTitleVisible(enabled);
+ m_graph->axisY()->setTitleVisible(enabled);
+ m_graph->axisZ()->setTitleVisible(enabled);
+}
+
+void GraphModifier::toggleAxisTitleFixed(int enabled)
+{
+ m_graph->axisX()->setTitleFixed(enabled);
+ m_graph->axisY()->setTitleFixed(enabled);
+ m_graph->axisZ()->setTitleFixed(enabled);
+}
+
+void GraphModifier::toggleXAscending(int enabled)
+{
+ m_ascendingX = enabled;
+
+ // Flip data array contents if necessary
+ foreach (QSurface3DSeries *series, m_graph->seriesList()) {
+ QSurfaceDataArray *array = const_cast<QSurfaceDataArray *>(series->dataProxy()->array());
+ const int rowCount = array->size();
+ const int columnCount = array->at(0)->size();
+ const bool dataAscending = array->at(0)->at(0).x() < array->at(0)->at(columnCount - 1).x();
+ if (dataAscending != enabled) {
+ // Create new array of equal size
+ QSurfaceDataArray *newArray = new QSurfaceDataArray;
+ newArray->reserve(rowCount);
+ for (int i = 0; i < rowCount; i++)
+ newArray->append(new QSurfaceDataRow(columnCount));
+
+ // Flip each row
+ for (int i = 0; i < rowCount; i++) {
+ QSurfaceDataRow *oldRow = array->at(i);
+ QSurfaceDataRow *newRow = newArray->at(i);
+ for (int j = 0; j < columnCount; j++)
+ (*newRow)[j] = oldRow->at(columnCount - 1 - j);
+ }
+
+ series->dataProxy()->resetArray(newArray);
+ }
+ }
+}
+
+void GraphModifier::toggleZAscending(int enabled)
+{
+ m_ascendingZ = enabled;
+
+ // Flip data array contents if necessary
+ foreach (QSurface3DSeries *series, m_graph->seriesList()) {
+ QSurfaceDataArray *array = const_cast<QSurfaceDataArray *>(series->dataProxy()->array());
+ const int rowCount = array->size();
+ const int columnCount = array->at(0)->size();
+ const bool dataAscending = array->at(0)->at(0).z() < array->at(rowCount - 1)->at(0).z();
+ if (dataAscending != enabled) {
+ // Create new array of equal size
+ QSurfaceDataArray *newArray = new QSurfaceDataArray;
+ newArray->reserve(rowCount);
+ for (int i = 0; i < rowCount; i++)
+ newArray->append(new QSurfaceDataRow(columnCount));
+
+ // Flip each column
+ for (int i = 0; i < rowCount; i++) {
+ QSurfaceDataRow *oldRow = array->at(rowCount - 1 - i);
+ QSurfaceDataRow *newRow = newArray->at(i);
+ for (int j = 0; j < columnCount; j++)
+ (*newRow)[j] = oldRow->at(j);
+ }
+
+ series->dataProxy()->resetArray(newArray);
+ }
+ }
+}
+
+void GraphModifier::togglePolar(int enabled)
+{
+ m_graph->setPolar(enabled);
+}
+
+void GraphModifier::setCameraTargetX(int value)
+{
+ // Value is (-100, 100), normalize
+ m_cameraTarget.setX(float(value) / 100.0f);
+ m_graph->scene()->activeCamera()->setTarget(m_cameraTarget);
+ qDebug() << "m_cameraTarget:" << m_cameraTarget;
+}
+
+void GraphModifier::setCameraTargetY(int value)
+{
+ // Value is (-100, 100), normalize
+ m_cameraTarget.setY(float(value) / 100.0f);
+ m_graph->scene()->activeCamera()->setTarget(m_cameraTarget);
+ qDebug() << "m_cameraTarget:" << m_cameraTarget;
+}
+
+void GraphModifier::setCameraTargetZ(int value)
+{
+ // Value is (-100, 100), normalize
+ m_cameraTarget.setZ(float(value) / 100.0f);
+ m_graph->scene()->activeCamera()->setTarget(m_cameraTarget);
+ qDebug() << "m_cameraTarget:" << m_cameraTarget;
+}
+
+void GraphModifier::setGraphMargin(int value)
+{
+ m_graph->setMargin(qreal(value) / 100.0);
+ qDebug() << "Setting margin:" << m_graph->margin() << value;
+}
+
+void GraphModifier::resetArrayAndSliders(QSurfaceDataArray *array, float minZ, float maxZ, float minX, float maxX)
+{
+ m_axisMinSliderX->setValue(minX);
+ m_axisMinSliderZ->setValue(minZ);
+ m_axisRangeSliderX->setValue(maxX - minX);
+ m_axisRangeSliderZ->setValue(maxZ - minZ);
+
+ m_theSeries->dataProxy()->resetArray(array);
+}
+
+void GraphModifier::changeShadowQuality(int quality)
+{
+ QAbstract3DGraph::ShadowQuality sq = QAbstract3DGraph::ShadowQuality(quality);
+ m_graph->setShadowQuality(sq);
+}
+
+void GraphModifier::changeSelectionMode(int mode)
+{
+ QComboBox *comboBox = qobject_cast<QComboBox *>(sender());
+ if (comboBox) {
+ int flags = comboBox->itemData(mode).toInt();
+ m_graph->setSelectionMode(QAbstract3DGraph::SelectionFlags(flags));
+ }
+}
+
+void GraphModifier::changeRow()
+{
+ if (m_activeSample == GraphModifier::SqrtSin) {
+ qDebug() << "Generating new values to a row at random pos";
+ float minX = -10.0f;
+ float maxX = 10.0f;
+ float minZ = -10.0f;
+ float maxZ = 10.0f;
+ float stepX = (maxX - minX) / float(m_xCount - 1);
+ float stepZ = (maxZ - minZ) / float(m_zCount - 1);
+ float i = float(QRandomGenerator::global()->bounded(m_zCount));
+
+ QSurfaceDataRow *newRow = new QSurfaceDataRow(m_xCount);
+ float z = qMin(maxZ, (i * stepZ + minZ));
+ for (float j = 0; j < m_xCount; j++) {
+ float x = qMin(maxX, (j * stepX + minX));
+ float R = qSqrt(x * x + z * z) + 0.01f;
+ float y = (qSin(R) / R + 0.24f) * 1.61f + 1.2f;
+ (*newRow)[j].setPosition(QVector3D(x, y, z));
+ }
+
+ m_theSeries->dataProxy()->setRow(int(i), newRow);
+ } else {
+#ifdef MULTI_SERIES
+ static int changeRowSeries = 0;
+ qDebug() << "Generating new values to a row at random pos for series " << changeRowSeries;
+
+ int row = QRandomGenerator::global()->bounded(m_zCount);
+ QSurfaceDataRow *newRow = createMultiRow(row, changeRowSeries, true);
+ if (m_ascendingZ)
+ m_multiseries[changeRowSeries]->dataProxy()->setRow(row, newRow);
+ else
+ m_multiseries[changeRowSeries]->dataProxy()->setRow((m_zCount - 1) - row, newRow);
+
+ changeRowSeries++;
+ if (changeRowSeries > 3)
+ changeRowSeries = 0;
+#else
+ qDebug() << "Change row function active only for SqrtSin";
+#endif
+ }
+}
+
+QSurfaceDataRow *GraphModifier::createMultiRow(int row, int series, bool change)
+{
+ int full = m_limitX * m_limitZ;
+ float i = float(row);
+ QSurfaceDataRow *newRow = new QSurfaceDataRow(m_xCount);
+ float z = float(i) - m_limitZ + 0.5f + m_multiSampleOffsetZ[series];
+ if (m_ascendingX) {
+ for (int j = 0; j < m_xCount; j++) {
+ float x = float(j) - m_limitX + 0.5f + m_multiSampleOffsetX[series];
+ float angle = (z * x) / float(full) * 1.57f;
+ float y = qSin(angle * float(qPow(1.3f, series))) + 0.2f * float(change) + 1.1f *series;
+ (*newRow)[j].setPosition(QVector3D(x, y, z));
+ }
+ } else {
+ for (int j = m_xCount - 1; j >= 0 ; j--) {
+ float x = float(j) - m_limitX + 0.5f + m_multiSampleOffsetX[series];
+ float angle = (z * x) / float(full) * 1.57f;
+ float y = qSin(angle * float(qPow(1.3f, series))) + 0.2f * float(change) + 1.1f *series;
+ (*newRow)[(m_xCount - 1) - j].setPosition(QVector3D(x, y, z));
+ }
+ }
+
+ return newRow;
+}
+
+void GraphModifier::populateRisingSeries(QSurface3DSeries *series, int rows, int columns,
+ float minValue, float maxValue, bool ascendingX,
+ bool ascendingZ)
+{
+ QSurfaceDataArray *dataArray = new QSurfaceDataArray;
+ dataArray->reserve(rows);
+ float range = maxValue - minValue;
+ int arraySize = rows * columns;
+ for (int i = 0; i < rows; i++) {
+ QSurfaceDataRow *dataRow = new QSurfaceDataRow(columns);
+ for (int j = 0; j < columns; j++) {
+ float xValue = ascendingX ? float(j) : float(columns - j - 1);
+ float yValue = minValue + (range * i * j / arraySize);
+ float zValue = ascendingZ ? float(i) : float(rows - i - 1);
+ (*dataRow)[j].setPosition(QVector3D(xValue, yValue, zValue));
+ }
+ dataArray->append(dataRow);
+ }
+ series->dataProxy()->resetArray(dataArray);
+
+}
+
+void GraphModifier::changeRows()
+{
+ if (m_activeSample == GraphModifier::SqrtSin) {
+ qDebug() << "Generating new values to 3 rows from random pos";
+
+ float minX = -10.0f;
+ float maxX = 10.0f;
+ float minZ = -10.0f;
+ float maxZ = 10.0f;
+ float stepX = (maxX - minX) / float(m_xCount - 1);
+ float stepZ = (maxZ - minZ) / float(m_zCount - 1);
+ float start = float(QRandomGenerator::global()->bounded(m_zCount - 3));
+
+ QSurfaceDataArray dataArray;
+
+ for (float i = start; i < (start + 3.0f); i++) {
+ QSurfaceDataRow *newRow = new QSurfaceDataRow(m_xCount);
+ float z = qMin(maxZ, (i * stepZ + minZ));
+ for (float j = 0; j < m_xCount; j++) {
+ float x = qMin(maxX, (j * stepX + minX));
+ float R = qSqrt(x * x + z * z) + 0.01f;
+ float y = (qSin(R) / R + 0.24f) * 1.61f + 1.2f;
+ (*newRow)[j].setPosition(QVector3D(x, y, z));
+ }
+ dataArray.append(newRow);
+ }
+
+ m_theSeries->dataProxy()->setRows(int(start), dataArray);
+ } else {
+#ifdef MULTI_SERIES
+ static int changeRowSeries = 0;
+ qDebug() << "Generating new values for 3 rows at random pos for series " << changeRowSeries;
+
+ int row = QRandomGenerator::global()->bounded(m_zCount - 3);
+ QSurfaceDataArray dataArray;
+ for (int i = 0; i < 3; i++) {
+ QSurfaceDataRow *newRow = createMultiRow(row + i, changeRowSeries, true);
+ dataArray.append(newRow);
+ }
+ m_multiseries[changeRowSeries]->dataProxy()->setRows(row, dataArray);
+
+ changeRowSeries++;
+ if (changeRowSeries > 3)
+ changeRowSeries = 0;
+#else
+ qDebug() << "Change row function active only for SqrtSin";
+#endif
+ }
+}
+
+void GraphModifier::changeItem()
+{
+ if (m_activeSample == GraphModifier::SqrtSin) {
+ qDebug() << "Generating new values for an item at random pos";
+ float minX = -10.0f;
+ float maxX = 10.0f;
+ float minZ = -10.0f;
+ float maxZ = 10.0f;
+ float stepX = (maxX - minX) / float(m_xCount - 1);
+ float stepZ = (maxZ - minZ) / float(m_zCount - 1);
+ float i = float(QRandomGenerator::global()->bounded(m_zCount));
+ float j = float(QRandomGenerator::global()->bounded(m_xCount));
+
+ float x = qMin(maxX, (j * stepX + minX));
+ float z = qMin(maxZ, (i * stepZ + minZ));
+ float R = qSqrt(x * x + z * z) + 0.01f;
+ float y = (qSin(R) / R + 0.24f) * 1.61f + 1.2f;
+ QSurfaceDataItem newItem(QVector3D(x, y, z));
+
+ m_theSeries->dataProxy()->setItem(int(i), int(j), newItem);
+ } else {
+#ifdef MULTI_SERIES
+ static int changeItemSeries = 0;
+ int full = m_limitX * m_limitZ;
+ float i = float(QRandomGenerator::global()->bounded(m_zCount));
+ float j = float(QRandomGenerator::global()->bounded(m_xCount));
+ float x = float(j) - m_limitX + 0.5f + m_multiSampleOffsetX[changeItemSeries];
+ float z = float(i) - m_limitZ + 0.5f + m_multiSampleOffsetZ[changeItemSeries];
+ float angle = (z * x) / float(full) * 1.57f;
+ float y = qSin(angle * float(qPow(1.3f, changeItemSeries))) + 0.2f + 1.1f *changeItemSeries;
+ QSurfaceDataItem newItem(QVector3D(x, y, z));
+
+ if (m_ascendingZ && m_ascendingX)
+ m_multiseries[changeItemSeries]->dataProxy()->setItem(int(i), int(j), newItem);
+ else if (!m_ascendingZ && m_ascendingX)
+ m_multiseries[changeItemSeries]->dataProxy()->setItem(m_zCount - 1 - int(i), int(j), newItem);
+ else if (m_ascendingZ && !m_ascendingX)
+ m_multiseries[changeItemSeries]->dataProxy()->setItem(int(i), m_xCount - 1 - int(j), newItem);
+ else
+ m_multiseries[changeItemSeries]->dataProxy()->setItem(m_zCount - 1 - int(i), m_xCount - 1 - int(j), newItem);
+ //m_multiseries[changeItemSeries]->setSelectedPoint(QPoint(i, j));
+ changeItemSeries++;
+ if (changeItemSeries > 3)
+ changeItemSeries = 0;
+#else
+ qDebug() << "Change item function active only for SqrtSin";
+#endif
+ }
+}
+
+void GraphModifier::changeMultipleRows()
+{
+ for (int i = 0; i < 30; i++)
+ changeRow();
+}
+
+void GraphModifier::changeMultipleItem()
+{
+ for (int i = 0; i < 30; i++)
+ changeItem();
+}
+
+void GraphModifier::addRow()
+{
+ if (m_activeSample == GraphModifier::SqrtSin) {
+ qDebug() << "Adding a new row";
+
+ float minX = -10.0f;
+ float maxX = 10.0f;
+ float minZ = -10.0f;
+ float maxZ = 10.0f;
+ float stepX = (maxX - minX) / float(m_xCount - 1);
+ float stepZ = (maxZ - minZ) / float(m_zCount - 1);
+
+ QSurfaceDataRow *newRow = new QSurfaceDataRow(m_xCount);
+ float z = float(m_addRowCounter) * stepZ + minZ;
+ for (float j = 0; j < m_xCount; j++) {
+ float x = qMin(maxX, (j * stepX + minX));
+ float R = qSqrt(x * x + z * z) + 0.01f;
+ float y = (qSin(R) / R + 0.24f) * 1.61f + 1.0f;
+ (*newRow)[j].setPosition(QVector3D(x, y, z));
+ }
+ m_addRowCounter++;
+
+ m_theSeries->dataProxy()->addRow(newRow);
+ } else {
+#ifdef MULTI_SERIES
+ qDebug() << "Adding a row into series 3";
+ int full = m_limitX * m_limitZ;
+ int series = 2;
+
+ QSurfaceDataRow *newRow = new QSurfaceDataRow(m_xCount);
+ float z = float(m_addRowCounter) - m_limitZ + 0.5f + m_multiSampleOffsetZ[series];
+ for (int j = 0; j < m_xCount; j++) {
+ float x = float(j) - m_limitX + 0.5f + m_multiSampleOffsetX[series];
+ float angle = float(z * x) / float(full) * 1.57f;
+ (*newRow)[j].setPosition(QVector3D(x, qSin(angle *float(qPow(1.3f, series))) + 1.1f * series, z));
+ }
+ m_addRowCounter++;
+
+ m_multiseries[series]->dataProxy()->addRow(newRow);
+#else
+ qDebug() << "Add row function active only for SqrtSin";
+#endif
+ }
+}
+
+void GraphModifier::addRows()
+{
+ if (m_activeSample == GraphModifier::SqrtSin) {
+ qDebug() << "Adding few new row";
+
+ float minX = -10.0f;
+ float maxX = 10.0f;
+ float minZ = -10.0f;
+ float maxZ = 10.0f;
+ float stepX = (maxX - minX) / float(m_xCount - 1);
+ float stepZ = (maxZ - minZ) / float(m_zCount - 1);
+
+ QSurfaceDataArray dataArray;
+
+ for (int i = 0; i < 3; i++) {
+ QSurfaceDataRow *newRow = new QSurfaceDataRow(m_xCount);
+ float z = m_addRowCounter * stepZ + minZ;
+ for (float j = 0; j < m_xCount; j++) {
+ float x = qMin(maxX, (j * stepX + minX));
+ float R = qSqrt(x * x + z * z) + 0.01f;
+ float y = (qSin(R) / R + 0.24f) * 1.61f + 1.0f;
+ (*newRow)[j].setPosition(QVector3D(x, y, z));
+ }
+ dataArray.append(newRow);
+ m_addRowCounter++;
+ }
+
+ m_theSeries->dataProxy()->addRows(dataArray);
+ } else {
+#ifdef MULTI_SERIES
+ qDebug() << "Adding 3 rows into series 3";
+ int changedSeries = 2;
+
+ QSurfaceDataArray dataArray;
+ for (int i = 0; i < 3; i++) {
+ QSurfaceDataRow *newRow = createMultiRow(m_addRowCounter, changedSeries, false);
+ dataArray.append(newRow);
+ m_addRowCounter++;
+ }
+
+ m_multiseries[changedSeries]->dataProxy()->addRows(dataArray);
+#else
+ qDebug() << "Add rows function active only for SqrtSin";
+#endif
+ }
+}
+
+void GraphModifier::insertRow()
+{
+ if (m_activeSample == GraphModifier::SqrtSin) {
+ qDebug() << "Inserting a row";
+ float minX = -10.0f;
+ float maxX = 10.0f;
+ float minZ = -10.0f;
+ float maxZ = 10.0f;
+ float stepX = (maxX - minX) / float(m_xCount - 1);
+ float stepZ = (maxZ - minZ) / float(m_zCount - 1);
+
+ QSurfaceDataRow *newRow = new QSurfaceDataRow(m_xCount);
+ float z = qMin(maxZ, (float(m_insertTestZPos) * stepZ + minZ + (stepZ / 2.0f)));
+ for (float j = 0; j < m_xCount; j++) {
+ float x = qMin(maxX, (j * stepX + minX));
+ float R = qSqrt(x * x + z * z) + 0.01f;
+ float y = (qSin(R) / R + 0.24f) * 1.61f + 1.3f;
+ (*newRow)[j].setPosition(QVector3D(x, y, z));
+ }
+ m_insertTestZPos++;
+
+ m_theSeries->dataProxy()->insertRow(m_insertTestIndexPos, newRow);
+ m_insertTestIndexPos += 2;
+ } else {
+#ifdef MULTI_SERIES
+ qDebug() << "Inserting a row into series 3";
+ int full = m_limitX * m_limitZ;
+ int changedSeries = 2;
+
+ QSurfaceDataRow *newRow = new QSurfaceDataRow(m_xCount);
+ float z = float(m_insertTestZPos) - m_limitZ + m_multiSampleOffsetZ[changedSeries];
+ for (int j = 0; j < m_xCount; j++) {
+ float x = float(j) - m_limitX + m_multiSampleOffsetX[changedSeries];
+ float angle = (z * x) / float(full) * 1.57f;
+ (*newRow)[j].setPosition(QVector3D(x + 0.5f,
+ qSin(angle * float(qPow(1.3f, changedSeries))) + 1.2f * changedSeries,
+ z + 1.0f));
+ }
+
+ m_insertTestZPos++;
+
+ m_multiseries[2]->dataProxy()->insertRow(m_insertTestIndexPos, newRow);
+ m_insertTestIndexPos += 2;
+#else
+ qDebug() << "Insert row function active only for SqrtSin";
+#endif
+ }
+}
+
+void GraphModifier::insertRows()
+{
+ if (m_activeSample == GraphModifier::SqrtSin) {
+ qDebug() << "Inserting 3 rows";
+ float minX = -10.0f;
+ float maxX = 10.0f;
+ float minZ = -10.0f;
+ float maxZ = 10.0f;
+ float stepX = (maxX - minX) / float(m_xCount - 1);
+ float stepZ = (maxZ - minZ) / float(m_zCount - 1);
+
+ QSurfaceDataArray dataArray;
+ for (int i = 0; i < 3; i++) {
+ QSurfaceDataRow *newRow = new QSurfaceDataRow(m_xCount);
+ float z = qMin(maxZ, (float(m_insertTestZPos) * stepZ + minZ + i * (stepZ / 4.0f)));
+ for (float j = 0; j < m_xCount; j++) {
+ float x = qMin(maxX, (j * stepX + minX));
+ float R = qSqrt(x * x + z * z) + 0.01f;
+ float y = (qSin(R) / R + 0.24f) * 1.61f + 1.3f;
+ (*newRow)[j].setPosition(QVector3D(x, y, z));
+ }
+ dataArray.append(newRow);
+ }
+ m_insertTestZPos++;
+
+ m_theSeries->dataProxy()->insertRows(m_insertTestIndexPos, dataArray);
+ m_insertTestIndexPos += 4;
+ } else {
+#ifdef MULTI_SERIES
+ qDebug() << "Inserting 3 rows into series 3";
+ int full = m_limitX * m_limitZ;
+ int changedSeries = 2;
+
+ QSurfaceDataArray dataArray;
+ float zAdd = 0.25f;
+ for (int i = 0; i < 3; i++) {
+ QSurfaceDataRow *newRow = new QSurfaceDataRow(m_xCount);
+ float z = float(m_insertTestZPos) - m_limitZ + 0.5f + zAdd + m_multiSampleOffsetZ[changedSeries];
+ for (int j = 0; j < m_xCount; j++) {
+ float x = float(j) - m_limitX + 0.5f + m_multiSampleOffsetX[changedSeries];
+ float angle = (z * x) / float(full) * 1.57f;
+ float y = qSin(angle * float(qPow(1.3f, changedSeries))) + + 1.2f * changedSeries;
+ (*newRow)[j].setPosition(QVector3D(x, y, z));
+ }
+ zAdd += 0.25f;
+ dataArray.append(newRow);
+ }
+
+ m_insertTestZPos++;
+
+ m_multiseries[2]->dataProxy()->insertRows(m_insertTestIndexPos, dataArray);
+ m_insertTestIndexPos += 4;
+#else
+ qDebug() << "Insert rows function active only for SqrtSin";
+#endif
+ }
+}
+
+void GraphModifier::removeRow()
+{
+ qDebug() << "Remove an arbitrary row";
+ if (m_zCount < 1)
+ return;
+
+ int row = QRandomGenerator::global()->bounded(m_zCount);
+
+#ifdef MULTI_SERIES
+ int series = QRandomGenerator::global()->bounded(4);
+ m_multiseries[series]->dataProxy()->removeRows(row, 1);
+#else
+ m_theSeries->dataProxy()->removeRows(row, 1);
+#endif
+ m_zCount--;
+}
+
+void GraphModifier::resetArray()
+{
+ qDebug() << "Reset series data array";
+ int rows = 10;
+ int columns = 10;
+ float randFactor = float(QRandomGenerator::global()->bounded(100)) / 100.0f;
+ QSurfaceDataArray *planeArray = new QSurfaceDataArray;
+ planeArray->reserve(rows);
+
+ for (int i = 0; i < rows; i++) {
+ planeArray->append(new QSurfaceDataRow);
+ (*planeArray)[i]->resize(columns);
+ for (int j = 0; j < columns; j++) {
+ (*planeArray->at(i))[j].setX(float(j) * randFactor);
+ (*planeArray->at(i))[j].setY(float(i - j) * randFactor);
+ (*planeArray->at(i))[j].setZ(float(i));
+ }
+ }
+
+#ifdef MULTI_SERIES
+ int series = QRandomGenerator::global()->bounded(4);
+ m_multiseries[series]->dataProxy()->resetArray(planeArray);
+#else
+ m_theSeries->dataProxy()->resetArray(planeArray);
+#endif
+}
+
+void GraphModifier::resetArrayEmpty()
+{
+ QSurfaceDataArray *emptyArray = new QSurfaceDataArray;
+#ifdef MULTI_SERIES
+ int series = QRandomGenerator::global()->bounded(4);
+ m_multiseries[series]->dataProxy()->resetArray(emptyArray);
+#else
+ m_theSeries->dataProxy()->resetArray(emptyArray);
+#endif
+}
+
+void GraphModifier::massiveDataTest()
+{
+ static int testPhase = 0;
+ static const int cacheSize = 1000;
+ const int columns = 200;
+ const int rows = 200000;
+ const int visibleRows = 200;
+ const float yRangeMin = 0.0f;
+ const float yRangeMax = 1.0f;
+ const float yRangeMargin = 0.05f;
+ static QTimer *massiveTestTimer = 0;
+ static QSurface3DSeries *series = new QSurface3DSeries;
+
+ // To speed up massive array creation, we generate a smaller cache array
+ // and copy rows from that to our main array
+ if (!m_massiveTestCacheArray.size()) {
+ m_massiveTestCacheArray.reserve(cacheSize);
+ float minY = yRangeMin + yRangeMargin;
+ float maxY = yRangeMax - yRangeMargin;
+ float rowBase = minY;
+ float direction = 1.0f;
+ for (int i = 0; i < cacheSize; i++) {
+ m_massiveTestCacheArray.append(new QSurfaceDataRow);
+ m_massiveTestCacheArray[i]->resize(columns);
+ rowBase += direction * (float(QRandomGenerator::global()->bounded(3)) / 100.0f);
+ if (rowBase > maxY) {
+ rowBase = maxY;
+ direction = -1.0f;
+ } else if (rowBase < minY) {
+ rowBase = minY;
+ direction = 1.0f;
+ }
+ for (int j = 0; j < columns; j++) {
+ float randFactor = float(QRandomGenerator::global()->bounded(100)) / (100 / yRangeMargin);
+ (*m_massiveTestCacheArray.at(i))[j].setX(float(j));
+ (*m_massiveTestCacheArray.at(i))[j].setY(rowBase + randFactor);
+ // Z value is irrelevant, we replace it anyway when we take row to use
+ }
+ }
+ massiveTestTimer = new QTimer;
+ }
+
+ switch (testPhase) {
+ case 0: {
+ qDebug() << __FUNCTION__ << testPhase << ": Setting the graph up...";
+ QValue3DAxis *xAxis = new QValue3DAxis();
+ QValue3DAxis *yAxis = new QValue3DAxis();
+ QValue3DAxis *zAxis = new QValue3DAxis();
+ xAxis->setRange(0.0f, float(columns));
+ yAxis->setRange(yRangeMin, yRangeMax);
+ zAxis->setRange(0.0f, float(visibleRows));
+ xAxis->setSegmentCount(1);
+ yAxis->setSegmentCount(1);
+ zAxis->setSegmentCount(1);
+ m_graph->setMeasureFps(true);
+ m_graph->setAxisX(xAxis);
+ m_graph->setAxisY(yAxis);
+ m_graph->setAxisZ(zAxis);
+ m_graph->scene()->activeCamera()->setCameraPreset(Q3DCamera::CameraPresetRight);
+ m_graph->setShadowQuality(QAbstract3DGraph::ShadowQualityNone);
+ foreach (QAbstract3DSeries *series, m_graph->seriesList())
+ m_graph->removeSeries(static_cast<QSurface3DSeries *>(series));
+
+ qDebug() << __FUNCTION__ << testPhase << ": Creating massive array..."
+ << rows << "x" << columns;
+ // Reset to zero first to avoid having memory allocated for two massive arrays at the same
+ // time on the second and subsequent runs.
+ series->dataProxy()->resetArray(0);
+ QSurfaceDataArray *massiveArray = new QSurfaceDataArray;
+ massiveArray->reserve(rows);
+
+ for (int i = 0; i < rows; i++) {
+ QSurfaceDataRow *newRow = new QSurfaceDataRow(*m_massiveTestCacheArray.at(i % cacheSize));
+ for (int j = 0; j < columns; j++)
+ (*newRow)[j].setZ(float(i));
+ massiveArray->append(newRow);
+ }
+ qDebug() << __FUNCTION__ << testPhase << ": Massive array creation finished!";
+
+ series->dataProxy()->resetArray(massiveArray);
+ m_graph->addSeries(series);
+ break;
+ }
+ case 1: {
+ qDebug() << __FUNCTION__ << testPhase << ": Scroll";
+ QObject::disconnect(massiveTestTimer, 0, this, 0);
+ QObject::connect(massiveTestTimer, &QTimer::timeout, this,
+ &GraphModifier::massiveTestScroll);
+ massiveTestTimer->start(16);
+ break;
+ }
+ case 2: {
+ qDebug() << __FUNCTION__ << testPhase << ": Append and scroll";
+ massiveTestTimer->stop();
+ QObject::disconnect(massiveTestTimer, 0, this, 0);
+ QObject::connect(massiveTestTimer, &QTimer::timeout, this,
+ &GraphModifier::massiveTestAppendAndScroll);
+ m_graph->axisZ()->setRange(rows - visibleRows, rows);
+ massiveTestTimer->start(16);
+ break;
+ }
+ default:
+ QObject::disconnect(massiveTestTimer, 0, this, 0);
+ massiveTestTimer->stop();
+ qDebug() << __FUNCTION__ << testPhase << ": Resetting the test";
+ testPhase = -1;
+ }
+ testPhase++;
+}
+
+void GraphModifier::massiveTestScroll()
+{
+ const int scrollAmount = 20;
+ int maxRows = m_graph->seriesList().at(0)->dataProxy()->rowCount();
+ int min = m_graph->axisZ()->min() + scrollAmount;
+ int max = m_graph->axisZ()->max() + scrollAmount;
+ if (max >= maxRows) {
+ max = max - min;
+ min = 0;
+ }
+ m_graph->axisZ()->setRange(min, max);
+}
+
+void GraphModifier::massiveTestAppendAndScroll()
+{
+ const int addedRows = 50;
+ int maxRows = m_graph->seriesList().at(0)->dataProxy()->rowCount();
+ int columns = m_graph->seriesList().at(0)->dataProxy()->columnCount();
+
+ QSurfaceDataArray appendArray;
+ appendArray.reserve(addedRows);
+ for (int i = 0; i < addedRows; i++) {
+ QSurfaceDataRow *newRow = new QSurfaceDataRow(*m_massiveTestCacheArray.at((i + maxRows) % 1000));
+ for (int j = 0; j < columns; j++)
+ (*newRow)[j].setZ(float(maxRows + i));
+ appendArray.append(newRow);
+ }
+ m_graph->seriesList().at(0)->dataProxy()->addRows(appendArray);
+ int min = m_graph->axisZ()->min() + addedRows;
+ int max = m_graph->axisZ()->max() + addedRows;
+ m_graph->axisZ()->setRange(min, max);
+}
+
+void GraphModifier::testAxisReverse()
+{
+ static int counter = 0;
+ const int rowCount = 16;
+ const int colCount = 16;
+ static QSurface3DSeries *series0 = 0;
+ static QSurface3DSeries *series1 = 0;
+
+ switch (counter) {
+ case 0: {
+ qDebug() << __FUNCTION__ << counter << "Setup test";
+ foreach (QSurface3DSeries *series, m_graph->seriesList())
+ m_graph->removeSeries(series);
+ foreach (QValue3DAxis *axis, m_graph->axes())
+ m_graph->releaseAxis(axis);
+ delete series0;
+ delete series1;
+ series0 = new QSurface3DSeries;
+ series1 = new QSurface3DSeries;
+ populateRisingSeries(series0, rowCount, colCount, 0.0f, 50.0f, true, true);
+ populateRisingSeries(series1, rowCount, colCount, -20.0f, 30.0f, true, true);
+ m_graph->axisX()->setRange(0.0f, 10.0f);
+ m_graph->axisY()->setRange(-20.0f, 50.0f);
+ m_graph->axisZ()->setRange(5.0f, 15.0f);
+ m_graph->addSeries(series0);
+ m_graph->addSeries(series1);
+ }
+ break;
+ case 1: {
+ qDebug() << __FUNCTION__ << counter << "Reverse X axis";
+ m_graph->axisX()->setReversed(true);
+ }
+ break;
+ case 2: {
+ qDebug() << __FUNCTION__ << counter << "Reverse Y axis";
+ m_graph->axisY()->setReversed(true);
+ }
+ break;
+ case 3: {
+ qDebug() << __FUNCTION__ << counter << "Reverse Z axis";
+ m_graph->axisZ()->setReversed(true);
+ }
+ break;
+ case 4: {
+ qDebug() << __FUNCTION__ << counter << "Return all axes to normal";
+ m_graph->axisX()->setReversed(false);
+ m_graph->axisY()->setReversed(false);
+ m_graph->axisZ()->setReversed(false);
+ }
+ break;
+ case 5: {
+ qDebug() << __FUNCTION__ << counter << "Reverse all axes";
+ m_graph->axisX()->setReversed(true);
+ m_graph->axisY()->setReversed(true);
+ m_graph->axisZ()->setReversed(true);
+ }
+ break;
+ default:
+ qDebug() << __FUNCTION__ << "Resetting test";
+ counter = -1;
+ }
+ counter++;
+}
+
+void GraphModifier::testDataOrdering()
+{
+ static int counter = 0;
+ const int rowCount = 20;
+ const int colCount = 20;
+ static QSurface3DSeries *series0 = 0;
+ static QSurface3DSeries *series1 = 0;
+ const float series0min = 0.0f;
+ const float series0max = 50.0f;
+ const float series1min = -20.0f;
+ const float series1max = 30.0f;
+
+ switch (counter) {
+ case 0: {
+ qDebug() << __FUNCTION__ << counter << "Setup test - both ascending";
+ foreach (QSurface3DSeries *series, m_graph->seriesList())
+ m_graph->removeSeries(series);
+ foreach (QValue3DAxis *axis, m_graph->axes())
+ m_graph->releaseAxis(axis);
+ delete series0;
+ delete series1;
+ series0 = new QSurface3DSeries;
+ series1 = new QSurface3DSeries;
+ populateRisingSeries(series0, rowCount, colCount, series0min, series0max, true, true);
+ populateRisingSeries(series1, rowCount, colCount, series1min, series1max, true, true);
+ m_graph->axisX()->setRange(5.0f, 15.0f);
+ m_graph->axisY()->setRange(-20.0f, 50.0f);
+ m_graph->axisZ()->setRange(5.0f, 15.0f);
+ m_graph->addSeries(series0);
+ m_graph->addSeries(series1);
+ }
+ break;
+ case 1: {
+ qDebug() << __FUNCTION__ << counter << "Ascending X, descending Z";
+ populateRisingSeries(series0, rowCount, colCount, series0min, series0max, true, false);
+ populateRisingSeries(series1, rowCount, colCount, series1min, series1max, true, false);
+ }
+ break;
+ case 2: {
+ qDebug() << __FUNCTION__ << counter << "Descending X, ascending Z";
+ populateRisingSeries(series0, rowCount, colCount, series0min, series0max, false, true);
+ populateRisingSeries(series1, rowCount, colCount, series1min, series1max, false, true);
+ }
+ break;
+ case 3: {
+ qDebug() << __FUNCTION__ << counter << "Both descending";
+ populateRisingSeries(series0, rowCount, colCount, series0min, series0max, false, false);
+ populateRisingSeries(series1, rowCount, colCount, series1min, series1max, false, false);
+ }
+ break;
+ default:
+ qDebug() << __FUNCTION__ << "Resetting test";
+ counter = -1;
+ }
+ counter++;
+}
+
+void GraphModifier::changeMesh()
+{
+ static int model = 0;
+ switch (model) {
+ case 0:
+ m_graph->seriesList().at(0)->setMesh(QAbstract3DSeries::MeshCylinder);
+ m_graph->seriesList().at(0)->setMeshSmooth(false);
+ break;
+ case 1:
+ m_graph->seriesList().at(0)->setMesh(QAbstract3DSeries::MeshCylinder);
+ m_graph->seriesList().at(0)->setMeshSmooth(true);
+ break;
+ case 2:
+ m_graph->seriesList().at(0)->setMesh(QAbstract3DSeries::MeshCone);
+ m_graph->seriesList().at(0)->setMeshSmooth(false);
+ break;
+ case 3:
+ m_graph->seriesList().at(0)->setMesh(QAbstract3DSeries::MeshCone);
+ m_graph->seriesList().at(0)->setMeshSmooth(true);
+ break;
+ case 4:
+ m_graph->seriesList().at(0)->setMesh(QAbstract3DSeries::MeshBar);
+ m_graph->seriesList().at(0)->setMeshSmooth(false);
+ break;
+ case 5:
+ m_graph->seriesList().at(0)->setMesh(QAbstract3DSeries::MeshBar);
+ m_graph->seriesList().at(0)->setMeshSmooth(true);
+ break;
+ case 6:
+ m_graph->seriesList().at(0)->setMesh(QAbstract3DSeries::MeshPyramid);
+ m_graph->seriesList().at(0)->setMeshSmooth(false);
+ break;
+ case 7:
+ m_graph->seriesList().at(0)->setMesh(QAbstract3DSeries::MeshPyramid);
+ m_graph->seriesList().at(0)->setMeshSmooth(true);
+ break;
+ case 8:
+ m_graph->seriesList().at(0)->setMesh(QAbstract3DSeries::MeshBevelBar);
+ m_graph->seriesList().at(0)->setMeshSmooth(false);
+ break;
+ case 9:
+ m_graph->seriesList().at(0)->setMesh(QAbstract3DSeries::MeshBevelBar);
+ m_graph->seriesList().at(0)->setMeshSmooth(true);
+ break;
+ }
+ model++;
+ if (model > 9)
+ model = 0;
+}
+
+void GraphModifier::updateSamples()
+{
+ switch (m_activeSample) {
+ case SqrtSin:
+ toggleSqrtSin(true);
+ break;
+
+ case Plane:
+ togglePlane(true);
+ break;
+
+ default:
+ break;
+ }
+}
+
+void GraphModifier::setAspectRatio(int ratio)
+{
+ qreal aspectRatio = qreal(ratio) / 10.0;
+ m_graph->setAspectRatio(aspectRatio);
+}
+
+void GraphModifier::setHorizontalAspectRatio(int ratio)
+{
+ qreal aspectRatio = qreal(ratio) / 100.0;
+ m_graph->setHorizontalAspectRatio(aspectRatio);
+}
+
+void GraphModifier::setSurfaceTexture(int enabled)
+{
+ if (enabled)
+ m_multiseries[3]->setTexture(QImage(":/maps/mapimage"));
+ else
+ m_multiseries[3]->setTexture(QImage());
+}
diff --git a/tests/manual/surfacetest/graphmodifier.h b/tests/manual/surfacetest/graphmodifier.h
new file mode 100644
index 0000000..63cf66a
--- /dev/null
+++ b/tests/manual/surfacetest/graphmodifier.h
@@ -0,0 +1,184 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef GRAPHMODIFIER_H
+#define GRAPHMODIFIER_H
+
+#include <QtGraphs/Q3DSurface>
+#include <QtGraphs/QSurfaceDataProxy>
+#include <QtGraphs/QSurface3DSeries>
+#include <QSlider>
+#include <QTimer>
+#include <QLabel>
+#include <QCheckBox>
+
+#define MULTI_SERIES
+
+class GraphModifier : public QObject
+{
+ Q_OBJECT
+public:
+ enum Samples {
+ SqrtSin = 1,
+ Plane,
+ Map
+ };
+
+ explicit GraphModifier(Q3DSurface *graph, QWidget *parentWidget);
+ ~GraphModifier();
+
+ void toggleSeries1(int enabled);
+ void toggleSeries2(int enabled);
+ void toggleSeries3(int enabled);
+ void toggleSeries4(int enabled);
+ void toggleSmooth(int enabled);
+ void toggleSurfaceGrid(int enable);
+ void toggleSurface(int enable);
+ void toggleSeriesVisible(int enable);
+ void toggleSmoothS2(int enabled);
+ void toggleSurfaceGridS2(int enable);
+ void toggleSurfaceS2(int enable);
+ void toggleSeries2Visible(int enable);
+ void toggleSmoothS3(int enabled);
+ void toggleSurfaceGridS3(int enable);
+ void toggleSurfaceS3(int enable);
+ void toggleSeries3Visible(int enable);
+ void toggleSmoothS4(int enabled);
+ void toggleSurfaceGridS4(int enable);
+ void toggleSurfaceS4(int enable);
+ void toggleSeries4Visible(int enable);
+
+ void toggleSqrtSin(int enable);
+ void togglePlane(int enable);
+ void setHeightMapData(bool enable);
+ void toggleGridSliderLock(bool enable);
+ void setGridSliderX(QSlider *slider) { m_gridSliderX = slider; }
+ void setGridSliderZ(QSlider *slider) { m_gridSliderZ = slider; }
+ void setAxisRangeSliderX(QSlider *slider) { m_axisRangeSliderX = slider; }
+ void setAxisRangeSliderZ(QSlider *slider) { m_axisRangeSliderZ = slider; }
+ void setAxisMinSliderX(QSlider *slider) { m_axisMinSliderX = slider; }
+ void setAxisMinSliderZ(QSlider *slider) { m_axisMinSliderZ = slider; }
+ void setSeries1CB(QCheckBox *cb) { m_series1CB = cb; }
+ void setSeries2CB(QCheckBox *cb) { m_series2CB = cb; }
+ void setSeries3CB(QCheckBox *cb) { m_series3CB = cb; }
+ void setSeries4CB(QCheckBox *cb) { m_series4CB = cb; }
+ void adjustXCount(int count);
+ void adjustZCount(int count);
+ void adjustXRange(int range);
+ void adjustYRange(int range);
+ void adjustZRange(int range);
+ void adjustXMin(int min);
+ void adjustYMin(int min);
+ void adjustZMin(int min);
+ void updateSamples();
+ void gradientPressed();
+ void changeFont(const QFont &font);
+ void changeStyle();
+ void selectButtonClicked();
+ void setSelectionInfoLabel(QLabel *label) {m_selectionInfoLabel = label; }
+ void selectedPointChanged(const QPoint &point);
+ void changeRow();
+ void changeRows();
+ void changeMesh();
+ void changeItem();
+ void changeMultipleItem();
+ void changeMultipleRows();
+ void addRow();
+ void addRows();
+ void insertRow();
+ void insertRows();
+ void removeRow();
+ void resetArray();
+ void resetArrayEmpty();
+ void massiveDataTest();
+ void massiveTestScroll();
+ void massiveTestAppendAndScroll();
+ void testAxisReverse();
+ void testDataOrdering();
+ void setAspectRatio(int ratio);
+ void setHorizontalAspectRatio(int ratio);
+ void setSurfaceTexture(int enabled);
+
+public Q_SLOTS:
+ void changeShadowQuality(int quality);
+ void changeTheme(int theme);
+ void flipViews();
+ void changeSelectionMode(int mode);
+ void timeout();
+ void graphQueryTimeout();
+
+ void handleAxisXChanged(QValue3DAxis *axis);
+ void handleAxisYChanged(QValue3DAxis *axis);
+ void handleAxisZChanged(QValue3DAxis *axis);
+ void handleFpsChange(qreal fps);
+ void changeLabelRotation(int rotation);
+ void toggleAxisTitleVisibility(int enabled);
+ void toggleAxisTitleFixed(int enabled);
+ void toggleXAscending(int enabled);
+ void toggleZAscending(int enabled);
+ void togglePolar(int enabled);
+ void setCameraTargetX(int value);
+ void setCameraTargetY(int value);
+ void setCameraTargetZ(int value);
+ void setGraphMargin(int value);
+
+private:
+ void fillSeries();
+ void resetArrayAndSliders(QSurfaceDataArray *array, float minZ, float maxZ, float minX,
+ float maxX);
+ QSurfaceDataRow *createMultiRow(int row, int series, bool change);
+ void populateRisingSeries(QSurface3DSeries *series, int rows, int columns, float minValue,
+ float maxValue, bool ascendingX, bool ascendingZ);
+
+ Q3DSurface *m_graph;
+ QSurface3DSeries *m_multiseries[4];
+ QSurface3DSeries *m_series1;
+ QSurface3DSeries *m_series2;
+ QSurface3DSeries *m_series3;
+ QSurface3DSeries *m_series4;
+ QSlider *m_gridSliderX;
+ QSlider *m_gridSliderZ;
+ QSlider *m_axisRangeSliderX;
+ QSlider *m_axisRangeSliderZ;
+ QSlider *m_axisMinSliderX;
+ QSlider *m_axisMinSliderZ;
+ QCheckBox *m_series1CB;
+ QCheckBox *m_series2CB;
+ QCheckBox *m_series3CB;
+ QCheckBox *m_series4CB;
+ bool m_gridSlidersLocked;
+ int m_xCount;
+ int m_zCount;
+ int m_activeSample;
+ int m_fontSize;
+ float m_rangeX;
+ float m_rangeY;
+ float m_rangeZ;
+ float m_minX;
+ float m_minY;
+ float m_minZ;
+ int m_addRowCounter;
+ int m_insertTestZPos;
+ int m_insertTestIndexPos;
+ QTimer m_timer;
+ QSurfaceDataArray *m_planeArray;
+ QLabel *m_selectionInfoLabel;
+ QSurface3DSeries *m_theSeries;
+ QSurface3DSeries::DrawFlags m_drawMode;
+ QSurface3DSeries::DrawFlags m_drawMode2;
+ QSurface3DSeries::DrawFlags m_drawMode3;
+ QSurface3DSeries::DrawFlags m_drawMode4;
+ float m_limitX;
+ float m_limitZ;
+ float m_offset;
+ float m_multiSampleOffsetX[4];
+ float m_multiSampleOffsetZ[4];
+ QSurfaceDataArray m_massiveTestCacheArray;
+ QVector3D m_cameraTarget;
+ QWidget *m_parentWidget;
+ QTimer m_graphPositionQueryTimer;
+ bool m_ascendingX;
+ bool m_ascendingZ;
+};
+
+#endif
diff --git a/tests/manual/surfacetest/main.cpp b/tests/manual/surfacetest/main.cpp
new file mode 100644
index 0000000..30c3e14
--- /dev/null
+++ b/tests/manual/surfacetest/main.cpp
@@ -0,0 +1,748 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "graphmodifier.h"
+#include "buttonwrapper.h"
+#include "checkboxwrapper.h"
+#include <QtGraphs/q3dtheme.h>
+
+#include <QApplication>
+#include <QWidget>
+#include <QHBoxLayout>
+#include <QVBoxLayout>
+#include <QPushButton>
+#include <QCheckBox>
+#include <QRadioButton>
+#include <QSlider>
+#include <QLabel>
+#include <QScreen>
+#include <QPainter>
+#include <QFontComboBox>
+#include <QFrame>
+#include <QDebug>
+
+const int initialTheme = 4;
+
+int main(int argc, char *argv[])
+{
+ qputenv("QSG_RHI_BACKEND", "opengl");
+ QApplication app(argc, argv);
+
+ QWidget *widget = new QWidget;
+ QHBoxLayout *hLayout = new QHBoxLayout(widget);
+ QVBoxLayout *vLayout = new QVBoxLayout();
+ QVBoxLayout *vLayout2 = new QVBoxLayout();
+ QVBoxLayout *vLayout3 = new QVBoxLayout();
+ vLayout->setAlignment(Qt::AlignTop);
+ vLayout2->setAlignment(Qt::AlignTop);
+ vLayout3->setAlignment(Qt::AlignTop);
+
+ Q3DSurface *surfaceGraph = new Q3DSurface();
+ QSize screenSize = surfaceGraph->screen()->size();
+
+ // Set to default, should be same as the initial on themeList
+ surfaceGraph->activeTheme()->setType(Q3DTheme::Theme(initialTheme));
+
+ QWidget *container = QWidget::createWindowContainer(surfaceGraph);
+ container->setMinimumSize(QSize(screenSize.width() / 2, screenSize.height() / 4));
+ container->setMaximumSize(screenSize);
+ container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+ container->setFocusPolicy(Qt::StrongFocus);
+
+ widget->setWindowTitle(QStringLiteral("Surface tester"));
+
+ hLayout->addWidget(container, 1);
+ hLayout->addLayout(vLayout);
+ hLayout->addLayout(vLayout2);
+ hLayout->addLayout(vLayout3);
+
+ QCheckBox *smoothCB = new QCheckBox(widget);
+ smoothCB->setText(QStringLiteral("Flat Surface"));
+ smoothCB->setChecked(true);
+
+ QCheckBox *surfaceGridCB = new QCheckBox(widget);
+ surfaceGridCB->setText(QStringLiteral("Surface Grid"));
+ surfaceGridCB->setChecked(true);
+
+ QCheckBox *surfaceCB = new QCheckBox(widget);
+ surfaceCB->setText(QStringLiteral("Surface Visible"));
+ surfaceCB->setChecked(true);
+
+ QCheckBox *seriesVisibleCB = new QCheckBox(widget);
+ seriesVisibleCB->setText(QStringLiteral("Series Visible"));
+ seriesVisibleCB->setChecked(true);
+
+#ifdef MULTI_SERIES
+ smoothCB->setText(QStringLiteral("S1 Flat Surface"));
+ surfaceGridCB->setText(QStringLiteral("S1 Surface Grid"));
+ surfaceCB->setText(QStringLiteral("S1 Surface Visible"));
+ seriesVisibleCB->setText(QStringLiteral("Series 1 Visible"));
+
+ QCheckBox *smoothS2CB = new QCheckBox(widget);
+ smoothS2CB->setText(QStringLiteral("S2 Flat Surface"));
+ smoothS2CB->setChecked(true);
+
+ QCheckBox *surfaceGridS2CB = new QCheckBox(widget);
+ surfaceGridS2CB->setText(QStringLiteral("S2 Surface Grid"));
+ surfaceGridS2CB->setChecked(true);
+
+ QCheckBox *surfaceS2CB = new QCheckBox(widget);
+ surfaceS2CB->setText(QStringLiteral("S2 Surface Visible"));
+ surfaceS2CB->setChecked(true);
+
+ QCheckBox *series2VisibleCB = new QCheckBox(widget);
+ series2VisibleCB->setText(QStringLiteral("Series 2 Visible"));
+ series2VisibleCB->setChecked(true);
+
+ QCheckBox *smoothS3CB = new QCheckBox(widget);
+ smoothS3CB->setText(QStringLiteral("S3 Flat Surface"));
+ smoothS3CB->setChecked(true);
+
+ QCheckBox *surfaceGridS3CB = new QCheckBox(widget);
+ surfaceGridS3CB->setText(QStringLiteral("S3 Surface Grid"));
+ surfaceGridS3CB->setChecked(true);
+
+ QCheckBox *surfaceS3CB = new QCheckBox(widget);
+ surfaceS3CB->setText(QStringLiteral("S3 Surface Visible"));
+ surfaceS3CB->setChecked(true);
+
+ QCheckBox *series3VisibleCB = new QCheckBox(widget);
+ series3VisibleCB->setText(QStringLiteral("Series 3 Visible"));
+ series3VisibleCB->setChecked(true);
+
+ QCheckBox *smoothS4CB = new QCheckBox(widget);
+ smoothS4CB->setText(QStringLiteral("S4 Flat Surface"));
+ smoothS4CB->setChecked(true);
+
+ QCheckBox *surfaceGridS4CB = new QCheckBox(widget);
+ surfaceGridS4CB->setText(QStringLiteral("S4 Surface Grid"));
+ surfaceGridS4CB->setChecked(true);
+
+ QCheckBox *surfaceS4CB = new QCheckBox(widget);
+ surfaceS4CB->setText(QStringLiteral("S4 Surface Visible"));
+ surfaceS4CB->setChecked(true);
+
+ QCheckBox *series4VisibleCB = new QCheckBox(widget);
+ series4VisibleCB->setText(QStringLiteral("Series 4 Visible"));
+ series4VisibleCB->setChecked(true);
+
+ QCheckBox *series1CB = new QCheckBox(widget);
+ series1CB->setText(QStringLiteral("Series 1"));
+
+ QCheckBox *series2CB = new QCheckBox(widget);
+ series2CB->setText(QStringLiteral("Series 2"));
+
+ QCheckBox *series3CB = new QCheckBox(widget);
+ series3CB->setText(QStringLiteral("Series 3"));
+
+ QCheckBox *series4CB = new QCheckBox(widget);
+ series4CB->setText(QStringLiteral("Series 4"));
+#else
+ //QCheckBox *sqrtSinCB = new QCheckBox(widget);
+ QRadioButton *sqrtSinCB = new QRadioButton(widget);
+ sqrtSinCB->setText(QStringLiteral("Sqrt & Sin"));
+ sqrtSinCB->setChecked(false);
+
+ QRadioButton *planeCB = new QRadioButton(widget);
+ planeCB->setText(QStringLiteral("Plane"));
+ planeCB->setChecked(false);
+
+ QRadioButton *heightMapCB = new QRadioButton(widget);
+ heightMapCB->setText(QStringLiteral("Height map"));
+ heightMapCB->setChecked(false);
+
+ QCheckBox *gridSlidersLockCB = new QCheckBox(widget);
+ gridSlidersLockCB->setText(QStringLiteral("Lock"));
+ gridSlidersLockCB->setChecked(false);
+
+ QSlider *gridSliderX = new QSlider(Qt::Horizontal, widget);
+ gridSliderX->setTickInterval(1);
+ gridSliderX->setMinimum(2);
+ gridSliderX->setValue(30);
+ gridSliderX->setMaximum(200);
+ gridSliderX->setEnabled(true);
+ QSlider *gridSliderZ = new QSlider(Qt::Horizontal, widget);
+ gridSliderZ->setTickInterval(1);
+ gridSliderZ->setMinimum(2);
+ gridSliderZ->setValue(30);
+ gridSliderZ->setMaximum(200);
+ gridSliderZ->setEnabled(true);
+#endif
+
+ QSlider *axisRangeSliderX = new QSlider(Qt::Horizontal, widget);
+ axisRangeSliderX->setTickInterval(1);
+ axisRangeSliderX->setMinimum(1);
+ axisRangeSliderX->setValue(34);
+ axisRangeSliderX->setMaximum(100);
+ axisRangeSliderX->setEnabled(true);
+ QSlider *axisRangeSliderY = new QSlider(Qt::Horizontal, widget);
+ axisRangeSliderY->setTickInterval(1);
+ axisRangeSliderY->setMinimum(1);
+ axisRangeSliderY->setValue(16);
+ axisRangeSliderY->setMaximum(100);
+ axisRangeSliderY->setEnabled(true);
+ QSlider *axisRangeSliderZ = new QSlider(Qt::Horizontal, widget);
+ axisRangeSliderZ->setTickInterval(1);
+ axisRangeSliderZ->setMinimum(1);
+ axisRangeSliderZ->setValue(34);
+ axisRangeSliderZ->setMaximum(100);
+ axisRangeSliderZ->setEnabled(true);
+
+ QSlider *axisMinSliderX = new QSlider(Qt::Horizontal, widget);
+ axisMinSliderX->setTickInterval(1);
+ axisMinSliderX->setMinimum(-100);
+ axisMinSliderX->setValue(-17);
+ axisMinSliderX->setMaximum(100);
+ axisMinSliderX->setEnabled(true);
+ QSlider *axisMinSliderY = new QSlider(Qt::Horizontal, widget);
+ axisMinSliderY->setTickInterval(1);
+ axisMinSliderY->setMinimum(-100);
+ axisMinSliderY->setValue(-8);
+ axisMinSliderY->setMaximum(100);
+ axisMinSliderY->setEnabled(true);
+ QSlider *axisMinSliderZ = new QSlider(Qt::Horizontal, widget);
+ axisMinSliderZ->setTickInterval(1);
+ axisMinSliderZ->setMinimum(-100);
+ axisMinSliderZ->setValue(-17);
+ axisMinSliderZ->setMaximum(100);
+ axisMinSliderZ->setEnabled(true);
+
+ QSlider *aspectRatioSlider = new QSlider(Qt::Horizontal, widget);
+ aspectRatioSlider->setMinimum(1);
+ aspectRatioSlider->setValue(20);
+ aspectRatioSlider->setMaximum(100);
+
+ QSlider *horizontalAspectRatioSlider = new QSlider(Qt::Horizontal, widget);
+ horizontalAspectRatioSlider->setMinimum(0);
+ horizontalAspectRatioSlider->setValue(0);
+ horizontalAspectRatioSlider->setMaximum(300);
+
+ QLinearGradient gr(0, 0, 100, 1);
+ gr.setColorAt(0.0, Qt::black);
+ gr.setColorAt(0.33, Qt::blue);
+ gr.setColorAt(0.67, Qt::red);
+ gr.setColorAt(1.0, Qt::yellow);
+ QPixmap pm(100, 24);
+ QPainter pmp(&pm);
+ pmp.setBrush(QBrush(gr));
+ pmp.setPen(Qt::NoPen);
+ pmp.drawRect(0, 0, 100, 24);
+ QPushButton *colorPB = new QPushButton();
+ colorPB->setIcon(QIcon(pm));
+ colorPB->setIconSize(QSize(100, 24));
+
+ QFontComboBox *fontList = new QFontComboBox(widget);
+ fontList->setCurrentFont(QFont("Arial"));
+
+ QPushButton *labelButton = new QPushButton(widget);
+ labelButton->setText(QStringLiteral("Change label style"));
+
+ QPushButton *meshButton = new QPushButton(widget);
+ meshButton->setText(QStringLiteral("Change pointer mesh"));
+
+ QComboBox *themeList = new QComboBox(widget);
+ themeList->addItem(QStringLiteral("Qt"));
+ themeList->addItem(QStringLiteral("Primary Colors"));
+ themeList->addItem(QStringLiteral("Digia"));
+ themeList->addItem(QStringLiteral("Stone Moss"));
+ themeList->addItem(QStringLiteral("Army Blue"));
+ themeList->addItem(QStringLiteral("Retro"));
+ themeList->addItem(QStringLiteral("Ebony"));
+ themeList->addItem(QStringLiteral("Isabelle"));
+ themeList->setCurrentIndex(initialTheme);
+
+ QComboBox *shadowQuality = new QComboBox(widget);
+ shadowQuality->addItem(QStringLiteral("None"));
+ shadowQuality->addItem(QStringLiteral("Low"));
+ shadowQuality->addItem(QStringLiteral("Medium"));
+ shadowQuality->addItem(QStringLiteral("High"));
+ shadowQuality->addItem(QStringLiteral("Low Soft"));
+ shadowQuality->addItem(QStringLiteral("Medium Soft"));
+ shadowQuality->addItem(QStringLiteral("High Soft"));
+ shadowQuality->setCurrentIndex(3);
+
+ QComboBox *selectionMode = new QComboBox(widget);
+ selectionMode->addItem(QStringLiteral("None"),
+ int(QAbstract3DGraph::SelectionNone));
+ selectionMode->addItem(QStringLiteral("Item"),
+ int(QAbstract3DGraph::SelectionItem));
+ selectionMode->addItem(QStringLiteral("Multi: Item"),
+ int(QAbstract3DGraph::SelectionItem | QAbstract3DGraph::SelectionMultiSeries));
+ selectionMode->addItem(QStringLiteral("Row"),
+ int(QAbstract3DGraph::SelectionRow));
+ selectionMode->addItem(QStringLiteral("Item and Row"),
+ int(QAbstract3DGraph::SelectionItemAndRow));
+ selectionMode->addItem(QStringLiteral("Column"),
+ int(QAbstract3DGraph::SelectionColumn));
+ selectionMode->addItem(QStringLiteral("Item and Column"),
+ int(QAbstract3DGraph::SelectionItemAndColumn));
+ selectionMode->addItem(QStringLiteral("Row and Column"),
+ int(QAbstract3DGraph::SelectionRowAndColumn));
+ selectionMode->addItem(QStringLiteral("Item, Row and Column"),
+ int(QAbstract3DGraph::SelectionItemRowAndColumn));
+ selectionMode->addItem(QStringLiteral("Multi: Item, Row and Column"),
+ int(QAbstract3DGraph::SelectionItemRowAndColumn | QAbstract3DGraph::SelectionMultiSeries));
+ selectionMode->addItem(QStringLiteral("Slice into Row"),
+ int(QAbstract3DGraph::SelectionSlice | QAbstract3DGraph::SelectionRow));
+ selectionMode->addItem(QStringLiteral("Slice into Row and Item"),
+ int(QAbstract3DGraph::SelectionSlice | QAbstract3DGraph::SelectionItemAndRow));
+ selectionMode->addItem(QStringLiteral("Multi: Slice, Row & Item"),
+ int(QAbstract3DGraph::SelectionSlice | QAbstract3DGraph::SelectionItemAndRow
+ | QAbstract3DGraph::SelectionMultiSeries));
+ selectionMode->addItem(QStringLiteral("Slice into Column"),
+ int(QAbstract3DGraph::SelectionSlice | QAbstract3DGraph::SelectionColumn));
+ selectionMode->addItem(QStringLiteral("Slice into Column and Item"),
+ int(QAbstract3DGraph::SelectionSlice | QAbstract3DGraph::SelectionItemAndColumn));
+ selectionMode->addItem(QStringLiteral("Multi: Slice, Column & Item"),
+ int(QAbstract3DGraph::SelectionSlice | QAbstract3DGraph::SelectionItemAndColumn
+ | QAbstract3DGraph::SelectionMultiSeries));
+
+#ifndef MULTI_SERIES
+ QPushButton *selectButton = new QPushButton(widget);
+ selectButton->setText(QStringLiteral("Select random point"));
+
+ QPushButton *flipViewsButton = new QPushButton(widget);
+ flipViewsButton->setText(QStringLiteral("Flip Views"));
+
+ QLabel *selectionInfoLabel = new QLabel(widget);
+#endif
+
+ QPushButton *changeRowButton = new QPushButton(widget);
+ changeRowButton->setText(QStringLiteral("Change a row"));
+
+ QPushButton *changeRowsButton = new QPushButton(widget);
+ changeRowsButton->setText(QStringLiteral("Change 3 rows"));
+
+ QPushButton *changeItemButton = new QPushButton(widget);
+ changeItemButton->setText(QStringLiteral("Change item"));
+
+ QPushButton *changeMultipleItemButton = new QPushButton(widget);
+ changeMultipleItemButton->setText(QStringLiteral("Change many items"));
+
+ QPushButton *changeMultipleRowsButton = new QPushButton(widget);
+ changeMultipleRowsButton->setText(QStringLiteral("Change many rows"));
+
+ QPushButton *addRowButton = new QPushButton(widget);
+ addRowButton->setText(QStringLiteral("Add a row"));
+
+ QPushButton *addRowsButton = new QPushButton(widget);
+ addRowsButton->setText(QStringLiteral("Add 3 rows"));
+
+ QPushButton *insertRowButton = new QPushButton(widget);
+ insertRowButton->setText(QStringLiteral("Insert a row"));
+
+ QPushButton *insertRowsButton = new QPushButton(widget);
+ insertRowsButton->setText(QStringLiteral("Insert 3 rows"));
+
+ QPushButton *removeRowButton = new QPushButton(widget);
+ removeRowButton->setText(QStringLiteral("Remove a row"));
+
+ QPushButton *resetArrayButton = new QPushButton(widget);
+ resetArrayButton->setText(QStringLiteral("Reset Series Array to plane"));
+
+ QPushButton *resetArrayEmptyButton = new QPushButton(widget);
+ resetArrayEmptyButton->setText(QStringLiteral("Reset Series Array to empty"));
+
+ QPushButton *massiveDataTestButton = new QPushButton(widget);
+ massiveDataTestButton->setText(QStringLiteral("Massive data test"));
+
+ QPushButton *testReverseButton = new QPushButton(widget);
+ testReverseButton->setText(QStringLiteral("Test Axis Reversing"));
+
+ QPushButton *testDataOrderingButton = new QPushButton(widget);
+ testDataOrderingButton->setText(QStringLiteral("Test data ordering"));
+
+ QFrame* line = new QFrame();
+ line->setFrameShape(QFrame::HLine);
+ line->setFrameShadow(QFrame::Sunken);
+
+ QFrame* line2 = new QFrame();
+ line2->setFrameShape(QFrame::HLine);
+ line2->setFrameShadow(QFrame::Sunken);
+
+ QFrame* line3 = new QFrame();
+ line3->setFrameShape(QFrame::HLine);
+ line3->setFrameShadow(QFrame::Sunken);
+
+ QCheckBox *axisTitlesVisibleCB = new QCheckBox(widget);
+ axisTitlesVisibleCB->setText(QStringLiteral("Axis titles visible"));
+ axisTitlesVisibleCB->setChecked(false);
+
+ QCheckBox *axisTitlesFixedCB = new QCheckBox(widget);
+ axisTitlesFixedCB->setText(QStringLiteral("Axis titles fixed"));
+ axisTitlesFixedCB->setChecked(true);
+
+ QSlider *axisLabelRotationSlider = new QSlider(Qt::Horizontal, widget);
+ axisLabelRotationSlider->setTickInterval(10);
+ axisLabelRotationSlider->setTickPosition(QSlider::TicksBelow);
+ axisLabelRotationSlider->setMinimum(0);
+ axisLabelRotationSlider->setValue(0);
+ axisLabelRotationSlider->setMaximum(90);
+
+ QCheckBox *xAscendingCB = new QCheckBox(widget);
+ xAscendingCB->setText(QStringLiteral("X Ascending"));
+ xAscendingCB->setChecked(true);
+
+ QCheckBox *zAscendingCB = new QCheckBox(widget);
+ zAscendingCB->setText(QStringLiteral("Z Ascending"));
+ zAscendingCB->setChecked(true);
+
+ QCheckBox *polarCB = new QCheckBox(widget);
+ polarCB->setText(QStringLiteral("Polar"));
+ polarCB->setChecked(false);
+
+ QCheckBox *surfaceTextureCB = new QCheckBox(widget);
+ surfaceTextureCB->setText(QStringLiteral("Map texture"));
+ surfaceTextureCB->setChecked(false);
+
+ QSlider *cameraTargetSliderX = new QSlider(Qt::Horizontal, widget);
+ cameraTargetSliderX->setTickInterval(1);
+ cameraTargetSliderX->setMinimum(-100);
+ cameraTargetSliderX->setValue(0);
+ cameraTargetSliderX->setMaximum(100);
+ QSlider *cameraTargetSliderY = new QSlider(Qt::Horizontal, widget);
+ cameraTargetSliderY->setTickInterval(1);
+ cameraTargetSliderY->setMinimum(-100);
+ cameraTargetSliderY->setValue(0);
+ cameraTargetSliderY->setMaximum(100);
+ QSlider *cameraTargetSliderZ = new QSlider(Qt::Horizontal, widget);
+ cameraTargetSliderZ->setTickInterval(1);
+ cameraTargetSliderZ->setMinimum(-100);
+ cameraTargetSliderZ->setValue(0);
+ cameraTargetSliderZ->setMaximum(100);
+
+ QSlider *marginSlider = new QSlider(Qt::Horizontal, widget);
+ marginSlider->setMinimum(-1);
+ marginSlider->setValue(-1);
+ marginSlider->setMaximum(100);
+
+ // Add controls to the layout
+#ifdef MULTI_SERIES
+ vLayout->addWidget(series1CB);
+#endif
+ vLayout->addWidget(smoothCB);
+ vLayout->addWidget(surfaceGridCB);
+ vLayout->addWidget(surfaceCB);
+ vLayout->addWidget(seriesVisibleCB);
+#ifdef MULTI_SERIES
+ vLayout->addWidget(line);
+ vLayout->addWidget(series2CB);
+ vLayout->addWidget(smoothS2CB);
+ vLayout->addWidget(surfaceGridS2CB);
+ vLayout->addWidget(surfaceS2CB);
+ vLayout->addWidget(series2VisibleCB);
+ vLayout->addWidget(line2);
+ vLayout->addWidget(series3CB);
+ vLayout->addWidget(smoothS3CB);
+ vLayout->addWidget(surfaceGridS3CB);
+ vLayout->addWidget(surfaceS3CB);
+ vLayout->addWidget(series3VisibleCB);
+ vLayout->addWidget(line3);
+ vLayout->addWidget(series4CB);
+ vLayout->addWidget(smoothS4CB);
+ vLayout->addWidget(surfaceGridS4CB);
+ vLayout->addWidget(surfaceS4CB);
+ vLayout->addWidget(series4VisibleCB);
+ vLayout->addWidget(surfaceTextureCB, 1, Qt::AlignTop);
+#endif
+#ifndef MULTI_SERIES
+ vLayout->addWidget(new QLabel(QStringLiteral("Select surface sample")));
+ vLayout->addWidget(sqrtSinCB);
+ vLayout->addWidget(planeCB);
+ vLayout->addWidget(heightMapCB);
+ vLayout->addWidget(new QLabel(QStringLiteral("Adjust sample count")));
+ vLayout->addWidget(gridSlidersLockCB);
+ vLayout->addWidget(gridSliderX);
+ vLayout->addWidget(gridSliderZ, 1, Qt::AlignTop);
+#endif
+
+ vLayout2->addWidget(new QLabel(QStringLiteral("Adjust vertical aspect ratio")));
+ vLayout2->addWidget(aspectRatioSlider);
+ vLayout2->addWidget(new QLabel(QStringLiteral("Adjust horizontal aspect ratio")));
+ vLayout2->addWidget(horizontalAspectRatioSlider);
+ vLayout2->addWidget(new QLabel(QStringLiteral("Adjust axis range")));
+ vLayout2->addWidget(axisRangeSliderX);
+ vLayout2->addWidget(axisRangeSliderY);
+ vLayout2->addWidget(axisRangeSliderZ);
+ vLayout2->addWidget(new QLabel(QStringLiteral("Adjust axis minimum")));
+ vLayout2->addWidget(axisMinSliderX);
+ vLayout2->addWidget(axisMinSliderY);
+ vLayout2->addWidget(axisMinSliderZ);
+ vLayout2->addWidget(xAscendingCB);
+ vLayout2->addWidget(zAscendingCB);
+ vLayout2->addWidget(polarCB);
+ vLayout2->addWidget(new QLabel(QStringLiteral("Change font")));
+ vLayout2->addWidget(fontList);
+ vLayout2->addWidget(new QLabel(QStringLiteral("Change theme")));
+ vLayout2->addWidget(themeList);
+ vLayout2->addWidget(new QLabel(QStringLiteral("Adjust shadow quality")));
+ vLayout2->addWidget(shadowQuality);
+ vLayout2->addWidget(new QLabel(QStringLiteral("Selection Mode")));
+ vLayout2->addWidget(selectionMode);
+ vLayout2->addWidget(new QLabel(QStringLiteral("Camera target")));
+ vLayout2->addWidget(cameraTargetSliderX);
+ vLayout2->addWidget(cameraTargetSliderY);
+ vLayout2->addWidget(cameraTargetSliderZ);
+ vLayout2->addWidget(new QLabel(QStringLiteral("Adjust margin")), 0, Qt::AlignTop);
+ vLayout2->addWidget(marginSlider, 1, Qt::AlignTop);
+
+ vLayout3->addWidget(labelButton);
+ vLayout3->addWidget(meshButton);
+#ifndef MULTI_SERIES
+ vLayout3->addWidget(selectButton);
+ vLayout3->addWidget(selectionInfoLabel);
+ vLayout3->addWidget(flipViewsButton);
+#endif
+
+ vLayout3->addWidget(colorPB);
+ vLayout3->addWidget(changeRowButton);
+ vLayout3->addWidget(changeRowsButton);
+ vLayout3->addWidget(changeMultipleRowsButton);
+ vLayout3->addWidget(changeItemButton);
+ vLayout3->addWidget(changeMultipleItemButton);
+ vLayout3->addWidget(addRowButton);
+ vLayout3->addWidget(addRowsButton);
+ vLayout3->addWidget(insertRowButton);
+ vLayout3->addWidget(insertRowsButton);
+ vLayout3->addWidget(removeRowButton);
+ vLayout3->addWidget(resetArrayButton);
+ vLayout3->addWidget(resetArrayEmptyButton);
+ vLayout3->addWidget(massiveDataTestButton);
+ vLayout3->addWidget(testReverseButton);
+ vLayout3->addWidget(testDataOrderingButton);
+ vLayout3->addWidget(axisTitlesVisibleCB);
+ vLayout3->addWidget(axisTitlesFixedCB);
+ vLayout3->addWidget(new QLabel(QStringLiteral("Axis label rotation")));
+ vLayout3->addWidget(axisLabelRotationSlider, 1, Qt::AlignTop);
+
+ widget->show();
+
+ GraphModifier *modifier = new GraphModifier(surfaceGraph, container);
+
+ // Connect controls to slots on modifier
+ QObject::connect(smoothCB, &QCheckBox::stateChanged,
+ modifier, &GraphModifier::toggleSmooth);
+ QObject::connect(surfaceGridCB, &QCheckBox::stateChanged,
+ modifier, &GraphModifier::toggleSurfaceGrid);
+ QObject::connect(surfaceCB, &QCheckBox::stateChanged,
+ modifier, &GraphModifier::toggleSurface);
+ QObject::connect(seriesVisibleCB, &QCheckBox::stateChanged,
+ modifier, &GraphModifier::toggleSeriesVisible);
+#ifdef MULTI_SERIES
+ QObject::connect(smoothS2CB, &QCheckBox::stateChanged,
+ modifier, &GraphModifier::toggleSmoothS2);
+ QObject::connect(surfaceGridS2CB, &QCheckBox::stateChanged,
+ modifier, &GraphModifier::toggleSurfaceGridS2);
+ QObject::connect(surfaceS2CB, &QCheckBox::stateChanged,
+ modifier, &GraphModifier::toggleSurfaceS2);
+ QObject::connect(series2VisibleCB, &QCheckBox::stateChanged,
+ modifier, &GraphModifier::toggleSeries2Visible);
+
+ QObject::connect(smoothS3CB, &QCheckBox::stateChanged,
+ modifier, &GraphModifier::toggleSmoothS3);
+ QObject::connect(surfaceGridS3CB, &QCheckBox::stateChanged,
+ modifier, &GraphModifier::toggleSurfaceGridS3);
+ QObject::connect(surfaceS3CB, &QCheckBox::stateChanged,
+ modifier, &GraphModifier::toggleSurfaceS3);
+ QObject::connect(series3VisibleCB, &QCheckBox::stateChanged,
+ modifier, &GraphModifier::toggleSeries3Visible);
+
+ QObject::connect(smoothS4CB, &QCheckBox::stateChanged,
+ modifier, &GraphModifier::toggleSmoothS4);
+ QObject::connect(surfaceGridS4CB, &QCheckBox::stateChanged,
+ modifier, &GraphModifier::toggleSurfaceGridS4);
+ QObject::connect(surfaceS4CB, &QCheckBox::stateChanged,
+ modifier, &GraphModifier::toggleSurfaceS4);
+ QObject::connect(series4VisibleCB, &QCheckBox::stateChanged,
+ modifier, &GraphModifier::toggleSeries4Visible);
+
+ CheckBoxWrapper *series1SmoothCBWrapper = new CheckBoxWrapper(smoothCB);
+ CheckBoxWrapper *series1SurfaceGridCBWrapper = new CheckBoxWrapper(surfaceGridCB);
+ CheckBoxWrapper *series1surfaceCBWrapper = new CheckBoxWrapper(surfaceCB);
+ CheckBoxWrapper *series1VisibleCBWrapper = new CheckBoxWrapper(seriesVisibleCB);
+ QObject::connect(series1CB, &QCheckBox::stateChanged,
+ modifier, &GraphModifier::toggleSeries1);
+ QObject::connect(series1CB, &QCheckBox::stateChanged,
+ series1SmoothCBWrapper, &CheckBoxWrapper::setEnabled);
+ QObject::connect(series1CB, &QCheckBox::stateChanged,
+ series1SurfaceGridCBWrapper, &CheckBoxWrapper::setEnabled);
+ QObject::connect(series1CB, &QCheckBox::stateChanged,
+ series1surfaceCBWrapper, &CheckBoxWrapper::setEnabled);
+ QObject::connect(series1CB, &QCheckBox::stateChanged,
+ series1VisibleCBWrapper, &CheckBoxWrapper::setEnabled);
+
+
+ CheckBoxWrapper *series2SmoothCBWrapper = new CheckBoxWrapper(smoothS2CB);
+ CheckBoxWrapper *series2SurfaceGridCBWrapper = new CheckBoxWrapper(surfaceGridS2CB);
+ CheckBoxWrapper *series2surfaceCBWrapper = new CheckBoxWrapper(surfaceS2CB);
+ CheckBoxWrapper *series2VisibleCBWrapper = new CheckBoxWrapper(series2VisibleCB);
+ QObject::connect(series2CB, &QCheckBox::stateChanged,
+ modifier, &GraphModifier::toggleSeries2);
+ QObject::connect(series2CB, &QCheckBox::stateChanged,
+ series2SmoothCBWrapper, &CheckBoxWrapper::setEnabled);
+ QObject::connect(series2CB, &QCheckBox::stateChanged,
+ series2SurfaceGridCBWrapper, &CheckBoxWrapper::setEnabled);
+ QObject::connect(series2CB, &QCheckBox::stateChanged,
+ series2surfaceCBWrapper, &CheckBoxWrapper::setEnabled);
+ QObject::connect(series2CB, &QCheckBox::stateChanged,
+ series2VisibleCBWrapper, &CheckBoxWrapper::setEnabled);
+
+ CheckBoxWrapper *series3SmoothCBWrapper = new CheckBoxWrapper(smoothS3CB);
+ CheckBoxWrapper *series3SurfaceGridCBWrapper = new CheckBoxWrapper(surfaceGridS3CB);
+ CheckBoxWrapper *series3surfaceCBWrapper = new CheckBoxWrapper(surfaceS3CB);
+ CheckBoxWrapper *series3VisibleCBWrapper = new CheckBoxWrapper(series3VisibleCB);
+ QObject::connect(series3CB, &QCheckBox::stateChanged,
+ modifier, &GraphModifier::toggleSeries3);
+ QObject::connect(series3CB, &QCheckBox::stateChanged,
+ series3SmoothCBWrapper, &CheckBoxWrapper::setEnabled);
+ QObject::connect(series3CB, &QCheckBox::stateChanged,
+ series3SurfaceGridCBWrapper, &CheckBoxWrapper::setEnabled);
+ QObject::connect(series3CB, &QCheckBox::stateChanged,
+ series3surfaceCBWrapper, &CheckBoxWrapper::setEnabled);
+ QObject::connect(series3CB, &QCheckBox::stateChanged,
+ series3VisibleCBWrapper, &CheckBoxWrapper::setEnabled);
+
+ CheckBoxWrapper *series4SmoothCBWrapper = new CheckBoxWrapper(smoothS4CB);
+ CheckBoxWrapper *series4SurfaceGridCBWrapper = new CheckBoxWrapper(surfaceGridS4CB);
+ CheckBoxWrapper *series4surfaceCBWrapper = new CheckBoxWrapper(surfaceS4CB);
+ CheckBoxWrapper *series4VisibleCBWrapper = new CheckBoxWrapper(series4VisibleCB);
+ QObject::connect(series4CB, &QCheckBox::stateChanged,
+ modifier, &GraphModifier::toggleSeries4);
+ QObject::connect(series4CB, &QCheckBox::stateChanged,
+ series4SmoothCBWrapper, &CheckBoxWrapper::setEnabled);
+ QObject::connect(series4CB, &QCheckBox::stateChanged,
+ series4SurfaceGridCBWrapper, &CheckBoxWrapper::setEnabled);
+ QObject::connect(series4CB, &QCheckBox::stateChanged,
+ series4surfaceCBWrapper, &CheckBoxWrapper::setEnabled);
+ QObject::connect(series4CB, &QCheckBox::stateChanged,
+ series4VisibleCBWrapper, &CheckBoxWrapper::setEnabled);
+#else
+ QObject::connect(sqrtSinCB, &QRadioButton::toggled,
+ modifier, &GraphModifier::toggleSqrtSin);
+ QObject::connect(planeCB, &QCheckBox::toggled,
+ modifier, &GraphModifier::togglePlane);
+ QObject::connect(heightMapCB, &QCheckBox::toggled,
+ modifier, &GraphModifier::setHeightMapData);
+ QObject::connect(gridSlidersLockCB, &QCheckBox::stateChanged,
+ modifier, &GraphModifier::toggleGridSliderLock);
+ QObject::connect(gridSliderX, &QSlider::valueChanged,
+ modifier, &GraphModifier::adjustXCount);
+ QObject::connect(gridSliderZ, &QSlider::valueChanged,
+ modifier, &GraphModifier::adjustZCount);
+#endif
+ QObject::connect(axisRangeSliderX, &QSlider::valueChanged,
+ modifier, &GraphModifier::adjustXRange);
+ QObject::connect(axisRangeSliderY, &QSlider::valueChanged,
+ modifier, &GraphModifier::adjustYRange);
+ QObject::connect(axisRangeSliderZ, &QSlider::valueChanged,
+ modifier, &GraphModifier::adjustZRange);
+ QObject::connect(axisMinSliderX, &QSlider::valueChanged,
+ modifier, &GraphModifier::adjustXMin);
+ QObject::connect(axisMinSliderY, &QSlider::valueChanged,
+ modifier, &GraphModifier::adjustYMin);
+ QObject::connect(axisMinSliderZ, &QSlider::valueChanged,
+ modifier, &GraphModifier::adjustZMin);
+ QObject::connect(colorPB, &QPushButton::pressed,
+ modifier, &GraphModifier::gradientPressed);
+ QObject::connect(fontList, &QFontComboBox::currentFontChanged,
+ modifier, &GraphModifier::changeFont);
+ QObject::connect(labelButton, &QPushButton::clicked,
+ modifier, &GraphModifier::changeStyle);
+ QObject::connect(meshButton, &QPushButton::clicked,
+ modifier, &GraphModifier::changeMesh);
+ QObject::connect(themeList, SIGNAL(currentIndexChanged(int)),
+ modifier, SLOT(changeTheme(int)));
+ QObject::connect(shadowQuality, SIGNAL(currentIndexChanged(int)),
+ modifier, SLOT(changeShadowQuality(int)));
+ QObject::connect(selectionMode, SIGNAL(currentIndexChanged(int)),
+ modifier, SLOT(changeSelectionMode(int)));
+#ifndef MULTI_SERIES
+ QObject::connect(selectButton, &QPushButton::clicked,
+ modifier, &GraphModifier::selectButtonClicked);
+ QObject::connect(flipViewsButton, &QPushButton::clicked,
+ modifier, &GraphModifier::flipViews);
+#endif
+ QObject::connect(changeRowButton,&QPushButton::clicked,
+ modifier, &GraphModifier::changeRow);
+ QObject::connect(changeRowsButton,&QPushButton::clicked,
+ modifier, &GraphModifier::changeRows);
+ QObject::connect(changeItemButton,&QPushButton::clicked,
+ modifier, &GraphModifier::changeItem);
+ QObject::connect(changeMultipleItemButton,&QPushButton::clicked,
+ modifier, &GraphModifier::changeMultipleItem);
+ QObject::connect(changeMultipleRowsButton,&QPushButton::clicked,
+ modifier, &GraphModifier::changeMultipleRows);
+ QObject::connect(addRowButton,&QPushButton::clicked,
+ modifier, &GraphModifier::addRow);
+ QObject::connect(addRowsButton,&QPushButton::clicked,
+ modifier, &GraphModifier::addRows);
+ QObject::connect(insertRowButton,&QPushButton::clicked,
+ modifier, &GraphModifier::insertRow);
+ QObject::connect(insertRowsButton,&QPushButton::clicked,
+ modifier, &GraphModifier::insertRows);
+ QObject::connect(removeRowButton,&QPushButton::clicked,
+ modifier, &GraphModifier::removeRow);
+ QObject::connect(resetArrayButton,&QPushButton::clicked,
+ modifier, &GraphModifier::resetArray);
+ QObject::connect(resetArrayEmptyButton,&QPushButton::clicked,
+ modifier, &GraphModifier::resetArrayEmpty);
+ QObject::connect(massiveDataTestButton,&QPushButton::clicked,
+ modifier, &GraphModifier::massiveDataTest);
+ QObject::connect(testReverseButton, &QPushButton::clicked,
+ modifier, &GraphModifier::testAxisReverse);
+ QObject::connect(testDataOrderingButton, &QPushButton::clicked,
+ modifier, &GraphModifier::testDataOrdering);
+ QObject::connect(axisTitlesVisibleCB, &QCheckBox::stateChanged,
+ modifier, &GraphModifier::toggleAxisTitleVisibility);
+ QObject::connect(axisTitlesFixedCB, &QCheckBox::stateChanged,
+ modifier, &GraphModifier::toggleAxisTitleFixed);
+ QObject::connect(axisLabelRotationSlider, &QSlider::valueChanged, modifier,
+ &GraphModifier::changeLabelRotation);
+ QObject::connect(xAscendingCB, &QCheckBox::stateChanged,
+ modifier, &GraphModifier::toggleXAscending);
+ QObject::connect(zAscendingCB, &QCheckBox::stateChanged,
+ modifier, &GraphModifier::toggleZAscending);
+ QObject::connect(polarCB, &QCheckBox::stateChanged,
+ modifier, &GraphModifier::togglePolar);
+
+ QObject::connect(aspectRatioSlider, &QSlider::valueChanged,
+ modifier, &GraphModifier::setAspectRatio);
+ QObject::connect(horizontalAspectRatioSlider, &QSlider::valueChanged,
+ modifier, &GraphModifier::setHorizontalAspectRatio);
+ QObject::connect(surfaceTextureCB, &QCheckBox::stateChanged,
+ modifier, &GraphModifier::setSurfaceTexture);
+ QObject::connect(cameraTargetSliderX, &QSlider::valueChanged, modifier,
+ &GraphModifier::setCameraTargetX);
+ QObject::connect(cameraTargetSliderY, &QSlider::valueChanged, modifier,
+ &GraphModifier::setCameraTargetY);
+ QObject::connect(cameraTargetSliderZ, &QSlider::valueChanged, modifier,
+ &GraphModifier::setCameraTargetZ);
+ QObject::connect(marginSlider, &QSlider::valueChanged, modifier,
+ &GraphModifier::setGraphMargin);
+
+#ifdef MULTI_SERIES
+ modifier->setSeries1CB(series1CB);
+ modifier->setSeries2CB(series2CB);
+ modifier->setSeries3CB(series3CB);
+ modifier->setSeries4CB(series4CB);
+ series1CB->setChecked(true);
+ series2CB->setChecked(true);
+ series3CB->setChecked(true);
+ series4CB->setChecked(true);
+#endif
+ modifier->setAxisRangeSliderX(axisRangeSliderX);
+ modifier->setAxisRangeSliderZ(axisRangeSliderZ);
+ modifier->setAxisMinSliderX(axisMinSliderX);
+ modifier->setAxisMinSliderZ(axisMinSliderZ);
+ selectionMode->setCurrentIndex(1);
+#ifndef MULTI_SERIES
+ modifier->setGridSliderZ(gridSliderZ);
+ modifier->setGridSliderX(gridSliderX);
+ modifier->toggleGridSliderLock(gridSlidersLockCB->checkState());
+ modifier->setSelectionInfoLabel(selectionInfoLabel);
+ sqrtSinCB->setChecked(true);
+#endif
+ shadowQuality->setCurrentIndex(3);
+
+ return app.exec();
+}
diff --git a/tests/manual/surfacetest/mapimage.png b/tests/manual/surfacetest/mapimage.png
new file mode 100644
index 0000000..079d040
--- /dev/null
+++ b/tests/manual/surfacetest/mapimage.png
Binary files differ
diff --git a/tests/manual/surfacetest/surfacetest.pro b/tests/manual/surfacetest/surfacetest.pro
new file mode 100644
index 0000000..134e8a6
--- /dev/null
+++ b/tests/manual/surfacetest/surfacetest.pro
@@ -0,0 +1,14 @@
+!include( ../tests.pri ) {
+ error( "Couldn't find the tests.pri file!" )
+}
+
+SOURCES += main.cpp \
+ graphmodifier.cpp
+
+QT += widgets
+
+HEADERS += \
+ graphmodifier.h
+
+RESOURCES += \
+ surfacetest.qrc
diff --git a/tests/manual/surfacetest/surfacetest.qrc b/tests/manual/surfacetest/surfacetest.qrc
new file mode 100644
index 0000000..266ed7e
--- /dev/null
+++ b/tests/manual/surfacetest/surfacetest.qrc
@@ -0,0 +1,6 @@
+<RCC>
+ <qresource prefix="/maps">
+ <file alias="map">Heightmap.png</file>
+ <file alias="mapimage">mapimage.png</file>
+ </qresource>
+</RCC>
diff --git a/tests/manual/tests.pri b/tests/manual/tests.pri
new file mode 100644
index 0000000..e367c3f
--- /dev/null
+++ b/tests/manual/tests.pri
@@ -0,0 +1,14 @@
+INCLUDEPATH += ../../../include
+
+LIBS += -L$$OUT_PWD/../../lib
+
+TEMPLATE = app
+
+QT += graphs
+
+contains(TARGET, qml.*) {
+ QT += qml quick
+}
+
+target.path = $$[QT_INSTALL_TESTS]/graphs/$$TARGET
+INSTALLS += target
diff --git a/tests/manual/volumetrictest/CMakeLists.txt b/tests/manual/volumetrictest/CMakeLists.txt
new file mode 100644
index 0000000..abb2f24
--- /dev/null
+++ b/tests/manual/volumetrictest/CMakeLists.txt
@@ -0,0 +1,33 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+qt_internal_add_manual_test(volumetrictest
+ GUI
+ SOURCES
+ main.cpp
+ volumetrictest.cpp volumetrictest.h
+ )
+
+target_link_libraries(volumetrictest PUBLIC
+ Qt::Gui
+ Qt::Widgets
+ Qt::Graphs
+ )
+
+set_source_files_properties("cubeFilledFlat.obj"
+ PROPERTIES QT_RESOURCE_ALIAS "mesh"
+ )
+set(volumetrictest_resource_files
+ "cubeFilledFlat.obj"
+ "logo.png"
+ "logo_no_padding.png"
+ )
+
+qt_internal_add_resource(volumetrictest "volumetrictest"
+ PREFIX
+ "/"
+ FILES
+ ${volumetrictest_resource_files}
+ )
diff --git a/tests/manual/volumetrictest/cubeFilledFlat.obj b/tests/manual/volumetrictest/cubeFilledFlat.obj
new file mode 100644
index 0000000..108cf7a
--- /dev/null
+++ b/tests/manual/volumetrictest/cubeFilledFlat.obj
@@ -0,0 +1,54 @@
+# Blender v2.66 (sub 0) OBJ File: 'cube_filled.blend'
+# www.blender.org
+o Cube
+v -1.000000 -1.000000 1.000000
+v -1.000000 -1.000000 -1.000000
+v 1.000000 -1.000000 -1.000000
+v 1.000000 -1.000000 1.000000
+v -1.000000 1.000000 1.000000
+v -1.000000 1.000000 -1.000000
+v 1.000000 1.000000 -1.000000
+v 1.000000 1.000000 1.000000
+vt 0.666667 0.332314
+vt 0.334353 0.333333
+vt 0.665647 0.000000
+vt 0.001020 0.333333
+vt 0.000000 0.001020
+vt 0.333333 0.332314
+vt 0.333333 0.665647
+vt 0.001019 0.666667
+vt 0.000000 0.334353
+vt 0.334353 0.666667
+vt 0.333333 0.334353
+vt 0.665647 0.333333
+vt 0.333333 0.667686
+vt 0.665647 0.666667
+vt 0.666667 0.998980
+vt 0.667686 0.333333
+vt 0.666667 0.001019
+vt 0.998980 0.000000
+vt 0.333333 0.001019
+vt 0.332314 0.000000
+vt 0.332314 0.333333
+vt 0.666667 0.665647
+vt 0.334353 1.000000
+vt 1.000000 0.332314
+vn -1.000000 0.000000 0.000000
+vn 0.000000 0.000000 -1.000000
+vn 1.000000 -0.000000 0.000000
+vn 0.000000 0.000000 1.000000
+vn 0.000000 1.000000 0.000000
+vn -0.000000 -1.000000 -0.000000
+s off
+f 5/1/1 6/2/1 1/3/1
+f 6/4/2 7/5/2 2/6/2
+f 7/7/3 8/8/3 4/9/3
+f 8/10/4 5/11/4 1/12/4
+f 8/13/5 7/14/5 6/15/5
+f 2/16/6 3/17/6 4/18/6
+f 6/2/1 2/19/1 1/3/1
+f 7/5/2 3/20/2 2/6/2
+f 3/21/3 7/7/3 4/9/3
+f 4/22/4 8/10/4 1/12/4
+f 5/23/5 8/13/5 6/15/5
+f 1/24/6 2/16/6 4/18/6
diff --git a/tests/manual/volumetrictest/logo.png b/tests/manual/volumetrictest/logo.png
new file mode 100644
index 0000000..1e7ed4c
--- /dev/null
+++ b/tests/manual/volumetrictest/logo.png
Binary files differ
diff --git a/tests/manual/volumetrictest/logo_no_padding.png b/tests/manual/volumetrictest/logo_no_padding.png
new file mode 100644
index 0000000..714234a
--- /dev/null
+++ b/tests/manual/volumetrictest/logo_no_padding.png
Binary files differ
diff --git a/tests/manual/volumetrictest/main.cpp b/tests/manual/volumetrictest/main.cpp
new file mode 100644
index 0000000..a36aa5e
--- /dev/null
+++ b/tests/manual/volumetrictest/main.cpp
@@ -0,0 +1,154 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "volumetrictest.h"
+#include <QtWidgets/QApplication>
+#include <QtWidgets/QWidget>
+#include <QtWidgets/QHBoxLayout>
+#include <QtWidgets/QVBoxLayout>
+#include <QtWidgets/QRadioButton>
+#include <QtWidgets/QSlider>
+#include <QtWidgets/QCheckBox>
+#include <QtWidgets/QLabel>
+#include <QtWidgets/QPushButton>
+#include <QtGui/QScreen>
+
+int main(int argc, char **argv)
+{
+ qputenv("QSG_RHI_BACKEND", "opengl");
+ QApplication app(argc, argv);
+ //Q3DScatter *graph = new Q3DScatter();
+ //Q3DSurface *graph = new Q3DSurface();
+ Q3DBars *graph = new Q3DBars();
+ QWidget *container = QWidget::createWindowContainer(graph);
+
+ QSize screenSize = graph->screen()->size();
+ container->setMinimumSize(QSize(screenSize.width() / 4, screenSize.height() / 4));
+ container->setMaximumSize(screenSize);
+ container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+ container->setFocusPolicy(Qt::StrongFocus);
+
+ QWidget *widget = new QWidget;
+ QHBoxLayout *hLayout = new QHBoxLayout(widget);
+ QVBoxLayout *vLayout = new QVBoxLayout();
+ hLayout->addWidget(container, 1);
+ hLayout->addLayout(vLayout);
+
+ widget->setWindowTitle(QStringLiteral("Volumetric TEST"));
+ widget->resize(QSize(screenSize.width() / 1.5, screenSize.height() / 1.5));
+
+ QCheckBox *sliceXCheckBox = new QCheckBox(widget);
+ sliceXCheckBox->setText(QStringLiteral("Slice volume on X axis"));
+ sliceXCheckBox->setChecked(false);
+ QCheckBox *sliceYCheckBox = new QCheckBox(widget);
+ sliceYCheckBox->setText(QStringLiteral("Slice volume on Y axis"));
+ sliceYCheckBox->setChecked(false);
+ QCheckBox *sliceZCheckBox = new QCheckBox(widget);
+ sliceZCheckBox->setText(QStringLiteral("Slice volume on Z axis"));
+ sliceZCheckBox->setChecked(false);
+
+ QSlider *sliceXSlider = new QSlider(Qt::Horizontal, widget);
+ sliceXSlider->setMinimum(0);
+ sliceXSlider->setMaximum(1024);
+ sliceXSlider->setValue(512);
+ sliceXSlider->setEnabled(true);
+ QSlider *sliceYSlider = new QSlider(Qt::Horizontal, widget);
+ sliceYSlider->setMinimum(0);
+ sliceYSlider->setMaximum(1024);
+ sliceYSlider->setValue(512);
+ sliceYSlider->setEnabled(true);
+ QSlider *sliceZSlider = new QSlider(Qt::Horizontal, widget);
+ sliceZSlider->setMinimum(0);
+ sliceZSlider->setMaximum(1024);
+ sliceZSlider->setValue(512);
+ sliceZSlider->setEnabled(true);
+
+ QLabel *fpsLabel = new QLabel(QStringLiteral("Fps: "), widget);
+
+ QLabel *sliceImageXLabel = new QLabel(widget);
+ QLabel *sliceImageYLabel = new QLabel(widget);
+ QLabel *sliceImageZLabel = new QLabel(widget);
+ sliceImageXLabel->setMinimumSize(QSize(200, 100));
+ sliceImageYLabel->setMinimumSize(QSize(200, 200));
+ sliceImageZLabel->setMinimumSize(QSize(200, 100));
+ sliceImageXLabel->setMaximumSize(QSize(200, 100));
+ sliceImageYLabel->setMaximumSize(QSize(200, 200));
+ sliceImageZLabel->setMaximumSize(QSize(200, 100));
+ sliceImageXLabel->setFrameShape(QFrame::Box);
+ sliceImageYLabel->setFrameShape(QFrame::Box);
+ sliceImageZLabel->setFrameShape(QFrame::Box);
+ sliceImageXLabel->setScaledContents(true);
+ sliceImageYLabel->setScaledContents(true);
+ sliceImageZLabel->setScaledContents(true);
+
+ QPushButton *testSubTextureSetting = new QPushButton(widget);
+ testSubTextureSetting->setText(QStringLiteral("Test subtexture settings"));
+
+ QLabel *rangeSliderLabel = new QLabel(QStringLiteral("Adjust ranges:"), widget);
+
+ QSlider *rangeXSlider = new QSlider(Qt::Horizontal, widget);
+ rangeXSlider->setMinimum(0);
+ rangeXSlider->setMaximum(1024);
+ rangeXSlider->setValue(512);
+ rangeXSlider->setEnabled(true);
+ QSlider *rangeYSlider = new QSlider(Qt::Horizontal, widget);
+ rangeYSlider->setMinimum(0);
+ rangeYSlider->setMaximum(1024);
+ rangeYSlider->setValue(512);
+ rangeYSlider->setEnabled(true);
+ QSlider *rangeZSlider = new QSlider(Qt::Horizontal, widget);
+ rangeZSlider->setMinimum(0);
+ rangeZSlider->setMaximum(1024);
+ rangeZSlider->setValue(512);
+ rangeZSlider->setEnabled(true);
+
+ QPushButton *testBoundsSetting = new QPushButton(widget);
+ testBoundsSetting->setText(QStringLiteral("Test data bounds"));
+
+ vLayout->addWidget(fpsLabel);
+ vLayout->addWidget(sliceXCheckBox);
+ vLayout->addWidget(sliceXSlider);
+ vLayout->addWidget(sliceImageXLabel);
+ vLayout->addWidget(sliceYCheckBox);
+ vLayout->addWidget(sliceYSlider);
+ vLayout->addWidget(sliceImageYLabel);
+ vLayout->addWidget(sliceZCheckBox);
+ vLayout->addWidget(sliceZSlider);
+ vLayout->addWidget(sliceImageZLabel);
+ vLayout->addWidget(rangeSliderLabel);
+ vLayout->addWidget(rangeXSlider);
+ vLayout->addWidget(rangeYSlider);
+ vLayout->addWidget(rangeZSlider);
+ vLayout->addWidget(testBoundsSetting);
+ vLayout->addWidget(testSubTextureSetting, 1, Qt::AlignTop);
+
+ VolumetricModifier *modifier = new VolumetricModifier(graph);
+ modifier->setFpsLabel(fpsLabel);
+ modifier->setSliceLabels(sliceImageXLabel, sliceImageYLabel, sliceImageZLabel);
+
+ QObject::connect(sliceXCheckBox, &QCheckBox::stateChanged, modifier,
+ &VolumetricModifier::sliceX);
+ QObject::connect(sliceYCheckBox, &QCheckBox::stateChanged, modifier,
+ &VolumetricModifier::sliceY);
+ QObject::connect(sliceZCheckBox, &QCheckBox::stateChanged, modifier,
+ &VolumetricModifier::sliceZ);
+ QObject::connect(sliceXSlider, &QSlider::valueChanged, modifier,
+ &VolumetricModifier::adjustSliceX);
+ QObject::connect(sliceYSlider, &QSlider::valueChanged, modifier,
+ &VolumetricModifier::adjustSliceY);
+ QObject::connect(sliceZSlider, &QSlider::valueChanged, modifier,
+ &VolumetricModifier::adjustSliceZ);
+ QObject::connect(testSubTextureSetting, &QPushButton::clicked, modifier,
+ &VolumetricModifier::testSubtextureSetting);
+ QObject::connect(rangeXSlider, &QSlider::valueChanged, modifier,
+ &VolumetricModifier::adjustRangeX);
+ QObject::connect(rangeYSlider, &QSlider::valueChanged, modifier,
+ &VolumetricModifier::adjustRangeY);
+ QObject::connect(rangeZSlider, &QSlider::valueChanged, modifier,
+ &VolumetricModifier::adjustRangeZ);
+ QObject::connect(testBoundsSetting, &QPushButton::clicked, modifier,
+ &VolumetricModifier::testBoundsSetting);
+
+ widget->show();
+ return app.exec();
+}
diff --git a/tests/manual/volumetrictest/volumetrictest.cpp b/tests/manual/volumetrictest/volumetrictest.cpp
new file mode 100644
index 0000000..2e19645
--- /dev/null
+++ b/tests/manual/volumetrictest/volumetrictest.cpp
@@ -0,0 +1,699 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "volumetrictest.h"
+#include <QtGraphs/qbar3dseries.h>
+#include <QtGraphs/qvalue3daxis.h>
+#include <QtGraphs/q3dscene.h>
+#include <QtGraphs/q3dcamera.h>
+#include <QtGraphs/q3dtheme.h>
+#include <QtGraphs/qcustom3dlabel.h>
+#include <QtCore/qmath.h>
+#include <QtGui/QRgb>
+#include <QtGui/QImage>
+#include <QtWidgets/QLabel>
+#include <QtCore/QDebug>
+
+const int imageCount = 512;
+const float xMiddle = 100.0f;
+const float yMiddle = 2.5f;
+const float zMiddle = 100.0f;
+const float xRange = 40.0f;
+const float yRange = 7.5f;
+const float zRange = 20.0f;
+
+VolumetricModifier::VolumetricModifier(QAbstract3DGraph *scatter)
+ : m_graph(scatter),
+ m_volumeItem(0),
+ m_volumeItem2(0),
+ m_volumeItem3(0),
+ m_sliceIndexX(0),
+ m_sliceIndexY(0),
+ m_sliceIndexZ(0)
+{
+ m_graph->activeTheme()->setType(Q3DTheme::ThemeQt);
+ //m_graph->activeTheme()->setType(Q3DTheme::ThemeIsabelle);
+ m_graph->setShadowQuality(QAbstract3DGraph::ShadowQualityNone);
+ m_graph->scene()->activeCamera()->setCameraPreset(Q3DCamera::CameraPresetFront);
+ m_graph->setOrthoProjection(true);
+ //m_graph->scene()->activeCamera()->setTarget(QVector3D(-2.0f, 1.0f, 2.0f));
+ m_scatterGraph = qobject_cast<Q3DScatter *>(m_graph);
+ m_surfaceGraph = qobject_cast<Q3DSurface *>(m_graph);
+ m_barGraph = qobject_cast<Q3DBars *>(m_graph);
+ if (m_scatterGraph) {
+ m_scatterGraph->axisX()->setRange(xMiddle - xRange, xMiddle + xRange);
+ m_scatterGraph->axisX()->setSegmentCount(8);
+ m_scatterGraph->axisY()->setRange(yMiddle - yRange, yMiddle + yRange);
+ m_scatterGraph->axisY()->setSegmentCount(3);
+ m_scatterGraph->axisZ()->setRange(zMiddle - zRange, zMiddle + zRange);
+ m_scatterGraph->axisZ()->setSegmentCount(8);
+ } else if (m_surfaceGraph) {
+ m_surfaceGraph->axisX()->setRange(xMiddle - xRange, xMiddle + xRange);
+ m_surfaceGraph->axisX()->setSegmentCount(8);
+ m_surfaceGraph->axisY()->setRange(yMiddle - yRange, yMiddle + yRange);
+ m_surfaceGraph->axisY()->setSegmentCount(3);
+ m_surfaceGraph->axisZ()->setRange(zMiddle - zRange, zMiddle + zRange);
+ m_surfaceGraph->axisZ()->setSegmentCount(8);
+ } else if (m_barGraph) {
+ QStringList rowLabels;
+ QStringList columnLabels;
+ for (int i = 0; i < xMiddle + xRange; i++) {
+ if (i % 5 == 0)
+ columnLabels << QString::number(i);
+ else
+ columnLabels << QString();
+ }
+ for (int i = 0; i < zMiddle + zRange; i++) {
+ if (i % 5 == 0)
+ rowLabels << QString::number(i);
+ else
+ rowLabels << QString();
+ }
+
+ QBar3DSeries *series = new QBar3DSeries;
+ QBarDataArray *array = new QBarDataArray();
+ array->reserve(zRange * 2 + 1);
+ for (int i = 0; i < zRange * 2 + 1; i++)
+ array->append(new QBarDataRow(xRange * 2 + 1));
+
+ series->dataProxy()->resetArray(array, rowLabels, columnLabels);
+ m_barGraph->addSeries(series);
+
+ m_barGraph->columnAxis()->setRange(xMiddle - xRange, xMiddle + xRange);
+ m_barGraph->valueAxis()->setRange(yMiddle - yRange, yMiddle + yRange);
+ m_barGraph->rowAxis()->setRange(zMiddle - zRange, zMiddle + zRange);
+ //m_barGraph->setReflection(true);
+ }
+ m_graph->activeTheme()->setBackgroundEnabled(false);
+
+ createVolume();
+ createAnotherVolume();
+ createYetAnotherVolume();
+
+// m_volumeItem->setUseHighDefShader(false);
+// m_volumeItem2->setUseHighDefShader(false);
+// m_volumeItem3->setUseHighDefShader(false);
+
+ m_volumeItem->setScalingAbsolute(false);
+ m_volumeItem2->setScalingAbsolute(false);
+ m_volumeItem3->setScalingAbsolute(false);
+ m_volumeItem->setPositionAbsolute(false);
+ m_volumeItem2->setPositionAbsolute(false);
+ m_volumeItem3->setPositionAbsolute(false);
+
+
+ m_plainItem = new QCustom3DItem;
+ QImage texture(2, 2, QImage::Format_ARGB32);
+ texture.fill(QColor(200, 200, 200, 130));
+ m_plainItem->setMeshFile(QStringLiteral(":/mesh"));
+ m_plainItem->setTextureImage(texture);
+ m_plainItem->setRotation(m_volumeItem->rotation());
+ m_plainItem->setPosition(QVector3D(xMiddle + xRange / 2.0f, yMiddle + yRange / 2.0f, zMiddle));
+ m_plainItem->setScaling(QVector3D(20.0f, 5.0f, 10.0f));
+ m_plainItem->setScalingAbsolute(false);
+
+ m_graph->addCustomItem(m_volumeItem);
+ m_graph->addCustomItem(m_volumeItem2);
+ m_graph->addCustomItem(m_volumeItem3);
+ m_graph->addCustomItem(m_plainItem);
+ //m_graph->setMeasureFps(true);
+
+ // Create label to cut through the volume 3
+ QCustom3DLabel *label = new QCustom3DLabel;
+ label->setText(QStringLiteral("FOO BAR - FOO BAR - FOO BAR"));
+ QFont font;
+ font.setPixelSize(100);
+ label->setFont(font);
+ label->setScaling(QVector3D(2.0f, 2.0f, 0.0f));
+ label->setRotationAxisAndAngle(QVector3D(0.0f, 1.0f, 0.0f), 45.0f);
+ label->setPosition(m_volumeItem3->position());
+ label->setPositionAbsolute(false);
+ label->setScalingAbsolute(true);
+
+ m_graph->addCustomItem(label);
+
+ QObject::connect(m_graph, &QAbstract3DGraph::currentFpsChanged, this,
+ &VolumetricModifier::handleFpsChange);
+// QObject::connect(m_graph->scene(), &Q3DScene::viewportChanged, this,
+// &VolumetricModifier::handleFpsChange);
+}
+
+VolumetricModifier::~VolumetricModifier()
+{
+ delete m_graph;
+}
+
+void VolumetricModifier::setFpsLabel(QLabel *fpsLabel)
+{
+ m_fpsLabel = fpsLabel;
+}
+
+void VolumetricModifier::sliceX(int enabled)
+{
+ m_volumeItem->setSliceIndexX(enabled ? m_sliceIndexX : -1);
+ m_volumeItem2->setSliceIndexX(enabled ? m_sliceIndexX : -1);
+}
+
+void VolumetricModifier::sliceY(int enabled)
+{
+ m_volumeItem->setSliceIndexY(enabled ? m_sliceIndexY : -1);
+ m_volumeItem2->setSliceIndexY(enabled ? m_sliceIndexY : -1);
+}
+
+void VolumetricModifier::sliceZ(int enabled)
+{
+ m_volumeItem->setSliceIndexZ(enabled ? m_sliceIndexZ : -1);
+ m_volumeItem2->setSliceIndexZ(enabled ? m_sliceIndexZ : -1);
+}
+
+void VolumetricModifier::adjustSliceX(int value)
+{
+ m_sliceIndexX = int(float(value) / (1024.0f / m_volumeItem->textureWidth()));
+ if (m_sliceIndexX == m_volumeItem->textureWidth())
+ m_sliceIndexX--;
+ qDebug() << "m_sliceIndexX:" << m_sliceIndexX;
+ if (m_volumeItem->sliceIndexX() != -1) {
+ m_volumeItem->setSliceIndexX(m_sliceIndexX);
+ m_volumeItem2->setSliceIndexX(m_sliceIndexX);
+ }
+ m_sliceLabelX->setPixmap(QPixmap::fromImage(
+ m_volumeItem->renderSlice(Qt::XAxis, m_sliceIndexX)));
+
+}
+
+void VolumetricModifier::adjustSliceY(int value)
+{
+ m_sliceIndexY = int(float(value) / (1024.0f / m_volumeItem->textureHeight()));
+ if (m_sliceIndexY == m_volumeItem->textureHeight())
+ m_sliceIndexY--;
+ qDebug() << "m_sliceIndexY:" << m_sliceIndexY;
+ if (m_volumeItem->sliceIndexY() != -1) {
+ m_volumeItem->setSliceIndexY(m_sliceIndexY);
+ m_volumeItem2->setSliceIndexY(m_sliceIndexY);
+ }
+ m_sliceLabelY->setPixmap(QPixmap::fromImage(
+ m_volumeItem->renderSlice(Qt::YAxis, m_sliceIndexY)));
+}
+
+void VolumetricModifier::adjustSliceZ(int value)
+{
+ m_sliceIndexZ = int(float(value) / (1024.0f / m_volumeItem->textureDepth()));
+ if (m_sliceIndexZ == m_volumeItem->textureDepth())
+ m_sliceIndexZ--;
+ qDebug() << "m_sliceIndexZ:" << m_sliceIndexZ;
+ if (m_volumeItem->sliceIndexZ() != -1) {
+ m_volumeItem->setSliceIndexZ(m_sliceIndexZ);
+ m_volumeItem2->setSliceIndexZ(m_sliceIndexZ);
+ }
+ m_sliceLabelZ->setPixmap(QPixmap::fromImage(
+ m_volumeItem->renderSlice(Qt::ZAxis, m_sliceIndexZ)));
+}
+
+void VolumetricModifier::handleFpsChange()
+{
+ const QString fpsFormat = QStringLiteral("Fps: %1");
+ int fps10 = int(m_graph->currentFps() * 10.0);
+ m_fpsLabel->setText(fpsFormat.arg(QString::number(qreal(fps10) / 10.0)));
+// const QString sceneDimensionsFormat = QStringLiteral("%1 x %2");
+// m_fpsLabel->setText(sceneDimensionsFormat
+// .arg(m_graph->scene()->viewport().width())
+ // .arg(m_graph->scene()->viewport().height()));
+}
+
+void VolumetricModifier::testSubtextureSetting()
+{
+ // Setting the rendered Slice as subtexture should result in identical volume
+ QList<uchar> dataBefore = *m_volumeItem->textureData();
+ dataBefore[0] = dataBefore.at(0); // Make sure we are detached
+
+ checkRenderCase(1, Qt::XAxis, 56, dataBefore, m_volumeItem);
+ checkRenderCase(2, Qt::YAxis, 64, dataBefore, m_volumeItem);
+ checkRenderCase(3, Qt::ZAxis, 87, dataBefore, m_volumeItem);
+
+ checkRenderCase(4, Qt::XAxis, 0, dataBefore, m_volumeItem);
+ checkRenderCase(5, Qt::YAxis, 0, dataBefore, m_volumeItem);
+ checkRenderCase(6, Qt::ZAxis, 0, dataBefore, m_volumeItem);
+
+ checkRenderCase(7, Qt::XAxis, m_volumeItem->textureWidth() - 1, dataBefore, m_volumeItem);
+ checkRenderCase(8, Qt::YAxis, m_volumeItem->textureHeight() - 1, dataBefore, m_volumeItem);
+ checkRenderCase(9, Qt::ZAxis, m_volumeItem->textureDepth() - 1, dataBefore, m_volumeItem);
+
+ dataBefore = *m_volumeItem2->textureData();
+ dataBefore[0] = dataBefore.at(0); // Make sure we are detached
+
+ checkRenderCase(11, Qt::XAxis, 56, dataBefore, m_volumeItem2);
+ checkRenderCase(12, Qt::YAxis, 64, dataBefore, m_volumeItem2);
+ checkRenderCase(13, Qt::ZAxis, 87, dataBefore, m_volumeItem2);
+
+ checkRenderCase(14, Qt::XAxis, 0, dataBefore, m_volumeItem2);
+ checkRenderCase(15, Qt::YAxis, 0, dataBefore, m_volumeItem2);
+ checkRenderCase(16, Qt::ZAxis, 0, dataBefore, m_volumeItem2);
+
+ checkRenderCase(17, Qt::XAxis, m_volumeItem2->textureWidth() - 1, dataBefore, m_volumeItem2);
+ checkRenderCase(18, Qt::YAxis, m_volumeItem2->textureHeight() - 1, dataBefore, m_volumeItem2);
+ checkRenderCase(19, Qt::ZAxis, m_volumeItem2->textureDepth() - 1, dataBefore, m_volumeItem2);
+
+ // Do some visible swaps on volume 3
+ QImage slice = m_volumeItem3->renderSlice(Qt::XAxis, 144);
+ slice = slice.mirrored();
+ m_volumeItem3->setSubTextureData(Qt::XAxis, 144, slice);
+
+ slice = m_volumeItem3->renderSlice(Qt::YAxis, 80);
+ slice = slice.mirrored();
+ m_volumeItem3->setSubTextureData(Qt::YAxis, 80, slice);
+
+ slice = m_volumeItem3->renderSlice(Qt::ZAxis, 190);
+ slice = slice.mirrored(true, false);
+ m_volumeItem3->setSubTextureData(Qt::ZAxis, 190, slice);
+}
+
+void VolumetricModifier::adjustRangeX(int value)
+{
+ float adjustment = float(value - 512) / 10.0f;
+ if (m_scatterGraph)
+ m_scatterGraph->axisX()->setRange(xMiddle + adjustment - xRange, xMiddle + adjustment + xRange);
+ if (m_surfaceGraph)
+ m_surfaceGraph->axisX()->setRange(xMiddle + adjustment - xRange, xMiddle + adjustment + xRange);
+ if (m_barGraph)
+ m_barGraph->columnAxis()->setRange(xMiddle + adjustment - xRange, xMiddle + adjustment + xRange);
+}
+
+void VolumetricModifier::adjustRangeY(int value)
+{
+ float adjustment = float(value - 512) / 10.0f;
+ if (m_scatterGraph)
+ m_scatterGraph->axisY()->setRange(yMiddle + adjustment - yRange, yMiddle + adjustment + yRange);
+ if (m_surfaceGraph)
+ m_surfaceGraph->axisY()->setRange(yMiddle + adjustment - yRange, yMiddle + adjustment + yRange);
+ if (m_barGraph)
+ m_barGraph->valueAxis()->setRange(yMiddle + adjustment - yRange, yMiddle + adjustment + yRange);
+}
+
+void VolumetricModifier::adjustRangeZ(int value)
+{
+ float adjustment = float(value - 512) / 10.0f;
+ if (m_scatterGraph)
+ m_scatterGraph->axisZ()->setRange(zMiddle + adjustment - zRange, zMiddle + adjustment + zRange);
+ if (m_surfaceGraph)
+ m_surfaceGraph->axisZ()->setRange(zMiddle + adjustment - zRange, zMiddle + adjustment + zRange);
+ if (m_barGraph)
+ m_barGraph->rowAxis()->setRange(zMiddle + adjustment - zRange, zMiddle + adjustment + zRange);
+}
+
+void VolumetricModifier::testBoundsSetting()
+{
+ static QVector3D scaling1 = m_volumeItem->scaling();
+ static QVector3D scaling2 = m_volumeItem2->scaling();
+ static QVector3D scaling3 = m_volumeItem3->scaling();
+ static QVector3D scaleVector = QVector3D(0.5f, 0.3f, 0.2f);
+
+ if (m_volumeItem->isScalingAbsolute()) {
+ m_volumeItem->setScalingAbsolute(false);
+ m_volumeItem2->setScalingAbsolute(false);
+ m_volumeItem3->setScalingAbsolute(false);
+
+ m_volumeItem->setScaling(scaling1);
+ m_volumeItem2->setScaling(scaling2);
+ m_volumeItem3->setScaling(scaling3);
+ } else {
+ m_volumeItem->setScalingAbsolute(true);
+ m_volumeItem2->setScalingAbsolute(true);
+ m_volumeItem3->setScalingAbsolute(true);
+
+ m_volumeItem->setScaling(scaleVector);
+ m_volumeItem2->setScaling(scaleVector);
+ m_volumeItem3->setScaling(scaleVector);
+ }
+}
+
+void VolumetricModifier::checkRenderCase(int id, Qt::Axis axis, int index,
+ const QList<uchar> &dataBefore,
+ QCustom3DVolume *volumeItem)
+{
+ QImage slice = volumeItem->renderSlice(axis, index);
+ volumeItem->setSubTextureData(axis, index, slice);
+
+ if (dataBefore == *volumeItem->textureData())
+ qDebug() << __FUNCTION__ << "Case:" << id << "Vectors identical";
+ else
+ qDebug() << __FUNCTION__ << "Case:" << id << "BUG: VECTORS DIFFER!";
+}
+
+void VolumetricModifier::createVolume()
+{
+ m_volumeItem = new QCustom3DVolume;
+ m_volumeItem->setTextureFormat(QImage::Format_ARGB32);
+ m_volumeItem->setRotation(QQuaternion::fromAxisAndAngle(1.0f, 1.0f, 0.0f, 20.0f));
+ m_volumeItem->setPosition(QVector3D(xMiddle - (xRange / 2.0f), yMiddle + (yRange / 2.0f), zMiddle));
+ //m_volumeItem->setPosition(QVector3D(xMiddle, yMiddle, zMiddle));
+ m_volumeItem->setDrawSliceFrames(true);
+ m_volumeItem->setDrawSlices(true);
+
+ QImage logo;
+ logo.load(QStringLiteral(":/logo_no_padding.png"));
+ //logo.load(QStringLiteral(":/logo.png"));
+ qDebug() << "image dimensions:" << logo.width() << logo.height()
+ << logo.sizeInBytes() << (logo.width() * logo.height())
+ << logo.bytesPerLine();
+
+ QList<QImage *> imageArray(imageCount);
+ for (int i = 0; i < imageCount; i++) {
+ QImage *newImage = new QImage(logo);
+ imageArray[i] = newImage;
+ }
+
+ m_volumeItem->createTextureData(imageArray);
+
+ for (int i = 0; i < imageCount; i++)
+ delete imageArray[i];
+
+ m_sliceIndexX = m_volumeItem->textureWidth() / 2;
+ m_sliceIndexY = m_volumeItem->textureWidth() / 2;
+ m_sliceIndexZ = m_volumeItem->textureWidth() / 2;
+
+ QList<QRgb> colorTable = m_volumeItem->colorTable();
+
+ // Hack some alpha to the picture
+ for (int i = 0; i < colorTable.size(); i++) {
+ if (qAlpha(colorTable.at(i)) > 0) {
+ colorTable[i] = qRgba(qRed(colorTable.at(i)),
+ qGreen(colorTable.at(i)),
+ qBlue(colorTable.at(i)),
+ 50);
+ }
+ }
+
+ colorTable.append(qRgba(0, 0, 0, 0));
+ colorTable.append(qRgba(255, 0, 0, 255));
+ colorTable.append(qRgba(0, 255, 0, 255));
+ colorTable.append(qRgba(0, 0, 255, 255));
+
+ m_volumeItem->setColorTable(colorTable);
+ int alphaIndex = colorTable.size() - 4;
+ int redIndex = colorTable.size() - 3;
+ int greenIndex = colorTable.size() - 2;
+ int blueIndex = colorTable.size() - 1;
+ int width = m_volumeItem->textureDataWidth();
+ int height = m_volumeItem->textureHeight();
+ int depth = m_volumeItem->textureDepth();
+ int frameSize = width * height;
+ qDebug() << width << height << depth << m_volumeItem->textureData()->size();
+ m_volumeItem->setScaling(QVector3D(xRange, yRange, zRange) / 2.0f);
+
+ uchar *data = m_volumeItem->textureData()->data();
+ uchar *p = data;
+
+ // Change one picture using subtexture replacement
+ QImage flipped = logo.mirrored();
+ m_volumeItem->setSubTextureData(Qt::ZAxis, 100, flipped);
+
+ // Clean up the two extra pixels
+ p = data + width - 1;
+ for (int k = 0; k < depth; k++) {
+ for (int j = 0; j < height; j++) {
+ *p = alphaIndex;
+ p += width;
+ }
+ }
+ p = data + width - 2;
+ for (int k = 0; k < depth; k++) {
+ for (int j = 0; j < height; j++) {
+ *p = alphaIndex;
+ p += width;
+ }
+ }
+ // Red first subtexture
+ p = data;
+ for (int j = 0; j < height; j++) {
+ for (int i = 0; i < width; i++) {
+ *p = redIndex;
+ p++;
+ }
+ }
+
+ // Red last subtexture
+// p = data + frameSize * (imageCount - 1);
+// for (int j = 0; j < height; j++) {
+// for (int i = 0; i < width; i++) {
+// *p = redIndex;
+// p++;
+// }
+// }
+
+// // Blue second to last subtexture
+// p = data + frameSize * (imageCount - 2);
+// for (int j = 0; j < height; j++) {
+// for (int i = 0; i < width; i++) {
+// *p = blueIndex;
+// p++;
+// }
+// }
+
+ // Blue x = 0
+// p = data;
+// for (int k = 0; k < depth; k++) {
+// for (int j = 0; j < height; j++) {
+// *p = blueIndex;
+// p += width;
+// }
+// }
+
+ // Blue x = max
+ p = data + width - 1;
+ for (int k = 0; k < depth; k++) {
+ for (int j = 0; j < height; j++) {
+ *p = blueIndex;
+ p += width;
+ }
+ }
+ // green x = max - 1
+ p = data + width - 2;
+ for (int k = 0; k < depth; k++) {
+ for (int j = 0; j < height; j++) {
+ *p = greenIndex;
+ p += width;
+ }
+ }
+
+ // Green y = 0
+ p = data;
+ for (int k = 0; k < depth; k++) {
+ for (int i = 0; i < width; i++) {
+ *p = greenIndex;
+ p++;
+ }
+ p += (frameSize - width);
+ }
+
+ // Green y = max
+// p = data + frameSize - width;
+// for (int k = 0; k < depth; k++) {
+// for (int i = 0; i < width; i++) {
+// *p = greenIndex;
+// p++;
+// }
+// p += (frameSize - width);
+// }
+}
+
+void VolumetricModifier::createAnotherVolume()
+{
+ m_volumeItem2 = new QCustom3DVolume;
+ m_volumeItem2->setTextureFormat(QImage::Format_ARGB32);
+ m_volumeItem2->setPosition(QVector3D(xMiddle + (xRange / 2.0f), yMiddle - (yRange / 2.0f), zMiddle));
+
+ QImage logo;
+ logo.load(QStringLiteral(":/logo_no_padding.png"));
+ //logo.load(QStringLiteral(":/logo.png"));
+ logo = logo.convertToFormat(QImage::Format_ARGB8555_Premultiplied);
+ qDebug() << "second image dimensions:" << logo.width() << logo.height()
+ << logo.sizeInBytes() << (logo.width() * logo.height())
+ << logo.bytesPerLine();
+
+ logo.save("d:/qt/goobar.png");
+
+ QList<QImage *> imageArray(imageCount);
+ for (int i = 0; i < imageCount; i++) {
+ QImage *newImage = new QImage(logo);
+ imageArray[i] = newImage;
+ }
+
+ m_volumeItem2->createTextureData(imageArray);
+
+ for (int i = 0; i < imageCount; i++)
+ delete imageArray[i];
+
+ m_sliceIndexX = m_volumeItem2->textureWidth() / 2;
+ m_sliceIndexY = m_volumeItem2->textureWidth() / 2;
+ m_sliceIndexZ = m_volumeItem2->textureWidth() / 2;
+
+ int width = m_volumeItem2->textureWidth();
+ int height = m_volumeItem2->textureHeight();
+ int depth = m_volumeItem2->textureDepth();
+ qDebug() << width << height << depth << m_volumeItem2->textureData()->size();
+ m_volumeItem2->setScaling(QVector3D(float(width) / float(depth) * xRange * 2.0f,
+ float(height) / float(depth) * yRange * 2.0f,
+ zRange * 2.0f));
+
+ // Change one picture using subtexture replacement
+ QImage flipped = logo.mirrored();
+ m_volumeItem2->setSubTextureData(Qt::ZAxis, 100, flipped);
+ //m_volumeItem2->setAlphaMultiplier(0.2f);
+ m_volumeItem2->setPreserveOpacity(false);
+}
+
+void VolumetricModifier::createYetAnotherVolume()
+{
+ m_volumeItem3 = new QCustom3DVolume;
+ m_volumeItem3->setTextureFormat(QImage::Format_Indexed8);
+// m_volumeItem2->setRotation(QQuaternion::fromAxisAndAngle(1.0f, 1.0f, 0.0f, 10.0f));
+ m_volumeItem3->setPosition(QVector3D(xMiddle - (xRange / 2.0f), yMiddle - (yRange / 2.0f), zMiddle));
+
+// m_volumeItem3->setTextureDimensions(m_volumeItem->textureDataWidth(),
+// m_volumeItem->textureHeight(),
+// m_volumeItem->textureDepth());
+ m_volumeItem3->setTextureDimensions(200, 200, 200);
+
+ QList<uchar> *tdata =
+ new QList<uchar>(m_volumeItem3->textureDataWidth() * m_volumeItem3->textureHeight()
+ * m_volumeItem3->textureDepth());
+ tdata->fill(0);
+ m_volumeItem3->setTextureData(tdata);
+
+ m_sliceIndexX = m_volumeItem3->textureWidth() / 2;
+ m_sliceIndexY = m_volumeItem3->textureWidth() / 2;
+ m_sliceIndexZ = m_volumeItem3->textureWidth() / 2;
+
+ QList<QRgb> colorTable = m_volumeItem->colorTable();
+ colorTable[0] = qRgba(0, 0, 0, 0);
+ m_volumeItem3->setColorTable(colorTable);
+ int redIndex = colorTable.size() - 3;
+ int greenIndex = colorTable.size() - 2;
+ int blueIndex = colorTable.size() - 1;
+ int width = m_volumeItem3->textureDataWidth();
+ int height = m_volumeItem3->textureHeight();
+ int depth = m_volumeItem3->textureDepth();
+ int frameSize = width * height;
+ qDebug() << width << height << depth << m_volumeItem3->textureData()->size();
+ m_volumeItem3->setScaling(m_volumeItem->scaling());
+
+ uchar *data = tdata->data();
+ uchar *p = data;
+
+ // Red first subtexture
+// for (int j = 0; j < height; j++) {
+// for (int i = 0; i < width; i++) {
+// *p = redIndex;
+// p++;
+// }
+// }
+
+ // Red last subtexture
+ p = data + frameSize * (depth - 1);
+ for (int j = 0; j < height; j++) {
+ for (int i = 0; i < width; i++) {
+ *p = redIndex;
+ p++;
+ }
+ }
+
+ // green edge
+ p = data + frameSize * (depth - 1);
+ for (int i = 0; i < width; i++) {
+ *p = greenIndex;
+ p++;
+ }
+ for (int j = 2; j < height; j++) {
+ *p = greenIndex;
+ p += width - 1;
+ *p = j % 7 ? greenIndex : blueIndex;
+ p++;
+ }
+ for (int i = 0; i < width; i++) {
+ *p = greenIndex;
+ p++;
+ }
+
+// // Blue second to last subtexture
+// p = data + frameSize * (depth - 2);
+// for (int j = 0; j < height; j++) {
+// for (int i = 0; i < width; i++) {
+// *p = blueIndex;
+// p++;
+// }
+// }
+
+// // green third to last subtexture
+// p = data + frameSize * (depth - 3);
+// for (int j = 0; j < height; j++) {
+// for (int i = 0; i < width; i++) {
+// *p = greenIndex;
+// p++;
+// }
+// }
+
+ // Blue x = 0
+ p = data;
+ for (int k = 0; k < depth; k++) {
+ for (int j = 0; j < height; j++) {
+ *p = blueIndex;
+ p += width;
+ }
+ }
+
+// // Blue x = max
+// p = data + width - 1;
+// for (int k = 0; k < depth; k++) {
+// for (int j = 0; j < height; j++) {
+// *p = blueIndex;
+// p += width;
+// }
+// }
+
+ // Green y = 0
+// p = data;
+// for (int k = 0; k < depth; k++) {
+// for (int i = 0; i < width; i++) {
+// *p = greenIndex;
+// p++;
+// }
+// p += (frameSize - width);
+// }
+
+// // Green y = max
+ p = data + frameSize - width;
+ for (int k = 0; k < depth; k++) {
+ for (int i = 0; i < width; i++) {
+ *p = greenIndex;
+ p++;
+ }
+ p += (frameSize - width);
+ }
+
+
+// // Fill with alternating pixels
+// p = data;
+// for (int k = 0; k < (depth * width * height / 4); k++) {
+// *p = greenIndex;
+// p++;
+// *p = greenIndex;
+// p++;
+// *p = blueIndex;
+// p++;
+// *p = redIndex;
+// p++;
+// }
+
+
+}
+
+void VolumetricModifier::setSliceLabels(QLabel *xLabel, QLabel *yLabel, QLabel *zLabel)
+{
+ m_sliceLabelX = xLabel;
+ m_sliceLabelY = yLabel;
+ m_sliceLabelZ = zLabel;
+
+ adjustSliceX(512);
+ adjustSliceY(512);
+ adjustSliceZ(512);
+}
diff --git a/tests/manual/volumetrictest/volumetrictest.h b/tests/manual/volumetrictest/volumetrictest.h
new file mode 100644
index 0000000..8f75171
--- /dev/null
+++ b/tests/manual/volumetrictest/volumetrictest.h
@@ -0,0 +1,62 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef VOLUMETRICMODIFIER_H
+#define VOLUMETRICMODIFIER_H
+
+#include <QtGraphs/qcustom3dvolume.h>
+#include <QtGraphs/qcustom3ditem.h>
+#include <QtGraphs/q3dscatter.h>
+#include <QtGraphs/q3dsurface.h>
+#include <QtGraphs/q3dbars.h>
+#include <QtWidgets/QLabel>
+
+class VolumetricModifier : public QObject
+{
+ Q_OBJECT
+public:
+ explicit VolumetricModifier(QAbstract3DGraph *scatter);
+ ~VolumetricModifier();
+
+ void setFpsLabel(QLabel *fpsLabel);
+ void setSliceLabels(QLabel *xLabel, QLabel *yLabel, QLabel *zLabel);
+
+public Q_SLOTS:
+ void sliceX(int enabled);
+ void sliceY(int enabled);
+ void sliceZ(int enabled);
+ void adjustSliceX(int value);
+ void adjustSliceY(int value);
+ void adjustSliceZ(int value);
+ void handleFpsChange();
+ void testSubtextureSetting();
+ void adjustRangeX(int value);
+ void adjustRangeY(int value);
+ void adjustRangeZ(int value);
+ void testBoundsSetting();
+
+private:
+ void createVolume();
+ void createAnotherVolume();
+ void createYetAnotherVolume();
+ void checkRenderCase(int id, Qt::Axis axis, int index, const QList<uchar> &dataBefore,
+ QCustom3DVolume *volumeItem);
+
+ QAbstract3DGraph *m_graph;
+ Q3DScatter *m_scatterGraph;
+ Q3DSurface *m_surfaceGraph;
+ Q3DBars *m_barGraph;
+ QCustom3DVolume *m_volumeItem;
+ QCustom3DVolume *m_volumeItem2;
+ QCustom3DVolume *m_volumeItem3;
+ QCustom3DItem *m_plainItem;
+ int m_sliceIndexX;
+ int m_sliceIndexY;
+ int m_sliceIndexZ;
+ QLabel *m_fpsLabel;
+ QLabel *m_sliceLabelX;
+ QLabel *m_sliceLabelY;
+ QLabel *m_sliceLabelZ;
+};
+
+#endif
diff --git a/tests/manual/volumetrictest/volumetrictest.pro b/tests/manual/volumetrictest/volumetrictest.pro
new file mode 100644
index 0000000..df3ef0d
--- /dev/null
+++ b/tests/manual/volumetrictest/volumetrictest.pro
@@ -0,0 +1,14 @@
+!include( ../tests.pri ) {
+ error( "Couldn't find the tests.pri file!" )
+}
+
+SOURCES += main.cpp volumetrictest.cpp
+HEADERS += volumetrictest.h
+
+QT += widgets
+
+OTHER_FILES += doc/src/* \
+ doc/images/*
+
+RESOURCES += \
+ volumetrictest.qrc
diff --git a/tests/manual/volumetrictest/volumetrictest.qrc b/tests/manual/volumetrictest/volumetrictest.qrc
new file mode 100644
index 0000000..7cd8533
--- /dev/null
+++ b/tests/manual/volumetrictest/volumetrictest.qrc
@@ -0,0 +1,7 @@
+<RCC>
+ <qresource prefix="/">
+ <file>logo.png</file>
+ <file alias="mesh">cubeFilledFlat.obj</file>
+ <file>logo_no_padding.png</file>
+ </qresource>
+</RCC>
diff --git a/tools/blender/arrow.blend b/tools/blender/arrow.blend
new file mode 100644
index 0000000..9c0ef46
--- /dev/null
+++ b/tools/blender/arrow.blend
Binary files differ
diff --git a/tools/blender/backgroud.blend b/tools/blender/backgroud.blend
new file mode 100644
index 0000000..83cc2f5
--- /dev/null
+++ b/tools/blender/backgroud.blend
Binary files differ
diff --git a/tools/blender/backgroudNegatives.blend b/tools/blender/backgroudNegatives.blend
new file mode 100644
index 0000000..067ea19
--- /dev/null
+++ b/tools/blender/backgroudNegatives.blend
Binary files differ
diff --git a/tools/blender/backgroudNegativesWall.blend b/tools/blender/backgroudNegativesWall.blend
new file mode 100644
index 0000000..77566dc
--- /dev/null
+++ b/tools/blender/backgroudNegativesWall.blend
Binary files differ
diff --git a/tools/blender/cone.blend b/tools/blender/cone.blend
new file mode 100644
index 0000000..2782e34
--- /dev/null
+++ b/tools/blender/cone.blend
Binary files differ
diff --git a/tools/blender/cone_filled.blend b/tools/blender/cone_filled.blend
new file mode 100644
index 0000000..cbdf781
--- /dev/null
+++ b/tools/blender/cone_filled.blend
Binary files differ
diff --git a/tools/blender/cube.blend b/tools/blender/cube.blend
new file mode 100644
index 0000000..cb3c63a
--- /dev/null
+++ b/tools/blender/cube.blend
Binary files differ
diff --git a/tools/blender/cube_filled.blend b/tools/blender/cube_filled.blend
new file mode 100644
index 0000000..1be0865
--- /dev/null
+++ b/tools/blender/cube_filled.blend
Binary files differ
diff --git a/tools/blender/cylinder.blend b/tools/blender/cylinder.blend
new file mode 100644
index 0000000..c787b01
--- /dev/null
+++ b/tools/blender/cylinder.blend
Binary files differ
diff --git a/tools/blender/cylinder_filled.blend b/tools/blender/cylinder_filled.blend
new file mode 100644
index 0000000..1d681b4
--- /dev/null
+++ b/tools/blender/cylinder_filled.blend
Binary files differ
diff --git a/tools/blender/narrowArrow.blend b/tools/blender/narrowArrow.blend
new file mode 100644
index 0000000..3fd3dff
--- /dev/null
+++ b/tools/blender/narrowArrow.blend
Binary files differ
diff --git a/tools/blender/oilrefinery.blend b/tools/blender/oilrefinery.blend
new file mode 100644
index 0000000..ab50b8d
--- /dev/null
+++ b/tools/blender/oilrefinery.blend
Binary files differ
diff --git a/tools/blender/oilrig.blend b/tools/blender/oilrig.blend
new file mode 100644
index 0000000..d7a3b5f
--- /dev/null
+++ b/tools/blender/oilrig.blend
Binary files differ
diff --git a/tools/blender/plane.blend b/tools/blender/plane.blend
new file mode 100644
index 0000000..96becb4
--- /dev/null
+++ b/tools/blender/plane.blend
Binary files differ
diff --git a/tools/blender/pyramid.blend b/tools/blender/pyramid.blend
new file mode 100644
index 0000000..bcf3610
--- /dev/null
+++ b/tools/blender/pyramid.blend
Binary files differ
diff --git a/tools/blender/pyramid_filled.blend b/tools/blender/pyramid_filled.blend
new file mode 100644
index 0000000..eb9801a
--- /dev/null
+++ b/tools/blender/pyramid_filled.blend
Binary files differ
diff --git a/tools/blender/scatterdot.blend b/tools/blender/scatterdot.blend
new file mode 100644
index 0000000..4d4d624
--- /dev/null
+++ b/tools/blender/scatterdot.blend
Binary files differ
diff --git a/tools/blender/smoothcube.blend b/tools/blender/smoothcube.blend
new file mode 100644
index 0000000..eff5fd8
--- /dev/null
+++ b/tools/blender/smoothcube.blend
Binary files differ
diff --git a/tools/blender/smoothcube_filled.blend b/tools/blender/smoothcube_filled.blend
new file mode 100644
index 0000000..7ba1877
--- /dev/null
+++ b/tools/blender/smoothcube_filled.blend
Binary files differ
diff --git a/tools/blender/sphere.blend b/tools/blender/sphere.blend
new file mode 100644
index 0000000..b3f817c
--- /dev/null
+++ b/tools/blender/sphere.blend
Binary files differ