feat: replay chart with zoomable viewpor

This commit is contained in:
jack
2026-07-10 20:54:24 -07:00
parent 4bb855a940
commit fed4d9b590
5 changed files with 645 additions and 22 deletions
+20
View File
@@ -182,6 +182,26 @@ Item {
}
}
Rectangle {
id: replayChartPane
Layout.fillWidth: true
Layout.fillHeight: false
Layout.preferredHeight: 260
Layout.minimumHeight: 180
visible: streamController.mode !== "live" && streamController.loadedFile !== ""
color: Theme.cardBackground
border.color: Theme.cardBorder
border.width: 1
radius: Theme.radiusMd
ReplayChart {
anchors.fill: parent
anchors.margins: 8
sensorNames: streamController.graphSensorNames ? streamController.graphSensorNames.split(",") : []
seriesData: JSON.parse(streamController.graphSeriesJson || "[]")
}
}
Item {
Layout.fillWidth: true
visible: transportBar.hasContent
@@ -0,0 +1,136 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import ISC
import ISC.Wafer
// ===== Replay Chart =====
// Zoomable whole-run multi-sensor chart (C# parity: PopupChartForm.cs).
// Embedded inline in WaferMapTab's replay slot rather than a separate popup
// window. Mouse wheel zooms around the cursor, drag pans; the strip below
// shows the aggregate min/max/avg over the *visible* range (not the whole
// run), matching PopupChartForm's recalc_stats. Cursor/playhead tracking is
// out of scope — see specs/plans/2026-07-10-popup-chart.md and
// docs/adr/0004-graphquickitem-viewport-for-replay-chart.md.
Item {
id: root
property alias sensorNames: chart.sensorNames
property alias seriesData: chart.seriesData
ColumnLayout {
anchors.fill: parent
spacing: 8
Item {
Layout.fillWidth: true
Layout.fillHeight: true
GraphQuickItem {
id: chart
anchors.fill: parent
showMinMaxMarkers: true
title: "Replay Chart"
xLabel: "Measurement Interval"
yLabel: "Temperature (°C)"
}
MouseArea {
id: interaction
anchors.fill: parent
acceptedButtons: Qt.LeftButton
property real lastX: 0
onWheel: function(wheel) {
var frac = Math.max(0, Math.min(1, wheel.x / width));
var factor = wheel.angleDelta.y > 0 ? 0.8 : 1.25;
chart.zoomAtFraction(frac, factor);
wheel.accepted = true;
}
onPressed: function(mouse) {
lastX = mouse.x;
}
onPositionChanged: function(mouse) {
if (pressed && width > 0) {
chart.panByFraction(-(mouse.x - lastX) / width);
lastX = mouse.x;
}
}
}
}
// ── Min/Max/Avg readout strip + Reset Zoom ─────────────────────
RowLayout {
Layout.fillWidth: true
spacing: 16
Label {
text: "Min:"
color: Theme.sensorLow
font.family: Theme.uiFontFamily
font.pixelSize: Theme.fontSm
font.weight: Font.Medium
}
Label {
text: chart.viewMin.toFixed(2) + (chart.viewMinSensor ? " (" + chart.viewMinSensor + ")" : "")
color: Theme.headingColor
font.family: Theme.codeFontFamily
font.pixelSize: Theme.fontSm
}
Label {
text: "Max:"
color: Theme.sensorHigh
font.family: Theme.uiFontFamily
font.pixelSize: Theme.fontSm
font.weight: Font.Medium
}
Label {
text: chart.viewMax.toFixed(2) + (chart.viewMaxSensor ? " (" + chart.viewMaxSensor + ")" : "")
color: Theme.headingColor
font.family: Theme.codeFontFamily
font.pixelSize: Theme.fontSm
}
Label {
text: "Avg:"
color: Theme.sideMutedText
font.family: Theme.uiFontFamily
font.pixelSize: Theme.fontSm
font.weight: Font.Medium
}
Label {
text: chart.viewAvg.toFixed(2)
color: Theme.headingColor
font.family: Theme.codeFontFamily
font.pixelSize: Theme.fontSm
}
Item { Layout.fillWidth: true }
Button {
id: resetZoomBtn
implicitHeight: 32
hoverEnabled: true
text: "Reset Zoom"
font.family: Theme.uiFontFamily
font.pixelSize: Theme.fontSm
font.weight: Font.Medium
background: Rectangle {
radius: Theme.radiusSm
color: resetZoomBtn.pressed ? Theme.transportButtonHover : Theme.transportButtonBg
border.color: Theme.sideBorder
border.width: 1
}
contentItem: Text {
text: resetZoomBtn.text
color: Theme.headingColor
font: resetZoomBtn.font
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
onClicked: chart.resetZoom()
}
}
}
}
+2 -1
View File
@@ -22,4 +22,5 @@ PanelSlider 1.0 PanelSlider.qml
EditableScrubStat 1.0 EditableScrubStat.qml
ComparisonChartCard 1.0 ComparisonChartCard.qml
OverlapMapCard 1.0 OverlapMapCard.qml
ComparisonSidePanel 1.0 ComparisonSidePanel.qml
ComparisonSidePanel 1.0 ComparisonSidePanel.qml
ReplayChart 1.0 ReplayChart.qml