From 8c707b2009972cc160d268b8c4b85842578b12c3 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Wed, 20 Nov 2024 17:35:38 -0500 Subject: [PATCH 1/7] set key function for layout images --- src/components/images/draw.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/images/draw.js b/src/components/images/draw.js index 7b59ef1d850..1c52dfea11c 100644 --- a/src/components/images/draw.js +++ b/src/components/images/draw.js @@ -203,10 +203,14 @@ module.exports = function draw(gd) { ); } + function imgDataFunc(d) { + return [d.xref, d.x, d.sizex, d.yref, d.y, d.sizey].join('_'); + } + var imagesBelow = fullLayout._imageLowerLayer.selectAll('image') - .data(imageDataBelow); + .data(imageDataBelow, imgDataFunc); var imagesAbove = fullLayout._imageUpperLayer.selectAll('image') - .data(imageDataAbove); + .data(imageDataAbove, imgDataFunc); imagesBelow.enter().append('image'); imagesAbove.enter().append('image'); From e18ffd6b2b32a9ef01f912582cfb0b15725dc67c Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Wed, 20 Nov 2024 18:15:59 -0500 Subject: [PATCH 2/7] ensure images stay sorted properly with the new data key --- src/components/images/draw.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/images/draw.js b/src/components/images/draw.js index 1c52dfea11c..4de8e2c4ada 100644 --- a/src/components/images/draw.js +++ b/src/components/images/draw.js @@ -207,6 +207,8 @@ module.exports = function draw(gd) { return [d.xref, d.x, d.sizex, d.yref, d.y, d.sizey].join('_'); } + function imgSort(a, b) { return a._index - b._index; } + var imagesBelow = fullLayout._imageLowerLayer.selectAll('image') .data(imageDataBelow, imgDataFunc); var imagesAbove = fullLayout._imageUpperLayer.selectAll('image') @@ -226,6 +228,8 @@ module.exports = function draw(gd) { setImage.bind(this)(d); applyAttributes.bind(this)(d); }); + imagesBelow.sort(imgSort); + imagesAbove.sort(imgSort); var allSubplots = Object.keys(fullLayout._plots); for(i = 0; i < allSubplots.length; i++) { From 8b41fd6bbd0593b435878c2e9d0062355de28456 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Wed, 20 Nov 2024 19:38:33 -0500 Subject: [PATCH 3/7] test layout image identity --- test/jasmine/tests/layout_images_test.js | 34 ++++++++++++++++++------ 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/test/jasmine/tests/layout_images_test.js b/test/jasmine/tests/layout_images_test.js index a5166349ecb..5b65d473bb5 100644 --- a/test/jasmine/tests/layout_images_test.js +++ b/test/jasmine/tests/layout_images_test.js @@ -441,6 +441,8 @@ describe('Layout images', function() { var data = [{ x: [1, 2, 3], y: [1, 2, 3] }]; var layout = { width: 500, height: 400 }; + var imgEls; + function makeImage(source, x, y) { return { source: source, @@ -451,8 +453,21 @@ describe('Layout images', function() { }; } - function assertImages(cnt) { + function getImageEls() { + return Array.from(gd.querySelectorAll('image')); + } + + function assertImages(cnt, expectedEls, msg) { expect(d3SelectAll('image').size()).toEqual(cnt); + + if (expectedEls) { + var foundImageEls = getImageEls(); + expectedEls.forEach(function(expi, i) { + if (expi) { + expect(foundImageEls[i]).toBe(expi, msg + ': ' + i); + } + }); + } } Plotly.newPlot(gd, data, layout).then(function() { @@ -463,42 +478,45 @@ describe('Layout images', function() { }) .then(function() { assertImages(1); + imgEls = getImageEls(); return Plotly.relayout(gd, 'images[1]', makeImage(pythonLogo, 0.9, 0.9)); }) .then(function() { - assertImages(2); + assertImages(2, [imgEls[0], null], 'add second image'); + imgEls = getImageEls(); // insert an image not at the end of the array return Plotly.relayout(gd, 'images[0]', makeImage(pythonLogo, 0.2, 0.5)); }) .then(function() { - assertImages(3); + assertImages(3, [null, imgEls[0], imgEls[1]], 'add third at the start'); expect(gd.layout.images.length).toEqual(3); + imgEls = getImageEls(); return Plotly.relayout(gd, 'images[1].visible', false); }) .then(function() { - assertImages(2); - expect(gd.layout.images.length).toEqual(3); + assertImages(2, [imgEls[0], imgEls[2]], 'hide second'); return Plotly.relayout(gd, 'images[1].visible', true); }) .then(function() { - assertImages(3); + assertImages(3, [imgEls[0], null, imgEls[2]], 'reshow second'); expect(gd.layout.images.length).toEqual(3); + imgEls = getImageEls(); // delete not from the end of the array return Plotly.relayout(gd, 'images[0]', null); }) .then(function() { - assertImages(2); + assertImages(2, [imgEls[1], imgEls[2]], 'delete first'); expect(gd.layout.images.length).toEqual(2); return Plotly.relayout(gd, 'images[1]', null); }) .then(function() { - assertImages(1); + assertImages(1, [imgEls[1]], 'delete last'); expect(gd.layout.images.length).toEqual(1); return Plotly.relayout(gd, 'images[0]', null); From bed729e3aece36cda46223bf69156991af40fe75 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Wed, 20 Nov 2024 19:42:26 -0500 Subject: [PATCH 4/7] changelog for layout image identity fix --- draftlogs/7277_fix.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 draftlogs/7277_fix.md diff --git a/draftlogs/7277_fix.md b/draftlogs/7277_fix.md new file mode 100644 index 00000000000..f17a3453b75 --- /dev/null +++ b/draftlogs/7277_fix.md @@ -0,0 +1 @@ +- Maintain layout images element identity based on coordinates, for smoother updates when you add or remove images early in the list. [[#7277](https://github.com/plotly/plotly.js/pull/7277)] From 5b945362d29a8d09c6d494761ccbc72b3e3574f5 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Wed, 20 Nov 2024 19:45:00 -0500 Subject: [PATCH 5/7] exclude CITATION.cff from lowercase filename test --- tasks/test_syntax.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tasks/test_syntax.js b/tasks/test_syntax.js index c9c0f1b78f9..0a5b0eb8cf3 100644 --- a/tasks/test_syntax.js +++ b/tasks/test_syntax.js @@ -199,6 +199,7 @@ function assertFileNames() { base === 'SECURITY.md' || base === 'BUILDING.md' || base === 'CUSTOM_BUNDLE.md' || + base === 'CITATION.cff' || file.indexOf('mathjax') !== -1 ) return; From 58c69e95fb81f51d407d0025a6abda6559d9d97f Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Wed, 20 Nov 2024 21:22:03 -0500 Subject: [PATCH 6/7] also key and sort subplot images --- src/components/images/draw.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/images/draw.js b/src/components/images/draw.js index 4de8e2c4ada..57c3a57e945 100644 --- a/src/components/images/draw.js +++ b/src/components/images/draw.js @@ -242,7 +242,7 @@ module.exports = function draw(gd) { var imagesOnSubplot = subplotObj.imagelayer.selectAll('image') // even if there are no images on this subplot, we need to run // enter and exit in case there were previously - .data(imageDataSubplot[subplot] || []); + .data(imageDataSubplot[subplot] || [], imgDataFunc); imagesOnSubplot.enter().append('image'); imagesOnSubplot.exit().remove(); @@ -251,5 +251,6 @@ module.exports = function draw(gd) { setImage.bind(this)(d); applyAttributes.bind(this)(d); }); + imagesOnSubplot.sort(imgSort); } }; From 148c693afca830dfb165eabfc7ab14ea0d80766f Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Wed, 20 Nov 2024 21:22:16 -0500 Subject: [PATCH 7/7] fix image transition test - but why does it work? --- test/jasmine/tests/transition_test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/jasmine/tests/transition_test.js b/test/jasmine/tests/transition_test.js index 4c8858b5c93..72204267182 100644 --- a/test/jasmine/tests/transition_test.js +++ b/test/jasmine/tests/transition_test.js @@ -106,7 +106,7 @@ describe('Plots.transition', function() { var pythonLogo = 'https://images.plot.ly/language-icons/api-home/python-logo.png'; function imageel() { - return gd._fullLayout._imageUpperLayer.select('image').node(); + return gd._fullLayout._imageUpperLayer.node().querySelector('image'); } function imagesrc() { return imageel().getAttribute('href');