Add data-driven popups (tooltips) and panels to any Flourish template, with style settings.
Installation
npm install @flourish/info-popup
Usage
There are typically five steps to using the module:
- Add a state property for the module, with an object as a value. E.g.:
var state = {
my_popup: {} // Add any properties to this object to override the default settings
}
- Initialize the popup, passing in the state property and optionally containers than determine the position of the popups and panels. E.g.:
import InfoPopup from "@flourish/info-popup";
var popup = InfoPopup(state.my_popup, panel_container, popup_container)`;
If you want to support "locked" popups properly, for example to position popups programmatically in story slides based on saved state, you should also add a getLockedPosition
function during initialisation. See below.
- Import the settings into your
template.yml
file. E.g.:
- Popups
- property: my_popup
import: "@flourish/info-popup"
-
Attach event-based methods to data points to open/close the popups when the user clicks or mouseovers, as described below.
-
Make sure in your update function you call .update()
and .addColumnNames()
, as described below.
Showing and hiding the popups in response to user events
The module provides event-based functions to simplify working with the many combinations of events and settings.
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 (see below). locked_id
is an identifier for the clicked data point. Assuming locked_id
is not undefined, the popup will be "locked" and will have CSS pointer events enabled, so for example the end viewer can click a link in a popup. If locked_id
is undefined, the popup won't be locked.
popup.mouseover(coords_or_node, data, callback)
Opens the popup or panel depending if it's not locked open. (Note: this doesn't take the locked_id
argument since you can never lock a popup by mousing over a data point.)
popup.touch(coords_or_node, data, locked_id, callback)
For touch devices. 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()
Closes or resets the popup or panel unless it is locked.
popup.clickout()
Closes the popup if it's locked; typically used in a click handler for the background (e.g. an SVG or canvas element).
Make sure you call popup.addColumnNames()
and popup.update([callback])
in your update function so that:
For the latter to work properly, you need define a getLockedPosition
function so that, when update is called, if the popup is in a "locked" state, the popup module knows where to position the popup. This function is called, passing in the locked_id
as an argument and needs to return an array in the form [node_or_coordinate, data]
.
Working with data and column names
When opening or updating a popup, you will typically pass in data
representing the clicked/hovered/searched data point.
Typically this object will have internal property names, such as data binding keys. To convert these into readable names you also need to call addColumnNames
to provide a display value for each key.
(The name of this function reflects the fact that very often the display names are column headers from a user's data table.)
For example, if the user click/hovers on a data point on a scatter plot, you could pass all the data associated with that point to the popup as an object, which might look like this:
{ id: "John", x_value: 24, y_value: 198, index: 10 }
None of these would appear in the popup, however, unless you've also called addColumnNames
, such as:
addColumnNames({ id: "Name", x_value: "Age", y_value: "Weight" })
The result would be popup content such as:
Name: John
Age: 24
Weight: 198
(In this example index
doesn't show up as it is not included when calling addColumnNames.)
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 {{name_of_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.
Other methods
popup.locked()
– gets the current locked_id
from the popup state. Returns null
if the popup is not in a "locked" state.
popup.mode()
– gets the current mode
from the popup state.
popup.hidePopup()
will hide the popup without resetting the locked_id
.
popup.hidePanel()
will hide the panel without resetting the locked_id
.
Example implementation
import InfoPopup from "@flourish/info-popup";
import data from "./data";
let popup = InfoPopup(state.popup);
function getLockedPositionCallback(id) {
// get element corresponding to id
let el = getElementFromId(id);
// get datum corresponding to id
let datum = getDatumFromId(id);
return [el, datum];
}
initPopup() {
popup
.getLockedPosition(getLockedPositionCallback)
.addColumnNames(data.data.column_names);
}
export { popup };
initPopup
initialises the popup. This sets up the getLockedPositionCallback
callback. (This callback is
called by popup.update
which passes in an id
. This id
should be used to lookup the corresponding element (or position) and datum.)
addColumnNames
may also be called when initialising to set up which columns will appear in the popup.
update-chart.js
import { popup } from "./popup";
updateChart() {
d3.selectAll(...)
.on("mouseover", function(d) {
let el = this;
popup.mouseover(el, d);
})
.on("mouseout", function() {
popup.mouseout();
})
.on("click", function(d) {
let el = this;
popup.click(el, d, d.id);
});
}
export { updateChart };
The mouse events are set up in updateChart
. In general the hovered/clicked element (or position) and datum are passed into popup.mouseover
or
popup.click
.
Also pass the clicked item's id into popup.click
. This is so that if there's an update, the getLockedPositionCallback
will be
invoked. (This means that an up to date element/position and datum are used to update the popup.)
update-graphic.js
import { updateChart } from "./update-chart";
updateGraphic() {
...
popup.update();
updateChart();
}
Call popup.update
from updateGraphic
so that the popup (if frozen) is updated on state changes (e.g. settings change, or a change of
slide in story mode).
If the popup is frozen getLockedPositionCallback
is invoked which'll compute the position/element and datum of
the provided id.