LineUp is an interactive technique designed to create, visualize and explore rankings of items based on a set of heterogeneous attributes.
- scalable (~1M rows)
- heterogenous attribute types (string, numerical, categorical, boolean, date)
- composite column types (weighted sum, min, max, mean, median, impose, nested, ...)
- array (multi value) and map column types (strings, stringMap, numbers, numberMap, ...)
- filtering capabilities
- hierarchical sorting (sort by more than one sorting criteria)
- hierarchical grouping (split rows in multiple separate groups)
- group aggregations (show a whole group as a single group row)
- numerous visualizations for summaries, cells, and group aggregations
- side panel for easy filtering and column management
- React, Angular, Vue.js, Polymer, RShiny, Juypter, ObservableHQ, and Power BI wrapper
- Demo Application with CSV import and export capabilities
- API Documentation based on generated TypeDoc documenation
Installation
npm install --save lineupjs
<link href="https://unpkg.com/lineupjs/build/LineUpJS.css" rel="stylesheet">
<script src="https://unpkg.com/lineupjs/build/LineUpJS.js"></script>
Minimal Usage Example
// generate some data
const arr = [];
const cats = ['c1', 'c2', 'c3'];
for (let i = 0; i < 100; ++i) {
arr.push({
a: Math.random() * 10,
d: 'Row ' + i,
cat: cats[Math.floor(Math.random() * 3)],
cat2: cats[Math.floor(Math.random() * 3)]
})
}
const lineup = LineUpJS.asLineUp(document.body, arr);
Advanced Usage Example
// arr from before
const builder = LineUpJS.builder(arr);
// manually define columns
builder
.column(LineUpJS.buildStringColumn('d').label('Label').width(100))
.column(LineUpJS.buildCategoricalColumn('cat', cats).color('green'))
.column(LineUpJS.buildCategoricalColumn('cat2', cats).color('blue'))
.column(LineUpJS.buildNumberColumn('a', [0, 10]).color('blue'));
// and two rankings
const ranking = LineUpJS.buildRanking()
.supportTypes()
.allColumns() // add all columns
.impose('a+cat', 'a', 'cat2'); // create composite column
.groupBy('cat')
.sortBy('a', 'desc')
builder
.defaultRanking()
.ranking(ranking);
const lineup = builder.build(document.body);
- Chrome 64+ (best performance)
- Firefox 57+
- Edge 16+
A demo application is located at lineup_app. It support CSV Import, CSV Export, JSON Export, CodePen Export, nad local data management.
The application is deployed at https://lineup.js.org/app
LineUp is implemented in clean TypeScript in an object oriented manner. A fully generated API documentation based on TypeDoc is available at https://lineup.js.org/master/docs
LineUp can be build manually or using via the builder design pattern (see Advanced Usage Example). The builder design pattern in the more common way.
The simplest methods to create a new instance are:
- asLineUp returning a ready to use LineUp instance
asLineUp(node: HTMLElement, data: any[], ...columns: string[]): LineUp
- asTaggle returning a ready to use Taggle instance
asTaggle(node: HTMLElement, data: any[], ...columns: string[]): Taggle
- builder returning a new DataBuilder
builder(arr: any[]): DataBuilder`
The DataBuilder
allows on the one hand to specify the individual columns more specificly and the creation of custom rankings.
Builder factory functions for creating column descriptions include:
- buildStringColumn returning a new StringColumnBuilder
buildStringColumn(column: string): StringColumnBuilder
- buildNumberColumn returning a new NumberColumnBuilder
buildNumberColumn(column: string, domain?: [number, number]): NumberColumnBuilder
- buildCategoricalColumn returning a new CategoricalColumnBuilder
buildCategoricalColumn(column: string, categories?: (string | Partial<ICategory>)[]): CategoricalColumnBuilder
- buildHierarchicalColumn returning a new HierarchyColumnBuilder
buildHierarchicalColumn(column: string, hierarchy?: IPartialCategoryNode): HierarchyColumnBuilder
- buildDateColumn returning a new DateColumnBuilder
buildDateColumn(column: string): DateColumnBuilder
- buildActionsColumn returning a new ActionsColumnBuilder
buildActionsColumn(): ActionsColumnBuilder
In order to build custom rankings within the DataBuilder
the buildRanking returning a new RankingBuilder is used.
buildRanking(): RankingBuilder
The relevant classes for creating a LineUp instance manually are LineUp, Taggle, and LocalDataProvider. A LocalDataProvider
is an sub class of ADataProvider
implementing the data model management based on a local JavaScript array. LineUp
and Taggle
are the visual interfaces to the LocalDataProvider
.
The classes can be instantiated either using the factory pattern or via their regular class constructors:
createLineUp(container: HTMLElement, data: ADataProvider, config?: Partial<ILineUpOptions>): LineUp
createTaggle(container: HTMLElement, data: ADataProvider, config?: Partial<ITaggleOptions>): Taggle
createLocalDataProvider(data: any[], columns: IColumnDesc[], options?: Partial<ILocalDataProviderOptions>): LocalDataProvider
new LineUp(node: HTMLElement, data: DataProvider, options?: Partial<ILineUpOptions>): LineUp
new Taggle(node: HTMLElement, data: DataProvider, options?: Partial<ITaggleOptions>): Taggle
new LocalDataProvider(data: any[], columns?: IColumnDesc[], options?: Partial<ILocalDataProviderOptions & IDataProviderOptions>): LocalDataProvider
Both LineUp
and Taggle
are sub classes of ALineUp. The most important functions of this class include:
getHighlight(): number
/setHighlight(dataIndex: number): void
to get and set the highlighted row identified by its index in the data. If none is highlighted-1
is returned.getSelection(): number[]
/setSelection(dataIndices: number[]): void
to get and set the selected rows identified by their indices in the dataon(type: string, listener: IEventListener | null): this
to listen to highlight and selection events. LineUp.js event mechanism is based on d3 dispatch, thus instead of andoff
methodnull
is passed to disable listening to the event. The following events are sent out:
A React wrapper is located at lineupjsx.
Installation
npm install --save lineupjsx
<link href="https://unpkg.com/lineupjsx/build/LineUpJSx.css" rel="stylesheet">
<script src="https://unpkg.com/lineupjsx/build/LineUpJSx.js"></script>
Minimal Usage Example
// generate some data
const arr = [];
const cats = ['c1', 'c2', 'c3'];
for (let i = 0; i < 100; ++i) {
arr.push({
a: Math.random() * 10,
d: 'Row ' + i,
cat: cats[Math.floor(Math.random() * 3)],
cat2: cats[Math.floor(Math.random() * 3)]
})
}
<LineUp data={arr}/>
Result is same as the builder minimal example
Advanced Usage Example
// arr from before
<LineUp data={arr} defaultRanking>
<LineUpStringColumnDesc column="d" label="Label" width={100} />
<LineUpCategoricalColumnDesc column="cat" categories={cats} color="green" />
<LineUpCategoricalColumnDesc column="cat2" categories={cats} color="blue" />
<LineUpNumberColumnDesc column="a" domain={[0, 10]} color="blue" />
<LineUpRanking groupBy="cat" sortBy="a:desc">
<LineUpSupportColumn type="*" />
<LineUpColumn column="*" />
<LineUpImposeColumn label="a+cat" column="a" categeoricalColumn="cat2" />
</LineUpRanking>
</LineUp>;
Result is same as the builder advanced example
An Angular wrapper is located at nglineup.
Installation
npm install --save nglineup
Minimal Usage Example
app.module.ts
:
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { LineUpModule } from '../lib/lineup.module';
import { AppComponent } from './app.component.1';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
LineUpModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
app.component.ts
:
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent {
readonly data = <any[]>[];
readonly cats = ['c1', 'c2', 'c3'];
constructor() {
const cats = this.cats;
for (let i = 0; i < 100; ++i) {
this.data.push({
a: Math.random() * 10,
d: 'Row ' + i,
cat: cats[Math.floor(Math.random() * 3)],
cat2: cats[Math.floor(Math.random() * 3)]
});
}
}
}
app.component.html
:
<lineup-lineup [data]="data"></lineup-lineup>
Result is same as the builder minimal example
Advanced Usage Example
app.component.html
:
<lineup-lineup [data]="data" [defaultRanking]="true" style="height: 800px;">
<lineup-string-column-desc column="d" label="Label" [width]="100"></lineup-string-column-desc>
<lineup-categorical-column-desc column="cat" [categories]="cats" color="green"></lineup-categorical-column-desc>
<lineup-categorical-column-desc column="cat2" [categories]="cats" color="blue"></lineup-categorical-column-desc>
<lineup-number-column-desc column="a" [domain]="[0, 10]" color="blue"></lineup-number-column-desc>
<lineup-ranking groupBy="cat" sortBy="a:desc">
<lineup-support-column type="*"></lineup-support-column>
<lineup-column column="*"></lineup-column>
<lineup-impose-column label="a+cat" column="a" categoricalColumn="cat2"></lineup-impose-column>
</lineup-ranking>
</lineup-lineup>
Result is same as the builder advanced example
A Vue.js wrapper is located at vue-lineup.
Installation
npm install --save vue-lineup
Minimal Usage Example
const cats = ['c1', 'c2', 'c3'];
const data = [];
for (let i = 0; i < 100; ++i) {
data.push({
a: Math.random() * 10,
d: 'Row ' + i,
cat: cats[Math.floor(Math.random() * 3)],
cat2: cats[Math.floor(Math.random() * 3)],
});
}
// enable plugin to register components
Vue.use(VueLineUp);
const app = new Vue({
el: '#app',
template: `<LineUp v-bind:data="data" />`,
data: {
cats,
data
}
});
Result is same as the builder minimal example
Advanced Usage Example
const app = new Vue({
el: '#app',
template: `<LineUp v-bind:data="data" defaultRanking="true" style="height: 800px">
<LineUpStringColumnDesc column="d" label="Label" v-bind:width="100" />
<LineUpCategoricalColumnDesc column="cat" v-bind:categories="cats" color="green" />
<LineUpCategoricalColumnDesc column="cat2" v-bind:categories="cats" color="blue" />
<LineUpNumberColumnDesc column="a" v-bind:domain="[0, 10]" color="blue" />
<LineUpRanking groupBy="cat" sortBy="a:desc">
<LineUpSupportColumn type="*" />
<LineUpColumn column="*" />
</LineUpRanking>
</LineUp>`,
data: {
cats,
data
}
});
Result is same as the builder advanced example
A Polymer 2.0 web component wrapper is located at lineup-element.
Installation
bower install https://github.com/lineupjs/lineup-element
<link rel="import" href="bower_components/lineup-element/lineup-element.html">
Minimal Usage Example
// generate some data
const arr = [];
const cats = ['c1', 'c2', 'c3'];
for (let i = 0; i < 100; ++i) {
arr.push({
a: Math.random() * 10,
d: 'Row ' + i,
cat: cats[Math.floor(Math.random() * 3)],
cat2: cats[Math.floor(Math.random() * 3)]
})
}
conat data = { arr, cats };
<lineup-element data="[[data.arr]]"></lineup-element>
TODO CodePen
Result is same as the builder minimal example
Advanced Usage Example
// arr from before
<lineup-element data="[[data.arr]]" side-panel side-panel-collapsed default-ranking="true">
<lineup-string-desc column="d" label="Label" width="100" ></lineup-string-desc>
<lineup-categorical-desc column="cat" categories="[[cats]]" color="green" ></lineup-categorical-desc>
<lineup-categorical-desc column="cat2" categories="[[cats]]" color="blue" ></lineup-categorical-desc>
<lineup-number-desc column="a" domain="[0, 10]" color="blue" ></lineup-number-desc>
<lineup-ranking group-by="cat" sort-by="a:desc">
<lineup-support-column type="*" ></lineup-support-column>
<lineup-column column="*" ></lineup-column>
</lineup-ranking>
</lineup-element>
TODO CodePen
Result is same as the builder advanced example
A HTMLWidget wrapper for R is located at lineup_htmlwidget. It can be used within standalone R Shiny apps or R Markdown files. Integrated plotting does not work due to an outdated integrated Webkit version in RStudio. Crosstalk is supported for synching selections and filtering among widgets.
Installation
devtools::install_github("rstudio/crosstalk")
devtools::install_github("lineupjs/lineup_htmlwidget")
library(lineupjs)
Examples
lineup(iris)
A Jupyter Widget wrapper for Python is located at lineup_widget.
Installation
pip install -e git+https://github.com/lineupjs/lineup_widget.git#egg=lineup_widget
jupyter nbextension enable --py [--sys-prefix|--user|--system] lineup_widget
Or, if you use jupyterlab:
pip install -e git+https://github.com/lineupjs/lineup_widget.git#egg=lineup_widget
jupyter labextension install @jupyter-widgets/jupyterlab-manager
Examples
import lineup_widget
import pandas as pd
import numpy as np
df = pd.DataFrame(np.random.randint(0,100,size=(100, 4)), columns=list('ABCD'))
w = lineup_widget.LineUpWidget(df)
w.on_selection_changed(lambda selection: print(selection))
w
from __future__ import print_function
from ipywidgets import interact, interactive, interact_manual
def selection_changed(selection):
return df.iloc[selection]
interact(selection_changed, selection=lineup_widget.LineUpWidget(df));
A ObservableHQ wrapper is located at lineup-js-observable.
data = {
const arr = [];
const cats = ['c1', 'c2', 'c3'];
for (let i = 0; i < 100; ++i) {
arr.push({
a: Math.random() * 10,
d: 'Row ' + i,
cat: cats[Math.floor(Math.random() * 3)],
cat2: cats[Math.floor(Math.random() * 3)]
})
}
return arr;
}
import { asLineUp } from '@sgratzl/lineup-js-observable-library'
viewof selection = asLineUp(arr)
// arr from before
viewof selection = {
const b = builder(data);
b.column(
LineUpJS.buildStringColumn('d')
.label('Label')
.width(100)
)
.column(LineUpJS.buildCategoricalColumn('cat', cats).color('green'))
.column(LineUpJS.buildCategoricalColumn('cat2', cats).color('blue'))
.column(LineUpJS.buildNumberColumn('a', [0, 10]).color('blue'));
// and two rankings
const ranking = LineUpJS.buildRanking()
.supportTypes()
.allColumns() // add all columns
.impose('a+cat', 'a', 'cat2') // create composite column
.groupBy('cat')
.sortBy('a', 'desc');
b.defaultRanking().ranking(ranking);
return b.build();
}
A PowerBI Visual wrapper is located at lineup_powerbi.
Installation
TODO
Examples
TODO
See API documentation and Develop API documentation
See Demos, Develop Demos, and R Demos
LineUp: Visual Analysis of Multi-Attribute Rankings Paper Paper Website
Samuel Gratzl, Alexander Lex, Nils Gehlenborg, Hanspeter Pfister, and Marc Streit
IEEE Transactions on Visualization and Computer Graphics (InfoVis '13), 19(12), pp. 2277โ2286, doi:10.1109/TVCG.2013.173, 2013.
๐ IEEE VIS InfoVis 2013 Best Paper Award
Taggle: Scalable Visualization of Tabular Data through Aggregation Paper Preprint Paper Website
Katarina Furmanova, Samuel Gratzl, Holger Stitz, Thomas Zichner, Miroslava Jaresova, Martin Ennemoser, Alexander Lex, and Marc Streit
Information Visualization, 19(2): 114-136, doi:10.1177/1473871619878085, 2019.
LineUp.js depends on
- LineUpEngine table rendering engine
- D3 utilities: scales, format, dragging
- Popper.js dialogs
Development Dependencies
Webpack is used as build tool. LineUp itself is written in TypeScript and SASS.
Try the Gitpod online IDE and start coding instantly in your browser.
Otherwise follow the steps to setup a local development environment.
Installation
git clone https://github.com/lineupjs/lineupjs.git -b develop
cd lineupjs
npm install
Build distribution packages
npm run build
Run Linting
npm run lint
Run Unit Tests
npm test
Serve integrated webserver
npm start
Run E2E Tests
via cypress.io
Variant 1: with prebuilt LineUp
npm run compile
npm run build:prod
npm run cy:open
Variant 2: with webpack-dev-server
first shell:
npm start
second shell:
npm run cy:start
Link develop version
In order to use the library during development in another repository, one need to build and watch the library and produce development typings.
ln -s . <target_project>/node_modules/lineupjs
npm run compile:dev
npm run watch
The development typings are needed cause during production the are located at /src
. That causes problems cause during compilation of a dependent project the Typescript compiler will first find the original TypeScript file e.g. config.ts
before looking for config.d.ts
, will complain that the library owner should deliver JavaScript files and won't compile the file. Thus the typings have to lie at a different location in this scenario.
- Samuel Gratzl (@sgratzl)
- Holger Stitz (@thinkh)
- The Caleydo Team (@caleydo)
- datavisyn GmbH (@datavisyn)
This repository was created as part of the The Caleydo Project.