Skip to content

Commit 6d87753

Browse files
authored
Merge pull request #5307 from almarklein/reverse-y
Use CSS to support fast image rendering on linear axes
2 parents cdd836c + 632532f commit 6d87753

File tree

5 files changed

+115
-12
lines changed

5 files changed

+115
-12
lines changed

src/traces/image/plot.js

+19-9
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,12 @@
1010

1111
var d3 = require('d3');
1212
var Lib = require('../../lib');
13+
var strTranslate = Lib.strTranslate;
1314
var xmlnsNamespaces = require('../../constants/xmlns_namespaces');
1415
var constants = require('./constants');
1516

1617
var unsupportedBrowsers = Lib.isIOS() || Lib.isSafari() || Lib.isIE();
1718

18-
function compatibleAxis(ax) {
19-
return ax.type === 'linear' &&
20-
// y axis must be reversed but x axis mustn't be
21-
((ax.range[1] > ax.range[0]) === (ax._id.charAt(0) === 'x'));
22-
}
23-
2419
module.exports = function plot(gd, plotinfo, cdimage, imageLayer) {
2520
var xa = plotinfo.xaxis;
2621
var ya = plotinfo.yaxis;
@@ -31,7 +26,7 @@ module.exports = function plot(gd, plotinfo, cdimage, imageLayer) {
3126
var plotGroup = d3.select(this);
3227
var cd0 = cd[0];
3328
var trace = cd0.trace;
34-
var fastImage = supportsPixelatedImage && !trace._hasZ && trace._hasSource && compatibleAxis(xa) && compatibleAxis(ya);
29+
var fastImage = supportsPixelatedImage && !trace._hasZ && trace._hasSource && xa.type === 'linear' && ya.type === 'linear';
3530
trace._fastImage = fastImage;
3631

3732
var z = cd0.z;
@@ -144,8 +139,23 @@ module.exports = function plot(gd, plotinfo, cdimage, imageLayer) {
144139
// Pixelated image rendering
145140
// http://phrogz.net/tmp/canvas_image_zoom.html
146141
// https://developer.mozilla.org/en-US/docs/Web/CSS/image-rendering
147-
image3
148-
.attr('style', 'image-rendering: optimizeSpeed; image-rendering: -moz-crisp-edges; image-rendering: -o-crisp-edges; image-rendering: -webkit-optimize-contrast; image-rendering: optimize-contrast; image-rendering: crisp-edges; image-rendering: pixelated;');
142+
var style = 'image-rendering: optimizeSpeed; image-rendering: -moz-crisp-edges; image-rendering: -o-crisp-edges; image-rendering: -webkit-optimize-contrast; image-rendering: optimize-contrast; image-rendering: crisp-edges; image-rendering: pixelated;';
143+
if(fastImage) {
144+
var xRange = Lib.simpleMap(xa.range, xa.r2l);
145+
var yRange = Lib.simpleMap(ya.range, ya.r2l);
146+
147+
var flipX = xRange[1] < xRange[0];
148+
var flipY = yRange[1] > yRange[0];
149+
if(flipX || flipY) {
150+
var tx = left + imageWidth / 2;
151+
var ty = top + imageHeight / 2;
152+
style += 'transform:' +
153+
strTranslate(tx + 'px', ty + 'px') +
154+
'scale(' + (flipX ? -1 : 1) + ',' + (flipY ? -1 : 1) + ')' +
155+
strTranslate(-tx + 'px', -ty + 'px') + ';';
156+
}
157+
}
158+
image3.attr('style', style);
149159

150160
var p = new Promise(function(resolve) {
151161
if(trace._hasZ) {
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
{
2+
"data": [
3+
{
4+
"colormodel": "rgba",
5+
"type": "image",
6+
"source": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAlCAYAAAAA7LqSAAADmklEQVR4nO2XUWhTVxjHfyfdmKMN1oelK9QSLUVY2RWHrEKtLw66KQX30Ifpg0qxz31wb8O0UNjL1I3h09iDAy000D0Z0DJhEdslBXN7SwZBJmmbErKwNuU2UXDes4d4r700bib33KflD+Ge8+We78sv3/fdcy401VRTTTXVVFMIvxwnEhG5c97fP+lbLPARZHz8qBwY+AAA0zTp69MA/4B8cTo+flRq2n4MY42dMMFgEICRkZ+Uxw2odgigafudq2majn3nWLV8ATGMNdf44cPfd9lVyxeQwcF3MIw1gsEgY2OLzM9fYmDgPebnLzlQquVLj1y//pFcWan+R1eujLm+KxYTHDr0o/K4b6l2GI2efPnY3eTUqSMUiwmgBQDD+ANNC6sOCSgurUxmVAKUSpfp6tpHLJYiFksBL4AXaFr45Vy9lGUkkxmVhpEll9ukra1q6+raB+D68d3d76oK6ZKSjGQyozIWS5HLbVIujwCQyz0jHL4LVIHsT3f3r0xNfS3/zV8jUlpaNsT6eoS2tq/Q9TnSaZOZmRAzMyFKpcvo+hz5fEE5jGcQOxt2GeXzBQC2t6fY3p5y3bu+HnFsqmE8gdh9AThlZGtr67kzDoU6CIU6nPnGxioXLnzD0NCXLC7+rQTGE4jd3MePJ9H1OQBu3PjW2SO2tp6TTvfQ2dnhWjc9PSvs++2rVyl7aun6XTY2Vl22dLqHw4erp97Ozg6WlnqIRqfFqzVqIMADiL3x7cyGrb1733bGds8ArvK6f/+HRkPXVMNHhWj0pMzlNkkmw46tvT1NofAUTXtfPnnyrKbvgwf3yImJpPIznufSOns2C8Dt22EKhafMzq6KfL7XKpfZ1cStrUEeP/YasbY8Hd6KxS/kvXvfkc3eQdO+Z3j4kTh27BMrELCoVCpYlvUqkBBIKWltrb5cLSz8ojQrnpzZEOHwaYaHHwkAISSVSgWAlpYWDCMZMIxkwLIshBAIoXxTBzyA3Lr1p8xm7zA4+DnnzoUEwNWrnzkpEEKQSi04/peXFwMA5XKZoSGT8+c/tHZ7bVwNgcTjJQfixIl2AXDt2qeWlILe3iKAC8LW0lIicOZMdXzggNrDY90g8XhJPnjwswsCqiVlmn8hhETXf3ut38nJhC9vpXU5fR2ErWy2ws2by//pc2WlXE/YN9Ibg9gQ4fDpmhCgvlx8UTxekvF4qeYjJxLptyYmPq6reS9e7Kt7zf9C/wDLc4opp/2WUgAAAABJRU5ErkJggg=="
7+
},
8+
{
9+
"colormodel": "rgba",
10+
"type": "image",
11+
"xaxis": "x2",
12+
"yaxis": "y2",
13+
"source": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAlCAYAAAAA7LqSAAADmklEQVR4nO2XUWhTVxjHfyfdmKMN1oelK9QSLUVY2RWHrEKtLw66KQX30Ifpg0qxz31wb8O0UNjL1I3h09iDAy000D0Z0DJhEdslBXN7SwZBJmmbErKwNuU2UXDes4d4r700bib33KflD+Ge8+We78sv3/fdcy401VRTTTXVVFMIvxwnEhG5c97fP+lbLPARZHz8qBwY+AAA0zTp69MA/4B8cTo+flRq2n4MY42dMMFgEICRkZ+Uxw2odgigafudq2majn3nWLV8ATGMNdf44cPfd9lVyxeQwcF3MIw1gsEgY2OLzM9fYmDgPebnLzlQquVLj1y//pFcWan+R1eujLm+KxYTHDr0o/K4b6l2GI2efPnY3eTUqSMUiwmgBQDD+ANNC6sOCSgurUxmVAKUSpfp6tpHLJYiFksBL4AXaFr45Vy9lGUkkxmVhpEll9ukra1q6+raB+D68d3d76oK6ZKSjGQyozIWS5HLbVIujwCQyz0jHL4LVIHsT3f3r0xNfS3/zV8jUlpaNsT6eoS2tq/Q9TnSaZOZmRAzMyFKpcvo+hz5fEE5jGcQOxt2GeXzBQC2t6fY3p5y3bu+HnFsqmE8gdh9AThlZGtr67kzDoU6CIU6nPnGxioXLnzD0NCXLC7+rQTGE4jd3MePJ9H1OQBu3PjW2SO2tp6TTvfQ2dnhWjc9PSvs++2rVyl7aun6XTY2Vl22dLqHw4erp97Ozg6WlnqIRqfFqzVqIMADiL3x7cyGrb1733bGds8ArvK6f/+HRkPXVMNHhWj0pMzlNkkmw46tvT1NofAUTXtfPnnyrKbvgwf3yImJpPIznufSOns2C8Dt22EKhafMzq6KfL7XKpfZ1cStrUEeP/YasbY8Hd6KxS/kvXvfkc3eQdO+Z3j4kTh27BMrELCoVCpYlvUqkBBIKWltrb5cLSz8ojQrnpzZEOHwaYaHHwkAISSVSgWAlpYWDCMZMIxkwLIshBAIoXxTBzyA3Lr1p8xm7zA4+DnnzoUEwNWrnzkpEEKQSi04/peXFwMA5XKZoSGT8+c/tHZ7bVwNgcTjJQfixIl2AXDt2qeWlILe3iKAC8LW0lIicOZMdXzggNrDY90g8XhJPnjwswsCqiVlmn8hhETXf3ut38nJhC9vpXU5fR2ErWy2ws2by//pc2WlXE/YN9Ibg9gQ4fDpmhCgvlx8UTxekvF4qeYjJxLptyYmPq6reS9e7Kt7zf9C/wDLc4opp/2WUgAAAABJRU5ErkJggg=="
14+
},
15+
{
16+
"colormodel": "rgba",
17+
"type": "image",
18+
"xaxis": "x3",
19+
"yaxis": "y3",
20+
"source": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAlCAYAAAAA7LqSAAADmklEQVR4nO2XUWhTVxjHfyfdmKMN1oelK9QSLUVY2RWHrEKtLw66KQX30Ifpg0qxz31wb8O0UNjL1I3h09iDAy000D0Z0DJhEdslBXN7SwZBJmmbErKwNuU2UXDes4d4r700bib33KflD+Ge8+We78sv3/fdcy401VRTTTXVVFMIvxwnEhG5c97fP+lbLPARZHz8qBwY+AAA0zTp69MA/4B8cTo+flRq2n4MY42dMMFgEICRkZ+Uxw2odgigafudq2majn3nWLV8ATGMNdf44cPfd9lVyxeQwcF3MIw1gsEgY2OLzM9fYmDgPebnLzlQquVLj1y//pFcWan+R1eujLm+KxYTHDr0o/K4b6l2GI2efPnY3eTUqSMUiwmgBQDD+ANNC6sOCSgurUxmVAKUSpfp6tpHLJYiFksBL4AXaFr45Vy9lGUkkxmVhpEll9ukra1q6+raB+D68d3d76oK6ZKSjGQyozIWS5HLbVIujwCQyz0jHL4LVIHsT3f3r0xNfS3/zV8jUlpaNsT6eoS2tq/Q9TnSaZOZmRAzMyFKpcvo+hz5fEE5jGcQOxt2GeXzBQC2t6fY3p5y3bu+HnFsqmE8gdh9AThlZGtr67kzDoU6CIU6nPnGxioXLnzD0NCXLC7+rQTGE4jd3MePJ9H1OQBu3PjW2SO2tp6TTvfQ2dnhWjc9PSvs++2rVyl7aun6XTY2Vl22dLqHw4erp97Ozg6WlnqIRqfFqzVqIMADiL3x7cyGrb1733bGds8ArvK6f/+HRkPXVMNHhWj0pMzlNkkmw46tvT1NofAUTXtfPnnyrKbvgwf3yImJpPIznufSOns2C8Dt22EKhafMzq6KfL7XKpfZ1cStrUEeP/YasbY8Hd6KxS/kvXvfkc3eQdO+Z3j4kTh27BMrELCoVCpYlvUqkBBIKWltrb5cLSz8ojQrnpzZEOHwaYaHHwkAISSVSgWAlpYWDCMZMIxkwLIshBAIoXxTBzyA3Lr1p8xm7zA4+DnnzoUEwNWrnzkpEEKQSi04/peXFwMA5XKZoSGT8+c/tHZ7bVwNgcTjJQfixIl2AXDt2qeWlILe3iKAC8LW0lIicOZMdXzggNrDY90g8XhJPnjwswsCqiVlmn8hhETXf3ut38nJhC9vpXU5fR2ErWy2ws2by//pc2WlXE/YN9Ibg9gQ4fDpmhCgvlx8UTxekvF4qeYjJxLptyYmPq6reS9e7Kt7zf9C/wDLc4opp/2WUgAAAABJRU5ErkJggg=="
21+
},
22+
{
23+
"colormodel": "rgba",
24+
"type": "image",
25+
"xaxis": "x4",
26+
"yaxis": "y4",
27+
"source": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAlCAYAAAAA7LqSAAADmklEQVR4nO2XUWhTVxjHfyfdmKMN1oelK9QSLUVY2RWHrEKtLw66KQX30Ifpg0qxz31wb8O0UNjL1I3h09iDAy000D0Z0DJhEdslBXN7SwZBJmmbErKwNuU2UXDes4d4r700bib33KflD+Ge8+We78sv3/fdcy401VRTTTXVVFMIvxwnEhG5c97fP+lbLPARZHz8qBwY+AAA0zTp69MA/4B8cTo+flRq2n4MY42dMMFgEICRkZ+Uxw2odgigafudq2majn3nWLV8ATGMNdf44cPfd9lVyxeQwcF3MIw1gsEgY2OLzM9fYmDgPebnLzlQquVLj1y//pFcWan+R1eujLm+KxYTHDr0o/K4b6l2GI2efPnY3eTUqSMUiwmgBQDD+ANNC6sOCSgurUxmVAKUSpfp6tpHLJYiFksBL4AXaFr45Vy9lGUkkxmVhpEll9ukra1q6+raB+D68d3d76oK6ZKSjGQyozIWS5HLbVIujwCQyz0jHL4LVIHsT3f3r0xNfS3/zV8jUlpaNsT6eoS2tq/Q9TnSaZOZmRAzMyFKpcvo+hz5fEE5jGcQOxt2GeXzBQC2t6fY3p5y3bu+HnFsqmE8gdh9AThlZGtr67kzDoU6CIU6nPnGxioXLnzD0NCXLC7+rQTGE4jd3MePJ9H1OQBu3PjW2SO2tp6TTvfQ2dnhWjc9PSvs++2rVyl7aun6XTY2Vl22dLqHw4erp97Ozg6WlnqIRqfFqzVqIMADiL3x7cyGrb1733bGds8ArvK6f/+HRkPXVMNHhWj0pMzlNkkmw46tvT1NofAUTXtfPnnyrKbvgwf3yImJpPIznufSOns2C8Dt22EKhafMzq6KfL7XKpfZ1cStrUEeP/YasbY8Hd6KxS/kvXvfkc3eQdO+Z3j4kTh27BMrELCoVCpYlvUqkBBIKWltrb5cLSz8ojQrnpzZEOHwaYaHHwkAISSVSgWAlpYWDCMZMIxkwLIshBAIoXxTBzyA3Lr1p8xm7zA4+DnnzoUEwNWrnzkpEEKQSi04/peXFwMA5XKZoSGT8+c/tHZ7bVwNgcTjJQfixIl2AXDt2qeWlILe3iKAC8LW0lIicOZMdXzggNrDY90g8XhJPnjwswsCqiVlmn8hhETXf3ut38nJhC9vpXU5fR2ErWy2ws2by//pc2WlXE/YN9Ibg9gQ4fDpmhCgvlx8UTxekvF4qeYjJxLptyYmPq6reS9e7Kt7zf9C/wDLc4opp/2WUgAAAABJRU5ErkJggg=="
28+
}
29+
],
30+
"layout": {
31+
"grid": {
32+
"rows": 2,
33+
"columns": 2,
34+
"pattern": "independent"
35+
},
36+
"width": 600,
37+
"height": 600,
38+
"margin": {
39+
"t": 35,
40+
"l": 35,
41+
"b": 35,
42+
"r": 35
43+
},
44+
"xaxis2": {
45+
"autorange": "reversed"
46+
},
47+
"yaxis3": {
48+
"range": [
49+
"8",
50+
"32"
51+
]
52+
},
53+
"yaxis4": {
54+
"autorange": true
55+
},
56+
"xaxis4": {
57+
"autorange": "reversed"
58+
}
59+
}
60+
}

test/jasmine/tests/image_test.js

+34-3
Original file line numberDiff line numberDiff line change
@@ -423,9 +423,7 @@ describe('image plot', function() {
423423

424424
[
425425
['yaxis.type', 'log'],
426-
['xaxis.type', 'log'],
427-
['xaxis.range', [50, 0]],
428-
['yaxis.range', [0, 50]]
426+
['xaxis.type', 'log']
429427
].forEach(function(attr) {
430428
it('does not renders pixelated image when the axes are not compatible', function(done) {
431429
var mock = require('@mocks/image_astronaut_source.json');
@@ -669,5 +667,38 @@ describe('image hover:', function() {
669667
.then(done);
670668
});
671669
});
670+
671+
[
672+
[true, true],
673+
[true, 'reversed'], // the default image layout
674+
['reversed', true],
675+
['reversed', 'reversed']
676+
].forEach(function(test) {
677+
it('should show correct hover info regardless of axis directions ' + test, function(done) {
678+
var mockCopy = Lib.extendDeep({}, mock);
679+
mockCopy.layout.xaxis.autorange = test[0];
680+
mockCopy.layout.yaxis.autorange = test[1];
681+
mockCopy.data[0].colormodel = 'rgba';
682+
mockCopy.data[0].hovertemplate = 'x:%{x}, y:%{y}, z:%{z}<extra></extra>';
683+
Plotly.newPlot(gd, mockCopy)
684+
.then(function() {
685+
var x = 205;
686+
var y = 125;
687+
688+
// adjust considering css
689+
if(test[0] === 'reversed') x = 512 - x;
690+
if(test[1] !== 'reversed') y = 512 - y;
691+
_hover(x, y);
692+
})
693+
.then(function() {
694+
assertHoverLabelContent({
695+
nums: 'x:205, y:125, z:[202, 148, 125, 255]',
696+
name: ''
697+
}, 'positions should be correct!');
698+
})
699+
.catch(failTest)
700+
.then(done);
701+
});
702+
});
672703
});
673704
});

test/jasmine/tests/mock_test.js

+2
Original file line numberDiff line numberDiff line change
@@ -648,6 +648,7 @@ var list = [
648648
'image_colormodel',
649649
'image_non_numeric',
650650
'image_opacity',
651+
'image_source_axis_reverse',
651652
'image_with_gaps',
652653
'image_with_heatmap',
653654
'image_zmin_zmax',
@@ -1721,6 +1722,7 @@ figs['image_cat'] = require('@mocks/image_cat');
17211722
figs['image_colormodel'] = require('@mocks/image_colormodel');
17221723
figs['image_non_numeric'] = require('@mocks/image_non_numeric');
17231724
figs['image_opacity'] = require('@mocks/image_opacity');
1725+
figs['image_source_axis_reverse'] = require('@mocks/image_source_axis_reverse');
17241726
figs['image_with_gaps'] = require('@mocks/image_with_gaps');
17251727
figs['image_with_heatmap'] = require('@mocks/image_with_heatmap');
17261728
figs['image_zmin_zmax'] = require('@mocks/image_zmin_zmax');

0 commit comments

Comments
 (0)