npm install piling.js pixi.js
Optional: If you want to lay out piles by more than two attributes you have to install UMAP:
npm install umap-js
The bare minimum you need to define to get started with piling.js are the following two thing:
- A list of objects with a
src
property - A renderer that understands how to render
src
into an image
// import the piling.js library
import createPilingJs from 'piling.js';
// import the predefined image renderer
import { createImageRenderer } from 'piling.js';
// define your dataset
const data = [{ src: 'http://example.com/my-fancy-photo.png' }, ...];
// instantiate and configure the piling.js library
// 'demo' is the dom element which piling.js will be rendered in
const piling = createPilingJs(
document.getElementById('demo'), // dom element in which piling.js will be rendered
{
renderer: createImageRenderer(), // use the image renderer for rendering
items: data, // add the images
}
);
Et voilà 🎉
As a first step, you have to query the DOM for an element in which you want to render the piles.
const demoEl = document.getElementById('demo');
To render images in Piling.js, create an array of objects (items
) whose src
property is an URL to an image and instantiate an image renderer. Finally you have to create a Piling.js instance.
import createPilingJs, { createImageRenderer } from 'piling.js';
const items = [{ src: 'http://example.com/my-fancy-photo.png' }, ...];
const itemRenderer = createImageRenderer();
const piling = createPilingJs(demoEl, { items, itemRenderer });
To render SVG images as pile, your items
src
property needs to reference a SVG node or be a stringified version of a SVG node.
import createPilingJs, { createSvgRenderer } from 'piling.js';
const items = [{ src: '<svg viewBox="0,0,64,64"><rect ...></svg>' }, ...];
const itemRenderer = createSvgRenderer({
width: 64, height: 64, background: 'white'
});
const piling = createPilingJs(demoEl, { items, itemRenderer });
For convenience Piling.js also provides a createD3Renderer
so you can easily use D3 for drawing the SVGs. To use D3 simply create a function that takes as input an item's source and returns a SVG node that contain the rendered visualization.
For a live demo see https://observablehq.com/@flekschas/piling-js-with-d3.
import createPilingJs, { createD3Renderer } from 'piling.js';
import { create, range, scaleBand, scaleLinear } from 'd3';
const items = Array.from({ length: 10 }, () => ({
src: Array.from({ length: 5 }, () => Math.random()),
}));
// D3 Scales
const x = scaleBand().domain(range(5)).range([0, 64]).padding(0.1);
const y = scaleLinear().domain([0, 1]).range([64, 0]);
// D3 Renderer
const itemRenderer = createD3Renderer((itemSrc) => {
const svg = create('svg').attr('viewBox', [0, 0, 64, 64]);
svg
.selectAll('rect')
.data(itemSrc)
.join('rect')
.attr('x', (d, i) => x(i))
.attr('y', (d) => y(d))
.attr('height', (d) => y(0) - y(d))
.attr('width', x.bandwidth());
return svg.node();
});
const piling = createPilingJs(demoEl, { items, itemRenderer });
To render piles with Vega-Lite use createVegaLiteRenderer
. The factory function expects as input vega
, vegaLite
, and a base specification (baseSpec
).
For a live demo see https://observablehq.com/@flekschas/piling-js-with-vega-lite.
import * as vega from 'vega/build/vega';
import * as vegaLite from 'vega-lite/build/vega-lite';
import createPilingJs, { createVegaLiteRenderer } from 'piling.js';
const items = Array.from({ length: 10 }, () => ({
src: Array.from({ length: 5 }, () => Math.random()),
}));
// Vega Lite Renderer
const itemRenderer = createVegaLiteRenderer({
vega,
vegaLite,
baseSpec: {
$schema: 'https://vega.github.io/schema/vega-lite/v5.json',
width: itemWidth,
height: itemHeight,
mark: 'bar',
encoding: {
x: { field: 'index', type: 'ordinal' },
y: { field: 'value', type: 'quantitative' },
},
},
});
const piling = createPilingJs(demoEl, { items, itemRenderer });
To render piles with Observable Plot use createObservablePlotRenderer
. The factory function expects as input the Plot
instance, a function that translates an item source into Plot
marks, and Plot.plot()
options.
For a live demo see https://observablehq.com/@flekschas/piling-js-with-observable-plot.
import * as Plot from '@observablehq/plot';
import createPilingJs, { createObservablePlotRenderer } from 'piling.js';
const items = Array.from({ length: 10 }, () => ({
src: Array.from({ length: 5 }, (_, i) => ({ i, value: Math.random() })),
}));
// Observable Plot Renderer
const itemRenderer = createObservablePlotRenderer(
Plot,
(itemSrc) => [Plot.barY(itemSrc, { x: 'index', y: 'value' })],
{ width: itemWidth, height: itemHeight }
);
const piling = createPilingJs(demoEl, { items, itemRenderer });
First, import and instantiate a matrix renderer. If you want to have the aggregation and 1D previews of matrices when pile them up, you can also instantiate an cover renderer and a preview renderer here. (See matrix renderer for more information.)
import { createMatrixRenderer } from 'piling.js';
const itemRenderer = createMatrixRenderer({ colorMap, shape: [3, 3] });
const coverRenderer = createMatrixRenderer({
colorMap: aggregateColorMap,
shape: [3, 3],
});
const previewRenderer = createMatrixRenderer({ colorMap, shape: [3, 1] });
Then, you need aggregators for the aggregation and previews. So import and instantiate aggregators, and you can use mean value as the method of aggregation.
import {
createMatrixCoverAggregator,
createMatrixPreviewAggregator,
} from 'piling.js';
const coverAggregator = createMatrixCoverAggregator('mean');
const previewAggregator = createMatrixPreviewAggregator('mean');
Then add the renderers and aggregators to our piling.js library. Finally add the matrix data to the library.
import createPilingJs from 'piling.js';
const piling = createPilingJs(demoEl, {
items,
itemRenderer,
coverRenderer,
coverAggregator,
previewRenderer,
previewAggregator,
});
An array of objects with one required property src
and other optional user-defined properties:
-
src
: the item data. This can literally be anything. The only requirement is that the renderer understands how to render it.Note, mixed data types are currently not supported. I.e., each item is rendered with the same renderer.
Examples:
// External image data
{
src: 'https://github.com/test.png'
}
// A base64-encoded image
{
src: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACWUlEQVR4AXTRA5DmMACG4Zxt27Ztm2vbtm3btm3r19m2zdHpu0nW6sxb92mbkEO6x3ssjuc7PvlO4Pn0u8F76XFvte1E8TyWutQYWHo2mqZ58kxv+122QGshVxwqKdgjsEFk4b5lslOzz8RtgXWdMmxr1eBQr8ly4xp0yptr4RN2MWxAJ8DOR/XQUcV1ZQMPEhyPWE8R+jCFOmFtKF/vs/dFE0W/YpuRnX5hh/Scy8OE+mKT52zol4qzh81qZBhIo/vKeccgmb4HSjFnoR0gq9MJcLKNVNpxfAaGyg3GcvtJ7EYa/aqTvpsh5HwQ1sHGKCgpwLMrb/61jglZslnZaM4e15cyEokIGTMQa1cPZch8rbFQd5NFQnY0BIJrePPuDz7+Aaoe30RUeRQMI+W1GDBnpZj4tLUKnK1nvXBr1UJkD+iLEZMJzjuogC53fn5GoaAEoSEWsDu3EfrHl6R0m0aKzNtnjItb1wDTBqGGEOgdWA13wyMwXtmHfRmFL/cn+L1lQ303gEa/Im7bJnCmjsXl2ZPZAzEDCDvmLlmExk2r27rrZLOyGzCVbN9vr64Fzt3LbV16cJ3FvXeXxn4nPKYAO44k+HUDaLKSWgX5ji5Id0lCnF0iK8EmBGmOvqDnC8L8UamvCWVxm/oegVHzhNeOHHkIs5eaYPsBN4gIW8NIwxLBLm7s4VR/F3CKC8DjNGX2CDBktlBiUIgvXj6/gTt3b0Bw+RZqODcRncb5buqWf0XZJCJSVidYqFdg2wHV+bLqzsXGDkkhQv+X3J3nG9/sARLDlxspwgCV8d4y+cSemwAAAABJRU5ErkJggg=='
}
// Matrix data
{
src: {
data: [3.1, 2.0, 1.1, 2.1, 3.2, 2.3, 1.0, 2.0, 3.1],
shape: [3, 3],
dtype: 'float32'
}
}
// SVG string
{
src: '<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><line x1="0" y1="80" x2="100" y2="20" stroke="blue" /></svg>'
}
Arguments:
domElement
: reference to the DOM element that will host piling.js' canvasinitialProperties
(optional): an options object for setting initil view properties. The supported properties are the same as forset()
.
Returns: a new piling instance.
Arguments: Same as createLibrary()
.
Returns: a promise resolving to the new piling instance once the library was initialized.
Arguments:
domElement
: reference to the DOM element that will host piling.js' canvasstate
: a complete state object obtained fromexportState()
.options
(optional): options fromimportState()
.
Returns: a promise resolving to the new piling instance once the state was imported.
Returns: one of the properties documented in set()
Arguments:
property
: Either a string defining the property to be set or an object defining key-value pairs to set multiple properties at once.value
: Ifproperty
is a string,value
is the corresponding value. Otherwise,value
is ignored.
Position piles with user-specified arrangement method.
Arguments:
type
, objective
, and options
can be one of the following combinations:
Type | Objective | Options |
---|---|---|
null |
undefined (manual positioning) |
object |
'index' |
a string , object , or function |
object |
'ij' |
a string , object , or function |
object |
'xy' |
a string , object , or function |
object |
'uv' |
a string , object , or function |
object |
'data' |
a string , object , function , or array of the previous types |
object |
The following options are available for all types:
options.onGrouping
[type:boolean
default:false
]: Iftrue
applies the arrangement on every piling event.
Returns: a Promise
resolving once the piles have been arranged.
Notes and examples:
-
The signature of the callback function for types
index
,ij
,xy
anduv
should be as follows:function (pileState, pileId) { // Based on the `type` return the index, or the ij, xy, or uv coordinate return pilePosition; }
Alternatively, one can specify a data property holding the pile position as follows:
piling.arrangeBy('index', 'myIndex'); piling.arrangeBy('index', { property: 'myIndex', aggregator: 'median' });
The
property
must correspond to a property of the item. Theaggregator
can bemin
,max
,mean
,median
,sum
, or a custom function with the following signature:function (data) { // Do something with `data` // Based on the `type` the value must be a scalar or a tuple return aSingleValue; }
-
When
type === 'data'
,objective
can either be astring
,object
,function
, or an array of the previous types to produce a 1D ordering, 2D scatter plot, or multi-dimensional cluster plot.-
The
objective
object can contain the following properties:-
property
[type:string
orfunction
]: A function that retrieves that returns a numerical value for an pile's item.The signature of the callback function looks as follows and must return a numerical value:
function (itemState, itemId, itemIndex) { // Do something return aNumericalValue; }
For convenience, we can automatically access item properties by their name. E.g., the following objective are identical:
piling.arrangeBy('data', 'a'); piling.arrangeBy('data', (itemState) => itemState.a);
-
propertyIsVector
[type:boolean
default:false
]: Iftrue
we assume thatproperty()
is returning a numerical vector instead of a scalar. -
aggregator
[type:string
orfunction
default:mean
]: A function for aggregating the property values of the piles' items.For convenience, we provide the following pre-defined aggregators:
min
,max
,mean
andsum
. -
scale
[type:function
default:d3.scaleLinear
]: A D3 scale function -
inverse
[typeboolean
default:false
]: Iftrue
, the scale will be inverted
-
-
For convenience the following examples are all equivalent:
// Define the property via a simple string piling.arrangeBy('data', 'a'); // Define the property callback function piling.arrangeBy('data', (itemState) => itemState.a); // callback function // Define the property callback function as part of the `objective` object piling.arrangeBy('data', { property: (itemState) => itemState.a }); // Explicitly define piling.arrangeBy('data', ['a']);
-
1D orderings, 2D scatter plots, or multi-dimensional cluster plots are defined by the number passed to
arrangeBy('data', objectives)
:// 1D / linear ordering piling.arrangeBy('data', ['a']); // 2D scatter plot piling.arrangeBy('data', ['a', 'b']); // Multi dimensional cluster plot piling.arrangeBy('data', ['a', 'b', 'c', ...]);
-
-
When
type === 'data'
, it is possible to further customize the behavior with the followingoptions
:-
forceDimReduction
[type:boolean
default:false
]: Iftrue
, dimensionality reduction is always applied.// This can be useful when the property itself is multidimensional. E.g.: const items = [ { src: [0, 1, 1, 13.37, 9, ...] }, ... ] piling.set('items', items); piling.arrangeBy('data', 'src', { forceDimReduction: true });
-
runDimReductionOnPiles
[type:boolean
default:false
]: Iftrue
, dimensionality reduction is run on the current grouping status and updated everytime a pile changes.By default this is deactivated because dimensionality reduction transformations are often not deterministic and feeding even the same data to the algorithm can lead to vastly different layout. Therefore, by default we run the dimensionality reduction on the individual items and given that learned model position the piles. This allows us to keep the layout stable even as the piles change. If you want more fine-grain control over transformation updates we suggest running a
dimensionalityReducer
separately and using it's transform function in combination withpiling.arrangeBy('uv')
andpiling.halt()
/piling.resume()
.// Turning `runDimReductionOnPiles` on will cause a recalculation of the transformation everytime you change piles! piling.arrangeBy('data', ['a', 'b', 'c'], { runDimReductionOnPiles: true });
-
Programmatically group items and piles based on the layout, spatial proximity, or data together.
Arguments:
type
, objective
, and options
can be one of the following combinations:
Type | Objective | Options |
---|---|---|
row |
left , center (default), or right |
|
column |
top , center (default), or bottom |
|
grid |
null (default) or { columns, cellAspectRatio} |
|
overlap |
Overlap threshold in square pixels. Default is 0 . |
|
distance |
Distance threshold in pixels. Default is 0 . |
|
category |
A string , object , function , or array of the previous types. See examples below. |
|
cluster |
A string , object , function , or array of the previous types. See examples below. |
object |
The following options are available for all types:
options.onZoom
[type:boolean
default:false
]: Iftrue
evaluates whether piles should be grouped on every (debounced) zoom event.
Returns: a Promise
resolving once the piles have been grouped.
Notes and examples:
piling.groupBy('row', 'left'); // Pile by row and align pile to the left most item/pile.
piling.groupBy('column', 'top'); // Pile by column and align pile to the top most item/pile.
piling.groupBy('grid'); // Pile by grid using the current layout
piling.groupBy('grid', { columns: 10, cellAspectRatio: 1.5 }); // Pile by grid using a grid of 10 columns with a cell aspect ratio of 1.5 (= width/height)
piling.groupBy('overlap'); // Pile all overlapping items/piles
piling.groupBy('overlap', 64); // Pile all items/piles that overlap by 64 or more square pixels
piling.groupBy('distance'); // Pile all items/piles that touch each other
piling.groupBy('distance', 64); // Pile all items/piles that are 64 or less pixels apart from each other
piling.groupBy('category', 'country'); // Pile all items/piles that have the same country value
piling.groupBy('category', (item) => item.country); // Same as before
piling.groupBy('category', {
property: 'country',
aggregator: (countries) => countries[0],
}); // Same as before but with a custom aggregator that simply picks the first country to define the category
piling.groupBy('cluster', 'x'); // Pile all that cluster together based on the `x` property
piling.groupBy('cluster', (item) => item.x); // Same as above
piling.groupBy('cluster', { property: 'x', aggregator: 'max' }); // Same as above but with a custom aggregator that picks the max `x` value
piling.groupBy('cluster', 'x', { clusterer: dbscan }); // Same as above but with a custom clusterer
piling.groupBy('cluster', 'x', { clustererOptions: { k: 2 } }); // Same as above but with customized clusterer options
Destroys the piling instance by disposing all event listeners, the pubSub instance, canvas, and the root PIXI container.
This will display a popup across the entire piling.js element to temporarily block all interactions. This is useful if you are doing some asynchronous job outside piling and want to prevent user interactions.
Arguments:
options
(optional): Object with the two properties:text
andspinner
(defaulttrue
)
Render the root PIXI container.
This will the halting popup.
Scatter all the piles at the same time.
Subscribe to an event.
eventName
needs to be one of these events.
eventHandler
is a callback function which looks like this:
const eventHandler = (eventData) => {
// handle event here
};
Unsubscribe from an event. See events for all the events.
Arguments:
options
(optional): Object with the following properties:serialize
(defaultfalse
): Iftrue
Piling.js will serialize the state into a string. This operation is similar to but more specialized thanJSON.stringify
Returns: current state object.
Arguments:
state
: Previously exported state object.options
(optional): Object with the following properties:deserialize
(defaultfalse
): Iftrue
Piling.js will deserializestate
assuming it was serialized withpiling.exportState({ serialize: true })
overwriteState
(defaultfalse
): Iftrue
replaces the current store withstate
. Otherwise the properties instate
will only override properties in the current state.
Returns: a promise that resolves once the state was imported
Arguments:
serializedState
: Serialized state string fromserializeState()
Returns: a state object
Arguments:
state
: State object frompiling.exportState()
Returns: Serialized state as a string. This operation is similar to but more specialized than JSON.stringify
.
Name | Type | Default | Constraints | Unsettable |
---|---|---|---|---|
darkMode | boolean | false |
false |
|
coverRenderer | function | see renderers |
true |
|
backgroundColor | string or int | 0x000000 |
false |
|
focusedPiles | array | [] |
the id of current focused pile | true |
coverAggregator | function | see aggregators |
true |
|
depiledPile | array | [] |
the id of the pile to be depiled | true |
depileMethod | string | originalPos | originalPos or closestPos |
true |
easing | function | cubicInOut | see notes |
true |
gridColor | string or int | 0x787878 |
can be HEX, RGB, or RGBA string or hexadecimal value | false |
gridOpacity | float | 1.0 |
must be in [0 ,1 ] |
false |
items | array | [] |
see data |
false |
itemSize | int | number of pixels | true |
|
itemSizeRange | array | [0.7, 0.9] |
array of two numbers between (0, 1) | true |
columns | int | 10 |
ignored when itemSize is defined |
false |
rowHeight | int | true |
||
cellAspectRatio | float | ignored when rowHeight is defined |
false |
|
cellPadding | int | true |
||
cellSize | int | number of pixels | true |
|
lassoFillColor | string or int | 0xffffff |
can be HEX, RGB, or RGBA string or hexadecimal value | false |
lassoFillOpacity | float | 0.15 |
must be in [0 ,1 ] |
false |
lassoShowStartIndicator | boolean | true |
false |
|
lassoStartIndicatorOpacity | float | 0.1 |
must be in [0 ,1 ] |
false |
lassoStrokeColor | string or int | 0xffffff |
can be HEX, RGB, or RGBA string or hexadecimal value | false |
lassoStrokeOpacity | float | 0.8 |
must be in [0 ,1 ] |
false |
lassoStrokeSize | int | 1 |
must be greater or equal than 1 |
false |
layout | object | read-only | false |
|
orderer | function | row-major | see notes |
true |
magnifiedPiles | array | [] |
the id of current magnified pile | true |
navigationMode | string | auto | Can be one of auto, panZoom, or scroll | false |
pileBackgroundColor | string, int or function | can be HEX, RGB, or RGBA string or hexadecimal value | false |
|
pileBackgroundOpacity | float or function | 0 |
must be in [0 ,1 ] |
false |
pileBackgroundColorHover | string, int or function | can be HEX, RGB, or RGBA string or hexadecimal value | false |
|
pileBackgroundOpacityHover | float or function | 0.85 |
must be in [0 ,1 ] |
false |
pileBackgroundColorFocus | string, int or function | can be HEX, RGB, or RGBA string or hexadecimal value | false |
|
pileBackgroundOpacityFocus | float or function | must be in [0 ,1 ] |
false |
|
pileBackgroundColorActive | string, int or function | can be HEX, RGB, or RGBA string or hexadecimal value | false |
|
pileBackgroundOpacityActive | float or function | must be in [0 ,1 ] |
false |
|
pileBorderColor | string, int or function | 0x808080 |
can be HEX, RGB, or RGBA string or hexadecimal value | false |
pileBorderOpacity | float or function | 1.0 |
must be in [0 ,1 ] |
false |
pileBorderColorHover | string, int or function | 0x808080 |
can be HEX, RGB, or RGBA string or hexadecimal value | false |
pileBorderOpacityHover | float or function | 1.0 |
must be in [0 ,1 ] |
false |
pileBorderColorFocus | string, int or function | 0xeee462 |
can be HEX, RGB, or RGBA string or hexadecimal value | false |
pileBorderOpacityFocus | float or function | 1.0 |
must be in [0 ,1 ] |
false |
pileBorderColorActive | string, int or function | 0xffa5da |
can be HEX, RGB, or RGBA string or hexadecimal value | false |
pileBorderOpacityActive | float or function | 1.0 |
must be in [0 ,1 ] |
false |
pileBorderSize | float or function | 0 |
see notes |
true |
pileCellAlignment | string | topLeft | topLeft , topRight , bottomLeft , bottomRight or center |
true |
pileContextMenuItems | array | [] |
see examples below | true |
pileCoverInvert | boolean or function | false |
see examples below | false |
pileCoverScale | float or function | 1.0 |
see examples below | false |
pileItemBrightness | string, int or function | 0 |
must be in [-1,1] where -1 refers to black and 1 refers to white |
false |
pileItemInvert | boolean or function | false |
can only be true or false where true refers inverted colors and false are normal colors |
false |
pileItemOffset | array or function | [5, 5] |
see notes |
true |
pileItemOpacity | float or function | 1.0 |
see notes |
true |
pileItemRotation | float or function | 0 |
see notes |
true |
pileItemTint | string, int or function | 0xffffff |
can be HEX, RGB, or RGBA string or hexadecimal value | true |
pileLabel | string, array, function or object | see notes |
true |
|
pileLabelAlign | string or function | bottom |
bottom or top |
true |
pileLabelColor | array or function | see notes |
true |
|
pileLabelFontSize | int or function | 8 | true |
|
pileLabelHeight | float or function | 2 | true |
|
pileLabelStackAlign | string or function | horizontal |
horizontal or vertical |
true |
pileLabelSizeTransform | string or function | histogram |
see notes |
true |
pileLabelText | boolean or function | false |
see notes |
true |
pileLabelTextMapping | array or function | see notes |
true |
|
pileLabelTextColor | string or int | 0x000000 |
see notes |
true |
pileLabelTextOpacity | float | 1 |
see notes |
true |
pileLabelTextStyle | object | {} |
see PIXI.TextStyle | true |
pileOpacity | float or function | 1.0 |
see notes |
true |
pileOrderItems | function | see notes |
true |
|
pileScale | float or function | 1.0 |
see notes |
true |
pileSizeBadge | boolean or function | false |
if true show the pile size as a badge |
true |
pileSizeBadgeAlign | array or function | ['top', 'right'] |
if true show the pile size as a badge |
true |
popupBackgroundOpacity | float | 0.85 |
must be in [0 ,1 ] |
false |
previewAggregator | function | see aggregators |
true |
|
previewBackgroundColor | string, int or function | 'inherit' |
can be HEX, RGB, or RGBA string or hexadecimal value | false |
previewBackgroundOpacity | float or function | 'inherit' |
must be in [0 ,1 ] |
false |
previewBorderColor | string, int or function | 0xffffff |
can be HEX, RGB, or RGBA string or hexadecimal value | false |
previewBorderOpacity | float or function | 0.85 |
must be in [0 ,1 ] |
false |
previewItemOffset | function | see notes |
true |
|
previewOffset | number or function | 2 |
see notes |
false |
previewPadding | number or function | 2 |
see notes |
false |
previewRenderer | function | see renderers |
true |
|
previewScaling | array or function | [1,1] |
the spacing between 1D previews | false |
previewSpacing | number or function | 2 |
the spacing between 1D previews | true |
renderer | function | see renderers |
false |
|
showGrid | boolean | false |
false |
|
tempDepileDirection | string | horizontal | horizontal or vertical | true |
tempDepileOneDNum | number | 6 |
the maximum number of items to be temporarily depiled in 1D layout | true |
temporaryDepiledPile | array | [] |
the id of the pile to be temporarily depiled | true |
zoomScale | number or function | 1 |
Allows adjusting the zoom-induced pile scale | true |
Examples and Notes:
-
To set a single property do:
piling.set('propertyName', value);
To set multiple values at once do:
piling.set({ propertyNameA: valueA, propertyNameB: valueB, });
-
A property is considered unsettable if its value can be removed.
-
When
darkMode
istrue
we assume that piling.js is used with a black background and the color of certain UI elements are adjusted automatically -
orderer
is the function for positioning piles, the default function is row-major orderer which looks like this:// The default row-major order // A function that takes as input the number of columns and outputs // another function that takes in as input the position of a 1D ordering and // outputs the an array of `x` an `y` coordinates. const rowMajor = (cols) => (index) => [ index % cols, Math.floor(index / cols), ];
-
The following properties to define the grid:
cellSize
,cellPadding
,columns
,rowHeight
andcellAspectRatio
.One has to at least provide
columns
orcellSize
to define a grid. IfcellSize
is definedcolumns
are ignored. Similarly, whenrowHeight
is definedcellAspectRatio
is ignored.When
cellSize
is defined,cellSize
andcellPadding
add up together to define the cell width. WhencellSize
is undefined,cellSize
is defined by the derived cell width (givencolumns
) minuescellPadding
! -
itemSize
defines the size of the items. If it's not defined, it should be derived from the cell size. -
easing
is the easing function for animation, the default function iscubicInOut
which looks like this:const cubicInOut = (t) => { t *= 2; const p = (t <= 1 ? t * t * t : (t -= 2) * t * t + 2) / 2; return p; };
-
All color properties (like
backgroundColor
,lassoFillColor
, etc.) support HEX, RGB, and RGBA string and decimal values. E.g.,piling.set('lassoFillColor', '#ff0000'); piling.set('lassoFillColor', 'rgb(255, 0, 0)'); piling.set('lassoFillColor', 'rgba(255, 0, 0, 1)'); piling.set('lassoFillColor', 0xff0000);
Additionally, all lasso and pile related color properties (like
lassoFillColor
,pileBorderColor
, etc.) support automatic setting of the opacity. E.g.,// The following... piling.set('lassoFillColor', 'rgba(255, 0, 0, 0.66)'); // ...is a shorthand for... piling.set('lassoFillColor', 'rgb(255, 0, 0)'); piling.set('lassoFillOpacity', 0.66);
-
If
pileBackgroundColor
is not defined, it will be set to the same color as the background depending ondarkMode
.pileBackgroundColorActive
,pileBackgroundColorFocus
, andpileBackgroundColorHover
will inherit frompileBackgroundColor
if not defined.pileBackgroundOpacityFocus
andpileBackgroundOpacityActive
will inherit frompileBackgroundOpacityHover
if not defined. -
pileContextMenuItems
is an array of objects, which must have alabel
andcallback
property. Optionally, an object can also specify anid
, which will be assigned to the corresponding button in the context menu, andkeepOpen: true
to not close the context menu after clicking on the corresponding button. Thecallback
function is triggered whenever the user clicks on the corresponding button and it receives the pile definition, which contains the pile id, the ids of the items on the pile, and the pile's x and y coordinate.// Add a custom context menu const myClickHandler = (pile) => { console.log('Hi!', pile); // The log statement could look as follows for example: // Hi!, { id: 5, items: [2, 0, 8], x: 215, y: 8 } }; piling.set('pileContextMenuItems', [ { id: 'my-click', label: 'Click me!', callback: myClickHandler, }, ]);
-
pileBorderSize
,pileCoverInvert
,pileCoverScale
,pileOpacity
andpileScale
can be set to a static float value, or the user can specify a callback function to dynamically style piles. E.g.,// Set to a static number piling.set('pileScale', 2.0); // Set to a callback function piling.set('pileOpacity', (pile) => 1 / pile.items.length);
The callback function is evaluated for each pile and receives the current pile. The function’s return value is then used to set each pile’s corresponding property. I.e., the function signature is as follows:
function (pile) { // Do something return value; }
-
pileOrderItems
is used to sort the items on a pile before positioning the items. It should be set to a callback function which will receive the current pile, and should return an array of sorted itemIDs. E.g.,const pileOrderItems = (pileState) => pileState.items.sort((a, b) => a - b); piling.set('pileOrderItems', pileOrderItems);
The signature of the callback function is as follows:
function (pileState) { // Sort itemIDs return arrayOfSortedIds; }
-
pileItemOffset
can be set to an array or a callback function. The array should be a tuple specifying the x and y offset in pixel. E.g.,// Align items in y-axis piling.set('pileItemOffset', [0, 5]);
See the next note for the signature of the callback function ⬇️
-
pileItemBrightness
,pileItemInvert
,pileItemOpacity
,pileItemRotation
andpileItemTint
can either be set to a static value or a callback function to dynamically style items. E.g.,// Set to a static number piling.set('pileItemOpacity', 0.5); // Set to a callback function piling.set( 'pileItemOpacity', (item, i, pile) => (pile.items.length - i) / pile.items.length );
The callback function is evaluated, in order, for each item on every pile and receives the current item, the item's current index, and pile that the item belongs to. The function’s return value is then used to set the opacity of each pile’s item. I.e., the function signature is as follows:
function (item, index, pile) { // Do something return value; }
The function should return a value within
[0, 1]
. -
The default value of
previewBackgroundColor
andpreviewBackgroundOpacity
is'inherit'
, which means that their value inherits frompileBackgroundColor
andpileBackgroundOpacity
. If you want preview's background color to be different from pile's, you can set a specific color. -
pileLabel
can be set to astring
,object
,function
, orarray
of the previous types. E.g.,piling.set('pileLabel', 'country'); piling.set('pileLabel', (itemState) => itemState.country); piling.set('pileLabel', ['country', 'year']); piling.set('pileLabel', { property: (item) => item.country, aggregator: (countries) => countries[0], });
-
pileLabelColor
can be set to a HEX, RGB string or hexadecimal value, anarray
of the previous types, or a callback function. E.g.,piling.set('pileLabelColor', '#e05aa9'); piling.set('pileLabelColor', ['#e05aa9', '#e0722b', '#e0a638']); piling.set('pileLabelColor', (label, allLabels) => myOwnFancyColorMap[label]);
The callback function receives the current label (
string
), and an array of all the labels, and it should return a HEX, RGB string or hexadecimal value. The signature is as follows:function (label, allLabels) { // Pick the color for the `label` return color; }
-
pileLabelSizeTransform
is used to get a relative distribution of categories across a pile. It can be set to'histogram'
or a callback function. E.g.,// The following 2 examples are equivalent piling.set('pileLabelSizeTransform', 'histogram'); piling.set('pileLabelSizeTransform', (counts, labels) => { // This function normalizes the counts to be in [0,1] const maxCount = Math.max(...counts); return counts.map((x) => x / maxCount); });
The callback function should receive an array of the sum of each label and return an array of scale factors that ranges in
[0, 1]
. The signature is as follows:function (histogram) => { // Do stuff return arrayofScaleFactors; };
-
pileLabelTextMapping
can be set to anarray
of strings, or a callback function. E.g.,piling.set('pileLabelTextMapping', ['red', 'blue', 'yellow', 'green']); piling.set( 'pileLabelTextMapping', (label, allLabels) => `${abbreviation[label]}` );
The callback function receives the current label (
string
), and an array of all the labels, and it should return a text string. The signature is as follows:function (label, allLabels) { // Create text return text; }
-
pileSizeBadge
andpileSizeBadgeAlign
allow to show the pile size as badge. Both can be defined dynamically using a pile-specific callback function.pileSizeBadgeAlign
accepts an tuple of[yAlign, xAlign]
whereyAlign
can be one oftop
,center
,bottom
, andxAlign
can be one ofleft
,center
,right
. -
previewPadding
defines how much larger the preview items' background is sized. For example, a padding of2
means that the background of a preview item is 1 pixel larger in eath direction (top, right, bottom, left). -
previewItemOffset
is used to position the previews individually based on a per-preview item specific callback function. If it's not set, the preview will be positioned to the top of the cover by default. It should be set to a callback function which receives the current preview item, the item's current index, and the pile that the item belongs to, and it should return a tuple of xy position of the preview. I.e., the function signature is as follows:piling.set('previewItemOffset', (itemState, itemIndex, pileState) => { // Calculate the position return [x, y]; });
-
previewAlignment
,previewOffset
andpreviewSpacing
are used to globally position preview items.previewAlignment
specifies the alignment direction, which can one oftop
(default),left
,right
, orbottom
. -
previewOffset
defines the offset in pixel to the pile cover andpreviewSpacing
defines the combined spacing around a pile. E.g.,previewSpacing === 2
results in a 1px margin around the preview items. Both properties can be dynamically defines using a per-pile callback function as follows:piling.set('previewOffset', (pileState) => { // Define the offset return offset; });
-
previewScaling
defines how much preview items are scaled according to the cover. Normally, the previews' scale factor is identical to the cover's scale factor. Using this property the impact of this scale factor can be adjusted. The final x and y scale will then be determined as follows xScale = 1 + (scaleFactor - 1) * scaling[0]. E.g., to not adjust the y scale to the cover but keep the x scale one can setpreviewScaling = [1,0]
. The scaling can be determined dynamically using a per-pile callback function as follows:piling.set('previewScaling', (pileState) => { // Define the x and y scaling return [xScaling, yScaling]; });
Additionally,
previewScaleToCover
allows to automatically pick the scale factor such that the width or height of the preview are identical to the width and height of the cover. To do so,previewScaleToCover
accepts a tuple of Boolean values (for the width and height scaling), wheretrue
will make the preview's width/height scale to the cover's with/height. -
zoomScale
allows to dynamically adjust the scale factor related to zooming. By default zooming does not affect the scale!piling.set('zoomScale', (cameraScale) => cameraScale >= 1 ? 1 + (cameraScale - 1) / 2 : 1 - (1 - cameraScale) / 2 );
Name | Event Data | Description |
---|---|---|
render | Published when the data has been rendered | |
update | {action} |
Published when the redux store is updated |
itemUpdate | Published after items updates and their consequences have been applied | |
pileEnter | {target, sourceEvent} |
Published when the mouse cursor enters a pile |
pileLeave | {target, sourceEvent} |
Published when the mouse cursor leaves a pile |
pileDragStart | {target, sourceEvent} |
Published when a pile drag is started |
pileDragMove | {target, sourceEvent} |
Published when a pile is dragged |
pileDragEnd | {target, sourceEvent} |
Published when a pile is dropped |
pilesFocus | {targets} |
Published when piles are focused |
pilesBlur | {targets} |
Published when piles are blurred |
pilesActive | {targets} |
Published when piles are temporarily depiled |
pilesInactive | {targets} |
Published when temporarily depiled piles are closed |
pilesPositionEnd | {targets} |
Published when piles positioning ended |
Notes:
action
is the name of the action that triggered the updatepile
is the state of the corresponding pilesourceEvent
is the original browser event that triggered this event
In the following we describe the most important aspects of the library state. This description focuses on the user-facing aspects, primarily the pile
and item
state as these are used by dynamic properties and are returned by events.
A list of objects storing the item data
Type: Array of Objects
Example:
See the examples at #Data
A list of objects with the following properties:
items
: a list of item IDsx
: the current x positiony
: the current y position
Type: Array of Objects
Example:
[
{
items: [5, 12],
x: 10,
y: 10
},
...
]
A renderer should be a function that takes as input an array of the value of src
property in your data that determining the source, and outputs promises which resolve to Pixi Texture objects.
Piling.js support three types of renderer:
itemRenderer
: responsible for rendering items.coverRenderer
: responsible for rendering the cover of a pile.previewRenderer
: responsible for rendering item previews.
Piling.js ships with the following set of renderers, which can be imported from piling.js
.
createImageRenderer
: renders image data.createMatrixRenderer
: renders a numerical matrix as a heatmap.createSvgRenderer
: renders an SVG element. Useful when you're working with D3.createRepresentativeRenderer
: renders multiple items into one gallery of representatives.
Constructor:
import { createImageRenderer } from 'piling.js';
const imageRenderer = createImageRenderer();
Src/Data: image renderer can currently render images from an URL or base64 encoding. I.e., the src
property in your data needs be URL pointing to an image or the base64 encoding of an image.
Constructor:
import { createSvgRenderer } from 'piling.js';
const svgRenderer = createSvgRenderer(options);
Src/Data: The SVG renderer can render SVG strings and SVG DOM elements.
Options is an object of key-value pairs with support for the following properties:
Name | Type | Default | Constraints |
---|---|---|---|
width | int | Width of the rendered texture in pixel | |
height | int | Height of the rendered texture in pixel | |
color | string | A valid CSS color property | |
background | string | A valid CSS background property |
Constructor:
import { createMatrixRenderer } from 'piling.js';
const matrixRenderer = createMatrixRenderer(properties);
Src/Data: The matrix renderer requires that an item provides a src object of the following form:
{
data: [1, 2, 3, 4],
shape: [2, 2],
dtype: 'float32'
}
Properties is an object of key-value pairs. The list of all understood properties is given below.
Name | Type | Default | Constraints |
---|---|---|---|
colorMap | array | Array of rgba | |
shape | array | Matrix shape | |
minValue | number | 0 |
|
maxValue | number | 1 |
Note: shape
describes the size of matrix, e.g., for a 4x5 matrix, shape
should be [4, 5]
Examples:
import { interpolateRdPu } from 'd3-scale-chromatic';
import createMatrixRenderer from 'piling.js';
const rgbStrToRgba = (rgbStr, alpha = 1) => {
return [
...rgbStr
.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/)
.slice(1, 4)
.map((x) => parseInt(x, 10) / 256),
alpha,
];
};
const numColors = 256;
const colorMap = new Array(numColors)
.fill(0)
.map((x, i) => rgbStrToRgba(interpolateRdPu(i / numColors)));
const matrixRenderer = createMatrixRenderer({ colorMap, shape: [16, 16] });
Note:
You can pass in different color map or shape to create aggregateRender
for matrix aggregation and previewRender
for matrix preview, so that the pile will have an aggregation of all the matrices on the pile cover, and matrix previews on top of the aggregation.
For example:
const aggregateColorMap = new Array(numColors)
.fill(0)
.map((x, i) => rgbStr2rgba(interpolateOrRd((numColors - i) / numColors)));
const coverRenderer = createMatrixRenderer({
colorMap: aggregateColorMap,
shape: [16, 16],
});
const previewRenderer = createMatrixRenderer({ colorMap, shape: [16, 1] });
Note, you also need to define the appropriate cover and preview aggregators for this to work.
Constructor:
import { createRepresentativeRenderer } from 'piling.js';
const representativeRenderer = createRepresentativeRenderer(
itemRenderer,
options
);
Src/Data: Since the item-based rendering is done by itemRenderer
, the data needs to be in a format understood by itemRenderer
.
itemRenderer: The basic item renderer.
Options is an object of key-value pairs with support for the following properties:
Name | Type | Default | Constraints |
---|---|---|---|
size | int | 96 |
Size of the longer side, which is determined by the number of representatives |
innerPadding | int | 2 |
Padding in pixels between representative images |
outerPadding | int | 0 |
Padding in pixels the the outer border |
maxNumberOfRepresentatives | int | 9 |
Maximum number of representatives to be shown. You can typically ignore this setting as this number is derived by the representative aggregator. |
backgroundColor | int | 0x000000 |
A background color for the gallery in form of a hexa-decimal number |
Note:
- The representative renderer really only makes sense as the
coverRenderer
- The representative renderer only works if you also specify
representativeAggregator
If you want to define your own renderer to render your own data, you can do something as follows:
// The actual renderer
const renderCustomTexture = (src, options) => {
// A complicated function that turns the src into a PIXI texture object
return PIXI.Texture.from(...);
}
// Factory function
const createCustomRenderer = options => sources => {
Promise.all(
sources.map(src => {
return new Promise((resolve, reject) => {
const texture = renderCustomTexture(src, options);
if (!texture) reject(new Error('Could not render texture'));
resolve(texture);
});
})
)
};
Call set method to add renderers to the library.
// for all the items
piling.set('itemRenderer', itemRenderer);
// for the aggregation of a pile
piling.set('coverRenderer', coverRenderer);
// for the item preview
piling.set('previewRenderer', previewRenderer);
Aggregators are used to aggregate items.
An aggregator is a function that takes as input an array of items and outputs a promise which resolve to an array of aggregated source values that can be passed to the renderers.
Piling.js support three types of renderer:
coverAggregagtor
: responsible for aggregagting items for the cover.previewAggregagtor
: responsible for aggregagting full items to previews items.
Piling.js ships with the following set of aggregators, which can be imported from piling.js
.
createMatrixCoverAggregator
: aggregates numerical matrixs.createMatrixPreviewAggregator
: aggregates a numerical matrix to a numerical vector.createRepresentativeAggregator
: aggregates multiple items to a set of representatives items.
The aggregator for all the matrices on a pile, it will be shown on the cover of the pile.
Constructor:
import { createMatrixCoverAggregator } from 'piling.js';
const matrixCoverAggregator = createMatrixCoverAggregator(aggregator);
Aggregator
is the method of aggregation, could be'mean'
,'variance'
,'std'
. The default value is'mean'
.
The 1D preview aggregator for each matrix on a pile, it will be shown on top of the pile cover.
Constructor:
import { createMatrixPreviewAggregator } from 'piling.js';
const matrixPreviewAggregator = createMatrixPreviewAggregator(aggregator);
Aggregator
is the method of aggregation, could be'mean'
,'variance'
,'std'
. The default value is'mean'
.
The representative aggregator selects k
number of representative items from all the items on a pile. It should be used as a cover aggregator in combination with the representative cover renderer.
Constructor:
import { createRepresentativeAggregator } from 'piling.js';
const representativeAggregator = createRepresentativeAggregator(k, options);
The representative aggregator uses kmeans++ to determine k
clusters in the data and then selects the items closest to the cluster centroids. See piling.js.org/?example=vitessce for an example.
-
k
: the number of representatives to select. -
options
: is an object of key-value pairs with support for the following properties:Name Type Default Constraints distanceFunction function or string l2
The string can be one of l1
,mahattan
,l2
,euclidean
and falls back tol2
initialization array or string kmpp
A list of initial centroids or kmeans++
initializationmaxIterations int 1000 * log10(n)
A valid CSS color property valueGetter function x => x
A function defining how to access the data representation to be used for clustering. The accessed value must either be a number or an array.
If you want to define your own aggregator, you can do something as follows:
const customAggregator = (items) =>
new Promise((resolve, reject) => {
// Aggregate items somehow
const aggregatedSrc = myMagicAggregation(items);
if (!aggregatedSrc) reject(new Error('Aggregation failed'));
// The resolve source must be understood by the aggregate renderer!
resolve(aggregatedSrc);
});
Call set method to add aggregators to the library.
piling.set('coverAggregator', coverAggregator);
piling.set('previewAggregator', previewAggregator);
To limit the number of shown previews, the preview aggregator function can
resolve a list of aggregated sources where some of the entries are null
. These
null
entries will be filtered out by piling.js.
For example, the following aggregator limits the previews to those with an even index.
const customAggregator = (items) =>
Promise.resolve(items.map((item, i) => i % 2 === 0 ? item : null);
It's important that the number of items and number of aggregated previews match, otherwise piling.js wouldn't be able to match the previews to their associated item.
A dimensionality reducer is a transformation function that that reduced multi-dimensional input data down to two normalized dimension.
A dimensionality reducer should be a function that takes as input a 2D nested numerical array, and output promises which resolve to an array of aggregated source value that can be passed to the renderers.
We currently provide predefined dimensionality reducers for UMAP.
The 1D preview aggregator for each matrix on a pile, it will be shown on top of the pile cover.
Constructor:
import { createUmap } from 'piling.js';
const umap = createUmap(config, options);
config
is anobject
that lets you customize UMAP's parameters.options
is anobject
for customizing the output transformation with the follwing properties:
Name | Type | Default | Constraints |
---|---|---|---|
padding | float | 0.1 |
Must be greater than zero |
If you want to define your own dimensionality reducer, you can do something as follows:
// Factory function
const createCustomAggregator = () => {
// Your code here
return {
fit(data) {
// The following function must be asynchronous and return a promise that
// resolves once the fitting is done.
return asyncFitFunction(data);
},
transform(data) {
return getTransformedData(data);
},
};
};
Call set method to add aggregators to the library.
piling.set('dimensionalityReducer', umap);
- Click and hold on a pile
- Move the mouse onto another single pile
- Release the mouse
-
Hold down SHIFT
-
Click on the piles you want to select
-
Click on a previously selected pile to group all selected piles onto this target pile
-
Alternatively, double click on a new pile to group all selected piles onto this target pile
- Click on the background. A translucent circle will appear
- Click and hold into the circle to activate the lasso.
- Drag the cursor around the pile you want to group.
- Release the mouse to trigger the grouping.
- Click on the pile you want to browse
- Hover the mouse on the item's visible part to see the item
- Click on the pile you want to browse
- Hover the mouse on one item's preview to see the item
-
Double click on the pile to temporarily de-pile the pile
-
Double click again on the pile or on the background to close temporarily de-pile
-
Alternatively, right click on the pile to open the context menu
-
Click on Temp. Depile button to temporarily de-pile the pile
-
Right click on the pile again and click on Close Temp. Depile button to close temporarily de-pile
- Right click on the pile to open the context menu
- Click on Browse Separately button to browse the pile hierarchically
- Click on the breadcrumb trail to go back to the previous level
-
Hold down ALT
-
Click on a pile to de-pile it
-
Alternatively, right click on the pile to open the context menu
-
Click on Depile button to de-pile
-
Hold down ALT
-
Hover the mouse on a pile and scroll to manually magnify it
-
Click on the background to automatically unmagnify it
-
Alternatively, right click on the pile to open the context menu
-
Click on Magnify button to automatically magnify the pile
-
Right click on a magnified pile
-
Click on Unmagnify button to automatically unmagnify the pile
- Right click on the background to open the context menu
- Click on Show Grid button to show the grid
- Right click on the background again and click on Hide Grid button to hide the grid
- Right click will open the custormized context menu
- While pressing ALT, right click will show the default context menu in the browser