5.7 KiB · text History 7a88816
import QtQuick
import QtQuick.Layouts
import org.kde.kirigami as Kirigami

import "../Grafana.js" as G

// Gauge: a 240° arc per series, the threshold bands as a thin outer ring,
// and the reduced value in the middle.
Item {
    id: gauge

    property var frame: null

    readonly property var panel: frame ? frame.panel : ({})
    readonly property var options: panel.options || {}
    readonly property string calc: ((options.reduceOptions && options.reduceOptions.calcs) || ["lastNotNull"])[0] || "lastNotNull"
    readonly property bool markers: options.showThresholdMarkers !== false

    readonly property var entries: {
        var out = []
        var list = frame ? frame.series : []
        var tc = Kirigami.Theme.textColor.toString()
        for (var i = 0; i < list.length && i < 12; i++) {
            var cfg = G.fieldConfig(panel, list[i])
            // a gauge with no bounds set is a percentage, as in Grafana
            var min = cfg.min !== undefined && cfg.min !== null ? cfg.min : 0
            var max = cfg.max !== undefined && cfg.max !== null ? cfg.max : 100
            var v = G.reduce(list[i].values, calc)
            out.push({ name: G.displayName(list[i], cfg), value: v, min: min, max: max, cfg: cfg,
                       parts: G.formatParts(v, cfg.unit, cfg.decimals),
                       color: G.seriesColor(cfg, i, v, { min: min, max: max }, tc) })
        }
        return out
    }

    GridLayout {
        anchors.fill: parent
        columns: Math.max(1, Math.min(gauge.entries.length, Math.ceil(gauge.width / Math.max(1, gauge.height))))
        rowSpacing: 0
        columnSpacing: 0

        Repeater {
            model: gauge.entries

            delegate: Item {
                id: cell
                required property var modelData
                Layout.fillWidth: true
                Layout.fillHeight: true

                Canvas {
                    id: arc
                    anchors.centerIn: parent
                    width: Math.min(parent.width, parent.height * 1.15)
                    height: width
                    onWidthChanged: requestPaint()
                    Connections {
                        target: cell
                        function onModelDataChanged() { arc.requestPaint() }
                    }
                    onPaint: {
                        var ctx = getContext("2d")
                        ctx.reset()
                        var d = cell.modelData
                        var cx = width / 2, cy = height / 2, r = width / 2 - 4
                        var start = Math.PI * 0.75, sweep = Math.PI * 1.5
                        var thick = Math.max(4, r * 0.18)
                        var frac = d.max > d.min && d.value !== null ? Math.max(0, Math.min(1, (d.value - d.min) / (d.max - d.min))) : 0
                        var tc = Kirigami.Theme.textColor

                        ctx.lineCap = "butt"
                        ctx.lineWidth = thick
                        ctx.strokeStyle = Qt.rgba(tc.r, tc.g, tc.b, 0.1)
                        ctx.beginPath(); ctx.arc(cx, cy, r - thick, start, start + sweep); ctx.stroke()

                        ctx.strokeStyle = d.color === "transparent" ? Kirigami.Theme.highlightColor : d.color
                        ctx.beginPath(); ctx.arc(cx, cy, r - thick, start, start + sweep * frac); ctx.stroke()

                        if (gauge.markers && d.cfg.thresholds && d.cfg.thresholds.steps) {
                            var steps = d.cfg.thresholds.steps
                            var pct = d.cfg.thresholds.mode === "percentage"
                            ctx.lineWidth = Math.max(2, thick / 3)
                            for (var i = 0; i < steps.length; i++) {
                                var a = steps[i].value === null ? d.min : (pct ? d.min + steps[i].value / 100 * (d.max - d.min) : steps[i].value)
                                var b = i + 1 < steps.length ? (pct ? d.min + steps[i + 1].value / 100 * (d.max - d.min) : steps[i + 1].value) : d.max
                                var fa = Math.max(0, Math.min(1, (a - d.min) / (d.max - d.min)))
                                var fb = Math.max(0, Math.min(1, (b - d.min) / (d.max - d.min)))
                                if (fb <= fa) continue
                                ctx.strokeStyle = G.namedColor(steps[i].color, tc.toString())
                                ctx.beginPath(); ctx.arc(cx, cy, r - ctx.lineWidth / 2, start + sweep * fa, start + sweep * fb); ctx.stroke()
                            }
                        }
                    }
                }

                Text {
                    anchors.centerIn: arc
                    width: arc.width * 0.55
                    height: arc.height * 0.3
                    horizontalAlignment: Text.AlignHCenter
                    verticalAlignment: Text.AlignVCenter
                    fontSizeMode: Text.Fit
                    font.pixelSize: 100
                    minimumPixelSize: 8
                    font.weight: Font.DemiBold
                    color: cell.modelData.color === "transparent" ? Kirigami.Theme.textColor : cell.modelData.color
                    text: cell.modelData.parts.prefix + cell.modelData.parts.text + cell.modelData.parts.suffix
                }

                Text {
                    visible: gauge.entries.length > 1
                    anchors.horizontalCenter: arc.horizontalCenter
                    anchors.bottom: arc.bottom
                    width: arc.width * 0.7
                    horizontalAlignment: Text.AlignHCenter
                    elide: Text.ElideRight
                    font: Kirigami.Theme.smallFont
                    color: Kirigami.Theme.textColor
                    opacity: 0.75
                    text: cell.modelData.name
                }
            }
        }
    }
}