Deck.gl: RFC: Add @@function to interpret a string as a JavaScript function - @deck.gl/json

Created on 4 Dec 2020  路  8Comments  路  Source: visgl/deck.gl

  • Author: Adri谩n P茅rez (CARTO)
  • Date: December, 2020
  • Status: Draft

Proposed feature

Embed functions in @deck.gl/json module. Setting accessors as objects at declarative language.

{
  "description": "Style example",
  "initialViewState": {...},
  "views": [...],
  "layers": [
    {
      "@@type": "CartoSQLLayer",
      "data": "SELECT the_geom_webmercator, gn_pop FROM populated_places",
      "getFillColor": {
        "@@function": "colorBins",
        "prop": "gn_pop",
        "breaks": [100, 200, 300],
        "colors": [[225, 83, 131], [241, 109, 122], [250, 138, 118], [255, 166, 121]]
      }
    }
  ]
}

Introduce functions at the configuration:

const configuration = {
  classes: Object.assign({}, require('@deck.gl/layers'), require('@deck.gl/aggregation-layers')),
  functions: Object.assign({}, colorBins)
};

During the first pass for deck.gl/json it should transform @@function:

{
  "description": "Style example",
  "initialViewState": {...},
  "views": [...],
  "layers": [
    {
      "@@type": "CartoSQLLayer",
      "data": "SELECT the_geom_webmercator, gn_pop FROM populated_places",
      "getFillColor": colorBins({
        "prop": "gn_pop",
        "breaks": [100, 200, 300],
        "colors": [[225, 83, 131], [241, 109, 122], [250, 138, 118], [255, 166, 121]]
      })
    }
  ]
}

And then apply @@type:

{
  "description": "Style example",
  "initialViewState": {...},
  "views": [...],
  "layers": [
    {
      new CartoSQLLayer({
        "data": "SELECT the_geom_webmercator, gn_pop FROM populated_places",
        "getFillColor": colorBins({
          "prop": "gn_pop",
          "breaks": [100, 200, 300],
          "colors": [[225, 83, 131], [241, 109, 122], [250, 138, 118], [255, 166, 121]]
        })
      })
    }
  ]
}

colorsBins could be something like this:

import {scaleThreshold} from 'd3-scale';

function colorsBins({breaks, colors) {
  const color = scaleThreshold()
    .domain(breaks)
    .range(colors);

  return d => {
    return color(d);
  };
}

Motivation

Currently, using json module, the way to dynamically style data is using @@= prefix on accessors interpreting the rest of the string as a function:
"getFillColor": "@@=properties.pop > 1000 ? [0, 0, 0] : [255, 255, 255]"

The main idea of this RFC is to be able to config data accessors as objects, and pass the thrown data to a function.

feature

All 8 comments

I think it could be better to convert this into something more generic: Add @@function to interpret a string as a JavaScript function. It opens a new way to include functions for accesors at declarative language

{
  "description": "Style example",
  "initialViewState": {...},
  "views": [...],
  "layers": [
    {
      "@@type": "CartoSQLLayer",
      "data": "SELECT the_geom_webmercator, gn_pop FROM populated_places",
      "getFillColor": {
        "@@function": "colorBins",
        "prop": "gn_pop",
        "breaks": [100, 200, 300],
        "colors": [[225, 83, 131], [241, 109, 122], [250, 138, 118], [255, 166, 121]]
      }
    }
  ]
}

Introduce functions at the configuration:

const configuration = {
  classes: Object.assign({}, require('@deck.gl/layers'), require('@deck.gl/aggregation-layers')),
  functions: Object.assign({}, colorBins)
};

During the first pass for deck.gl/json it should transform @@function:

{
  "description": "Style example",
  "initialViewState": {...},
  "views": [...],
  "layers": [
    {
      "@@type": "CartoSQLLayer",
      "data": "SELECT the_geom_webmercator, gn_pop FROM populated_places",
      "getFillColor": colorBins({
        "prop": "gn_pop",
        "breaks": [100, 200, 300],
        "colors": [[225, 83, 131], [241, 109, 122], [250, 138, 118], [255, 166, 121]]
      })
    }
  ]
}

And then apply @@type:

{
  "description": "Style example",
  "initialViewState": {...},
  "views": [...],
  "layers": [
    {
      new CartoSQLLayer({
        "data": "SELECT the_geom_webmercator, gn_pop FROM populated_places",
        "getFillColor": colorBins({
          "prop": "gn_pop",
          "breaks": [100, 200, 300],
          "colors": [[225, 83, 131], [241, 109, 122], [250, 138, 118], [255, 166, 121]]
        })
      })
    }
  ]
}

colorsBins could be something like this:

import {scaleThreshold} from 'd3-scale';

function colorsBins({breaks, colors) {
  const color = scaleThreshold()
    .domain(breaks)
    .range(colors);

  return d => {
    return color(d);
  };
}

At the current version, the syntax at declarative language to apply a getFillColor with more than 2 colors using @@= is quite verbose:

 "getFillColor": "@@= properties.aggregated_total > 1000000 ? [207, 89, 126] : properties.aggregated_total > 100000 ? [232, 133, 113] : properties.aggregated_total > 10000 ? [238, 180, 121] : properties.aggregated_total > 1000 ? [233, 226, 156] : properties.aggregated_total > 100 ? [156, 203, 134] : properties.aggregated_total > 10 ? [57, 177, 133] : [0, 147, 146]",

@AdriSolid can you update your RFC to reflect the changes

@Pessimistress done

As mentioned, we do have support for @@type that handles any constructor

It is a little bit hacky, but you probably get things to work already today if you use the JS trick that a class constructor can return anything (instead of this).

class ColorBin {
  constructor({breaks, colors) {
    const color = scaleThreshold()
    .domain(breaks)
    .range(colors);

    return d => color(d);
  }
}
"getFillColor": {
        "@@type": "ColorBins",
        "breaks": [100, 200, 300],
        "colors": [[225, 83, 131], [241, 109, 122], [250, 138, 118], [255, 166, 121]]
      }
}

Also just to make sure I didn't miss something, I don't see the "prop" prop being used in the examples.

 "prop": "gn_pop",

@ibgreen thanks for your comments! Returning anything from a class constructor is a good start
The thing with "prop" is that we need to grab some "prop" (i.e: properties.gn_pop) to pass it through the (style) class

As mentioned, we do have support for @@type that handles any constructor

@ibgreen It was the first approach, but as you mention, it's a little bit hacky to create a class to return a function instead of this.

Was this page helpful?
0 / 5 - 0 ratings