1

Is it possible to use custom delegates for draggable points (i.e. Items, icons, Rectangles, etc) in Qt Charts or other 3rd party libraries like it is easily possible in Qt Location for MapItemView, MapQuickItem and their delegate property? Or it is possible to use some combinations of PathView and ChartView for such purpose? PathView inside ChartView could be a solution, however probably will need to convert chart coordinates to screen coordinates there: also not sure ChartView has methods for this. Need to make some mockup to check this. Had not found any documentation or samples about. Should be obvious and simple like it was implemented in Qt Location however it is not implemented for Qt Charts for some reasons.

Ideal way could be Map element using from Qt Location where I have all I need: MapItemView, MapQuickItem and MapPolygon or MapPolyline for graphics as I have cartographic information to draw. But again 2 questions:

  • how to draw axis: X - distance km, Y - altitude
  • how to draw chart grid
  • how to draw custom 2D BarSeries imitating terrain elevation (could be MapPolygon however).

Is it possible to dynamically draw some custom map here? Any ideas?

Sample chart/terrain elevation

Aleksey Kontsevich
  • 4,671
  • 4
  • 46
  • 101

1 Answers1

0

Found simple and elegant solution with ChartView with Repeater inside and ChartView mapping functions: mapToPosition and mapToValue to map chart<->screen coordinates easily.

Mockup:

import QtQuick 2.12
import QtCharts 2.3

Item {
    visible: true
    width: 640
    height: 480

    ChartView {
        id: chart
        anchors.fill: parent
        antialiasing: true

        ValueAxis {
            id: xAxis
            min: 0
            max: 1100
            tickCount: 12
            labelFormat: "%.0f"
        }

        ValueAxis {
            id: yAxis
            min: -50
            max: 200
            tickInterval: 50
            labelFormat: "%.0f"
        }

        ListModel {
            id: lineModel
            ListElement { x: 50; y: 155; }
            ListElement { x: 138; y: 175 }
            ListElement { x: 193; y: 50 }
            ListElement { x: 271; y: 90 }
            ListElement { x: 295; y: 90 }
            ListElement { x: 383; y: 150 }
            ListElement { x: 529; y: 100 }
            ListElement { x: 665; y: 150 }
            ListElement { x: 768; y: 90 }
            ListElement { x: 794; y: 90 }
            ListElement { x: 851; y: 50 }
            ListElement { x: 875; y: 50 }
            ListElement { x: 925; y: 175 }
            ListElement { x: 1060; y: 125 }
        }

        ListModel {
            id: areaModel
            ListElement { x: 0; y: 100 }
            ListElement { x: 138; y: 125 }
            ListElement { x: 193; y: 0 }
            ListElement { x: 271; y: 40 }
            ListElement { x: 295; y: 40 }
            ListElement { x: 383; y: 100 }
            ListElement { x: 529; y: 50 }
            ListElement { x: 665; y: 100 }
            ListElement { x: 768; y: 40 }
            ListElement { x: 794; y: 40 }
            ListElement { x: 851; y: 0 }
            ListElement { x: 875; y: 0 }
            ListElement { x: 925; y: 125 }
            ListElement { x: 1060; y: 75 }
            ListElement { x: 1100; y: 60 }
        }

        AreaSeries {
            name: "Terrain"
            axisX: xAxis
            axisY: yAxis
            borderColor: color
            upperSeries: LineSeries {
                id: areaSeries
            }
        }

        LineSeries {
            id: lineSeries
            name: "Flying path"
            axisX: xAxis
            axisY: yAxis
        }

        function adjustPosition(item, index) {
            let point = Qt.point(lineModel.get(index).x, lineModel.get(index).y)
            let position = chart.mapToPosition(point, lineSeries)
            item.x = position.x - item.width / 2
            item.y = position.y - item.height / 2
        }

        function adjustValue(item, index) {
            let position = Qt.point(item.x + item.width / 2, item.y + item.height / 2)
            let point = chart.mapToValue(position, lineSeries)
            lineModel.setProperty(index, "y", point.y)  // Change only Y-coordinate
            lineSeries.replace(lineSeries.at(index).x, lineSeries.at(index).y, // old
                               lineSeries.at(index).x, point.y)                // new
        }

        Repeater {
            model: lineModel

            Rectangle {
                id: indicator
                radius: 100
                width: radius / 2
                height: width
                color: "red"

                property real parentWidth: chart.width
                property real parentHeight: chart.height

                onParentWidthChanged: chart.adjustPosition(this, index)
                onParentHeightChanged: chart.adjustPosition(this, index)

                onYChanged: {
                    if(mouseArea.drag.active) {
                        chart.adjustValue(this, index)
                    }
                }

                Image {
                    id: waypoint
                    anchors.centerIn: parent
                    source: index ? "qrc:/waypoint.svg" : "qrc:/home.svg"
                }

                MouseArea {
                    id: mouseArea
                    anchors.fill: parent
                    drag.target: indicator
                    drag.axis: Drag.YAxis
                    preventStealing: true
                }
            }
        }

        Component.onCompleted: {
            lineSeries.clear()
            areaSeries.clear()
            for(let i = 0; i < lineModel.count; i++) {
                lineSeries.append(lineModel.get(i).x, lineModel.get(i).y)
            }

            for(let j = 0; j < areaModel.count; j++) {
                areaSeries.append(areaModel.get(j).x, areaModel.get(j).y)
            }
        }
    }
}

Any improvements, optimizations and suggestion like model binding via HXYModelMapper/VXYModelMapper instead of JS model filling are welcome. Will fix the answer.

Result screenshot:

enter image description here

Aleksey Kontsevich
  • 4,671
  • 4
  • 46
  • 101