@@ -13,14 +13,24 @@ var Lib = require('../../lib');
13
13
var xmlnsNamespaces = require ( '../../constants/xmlns_namespaces' ) ;
14
14
var constants = require ( './constants' ) ;
15
15
16
+ function compatibleAxis ( ax ) {
17
+ return ax . type === 'linear' &&
18
+ // y axis must be reversed but x axis mustn't be
19
+ ( ( ax . range [ 1 ] > ax . range [ 0 ] ) === ( ax . _id . charAt ( 0 ) === 'x' ) ) ;
20
+ }
21
+
16
22
module . exports = function plot ( gd , plotinfo , cdimage , imageLayer ) {
17
23
var xa = plotinfo . xaxis ;
18
24
var ya = plotinfo . yaxis ;
19
25
26
+ var supportsPixelatedImage = ! Lib . isSafari ( ) && ! gd . _context . _exportedPlot ;
27
+
20
28
Lib . makeTraceGroups ( imageLayer , cdimage , 'im' ) . each ( function ( cd ) {
21
29
var plotGroup = d3 . select ( this ) ;
22
30
var cd0 = cd [ 0 ] ;
23
31
var trace = cd0 . trace ;
32
+ var fastImage = supportsPixelatedImage && trace . _isFromSource && compatibleAxis ( xa ) && compatibleAxis ( ya ) ;
33
+ trace . _fastImage = fastImage ;
24
34
25
35
var z = cd0 . z ;
26
36
var x0 = cd0 . x0 ;
@@ -66,11 +76,14 @@ module.exports = function plot(gd, plotinfo, cdimage, imageLayer) {
66
76
}
67
77
68
78
// Reduce image size when zoomed in to save memory
69
- var extra = 0.5 ; // half the axis size
70
- left = Math . max ( - extra * xa . _length , left ) ;
71
- right = Math . min ( ( 1 + extra ) * xa . _length , right ) ;
72
- top = Math . max ( - extra * ya . _length , top ) ;
73
- bottom = Math . min ( ( 1 + extra ) * ya . _length , bottom ) ;
79
+ if ( ! fastImage ) {
80
+ var extra = 0.5 ; // half the axis size
81
+ left = Math . max ( - extra * xa . _length , left ) ;
82
+ right = Math . min ( ( 1 + extra ) * xa . _length , right ) ;
83
+ top = Math . max ( - extra * ya . _length , top ) ;
84
+ bottom = Math . min ( ( 1 + extra ) * ya . _length , bottom ) ;
85
+ }
86
+
74
87
var imageWidth = Math . round ( right - left ) ;
75
88
var imageHeight = Math . round ( bottom - top ) ;
76
89
@@ -82,48 +95,118 @@ module.exports = function plot(gd, plotinfo, cdimage, imageLayer) {
82
95
return ;
83
96
}
84
97
85
- // Draw each pixel
86
- var canvas = document . createElement ( 'canvas' ) ;
87
- canvas . width = imageWidth ;
88
- canvas . height = imageHeight ;
89
- var context = canvas . getContext ( '2d' ) ;
90
-
91
- var ipx = function ( i ) { return Lib . constrain ( Math . round ( xa . c2p ( x0 + i * dx ) - left ) , 0 , imageWidth ) ; } ;
92
- var jpx = function ( j ) { return Lib . constrain ( Math . round ( ya . c2p ( y0 + j * dy ) - top ) , 0 , imageHeight ) ; } ;
93
-
94
- var fmt = constants . colormodel [ trace . colormodel ] . fmt ;
95
- var c ;
96
- for ( i = 0 ; i < cd0 . w ; i ++ ) {
97
- var ipx0 = ipx ( i ) ; var ipx1 = ipx ( i + 1 ) ;
98
- if ( ipx1 === ipx0 || isNaN ( ipx1 ) || isNaN ( ipx0 ) ) continue ;
99
- for ( var j = 0 ; j < cd0 . h ; j ++ ) {
100
- var jpx0 = jpx ( j ) ; var jpx1 = jpx ( j + 1 ) ;
101
- if ( jpx1 === jpx0 || isNaN ( jpx1 ) || isNaN ( jpx0 ) || ! z [ j ] [ i ] ) continue ;
102
- c = trace . _scaler ( z [ j ] [ i ] ) ;
103
- if ( c ) {
104
- context . fillStyle = trace . colormodel + '(' + fmt ( c ) . join ( ',' ) + ')' ;
105
- } else {
106
- // Return a transparent pixel
107
- context . fillStyle = 'rgba(0,0,0,0)' ;
98
+ // Create a new canvas and draw magnified pixels on it
99
+ function drawMagnifiedPixelsOnCanvas ( readPixel ) {
100
+ var colormodel = trace . colormodel ;
101
+ var canvas = document . createElement ( 'canvas' ) ;
102
+ canvas . width = imageWidth ;
103
+ canvas . height = imageHeight ;
104
+ var context = canvas . getContext ( '2d' ) ;
105
+
106
+ var ipx = function ( i ) { return Lib . constrain ( Math . round ( xa . c2p ( x0 + i * dx ) - left ) , 0 , imageWidth ) ; } ;
107
+ var jpx = function ( j ) { return Lib . constrain ( Math . round ( ya . c2p ( y0 + j * dy ) - top ) , 0 , imageHeight ) ; } ;
108
+
109
+ var fmt = constants . colormodel [ colormodel ] . fmt ;
110
+ var c ;
111
+ for ( i = 0 ; i < cd0 . w ; i ++ ) {
112
+ var ipx0 = ipx ( i ) ; var ipx1 = ipx ( i + 1 ) ;
113
+ if ( ipx1 === ipx0 || isNaN ( ipx1 ) || isNaN ( ipx0 ) ) continue ;
114
+ for ( var j = 0 ; j < cd0 . h ; j ++ ) {
115
+ var jpx0 = jpx ( j ) ; var jpx1 = jpx ( j + 1 ) ;
116
+ if ( jpx1 === jpx0 || isNaN ( jpx1 ) || isNaN ( jpx0 ) || ! readPixel ( i , j ) ) continue ;
117
+ c = trace . _scaler ( readPixel ( i , j ) ) ;
118
+ if ( c ) {
119
+ context . fillStyle = colormodel + '(' + fmt ( c ) . join ( ',' ) + ')' ;
120
+ } else {
121
+ // Return a transparent pixel
122
+ context . fillStyle = 'rgba(0,0,0,0)' ;
123
+ }
124
+ context . fillRect ( ipx0 , jpx0 , ipx1 - ipx0 , jpx1 - jpx0 ) ;
108
125
}
109
- context . fillRect ( ipx0 , jpx0 , ipx1 - ipx0 , jpx1 - jpx0 ) ;
110
126
}
127
+
128
+ return canvas ;
129
+ }
130
+
131
+ function sizeImage ( image ) {
132
+ image . attr ( {
133
+ height : imageHeight ,
134
+ width : imageWidth ,
135
+ x : left ,
136
+ y : top
137
+ } ) ;
111
138
}
112
139
140
+ var data = ( trace . _isFromSource && ! fastImage ) ? [ cd , { hidden : true } ] : [ cd ] ;
113
141
var image3 = plotGroup . selectAll ( 'image' )
114
- . data ( cd ) ;
142
+ . data ( data ) ;
115
143
116
144
image3 . enter ( ) . append ( 'svg:image' ) . attr ( {
117
145
xmlns : xmlnsNamespaces . svg ,
118
146
preserveAspectRatio : 'none'
119
147
} ) ;
120
148
121
- image3 . attr ( {
122
- height : imageHeight ,
123
- width : imageWidth ,
124
- x : left ,
125
- y : top ,
126
- 'xlink:href' : canvas . toDataURL ( 'image/png' )
149
+ if ( fastImage ) sizeImage ( image3 ) ;
150
+ image3 . exit ( ) . remove ( ) ;
151
+
152
+ // Pixelated image rendering
153
+ // http://phrogz.net/tmp/canvas_image_zoom.html
154
+ // https://developer.mozilla.org/en-US/docs/Web/CSS/image-rendering
155
+ image3
156
+ . 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;' ) ;
157
+
158
+ new Promise ( function ( resolve ) {
159
+ if ( trace . _isFromZ ) {
160
+ resolve ( ) ;
161
+ } else if ( trace . _isFromSource ) {
162
+ // Transfer image to a canvas to access pixel information
163
+ trace . _canvas = trace . _canvas || document . createElement ( 'canvas' ) ;
164
+ trace . _canvas . width = w ;
165
+ trace . _canvas . height = h ;
166
+ var context = trace . _canvas . getContext ( '2d' ) ;
167
+
168
+ var sel ;
169
+ if ( fastImage ) {
170
+ // Use the displayed image
171
+ sel = image3 ;
172
+ } else {
173
+ // Use the hidden image
174
+ sel = d3 . select ( image3 [ 0 ] [ 1 ] ) ;
175
+ }
176
+
177
+ var image = sel . node ( ) ;
178
+ image . onload = function ( ) {
179
+ context . drawImage ( image , 0 , 0 ) ;
180
+ // we need to wait for the image to be loaded in order to redraw it from the canvas
181
+ if ( ! fastImage ) resolve ( ) ;
182
+ } ;
183
+ sel . attr ( 'xlink:href' , trace . source ) ;
184
+ if ( fastImage ) resolve ( ) ;
185
+ }
186
+ } )
187
+ . then ( function ( ) {
188
+ if ( ! fastImage ) {
189
+ var canvas ;
190
+ if ( trace . _isFromZ ) {
191
+ canvas = drawMagnifiedPixelsOnCanvas ( function ( i , j ) { return z [ j ] [ i ] ; } ) ;
192
+ } else if ( trace . _isFromSource ) {
193
+ var context = trace . _canvas . getContext ( '2d' ) ;
194
+ var data = context . getImageData ( 0 , 0 , w , h ) . data ;
195
+ canvas = drawMagnifiedPixelsOnCanvas ( function ( i , j ) {
196
+ var index = 4 * ( j * w + i ) ;
197
+ return [
198
+ data [ index + 0 ] ,
199
+ data [ index + 1 ] ,
200
+ data [ index + 2 ] ,
201
+ data [ index + 3 ]
202
+ ] ;
203
+ } ) ;
204
+ }
205
+ var href = canvas . toDataURL ( 'image/png' ) ;
206
+ var displayImage = d3 . select ( image3 [ 0 ] [ 0 ] ) ;
207
+ sizeImage ( displayImage ) ;
208
+ displayImage . attr ( 'xlink:href' , href ) ;
209
+ }
127
210
} ) ;
128
211
} ) ;
129
212
} ;
0 commit comments