Skip to content

Commit

Permalink
Add js module
Browse files Browse the repository at this point in the history
  • Loading branch information
domoritz committed Oct 31, 2018
1 parent b277d78 commit 07050a1
Show file tree
Hide file tree
Showing 20 changed files with 5,348 additions and 1,674 deletions.
2 changes: 1 addition & 1 deletion .editorconfig
Expand Up @@ -17,4 +17,4 @@ indent_size = 2
[*.tsx]
indent_size = 2
[*.json]
indent_size = 4
indent_size = 2
Binary file removed examples/examples.pages
Binary file not shown.
Binary file removed examples/examples.pdf
Binary file not shown.
5 changes: 5 additions & 0 deletions js/.gitignore
@@ -0,0 +1,5 @@
.cache
build
dist
node_modules
yarn-error.log
15 changes: 15 additions & 0 deletions js/.npmignore
@@ -0,0 +1,15 @@
.cache
.DS_Store
.gitignore
.prettierrc.json
.travis.yml
.vscode
concat_lp.sh
demo
dist
node_modules
rollup.config.js
test
tsconfig.json
tslint.json
yarn-error.log
13 changes: 13 additions & 0 deletions js/.prettierrc.json
@@ -0,0 +1,13 @@
{
"overrides": [
{
"files": "*.ts",
"options": {
"printWidth": 120,
"parser": "typescript",
"singleQuote": true,
"trailingComma": "es5"
}
}
]
}
3 changes: 3 additions & 0 deletions js/Readme.md
@@ -0,0 +1,3 @@
# Draco Core

JavaScript module with the Draco knowledge base and helper functions to convert from ASP to Vega-Lite and vice-versa.
43 changes: 43 additions & 0 deletions js/package.json
@@ -0,0 +1,43 @@
{
"name": "draco-core",
"description": "Visualization Knowledge as Constraints.",
"version": "0.0.1",
"author": "Dominik Moritz",
"license": "BSD-3-Clause",
"dependencies": {
"vega-lite": "^3.0.0-rc8"
},
"devDependencies": {
"@types/jest": "^23.3.9",
"jest": "^23.6.0",
"prettier": "^1.14.3",
"ts-jest": "^23.10.4",
"typescript": "^3.1.4"
},
"scripts": {
"test": "jest",
"concat": "bash scripts/concat_lp.sh ../asp build",
"build": "rm -rf build && tsc && concat",
"format": "tslint -p . --fix && prettier --write '{src,test}/**/*.ts'",
"lint": "tslint -p . && prettier --list-different '{src,test}/**/*.ts'"
},
"jest": {
"transform": {
"^.+\\.tsx?$": "ts-jest"
},
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
"moduleFileExtensions": [
"ts",
"tsx",
"js",
"jsx",
"json",
"node"
],
"testPathIgnorePatterns": [
"node_modules",
"<rootDir>/build",
"src"
]
}
}
25 changes: 25 additions & 0 deletions js/scripts/concat_lp.sh
@@ -0,0 +1,25 @@
# usage: ./concat_lp.sh srcdir destdir

declare -a files=("topk-lua"
"define"
"generate"
"hard"
"soft"
"weights"
"assign_weights"
"optimize"
"output"
)

output=""
newline=$'\n\n'

for file in "${files[@]}"
do
path="${1}/${file}.lp"
lp=$(cat $path | sed -e s/\`/\'/g)
const=$(echo $file | tr a-z A-Z | tr \- _)
output+="export const ${const}: string = \`${lp}\`;${newline}"
done

echo "$output" > $2/constraints.ts
151 changes: 151 additions & 0 deletions js/src/index.ts
@@ -0,0 +1,151 @@
import { TopLevelFacetedUnitSpec } from 'vega-lite/build/src/spec';

const REGEX = /(\w+)\(([\w\.\/]+)(,([\w\.]+))?\)/;

/**
* Convert from ASP to Vega-Lite.
*/
export function asp2vl(facts: string[]): TopLevelFacetedUnitSpec {
let mark = '';
let url = 'data/cars.json'; // default dataset
const encodings: { [enc: string]: any } = {};

for (const value of facts) {
// TODO: Better handle quoted fields. We currently simply remove all ".
const cleanedValue = value.replace(/\"/g, '');
const negSymbol = value.trim().startsWith(':-'); // TODO: remove this
const [_, predicate, first, __, second] = REGEX.exec(cleanedValue) as any;

if (predicate === 'mark') {
mark = first;
} else if (predicate === 'data') {
url = first;
} else if (predicate !== 'violation') {
if (!encodings[first]) {
encodings[first] = {};
}
// if it contains the neg symbol, and the field is a boolean field, its value would be false
// e.g., for the case ":- zero(e3)"
encodings[first][predicate] = second || !negSymbol;
}
}

const encoding: { [channel: string]: any } = {};

for (const e of Object.keys(encodings)) {
const enc = encodings[e];

// if quantitative encoding and zero is not set, set zero to false
if (enc.type === 'quantitative' && enc.zero === undefined && enc.bin === undefined) {
enc.zero = false;
}

const scale = {
...(enc.log ? { type: 'log' } : {}),
...(enc.zero === undefined ? {} : enc.zero ? { zero: true } : { zero: false }),
};

encoding[enc.channel] = {
type: enc.type,
...(enc.aggregate ? { aggregate: enc.aggregate } : {}),
...(enc.field ? { field: enc.field } : {}),
...(enc.stack ? { stack: enc.stack } : {}),
...(enc.bin !== undefined ? { bin: { maxbins: +enc.bin } } : {}),
...(Object.keys(scale).length ? { scale } : {}),
};
}

return {
$schema: 'https://vega.github.io/schema/vega-lite/v3.json',
data: { url: `${url}` },
mark,
encoding,
} as TopLevelFacetedUnitSpec;
}

/**
* Get the array of witnesses from clingo output.
* Return undefined if no witnesses were found.
*/
export function getModels(result: any) {
return (result.Call || []).reduce((arr: any[], el: any) => {
el.Witnesses.forEach((d: any) =>
arr.push({
facts: d.Value,
costs: d.Costs,
})
);
return arr;
}, []);
}

export function models2vl(models: any[]) {
return models.map(model => asp2vl(model.facts));
}

/**
* Convert from Vega-Lite to ASP.
*/
export function vl2asp(spec: TopLevelFacetedUnitSpec): string[] {
const facts = [`mark(${spec.mark})`];

if ('data' in spec && 'url' in spec.data) {
facts.push(`data("${spec.data.url}")`);
}

const encoding = spec.encoding || {};

let i = 0;
for (const channel of Object.keys(encoding)) {
const eid = `e${i++}`;
facts.push(`encoding(${eid})`);
facts.push(`channel(${eid},${channel})`);

let encFieldType = null;
let encZero = null;
let encBinned = null;

// translate encodings
for (const field of Object.keys(encoding[channel])) {
const fieldContent = encoding[channel][field];
if (field === 'type') {
encFieldType = fieldContent;
}
if (field === 'bin') {
encBinned = fieldContent;
}
if (field === 'scale') {
// translate two boolean fields
if ('zero' in fieldContent) {
encZero = fieldContent.zero;
if (fieldContent.zero) {
facts.push(`zero(${eid})`);
} else {
facts.push(`:- zero(${eid})`);
}
}
if ('log' in fieldContent) {
if (fieldContent.log) {
facts.push(`log(${eid})`);
} else {
facts.push(`:-log(${eid})`);
}
}
} else if (field === 'bin') {
facts.push(`${field}(${eid},${fieldContent.maxbins})`);
} else if (field === 'field') {
// fields can have spaces and start with capital letters
facts.push(`${field}(${eid},"${fieldContent}")`);
} else {
// translate normal fields
facts.push(`${field}(${eid},${fieldContent})`);
}
}

if (encFieldType === 'quantitative' && encZero === null && encBinned === null) {
facts.push(`zero(${eid})`);
}
}

return facts;
}
67 changes: 67 additions & 0 deletions js/test/index.test.ts
@@ -0,0 +1,67 @@
import { asp2vl, vl2asp } from '../src';
import { aspSpecs, vlSpecs } from './specs';

test('asp2vl and vl2asp work', () => {
for (let i = 0; i < vlSpecs.length; i++) {
const aspSpec = aspSpecs[i];
const vlSpec = vlSpecs[i];
expect([asp2vl(aspSpec), vl2asp(vlSpec).sort()]).toEqual([vlSpec, aspSpec.sort()]);
}
});

test('parses results correctly', () => {
expect(
asp2vl([
'mark(bar)',

'encoding(e0)',
'channel(e0,x)',
'field(e0,"foo")',
'type(e0,ordinal)',

'encoding(e1)',
'channel(e1,y)',
'aggregate(e1,count)',
'type(e1,quantitative)',
'zero(e1)',
])
).toEqual({
$schema: 'https://vega.github.io/schema/vega-lite/v3.json',
data: { url: 'data/cars.json' },
mark: 'bar',
encoding: {
x: { field: 'foo', type: 'ordinal' },
y: { aggregate: 'count', type: 'quantitative', scale: { zero: true } },
},
});
});

test('generates correct asp', () => {
expect(
vl2asp({
$schema: 'https://vega.github.io/schema/vega-lite/v3.json',
data: { url: 'data/cars.json' },
mark: 'bar',
encoding: {
x: { field: 'foo', type: 'ordinal' },
y: { aggregate: 'count', type: 'quantitative', scale: { zero: true } },
},
}).sort()
).toEqual(
[
'data("data/cars.json")',
'mark(bar)',

'encoding(e0)',
'channel(e0,x)',
'field(e0,"foo")',
'type(e0,ordinal)',

'encoding(e1)',
'channel(e1,y)',
'aggregate(e1,count)',
'type(e1,quantitative)',
'zero(e1)',
].sort()
);
});

0 comments on commit 07050a1

Please sign in to comment.