QML: делегат пользовательской перетаскиваемой точки для серии ChartView

Можно ли использовать пользовательские делегаты для перетаскиваемых точек (т. е. Item, значков, Rectangle и т. д.) в Qt Charts или других сторонних библиотеках, как это легко возможно в Qt Location для MapItemView, MapQuickItem и их свойства delegate? Или можно использовать какие-то комбинации PathView и ChartView для такой цели? PathView внутри ChartView может быть решением, однако, вероятно, потребуется преобразовать координаты диаграммы в координаты экрана: также не уверен, что ChartView имеет методы для этого. Нужно сделать макет, чтобы проверить это. Не нашел ни документации, ни образцов о. Должно быть очевидным и простым, как это было реализовано в Qt Location, однако по некоторым причинам это не реализовано для Qt Charts.

Идеальным способом может быть использование элемента Map из Qt Location, где у меня есть все, что мне нужно: MapItemView, MapQuickItem и MapPolygon или MapPolyline для графики, поскольку у меня есть картографическая информация для рисования. Но опять же 2 вопроса:

  • как рисовать оси: X - расстояние км, Y - высота
  • как нарисовать сетку диаграммы
  • как нарисовать пользовательскую 2D BarSeries, имитирующую высоту местности (однако может быть MapPolygon).

Можно ли динамически рисовать здесь какую-то пользовательскую карту? Любые идеи?

Образец карты/высота рельефа


person Aleksey Kontsevich    schedule 15.02.2020    source источник


Ответы (1)


Найдено простое и элегантное решение с ChartView с Repeater внутри и ChartView функциями отображения: mapToPosition и mapToValue для удобного отображения диаграммы‹-> экранных координат.

Макет:

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)
            }
        }
    }
}

Приветствуются любые улучшения, оптимизации и предложения, такие как привязка модели через HXYModelMapper/VXYModelMapper вместо заполнения модели JS. Исправлю ответ.

Снимок экрана с результатами:

введите здесь описание изображения

person Aleksey Kontsevich    schedule 20.02.2020