(Last Update: 2021-05-20)
GraphScape(paper) is a directed graph model of the visualization design space that supports automated reasoning about visualization similarity and sequencing. It uses the Vega-Lite language to model individual charts. This repository contains source code for building GraphScape models and automatically recommending sequences of charts.
# graphscape.sequence(charts, options[, editOpSet, callback]) <>
Generate recommended sequence orders for a collection of Vega-Lite charts. The return value is a ranked array of potential sequences and associated metadata.
Parameter | Type | Description |
---|---|---|
charts | Array | An array of Vega-Lite unit charts. |
options | Object | { "fixFirst": true / false } fixFirst: indicates whether the first chart in charts should be pinned as the first chart of the recommended sequence ( true ) or not (false ). |
editOpSet | Object | (Optional) Specifies custom rules for calculating sequence costss |
callback | Function | (Optional) function(result) { ... } A callback function to invoke with the results. |
The output is a ranked array of objects, each containing a sequence ordering and related metadata.
Property | Type | Description |
---|---|---|
charts | Array | The given input charts. If options.fixFirst was false , a null specification for an empty chart is included as the first entry. |
sequence | Array | Order of indexes of input charts. |
transitions | Array | Transitions between each pair of two adjacent charts with id . |
sequenceCost | Number | Final GraphScape sequence cost. |
sumOfTransitionCosts | Number | Sum of transition costs. |
patterns | Array | Observed patterns of the sequence. Each pattern is consist of pattern , appear , coverage , and patternScore . pattern : An array of transition id s composing the pattern.appear : An array of indexes of transitions where the pattern appears in the sequence. coverage : How much the pattern cover the sequence.patternScore : Final pattern score, which is the same as coverage now. |
globalWeightingTerm | Number | Global weighting term. |
filterSequenceCost | Number | Filter sequence cost. |
filterSequenceCostReasons | Array | Sum of filter value change score Increment of value : +1 Decrement of value : -1 Otherwise : 0 |
const gs = require('./graphscape.js')
const charts = []; // an array of Vega-Lite charts
charts.push({
"data": {"url": "data/cars.json"},
"mark": "point",
"encoding": {
"x": {"field": "Horsepower","type": "quantitative"},
}
});
charts.push({
"data": {"url": "data/cars.json"},
"mark": "point",
"encoding": {
"x": {"field": "Horsepower","type": "quantitative"},
"y": {"field": "Miles_per_Gallon","type": "quantitative"}
}
});
const options = { "fixFirst": false };
console.log(gs.sequence(charts, options));
# graphscape.transition(source chart, target chart) <>
Generate a transition from a source Vega-Lite chart to a target Vega-Lite chart. The transition has the minimum edit operation costs.
Parameter | Type | Description |
---|---|---|
source chart | Object | A Vega-Lite unit chart. |
target chart | Object | A Vega-Lite unit chart. |
The output is a ranked array of objects, each containing a sequence ordering and related metadata.
Property | Type | Description |
---|---|---|
mark | Array | Edit operations in mark category. |
transform | Array | Edit operations in transform category. |
encoding | Array | Edit operations in encoding category. |
cost | Number | Sum of all costs of edit operations in this transition. |
const gs = require('./graphscape.js')
const source = {
"data": {"url": "data/cars.json"},
"mark": "point",
"encoding": {
"x": {"field": "Horsepower","type": "quantitative"},
}
};
const target = {
"data": {"url": "data/cars.json"},
"mark": "point",
"encoding": {
"x": {"field": "Horsepower","type": "quantitative"},
"y": {"field": "Miles_per_Gallon","type": "quantitative"}
}
};
console.log(gs.transition(source, target));
# graphscape.apply(startChart, endChart, editOps) <>
Applies edit operations on the start chart to synthesize the intermeidate chart between the start and end. The edit operations should be provided with the corresponding end chart.
Parameter | Type | Description |
---|---|---|
startChart | Vega-Lite Spec | The start chart that the edit operations are applied to. |
editOps | Array | Edit operations between the start and end charts. Users can get these by .transition . |
endChart | Vega-Lite Spec | The end chart. The given edit operations should be extracted from the transition between the start and end. |
A Vega-Lite Spec.
const gs = require('./graphscape.js')
const startVL = {
"$schema": "https://vega.github.io/schema/vega-lite/v4.json",
"data": {"url": "data/penguins.json"},
"mark": "point",
"encoding": {"x": {"field": "A", "type": "nominal"}}
};
const endVL = {
"$schema": "https://vega.github.io/schema/vega-lite/v4.json",
"data": {"url": "data/penguins.json"},
"mark": "point",
"encoding": {
"y": {"field": "A", "type": "quantitative", "aggregate": "mean" }
}
};
const transition = await gs.transition(startVL, endVL);
const chart = gs.apply(startVL, endVL, transition.encoding)
console.log(chart);
/*
{
"$schema": "https://vega.github.io/schema/vega-lite/v4.json",
"data": {"url": "data/penguins.json"},
"mark": "point",
"encoding": {"y": {"field": "A", "type": "quantitative"}}
}
*/
# graphscape.path(startChart, endChart, M) <>
Recommends paths (chart sequences) from the start to the end with M transitions. Each path will have M+1 charts. Unlike .sequence
, it generates intermediate charts for given two ends.
Parameter | Type | Description |
---|---|---|
startChart | Vega-Lite Spec | The start chart for paths. |
endChart | Vega-Lite Spec | The end chart for paths. |
M | Inteager | The number of transitions for the paths. If M is undefined, it returns all possible paths. |
If M is specified, it returns a path array (Array<Path>
). If not, it returns object having possible Ms and corresponding paths as keys and values({ "1": Array<Path>, "2": ..., ...}
)
Each path has these properties:
{
"sequence": [startChart, ..., endChart ],
// The partition of the edit operations from the start and the end.
"editOpPartition": [editOpArray1, ..., editOpArrayM],
"eval": {
// GraphScape's heuristic evaluation score for this path. Higher means better.
"score": 1, //Number
"satisfiedRules": ... // The reasons for the scores.
}
}
const gs = require('./graphscape.js')
const startVL = {
"$schema": "https://vega.github.io/schema/vega-lite/v4.json",
"data": {"url": "data/penguins.json"},
"mark": "point",
"encoding": {"x": {"field": "A", "type": "nominal"}}
};
const endVL = {
"$schema": "https://vega.github.io/schema/vega-lite/v4.json",
"data": {"url": "data/penguins.json"},
"mark": "point",
"encoding": {
"y": {"field": "A", "type": "quantitative", "aggregate": "mean" }
}
};
const paths = await gs.path(startVL, endVL);
console.log(paths)
*More details of the implementation will be available in here(TBD).
The app/
folder contains a sequence recommender web application. Given a set of input Vega-Lite specifications, it produces a recommended sequence intended to improve chart reading and comprehension. To run this app, first you should install bower components:
$ cd app
$ bower install
Next, launch a local webserver to run the application. For example:
$ python -m SimpleHTTPServer 9000 # for Python 2
$ python -m http.server 9000 # for Python 3
To use a custom build of graphscape.js
, copy your new graphscape.js
file and paste it into the app/js
folder.
- MATLAB is required to solve
lp.m
. - Install npm dependencies via
npm install
. - You can customize rankings of edit operations by modifying
lp.js
and running the following commands:
$ cd src/rule
$ node lp.js
$ matlab < lp.m
$ node genEditOpSet.js # This will generate editOpSet.js.
# After creating your rankings, you must re-build `graphscape.js` to apply changes.
$ cd
$ npm run test
$ npm run build
If you use GraphScpae in published research, please cite this paper.