1 /** 2 * Constructs a new image with default properties. Images are not typically 3 * constructed directly, but by adding to a panel or an existing mark via 4 * {@link pv.Mark#add}. 5 * 6 * @class Represents an image, either a static resource or a dynamically- 7 * generated pixel buffer. Images share the same layout and style properties as 8 * bars. The external image resource is specified via the {@link #url} 9 * property. The optional fill, if specified, appears beneath the image, while 10 * the optional stroke appears above the image. 11 * 12 * <p>Dynamic images such as heatmaps are supported using the {@link #image} 13 * psuedo-property. This function is passed the <i>x</i> and <i>y</i> index, in 14 * addition to the current data stack. The return value is a {@link pv.Color}, 15 * or null for transparent. A string can also be returned, which will be parsed 16 * into a color; however, it is typically much faster to return an object with 17 * <tt>r</tt>, <tt>g</tt>, <tt>b</tt> and <tt>a</tt> attributes, to avoid the 18 * cost of parsing and object instantiation. 19 * 20 * <p>See {@link pv.Bar} for details on positioning properties. 21 * 22 * @extends pv.Bar 23 */ 24 pv.Image = function() { 25 pv.Bar.call(this); 26 }; 27 28 pv.Image.prototype = pv.extend(pv.Bar) 29 .property("url", String) 30 .property("imageWidth", Number) 31 .property("imageHeight", Number); 32 33 pv.Image.prototype.type = "image"; 34 35 /** 36 * The URL of the image to display. The set of supported image types is 37 * browser-dependent; PNG and JPEG are recommended. 38 * 39 * @type string 40 * @name pv.Image.prototype.url 41 */ 42 43 /** 44 * The width of the image in pixels. For static images, this property is 45 * computed implicitly from the loaded image resources. For dynamic images, this 46 * property can be used to specify the width of the pixel buffer; otherwise, the 47 * value is derived from the <tt>width</tt> property. 48 * 49 * @type number 50 * @name pv.Image.prototype.imageWidth 51 */ 52 53 /** 54 * The height of the image in pixels. For static images, this property is 55 * computed implicitly from the loaded image resources. For dynamic images, this 56 * property can be used to specify the height of the pixel buffer; otherwise, the 57 * value is derived from the <tt>height</tt> property. 58 * 59 * @type number 60 * @name pv.Image.prototype.imageHeight 61 */ 62 63 /** 64 * Default properties for images. By default, there is no stroke or fill style. 65 * 66 * @type pv.Image 67 */ 68 pv.Image.prototype.defaults = new pv.Image() 69 .extend(pv.Bar.prototype.defaults) 70 .fillStyle(null); 71 72 /** 73 * Specifies the dynamic image function. By default, no image function is 74 * specified and the <tt>url</tt> property is used to load a static image 75 * resource. If an image function is specified, it will be invoked for each 76 * pixel in the image, based on the related <tt>imageWidth</tt> and 77 * <tt>imageHeight</tt> properties. 78 * 79 * <p>For example, given a two-dimensional array <tt>heatmap</tt>, containing 80 * numbers in the range [0, 1] in row-major order, a simple monochrome heatmap 81 * image can be specified as: 82 * 83 * <pre>vis.add(pv.Image) 84 * .imageWidth(heatmap[0].length) 85 * .imageHeight(heatmap.length) 86 * .image(pv.ramp("white", "black").by(function(x, y) heatmap[y][x]));</pre> 87 * 88 * For fastest performance, use an ordinal scale which caches the fixed color 89 * palette, or return an object literal with <tt>r</tt>, <tt>g</tt>, <tt>b</tt> 90 * and <tt>a</tt> attributes. A {@link pv.Color} or string can also be returned, 91 * though this typically results in slower performance. 92 * 93 * @param {function} f the new sizing function. 94 * @returns {pv.Layout.Pack} this. 95 */ 96 pv.Image.prototype.image = function(f) { 97 /** @private */ 98 this.$image = function() { 99 var c = f.apply(this, arguments); 100 return c == null ? pv.Color.transparent 101 : typeof c == "string" ? pv.color(c) 102 : c; 103 }; 104 return this; 105 }; 106 107 /** @private Scan the proto chain for an image function. */ 108 pv.Image.prototype.bind = function() { 109 pv.Bar.prototype.bind.call(this); 110 var binds = this.binds, mark = this; 111 do { 112 binds.image = mark.$image; 113 } while (!binds.image && (mark = mark.proto)); 114 }; 115 116 /** @private */ 117 pv.Image.prototype.buildImplied = function(s) { 118 pv.Bar.prototype.buildImplied.call(this, s); 119 if (!s.visible) return; 120 121 /* Compute the implied image dimensions. */ 122 if (s.imageWidth == null) s.imageWidth = s.width; 123 if (s.imageHeight == null) s.imageHeight = s.height; 124 125 /* Compute the pixel values. */ 126 if ((s.url == null) && this.binds.image) { 127 128 /* Cache the canvas element to reuse across renders. */ 129 var canvas = this.$canvas || (this.$canvas = document.createElement("canvas")), 130 context = canvas.getContext("2d"), 131 w = s.imageWidth, 132 h = s.imageHeight, 133 stack = pv.Mark.stack, 134 data; 135 136 /* Evaluate the image function, storing into a CanvasPixelArray. */ 137 canvas.width = w; 138 canvas.height = h; 139 data = (s.image = context.createImageData(w, h)).data; 140 stack.unshift(null, null); 141 for (var y = 0, p = 0; y < h; y++) { 142 stack[1] = y; 143 for (var x = 0; x < w; x++) { 144 stack[0] = x; 145 var color = this.binds.image.apply(this, stack); 146 data[p++] = color.r; 147 data[p++] = color.g; 148 data[p++] = color.b; 149 data[p++] = 255 * color.a; 150 } 151 } 152 stack.splice(0, 2); 153 } 154 }; 155