Vega: Needs brush to resizes to the closest domain value

Created on 4 Aug 2017  路  3Comments  路  Source: vega/vega

I'm trying to make the brush snaps to the closest domain value like on this example...
https://bl.ocks.org/mbostock/6232537
image
I believe I need to change the delta value onmouseup but I can't figure out how to pass the mouse x() value to the scale...

"scales": [
    {
      "name": "xband",
      "type": "band",
      "domain": {"data": "graph", "field": "x"},
      "range": "width",
      "padding": 0.05,
      "round": true
    }
  ],
 "signals": [
        {
          "name": "brush", "value": 0,
          "on": [
            {
              "events": "@overview:mousedown",
              "update": "[x(), x()]"
            },
            {
              "events": "[@overview:mousedown, window:mouseup] > window:mousemove!",
              "update": "[brush[0], clamp(x(), 0, width)]"
            },
            {
              "events": {"signal": "delta"},
              "update": "clampRange([anchor[0] + delta, anchor[1] + delta], 0, width)"
            }
          ]
        },
        {
          "name": "anchor", "value": null,
          "on": [{"events": "@brush:mousedown", "update": "slice(brush)"}]
        },
        {
          "name": "xdown", "value": 0,
          "on": [{"events": "@brush:mousedown", "update": "x()"}]
        },
        {
          "name": "delta", "value": 0,
          "on": [
            {
              "events": "[@brush:mousedown, window:mouseup] > window:mousemove!",
              "update": "x() - xdown"
            }
          ]
        },
        {
          "name": "detailDomain",
          "push": "outer",
          "on": [
            {
              "events": {"signal": "brush"},
              "update": "span(brush) ? invert('xOverview', brush) : null"
            }
          ]
        }
      ],
question

Most helpful comment

Thanks Jeffrey, I got it to work. I've pasted the code in case someone would need something similar.
image

{
  "$schema": "https://vega.github.io/schema/vega/v3.0.json",
  "width": 720,
  "height": 70,
  "padding": 5,

  "data": [
     {
      "name": "graph",
      "values":[
        {
          "label": "traffic-statistics.output-bytes",
          "x": 1500492300000,
          "y": 63.87579568625058
        },
        {
          "label": "traffic-statistics.output-bytes",
          "x": 1500492420000,
          "y": 54.14065294117647
        },
        {
          "label": "traffic-statistics.output-bytes",
          "x": 1500492540000,
          "y": 72.7588843137255
        },
        {
          "label": "traffic-statistics.output-bytes",
          "x": 1500492660000,
          "y": 52.708751372572955
        },
        {
          "label": "traffic-statistics.output-bytes",
          "x": 1500492780000,
          "y": 56.16350784313726
        },
        {
          "label": "traffic-statistics.output-bytes",
          "x": 1500492900000,
          "y": 54.5772635293998
        },
        {
          "label": "traffic-statistics.output-bytes",
          "x": 1500493020000,
          "y": 68.9301290195839
        },
        {
          "label": "traffic-statistics.output-bytes",
          "x": 1500493140000,
          "y": 71.75086470588235
        },
        {
          "label": "traffic-statistics.output-bytes",
          "x": 1500493260000,
          "y": 60.39008392160453
        }
      ]
    }
  ],
  "scales": [
    {
      "name": "xband",
      "type": "band",
      "domain": {"data": "graph", "field": "x"},
      "range": "width",
      "padding": 0.05,
      "round": true
    },
    {
      "name": "xOverview",
      "type": "time",
      "range": "width",
      "domain": {"data": "graph", "field": "x"}
    },
    {
      "name": "yOverview",
      "type": "linear",
      "range": [70, 0],
      "domain": {"data": "graph", "field": "y"},
      "nice": true, "zero": true
    }
  ],
  "signals": [
    {
      "name": "detailDomain"
    }
  ],

  "marks": [
    {
      "type": "group",
      "name": "overview",
      "encode": {
        "enter": {
          "x": {"value": 0},
          "y": {"value": 0},
          "height": {"value": 70},
          "width": {"value": 720},
          "fill": {"value": "transparent"}
        }
      },
      "signals": [
        {
          "name": "brush", "value": 0,
          "on": [
            {
              "events": "@overview:mousedown",
              "update": "[scale('xOverview', invert('xband', x()) ) - 8, scale('xOverview', invert('xband', x())) - 8]"
            },
            {
              "events": "[@overview:mousedown, window:mouseup] > window:mousemove!",
              "update": "[brush[0], clamp(scale('xOverview', invert('xband', x())) - 8, -8, width)]"
            },
            {
              "events": {"signal": "delta"},
              "update": "clampRange([anchor[0] + delta, anchor[1] + delta], 0, width)"
            }
          ]
        },
        {
          "name": "anchor", "value": null,
          "on": [{"events": "@brush:mousedown", "update": "slice(brush)"}]
        },
        {
          "name": "xdown", "value": 0,
          "on": [{"events": "@brush:mousedown", "update": "x()"}]
        },
        {
          "name": "delta", "value": 0,
          "on": [
            {
              "events": "[@brush:mousedown, window:mouseup] > window:mousemove!",
              "update": "x() - xdown"
            }
          ]
        },
        {
          "name": "detailDomain",
          "push": "outer",
          "on": [
            {
              "events": {"signal": "brush"},
              "update": "span(brush) ? invert('xOverview', brush) : null"
            }
          ]
        }
      ],
      "axes": [
        {"orient": "bottom", "scale": "xOverview", "position": -8}
      ],
      "marks": [     
         {"type": "group",
          "encode": {
            "enter": {
              "height": {"field": {"group": "height"}},
              "width": {"field": {"group": "width"}},
              "clip": {"value": true}
            }
          },
        "marks": [{
          "type": "rect",
          "from": {"data": "graph"},
          "interactive": false,
          "encode": {
            "update": {
              "x": {"scale": "xOverview", "field": "x"},
              "y": {"scale": "yOverview", "field": "y"},
              "y2": {"scale": "yOverview", "value": 0},
              "width": {"scale": "xband", "band": 1},
              "fill": {"value": "steelblue"}
            }
          }
        }]},
        {
          "type": "rect",
          "name": "brush",
          "interactive": false,
          "encode": {
            "enter": {
              "y": {"value": 0},
              "height": {"value": 70},
              "fill": {"value": "#333"},
              "fillOpacity": {"value": 0.2}
            },
            "update": {
              "x": {"signal": "brush[0]"},
              "x2": {"signal": "brush[1]"}
            }
          }
        },
        {
          "type": "rect",
          "interactive": false,
          "encode": {
            "enter": {
              "y": {"value": 0},
              "height": {"value": 70},
              "width": {"value": 1},
              "fill": {"value": "firebrick"}
            },
            "update": {
              "x": {"signal": "brush[0]"}
            }
          }
        },
        {
          "type": "rect",
          "interactive": false,
          "encode": {
            "enter": {
              "y": {"value": 0},
              "height": {"value": 70},
              "width": {"value": 1},
              "fill": {"value": "firebrick"}
            },
            "update": {
              "x": {"signal": "brush[1]"}
            }
          }
        }
      ]
    }
  ]
}

All 3 comments

The linked D3 example uses the following strategy:

  1. Get the x-coordinate value.
  2. Run it through a scale inversion (x.invert) to map from pixel domain to data domain.
  3. Perform "snapping" by rounding / truncating values in the data domain.
  4. Re-apply the x scale to get the snapped pixel coordinate.

You can use the same strategy in Vega within signal expressions.

  • To invert the scale, you can use the invert expression function. For example invert('scaleName', x()).
  • Then perform whatever "rounding" you want to perform in the data domain. To snap to time values, you can apply one of the various datetime expression functions. For example to create a datetime value truncated to the previous day boundary: datetime(year(d), month(d), date(d)).
  • To map data values back to the pixel domain, use the scale expression function. For example scale('scaleName', value).

Thanks Jeffrey, I got it to work. I've pasted the code in case someone would need something similar.
image

{
  "$schema": "https://vega.github.io/schema/vega/v3.0.json",
  "width": 720,
  "height": 70,
  "padding": 5,

  "data": [
     {
      "name": "graph",
      "values":[
        {
          "label": "traffic-statistics.output-bytes",
          "x": 1500492300000,
          "y": 63.87579568625058
        },
        {
          "label": "traffic-statistics.output-bytes",
          "x": 1500492420000,
          "y": 54.14065294117647
        },
        {
          "label": "traffic-statistics.output-bytes",
          "x": 1500492540000,
          "y": 72.7588843137255
        },
        {
          "label": "traffic-statistics.output-bytes",
          "x": 1500492660000,
          "y": 52.708751372572955
        },
        {
          "label": "traffic-statistics.output-bytes",
          "x": 1500492780000,
          "y": 56.16350784313726
        },
        {
          "label": "traffic-statistics.output-bytes",
          "x": 1500492900000,
          "y": 54.5772635293998
        },
        {
          "label": "traffic-statistics.output-bytes",
          "x": 1500493020000,
          "y": 68.9301290195839
        },
        {
          "label": "traffic-statistics.output-bytes",
          "x": 1500493140000,
          "y": 71.75086470588235
        },
        {
          "label": "traffic-statistics.output-bytes",
          "x": 1500493260000,
          "y": 60.39008392160453
        }
      ]
    }
  ],
  "scales": [
    {
      "name": "xband",
      "type": "band",
      "domain": {"data": "graph", "field": "x"},
      "range": "width",
      "padding": 0.05,
      "round": true
    },
    {
      "name": "xOverview",
      "type": "time",
      "range": "width",
      "domain": {"data": "graph", "field": "x"}
    },
    {
      "name": "yOverview",
      "type": "linear",
      "range": [70, 0],
      "domain": {"data": "graph", "field": "y"},
      "nice": true, "zero": true
    }
  ],
  "signals": [
    {
      "name": "detailDomain"
    }
  ],

  "marks": [
    {
      "type": "group",
      "name": "overview",
      "encode": {
        "enter": {
          "x": {"value": 0},
          "y": {"value": 0},
          "height": {"value": 70},
          "width": {"value": 720},
          "fill": {"value": "transparent"}
        }
      },
      "signals": [
        {
          "name": "brush", "value": 0,
          "on": [
            {
              "events": "@overview:mousedown",
              "update": "[scale('xOverview', invert('xband', x()) ) - 8, scale('xOverview', invert('xband', x())) - 8]"
            },
            {
              "events": "[@overview:mousedown, window:mouseup] > window:mousemove!",
              "update": "[brush[0], clamp(scale('xOverview', invert('xband', x())) - 8, -8, width)]"
            },
            {
              "events": {"signal": "delta"},
              "update": "clampRange([anchor[0] + delta, anchor[1] + delta], 0, width)"
            }
          ]
        },
        {
          "name": "anchor", "value": null,
          "on": [{"events": "@brush:mousedown", "update": "slice(brush)"}]
        },
        {
          "name": "xdown", "value": 0,
          "on": [{"events": "@brush:mousedown", "update": "x()"}]
        },
        {
          "name": "delta", "value": 0,
          "on": [
            {
              "events": "[@brush:mousedown, window:mouseup] > window:mousemove!",
              "update": "x() - xdown"
            }
          ]
        },
        {
          "name": "detailDomain",
          "push": "outer",
          "on": [
            {
              "events": {"signal": "brush"},
              "update": "span(brush) ? invert('xOverview', brush) : null"
            }
          ]
        }
      ],
      "axes": [
        {"orient": "bottom", "scale": "xOverview", "position": -8}
      ],
      "marks": [     
         {"type": "group",
          "encode": {
            "enter": {
              "height": {"field": {"group": "height"}},
              "width": {"field": {"group": "width"}},
              "clip": {"value": true}
            }
          },
        "marks": [{
          "type": "rect",
          "from": {"data": "graph"},
          "interactive": false,
          "encode": {
            "update": {
              "x": {"scale": "xOverview", "field": "x"},
              "y": {"scale": "yOverview", "field": "y"},
              "y2": {"scale": "yOverview", "value": 0},
              "width": {"scale": "xband", "band": 1},
              "fill": {"value": "steelblue"}
            }
          }
        }]},
        {
          "type": "rect",
          "name": "brush",
          "interactive": false,
          "encode": {
            "enter": {
              "y": {"value": 0},
              "height": {"value": 70},
              "fill": {"value": "#333"},
              "fillOpacity": {"value": 0.2}
            },
            "update": {
              "x": {"signal": "brush[0]"},
              "x2": {"signal": "brush[1]"}
            }
          }
        },
        {
          "type": "rect",
          "interactive": false,
          "encode": {
            "enter": {
              "y": {"value": 0},
              "height": {"value": 70},
              "width": {"value": 1},
              "fill": {"value": "firebrick"}
            },
            "update": {
              "x": {"signal": "brush[0]"}
            }
          }
        },
        {
          "type": "rect",
          "interactive": false,
          "encode": {
            "enter": {
              "y": {"value": 0},
              "height": {"value": 70},
              "width": {"value": 1},
              "fill": {"value": "firebrick"}
            },
            "update": {
              "x": {"signal": "brush[1]"}
            }
          }
        }
      ]
    }
  ]
}

Glad it worked out. Thanks for sharing!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

nyurik picture nyurik  路  3Comments

kanitw picture kanitw  路  3Comments

mhf-ir picture mhf-ir  路  3Comments

rubjo picture rubjo  路  4Comments

sonlichao picture sonlichao  路  4Comments