import QtQuick import qs.Commons Item { id: root property color fillColor: Color.mPrimary property color strokeColor: Color.mOnSurface property int strokeWidth: 0 property var values: [] property bool vertical: false // Minimum signal properties property bool showMinimumSignal: false property real minimumSignalValue: 0.05 // Default to 5% of height // Rendering active state - only redraw when visible and values are changing property bool renderingActive: visible && values && values.length > 0 // Redraw when necessary - only if rendering is active onWidthChanged: if (renderingActive) canvas.requestPaint() onHeightChanged: if (renderingActive) canvas.requestPaint() onValuesChanged: if (renderingActive) canvas.requestPaint() onFillColorChanged: if (renderingActive) canvas.requestPaint() onStrokeColorChanged: if (renderingActive) canvas.requestPaint() onShowMinimumSignalChanged: if (renderingActive) canvas.requestPaint() onMinimumSignalValueChanged: if (renderingActive) canvas.requestPaint() onVerticalChanged: if (renderingActive) canvas.requestPaint() // Clear canvas when not rendering onRenderingActiveChanged: { if (!renderingActive) { var ctx = canvas.getContext("2d"); if (ctx) ctx.reset(); canvas.requestPaint(); } } Canvas { id: canvas anchors.fill: parent antialiasing: false // Disable for better performance - shape is smooth enough without it renderStrategy: Canvas.Threaded // Render in separate thread to reduce main thread load renderTarget: Canvas.FramebufferObject // Use FBO for better performance onPaint: { var ctx = getContext("2d"); ctx.reset(); if (!values || !Array.isArray(values) || values.length === 0) { return; } // Apply minimum signal if enabled var processedValues = values.map(function (v) { return (root.showMinimumSignal && v === 0) ? root.minimumSignalValue : v; }); // Create the mirrored values const partToMirror = processedValues.slice(1).reverse(); const mirroredValues = partToMirror.concat(processedValues); if (mirroredValues.length < 2) { return; } ctx.fillStyle = root.fillColor; ctx.strokeStyle = root.strokeColor; ctx.lineWidth = root.strokeWidth; const count = mirroredValues.length; if (root.vertical) { // Vertical orientation const stepY = height / (count - 1); const centerX = width / 2; const amplitude = width / 2; ctx.beginPath(); // Draw the left half of the waveform from top to bottom var xOffset = mirroredValues[0] * amplitude; ctx.moveTo(centerX - xOffset, 0); for (var i = 1; i < count; i++) { const y = i * stepY; xOffset = mirroredValues[i] * amplitude; const x = centerX - xOffset; ctx.lineTo(x, y); } // Draw the right half of the waveform from bottom to top to create a closed shape for (var i = count - 1; i >= 0; i--) { const y = i * stepY; xOffset = mirroredValues[i] * amplitude; const x = centerX + xOffset; // Mirrored across the center ctx.lineTo(x, y); } ctx.closePath(); } else { // Horizontal orientation const stepX = width / (count - 1); const centerY = height / 2; const amplitude = height / 2; ctx.beginPath(); // Draw the top half of the waveform from left to right var yOffset = mirroredValues[0] * amplitude; ctx.moveTo(0, centerY - yOffset); for (var i = 1; i < count; i++) { const x = i * stepX; yOffset = mirroredValues[i] * amplitude; const y = centerY - yOffset; ctx.lineTo(x, y); } // Draw the bottom half of the waveform from right to left to create a closed shape for (var i = count - 1; i >= 0; i--) { const x = i * stepX; yOffset = mirroredValues[i] * amplitude; const y = centerY + yOffset; // Mirrored across the center ctx.lineTo(x, y); } ctx.closePath(); } // --- Render the path --- if (root.fillColor.a > 0) { ctx.fill(); } if (root.strokeWidth > 0) { ctx.stroke(); } } } }