vega-lite
Advanced tools
Comparing version 0.8.3 to 0.9.0
@@ -25,8 +25,10 @@ { | ||
"bower_components", | ||
"test", | ||
"tests" | ||
"test" | ||
], | ||
"dependencies": { | ||
"datalib": "^1.3.0" | ||
"datalib": "^1.5.2" | ||
}, | ||
"devDependencies": { | ||
"vega": "^2.4.1" | ||
} | ||
} |
{ | ||
"name": "vega-lite", | ||
"author": "Jeffrey Heer, Dominik Moritz, Kanit \"Ham\" Wongsuphasawat", | ||
"version": "0.8.3", | ||
"version": "0.9.0", | ||
"collaborators": [ | ||
@@ -11,3 +11,3 @@ "Kanit Wongsuphasawat <kanitw@gmail.com> (http://kanitw.yellowpigz.com)", | ||
"description": "Vega-lite provides a higher-level grammar for visual analysis, comparable to ggplot or Tableau, that generates complete Vega specifications.", | ||
"main": "src/vl.js", | ||
"main": "vega-lite.js", | ||
"directories": { | ||
@@ -17,12 +17,14 @@ "test": "test" | ||
"scripts": { | ||
"build": "gulp build", | ||
"cover": "gulp coverage", | ||
"deploy": "npm run lint && npm run test && scripts/deploy.sh", | ||
"feature": "gulp feature", | ||
"lint": "gulp jshint", | ||
"patch": "gulp patch", | ||
"release": "gulp release", | ||
"start": "gulp serve", | ||
"test": "gulp test", | ||
"watch": "gulp bundle watch-schema watch-test" | ||
"build": "browserify src/vl.ts -p tsify -d -s vl | exorcist vega-lite.js.map > vega-lite.js", | ||
"postbuild": "uglifyjs vega-lite.js -cm --source-map vega-lite.min.js.map > vega-lite.min.js && npm run schema", | ||
"cover": "tsc && istanbul cover node_modules/.bin/_mocha -- --recursive", | ||
"clean": "rm -f vega-lite.* vega-lite-schema.json & find src -name '*.js*' -type f -delete & find test -name '*.js*' -type f -delete", | ||
"deploy": "npm run clean && npm run lint && npm run test && scripts/deploy.sh", | ||
"lint": "tslint -c tslint.json src/**/*.ts test/**/*.ts", | ||
"start": "npm run watch:build & browser-sync start --server --files 'vega-lite.js' --index 'gallery.html'", | ||
"schema": "tsc && node src/schema/schemagen.js > vega-lite-schema.json", | ||
"test": "tsc && mocha --recursive --require source-map-support/register", | ||
"watch:build": "watchify src/vl.ts -p tsify -v -d -s vl -o 'exorcist vega-lite.js.map > vega-lite.js'", | ||
"watch:dev": "nodemon -e ts -x 'npm test && npm run lint'", | ||
"watch:all": "nodemon -e ts -x 'npm test && npm run lint && npm run build'" | ||
}, | ||
@@ -39,45 +41,31 @@ "repository": { | ||
"devDependencies": { | ||
"browser-sync": "^2.9.11", | ||
"browserify": "^11.2.0", | ||
"browserify-shim": "^3.8.10", | ||
"browser-sync": "^2.10.0", | ||
"browserify": "^12.0.1", | ||
"browserify-shim": "^3.8.11", | ||
"browserify-versionify": "^1.0.6", | ||
"chai": "^3.4.0", | ||
"commander": "^2.9.0", | ||
"d3": "^3.5.6", | ||
"deep-diff": "^0.3.3", | ||
"gulp": "^3.9.0", | ||
"gulp-bump": "^1.0.0", | ||
"gulp-git": "^1.6.0", | ||
"gulp-jshint": "^1.11.2", | ||
"gulp-load-plugins": "^1.0.0", | ||
"gulp-rename": "^1.2.2", | ||
"gulp-run": "^1.6.11", | ||
"gulp-sourcemaps": "^1.6.0", | ||
"gulp-spawn-mocha": "^2.2.1", | ||
"gulp-tag-version": "^1.3.0", | ||
"gulp-uglify": "^1.4.2", | ||
"gulp-util": "^3.0.7", | ||
"jshint-stylish": "^2.0.1", | ||
"lodash": "^3.10.1", | ||
"mocha": "^2.3.3", | ||
"require-dir": "^0.3.0", | ||
"vinyl-buffer": "^1.0.0", | ||
"vinyl-source-stream": "^1.1.0", | ||
"watchify": "~3.4.0", | ||
"z-schema": "^3.15.4", | ||
"jstransform": "^11.0.3", | ||
"through": "^2.3.8" | ||
"chai": "^3.4.1", | ||
"exorcist": "^0.4.0", | ||
"mocha": "^2.3.4", | ||
"nodemon": "^1.8.1", | ||
"source-map-support": "^0.4.0", | ||
"tsify": "^0.13.1", | ||
"tslint": "^3.1.1", | ||
"typescript": "^1.7.3", | ||
"uglify-js": "^2.6.1", | ||
"watchify": "^3.6.1", | ||
"z-schema": "^3.16.0" | ||
}, | ||
"dependencies": { | ||
"colorbrewer": "0.0.2", | ||
"d3-color": "^0.2.6", | ||
"d3-format": "^0.3.3", | ||
"d3-time-format": "0.1.3", | ||
"datalib": "^1.4.6", | ||
"yargs": "^3.29.0", | ||
"vega": "^2.3.1" | ||
"d3-color": "^0.3.1", | ||
"d3-format": "^0.4.0", | ||
"d3-time-format": "0.2.0", | ||
"datalib": "^1.4.12", | ||
"yargs": "^3.30.0", | ||
"vega": "^2.4.1" | ||
}, | ||
"browserify": { | ||
"transform": [ | ||
"browserify-shim" | ||
"browserify-shim", | ||
"browserify-versionify" | ||
] | ||
@@ -84,0 +72,0 @@ }, |
@@ -7,16 +7,15 @@ # Vega-Lite | ||
**Vega-Lite is still in alpha phase and we are working on improving the code and [documentation](docs/Documentation.md). | ||
Note that our syntax might change slightly before we release 1.0.** | ||
Vega-Lite provides a higher-level grammar for visual analysis, akin to ggplot or Tableau, that generates complete [Vega](https://vega.github.io/) specifications. | ||
Vega-Lite provides a higher-level grammar for visual analysis, comparable to ggplot or Tableau, that generates complete [Vega](https://vega.github.io/) specifications. | ||
Vega-Lite specifications consist of simple mappings of variables in a data set to visual encoding channels such as position (`x`,`y`), `size`, `color` and `shape`. These mappings are then translated into detailed visualization specifications in the form of Vega specification language. Vega-Lite produces default values for visualization components (e.g., scales, axes, and legends) in the output Vega specification using a rule-based approach, but users can explicit specify these properties to override default values. | ||
Vega-Lite specifications consist of simple mappings of variables in a data set to visual encoding channels such as position (`x`,`y`), `size`, `color` and `shape`. These mappings are then translated into a full visualization specifications. These resulting visualizations can then be exported or further modified to customize the display. | ||
__Try using Vega-Lite in the online [Vega Editor](http://vega.github.io/vega-editor/?mode=vega-lite)__. | ||
If you are using Vega-Lite for your project(s), please let us know by emailing us at [Vega-Lite \[at\] cs.washington.edu](mailto:vega-lite@cs.washington.edu). Feedback is also welcome. | ||
The complete schema for specifications as [JSON schema](http://json-schema.org/) is at [vega-lite-schema.json](https://vega.github.io/vega-lite/vega-lite-schema.json). | ||
**Note: Vega-Lite is still in alpha phase and we are working on improving the code and [documentation](https://vega.github.io/vega-lite/docs/). | ||
Our syntax might change slightly before we release 1.0.** See our wiki pages for [the development roadmap](https://github.com/vega/vega-lite/wiki/Roadmap) and [how you can contribute](https://github.com/vega/vega-lite/wiki/Contribute). | ||
If you find a bug or have a feature request, please [create an issue](https://github.com/vega/vega-lite/issues/new). | ||
__Try using Vega-lite in the online [Vega Editor](http://vega.github.io/vega-editor/?mode=vega-lite)__. | ||
The complete schema for specifications as [JSON schema](http://json-schema.org/) is at [vega-lite-schema.json](https://vega.github.io/vega-lite/vega-lite-schema.json). | ||
## Example specification | ||
@@ -31,12 +30,12 @@ | ||
"data": {"url": "data/barley.json"}, | ||
"marktype": "point", | ||
"mark": "point", | ||
"encoding": { | ||
"x": {"type": "Q","name": "yield","aggregate": "mean"}, | ||
"x": {"type": "quantitative", "field": "yield","aggregate": "mean"}, | ||
"y": { | ||
"sort": {"name": "yield","aggregate": "mean","reverse": false}, | ||
"type": "O", | ||
"name": "variety" | ||
"sort": {"field": "yield", "aggregate": "mean", "reverse": false}, | ||
"type": "ordinal", | ||
"field": "variety" | ||
}, | ||
"row": {"type": "O","name": "site"}, | ||
"color": {"type": "O","name": "year"} | ||
"row": {"type": "ordinal", "field": "site"}, | ||
"color": {"type": "ordinal", "field": "year"} | ||
} | ||
@@ -59,6 +58,6 @@ } | ||
}, | ||
"marktype": "bar", | ||
"mark": "bar", | ||
"encoding": { | ||
"x": {"type": "O","name": "a"}, | ||
"y": {"type": "Q","name": "b"} | ||
"x": {"type": "ordinal", "field": "a"}, | ||
"y": {"type": "quantitative", "field": "b"} | ||
} | ||
@@ -79,13 +78,28 @@ } | ||
Since Vega-Lite is written in Typescript, you should also install TypeScript | ||
```sh | ||
npm install -g typescript | ||
``` | ||
We use the [atom](atom.io) editor with typescript support. If you don't want to see intermediate files (`.js`, `.js.map`), you can "Hide VCS Ignored Files" in the `tree-view` plugin. | ||
### Commands | ||
You can run `npm run build` to compile vega-lite or run `npm start` to open the live vega-lite editor. | ||
You can run `npm run build` to compile Vega-Lite and regenerate `vega-lite-schema.json`. More commands are available in `npm run`. | ||
You can `npm run watch` to start a watcher task that regenerate the `vega-lite-schema.json` file whenever `schema.js` changes, and lints and tests all JS files when any `.js` file in `test/` or `src/` changes. | ||
#### Watch tasks | ||
Note: These commands use [Gulp](http://gulpjs.com) internally; to run them directly (instead of through the `npm run` aliases), install gulp globally with | ||
```sh | ||
npm install -g gulp | ||
``` | ||
During development, it can be convenient to rebuild automatically or run tests in the background. | ||
You can `npm run watch:dev` to start a watcher task that **lints and runs tests** when any `.ts` file changes. | ||
You can use `npm run watch:build` to start a watcher task that **re-compiles Vega-Lite** when `.ts` files related to VL change. | ||
The previous two commands run very fast but don't run all tasks that you may want. If you are okay to use a slow command, you can use `npm run watch:all` to start a watcher task that when any `.ts` file changes: | ||
- lints and runs tests | ||
- re-compiles Vega-Lite | ||
- regenerates `vega-lite-schema.json` | ||
### Developing Vega-Lite and Datalib | ||
@@ -97,3 +111,3 @@ | ||
``` | ||
```sh | ||
# first link datalib global npm | ||
@@ -100,0 +114,0 @@ cd path/to/datalib |
@@ -1,259 +0,189 @@ | ||
'use strict'; | ||
require('../globals'); | ||
var util = require('../util'), | ||
setter = util.setter, | ||
getter = util.getter, | ||
time = require('./time'); | ||
var axis = module.exports = {}; | ||
axis.def = function(name, encoding, layout, stats, opt) { | ||
var isCol = name == COL, | ||
isRow = name == ROW, | ||
type = isCol ? 'x' : isRow ? 'y' : name; | ||
// TODO: rename def to axisDef and avoid side effects where possible. | ||
var def = { | ||
type: type, | ||
scale: name, | ||
properties: {}, | ||
layer: encoding.encDef(name).axis.layer | ||
}; | ||
var orient = axis.orient(encoding, name, stats); | ||
if (orient) { | ||
def.orient = orient; | ||
} | ||
// Add axis label custom scale (for bin / time) | ||
def = axis.labels.scale(def, encoding, name); | ||
def = axis.labels.format(def, encoding, name, stats); | ||
def = axis.labels.angle(def, encoding, name); | ||
// for x-axis, set ticks for Q or rotate scale for ordinal scale | ||
if (name == X) { | ||
if ((encoding.isDimension(X) || encoding.isType(X, T)) && | ||
!('angle' in getter(def, ['properties', 'labels']))) { | ||
// TODO(kanitw): Jul 19, 2015 - #506 add condition for rotation | ||
def = axis.labels.rotate(def); | ||
} else { // Q | ||
def.ticks = encoding.encDef(name).axis.ticks; | ||
var util_1 = require('../util'); | ||
var type_1 = require('../type'); | ||
var channel_1 = require('../channel'); | ||
var time = require('./time'); | ||
function compileAxis(channel, model) { | ||
var isCol = channel === channel_1.COLUMN, isRow = channel === channel_1.ROW, type = isCol ? 'x' : isRow ? 'y' : channel; | ||
var def = { | ||
type: type, | ||
scale: channel | ||
}; | ||
[ | ||
'format', 'grid', 'layer', 'orient', 'tickSize', 'ticks', 'title', | ||
'offset', 'tickPadding', 'tickSize', 'tickSizeMajor', 'tickSizeMinor', 'tickSizeEnd', | ||
'titleOffset', 'values', 'subdivide' | ||
].forEach(function (property) { | ||
var method; | ||
var value = (method = exports[property]) ? | ||
method(model, channel, def) : | ||
model.fieldDef(channel).axis[property]; | ||
if (value !== undefined) { | ||
def[property] = value; | ||
} | ||
}); | ||
var props = model.fieldDef(channel).axis.properties || {}; | ||
[ | ||
'axis', 'labels', | ||
'grid', 'title', 'ticks', 'majorTicks', 'minorTicks' | ||
].forEach(function (group) { | ||
var value = properties[group] ? | ||
properties[group](model, channel, props[group], def) : | ||
props[group]; | ||
if (value !== undefined) { | ||
def.properties = def.properties || {}; | ||
def.properties[group] = value; | ||
} | ||
}); | ||
return def; | ||
} | ||
exports.compileAxis = compileAxis; | ||
function format(model, channel) { | ||
var fieldDef = model.fieldDef(channel); | ||
var format = fieldDef.axis.format; | ||
if (format !== undefined) { | ||
return format; | ||
} | ||
} | ||
// TitleOffset depends on labels rotation | ||
def.titleOffset = axis.titleOffset(encoding, layout, name); | ||
//def.offset is used in axis.grid | ||
if(isRow) def.offset = axis.titleOffset(encoding, layout, Y) + 20; | ||
// FIXME(kanitw): Jul 19, 2015 - offset for column when x is put on top | ||
def = axis.grid(def, encoding, name, layout); | ||
def = axis.title(def, encoding, name, layout, opt); | ||
if (isRow || isCol) { | ||
def = axis.hideTicks(def); | ||
} | ||
return def; | ||
}; | ||
axis.orient = function(encoding, name, stats) { | ||
var orient = encoding.encDef(name).axis.orient; | ||
if (orient) { | ||
return orient; | ||
} else if (name === COL) { | ||
return 'top'; | ||
} else if (name === X && encoding.has(Y) && encoding.isOrdinalScale(Y) && encoding.cardinality(Y, stats) > 30) { | ||
// x-axis for long y - put on top | ||
return 'top'; | ||
} | ||
return undefined; | ||
}; | ||
axis.grid = function(def, encoding, name, layout) { | ||
var cellPadding = layout.cellPadding, | ||
isCol = name == COL, | ||
isRow = name == ROW; | ||
var _grid = encoding.axis(name).grid; | ||
// If `grid` is unspecified, the default value is `true` for ROW and COL. | ||
// For X and Y, the default value is `true` for (1) quantitative fields that are not binned and (2) time fields. | ||
// Otherwise, the default value is `false`. | ||
var grid = _grid === undefined ? | ||
( name === ROW || name === COL || | ||
(encoding.isTypes(name, [Q, T]) && !encoding.encDef(name).bin) | ||
) : _grid; | ||
if (grid) { | ||
def.grid = true; | ||
if (isCol) { | ||
// set grid property -- put the lines on the right the cell | ||
var yOffset = encoding.config('cellGridOffset'); | ||
// TODO(#677): this should depend on orient | ||
def.properties.grid = { | ||
x: { | ||
offset: layout.cellWidth * (1+ cellPadding/2.0), | ||
// default value(s) -- vega doesn't do recursive merge | ||
scale: 'col', | ||
field: 'data' | ||
}, | ||
y: { | ||
value: -yOffset, | ||
}, | ||
y2: { | ||
field: {group: 'mark.group.height'}, | ||
offset: yOffset | ||
}, | ||
stroke: { value: encoding.config('cellGridColor') }, | ||
strokeOpacity: { value: encoding.config('cellGridOpacity') } | ||
}; | ||
} else if (isRow) { | ||
var xOffset = encoding.config('cellGridOffset'); | ||
// TODO(#677): this should depend on orient | ||
// set grid property -- put the lines on the top | ||
def.properties.grid = { | ||
y: { | ||
offset: -layout.cellHeight * (cellPadding/2), | ||
// default value(s) -- vega doesn't do recursive merge | ||
scale: 'row', | ||
field: 'data' | ||
}, | ||
x: { | ||
value: def.offset - xOffset | ||
}, | ||
x2: { | ||
field: {group: 'mark.group.width'}, | ||
offset: def.offset + xOffset, | ||
// default value(s) -- vega doesn't do recursive merge | ||
mult: 1 | ||
}, | ||
stroke: { value: encoding.config('cellGridColor') }, | ||
strokeOpacity: { value: encoding.config('cellGridOpacity') } | ||
}; | ||
} else { | ||
def.properties.grid = { | ||
stroke: { value: encoding.config('gridColor') }, | ||
strokeOpacity: { value: encoding.config('gridOpacity') } | ||
}; | ||
if (fieldDef.type === type_1.QUANTITATIVE) { | ||
return model.numberFormat(channel); | ||
} | ||
} | ||
return def; | ||
}; | ||
axis.hideTicks = function(def) { | ||
def.properties.ticks = {opacity: {value: 0}}; | ||
def.properties.majorTicks = {opacity: {value: 0}}; | ||
def.properties.axis = {opacity: {value: 0}}; | ||
return def; | ||
}; | ||
axis.title = function (def, encoding, name, layout) { | ||
var ax = encoding.encDef(name).axis; | ||
if (ax.title) { | ||
def.title = ax.title; | ||
} else { | ||
// if not defined, automatically determine axis title from field def | ||
var fieldTitle = encoding.fieldTitle(name), | ||
maxLength; | ||
if (ax.titleMaxLength) { | ||
maxLength = ax.titleMaxLength; | ||
} else if (name===X) { | ||
maxLength = layout.cellWidth / encoding.config('characterWidth'); | ||
} else if (name === Y) { | ||
maxLength = layout.cellHeight / encoding.config('characterWidth'); | ||
else if (fieldDef.type === type_1.TEMPORAL) { | ||
var timeUnit = fieldDef.timeUnit; | ||
if (!timeUnit) { | ||
return model.config('timeFormat'); | ||
} | ||
else if (timeUnit === 'year') { | ||
return 'd'; | ||
} | ||
} | ||
def.title = maxLength ? util.truncate(fieldTitle, maxLength) : fieldTitle; | ||
} | ||
if (name === ROW) { | ||
def.properties.title = { | ||
angle: {value: 0}, | ||
align: {value: 'right'}, | ||
baseline: {value: 'middle'}, | ||
dy: {value: (-layout.height/2) -20} | ||
}; | ||
} | ||
return def; | ||
}; | ||
axis.labels = {}; | ||
/** add custom label for time type and bin */ | ||
axis.labels.scale = function(def, encoding, name) { | ||
// time | ||
var timeUnit = encoding.encDef(name).timeUnit; | ||
if (encoding.isType(name, T) && timeUnit && (time.hasScale(timeUnit))) { | ||
setter(def, ['properties','labels','text','scale'], 'time-'+ timeUnit); | ||
} | ||
// FIXME bin | ||
return def; | ||
}; | ||
/** | ||
* Determine number format or truncate if maxLabel length is presented. | ||
*/ | ||
axis.labels.format = function (def, encoding, name, stats) { | ||
var fieldStats = stats[encoding.encDef(name).name]; | ||
if (encoding.axis(name).format) { | ||
def.format = encoding.axis(name).format; | ||
} else if (encoding.isType(name, Q) || fieldStats.type === 'number') { | ||
def.format = encoding.numberFormat(fieldStats); | ||
} else if (encoding.isType(name, T)) { | ||
var timeUnit = encoding.encDef(name).timeUnit; | ||
if (!timeUnit) { | ||
def.format = encoding.config('timeFormat'); | ||
} else if (timeUnit === 'year') { | ||
def.format = 'd'; | ||
return undefined; | ||
} | ||
exports.format = format; | ||
function grid(model, channel) { | ||
var fieldDef = model.fieldDef(channel); | ||
var grid = fieldDef.axis.grid; | ||
if (grid !== undefined) { | ||
return grid; | ||
} | ||
} else if (encoding.isTypes(name, [N, O]) && encoding.axis(name).maxLabelLength) { | ||
setter(def, | ||
['properties','labels','text','template'], | ||
'{{ datum.data | truncate:' + | ||
encoding.axis(name).maxLabelLength + '}}' | ||
); | ||
} | ||
return def; | ||
}; | ||
axis.labels.angle = function(def, encoding, name) { | ||
var angle = encoding.axis(name).labelAngle; | ||
if (typeof angle === 'undefined') return def; | ||
setter(def, ['properties', 'labels', 'angle', 'value'], angle); | ||
return def; | ||
}; | ||
axis.labels.rotate = function(def) { | ||
var align = def.orient ==='top' ? 'left' : 'right'; | ||
setter(def, ['properties','labels', 'angle', 'value'], 270); | ||
setter(def, ['properties','labels', 'align', 'value'], align); | ||
setter(def, ['properties','labels', 'baseline', 'value'], 'middle'); | ||
return def; | ||
}; | ||
axis.titleOffset = function (encoding, layout, name) { | ||
// return specified value if specified | ||
var value = encoding.axis(name).titleOffset; | ||
if (value) return value; | ||
switch (name) { | ||
//FIXME make this adjustable | ||
case ROW: return 0; | ||
case COL: return 35; | ||
} | ||
return getter(layout, [name, 'axisTitleOffset']); | ||
}; | ||
return !model.isOrdinalScale(channel) && !fieldDef.bin; | ||
} | ||
exports.grid = grid; | ||
function layer(model, channel, def) { | ||
var layer = model.fieldDef(channel).axis.layer; | ||
if (layer !== undefined) { | ||
return layer; | ||
} | ||
if (def.grid) { | ||
return 'back'; | ||
} | ||
return undefined; | ||
} | ||
exports.layer = layer; | ||
; | ||
function orient(model, channel) { | ||
var orient = model.fieldDef(channel).axis.orient; | ||
if (orient) { | ||
return orient; | ||
} | ||
else if (channel === channel_1.COLUMN) { | ||
return 'top'; | ||
} | ||
else if (channel === channel_1.ROW) { | ||
if (model.has(channel_1.Y) && model.fieldDef(channel_1.Y).axis.orient !== 'right') { | ||
return 'right'; | ||
} | ||
} | ||
return undefined; | ||
} | ||
exports.orient = orient; | ||
function ticks(model, channel) { | ||
var ticks = model.fieldDef(channel).axis.ticks; | ||
if (ticks !== undefined) { | ||
return ticks; | ||
} | ||
if (channel === channel_1.X && !model.fieldDef(channel).bin) { | ||
return 5; | ||
} | ||
return undefined; | ||
} | ||
exports.ticks = ticks; | ||
function tickSize(model, channel) { | ||
var tickSize = model.fieldDef(channel).axis.tickSize; | ||
if (tickSize !== undefined) { | ||
return tickSize; | ||
} | ||
if (channel === channel_1.ROW || channel === channel_1.COLUMN) { | ||
return 0; | ||
} | ||
return undefined; | ||
} | ||
exports.tickSize = tickSize; | ||
function title(model, channel) { | ||
var axisSpec = model.fieldDef(channel).axis; | ||
if (axisSpec.title !== undefined) { | ||
return axisSpec.title; | ||
} | ||
var fieldTitle = model.fieldTitle(channel); | ||
var layout = model.layout(); | ||
var maxLength; | ||
if (axisSpec.titleMaxLength) { | ||
maxLength = axisSpec.titleMaxLength; | ||
} | ||
else if (channel === channel_1.X && typeof layout.cellWidth === 'number') { | ||
maxLength = layout.cellWidth / model.config('characterWidth'); | ||
} | ||
else if (channel === channel_1.Y && typeof layout.cellHeight === 'number') { | ||
maxLength = layout.cellHeight / model.config('characterWidth'); | ||
} | ||
return maxLength ? util_1.truncate(fieldTitle, maxLength) : fieldTitle; | ||
} | ||
exports.title = title; | ||
var properties; | ||
(function (properties) { | ||
function axis(model, channel, spec) { | ||
if (channel === channel_1.ROW || channel === channel_1.COLUMN) { | ||
return util_1.extend({ | ||
opacity: { value: 0 } | ||
}, spec || {}); | ||
} | ||
return spec || undefined; | ||
} | ||
properties.axis = axis; | ||
function labels(model, channel, spec, def) { | ||
var fieldDef = model.fieldDef(channel); | ||
var filterName = time.labelTemplate(fieldDef.timeUnit, fieldDef.axis.shortTimeNames); | ||
if (fieldDef.type === type_1.TEMPORAL && filterName) { | ||
spec = util_1.extend({ | ||
text: { template: '{{datum.data | ' + filterName + '}}' } | ||
}, spec || {}); | ||
} | ||
if (util_1.contains([type_1.NOMINAL, type_1.ORDINAL], fieldDef.type) && fieldDef.axis.labelMaxLength) { | ||
spec = util_1.extend({ | ||
text: { | ||
template: '{{ datum.data | truncate:' + fieldDef.axis.labelMaxLength + '}}' | ||
} | ||
}, spec || {}); | ||
} | ||
switch (channel) { | ||
case channel_1.X: | ||
if (model.isDimension(channel_1.X) || fieldDef.type === type_1.TEMPORAL) { | ||
spec = util_1.extend({ | ||
angle: { value: 270 }, | ||
align: { value: def.orient === 'top' ? 'left' : 'right' }, | ||
baseline: { value: 'middle' } | ||
}, spec || {}); | ||
} | ||
break; | ||
case channel_1.ROW: | ||
if (def.orient === 'right') { | ||
spec = util_1.extend({ | ||
angle: { value: 90 }, | ||
align: { value: 'center' }, | ||
baseline: { value: 'bottom' } | ||
}, spec || {}); | ||
} | ||
} | ||
return spec || undefined; | ||
} | ||
properties.labels = labels; | ||
})(properties || (properties = {})); | ||
//# sourceMappingURL=axis.js.map |
@@ -1,131 +0,76 @@ | ||
'use strict'; | ||
var summary = module.exports = require('datalib/src/stats').summary; | ||
require('../globals'); | ||
/** | ||
* Module for compiling Vega-lite spec into Vega spec. | ||
*/ | ||
var compiler = module.exports = {}; | ||
var Encoding = require('../Encoding'), | ||
axis = compiler.axis = require('./axis'), | ||
legend = compiler.legend = require('./legend'), | ||
marks = compiler.marks = require('./marks'), | ||
scale = compiler.scale = require('./scale'); | ||
compiler.data = require('./data'); | ||
compiler.facet = require('./facet'); | ||
compiler.layout = require('./layout'); | ||
compiler.stack = require('./stack'); | ||
compiler.style = require('./style'); | ||
compiler.subfacet = require('./subfacet'); | ||
compiler.time = require('./time'); | ||
compiler.compile = function (spec, stats, theme) { | ||
return compiler.compileEncoding(Encoding.fromSpec(spec, theme), stats); | ||
}; | ||
compiler.shorthand = function (shorthand, stats, config, theme) { | ||
return compiler.compileEncoding(Encoding.fromShorthand(shorthand, config, theme), stats); | ||
}; | ||
/** | ||
* Create a Vega specification from a Vega-lite Encoding object. | ||
*/ | ||
compiler.compileEncoding = function (encoding, stats) { | ||
// no need to pass stats if you pass in the data | ||
if (!stats) { | ||
if (encoding.hasValues()) { | ||
stats = summary(encoding.data().values).reduce(function(s, p) { | ||
s[p.field] = p; | ||
return s; | ||
}, {}); | ||
} else { | ||
console.error('No stats provided and data is not embedded.'); | ||
} | ||
} | ||
var layout = compiler.layout(encoding, stats); | ||
var spec = { | ||
width: layout.width, | ||
height: layout.height, | ||
padding: 'auto', | ||
data: compiler.data(encoding), | ||
// global scales contains only time unit scales | ||
scales: compiler.time.scales(encoding), | ||
marks: [{ | ||
name: 'cell', | ||
var Model_1 = require('./Model'); | ||
var axis_1 = require('./axis'); | ||
var data_1 = require('./data'); | ||
var facet_1 = require('./facet'); | ||
var legend_1 = require('./legend'); | ||
var marks_1 = require('./marks'); | ||
var scale_1 = require('./scale'); | ||
var util_1 = require('../util'); | ||
var data_2 = require('../data'); | ||
var channel_1 = require('../channel'); | ||
var Model_2 = require('./Model'); | ||
exports.Model = Model_2.Model; | ||
function compile(spec, theme) { | ||
var model = new Model_1.Model(spec, theme); | ||
var layout = model.layout(); | ||
var rootGroup = util_1.extend({ | ||
name: spec.name ? spec.name + '_root' : 'root', | ||
type: 'group', | ||
}, spec.description ? { description: spec.description } : {}, { | ||
from: { data: data_2.LAYOUT }, | ||
properties: { | ||
enter: { | ||
width: layout.cellWidth ? | ||
{value: layout.cellWidth} : | ||
{field: {group: 'width'}}, | ||
height: layout.cellHeight ? | ||
{value: layout.cellHeight} : | ||
{field: {group: 'height'}} | ||
} | ||
update: { | ||
width: layout.width.field ? | ||
{ field: layout.width.field } : | ||
{ value: layout.width }, | ||
height: layout.height.field ? | ||
{ field: layout.height.field } : | ||
{ value: layout.height } | ||
} | ||
} | ||
}] | ||
}; | ||
var group = spec.marks[0]; | ||
// marks | ||
var style = compiler.style(encoding, stats), | ||
mdefs = group.marks = marks.def(encoding, layout, style, stats), | ||
mdef = mdefs[mdefs.length - 1]; // TODO: remove this dirty hack by refactoring the whole flow | ||
var stack = encoding.stack(); | ||
if (stack) { | ||
// modify mdef.{from,properties} | ||
compiler.stack(encoding, mdef, stack); | ||
} | ||
var lineType = marks[encoding.marktype()].line; | ||
// handle subfacets | ||
var details = encoding.details(); | ||
if (details.length > 0 && lineType) { | ||
//subfacet to group area / line together in one group | ||
compiler.subfacet(group, mdef, details); | ||
} | ||
// auto-sort line/area values | ||
if (lineType && encoding.config('autoSortLine')) { | ||
var f = (encoding.isMeasure(X) && encoding.isDimension(Y)) ? Y : X; | ||
if (!mdef.from) { | ||
mdef.from = {}; | ||
}); | ||
var marks = marks_1.compileMarks(model); | ||
if (model.has(channel_1.ROW) || model.has(channel_1.COLUMN)) { | ||
util_1.extend(rootGroup, facet_1.facetMixins(model, marks)); | ||
} | ||
// TODO: why - ? | ||
mdef.from.transform = [{type: 'sort', by: '-' + encoding.fieldRef(f)}]; | ||
} | ||
// get a flattened list of all scale names that are used in the vl spec | ||
var singleScaleNames = [].concat.apply([], mdefs.map(function(markProps) { | ||
return scale.names(markProps.properties.update); | ||
})); | ||
// Small Multiples | ||
if (encoding.has(ROW) || encoding.has(COL)) { | ||
spec = compiler.facet(group, encoding, layout, spec, singleScaleNames, stats); | ||
spec.legends = legend.defs(encoding, style); | ||
} else { | ||
group.scales = scale.defs(singleScaleNames, encoding, layout, stats); | ||
group.axes = []; | ||
if (encoding.has(X)) { | ||
group.axes.push(axis.def(X, encoding, layout, stats)); | ||
else { | ||
rootGroup.marks = marks.map(function (marks) { | ||
marks.from = marks.from || {}; | ||
marks.from.data = model.dataTable(); | ||
return marks; | ||
}); | ||
var scaleNames = model.map(function (_, channel) { | ||
return channel; | ||
}); | ||
rootGroup.scales = scale_1.compileScales(scaleNames, model); | ||
var axes = (model.has(channel_1.X) ? [axis_1.compileAxis(channel_1.X, model)] : []) | ||
.concat(model.has(channel_1.Y) ? [axis_1.compileAxis(channel_1.Y, model)] : []); | ||
if (axes.length > 0) { | ||
rootGroup.axes = axes; | ||
} | ||
} | ||
if (encoding.has(Y)) { | ||
group.axes.push(axis.def(Y, encoding, layout, stats)); | ||
var legends = legend_1.compileLegends(model); | ||
if (legends.length > 0) { | ||
rootGroup.legends = legends; | ||
} | ||
group.legends = legend.defs(encoding, style); | ||
} | ||
return spec; | ||
}; | ||
var FIT = 1; | ||
var output = util_1.extend(spec.name ? { name: spec.name } : {}, { | ||
width: layout.width.field ? FIT : layout.width, | ||
height: layout.height.field ? FIT : layout.height, | ||
padding: 'auto' | ||
}, ['viewport', 'background', 'scene'].reduce(function (topLevelConfig, property) { | ||
var value = model.config(property); | ||
if (value !== undefined) { | ||
topLevelConfig[property] = value; | ||
} | ||
return topLevelConfig; | ||
}, {}), { | ||
data: data_1.compileData(model), | ||
marks: [rootGroup] | ||
}); | ||
return { | ||
spec: output | ||
}; | ||
} | ||
exports.compile = compile; | ||
//# sourceMappingURL=compiler.js.map |
@@ -1,279 +0,329 @@ | ||
'use strict'; | ||
require('../globals'); | ||
module.exports = data; | ||
var vlEncDef = require('../encdef'), | ||
util = require('../util'), | ||
time = require('./time'); | ||
/** | ||
* Create Vega's data array from a given encoding. | ||
* | ||
* @param {Encoding} encoding | ||
* @return {Array} Array of Vega data. | ||
* This always includes a "raw" data table. | ||
* If the encoding contains aggregate value, this will also create | ||
* aggregate table as well. | ||
*/ | ||
function data(encoding) { | ||
var def = [data.raw(encoding)]; | ||
var aggregate = data.aggregate(encoding); | ||
if (aggregate) { | ||
def.push(data.aggregate(encoding)); | ||
} | ||
// TODO add "having" filter here | ||
// append non-positive filter at the end for the data table | ||
data.filterNonPositive(def[def.length - 1], encoding); | ||
// Stack | ||
var stack = encoding.stack(); | ||
if (stack) { | ||
def.push(data.stack(encoding, stack)); | ||
} | ||
return def; | ||
var vlFieldDef = require('../fielddef'); | ||
var util = require('../util'); | ||
var bin_1 = require('../bin'); | ||
var channel_1 = require('../channel'); | ||
var data_1 = require('../data'); | ||
var time = require('./time'); | ||
var type_1 = require('../type'); | ||
function compileData(model) { | ||
var def = [source.def(model)]; | ||
var summaryDef = summary.def(model); | ||
if (summaryDef) { | ||
def.push(summaryDef); | ||
} | ||
filterNonPositiveForLog(def[def.length - 1], model); | ||
var statsDef = layout.def(model); | ||
if (statsDef) { | ||
def.push(statsDef); | ||
} | ||
var stackDef = model.stack(); | ||
if (stackDef) { | ||
def.push(stack.def(model, stackDef)); | ||
} | ||
return def; | ||
} | ||
data.raw = function(encoding) { | ||
var raw = {name: RAW}; | ||
// Data source (url or inline) | ||
if (encoding.hasValues()) { | ||
raw.values = encoding.data().values; | ||
raw.format = {type: 'json'}; | ||
} else { | ||
raw.url = encoding.data().url; | ||
raw.format = {type: encoding.data().formatType}; | ||
} | ||
// Set data's format.parse if needed | ||
var parse = data.raw.formatParse(encoding); | ||
if (parse) { | ||
raw.format.parse = parse; | ||
} | ||
raw.transform = data.raw.transform(encoding); | ||
return raw; | ||
}; | ||
data.raw.formatParse = function(encoding) { | ||
var parse; | ||
encoding.forEach(function(encDef) { | ||
if (encDef.type == T) { | ||
parse = parse || {}; | ||
parse[encDef.name] = 'date'; | ||
} else if (encDef.type == Q) { | ||
if (vlEncDef.isCount(encDef)) return; | ||
parse = parse || {}; | ||
parse[encDef.name] = 'number'; | ||
exports.compileData = compileData; | ||
var source; | ||
(function (source_1) { | ||
function def(model) { | ||
var source = { name: data_1.SOURCE }; | ||
if (model.hasValues()) { | ||
source.values = model.data().values; | ||
source.format = { type: 'json' }; | ||
} | ||
else { | ||
source.url = model.data().url; | ||
source.format = { type: model.data().formatType }; | ||
} | ||
var parse = formatParse(model); | ||
if (parse) { | ||
source.format.parse = parse; | ||
} | ||
source.transform = transform(model); | ||
return source; | ||
} | ||
}); | ||
return parse; | ||
}; | ||
/** | ||
* Generate Vega transforms for the raw data table. This can include | ||
* transforms for time unit, binning and filtering. | ||
*/ | ||
data.raw.transform = function(encoding) { | ||
// null filter comes first so transforms are not performed on null values | ||
// time and bin should come before filter so we can filter by time and bin | ||
return data.raw.transform.nullFilter(encoding).concat( | ||
data.raw.transform.formula(encoding), | ||
data.raw.transform.time(encoding), | ||
data.raw.transform.bin(encoding), | ||
data.raw.transform.filter(encoding) | ||
); | ||
}; | ||
data.raw.transform.time = function(encoding) { | ||
return encoding.reduce(function(transform, encDef, encType) { | ||
if (encDef.type === T && encDef.timeUnit) { | ||
var fieldRef = encoding.fieldRef(encType, {nofn: true, datum: true}); | ||
transform.push({ | ||
type: 'formula', | ||
field: encoding.fieldRef(encType), | ||
expr: time.formula(encDef.timeUnit, fieldRef) | ||
}); | ||
source_1.def = def; | ||
function formatParse(model) { | ||
var parse; | ||
model.forEach(function (fieldDef) { | ||
if (fieldDef.type === type_1.TEMPORAL) { | ||
parse = parse || {}; | ||
parse[fieldDef.field] = 'date'; | ||
} | ||
else if (fieldDef.type === type_1.QUANTITATIVE) { | ||
if (vlFieldDef.isCount(fieldDef)) | ||
return; | ||
parse = parse || {}; | ||
parse[fieldDef.field] = 'number'; | ||
} | ||
}); | ||
return parse; | ||
} | ||
return transform; | ||
}, []); | ||
}; | ||
data.raw.transform.bin = function(encoding) { | ||
return encoding.reduce(function(transform, encDef, encType) { | ||
if (encoding.bin(encType)) { | ||
transform.push({ | ||
type: 'bin', | ||
field: encDef.name, | ||
output: { | ||
start: encoding.fieldRef(encType, {bin_suffix: '_start'}), | ||
end: encoding.fieldRef(encType, {bin_suffix: '_end'}) | ||
}, | ||
maxbins: encoding.bin(encType).maxbins | ||
}); | ||
// temporary fix for adding missing `bin_mid` from the bin transform | ||
transform.push({ | ||
type: 'formula', | ||
field: encoding.fieldRef(encType, {bin_suffix: '_mid'}), | ||
expr: '(' + encoding.fieldRef(encType, {datum:1, bin_suffix: '_start'}) + '+' + encoding.fieldRef(encType, {datum:1, bin_suffix: '_end'}) + ')/2' | ||
}); | ||
function transform(model) { | ||
return nullFilterTransform(model).concat(formulaTransform(model), timeTransform(model), binTransform(model), filterTransform(model)); | ||
} | ||
return transform; | ||
}, []); | ||
}; | ||
/** | ||
* @return {Array} An array that might contain a filter transform for filtering null value based on filterNul config | ||
*/ | ||
data.raw.transform.nullFilter = function(encoding) { | ||
var filteredFields = util.reduce(encoding.fields(), | ||
function(filteredFields, fieldList, fieldName) { | ||
if (fieldName === '*') return filteredFields; //count | ||
// TODO(#597) revise how filterNull is structured. | ||
if ((encoding.config('filterNull').Q && fieldList.containsType[Q]) || | ||
(encoding.config('filterNull').T && fieldList.containsType[T]) || | ||
(encoding.config('filterNull').O && fieldList.containsType[O]) || | ||
(encoding.config('filterNull').N && fieldList.containsType[N])) { | ||
filteredFields.push(fieldName); | ||
} | ||
return filteredFields; | ||
}, []); | ||
return filteredFields.length > 0 ? | ||
[{ | ||
type: 'filter', | ||
test: filteredFields.map(function(fieldName) { | ||
return 'datum.' + fieldName + '!==null'; | ||
}).join(' && ') | ||
}] : []; | ||
}; | ||
data.raw.transform.filter = function(encoding) { | ||
var filter = encoding.data().filter; | ||
return filter ? [{ | ||
type: 'filter', | ||
test: filter | ||
}] : []; | ||
}; | ||
data.raw.transform.formula = function(encoding) { | ||
var formulas = encoding.data().formulas; | ||
if (formulas === undefined) { | ||
return []; | ||
} | ||
return formulas.reduce(function(transform, formula) { | ||
formula.type = 'formula'; | ||
transform.push(formula); | ||
return transform; | ||
}, []); | ||
}; | ||
data.aggregate = function(encoding) { | ||
/* dict set for dimensions */ | ||
var dims = {}; | ||
/* dictionary mapping field name => dict set of aggregation functions */ | ||
var meas = {}; | ||
var hasAggregate = false; | ||
encoding.forEach(function(encDef, encType) { | ||
if (encDef.aggregate) { | ||
hasAggregate = true; | ||
if (encDef.aggregate === 'count') { | ||
meas['*'] = meas['*'] || {}; | ||
meas['*'].count = true; | ||
} else { | ||
meas[encDef.name] = meas[encDef.name] || {}; | ||
meas[encDef.name][encDef.aggregate] = true; | ||
} | ||
} else { | ||
if (encDef.bin) { | ||
// TODO(#694) only add dimension for the required ones. | ||
dims[encoding.fieldRef(encType, {bin_suffix: '_start'})] = encoding.fieldRef(encType, {bin_suffix: '_start'}); | ||
dims[encoding.fieldRef(encType, {bin_suffix: '_mid'})] = encoding.fieldRef(encType, {bin_suffix: '_mid'}); | ||
dims[encoding.fieldRef(encType, {bin_suffix: '_end'})] = encoding.fieldRef(encType, {bin_suffix: '_end'}); | ||
} else { | ||
dims[encDef.name] = encoding.fieldRef(encType); | ||
} | ||
source_1.transform = transform; | ||
function timeTransform(model) { | ||
return model.reduce(function (transform, fieldDef, channel) { | ||
if (fieldDef.type === type_1.TEMPORAL && fieldDef.timeUnit) { | ||
var field = model.field(channel, { nofn: true, datum: true }); | ||
transform.push({ | ||
type: 'formula', | ||
field: model.field(channel), | ||
expr: time.formula(fieldDef.timeUnit, field) | ||
}); | ||
} | ||
return transform; | ||
}, []); | ||
} | ||
}); | ||
var groupby = util.vals(dims); | ||
// short-format summarize object for Vega's aggregate transform | ||
// https://github.com/vega/vega/wiki/Data-Transforms#-aggregate | ||
var summarize = util.reduce(meas, function(summarize, fnDictSet, field) { | ||
summarize[field] = util.keys(fnDictSet); | ||
return summarize; | ||
}, {}); | ||
if (hasAggregate) { | ||
return { | ||
name: AGGREGATE, | ||
source: RAW, | ||
transform: [{ | ||
type: 'aggregate', | ||
groupby: groupby, | ||
summarize: summarize | ||
}] | ||
}; | ||
} | ||
return null; | ||
}; | ||
/** | ||
* Add stacked data source, for feeding the shared scale. | ||
*/ | ||
data.stack = function(encoding, stack) { | ||
var dim = stack.groupby; | ||
var val = stack.value; | ||
var facets = encoding.facets(); | ||
var stacked = { | ||
name: STACKED, | ||
source: encoding.dataTable(), | ||
transform: [{ | ||
type: 'aggregate', | ||
groupby: [encoding.fieldRef(dim)].concat(facets), // dim and other facets | ||
summarize: [{ops: ['sum'], field: encoding.fieldRef(val)}] | ||
}] | ||
}; | ||
if (facets && facets.length > 0) { | ||
stacked.transform.push({ //calculate max for each facet | ||
type: 'aggregate', | ||
groupby: facets, | ||
summarize: [{ | ||
ops: ['max'], | ||
// we want max of sum from above transform | ||
field: encoding.fieldRef(val, {prefn: 'sum_'}) | ||
}] | ||
source_1.timeTransform = timeTransform; | ||
function binTransform(model) { | ||
return model.reduce(function (transform, fieldDef, channel) { | ||
var bin = model.bin(channel); | ||
if (bin) { | ||
transform.push({ | ||
type: 'bin', | ||
field: fieldDef.field, | ||
output: { | ||
start: model.field(channel, { binSuffix: '_start' }), | ||
mid: model.field(channel, { binSuffix: '_mid' }), | ||
end: model.field(channel, { binSuffix: '_end' }) | ||
}, | ||
maxbins: typeof bin === 'boolean' ? bin_1.MAXBINS_DEFAULT : bin.maxbins | ||
}); | ||
} | ||
return transform; | ||
}, []); | ||
} | ||
source_1.binTransform = binTransform; | ||
function nullFilterTransform(model) { | ||
var filterNull = model.config('filterNull'); | ||
var filteredFields = util.keys(model.reduce(function (filteredFields, fieldDef) { | ||
if (fieldDef.field && fieldDef.field !== '*' && filterNull[fieldDef.type]) { | ||
filteredFields[fieldDef.field] = true; | ||
} | ||
return filteredFields; | ||
}, {})); | ||
return filteredFields.length > 0 ? | ||
[{ | ||
type: 'filter', | ||
test: filteredFields.map(function (fieldName) { | ||
return 'datum.' + fieldName + '!==null'; | ||
}).join(' && ') | ||
}] : []; | ||
} | ||
source_1.nullFilterTransform = nullFilterTransform; | ||
function filterTransform(model) { | ||
var filter = model.data().filter; | ||
return filter ? [{ | ||
type: 'filter', | ||
test: filter | ||
}] : []; | ||
} | ||
source_1.filterTransform = filterTransform; | ||
function formulaTransform(model) { | ||
var calculate = model.data().calculate; | ||
if (calculate === undefined) { | ||
return []; | ||
} | ||
return calculate.reduce(function (transform, formula) { | ||
transform.push(util.extend({ type: 'formula' }, formula)); | ||
return transform; | ||
}, []); | ||
} | ||
source_1.formulaTransform = formulaTransform; | ||
})(source = exports.source || (exports.source = {})); | ||
var layout; | ||
(function (layout_1) { | ||
function def(model) { | ||
var summarize = []; | ||
var formulas = []; | ||
if (model.has(channel_1.X) && model.isOrdinalScale(channel_1.X)) { | ||
var xScale = model.fieldDef(channel_1.X).scale; | ||
var xHasDomain = xScale.domain instanceof Array; | ||
if (!xHasDomain) { | ||
summarize.push({ | ||
field: model.field(channel_1.X), | ||
ops: ['distinct'] | ||
}); | ||
} | ||
var xCardinality = xHasDomain ? xScale.domain.length : | ||
model.field(channel_1.X, { datum: true, prefn: 'distinct_' }); | ||
formulas.push({ | ||
type: 'formula', | ||
field: 'cellWidth', | ||
expr: '(' + xCardinality + ' + ' + xScale.padding + ') * ' + xScale.bandWidth | ||
}); | ||
} | ||
if (model.has(channel_1.Y) && model.isOrdinalScale(channel_1.Y)) { | ||
var yScale = model.fieldDef(channel_1.Y).scale; | ||
var yHasDomain = yScale.domain instanceof Array; | ||
if (!yHasDomain) { | ||
summarize.push({ | ||
field: model.field(channel_1.Y), | ||
ops: ['distinct'] | ||
}); | ||
} | ||
var yCardinality = yHasDomain ? yScale.domain.length : | ||
model.field(channel_1.Y, { datum: true, prefn: 'distinct_' }); | ||
formulas.push({ | ||
type: 'formula', | ||
field: 'cellHeight', | ||
expr: '(' + yCardinality + ' + ' + yScale.padding + ') * ' + yScale.bandWidth | ||
}); | ||
} | ||
var cellPadding = model.config('cell').padding; | ||
var layout = model.layout(); | ||
if (model.has(channel_1.COLUMN)) { | ||
var cellWidth = layout.cellWidth.field ? | ||
'datum.' + layout.cellWidth.field : | ||
layout.cellWidth; | ||
var colScale = model.fieldDef(channel_1.COLUMN).scale; | ||
var colHasDomain = colScale.domain instanceof Array; | ||
if (!colHasDomain) { | ||
summarize.push({ | ||
field: model.fieldDef(channel_1.COLUMN).field, | ||
ops: ['distinct'] | ||
}); | ||
} | ||
var colCardinality = colHasDomain ? colScale.domain.length : | ||
model.field(channel_1.COLUMN, { datum: true, prefn: 'distinct_' }); | ||
formulas.push({ | ||
type: 'formula', | ||
field: 'width', | ||
expr: cellWidth + ' * ' + colCardinality + ' + ' + | ||
'(' + colCardinality + ' - 1) * ' + cellPadding | ||
}); | ||
} | ||
if (model.has(channel_1.ROW)) { | ||
var cellHeight = layout.cellHeight.field ? | ||
'datum.' + layout.cellHeight.field : | ||
layout.cellHeight; | ||
var rowScale = model.fieldDef(channel_1.ROW).scale; | ||
var rowHasDomain = rowScale.domain instanceof Array; | ||
if (!rowHasDomain) { | ||
summarize.push({ | ||
field: model.fieldDef(channel_1.ROW).field, | ||
ops: ['distinct'] | ||
}); | ||
} | ||
var rowCardinality = rowHasDomain ? rowScale.domain.length : | ||
model.field(channel_1.ROW, { datum: true, prefn: 'distinct_' }); | ||
formulas.push({ | ||
type: 'formula', | ||
field: 'height', | ||
expr: cellHeight + ' * ' + rowCardinality + ' + ' + | ||
'(' + rowCardinality + ' - 1) * ' + cellPadding | ||
}); | ||
} | ||
if (formulas.length > 0) { | ||
return summarize.length > 0 ? { | ||
name: data_1.LAYOUT, | ||
source: model.dataTable(), | ||
transform: [{ | ||
type: 'aggregate', | ||
summarize: summarize | ||
}].concat(formulas) | ||
} : { | ||
name: data_1.LAYOUT, | ||
values: [{}], | ||
transform: formulas | ||
}; | ||
} | ||
return null; | ||
} | ||
layout_1.def = def; | ||
})(layout = exports.layout || (exports.layout = {})); | ||
var summary; | ||
(function (summary) { | ||
function def(model) { | ||
var dims = {}; | ||
var meas = {}; | ||
var hasAggregate = false; | ||
model.forEach(function (fieldDef, channel) { | ||
if (fieldDef.aggregate) { | ||
hasAggregate = true; | ||
if (fieldDef.aggregate === 'count') { | ||
meas['*'] = meas['*'] || {}; | ||
meas['*'].count = true; | ||
} | ||
else { | ||
meas[fieldDef.field] = meas[fieldDef.field] || {}; | ||
meas[fieldDef.field][fieldDef.aggregate] = true; | ||
} | ||
} | ||
else { | ||
if (fieldDef.bin) { | ||
dims[model.field(channel, { binSuffix: '_start' })] = model.field(channel, { binSuffix: '_start' }); | ||
dims[model.field(channel, { binSuffix: '_mid' })] = model.field(channel, { binSuffix: '_mid' }); | ||
dims[model.field(channel, { binSuffix: '_end' })] = model.field(channel, { binSuffix: '_end' }); | ||
} | ||
else { | ||
dims[fieldDef.field] = model.field(channel); | ||
} | ||
} | ||
}); | ||
var groupby = util.vals(dims); | ||
var summarize = util.reduce(meas, function (summarize, fnDictSet, field) { | ||
summarize[field] = util.keys(fnDictSet); | ||
return summarize; | ||
}, {}); | ||
if (hasAggregate) { | ||
return { | ||
name: data_1.SUMMARY, | ||
source: data_1.SOURCE, | ||
transform: [{ | ||
type: 'aggregate', | ||
groupby: groupby, | ||
summarize: summarize | ||
}] | ||
}; | ||
} | ||
return null; | ||
} | ||
summary.def = def; | ||
; | ||
})(summary = exports.summary || (exports.summary = {})); | ||
var stack; | ||
(function (stack) { | ||
function def(model, stackProps) { | ||
var groupbyChannel = stackProps.groupbyChannel; | ||
var fieldChannel = stackProps.fieldChannel; | ||
var facetFields = (model.has(channel_1.COLUMN) ? [model.field(channel_1.COLUMN)] : []) | ||
.concat((model.has(channel_1.ROW) ? [model.field(channel_1.ROW)] : [])); | ||
var stacked = { | ||
name: data_1.STACKED, | ||
source: model.dataTable(), | ||
transform: [{ | ||
type: 'aggregate', | ||
groupby: [model.field(groupbyChannel)].concat(facetFields), | ||
summarize: [{ ops: ['sum'], field: model.field(fieldChannel) }] | ||
}] | ||
}; | ||
if (facetFields && facetFields.length > 0) { | ||
stacked.transform.push({ | ||
type: 'aggregate', | ||
groupby: facetFields, | ||
summarize: [{ | ||
ops: ['max'], | ||
field: model.field(fieldChannel, { prefn: 'sum_' }) | ||
}] | ||
}); | ||
} | ||
return stacked; | ||
} | ||
stack.def = def; | ||
; | ||
})(stack = exports.stack || (exports.stack = {})); | ||
function filterNonPositiveForLog(dataTable, model) { | ||
model.forEach(function (_, channel) { | ||
if (model.fieldDef(channel).scale.type === 'log') { | ||
dataTable.transform.push({ | ||
type: 'filter', | ||
test: model.field(channel, { datum: true }) + ' > 0' | ||
}); | ||
} | ||
}); | ||
} | ||
return stacked; | ||
}; | ||
data.filterNonPositive = function(dataTable, encoding) { | ||
encoding.forEach(function(encDef, encType) { | ||
if (encoding.scale(encType).type === 'log') { | ||
dataTable.transform.push({ | ||
type: 'filter', | ||
test: encoding.fieldRef(encType, {datum: 1}) + ' > 0' | ||
}); | ||
} | ||
}); | ||
}; | ||
} | ||
exports.filterNonPositiveForLog = filterNonPositiveForLog; | ||
//# sourceMappingURL=data.js.map |
@@ -1,145 +0,224 @@ | ||
'use strict'; | ||
require('../globals'); | ||
var util = require('../util'); | ||
var axis = require('./axis'), | ||
scale = require('./scale'); | ||
module.exports = faceting; | ||
function groupdef(name, opt) { | ||
opt = opt || {}; | ||
var group = { | ||
name: name || undefined, | ||
type: 'group', | ||
properties: { | ||
enter: { | ||
width: opt.width || {field: {group: 'width'}}, | ||
height: opt.height || {field: {group: 'height'}} | ||
} | ||
var channel_1 = require('../channel'); | ||
var axis_1 = require('./axis'); | ||
var scale_1 = require('./scale'); | ||
function facetMixins(model, marks) { | ||
var layout = model.layout(); | ||
var cellWidth = !model.has(channel_1.COLUMN) ? | ||
{ field: { group: 'width' } } : | ||
layout.cellWidth.field ? | ||
{ scale: 'column', band: true } : | ||
{ value: layout.cellWidth }; | ||
var cellHeight = !model.has(channel_1.ROW) ? | ||
{ field: { group: 'height' } } : | ||
layout.cellHeight.field ? | ||
{ scale: 'row', band: true } : | ||
{ value: layout.cellHeight }; | ||
var facetGroupProperties = { | ||
width: cellWidth, | ||
height: cellHeight | ||
}; | ||
var cellConfig = model.config('cell'); | ||
['fill', 'fillOpacity', 'stroke', 'strokeWidth', | ||
'strokeOpacity', 'strokeDash', 'strokeDashOffset'] | ||
.forEach(function (property) { | ||
var value = cellConfig[property]; | ||
if (value !== undefined) { | ||
facetGroupProperties[property] = value; | ||
} | ||
}); | ||
var rootMarks = [], rootAxes = [], facetKeys = [], cellAxes = []; | ||
var hasRow = model.has(channel_1.ROW), hasCol = model.has(channel_1.COLUMN); | ||
if (hasRow) { | ||
if (!model.isDimension(channel_1.ROW)) { | ||
util.error('Row encoding should be ordinal.'); | ||
} | ||
facetGroupProperties.y = { | ||
scale: channel_1.ROW, | ||
field: model.field(channel_1.ROW) | ||
}; | ||
facetKeys.push(model.field(channel_1.ROW)); | ||
rootAxes.push(axis_1.compileAxis(channel_1.ROW, model)); | ||
if (model.has(channel_1.X)) { | ||
rootMarks.push(getXAxesGroup(model, cellWidth, hasCol)); | ||
} | ||
rootMarks.push(getRowRulesGroup(model, cellHeight)); | ||
} | ||
}; | ||
if (opt.from) { | ||
group.from = opt.from; | ||
} | ||
if (opt.x) { | ||
group.properties.enter.x = opt.x; | ||
} | ||
if (opt.y) { | ||
group.properties.enter.y = opt.y; | ||
} | ||
if (opt.axes) { | ||
group.axes = opt.axes; | ||
} | ||
return group; | ||
} | ||
function faceting(group, encoding, layout, spec, singleScaleNames, stats) { | ||
var enter = group.properties.enter; | ||
var facetKeys = [], cellAxes = [], from, axesGrp; | ||
var hasRow = encoding.has(ROW), hasCol = encoding.has(COL); | ||
enter.fill = {value: encoding.config('cellBackgroundColor')}; | ||
//move "from" to cell level and add facet transform | ||
group.from = {data: group.marks[0].from.data}; | ||
// Hack, this needs to be refactored | ||
for (var i = 0; i < group.marks.length; i++) { | ||
var mark = group.marks[i]; | ||
if (mark.from.transform) { | ||
delete mark.from.data; //need to keep transform for subfacetting case | ||
} else { | ||
delete mark.from; | ||
else { | ||
if (model.has(channel_1.X)) { | ||
cellAxes.push(axis_1.compileAxis(channel_1.X, model)); | ||
} | ||
} | ||
} | ||
if (hasRow) { | ||
if (!encoding.isDimension(ROW)) { | ||
util.error('Row encoding should be ordinal.'); | ||
} | ||
enter.y = {scale: ROW, field: encoding.fieldRef(ROW)}; | ||
enter.height = {'value': layout.cellHeight}; // HACK | ||
facetKeys.push(encoding.fieldRef(ROW)); | ||
if (hasCol) { | ||
from = util.duplicate(group.from); | ||
from.transform = from.transform || []; | ||
from.transform.unshift({type: 'facet', groupby: [encoding.fieldRef(COL)]}); | ||
if (!model.isDimension(channel_1.COLUMN)) { | ||
util.error('Col encoding should be ordinal.'); | ||
} | ||
facetGroupProperties.x = { | ||
scale: channel_1.COLUMN, | ||
field: model.field(channel_1.COLUMN) | ||
}; | ||
facetKeys.push(model.field(channel_1.COLUMN)); | ||
rootAxes.push(axis_1.compileAxis(channel_1.COLUMN, model)); | ||
if (model.has(channel_1.Y)) { | ||
rootMarks.push(getYAxesGroup(model, cellHeight, hasRow)); | ||
} | ||
rootMarks.push(getColumnRulesGroup(model, cellWidth)); | ||
} | ||
axesGrp = groupdef('x-axes', { | ||
axes: encoding.has(X) ? [axis.def(X, encoding, layout, stats)] : undefined, | ||
x: hasCol ? {scale: COL, field: encoding.fieldRef(COL)} : {value: 0}, | ||
width: hasCol && {'value': layout.cellWidth}, //HACK? | ||
from: from | ||
}); | ||
spec.marks.unshift(axesGrp); // need to prepend so it appears under the plots | ||
(spec.axes = spec.axes || []); | ||
spec.axes.push(axis.def(ROW, encoding, layout, stats)); | ||
} else { // doesn't have row | ||
if (encoding.has(X)) { | ||
//keep x axis in the cell | ||
cellAxes.push(axis.def(X, encoding, layout, stats)); | ||
else { | ||
if (model.has(channel_1.Y)) { | ||
cellAxes.push(axis_1.compileAxis(channel_1.Y, model)); | ||
} | ||
} | ||
} | ||
if (hasCol) { | ||
if (!encoding.isDimension(COL)) { | ||
util.error('Col encoding should be ordinal.'); | ||
var facetGroup = { | ||
name: 'cell', | ||
type: 'group', | ||
from: { | ||
data: model.dataTable(), | ||
transform: [{ type: 'facet', groupby: facetKeys }] | ||
}, | ||
properties: { | ||
update: facetGroupProperties | ||
}, | ||
marks: marks | ||
}; | ||
if (cellAxes.length > 0) { | ||
facetGroup.axes = cellAxes; | ||
} | ||
enter.x = {scale: COL, field: encoding.fieldRef(COL)}; | ||
enter.width = {'value': layout.cellWidth}; // HACK | ||
facetKeys.push(encoding.fieldRef(COL)); | ||
rootMarks.push(facetGroup); | ||
var scaleNames = model.map(function (_, channel) { | ||
return channel; | ||
}); | ||
return { | ||
marks: rootMarks, | ||
axes: rootAxes, | ||
scales: scale_1.compileScales(scaleNames, model) | ||
}; | ||
} | ||
exports.facetMixins = facetMixins; | ||
function getXAxesGroup(model, cellWidth, hasCol) { | ||
var xAxesGroup = { | ||
name: 'x-axes', | ||
type: 'group', | ||
properties: { | ||
update: { | ||
width: cellWidth, | ||
height: { field: { group: 'height' } }, | ||
x: hasCol ? { scale: channel_1.COLUMN, field: model.field(channel_1.COLUMN) } : { value: 0 }, | ||
y: { value: -model.config('cell').padding / 2 } | ||
} | ||
}, | ||
axes: [axis_1.compileAxis(channel_1.X, model)] | ||
}; | ||
if (hasCol) { | ||
xAxesGroup.from = { | ||
data: model.dataTable(), | ||
transform: { type: 'facet', groupby: [model.field(channel_1.COLUMN)] } | ||
}; | ||
} | ||
return xAxesGroup; | ||
} | ||
function getYAxesGroup(model, cellHeight, hasRow) { | ||
var yAxesGroup = { | ||
name: 'y-axes', | ||
type: 'group', | ||
properties: { | ||
update: { | ||
width: { field: { group: 'width' } }, | ||
height: cellHeight, | ||
x: { value: -model.config('cell').padding / 2 }, | ||
y: hasRow ? { scale: channel_1.ROW, field: model.field(channel_1.ROW) } : { value: 0 } | ||
} | ||
}, | ||
axes: [axis_1.compileAxis(channel_1.Y, model)] | ||
}; | ||
if (hasRow) { | ||
from = util.duplicate(group.from); | ||
from.transform = from.transform || []; | ||
from.transform.unshift({type: 'facet', groupby: [encoding.fieldRef(ROW)]}); | ||
yAxesGroup.from = { | ||
data: model.dataTable(), | ||
transform: { type: 'facet', groupby: [model.field(channel_1.ROW)] } | ||
}; | ||
} | ||
axesGrp = groupdef('y-axes', { | ||
axes: encoding.has(Y) ? [axis.def(Y, encoding, layout, stats)] : undefined, | ||
y: hasRow && {scale: ROW, field: encoding.fieldRef(ROW)}, | ||
x: hasRow && {value: 0}, | ||
height: hasRow && {'value': layout.cellHeight}, //HACK? | ||
from: from | ||
}); | ||
spec.marks.unshift(axesGrp); // need to prepend so it appears under the plots | ||
(spec.axes = spec.axes || []); | ||
spec.axes.push(axis.def(COL, encoding, layout, stats)); | ||
} else { // doesn't have col | ||
if (encoding.has(Y)) { | ||
cellAxes.push(axis.def(Y, encoding, layout, stats)); | ||
return yAxesGroup; | ||
} | ||
function getRowRulesGroup(model, cellHeight) { | ||
var rowRulesOnTop = !model.has(channel_1.X) || model.fieldDef(channel_1.X).axis.orient !== 'top'; | ||
var offset = model.config('cell').padding / 2 - 1; | ||
var rowRules = { | ||
name: 'row-rules', | ||
type: 'rule', | ||
from: { | ||
data: model.dataTable(), | ||
transform: [{ type: 'facet', groupby: [model.field(channel_1.ROW)] }] | ||
}, | ||
properties: { | ||
update: { | ||
y: { | ||
scale: 'row', | ||
field: model.field(channel_1.ROW), | ||
offset: (rowRulesOnTop ? -1 : 1) * offset | ||
}, | ||
x: { value: 0, offset: -model.config('cell').gridOffset }, | ||
x2: { field: { group: 'width' }, offset: model.config('cell').gridOffset }, | ||
stroke: { value: model.config('cell').gridColor }, | ||
strokeOpacity: { value: model.config('cell').gridOpacity } | ||
} | ||
} | ||
}; | ||
if (rowRulesOnTop) { | ||
return rowRules; | ||
} | ||
} | ||
// assuming equal cellWidth here | ||
// TODO: support heterogenous cellWidth (maybe by using multiple scales?) | ||
spec.scales = (spec.scales || []).concat(scale.defs( | ||
scale.names(enter).concat(singleScaleNames), | ||
encoding, | ||
layout, | ||
stats, | ||
true | ||
)); // row/col scales + cell scales | ||
if (cellAxes.length > 0) { | ||
group.axes = cellAxes; | ||
} | ||
// add facet transform | ||
var trans = (group.from.transform || (group.from.transform = [])); | ||
trans.unshift({type: 'facet', groupby: facetKeys}); | ||
return spec; | ||
return { | ||
name: 'row-rules-group', | ||
type: 'group', | ||
properties: { | ||
update: { | ||
y: cellHeight.value ? | ||
cellHeight : | ||
{ field: { parent: 'cellHeight' } }, | ||
width: { field: { group: 'width' } } | ||
} | ||
}, | ||
marks: [rowRules] | ||
}; | ||
} | ||
function getColumnRulesGroup(model, cellWidth) { | ||
var colRulesOnLeft = !model.has(channel_1.Y) || model.fieldDef(channel_1.Y).axis.orient === 'right'; | ||
var offset = model.config('cell').padding / 2 - 1; | ||
var columnRules = { | ||
name: 'column-rules', | ||
type: 'rule', | ||
from: { | ||
data: model.dataTable(), | ||
transform: [{ type: 'facet', groupby: [model.field(channel_1.COLUMN)] }] | ||
}, | ||
properties: { | ||
update: { | ||
x: { | ||
scale: 'column', | ||
field: model.field(channel_1.COLUMN), | ||
offset: (colRulesOnLeft ? -1 : 1) * offset | ||
}, | ||
y: { value: 0, offset: -model.config('cell').gridOffset }, | ||
y2: { field: { group: 'height' }, offset: model.config('cell').gridOffset }, | ||
stroke: { value: model.config('cell').gridColor }, | ||
strokeOpacity: { value: model.config('cell').gridOpacity } | ||
} | ||
} | ||
}; | ||
if (colRulesOnLeft) { | ||
return columnRules; | ||
} | ||
return { | ||
name: 'column-rules-group', | ||
type: 'group', | ||
properties: { | ||
update: { | ||
x: cellWidth.value ? | ||
cellWidth : | ||
{ field: { parent: 'cellWidth' } }, | ||
height: { field: { group: 'height' } } | ||
} | ||
}, | ||
marks: [columnRules] | ||
}; | ||
} | ||
//# sourceMappingURL=facet.js.map |
@@ -1,156 +0,50 @@ | ||
'use strict'; | ||
require('../globals'); | ||
var util = require('../util'), | ||
setter = util.setter, | ||
time = require('./time'), | ||
d3_format = require('d3-format'); | ||
module.exports = vllayout; | ||
function vllayout(encoding, stats) { | ||
var layout = box(encoding, stats); | ||
layout = offset(encoding, stats, layout); | ||
return layout; | ||
var channel_1 = require('../channel'); | ||
var mark_1 = require('../mark'); | ||
var data_1 = require('../data'); | ||
function compileLayout(model) { | ||
var cellWidth = getCellWidth(model); | ||
var cellHeight = getCellHeight(model); | ||
return { | ||
cellWidth: cellWidth, | ||
cellHeight: cellHeight, | ||
width: getWidth(model, cellWidth), | ||
height: getHeight(model, cellHeight) | ||
}; | ||
} | ||
/* | ||
HACK to set chart size | ||
NOTE: this fails for plots driven by derived values (e.g., aggregates) | ||
One solution is to update Vega to support auto-sizing | ||
In the meantime, auto-padding (mostly) does the trick | ||
*/ | ||
function box(encoding, stats) { | ||
var hasRow = encoding.has(ROW), | ||
hasCol = encoding.has(COL), | ||
hasX = encoding.has(X), | ||
hasY = encoding.has(Y), | ||
marktype = encoding.marktype(); | ||
// FIXME/HACK we need to take filter into account | ||
var xCardinality = hasX && encoding.isDimension(X) ? encoding.cardinality(X, stats) : 1, | ||
yCardinality = hasY && encoding.isDimension(Y) ? encoding.cardinality(Y, stats) : 1; | ||
var useSmallBand = xCardinality > encoding.config('largeBandMaxCardinality') || | ||
yCardinality > encoding.config('largeBandMaxCardinality'); | ||
var cellWidth, cellHeight, cellPadding = encoding.config('cellPadding'); | ||
// set cellWidth | ||
if (hasX) { | ||
if (encoding.isOrdinalScale(X)) { | ||
// for ordinal, hasCol or not doesn't matter -- we scale based on cardinality | ||
cellWidth = (xCardinality + encoding.encDef(X).band.padding) * encoding.bandSize(X, useSmallBand); | ||
} else { | ||
cellWidth = hasCol || hasRow ? encoding.encDef(COL).width : encoding.config('singleWidth'); | ||
exports.compileLayout = compileLayout; | ||
function getCellWidth(model) { | ||
if (model.has(channel_1.X)) { | ||
if (model.isOrdinalScale(channel_1.X)) { | ||
return { data: data_1.LAYOUT, field: 'cellWidth' }; | ||
} | ||
return model.config('cell').width; | ||
} | ||
} else { | ||
if (marktype === TEXT) { | ||
cellWidth = encoding.config('textCellWidth'); | ||
} else { | ||
cellWidth = encoding.bandSize(X); | ||
if (model.mark() === mark_1.TEXT) { | ||
return model.config('textCellWidth'); | ||
} | ||
} | ||
// set cellHeight | ||
if (hasY) { | ||
if (encoding.isOrdinalScale(Y)) { | ||
// for ordinal, hasCol or not doesn't matter -- we scale based on cardinality | ||
cellHeight = (yCardinality + encoding.encDef(Y).band.padding) * encoding.bandSize(Y, useSmallBand); | ||
} else { | ||
cellHeight = hasCol || hasRow ? encoding.encDef(ROW).height : encoding.config('singleHeight'); | ||
return model.fieldDef(channel_1.X).scale.bandWidth; | ||
} | ||
function getWidth(model, cellWidth) { | ||
if (model.has(channel_1.COLUMN)) { | ||
return { data: data_1.LAYOUT, field: 'width' }; | ||
} | ||
} else { | ||
cellHeight = encoding.bandSize(Y); | ||
} | ||
// Cell bands use rangeBands(). There are n-1 padding. Outerpadding = 0 for cells | ||
var width = cellWidth, height = cellHeight; | ||
if (hasCol) { | ||
var colCardinality = encoding.cardinality(COL, stats); | ||
width = cellWidth * ((1 + cellPadding) * (colCardinality - 1) + 1); | ||
} | ||
if (hasRow) { | ||
var rowCardinality = encoding.cardinality(ROW, stats); | ||
height = cellHeight * ((1 + cellPadding) * (rowCardinality - 1) + 1); | ||
} | ||
return { | ||
// width and height of the whole cell | ||
cellWidth: cellWidth, | ||
cellHeight: cellHeight, | ||
cellPadding: cellPadding, | ||
// width and height of the chart | ||
width: width, | ||
height: height, | ||
// information about x and y, such as band size | ||
x: {useSmallBand: useSmallBand}, | ||
y: {useSmallBand: useSmallBand} | ||
}; | ||
return cellWidth; | ||
} | ||
// FIXME fieldStats.max isn't always the longest | ||
function getMaxNumberLength(encoding, et, fieldStats) { | ||
var format = encoding.numberFormat(et, fieldStats); | ||
return d3_format.format(format)(fieldStats.max).length; | ||
} | ||
// TODO(#600) revise this | ||
function getMaxLength(encoding, stats, et) { | ||
var encDef = encoding.encDef(et), | ||
fieldStats = stats[encDef.name]; | ||
if (encDef.bin) { | ||
// TODO once bin support range, need to update this | ||
return getMaxNumberLength(encoding, et, fieldStats); | ||
} if (encoding.isType(et, Q)) { | ||
return getMaxNumberLength(encoding, et, fieldStats); | ||
} else if (encoding.isType(et, T)) { | ||
return time.maxLength(encoding.encDef(et).timeUnit, encoding); | ||
} else if (encoding.isTypes(et, [N, O])) { | ||
if(fieldStats.type === 'number') { | ||
return getMaxNumberLength(encoding, et, fieldStats); | ||
} else { | ||
return Math.min(fieldStats.max, encoding.axis(et).maxLabelLength || Infinity); | ||
function getCellHeight(model) { | ||
if (model.has(channel_1.Y)) { | ||
if (model.isOrdinalScale(channel_1.Y)) { | ||
return { data: data_1.LAYOUT, field: 'cellHeight' }; | ||
} | ||
else { | ||
return model.config('cell').height; | ||
} | ||
} | ||
} | ||
return model.fieldDef(channel_1.Y).scale.bandWidth; | ||
} | ||
function offset(encoding, stats, layout) { | ||
[X, Y].forEach(function (et) { | ||
// TODO(kanitw): Jul 19, 2015 - create a set of visual test for extraOffset | ||
var extraOffset = et === X ? 20 : 22, | ||
maxLength; | ||
if (encoding.isDimension(et) || encoding.isType(et, T)) { | ||
maxLength = getMaxLength(encoding, stats, et); | ||
} else if ( | ||
// TODO once we have #512 (allow using inferred type) | ||
// Need to adjust condition here. | ||
encoding.isType(et, Q) || | ||
encoding.encDef(et).aggregate === 'count' | ||
) { | ||
if ( | ||
et===Y | ||
// || (et===X && false) | ||
// FIXME determine when X would rotate, but should move this to axis.js first #506 | ||
) { | ||
maxLength = getMaxLength(encoding, stats, et); | ||
} | ||
} else { | ||
// nothing | ||
function getHeight(model, cellHeight) { | ||
if (model.has(channel_1.ROW)) { | ||
return { data: data_1.LAYOUT, field: 'height' }; | ||
} | ||
if (maxLength) { | ||
setter(layout,[et, 'axisTitleOffset'], encoding.config('characterWidth') * maxLength + extraOffset); | ||
} else { | ||
// if no max length (no rotation case), use maxLength = 3 | ||
setter(layout,[et, 'axisTitleOffset'], encoding.config('characterWidth') * 3 + extraOffset); | ||
} | ||
}); | ||
return layout; | ||
return cellHeight; | ||
} | ||
//# sourceMappingURL=layout.js.map |
@@ -1,108 +0,117 @@ | ||
'use strict'; | ||
require('../globals'); | ||
var time = require('./time'), | ||
util = require('../util'), | ||
setter = util.setter, | ||
getter = util.getter; | ||
var legend = module.exports = {}; | ||
legend.defs = function(encoding, style) { | ||
var defs = []; | ||
if (encoding.has(COLOR) && encoding.encDef(COLOR).legend) { | ||
defs.push(legend.def(COLOR, encoding, { | ||
fill: COLOR | ||
}, style)); | ||
} | ||
if (encoding.has(SIZE) && encoding.encDef(SIZE).legend) { | ||
defs.push(legend.def(SIZE, encoding, { | ||
size: SIZE | ||
}, style)); | ||
} | ||
if (encoding.has(SHAPE) && encoding.encDef(SHAPE).legend) { | ||
defs.push(legend.def(SHAPE, encoding, { | ||
shape: SHAPE | ||
}, style)); | ||
} | ||
return defs; | ||
}; | ||
legend.def = function(name, encoding, def, style) { | ||
var timeUnit = encoding.encDef(name).timeUnit; | ||
def.title = legend.title(name, encoding); | ||
def.orient = encoding.encDef(name).legend.orient; | ||
def = legend.style(name, encoding, def, style); | ||
if (encoding.isType(name, T) && | ||
timeUnit && | ||
time.hasScale(timeUnit) | ||
) { | ||
setter(def, ['properties', 'labels', 'text', 'scale'], 'time-'+ timeUnit); | ||
} | ||
return def; | ||
}; | ||
legend.style = function(name, e, def, style) { | ||
var symbols = getter(def, ['properties', 'symbols']), | ||
marktype = e.marktype(); | ||
switch (marktype) { | ||
case 'bar': | ||
case 'tick': | ||
case 'text': | ||
symbols.stroke = {value: 'transparent'}; | ||
symbols.shape = {value: 'square'}; | ||
break; | ||
case 'circle': | ||
case 'square': | ||
symbols.shape = {value: marktype}; | ||
/* fall through */ | ||
case 'point': | ||
// fill or stroke | ||
if (e.encDef(SHAPE).filled) { | ||
if (e.has(COLOR) && name === COLOR) { | ||
symbols.fill = {scale: COLOR, field: 'data'}; | ||
} else { | ||
symbols.fill = {value: e.value(COLOR)}; | ||
var util_1 = require('../util'); | ||
var channel_1 = require('../channel'); | ||
var time = require('./time'); | ||
var type_1 = require('../type'); | ||
var mark_1 = require('../mark'); | ||
function compileLegends(model) { | ||
var defs = []; | ||
if (model.has(channel_1.COLOR) && model.fieldDef(channel_1.COLOR).legend) { | ||
defs.push(compileLegend(model, channel_1.COLOR, { | ||
fill: channel_1.COLOR | ||
})); | ||
} | ||
if (model.has(channel_1.SIZE) && model.fieldDef(channel_1.SIZE).legend) { | ||
defs.push(compileLegend(model, channel_1.SIZE, { | ||
size: channel_1.SIZE | ||
})); | ||
} | ||
if (model.has(channel_1.SHAPE) && model.fieldDef(channel_1.SHAPE).legend) { | ||
defs.push(compileLegend(model, channel_1.SHAPE, { | ||
shape: channel_1.SHAPE | ||
})); | ||
} | ||
return defs; | ||
} | ||
exports.compileLegends = compileLegends; | ||
function compileLegend(model, channel, def) { | ||
var legend = model.fieldDef(channel).legend; | ||
def.title = title(model, channel); | ||
['orient', 'format', 'values'].forEach(function (property) { | ||
var value = legend[property]; | ||
if (value !== undefined) { | ||
def[property] = value; | ||
} | ||
symbols.stroke = {value: 'transparent'}; | ||
} else { | ||
if (e.has(COLOR) && name === COLOR) { | ||
symbols.stroke = {scale: COLOR, field: 'data'}; | ||
} else { | ||
symbols.stroke = {value: e.value(COLOR)}; | ||
}); | ||
var props = (typeof legend !== 'boolean' && legend.properties) || {}; | ||
['title', 'labels', 'symbols', 'legend'].forEach(function (group) { | ||
var value = properties[group] ? | ||
properties[group](model, channel, props[group]) : | ||
props[group]; | ||
if (value !== undefined) { | ||
def.properties = def.properties || {}; | ||
def.properties[group] = value; | ||
} | ||
symbols.fill = {value: 'transparent'}; | ||
symbols.strokeWidth = {value: e.config('strokeWidth')}; | ||
} | ||
break; | ||
case 'line': | ||
case 'area': | ||
// TODO use shape here after implementing #508 | ||
break; | ||
} | ||
var opacity = e.encDef(COLOR).opacity || style.opacity; | ||
if (opacity) { | ||
symbols.opacity = {value: opacity}; | ||
} | ||
return def; | ||
}; | ||
legend.title = function(name, encoding) { | ||
var leg = encoding.encDef(name).legend; | ||
if (leg.title) return leg.title; | ||
return encoding.fieldTitle(name); | ||
}; | ||
}); | ||
return def; | ||
} | ||
exports.compileLegend = compileLegend; | ||
function title(model, channel) { | ||
var legend = model.fieldDef(channel).legend; | ||
if (typeof legend !== 'boolean' && legend.title) { | ||
return legend.title; | ||
} | ||
return model.fieldTitle(channel); | ||
} | ||
exports.title = title; | ||
var properties; | ||
(function (properties) { | ||
function labels(model, channel, spec) { | ||
var fieldDef = model.fieldDef(channel); | ||
var timeUnit = fieldDef.timeUnit; | ||
if (fieldDef.type === type_1.TEMPORAL && timeUnit && time.labelTemplate(timeUnit)) { | ||
return util_1.extend({ | ||
text: { | ||
template: '{{datum.data | ' + time.labelTemplate(timeUnit) + '}}' | ||
} | ||
}, spec || {}); | ||
} | ||
return spec; | ||
} | ||
properties.labels = labels; | ||
function symbols(model, channel, spec) { | ||
var symbols = {}; | ||
var mark = model.mark(); | ||
switch (mark) { | ||
case mark_1.BAR: | ||
case mark_1.TICK: | ||
case mark_1.TEXT: | ||
symbols.stroke = { value: 'transparent' }; | ||
symbols.shape = { value: 'square' }; | ||
break; | ||
case mark_1.CIRCLE: | ||
case mark_1.SQUARE: | ||
symbols.shape = { value: mark }; | ||
case mark_1.POINT: | ||
if (model.config('marks').filled) { | ||
if (model.has(channel_1.COLOR) && channel === channel_1.COLOR) { | ||
symbols.fill = { scale: channel_1.COLOR, field: 'data' }; | ||
} | ||
else { | ||
symbols.fill = { value: model.fieldDef(channel_1.COLOR).value }; | ||
} | ||
symbols.stroke = { value: 'transparent' }; | ||
} | ||
else { | ||
if (model.has(channel_1.COLOR) && channel === channel_1.COLOR) { | ||
symbols.stroke = { scale: channel_1.COLOR, field: 'data' }; | ||
} | ||
else { | ||
symbols.stroke = { value: model.fieldDef(channel_1.COLOR).value }; | ||
} | ||
symbols.fill = { value: 'transparent' }; | ||
symbols.strokeWidth = { value: model.config('marks').strokeWidth }; | ||
} | ||
break; | ||
case mark_1.LINE: | ||
case mark_1.AREA: | ||
break; | ||
} | ||
var opacity = model.markOpacity(); | ||
if (opacity) | ||
symbols.opacity = { value: opacity }; | ||
symbols = util_1.extend(symbols, spec || {}); | ||
return util_1.keys(symbols).length > 0 ? symbols : undefined; | ||
} | ||
properties.symbols = symbols; | ||
})(properties || (properties = {})); | ||
//# sourceMappingURL=legend.js.map |
@@ -1,452 +0,584 @@ | ||
'use strict'; | ||
require('../globals'); | ||
var marks = module.exports = {}; | ||
marks.def = function(encoding, layout, style, stats) { | ||
var defs = [], | ||
mark = marks[encoding.marktype()], | ||
from = encoding.dataTable(); | ||
// to add a background to text, we need to add it before the text | ||
if (encoding.marktype() === TEXT && encoding.has(COLOR)) { | ||
var bg = { | ||
x: {value: 0}, | ||
y: {value: 0}, | ||
x2: {value: layout.cellWidth}, | ||
y2: {value: layout.cellHeight}, | ||
fill: {scale: COLOR, field: encoding.fieldRef(COLOR)} | ||
}; | ||
defs.push({ | ||
type: 'rect', | ||
from: {data: from}, | ||
properties: {enter: bg, update: bg} | ||
}); | ||
} | ||
// add the mark def for the main thing | ||
var p = mark.prop(encoding, layout, style, stats); | ||
defs.push({ | ||
type: mark.type, | ||
from: {data: from}, | ||
properties: {enter: p, update: p} | ||
}); | ||
return defs; | ||
var channel_1 = require('../channel'); | ||
var mark_1 = require('../mark'); | ||
var type_1 = require('../type'); | ||
var stack_1 = require('./stack'); | ||
var MARKTYPES_MAP = { | ||
bar: 'rect', | ||
tick: 'rect', | ||
point: 'symbol', | ||
line: 'line', | ||
area: 'area', | ||
text: 'text', | ||
circle: 'symbol', | ||
square: 'symbol' | ||
}; | ||
marks.bar = { | ||
type: 'rect', | ||
prop: bar_props, | ||
supportedEncoding: {row: 1, col: 1, x: 1, y: 1, size: 1, color: 1} | ||
}; | ||
marks.line = { | ||
type: 'line', | ||
line: true, | ||
prop: line_props, | ||
requiredEncoding: ['x', 'y'], | ||
supportedEncoding: {row: 1, col: 1, x: 1, y: 1, color: 1, detail:1} | ||
}; | ||
marks.area = { | ||
type: 'area', | ||
line: true, | ||
requiredEncoding: ['x', 'y'], | ||
prop: area_props, | ||
supportedEncoding: {row: 1, col: 1, x: 1, y: 1, color: 1} | ||
}; | ||
marks.tick = { | ||
type: 'rect', | ||
prop: tick_props, | ||
supportedEncoding: {row: 1, col: 1, x: 1, y: 1, color: 1, detail: 1} | ||
}; | ||
marks.circle = { | ||
type: 'symbol', | ||
prop: filled_point_props('circle'), | ||
supportedEncoding: {row: 1, col: 1, x: 1, y: 1, size: 1, color: 1, detail: 1} | ||
}; | ||
marks.square = { | ||
type: 'symbol', | ||
prop: filled_point_props('square'), | ||
supportedEncoding: marks.circle.supportedEncoding | ||
}; | ||
marks.point = { | ||
type: 'symbol', | ||
prop: point_props, | ||
supportedEncoding: {row: 1, col: 1, x: 1, y: 1, size: 1, color: 1, shape: 1, detail: 1} | ||
}; | ||
marks.text = { | ||
type: 'text', | ||
prop: text_props, | ||
requiredEncoding: ['text'], | ||
supportedEncoding: {row: 1, col: 1, size: 1, color: 1, text: 1} | ||
}; | ||
function bar_props(e, layout, style) { | ||
// jshint unused:false | ||
var p = {}; | ||
// x's and width | ||
if (e.encDef(X).bin) { | ||
p.x = {scale: X, field: e.fieldRef(X, {bin_suffix: '_start'}), offset: 1}; | ||
p.x2 = {scale: X, field: e.fieldRef(X, {bin_suffix: '_end'})}; | ||
} else if (e.isMeasure(X)) { | ||
p.x = {scale: X, field: e.fieldRef(X)}; | ||
if (!e.has(Y) || e.isDimension(Y)) { | ||
p.x2 = {value: 0}; | ||
function compileMarks(model) { | ||
var mark = model.mark(); | ||
if (mark === mark_1.LINE || mark === mark_1.AREA) { | ||
var sortBy = mark === mark_1.LINE ? model.config('sortLineBy') : undefined; | ||
if (!sortBy) { | ||
var sortField = (model.isMeasure(channel_1.X) && model.isDimension(channel_1.Y)) ? channel_1.Y : channel_1.X; | ||
sortBy = '-' + model.field(sortField); | ||
} | ||
var pathMarks = { | ||
type: MARKTYPES_MAP[mark], | ||
from: { | ||
transform: [{ type: 'sort', by: sortBy }] | ||
}, | ||
properties: { | ||
update: properties[mark](model) | ||
} | ||
}; | ||
var details = detailFields(model); | ||
if (details.length > 0) { | ||
var facetTransform = { type: 'facet', groupby: details }; | ||
var transform = mark === mark_1.AREA && model.stack() ? | ||
[stack_1.imputeTransform(model), stack_1.stackTransform(model), facetTransform] : | ||
[facetTransform]; | ||
return [{ | ||
name: mark + '-facet', | ||
type: 'group', | ||
from: { | ||
transform: transform | ||
}, | ||
properties: { | ||
update: { | ||
width: { field: { group: 'width' } }, | ||
height: { field: { group: 'height' } } | ||
} | ||
}, | ||
marks: [pathMarks] | ||
}]; | ||
} | ||
else { | ||
return [pathMarks]; | ||
} | ||
} | ||
} else { | ||
if (e.has(X)) { // is ordinal | ||
p.xc = {scale: X, field: e.fieldRef(X)}; | ||
} else { | ||
p.x = {value: 0, offset: e.config('singleBarOffset')}; | ||
} | ||
} | ||
// width | ||
if (!p.x2) { | ||
if (!e.has(X) || e.isOrdinalScale(X)) { // no X or X is ordinal | ||
if (e.has(SIZE)) { | ||
p.width = {scale: SIZE, field: e.fieldRef(SIZE)}; | ||
} else { | ||
p.width = { | ||
value: e.bandSize(X, layout.x.useSmallBand), | ||
offset: -1 | ||
else { | ||
var marks = []; | ||
if (mark === mark_1.TEXT && model.has(channel_1.COLOR)) { | ||
marks.push({ | ||
type: 'rect', | ||
properties: { update: properties.textBackground(model) } | ||
}); | ||
} | ||
var mainDef = { | ||
type: MARKTYPES_MAP[mark], | ||
properties: { | ||
update: properties[mark](model) | ||
} | ||
}; | ||
} | ||
} else { // X is Quant or Time Scale | ||
p.width = {value: 2}; | ||
var stack = model.stack(); | ||
if (mark === mark_1.BAR && stack) { | ||
mainDef.from = { | ||
transform: [stack_1.stackTransform(model)] | ||
}; | ||
} | ||
marks.push(mainDef); | ||
return marks; | ||
} | ||
} | ||
// y's & height | ||
if (e.encDef(Y).bin) { | ||
p.y = {scale: Y, field: e.fieldRef(Y, {bin_suffix: '_start'})}; | ||
p.y2 = {scale: Y, field: e.fieldRef(Y, {bin_suffix: '_end'}), offset: 1}; | ||
} else if (e.isMeasure(Y)) { | ||
p.y = {scale: Y, field: e.fieldRef(Y)}; | ||
p.y2 = {field: {group: 'height'}}; | ||
} else { | ||
if (e.has(Y)) { // is ordinal | ||
p.yc = {scale: Y, field: e.fieldRef(Y)}; | ||
} else { | ||
p.y2 = { | ||
field: {group: 'height'}, | ||
offset: -e.config('singleBarOffset') | ||
}; | ||
} | ||
if (e.has(SIZE)) { | ||
p.height = {scale: SIZE, field: e.fieldRef(SIZE)}; | ||
} else { | ||
p.height = { | ||
value: e.bandSize(Y, layout.y.useSmallBand), | ||
offset: -1 | ||
}; | ||
} | ||
} | ||
// fill | ||
if (e.has(COLOR)) { | ||
p.fill = {scale: COLOR, field: e.fieldRef(COLOR)}; | ||
} else { | ||
p.fill = {value: e.value(COLOR)}; | ||
} | ||
// opacity | ||
var opacity = e.encDef(COLOR).opacity; | ||
if (opacity) p.opacity = {value: opacity}; | ||
return p; | ||
} | ||
function point_props(e, layout, style) { | ||
var p = {}; | ||
// x | ||
if (e.has(X)) { | ||
p.x = {scale: X, field: e.fieldRef(X, {bin_suffix: '_mid'})}; | ||
} else if (!e.has(X)) { | ||
p.x = {value: e.bandSize(X, layout.x.useSmallBand) / 2}; | ||
} | ||
// y | ||
if (e.has(Y)) { | ||
p.y = {scale: Y, field: e.fieldRef(Y, {bin_suffix: '_mid'})}; | ||
} else if (!e.has(Y)) { | ||
p.y = {value: e.bandSize(Y, layout.y.useSmallBand) / 2}; | ||
} | ||
// size | ||
if (e.has(SIZE)) { | ||
p.size = {scale: SIZE, field: e.fieldRef(SIZE)}; | ||
} else if (!e.has(SIZE)) { | ||
p.size = {value: e.value(SIZE)}; | ||
} | ||
// shape | ||
if (e.has(SHAPE)) { | ||
p.shape = {scale: SHAPE, field: e.fieldRef(SHAPE)}; | ||
} else if (!e.has(SHAPE)) { | ||
p.shape = {value: e.value(SHAPE)}; | ||
} | ||
// fill or stroke | ||
if (e.encDef(SHAPE).filled) { | ||
if (e.has(COLOR)) { | ||
p.fill = {scale: COLOR, field: e.fieldRef(COLOR)}; | ||
} else if (!e.has(COLOR)) { | ||
p.fill = {value: e.value(COLOR)}; | ||
} | ||
} else { | ||
if (e.has(COLOR)) { | ||
p.stroke = {scale: COLOR, field: e.fieldRef(COLOR)}; | ||
} else if (!e.has(COLOR)) { | ||
p.stroke = {value: e.value(COLOR)}; | ||
} | ||
p.strokeWidth = {value: e.config('strokeWidth')}; | ||
} | ||
// opacity | ||
var opacity = e.encDef(COLOR).opacity || style.opacity; | ||
if (opacity) p.opacity = {value: opacity}; | ||
return p; | ||
exports.compileMarks = compileMarks; | ||
function applyMarksConfig(marksProperties, marksConfig, propsList) { | ||
propsList.forEach(function (property) { | ||
var value = marksConfig[property]; | ||
if (value !== undefined) { | ||
marksProperties[property] = { value: value }; | ||
} | ||
}); | ||
} | ||
function line_props(e,layout, style) { | ||
// jshint unused:false | ||
var p = {}; | ||
// x | ||
if (e.has(X)) { | ||
p.x = {scale: X, field: e.fieldRef(X, {bin_suffix: '_mid'})}; | ||
} else if (!e.has(X)) { | ||
p.x = {value: 0}; | ||
} | ||
// y | ||
if (e.has(Y)) { | ||
p.y = {scale: Y, field: e.fieldRef(Y, {bin_suffix: '_mid'})}; | ||
} else if (!e.has(Y)) { | ||
p.y = {field: {group: 'height'}}; | ||
} | ||
// stroke | ||
if (e.has(COLOR)) { | ||
p.stroke = {scale: COLOR, field: e.fieldRef(COLOR)}; | ||
} else if (!e.has(COLOR)) { | ||
p.stroke = {value: e.value(COLOR)}; | ||
} | ||
var opacity = e.encDef(COLOR).opacity; | ||
if (opacity) p.opacity = {value: opacity}; | ||
p.strokeWidth = {value: e.config('strokeWidth')}; | ||
return p; | ||
function detailFields(model) { | ||
return [channel_1.COLOR, channel_1.DETAIL, channel_1.SHAPE].reduce(function (details, channel) { | ||
if (model.has(channel) && !model.fieldDef(channel).aggregate) { | ||
details.push(model.field(channel)); | ||
} | ||
return details; | ||
}, []); | ||
} | ||
// TODO(#694): optimize area's usage with bin | ||
function area_props(e, layout, style) { | ||
// jshint unused:false | ||
var p = {}; | ||
// x | ||
if (e.isMeasure(X)) { | ||
p.x = {scale: X, field: e.fieldRef(X)}; | ||
if (e.isDimension(Y)) { | ||
p.x2 = {scale: X, value: 0}; | ||
p.orient = {value: 'horizontal'}; | ||
var properties; | ||
(function (properties) { | ||
function bar(model) { | ||
var stack = model.stack(); | ||
var p = {}; | ||
if (stack && channel_1.X === stack.fieldChannel) { | ||
p.x = { | ||
scale: channel_1.X, | ||
field: model.field(channel_1.X) + '_start' | ||
}; | ||
p.x2 = { | ||
scale: channel_1.X, | ||
field: model.field(channel_1.X) + '_end' | ||
}; | ||
} | ||
else if (model.fieldDef(channel_1.X).bin) { | ||
p.x = { | ||
scale: channel_1.X, | ||
field: model.field(channel_1.X, { binSuffix: '_start' }), | ||
offset: 1 | ||
}; | ||
p.x2 = { | ||
scale: channel_1.X, | ||
field: model.field(channel_1.X, { binSuffix: '_end' }) | ||
}; | ||
} | ||
else if (model.isMeasure(channel_1.X)) { | ||
p.x = { | ||
scale: channel_1.X, | ||
field: model.field(channel_1.X) | ||
}; | ||
if (!model.has(channel_1.Y) || model.isDimension(channel_1.Y)) { | ||
p.x2 = { value: 0 }; | ||
} | ||
} | ||
else { | ||
if (model.has(channel_1.X)) { | ||
p.xc = { | ||
scale: channel_1.X, | ||
field: model.field(channel_1.X) | ||
}; | ||
} | ||
else { | ||
p.x = { value: 0, offset: model.config('singleBarOffset') }; | ||
} | ||
} | ||
if (!p.x2) { | ||
if (!model.has(channel_1.X) || model.isOrdinalScale(channel_1.X)) { | ||
if (model.has(channel_1.SIZE)) { | ||
p.width = { | ||
scale: channel_1.SIZE, | ||
field: model.field(channel_1.SIZE) | ||
}; | ||
} | ||
else { | ||
p.width = { | ||
value: model.fieldDef(channel_1.X).scale.bandWidth, | ||
offset: -1 | ||
}; | ||
} | ||
} | ||
else { | ||
p.width = { value: 2 }; | ||
} | ||
} | ||
if (stack && channel_1.Y === stack.fieldChannel) { | ||
p.y = { | ||
scale: channel_1.Y, | ||
field: model.field(channel_1.Y) + '_start' | ||
}; | ||
p.y2 = { | ||
scale: channel_1.Y, | ||
field: model.field(channel_1.Y) + '_end' | ||
}; | ||
} | ||
else if (model.fieldDef(channel_1.Y).bin) { | ||
p.y = { | ||
scale: channel_1.Y, | ||
field: model.field(channel_1.Y, { binSuffix: '_start' }) | ||
}; | ||
p.y2 = { | ||
scale: channel_1.Y, | ||
field: model.field(channel_1.Y, { binSuffix: '_end' }), | ||
offset: 1 | ||
}; | ||
} | ||
else if (model.isMeasure(channel_1.Y)) { | ||
p.y = { | ||
scale: channel_1.Y, | ||
field: model.field(channel_1.Y) | ||
}; | ||
p.y2 = { field: { group: 'height' } }; | ||
} | ||
else { | ||
if (model.has(channel_1.Y)) { | ||
p.yc = { | ||
scale: channel_1.Y, | ||
field: model.field(channel_1.Y) | ||
}; | ||
} | ||
else { | ||
p.y2 = { | ||
field: { group: 'height' }, | ||
offset: -model.config('singleBarOffset') | ||
}; | ||
} | ||
if (model.has(channel_1.SIZE)) { | ||
p.height = { | ||
scale: channel_1.SIZE, | ||
field: model.field(channel_1.SIZE) | ||
}; | ||
} | ||
else { | ||
p.height = { | ||
value: model.fieldDef(channel_1.Y).scale.bandWidth, | ||
offset: -1 | ||
}; | ||
} | ||
} | ||
if (model.has(channel_1.COLOR)) { | ||
p.fill = { | ||
scale: channel_1.COLOR, | ||
field: model.field(channel_1.COLOR) | ||
}; | ||
} | ||
else { | ||
p.fill = { value: model.fieldDef(channel_1.COLOR).value }; | ||
} | ||
var opacity = model.markOpacity(); | ||
if (opacity) | ||
p.opacity = { value: opacity }; | ||
return p; | ||
} | ||
} else if (e.has(X)) { | ||
p.x = {scale: X, field: e.fieldRef(X, {bin_suffix: '_mid'})}; | ||
} else { | ||
p.x = {value: 0}; | ||
} | ||
// y | ||
if (e.isMeasure(Y)) { | ||
p.y = {scale: Y, field: e.fieldRef(Y)}; | ||
p.y2 = {scale: Y, value: 0}; | ||
} else if (e.has(Y)) { | ||
p.y = {scale: Y, field: e.fieldRef(Y, {bin_suffix: '_mid'})}; | ||
} else { | ||
p.y = {field: {group: 'height'}}; | ||
} | ||
// fill | ||
if (e.has(COLOR)) { | ||
p.fill = {scale: COLOR, field: e.fieldRef(COLOR)}; | ||
} else if (!e.has(COLOR)) { | ||
p.fill = {value: e.value(COLOR)}; | ||
} | ||
var opacity = e.encDef(COLOR).opacity; | ||
if (opacity) p.opacity = {value: opacity}; | ||
return p; | ||
} | ||
function tick_props(e, layout, style) { | ||
var p = {}; | ||
// x | ||
if (e.has(X)) { | ||
p.x = {scale: X, field: e.fieldRef(X, {bin_suffix: '_mid'})}; | ||
if (e.isDimension(X)) { | ||
p.x.offset = -e.bandSize(X, layout.x.useSmallBand) / 3; | ||
properties.bar = bar; | ||
function point(model) { | ||
var p = {}; | ||
var marksConfig = model.config('marks'); | ||
if (model.has(channel_1.X)) { | ||
p.x = { | ||
scale: channel_1.X, | ||
field: model.field(channel_1.X, { binSuffix: '_mid' }) | ||
}; | ||
} | ||
else if (!model.has(channel_1.X)) { | ||
p.x = { value: model.fieldDef(channel_1.X).scale.bandWidth / 2 }; | ||
} | ||
if (model.has(channel_1.Y)) { | ||
p.y = { | ||
scale: channel_1.Y, | ||
field: model.field(channel_1.Y, { binSuffix: '_mid' }) | ||
}; | ||
} | ||
else if (!model.has(channel_1.Y)) { | ||
p.y = { value: model.fieldDef(channel_1.Y).scale.bandWidth / 2 }; | ||
} | ||
if (model.has(channel_1.SIZE)) { | ||
p.size = { | ||
scale: channel_1.SIZE, | ||
field: model.field(channel_1.SIZE) | ||
}; | ||
} | ||
else if (!model.has(channel_1.SIZE)) { | ||
p.size = { value: model.fieldDef(channel_1.SIZE).value }; | ||
} | ||
if (model.has(channel_1.SHAPE)) { | ||
p.shape = { | ||
scale: channel_1.SHAPE, | ||
field: model.field(channel_1.SHAPE) | ||
}; | ||
} | ||
else if (!model.has(channel_1.SHAPE)) { | ||
p.shape = { value: model.fieldDef(channel_1.SHAPE).value }; | ||
} | ||
if (marksConfig.filled) { | ||
if (model.has(channel_1.COLOR)) { | ||
p.fill = { | ||
scale: channel_1.COLOR, | ||
field: model.field(channel_1.COLOR) | ||
}; | ||
} | ||
else if (!model.has(channel_1.COLOR)) { | ||
p.fill = { value: model.fieldDef(channel_1.COLOR).value }; | ||
} | ||
} | ||
else { | ||
if (model.has(channel_1.COLOR)) { | ||
p.stroke = { | ||
scale: channel_1.COLOR, | ||
field: model.field(channel_1.COLOR) | ||
}; | ||
} | ||
else if (!model.has(channel_1.COLOR)) { | ||
p.stroke = { value: model.fieldDef(channel_1.COLOR).value }; | ||
} | ||
p.strokeWidth = { value: model.config('marks').strokeWidth }; | ||
} | ||
var opacity = model.markOpacity(); | ||
if (opacity) | ||
p.opacity = { value: opacity }; | ||
return p; | ||
} | ||
} else if (!e.has(X)) { | ||
p.x = {value: 0}; | ||
} | ||
// y | ||
if (e.has(Y)) { | ||
p.y = {scale: Y, field: e.fieldRef(Y, {bin_suffix: '_mid'})}; | ||
if (e.isDimension(Y)) { | ||
p.y.offset = -e.bandSize(Y, layout.y.useSmallBand) / 3; | ||
properties.point = point; | ||
function line(model) { | ||
var p = {}; | ||
if (model.has(channel_1.X)) { | ||
p.x = { | ||
scale: channel_1.X, | ||
field: model.field(channel_1.X, { binSuffix: '_mid' }) | ||
}; | ||
} | ||
else if (!model.has(channel_1.X)) { | ||
p.x = { value: 0 }; | ||
} | ||
if (model.has(channel_1.Y)) { | ||
p.y = { | ||
scale: channel_1.Y, | ||
field: model.field(channel_1.Y, { binSuffix: '_mid' }) | ||
}; | ||
} | ||
else if (!model.has(channel_1.Y)) { | ||
p.y = { field: { group: 'height' } }; | ||
} | ||
if (model.has(channel_1.COLOR)) { | ||
p.stroke = { | ||
scale: channel_1.COLOR, | ||
field: model.field(channel_1.COLOR) | ||
}; | ||
} | ||
else if (!model.has(channel_1.COLOR)) { | ||
p.stroke = { value: model.fieldDef(channel_1.COLOR).value }; | ||
} | ||
var opacity = model.markOpacity(); | ||
if (opacity) | ||
p.opacity = { value: opacity }; | ||
p.strokeWidth = { value: model.config('marks').strokeWidth }; | ||
applyMarksConfig(p, model.config('marks'), ['interpolate', 'tension']); | ||
return p; | ||
} | ||
} else if (!e.has(Y)) { | ||
p.y = {value: 0}; | ||
} | ||
// width | ||
if (!e.has(X) || e.isDimension(X)) { | ||
// TODO(#694): optimize tick's width for bin | ||
p.width = {value: e.bandSize(X, layout.y.useSmallBand) / 1.5}; | ||
} else { | ||
p.width = {value: 1}; | ||
} | ||
// height | ||
if (!e.has(Y) || e.isDimension(Y)) { | ||
// TODO(#694): optimize tick's height for bin | ||
p.height = {value: e.bandSize(Y, layout.y.useSmallBand) / 1.5}; | ||
} else { | ||
p.height = {value: 1}; | ||
} | ||
// fill | ||
if (e.has(COLOR)) { | ||
p.fill = {scale: COLOR, field: e.fieldRef(COLOR)}; | ||
} else { | ||
p.fill = {value: e.value(COLOR)}; | ||
} | ||
var opacity = e.encDef(COLOR).opacity || style.opacity; | ||
if(opacity) p.opacity = {value: opacity}; | ||
return p; | ||
} | ||
function filled_point_props(shape) { | ||
return function(e, layout, style) { | ||
var p = {}; | ||
// x | ||
if (e.has(X)) { | ||
p.x = {scale: X, field: e.fieldRef(X, {bin_suffix: '_mid'})}; | ||
} else if (!e.has(X)) { | ||
p.x = {value: e.bandSize(X, layout.x.useSmallBand) / 2}; | ||
properties.line = line; | ||
function area(model) { | ||
var stack = model.stack(); | ||
var p = {}; | ||
if (stack && channel_1.X === stack.fieldChannel) { | ||
p.x = { | ||
scale: channel_1.X, | ||
field: model.field(channel_1.X) + '_start' | ||
}; | ||
p.x2 = { | ||
scale: channel_1.X, | ||
field: model.field(channel_1.X) + '_end' | ||
}; | ||
} | ||
else if (model.isMeasure(channel_1.X)) { | ||
p.x = { scale: channel_1.X, field: model.field(channel_1.X) }; | ||
if (model.isDimension(channel_1.Y)) { | ||
p.x2 = { | ||
scale: channel_1.X, | ||
value: 0 | ||
}; | ||
p.orient = { value: 'horizontal' }; | ||
} | ||
} | ||
else if (model.has(channel_1.X)) { | ||
p.x = { | ||
scale: channel_1.X, | ||
field: model.field(channel_1.X, { binSuffix: '_mid' }) | ||
}; | ||
} | ||
else { | ||
p.x = { value: 0 }; | ||
} | ||
if (stack && channel_1.Y === stack.fieldChannel) { | ||
p.y = { | ||
scale: channel_1.Y, | ||
field: model.field(channel_1.Y) + '_start' | ||
}; | ||
p.y2 = { | ||
scale: channel_1.Y, | ||
field: model.field(channel_1.Y) + '_end' | ||
}; | ||
} | ||
else if (model.isMeasure(channel_1.Y)) { | ||
p.y = { | ||
scale: channel_1.Y, | ||
field: model.field(channel_1.Y) | ||
}; | ||
p.y2 = { | ||
scale: channel_1.Y, | ||
value: 0 | ||
}; | ||
} | ||
else if (model.has(channel_1.Y)) { | ||
p.y = { | ||
scale: channel_1.Y, | ||
field: model.field(channel_1.Y, { binSuffix: '_mid' }) | ||
}; | ||
} | ||
else { | ||
p.y = { field: { group: 'height' } }; | ||
} | ||
if (model.has(channel_1.COLOR)) { | ||
p.fill = { | ||
scale: channel_1.COLOR, | ||
field: model.field(channel_1.COLOR) | ||
}; | ||
} | ||
else if (!model.has(channel_1.COLOR)) { | ||
p.fill = { value: model.fieldDef(channel_1.COLOR).value }; | ||
} | ||
var opacity = model.markOpacity(); | ||
if (opacity) | ||
p.opacity = { value: opacity }; | ||
applyMarksConfig(p, model.config('marks'), ['interpolate', 'tension']); | ||
return p; | ||
} | ||
// y | ||
if (e.has(Y)) { | ||
p.y = {scale: Y, field: e.fieldRef(Y, {bin_suffix: '_mid'})}; | ||
} else if (!e.has(Y)) { | ||
p.y = {value: e.bandSize(Y, layout.y.useSmallBand) / 2}; | ||
properties.area = area; | ||
function tick(model) { | ||
var p = {}; | ||
if (model.has(channel_1.X)) { | ||
p.x = { | ||
scale: channel_1.X, | ||
field: model.field(channel_1.X, { binSuffix: '_mid' }) | ||
}; | ||
if (model.isDimension(channel_1.X)) { | ||
p.x.offset = -model.fieldDef(channel_1.X).scale.bandWidth / 3; | ||
} | ||
} | ||
else if (!model.has(channel_1.X)) { | ||
p.x = { value: 0 }; | ||
} | ||
if (model.has(channel_1.Y)) { | ||
p.y = { | ||
scale: channel_1.Y, | ||
field: model.field(channel_1.Y, { binSuffix: '_mid' }) | ||
}; | ||
if (model.isDimension(channel_1.Y)) { | ||
p.y.offset = -model.fieldDef(channel_1.Y).scale.bandWidth / 3; | ||
} | ||
} | ||
else if (!model.has(channel_1.Y)) { | ||
p.y = { value: 0 }; | ||
} | ||
if (!model.has(channel_1.X) || model.isDimension(channel_1.X)) { | ||
p.width = { value: model.fieldDef(channel_1.X).scale.bandWidth / 1.5 }; | ||
} | ||
else { | ||
p.width = { value: 1 }; | ||
} | ||
if (!model.has(channel_1.Y) || model.isDimension(channel_1.Y)) { | ||
p.height = { value: model.fieldDef(channel_1.Y).scale.bandWidth / 1.5 }; | ||
} | ||
else { | ||
p.height = { value: 1 }; | ||
} | ||
if (model.has(channel_1.COLOR)) { | ||
p.fill = { | ||
scale: channel_1.COLOR, | ||
field: model.field(channel_1.COLOR) | ||
}; | ||
} | ||
else { | ||
p.fill = { value: model.fieldDef(channel_1.COLOR).value }; | ||
} | ||
var opacity = model.markOpacity(); | ||
if (opacity) | ||
p.opacity = { value: opacity }; | ||
return p; | ||
} | ||
// size | ||
if (e.has(SIZE)) { | ||
p.size = {scale: SIZE, field: e.fieldRef(SIZE)}; | ||
} else if (!e.has(X)) { | ||
p.size = {value: e.value(SIZE)}; | ||
properties.tick = tick; | ||
function filled_point_props(shape) { | ||
return function (model) { | ||
var p = {}; | ||
if (model.has(channel_1.X)) { | ||
p.x = { | ||
scale: channel_1.X, | ||
field: model.field(channel_1.X, { binSuffix: '_mid' }) | ||
}; | ||
} | ||
else if (!model.has(channel_1.X)) { | ||
p.x = { value: model.fieldDef(channel_1.X).scale.bandWidth / 2 }; | ||
} | ||
if (model.has(channel_1.Y)) { | ||
p.y = { | ||
scale: channel_1.Y, | ||
field: model.field(channel_1.Y, { binSuffix: '_mid' }) | ||
}; | ||
} | ||
else if (!model.has(channel_1.Y)) { | ||
p.y = { value: model.fieldDef(channel_1.Y).scale.bandWidth / 2 }; | ||
} | ||
if (model.has(channel_1.SIZE)) { | ||
p.size = { | ||
scale: channel_1.SIZE, | ||
field: model.field(channel_1.SIZE) | ||
}; | ||
} | ||
else if (!model.has(channel_1.X)) { | ||
p.size = { value: model.fieldDef(channel_1.SIZE).value }; | ||
} | ||
p.shape = { value: shape }; | ||
if (model.has(channel_1.COLOR)) { | ||
p.fill = { | ||
scale: channel_1.COLOR, | ||
field: model.field(channel_1.COLOR) | ||
}; | ||
} | ||
else if (!model.has(channel_1.COLOR)) { | ||
p.fill = { value: model.fieldDef(channel_1.COLOR).value }; | ||
} | ||
var opacity = model.markOpacity(); | ||
if (opacity) | ||
p.opacity = { value: opacity }; | ||
return p; | ||
}; | ||
} | ||
// shape | ||
p.shape = {value: shape}; | ||
// fill | ||
if (e.has(COLOR)) { | ||
p.fill = {scale: COLOR, field: e.fieldRef(COLOR)}; | ||
} else if (!e.has(COLOR)) { | ||
p.fill = {value: e.value(COLOR)}; | ||
properties.circle = filled_point_props('circle'); | ||
properties.square = filled_point_props('square'); | ||
function textBackground(model) { | ||
return { | ||
x: { value: 0 }, | ||
y: { value: 0 }, | ||
width: { field: { group: 'width' } }, | ||
height: { field: { group: 'height' } }, | ||
fill: { scale: channel_1.COLOR, field: model.field(channel_1.COLOR) } | ||
}; | ||
} | ||
var opacity = e.encDef(COLOR).opacity || style.opacity; | ||
if(opacity) p.opacity = {value: opacity}; | ||
return p; | ||
}; | ||
} | ||
function text_props(e, layout, style, stats) { | ||
var p = {}, | ||
encDef = e.encDef(TEXT); | ||
// x | ||
if (e.has(X)) { | ||
p.x = {scale: X, field: e.fieldRef(X, {bin_suffix: '_mid'})}; | ||
} else if (!e.has(X)) { | ||
if (e.has(TEXT) && e.isType(TEXT, Q)) { | ||
p.x = {value: layout.cellWidth-5}; | ||
} else { | ||
p.x = {value: e.bandSize(X, layout.x.useSmallBand) / 2}; | ||
properties.textBackground = textBackground; | ||
function text(model) { | ||
var p = {}; | ||
var fieldDef = model.fieldDef(channel_1.TEXT); | ||
var marksConfig = model.config('marks'); | ||
if (model.has(channel_1.X)) { | ||
p.x = { | ||
scale: channel_1.X, | ||
field: model.field(channel_1.X, { binSuffix: '_mid' }) | ||
}; | ||
} | ||
else if (!model.has(channel_1.X)) { | ||
if (model.has(channel_1.TEXT) && model.fieldDef(channel_1.TEXT).type === type_1.QUANTITATIVE) { | ||
p.x = { field: { group: 'width' }, offset: -5 }; | ||
} | ||
else { | ||
p.x = { value: model.fieldDef(channel_1.X).scale.bandWidth / 2 }; | ||
} | ||
} | ||
if (model.has(channel_1.Y)) { | ||
p.y = { | ||
scale: channel_1.Y, | ||
field: model.field(channel_1.Y, { binSuffix: '_mid' }) | ||
}; | ||
} | ||
else if (!model.has(channel_1.Y)) { | ||
p.y = { value: model.fieldDef(channel_1.Y).scale.bandWidth / 2 }; | ||
} | ||
if (model.has(channel_1.SIZE)) { | ||
p.fontSize = { | ||
scale: channel_1.SIZE, | ||
field: model.field(channel_1.SIZE) | ||
}; | ||
} | ||
else if (!model.has(channel_1.SIZE)) { | ||
p.fontSize = { value: marksConfig.fontSize }; | ||
} | ||
var opacity = model.markOpacity(); | ||
if (opacity) | ||
p.opacity = { value: opacity }; | ||
if (model.has(channel_1.TEXT)) { | ||
if (model.fieldDef(channel_1.TEXT).type === type_1.QUANTITATIVE) { | ||
var numberFormat = marksConfig.format !== undefined ? | ||
marksConfig.format : model.numberFormat(channel_1.TEXT); | ||
p.text = { template: '{{' + model.field(channel_1.TEXT, { datum: true }) + | ||
' | number:\'' + numberFormat + '\'}}' }; | ||
} | ||
else { | ||
p.text = { field: model.field(channel_1.TEXT) }; | ||
} | ||
} | ||
else { | ||
p.text = { value: fieldDef.value }; | ||
} | ||
applyMarksConfig(p, marksConfig, ['angle', 'align', 'baseline', 'dx', 'dy', 'fill', 'font', 'fontWeight', | ||
'fontStyle', 'radius', 'theta']); | ||
return p; | ||
} | ||
} | ||
// y | ||
if (e.has(Y)) { | ||
p.y = {scale: Y, field: e.fieldRef(Y, {bin_suffix: '_mid'})}; | ||
} else if (!e.has(Y)) { | ||
p.y = {value: e.bandSize(Y, layout.y.useSmallBand) / 2}; | ||
} | ||
// size | ||
if (e.has(SIZE)) { | ||
p.fontSize = {scale: SIZE, field: e.fieldRef(SIZE)}; | ||
} else if (!e.has(SIZE)) { | ||
p.fontSize = {value: encDef.font.size}; | ||
} | ||
// fill | ||
// color should be set to background | ||
p.fill = {value: encDef.color}; | ||
var opacity = e.encDef(COLOR).opacity || style.opacity; | ||
if(opacity) p.opacity = {value: opacity}; | ||
// text | ||
if (e.has(TEXT)) { | ||
if (e.isType(TEXT, Q)) { | ||
var fieldStats = stats[e.encDef(TEXT).name], | ||
numberFormat = encDef.format || e.numberFormat(fieldStats); | ||
p.text = {template: '{{' + e.fieldRef(TEXT, {datum: true}) + ' | number:\'' + | ||
numberFormat +'\'}}'}; | ||
p.align = {value: encDef.align}; | ||
} else { | ||
p.text = {field: e.fieldRef(TEXT)}; | ||
} | ||
} else { | ||
p.text = {value: encDef.placeholder}; | ||
} | ||
p.font = {value: encDef.font.family}; | ||
p.fontWeight = {value: encDef.font.weight}; | ||
p.fontStyle = {value: encDef.font.style}; | ||
p.baseline = {value: encDef.baseline}; | ||
return p; | ||
} | ||
properties.text = text; | ||
})(properties = exports.properties || (exports.properties = {})); | ||
//# sourceMappingURL=marks.js.map |
@@ -1,359 +0,273 @@ | ||
'use strict'; | ||
require('../globals'); | ||
var util = require('../util'), | ||
time = require('./time'), | ||
colorbrewer = require('colorbrewer'), | ||
interpolate = require('d3-color').interpolateHsl; | ||
var scale = module.exports = {}; | ||
scale.names = function(props) { | ||
return util.keys(util.keys(props).reduce(function(a, x) { | ||
if (props[x] && props[x].scale) a[props[x].scale] = 1; | ||
return a; | ||
}, {})); | ||
}; | ||
scale.defs = function(names, encoding, layout, stats, facet) { | ||
return names.reduce(function(a, name) { | ||
var scaleDef = {}; | ||
scaleDef.name = name; | ||
scaleDef.type = scale.type(name, encoding); | ||
scaleDef.domain = scale.domain(encoding, name, scaleDef.type, facet); | ||
// Add optional properties | ||
var reverse = scale.reverse(encoding, name); | ||
if (reverse) { | ||
scaleDef.reverse = reverse; | ||
var util_1 = require('../util'); | ||
var aggregate_1 = require('../aggregate'); | ||
var channel_1 = require('../channel'); | ||
var data_1 = require('../data'); | ||
var time = require('./time'); | ||
var type_1 = require('../type'); | ||
var mark_1 = require('../mark'); | ||
function compileScales(names, model) { | ||
return names.reduce(function (a, channel) { | ||
var scaleDef = { | ||
name: channel, | ||
type: type(channel, model), | ||
}; | ||
scaleDef.domain = domain(model, channel, scaleDef.type); | ||
util_1.extend(scaleDef, rangeMixins(model, channel, scaleDef.type)); | ||
[ | ||
'reverse', 'round', | ||
'clamp', 'nice', | ||
'exponent', 'zero', | ||
'bandWidth', 'outerPadding', 'padding', 'points' | ||
].forEach(function (property) { | ||
var value = exports[property](model, channel, scaleDef.type); | ||
if (value !== undefined) { | ||
scaleDef[property] = value; | ||
} | ||
}); | ||
return (a.push(scaleDef), a); | ||
}, []); | ||
} | ||
exports.compileScales = compileScales; | ||
function type(channel, model) { | ||
var fieldDef = model.fieldDef(channel); | ||
switch (fieldDef.type) { | ||
case type_1.NOMINAL: | ||
return 'ordinal'; | ||
case type_1.ORDINAL: | ||
var range = fieldDef.scale.range; | ||
return channel === channel_1.COLOR && (typeof range !== 'string') ? 'linear' : 'ordinal'; | ||
case type_1.TEMPORAL: | ||
return time.scale.type(fieldDef.timeUnit, channel); | ||
case type_1.QUANTITATIVE: | ||
if (model.bin(channel)) { | ||
return channel === channel_1.ROW || channel === channel_1.COLUMN || channel === channel_1.SHAPE ? 'ordinal' : 'linear'; | ||
} | ||
return fieldDef.scale.type; | ||
} | ||
var zero = scale.zero(encoding, name); | ||
if (zero !== undefined) { | ||
scaleDef.zero = zero; | ||
} | ||
exports.type = type; | ||
function domain(model, channel, type) { | ||
var fieldDef = model.fieldDef(channel); | ||
if (fieldDef.scale.domain) { | ||
return fieldDef.scale.domain; | ||
} | ||
// TODO split scale.range into methods for each properties | ||
scaleDef = scale.range(scaleDef, encoding, layout, stats); | ||
return (a.push(scaleDef), a); | ||
}, []); | ||
}; | ||
scale.type = function(name, encoding) { | ||
switch (encoding.type(name)) { | ||
case N: //fall through | ||
case O: return 'ordinal'; | ||
case T: | ||
var timeUnit = encoding.encDef(name).timeUnit; | ||
return timeUnit ? time.scale.type(timeUnit, name) : 'time'; | ||
case Q: | ||
if (encoding.bin(name)) { | ||
return 'linear'; | ||
} | ||
return encoding.scale(name).type; | ||
} | ||
}; | ||
scale.domain = function (encoding, name, type, facet) { | ||
var encDef = encoding.encDef(name); | ||
// special case for temporal scale | ||
if (encoding.isType(name, T)) { | ||
var range = time.scale.domain(encDef.timeUnit, name); | ||
if (range) return range; | ||
} | ||
// For stack, use STACKED data. | ||
var stack = encoding.stack(); | ||
if (stack && name === stack.value) { | ||
return { | ||
data: STACKED, | ||
field: encoding.fieldRef(name, { | ||
// If faceted, scale is determined by the max of sum in each facet. | ||
prefn: (facet ? 'max_' : '') + 'sum_' | ||
}) | ||
}; | ||
} | ||
var useRawDomain = scale._useRawDomain(encoding, name); | ||
var sort = scale.sort(encoding, name, type); | ||
if (useRawDomain) { // useRawDomain - only Q/T | ||
return { | ||
data: RAW, | ||
field: encoding.fieldRef(name, {noAggregate:true}) | ||
}; | ||
} else if (encDef.bin) { // bin -- need to merge both bin_start and bin_end | ||
return { | ||
data: encoding.dataTable(), | ||
field: [ | ||
encoding.fieldRef(name, {bin_suffix:'_start'}), | ||
encoding.fieldRef(name, {bin_suffix:'_end'}) | ||
] | ||
}; | ||
} else if (sort) { // have sort -- only for ordinal | ||
return { | ||
// If sort by aggregation of a specified sort field, we need to use RAW table, | ||
// so we can aggregate values for the scale independently from the main aggregation. | ||
data: sort.op ? RAW : encoding.dataTable(), | ||
field: encoding.fieldRef(name), | ||
sort: sort | ||
}; | ||
} else { | ||
return { | ||
data: encoding.dataTable(), | ||
field: encoding.fieldRef(name) | ||
}; | ||
} | ||
}; | ||
scale.sort = function(encoding, name, type) { | ||
var sort = encoding.encDef(name).sort; | ||
if (sort === 'ascending' || sort === 'descending') { | ||
return true; | ||
} | ||
// Sorted based on an aggregate calculation over a specified sort field (only for ordinal scale) | ||
if (type === 'ordinal' && util.isObject(sort)) { | ||
return { | ||
op: sort.op, | ||
field: sort.field | ||
}; | ||
} | ||
return undefined; | ||
}; | ||
scale.reverse = function(encoding, name) { | ||
var sort = encoding.encDef(name).sort; | ||
return sort && (sort === 'descending' || (sort.order === 'descending')); | ||
}; | ||
/** | ||
* Determine if useRawDomain should be activated for this scale. | ||
* @return {Boolean} Returns true if all of the following conditons applies: | ||
* 1. `useRawDomain` is enabled either through scale or config | ||
* 2. Aggregation function is not `count` or `sum` | ||
* 3. The scale is quantitative or time scale. | ||
*/ | ||
scale._useRawDomain = function (encoding, name) { | ||
var encDef = encoding.encDef(name); | ||
// scale value | ||
var scaleUseRawDomain = encoding.scale(name).useRawDomain; | ||
// Determine if useRawDomain is enabled. If scale value is specified, use scale value. | ||
// Otherwise, use config value. | ||
var useRawDomainEnabled = scaleUseRawDomain !== undefined ? | ||
scaleUseRawDomain : encoding.config('useRawDomain'); | ||
var notCountOrSum = !encDef.aggregate || | ||
(encDef.aggregate !=='count' && encDef.aggregate !== 'sum'); | ||
return useRawDomainEnabled && | ||
notCountOrSum && ( | ||
// Q always uses quantitative scale except when it's binned and thus uses ordinal scale. | ||
( | ||
encoding.isType(name, Q) && | ||
!encDef.bin // TODO(#614): this must be changed once bin is reimplemented | ||
) || | ||
// TODO: revise this | ||
// T uses non-ordinal scale when there's no unit or when the unit is not ordinal. | ||
( | ||
encoding.isType(name, T) && | ||
(!encDef.timeUnit || !time.isOrdinalFn(encDef.timeUnit)) | ||
) | ||
); | ||
}; | ||
// FIXME revise if we should produce undefined for shorter spec (and just use vega's default value.) | ||
// However, let's ignore it for now as it is unclear what is Vega's default value. | ||
scale.zero = function(encoding, name) { | ||
var spec = encoding.scale(name); | ||
var encDef = encoding.encDef(name); | ||
var timeUnit = encDef.timeUnit; | ||
if (spec.zero) { | ||
return spec.zero; // return explicit value if defined | ||
} | ||
if (encoding.isType(name, T) && (!timeUnit || timeUnit === 'year')) { // FIXME revise this | ||
// Returns false (undefined) by default for time scale | ||
return false; | ||
} | ||
if (encDef.bin) { | ||
// Returns false (undefined) by default of bin | ||
return false; | ||
} | ||
// if not bin / temporal, returns true by default | ||
return name === X || name === Y || name === SIZE; | ||
}; | ||
scale.range = function (scaleDef, encoding, layout, stats) { | ||
var encDef = encoding.encDef(scaleDef.name); | ||
var timeUnit = encDef.timeUnit; | ||
switch (scaleDef.name) { | ||
case X: | ||
scaleDef.range = layout.cellWidth ? [0, layout.cellWidth] : 'width'; | ||
if (scaleDef.type === 'ordinal') { | ||
scaleDef.bandWidth = encoding.bandSize(X, layout.x.useSmallBand); | ||
} | ||
scaleDef.round = true; | ||
if (scaleDef.type === 'time') { | ||
scaleDef.nice = timeUnit || encoding.config('timeScaleNice'); | ||
}else { | ||
scaleDef.nice = true; | ||
} | ||
break; | ||
case Y: | ||
if (scaleDef.type === 'ordinal') { | ||
scaleDef.range = layout.cellHeight ? | ||
(encDef.bin ? [layout.cellHeight, 0] : [0, layout.cellHeight]) : | ||
'height'; | ||
scaleDef.bandWidth = encoding.bandSize(Y, layout.y.useSmallBand); | ||
} else { | ||
scaleDef.range = layout.cellHeight ? [layout.cellHeight, 0] : 'height'; | ||
} | ||
scaleDef.round = true; | ||
if (scaleDef.type === 'time') { | ||
scaleDef.nice = timeUnit || encoding.config('timeScaleNice'); | ||
}else { | ||
scaleDef.nice = true; | ||
} | ||
break; | ||
case ROW: // support only ordinal | ||
scaleDef.bandWidth = layout.cellHeight; | ||
scaleDef.round = true; | ||
scaleDef.nice = true; | ||
break; | ||
case COL: // support only ordinal | ||
scaleDef.bandWidth = layout.cellWidth; | ||
scaleDef.round = true; | ||
scaleDef.nice = true; | ||
break; | ||
case SIZE: | ||
if (encoding.is('bar')) { | ||
// FIXME this is definitely incorrect | ||
// but let's fix it later since bar size is a bad encoding anyway | ||
scaleDef.range = [3, Math.max(encoding.bandSize(X), encoding.bandSize(Y))]; | ||
} else if (encoding.is(TEXT)) { | ||
scaleDef.range = [8, 40]; | ||
} else { //point | ||
var bandSize = Math.min(encoding.bandSize(X), encoding.bandSize(Y)) - 1; | ||
scaleDef.range = [10, 0.8 * bandSize*bandSize]; | ||
} | ||
scaleDef.round = true; | ||
break; | ||
case SHAPE: | ||
scaleDef.range = 'shapes'; | ||
break; | ||
case COLOR: | ||
scaleDef.range = scale.color(scaleDef, encoding, stats); | ||
break; | ||
default: | ||
throw new Error('Unknown encoding name: '+ scaleDef.name); | ||
} | ||
// FIXME(kanitw): Jul 29, 2015 - consolidate this with above | ||
switch (scaleDef.name) { | ||
case ROW: | ||
case COL: | ||
scaleDef.padding = encoding.config('cellPadding'); | ||
scaleDef.outerPadding = 0; | ||
break; | ||
case X: | ||
case Y: | ||
if (scaleDef.type === 'ordinal') { //&& !s.bandWidth | ||
scaleDef.points = true; | ||
scaleDef.padding = encoding.encDef(scaleDef.name).band.padding; | ||
} | ||
} | ||
return scaleDef; | ||
}; | ||
scale.color = function(s, encoding, stats) { | ||
var colorScale = encoding.scale(COLOR), | ||
range = colorScale.range, | ||
cardinality = encoding.cardinality(COLOR, stats), | ||
type = encoding.type(COLOR); | ||
if (range === undefined) { | ||
var ordinalPalette = colorScale.ordinalPalette, | ||
quantitativeRange = colorScale.quantitativeRange; | ||
if (s.type === 'ordinal') { | ||
if (type === N) { | ||
// use categorical color scale | ||
if (cardinality <= 10) { | ||
range = colorScale.c10palette; | ||
} else { | ||
range = colorScale.c20palette; | ||
if (fieldDef.type === type_1.TEMPORAL) { | ||
var range = time.scale.domain(fieldDef.timeUnit, channel); | ||
if (range) | ||
return range; | ||
} | ||
var stack = model.stack(); | ||
if (stack && channel === stack.fieldChannel) { | ||
var facet = model.has(channel_1.ROW) || model.has(channel_1.COLUMN); | ||
return { | ||
data: data_1.STACKED, | ||
field: model.field(channel, { | ||
prefn: (facet ? 'max_' : '') + 'sum_' | ||
}) | ||
}; | ||
} | ||
var useRawDomain = _useRawDomain(model, channel); | ||
var sort = domainSort(model, channel, type); | ||
if (useRawDomain) { | ||
return { | ||
data: data_1.SOURCE, | ||
field: model.field(channel, { noAggregate: true }) | ||
}; | ||
} | ||
else if (fieldDef.bin) { | ||
return { | ||
data: model.dataTable(), | ||
field: type === 'ordinal' ? | ||
model.field(channel, { binSuffix: '_start' }) : | ||
[ | ||
model.field(channel, { binSuffix: '_start' }), | ||
model.field(channel, { binSuffix: '_end' }) | ||
] | ||
}; | ||
} | ||
else if (sort) { | ||
return { | ||
data: sort.op ? data_1.SOURCE : model.dataTable(), | ||
field: model.field(channel), | ||
sort: sort | ||
}; | ||
} | ||
else { | ||
return { | ||
data: model.dataTable(), | ||
field: model.field(channel) | ||
}; | ||
} | ||
} | ||
exports.domain = domain; | ||
function domainSort(model, channel, type) { | ||
var sort = model.fieldDef(channel).sort; | ||
if (sort === 'ascending' || sort === 'descending') { | ||
return true; | ||
} | ||
if (type === 'ordinal' && typeof sort !== 'string') { | ||
return { | ||
op: sort.op, | ||
field: sort.field | ||
}; | ||
} | ||
return undefined; | ||
} | ||
exports.domainSort = domainSort; | ||
function reverse(model, channel) { | ||
var sort = model.fieldDef(channel).sort; | ||
return sort && (typeof sort === 'string' ? | ||
sort === 'descending' : | ||
sort.order === 'descending') ? true : undefined; | ||
} | ||
exports.reverse = reverse; | ||
function _useRawDomain(model, channel) { | ||
var fieldDef = model.fieldDef(channel); | ||
return fieldDef.scale.useRawDomain && | ||
fieldDef.aggregate && | ||
aggregate_1.SHARED_DOMAIN_OPS.indexOf(fieldDef.aggregate) >= 0 && | ||
((fieldDef.type === type_1.QUANTITATIVE && !fieldDef.bin) || | ||
(fieldDef.type === type_1.TEMPORAL && | ||
(!fieldDef.timeUnit || time.scale.type(fieldDef.timeUnit, channel) === 'linear'))); | ||
} | ||
exports._useRawDomain = _useRawDomain; | ||
function bandWidth(model, channel, scaleType) { | ||
if (scaleType === 'ordinal') { | ||
return model.fieldDef(channel).scale.bandWidth; | ||
} | ||
return undefined; | ||
} | ||
exports.bandWidth = bandWidth; | ||
function clamp(model, channel) { | ||
return model.fieldDef(channel).scale.clamp; | ||
} | ||
exports.clamp = clamp; | ||
function exponent(model, channel) { | ||
return model.fieldDef(channel).scale.exponent; | ||
} | ||
exports.exponent = exponent; | ||
function nice(model, channel, scaleType) { | ||
if (model.fieldDef(channel).scale.nice !== undefined) { | ||
return model.fieldDef(channel).scale.nice; | ||
} | ||
switch (channel) { | ||
case channel_1.X: | ||
case channel_1.Y: | ||
if (scaleType === 'time' || scaleType === 'ordinal') { | ||
return undefined; | ||
} | ||
return true; | ||
case channel_1.ROW: | ||
case channel_1.COLUMN: | ||
return true; | ||
} | ||
return undefined; | ||
} | ||
exports.nice = nice; | ||
function outerPadding(model, channel, scaleType) { | ||
if (scaleType === 'ordinal') { | ||
if (model.fieldDef(channel).scale.outerPadding !== undefined) { | ||
return model.fieldDef(channel).scale.outerPadding; | ||
} | ||
return scale.color.palette(range, cardinality, type); | ||
} else { | ||
if (ordinalPalette) { | ||
return scale.color.palette(ordinalPalette, cardinality, type); | ||
} | ||
return undefined; | ||
} | ||
exports.outerPadding = outerPadding; | ||
function padding(model, channel, scaleType) { | ||
if (scaleType === 'ordinal') { | ||
return model.fieldDef(channel).scale.padding; | ||
} | ||
return undefined; | ||
} | ||
exports.padding = padding; | ||
function points(model, channel, scaleType) { | ||
if (scaleType === 'ordinal') { | ||
if (model.fieldDef(channel).scale.points !== undefined) { | ||
return model.fieldDef(channel).scale.points; | ||
} | ||
return scale.color.interpolate(quantitativeRange[0], quantitativeRange[1], cardinality); | ||
} | ||
} else { //time or quantitative | ||
return [quantitativeRange[0], quantitativeRange[1]]; | ||
switch (channel) { | ||
case channel_1.X: | ||
case channel_1.Y: | ||
return true; | ||
} | ||
} | ||
} | ||
}; | ||
scale.color.palette = function(range, cardinality, type) { | ||
// FIXME(kanitw): Jul 29, 2015 - check range is string | ||
switch (range) { | ||
case 'category10k': | ||
// tableau's category 10, ordered by perceptual kernel study results | ||
// https://github.com/uwdata/perceptual-kernels | ||
return ['#2ca02c', '#e377c2', '#7f7f7f', '#17becf', '#8c564b', '#d62728', '#bcbd22', '#9467bd', '#ff7f0e', '#1f77b4']; | ||
// d3/tableau category10/20/20b/20c | ||
case 'category10': | ||
return ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd', '#8c564b', '#e377c2', '#7f7f7f', '#bcbd22', '#17becf']; | ||
case 'category20': | ||
return ['#1f77b4', '#aec7e8', '#ff7f0e', '#ffbb78', '#2ca02c', '#98df8a', '#d62728', '#ff9896', '#9467bd', '#c5b0d5', '#8c564b', '#c49c94', '#e377c2', '#f7b6d2', '#7f7f7f', '#c7c7c7', '#bcbd22', '#dbdb8d', '#17becf', '#9edae5']; | ||
case 'category20b': | ||
return ['#393b79', '#5254a3', '#6b6ecf', '#9c9ede', '#637939', '#8ca252', '#b5cf6b', '#cedb9c', '#8c6d31', '#bd9e39', '#e7ba52', '#e7cb94', '#843c39', '#ad494a', '#d6616b', '#e7969c', '#7b4173', '#a55194', '#ce6dbd', '#de9ed6']; | ||
case 'category20c': | ||
return ['#3182bd', '#6baed6', '#9ecae1', '#c6dbef', '#e6550d', '#fd8d3c', '#fdae6b', '#fdd0a2', '#31a354', '#74c476', '#a1d99b', '#c7e9c0', '#756bb1', '#9e9ac8', '#bcbddc', '#dadaeb', '#636363', '#969696', '#bdbdbd', '#d9d9d9']; | ||
} | ||
// TODO add our own set of custom ordinal color palette | ||
if (range in colorbrewer) { | ||
var palette = colorbrewer[range]; | ||
// if cardinality pre-defined, use it. | ||
if (cardinality in palette) return palette[cardinality]; | ||
// if not, use the highest cardinality one for nominal | ||
if (type === N) { | ||
return palette[Math.max.apply(null, util.keys(palette))]; | ||
return undefined; | ||
} | ||
exports.points = points; | ||
function rangeMixins(model, channel, scaleType) { | ||
var fieldDef = model.fieldDef(channel); | ||
if (fieldDef.scale.range) { | ||
return { range: fieldDef.scale.range }; | ||
} | ||
// otherwise, interpolate | ||
var ps = cardinality < 3 ? 3 : Math.max.apply(null, util.keys(palette)), | ||
from = 0 , to = ps - 1; | ||
// FIXME add config for from / to | ||
return scale.color.interpolate(palette[ps][from], palette[ps][to], cardinality); | ||
} | ||
return range; | ||
}; | ||
scale.color.interpolate = function (start, end, cardinality) { | ||
var interpolator = interpolate(start, end); | ||
return util.range(cardinality).map(function(i) { return interpolator(i*1.0/(cardinality-1)); }); | ||
}; | ||
switch (channel) { | ||
case channel_1.X: | ||
return { rangeMin: 0, rangeMax: model.layout().cellWidth }; | ||
case channel_1.Y: | ||
if (scaleType === 'ordinal') { | ||
return { rangeMin: 0, rangeMax: model.layout().cellHeight }; | ||
} | ||
return { rangeMin: model.layout().cellHeight, rangeMax: 0 }; | ||
case channel_1.SIZE: | ||
if (model.is(mark_1.BAR)) { | ||
return { | ||
range: [3, Math.max(model.fieldDef(channel_1.X).scale.bandWidth, model.fieldDef(channel_1.Y).scale.bandWidth)] | ||
}; | ||
} | ||
else if (model.is(mark_1.TEXT)) { | ||
return { range: [8, 40] }; | ||
} | ||
var bandWidth = Math.min(model.fieldDef(channel_1.X).scale.bandWidth, model.fieldDef(channel_1.Y).scale.bandWidth) - 1; | ||
return { range: [10, 0.8 * bandWidth * bandWidth] }; | ||
case channel_1.SHAPE: | ||
return { range: 'shapes' }; | ||
case channel_1.COLOR: | ||
if (scaleType === 'ordinal') { | ||
return { range: 'category10' }; | ||
} | ||
else { | ||
return { range: ['#AFC6A3', '#09622A'] }; | ||
} | ||
case channel_1.ROW: | ||
return { range: 'height' }; | ||
case channel_1.COLUMN: | ||
return { range: 'width' }; | ||
} | ||
return {}; | ||
} | ||
exports.rangeMixins = rangeMixins; | ||
function round(model, channel) { | ||
if (model.fieldDef(channel).scale.round !== undefined) { | ||
return model.fieldDef(channel).scale.round; | ||
} | ||
switch (channel) { | ||
case channel_1.X: | ||
case channel_1.Y: | ||
case channel_1.ROW: | ||
case channel_1.COLUMN: | ||
case channel_1.SIZE: | ||
return true; | ||
} | ||
return undefined; | ||
} | ||
exports.round = round; | ||
function zero(model, channel) { | ||
var fieldDef = model.fieldDef(channel); | ||
var timeUnit = fieldDef.timeUnit; | ||
if (fieldDef.scale.zero !== undefined) { | ||
return fieldDef.scale.zero; | ||
} | ||
if (fieldDef.type === type_1.TEMPORAL) { | ||
if (timeUnit === 'year') { | ||
return false; | ||
} | ||
return undefined; | ||
} | ||
if (fieldDef.bin) { | ||
return false; | ||
} | ||
return channel === channel_1.X || channel === channel_1.Y ? | ||
undefined : | ||
false; | ||
} | ||
exports.zero = zero; | ||
//# sourceMappingURL=scale.js.map |
@@ -1,58 +0,40 @@ | ||
'use strict'; | ||
require('../globals'); | ||
module.exports = stacking; | ||
function stacking(encoding, mdef, stack) { | ||
var groupby = stack.groupby; | ||
var field = stack.value; | ||
var valName = encoding.fieldRef(field); | ||
var startField = valName + '_start'; | ||
var endField = valName + '_end'; | ||
var transforms = []; | ||
if (encoding.marktype() === 'area') { | ||
// Add impute transform to ensure we have all values for each series | ||
transforms.push({ | ||
type: 'impute', | ||
field: encoding.fieldRef(field), | ||
groupby: [encoding.fieldRef(stack.stack)], | ||
orderby: [encoding.fieldRef(groupby)], | ||
method: 'value', | ||
value: 0 | ||
}); | ||
} | ||
// add stack transform to mark | ||
var stackTransform = { | ||
type: 'stack', | ||
groupby: [encoding.fieldRef(groupby)], | ||
field: encoding.fieldRef(field), | ||
sortby: [(stack.properties.reverse ? '-' : '') + encoding.fieldRef(stack.stack)], | ||
output: {start: startField, end: endField} | ||
}; | ||
if (stack.properties.offset) { | ||
stackTransform.offset = stack.properties.offset; | ||
} | ||
transforms.push(stackTransform) | ||
mdef.from.transform = transforms; | ||
// TODO(#276): This is super hack-ish -- consolidate into modular mark properties? | ||
mdef.properties.update[field] = mdef.properties.enter[field] = { | ||
scale: field, | ||
field: startField | ||
}; | ||
mdef.properties.update[field + '2'] = mdef.properties.enter[field + '2'] = { | ||
scale: field, | ||
field: endField | ||
}; | ||
return field; //return stack encoding | ||
var util_1 = require('../util'); | ||
function imputeTransform(model) { | ||
var stack = model.stack(); | ||
return { | ||
type: 'impute', | ||
field: model.field(stack.fieldChannel), | ||
groupby: [model.field(stack.stackChannel)], | ||
orderby: [model.field(stack.groupbyChannel)], | ||
method: 'value', | ||
value: 0 | ||
}; | ||
} | ||
exports.imputeTransform = imputeTransform; | ||
function stackTransform(model) { | ||
var stack = model.stack(); | ||
var sortby = stack.config.sort === 'descending' ? | ||
'-' + model.field(stack.stackChannel) : | ||
stack.config.sort === 'ascending' ? | ||
model.field(stack.stackChannel) : | ||
util_1.isObject(stack.config.sort) ? | ||
stack.config.sort : | ||
'-' + model.field(stack.stackChannel); | ||
var valName = model.field(stack.fieldChannel); | ||
var transform = { | ||
type: 'stack', | ||
groupby: [model.field(stack.groupbyChannel)], | ||
field: model.field(stack.fieldChannel), | ||
sortby: sortby, | ||
output: { | ||
start: valName + '_start', | ||
end: valName + '_end' | ||
} | ||
}; | ||
if (stack.config.offset) { | ||
transform.offset = stack.config.offset; | ||
} | ||
return transform; | ||
} | ||
exports.stackTransform = stackTransform; | ||
//# sourceMappingURL=stack.js.map |
@@ -1,163 +0,77 @@ | ||
'use strict'; | ||
var util = require('../util'), | ||
d3_time_format = require('d3-time-format'); | ||
var time = module.exports = {}; | ||
// 'Wednesday September 17 04:00:00 2014' | ||
// Wednesday is the longest date | ||
// September is the longest month (8 in javascript as it is zero-indexed). | ||
var LONG_DATE = new Date(Date.UTC(2014, 8, 17)); | ||
time.cardinality = function(encDef, stats, filterNull, type) { | ||
var timeUnit = encDef.timeUnit; | ||
switch (timeUnit) { | ||
case 'seconds': return 60; | ||
case 'minutes': return 60; | ||
case 'hours': return 24; | ||
case 'day': return 7; | ||
case 'date': return 31; | ||
case 'month': return 12; | ||
case 'year': | ||
var stat = stats[encDef.name], | ||
yearstat = stats['year_' + encDef.name]; | ||
if (!yearstat) { return null; } | ||
return yearstat.distinct - | ||
(stat.missing > 0 && filterNull[type] ? 1 : 0); | ||
} | ||
return null; | ||
}; | ||
time.formula = function(timeUnit, fieldRef) { | ||
// TODO(kanitw): add formula to other time format | ||
var fn = 'utc' + timeUnit; | ||
return fn + '(' + fieldRef + ')'; | ||
}; | ||
time.maxLength = function(timeUnit, encoding) { | ||
switch (timeUnit) { | ||
case 'seconds': | ||
case 'minutes': | ||
case 'hours': | ||
case 'date': | ||
return 2; | ||
case 'month': | ||
case 'day': | ||
var range = time.range(timeUnit, encoding); | ||
if (range) { | ||
// return the longest name in the range | ||
return Math.max.apply(null, range.map(function(r) {return r.length;})); | ||
} | ||
return 2; | ||
case 'year': | ||
return 4; //'1998' | ||
} | ||
// TODO(#600) revise this | ||
// no time unit | ||
var timeFormat = encoding.config('timeFormat'); | ||
return d3_time_format.utcFormat(timeFormat)(LONG_DATE).length; | ||
}; | ||
time.range = function(timeUnit, encoding) { | ||
var labelLength = encoding.config('timeScaleLabelLength'), | ||
scaleLabel; | ||
switch (timeUnit) { | ||
case 'day': | ||
scaleLabel = encoding.config('dayScaleLabel'); | ||
break; | ||
case 'month': | ||
scaleLabel = encoding.config('monthScaleLabel'); | ||
break; | ||
} | ||
if (scaleLabel) { | ||
return labelLength ? scaleLabel.map( | ||
function(s) { return s.substr(0, labelLength);} | ||
) : scaleLabel; | ||
} | ||
return; | ||
}; | ||
/** | ||
* @param {Object} encoding | ||
* @return {Array} scales for time unit names | ||
*/ | ||
time.scales = function(encoding) { | ||
var scales = encoding.reduce(function(scales, encDef) { | ||
var timeUnit = encDef.timeUnit; | ||
if (encDef.type === T && timeUnit && !scales[timeUnit]) { | ||
var scale = time.scale.def(encDef.timeUnit, encoding); | ||
if (scale) scales[timeUnit] = scale; | ||
var util = require('../util'); | ||
var channel_1 = require('../channel'); | ||
function cardinality(fieldDef, stats, filterNull, type) { | ||
var timeUnit = fieldDef.timeUnit; | ||
switch (timeUnit) { | ||
case 'seconds': return 60; | ||
case 'minutes': return 60; | ||
case 'hours': return 24; | ||
case 'day': return 7; | ||
case 'date': return 31; | ||
case 'month': return 12; | ||
case 'year': | ||
var stat = stats[fieldDef.field], yearstat = stats['year_' + fieldDef.field]; | ||
if (!yearstat) { | ||
return null; | ||
} | ||
return yearstat.distinct - | ||
(stat.missing > 0 && filterNull[type] ? 1 : 0); | ||
} | ||
return scales; | ||
}, {}); | ||
return util.vals(scales); | ||
}; | ||
time.scale = {}; | ||
/** append custom time scales for axis label */ | ||
time.scale.def = function(timeUnit, encoding) { | ||
var range = time.range(timeUnit, encoding); | ||
if (range) { | ||
return { | ||
name: 'time-'+timeUnit, | ||
type: 'ordinal', | ||
domain: time.scale.domain(timeUnit), | ||
range: range | ||
}; | ||
} | ||
return null; | ||
}; | ||
time.isOrdinalFn = function(timeUnit) { | ||
switch (timeUnit) { | ||
case 'seconds': | ||
case 'minutes': | ||
case 'hours': | ||
case 'day': | ||
case 'date': | ||
case 'month': | ||
return true; | ||
} | ||
return false; | ||
}; | ||
time.scale.type = function(timeUnit, name) { | ||
if (name === COLOR) { | ||
return 'linear'; // time has order, so use interpolated ordinal color scale. | ||
} | ||
// FIXME revise this -- should 'year' be linear too? | ||
return time.isOrdinalFn(timeUnit) || name === COL || name === ROW ? 'ordinal' : 'linear'; | ||
}; | ||
time.scale.domain = function(timeUnit, name) { | ||
var isColor = name === COLOR; | ||
switch (timeUnit) { | ||
case 'seconds': | ||
case 'minutes': return isColor ? [0,59] : util.range(0, 60); | ||
case 'hours': return isColor ? [0,23] : util.range(0, 24); | ||
case 'day': return isColor ? [0,6] : util.range(0, 7); | ||
case 'date': return isColor ? [1,31] : util.range(1, 32); | ||
case 'month': return isColor ? [0,11] : util.range(0, 12); | ||
} | ||
return null; | ||
}; | ||
/** whether a particular time function has custom scale for labels implemented in time.scale */ | ||
time.hasScale = function(timeUnit) { | ||
switch (timeUnit) { | ||
case 'day': | ||
case 'month': | ||
return true; | ||
} | ||
return false; | ||
}; | ||
return null; | ||
} | ||
exports.cardinality = cardinality; | ||
function formula(timeUnit, field) { | ||
var fn = 'utc' + timeUnit; | ||
return fn + '(' + field + ')'; | ||
} | ||
exports.formula = formula; | ||
var scale; | ||
(function (scale) { | ||
function type(timeUnit, channel) { | ||
if (channel === channel_1.COLOR) { | ||
return 'linear'; | ||
} | ||
if (channel === channel_1.COLUMN || channel === channel_1.ROW) { | ||
return 'ordinal'; | ||
} | ||
switch (timeUnit) { | ||
case 'hours': | ||
case 'day': | ||
case 'date': | ||
case 'month': | ||
return 'ordinal'; | ||
case 'year': | ||
case 'second': | ||
case 'minute': | ||
return 'linear'; | ||
} | ||
return 'time'; | ||
} | ||
scale.type = type; | ||
function domain(timeUnit, channel) { | ||
var isColor = channel === channel_1.COLOR; | ||
switch (timeUnit) { | ||
case 'seconds': | ||
case 'minutes': return isColor ? [0, 59] : util.range(0, 60); | ||
case 'hours': return isColor ? [0, 23] : util.range(0, 24); | ||
case 'day': return isColor ? [0, 6] : util.range(0, 7); | ||
case 'date': return isColor ? [1, 31] : util.range(1, 32); | ||
case 'month': return isColor ? [0, 11] : util.range(0, 12); | ||
} | ||
return null; | ||
} | ||
scale.domain = domain; | ||
})(scale = exports.scale || (exports.scale = {})); | ||
function labelTemplate(timeUnit, abbreviated) { | ||
if (abbreviated === void 0) { abbreviated = false; } | ||
var postfix = abbreviated ? '-abbrev' : ''; | ||
switch (timeUnit) { | ||
case 'day': | ||
return 'day' + postfix; | ||
case 'month': | ||
return 'month' + postfix; | ||
} | ||
return null; | ||
} | ||
exports.labelTemplate = labelTemplate; | ||
//# sourceMappingURL=time.js.map |
@@ -1,30 +0,13 @@ | ||
'use strict'; | ||
require('./globals'); | ||
var stats = require('datalib/src/stats'); | ||
var vldata = module.exports = {}; | ||
/** Mapping from datalib's inferred type to Vega-lite's type */ | ||
vldata.types = { | ||
'boolean': N, | ||
'number': Q, | ||
'integer': Q, | ||
'date': T, | ||
'string': N | ||
var type_1 = require('./type'); | ||
exports.SUMMARY = 'summary'; | ||
exports.SOURCE = 'source'; | ||
exports.STACKED = 'stacked'; | ||
exports.LAYOUT = 'layout'; | ||
exports.types = { | ||
'boolean': type_1.NOMINAL, | ||
'number': type_1.QUANTITATIVE, | ||
'integer': type_1.QUANTITATIVE, | ||
'date': type_1.TEMPORAL, | ||
'string': type_1.NOMINAL | ||
}; | ||
vldata.stats = function(data) { | ||
var summary = stats.summary(data); | ||
return summary.reduce(function(s, profile) { | ||
s[profile.field] = profile; | ||
return s; | ||
}, { | ||
'*': { | ||
max: data.length, | ||
min: 0 | ||
} | ||
}); | ||
}; | ||
//# sourceMappingURL=data.js.map |
@@ -1,840 +0,32 @@ | ||
// Package of defining Vega-lite Specification's json schema | ||
'use strict'; | ||
require('../globals'); | ||
var schema = module.exports = {}, | ||
util = require('../util'), | ||
toMap = util.toMap, | ||
colorbrewer = require('colorbrewer'); | ||
var VALID_AGG_OPS = require('vega/src/transforms/Aggregate').VALID_OPS; | ||
// TODO(#620) refer to vega schema | ||
// var vgStackSchema = require('vega/src/transforms/Stack').schema; | ||
schema.util = require('./schemautil'); | ||
schema.marktype = { | ||
type: 'string', | ||
enum: ['point', 'tick', 'bar', 'line', 'area', 'circle', 'square', 'text'] | ||
}; | ||
schema.aggregate = { | ||
type: 'string', | ||
enum: VALID_AGG_OPS, | ||
supportedEnums: { | ||
Q: VALID_AGG_OPS, | ||
O: ['median','min','max'], | ||
N: [], | ||
T: ['mean', 'median', 'min', 'max'], | ||
'': ['count'] | ||
}, | ||
supportedTypes: toMap([Q, N, O, T, '']) | ||
}; | ||
schema.getSupportedRole = function(encType) { | ||
return schema.schema.properties.encoding.properties[encType].supportedRole; | ||
}; | ||
schema.timeUnits = ['year', 'month', 'day', 'date', 'hours', 'minutes', 'seconds']; | ||
schema.defaultTimeFn = 'month'; | ||
schema.timeUnit = { | ||
type: 'string', | ||
enum: schema.timeUnits, | ||
supportedTypes: toMap([T]) | ||
}; | ||
schema.scale_type = { | ||
type: 'string', | ||
// TODO(kanitw) read vega's schema here, add description | ||
enum: ['linear', 'log', 'pow', 'sqrt', 'quantile'], | ||
default: 'linear', | ||
supportedTypes: toMap([Q]) | ||
}; | ||
schema.field = { | ||
type: 'object', | ||
properties: { | ||
name: { | ||
type: 'string' | ||
} | ||
} | ||
}; | ||
var clone = util.duplicate; | ||
var merge = schema.util.merge; | ||
schema.MAXBINS_DEFAULT = 15; | ||
var bin = { | ||
type: ['boolean', 'object'], | ||
default: false, | ||
properties: { | ||
maxbins: { | ||
type: 'integer', | ||
default: schema.MAXBINS_DEFAULT, | ||
minimum: 2, | ||
description: 'Maximum number of bins.' | ||
} | ||
}, | ||
supportedTypes: toMap([Q]) // TODO: add O after finishing #81 | ||
}; | ||
var typicalField = merge(clone(schema.field), { | ||
type: 'object', | ||
properties: { | ||
type: { | ||
type: 'string', | ||
enum: [N, O, Q, T] | ||
}, | ||
aggregate: schema.aggregate, | ||
timeUnit: schema.timeUnit, | ||
bin: bin, | ||
scale: { | ||
type: 'object', | ||
properties: { | ||
/* Common Scale Properties */ | ||
type: schema.scale_type, | ||
/* Quantitative Scale Properties */ | ||
nice: { | ||
type: 'string', | ||
enum: ['second', 'minute', 'hour', 'day', 'week', 'month', 'year'], | ||
supportedTypes: toMap([T]) | ||
var schemaUtil = require('./schemautil'); | ||
var mark_schema_1 = require('./mark.schema'); | ||
var config_schema_1 = require('./config.schema'); | ||
var data_schema_1 = require('./data.schema'); | ||
var encoding_schema_1 = require('./encoding.schema'); | ||
var fielddef_schema_1 = require('./fielddef.schema'); | ||
exports.aggregate = fielddef_schema_1.aggregate; | ||
exports.util = schemaUtil; | ||
exports.schema = { | ||
$schema: 'http://json-schema.org/draft-04/schema#', | ||
description: 'Schema for Vega-lite specification', | ||
type: 'object', | ||
required: ['mark', 'encoding'], | ||
properties: { | ||
name: { | ||
type: 'string' | ||
}, | ||
zero: { | ||
type: 'boolean', | ||
description: 'Include zero', | ||
default: undefined, | ||
supportedTypes: toMap([Q, T]) | ||
description: { | ||
type: 'string' | ||
}, | ||
/* Vega-lite only Properties */ | ||
useRawDomain: { | ||
type: 'boolean', | ||
default: undefined, | ||
description: 'Use the raw data range as scale domain instead of ' + | ||
'aggregated data for aggregate axis. ' + | ||
'This option does not work with sum or count aggregate' + | ||
'as they might have a substantially larger scale range.' + | ||
'By default, use value from config.useRawDomain.' | ||
} | ||
} | ||
data: data_schema_1.data, | ||
mark: mark_schema_1.mark, | ||
encoding: encoding_schema_1.encoding, | ||
config: config_schema_1.config | ||
} | ||
} | ||
}); | ||
var onlyOrdinalField = merge(clone(schema.field), { | ||
type: 'object', | ||
supportedRole: { | ||
dimension: true | ||
}, | ||
properties: { | ||
type: { | ||
type: 'string', | ||
enum: [N, O, Q, T] // ordinal-only field supports Q when bin is applied and T when time unit is applied. | ||
}, | ||
timeUnit: schema.timeUnit, | ||
bin: bin, | ||
aggregate: { | ||
type: 'string', | ||
enum: ['count'], | ||
supportedTypes: toMap([N, O]) // FIXME this looks weird to me | ||
} | ||
} | ||
}); | ||
var axisMixin = { | ||
type: 'object', | ||
supportedMarktypes: {point: true, tick: true, bar: true, line: true, area: true, circle: true, square: true}, | ||
properties: { | ||
axis: { | ||
type: 'object', | ||
properties: { | ||
/* Vega Axis Properties */ | ||
format: { | ||
type: 'string', | ||
default: undefined, // auto | ||
description: 'The formatting pattern for axis labels. '+ | ||
'If not undefined, this will be determined by ' + | ||
'small/largeNumberFormat and the max value ' + | ||
'of the field.' | ||
}, | ||
grid: { | ||
type: 'boolean', | ||
default: undefined, | ||
description: 'A flag indicate if gridlines should be created in addition to ticks. If `grid` is unspecified, the default value is `true` for ROW and COL. For X and Y, the default value is `true` for quantitative and time fields and `false` otherwise.' | ||
}, | ||
layer: { | ||
type: 'string', | ||
default: 'back', | ||
description: 'A string indicating if the axis (and any gridlines) should be placed above or below the data marks. One of "front" (default) or "back".' | ||
}, | ||
orient: { | ||
type: 'string', | ||
default: undefined, | ||
enum: ['top', 'right', 'left', 'bottom'], | ||
description: 'The orientation of the axis. One of top, bottom, left or right. The orientation can be used to further specialize the axis type (e.g., a y axis oriented for the right edge of the chart).' | ||
}, | ||
ticks: { | ||
type: 'integer', | ||
default: 5, | ||
minimum: 0, | ||
description: 'A desired number of ticks, for axes visualizing quantitative scales. The resulting number may be different so that values are "nice" (multiples of 2, 5, 10) and lie within the underlying scale\'s range.' | ||
}, | ||
/* Vega Axis Properties that are automatically populated by Vega-lite */ | ||
title: { | ||
type: 'string', | ||
default: undefined, | ||
description: 'A title for the axis. (Shows field name and its function by default.)' | ||
}, | ||
/* Vega-lite only */ | ||
maxLabelLength: { | ||
type: 'integer', | ||
default: 25, | ||
minimum: 0, | ||
description: 'Truncate labels that are too long.' | ||
}, | ||
labelAngle: { | ||
type: 'integer', | ||
default: undefined, // auto | ||
minimum: 0, | ||
maximum: 360, | ||
description: 'Angle by which to rotate labels. Set to 0 to force horizontal.' | ||
}, | ||
titleMaxLength: { | ||
type: 'integer', | ||
default: undefined, | ||
minimum: 0, | ||
description: 'Max length for axis title if the title is automatically generated from the field\'s description' | ||
}, | ||
titleOffset: { | ||
type: 'integer', | ||
default: undefined, // auto | ||
description: 'A title offset value for the axis.' | ||
}, | ||
} | ||
} | ||
} | ||
}; | ||
var sortMixin = { | ||
type: 'object', | ||
properties: { | ||
sort: { | ||
default: 'ascending', | ||
supportedTypes: toMap([N, O]), | ||
oneOf: [ | ||
{ | ||
type: 'string', | ||
enum: ['ascending', 'descending', 'unsorted'] | ||
}, | ||
{ // sort by aggregation of another field | ||
type: 'object', | ||
required: ['field', 'op'], | ||
properties: { | ||
field: { | ||
type: 'string', | ||
description: 'The field name to aggregate over.' | ||
}, | ||
op: { | ||
type: 'string', | ||
enum: VALID_AGG_OPS, | ||
description: 'The field name to aggregate over.' | ||
}, | ||
order: { | ||
type: 'string', | ||
enum: ['ascending', 'descending'] | ||
} | ||
} | ||
} | ||
] | ||
} | ||
} | ||
}; | ||
var bandMixin = { | ||
type: 'object', | ||
properties: { | ||
band: { | ||
type: 'object', | ||
properties: { | ||
size: { | ||
type: 'integer', | ||
minimum: 0, | ||
default: undefined | ||
}, | ||
padding: { | ||
type: 'integer', | ||
minimum: 0, | ||
default: 1 | ||
} | ||
} | ||
} | ||
} | ||
}; | ||
var legendMixin = { | ||
type: 'object', | ||
properties: { | ||
legend: { | ||
type: 'object', | ||
description: 'Properties of a legend.', | ||
properties: { | ||
title: { | ||
type: 'string', | ||
default: undefined, | ||
description: 'A title for the legend. (Shows field name and its function by default.)' | ||
}, | ||
orient: { | ||
type: 'string', | ||
default: 'right', | ||
description: 'The orientation of the legend. One of "left" or "right". This determines how the legend is positioned within the scene. The default is "right".' | ||
} | ||
} | ||
} | ||
} | ||
}; | ||
var textMixin = { | ||
type: 'object', | ||
supportedMarktypes: {'text': true}, | ||
properties: { | ||
align: { | ||
type: 'string', | ||
default: 'right' | ||
}, | ||
baseline: { | ||
type: 'string', | ||
default: 'middle' | ||
}, | ||
color: { | ||
type: 'string', | ||
role: 'color', | ||
default: '#000000' | ||
}, | ||
margin: { | ||
type: 'integer', | ||
default: 4, | ||
minimum: 0 | ||
}, | ||
placeholder: { | ||
type: 'string', | ||
default: 'Abc' | ||
}, | ||
font: { | ||
type: 'object', | ||
properties: { | ||
weight: { | ||
type: 'string', | ||
enum: ['normal', 'bold'], | ||
default: 'normal' | ||
}, | ||
size: { | ||
type: 'integer', | ||
default: 10, | ||
minimum: 0 | ||
}, | ||
family: { | ||
type: 'string', | ||
default: 'Helvetica Neue' | ||
}, | ||
style: { | ||
type: 'string', | ||
default: 'normal', | ||
enum: ['normal', 'italic'] | ||
} | ||
} | ||
}, | ||
format: { | ||
type: 'string', | ||
default: undefined, // auto | ||
description: 'The formatting pattern for text value. '+ | ||
'If not undefined, this will be determined by ' + | ||
'small/largeNumberFormat and the max value ' + | ||
'of the field.' | ||
}, | ||
} | ||
}; | ||
var sizeMixin = { | ||
type: 'object', | ||
supportedMarktypes: {point: true, bar: true, circle: true, square: true, text: true}, | ||
properties: { | ||
value: { | ||
type: 'integer', | ||
default: 30, | ||
minimum: 0, | ||
description: 'Size of marks.' | ||
} | ||
} | ||
}; | ||
var colorMixin = { | ||
type: 'object', | ||
supportedMarktypes: {point: true, tick: true, bar: true, line: true, area: true, circle: true, square: true, 'text': true}, | ||
properties: { | ||
value: { | ||
type: 'string', | ||
role: 'color', | ||
default: '#4682b4', | ||
description: 'Color to be used for marks.' | ||
}, | ||
opacity: { | ||
type: 'number', | ||
default: undefined, // auto | ||
minimum: 0, | ||
maximum: 1 | ||
}, | ||
scale: { | ||
type: 'object', | ||
properties: { | ||
range: { | ||
type: ['string', 'array'], | ||
default: undefined, | ||
description: | ||
'Color palette, if undefined vega-lite will use data property' + | ||
'to pick one from c10palette, c20palette, or ordinalPalette.' | ||
//FIXME | ||
}, | ||
c10palette: { | ||
type: 'string', | ||
default: 'category10', | ||
enum: [ | ||
// Tableau | ||
'category10', 'category10k', | ||
// Color Brewer | ||
'Pastel1', 'Pastel2', 'Set1', 'Set2', 'Set3' | ||
] | ||
}, | ||
c20palette: { | ||
type: 'string', | ||
default: 'category20', | ||
enum: ['category20', 'category20b', 'category20c'] | ||
}, | ||
ordinalPalette: { | ||
type: 'string', | ||
default: undefined, | ||
description: 'Color palette to encode ordinal variables.', | ||
enum: util.keys(colorbrewer) | ||
}, | ||
quantitativeRange: { | ||
type: 'array', | ||
default: ['#AFC6A3', '#09622A'], // tableau greens | ||
// default: ['#ccece6', '#00441b'], // BuGn.9 [2-8] | ||
description: 'Color range to encode quantitative variables.', | ||
minItems: 2, | ||
maxItems: 2, | ||
items: { | ||
type: 'string', | ||
role: 'color' | ||
} | ||
} | ||
} | ||
} | ||
} | ||
}; | ||
var stackMixin = { | ||
type: 'object', | ||
properties: { | ||
stack: { | ||
type: ['boolean', 'object'], | ||
default: true, | ||
description: 'Enable stacking (for bar and area marks only).', | ||
properties: { | ||
reverse: { | ||
type: 'boolean', | ||
default: false, | ||
description: 'Whether to reverse the stack\'s sortby.' | ||
}, | ||
offset: { | ||
type: 'string', | ||
default: undefined, | ||
enum: ['zero', 'center', 'normalize'] | ||
// TODO(#620) refer to Vega spec once it doesn't throw error | ||
// enum: vgStackSchema.properties.offset.oneOf[0].enum | ||
} | ||
} | ||
} | ||
} | ||
}; | ||
var shapeMixin = { | ||
type: 'object', | ||
supportedMarktypes: {point: true, circle: true, square: true}, | ||
properties: { | ||
value: { | ||
type: 'string', | ||
enum: ['circle', 'square', 'cross', 'diamond', 'triangle-up', 'triangle-down'], | ||
default: 'circle', | ||
description: 'Mark to be used.' | ||
}, | ||
filled: { | ||
type: 'boolean', | ||
default: false, | ||
description: 'Whether the shape\'s color should be used as fill color instead of stroke color.' | ||
} | ||
} | ||
}; | ||
var detailMixin = { | ||
type: 'object', | ||
supportedMarktypes: {point: true, tick: true, line: true, circle: true, square: true} | ||
}; | ||
var rowMixin = { | ||
properties: { | ||
height: { | ||
type: 'number', | ||
minimum: 0, | ||
default: 150 | ||
} | ||
} | ||
}; | ||
var colMixin = { | ||
properties: { | ||
width: { | ||
type: 'number', | ||
minimum: 0, | ||
default: 150 | ||
}, | ||
axis: { | ||
properties: { | ||
maxLabelLength: { | ||
type: 'integer', | ||
default: 12, | ||
minimum: 0, | ||
description: 'Truncate labels that are too long.' | ||
} | ||
} | ||
} | ||
} | ||
}; | ||
var facetMixin = { | ||
type: 'object', | ||
supportedMarktypes: {point: true, tick: true, bar: true, line: true, area: true, circle: true, square: true, text: true}, | ||
properties: { | ||
padding: { | ||
type: 'number', | ||
minimum: 0, | ||
maximum: 1, | ||
default: 0.1 | ||
} | ||
} | ||
}; | ||
var requiredNameType = { | ||
required: ['name', 'type'] | ||
}; | ||
var multiRoleField = merge(clone(typicalField), { | ||
supportedRole: { | ||
measure: true, | ||
dimension: true | ||
} | ||
}); | ||
var quantitativeField = merge(clone(typicalField), { | ||
supportedRole: { | ||
measure: true, | ||
dimension: 'ordinal-only' // using size to encoding category lead to order interpretation | ||
} | ||
}); | ||
var onlyQuantitativeField = merge(clone(typicalField), { | ||
supportedRole: { | ||
measure: true | ||
} | ||
}); | ||
var x = merge(clone(multiRoleField), axisMixin, bandMixin, requiredNameType, sortMixin); | ||
var y = clone(x); | ||
var facet = merge(clone(onlyOrdinalField), requiredNameType, facetMixin, sortMixin); | ||
var row = merge(clone(facet), axisMixin, rowMixin); | ||
var col = merge(clone(facet), axisMixin, colMixin); | ||
var size = merge(clone(quantitativeField), legendMixin, sizeMixin, sortMixin); | ||
var color = merge(clone(multiRoleField), legendMixin, colorMixin, stackMixin, sortMixin); | ||
var shape = merge(clone(onlyOrdinalField), legendMixin, shapeMixin, sortMixin); | ||
var detail = merge(clone(onlyOrdinalField), detailMixin, stackMixin, sortMixin); | ||
// we only put aggregated measure in pivot table | ||
var text = merge(clone(onlyQuantitativeField), textMixin, sortMixin); | ||
// TODO add label | ||
var data = { | ||
type: 'object', | ||
properties: { | ||
// data source | ||
formatType: { | ||
type: 'string', | ||
enum: ['json', 'csv'], | ||
default: 'json' | ||
}, | ||
url: { | ||
type: 'string', | ||
default: undefined | ||
}, | ||
values: { | ||
type: 'array', | ||
default: undefined, | ||
description: 'Pass array of objects instead of a url to a file.', | ||
items: { | ||
type: 'object', | ||
additionalProperties: true | ||
} | ||
}, | ||
// we generate a vega filter transform | ||
filter: { | ||
type: 'string', | ||
default: undefined, | ||
description: 'A string containing the filter Vega expression. Use `datum` to refer to the current data object.' | ||
}, | ||
// we generate a vega formula transform | ||
formulas: { | ||
type: 'array', | ||
default: undefined, | ||
description: 'Array of formula transforms. Formulas are applied before filter.', | ||
items: { | ||
type: 'object', | ||
properties: { | ||
field: { | ||
type: 'string', | ||
description: 'The property name in which to store the computed formula value.' | ||
}, | ||
expr: { | ||
type: 'string', | ||
description: 'A string containing an expression for the formula. Use the variable `datum` to to refer to the current data object.' | ||
} | ||
} | ||
} | ||
} | ||
} | ||
}; | ||
var config = { | ||
type: 'object', | ||
properties: { | ||
// template | ||
width: { | ||
type: 'integer', | ||
default: undefined | ||
}, | ||
height: { | ||
type: 'integer', | ||
default: undefined | ||
}, | ||
viewport: { | ||
type: 'array', | ||
items: { | ||
type: 'integer' | ||
}, | ||
default: undefined | ||
}, | ||
gridColor: { | ||
type: 'string', | ||
role: 'color', | ||
default: '#000000' | ||
}, | ||
gridOpacity: { | ||
type: 'number', | ||
minimum: 0, | ||
maximum: 1, | ||
default: 0.08 | ||
}, | ||
// filter null | ||
// TODO(#597) revise this config | ||
filterNull: { | ||
type: 'object', | ||
properties: { | ||
N: {type:'boolean', default: false}, | ||
O: {type:'boolean', default: false}, | ||
Q: {type:'boolean', default: true}, | ||
T: {type:'boolean', default: true} | ||
} | ||
}, | ||
autoSortLine: { | ||
type: 'boolean', | ||
default: true | ||
}, | ||
// single plot | ||
singleHeight: { | ||
// will be overwritten by bandWidth * (cardinality + padding) | ||
type: 'integer', | ||
default: 200, | ||
minimum: 0 | ||
}, | ||
singleWidth: { | ||
// will be overwritten by bandWidth * (cardinality + padding) | ||
type: 'integer', | ||
default: 200, | ||
minimum: 0 | ||
}, | ||
// band size | ||
largeBandSize: { | ||
type: 'integer', | ||
default: 21, | ||
minimum: 0 | ||
}, | ||
smallBandSize: { | ||
//small multiples or single plot with high cardinality | ||
type: 'integer', | ||
default: 12, | ||
minimum: 0 | ||
}, | ||
largeBandMaxCardinality: { | ||
type: 'integer', | ||
default: 10 | ||
}, | ||
// small multiples | ||
cellPadding: { | ||
type: 'number', | ||
default: 0.1 | ||
}, | ||
cellGridColor: { | ||
type: 'string', | ||
role: 'color', | ||
default: '#000000' | ||
}, | ||
cellGridOpacity: { | ||
type: 'number', | ||
minimum: 0, | ||
maximum: 1, | ||
default: 0.25 | ||
}, | ||
cellGridOffset: { | ||
type: 'number', | ||
default: 6 // equal to tickSize | ||
}, | ||
cellBackgroundColor: { | ||
type: 'string', | ||
role: 'color', | ||
default: 'rgba(0,0,0,0)' | ||
}, | ||
textCellWidth: { | ||
type: 'integer', | ||
default: 90, | ||
minimum: 0 | ||
}, | ||
// marks | ||
strokeWidth: { | ||
type: 'integer', | ||
default: 2, | ||
minimum: 0 | ||
}, | ||
singleBarOffset: { | ||
type: 'integer', | ||
default: 5, | ||
minimum: 0 | ||
}, | ||
// scales | ||
timeScaleLabelLength: { | ||
type: 'integer', | ||
default: 3, | ||
minimum: 0, | ||
description: 'Max length for values in dayScaleLabel and monthScaleLabel. Zero means using full names in dayScaleLabel/monthScaleLabel.' | ||
}, | ||
dayScaleLabel: { | ||
type: 'array', | ||
items: { | ||
type: 'string' | ||
}, | ||
default: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], | ||
description: 'Axis labels for day of week, starting from Sunday.' + | ||
'(Consistent with Javascript -- See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getDay.' | ||
}, | ||
monthScaleLabel: { | ||
type: 'array', | ||
items: { | ||
type: 'string' | ||
}, | ||
default: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], | ||
description: 'Axis labels for month.' | ||
}, | ||
// other | ||
characterWidth: { | ||
type: 'integer', | ||
default: 6 | ||
}, | ||
maxSmallNumber: { | ||
type: 'number', | ||
default: 10000, | ||
description: 'maximum number that a field will be considered smallNumber.'+ | ||
'Used for axis labelling.' | ||
}, | ||
smallNumberFormat: { | ||
type: 'string', | ||
default: '', | ||
description: 'D3 Number format for axis labels and text tables '+ | ||
'for number <= maxSmallNumber. Used for axis labelling.' | ||
}, | ||
largeNumberFormat: { | ||
type: 'string', | ||
default: '.3s', | ||
description: 'D3 Number format for axis labels and text tables ' + | ||
'for number > maxSmallNumber.' | ||
}, | ||
timeFormat: { | ||
type: 'string', | ||
default: '%Y-%m-%d', | ||
description: 'Date format for axis labels.' | ||
}, | ||
useRawDomain: { | ||
type: 'boolean', | ||
default: false, | ||
description: 'Use the raw data range as scale domain instead of ' + | ||
'aggregated data for aggregate axis. ' + | ||
'This option does not work with sum or count aggregate' + | ||
'as they might have a substantially larger scale range.' + | ||
'By default, use value from config.useRawDomain.' | ||
} | ||
} | ||
}; | ||
/** @type Object Schema of a vega-lite specification */ | ||
schema.schema = { | ||
$schema: 'http://json-schema.org/draft-04/schema#', | ||
description: 'Schema for Vega-lite specification', | ||
type: 'object', | ||
required: ['marktype', 'encoding', 'data'], | ||
properties: { | ||
data: data, | ||
marktype: schema.marktype, | ||
encoding: { | ||
type: 'object', | ||
properties: { | ||
x: x, | ||
y: y, | ||
row: row, | ||
col: col, | ||
size: size, | ||
color: color, | ||
shape: shape, | ||
text: text, | ||
detail: detail | ||
} | ||
}, | ||
config: config | ||
} | ||
}; | ||
schema.encTypes = util.keys(schema.schema.properties.encoding.properties); | ||
/** Instantiate a verbose vl spec from the schema */ | ||
schema.instantiate = function() { | ||
return schema.util.instantiate(schema.schema); | ||
}; | ||
function instantiate() { | ||
return schemaUtil.instantiate(exports.schema); | ||
} | ||
exports.instantiate = instantiate; | ||
; | ||
//# sourceMappingURL=schema.js.map |
@@ -1,6 +0,3 @@ | ||
'use strict'; | ||
var schema = require('./schema').schema, | ||
json3 = require('../../lib/json3-compactstringify.js'); | ||
process.stdout.write(json3.stringify(schema, null, 1, 80) + '\n'); | ||
var schema_1 = require('./schema'); | ||
process.stdout.write(JSON.stringify(schema_1.schema, null, 4) + '\n'); | ||
//# sourceMappingURL=schemagen.js.map |
@@ -1,87 +0,110 @@ | ||
'use strict'; | ||
var schemautil = module.exports = {}, | ||
util = require('../util'); | ||
var isEmpty = function(obj) { | ||
return Object.keys(obj).length === 0; | ||
}; | ||
schemautil.extend = function(instance, schema) { | ||
return schemautil.merge(schemautil.instantiate(schema), instance); | ||
}; | ||
// instantiate a schema | ||
schemautil.instantiate = function(schema) { | ||
var val; | ||
if (schema === undefined) { | ||
var util = require('../util'); | ||
function isEmpty(obj) { | ||
return Object.keys(obj).length === 0; | ||
} | ||
; | ||
function extend(instance, schema) { | ||
return merge(instantiate(schema), instance); | ||
} | ||
exports.extend = extend; | ||
; | ||
function instantiate(schema) { | ||
var val; | ||
if (schema === undefined) { | ||
return undefined; | ||
} | ||
else if ('default' in schema) { | ||
val = schema.default; | ||
return util.isObject(val) ? util.duplicate(val) : val; | ||
} | ||
else if (schema.type === 'object') { | ||
var instance = {}; | ||
for (var name in schema.properties) { | ||
val = instantiate(schema.properties[name]); | ||
if (val !== undefined) { | ||
instance[name] = val; | ||
} | ||
} | ||
return instance; | ||
} | ||
else if (schema.type === 'array') { | ||
return undefined; | ||
} | ||
return undefined; | ||
} else if ('default' in schema) { | ||
val = schema.default; | ||
return util.isObject(val) ? util.duplicate(val) : val; | ||
} else if (schema.type === 'object') { | ||
var instance = {}; | ||
for (var name in schema.properties) { | ||
val = schemautil.instantiate(schema.properties[name]); | ||
if (val !== undefined) { | ||
instance[name] = val; | ||
} | ||
} | ||
exports.instantiate = instantiate; | ||
; | ||
function subtract(instance, defaults) { | ||
var changes = {}; | ||
for (var prop in instance) { | ||
var def = defaults[prop]; | ||
var ins = instance[prop]; | ||
if (!defaults || def !== ins) { | ||
if (typeof ins === 'object' && !util.isArray(ins) && def) { | ||
var c = subtract(ins, def); | ||
if (!isEmpty(c)) { | ||
changes[prop] = c; | ||
} | ||
} | ||
else if (util.isArray(ins)) { | ||
if (util.isArray(def)) { | ||
if (ins.length === def.length) { | ||
var equal = true; | ||
for (var i = 0; i < ins.length; i++) { | ||
if (ins[i] !== def[i]) { | ||
equal = false; | ||
break; | ||
} | ||
} | ||
if (equal) { | ||
continue; | ||
} | ||
} | ||
} | ||
changes[prop] = ins; | ||
} | ||
else { | ||
changes[prop] = ins; | ||
} | ||
} | ||
} | ||
return instance; | ||
} else if (schema.type === 'array') { | ||
return []; | ||
} | ||
return undefined; | ||
}; | ||
// remove all defaults from an instance | ||
schemautil.subtract = function(instance, defaults) { | ||
var changes = {}; | ||
for (var prop in instance) { | ||
var def = defaults[prop]; | ||
var ins = instance[prop]; | ||
// Note: does not properly subtract arrays | ||
if (!defaults || def !== ins) { | ||
if (typeof ins === 'object' && !util.isArray(ins) && def) { | ||
var c = schemautil.subtract(ins, def); | ||
if (!isEmpty(c)) | ||
changes[prop] = c; | ||
} else if (!util.isArray(ins) || ins.length > 0) { | ||
changes[prop] = ins; | ||
} | ||
return changes; | ||
} | ||
exports.subtract = subtract; | ||
; | ||
function merge(dest) { | ||
var src = []; | ||
for (var _i = 1; _i < arguments.length; _i++) { | ||
src[_i - 1] = arguments[_i]; | ||
} | ||
} | ||
return changes; | ||
}; | ||
schemautil.merge = function(/*dest*, src0, src1, ...*/){ | ||
var dest = arguments[0]; | ||
for (var i=1 ; i<arguments.length; i++) { | ||
dest = merge(dest, arguments[i]); | ||
} | ||
return dest; | ||
}; | ||
// recursively merges src into dest | ||
function merge(dest, src) { | ||
if (typeof src !== 'object' || src === null) { | ||
for (var i = 0; i < src.length; i++) { | ||
dest = merge_(dest, src[i]); | ||
} | ||
return dest; | ||
} | ||
for (var p in src) { | ||
if (!src.hasOwnProperty(p)) { | ||
continue; | ||
} | ||
exports.merge = merge; | ||
; | ||
function merge_(dest, src) { | ||
if (typeof src !== 'object' || src === null) { | ||
return dest; | ||
} | ||
if (src[p] === undefined) { | ||
continue; | ||
for (var p in src) { | ||
if (!src.hasOwnProperty(p)) { | ||
continue; | ||
} | ||
if (src[p] === undefined) { | ||
continue; | ||
} | ||
if (typeof src[p] !== 'object' || src[p] === null) { | ||
dest[p] = src[p]; | ||
} | ||
else if (typeof dest[p] !== 'object' || dest[p] === null) { | ||
dest[p] = merge(src[p].constructor === Array ? [] : {}, src[p]); | ||
} | ||
else { | ||
merge(dest[p], src[p]); | ||
} | ||
} | ||
if (typeof src[p] !== 'object' || src[p] === null) { | ||
dest[p] = src[p]; | ||
} else if (typeof dest[p] !== 'object' || dest[p] === null) { | ||
dest[p] = merge(src[p].constructor === Array ? [] : {}, src[p]); | ||
} else { | ||
merge(dest[p], src[p]); | ||
} | ||
} | ||
return dest; | ||
} | ||
return dest; | ||
} | ||
//# sourceMappingURL=schemautil.js.map |
170
src/util.js
@@ -1,102 +0,78 @@ | ||
'use strict'; | ||
var util = module.exports = require('datalib/src/util'); | ||
util.extend(util, require('datalib/src/generate')); | ||
util.extend(util, require('datalib/src/stats')); | ||
util.extend(util, require('./logger')('[VL Error]')); | ||
util.bin = require('datalib/src/bins/bins'); | ||
util.isin = function(item, array) { | ||
return array.indexOf(item) !== -1; | ||
}; | ||
util.forEach = function(obj, f, thisArg) { | ||
if (obj.forEach) { | ||
obj.forEach.call(thisArg, f); | ||
} else { | ||
for (var k in obj) { | ||
f.call(thisArg, obj[k], k , obj); | ||
function __export(m) { | ||
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; | ||
} | ||
__export(require('datalib/src/util')); | ||
__export(require('datalib/src/generate')); | ||
__export(require('datalib/src/stats')); | ||
function contains(array, item) { | ||
return array.indexOf(item) > -1; | ||
} | ||
exports.contains = contains; | ||
function forEach(obj, f, thisArg) { | ||
if (obj.forEach) { | ||
obj.forEach.call(thisArg, f); | ||
} | ||
} | ||
}; | ||
util.reduce = function(obj, f, init, thisArg) { | ||
if (obj.reduce) { | ||
return obj.reduce.call(thisArg, f, init); | ||
} else { | ||
for (var k in obj) { | ||
init = f.call(thisArg, init, obj[k], k, obj); | ||
else { | ||
for (var k in obj) { | ||
f.call(thisArg, obj[k], k, obj); | ||
} | ||
} | ||
return init; | ||
} | ||
}; | ||
util.map = function(obj, f, thisArg) { | ||
if (obj.map) { | ||
return obj.map.call(thisArg, f); | ||
} else { | ||
var output = []; | ||
for (var k in obj) { | ||
output.push( f.call(thisArg, obj[k], k, obj)); | ||
} | ||
exports.forEach = forEach; | ||
function reduce(obj, f, init, thisArg) { | ||
if (obj.reduce) { | ||
return obj.reduce.call(thisArg, f, init); | ||
} | ||
} | ||
}; | ||
util.any = function(arr, f) { | ||
var i = 0, k; | ||
for (k in arr) { | ||
if (f(arr[k], k, i++)) return true; | ||
} | ||
return false; | ||
}; | ||
util.all = function(arr, f) { | ||
var i = 0, k; | ||
for (k in arr) { | ||
if (!f(arr[k], k, i++)) return false; | ||
} | ||
return true; | ||
}; | ||
util.getbins = function(stats, maxbins) { | ||
return util.bin({ | ||
min: stats.min, | ||
max: stats.max, | ||
maxbins: maxbins | ||
}); | ||
}; | ||
/** | ||
* x[p[0]]...[p[n]] = val | ||
* @param noaugment determine whether new object should be added f | ||
* or non-existing properties along the path | ||
*/ | ||
util.setter = function(x, p, val, noaugment) { | ||
for (var i=0; i<p.length-1; ++i) { | ||
if (!noaugment && !(p[i] in x)){ | ||
x = x[p[i]] = {}; | ||
} else { | ||
x = x[p[i]]; | ||
else { | ||
for (var k in obj) { | ||
init = f.call(thisArg, init, obj[k], k, obj); | ||
} | ||
return init; | ||
} | ||
} | ||
x[p[i]] = val; | ||
}; | ||
/** | ||
* returns x[p[0]]...[p[n]] | ||
* @param augment determine whether new object should be added f | ||
* or non-existing properties along the path | ||
*/ | ||
util.getter = function(x, p, noaugment) { | ||
for (var i=0; i<p.length; ++i) { | ||
if (!noaugment && !(p[i] in x)){ | ||
x = x[p[i]] = {}; | ||
} else { | ||
x = x[p[i]]; | ||
} | ||
exports.reduce = reduce; | ||
function map(obj, f, thisArg) { | ||
if (obj.map) { | ||
return obj.map.call(thisArg, f); | ||
} | ||
} | ||
return x; | ||
}; | ||
else { | ||
var output = []; | ||
for (var k in obj) { | ||
output.push(f.call(thisArg, obj[k], k, obj)); | ||
} | ||
return output; | ||
} | ||
} | ||
exports.map = map; | ||
function any(arr, f) { | ||
var i = 0, k; | ||
for (k in arr) { | ||
if (f(arr[k], k, i++)) | ||
return true; | ||
} | ||
return false; | ||
} | ||
exports.any = any; | ||
function all(arr, f) { | ||
var i = 0, k; | ||
for (k in arr) { | ||
if (!f(arr[k], k, i++)) | ||
return false; | ||
} | ||
return true; | ||
} | ||
exports.all = all; | ||
var dlBin = require('datalib/src/bins/bins'); | ||
function getbins(stats, maxbins) { | ||
return dlBin({ | ||
min: stats.min, | ||
max: stats.max, | ||
maxbins: maxbins | ||
}); | ||
} | ||
exports.getbins = getbins; | ||
function error(message) { | ||
console.error('[VL Error]', message); | ||
} | ||
exports.error = error; | ||
//# sourceMappingURL=util.js.map |
@@ -1,22 +0,29 @@ | ||
'use strict'; | ||
require('./globals'); | ||
var util = require('./util'), | ||
consts = require('./consts'); | ||
var vl = {}; | ||
util.extend(vl, consts, util); | ||
vl.Encoding = require('./Encoding'); | ||
vl.compiler = require('./compiler/compiler'); | ||
vl.compile = vl.compiler.compile; | ||
vl.data = require('./data'); | ||
vl.enc = require('./enc'); | ||
vl.encDef = require('./encdef'); | ||
vl.schema = require('./schema/schema'); | ||
vl.toShorthand = vl.Encoding.shorthand; | ||
vl.format = require('d3-format').format; | ||
module.exports = vl; | ||
var vlBin = require('./bin'); | ||
var vlChannel = require('./channel'); | ||
var vlData = require('./data'); | ||
var vlEncoding = require('./encoding'); | ||
var vlFieldDef = require('./fielddef'); | ||
var vlCompiler = require('./compiler/compiler'); | ||
var vlSchema = require('./schema/schema'); | ||
var vlShorthand = require('./shorthand'); | ||
var vlSpec = require('./spec'); | ||
var vlTimeUnit = require('./timeunit'); | ||
var vlType = require('./type'); | ||
var vlValidate = require('./validate'); | ||
var vlUtil = require('./util'); | ||
exports.bin = vlBin; | ||
exports.channel = vlChannel; | ||
exports.compiler = vlCompiler; | ||
exports.compile = vlCompiler.compile; | ||
exports.data = vlData; | ||
exports.encoding = vlEncoding; | ||
exports.fieldDef = vlFieldDef; | ||
exports.schema = vlSchema; | ||
exports.shorthand = vlShorthand; | ||
exports.spec = vlSpec; | ||
exports.timeUnit = vlTimeUnit; | ||
exports.type = vlType; | ||
exports.util = vlUtil; | ||
exports.validate = vlValidate; | ||
exports.version = '__VERSION__'; | ||
//# sourceMappingURL=vl.js.map |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
2451856
15
249
17554
117
4
2
+ Addedd3-color@0.3.4(transitive)
+ Addedd3-time@0.0.7(transitive)
+ Addedd3-time-format@0.2.0(transitive)
- Removedd3-color@0.2.8(transitive)
- Removedd3-format@0.3.6(transitive)
- Removedd3-time-format@0.1.30.2.1(transitive)
Updatedd3-color@^0.3.1
Updatedd3-format@^0.4.0
Updatedd3-time-format@0.2.0
Updateddatalib@^1.4.12
Updatedvega@^2.4.1
Updatedyargs@^3.30.0