diff --git a/examples/quads/main.js b/examples/quads/main.js index c796b1d937..eb01cf23ac 100644 --- a/examples/quads/main.js +++ b/examples/quads/main.js @@ -20,91 +20,125 @@ $(function () { features: query.renderer ? undefined : ['quad'] }); var quads = layer.createFeature('quad', {selectionAPI: true}); + var quadData = [{ + ll: {x: -108, y: 29}, + ur: {x: -88, y: 49}, + image: '../../data/tilefancy.png' + }, { + ll: {x: -88, y: 29}, + ur: {x: -58, y: 49}, + image: 'flower1.jpg', + opacity: 0.75 + }, { + ul: {x: -108, y: 29}, + ur: {x: -58, y: 29}, + ll: {x: -98, y: 9}, + lr: {x: -68, y: 9}, + previewImage: null, + image: 'flower3.jpg' + }, { + lr: {x: -58, y: 29}, + ur: {x: -58, y: 49}, + ul: {x: -38, y: 54}, + ll: {x: -33, y: 34}, + image: 'flower2.jpg', + opacity: 0.15 + }, { + ll: {x: -33, y: 34}, + lr: {x: -33, y: 9}, + ur: {x: -68, y: 9}, + ul: {x: -58, y: 29}, + image: '../../data/tilefancy.png' + }, { + ll: {x: -128, y: 29}, + ur: {x: -108, y: 49}, + image: '../../data/nosuchimage.png' + }, { + ul: {x: -128, y: 29}, + ur: {x: -108, y: 29}, + ll: {x: -123, y: 9}, + lr: {x: -98, y: 9}, + previewImage: null, + image: '../../data/nosuchimage.png' + }, { + ul: {x: -148, y: 29}, + ur: {x: -128, y: 29}, + ll: {x: -148, y: 9}, + lr: {x: -123, y: 9}, + previewImage: previewImage, + image: '../../data/nosuchimage.png' + }, { + ll: {x: -138, y: 29}, + ur: {x: -128, y: 39}, + color: '#FF0000' + }, { + ll: {x: -148, y: 39}, + ur: {x: -138, y: 49}, + color: '#FF0000' + }, { + ll: {x: -138, y: 39}, + ur: {x: -128, y: 49}, + color: '#00FFFF' + }, { + ll: {x: -148, y: 29}, + ur: {x: -138, y: 39}, + opacity: 0.25, + color: '#0000FF' + }]; + if (query.canvas === 'true') { + // You can render a canvas on a quad, but only on the canvas and vgl + // renderers. On the vgl renderer, it may not update when the quad's + // canvas element is changed. + var canvasElement = document.createElement('canvas'); + canvasElement.width = 640; + canvasElement.height = 480; + var context = canvasElement.getContext('2d'); + var gradient = context.createRadialGradient(320, 240, 0, 320, 240, 320); + gradient.addColorStop(0, 'black'); + gradient.addColorStop(1, 'green'); + context.fillStyle = gradient; + context.fillRect(0, 0, 640, 480); + quadData.push({ + ul: {x: -98, y: 9}, + ur: {x: -68, y: 9}, + ll: {x: -98, y: -11}, + lr: {x: -68, y: -11}, + image: canvasElement + }); + // change the gradient after 10 seconds + window.setTimeout(function () { + var gradient = context.createRadialGradient(320, 240, 0, 320, 240, 320); + gradient.addColorStop(0, 'green'); + gradient.addColorStop(1, 'black'); + context.fillStyle = gradient; + context.fillRect(0, 0, 640, 480); + quads.draw(); + }, 10000); + } + if (query.warped === 'true') { + /* You can specify quads so that the corners are 'twisted' and the quad + * would be non-convex. In this case, the quads are each rendered as a + * pair of triangles, but they probably aren't what is desired. */ + quadData.push({ + ll: {x: -108, y: 49}, + lr: {x: -88, y: 49}, + ur: {x: -108, y: 59}, + ul: {x: -88, y: 59}, + image: '../../data/tilefancy.png' + }); + quadData.push({ + ll: {x: -88, y: 49}, + ur: {x: -68, y: 49}, + ul: {x: -88, y: 59}, + lr: {x: -68, y: 59}, + image: '../../data/tilefancy.png' + }); + } var previewImage = new Image(); previewImage.onload = function () { quads - .data([{ - ll: {x: -108, y: 29}, - ur: {x: -88, y: 49}, - image: '../../data/tilefancy.png' - }, { - ll: {x: -88, y: 29}, - ur: {x: -58, y: 49}, - image: 'flower1.jpg', - opacity: 0.75 - }, { - ul: {x: -108, y: 29}, - ur: {x: -58, y: 29}, - ll: {x: -98, y: 9}, - lr: {x: -68, y: 9}, - previewImage: null, - image: 'flower3.jpg' - }, { - lr: {x: -58, y: 29}, - ur: {x: -58, y: 49}, - ul: {x: -38, y: 54}, - ll: {x: -33, y: 34}, - image: 'flower2.jpg', - opacity: 0.15 - }, { - ll: {x: -33, y: 34}, - lr: {x: -33, y: 9}, - ur: {x: -68, y: 9}, - ul: {x: -58, y: 29}, - image: '../../data/tilefancy.png' - }, { - ll: {x: -128, y: 29}, - ur: {x: -108, y: 49}, - image: '../../data/nosuchimage.png' - }, { - ul: {x: -128, y: 29}, - ur: {x: -108, y: 29}, - ll: {x: -123, y: 9}, - lr: {x: -98, y: 9}, - previewImage: null, - image: '../../data/nosuchimage.png' - }, { - ul: {x: -148, y: 29}, - ur: {x: -128, y: 29}, - ll: {x: -148, y: 9}, - lr: {x: -123, y: 9}, - previewImage: previewImage, - image: '../../data/nosuchimage.png' - }, { - ll: {x: -138, y: 29}, - ur: {x: -128, y: 39}, - color: '#FF0000' - }, { - ll: {x: -148, y: 39}, - ur: {x: -138, y: 49}, - color: '#FF0000' - }, { - ll: {x: -138, y: 39}, - ur: {x: -128, y: 49}, - color: '#00FFFF' - }, { - ll: {x: -148, y: 29}, - ur: {x: -138, y: 39}, - opacity: 0.25, - color: '#0000FF' - /* You can specify quads so that the corners are 'twisted' and the quad - * would be non-convex. In this case, the quads are each rendered as a - * pair of triangles, but they probably aren't what is desired. - }, { - ll: {x: -108, y: 49}, - lr: {x: -88, y: 49}, - ur: {x: -108, y: 59}, - ul: {x: -88, y: 59}, - image: '../../data/tilefancy.png' - }, { - ll: {x: -88, y: 49}, - ur: {x: -68, y: 49}, - ul: {x: -88, y: 59}, - lr: {x: -68, y: 59}, - image: '../../data/tilefancy.png' - */ - }]) + .data(quadData) .style({ opacity: function (d) { return d.opacity !== undefined ? d.opacity : 1; diff --git a/src/canvas/canvasRenderer.js b/src/canvas/canvasRenderer.js index 2d0e8090ba..36600d011f 100644 --- a/src/canvas/canvasRenderer.js +++ b/src/canvas/canvasRenderer.js @@ -105,7 +105,9 @@ var canvasRenderer = function (arg) { var features = layer.features(); for (var i = 0; i < features.length; i += 1) { - features[i]._renderOnCanvas(m_this.context2d, map); + if (features[i].visible()) { + features[i]._renderOnCanvas(m_this.context2d, map); + } } }); } diff --git a/src/canvas/quadFeature.js b/src/canvas/quadFeature.js index 6750d18c6e..29a52a068f 100644 --- a/src/canvas/quadFeature.js +++ b/src/canvas/quadFeature.js @@ -154,6 +154,7 @@ capabilities[quadFeature.capabilities.image] = true; capabilities[quadFeature.capabilities.imageCrop] = true; capabilities[quadFeature.capabilities.imageFixedScale] = true; capabilities[quadFeature.capabilities.imageFull] = false; +capabilities[quadFeature.capabilities.canvas] = true; registerFeature('canvas', 'quad', canvas_quadFeature, capabilities); module.exports = canvas_quadFeature; diff --git a/src/d3/d3Renderer.js b/src/d3/d3Renderer.js index ad70802fe0..e24b90d8c6 100644 --- a/src/d3/d3Renderer.js +++ b/src/d3/d3Renderer.js @@ -478,6 +478,7 @@ var d3Renderer = function (arg) { data: arg.data, index: arg.dataIndex, style: arg.style, + visible: arg.visible, attributes: arg.attributes, classes: arg.classes, append: arg.append, @@ -538,6 +539,7 @@ var d3Renderer = function (arg) { var data = m_features[id].data, index = m_features[id].index, style = m_features[id].style, + visible = m_features[id].visible, attributes = m_features[id].attributes, classes = m_features[id].classes, append = m_features[id].append, @@ -549,6 +551,9 @@ var d3Renderer = function (arg) { setAttrs(rendersel, attributes); rendersel.attr('class', classes.concat([id]).join(' ')); setStyles(rendersel, style); + if (visible) { + rendersel.style('visibility', visible() ? 'visible' : 'hidden'); + } if (entries.size() && m_features[id].sortByZ) { selection.sort(function (a, b) { return (a.zIndex || 0) - (b.zIndex || 0); diff --git a/src/d3/lineFeature.js b/src/d3/lineFeature.js index 0c101c3670..2e38f88d49 100644 --- a/src/d3/lineFeature.js +++ b/src/d3/lineFeature.js @@ -99,6 +99,7 @@ var d3_lineFeature = function (arg) { }, id: m_this._d3id() + idx, classes: ['d3LineFeature', 'd3SubLine-' + idx], + visible: m_this.visible, style: style }; diff --git a/src/d3/pathFeature.js b/src/d3/pathFeature.js index 26a49e5f0e..6360f5c623 100644 --- a/src/d3/pathFeature.js +++ b/src/d3/pathFeature.js @@ -94,6 +94,7 @@ var d3_pathFeature = function (arg) { 'fill': function () { return false; }, 'fillColor': function () { return { r: 0, g: 0, b: 0 }; } }, s_style); + m_style.visible = m_this.visible; m_this.renderer()._drawFeatures(m_style); diff --git a/src/d3/pointFeature.js b/src/d3/pointFeature.js index eb98fc43f8..9d9fc7253a 100644 --- a/src/d3/pointFeature.js +++ b/src/d3/pointFeature.js @@ -81,6 +81,7 @@ var d3_pointFeature = function (arg) { }; m_style.style = s_style; m_style.classes = ['d3PointFeature']; + m_style.visible = m_this.visible; // pass to renderer to draw m_this.renderer()._drawFeatures(m_style); diff --git a/src/d3/quadFeature.js b/src/d3/quadFeature.js index 2f4c14d9a2..7b01825878 100644 --- a/src/d3/quadFeature.js +++ b/src/d3/quadFeature.js @@ -182,6 +182,7 @@ var d3_quadFeature = function (arg) { }, onlyRenderNew: !this.style('previewColor') && !this.style('previewImage'), sortByZ: true, + visible: m_this.visible, classes: ['d3QuadFeature'] }; renderer._drawFeatures(feature); @@ -234,6 +235,7 @@ capabilities[quadFeature.capabilities.image] = true; capabilities[quadFeature.capabilities.imageCrop] = false; capabilities[quadFeature.capabilities.imageFixedScale] = false; capabilities[quadFeature.capabilities.imageFull] = false; +capabilities[quadFeature.capabilities.canvas] = false; registerFeature('d3', 'quad', d3_quadFeature, capabilities); module.exports = d3_quadFeature; diff --git a/src/d3/vectorFeature.js b/src/d3/vectorFeature.js index 31e9492313..976748ee3b 100644 --- a/src/d3/vectorFeature.js +++ b/src/d3/vectorFeature.js @@ -242,6 +242,7 @@ var d3_vectorFeature = function (arg) { endStyle: s_style.endStyle }; m_style.classes = ['d3VectorFeature']; + m_style.visible = m_this.visible; // Add markers to the defition list updateMarkers(data, s_style.strokeColor, s_style.strokeOpacity, s_style.originStyle, s_style.endStyle); diff --git a/src/feature.js b/src/feature.js index 9c96636e03..42bdcf106c 100644 --- a/src/feature.js +++ b/src/feature.js @@ -41,6 +41,7 @@ var feature = function (arg) { m_dataTime = timestamp(), m_buildTime = timestamp(), m_updateTime = timestamp(), + m_dependentFeatures = [], m_selectedFeatures = []; //////////////////////////////////////////////////////////////////////////// @@ -417,7 +418,8 @@ var feature = function (arg) { this.visible = function (val) { if (val === undefined) { return m_visible; - } else { + } + if (m_visible !== val) { m_visible = val; m_this.modified(); @@ -427,9 +429,25 @@ var feature = function (arg) { } else { m_this._unbindMouseHandlers(); } + for (var i = 0; i < m_dependentFeatures.length; i += 1) { + m_dependentFeatures[i].visible(val); + } + } + return m_this; + }; - return m_this; + //////////////////////////////////////////////////////////////////////////// + /** + * Get/Set a list of dependent features. Dependent features have their + * visibility changed at the same time as the feature. + */ + //////////////////////////////////////////////////////////////////////////// + this.dependentFeatures = function (arg) { + if (arg === undefined) { + return m_dependentFeatures.slice(); } + m_dependentFeatures = arg.slice(); + return m_this; }; //////////////////////////////////////////////////////////////////////////// diff --git a/src/gl/quadFeature.js b/src/gl/quadFeature.js index c07e20ff1b..e0f105362b 100644 --- a/src/gl/quadFeature.js +++ b/src/gl/quadFeature.js @@ -423,6 +423,7 @@ capabilities[quadFeature.capabilities.image] = true; capabilities[quadFeature.capabilities.imageCrop] = true; capabilities[quadFeature.capabilities.imageFixedScale] = false; capabilities[quadFeature.capabilities.imageFull] = true; +capabilities[quadFeature.capabilities.canvas] = false; registerFeature('vgl', 'quad', gl_quadFeature, capabilities); module.exports = gl_quadFeature; diff --git a/src/polyfills.js b/src/polyfills.js index 87d42fe1d8..22c62d5315 100644 --- a/src/polyfills.js +++ b/src/polyfills.js @@ -34,4 +34,3 @@ Math.sinh = Math.sinh || function (x) { var y = Math.exp(x); return (y - 1 / y) / 2; }; - diff --git a/src/polygonFeature.js b/src/polygonFeature.js index 30675c6aac..f745177bf4 100644 --- a/src/polygonFeature.js +++ b/src/polygonFeature.js @@ -260,6 +260,7 @@ var polygonFeature = function (arg) { if (m_lineFeature && m_this.layer()) { m_this.layer().deleteFeature(m_lineFeature); m_lineFeature = null; + m_this.dependentFeatures([]); } return; } @@ -267,8 +268,12 @@ var polygonFeature = function (arg) { return; } if (!m_lineFeature) { - m_lineFeature = m_this.layer().createFeature( - 'line', {selectionAPI: false, gcs: this.gcs()}); + m_lineFeature = m_this.layer().createFeature('line', { + selectionAPI: false, + gcs: m_this.gcs(), + visible: m_this.visible() + }); + m_this.dependentFeatures([m_lineFeature]); } var polyStyle = m_this.style(); m_lineFeature.style({ @@ -345,6 +350,7 @@ var polygonFeature = function (arg) { if (m_lineFeature && m_this.layer()) { m_this.layer().deleteFeature(m_lineFeature); m_lineFeature = null; + m_this.dependentFeatures([]); } s_exit(); }; diff --git a/src/quadFeature.js b/src/quadFeature.js index 16cb9585ef..d64592550e 100644 --- a/src/quadFeature.js +++ b/src/quadFeature.js @@ -357,7 +357,7 @@ var quadFeature = function (arg) { } else { image = m_this._objectListGet(m_images, img); if (image === undefined) { - if (img instanceof Image) { + if (img instanceof Image || img instanceof HTMLCanvasElement) { image = img; } else { image = new Image(); @@ -376,7 +376,8 @@ var quadFeature = function (arg) { if (d.crop) { quad.crop = d.crop; } - if (image.complete && image.naturalWidth && image.naturalHeight) { + if ((image.complete && image.naturalWidth && image.naturalHeight) || + image instanceof HTMLCanvasElement) { quad.image = image; } else { previewColor = undefined; @@ -508,7 +509,9 @@ quadFeature.capabilities = { /* support for fixed-scale quad images */ imageFixedScale: 'quad.imageFixedScale', /* support for arbitrary quad images */ - imageFull: 'quad.imageFull' + imageFull: 'quad.imageFull', + /* support for canvas as content in image quads*/ + canvas: 'quad.canvas' }; inherit(quadFeature, feature); diff --git a/src/util/init.js b/src/util/init.js index c5f601aeaf..14815f9ff9 100644 --- a/src/util/init.js +++ b/src/util/init.js @@ -135,24 +135,135 @@ return s; }, + /* This is a list of regex and processing functions for color conversions + * to rgb objects. Each entry contains: + * name: a name of the color conversion. + * regex: a regex that, if it matches the color string, will cause the + * process function to be invoked. + * process: a function that takes (color, match) with the original color + * string and the results of matching the regex. It outputs an rgb + * color object or the original color string if there is still a + * parsing failure. + * In general, these conversions are somewhat more forgiving than the css + * specification (see https://drafts.csswg.org/css-color/) in that + * percentages may be mixed with numbers, and that floating point values + * are accepted for all numbers. Commas are optional. As per the latest + * draft standard, rgb and rgba are aliases of each other, as are hsl and + * hsla. + */ + cssColorConversions: [{ + name: 'rgb', + regex: new RegExp( + '^\\s*rgba?' + + '\\(\\s*(\\d+\\.?\\d*|\\.\\d?)\\s*(%?)\\s*' + + ',?\\s*(\\d+\\.?\\d*|\\.\\d?)\\s*(%?)\\s*' + + ',?\\s*(\\d+\\.?\\d*|\\.\\d?)\\s*(%?)\\s*' + + '(,?\\s*(\\d+\\.?\\d*|\\.\\d?)\\s*(%?)\\s*)?' + + '\\)\\s*$'), + process: function (color, match) { + color = { + r: Math.min(1, Math.max(0, +match[1] / (match[2] ? 100 : 255))), + g: Math.min(1, Math.max(0, +match[3] / (match[4] ? 100 : 255))), + b: Math.min(1, Math.max(0, +match[5] / (match[6] ? 100 : 255))) + }; + if (match[7]) { + color.a = Math.min(1, Math.max(0, +match[8] / (match[9] ? 100 : 1))); + } + return color; + } + }, { + name: 'hsl', + regex: new RegExp( + '^\\s*hsla?' + + '\\(\\s*(\\d+\\.?\\d*|\\.\\d?)\\s*(deg)?\\s*' + + ',?\\s*(\\d+\\.?\\d*|\\.\\d?)\\s*%\\s*' + + ',?\\s*(\\d+\\.?\\d*|\\.\\d?)\\s*%\\s*' + + '(,?\\s*(\\d+\\.?\\d*|\\.\\d?)\\s*(%?)\\s*)?' + + '\\)\\s*$'), + process: function (color, match) { + /* Conversion from https://www.w3.org/TR/2011/REC-css3-color-20110607 + */ + var hue_to_rgb = function (m1, m2, h) { + h = h - Math.floor(h); + if (h * 6 < 1) { + return m1 + (m2 - m1) * h * 6; + } + if (h * 6 < 3) { + return m2; + } + if (h * 6 < 4) { + return m1 + (m2 - m1) * (2 / 3 - h) * 6; + } + return m1; + }; + + var h = +match[1] / 360, + s = Math.min(1, Math.max(0, +match[3] / 100)), + l = Math.min(1, Math.max(0, +match[4] / 100)), + m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s, + m1 = l * 2 - m2; + color = { + r: hue_to_rgb(m1, m2, h + 1 / 3), + g: hue_to_rgb(m1, m2, h), + b: hue_to_rgb(m1, m2, h - 1 / 3) + }; + if (match[5]) { + color.a = Math.min(1, Math.max(0, +match[6] / (match[7] ? 100 : 1))); + } + return color; + } + }], + /** - * Convert a color from hex value or css name to rgb objects + * Convert a color to a standard rgb object. Allowed inputs: + * - rgb object with optional 'a' (alpha) value. + * - css color name + * - #rrggbb, #rrggbbaa, #rgb, #rgba hexadecimal colors + * - rgb(), rgba(), hsl(), and hsla() css colors + * - transparent + * The output object always contains r, g, b on a scale of [0-1]. If an + * alpha value is specified, the output will also contain an 'a' value on a + * scale of [0-1]. Objects already in rgb format are not checked to make + * sure that all parameters are in the range of [0-1], but string inputs + * are so validated. + * + * @param {object|string} color: one of the various input formats. + * @returns {object} an rgb color object, possibly with an 'a' value. If + * the input cannot be converted to a valid color, the input value is + * returned. */ convertColor: function (color) { if (color.r !== undefined && color.g !== undefined && color.b !== undefined) { return color; } + var opacity; if (typeof color === 'string') { if (geo.util.cssColors.hasOwnProperty(color)) { color = geo.util.cssColors[color]; } else if (color.charAt(0) === '#') { - if (color.length === 4) { - /* interpret values of the form #rgb as #rrggbb */ - color = parseInt(color.slice(1), 16); + if (color.length === 4 || color.length === 5) { + /* interpret values of the form #rgb as #rrggbb and #rgba as + * #rrggbbaa */ + if (color.length === 5) { + opacity = parseInt(color.slice(4), 16) / 0xf; + } + color = parseInt(color.slice(1, 4), 16); color = (color & 0xf00) * 0x1100 + (color & 0xf0) * 0x110 + (color & 0xf) * 0x11; - } else { - color = parseInt(color.slice(1), 16); + } else if (color.length === 7 || color.length === 9) { + if (color.length === 9) { + opacity = parseInt(color.slice(7), 16) / 0xff; + } + color = parseInt(color.slice(1, 7), 16); + } + } else if (color === 'transparent') { + opacity = color = 0; + } else if (color.indexOf('(') >= 0) { + for (var idx = 0; idx < geo.util.cssColorConversions.length; idx += 1) { + var match = geo.util.cssColorConversions[idx].regex.exec(color); + if (match) { + return geo.util.cssColorConversions[idx].process(color, match); + } } } } @@ -163,20 +274,26 @@ b: ((color & 0xff)) / 255 }; } + if (opacity !== undefined) { + color.a = opacity; + } return color; }, /** - * Convert a color to a six digit hex value prefixed with #. + * Convert a color to a six or eight digit hex value prefixed with #. */ - convertColorToHex: function (color) { - var value = geo.util.convertColor(color); - if (!value.r && !value.g && !value.b) { + convertColorToHex: function (color, allowAlpha) { + var rgb = geo.util.convertColor(color), value; + if (!rgb.r && !rgb.g && !rgb.b) { value = '#000000'; } else { - value = '#' + ((1 << 24) + (Math.round(value.r * 255) << 16) + - (Math.round(value.g * 255) << 8) + - Math.round(value.b * 255)).toString(16).slice(1); + value = '#' + ((1 << 24) + (Math.round(rgb.r * 255) << 16) + + (Math.round(rgb.g * 255) << 8) + + Math.round(rgb.b * 255)).toString(16).slice(1); + } + if (rgb.a !== undefined && allowAlpha) { + value += (256 + Math.round(rgb.a * 255)).toString(16).slice(1); } return value; }, @@ -817,6 +934,7 @@ plum: 0xdda0dd, powderblue: 0xb0e0e6, purple: 0x800080, + rebeccapurple: 0x663399, red: 0xff0000, rosybrown: 0xbc8f8f, royalblue: 0x4169e1, diff --git a/tests/cases/colors.js b/tests/cases/colors.js index bf233f43d5..46146213ca 100644 --- a/tests/cases/colors.js +++ b/tests/cases/colors.js @@ -1,60 +1,65 @@ +/* global $ */ + describe('geo.util.convertColor', function () { 'use strict'; var geo = require('../test-utils').geo; - describe('From hex string', function () { - it('#000000', function () { - var c = geo.util.convertColor('#000000'); - expect(c).toEqual({ - r: 0, - g: 0, - b: 0 - }); - }); - it('#ffffff', function () { - var c = geo.util.convertColor('#ffffff'); - expect(c).toEqual({ - r: 1, - g: 1, - b: 1 - }); - }); - it('#1256ab', function () { - var c = geo.util.convertColor('#1256ab'); - expect(c).toEqual({ - r: 18 / 255, - g: 86 / 255, - b: 171 / 255 - }); - }); - }); - describe('From short hex string', function () { - it('#000', function () { - var c = geo.util.convertColor('#000'); - expect(c).toEqual({ - r: 0, - g: 0, - b: 0 - }); - }); - it('#fff', function () { - var c = geo.util.convertColor('#fff'); - expect(c).toEqual({ - r: 1, - g: 1, - b: 1 - }); - }); - it('#26b', function () { - var c = geo.util.convertColor('#26b'); - expect(c).toEqual({ - r: 2 / 15, - g: 6 / 15, - b: 11 / 15 + var tests = { + // #rrggbb + '#000000': {r: 0, g: 0, b: 0}, + '#ffffff': {r: 1, g: 1, b: 1}, + '#1256ab': {r: 18 / 255, g: 86 / 255, b: 171 / 255}, + // #rrggbbaa + '#00000000': {r: 0, g: 0, b: 0, a: 0}, + '#ffffffff': {r: 1, g: 1, b: 1, a: 1}, + '#1256ab43': {r: 18 / 255, g: 86 / 255, b: 171 / 255, a: 67 / 255}, + // #rgb + '#000': {r: 0, g: 0, b: 0}, + '#fff': {r: 1, g: 1, b: 1}, + '#26b': {r: 2 / 15, g: 6 / 15, b: 11 / 15}, + // # rgba + '#0001': {r: 0, g: 0, b: 0, a: 1 / 15}, + '#fff2': {r: 1, g: 1, b: 1, a: 2 / 15}, + '#26b3': {r: 2 / 15, g: 6 / 15, b: 11 / 15, a: 3 / 15}, + // css color names + 'red': {r: 1, g: 0, b: 0}, + 'green': {r: 0, g: 128 / 255, b: 0}, + 'blue': {r: 0, g: 0, b: 1}, + 'steelblue': {r: 70 / 255, g: 130 / 255, b: 180 / 255}, + // rgb() and rgba() + 'rgb(18, 86, 171)': {r: 18 / 255, g: 86 / 255, b: 171 / 255}, + 'rgb(18 86 171)': {r: 18 / 255, g: 86 / 255, b: 171 / 255}, + 'rgba(18 86 171)': {r: 18 / 255, g: 86 / 255, b: 171 / 255}, + 'rgb( 18 ,86,171 )': {r: 18 / 255, g: 86 / 255, b: 171 / 255}, + 'rgb(10% 35% 63.2%)': {r: 0.1, g: 0.35, b: 0.632}, + 'rgb(18 120% 300)': {r: 18 / 255, g: 1, b: 1}, + 'rgba(18 86 171 0.3)': {r: 18 / 255, g: 86 / 255, b: 171 / 255, a: 0.3}, + 'rgb(18 86 171 0.3)': {r: 18 / 255, g: 86 / 255, b: 171 / 255, a: 0.3}, + 'rgba(10% 35% 63.2% 40%)': {r: 0.1, g: 0.35, b: 0.632, a: 0.4}, + // hsl() and hsla() + 'hsl(120, 100%, 25%)': {r: 0, g: 0.5, b: 0}, + 'hsla(120, 100%, 25%)': {r: 0, g: 0.5, b: 0}, + 'hsl(120, 100%, 25%, 0.3)': {r: 0, g: 0.5, b: 0, a: 0.3}, + 'hsla(120, 100%, 25%, 30%)': {r: 0, g: 0.5, b: 0, a: 0.3}, + 'hsl(120deg 100% 25%)': {r: 0, g: 0.5, b: 0}, + 'hsl(207 44% 49%)': {r: 0.2744, g: 0.51156, b: 0.7056}, + 'hsl(207 100% 50%)': {r: 0, g: 0.55, b: 1}, + // transparent + 'transparent': {r: 0, g: 0, b: 0, a: 0}, + // unknown strings + 'none': 'none' + }; + + describe('From strings', function () { + $.each(tests, function (key, value) { + it(key, function () { + var c = geo.util.convertColor(key); + expect(c).toEqual(value); }); }); }); + describe('From hex value', function () { it('0x000000', function () { var c = geo.util.convertColor(0x000000); @@ -81,45 +86,7 @@ describe('geo.util.convertColor', function () { }); }); }); - describe('From css name', function () { - it('red', function () { - var c = geo.util.convertColor('red'); - expect(c).toEqual({ - r: 1, - g: 0, - b: 0 - }); - }); - it('green', function () { - var c = geo.util.convertColor('green'); - expect(c).toEqual({ - r: 0, - g: 128 / 255, - b: 0 - }); - }); - it('blue', function () { - var c = geo.util.convertColor('blue'); - expect(c).toEqual({ - r: 0, - g: 0, - b: 1 - }); - }); - it('steelblue', function () { - var c = geo.util.convertColor('steelblue'); - expect(c).toEqual({ - r: 70 / 255, - g: 130 / 255, - b: 180 / 255 - }); - }); - }); describe('Pass through unknown colors', function () { - it('none', function () { - var c = geo.util.convertColor('none'); - expect(c).toEqual('none'); - }); it('object', function () { var c = geo.util.convertColor({ r: 0, @@ -216,4 +183,22 @@ describe('geo.util.convertColorToHex', function () { expect(c).toEqual('#000000'); }); }); + describe('With alpha', function () { + it('no flag', function () { + var c = geo.util.convertColorToHex({r: 0, g: 1, b: 1, a: 0.3}); + expect(c).toEqual('#00ffff'); + }); + it('true flag and alpha', function () { + var c = geo.util.convertColorToHex({r: 0, g: 1, b: 1, a: 0.3}, true); + expect(c).toEqual('#00ffff4d'); + }); + it('true flag and no alpha', function () { + var c = geo.util.convertColorToHex({r: 0, g: 1, b: 1}, true); + expect(c).toEqual('#00ffff'); + }); + it('false flag and alpha', function () { + var c = geo.util.convertColorToHex({r: 0, g: 1, b: 1, a: 0.3}, false); + expect(c).toEqual('#00ffff'); + }); + }); }); diff --git a/tests/cases/feature.js b/tests/cases/feature.js index 36c4a0dfb9..178df220c7 100644 --- a/tests/cases/feature.js +++ b/tests/cases/feature.js @@ -186,6 +186,18 @@ describe('geo.feature', function () { expect(feat.visible(false)).toBe(feat); expect(feat.visible()).toBe(false); expect(feat.getMTime()).toBeGreaterThan(modTime); + + expect(feat.visible(true)).toBe(feat); + var depFeat = geo.feature({layer: layer, renderer: layer.renderer()}); + feat.dependentFeatures([depFeat]); + modTime = depFeat.getMTime(); + expect(feat.visible(false)).toBe(feat); + expect(feat.visible()).toBe(false); + expect(depFeat.visible()).toBe(false); + expect(depFeat.getMTime()).toBeGreaterThan(modTime); + feat.dependentFeatures([]); + expect(feat.visible(true)).toBe(feat); + expect(depFeat.visible()).toBe(false); }); }); describe('Check class accessors', function () { @@ -221,6 +233,12 @@ describe('geo.feature', function () { expect(feat.gcs('EPSG:3857')).toBe(feat); expect(feat.gcs()).toBe('EPSG:3857'); }); + it('dependentFeatures', function () { + expect(feat.dependentFeatures()).toEqual([]); + var depFeat = geo.feature({layer: layer, renderer: layer.renderer()}); + expect(feat.dependentFeatures([depFeat])).toBe(feat); + expect(feat.dependentFeatures()).toEqual([depFeat]); + }); it('bin', function () { expect(feat.bin()).toBe(0); expect(feat.bin(5)).toBe(feat); diff --git a/tests/cases/polygonFeature.js b/tests/cases/polygonFeature.js index 570600aed0..e7eca2662b 100644 --- a/tests/cases/polygonFeature.js +++ b/tests/cases/polygonFeature.js @@ -118,6 +118,25 @@ describe('geo.polygonFeature', function () { polygon._init({style: {data: pos}}); expect(polygon.data()).toEqual(pos); }); + + it('style', function () { + mockVGLRenderer(); + map = create_map(); + // we have to use a valid renderer so that the stroke can be enabled. + layer = map.createLayer('feature', {renderer: 'vgl'}); + polygon = geo.polygonFeature({layer: layer}); + polygon._init(); + expect(polygon.style().stroke).toBe(false); + expect(polygon.dependentFeatures()).toEqual([]); + polygon.style('stroke', true); + expect(polygon.style().stroke).toBe(true); + expect(polygon.dependentFeatures().length).toEqual(1); + polygon.style({stroke: false}); + expect(polygon.style().stroke).toBe(false); + expect(polygon.dependentFeatures()).toEqual([]); + map.deleteLayer(layer); + restoreVGLRenderer(); + }); }); describe('Public utility methods', function () { diff --git a/tests/cases/quadFeature.js b/tests/cases/quadFeature.js index af19415092..2ba3d1a27f 100644 --- a/tests/cases/quadFeature.js +++ b/tests/cases/quadFeature.js @@ -527,7 +527,7 @@ describe('geo.quadFeature', function () { /* This is a basic integration test of geo.canvas.quadFeature. */ describe('geo.canvas.quadFeature', function () { - var map, layer, quads, counts; + var map, layer, quads, counts, canvasElement; it('load preview image', load_preview_image); it('basic usage', function () { var buildTime; @@ -575,6 +575,38 @@ describe('geo.quadFeature', function () { return window._canvasLog.counts.drawImage >= counts.drawImage + 200 && window._canvasLog.counts.clearRect >= counts.clearRect + 1; }); + it('canvas quads', function () { + canvasElement = document.createElement('canvas'); + canvasElement.width = 640; + canvasElement.height = 480; + var context = canvasElement.getContext('2d'); + context.fillStyle = 'green'; + context.fillRect(0, 0, 640, 480); + var data = [{ + ul: {x: -98, y: 29}, + ur: {x: -68, y: 29}, + ll: {x: -98, y: 9}, + lr: {x: -68, y: 9}, + previewImage: null, + image: canvasElement + }]; + counts = $.extend({}, window._canvasLog.counts); + logCanvas2D(true); // enable call logging + quads.data(data); + map.draw(); + }); + waitForIt('next render canvas D', function () { + return window._canvasLog.counts.drawImage >= counts.drawImage + 1 && + window._canvasLog.counts.clearRect >= counts.clearRect + 1; + }); + it('confirm canvas quads', function () { + var log = window._canvasLog.log, i = log.length - 1; + while (i > 0 && log[i].func !== 'drawImage') { + i -= 1; + } + expect(log[i].arg[0]).toBe(canvasElement); + logCanvas2D(false); // disable call logging + }); it('_exit', function () { var buildTime = quads.buildTime().getMTime(); layer.deleteFeature(quads); @@ -594,7 +626,6 @@ describe('geo.quadFeature', function () { $.each(testQuads, function (idx, quad) { delete quad._cachedQuad; }); - logCanvas2D(); map = create_map(); layer = map.createLayer('feature', {renderer: 'd3'}); quads = layer.createFeature('quad', {style: testStyle, data: testQuads});