vega-lite
Advanced tools
Comparing version 0.7.7 to 0.7.8
{ | ||
"name": "vega-lite", | ||
"main": "vega-lite.js", | ||
"version": "0.7.7", | ||
"version": "0.7.8", | ||
"homepage": "https://github.com/uwdata/vega-lite", | ||
@@ -6,0 +6,0 @@ "authors": [ |
@@ -158,6 +158,3 @@ 'use strict'; | ||
if (err) return alert('Error loading data ' + err.statusText); | ||
dataset.stats = vl.summary(data).reduce(function(s, p) { | ||
s[p.field] = p; | ||
return s; | ||
},{}); | ||
dataset.stats = vl.data.stats(data); | ||
callback(); | ||
@@ -164,0 +161,0 @@ }); |
@@ -8,3 +8,3 @@ 'use strict'; | ||
// runs the tests | ||
gulp.task('test', ['jshint'], function() { | ||
gulp.task('test', function() { | ||
return gulp.src(['test/**/*.spec.js'], { read: false }) | ||
@@ -15,2 +15,11 @@ .pipe(mocha({ | ||
.on('error', gutil.log); | ||
}); | ||
// quick test | ||
gulp.task('t', function() { | ||
return gulp.src(['test/**/*.spec.js'], { read: false }) | ||
.pipe(mocha({ | ||
istanbul: false | ||
})) | ||
.on('error', gutil.log); | ||
}); |
@@ -12,3 +12,3 @@ 'use strict'; | ||
gulp.task('watch-test', function() { | ||
gulp.watch(['src/**', 'test/**'], ['test']); | ||
gulp.watch(['src/**', 'test/**'], ['jshint', 'test']); | ||
}); |
{ | ||
"name": "vega-lite", | ||
"author": "Jeffrey Heer, Dominik Moritz, Kanit \"Ham\" Wongsuphasawat", | ||
"version": "0.7.7", | ||
"version": "0.7.8", | ||
"collaborators": [ | ||
@@ -16,3 +16,5 @@ "Kanit Wongsuphasawat <kanitw@gmail.com> (http://kanitw.yellowpigz.com)", | ||
"scripts": { | ||
"test": "gulp test" | ||
"deploy": "npm run lint && npm run test && scripts/deploy.sh", | ||
"lint": "gulp jshint", | ||
"test": "gulp t" | ||
}, | ||
@@ -29,30 +31,30 @@ "repository": { | ||
"devDependencies": { | ||
"browser-sync": "^1.9.1", | ||
"browserify": "^8.1.1", | ||
"browserify-shim": "^3.8.5", | ||
"chai": "^1.10.0", | ||
"commander": "^2.6.0", | ||
"browser-sync": "^2.7.13", | ||
"browserify": "^10.2.4", | ||
"browserify-shim": "^3.8.9", | ||
"chai": "^3.0.0", | ||
"commander": "^2.8.1", | ||
"coveralls": "^2.11.2", | ||
"d3": "^3.5.5", | ||
"deep-diff": "^0.3.0", | ||
"gulp": "^3.8.10", | ||
"gulp-bump": "^0.3.0", | ||
"gulp-git": "^1.2.3", | ||
"gulp-jshint": "^1.10.0", | ||
"gulp-load-plugins": "^0.10.0", | ||
"gulp-rename": "^1.2.0", | ||
"gulp-run": "^1.6.6", | ||
"gulp-sourcemaps": "^1.3.0", | ||
"gulp-spawn-mocha": "^2.0.1", | ||
"d3": "^3.5.6", | ||
"deep-diff": "^0.3.2", | ||
"gulp": "^3.9.0", | ||
"gulp-bump": "^0.3.1", | ||
"gulp-git": "^1.2.4", | ||
"gulp-jshint": "^1.11.1", | ||
"gulp-load-plugins": "^1.0.0-rc", | ||
"gulp-rename": "^1.2.2", | ||
"gulp-run": "^1.6.8", | ||
"gulp-sourcemaps": "^1.5.2", | ||
"gulp-spawn-mocha": "^2.2.1", | ||
"gulp-tag-version": "^1.2.1", | ||
"gulp-uglify": "^1.1.0", | ||
"gulp-util": "^3.0.2", | ||
"jshint-stylish": "^1.0.2", | ||
"lodash": "^2.4.1", | ||
"mocha": "^2.1.0", | ||
"gulp-uglify": "^1.2.0", | ||
"gulp-util": "^3.0.6", | ||
"jshint-stylish": "^2.0.1", | ||
"lodash": "^3.10.0", | ||
"mocha": "^2.2.5", | ||
"require-dir": "^0.3.0", | ||
"vinyl-buffer": "^1.0.0", | ||
"vinyl-source-stream": "^1.0.0", | ||
"watchify": "^2.2.1", | ||
"z-schema": "^3.4.3" | ||
"vinyl-source-stream": "^1.1.0", | ||
"watchify": "^3.2.3", | ||
"z-schema": "^3.12.0" | ||
}, | ||
@@ -62,3 +64,5 @@ "dependencies": { | ||
"d3-color": "^0.2.1", | ||
"datalib": "^1.1.12" | ||
"d3-format": "^0.2.3", | ||
"d3-time-format": "0.0.2", | ||
"datalib": "^1.3.0" | ||
}, | ||
@@ -65,0 +69,0 @@ "browserify": { |
@@ -25,3 +25,3 @@ 'use strict'; | ||
} else { | ||
dims[field.name] = encoding.field(encType); | ||
dims[field.name] = encoding.fieldRef(encType); | ||
if (encType == ROW || encType == COL) { | ||
@@ -28,0 +28,0 @@ facets[field.name] = dims[field.name]; |
@@ -28,23 +28,68 @@ 'use strict'; | ||
axis.def = function(name, encoding, layout, stats, opt) { | ||
var type = name; | ||
var isCol = name == COL, isRow = name == ROW; | ||
var rowOffset = axisTitleOffset(encoding, layout, Y) + 20, | ||
cellPadding = layout.cellPadding; | ||
var isCol = name == COL, | ||
isRow = name == ROW, | ||
type = isCol ? 'x' : isRow ? 'y' : name; | ||
if (isCol) type = 'x'; | ||
if (isRow) type = 'y'; | ||
var def = { | ||
type: type, | ||
scale: name | ||
scale: name, | ||
properties: {}, | ||
layer: encoding.field(name).axis.layer, | ||
orient: axis.orient(name, encoding, stats) | ||
}; | ||
// Add axis label custom scale (for bin / time) | ||
def = axis.labels.scale(def, encoding, name); | ||
def = axis.labels.format(def, name, encoding, stats); | ||
// for x-axis, set ticks for Q or rotate scale for ordinal scale | ||
if (name == X) { | ||
if (encoding.isDimension(X) || encoding.isType(X, T)) { | ||
// TODO(kanitw): Jul 19, 2015 - #506 add condition for rotation | ||
def = axis.labels.rotate(def); | ||
} else { // Q | ||
def.ticks = encoding.field(name).axis.ticks; | ||
} | ||
} | ||
// 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, name, encoding, layout); | ||
def = axis.title(def, name, encoding, layout, opt); | ||
if (isRow || isCol) def = axis.hideTicks(def); | ||
return def; | ||
}; | ||
axis.orient = function(name, encoding, stats) { | ||
var orient = encoding.field(name).axis.orient; | ||
if (orient) return orient; | ||
if (name===COL) return 'top'; | ||
// x-axis for long y - put on top | ||
if (name===X && encoding.has(Y) && encoding.isOrdinalScale(Y) && encoding.cardinality(Y, stats) > 30) { | ||
return 'top'; | ||
} | ||
return undefined; | ||
}; | ||
axis.grid = function(def, name, encoding, layout) { | ||
var cellPadding = layout.cellPadding, | ||
isCol = name == COL, | ||
isRow = name == ROW; | ||
if (encoding.axis(name).grid) { | ||
def.grid = true; | ||
def.layer = 'back'; | ||
if (isCol) { | ||
// set grid property -- put the lines on the right the cell | ||
setter(def, ['properties', 'grid'], { | ||
def.properties.grid = { | ||
x: { | ||
@@ -60,6 +105,6 @@ offset: layout.cellWidth * (1+ cellPadding/2.0), | ||
opacity: { value: encoding.config('cellGridOpacity') } | ||
}); | ||
}; | ||
} else if (isRow) { | ||
// set grid property -- put the lines on the top | ||
setter(def, ['properties', 'grid'], { | ||
def.properties.grid = { | ||
y: { | ||
@@ -71,6 +116,6 @@ offset: -layout.cellHeight * (cellPadding/2), | ||
x: { | ||
value: rowOffset | ||
value: def.offset | ||
}, | ||
x2: { | ||
offset: rowOffset + (layout.cellWidth * 0.05), | ||
offset: def.offset + (layout.cellWidth * 0.05), | ||
// default value(s) -- vega doesn't do recursive merge | ||
@@ -82,71 +127,43 @@ group: 'mark.group.width', | ||
opacity: { value: encoding.config('cellGridOpacity') } | ||
}); | ||
}; | ||
} else { | ||
setter(def, ['properties', 'grid'], { | ||
def.properties.grid = { | ||
stroke: { value: encoding.config('gridColor') }, | ||
opacity: { value: encoding.config('gridOpacity') } | ||
}); | ||
}; | ||
} | ||
} | ||
return def; | ||
}; | ||
if (encoding.axis(name).title) { | ||
def = axis_title(def, name, encoding, layout, opt); | ||
} | ||
axis.hideTicks = function(def) { | ||
def.properties.ticks = {opacity: {value: 0}}; | ||
def.properties.majorTicks = {opacity: {value: 0}}; | ||
def.properties.axis = {opacity: {value: 0}}; | ||
return def; | ||
}; | ||
if (isRow || isCol) { | ||
setter(def, ['properties', 'ticks'], { | ||
opacity: {value: 0} | ||
}); | ||
setter(def, ['properties', 'majorTicks'], { | ||
opacity: {value: 0} | ||
}); | ||
setter(def, ['properties', 'axis'], { | ||
opacity: {value: 0} | ||
}); | ||
} | ||
axis.title = function (def, name, encoding, layout) { | ||
var ax = encoding.field(name).axis; | ||
if (isCol) { | ||
def.orient = 'top'; | ||
} | ||
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 (isRow) { | ||
def.offset = rowOffset; | ||
} | ||
if (name == X) { | ||
if (encoding.has(Y) && encoding.isOrdinalScale(Y) && encoding.cardinality(Y, stats) > 30) { | ||
def.orient = 'top'; | ||
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'); | ||
} | ||
if (encoding.isDimension(X) || encoding.isType(X, T)) { | ||
setter(def, ['properties','labels'], { | ||
angle: {value: 270}, | ||
align: {value: 'right'}, | ||
baseline: {value: 'middle'} | ||
}); | ||
} else { // Q | ||
def.ticks = 5; | ||
} | ||
def.title = maxLength ? util.truncate(fieldTitle, maxLength) : fieldTitle; | ||
} | ||
def = axis_labels(def, name, encoding, layout, opt); | ||
return def; | ||
}; | ||
function axis_title(def, name, encoding, layout, opt) { | ||
// jshint unused:false | ||
var maxlength = null, | ||
fieldTitle = encoding.fieldTitle(name); | ||
if (name===X) { | ||
maxlength = layout.cellWidth / encoding.config('characterWidth'); | ||
} else if (name === Y) { | ||
maxlength = layout.cellHeight / encoding.config('characterWidth'); | ||
} | ||
def.title = maxlength ? util.truncate(fieldTitle, maxlength) : fieldTitle; | ||
if (name === ROW) { | ||
setter(def, ['properties','title'], { | ||
def.properties.title = { | ||
angle: {value: 0}, | ||
@@ -156,44 +173,63 @@ align: {value: 'right'}, | ||
dy: {value: (-layout.height/2) -20} | ||
}); | ||
}; | ||
} | ||
def.titleOffset = axisTitleOffset(encoding, layout, name); | ||
return def; | ||
} | ||
}; | ||
function axis_labels(def, name, encoding, layout, opt) { | ||
// jshint unused:false | ||
axis.labels = {}; | ||
var timeUnit; | ||
// add custom label for time type | ||
if (encoding.isType(name, T) && (timeUnit = encoding.timeUnit(name)) && (time.hasScale(timeUnit))) { | ||
/** add custom label for time type and bin */ | ||
axis.labels.scale = function(def, encoding, name) { | ||
// time | ||
var timeUnit = encoding.field(name).timeUnit; | ||
if (encoding.isType(name, T) && timeUnit && (time.hasScale(timeUnit))) { | ||
setter(def, ['properties','labels','text','scale'], 'time-'+ timeUnit); | ||
} | ||
// FIXME bin | ||
return def; | ||
}; | ||
var textTemplatePath = ['properties','labels','text','template']; | ||
/** | ||
* Determine number format or truncate if maxLabel length is presented. | ||
*/ | ||
axis.labels.format = function (def, name, encoding, stats) { | ||
var fieldStats = stats[encoding.field(name).name]; | ||
if (encoding.axis(name).format) { | ||
def.format = encoding.axis(name).format; | ||
} else if (encoding.isType(name, Q)) { | ||
setter(def, textTemplatePath, '{{data | number:\'.3s\'}}'); | ||
} else if (encoding.isType(name, Q) || fieldStats.type === 'number') { | ||
def.format = encoding.numberFormat(fieldStats); | ||
} else if (encoding.isType(name, T)) { | ||
if (!encoding.timeUnit(name)) { | ||
setter(def, textTemplatePath, '{{data | time:\'%Y-%m-%d\'}}'); | ||
} else if (encoding.timeUnit(name) === 'year') { | ||
setter(def, textTemplatePath, '{{data | number:\'d\'}}'); | ||
var timeUnit = encoding.field(name).timeUnit; | ||
if (!timeUnit) { | ||
def.format = encoding.config('timeFormat'); | ||
} else if (timeUnit === 'year') { | ||
def.format = 'd'; | ||
} | ||
} else if (encoding.isTypes(name, [N, O]) && encoding.axis(name).maxLabelLength) { | ||
setter(def, textTemplatePath, '{{data | truncate:' + encoding.axis(name).maxLabelLength + '}}'); | ||
} else { | ||
// nothing | ||
setter(def, | ||
['properties','labels','text','template'], | ||
'{{data | truncate:' + encoding.axis(name).maxLabelLength + '}}' | ||
); | ||
} | ||
return def; | ||
} | ||
}; | ||
function axisTitleOffset(encoding, layout, name) { | ||
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; | ||
} | ||
if (value) return value; | ||
switch (name) { | ||
//FIXME make this adjustable | ||
case ROW: return 0; | ||
@@ -203,2 +239,2 @@ case COL: return 35; | ||
return getter(layout, [name, 'axisTitleOffset']); | ||
} | ||
}; |
@@ -16,4 +16,4 @@ 'use strict'; | ||
type: 'bin', | ||
field: encoding.field(encType, false, /*nofn*/ true), | ||
output: encoding.field(encType), | ||
field: encoding.fieldRef(encType, {nofn: true}), | ||
output: encoding.fieldRef(encType), | ||
maxbins: encoding.bin(encType).maxbins | ||
@@ -20,0 +20,0 @@ }); |
@@ -53,4 +53,4 @@ 'use strict'; | ||
rawTable = filter.addFilters(rawTable, encoding); // modify rawTable | ||
spec = compiler.time(spec, encoding); // modify rawTable, add scales | ||
dataTable = compiler.bin(dataTable, encoding); // modify dataTable | ||
spec = compiler.time(spec, encoding); // modify dataTable, add scales | ||
var aggResult = compiler.aggregate(dataTable, encoding); // modify dataTable | ||
@@ -63,3 +63,3 @@ var sorting = compiler.sort(spec.data, encoding, stats); // append new data | ||
mark = marks[encoding.marktype()], | ||
mdefs = marks.def(mark, encoding, layout, style), | ||
mdefs = marks.def(mark, encoding, layout, style, stats), | ||
mdef = mdefs[0]; // TODO: remove this dirty hack by refactoring the whole flow | ||
@@ -90,3 +90,3 @@ | ||
// TODO: why - ? | ||
mdef.from.transform = [{type: 'sort', by: '-' + encoding.field(f)}]; | ||
mdef.from.transform = [{type: 'sort', by: '-' + encoding.fieldRef(f)}]; | ||
} | ||
@@ -93,0 +93,0 @@ |
@@ -41,3 +41,3 @@ 'use strict'; | ||
facetKeys.push(encoding.field(ROW)); | ||
facetKeys.push(encoding.fieldRef(ROW)); | ||
@@ -47,3 +47,3 @@ if (hasCol) { | ||
from.transform = from.transform || []; | ||
from.transform.unshift({type: 'facet', keys: [encoding.field(COL)]}); | ||
from.transform.unshift({type: 'facet', keys: [encoding.fieldRef(COL)]}); | ||
} | ||
@@ -75,3 +75,3 @@ | ||
facetKeys.push(encoding.field(COL)); | ||
facetKeys.push(encoding.fieldRef(COL)); | ||
@@ -81,3 +81,3 @@ if (hasRow) { | ||
from.transform = from.transform || []; | ||
from.transform.unshift({type: 'facet', keys: [encoding.field(ROW)]}); | ||
from.transform.unshift({type: 'facet', keys: [encoding.fieldRef(ROW)]}); | ||
} | ||
@@ -84,0 +84,0 @@ |
@@ -23,3 +23,3 @@ 'use strict'; | ||
// add custom filters | ||
for (var i in filters) { | ||
for (var i=0, l=filters.length; i<l; i++) { | ||
var filter = filters[i]; | ||
@@ -44,3 +44,3 @@ | ||
// expects a number of fields | ||
for (var j in operands) { | ||
for (var j=0; j<operands.length; j++) { | ||
condition += d + operands[j] + '!==null'; | ||
@@ -70,3 +70,3 @@ if (j < operands.length - 1) { | ||
type: 'filter', | ||
test: 'd.' + encoding.field(encType) + '>0' | ||
test: 'd.' + encoding.fieldRef(encType) + '>0' | ||
}); | ||
@@ -76,2 +76,1 @@ } | ||
}; | ||
@@ -6,3 +6,5 @@ 'use strict'; | ||
var util = require('../util'), | ||
setter = util.setter; | ||
setter = util.setter, | ||
time = require('./time'), | ||
d3_format = require('d3-format'); | ||
@@ -43,5 +45,5 @@ module.exports = vllayout; | ||
// for ordinal, hasCol or not doesn't matter -- we scale based on cardinality | ||
cellWidth = (xCardinality + encoding.band(X).padding) * encoding.bandSize(X, useSmallBand); | ||
cellWidth = (xCardinality + encoding.field(X).band.padding) * encoding.bandSize(X, useSmallBand); | ||
} else { | ||
cellWidth = hasCol || hasRow ? encoding.enc(COL).width : encoding.config('singleWidth'); | ||
cellWidth = hasCol || hasRow ? encoding.field(COL).width : encoding.config('singleWidth'); | ||
} | ||
@@ -60,5 +62,5 @@ } else { | ||
// for ordinal, hasCol or not doesn't matter -- we scale based on cardinality | ||
cellHeight = (yCardinality + encoding.band(Y).padding) * encoding.bandSize(Y, useSmallBand); | ||
cellHeight = (yCardinality + encoding.field(Y).band.padding) * encoding.bandSize(Y, useSmallBand); | ||
} else { | ||
cellHeight = hasCol || hasRow ? encoding.enc(ROW).height : encoding.config('singleHeight'); | ||
cellHeight = hasCol || hasRow ? encoding.field(ROW).height : encoding.config('singleHeight'); | ||
} | ||
@@ -95,30 +97,49 @@ } else { | ||
// 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; | ||
} | ||
function getMaxLength(encoding, stats, et) { | ||
// FIXME determine constant for Q and T in a nicer way | ||
if (encoding.bin(et)) { | ||
return 5; | ||
} else if (encoding.isType(et, Q)) { | ||
return 10; | ||
var field = encoding.field(et), | ||
fieldStats = stats[field.name]; | ||
if (field.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 15; | ||
} else if (encoding.isTypes(et, [N, O]) && encoding.axis(et).maxLabelLength) { | ||
return Math.min(stats[encoding.fieldName(et)].max, encoding.axis(et).maxLabelLength); | ||
return time.maxLength(encoding.field(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); | ||
} | ||
} | ||
return stats[encoding.fieldName(et)].max; | ||
} | ||
function offset(encoding, stats, layout) { | ||
[X, Y].forEach(function (x) { | ||
var maxLength; | ||
if (encoding.isDimension(x) || encoding.isType(x, T)) { | ||
maxLength = getMaxLength(encoding, stats, x); | ||
} else if (encoding.aggregate(x) === 'count') { | ||
//assign default value for count as it won't have stats | ||
maxLength = 3; | ||
} else if (encoding.isType(x, Q)) { | ||
if (x===X) { | ||
maxLength = 3; | ||
} else { // Y | ||
//assume that default formating is always shorter than 7 | ||
maxLength = Math.min(getMaxLength(encoding, stats, x), 7); | ||
[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.aggregate(et) === '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); | ||
} | ||
@@ -128,5 +149,12 @@ } else { | ||
} | ||
setter(layout,[x, 'axisTitleOffset'], encoding.config('characterWidth') * maxLength + 20); | ||
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; | ||
} |
@@ -13,3 +13,3 @@ 'use strict'; | ||
if (encoding.has(COLOR) && encoding.legend(COLOR)) { | ||
if (encoding.has(COLOR) && encoding.field(COLOR).legend) { | ||
defs.push(legend.def(COLOR, encoding, { | ||
@@ -21,3 +21,3 @@ fill: COLOR, | ||
if (encoding.has(SIZE) && encoding.legend(SIZE)) { | ||
if (encoding.has(SIZE) && encoding.field(SIZE).legend) { | ||
defs.push(legend.def(SIZE, encoding, { | ||
@@ -29,3 +29,3 @@ size: SIZE, | ||
if (encoding.has(SHAPE) && encoding.legend(SHAPE)) { | ||
if (encoding.has(SHAPE) && encoding.field(SHAPE).legend) { | ||
if (defs.length === 2) { | ||
@@ -46,8 +46,11 @@ // TODO: fix this | ||
legend.def = function(name, encoding, props) { | ||
var def = props, timeUnit; | ||
var def = props, | ||
timeUnit = encoding.field(name).timeUnit; | ||
def.title = encoding.fieldTitle(name); | ||
if (encoding.isType(name, T) && (timeUnit = encoding.timeUnit(name)) && | ||
time.hasScale(timeUnit)) { | ||
if (encoding.isType(name, T) && | ||
timeUnit && | ||
time.hasScale(timeUnit) | ||
) { | ||
var properties = def.properties = def.properties || {}, | ||
@@ -54,0 +57,0 @@ labels = properties.labels = properties.labels || {}, |
@@ -7,3 +7,3 @@ 'use strict'; | ||
marks.def = function(mark, encoding, layout, style) { | ||
marks.def = function(mark, encoding, layout, style, stats) { | ||
var defs = []; | ||
@@ -18,3 +18,3 @@ | ||
y2: {value: layout.cellHeight}, | ||
fill: {scale: COLOR, field: encoding.field(COLOR)} | ||
fill: {scale: COLOR, field: encoding.fieldRef(COLOR)} | ||
}; | ||
@@ -29,3 +29,3 @@ defs.push({ | ||
// add the mark def for the main thing | ||
var p = mark.prop(encoding, layout, style); | ||
var p = mark.prop(encoding, layout, style, stats); | ||
defs.push({ | ||
@@ -103,3 +103,3 @@ type: mark.type, | ||
if (e.isMeasure(X)) { | ||
p.x = {scale: X, field: e.field(X)}; | ||
p.x = {scale: X, field: e.fieldRef(X)}; | ||
if (!e.has(Y) || e.isDimension(Y)) { | ||
@@ -110,3 +110,3 @@ p.x2 = {value: 0}; | ||
if (e.has(X)) { // is ordinal | ||
p.xc = {scale: X, field: e.field(X)}; | ||
p.xc = {scale: X, field: e.fieldRef(X)}; | ||
} else { | ||
@@ -121,3 +121,3 @@ p.x = {value: 0, offset: e.config('singleBarOffset')}; | ||
if (e.has(SIZE)) { | ||
p.width = {scale: SIZE, field: e.field(SIZE)}; | ||
p.width = {scale: SIZE, field: e.fieldRef(SIZE)}; | ||
} else { | ||
@@ -136,7 +136,7 @@ p.width = { | ||
if (e.isMeasure(Y)) { | ||
p.y = {scale: Y, field: e.field(Y)}; | ||
p.y = {scale: Y, field: e.fieldRef(Y)}; | ||
p.y2 = {group: 'height'}; | ||
} else { | ||
if (e.has(Y)) { // is ordinal | ||
p.yc = {scale: Y, field: e.field(Y)}; | ||
p.yc = {scale: Y, field: e.fieldRef(Y)}; | ||
} else { | ||
@@ -147,3 +147,3 @@ p.y2 = {group: 'height', offset: -e.config('singleBarOffset')}; | ||
if (e.has(SIZE)) { | ||
p.height = {scale: SIZE, field: e.field(SIZE)}; | ||
p.height = {scale: SIZE, field: e.fieldRef(SIZE)}; | ||
} else { | ||
@@ -161,3 +161,3 @@ p.height = { | ||
if (e.has(COLOR)) { | ||
p.fill = {scale: COLOR, field: e.field(COLOR)}; | ||
p.fill = {scale: COLOR, field: e.fieldRef(COLOR)}; | ||
} else { | ||
@@ -169,3 +169,3 @@ p.fill = {value: e.value(COLOR)}; | ||
if (e.has(ALPHA)) { | ||
p.opacity = {scale: ALPHA, field: e.field(ALPHA)}; | ||
p.opacity = {scale: ALPHA, field: e.fieldRef(ALPHA)}; | ||
} else if (e.value(ALPHA) !== undefined) { | ||
@@ -183,3 +183,3 @@ p.opacity = {value: e.value(ALPHA)}; | ||
if (e.has(X)) { | ||
p.x = {scale: X, field: e.field(X)}; | ||
p.x = {scale: X, field: e.fieldRef(X)}; | ||
} else if (!e.has(X)) { | ||
@@ -191,3 +191,3 @@ p.x = {value: e.bandSize(X, layout.x.useSmallBand) / 2}; | ||
if (e.has(Y)) { | ||
p.y = {scale: Y, field: e.field(Y)}; | ||
p.y = {scale: Y, field: e.fieldRef(Y)}; | ||
} else if (!e.has(Y)) { | ||
@@ -199,3 +199,3 @@ p.y = {value: e.bandSize(Y, layout.y.useSmallBand) / 2}; | ||
if (e.has(SIZE)) { | ||
p.size = {scale: SIZE, field: e.field(SIZE)}; | ||
p.size = {scale: SIZE, field: e.fieldRef(SIZE)}; | ||
} else if (!e.has(SIZE)) { | ||
@@ -207,3 +207,3 @@ p.size = {value: e.value(SIZE)}; | ||
if (e.has(SHAPE)) { | ||
p.shape = {scale: SHAPE, field: e.field(SHAPE)}; | ||
p.shape = {scale: SHAPE, field: e.fieldRef(SHAPE)}; | ||
} else if (!e.has(SHAPE)) { | ||
@@ -214,6 +214,15 @@ p.shape = {value: e.value(SHAPE)}; | ||
// stroke | ||
if (e.has(COLOR)) { | ||
p.stroke = {scale: COLOR, field: e.field(COLOR)}; | ||
} else if (!e.has(COLOR)) { | ||
p.stroke = {value: e.value(COLOR)}; | ||
if (e.field(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')}; | ||
} | ||
@@ -223,3 +232,3 @@ | ||
if (e.has(ALPHA)) { | ||
p.opacity = {scale: ALPHA, field: e.field(ALPHA)}; | ||
p.opacity = {scale: ALPHA, field: e.fieldRef(ALPHA)}; | ||
} else if (e.value(ALPHA) !== undefined) { | ||
@@ -231,4 +240,2 @@ p.opacity = {value: e.value(ALPHA)}; | ||
p.strokeWidth = {value: e.config('strokeWidth')}; | ||
return p; | ||
@@ -243,3 +250,3 @@ } | ||
if (e.has(X)) { | ||
p.x = {scale: X, field: e.field(X)}; | ||
p.x = {scale: X, field: e.fieldRef(X)}; | ||
} else if (!e.has(X)) { | ||
@@ -251,3 +258,3 @@ p.x = {value: 0}; | ||
if (e.has(Y)) { | ||
p.y = {scale: Y, field: e.field(Y)}; | ||
p.y = {scale: Y, field: e.fieldRef(Y)}; | ||
} else if (!e.has(Y)) { | ||
@@ -259,3 +266,3 @@ p.y = {group: 'height'}; | ||
if (e.has(COLOR)) { | ||
p.stroke = {scale: COLOR, field: e.field(COLOR)}; | ||
p.stroke = {scale: COLOR, field: e.fieldRef(COLOR)}; | ||
} else if (!e.has(COLOR)) { | ||
@@ -267,3 +274,3 @@ p.stroke = {value: e.value(COLOR)}; | ||
if (e.has(ALPHA)) { | ||
p.opacity = {scale: ALPHA, field: e.field(ALPHA)}; | ||
p.opacity = {scale: ALPHA, field: e.fieldRef(ALPHA)}; | ||
} else if (e.value(ALPHA) !== undefined) { | ||
@@ -284,3 +291,3 @@ p.opacity = {value: e.value(ALPHA)}; | ||
if (e.isMeasure(X)) { | ||
p.x = {scale: X, field: e.field(X)}; | ||
p.x = {scale: X, field: e.fieldRef(X)}; | ||
if (e.isDimension(Y)) { | ||
@@ -291,3 +298,3 @@ p.x2 = {scale: X, value: 0}; | ||
} else if (e.has(X)) { | ||
p.x = {scale: X, field: e.field(X)}; | ||
p.x = {scale: X, field: e.fieldRef(X)}; | ||
} else { | ||
@@ -299,6 +306,6 @@ p.x = {value: 0}; | ||
if (e.isMeasure(Y)) { | ||
p.y = {scale: Y, field: e.field(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.field(Y)}; | ||
p.y = {scale: Y, field: e.fieldRef(Y)}; | ||
} else { | ||
@@ -308,5 +315,5 @@ p.y = {group: 'height'}; | ||
// stroke | ||
// fill | ||
if (e.has(COLOR)) { | ||
p.fill = {scale: COLOR, field: e.field(COLOR)}; | ||
p.fill = {scale: COLOR, field: e.fieldRef(COLOR)}; | ||
} else if (!e.has(COLOR)) { | ||
@@ -318,3 +325,3 @@ p.fill = {value: e.value(COLOR)}; | ||
if (e.has(ALPHA)) { | ||
p.opacity = {scale: ALPHA, field: e.field(ALPHA)}; | ||
p.opacity = {scale: ALPHA, field: e.fieldRef(ALPHA)}; | ||
} else if (e.value(ALPHA) !== undefined) { | ||
@@ -332,3 +339,3 @@ p.opacity = {value: e.value(ALPHA)}; | ||
if (e.has(X)) { | ||
p.x = {scale: X, field: e.field(X)}; | ||
p.x = {scale: X, field: e.fieldRef(X)}; | ||
if (e.isDimension(X)) { | ||
@@ -343,3 +350,3 @@ p.x.offset = -e.bandSize(X, layout.x.useSmallBand) / 3; | ||
if (e.has(Y)) { | ||
p.y = {scale: Y, field: e.field(Y)}; | ||
p.y = {scale: Y, field: e.fieldRef(Y)}; | ||
if (e.isDimension(Y)) { | ||
@@ -368,3 +375,3 @@ p.y.offset = -e.bandSize(Y, layout.y.useSmallBand) / 3; | ||
if (e.has(COLOR)) { | ||
p.fill = {scale: COLOR, field: e.field(COLOR)}; | ||
p.fill = {scale: COLOR, field: e.fieldRef(COLOR)}; | ||
} else { | ||
@@ -376,3 +383,3 @@ p.fill = {value: e.value(COLOR)}; | ||
if (e.has(ALPHA)) { | ||
p.opacity = {scale: ALPHA, field: e.field(ALPHA)}; | ||
p.opacity = {scale: ALPHA, field: e.fieldRef(ALPHA)}; | ||
} else if (e.value(ALPHA) !== undefined) { | ||
@@ -393,3 +400,3 @@ p.opacity = {value: e.value(ALPHA)}; | ||
if (e.has(X)) { | ||
p.x = {scale: X, field: e.field(X)}; | ||
p.x = {scale: X, field: e.fieldRef(X)}; | ||
} else if (!e.has(X)) { | ||
@@ -401,3 +408,3 @@ p.x = {value: e.bandSize(X, layout.x.useSmallBand) / 2}; | ||
if (e.has(Y)) { | ||
p.y = {scale: Y, field: e.field(Y)}; | ||
p.y = {scale: Y, field: e.fieldRef(Y)}; | ||
} else if (!e.has(Y)) { | ||
@@ -409,3 +416,3 @@ p.y = {value: e.bandSize(Y, layout.y.useSmallBand) / 2}; | ||
if (e.has(SIZE)) { | ||
p.size = {scale: SIZE, field: e.field(SIZE)}; | ||
p.size = {scale: SIZE, field: e.fieldRef(SIZE)}; | ||
} else if (!e.has(X)) { | ||
@@ -420,3 +427,3 @@ p.size = {value: e.value(SIZE)}; | ||
if (e.has(COLOR)) { | ||
p.fill = {scale: COLOR, field: e.field(COLOR)}; | ||
p.fill = {scale: COLOR, field: e.fieldRef(COLOR)}; | ||
} else if (!e.has(COLOR)) { | ||
@@ -428,3 +435,3 @@ p.fill = {value: e.value(COLOR)}; | ||
if (e.has(ALPHA)) { | ||
p.opacity = {scale: ALPHA, field: e.field(ALPHA)}; | ||
p.opacity = {scale: ALPHA, field: e.fieldRef(ALPHA)}; | ||
} else if (e.value(ALPHA) !== undefined) { | ||
@@ -440,8 +447,9 @@ p.opacity = {value: e.value(ALPHA)}; | ||
function text_props(e, layout, style) { | ||
var p = {}; | ||
function text_props(e, layout, style, stats) { | ||
var p = {}, | ||
field = e.field(TEXT); | ||
// x | ||
if (e.has(X)) { | ||
p.x = {scale: X, field: e.field(X)}; | ||
p.x = {scale: X, field: e.fieldRef(X)}; | ||
} else if (!e.has(X)) { | ||
@@ -457,3 +465,3 @@ if (e.has(TEXT) && e.isType(TEXT, Q)) { | ||
if (e.has(Y)) { | ||
p.y = {scale: Y, field: e.field(Y)}; | ||
p.y = {scale: Y, field: e.fieldRef(Y)}; | ||
} else if (!e.has(Y)) { | ||
@@ -465,5 +473,5 @@ p.y = {value: e.bandSize(Y, layout.y.useSmallBand) / 2}; | ||
if (e.has(SIZE)) { | ||
p.fontSize = {scale: SIZE, field: e.field(SIZE)}; | ||
p.fontSize = {scale: SIZE, field: e.fieldRef(SIZE)}; | ||
} else if (!e.has(SIZE)) { | ||
p.fontSize = {value: e.font('size')}; | ||
p.fontSize = {value: field.font.size}; | ||
} | ||
@@ -473,7 +481,7 @@ | ||
// color should be set to background | ||
p.fill = {value: 'black'}; | ||
p.fill = {value: field.text.color}; | ||
// alpha | ||
if (e.has(ALPHA)) { | ||
p.opacity = {scale: ALPHA, field: e.field(ALPHA)}; | ||
p.opacity = {scale: ALPHA, field: e.fieldRef(ALPHA)}; | ||
} else if (e.value(ALPHA) !== undefined) { | ||
@@ -488,17 +496,21 @@ p.opacity = {value: e.value(ALPHA)}; | ||
if (e.isType(TEXT, Q)) { | ||
p.text = {template: '{{' + e.field(TEXT) + ' | number:\'.3s\'}}'}; | ||
p.align = {value: 'right'}; | ||
var fieldStats = stats[e.fieldName(name)], | ||
numberFormat = field.format || e.numberFormat(fieldStats); | ||
p.text = {template: '{{' + e.fieldRef(TEXT) + ' | number:\'' + | ||
numberFormat +'\'}}'}; | ||
p.align = {value: field.align}; | ||
} else { | ||
p.text = {field: e.field(TEXT)}; | ||
p.text = {field: e.fieldRef(TEXT)}; | ||
} | ||
} else { | ||
p.text = {value: 'Abc'}; | ||
p.text = {value: field.placeholder}; | ||
} | ||
p.font = {value: e.font('family')}; | ||
p.fontWeight = {value: e.font('weight')}; | ||
p.fontStyle = {value: e.font('style')}; | ||
p.baseline = {value: e.text('baseline')}; | ||
p.font = {value: field.font.family}; | ||
p.fontWeight = {value: field.font.weight}; | ||
p.fontStyle = {value: field.font.style}; | ||
p.baseline = {value: field.baseline}; | ||
return p; | ||
} |
@@ -6,3 +6,4 @@ 'use strict'; | ||
colorbrewer = require('colorbrewer'), | ||
interpolateLab = require('d3-color').interpolateLab; | ||
interpolateLab = require('d3-color').interpolateLab, | ||
schema = require('../schema/schema'); | ||
@@ -25,10 +26,9 @@ var scale = module.exports = {}; | ||
type: scale.type(name, encoding), | ||
domain: scale.domain(name, encoding, sorting, opt) | ||
domain: scale.domain(name, encoding, stats, sorting, opt) | ||
}; | ||
if (s.type === 'ordinal' && !encoding.bin(name) && encoding.sort(name).length === 0) { | ||
s.sort = true; | ||
} | ||
scale_range(s, encoding, layout, stats, style, opt); | ||
s.sort = scale.sort(s, encoding, name) || undefined; | ||
scale.range(s, encoding, layout, stats, opt); | ||
return (a.push(s), a); | ||
@@ -38,2 +38,9 @@ }, []); | ||
scale.sort = function(s, encoding, name) { | ||
return s.type === 'ordinal' && ( | ||
!!encoding.bin(name) || | ||
encoding.sort(name).length === 0 | ||
); | ||
}; | ||
scale.type = function(name, encoding) { | ||
@@ -45,4 +52,4 @@ | ||
case T: | ||
var timeUnit = encoding.timeUnit(name); | ||
return (timeUnit && time.scale.type(timeUnit, name)) || 'time'; | ||
var timeUnit = encoding.field(name).timeUnit; | ||
return timeUnit ? time.scale.type(timeUnit, name) : 'time'; | ||
case Q: | ||
@@ -56,8 +63,20 @@ if (encoding.bin(name)) { | ||
scale.domain = function (name, encoding, sorting, opt) { | ||
scale.domain = function (name, encoding, stats, sorting, opt) { | ||
var field = encoding.field(name); | ||
if (encoding.isType(name, T)) { | ||
var range = time.scale.domain(encoding.timeUnit(name), name); | ||
var range = time.scale.domain(field.timeUnit, name); | ||
if(range) return range; | ||
} | ||
if (field.bin) { | ||
// TODO(kanitw): this must be changed in vg2 | ||
var fieldStat = stats[field.name], | ||
bins = util.getbins(fieldStat, field.bin.maxbins || schema.MAXBINS_DEFAULT), | ||
numbins = (bins.stop - bins.start) / bins.step; | ||
return util.range(numbins).map(function(i) { | ||
return bins.start + bins.step * i; | ||
}); | ||
} | ||
if (name == opt.stack) { | ||
@@ -72,17 +91,35 @@ return { | ||
} | ||
return {data: sorting.getDataset(name), field: encoding.field(name)}; | ||
var aggregate = encoding.aggregate(name), | ||
timeUnit = field.timeUnit, | ||
scaleUseRawDomain = encoding.scale(name).useRawDomain, | ||
useRawDomain = scaleUseRawDomain !== undefined ? | ||
scaleUseRawDomain : encoding.config('useRawDomain'), | ||
notCountOrSum = !aggregate || (aggregate !=='count' && aggregate !== 'sum'); | ||
if ( useRawDomain && notCountOrSum && ( | ||
// Q always uses non-ordinal scale except when it's binned and thus uses ordinal scale. | ||
(encoding.isType(name, Q) && !field.bin) || | ||
// T uses non-ordinal scale when there's no unit or when the unit is not ordinal. | ||
(encoding.isType(name, T) && (!timeUnit || !time.isOrdinalFn(timeUnit))) | ||
) | ||
) { | ||
return {data: RAW, field: encoding.fieldRef(name, {nofn: !timeUnit})}; | ||
} | ||
return {data: sorting.getDataset(name), field: encoding.fieldRef(name)}; | ||
}; | ||
function scale_range(s, encoding, layout, stats, style, opt) { | ||
// jshint unused:false | ||
var spec = encoding.scale(s.name); | ||
scale.range = function (s, encoding, layout, stats) { | ||
var spec = encoding.scale(s.name), | ||
field = encoding.field(s.name), | ||
timeUnit = field.timeUnit; | ||
switch (s.name) { | ||
case X: | ||
s.range = layout.cellWidth ? [0, layout.cellWidth] : 'width'; | ||
if (s.type === 'ordinal') { | ||
s.bandWidth = encoding.bandSize(X, layout.x.useSmallBand); | ||
} else { | ||
s.range = layout.cellWidth ? [0, layout.cellWidth] : 'width'; | ||
if (encoding.isType(s.name,T) && encoding.timeUnit(s.name) === 'year') { | ||
if (encoding.isType(s.name,T) && timeUnit === 'year') { | ||
s.zero = false; | ||
@@ -97,3 +134,3 @@ } else { | ||
if (s.type === 'time') { | ||
s.nice = encoding.timeUnit(s.name); | ||
s.nice = timeUnit || encoding.config('timeScaleNice'); | ||
}else { | ||
@@ -105,7 +142,9 @@ s.nice = true; | ||
if (s.type === 'ordinal') { | ||
s.range = layout.cellHeight ? | ||
(field.bin ? [layout.cellHeight, 0] : [0, layout.cellHeight]) : | ||
'height'; | ||
s.bandWidth = encoding.bandSize(Y, layout.y.useSmallBand); | ||
} else { | ||
s.range = layout.cellHeight ? [layout.cellHeight, 0] : 'height'; | ||
if (encoding.isType(s.name,T) && encoding.timeUnit(s.name) === 'year') { | ||
if (encoding.isType(s.name,T) && timeUnit === 'year') { | ||
s.zero = false; | ||
@@ -122,3 +161,3 @@ } else { | ||
if (s.type === 'time') { | ||
s.nice = encoding.timeUnit(s.name) || encoding.config('timeScaleNice'); | ||
s.nice = timeUnit || encoding.config('timeScaleNice'); | ||
}else { | ||
@@ -175,9 +214,10 @@ s.nice = true; | ||
s.points = true; | ||
s.padding = encoding.band(s.name).padding; | ||
s.padding = encoding.field(s.name).band.padding; | ||
} | ||
} | ||
} | ||
}; | ||
scale.color = function(s, encoding, stats) { | ||
var range = encoding.scale(COLOR).range, | ||
var colorScale = encoding.scale(COLOR), | ||
range = colorScale.range, | ||
cardinality = encoding.cardinality(COLOR, stats), | ||
@@ -187,3 +227,3 @@ type = encoding.type(COLOR); | ||
if (range === undefined) { | ||
var ordinalPalette = encoding.config('ordinalPalette'); | ||
var ordinalPalette = colorScale.ordinalPalette; | ||
if (s.type === 'ordinal') { | ||
@@ -193,5 +233,5 @@ if (type === N) { | ||
if (cardinality <= 10) { | ||
range = encoding.config('c10palette'); | ||
range = colorScale.c10palette; | ||
} else { | ||
range = encoding.config('c20palette'); | ||
range = colorScale.c20palette; | ||
} | ||
@@ -258,2 +298,1 @@ } else { | ||
}; | ||
@@ -37,4 +37,4 @@ 'use strict'; | ||
type: 'aggregate', | ||
groupby: [encoding.field(dim)].concat(facets), // dim and other facets | ||
fields: [{op: 'sum', field: encoding.field(val)}] // TODO check if field with aggregate is correct? | ||
groupby: [encoding.fieldRef(dim)].concat(facets), // dim and other facets | ||
fields: [{op: 'sum', field: encoding.fieldRef(val)}] // TODO check if field with aggregate is correct? | ||
}] | ||
@@ -59,4 +59,4 @@ }; | ||
type: 'stack', | ||
point: encoding.field(dim), | ||
height: encoding.field(val), | ||
point: encoding.fieldRef(dim), | ||
height: encoding.fieldRef(val), | ||
output: {y1: val, y0: val + '2'} | ||
@@ -63,0 +63,0 @@ }]; |
@@ -22,4 +22,4 @@ 'use strict'; | ||
if (stack && encoding.has(COLOR)) { | ||
trans.unshift({type: 'sort', by: encoding.field(COLOR)}); | ||
trans.unshift({type: 'sort', by: encoding.fieldRef(COLOR)}); | ||
} | ||
} |
'use strict'; | ||
var util = require('../util'); | ||
var util = require('../util'), | ||
d3_time_format = require('d3-time-format'); | ||
module.exports = time; | ||
function time(spec, encoding, opt) { // FIXME refactor to reduce side effect #276 | ||
var LONG_DATE = new Date(2014, 8, 17); | ||
function time(spec, encoding) { // FIXME refactor to reduce side effect #276 | ||
// jshint unused:false | ||
@@ -14,3 +17,3 @@ var timeFields = {}, timeUnits = {}; | ||
if (field.type === T && field.timeUnit) { | ||
timeFields[encoding.field(encType)] = { | ||
timeFields[encoding.fieldRef(encType)] = { | ||
field: field, | ||
@@ -24,3 +27,3 @@ encType: encType | ||
// add formula transform | ||
var data = spec.data[1], | ||
var data = spec.data[0], | ||
transform = data.transform = data.transform || []; | ||
@@ -36,3 +39,4 @@ | ||
for (var timeUnit in timeUnits) { | ||
time.scale(scales, timeUnit, encoding); | ||
var scale = time.scale.def(timeUnit, encoding); | ||
if (scale) scales.push(scale); | ||
} | ||
@@ -42,4 +46,2 @@ return spec; | ||
time.cardinality = function(field, stats, filterNull, type) { | ||
@@ -67,2 +69,25 @@ var timeUnit = field.timeUnit; | ||
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' | ||
} | ||
// no time unit | ||
var timeFormat = encoding.config('timeFormat'); | ||
return d3_time_format.utcFormat(timeFormat)(LONG_DATE).length; | ||
}; | ||
function fieldFn(func, field) { | ||
@@ -83,3 +108,3 @@ return 'utc' + func + '(d.data.'+ field.name +')'; | ||
type: 'formula', | ||
field: encoding.field(encType), | ||
field: encoding.fieldRef(encType), | ||
expr: time.formula(field) | ||
@@ -89,30 +114,39 @@ }); | ||
/** append custom time scales for axis label */ | ||
time.scale = function(scales, timeUnit, encoding) { | ||
var labelLength = encoding.config('timeScaleLabelLength'); | ||
// TODO add option for shorter scale / custom range | ||
time.range = function(timeUnit, encoding) { | ||
var labelLength = encoding.config('timeScaleLabelLength'), | ||
scaleLabel; | ||
switch (timeUnit) { | ||
case 'day': | ||
scales.push({ | ||
name: 'time-'+timeUnit, | ||
type: 'ordinal', | ||
domain: util.range(0, 7), | ||
range: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'].map( | ||
function(s) { return s.substr(0, labelLength);} | ||
) | ||
}); | ||
scaleLabel = encoding.config('dayScaleLabel'); | ||
break; | ||
case 'month': | ||
scales.push({ | ||
name: 'time-'+timeUnit, | ||
type: 'ordinal', | ||
domain: util.range(0, 12), | ||
range: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'].map( | ||
function(s) { return s.substr(0, labelLength);} | ||
) | ||
}); | ||
scaleLabel = encoding.config('monthScaleLabel'); | ||
break; | ||
} | ||
if (scaleLabel) { | ||
return labelLength ? scaleLabel.map( | ||
function(s) { return s.substr(0, labelLength);} | ||
) : scaleLabel; | ||
} | ||
return; | ||
}; | ||
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) { | ||
@@ -133,3 +167,3 @@ switch (timeUnit) { | ||
if (name === COLOR) { | ||
return 'linear'; // this has order | ||
return 'linear'; // time has order, so use interpolated ordinal color scale. | ||
} | ||
@@ -162,3 +196,1 @@ | ||
}; | ||
@@ -24,3 +24,8 @@ 'use strict'; | ||
return s; | ||
}, {count: data.length}); | ||
}, { | ||
'*': { | ||
max: data.length, | ||
min: 0 | ||
} | ||
}); | ||
}; |
@@ -98,3 +98,3 @@ 'use strict'; | ||
proto.enc = function(et) { | ||
proto.field = function(et) { | ||
return this._enc[et]; | ||
@@ -125,11 +125,3 @@ }; | ||
// get "field" property for vega | ||
proto.field = function(et, nodata, nofn) { | ||
if (!this.has(et)) return null; | ||
return vlfield.fieldRef(this._enc[et], { | ||
nofn: nofn, | ||
data: !this._vega2 && !nodata | ||
}); | ||
}; | ||
// get "field" reference for vega | ||
proto.fieldRef = function(et, opt) { | ||
@@ -156,5 +148,5 @@ opt = opt || {}; | ||
} | ||
var timeUnit = this._enc[et].aggregate || this._enc[et].timeUnit || (this._enc[et].bin && 'bin'); | ||
if (timeUnit) { | ||
return timeUnit.toUpperCase() + '(' + this._enc[et].name + ')'; | ||
var fn = this._enc[et].aggregate || this._enc[et].timeUnit || (this._enc[et].bin && 'bin'); | ||
if (fn) { | ||
return fn.toUpperCase() + '(' + this._enc[et].name + ')'; | ||
} else { | ||
@@ -173,6 +165,2 @@ return this._enc[et].name; | ||
proto.band = function(et) { | ||
return this._enc[et].band || {}; | ||
}; | ||
proto.bandSize = function(encType, useSmallBand) { | ||
@@ -185,3 +173,3 @@ useSmallBand = useSmallBand || | ||
// if band.size is explicitly specified, follow the specification, otherwise draw value from config. | ||
return this.band(encType).size || | ||
return this.field(encType).band.size || | ||
this.config(useSmallBand ? 'smallBandSize' : 'largeBandSize'); | ||
@@ -206,6 +194,2 @@ }; | ||
proto.legend = function(et) { | ||
return this._enc[et].legend; | ||
}; | ||
proto.value = function(et) { | ||
@@ -215,4 +199,6 @@ return this._enc[et].value; | ||
proto.timeUnit = function(et) { | ||
return this._enc[et].timeUnit; | ||
proto.numberFormat = function(fieldStats) { | ||
var formatConfig = fieldStats.max > this.config('maxSmallNumber') ? | ||
'largeNumberFormat': 'smallNumberFormat'; | ||
return this.config(formatConfig); | ||
}; | ||
@@ -245,6 +231,2 @@ | ||
proto.length = function() { | ||
return util.keys(this._enc).length; | ||
}; | ||
proto.map = function(f) { | ||
@@ -266,18 +248,4 @@ return vlenc.map(this._enc, f); | ||
proto.role = function(et) { | ||
return this.has(et) ? vlfield.role(this._enc[et]) : null; | ||
}; | ||
proto.text = function(prop) { | ||
var text = this._enc[TEXT].text; | ||
return prop ? text[prop] : text; | ||
}; | ||
proto.font = function(prop) { | ||
var font = this._enc[TEXT].font; | ||
return prop ? font[prop] : font; | ||
}; | ||
proto.isType = function(et, type) { | ||
var field = this.enc(et); | ||
var field = this.field(et); | ||
return field && vlfield.isType(field, type); | ||
@@ -287,3 +255,3 @@ }; | ||
proto.isTypes = function(et, type) { | ||
var field = this.enc(et); | ||
var field = this.field(et); | ||
return field && vlfield.isTypes(field, type); | ||
@@ -293,11 +261,11 @@ }; | ||
Encoding.isOrdinalScale = function(encoding, encType) { | ||
return vlfield.isOrdinalScale(encoding.enc(encType)); | ||
return vlfield.isOrdinalScale(encoding.field(encType)); | ||
}; | ||
Encoding.isDimension = function(encoding, encType) { | ||
return vlfield.isDimension(encoding.enc(encType)); | ||
return vlfield.isDimension(encoding.field(encType)); | ||
}; | ||
Encoding.isMeasure = function(encoding, encType) { | ||
return vlfield.isMeasure(encoding.enc(encType)); | ||
return vlfield.isMeasure(encoding.field(encType)); | ||
}; | ||
@@ -342,3 +310,3 @@ | ||
proto.cardinality = function(encType, stats) { | ||
return vlfield.cardinality(this.enc(encType), stats, this.config('filterNull')); | ||
return vlfield.cardinality(this.field(encType), stats, this.config('filterNull')); | ||
}; | ||
@@ -345,0 +313,0 @@ |
@@ -97,35 +97,2 @@ 'use strict'; | ||
var typeOrder = { | ||
N: 0, | ||
O: 1, | ||
G: 2, | ||
T: 3, | ||
Q: 4 | ||
}; | ||
vlfield.order = {}; | ||
vlfield.order.type = function(field) { | ||
if (field.aggregate==='count') return 4; | ||
return typeOrder[field.type]; | ||
}; | ||
vlfield.order.typeThenName = function(field) { | ||
return vlfield.order.type(field) + '_' + | ||
(field.aggregate === 'count' ? '~' : field.name.toLowerCase()); | ||
// ~ is the last character in ASCII | ||
}; | ||
vlfield.order.original = function() { | ||
return 0; // no swap will occur | ||
}; | ||
vlfield.order.name = function(field) { | ||
return field.name; | ||
}; | ||
vlfield.order.typeThenCardinality = function(field, stats){ | ||
return stats[field.name].distinct; | ||
}; | ||
var isType = vlfield.isType = function (fieldDef, type) { | ||
@@ -169,6 +136,2 @@ return fieldDef.type === type; | ||
vlfield.role = function(field) { | ||
return isDimension(field) ? 'dimension' : 'measure'; | ||
}; | ||
vlfield.count = function() { | ||
@@ -175,0 +138,0 @@ return {name:'*', aggregate: 'count', type: Q, displayName: vlfield.count.displayName}; |
@@ -30,16 +30,2 @@ // Package of defining Vega-lite Specification's json schema | ||
}; | ||
schema.band = { | ||
type: 'object', | ||
properties: { | ||
size: { | ||
type: 'integer', | ||
minimum: 0 | ||
}, | ||
padding: { | ||
type: 'integer', | ||
minimum: 0, | ||
default: 1 | ||
} | ||
} | ||
}; | ||
@@ -60,6 +46,5 @@ schema.getSupportedRole = function(encType) { | ||
//TODO(kanitw): add other type of function here | ||
schema.scale_type = { | ||
type: 'string', | ||
// TODO(kanitw) read vega's schema here, add description | ||
enum: ['linear', 'log', 'pow', 'sqrt', 'quantile'], | ||
@@ -91,3 +76,4 @@ default: 'linear', | ||
default: schema.MAXBINS_DEFAULT, | ||
minimum: 2 | ||
minimum: 2, | ||
description: 'Maximum number of bins.' | ||
} | ||
@@ -127,2 +113,11 @@ }, | ||
supportedTypes: toMap([T]) | ||
}, | ||
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.' | ||
} | ||
@@ -166,7 +161,28 @@ } | ||
}, | ||
layer: { | ||
type: 'string', | ||
default: 'back', | ||
description: 'A string indicating if the axis (and any gridlines) should be placed above or below the data marks.' | ||
}, | ||
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, | ||
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.' | ||
}, | ||
title: { | ||
type: 'boolean', | ||
default: true, | ||
description: 'A title for the axis.' | ||
type: 'string', | ||
default: undefined, | ||
description: 'A title for the axis. (Shows field name and its function by default.)' | ||
}, | ||
titleMaxLength: { | ||
type: 'integer', | ||
default: undefined, | ||
description: 'Max length for axis title if the title is automatically generated from the field\'s description' | ||
}, | ||
titleOffset: { | ||
@@ -180,3 +196,6 @@ type: 'integer', | ||
default: undefined, // auto | ||
description: 'The formatting pattern for axis labels.' | ||
description: 'The formatting pattern for axis labels. '+ | ||
'If not undefined, this will be determined by ' + | ||
'small/largeNumberFormat and the max value ' + | ||
'of the field.' | ||
}, | ||
@@ -204,12 +223,14 @@ maxLabelLength: { | ||
required: ['name', 'aggregate'], | ||
name: { | ||
type: 'string' | ||
}, | ||
aggregate: { | ||
type: 'string', | ||
enum: ['avg', 'sum', 'min', 'max', 'count'] | ||
}, | ||
reverse: { | ||
type: 'boolean', | ||
default: false | ||
properties: { | ||
name: { | ||
type: 'string' | ||
}, | ||
aggregate: { | ||
type: 'string', | ||
enum: ['avg', 'sum', 'min', 'max', 'count'] | ||
}, | ||
reverse: { | ||
type: 'boolean', | ||
default: false | ||
} | ||
} | ||
@@ -224,3 +245,17 @@ } | ||
properties: { | ||
band: schema.band | ||
band: { | ||
type: 'object', | ||
properties: { | ||
size: { | ||
type: 'integer', | ||
minimum: 0, | ||
default: undefined | ||
}, | ||
padding: { | ||
type: 'integer', | ||
minimum: 0, | ||
default: 1 | ||
} | ||
} | ||
} | ||
} | ||
@@ -243,20 +278,24 @@ }; | ||
properties: { | ||
text: { | ||
type: 'object', | ||
properties: { | ||
align: { | ||
type: 'string', | ||
default: 'left' | ||
}, | ||
baseline: { | ||
type: 'string', | ||
default: 'middle' | ||
}, | ||
margin: { | ||
type: 'integer', | ||
default: 4, | ||
minimum: 0 | ||
} | ||
} | ||
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: { | ||
@@ -285,3 +324,11 @@ type: 'object', | ||
} | ||
} | ||
}, | ||
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.' | ||
}, | ||
} | ||
@@ -315,3 +362,27 @@ }; | ||
range: { | ||
type: ['string', 'array'] | ||
type: ['string', 'array'], | ||
default: undefined, | ||
description: | ||
'color palette, if undefined vega-lite will use data property' + | ||
'to pick one from c10palette, c20palette, or ordinalPalette' | ||
}, | ||
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: 'BuGn', | ||
enum: util.keys(colorbrewer) | ||
} | ||
@@ -344,2 +415,7 @@ } | ||
default: 'circle' | ||
}, | ||
filled: { | ||
type: 'boolean', | ||
default: false, | ||
description: 'whether the shape\'s color should be used as fill color instead of stroke color' | ||
} | ||
@@ -360,8 +436,3 @@ } | ||
default: 150 | ||
}, | ||
grid: { | ||
type: 'boolean', | ||
default: true, | ||
description: 'A flag indicate if gridlines should be created in addition to ticks.' | ||
}, | ||
} | ||
} | ||
@@ -601,25 +672,2 @@ }; | ||
}, | ||
// color | ||
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: 'BuGn', | ||
enum: util.keys(colorbrewer) | ||
}, | ||
// scales | ||
@@ -629,4 +677,22 @@ timeScaleLabelLength: { | ||
default: 3, | ||
minimum: 0 | ||
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 | ||
@@ -636,2 +702,34 @@ characterWidth: { | ||
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.' | ||
} | ||
@@ -638,0 +736,0 @@ } |
@@ -9,2 +9,8 @@ 'use strict'; | ||
describe('Axis', function() { | ||
var stats = {a: {distinct: 5}, b: {distinct: 32}}, | ||
layout = { | ||
cellWidth: 60, // default characterWidth = 6 | ||
cellHeight: 60 | ||
}; | ||
describe('(X) for Time Data', function() { | ||
@@ -29,3 +35,3 @@ var fieldName = 'a', | ||
} | ||
}); | ||
}, stats); | ||
@@ -41,2 +47,132 @@ //FIXME decouple the test here | ||
}); | ||
describe('grid()', function () { | ||
// FIXME(kanitw): Jul 19, 2015 - write test | ||
}); | ||
describe('hideTicks()', function () { | ||
var def = axis.hideTicks({properties:{}}); | ||
it('should adjust ticks', function () { | ||
expect(def.properties.ticks).to.eql({opacity: {value: 0}}); | ||
}); | ||
it('should adjust majorTicks', function () { | ||
expect(def.properties.majorTicks).to.eql({opacity: {value: 0}}); | ||
}); | ||
it('should adjust axis', function () { | ||
expect(def.properties.axis).to.eql({opacity: {value: 0}}); | ||
}); | ||
}); | ||
describe('labels.scale()', function () { | ||
// FIXME(kanitw): Jul 19, 2015 - write test | ||
}); | ||
describe('labels.format()', function () { | ||
// FIXME(kanitw): Jul 19, 2015 - write test | ||
}); | ||
describe('labels.rotate()', function () { | ||
// FIXME(kanitw): Jul 19, 2015 - write test | ||
}); | ||
describe('orient()', function () { | ||
it('should return specified orient', function () { | ||
var orient = axis.orient('x', Encoding.fromSpec({ | ||
encoding: { | ||
x: {name: 'a', axis:{orient: 'bottom'}} | ||
} | ||
}), stats); | ||
expect(orient).to.eql('bottom'); | ||
}); | ||
it('should return undefined by default', function () { | ||
var orient = axis.orient('x', Encoding.fromSpec({ | ||
encoding: { | ||
x: {name: 'a'} | ||
} | ||
}), stats); | ||
expect(orient).to.eql(undefined); | ||
}); | ||
it('should return top for COL', function () { | ||
var orient = axis.orient('col', Encoding.fromSpec({ | ||
encoding: { | ||
x: {name: 'a'}, | ||
col: {name: 'a'} | ||
} | ||
}), stats); | ||
expect(orient).to.eql('top'); | ||
}); | ||
it('should return top for X with high cardinality, ordinal Y', function () { | ||
var orient = axis.orient('x', Encoding.fromSpec({ | ||
encoding: { | ||
x: {name: 'a'}, | ||
y: {name: 'b', type: 'O'} | ||
} | ||
}), stats); | ||
expect(orient).to.eql('top'); | ||
}); | ||
}); | ||
describe('title()', function () { | ||
it('should add explicitly specified title', function () { | ||
var def = axis.title({}, 'x', Encoding.fromSpec({ | ||
encoding: { | ||
x: {name: 'a', axis: {title: 'Custom'}} | ||
} | ||
}), stats, layout); | ||
expect(def.title).to.eql('Custom'); | ||
}); | ||
it('should add return fieldTitle by default', function () { | ||
var encoding = Encoding.fromSpec({ | ||
encoding: { | ||
x: {name: 'a', axis: {titleMaxLength: '3'}} | ||
} | ||
}); | ||
var def = axis.title({}, 'x', encoding, layout); | ||
expect(def.title).to.eql('a'); | ||
}); | ||
it('should add return fieldTitle by default', function () { | ||
var encoding = Encoding.fromSpec({ | ||
encoding: { | ||
x: {name: 'a', aggregate: 'sum', axis: {titleMaxLength: '10'}} | ||
} | ||
}); | ||
var def = axis.title({}, 'x', encoding, layout); | ||
expect(def.title).to.eql('SUM(a)'); | ||
}); | ||
it('should add return fieldTitle by default and truncate', function () { | ||
var encoding = Encoding.fromSpec({ | ||
encoding: { | ||
x: {name: 'a', aggregate: 'sum', axis: {titleMaxLength: '3'}} | ||
} | ||
}); | ||
var def = axis.title({}, 'x', encoding, layout); | ||
expect(def.title).to.eql('SU…'); | ||
}); | ||
it('should add return fieldTitle by default and truncate', function () { | ||
var encoding = Encoding.fromSpec({ | ||
encoding: { | ||
x: {name: 'abcdefghijkl'} | ||
} | ||
}); | ||
var def = axis.title({}, 'x', encoding, layout); | ||
expect(def.title).to.eql('abcdefghi…'); | ||
}); | ||
}); | ||
describe('titleOffset()', function () { | ||
// FIXME(kanitw): Jul 19, 2015 - write test | ||
}); | ||
}); |
@@ -11,101 +11,236 @@ 'use strict'; | ||
describe('vl.compile.scale.domain', function() { | ||
it('should return correct stack', function() { | ||
var scale = vlscale.domain('y', Encoding.fromSpec({ | ||
encoding: { | ||
y: { | ||
name: 'origin' | ||
} | ||
} | ||
}), {}, { | ||
stack: 'y', | ||
facet: true | ||
describe('vl.compile.scale', function() { | ||
describe('sort()', function() { | ||
it('should return true for any ordinal or binned field', function() { | ||
var encoding = Encoding.fromSpec({ | ||
encoding: { | ||
x: { name: 'origin', type: O}, | ||
y: { bin: true, name: 'origin', type: Q} | ||
} | ||
}); | ||
expect(vlscale.sort({type: 'ordinal'}, encoding, 'x')) | ||
.to.eql(true); | ||
expect(vlscale.sort({type: 'ordinal'}, encoding, 'y')) | ||
.to.eql(true); | ||
}); | ||
expect(scale).to.eql({ | ||
data: 'stacked', | ||
field: 'data.max_sum_origin' | ||
}); | ||
}); | ||
it('should return correct aggregated stack', function() { | ||
var scale = vlscale.domain('y', Encoding.fromSpec({ | ||
encoding: { | ||
y: { | ||
aggregate: 'sum', | ||
name: 'origin' | ||
describe('domain()', function() { | ||
var sortingReturn = 'sorted', | ||
sorting = { | ||
getDataset: function() {return 'sorted';} | ||
}; | ||
it('should return correct stack', function() { | ||
var domain = vlscale.domain('y', Encoding.fromSpec({ | ||
encoding: { | ||
y: { | ||
name: 'origin' | ||
} | ||
} | ||
} | ||
}), {}, { | ||
stack: 'y', | ||
facet: true | ||
}), {}, {}, { | ||
stack: 'y', | ||
facet: true | ||
}); | ||
expect(domain).to.eql({ | ||
data: 'stacked', | ||
field: 'data.max_sum_origin' | ||
}); | ||
}); | ||
expect(scale).to.eql({ | ||
data: 'stacked', | ||
field: 'data.max_sum_sum_origin' | ||
it('should return correct aggregated stack', function() { | ||
var domain = vlscale.domain('y', Encoding.fromSpec({ | ||
encoding: { | ||
y: { | ||
aggregate: 'sum', | ||
name: 'origin' | ||
} | ||
} | ||
}), {}, {}, { | ||
stack: 'y', | ||
facet: true | ||
}); | ||
expect(domain).to.eql({ | ||
data: 'stacked', | ||
field: 'data.max_sum_sum_origin' | ||
}); | ||
}); | ||
}); | ||
// TODO test other cases | ||
}); | ||
it('should return the right domain if binned Q', | ||
function() { | ||
var domain = vlscale.domain('y', Encoding.fromSpec({ | ||
encoding: { | ||
y: { | ||
bin: {maxbins: 15}, | ||
name: 'origin', | ||
scale: {useRawDomain: true}, | ||
type: Q | ||
} | ||
} | ||
}), {origin: {min: -5, max:48}}, sorting, {}); | ||
describe('vl.compile.scale.color.palette', function() { | ||
it('should return tableau categories', function() { | ||
expect(vlscale.color.palette('category10k')).to.eql( | ||
['#2ca02c', '#e377c2', '#7f7f7f', '#17becf', '#8c564b', '#d62728', '#bcbd22', | ||
'#9467bd', '#ff7f0e', '#1f77b4' | ||
] | ||
); | ||
}); | ||
expect(domain).to.eql([-5, 0, 5, 10, 15, 20, 25, 30, 35, 40, 45]); | ||
}); | ||
it('should return pre-defined brewer palette if low cardinality', function() { | ||
var brewerPalettes = util.keys(colorbrewer); | ||
brewerPalettes.forEach(function(palette) { | ||
util.range(3, 9).forEach(function(cardinality) { | ||
expect(vlscale.color.palette(palette, cardinality)).to.eql( | ||
colorbrewer[palette][cardinality] | ||
); | ||
it('should return the raw domain if useRawDomain is true for non-bin, non-sum Q', | ||
function() { | ||
var domain = vlscale.domain('y', Encoding.fromSpec({ | ||
encoding: { | ||
y: { | ||
aggregate: 'mean', | ||
name: 'origin', | ||
scale: {useRawDomain: true}, | ||
type: Q | ||
} | ||
} | ||
}), {}, {}, {}); | ||
expect(domain.data).to.eql(RAW); | ||
}); | ||
it('should return the aggregate domain for sum Q', | ||
function() { | ||
var domain = vlscale.domain('y', Encoding.fromSpec({ | ||
encoding: { | ||
y: { | ||
aggregate: 'sum', | ||
name: 'origin', | ||
scale: {useRawDomain: true}, | ||
type: Q | ||
} | ||
} | ||
}), {}, sorting, {}); | ||
expect(domain.data).to.eql(sortingReturn); | ||
}); | ||
it('should return the raw domain if useRawDomain is true for raw T', | ||
function() { | ||
var domain = vlscale.domain('y', Encoding.fromSpec({ | ||
encoding: { | ||
y: { | ||
name: 'origin', | ||
scale: {useRawDomain: true}, | ||
type: T | ||
} | ||
} | ||
}), {}, {}, {}); | ||
expect(domain.data).to.eql(RAW); | ||
}); | ||
it('should return the raw domain if useRawDomain is true for year T', | ||
function() { | ||
var domain = vlscale.domain('y', Encoding.fromSpec({ | ||
encoding: { | ||
y: { | ||
name: 'origin', | ||
scale: {useRawDomain: true}, | ||
type: T, | ||
timeUnit: 'year' | ||
} | ||
} | ||
}), {}, {}, {}); | ||
expect(domain.data).to.eql(RAW); | ||
expect(domain.field.indexOf('year')).to.gt(-1); | ||
}); | ||
it('should return the correct domain for month T', | ||
function() { | ||
var domain = vlscale.domain('y', Encoding.fromSpec({ | ||
encoding: { | ||
y: { | ||
name: 'origin', | ||
scale: {useRawDomain: true}, | ||
type: T, | ||
timeUnit: 'month' | ||
} | ||
} | ||
}), {}, sorting, {}); | ||
expect(domain).to.eql([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]); | ||
}); | ||
it('should return the aggregated domain if useRawDomain is false', function() { | ||
var domain = vlscale.domain('y', Encoding.fromSpec({ | ||
encoding: { | ||
y: { | ||
aggregate: 'min', | ||
name: 'origin', | ||
scale: {useRawDomain: false}, | ||
type: Q | ||
} | ||
} | ||
}), {}, sorting, {}); | ||
expect(domain.data).to.eql(sortingReturn); | ||
}); | ||
// TODO test other cases | ||
}); | ||
it('should return pre-defined brewer palette if high cardinality N', function() { | ||
var brewerPalettes = util.keys(colorbrewer); | ||
brewerPalettes.forEach(function(palette) { | ||
var cardinality = 20; | ||
expect(vlscale.color.palette(palette, cardinality, 'N')).to.eql( | ||
colorbrewer[palette][Math.max.apply(null, util.keys(colorbrewer[palette]))] | ||
describe('color.palette', function() { | ||
it('should return tableau categories', function() { | ||
expect(vlscale.color.palette('category10k')).to.eql( | ||
['#2ca02c', '#e377c2', '#7f7f7f', '#17becf', '#8c564b', '#d62728', '#bcbd22', | ||
'#9467bd', '#ff7f0e', '#1f77b4' | ||
] | ||
); | ||
}); | ||
}); | ||
it('should return interpolated scale if high cardinality ordinal', function() { | ||
var brewerPalettes = util.keys(colorbrewer); | ||
brewerPalettes.forEach(function(palette) { | ||
var cardinality = 20, | ||
ps = 5, | ||
p = colorbrewer[palette], | ||
interpolator = d3.interpolateLab(p[ps][0], p[ps][ps - 1]); | ||
expect(vlscale.color.palette(palette, cardinality, 'O')).to.eql( | ||
util.range(cardinality).map(function(i) { | ||
return interpolator(i * 1.0 / (cardinality - 1)); | ||
}) | ||
); | ||
it('should return pre-defined brewer palette if low cardinality', function() { | ||
var brewerPalettes = util.keys(colorbrewer); | ||
brewerPalettes.forEach(function(palette) { | ||
util.range(3, 9).forEach(function(cardinality) { | ||
expect(vlscale.color.palette(palette, cardinality)).to.eql( | ||
colorbrewer[palette][cardinality] | ||
); | ||
}); | ||
}); | ||
}); | ||
it('should return pre-defined brewer palette if high cardinality N', function() { | ||
var brewerPalettes = util.keys(colorbrewer); | ||
brewerPalettes.forEach(function(palette) { | ||
var cardinality = 20; | ||
expect(vlscale.color.palette(palette, cardinality, 'N')).to.eql( | ||
colorbrewer[palette][Math.max.apply(null, util.keys(colorbrewer[palette]))] | ||
); | ||
}); | ||
}); | ||
it('should return interpolated scale if high cardinality ordinal', function() { | ||
var brewerPalettes = util.keys(colorbrewer); | ||
brewerPalettes.forEach(function(palette) { | ||
var cardinality = 20, | ||
ps = 5, | ||
p = colorbrewer[palette], | ||
interpolator = d3.interpolateLab(p[ps][0], p[ps][ps - 1]); | ||
expect(vlscale.color.palette(palette, cardinality, 'O')).to.eql( | ||
util.range(cardinality).map(function(i) { | ||
return interpolator(i * 1.0 / (cardinality - 1)); | ||
}) | ||
); | ||
}); | ||
}); | ||
}); | ||
}); | ||
describe('vl.compile.scale.color.interpolate', function() { | ||
it('should interpolate color along the lab space', function() { | ||
var interpolator = d3.interpolateLab('#ffffff', '#000000'), | ||
cardinality = 8; | ||
describe('color.interpolate', function() { | ||
it('should interpolate color along the lab space', function() { | ||
var interpolator = d3.interpolateLab('#ffffff', '#000000'), | ||
cardinality = 8; | ||
expect(vlscale.color.interpolate('#ffffff', '#000000', cardinality)) | ||
.to.eql( | ||
util.range(cardinality).map(function(i) { | ||
return interpolator(i * 1.0 / (cardinality - 1)); | ||
}) | ||
); | ||
expect(vlscale.color.interpolate('#ffffff', '#000000', cardinality)) | ||
.to.eql( | ||
util.range(cardinality).map(function(i) { | ||
return interpolator(i * 1.0 / (cardinality - 1)); | ||
}) | ||
); | ||
}); | ||
}); | ||
}); |
@@ -6,15 +6,4 @@ 'use strict'; | ||
var compile = require('../../src/vl').compile, | ||
util = require('../../src/util'); | ||
var compile = require('../../src/vl').compile; | ||
// mock util.getbins() | ||
util.getbins = function() { | ||
return { | ||
start: 0, | ||
stop: 10, | ||
step: 1 | ||
}; | ||
}; | ||
var stats = { | ||
@@ -21,0 +10,0 @@ 'Cost__Total_$': { |
@@ -8,3 +8,3 @@ 'use strict'; | ||
describe('Time', function() { | ||
describe('time', function() { | ||
var fieldName = 'a', | ||
@@ -20,7 +20,7 @@ timeUnit = 'month', | ||
it('should add formula transform', function() { | ||
var data = spec.data[1]; | ||
expect(data.transform).to.be.ok(); | ||
var data = spec.data[0]; | ||
expect(data.transform).to.be.ok; | ||
expect(data.transform.filter(function(t) { | ||
return t.type === 'formula' && t.field === encoding.field('x') && | ||
return t.type === 'formula' && t.field === encoding.fieldRef('x') && | ||
t.expr === time.formula(encoding._enc.x); | ||
@@ -35,2 +35,30 @@ }).length).to.be.above(0); | ||
}); | ||
describe('maxLength', function(){ | ||
it('should return max length based on time format', function () { | ||
expect(time.maxLength(undefined /*no timeUnit*/, { | ||
config: function(){ return '%A %B %e %H:%M:%S %Y';} | ||
})) | ||
.to.eql('Wednesday September 17 04:00:00 2014'.length); | ||
}); | ||
it('should return max length of the month custom scale', function () { | ||
expect(time.maxLength('month', Encoding.fromSpec({mark: 'point'}))) | ||
.to.eql(3); | ||
}); | ||
it('should return max length of the day custom scale', function () { | ||
expect(time.maxLength('day', Encoding.fromSpec({mark: 'point'}))) | ||
.to.eql(3); | ||
}); | ||
it('should return max length of the month custom scale', function () { | ||
expect(time.maxLength('month', Encoding.fromSpec({ | ||
mark: 'point', | ||
config: { | ||
timeScaleLabelLength: 0 | ||
} | ||
}))).to.eql(9); | ||
}); | ||
}); | ||
}); |
@@ -11,4 +11,4 @@ 'use strict'; | ||
var encoding = Encoding.fromShorthand(shorthand); | ||
expect(encoding.has('y')).ok(); | ||
expect(encoding.has('x')).ok(); | ||
expect(encoding.has('y')).ok; | ||
expect(encoding.has('x')).ok; | ||
@@ -15,0 +15,0 @@ }); |
@@ -23,3 +23,3 @@ 'use strict'; | ||
var cardinality = vlfield.cardinality(field, stats); | ||
expect(cardinality).to.equal(10); | ||
expect(cardinality).to.equal(15); | ||
}); | ||
@@ -26,0 +26,0 @@ }); |
Sorry, the diff of this file is too big to display
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
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
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
Uses eval
Supply chain riskPackage uses eval() which is a dangerous function. This prevents the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
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
High entropy strings
Supply chain riskContains high entropy strings. This could be a sign of encrypted data, leaked secrets or obfuscated code.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
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
11
0
8398282
5
97
124996
+ Addedd3-format@^0.2.3
+ Addedd3-time-format@0.0.2
+ Addedd3-format@0.2.3(transitive)
+ Addedd3-time-format@0.0.2(transitive)
Updateddatalib@^1.3.0