Visualize.Hooks.Brush (Visualize v0.2.35)

Copy Markdown View Source

Phoenix LiveView hook for brush selection interactions.

Enables rectangular or 1D brush selection on SVG elements for selecting data ranges, filtering, or zooming.

Usage

  1. Add the hook to your app.js:

    import { BrushHook } from "visualize/hooks"

    let liveSocket = new LiveSocket("/live", Socket, { hooks: { BrushHook } })

  2. Use in your LiveView template:

    <svg phx-hook="BrushHook"

      id="my-chart"
      data-brush-type="xy"
      data-brush-event="brush_select">

    <g class="chart-content">

     <!-- Your chart content -->

    </g> <g class="brush-overlay"></g> </svg>

  3. Handle brush events in your LiveView:

     def handle_event("brush_select", %{"x0" => x0, "y0" => y0, "x1" => x1, "y1" => y1}, socket) do
       # Filter data within selection
       {:noreply, assign(socket, selection: {x0, y0, x1, y1})}
     end
    
     def handle_event("brush_clear", _params, socket) do
       {:noreply, assign(socket, selection: nil)}
     end

The hook may also sit on a container around the chart's <svg>, as the other hooks do: it maps the pointer through, and draws in, the first <svg> inside it.

In a sync group

A chart whose design names a sync group (interaction: %{sync: "dash-42"}, spec/14 §2.9) shares its brush (spec/10 §6.4, D-117): the chart brushed publishes the brushed x extent in domain units on the group's bus, and every other member draws it as a band through its own scale, clipped to its domain, at once. Only the chart brushed pushes brush_select, once, on release, with the window as "domain" => [lo, hi] too, so one window change is one server request:

def handle_event("brush_select", %{"domain" => [from, to]}, socket) do
  {:noreply, assign(socket, window: {from, to})}
end

Brush Types

  • xy - 2D rectangular brush (default)
  • x - Horizontal brush only
  • y - Vertical brush only

Configuration

Data attributes on the SVG element:

  • data-brush-type - Brush type: "xy", "x", or "y" (default: "xy")
  • data-brush-event - Event name for selection (default: "brush_select")
  • data-brush-clear-event - Event name for clearing (default: "brush_clear")
  • data-brush-extent - Constrains brush to "x0,y0,x1,y1" (optional)
  • data-brush-color - Selection rectangle fill color (default: "rgba(119, 119, 119, 0.2)")
  • data-brush-stroke - Selection rectangle stroke color (default: "#666")

Summary

Functions

Keeps the data within the selection on every axis whose scale is given (spec/10 §6.3, D-29).

Returns the JavaScript code for the BrushHook.

Converts a pixel selection to domain values through the scales given (spec/10 §6.3, D-29).

Functions

filter_selection(data, selection, opts)

@spec filter_selection([any()], map(), keyword()) :: [any()]

Keeps the data within the selection on every axis whose scale is given (spec/10 §6.3, D-29).

opts takes :x_accessor with :x_scale, :y_accessor with :y_scale, or both pairs; an axis without a scale is not tested, so an "x" brush needs no y scale. Neither scale is an ArgumentError.

Example

filtered = Visualize.Hooks.Brush.filter_selection(
  data,
  selection,
  x_accessor: & &1.date,
  x_scale: x_scale
)

js_hook()

Returns the JavaScript code for the BrushHook.

Include this in your app.js or as a separate file.

selection_to_domain(selection, opts)

@spec selection_to_domain(map(), keyword()) :: map() | {any(), any()}

Converts a pixel selection to domain values through the scales given (spec/10 §6.3, D-29).

With both :x_scale and :y_scale: %{x0:, x1:, y0:, y1:}. With one: the {start, stop} pair of that axis in domain units, in pixel order — for a y brush under the usual flipped range start is the larger value. Neither scale is an ArgumentError.

Example

{from, to} = Visualize.Hooks.Brush.selection_to_domain(selection, x_scale: x_scale)