From 05a169f89b4269d6d6f9b66950e31f6833056274 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 20 Mar 2026 08:52:43 +0000
Subject: [PATCH 1/6] Replace QtCharts in StatViewer with Canvas-based
LineChart and LineChartLegend components
Co-authored-by: fabiencastan <153585+fabiencastan@users.noreply.github.com>
---
meshroom/ui/qml/Charts/LineChart.qml | 399 +++++++++++++++++++++
meshroom/ui/qml/Charts/LineChartLegend.qml | 110 ++++++
meshroom/ui/qml/Charts/qmldir | 2 +
meshroom/ui/qml/GraphEditor/StatViewer.qml | 268 +++++---------
4 files changed, 599 insertions(+), 180 deletions(-)
create mode 100644 meshroom/ui/qml/Charts/LineChart.qml
create mode 100644 meshroom/ui/qml/Charts/LineChartLegend.qml
diff --git a/meshroom/ui/qml/Charts/LineChart.qml b/meshroom/ui/qml/Charts/LineChart.qml
new file mode 100644
index 0000000000..b8cc781739
--- /dev/null
+++ b/meshroom/ui/qml/Charts/LineChart.qml
@@ -0,0 +1,399 @@
+import QtQuick
+
+/**
+ * LineChart is a generic Canvas-based 2D line chart.
+ *
+ * It renders one or more named, colored line series on a plot area with
+ * automatic axis scaling, grid lines, tick marks, and axis labels.
+ * No QtCharts dependency is required.
+ *
+ * Usage:
+ * LineChart {
+ * title: "CPU Usage"
+ * xAxisTitle: "Minutes"
+ * yAxisTitle: "%"
+ * xMin: 0; xMax: 60
+ * yMin: 0; yMax: 100
+ * textColor: "white"
+ * }
+ *
+ * // Add a series (returns the series index):
+ * var idx = chart.addSeries("CPU0", "#ff5722", [{x:0,y:10},{x:1,y:20}])
+ *
+ * // Remove all series:
+ * chart.removeAllSeries()
+ */
+
+Item {
+ id: root
+
+ // ---- Chart labels -------------------------------------------------------
+
+ /// Text shown centered above the plot area
+ property string title: ""
+ /// Label drawn along the X axis
+ property string xAxisTitle: ""
+ /// Label drawn along the Y axis (rotated 90°)
+ property string yAxisTitle: ""
+
+ // ---- Axis bounds --------------------------------------------------------
+
+ property real xMin: 0
+ property real xMax: 1
+ property real yMin: 0
+ property real yMax: 100
+
+ // ---- Display settings ---------------------------------------------------
+
+ property color textColor: palette.windowText
+ /// Approximate number of tick marks on each axis
+ property int suggestedTickCount: 5
+ property int fontSize: 10
+
+ // ---- Series API ---------------------------------------------------------
+
+ /// Read-only number of series currently in the chart
+ readonly property int count: _series.length
+
+ /// Internal series storage – plain JS array of series descriptor objects:
+ /// { name, color, points:[{x,y}], visible, lineWidth }
+ property var _series: []
+
+ // Emitted when a series is added (index = position in _series)
+ signal seriesAdded(int index)
+ // Emitted when a series is removed (index = former position)
+ signal seriesRemoved(int index)
+ // Emitted whenever any series property (visibility, line width…) changes
+ signal seriesChanged()
+
+ /**
+ * Add a series to the chart.
+ * @param name Display name for the legend
+ * @param seriesColor CSS color string or QML color value
+ * @param points Array of {x, y} objects
+ * @return Index of the new series
+ */
+ function addSeries(name, seriesColor, points) {
+ var s = {
+ name: name,
+ color: seriesColor,
+ points: points || [],
+ visible: true,
+ lineWidth: 1.5
+ }
+ var arr = _series.slice()
+ arr.push(s)
+ _series = arr
+ seriesAdded(_series.length - 1)
+ canvas.requestPaint()
+ return _series.length - 1
+ }
+
+ /// Remove all series from the chart
+ function removeAllSeries() {
+ var n = _series.length
+ _series = []
+ for (var i = 0; i < n; i++)
+ seriesRemoved(i)
+ seriesChanged()
+ canvas.requestPaint()
+ }
+
+ /// Return the series descriptor object at index i
+ function series(i) {
+ return _series[i]
+ }
+
+ /// Show or hide the series at index i
+ function setSeriesVisible(i, vis) {
+ if (i < 0 || i >= _series.length) return
+ var arr = _series.slice()
+ arr[i] = _copyWith(arr[i], { visible: vis })
+ _series = arr
+ seriesChanged()
+ canvas.requestPaint()
+ }
+
+ /// Set visible state for all series at once
+ function setAllSeriesVisible(vis) {
+ var arr = _series.map(function(s) { return _copyWith(s, { visible: vis }) })
+ _series = arr
+ seriesChanged()
+ canvas.requestPaint()
+ }
+
+ /// Set the stroke width for the series at index i
+ function setSeriesLineWidth(i, w) {
+ if (i < 0 || i >= _series.length) return
+ var arr = _series.slice()
+ arr[i] = _copyWith(arr[i], { lineWidth: w })
+ _series = arr
+ canvas.requestPaint()
+ }
+
+ // ---- Helpers (private) --------------------------------------------------
+
+ /// Shallow-copy an object, overriding keys from overrides
+ function _copyWith(obj, overrides) {
+ var result = {}
+ for (var k in obj) result[k] = obj[k]
+ for (var ok in overrides) result[ok] = overrides[ok]
+ return result
+ }
+
+ /**
+ * Compute a nice set of tick values for [minVal, maxVal].
+ * Returns an array of evenly-spaced round numbers.
+ */
+ function _niceTicks(minVal, maxVal, n) {
+ if (minVal >= maxVal) {
+ maxVal = minVal + 1
+ }
+ var range = maxVal - minVal
+ var rough = range / Math.max(n - 1, 1)
+ var mag = Math.pow(10, Math.floor(Math.log(rough) / Math.LN10))
+ var norm = rough / mag
+ var step
+ if (norm < 1.5) step = mag
+ else if (norm < 3.5) step = 2 * mag
+ else if (norm < 7.5) step = 5 * mag
+ else step = 10 * mag
+
+ var start = Math.floor(minVal / step) * step
+ start = parseFloat(start.toPrecision(10))
+
+ var ticks = []
+ var t = start
+ var maxIter = (n + 2) * 3
+ var iter = 0
+ while (iter < maxIter) {
+ iter++
+ var rounded = parseFloat(t.toPrecision(10))
+ if (rounded > maxVal + step * 0.01) break
+ if (rounded >= minVal - step * 0.01)
+ ticks.push(rounded)
+ t += step
+ t = parseFloat(t.toPrecision(10))
+ }
+ return ticks
+ }
+
+ /// Format a tick value compactly (no unnecessary trailing zeros)
+ function _fmtLabel(val) {
+ if (val === 0) return "0"
+ var abs = Math.abs(val)
+ if (abs >= 1000) return val.toFixed(0)
+ if (abs >= 100) return val.toFixed(0)
+ if (abs >= 10) return parseFloat(val.toFixed(1)).toString()
+ return parseFloat(val.toFixed(2)).toString()
+ }
+
+ // ---- Visual -------------------------------------------------------------
+
+ SystemPalette { id: palette }
+
+ Canvas {
+ id: canvas
+ anchors.fill: parent
+ antialiasing: true
+
+ onWidthChanged: requestPaint()
+ onHeightChanged: requestPaint()
+
+ Connections {
+ target: root
+ function onXMinChanged() { canvas.requestPaint() }
+ function onXMaxChanged() { canvas.requestPaint() }
+ function onYMinChanged() { canvas.requestPaint() }
+ function onYMaxChanged() { canvas.requestPaint() }
+ function onTextColorChanged() { canvas.requestPaint() }
+ function onTitleChanged() { canvas.requestPaint() }
+ function onXAxisTitleChanged(){ canvas.requestPaint() }
+ function onYAxisTitleChanged(){ canvas.requestPaint() }
+ }
+
+ onPaint: {
+ var ctx = getContext("2d")
+ ctx.clearRect(0, 0, width, height)
+
+ if (width <= 0 || height <= 0) return
+
+ var w = width
+ var h = height
+ var fs = root.fontSize
+
+ // ---- Compute axis ticks first (to measure Y label width) --------
+
+ var xMin = root.xMin, xMax = root.xMax
+ var yMin = root.yMin, yMax = root.yMax
+ if (xMax <= xMin) xMax = xMin + 1
+ if (yMax <= yMin) yMax = yMin + 1
+
+ var yTicks = root._niceTicks(yMin, yMax, root.suggestedTickCount)
+ var xTicks = root._niceTicks(xMin, xMax, root.suggestedTickCount)
+
+ ctx.font = fs + "px sans-serif"
+ var maxYLabelW = 0
+ for (var ti = 0; ti < yTicks.length; ti++) {
+ var lw = ctx.measureText(root._fmtLabel(yTicks[ti])).width
+ if (lw > maxYLabelW) maxYLabelW = lw
+ }
+
+ // ---- Layout margins ---------------------------------------------
+
+ var hasTitle = root.title.length > 0
+ var hasXTitle = root.xAxisTitle.length > 0
+ var hasYTitle = root.yAxisTitle.length > 0
+
+ var marginLeft = maxYLabelW + 14 + (hasYTitle ? fs + 6 : 0)
+ var marginRight = 10
+ var marginTop = hasTitle ? (fs + 2) * 2 + 4 : 10
+ var marginBottom = fs + 10 + (hasXTitle ? fs + 6 : 0)
+
+ var plotX = Math.floor(marginLeft)
+ var plotY = Math.floor(marginTop)
+ var plotW = Math.floor(w - marginLeft - marginRight)
+ var plotH = Math.floor(h - marginTop - marginBottom)
+
+ if (plotW < 10 || plotH < 10) return
+
+ // ---- Color helpers ----------------------------------------------
+
+ var tc = root.textColor
+ var tcCSS = "rgba(" + Math.round(tc.r*255) + "," + Math.round(tc.g*255) + "," + Math.round(tc.b*255) + "," + Math.min(tc.a, 1) + ")"
+ var gridCSS = "rgba(" + Math.round(tc.r*255) + "," + Math.round(tc.g*255) + "," + Math.round(tc.b*255) + ",0.15)"
+ var axisCSS = "rgba(" + Math.round(tc.r*255) + "," + Math.round(tc.g*255) + "," + Math.round(tc.b*255) + ",0.5)"
+
+ // ---- Coordinate mapping -----------------------------------------
+
+ function mapX(x) { return plotX + (x - xMin) / (xMax - xMin) * plotW }
+ function mapY(y) { return plotY + (1.0 - (y - yMin) / (yMax - yMin)) * plotH }
+
+ // ---- Draw title -------------------------------------------------
+
+ if (hasTitle) {
+ ctx.font = "bold " + (fs + 2) + "px sans-serif"
+ ctx.fillStyle = tcCSS
+ ctx.textAlign = "center"
+ ctx.textBaseline = "middle"
+ ctx.fillText(root.title, w / 2, marginTop / 2)
+ }
+
+ // ---- Draw Y axis label (rotated) ---------------------------------
+
+ if (hasYTitle) {
+ ctx.save()
+ ctx.font = fs + "px sans-serif"
+ ctx.fillStyle = tcCSS
+ ctx.textAlign = "center"
+ ctx.textBaseline = "middle"
+ ctx.translate(fs / 2 + 2, plotY + plotH / 2)
+ ctx.rotate(-Math.PI / 2)
+ ctx.fillText(root.yAxisTitle, 0, 0)
+ ctx.restore()
+ }
+
+ // ---- Draw X axis label ------------------------------------------
+
+ if (hasXTitle) {
+ ctx.font = fs + "px sans-serif"
+ ctx.fillStyle = tcCSS
+ ctx.textAlign = "center"
+ ctx.textBaseline = "bottom"
+ ctx.fillText(root.xAxisTitle, plotX + plotW / 2, h - 2)
+ }
+
+ // ---- Draw horizontal grid lines (at Y ticks) --------------------
+
+ ctx.strokeStyle = gridCSS
+ ctx.lineWidth = 1
+ for (var yi = 0; yi < yTicks.length; yi++) {
+ var gy = mapY(yTicks[yi])
+ if (gy < plotY - 0.5 || gy > plotY + plotH + 0.5) continue
+ ctx.beginPath()
+ ctx.moveTo(plotX, gy)
+ ctx.lineTo(plotX + plotW, gy)
+ ctx.stroke()
+ }
+
+ // ---- Draw vertical grid lines (at X ticks) ----------------------
+
+ for (var xi = 0; xi < xTicks.length; xi++) {
+ var gx = mapX(xTicks[xi])
+ if (gx < plotX - 0.5 || gx > plotX + plotW + 0.5) continue
+ ctx.beginPath()
+ ctx.moveTo(gx, plotY)
+ ctx.lineTo(gx, plotY + plotH)
+ ctx.stroke()
+ }
+
+ // ---- Draw plot border -------------------------------------------
+
+ ctx.strokeStyle = axisCSS
+ ctx.lineWidth = 1
+ ctx.beginPath()
+ ctx.rect(plotX, plotY, plotW, plotH)
+ ctx.stroke()
+
+ // ---- Draw Y tick marks and labels --------------------------------
+
+ ctx.font = fs + "px sans-serif"
+ ctx.fillStyle = tcCSS
+ ctx.strokeStyle = axisCSS
+ ctx.lineWidth = 1
+
+ for (var yk = 0; yk < yTicks.length; yk++) {
+ var ty = mapY(yTicks[yk])
+ if (ty < plotY - 0.5 || ty > plotY + plotH + 0.5) continue
+ ctx.textAlign = "right"
+ ctx.textBaseline = "middle"
+ ctx.fillText(root._fmtLabel(yTicks[yk]), plotX - 6, ty)
+ ctx.beginPath()
+ ctx.moveTo(plotX - 4, ty)
+ ctx.lineTo(plotX, ty)
+ ctx.stroke()
+ }
+
+ // ---- Draw X tick marks and labels --------------------------------
+
+ for (var xk = 0; xk < xTicks.length; xk++) {
+ var tx = mapX(xTicks[xk])
+ if (tx < plotX - 0.5 || tx > plotX + plotW + 0.5) continue
+ ctx.textAlign = "center"
+ ctx.textBaseline = "top"
+ ctx.fillText(root._fmtLabel(xTicks[xk]), tx, plotY + plotH + 5)
+ ctx.beginPath()
+ ctx.moveTo(tx, plotY + plotH)
+ ctx.lineTo(tx, plotY + plotH + 4)
+ ctx.stroke()
+ }
+
+ // ---- Draw series lines (clipped to plot area) -------------------
+
+ ctx.save()
+ ctx.beginPath()
+ ctx.rect(plotX, plotY, plotW, plotH)
+ ctx.clip()
+
+ for (var si = 0; si < root._series.length; si++) {
+ var s = root._series[si]
+ if (!s.visible || !s.points || s.points.length === 0) continue
+
+ ctx.strokeStyle = s.color.toString()
+ ctx.lineWidth = s.lineWidth || 1.5
+ ctx.lineJoin = "round"
+ ctx.lineCap = "round"
+
+ var pts = s.points
+ ctx.beginPath()
+ ctx.moveTo(mapX(pts[0].x), mapY(pts[0].y))
+ for (var pi = 1; pi < pts.length; pi++) {
+ ctx.lineTo(mapX(pts[pi].x), mapY(pts[pi].y))
+ }
+ ctx.stroke()
+ }
+
+ ctx.restore()
+ }
+ }
+}
diff --git a/meshroom/ui/qml/Charts/LineChartLegend.qml b/meshroom/ui/qml/Charts/LineChartLegend.qml
new file mode 100644
index 0000000000..1d6338a5c6
--- /dev/null
+++ b/meshroom/ui/qml/Charts/LineChartLegend.qml
@@ -0,0 +1,110 @@
+import QtQuick
+import QtQuick.Controls
+
+/**
+ * LineChartLegend is an interactive legend component for LineChart.
+ *
+ * It provides a labeled, colored CheckBox for each series in the associated
+ * LineChart, allowing the user to toggle series visibility.
+ *
+ * • Click – toggle the clicked series on/off
+ * • Ctrl + Click – show only the clicked series (solo mode)
+ * • Hover – highlight the hovered series, dim the others
+ *
+ * The component exposes a ButtonGroup so that an "ALL" master checkbox can
+ * display the aggregate check state of all legend items.
+ */
+
+Flow {
+ id: root
+
+ /// The LineChart instance whose series this legend represents
+ property var chartView: null
+
+ /// Expose the internal ButtonGroup so callers can read its checkState
+ readonly property ButtonGroup buttonGroup: legendGroup
+
+ ButtonGroup {
+ id: legendGroup
+ exclusive: false
+ }
+
+ // ---- Internal model rebuilt from chartView whenever series change -------
+
+ ListModel { id: seriesModel }
+
+ function _rebuild() {
+ seriesModel.clear()
+ if (!chartView) return
+ for (var i = 0; i < chartView.count; i++) {
+ var s = chartView.series(i)
+ if (!s) continue
+ seriesModel.append({
+ seriesIndex: i,
+ seriesName: s.name,
+ seriesColor: s.color.toString(),
+ seriesVisible: s.visible
+ })
+ }
+ }
+
+ onChartViewChanged: {
+ seriesModel.clear()
+ if (chartView) {
+ chartView.seriesAdded.connect(_rebuild)
+ chartView.seriesRemoved.connect(_rebuild)
+ chartView.seriesChanged.connect(_rebuild)
+ _rebuild()
+ }
+ }
+
+ // ---- Legend items -------------------------------------------------------
+
+ Repeater {
+ model: seriesModel
+
+ ChartViewCheckBox {
+ id: legendItem
+
+ ButtonGroup.group: legendGroup
+
+ checked: model.seriesVisible
+ text: model.seriesName
+ color: model.seriesColor
+
+ MouseArea {
+ anchors.fill: parent
+ hoverEnabled: true
+
+ onEntered: {
+ if (!chartView) return
+ for (var i = 0; i < chartView.count; i++) {
+ if (chartView.series(i) && chartView.series(i).visible) {
+ chartView.setSeriesLineWidth(
+ i, i === model.seriesIndex ? 3.0 : 0.5)
+ }
+ }
+ }
+
+ onExited: {
+ if (!chartView) return
+ for (var i = 0; i < chartView.count; i++) {
+ chartView.setSeriesLineWidth(i, 1.5)
+ }
+ }
+
+ onClicked: function(mouse) {
+ if (!chartView) return
+ if (mouse.modifiers & Qt.ControlModifier) {
+ // Solo: hide everything except the clicked series
+ chartView.setAllSeriesVisible(false)
+ chartView.setSeriesVisible(model.seriesIndex, true)
+ } else {
+ var cur = chartView.series(model.seriesIndex)
+ if (cur) chartView.setSeriesVisible(model.seriesIndex, !cur.visible)
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/meshroom/ui/qml/Charts/qmldir b/meshroom/ui/qml/Charts/qmldir
index 0b50d1ed53..e02eabbfe0 100644
--- a/meshroom/ui/qml/Charts/qmldir
+++ b/meshroom/ui/qml/Charts/qmldir
@@ -3,3 +3,5 @@ module Charts
ChartViewLegend 1.0 ChartViewLegend.qml
ChartViewCheckBox 1.0 ChartViewCheckBox.qml
InteractiveChartView 1.0 InteractiveChartView.qml
+LineChart 1.0 LineChart.qml
+LineChartLegend 1.0 LineChartLegend.qml
diff --git a/meshroom/ui/qml/GraphEditor/StatViewer.qml b/meshroom/ui/qml/GraphEditor/StatViewer.qml
index ed25d577ee..260a81a926 100644
--- a/meshroom/ui/qml/GraphEditor/StatViewer.qml
+++ b/meshroom/ui/qml/GraphEditor/StatViewer.qml
@@ -1,4 +1,3 @@
-import QtCharts
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
@@ -89,7 +88,6 @@ Item {
id: reloadTimer
interval: root.deltaTime * 60000; running: true; repeat: false
onTriggered: readSourceFile()
-
}
function readSourceFile() {
@@ -122,7 +120,7 @@ Item {
function resetCharts() {
root.fileVersion = 0.0
- cpuLegend.clear()
+ root.gpuMaxAxis = 100
cpuChart.removeAllSeries()
ramChart.removeAllSeries()
gpuChart.removeAllSeries()
@@ -158,57 +156,52 @@ Item {
root.nbCores = nbCores
root.cpuFrequency = getPropertyWithDefault(jsonObject.computer, "cpuFreq", -1)
+ root.nbReads = categories[0].length - 1
- root.nbReads = categories[0].length-1
-
+ // Build and add one series per CPU core
for (var j = 0; j < nbCores; j++) {
- var lineSerie = cpuChart.createSeries(ChartView.SeriesTypeLine, "CPU" + j, valueCpuX, valueCpuY)
-
- if (categories[j].length === 1) {
- lineSerie.append(0, categories[j][0])
- lineSerie.append(root.deltaTime, categories[j][0])
+ var cat = categories[j]
+ var corePoints = []
+ if (cat.length === 1) {
+ corePoints = [{ x: 0, y: cat[0] }, { x: root.deltaTime, y: cat[0] }]
} else {
- var displayLength = Math.min(maxDisplayLength, categories[j].length)
- var step = categories[j].length / displayLength
- for (var kk = 0; kk < displayLength; kk += step) {
- var k = Math.floor(kk * step)
- lineSerie.append(k * root.deltaTime, categories[j][k])
+ var displayLength = Math.min(maxDisplayLength, cat.length)
+ var step = cat.length / displayLength
+ for (var k = 0; k < displayLength; k++) {
+ var idx = Math.floor(k * step)
+ corePoints.push({ x: idx * root.deltaTime, y: cat[idx] })
}
}
- lineSerie.color = colors[j % colors.length]
+ cpuChart.addSeries("CPU" + j, colors[j % colors.length], corePoints)
}
- var averageLine = cpuChart.createSeries(ChartView.SeriesTypeLine, "AVERAGE", valueCpuX, valueCpuY)
+ // Compute and add the AVERAGE series
+ var avgDisplayLength = Math.min(maxDisplayLength, categories[0].length)
+ var avgStep = categories[0].length / avgDisplayLength
var average = []
-
- var displayLengthA = Math.min(maxDisplayLength, categories[0].length)
- var stepA = categories[0].length / displayLengthA
- for (var l = 0; l < displayLengthA; l += step) {
+ for (var l = 0; l < avgDisplayLength; l++) {
average.push(0)
}
for (var m = 0; m < categories.length; m++) {
var displayLengthB = Math.min(maxDisplayLength, categories[m].length)
- var stepB = categories[0].length / displayLengthB
- for (var nn = 0; nn < displayLengthB; nn++) {
- var n = Math.floor(nn * stepB)
- average[nn] += categories[m][n]
+ var stepB = categories[m].length / displayLengthB
+ for (var n = 0; n < displayLengthB; n++) {
+ average[n] += categories[m][Math.floor(n * stepB)]
}
}
+ var avgPoints = []
for (var q = 0; q < average.length; q++) {
- average[q] = average[q] / (categories.length)
- averageLine.append(q * root.deltaTime * stepA, average[q])
+ average[q] = average[q] / categories.length
+ avgPoints.push({ x: q * root.deltaTime * avgStep, y: average[q] })
}
-
- averageLine.color = colors[colors.length - 1]
+ cpuChart.addSeries("AVERAGE", colors[colors.length - 1], avgPoints)
}
function hideOtherCpu(index) {
- for (var i = 0; i < cpuChart.count; i++) {
- cpuChart.series(i).visible = false
- }
- cpuChart.series(index).visible = true
+ cpuChart.setAllSeriesVisible(false)
+ cpuChart.setSeriesVisible(index, true)
}
@@ -231,21 +224,19 @@ Item {
root.ramLabel = "RAM Max Peak: "
}
- var ramSerie = ramChart.createSeries(ChartView.SeriesTypeLine, root.ramLabel + root.ramTotal + "GB", valueRamX, valueRamY)
-
+ var ramPoints = []
if (ram.length === 1) {
- // Create 2 entries if we have only one input value to create a segment that can be display
- ramSerie.append(0, ram[0])
- ramSerie.append(root.deltaTime, ram[0])
+ // Create 2 entries if we have only one input value to create a segment that can be displayed
+ ramPoints = [{ x: 0, y: ram[0] }, { x: root.deltaTime, y: ram[0] }]
} else {
var displayLength = Math.min(maxDisplayLength, ram.length)
var step = ram.length / displayLength
- for(var ii = 0; ii < displayLength; ii++) {
+ for (var ii = 0; ii < displayLength; ii++) {
var i = Math.floor(ii * step)
- ramSerie.append(i * root.deltaTime, ram[i])
+ ramPoints.push({ x: i * root.deltaTime, y: ram[i] })
}
}
- ramSerie.color = colors[10]
+ ramChart.addSeries(root.ramLabel + root.ramTotal + "GB", colors[10], ramPoints)
}
@@ -261,35 +252,35 @@ Item {
var gpuUsed = getPropertyWithDefault(jsonObject.computer.curves, "gpuUsed", 0)
var gpuTemperature = getPropertyWithDefault(jsonObject.computer.curves, "gpuTemperature", 0)
- var gpuUsedSerie = gpuChart.createSeries(ChartView.SeriesTypeLine, "GPU", valueGpuX, valueGpuY)
- var gpuUsedMemorySerie = gpuChart.createSeries(ChartView.SeriesTypeLine, "Memory", valueGpuX, valueGpuY)
- var gpuTemperatureSerie = gpuChart.createSeries(ChartView.SeriesTypeLine, "Temperature", valueGpuX, valueGpuY)
-
var gpuMemoryRatio = root.gpuTotalMemory > 0 ? (100 / root.gpuTotalMemory) : 1
- if (gpuUsedMemory.length === 1) {
- gpuUsedSerie.append(0, gpuUsed[0])
- gpuUsedSerie.append(1 * root.deltaTime, gpuUsed[0])
-
- gpuUsedMemorySerie.append(0, gpuUsedMemory[0] * gpuMemoryRatio)
- gpuUsedMemorySerie.append(1 * root.deltaTime, gpuUsedMemory[0] * gpuMemoryRatio)
+ var gpuUsedPoints = []
+ var gpuMemPoints = []
+ var gpuTempPoints = []
- gpuTemperatureSerie.append(0, gpuTemperature[0])
- gpuTemperatureSerie.append(1 * root.deltaTime, gpuTemperature[0])
+ if (gpuUsedMemory.length === 1) {
+ gpuUsedPoints = [{ x: 0, y: gpuUsed[0] },
+ { x: root.deltaTime, y: gpuUsed[0] }]
+ gpuMemPoints = [{ x: 0, y: gpuUsedMemory[0] * gpuMemoryRatio },
+ { x: root.deltaTime, y: gpuUsedMemory[0] * gpuMemoryRatio }]
+ gpuTempPoints = [{ x: 0, y: gpuTemperature[0] },
+ { x: root.deltaTime, y: gpuTemperature[0] }]
root.gpuMaxAxis = Math.max(gpuMaxAxis, gpuTemperature[0])
} else {
var displayLength = Math.min(maxDisplayLength, gpuUsedMemory.length)
var step = gpuUsedMemory.length / displayLength
- for (var ii = 0; ii < displayLength; ii += step) {
- var i = Math.floor(ii*step)
- gpuUsedSerie.append(i * root.deltaTime, gpuUsed[i])
-
- gpuUsedMemorySerie.append(i * root.deltaTime, gpuUsedMemory[i] * gpuMemoryRatio)
-
- gpuTemperatureSerie.append(i * root.deltaTime, gpuTemperature[i])
+ for (var ii = 0; ii < displayLength; ii++) {
+ var i = Math.floor(ii * step)
+ gpuUsedPoints.push({ x: i * root.deltaTime, y: gpuUsed[i] })
+ gpuMemPoints.push({ x: i * root.deltaTime, y: gpuUsedMemory[i] * gpuMemoryRatio })
+ gpuTempPoints.push({ x: i * root.deltaTime, y: gpuTemperature[i] })
root.gpuMaxAxis = Math.max(gpuMaxAxis, gpuTemperature[i])
}
}
+
+ gpuChart.addSeries("GPU", colors[0], gpuUsedPoints)
+ gpuChart.addSeries("Memory", colors[5], gpuMemPoints)
+ gpuChart.addSeries("Temperature", colors[15], gpuTempPoints)
}
@@ -356,14 +347,12 @@ Item {
checkState: cpuLegend.buttonGroup.checkState
leftPadding: 0
onClicked: {
- var _checked = checked;
- for (var i = 0; i < cpuChart.count; ++i) {
- cpuChart.series(i).visible = _checked
- }
+ var _checked = checked
+ cpuChart.setAllSeriesVisible(_checked)
}
}
- ChartViewLegend {
+ LineChartLegend {
id: cpuLegend
Layout.fillWidth: true
Layout.fillHeight: true
@@ -372,147 +361,66 @@ Item {
}
}
- InteractiveChartView {
+ LineChart {
id: cpuChart
Layout.fillWidth: true
Layout.preferredHeight: width / 2
- margins.top: 0
- margins.bottom: 0
- antialiasing: true
-
- legend.visible: false
- theme: ChartView.ChartThemeLight
- backgroundColor: "transparent"
- plotAreaColor: "transparent"
- titleColor: textColor
- visible: (root.fileVersion > 0.0) // Only visible if we have valid information
+ textColor: root.textColor
title: "CPU: " + root.nbCores + " cores, " + root.cpuFrequency + "MHz"
-
- ValueAxis {
- id: valueCpuY
- min: 0
- max: 100
- titleText: "%"
- color: textColor
- gridLineColor: textColor
- minorGridLineColor: textColor
- shadesColor: textColor
- shadesBorderColor: textColor
- labelsColor: textColor
- }
-
- ValueAxis {
- id: valueCpuX
- min: 0
- max: root.deltaTime * Math.max(1, root.nbReads)
- titleText: "Minutes"
- color: textColor
- gridLineColor: textColor
- minorGridLineColor: textColor
- shadesColor: textColor
- shadesBorderColor: textColor
- labelsColor: textColor
- }
+ xAxisTitle: "Minutes"
+ yAxisTitle: "%"
+ xMin: 0
+ xMax: root.deltaTime * Math.max(1, root.nbReads)
+ yMin: 0
+ yMax: 100
+
+ visible: (root.fileVersion > 0.0)
}
/**************************
*** RAM UI ***
**************************/
- InteractiveChartView {
+ LineChart {
id: ramChart
- margins.top: 0
- margins.bottom: 0
+
Layout.fillWidth: true
Layout.preferredHeight: width / 2
- antialiasing: true
- legend.color: textColor
- legend.labelColor: textColor
- legend.visible: false
- theme: ChartView.ChartThemeLight
- backgroundColor: "transparent"
- plotAreaColor: "transparent"
- titleColor: textColor
-
- visible: (root.fileVersion > 0.0) // Only visible if we have valid information
- title: root.ramLabel + root.ramTotal + "GB"
-
- ValueAxis {
- id: valueRamY
- min: 0
- max: 100
- titleText: "%"
- color: textColor
- gridLineColor: textColor
- minorGridLineColor: textColor
- shadesColor: textColor
- shadesBorderColor: textColor
- labelsColor: textColor
- }
- ValueAxis {
- id: valueRamX
- min: 0
- max: root.deltaTime * Math.max(1, root.nbReads)
- titleText: "Minutes"
- color: textColor
- gridLineColor: textColor
- minorGridLineColor: textColor
- shadesColor: textColor
- shadesBorderColor: textColor
- labelsColor: textColor
- }
+ textColor: root.textColor
+ title: root.ramLabel + root.ramTotal + "GB"
+ xAxisTitle: "Minutes"
+ yAxisTitle: "%"
+ xMin: 0
+ xMax: root.deltaTime * Math.max(1, root.nbReads)
+ yMin: 0
+ yMax: 100
+
+ visible: (root.fileVersion > 0.0)
}
/**************************
*** GPU UI ***
**************************/
- InteractiveChartView {
+ LineChart {
id: gpuChart
Layout.fillWidth: true
- Layout.preferredHeight: width/2
- margins.top: 0
- margins.bottom: 0
- antialiasing: true
- legend.color: textColor
- legend.labelColor: textColor
- theme: ChartView.ChartThemeLight
- backgroundColor: "transparent"
- plotAreaColor: "transparent"
- titleColor: textColor
-
- visible: (root.fileVersion >= 2.0) // No GPU information was collected before stats 2.0 fileVersion
- title: (root.gpuName || root.gpuTotalMemory) ? ("GPU: " + root.gpuName + ", " + root.gpuTotalMemory + "MB") : "No GPU"
-
- ValueAxis {
- id: valueGpuY
- min: 0
- max: root.gpuMaxAxis
- titleText: "%, °C"
- color: textColor
- gridLineColor: textColor
- minorGridLineColor: textColor
- shadesColor: textColor
- shadesBorderColor: textColor
- labelsColor: textColor
- }
+ Layout.preferredHeight: width / 2
- ValueAxis {
- id: valueGpuX
- min: 0
- max: root.deltaTime * Math.max(1, root.nbReads)
- titleText: "Minutes"
- color: textColor
- gridLineColor: textColor
- minorGridLineColor: textColor
- shadesColor: textColor
- shadesBorderColor: textColor
- labelsColor: textColor
- }
+ textColor: root.textColor
+ title: (root.gpuName || root.gpuTotalMemory) ? ("GPU: " + root.gpuName + ", " + root.gpuTotalMemory + "MB") : "No GPU"
+ xAxisTitle: "Minutes"
+ yAxisTitle: "%, °C"
+ xMin: 0
+ xMax: root.deltaTime * Math.max(1, root.nbReads)
+ yMin: 0
+ yMax: root.gpuMaxAxis
+
+ visible: (root.fileVersion >= 2.0)
}
}
}
From fa9bc09cb8672331158d1e11159be00c3209b2df Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 20 Mar 2026 08:53:46 +0000
Subject: [PATCH 2/6] Address code review: rename ambiguous loop var, extract
color helper function
Co-authored-by: fabiencastan <153585+fabiencastan@users.noreply.github.com>
---
meshroom/ui/qml/Charts/LineChart.qml | 9 ++++++---
meshroom/ui/qml/GraphEditor/StatViewer.qml | 2 +-
2 files changed, 7 insertions(+), 4 deletions(-)
diff --git a/meshroom/ui/qml/Charts/LineChart.qml b/meshroom/ui/qml/Charts/LineChart.qml
index b8cc781739..0669124477 100644
--- a/meshroom/ui/qml/Charts/LineChart.qml
+++ b/meshroom/ui/qml/Charts/LineChart.qml
@@ -260,9 +260,12 @@ Item {
// ---- Color helpers ----------------------------------------------
var tc = root.textColor
- var tcCSS = "rgba(" + Math.round(tc.r*255) + "," + Math.round(tc.g*255) + "," + Math.round(tc.b*255) + "," + Math.min(tc.a, 1) + ")"
- var gridCSS = "rgba(" + Math.round(tc.r*255) + "," + Math.round(tc.g*255) + "," + Math.round(tc.b*255) + ",0.15)"
- var axisCSS = "rgba(" + Math.round(tc.r*255) + "," + Math.round(tc.g*255) + "," + Math.round(tc.b*255) + ",0.5)"
+ function tcToCSS(alpha) {
+ return "rgba(" + Math.round(tc.r*255) + "," + Math.round(tc.g*255) + "," + Math.round(tc.b*255) + "," + alpha + ")"
+ }
+ var tcCSS = tcToCSS(Math.min(tc.a, 1))
+ var gridCSS = tcToCSS(0.15)
+ var axisCSS = tcToCSS(0.5)
// ---- Coordinate mapping -----------------------------------------
diff --git a/meshroom/ui/qml/GraphEditor/StatViewer.qml b/meshroom/ui/qml/GraphEditor/StatViewer.qml
index 260a81a926..7222a5586d 100644
--- a/meshroom/ui/qml/GraphEditor/StatViewer.qml
+++ b/meshroom/ui/qml/GraphEditor/StatViewer.qml
@@ -179,7 +179,7 @@ Item {
var avgDisplayLength = Math.min(maxDisplayLength, categories[0].length)
var avgStep = categories[0].length / avgDisplayLength
var average = []
- for (var l = 0; l < avgDisplayLength; l++) {
+ for (var avgIdx = 0; avgIdx < avgDisplayLength; avgIdx++) {
average.push(0)
}
From e6fb7e909075b5c429643283565d0dbaf2b308b8 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 20 Mar 2026 09:56:20 +0000
Subject: [PATCH 3/6] Fix Ctrl+click solo mode and add missing GPU legend
Co-authored-by: fabiencastan <153585+fabiencastan@users.noreply.github.com>
---
meshroom/ui/qml/Charts/LineChartLegend.qml | 4 +++-
meshroom/ui/qml/GraphEditor/StatViewer.qml | 7 +++++++
2 files changed, 10 insertions(+), 1 deletion(-)
diff --git a/meshroom/ui/qml/Charts/LineChartLegend.qml b/meshroom/ui/qml/Charts/LineChartLegend.qml
index 1d6338a5c6..e09baddde8 100644
--- a/meshroom/ui/qml/Charts/LineChartLegend.qml
+++ b/meshroom/ui/qml/Charts/LineChartLegend.qml
@@ -96,9 +96,11 @@ Flow {
onClicked: function(mouse) {
if (!chartView) return
if (mouse.modifiers & Qt.ControlModifier) {
+ // Capture index before setAllSeriesVisible triggers a model rebuild
+ var idx = model.seriesIndex
// Solo: hide everything except the clicked series
chartView.setAllSeriesVisible(false)
- chartView.setSeriesVisible(model.seriesIndex, true)
+ chartView.setSeriesVisible(idx, true)
} else {
var cur = chartView.series(model.seriesIndex)
if (cur) chartView.setSeriesVisible(model.seriesIndex, !cur.visible)
diff --git a/meshroom/ui/qml/GraphEditor/StatViewer.qml b/meshroom/ui/qml/GraphEditor/StatViewer.qml
index 7222a5586d..01d4af48af 100644
--- a/meshroom/ui/qml/GraphEditor/StatViewer.qml
+++ b/meshroom/ui/qml/GraphEditor/StatViewer.qml
@@ -405,6 +405,13 @@ Item {
*** GPU UI ***
**************************/
+ LineChartLegend {
+ id: gpuLegend
+ Layout.fillWidth: true
+ Layout.leftMargin: 25
+ chartView: gpuChart
+ }
+
LineChart {
id: gpuChart
From cc0627dd966be34084bc861dcb22fc08cd2b760a Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 20 Mar 2026 17:30:51 +0000
Subject: [PATCH 4/6] Replace QtCharts with Canvas-based LineChart in
CameraResponseGraph
Co-authored-by: fabiencastan <153585+fabiencastan@users.noreply.github.com>
Agent-Logs-Url: https://github.com/alicevision/Meshroom/sessions/8dcbfe42-e6f0-4b11-803d-ae94dc80b532
---
meshroom/ui/components/csvData.py | 10 --
.../ui/qml/Viewer/CameraResponseGraph.qml | 122 ++++++------------
2 files changed, 38 insertions(+), 94 deletions(-)
diff --git a/meshroom/ui/components/csvData.py b/meshroom/ui/components/csvData.py
index 715af2a6b5..935dc912a9 100644
--- a/meshroom/ui/components/csvData.py
+++ b/meshroom/ui/components/csvData.py
@@ -1,7 +1,6 @@
from meshroom.common.qt import QObjectListModel
from PySide6.QtCore import QObject, Slot, Signal, Property
-from PySide6 import QtCharts
import csv
import os
@@ -112,14 +111,5 @@ def getLast(self):
return ""
return self._content[-1]
- @Slot(QtCharts.QXYSeries)
- def fillChartSerie(self, serie):
- """Fill XYSerie used for displaying QML Chart."""
- if not serie:
- return
- serie.clear()
- for index, value in enumerate(self._content):
- serie.append(float(index), float(value))
-
title = Property(str, lambda self: self._title, constant=True)
content = Property("QStringList", lambda self: self._content, constant=True)
diff --git a/meshroom/ui/qml/Viewer/CameraResponseGraph.qml b/meshroom/ui/qml/Viewer/CameraResponseGraph.qml
index 992d7c9fcc..84ab0a5154 100644
--- a/meshroom/ui/qml/Viewer/CameraResponseGraph.qml
+++ b/meshroom/ui/qml/Viewer/CameraResponseGraph.qml
@@ -2,8 +2,6 @@ import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
-import QtCharts
-
import Charts 1.0
import Controls 1.0
import DataObjects 1.0
@@ -34,101 +32,57 @@ FloatingPane {
// Note: We need to use csvData.getNbColumns() slot instead of the csvData.nbColumns property to avoid a crash on linux.
property bool crfReady: csvData && csvData.ready && (csvData.getNbColumns() >= 4)
onCrfReadyChanged: {
+ responseChart.removeAllSeries()
if (crfReady) {
- redCurve.clear()
- greenCurve.clear()
- blueCurve.clear()
- csvData.getColumn(1).fillChartSerie(redCurve)
- csvData.getColumn(2).fillChartSerie(greenCurve)
- csvData.getColumn(3).fillChartSerie(blueCurve)
- } else {
- redCurve.clear()
- greenCurve.clear()
- blueCurve.clear()
+ var xCol = csvData.getColumn(0).content
+ var curveColors = ["red", "green", "blue"]
+ for (var ci = 1; ci <= 3; ci++) {
+ var col = csvData.getColumn(ci)
+ var points = []
+ for (var i = 0; i < col.content.length; i++) {
+ points.push({ x: parseFloat(xCol[i]), y: parseFloat(col.content[i]) })
+ }
+ responseChart.addSeries(col.title, curveColors[ci - 1], points)
+ }
}
}
- Item {
- anchors.horizontalCenter: parent.horizontalCenter
- anchors.verticalCenter: parent.verticalCenter
- anchors.horizontalCenterOffset: -responseChart.width/2
- anchors.verticalCenterOffset: -responseChart.height/2
- InteractiveChartView {
- id: responseChart
- width: root.width > 400 ? 400 : (root.width < 350 ? 350 : root.width)
- height: width * 0.75
+ ColumnLayout {
+ anchors.fill: parent
- title: "Camera Response Function (CRF)"
- legend.visible: false
- antialiasing: true
+ RowLayout {
+ Layout.fillWidth: true
+ Layout.leftMargin: 10
- ValueAxis {
- id: valueAxisX
- labelFormat: "%i"
- titleText: "Camera Brightness"
- min: crfReady ? csvData.getColumn(0).getFirst() : 0
- max: crfReady ? csvData.getColumn(0).getLast() : 1
- }
- ValueAxis {
- id: valueAxisY
- titleText: "Normalized Radiance"
- min: 0.0
- max: 1.0
+ ChartViewCheckBox {
+ text: "ALL"
+ color: root.textColor
+ checkState: crfLegend.buttonGroup.checkState
+ leftPadding: 0
+ onClicked: responseChart.setAllSeriesVisible(checked)
}
- // We cannot use a Repeater with these Components so we need to instantiate them one by one
- LineSeries {
- // Red curve
- id: redCurve
- axisX: valueAxisX
- axisY: valueAxisY
- name: crfReady ? csvData.getColumn(1).title : ""
- color: name.toLowerCase()
- }
- LineSeries {
- // Green curve
- id: greenCurve
- axisX: valueAxisX
- axisY: valueAxisY
- name: crfReady ? csvData.getColumn(2).title : ""
- color: name.toLowerCase()
- }
- LineSeries {
- // Blue curve
- id: blueCurve
- axisX: valueAxisX
- axisY: valueAxisY
- name: crfReady ? csvData.getColumn(3).title : ""
- color: name.toLowerCase()
+ LineChartLegend {
+ id: crfLegend
+ Layout.fillWidth: true
+ chartView: responseChart
}
}
- Item {
- id: btnContainer
-
- anchors.bottom: responseChart.bottom
- anchors.bottomMargin: 35
- anchors.left: responseChart.left
- anchors.leftMargin: responseChart.width * 0.15
+ LineChart {
+ id: responseChart
- RowLayout {
- ChartViewCheckBox {
- text: "ALL"
- color: textColor
- checkState: legend.buttonGroup.checkState
- onClicked: {
- const _checked = checked
- for (let i = 0; i < responseChart.count; ++i) {
- responseChart.series(i).visible = _checked
- }
- }
- }
+ Layout.fillWidth: true
+ Layout.preferredHeight: width * 0.75
- ChartViewLegend {
- id: legend
- chartView: responseChart
- }
- }
+ textColor: root.textColor
+ title: "Camera Response Function (CRF)"
+ xAxisTitle: "Camera Brightness"
+ yAxisTitle: "Normalized Radiance"
+ xMin: crfReady ? parseFloat(csvData.getColumn(0).getFirst()) : 0
+ xMax: crfReady ? parseFloat(csvData.getColumn(0).getLast()) : 1
+ yMin: 0.0
+ yMax: 1.0
}
}
}
From 93099f1af1ce65250c4f810a9e4f4578abbf9d39 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 20 Mar 2026 17:48:08 +0000
Subject: [PATCH 5/6] Replace QtCharts with Canvas-based LineChart in
SfmStatsView and SfmGlobalStats
Co-authored-by: fabiencastan <153585+fabiencastan@users.noreply.github.com>
Agent-Logs-Url: https://github.com/alicevision/Meshroom/sessions/c691a49d-fe73-4e50-92a4-4b5d1b6b3a46
---
meshroom/ui/qml/Viewer/SfmGlobalStats.qml | 380 +++++++---------------
meshroom/ui/qml/Viewer/SfmStatsView.qml | 308 +++++++-----------
2 files changed, 234 insertions(+), 454 deletions(-)
diff --git a/meshroom/ui/qml/Viewer/SfmGlobalStats.qml b/meshroom/ui/qml/Viewer/SfmGlobalStats.qml
index 74b735d902..174735fc50 100644
--- a/meshroom/ui/qml/Viewer/SfmGlobalStats.qml
+++ b/meshroom/ui/qml/Viewer/SfmGlobalStats.qml
@@ -1,4 +1,3 @@
-import QtCharts
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
@@ -28,264 +27,123 @@ FloatingPane {
onWheel: {}
}
+ // Colors assigned to the 6 statistical curves (Min, Max, Mean, Median, Q1, Q3)
+ readonly property var statColors: ["#4169e1", "#dc143c", "#228b22", "#ff8c00", "#9932cc", "#20b2aa"]
- InteractiveChartView {
- id: residualsPerViewChart
- width: parent.width * 0.5
- height: parent.height * 0.5
-
- title: "Residuals Per View"
- legend.visible: false
- antialiasing: true
-
- ValueAxis {
- id: residualsPerViewValueAxisX
- labelFormat: "%i"
- titleText: "Ordered Views"
- min: 0
- max: sfmDataStat.residualsPerViewMaxAxisX
- }
- ValueAxis {
- id: residualsPerViewValueAxisY
- titleText: "Reprojection Error (pix)"
- min: 0
- max: sfmDataStat.residualsPerViewMaxAxisY
- tickAnchor: 0
- tickInterval: 0.50
- tickCount: sfmDataStat.residualsPerViewMaxAxisY * 2
- }
- LineSeries {
- id: residualsMinPerViewLineSerie
- axisX: residualsPerViewValueAxisX
- axisY: residualsPerViewValueAxisY
- name: "Min"
- }
- LineSeries {
- id: residualsMaxPerViewLineSerie
- axisX: residualsPerViewValueAxisX
- axisY: residualsPerViewValueAxisY
- name: "Max"
- }
- LineSeries {
- id: residualsMeanPerViewLineSerie
- axisX: residualsPerViewValueAxisX
- axisY: residualsPerViewValueAxisY
- name: "Mean"
- }
- LineSeries {
- id: residualsMedianPerViewLineSerie
- axisX: residualsPerViewValueAxisX
- axisY: residualsPerViewValueAxisY
- name: "Median"
- }
- LineSeries {
- id: residualsFirstQuartilePerViewLineSerie
- axisX: residualsPerViewValueAxisX
- axisY: residualsPerViewValueAxisY
- name: "Q1"
- }
- LineSeries {
- id: residualsThirdQuartilePerViewLineSerie
- axisX: residualsPerViewValueAxisX
- axisY: residualsPerViewValueAxisY
- name: "Q3"
- }
- }
-
- Item {
- id: residualsPerViewBtnContainer
-
- Layout.fillWidth: true
- anchors.bottom: residualsPerViewChart.bottom
- anchors.bottomMargin: 35
- anchors.left: residualsPerViewChart.left
- anchors.leftMargin: residualsPerViewChart.width * 0.25
-
- RowLayout {
- ChartViewCheckBox {
- id: allObservations
- text: "ALL"
- color: textColor
- checkState: residualsPerViewLegend.buttonGroup.checkState
- onClicked: {
- var _checked = checked;
- for (var i = 0; i < residualsPerViewChart.count; ++i) {
- residualsPerViewChart.series(i).visible = _checked
- }
+ GridLayout {
+ anchors.fill: parent
+ columns: 2
+
+ // Residuals Per View chart
+ ColumnLayout {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+
+ RowLayout {
+ Layout.fillWidth: true
+ Layout.leftMargin: 10
+ ChartViewCheckBox {
+ text: "ALL"
+ color: textColor
+ leftPadding: 0
+ checkState: residualsPerViewLegend.buttonGroup.checkState
+ onClicked: residualsPerViewChart.setAllSeriesVisible(checked)
+ }
+ LineChartLegend {
+ id: residualsPerViewLegend
+ Layout.fillWidth: true
+ chartView: residualsPerViewChart
}
}
-
- ChartViewLegend {
- id: residualsPerViewLegend
- chartView: residualsPerViewChart
+ LineChart {
+ id: residualsPerViewChart
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ textColor: root.textColor
+ title: "Residuals Per View"
+ xAxisTitle: "Ordered Views"
+ yAxisTitle: "Reprojection Error (pix)"
+ xMin: 0
+ xMax: sfmDataStat.residualsPerViewMaxAxisX
+ yMin: 0
+ yMax: sfmDataStat.residualsPerViewMaxAxisY
}
-
}
- }
-
- InteractiveChartView {
- id: observationsLengthsPerViewChart
- width: parent.width * 0.5
- height: parent.height * 0.5
- anchors.top: parent.top
- anchors.topMargin: (parent.height) * 0.5
- title: "Observations Lengths Per View"
- legend.visible: false
- antialiasing: true
-
- ValueAxis {
- id: observationsLengthsPerViewValueAxisX
- labelFormat: "%i"
- titleText: "Ordered Views"
- min: 0
- max: sfmDataStat.observationsLengthsPerViewMaxAxisX
- }
- ValueAxis {
- id: observationsLengthsPerViewValueAxisY
- titleText: "Observations Lengths"
- min: 0
- max: sfmDataStat.observationsLengthsPerViewMaxAxisY
- tickAnchor: 0
- tickInterval: 0.50
- tickCount: sfmDataStat.observationsLengthsPerViewMaxAxisY * 2
- }
-
- LineSeries {
- id: observationsLengthsMinPerViewLineSerie
- axisX: observationsLengthsPerViewValueAxisX
- axisY: observationsLengthsPerViewValueAxisY
- name: "Min"
- }
- LineSeries {
- id: observationsLengthsMaxPerViewLineSerie
- axisX: observationsLengthsPerViewValueAxisX
- axisY: observationsLengthsPerViewValueAxisY
- name: "Max"
- }
- LineSeries {
- id: observationsLengthsMeanPerViewLineSerie
- axisX: observationsLengthsPerViewValueAxisX
- axisY: observationsLengthsPerViewValueAxisY
- name: "Mean"
- }
- LineSeries {
- id: observationsLengthsMedianPerViewLineSerie
- axisX: observationsLengthsPerViewValueAxisX
- axisY: observationsLengthsPerViewValueAxisY
- name: "Median"
- }
- LineSeries {
- id: observationsLengthsFirstQuartilePerViewLineSerie
- axisX: observationsLengthsPerViewValueAxisX
- axisY: observationsLengthsPerViewValueAxisY
- name: "Q1"
- }
- LineSeries {
- id: observationsLengthsThirdQuartilePerViewLineSerie
- axisX: observationsLengthsPerViewValueAxisX
- axisY: observationsLengthsPerViewValueAxisY
- name: "Q3"
- }
- }
-
- Item {
- id: observationsLengthsPerViewBtnContainer
-
- Layout.fillWidth: true
- anchors.bottom: observationsLengthsPerViewChart.bottom
- anchors.bottomMargin: 35
- anchors.left: observationsLengthsPerViewChart.left
- anchors.leftMargin: observationsLengthsPerViewChart.width * 0.25
-
- RowLayout {
- ChartViewCheckBox {
- id: allModes
- text: "ALL"
- color: textColor
- checkState: observationsLengthsPerViewLegend.buttonGroup.checkState
- onClicked: {
- var _checked = checked;
- for (var i = 0; i < observationsLengthsPerViewChart.count; ++i) {
- observationsLengthsPerViewChart.series(i).visible = _checked
- }
+ // Landmarks Per View chart
+ ColumnLayout {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+
+ RowLayout {
+ Layout.fillWidth: true
+ Layout.leftMargin: 10
+ ChartViewCheckBox {
+ text: "ALL"
+ color: textColor
+ leftPadding: 0
+ checkState: landmarksFeatTracksPerViewLegend.buttonGroup.checkState
+ onClicked: landmarksPerViewChart.setAllSeriesVisible(checked)
+ }
+ LineChartLegend {
+ id: landmarksFeatTracksPerViewLegend
+ Layout.fillWidth: true
+ chartView: landmarksPerViewChart
}
}
-
- ChartViewLegend {
- id: observationsLengthsPerViewLegend
- chartView: observationsLengthsPerViewChart
+ LineChart {
+ id: landmarksPerViewChart
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ textColor: root.textColor
+ title: "Landmarks Per View"
+ xAxisTitle: "Ordered Views"
+ yAxisTitle: "Number of Landmarks"
+ xMin: 0
+ xMax: sfmDataStat.landmarksPerViewMaxAxisX
+ yMin: 0
+ yMax: sfmDataStat.landmarksPerViewMaxAxisY
}
}
- }
-
- InteractiveChartView {
- id: landmarksPerViewChart
- width: parent.width * 0.5
- height: parent.height * 0.5
- anchors.left: parent.left
- anchors.leftMargin: (parent.width) * 0.5
- anchors.top: parent.top
-
- title: "Landmarks Per View"
- legend.visible: false
- antialiasing: true
- ValueAxis {
- id: landmarksPerViewValueAxisX
- titleText: "Ordered Views"
- min: 0.0
- max: sfmDataStat.landmarksPerViewMaxAxisX
- }
- ValueAxis {
- id: landmarksPerViewValueAxisY
- labelFormat: "%i"
- titleText: "Number of Landmarks"
- min: 0
- max: sfmDataStat.landmarksPerViewMaxAxisY
- }
- LineSeries {
- id: landmarksPerViewLineSerie
- axisX: landmarksPerViewValueAxisX
- axisY: landmarksPerViewValueAxisY
- name: "Landmarks"
- }
- LineSeries {
- id: tracksPerViewLineSerie
- axisX: landmarksPerViewValueAxisX
- axisY: landmarksPerViewValueAxisY
- name: "Tracks"
- }
- }
-
- Item {
- id: landmarksFeatTracksPerViewBtnContainer
-
- Layout.fillWidth: true
- anchors.bottom: landmarksPerViewChart.bottom
- anchors.bottomMargin: 35
- anchors.left: landmarksPerViewChart.left
- anchors.leftMargin: landmarksPerViewChart.width * 0.25
-
- RowLayout {
- ChartViewCheckBox {
- id: allFeatures
- text: "ALL"
- color: textColor
- checkState: landmarksFeatTracksPerViewLegend.buttonGroup.checkState
- onClicked: {
- var _checked = checked;
- for (var i = 0; i < landmarksPerViewChart.count; ++i) {
- landmarksPerViewChart.series(i).visible = _checked
- }
+ // Observations Lengths Per View chart
+ ColumnLayout {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+
+ RowLayout {
+ Layout.fillWidth: true
+ Layout.leftMargin: 10
+ ChartViewCheckBox {
+ text: "ALL"
+ color: textColor
+ leftPadding: 0
+ checkState: observationsLengthsPerViewLegend.buttonGroup.checkState
+ onClicked: observationsLengthsPerViewChart.setAllSeriesVisible(checked)
+ }
+ LineChartLegend {
+ id: observationsLengthsPerViewLegend
+ Layout.fillWidth: true
+ chartView: observationsLengthsPerViewChart
}
}
-
- ChartViewLegend {
- id: landmarksFeatTracksPerViewLegend
- chartView: landmarksPerViewChart
+ LineChart {
+ id: observationsLengthsPerViewChart
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ textColor: root.textColor
+ title: "Observations Lengths Per View"
+ xAxisTitle: "Ordered Views"
+ yAxisTitle: "Observations Lengths"
+ xMin: 0
+ xMax: sfmDataStat.observationsLengthsPerViewMaxAxisX
+ yMin: 0
+ yMax: sfmDataStat.observationsLengthsPerViewMaxAxisY
}
}
+
+ // (empty fourth cell)
+ Item { Layout.fillWidth: true; Layout.fillHeight: true }
}
// Stats from the sfmData
@@ -295,20 +153,26 @@ FloatingPane {
mTracks: root.mTracks
onAxisChanged: {
- fillLandmarksPerViewSerie(landmarksPerViewLineSerie)
- fillTracksPerViewSerie(tracksPerViewLineSerie)
- fillResidualsMinPerViewSerie(residualsMinPerViewLineSerie)
- fillResidualsMaxPerViewSerie(residualsMaxPerViewLineSerie)
- fillResidualsMeanPerViewSerie(residualsMeanPerViewLineSerie)
- fillResidualsMedianPerViewSerie(residualsMedianPerViewLineSerie)
- fillResidualsFirstQuartilePerViewSerie(residualsFirstQuartilePerViewLineSerie)
- fillResidualsThirdQuartilePerViewSerie(residualsThirdQuartilePerViewLineSerie)
- fillObservationsLengthsMinPerViewSerie(observationsLengthsMinPerViewLineSerie)
- fillObservationsLengthsMaxPerViewSerie(observationsLengthsMaxPerViewLineSerie)
- fillObservationsLengthsMeanPerViewSerie(observationsLengthsMeanPerViewLineSerie)
- fillObservationsLengthsMedianPerViewSerie(observationsLengthsMedianPerViewLineSerie)
- fillObservationsLengthsFirstQuartilePerViewSerie(observationsLengthsFirstQuartilePerViewLineSerie)
- fillObservationsLengthsThirdQuartilePerViewSerie(observationsLengthsThirdQuartilePerViewLineSerie)
+ landmarksPerViewChart.removeAllSeries()
+ landmarksPerViewChart.addSeries("Landmarks", root.statColors[0], sfmDataStat.getLandmarksPerViewPoints())
+ landmarksPerViewChart.addSeries("Tracks", root.statColors[1], sfmDataStat.getTracksPerViewPoints())
+
+ residualsPerViewChart.removeAllSeries()
+ residualsPerViewChart.addSeries("Min", root.statColors[0], sfmDataStat.getResidualsMinPerViewPoints())
+ residualsPerViewChart.addSeries("Max", root.statColors[1], sfmDataStat.getResidualsMaxPerViewPoints())
+ residualsPerViewChart.addSeries("Mean", root.statColors[2], sfmDataStat.getResidualsMeanPerViewPoints())
+ residualsPerViewChart.addSeries("Median", root.statColors[3], sfmDataStat.getResidualsMedianPerViewPoints())
+ residualsPerViewChart.addSeries("Q1", root.statColors[4], sfmDataStat.getResidualsFirstQuartilePerViewPoints())
+ residualsPerViewChart.addSeries("Q3", root.statColors[5], sfmDataStat.getResidualsThirdQuartilePerViewPoints())
+
+ observationsLengthsPerViewChart.removeAllSeries()
+ observationsLengthsPerViewChart.addSeries("Min", root.statColors[0], sfmDataStat.getObservationsLengthsMinPerViewPoints())
+ observationsLengthsPerViewChart.addSeries("Max", root.statColors[1], sfmDataStat.getObservationsLengthsMaxPerViewPoints())
+ observationsLengthsPerViewChart.addSeries("Mean", root.statColors[2], sfmDataStat.getObservationsLengthsMeanPerViewPoints())
+ observationsLengthsPerViewChart.addSeries("Median", root.statColors[3], sfmDataStat.getObservationsLengthsMedianPerViewPoints())
+ observationsLengthsPerViewChart.addSeries("Q1", root.statColors[4], sfmDataStat.getObservationsLengthsFirstQuartilePerViewPoints())
+ observationsLengthsPerViewChart.addSeries("Q3", root.statColors[5], sfmDataStat.getObservationsLengthsThirdQuartilePerViewPoints())
}
}
}
+
diff --git a/meshroom/ui/qml/Viewer/SfmStatsView.qml b/meshroom/ui/qml/Viewer/SfmStatsView.qml
index 859da48900..a2d9e46156 100644
--- a/meshroom/ui/qml/Viewer/SfmStatsView.qml
+++ b/meshroom/ui/qml/Viewer/SfmStatsView.qml
@@ -1,4 +1,3 @@
-import QtCharts
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
@@ -29,209 +28,120 @@ FloatingPane {
onWheel: {}
}
- InteractiveChartView {
- id: residualChart
- width: parent.width * 0.5
- height: parent.height * 0.5
-
- title: "Reprojection Errors"
- legend.visible: false
- antialiasing: true
-
- ValueAxis {
- id: residualValueAxisX
- titleText: "Reprojection Error"
- min: 0.0
- max: viewStat.residualMaxAxisX
- }
- ValueAxis {
- id: residualValueAxisY
- labelFormat: "%i"
- titleText: "Number of Points"
- min: 0
- max: viewStat.residualMaxAxisY
- }
- LineSeries {
- id: residualFullLineSerie
- axisX: residualValueAxisX
- axisY: residualValueAxisY
- name: "Average on All Cameras"
- }
- LineSeries {
- id: residualViewLineSerie
- axisX: residualValueAxisX
- axisY: residualValueAxisY
- name: "Current"
- }
- }
-
- Item {
- id: residualBtnContainer
-
- Layout.fillWidth: true
- anchors.bottom: residualChart.bottom
- anchors.bottomMargin: 35
- anchors.left: residualChart.left
- anchors.leftMargin: residualChart.width * 0.15
-
- RowLayout {
-
- ChartViewCheckBox {
- id: allResiduals
- text: "ALL"
- color: textColor
- checkState: residualLegend.buttonGroup.checkState
- onClicked: {
- var _checked = checked;
- for (var i = 0; i < residualChart.count; ++i) {
- residualChart.series(i).visible = _checked
- }
+ GridLayout {
+ anchors.fill: parent
+ columns: 2
+
+ // Reprojection Errors chart
+ ColumnLayout {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+
+ RowLayout {
+ Layout.fillWidth: true
+ Layout.leftMargin: 10
+ ChartViewCheckBox {
+ text: "ALL"
+ color: textColor
+ leftPadding: 0
+ checkState: residualLegend.buttonGroup.checkState
+ onClicked: residualChart.setAllSeriesVisible(checked)
+ }
+ LineChartLegend {
+ id: residualLegend
+ Layout.fillWidth: true
+ chartView: residualChart
}
}
-
- ChartViewLegend {
- id: residualLegend
- chartView: residualChart
+ LineChart {
+ id: residualChart
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ textColor: root.textColor
+ title: "Reprojection Errors"
+ xAxisTitle: "Reprojection Error"
+ yAxisTitle: "Number of Points"
+ xMin: 0
+ xMax: viewStat.residualMaxAxisX
+ yMin: 0
+ yMax: viewStat.residualMaxAxisY
}
}
- }
- InteractiveChartView {
- id: observationsLengthsChart
- width: parent.width * 0.5
- height: parent.height * 0.5
- anchors.top: parent.top
- anchors.topMargin: (parent.height) * 0.5
-
- legend.visible: false
- title: "Observations Lengths"
-
- ValueAxis {
- id: observationsLengthsvalueAxisX
- labelFormat: "%i"
- titleText: "Observations Length"
- min: 2
- max: viewStat.observationsLengthsMaxAxisX
- tickAnchor: 2
- tickInterval: 1
- tickCount: 5
- }
- ValueAxis {
- id: observationsLengthsvalueAxisY
- labelFormat: "%i"
- titleText: "Number of Points"
- min: 0
- max: viewStat.observationsLengthsMaxAxisY
- }
- LineSeries {
- id: observationsLengthsFullLineSerie
- axisX: observationsLengthsvalueAxisX
- axisY: observationsLengthsvalueAxisY
- name: "All Cameras"
- }
- LineSeries {
- id: observationsLengthsViewLineSerie
- axisX: observationsLengthsvalueAxisX
- axisY: observationsLengthsvalueAxisY
- name: "Current"
- }
- }
-
- Item {
- id: observationsLengthsBtnContainer
-
- Layout.fillWidth: true
- anchors.bottom: observationsLengthsChart.bottom
- anchors.bottomMargin: 35
- anchors.left: observationsLengthsChart.left
- anchors.leftMargin: observationsLengthsChart.width * 0.25
-
- RowLayout {
- ChartViewCheckBox {
- id: allObservations
- text: "ALL"
- color: textColor
- checkState: observationsLengthsLegend.buttonGroup.checkState
- onClicked: {
- var _checked = checked;
- for (var i = 0; i < observationsLengthsChart.count; ++i) {
- observationsLengthsChart.series(i).visible = _checked
- }
+ // Observations Scale chart
+ ColumnLayout {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+
+ RowLayout {
+ Layout.fillWidth: true
+ Layout.leftMargin: 10
+ ChartViewCheckBox {
+ text: "ALL"
+ color: textColor
+ leftPadding: 0
+ checkState: observationsScaleLegend.buttonGroup.checkState
+ onClicked: observationsScaleChart.setAllSeriesVisible(checked)
+ }
+ LineChartLegend {
+ id: observationsScaleLegend
+ Layout.fillWidth: true
+ chartView: observationsScaleChart
}
}
-
- ChartViewLegend {
- id: observationsLengthsLegend
- chartView: observationsLengthsChart
+ LineChart {
+ id: observationsScaleChart
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ textColor: root.textColor
+ title: "Observations Scale"
+ xAxisTitle: "Scale"
+ yAxisTitle: "Number of Points"
+ xMin: 0
+ xMax: viewStat.observationsScaleMaxAxisX
+ yMin: 0
+ yMax: viewStat.observationsScaleMaxAxisY
}
}
- }
-
- InteractiveChartView {
- id: observationsScaleChart
- width: parent.width * 0.5
- height: parent.height * 0.5
- anchors.left: parent.left
- anchors.leftMargin: (parent.width) * 0.5
- anchors.top: parent.top
-
- legend.visible: false
- title: "Observations Scale"
-
- ValueAxis {
- id: observationsScaleValueAxisX
- titleText: "Scale"
- min: 0
- max: viewStat.observationsScaleMaxAxisX
- }
- ValueAxis {
- id: observationsScaleValueAxisY
- titleText: "Number of Points"
- min: 0
- max: viewStat.observationsScaleMaxAxisY
- }
- LineSeries {
- id: observationsScaleFullLineSerie
- axisX: observationsScaleValueAxisX
- axisY: observationsScaleValueAxisY
- name: " Average on All Cameras"
- }
- LineSeries {
- id: observationsScaleViewLineSerie
- axisX: observationsScaleValueAxisX
- axisY: observationsScaleValueAxisY
- name: "Current"
- }
- }
- Item {
- id: observationsScaleBtnContainer
-
- Layout.fillWidth: true
- anchors.bottom: observationsScaleChart.bottom
- anchors.bottomMargin: 35
- anchors.left: observationsScaleChart.left
- anchors.leftMargin: observationsScaleChart.width * 0.15
-
- RowLayout {
- ChartViewCheckBox {
- id: allObservationsScales
- text: "ALL"
- color: textColor
- checkState: observationsScaleLegend.buttonGroup.checkState
- onClicked: {
- var _checked = checked;
- for (var i = 0; i < observationsScaleChart.count; ++i) {
- observationsScaleChart.series(i).visible = _checked
- }
+ // Observations Lengths chart
+ ColumnLayout {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+
+ RowLayout {
+ Layout.fillWidth: true
+ Layout.leftMargin: 10
+ ChartViewCheckBox {
+ text: "ALL"
+ color: textColor
+ leftPadding: 0
+ checkState: observationsLengthsLegend.buttonGroup.checkState
+ onClicked: observationsLengthsChart.setAllSeriesVisible(checked)
+ }
+ LineChartLegend {
+ id: observationsLengthsLegend
+ Layout.fillWidth: true
+ chartView: observationsLengthsChart
}
}
-
- ChartViewLegend {
- id: observationsScaleLegend
- chartView: observationsScaleChart
+ LineChart {
+ id: observationsLengthsChart
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ textColor: root.textColor
+ title: "Observations Lengths"
+ xAxisTitle: "Observations Length"
+ yAxisTitle: "Number of Points"
+ xMin: 2
+ xMax: viewStat.observationsLengthsMaxAxisX
+ yMin: 0
+ yMax: viewStat.observationsLengthsMaxAxisY
}
}
+
+ // (empty fourth cell)
+ Item { Layout.fillWidth: true; Layout.fillHeight: true }
}
// Stats from a view the sfmData
@@ -240,12 +150,18 @@ FloatingPane {
msfmData: (root.visible && root.msfmData && root.msfmData.status === AliceVision.MSfMData.Ready) ? root.msfmData : null
viewId: root.viewId
onViewStatsChanged: {
- fillResidualFullSerie(residualFullLineSerie)
- fillResidualViewSerie(residualViewLineSerie)
- fillObservationsLengthsFullSerie(observationsLengthsFullLineSerie)
- fillObservationsLengthsViewSerie(observationsLengthsViewLineSerie)
- fillObservationsScaleFullSerie(observationsScaleFullLineSerie)
- fillObservationsScaleViewSerie(observationsScaleViewLineSerie)
+ residualChart.removeAllSeries()
+ residualChart.addSeries("Average on All Cameras", "#dc143c", viewStat.getResidualFullPoints())
+ residualChart.addSeries("Current", "#00008b", viewStat.getResidualViewPoints())
+
+ observationsLengthsChart.removeAllSeries()
+ observationsLengthsChart.addSeries("All Cameras", "#dc143c", viewStat.getObservationsLengthsFullPoints())
+ observationsLengthsChart.addSeries("Current", "#00008b", viewStat.getObservationsLengthsViewPoints())
+
+ observationsScaleChart.removeAllSeries()
+ observationsScaleChart.addSeries("Average on All Cameras", "#dc143c", viewStat.getObservationsScaleFullPoints())
+ observationsScaleChart.addSeries("Current", "#00008b", viewStat.getObservationsScaleViewPoints())
}
}
}
+
From 7ff014b47b74dd4c492fbd67b2188a6cf4d91b3e Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 20 Mar 2026 18:42:41 +0000
Subject: [PATCH 6/6] Remove remaining QtCharts files, qmldir entries, and
Docker addon references
Co-authored-by: fabiencastan <153585+fabiencastan@users.noreply.github.com>
Agent-Logs-Url: https://github.com/alicevision/Meshroom/sessions/d501c84e-b9f6-417c-a83a-b092bb236c2b
---
docker/Dockerfile_rocky_deps | 2 +-
docker/Dockerfile_ubuntu_deps | 2 +-
meshroom/ui/qml/Charts/ChartViewLegend.qml | 103 ------------------
.../ui/qml/Charts/InteractiveChartView.qml | 27 -----
meshroom/ui/qml/Charts/qmldir | 2 -
5 files changed, 2 insertions(+), 134 deletions(-)
delete mode 100644 meshroom/ui/qml/Charts/ChartViewLegend.qml
delete mode 100644 meshroom/ui/qml/Charts/InteractiveChartView.qml
diff --git a/docker/Dockerfile_rocky_deps b/docker/Dockerfile_rocky_deps
index 4a456eec0c..d3ece99c5f 100644
--- a/docker/Dockerfile_rocky_deps
+++ b/docker/Dockerfile_rocky_deps
@@ -34,7 +34,7 @@ RUN chmod +x qt.run
RUN ./qt.run --root /opt/Qt --verbose --email ${QT_CI_LOGIN} --password ${QT_CI_P} --accept-obligations \
--accept-licenses --default-answer --platform minimal --auto-answer installationErrorWithCancel=Ignore \
--no-force-installations --no-default-installations --confirm-command \
- install qt.qt6.683.linux_gcc_64 qt.qt6.683.addons.qtcharts qt.qt6.683.addons.qt3d
+ install qt.qt6.683.linux_gcc_64 qt.qt6.683.addons.qt3d
RUN rm qt.run
# Strip sections containing ".note.ABI.tag" from .so: https://github.com/Microsoft/WSL/issues/3023
diff --git a/docker/Dockerfile_ubuntu_deps b/docker/Dockerfile_ubuntu_deps
index 77193f7813..4aac10a112 100644
--- a/docker/Dockerfile_ubuntu_deps
+++ b/docker/Dockerfile_ubuntu_deps
@@ -63,7 +63,7 @@ RUN chmod +x qt.run
RUN ./qt.run --root /opt/Qt --verbose --email ${QT_CI_LOGIN} --password ${QT_CI_P} --accept-obligations \
--accept-licenses --default-answer --platform minimal --auto-answer installationErrorWithCancel=Ignore \
--no-force-installations --no-default-installations --confirm-command \
- install qt.qt6.683.linux_gcc_64 qt.qt6.683.addons.qtcharts qt.qt6.683.addons.qt3d
+ install qt.qt6.683.linux_gcc_64 qt.qt6.683.addons.qt3d
RUN rm qt.run
# Strip sections containing ".note.ABI.tag" from .so: https://github.com/Microsoft/WSL/issues/3023
diff --git a/meshroom/ui/qml/Charts/ChartViewLegend.qml b/meshroom/ui/qml/Charts/ChartViewLegend.qml
deleted file mode 100644
index 576c635343..0000000000
--- a/meshroom/ui/qml/Charts/ChartViewLegend.qml
+++ /dev/null
@@ -1,103 +0,0 @@
-import QtQuick
-import QtQuick.Controls
-import QtCharts
-
-/**
- * ChartViewLegend is an interactive legend component for ChartViews.
- * It provides a CheckBox for each series that can control its visibility,
- * and highlight on hovering.
- */
-
-Flow {
- id: root
-
- // The ChartView to create the legend for
- property ChartView chartView
- // Currently hovered series
- property var hoveredSeries: null
-
- readonly property ButtonGroup buttonGroup: ButtonGroup {
- id: legendGroup
- exclusive: false
- }
-
- /// Shortcut function to clear legend
- function clear() {
- seriesModel.clear()
- }
-
- // Update internal ListModel when ChartView's series change
- Connections {
- target: chartView
- function onSeriesAdded(series) {
- seriesModel.append({"series": series})
- }
- function onSeriesRemoved(series) {
- for (var i = 0; i < seriesModel.count; ++i) {
- if (seriesModel.get(i)["series"] === series) {
- seriesModel.remove(i)
- return
- }
- }
- }
- }
-
- onChartViewChanged: {
- clear()
- for (var i = 0; i < chartView.count; ++i)
- seriesModel.append({"series": chartView.series(i)})
- }
-
- Repeater {
- // ChartView series cannot be accessed directly as a model.
- // Use an intermediate ListModel populated with those series.
- model: ListModel {
- id: seriesModel
- }
-
- ChartViewCheckBox {
- ButtonGroup.group: legendGroup
-
- checked: series.visible
- text: series.name
- color: series.color
-
- onHoveredChanged: {
- if (hovered && series.visible)
- root.hoveredSeries = series
- else
- root.hoveredSeries = null
- }
-
- // Hovered serie properties override
- states: [
- State {
- when: series && root.hoveredSeries === series
- PropertyChanges { target: series; width: 5.0 }
- },
- State {
- when: series && root.hoveredSeries && root.hoveredSeries !== series
- PropertyChanges { target: series; width: 0.2 }
- }
- ]
-
- MouseArea {
- anchors.fill: parent
- onClicked: function(mouse) {
- if (mouse.modifiers & Qt.ControlModifier)
- root.soloSeries(index)
- else
- series.visible = !series.visible
- }
- }
- }
- }
-
- /// Hide all series but the one at index 'idx'
- function soloSeries(idx) {
- for (var i = 0; i < seriesModel.count; i++) {
- chartView.series(i).visible = false
- }
- chartView.series(idx).visible = true
- }
-}
diff --git a/meshroom/ui/qml/Charts/InteractiveChartView.qml b/meshroom/ui/qml/Charts/InteractiveChartView.qml
deleted file mode 100644
index 2434c0cbe3..0000000000
--- a/meshroom/ui/qml/Charts/InteractiveChartView.qml
+++ /dev/null
@@ -1,27 +0,0 @@
-import QtQuick
-import QtQuick.Layouts
-import QtCharts
-
-ChartView {
- id: root
- antialiasing: true
-
- Rectangle {
- id: plotZone
- x: root.plotArea.x
- y: root.plotArea.y
- width: root.plotArea.width
- height: root.plotArea.height
- color: "transparent"
-
- MouseArea {
- anchors.fill: parent
-
- property double degreeToScale: 1.0 / 120.0 // Default mouse scroll is 15 degree
- acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
- onClicked: {
- root.zoomReset()
- }
- }
- }
-}
diff --git a/meshroom/ui/qml/Charts/qmldir b/meshroom/ui/qml/Charts/qmldir
index e02eabbfe0..054e25e644 100644
--- a/meshroom/ui/qml/Charts/qmldir
+++ b/meshroom/ui/qml/Charts/qmldir
@@ -1,7 +1,5 @@
module Charts
-ChartViewLegend 1.0 ChartViewLegend.qml
ChartViewCheckBox 1.0 ChartViewCheckBox.qml
-InteractiveChartView 1.0 InteractiveChartView.qml
LineChart 1.0 LineChart.qml
LineChartLegend 1.0 LineChartLegend.qml