diff --git a/docs/.eslintrc b/docs/.eslintrc index 79ad8a0c5e3..0b620fc837a 100644 --- a/docs/.eslintrc +++ b/docs/.eslintrc @@ -1,13 +1,5 @@ { - "parser": "espree", - "parserOptions": { - "ecmaVersion": 5 - }, - "env": { - "es6": false, - "browser": true - }, - "globals": { - "Uint8Array": true + "rules": { + "flowtype/require-valid-file-annotation": [0] } } diff --git a/docs/_posts/examples/.eslintrc b/docs/_posts/examples/.eslintrc index c33e331b715..05487f52657 100644 --- a/docs/_posts/examples/.eslintrc +++ b/docs/_posts/examples/.eslintrc @@ -1,11 +1,16 @@ { + "parser": "espree", + "parserOptions": { + "ecmaVersion": 5 + }, "plugins": ["html"], "globals": { "mapboxgl": true, "MapboxGeocoder": true, "MapboxDirections": true, "turf": true, - "d3": true + "d3": true, + "Uint8Array": true }, "rules": { "flowtype/require-valid-file-annotation": [0], @@ -17,6 +22,7 @@ "prefer-template": "off" }, "env": { + "es6": false, "browser": true } } diff --git a/docs/style-spec/_generate/expression-types.js b/docs/style-spec/_generate/expression-types.js new file mode 100644 index 00000000000..2a339156fc1 --- /dev/null +++ b/docs/style-spec/_generate/expression-types.js @@ -0,0 +1,102 @@ +'use strict'; +require('flow-remove-types/register'); + +const toString = require('../../../src/style-spec/function/types').toString; +const CompoundExpression = require('../../../src/style-spec/function/compound_expression').CompoundExpression; + +// registers compound expressions +require('../../../src/style-spec/function/definitions'); + +const results = { + array: [{ + type: 'Array', + parameters: ['Value'], + }, { + type: 'Array', + parameters: [ + {name: 'type', type: '"string" | "number" | "boolean"'}, + 'Value' + ], + }, { + type: 'Array', + parameters: [ + {name: 'type', type: '"string" | "number" | "boolean"'}, + {name: 'N', type: 'number (literal)'}, + 'Value' + ] + }], + at: [{ + type: 'T', + parameters: ['number', 'Array'] + }], + case: [{ + type: 'T', + parameters: [{ repeat: ['boolean', 'T'] }, 'T'] + }], + coalesce: [{ + type: 'T', + parameters: [{repeat: 'T'}] + }], + contains: [{ + type: 'boolean', + parameters: ['T', 'Array | Array'] + }], + curve: [{ + type: 'T', + parameters: [ + {name: 'input', type: 'number'}, + {name: 'interpolation', type: '["step"] | ["linear"] | ["exponential", base] | ["cubic-bezier", x1, y1, x2, y2 ]'}, + {repeat: ['number', 'T']} + ] + }], + let: [{ + type: 'T', + parameters: [{ repeat: ['alphanumeric string literal', 'any']}, 'T'] + }], + literal: [{ + type: 'Array', + parameters: ['[...] (JSON array literal)'] + }, { + type: 'Object', + parameters: ['{...} (JSON object literal)'] + }], + match: [{ + type: 'U', + parameters: [ + {name: 'input', type: 'T'}, + {repeat: ['T | [T, T, ...]', 'U']}, + 'U' + ] + }], + var: [{ + type: 'the type of the bound expression', + parameters: ['previously bound variable name'] + }] +}; + +for (const name in CompoundExpression.definitions) { + const definition = CompoundExpression.definitions[name]; + if (Array.isArray(definition)) { + results[name] = [{ + type: toString(definition[0]), + parameters: processParameters(definition[1]) + }]; + } else { + results[name] = definition.overloads.map((o) => { + return { + type: toString(definition.type), + parameters: processParameters(o[0]) + }; + }); + } +} + +function processParameters(params) { + if (Array.isArray(params)) { + return params.map(toString); + } else { + return [{repeat: [toString(params.type)]}]; + } +} + +module.exports = results; diff --git a/docs/style-spec/_generate/expression.html b/docs/style-spec/_generate/expression.html new file mode 100644 index 00000000000..0f57be11346 --- /dev/null +++ b/docs/style-spec/_generate/expression.html @@ -0,0 +1,13 @@ +
+ <%= name %> + <% for (const overload of expressionTypes[name]) { %> +
+{% highlight javascript %} +[<%= JSON.stringify(name) %><%=renderParams(overload.parameters) %>]: <%= overload.type %> +{% endhighlight %} +
+ <% } %> + <% if (expressionDocs[name] && expressionDocs[name].doc.trim().length) { %> +
<%= md(expressionDocs[name].doc) %>
+ <% } %> +
diff --git a/docs/style-spec/_generate/generate.js b/docs/style-spec/_generate/generate.js index e76b572755e..5cf1ec638f9 100755 --- a/docs/style-spec/_generate/generate.js +++ b/docs/style-spec/_generate/generate.js @@ -9,8 +9,11 @@ var _ = require('lodash'); var remark = require('remark'); var html = require('remark-html'); +var expressionTypes = require('./expression-types'); + + function tmpl(x, options) { - return _.template(fs.readFileSync(path.join(__dirname, x), 'utf-8'), options); + return _.template(fs.readFileSync(path.join(__dirname, x), 'utf-8'), options); } var index = tmpl('index.html', { @@ -23,8 +26,36 @@ var index = tmpl('index.html', { return remark().use(html).process(markdown); } } + }), + expressions: Object.keys(expressionTypes).sort((a, b) => a.localeCompare(b)), + renderExpression: tmpl('expression.html', { + imports: { + _: _, + expressionDocs: ref['expression_name'].values, + expressionTypes: expressionTypes, + renderParams: renderParams, + md: function(markdown) { + return remark().use(html).process(markdown) + } + } }) } }); +function renderParams (params) { + let result = ''; + for (const t of params) { + result += ', '; + if (typeof t === 'string') { + result += t; + } else if (t.name) { + result += `${JSON.stringify(t.name)}: ${t.type}`; + } else if (t.repeat) { + const repeated = renderParams(t.repeat); + result += `${repeated.slice(2)}${repeated}, ...`; + } + } + return result; +} + fs.writeFileSync(path.join(__dirname, '../index.html'), index({ ref: ref })); diff --git a/docs/style-spec/_generate/index.html b/docs/style-spec/_generate/index.html index 7b18bc477e3..12ae8ded7c1 100755 --- a/docs/style-spec/_generate/index.html +++ b/docs/style-spec/_generate/index.html @@ -828,67 +828,20 @@

Fun
- -
Required (except for identity functions) array.
-
Functions are defined in terms of input and output values. A set of one input value and one output value is known as a "stop."
-
-
- -
Optional string.
-
If specified, the function will take the specified feature property as an input. See Zoom Functions and Property Functions for more information.
-
-
- -
Optional number. Default is <%= ref.function.base.default %>.
-
The exponential base of the interpolation curve. It controls the rate at which the function output increases. Higher values make the output increase more towards the high end of the range. With values close to 1 the output increases linearly.
-
-
- -
Optional enum. One of identity, exponential, interval, categorical.
-
-
identity
-
functions return their input as their output.
-
exponential
-
functions generate an output by interpolating between stops just less than and just greater than the - function input. The domain (input value) must be numeric, and the style property must support - interpolation. Style properties that support interpolation are marked marked with , the - "exponential" symbol, and exponential is the default function type for these properties.
-
interval
-
functions return the output value of the stop just less than the function input. The domain (input - value) must be numeric. Any style property may use interval functions. For properties marked with - , the "interval" - symbol, this is the default function type.
-
categorical
-
functions return the output value of the stop equal to the function input.
-
-
-
- -
A value to serve as a fallback function result when a value isn't otherwise available. It is used in the following circumstances:
-
    -
  • In categorical functions, when the feature value does not match any of the stop domain values.
  • -
  • In property and zoom-and-property functions, when a feature does not contain a value for the specified property.
  • -
  • In identity functions, when the feature value is not valid for the style property (for example, if the function is being used for a circle-color property but the feature property value is not a string or not a valid color).
  • -
  • In interval or exponential property and zoom-and-property functions, when the feature value is not numeric.
  • -
-
If no default is provided, the style property's default is used in these circumstances.
-
-
- -
Optional enum. One of rgb, lab, hcl.
-
The color space in which colors interpolated. Interpolating colors in perceptual color spaces like LAB and HCL tend to produce color ramps that look more consistent and produce colors that can be differentiated more easily than those interpolated in RGB space.
-
-
rgb
-
Use the RGB color space to interpolate color values
-
lab
-
Use the LAB color space to interpolate color values.
-
hcl
-
Use the HCL color space to interpolate color values, interpolating the Hue, Chroma, and Luminance channels individually.
-
+ +
Required expression.
+
+ An expression defines how one or more feature property values + and/or the current zoom level are combined using logical, + mathematical, string, or color operations to produce the + appropriate style value. See + expressions for syntax details. +
+

In prior versions of the style specification, functions were defined using stops, property, default values.

+ @@ -907,6 +860,13 @@

Fun

+ + + + + + + @@ -958,7 +918,7 @@

Fun

- + @@ -966,80 +926,104 @@

Fun

>= 2.0.0 >= 0.1.0
expressions>= 0.40.0Not yet supportedNot yet supportedNot yet supported
property >= 0.18.0
colorSpace>= 0.26.00.26.0 - 0.39.1 Not yet supported Not yet supported Not yet supported
-

Zoom functions allow the appearance of a map feature to change with map’s zoom level. Zoom functions can be used to create the illusion of depth and control data density. Each stop is an array with two elements: the first is a zoom level and the second is a function output value.

+

A property function is any function defined using an expression that includes a reference to ["properties"]. Property functions allow the appearance of a map feature to change with its properties. They can be used to visually differentate types of features within the same layer or create data visualizations. Note that support for property functions is not available across all properties and platforms at this time.

-
- {% highlight js %} +
+ {% highlight js %} { - "circle-radius": { - "stops": [ - - // zoom is 5 -> circle radius will be 1px - [5, 1], - - // zoom is 10 -> circle radius will be 2px - [10, 2] - + "circle-color": { + "expression": [ + 'rgb', + // red is higher when feature.properties.temperature is higher + ["get", "temperature"], + 0, + // blue is higher when feature.properties.temperature is lower + ["-", 100, ["get", "temperature"]] ] } } - {% endhighlight %} -
+{% endhighlight %} +
-

The rendered values of color, number, and array properties are intepolated between stops. Enum, boolean, and string property values cannot be intepolated, so their rendered values only change at the specified stops.

+A zoom function is any function defined using an expression that includes a reference to ["zoom"]. Such functions allow the appearance of a map feature change with map’s zoom level. Zoom functions can be used to create the illusion of depth and control data density. -

There is an important difference between the way that zoom functions render for layout and paint properties. Paint properties are continuously re-evaluated whenever the zoom level changes, even fractionally. The rendered value of a paint property will change, for example, as the map moves between zoom levels 4.1 and 4.6. Layout properties, on the other hand, are evaluated only once for each integer zoom level. To continue the prior example: the rendering of a layout property will not change between zoom levels 4.1 and 4.6, no matter what stops are specified; but at zoom level 5, the function will be re-evaluated according to the function, and the property's rendered value will change. (You can include fractional zoom levels in a layout property zoom function, and it will affect the generated values; but, still, the rendering will only change at integer zoom levels.)

+ A zoom function must be of the following form (see curves for more details): -

Property functions allow the appearance of a map feature to change with its properties. Property functions can be used to visually differentate types of features within the same layer or create data visualizations. Each stop is an array with two elements, the first is a property input value and the second is a function output value. Note that support for property functions is not available across all properties and platforms at this time.

+
+{% highlight js %} +{ + "expression": [ "curve", interpolation, ["zoom"], ... ] +} +{% endhighlight %} +
+ + Or: -
- {% highlight js %} +
+{% highlight js %} { - "circle-color": { - "property": "temperature", - "stops": [ + "expression": [ "coalesce", [ "curve", interpolation, ["zoom"], ... ], ... ] +} +{% endhighlight %} +
+ +

(["zoom"] may not appear anywhere else in the expression.)

- // "temperature" is 0 -> circle color will be blue - [0, 'blue'], - // "temperature" is 100 -> circle color will be red - [100, 'red'] +

Example: a zoom-only function

+
+{% highlight js %} +{ + "circle-radius": { + "expression": [ + "curve", "linear", ["zoom"], + // zoom is 5 (or less) -> circle radius will be 1px + // zoom is 10 (or greater) -> circle radius will be 2px + 5, 1, 10, 2 ] } } - {% endhighlight %} -
+{% endhighlight %} +
-

Zoom-and-property functions allow the appearance of a map feature to change with both its properties and zoom. Each stop is an array with two elements, the first is an object with a property input value and a zoom, and the second is a function output value. Note that support for property functions is not yet complete.

+

Example: a zoom-and-property function

-
- {% highlight js %} +

Using property functions as the output value for one or more zoom stops + allows the appearance of a map feature to change with both the zoom level + and the feature's properties.

+ +
+{% highlight js %} { "circle-radius": { - "property": "rating", - "stops": [ + "expression": [ + "curve", "linear", ["zoom"], // zoom is 0 and "rating" is 0 -> circle radius will be 0px - [{zoom: 0, value: 0}, 0], - // zoom is 0 and "rating" is 5 -> circle radius will be 5px - [{zoom: 0, value: 5}, 5], - - // zoom is 20 and "rating" is 0 -> circle radius will be 0px - [{zoom: 20, value: 0}, 0], - - // zoom is 20 and "rating" is 5 -> circle radius will be 20px - [{zoom: 20, value: 5}, 20] - + 0, ["number", [ "get", "rating" ], + // zoom is 20 and "rating" is 0 -> circle radius will be 4 * 0 = 0px + // zoom is 20 and "rating" is 5 -> circle radius will be 4 * 5 = 20px + 10, [ "*", 4, [ "number", [ "get", ["properties"] ] ] ] ] } } - {% endhighlight %} -
+{% endhighlight %} +
+ +

There is an important difference between the way that zoom functions render for layout and paint properties. Paint properties are continuously re-evaluated whenever the zoom level changes, even fractionally. The rendered value of a paint property will change, for example, as the map moves between zoom levels 4.1 and 4.6. Layout properties, on the other hand, are evaluated only once for each integer zoom level. To continue the prior example: the rendering of a layout property will not change between zoom levels 4.1 and 4.6, no matter what stops are specified; but at zoom level 5, the function will be re-evaluated according to the function, and the property's rendered value will change. (You can include fractional zoom levels in a layout property zoom function, and it will affect the generated values; but, still, the rendering will only change at integer zoom levels.)

+
+ +

Expression

+ <% for (var name of expressions) { %> + <%= renderExpression({name: name}) %> + <% } %> +
+

Filter

diff --git a/docs/style-spec/expressions.json b/docs/style-spec/expressions.json new file mode 100644 index 00000000000..1a67ea01dc5 --- /dev/null +++ b/docs/style-spec/expressions.json @@ -0,0 +1,7 @@ +[ + { + "name": "curve", + "group": "curve", + "doc": "Curve" + } +] diff --git a/src/style-spec/reference/v8.json b/src/style-spec/reference/v8.json index 964db34408e..0af248fa9ae 100644 --- a/src/style-spec/reference/v8.json +++ b/src/style-spec/reference/v8.json @@ -1833,6 +1833,195 @@ "minimum": 1, "doc": "An expression defines a function that can be used for data-driven style properties or feature filters." }, + "expression_name": { + "doc": "", + "type": "enum", + "values": { + "let": { + "doc": "Binds expressions to named variables, which can then be referenced in the result expression using [\"var\", \"variable_name\"]." + }, + "var": { + "doc": "References variable bound using \"let\"." + }, + "literal": { + "doc": "Provides a literal array or object value." + }, + "array": { + "doc": "Asserts that the input is an array (optinally with a specific item type and length)." + }, + "at": { + "doc": "Retrieves an item from an array." + }, + "contains": { + "doc": "Tests whether a value exists in an array." + }, + "case": { + "doc": "Yields the value of the first output expression whose corresponding test evaluates to true." + }, + "match": { + "doc": "Yields the output value whose label value matches the input, or the fallback value if no match is found." + }, + "coalesce": { + "doc": "Evaluates each expression in turn until the first non-null, non-error value is obtained, and returns that value" + }, + "curve": { + "doc": "Interpolates an output value from the given input/output pairs using the specified interpolation strategy. The input levels must be numeric literals in strictly ascending order." + }, + "ln2": { + "doc": "Returns mathematical constant ln(2)." + }, + "pi": { + "doc": "Returns the mathematical constant pi." + }, + "e": { + "doc": "Returns the mathematical constant e." + }, + "typeof": { + "doc": "Returns a string describing the type of the given value." + }, + "string": { + "doc": "Asserts that the input value is a String." + }, + "number": { + "doc": "Asserts that the input value is a Number." + }, + "boolean": { + "doc": "Asserts that the input value is a Boolean." + }, + "object": { + "doc": "Asserts that the input value is an Objects." + }, + "to-string": { + "doc": "Coerces the input value to a String." + }, + "to-number": { + "doc": "Coerces the input value to a Number, if possible." + }, + "to-boolean": { + "doc": "Coerces the input value to a Boolean." + }, + "to-rgba": { + "doc": "Returns the an array of the given color's r, g, b, a components." + }, + "to-color": { + "doc": "Coerces the input value to a Color." + }, + "rgb": { + "doc": "Creates a color value from r, g, b components." + }, + "rgba": { + "doc": "Creates a color value from r, g, b, a components." + }, + "get": { + "doc": "Retrieves an the object property value. If it's not provided, the object argument defaults to [\"properties\"]." + }, + "has": { + "doc": "Tests for the presence of an object property value. If it's not provided, the object argument defaults to [\"properties\"]." + }, + "length": { + "doc": "Gets the length of an array or string." + }, + "properties": { + "doc": "Gets the feature properties object. Note that in some cases, it may be more efficient to use [\"get\", \"property_name\"] directly." + }, + "geometry-type": { + "doc": "Gets the feature's geometry type: Point, MultiPoint, LineString, MultiLineString, Polygon, MultiPolygon." + }, + "id": { + "doc": "Gets the feature's id, if it has one." + }, + "zoom": { + "doc": "Get the current zoom level. Note that in style layout and paint properties, [\"zoom\"] may only appear as the input to a top-level [\"curve\"] expression." + }, + "+": { + "doc": "" + }, + "*": { + "doc": "" + }, + "-": { + "doc": "" + }, + "/": { + "doc": "" + }, + "%": { + "doc": "" + }, + "^": { + "doc": "" + }, + "log10": { + "doc": "" + }, + "ln": { + "doc": "" + }, + "log2": { + "doc": "" + }, + "sin": { + "doc": "" + }, + "cos": { + "doc": "" + }, + "tan": { + "doc": "" + }, + "asin": { + "doc": "" + }, + "acos": { + "doc": "" + }, + "atan": { + "doc": "" + }, + "min": { + "doc": "" + }, + "max": { + "doc": "" + }, + "==": { + "doc": "" + }, + "!=": { + "doc": "" + }, + ">": { + "doc": "" + }, + "<": { + "doc": "" + }, + ">=": { + "doc": "" + }, + "<=": { + "doc": "" + }, + "&&": { + "doc": "" + }, + "||": { + "doc": "" + }, + "!": { + "doc": "" + }, + "upcase": { + "doc": "" + }, + "downcase": { + "doc": "" + }, + "concat": { + "doc": "Concetenate the given strings." + } + } + }, "light": { "anchor": { "type": "enum",