From e2e15d8718c994074a6240521701f5dcd4c41415 Mon Sep 17 00:00:00 2001 From: Ankur Juneja Date: Wed, 2 Sep 2026 10:00:16 -0700 Subject: [PATCH] Fix black rectangles in QC plot SVG and PDF exports (#1276) --- webapp/TargetedMS/css/qcTrendPlotReport.css | 5 +++++ webapp/TargetedMS/js/QCPlotHelperBase.js | 8 ++++---- webapp/TargetedMS/js/QCTrendPlotPanel.js | 6 +++--- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/webapp/TargetedMS/css/qcTrendPlotReport.css b/webapp/TargetedMS/css/qcTrendPlotReport.css index 2efcc589a..559674d9a 100644 --- a/webapp/TargetedMS/css/qcTrendPlotReport.css +++ b/webapp/TargetedMS/css/qcTrendPlotReport.css @@ -118,10 +118,15 @@ background-color: #f0f0f0; } +/* Cursors stay in CSS: Batik rejects zoom-in/zoom-out in an inline style when exporting the SVG. */ .y-zoom-overlay { cursor: zoom-in; } +.y-zoom-overlay.zoomed { + cursor: zoom-out; +} + .y-zoom-pending-line { stroke: rgba(20, 204, 201, 1); stroke-width: 2px; diff --git a/webapp/TargetedMS/js/QCPlotHelperBase.js b/webapp/TargetedMS/js/QCPlotHelperBase.js index b701e87fa..03cec5bb3 100644 --- a/webapp/TargetedMS/js/QCPlotHelperBase.js +++ b/webapp/TargetedMS/js/QCPlotHelperBase.js @@ -1145,12 +1145,12 @@ Ext4.define("LABKEY.targetedms.QCPlotHelperBase", { let isZoomed = !!(zoomEntry && zoomEntry[axis]); let overlayEl = svg.append('rect') - .attr('class', 'y-zoom-overlay') + .attr('class', 'y-zoom-overlay' + (isZoomed ? ' zoomed' : '')) .attr('x', overlayX) .attr('y', gridTop) .attr('width', overlayW) .attr('height', gridBottom - gridTop) - .style({'fill': 'transparent', 'cursor': isZoomed ? 'zoom-out' : 'zoom-in'}); + .style({'fill': '#ffffff', 'fill-opacity': 0, 'pointer-events': 'all'}); if (isZoomed) { overlayEl.on('click', function() { me.resetYZoom(plotId, axis); }); @@ -1181,7 +1181,7 @@ Ext4.define("LABKEY.targetedms.QCPlotHelperBase", { .attr('x', 0).attr('y', 0) .attr('width', parseFloat(svg.attr('width')) || (gridRight + 80)) .attr('height', parseFloat(svg.attr('height')) || (gridBottom + 50)) - .style({'fill': 'transparent', 'pointer-events': 'all', 'cursor': 'default'}); + .style({'fill': '#ffffff', 'fill-opacity': 0, 'pointer-events': 'all', 'cursor': 'default'}); zoomButtonGroup = svg.append('g').attr('class', 'y-zoom-buttons'); @@ -1254,7 +1254,7 @@ Ext4.define("LABKEY.targetedms.QCPlotHelperBase", { plotClickCapture = svg.append('rect') .attr('x', gridLeft).attr('y', gridTop) .attr('width', gridRight - gridLeft).attr('height', gridBottom - gridTop) - .style({'fill': 'transparent', 'cursor': 'crosshair'}) + .style({'fill': '#ffffff', 'fill-opacity': 0, 'pointer-events': 'all', 'cursor': 'crosshair'}) .on('click', function() { let clickY = clampY(d3.mouse(svg.node())[1]); let firstY = pendingStartY; diff --git a/webapp/TargetedMS/js/QCTrendPlotPanel.js b/webapp/TargetedMS/js/QCTrendPlotPanel.js index d13f0695c..5a6783342 100644 --- a/webapp/TargetedMS/js/QCTrendPlotPanel.js +++ b/webapp/TargetedMS/js/QCTrendPlotPanel.js @@ -1639,7 +1639,7 @@ Ext4.define('LABKEY.targetedms.QCTrendPlotPanel', { if (!d || !d.name || d.separator) return; let isHidden = !!(hidden[d.hoverText || d.name.split(LABKEY.targetedms.QCPlotHelperBase.SERIES_NAME_SEP)[0]]); let item = d3.select(this); - item.select('path').attr('fill', isHidden ? 'transparent' : (d.color || null)); + item.select('path').attr('fill', d.color || null).attr('fill-opacity', isHidden ? 0 : null); item.select('text').attr('opacity', isHidden ? 0.3 : 1); item.select('.legend-check').remove(); if (!isHidden) { @@ -1688,7 +1688,7 @@ Ext4.define('LABKEY.targetedms.QCTrendPlotPanel', { let isHidden = !!hidden[frag]; let isActive = d.name.indexOf(fragment + (hasYRightMetric ? LABKEY.targetedms.QCPlotHelperBase.SERIES_NAME_SEP : '')) === 0; let item = d3.select(this); - item.select('path').attr('fill', isHidden ? 'transparent' : (d.color || null)); + item.select('path').attr('fill', d.color || null).attr('fill-opacity', isHidden ? 0 : null); item.select('text').attr('opacity', isHidden ? 0.1 : (isActive ? 1 : 0.1)); }); @@ -1779,7 +1779,7 @@ Ext4.define('LABKEY.targetedms.QCTrendPlotPanel', { if (!d || !d.name || d.separator) return; let isHidden = !!hidden[d.hoverText || d.name.split(LABKEY.targetedms.QCPlotHelperBase.SERIES_NAME_SEP)[0]]; let item = d3.select(this); - item.select('path').attr('fill', isHidden ? 'transparent' : (d.color || null)); + item.select('path').attr('fill', d.color || null).attr('fill-opacity', isHidden ? 0 : null); item.select('text').attr('opacity', isHidden ? 0.3 : 1); item.select('.legend-check').remove(); if (!isHidden) {