explorable-viz / fluid

Data-linked visualisations
http://f.luid.org
MIT License
34 stars 2 forks source link

Facet plot #1068

Open rolyp opened 3 weeks ago

rolyp commented 3 weeks ago

A kind of composite plot which consists of a list of parameter values and for each value of the parameter, another kind of visualisation instantiated with that parameter value. For example, given a list of sources of methane emissions [Agriculture, Energy Sector, Forest Burning], a line chart plotting historical and projected methane emissions. The facet would support tabular layout of the individual subplots, but also a carousel-like interface which allows tabbing between a “current” subplot.

See also:

JosephBond commented 2 weeks ago

Current proposal is to construct this functionality in two main stages:

Step 1

rolyp commented 2 weeks ago

@JosephBond Wondering if we need to modify loadfig, or whether we can think of this as an internal detail of a new kind of View (e.g. FacetView). Maybe you could construct a FacetView by supplying:

JosephBond commented 2 weeks ago

Step 2

Note: Re-evaluating fluid programs interactively dove-tails with the planned next steps of https://github.com/explorable-viz/fluid/issues/1040, since we need to examine how we do this. In my mind we will eventually want the backquote mechanism (and future synthesis of explanatory expressions) to be operate similarly to Faceting, (perhaps an interface like Github issues, with a preview tab showing the view you get from running the code.

JosephBond commented 2 weeks ago

@rolyp I think a dict makes more sense, with each faceted parameter being a key in the dict, then instead of setting variable names we could use the syntax for accessing dicts. Unfortunately, we have no special surface syntax for working with dicts.

For a contrived example, faceting a line-chart for a time series by a range of years, where get_points is an accessor function that behaves as you'd expect, and the dict grid binds the key "range" to a list of possible ranges, we'd need something like: LinePlot { name: "Example", data: get_points (nth choice (dict_get "range" grid)) }

In other words, it works but we'd probably want to add syntax for it at a later date. I'm still uncertain how we would then change the value of the variable choice to match the user picking a different entry in the set of allowed ranges. Maybe I'm overcomplicating it, but it feels like even then we will need to spend time with the data-bindings, and use them to modify the values being returned to fluid, as well as the selections. Maybe it's not that difficult/I'm overcomplicating things though

rolyp commented 2 weeks ago

Spitballing here (erring on the side of over-simplification rather than over-complication!), but perhaps FacetView can be a list of {index, view} records, where index has some primitive type, and view is an arbitrary view that may depend on index. The renderer for FacetView would provide custom UI for switching between different {index, view} pairs, or perhaps arranging the various facets all in a column or table layout. The Fluid code for your example would be something like:

let series = [ … ]; // list of records with `year` field
    years = [2015..2019] in
FacetView {
   caption: “Example facet view”,
   facets: [{ 
      index: year, 
      view: LinePlot { 
         name: “Plot for “ ++ numToStr year, 
         data: [row | row <- series, row.year = year] 
      }
   } | year <- years]
}

Here the “grid” isn’t explicit but rather just implicit in the various values of index.

The naive repeated filtering here would be expensive, so some kind groupBy or pivot library function would help express this in an efficient way.

See also:

rolyp commented 2 weeks ago

Thinking of FacetView as simply another kind of View would allow us to e.g. reimplement LineChart as a FacetView where each facet is a LinePlot, with presentation options like:

I think the question about interaction and what it might suggest in terms of more granular or demand-driven execution models is probably a distraction at this stage. Call-by-value programs in general do more work than strictly necessary, and this would just be another example of that – not fundamentally different from computing a list of 1,000 elements but only returning the first 10 elements to the user, or (as a more interactive example closer to the intuition you have in mind) presenting a view of the 1,000 records where only a window of 10 records is visible at any point in time (perhaps through a scrollable viewport).