Add text opacity support.
This adds a utility function to combine a color with an optional opacity
and an opacity value into a single color with opacity value.
MODIFY
examples/annotations/index.jade
examples/annotations/index.jade
@@ -117,7 +117,7 @@ block append mainContent
.form-group(annotation-types='all', title='Horizontal alignment')
label(for='edit-textAlign') Horizontal Align.
select#edit-textAlign(option='textAlign', format='text', optiontype='label')
- option(value='middle') Middle
+ option(value='center') Center
option(value='start') Start
option(value='end') End
option(value='left') Left

MODIFY
examples/annotations/main.js
examples/annotations/main.js
@@ -333,7 +333,7 @@ $(function () {
switch (format) {
case 'color':
// always show colors as hex values
- value = geo.util.convertColorToHex(value || {r: 0, g: 0, b: 0});
+ value = geo.util.convertColorToHex(value || {r: 0, g: 0, b: 0}, 'needed');
break;
}
if (value !== undefined) {

MODIFY
src/annotation.js
src/annotation.js
@@ -273,6 +273,13 @@ var annotation = function (type, args) {
}
if (arg2 === undefined) {
m_options = $.extend(true, m_options, arg1);
+ /* For style objects, reextend them without recursiion. This allows
+ * setting colors without an opacity field, for instance. */
+ ['style', 'editStyle', 'labelStyle'].forEach(function (key) {
+ if (arg1[key] !== undefined) {
+ $.extend(m_options[key], arg1[key]);
+ }
+ });
} else {
m_options[arg1] = arg2;
}
@@ -340,17 +347,17 @@ var annotation = function (type, args) {
* @returns {object|this} Either the entire style object, the value of a
* specific style, or the current class instance.
*/
- this.editstyle = function (arg1, arg2) {
+ this.editStyle = function (arg1, arg2) {
if (arg1 === undefined) {
- return m_options.editstyle;
+ return m_options.editStyle;
}
if (typeof arg1 === 'string' && arg2 === undefined) {
- return m_options.editstyle[arg1];
+ return m_options.editStyle[arg1];
}
if (arg2 === undefined) {
- m_options.editstyle = $.extend(true, m_options.editstyle, arg1);
+ m_options.editStyle = $.extend(true, m_options.editStyle, arg1);
} else {
- m_options.editstyle[arg1] = arg2;
+ m_options.editStyle[arg1] = arg2;
}
this.modified();
return this;
@@ -537,7 +544,7 @@ var annotation = function (type, args) {
value = util.ensureFunction(objStyle[key])();
if (value !== undefined) {
if (key.toLowerCase().match(/color$/)) {
- value = util.convertColorToHex(value);
+ value = util.convertColorToHex(value, 'needed');
}
obj.properties[key] = value;
}
@@ -547,7 +554,7 @@ var annotation = function (type, args) {
value = util.ensureFunction(objLabelStyle[key])();
if (value !== undefined) {
if (key.toLowerCase().match(/color$/)) {
- value = util.convertColorToHex(value);
+ value = util.convertColorToHex(value, 'needed');
}
obj.properties['label' + key.charAt(0).toUpperCase() + key.slice(1)] = value;
}
@@ -596,7 +603,7 @@ var annotation = function (type, args) {
* This uses styles for polygons, including `fill`, `fillColor`,
* `fillOpacity`, `stroke`, `strokeWidth`, `strokeColor`, and
* `strokeOpacity`.
- * @param {object} [args.editstyle] The style to apply to a rectangle in edit
+ * @param {object} [args.editStyle] The style to apply to a rectangle in edit
* mode. This uses styles for polygons and lines, including `fill`,
* `fillColor`, `fillOpacity`, `stroke`, `strokeWidth`, `strokeColor`, and
* `strokeOpacity`.
@@ -619,7 +626,7 @@ var rectangleAnnotation = function (args) {
strokeWidth: 3,
uniformPolygon: true
},
- editstyle: {
+ editStyle: {
fill: true,
fillColor: {r: 0.3, g: 0.3, b: 0.3},
fillOpacity: 0.25,
@@ -705,7 +712,7 @@ var rectangleAnnotation = function (args) {
features = [{
polygon: {
polygon: opt.corners,
- style: opt.editstyle
+ style: opt.editStyle
}
}];
}
@@ -887,7 +894,7 @@ registerAnnotation('rectangle', rectangleAnnotation, rectangleRequiredFeatures);
* This uses styles for polygons, including `fill`, `fillColor`,
* `fillOpacity`, `stroke`, `strokeWidth`, `strokeColor`, and
* `strokeOpacity`.
- * @param {object} [args.editstyle] The style to apply to a polygon in edit
+ * @param {object} [args.editStyle] The style to apply to a polygon in edit
* mode. This uses styles for polygons and lines, including `fill`,
* `fillColor`, `fillOpacity`, `stroke`, `strokeWidth`, `strokeColor`, and
* `strokeOpacity`.
@@ -912,7 +919,7 @@ var polygonAnnotation = function (args) {
strokeWidth: 3,
uniformPolygon: true
},
- editstyle: {
+ editStyle: {
closed: false,
fill: true,
fillColor: {r: 0.3, g: 0.3, b: 0.3},
@@ -956,7 +963,7 @@ var polygonAnnotation = function (args) {
features[1] = {
polygon: {
polygon: opt.vertices,
- style: opt.editstyle
+ style: opt.editStyle
}
};
}
@@ -964,7 +971,7 @@ var polygonAnnotation = function (args) {
features[2] = {
line: {
line: opt.vertices,
- style: opt.editstyle
+ style: opt.editStyle
}
};
}
@@ -1145,7 +1152,7 @@ registerAnnotation('polygon', polygonAnnotation, polygonRequiredFeatures);
* @param {object} [args.style] The style to apply to a finished line.
* This uses styles for lines, including `strokeWidth`, `strokeColor`,
* `strokeOpacity`, `strokeOffset`, `closed`, `lineCap`, and `lineJoin`.
- * @param {object} [args.editstyle] The style to apply to a line in edit
+ * @param {object} [args.editStyle] The style to apply to a line in edit
* mode. This uses styles for lines, including `strokeWidth`,
* `strokeColor`, `strokeOpacity`, `strokeOffset`, `closed`, `lineCap`,
* and `lineJoin`.
@@ -1176,7 +1183,7 @@ var lineAnnotation = function (args) {
lineCap: 'butt',
lineJoin: 'miter'
},
- editstyle: {
+ editStyle: {
line: function (d) {
/* Return an array that has the same number of items as we have
* vertices. */
@@ -1212,7 +1219,7 @@ var lineAnnotation = function (args) {
features = [{
line: {
line: opt.vertices,
- style: opt.editstyle
+ style: opt.editStyle
}
}];
break;
@@ -1459,7 +1466,7 @@ registerAnnotation('line', lineAnnotation, lineRequiredFeatures);
* zoom level. If it is `true`, the radius is based on the zoom level at
* first instantiation. Otherwise, if it is a number, the radius is used
* at that zoom level.
- * @param {object} [args.editstyle] The style to apply to a line in edit
+ * @param {object} [args.editStyle] The style to apply to a line in edit
* mode. This uses styles for lines.
*/
var pointAnnotation = function (args) {

MODIFY
src/canvas/textFeature.js
src/canvas/textFeature.js
@@ -89,7 +89,7 @@ var canvas_textFeature = function (arg) {
var data = m_this.data(),
posFunc = m_this.style.get('position'),
textFunc = m_this.style.get('text'),
- fontFromSubValues, text, pos, val, opac;
+ fontFromSubValues, text, pos, val;
/* If any of the font styles other than `font` have values, then we need to
* construct a single font value from the subvalues. Otherwise, we can
@@ -97,11 +97,20 @@ var canvas_textFeature = function (arg) {
fontFromSubValues = ['fontStyle', 'fontVariant', 'fontWeight', 'fontStretch', 'fontSize', 'lineHeight', 'fontFamily'].some(function (key) {
return m_this.style(key) !== null && m_this.style(key) !== undefined;
});
+ /* Clear the canvas property buffer */
+ m_this._canvasProperty();
data.forEach(function (d, i) {
val = m_this.style.get('visible')(d, i);
if (!val && val !== undefined) {
return;
}
+ val = util.convertColorAndOpacity(
+ m_this.style.get('color')(d, i), m_this.style.get('textOpacity')(d, i));
+ if (val.a === 0) {
+ return;
+ }
+ m_this._canvasProperty(context2d, 'globalAlpha', val.a);
+ m_this._canvasProperty(context2d, 'fillStyle', util.convertColorToHex(val));
// TODO: get the position position without transform. If it is outside
// of the map to an extent that there is no chance of text showing,
// skip further processing.
@@ -110,24 +119,11 @@ var canvas_textFeature = function (arg) {
m_this._canvasProperty(context2d, 'font', m_this.getFontFromStyles(fontFromSubValues, d, i));
m_this._canvasProperty(context2d, 'textAlign', m_this.style.get('textAlign')(d, i) || 'center');
m_this._canvasProperty(context2d, 'textBaseline', m_this.style.get('textBaseline')(d, i) || 'middle');
- val = m_this.style.get('color')(d, i) || {r: 0, g: 0, b: 0};
- opac = m_this.style.get('textOpacity')(d, i);
- if (opac === undefined || opac === null) {
- opac = 1;
- }
- if (val.a !== undefined && val.a !== null && val.a !== 1) {
- opac *= val.a;
- }
- if (opac <= 0) {
- return;
- }
- m_this._canvasProperty(context2d, 'globalAlpha', opac > 1 ? 1 : opac);
- m_this._canvasProperty(context2d, 'fillStyle', util.convertColorToHex(val));
// TODO: fetch and use other properties:
// 'direction', 'rotation', 'rotateWithMap', 'scale',
// 'scaleWithMap', 'offset', 'width', 'shadowColor', 'shadowOffset',
- // 'shadowBlur', 'shadowRotate'
+ // 'shadowBlur', 'shadowRotate', 'shadowOpacity'
m_this._canvasProperty(context2d, 'direction', 'inherit'); // ltr, rtl, inherit
/*
ctx.shadowColor = "black";

MODIFY
src/textFeature.js
src/textFeature.js
@@ -39,8 +39,8 @@ var feature = require('./feature');
* of `ltr`, `rtl`, or `inherit`.
* @property {geo.geoColor|function} [style.color='black'] Text color. May
* include opacity.
- * @property {number|function} [style.opacity=1] The opacity of the text. If
- * the color includes opacity, this is combined with that value.
+ * @property {number|function} [style.textOpacity=1] The opacity of the text.
+ * If the color includes opacity, this is combined with that value.
* @property {number|function} [style.rotation=0] Text rotation in radians.
* @property {boolean|function} [style.rotateWithMap=false] If truthy, rotate
* the text when the map rotates. Otherwise, the text is always in the
@@ -56,6 +56,9 @@ var feature = require('./feature');
* pixels. `null` or 0 for no maximum.
* @property {geo.geoColor|function} [style.shadowColor='black'] Text shadow
* color. May include opacity.
+ * @property {number|function} [style.shadowOpacity=1] The opacity of the
+ * shadow. If the color includes opacity, this is combined with that
+ * value.
* @property {geo.screenPosition|function} [style.shadowOffset] Offset for a
* text shadow. This is applied before rotation.
* @property {number|null|function} [style.shadowBlur] If not null, add a text
@@ -179,8 +182,8 @@ textFeature.usedStyles = [
'visible', 'font', 'fontStyle', 'fontVariant', 'fontWeight', 'fontStretch',
'fontSize', 'lineHeight', 'fontFamily', 'textAlign', 'textBaseline',
'direction', 'color', 'textOpacity', 'rotation', 'rotateWithMap', 'scale',
- 'scaleWithMap', 'offset', 'width', 'shadowColor', 'shadowOffset',
- 'shadowBlur', 'shadowRotate'
+ 'scaleWithMap', 'offset', 'width', 'shadowColor', 'shadowOpacity',
+ 'shadowOffset', 'shadowBlur', 'shadowRotate'
];
/**

MODIFY
src/util/index.js
src/util/index.js
@@ -257,18 +257,53 @@ var util = module.exports = {
b: ((color & 0xff)) / 255
};
}
- if (opacity !== undefined) {
+ if (opacity !== undefined && color && color.r !== undefined) {
color.a = opacity;
}
return color;
},
+ /**
+ * Convert a color (possibly with opacity) and an optional opacity value to
+ * a color object that always has opacity. The opacity is guaranteed to be
+ * within [0-1]. A valid color object is always returned.
+ *
+ * @param {geo.geoColor} [color] Any valid color input. If an invalid value
+ * or no value is supplied, the `defaultColor` is used.
+ * @param {number} [opacity=1] A value from [0-1]. This is multipled with
+ * the opacity from `color`.
+ * @param {geo.geoColorObject} [defaultColor={r: 0, g: 0, b: 0}] The color
+ * to use if an invalid color is supplied.
+ * @returns {geo.geoColorObject} An rgba color object.
+ * @memberof geo.util
+ */
+ convertColorAndOpacity: function (color, opacity, defaultColor) {
+ color = util.convertColor(color);
+ if (!color || color.r === undefined || color.g === undefined || color.b === undefined) {
+ color = util.convertColor(defaultColor || {r: 0, g: 0, b: 0});
+ }
+ if (!color || color.r === undefined || color.g === undefined || color.b === undefined) {
+ color = {r: 0, g: 0, b: 0};
+ }
+ color = {
+ r: isFinite(color.r) && color.r >= 0 ? (color.r <= 1 ? +color.r : 1) : 0,
+ g: isFinite(color.g) && color.g >= 0 ? (color.g <= 1 ? +color.g : 1) : 0,
+ b: isFinite(color.b) && color.b >= 0 ? (color.b <= 1 ? +color.b : 1) : 0,
+ a: isFinite(color.a) && color.a >= 0 ? (color.a <= 1 ? +color.a : 1) : 1
+ };
+ if (isFinite(opacity) && opacity < 1) {
+ color.a = opacity <= 0 ? 0 : color.a * opacity;
+ }
+ return color;
+ },
+
/**
* Convert a color to a six or eight digit hex value prefixed with #.
*
* @param {geo.geoColorObject} color The color object to convert.
* @param {boolean} [allowAlpha] If truthy and `color` has a defined `a`
- * value, include the alpha channel in the output.
+ * value, include the alpha channel in the output. If `'needed'`, only
+ * include the alpha channel if it is set and not 1.
* @returns {string} A color string.
* @memberof geo.util
*/
@@ -281,7 +316,7 @@ var util = module.exports = {
(Math.round(rgb.g * 255) << 8) +
Math.round(rgb.b * 255)).toString(16).slice(1);
}
- if (rgb.a !== undefined && allowAlpha) {
+ if (rgb.a !== undefined && allowAlpha && (rgb.a < 1 || allowAlpha !== 'needed')) {
value += (256 + Math.round(rgb.a * 255)).toString(16).slice(1);
}
return value;

MODIFY
tests/cases/annotation.js
tests/cases/annotation.js
@@ -120,9 +120,9 @@ describe('geo.annotation', function () {
expect(ann.options().coordinates).toBe(undefined);
expect(ann._coordinates()).toBe(testval);
});
- it('style and editstyle', function () {
+ it('style and editStyle', function () {
var ann = geo.annotation.annotation('test', {
- layer: layer, style: {testopt: 30}, editstyle: {testopt: 50}});
+ layer: layer, style: {testopt: 30}, editStyle: {testopt: 50}});
expect(ann.options('style').testopt).toBe(30);
expect(ann.style().testopt).toBe(30);
expect(ann.style('testopt')).toBe(30);
@@ -130,13 +130,13 @@ describe('geo.annotation', function () {
expect(ann.style().testopt).toBe(40);
expect(ann.style({testopt: 30})).toBe(ann);
expect(ann.style().testopt).toBe(30);
- expect(ann.options('editstyle').testopt).toBe(50);
- expect(ann.editstyle().testopt).toBe(50);
- expect(ann.editstyle('testopt')).toBe(50);
- expect(ann.editstyle('testopt', 60)).toBe(ann);
- expect(ann.editstyle().testopt).toBe(60);
- expect(ann.editstyle({testopt: 50})).toBe(ann);
- expect(ann.editstyle().testopt).toBe(50);
+ expect(ann.options('editStyle').testopt).toBe(50);
+ expect(ann.editStyle().testopt).toBe(50);
+ expect(ann.editStyle('testopt')).toBe(50);
+ expect(ann.editStyle('testopt', 60)).toBe(ann);
+ expect(ann.editStyle().testopt).toBe(60);
+ expect(ann.editStyle({testopt: 50})).toBe(ann);
+ expect(ann.editStyle().testopt).toBe(50);
});
it('coordinates', function () {
var ann = geo.annotation.annotation('test', {layer: layer});

Add text opacity support.

This adds a utility function to combine a color with an optional opacity
and an opacity value into a single color with opacity value.