@flourish/info-popup
v8.1.0
Published
Popup with data insertion
Downloads
655
Maintainers
Keywords
Readme
Flourish Info Popup
Add data-driven popups (tooltips) and panels to any Flourish template, with style settings.
There are typically seven steps to using the module:
1 - Installation
npm install @flourish/info-popup
2 - Add a state property for the module, with an object as a value
const state = {
popup: {} // Add any properties to this object to override the default settings
}
3 - Import the settings into your template.yml
file
- Popups & panels
- property: popup
import: "@flourish/info-popup"
4 - Initialize the popup
Initialize the popup, passing in the state property and optionally containers that determine the position of the popups and panels
import initialisePopup from "@flourish/info-popup";
const popup = initialisePopup(state.popup, panel_container, popup_container);
If you want the panel to fill the whole page (i.e. cover the header & footer), just ignore the panel_container
and popup_container
arguments.
5 - Attach event-based methods to the data
The important thing to understand with this module is that the template passes on information about user triggered events (e.g. click, mouseover etc) and then the module decides what should happen as a result of this event. This is because there are many different ways that popups and panels behave in response to user events depending on the settings selected. For example here are some of the different scenarios for the mouseover
event:
- When in Popup mode: popup shows over data point
- When in Popup mode, with sticky content: nothing happens
- When in Panel mode: nothing happens
- When in Panel mode,
Always show
turned on: content loads in panel - When in Panel mode,
Always show
turned on, with sticky content: nothing happens - When in Both mode: Popup with preview content shows over data point
So for consistency this logic is abstracted from the template. This is why there is no method for actions such as opening a popup because instead we call popup.mouseover()
and let the module look at all the different settings and decide what should happen as a result of that event.
All event based methods are listed below in the methods section but a common implementation will look something like this:
import { popup } from "./popup";
function updateGraphic() {
d3.selectAll(...) // datapoints
.on("mouseover", function() {
const el = this;
popup.mouseover(el, el.__data__);
})
.on("mouseout", function() {
popup.mouseout();
})
.on("click", function() {
const el = this;
const data = el.__data__
const locked_id = data.id
popup.click(el, data, locked_id); // See step 8 for more information on locked_id
});
}
6 - Making sure the popup displays the correct, formatted, data with .setColumnNames()
and .setFormatters()
For each popup we want to make sure we're displaying the correct labels and formatted values from the data. There are two parts to making this work:
Part One - passing in the data
This happens as part of step 5 when passing data in to popup.click
, popup.mouseover
or popup.touch
. You pass in the data that is bound to the node that you are interacting with. If you had the following data bindings: binding_one
,binding_two
, binding_three
and binding_four
the data on each data point (and therefore the data you would be passing to the popup module) would look something like this:
{
binding_one: "Joe Biden",
binding_two: "President",
binding_three: "https://public.flourish.studio/uploads/ff3f9984-470c-4ed9-beb8-2a86bb7019ea.jpg",
binding_four: "Democrat"
}
Part Two - setting the column names
Currently the popup has been passed a set of values that correlate to data bindings, we now have to set a column name for each data binding we wish to appear in the popup:
popup
.setColumnNames({
binding_one: "Name",
binding_two: "Job title",
binding_four: "Party"
})
.update()
Data will only show if we have set a column name for that data binding. We didnt set a column name for binding_three
so the resulting information displayed in the popup would be:
Name: Joe Biden
Job title: President
Party: Democrat
If there are no columns that you wish to exclude you can pass in the column_names
object from the Flourish dataset:
popup
.setColumnNames(data.data.column_names)
.update()
If there are columns you'd like to be available for custom content, but don't want to show in the default popup or panel content (eg. long series data), you can exclude them using .hideColumnNames()
. This takes an array of binding names. In the above example, calling .hideColumnNames(["binding_four"])
will not show the Party information in the default popup or panel.
Part Three - setting the formatters
Optionally, you can also set formatters for each data binding. These must match the data bindings passed in .setColumnNames()
but you do not need to provide a formatter for all bindings.
Formatters take two forms:
- A function taking a value and returning a string. Any formatters generated by
initNumberFormatter
in @flourish/formatters should by passed like this so that formatting changes in the settings panel are reflected in the popup. - An object containing an
output_format_id
string property used in the [@flourish/interpreter] module. This is passed to @flourish/formatters to generate a formatter function.
For multi-column bindings you can pass in an array of formatters (where each formatter is a function or an object containing output_format_id
).
popup
.setColumnNames({
binding_one: "Name",
binding_two: "Cost",
binding_three: "Sold",
binding_four: "Released"
})
.setFormatters({
binding_two: d => `£${d}`,
binding_three: { output_format_id: "number$comma_point" }
binding_four: { output_format_id: "datetime$%d/%m/%Y" },
})
.update()
If the data below was passed to the popup:
{
binding_one: "Pain au chocolat",
binding_two: 2,
binding_three: 1235000,
binding_four: new Date("2022-05-12T03:24:00")
}
The resulting information would be displayed in the popup:
Name: Pain au chocolat
Cost: £2
Sold: 1,235,000
Released: 05/12/2022
If you do not want to pass any custom formatter functions you can set formatters for all bindings by passing the metadata
object for a dataset. This object includes entries with an output_format_id
string property for each binding (these can be set by the user in the data panel).
popup
.setColumnNames(data.data.column_names)
.setFormatters(data.data.metadata)
.update()
7 - Updating the popup with .update()
To make sure that the popup is using the latest settings, adds the correct content based on what's passed in via .setColumnNames()
, we need to call popup.update()
inside the template's update()
loop.
8 - Set up locking functionality (optional)
Locked functionality is the ability to 'lock' a popup in an open state. A locked popup will remain open until clickout
is triggered and while locked it will have CSS pointer events enabled, so that the end viewer can for example click a link inside the popup.
To set up locking functionality you need to define a getLockedPosition
function. If there is a locked popup this function will retreive the locked node or coordinate information of the locked item, and the data that's associated with it. It returns an array: [node_or_coordinate, data]
This function needs only be called once so can be called straight after the popup is initialised, but can also be called as part of an update function.
popup.getLockedPosition(getLockedPositionCallback)
function getLockedPositionCallback(locked_id) {
// get the node that relates to this id (this is just one common way you might do this)
const el_node = select(`#${locked_id}`).node();
// if there is no locked popup, or there is currently no element on the screen
// corresponding to the stored locked popup return null
if (!el_node) return null;
// get the data from that node
const datum = el_node && el_node.__data__;
// return node and data
return [el_node, datum];
}
Methods
Event-based methods
popup.click(coords_or_node, data, locked_id, callback)
Used when the viewer clicks a data point.coords_or_node
can be a coordinate array ([x, y]) or an DOM element.data
is the data to display (usually the data attached to the element you are clicking).locked_id
is an identifier for the clicked data point. Iflocked_id
is undefined it wont be locked, if it is defined the value will be stored as the locked_id and passed to thegetLockedPositionCallback
function.popup.mouseover(coords_or_node, data, callback)
Used when the viewer mouseovers a data point. (Note: this doesn't take thelocked_id
argument since you can never lock a popup by mousing over a data point.)popup.touch(coords_or_node, data, locked_id, callback)
Used when the viewer is on a touch device and touches a data point. It has the same behavior as mouseover except that in "Both" mode it makes it possible to touch once to open a popup and then touch the popup to open the panel. (Hence it needs the locked_id argument since this second touch locks the panel open.)popup.mouseout()
Used when the viewer mouse leaves a data point. It closes or resets the popup or panel unless it is locked.popup.clickout()
Used when the viewer clicks on something that isnt a data point. It closes the popup if it's locked; typically used in a click handler for the background (e.g. an SVG or canvas element). Note: this method can make code look quite counterintuitive e.g:document.body.onclick = function() { popups.clickout(); };
This is because to the browser you are clicking but to the popups you are clicking out (because you are clicking on something that isnt a data point).
Setting a title and subtitle
It's possible to specify that certain keys in the data represent the title and subtitle for the popup or panel. These appear in the header section of the popup with different styling from the rest of the popup.
popup.titleKey(data_binding_name)
popup.subtitleKey(data_binding_name)
Specifying a default template
You can specify a different template for the popup or panel content, for instance if you want to add an <img>
tag or the content needs to be shown in a specific order. This is done when initializing:
popup.popupDefaultTemplate(template_string)
popup.panelDefaultTemplate(template_string)
The template_string
can include data binding keys between curly brackets, such as <h1>{{name}}</h1>
where name
is your data binding.
Positional methods
popup.popupDirections(directions)
– gets or sets the popup directions.popup.margins()
– gets the space on the visualisation edges being used for the panel. This is useful for when you don't want the panel to overlay over the content.
Hiding methods
popup.hidePopup()
will hide the popup without resetting thelocked_id
.popup.hidePanel()
will hide the panel without resetting thelocked_id
.
Getter methods
popup.locked()
– gets the currentlocked_id
from the popup state. Returnsnull
if there is currently no popup "locked" open.popup.mode()
– gets the currentmode
from the popup state.
Misc methods
popup.onPanelClose(fn)
will fire when the panel gets closed
Adjusting which data bindings to show in the custom content setting
On default, all data bindings are used as little buttons below the custom content setting. You can change this by adding a setting override:
overrides:
- property: [ popup_custom_main, popup_custom_header ]
editor:
style: true
url: true
flourish_embed: true
bindings: [ dataset_name.databinding_name_1, dataset_name.databinding_name_2 ]
Templates to reference
Templates that use info-popups
in a fairly straightforward way inculde:
A template that uses info-popups
with canvas include:
For templates with multiple datasheets
If a template has multiple datasheets you will need to pass in the name of the datasheet that the info popup binding relates to. If it is not passed in the tests will use the first datasheet in the column_names
object.