Repository: vega/vega-lite
Pull request #622 (2015-10-07 17:20:30)

Refactor
- field.js => encDef.js
- eliminate `encoding.fieldName()` and just use `encoding.field(...).name`
- fieldName => field

Please merge #621 first !  (See [Branch Specific Diff](https://github.com/uwdata/vega-lite/compare/kw/vg2-stack...kw/field))


Patch:
diff --git a/editor/bower.json b/editor/bower.json
index 12708b49b..c85e79739 100644
--- a/editor/bower.json
+++ b/editor/bower.json
@@ -15,6 +15,6 @@
     "tests"
   ],
   "dependencies": {
-    "vega": "^1.5.4"
+    "vega": "^2.2.6"
   }
 }
diff --git a/editor/editor.js b/editor/editor.js
index fc4e4bb3d..e8486aca0 100644
--- a/editor/editor.js
+++ b/editor/editor.js
@@ -204,9 +204,9 @@ vled.init = function() {
     document.getElementById('vlspec').value = JSON.stringify({
       marktype: 'point',
       encoding: {
-        x: {type: 'Q',name: 'yield',aggr: 'avg'},
+        x: {type: 'Q',name: 'yield',aggr: 'mean'},
         y: {
-          sort: [{name: 'yield', aggr: 'avg', reverse: false}],
+          sort: [{name: 'yield', aggr: 'mean', reverse: false}],
           type: 'N',
           name: 'variety'
         },
diff --git a/gallery/bower.json b/gallery/bower.json
index fc0b3cc8b..835ae31ad 100644
--- a/gallery/bower.json
+++ b/gallery/bower.json
@@ -15,7 +15,7 @@
     "tests"
   ],
   "dependencies": {
-    "vega": "^1.0.0",
+    "vega": "^2.2.6",
     "angular": "^1.4.3"
   }
 }
diff --git a/gallery/gallery.js b/gallery/gallery.js
index 4cb56a5d9..bcd5c4199 100644
--- a/gallery/gallery.js
+++ b/gallery/gallery.js
@@ -10,8 +10,8 @@ var EXAMPLES = [
       data: {
         values: [
           {'x':'A', 'y':28}, {'x':'B', 'y':55}, {'x':'C', 'y':43},
-          {'x':'D', 'y':91}, {'x':'E', 'y':81}, {'x':'F', 'y':53},
-          {'x':'G', 'y':19}, {'x':'H', 'y':87}, {'x':'I', 'y':52}
+          {'x':'G', 'y':19}, {'x':'H', 'y':87}, {'x':'I', 'y':52},
+          {'x':'D', 'y':91}, {'x':'E', 'y':81}, {'x':'F', 'y':53}
         ]
       },
       marktype: 'bar',
@@ -37,7 +37,7 @@ var EXAMPLES = [
       marktype: 'line',
       encoding: {
         x: {'name': 'Year','type': 'T','timeUnit': 'year'},
-        y: {'name': 'Horsepower','type': 'Q','aggregate': 'avg'}
+        y: {'name': 'Horsepower','type': 'Q','aggregate': 'mean'}
       },
       data: {'url': 'data/cars.json'}
     }
@@ -53,15 +53,36 @@ var EXAMPLES = [
       },
       data: {'url': 'data/cars.json'}
     }
+  },{
+    title: 'Aggregate Bar Chart',
+    spec: {
+      'marktype': 'bar',
+      'encoding': {
+        'x': {'name': 'Cylinders','type': 'O'},
+        'y': {'name': 'Acceleration','type': 'Q','aggregate': 'mean'}
+      },
+      'data': {'url': 'data/cars.json'}
+    }
+  },{
+    title: 'Stacked Bar Chart',
+    spec: {
+      'marktype': 'bar',
+      'encoding': {
+        'x': {'name': 'variety','type': 'N'},
+        'y': {'name': 'yield','type': 'Q','aggregate': 'sum'},
+        'color': {'name': 'site', 'type': 'N'}
+      },
+      'data': {'url': 'data/barley.json'}
+    }
   },{
     title: 'Barleys',
     spec: {
       data: {url: 'data/barley.json'},
       marktype: 'point',
       encoding: {
-        x: {type: 'Q',name: 'yield',aggregate: 'avg'},
+        x: {type: 'Q',name: 'yield', aggregate: 'mean'},
         y: {
-          sort: [{name: 'yield',aggregate: 'avg',reverse: false}],
+          sort: {field: 'yield', op: 'mean'},
           type: 'O',
           name: 'variety'
         },
@@ -112,8 +133,8 @@ var EXAMPLES = [
       'marktype': 'text',
       'encoding': {
         'row': {'name': 'Origin','type': 'O'},
-        'col': {'axis': {'maxLabelLength': 25},'name': 'Cylinders','type': 'O'},
-        'color': {'name': 'Horsepower','type': 'Q','aggregate': 'avg'},
+        'col': {'name': 'Cylinders','type': 'O'},
+        'color': {'name': 'Horsepower','type': 'Q','aggregate': 'mean'},
         'text': {'name': '*','type': 'Q','aggregate': 'count'}
       },
       'data': {'url': 'data/cars.json'}
@@ -125,30 +146,7 @@ var EXAMPLES = [
       'encoding': {
         'x': {'name': 'Year','type': 'T','timeUnit': 'year'},
         'y': {'name': 'Weight_in_lbs','type': 'Q','aggregate': 'sum'},
-        'color': {
-          'scale': {'quantitativeRange': ['#AFC6A3','#09622A']},
-          'name': 'Cylinders',
-          'type': 'O'
-        }
-      },
-      'data': {'url': 'data/cars.json'}
-    }
-  },{
-    title: 'Grouped Bar Chart',
-    spec: {
-      'marktype': 'bar',
-      'encoding': {
-        'x': {
-          'name': 'Acceleration',
-          'type': 'Q',
-          'aggregate': 'median'
-        },
-        'y': {'name': 'Origin','type': 'N'},
-        'row': {
-          'axis': {'maxLabelLength': 12},
-          'name': 'Cylinders',
-          'type': 'O'
-        }
+        'color': {'name': 'Cylinders', 'type': 'O'}
       },
       'data': {'url': 'data/cars.json'}
     }
diff --git a/src/Encoding.js b/src/Encoding.js
index 9fa5d2c8a..d056b0062 100644
--- a/src/Encoding.js
+++ b/src/Encoding.js
@@ -4,7 +4,7 @@ require('./globals');
 
 var consts = require('./consts'),
   util = require('./util'),
-  vlfield = require('./field'),
+  vlEncDef = require('./encdef'),
   vlenc = require('./enc'),
   schema = require('./schema/schema');
 
@@ -18,7 +18,6 @@ module.exports = (function() {
     this._enc = specExtended.encoding;
     this._config = specExtended.config;
     this._filter = specExtended.filter;
-    // this._vega2 = true;
   }
 
   var proto = Encoding.prototype;
@@ -95,41 +94,18 @@ module.exports = (function() {
     return this._enc[encType].name !== undefined;
   };
 
-  proto.field = function(et) {
+  proto.encDef = function(et) {
     return this._enc[et];
   };
 
   proto.filter = function() {
-    var filterNull = [],
-      fields = this.fields(),
-      self = this;
-
-    util.forEach(fields, function(fieldList, fieldName) {
-      if (fieldName === '*') return; //count
-
-      if ((self.config('filterNull').Q && fieldList.containsType[Q]) ||
-          (self.config('filterNull').T && fieldList.containsType[T]) ||
-          (self.config('filterNull').O && fieldList.containsType[O]) ||
-          (self.config('filterNull').N && fieldList.containsType[N])) {
-        filterNull.push({
-          operands: [fieldName],
-          operator: 'notNull'
-        });
-      }
-    });
-
-    return filterNull.concat(this._filter);
+    return this._filter;
   };
 
   // get "field" reference for vega
   proto.fieldRef = function(et, opt) {
     opt = opt || {};
-    opt.data = !this._vega2 && (opt.data !== false);
-    return vlfield.fieldRef(this._enc[et], opt);
-  };
-
-  proto.fieldName = function(et) {
-    return this._enc[et].name;
+    return vlEncDef.fieldRef(this._enc[et], opt);
   };
 
   /*
@@ -140,13 +116,12 @@ module.exports = (function() {
   };
 
   proto.fieldTitle = function(et) {
-    if (vlfield.isCount(this._enc[et])) {
-      return vlfield.count.displayName;
+    if (vlEncDef.isCount(this._enc[et])) {
+      return vlEncDef.count.displayName;
     }
     var fn = this._enc[et].aggregate || this._enc[et].timeUnit || (this._enc[et].bin && 'bin');
     if (fn) {
-      var uppercase = fn === 'avg' ? 'MEAN' :fn.toUpperCase();
-      return uppercase + '(' + this._enc[et].name + ')';
+      return fn.toUpperCase() + '(' + this._enc[et].name + ')';
     } else {
       return this._enc[et].name;
     }
@@ -167,7 +142,7 @@ module.exports = (function() {
       (encType === X && this.has(COL) && this.has(X));
 
     // if band.size is explicitly specified, follow the specification, otherwise draw value from config.
-    return this.field(encType).band.size ||
+    return this.encDef(encType).band.size ||
       this.config(useSmallBand ? 'smallBandSize' : 'largeBandSize');
   };
 
@@ -200,7 +175,7 @@ module.exports = (function() {
   proto.sort = function(et, stats) {
     var sort = this._enc[et].sort,
       enc = this._enc,
-      isTypes = vlfield.isTypes;
+      isTypes = vlEncDef.isTypes;
 
     if ((!sort || sort.length===0) &&
         // FIXME
@@ -239,26 +214,26 @@ module.exports = (function() {
   };
 
   proto.isType = function(et, type) {
-    var field = this.field(et);
-    return field && vlfield.isType(field, type);
+    var encDef = this.encDef(et);
+    return encDef && vlEncDef.isType(encDef, type);
   };
 
 
   proto.isTypes = function(et, type) {
-    var field = this.field(et);
-    return field && vlfield.isTypes(field, type);
+    var encDef = this.encDef(et);
+    return encDef && vlEncDef.isTypes(encDef, type);
   };
 
   Encoding.isOrdinalScale = function(encoding, encType) {
-    return vlfield.isOrdinalScale(encoding.field(encType));
+    return vlEncDef.isOrdinalScale(encoding.encDef(encType));
   };
 
   Encoding.isDimension = function(encoding, encType) {
-    return vlfield.isDimension(encoding.field(encType));
+    return vlEncDef.isDimension(encoding.encDef(encType));
   };
 
   Encoding.isMeasure = function(encoding, encType) {
-    return vlfield.isMeasure(encoding.field(encType));
+    return vlEncDef.isMeasure(encoding.encDef(encType));
   };
 
   proto.isOrdinalScale = function(encType) {
@@ -322,7 +297,7 @@ module.exports = (function() {
   };
 
   proto.cardinality = function(encType, stats) {
-    return vlfield.cardinality(this.field(encType), stats, this.config('filterNull'));
+    return vlEncDef.cardinality(this.encDef(encType), stats, this.config('filterNull'));
   };
 
   proto.isRaw = function() {
@@ -375,7 +350,7 @@ module.exports = (function() {
 
   Encoding.toggleSort.support = function(spec, stats) {
     var enc = spec.encoding,
-      isTypes = vlfield.isTypes;
+      isTypes = vlEncDef.isTypes;
 
     if (vlenc.has(enc, ROW) || vlenc.has(enc, COL) ||
       !vlenc.has(enc, X) || !vlenc.has(enc, Y) ||
@@ -383,8 +358,8 @@ module.exports = (function() {
       return false;
     }
 
-    return ( isTypes(enc.x, [N,O]) && vlfield.isMeasure(enc.y)) ? 'x' :
-      ( isTypes(enc.y, [N,O]) && vlfield.isMeasure(enc.x)) ? 'y' : false;
+    return ( isTypes(enc.x, [N,O]) && vlEncDef.isMeasure(enc.y)) ? 'x' :
+      ( isTypes(enc.y, [N,O]) && vlEncDef.isMeasure(enc.x)) ? 'y' : false;
   };
 
   Encoding.toggleFilterNullO = function(spec) {
diff --git a/src/compiler/axis.js b/src/compiler/axis.js
index 759fae986..66e6b0d21 100644
--- a/src/compiler/axis.js
+++ b/src/compiler/axis.js
@@ -18,7 +18,7 @@ axis.def = function(name, encoding, layout, stats, opt) {
     type: type,
     scale: name,
     properties: {},
-    layer: encoding.field(name).axis.layer,
+    layer: encoding.encDef(name).axis.layer,
     orient: axis.orient(name, encoding, stats)
   };
 
@@ -34,7 +34,7 @@ axis.def = function(name, encoding, layout, stats, opt) {
       // TODO(kanitw): Jul 19, 2015 - #506 add condition for rotation
       def = axis.labels.rotate(def);
     } else { // Q
-      def.ticks = encoding.field(name).axis.ticks;
+      def.ticks = encoding.encDef(name).axis.ticks;
     }
   }
 
@@ -54,7 +54,7 @@ axis.def = function(name, encoding, layout, stats, opt) {
 };
 
 axis.orient = function(name, encoding, stats) {
-  var orient = encoding.field(name).axis.orient;
+  var orient = encoding.encDef(name).axis.orient;
   if (orient) return orient;
 
   if (name === COL) return 'top';
@@ -101,9 +101,9 @@ axis.grid = function(def, name, encoding, layout) {
           value: def.offset
         },
         x2: {
+          field: {group: 'mark.group.width'},
           offset: def.offset + (layout.cellWidth * 0.05),
           // default value(s) -- vega doesn't do recursive merge
-          group: 'mark.group.width',
           mult: 1
         },
         stroke: { value: encoding.config('cellGridColor') },
@@ -127,7 +127,7 @@ axis.hideTicks = function(def) {
 };
 
 axis.title = function (def, name, encoding, layout) {
-  var ax = encoding.field(name).axis;
+  var ax = encoding.encDef(name).axis;
 
   if (ax.title) {
     def.title = ax.title;
@@ -164,7 +164,7 @@ axis.labels = {};
 /** add custom label for time type and bin */
 axis.labels.scale = function(def, encoding, name) {
   // time
-  var timeUnit = encoding.field(name).timeUnit;
+  var timeUnit = encoding.encDef(name).timeUnit;
   if (encoding.isType(name, T) && timeUnit && (time.hasScale(timeUnit))) {
     setter(def, ['properties','labels','text','scale'], 'time-'+ timeUnit);
   }
@@ -176,14 +176,14 @@ axis.labels.scale = function(def, encoding, name) {
  * 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];
+  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.field(name).timeUnit;
+    var timeUnit = encoding.encDef(name).timeUnit;
     if (!timeUnit) {
       def.format = encoding.config('timeFormat');
     } else if (timeUnit === 'year') {
@@ -192,8 +192,9 @@ axis.labels.format = function (def, name, encoding, stats) {
   } else if (encoding.isTypes(name, [N, O]) && encoding.axis(name).maxLabelLength) {
     setter(def,
       ['properties','labels','text','template'],
-      '{{data | truncate:' + encoding.axis(name).maxLabelLength + '}}'
-      );
+      '{{ datum.data | truncate:' +
+      encoding.axis(name).maxLabelLength + '}}'
+    );
   }
 
   return def;
diff --git a/src/compiler/compiler.js b/src/compiler/compiler.js
index d4f57316e..5dafd0dc4 100644
--- a/src/compiler/compiler.js
+++ b/src/compiler/compiler.js
@@ -18,7 +18,6 @@ var Encoding = require('../Encoding'),
 compiler.data = require('./data');
 compiler.facet = require('./facet');
 compiler.layout = require('./layout');
-compiler.sort = require('./sort');
 compiler.stack = require('./stack');
 compiler.style = require('./style');
 compiler.subfacet = require('./subfacet');
@@ -54,16 +53,21 @@ compiler.compileEncoding = function (encoding, stats) {
       width: layout.width,
       height: layout.height,
       padding: 'auto',
-      data: compiler.data(encoding),
+      // FIXME(#514): eliminate stats
+      data: compiler.data(encoding, stats),
       // global scales contains only time unit scales
       scales: compiler.time.scales(encoding),
       marks: [{
-        _name: 'cell',
+        name: 'cell',
         type: 'group',
         properties: {
           enter: {
-            width: layout.cellWidth ? {value: layout.cellWidth} : {group: 'width'},
-            height: layout.cellHeight ? {value: layout.cellHeight} : {group: 'height'}
+            width: layout.cellWidth ?
+                     {value: layout.cellWidth} :
+                     {field: {group: 'width'}},
+            height: layout.cellHeight ?
+                    {value: layout.cellHeight} :
+                    {field: {group: 'height'}}
           }
         }
       }]
@@ -71,9 +75,6 @@ compiler.compileEncoding = function (encoding, stats) {
 
   var group = spec.marks[0];
 
-  // FIXME remove compiler.sort after migrating to vega 2.
-  spec.data = compiler.sort(spec.data, encoding, stats); // append new data
-
   // marks
   var style = compiler.style(encoding, stats),
     mdefs = group.marks = marks.def(encoding, layout, style, stats),
@@ -86,7 +87,7 @@ compiler.compileEncoding = function (encoding, stats) {
   var details = encoding.details(),
     stack = encoding.isAggregate() && details.length > 0 && compiler.stack(spec.data, encoding, mdef); // modify spec.data, mdef.{from,properties}
 
-  if (details.length > 0 && (stack || lineType)) {
+  if (details.length > 0 && lineType) {
     //subfacet to group stack / line together in one group
     compiler.subfacet(group, mdef, details, stack, encoding);
   }
diff --git a/src/compiler/data.js b/src/compiler/data.js
index dab1f0541..51d65e2c9 100644
--- a/src/compiler/data.js
+++ b/src/compiler/data.js
@@ -4,7 +4,7 @@ require('../globals');
 
 module.exports = data;
 
-var vlfield = require('../field'),
+var vlEncDef = require('../encdef'),
   util = require('../util'),
   time = require('./time');
 
@@ -17,8 +17,9 @@ var vlfield = require('../field'),
  *                 If the encoding contains aggregate value, this will also create
  *                 aggregate table as well.
  */
-function data(encoding) {
-  var def = [data.raw(encoding)];
+// FIXME(#514): eliminate stats
+function data(encoding, stats) {
+  var def = [data.raw(encoding, stats)];
 
   var aggregate = data.aggregate(encoding);
   if (aggregate) def.push(data.aggregate(encoding));
@@ -31,7 +32,8 @@ function data(encoding) {
   return def;
 }
 
-data.raw = function(encoding) {
+// FIXME(#514): eliminate stats
+data.raw = function(encoding, stats) {
   var raw = {name: RAW};
 
   // Data source (url or inline)
@@ -49,21 +51,21 @@ data.raw = function(encoding) {
     raw.format.parse = parse;
   }
 
-  raw.transform = data.raw.transform(encoding);
+  raw.transform = data.raw.transform(encoding, stats);
   return raw;
 };
 
 data.raw.formatParse = function(encoding) {
   var parse;
 
-  encoding.forEach(function(field) {
-    if (field.type == T) {
+  encoding.forEach(function(encDef) {
+    if (encDef.type == T) {
       parse = parse || {};
-      parse[field.name] = 'date';
-    } else if (field.type == Q) {
-      if (vlfield.isCount(field)) return;
+      parse[encDef.name] = 'date';
+    } else if (encDef.type == Q) {
+      if (vlEncDef.isCount(encDef)) return;
       parse = parse || {};
-      parse[field.name] = 'number';
+      parse[encDef.name] = 'number';
     }
   });
 
@@ -74,121 +76,125 @@ data.raw.formatParse = function(encoding) {
  * Generate Vega transforms for the raw data table.  This can include
  * transforms for time unit, binning and filtering.
  */
-data.raw.transform = function(encoding) {
+// FIXME(#514): eliminate stats
+data.raw.transform = function(encoding, stats) {
+  // 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.time(encoding).concat(
-    data.raw.transform.bin(encoding),
+  return data.raw.transform.nullFilter(encoding).concat(
+    data.raw.transform.time(encoding),
+    data.raw.transform.bin(encoding, stats),
     data.raw.transform.filter(encoding)
   );
 };
 
-var BINARY = {
-  '>':  true,
-  '>=': true,
-  '=':  true,
-  '!=': true,
-  '<':  true,
-  '<=': true
-};
-
 data.raw.transform.time = function(encoding) {
-  return encoding.reduce(function(transform, field, encType) {
-    if (field.type === T && field.timeUnit) {
+  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(field.timeUnit,
-                           encoding.fieldRef(encType, {nofn: true, d: true})
-                          )
+        expr: time.formula(encDef.timeUnit, fieldRef)
       });
     }
     return transform;
   }, []);
 };
 
-data.raw.transform.bin = function(encoding) {
-  return encoding.reduce(function(transform, field, encType) {
+// FIXME(#514): eliminate stats
+data.raw.transform.bin = function(encoding, stats) {
+  return encoding.reduce(function(transform, encDef, encType) {
     if (encoding.bin(encType)) {
       transform.push({
         type: 'bin',
-        field: encoding.fieldRef(encType, {nofn: true}),
-        output: encoding.fieldRef(encType),
-        maxbins: encoding.bin(encType).maxbins
+        field: encDef.name,
+        output: {bin: encoding.fieldRef(encType)},
+        maxbins: encoding.bin(encType).maxbins,
+        min: stats[encDef.name].min,
+        max: stats[encDef.name].max
       });
     }
     return transform;
   }, []);
 };
 
-data.raw.transform.filter = function(encoding) {
-  var filters = encoding.filter().reduce(function(f, filter) {
-    var condition = '';
-    var operator = filter.operator;
-    var operands = filter.operands;
-
-    var d = 'd.' + (encoding._vega2 ? '' : 'data.');
-
-    if (BINARY[operator]) {
-      // expects a field and a value
-      if (operator === '=') {
-        operator = '==';
+/**
+ * @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;
+    }, []);
 
-      var op1 = operands[0];
-      var op2 = operands[1];
-      condition = d + op1 + ' ' + operator + ' ' + op2;
-    } else if (operator === 'notNull') {
-      // expects a number of fields
-      for (var j=0; j<operands.length; j++) {
-        condition += d + operands[j] + '!==null';
-        if (j < operands.length - 1) {
-          condition += ' && ';
-        }
-      }
-    } else {
-      util.warn('Unsupported operator: ', operator);
-      return f;
-    }
-    f.push('(' + condition + ')');
-    return f;
-  }, []);
-  if (filters.length === 0) return [];
+  return filteredFields.length > 0 ?
+    [{
+      type: 'filter',
+      test: filteredFields.map(function(fieldName) {
+        return 'datum.' + fieldName + '!==null';
+      }).join(' && ')
+    }] : [];
+};
 
-  return [{
+data.raw.transform.filter = function(encoding) {
+  var filter = encoding.data().filter;
+  return filter ? [{
       type: 'filter',
-      test: filters.join(' && ')
-  }];
+      test: filter
+  }] : [];
 };
 
 data.aggregate = function(encoding) {
-  var dims = {}, meas = {};
-
-  encoding.forEach(function(field, encType) {
-    if (field.aggregate) {
-      if (field.aggregate === 'count') {
-        meas.count = {op: 'count', field: '*'};
-      }else {
-        meas[field.aggregate + '|' + field.name] = {
-          op: field.aggregate,
-          field: encoding.fieldRef(encType, {nofn: true})
-        };
+  /* 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 {
-      dims[field.name] = encoding.fieldRef(encType);
+      dims[encDef.name] = encoding.fieldRef(encType);
     }
   });
 
-  dims = util.vals(dims);
-  meas = util.vals(meas);
+  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 (meas.length > 0) {
+  if (hasAggregate) {
     return {
       name: AGGREGATE,
       source: RAW,
       transform: [{
         type: 'aggregate',
-        groupby: dims,
-        fields: meas
+        groupby: groupby,
+        summarize: summarize
       }]
     };
   }
@@ -197,11 +203,11 @@ data.aggregate = function(encoding) {
 };
 
 data.filterNonPositive = function(dataTable, encoding) {
-  encoding.forEach(function(field, encType) {
+  encoding.forEach(function(encDef, encType) {
     if (encoding.scale(encType).type === 'log') {
       dataTable.transform.push({
         type: 'filter',
-        test: encoding.fieldRef(encType, {d: 1}) + ' > 0'
+        test: encoding.fieldRef(encType, {datum: 1}) + ' > 0'
       });
     }
   });
diff --git a/src/compiler/facet.js b/src/compiler/facet.js
index be9a389d3..249ba15d3 100644
--- a/src/compiler/facet.js
+++ b/src/compiler/facet.js
@@ -12,15 +12,15 @@ module.exports = faceting;
 function groupdef(name, opt) {
   opt = opt || {};
   return {
-    _name: name || undefined,
+    name: name || undefined,
     type: 'group',
     from: opt.from,
     properties: {
       enter: {
         x: opt.x || undefined,
         y: opt.y || undefined,
-        width: opt.width || {group: 'width'},
-        height: opt.height || {group: 'height'}
+        width: opt.width || {field: {group: 'width'}},
+        height: opt.height || {field: {group: 'height'}}
       }
     },
     scales: opt.scales || undefined,
@@ -54,7 +54,7 @@ function faceting(group, encoding, layout, spec, singleScaleNames, stack, stats)
     if (!encoding.isDimension(ROW)) {
       util.error('Row encoding should be ordinal.');
     }
-    enter.y = {scale: ROW, field: 'keys.' + facetKeys.length};
+    enter.y = {scale: ROW, field: encoding.encDef(ROW).name};
     enter.height = {'value': layout.cellHeight}; // HACK
 
     facetKeys.push(encoding.fieldRef(ROW));
@@ -62,7 +62,7 @@ function faceting(group, encoding, layout, spec, singleScaleNames, stack, stats)
     if (hasCol) {
       from = util.duplicate(group.from);
       from.transform = from.transform || [];
-      from.transform.unshift({type: 'facet', keys: [encoding.fieldRef(COL)]});
+      from.transform.unshift({type: 'facet', groupby: [encoding.fieldRef(COL)]});
     }
 
     axesGrp = groupdef('x-axes', {
@@ -86,7 +86,7 @@ function faceting(group, encoding, layout, spec, singleScaleNames, stack, stats)
     if (!encoding.isDimension(COL)) {
       util.error('Col encoding should be ordinal.');
     }
-    enter.x = {scale: COL, field: 'keys.' + facetKeys.length};
+    enter.x = {scale: COL, field: encoding.encDef(COL).name};
     enter.width = {'value': layout.cellWidth}; // HACK
 
     facetKeys.push(encoding.fieldRef(COL));
@@ -94,7 +94,7 @@ function faceting(group, encoding, layout, spec, singleScaleNames, stack, stats)
     if (hasRow) {
       from = util.duplicate(group.from);
       from.transform = from.transform || [];
-      from.transform.unshift({type: 'facet', keys: [encoding.fieldRef(ROW)]});
+      from.transform.unshift({type: 'facet', groupby: [encoding.fieldRef(ROW)]});
     }
 
     axesGrp = groupdef('y-axes', {
@@ -130,7 +130,7 @@ function faceting(group, encoding, layout, spec, singleScaleNames, stack, stats)
 
   // add facet transform
   var trans = (group.from.transform || (group.from.transform = []));
-  trans.unshift({type: 'facet', keys: facetKeys});
+  trans.unshift({type: 'facet', groupby: facetKeys});
 
   return spec;
 }
diff --git a/src/compiler/layout.js b/src/compiler/layout.js
index 8290c0044..e57858ce6 100644
--- a/src/compiler/layout.js
+++ b/src/compiler/layout.js
@@ -41,9 +41,9 @@ function box(encoding, stats) {
   if (hasX) {
     if (encoding.isOrdinalScale(X)) {
       // for ordinal, hasCol or not doesn't matter -- we scale based on cardinality
-      cellWidth = (xCardinality + encoding.field(X).band.padding) * encoding.bandSize(X, useSmallBand);
+      cellWidth = (xCardinality + encoding.encDef(X).band.padding) * encoding.bandSize(X, useSmallBand);
     } else {
-      cellWidth = hasCol || hasRow ? encoding.field(COL).width :  encoding.config('singleWidth');
+      cellWidth = hasCol || hasRow ? encoding.encDef(COL).width :  encoding.config('singleWidth');
     }
   } else {
     if (marktype === TEXT) {
@@ -57,9 +57,9 @@ function box(encoding, stats) {
   if (hasY) {
     if (encoding.isOrdinalScale(Y)) {
       // for ordinal, hasCol or not doesn't matter -- we scale based on cardinality
-      cellHeight = (yCardinality + encoding.field(Y).band.padding) * encoding.bandSize(Y, useSmallBand);
+      cellHeight = (yCardinality + encoding.encDef(Y).band.padding) * encoding.bandSize(Y, useSmallBand);
     } else {
-      cellHeight = hasCol || hasRow ? encoding.field(ROW).height :  encoding.config('singleHeight');
+      cellHeight = hasCol || hasRow ? encoding.encDef(ROW).height :  encoding.config('singleHeight');
     }
   } else {
     cellHeight = encoding.bandSize(Y);
@@ -101,16 +101,16 @@ function getMaxNumberLength(encoding, et, fieldStats) {
 
 // TODO(#600) revise this
 function getMaxLength(encoding, stats, et) {
-  var field = encoding.field(et),
-    fieldStats = stats[field.name];
+  var encDef = encoding.encDef(et),
+    fieldStats = stats[encDef.name];
 
-  if (field.bin) {
+  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.field(et).timeUnit, encoding);
+    return time.maxLength(encoding.encDef(et).timeUnit, encoding);
   } else if (encoding.isTypes(et, [N, O])) {
     if(fieldStats.type === 'number') {
       return getMaxNumberLength(encoding, et, fieldStats);
diff --git a/src/compiler/legend.js b/src/compiler/legend.js
index ebd28bf00..4eb5c6315 100644
--- a/src/compiler/legend.js
+++ b/src/compiler/legend.js
@@ -12,36 +12,32 @@ var legend = module.exports = {};
 legend.defs = function(encoding, style) {
   var defs = [];
 
-  if (encoding.has(COLOR) && encoding.field(COLOR).legend) {
+  if (encoding.has(COLOR) && encoding.encDef(COLOR).legend) {
     defs.push(legend.def(COLOR, encoding, {
-      fill: COLOR,
-      orient: 'right'
+      fill: COLOR
     }, style));
   }
 
-  if (encoding.has(SIZE) && encoding.field(SIZE).legend) {
+  if (encoding.has(SIZE) && encoding.encDef(SIZE).legend) {
     defs.push(legend.def(SIZE, encoding, {
-      size: SIZE,
-      orient: defs.length === 1 ? 'left' : 'right'
+      size: SIZE
     }, style));
   }
 
-  if (encoding.has(SHAPE) && encoding.field(SHAPE).legend) {
-    if (defs.length === 2) {
-      console.error('Vega-lite currently only supports two legends');
-    }
+  if (encoding.has(SHAPE) && encoding.encDef(SHAPE).legend) {
     defs.push(legend.def(SHAPE, encoding, {
-      shape: SHAPE,
-      orient: defs.length === 1 ? 'left' : 'right'
+      shape: SHAPE
     }, style));
   }
   return defs;
 };
 
 legend.def = function(name, encoding, def, style) {
-  var timeUnit = encoding.field(name).timeUnit;
+  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) &&
@@ -72,7 +68,7 @@ legend.style = function(name, e, def, style) {
       /* fall through */
     case 'point':
       // fill or stroke
-      if (e.field(SHAPE).filled) {
+      if (e.encDef(SHAPE).filled) {
         if (e.has(COLOR) && name === COLOR) {
           symbols.fill = {scale: COLOR, field: 'data'};
         } else {
@@ -96,7 +92,7 @@ legend.style = function(name, e, def, style) {
       break;
   }
 
-  var opacity = e.field(COLOR).opacity || style.opacity;
+  var opacity = e.encDef(COLOR).opacity || style.opacity;
   if (opacity) {
     symbols.opacity = {value: opacity};
   }
@@ -104,7 +100,7 @@ legend.style = function(name, e, def, style) {
 };
 
 legend.title = function(name, encoding) {
-  var leg = encoding.field(name).legend;
+  var leg = encoding.encDef(name).legend;
 
   if (leg.title) return leg.title;
 
diff --git a/src/compiler/marks.js b/src/compiler/marks.js
index 355433b25..55fe2f140 100644
--- a/src/compiler/marks.js
+++ b/src/compiler/marks.js
@@ -130,12 +130,15 @@ function bar_props(e, layout, style) {
   // y's & height
   if (e.isMeasure(Y)) {
     p.y = {scale: Y, field: e.fieldRef(Y)};
-    p.y2 = {group: 'height'};
+    p.y2 = {field: {group: 'height'}};
   } else {
     if (e.has(Y)) { // is ordinal
       p.yc = {scale: Y, field: e.fieldRef(Y)};
     } else {
-      p.y2 = {group: 'height', offset: -e.config('singleBarOffset')};
+      p.y2 = {
+        field: {group: 'height'},
+        offset: -e.config('singleBarOffset')
+      };
     }
 
     if (e.has(SIZE)) {
@@ -156,7 +159,7 @@ function bar_props(e, layout, style) {
   }
 
   // opacity
-  var opacity = e.field(COLOR).opacity;
+  var opacity = e.encDef(COLOR).opacity;
   if (opacity) p.opacity = {value: opacity};
 
   return p;
@@ -194,7 +197,7 @@ function point_props(e, layout, style) {
   }
 
   // fill or stroke
-  if (e.field(SHAPE).filled) {
+  if (e.encDef(SHAPE).filled) {
     if (e.has(COLOR)) {
       p.fill = {scale: COLOR, field: e.fieldRef(COLOR)};
     } else if (!e.has(COLOR)) {
@@ -210,7 +213,7 @@ function point_props(e, layout, style) {
   }
 
   // opacity
-  var opacity = e.field(COLOR).opacity || style.opacity;
+  var opacity = e.encDef(COLOR).opacity || style.opacity;
   if (opacity) p.opacity = {value: opacity};
 
   return p;
@@ -231,7 +234,7 @@ function line_props(e,layout, style) {
   if (e.has(Y)) {
     p.y = {scale: Y, field: e.fieldRef(Y)};
   } else if (!e.has(Y)) {
-    p.y = {group: 'height'};
+    p.y = {field: {group: 'height'}};
   }
 
   // stroke
@@ -241,7 +244,7 @@ function line_props(e,layout, style) {
     p.stroke = {value: e.value(COLOR)};
   }
 
-  var opacity = e.field(COLOR).opacity;
+  var opacity = e.encDef(COLOR).opacity;
   if (opacity) p.opacity = {value: opacity};
 
   p.strokeWidth = {value: e.config('strokeWidth')};
@@ -273,7 +276,7 @@ function area_props(e, layout, style) {
   } else if (e.has(Y)) {
     p.y = {scale: Y, field: e.fieldRef(Y)};
   } else {
-    p.y = {group: 'height'};
+    p.y = {field: {group: 'height'}};
   }
 
   // fill
@@ -283,7 +286,7 @@ function area_props(e, layout, style) {
     p.fill = {value: e.value(COLOR)};
   }
 
-  var opacity = e.field(COLOR).opacity;
+  var opacity = e.encDef(COLOR).opacity;
   if (opacity) p.opacity = {value: opacity};
 
   return p;
@@ -333,7 +336,7 @@ function tick_props(e, layout, style) {
     p.fill = {value: e.value(COLOR)};
   }
 
-  var opacity = e.field(COLOR).opacity  || style.opacity;
+  var opacity = e.encDef(COLOR).opacity  || style.opacity;
   if(opacity) p.opacity = {value: opacity};
 
   return p;
@@ -374,7 +377,7 @@ function filled_point_props(shape) {
       p.fill = {value: e.value(COLOR)};
     }
 
-    var opacity = e.field(COLOR).opacity  || style.opacity;
+    var opacity = e.encDef(COLOR).opacity  || style.opacity;
     if(opacity) p.opacity = {value: opacity};
 
     return p;
@@ -383,7 +386,7 @@ function filled_point_props(shape) {
 
 function text_props(e, layout, style, stats) {
   var p = {},
-    field = e.field(TEXT);
+    encDef = e.encDef(TEXT);
 
   // x
   if (e.has(X)) {
@@ -407,36 +410,36 @@ function text_props(e, layout, style, stats) {
   if (e.has(SIZE)) {
     p.fontSize = {scale: SIZE, field: e.fieldRef(SIZE)};
   } else if (!e.has(SIZE)) {
-    p.fontSize = {value: field.font.size};
+    p.fontSize = {value: encDef.font.size};
   }
 
   // fill
   // color should be set to background
-  p.fill = {value: field.color};
+  p.fill = {value: encDef.color};
 
-  var opacity = e.field(COLOR).opacity  || style.opacity;
+  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.fieldName(TEXT)],
-        numberFormat = field.format || e.numberFormat(fieldStats);
+      var fieldStats = stats[e.encDef(TEXT).name],
+        numberFormat = encDef.format || e.numberFormat(fieldStats);
 
       p.text = {template: '{{' + e.fieldRef(TEXT) + ' | number:\'' +
         numberFormat +'\'}}'};
-      p.align = {value: field.align};
+      p.align = {value: encDef.align};
     } else {
       p.text = {field: e.fieldRef(TEXT)};
     }
   } else {
-    p.text = {value: field.placeholder};
+    p.text = {value: encDef.placeholder};
   }
 
-  p.font = {value: field.font.family};
-  p.fontWeight = {value: field.font.weight};
-  p.fontStyle = {value: field.font.style};
-  p.baseline = {value: field.baseline};
+  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;
 }
diff --git a/src/compiler/scale.js b/src/compiler/scale.js
index 186a72adf..4c66a8e3d 100644
--- a/src/compiler/scale.js
+++ b/src/compiler/scale.js
@@ -4,8 +4,7 @@ var util = require('../util'),
   time = require('./time'),
   colorbrewer = require('colorbrewer'),
   interpolate = require('d3-color').interpolateHsl,
-  schema = require('../schema/schema'),
-  vlsort = require('./sort');
+  schema = require('../schema/schema');
 
 var scale = module.exports = {};
 
@@ -20,197 +19,206 @@ scale.defs = function(names, encoding, layout, stats, opt) {
   opt = opt || {};
 
   return names.reduce(function(a, name) {
-    var s = {
+    var scaleDef = {
       name: name,
-      type: scale.type(name, encoding),
-      domain: scale.domain(name, encoding, stats, opt)
+      type: scale.type(name, encoding)
     };
 
-    s.sort = scale.sort(s, encoding, name) || undefined;
+    scaleDef.domain = scale.domain(scaleDef, encoding, stats, opt);
+    scaleDef = scale.range(scaleDef, encoding, layout, stats, opt);
 
-    scale.range(s, encoding, layout, stats, opt);
-
-    return (a.push(s), a);
+    return (a.push(scaleDef), a);
   }, []);
 };
 
-scale.sort = function(s, encoding, name) {
-  return s.type === 'ordinal' && (
-    !!encoding.bin(name) ||
-    encoding.sort(name).length === 0
-  );
-};
-
 scale.type = function(name, encoding) {
 
   switch (encoding.type(name)) {
     case N: //fall through
     case O: return 'ordinal';
     case T:
-      var timeUnit = encoding.field(name).timeUnit;
+      var timeUnit = encoding.encDef(name).timeUnit;
       return timeUnit ? time.scale.type(timeUnit, name) : 'time';
     case Q:
       if (encoding.bin(name)) {
+        // TODO: revise this
         return name === COLOR ? 'linear' : 'ordinal';
       }
       return encoding.scale(name).type;
   }
 };
 
-scale.domain = function (name, encoding, stats, opt) {
-  var field = encoding.field(name);
+scale.domain = function (scaleDef, encoding, stats, opt) {
+  opt = opt || {};
+
+  var name = scaleDef.name;
+
+  var encDef = encoding.encDef(name);
 
+  // special case for temporal scale
   if (encoding.isType(name, T)) {
-    var range = time.scale.domain(field.timeUnit, name);
-    if(range) return range;
+    var range = time.scale.domain(encDef.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),
+  // For binned, produce fixed stepped domain.
+  // TODO(#614): this must be changed in vg2
+  if (encDef.bin) {
+
+    var fieldStat = stats[encDef.name],
+      bins = util.getbins(fieldStat, encDef.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;
     });
   }
 
+  // For stack, use STACKED data.
+  // TODO(#412) revise this
   if (name == opt.stack) {
     return {
       data: STACKED,
       field: encoding.fieldRef(name, {
-        data: !encoding._vega2,
         prefn: (opt.facet ? 'max_' : '') + 'sum_'
       })
     };
   }
+
   var aggregate = encoding.aggregate(name),
-    timeUnit = field.timeUnit,
-    scaleUseRawDomain = encoding.scale(name).useRawDomain,
+    timeUnit = encDef.timeUnit;
+
+  // determine useRawDomain value
+  var scaleUseRawDomain = encoding.scale(name).useRawDomain,
     useRawDomain = scaleUseRawDomain !== undefined ?
       scaleUseRawDomain : encoding.config('useRawDomain'),
     notCountOrSum = !aggregate || (aggregate !=='count' && aggregate !== 'sum');
 
-  // FIXME revise this part
-
+  var dataTable = encoding.dataTable();
   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) ||
+      (encoding.isType(name, Q) && !encDef.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})};
+    dataTable = RAW;
   }
 
-  var data = encoding.sort(name, stats).length > 0 ?
-    vlsort.getDataName(name):
-    encoding.dataTable();
+  var domain = {
+    data: dataTable,
+    field: encoding.fieldRef(name)
+  };
+
+  // For ordinal scale, add domain's property if provided.
+  var sort = scaleDef.type === 'ordinal' && encoding.sort(name);
+  if (sort) { domain.sort = sort; }
 
-  return {data: data, field: encoding.fieldRef(name)};
+  return domain;
 };
 
 
-scale.range = function (s, encoding, layout, stats) {
-  var spec = encoding.scale(s.name),
-    field = encoding.field(s.name),
-    timeUnit = field.timeUnit;
+scale.range = function (scaleDef, encoding, layout, stats) {
+  var spec = encoding.scale(scaleDef.name),
+    encDef = encoding.encDef(scaleDef.name),
+    timeUnit = encDef.timeUnit;
 
-  switch (s.name) {
+  switch (scaleDef.name) {
     case X:
-      s.range = layout.cellWidth ? [0, layout.cellWidth] : 'width';
-      if (s.type === 'ordinal') {
-        s.bandWidth = encoding.bandSize(X, layout.x.useSmallBand);
+      scaleDef.range = layout.cellWidth ? [0, layout.cellWidth] : 'width';
+      if (scaleDef.type === 'ordinal') {
+        scaleDef.bandWidth = encoding.bandSize(X, layout.x.useSmallBand);
       } else {
-        if (encoding.isType(s.name,T) && timeUnit === 'year') {
-          s.zero = false;
+        if (encoding.isType(scaleDef.name,T) && timeUnit === 'year') {
+          scaleDef.zero = false;
         } else {
-          s.zero = spec.zero === undefined ? true : spec.zero;
+          scaleDef.zero = spec.zero === undefined ? true : spec.zero;
         }
 
-        s.reverse = spec.reverse;
+        scaleDef.reverse = spec.reverse;
       }
-      s.round = true;
-      if (s.type === 'time') {
-        s.nice = timeUnit || encoding.config('timeScaleNice');
+      scaleDef.round = true;
+      if (scaleDef.type === 'time') {
+        scaleDef.nice = timeUnit || encoding.config('timeScaleNice');
       }else {
-        s.nice = true;
+        scaleDef.nice = true;
       }
       break;
     case Y:
-      if (s.type === 'ordinal') {
-        s.range = layout.cellHeight ?
-          (field.bin ? [layout.cellHeight, 0] : [0, layout.cellHeight]) :
+      if (scaleDef.type === 'ordinal') {
+        scaleDef.range = layout.cellHeight ?
+          (encDef.bin ? [layout.cellHeight, 0] : [0, layout.cellHeight]) :
           'height';
-        s.bandWidth = encoding.bandSize(Y, layout.y.useSmallBand);
+        scaleDef.bandWidth = encoding.bandSize(Y, layout.y.useSmallBand);
       } else {
-        s.range = layout.cellHeight ? [layout.cellHeight, 0] : 'height';
-        if (encoding.isType(s.name,T) && timeUnit === 'year') {
-          s.zero = false;
+        scaleDef.range = layout.cellHeight ? [layout.cellHeight, 0] : 'height';
+        if (encoding.isType(scaleDef.name,T) && timeUnit === 'year') {
+          scaleDef.zero = false;
         } else {
-          s.zero = spec.zero === undefined ? true : spec.zero;
+          scaleDef.zero = spec.zero === undefined ? true : spec.zero;
         }
 
-        s.reverse = spec.reverse;
+        scaleDef.reverse = spec.reverse;
       }
 
-      s.round = true;
+      scaleDef.round = true;
 
-      if (s.type === 'time') {
-        s.nice = timeUnit || encoding.config('timeScaleNice');
+      if (scaleDef.type === 'time') {
+        scaleDef.nice = timeUnit || encoding.config('timeScaleNice');
       }else {
-        s.nice = true;
+        scaleDef.nice = true;
       }
       break;
     case ROW: // support only ordinal
-      s.bandWidth = layout.cellHeight;
-      s.round = true;
-      s.nice = true;
+      scaleDef.bandWidth = layout.cellHeight;
+      scaleDef.round = true;
+      scaleDef.nice = true;
       break;
     case COL: // support only ordinal
-      s.bandWidth = layout.cellWidth;
-      s.round = true;
-      s.nice = true;
+      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
-        s.range = [3, Math.max(encoding.bandSize(X), encoding.bandSize(Y))];
+        scaleDef.range = [3, Math.max(encoding.bandSize(X), encoding.bandSize(Y))];
       } else if (encoding.is(TEXT)) {
-        s.range = [8, 40];
+        scaleDef.range = [8, 40];
       } else { //point
         var bandSize = Math.min(encoding.bandSize(X), encoding.bandSize(Y)) - 1;
-        s.range = [10, 0.8 * bandSize*bandSize];
+        scaleDef.range = [10, 0.8 * bandSize*bandSize];
       }
-      s.round = true;
-      s.zero = false;
+      scaleDef.round = true;
+      scaleDef.zero = false;
       break;
     case SHAPE:
-      s.range = 'shapes';
+      scaleDef.range = 'shapes';
       break;
     case COLOR:
-      s.range = scale.color(s, encoding, stats);
-      if (s.type !== 'ordinal') s.zero = false;
+      scaleDef.range = scale.color(scaleDef, encoding, stats);
+      if (scaleDef.type !== 'ordinal') scaleDef.zero = false;
       break;
     default:
-      throw new Error('Unknown encoding name: '+ s.name);
+      throw new Error('Unknown encoding name: '+ scaleDef.name);
   }
 
   // FIXME(kanitw): Jul 29, 2015 - consolidate this with above
-  switch (s.name) {
+  switch (scaleDef.name) {
     case ROW:
     case COL:
-      s.padding = encoding.config('cellPadding');
-      s.outerPadding = 0;
+      scaleDef.padding = encoding.config('cellPadding');
+      scaleDef.outerPadding = 0;
       break;
     case X:
     case Y:
-      if (s.type === 'ordinal') { //&& !s.bandWidth
-        s.points = true;
-        s.padding = encoding.field(s.name).band.padding;
+      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) {
diff --git a/src/compiler/sort.js b/src/compiler/sort.js
deleted file mode 100644
index e40387f7c..000000000
--- a/src/compiler/sort.js
+++ /dev/null
@@ -1,60 +0,0 @@
-'use strict';
-
-require('../globals');
-
-var vlfield = require('../field');
-
-module.exports = sort;
-
-// adds new transforms that produce sorted fields
-function sort(data, encoding, stats, opt) {
-  // jshint unused:false
-
-  var datasetMapping = {};
-
-  encoding.forEach(function(field, encType) {
-    var sortBy = encoding.sort(encType, stats);
-    if (sortBy.length > 0) {
-      var fields = sortBy.map(function(d) {
-        return {
-          op: d.aggregate,
-          field: vlfield.fieldRef(d, {nofn: true, data: !encoding._vega2})
-        };
-      });
-
-      var byClause = sortBy.map(function(d) {
-        var reverse = (d.reverse ? '-' : '');
-        return reverse + vlfield.fieldRef(d, {data: !encoding._vega2});
-      });
-
-      var dataName = sort.getDataName(encType);
-
-      var transforms = [
-        {
-          type: 'aggregate',
-          groupby: [ encoding.fieldRef(encType) ],
-          fields: fields
-        },
-        {
-          type: 'sort',
-          by: byClause
-        }
-      ];
-
-      data.push({
-        name: dataName,
-        source: RAW,
-        transform: transforms
-      });
-
-      datasetMapping[encType] = dataName;
-    }
-  });
-
-  return data;
-}
-
-sort.getDataName = function(encType) {
-  return 'sorted-' + encType;
-};
-
diff --git a/src/compiler/stack.js b/src/compiler/stack.js
index 9df167736..c6d7cfb36 100644
--- a/src/compiler/stack.js
+++ b/src/compiler/stack.js
@@ -36,7 +36,7 @@ function stacking(data, encoding, mdef) {
     transform: [{
       type: 'aggregate',
       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?
+      summarize: [{ops: ['sum'], field: encoding.fieldRef(val)}] // TODO check if field with aggregate is correct?
     }]
   };
 
@@ -44,26 +44,37 @@ function stacking(data, encoding, mdef) {
     stacked.transform.push({ //calculate max for each facet
       type: 'aggregate',
       groupby: facets,
-      fields: [{
-        op: 'max',
-        field: encoding.fieldName(val, {fn: 'sum'})
+      summarize: [{
+        ops: ['max'],
+        field: encoding.fieldRef(val, {prefn: 'sum_'})
       }]
     });
   }
 
   data.push(stacked);
 
+  var valName = encoding.encDef(val).name;
+  var startField = valName + '_start';
+  var endField = valName + '_end';
+
   // add stack transform to mark
   mdef.from.transform = [{
     type: 'stack',
-    point: encoding.fieldRef(dim),
-    height: encoding.fieldRef(val),
-    output: {y1: val, y0: val + '2'}
+    groupby: encoding.fieldRef(dim),
+    field: encoding.fieldRef(val),
+    // TODO(#39) add sort by
+    output: {start: startField, end: endField}
   }];
 
-  // TODO: This is super hack-ish -- consolidate into modular mark properties?
-  mdef.properties.update[val] = mdef.properties.enter[val] = {scale: val, field: val};
-  mdef.properties.update[val + '2'] = mdef.properties.enter[val + '2'] = {scale: val, field: val + '2'};
+  // TODO(#276): This is super hack-ish -- consolidate into modular mark properties?
+  mdef.properties.update[val] = mdef.properties.enter[val] = {
+    scale: val,
+    field: startField
+  };
+  mdef.properties.update[val + '2'] = mdef.properties.enter[val + '2'] = {
+    scale: val,
+    field: endField
+  };
 
   return val; //return stack encoding
 }
diff --git a/src/compiler/style.js b/src/compiler/style.js
index 28d54128b..e4d699c9c 100644
--- a/src/compiler/style.js
+++ b/src/compiler/style.js
@@ -2,7 +2,7 @@
 
 require('../globals');
 
-var vlfield = require('../field');
+var vlEncDef = require('../encdef');
 
 module.exports = function(encoding, stats) {
   return {
@@ -25,11 +25,11 @@ function estimateOpacity(encoding,stats) {
     //  for each non faceting and non-ordinal X / Y fields
     //  note that ordinal x,y are not include since we can
     //  consider that ordinal x are subdividing the cell into subcells anyway
-    encoding.forEach(function(field, encType) {
+    encoding.forEach(function(encDef, encType) {
 
       if (encType !== ROW && encType !== COL &&
           !((encType === X || encType === Y) &&
-          vlfield.isOrdinalScale(field))
+          vlEncDef.isOrdinalScale(encDef))
         ) {
         numPoints *= encoding.cardinality(encType, stats);
       }
diff --git a/src/compiler/subfacet.js b/src/compiler/subfacet.js
index 1ee2f524a..e2de0c7ca 100644
--- a/src/compiler/subfacet.js
+++ b/src/compiler/subfacet.js
@@ -7,13 +7,13 @@ module.exports = subfaceting;
 function subfaceting(group, mdef, details, stack, encoding) {
   var m = group.marks;
   var g = {
-    _name: 'subfacet',
+    name: 'subfacet',
     type: 'group',
     from: mdef.from,
     properties: {
       enter: {
-        width: {group: 'width'},
-        height: {group: 'height'}
+        width: {field: {group: 'width'}},
+        height: {field: {group: 'height'}}
       }
     },
     marks: m
@@ -24,8 +24,9 @@ function subfaceting(group, mdef, details, stack, encoding) {
 
   //TODO test LOD -- we should support stack / line without color (LOD) field
   var trans = (g.from.transform || (g.from.transform = []));
-  trans.unshift({type: 'facet', keys: details});
+  trans.push({type: 'facet', groupby: details});
 
+  // TODO: understand why we need this sort transform and write comment
   if (stack && encoding.has(COLOR)) {
     trans.unshift({type: 'sort', by: encoding.fieldRef(COLOR)});
   }
diff --git a/src/compiler/time.js b/src/compiler/time.js
index f0d2d4936..ebd840359 100644
--- a/src/compiler/time.js
+++ b/src/compiler/time.js
@@ -10,8 +10,8 @@ var time = module.exports = {};
 // 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(field, stats, filterNull, type) {
-  var timeUnit = field.timeUnit;
+time.cardinality = function(encDef, stats, filterNull, type) {
+  var timeUnit = encDef.timeUnit;
   switch (timeUnit) {
     case 'seconds': return 60;
     case 'minutes': return 60;
@@ -20,8 +20,8 @@ time.cardinality = function(field, stats, filterNull, type) {
     case 'date': return 31;
     case 'month': return 12;
     case 'year':
-      var stat = stats[field.name],
-        yearstat = stats['year_'+field.name];
+      var stat = stats[encDef.name],
+        yearstat = stats['year_' + encDef.name];
 
       if (!yearstat) { return null; }
 
@@ -87,10 +87,10 @@ time.range = function(timeUnit, encoding) {
  * @return {Array}  scales for time unit names
  */
 time.scales = function(encoding) {
-  var scales = encoding.reduce(function(scales, field) {
-    var timeUnit = field.timeUnit;
-    if (field.type === T && timeUnit && !scales[timeUnit]) {
-      var scale = time.scale.def(field.timeUnit, 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;
     }
     return scales;
diff --git a/src/enc.js b/src/enc.js
index f8ab8ea8f..ac68b3afc 100644
--- a/src/enc.js
+++ b/src/enc.js
@@ -4,7 +4,7 @@
 
 var consts = require('./consts'),
   c = consts.shorthand,
-  vlfield = require('./field'),
+  vlEncDef = require('./encdef'),
   util = require('./util'),
   schema = require('./schema/schema'),
   encTypes = schema.encTypes;
@@ -81,7 +81,7 @@ vlenc.fields = function(enc) {
 
 vlenc.shorthand = function(enc) {
   return vlenc.map(enc, function(field, et) {
-    return et + c.assign + vlfield.shorthand(field);
+    return et + c.assign + vlEncDef.shorthand(field);
   }).join(c.delim);
 };
 
@@ -92,7 +92,7 @@ vlenc.fromShorthand = function(shorthand) {
         enctype = split[0].trim(),
         field = split[1];
 
-    m[enctype] = vlfield.fromShorthand(field);
+    m[enctype] = vlEncDef.fromShorthand(field);
     return m;
   }, {});
 };
diff --git a/src/field.js b/src/encdef.js
similarity index 97%
rename from src/field.js
rename to src/encdef.js
index c8702986b..025bca8fd 100644
--- a/src/field.js
+++ b/src/encdef.js
@@ -16,8 +16,7 @@ var vlfield = module.exports = {};
  * @param field
  * @param opt
  *   opt.nofn -- exclude bin, aggregate, timeUnit
- *   opt.data - include 'data.'
- *   opt.d - include 'd.'
+ *   opt.datum - include 'datum.'
  *   opt.fn - replace fn with custom function prefix
  *   opt.prefn - prepend fn with custom function prefix
 
@@ -26,8 +25,7 @@ var vlfield = module.exports = {};
 vlfield.fieldRef = function(field, opt) {
   opt = opt || {};
 
-  var f = (opt.d ? 'd.' : '') +
-          (opt.data ? 'data.' : '') +
+  var f = (opt.datum ? 'datum.' : '') +
           (opt.prefn || ''),
     nofn = opt.nofn || opt.fn,
     name = field.name;
diff --git a/src/schema/schema.js b/src/schema/schema.js
index 6e57a4f86..cd9b128a8 100644
--- a/src/schema/schema.js
+++ b/src/schema/schema.js
@@ -8,6 +8,16 @@ var schema = module.exports = {},
   toMap = util.toMap,
   colorbrewer = require('colorbrewer');
 
+
+var VALID_OPS = [
+  'values', 'count', 'valid', 'missing', 'distinct',
+  'sum', 'mean', 'average', 'variance', 'variancep', 'stdev',
+  'stdevp', 'median', 'q1', 'q3', 'modeskew', 'min', 'max',
+  'argmin', 'argmax'
+];
+// TODO(#620): refer to VALID_OPS in Vega schema instead
+// var VALID_OPS = require('vega/src/transforms/Aggregate').VALID_OPS;
+
 schema.util = require('./schemautil');
 
 schema.marktype = {
@@ -17,12 +27,12 @@ schema.marktype = {
 
 schema.aggregate = {
   type: 'string',
-  enum: ['avg', 'sum', 'median', 'min', 'max', 'count'],
+  enum: VALID_OPS,
   supportedEnums: {
-    Q: ['avg', 'median', 'sum', 'min', 'max', 'count'],
+    Q: VALID_OPS,
     O: ['median','min','max'],
     N: [],
-    T: ['avg', 'median', 'min', 'max'],
+    T: ['mean', 'median', 'min', 'max'],
     '': ['count']
   },
   supportedTypes: toMap([Q, N, O, T, ''])
@@ -225,24 +235,19 @@ var sortMixin = {
   type: 'object',
   properties: {
     sort: {
-      type: 'array',
-      default: [],
-      items: {
-        type: 'object',
-        supportedTypes: toMap([N, O]),
-        required: ['name', 'aggregate'],
-        properties: {
-          name: {
-            type: 'string'
-          },
-          aggregate: {
-            type: 'string',
-            enum: ['avg', 'sum', 'min', 'max', 'count']
-          },
-          reverse: {
-            type: 'boolean',
-            default: false
-          }
+      type: ['object', 'boolean'],
+      default: true,
+      supportedTypes: toMap([N, O]),
+      required: ['field', 'op'],
+      properties: {
+        field: {
+          type: 'string',
+          description: 'The field name to aggregate over.'
+        },
+        op: {
+          type: 'string',
+          enum: VALID_OPS,
+          description: 'The field name to aggregate over.'
         }
       }
     }
@@ -281,6 +286,11 @@ var legendMixin = {
           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".'
         }
       }
     }
@@ -534,25 +544,6 @@ var text = merge(clone(onlyQuantitativeField), textMixin, sortMixin);
 
 // TODO add label
 
-var filter = {
-  type: 'array',
-  items: {
-    type: 'object',
-    properties: {
-      operands: {
-        type: 'array',
-        items: {
-          type: ['string', 'boolean', 'integer', 'number']
-        }
-      },
-      operator: {
-        type: 'string',
-        enum: ['>', '>=', '=', '!=', '<', '<=', 'notNull']
-      }
-    }
-  }
-};
-
 var data = {
   type: 'object',
   properties: {
@@ -574,6 +565,11 @@ var data = {
         type: 'object',
         additionalProperties: true
       }
+    },
+    // filter
+    filter: {
+      type: 'string',
+      default: undefined
     }
   }
 };
@@ -610,9 +606,11 @@ var config = {
     },
 
     // 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}
@@ -781,7 +779,6 @@ schema.schema = {
         detail: detail
       }
     },
-    filter: filter,
     config: config
   }
 };
diff --git a/src/vl.js b/src/vl.js
index 020008705..f1aba3556 100644
--- a/src/vl.js
+++ b/src/vl.js
@@ -14,7 +14,7 @@ vl.compiler = require('./compiler/compiler');
 vl.compile = vl.compiler.compile;
 vl.data = require('./data');
 vl.enc = require('./enc');
-vl.field = require('./field');
+vl.encDef = require('./encdef');
 vl.schema = require('./schema/schema');
 vl.toShorthand = vl.Encoding.shorthand;
 vl.format = require('d3-format').format;


Test patch:
diff --git a/test/Encoding.spec.js b/test/Encoding.spec.js
index 9c2ac8a65..55e24bb88 100644
--- a/test/Encoding.spec.js
+++ b/test/Encoding.spec.js
@@ -6,7 +6,7 @@ var Encoding = require('../src/Encoding');
 
 describe('Encoding.fromShorthand()', function () {
   it('should parse shorthand correctly', function () {
-    var shorthand = 'mark=point|x=Effect__Amount_of_damage,O|y=avg_Cost__Total_$,Q';
+    var shorthand = 'mark=point|x=Effect__Amount_of_damage,O|y=mean_Cost__Total_$,Q';
     var encoding = Encoding.fromShorthand(shorthand);
     expect(encoding.has('y')).ok;
     expect(encoding.has('x')).ok;
@@ -14,29 +14,3 @@ describe('Encoding.fromShorthand()', function () {
   });
 });
 
-describe('encoding.filter()', function () {
-  var spec = {
-      marktype: 'point',
-      encoding: {
-        y: {name: 'Q', type:'Q'},
-        x: {name: 'T', type:'T'},
-        color: {name: 'O', type:'O'}
-      }
-    };
-  it('should add filterNull for Q and T by default', function () {
-    var encoding = Encoding.fromSpec(spec),
-      filter = encoding.filter();
-    expect(filter.length).to.equal(2);
-    expect(filter.indexOf({name: 'O', type:'O'})).to.equal(-1);
-  });
-
-  it('should add filterNull for O when specified', function () {
-    var encoding = Encoding.fromSpec(spec, {
-      config: {
-        filterNull: {O: true}
-      }
-    });
-    var filter = encoding.filter();
-    expect(filter.length).to.equal(3);
-  });
-});
diff --git a/test/compiler/data.spec.js b/test/compiler/data.spec.js
index ea7863174..96bc87539 100644
--- a/test/compiler/data.spec.js
+++ b/test/compiler/data.spec.js
@@ -36,7 +36,7 @@ describe('data', function () {
       var rawTransform = _data[0].transform;
       expect(rawTransform[rawTransform.length - 1]).to.eql({
         type: 'filter',
-        test: 'd.data.b > 0'
+        test: 'datum.b > 0'
       });
     });
   });
@@ -100,6 +100,9 @@ describe('data.raw', function() {
 
   describe('transform', function () {
     var encoding = Encoding.fromSpec({
+      data: {
+        filter: 'datum.a > datum.b && datum.c === datum.d'
+      },
       encoding: {
         x: {name: 'a', type:'T', timeUnit: 'year'},
         y: {
@@ -107,50 +110,72 @@ describe('data.raw', function() {
           'name': 'Acceleration',
           'type': 'Q'
         }
-      },
-      filter: [{
-        operator: '>',
-        operands: ['a', 'b']
-      },{
-        operator: '=',
-        operands: ['c', 'd']
-      }]
+      }
     });
 
+    var stats = {
+      Acceleration: {
+        min: 0,
+        max: 100
+      }
+    };
+
     describe('bin', function() {
       it('should add bin transform', function() {
-        var transform = data.raw.transform.bin(encoding);
+        var transform = data.raw.transform.bin(encoding, stats);
+
         expect(transform[0]).to.eql({
           type: 'bin',
-          field: 'data.Acceleration',
-          output: 'data.bin_Acceleration',
-          maxbins: 15
+          field: 'Acceleration',
+          output: {bin: 'bin_Acceleration'},
+          maxbins: 15,
+          min: 0,
+          max: 100
         });
       });
     });
 
-    describe('filter', function () {
-      it('should return filter transform that include filter null', function () {
-        var transform = data.raw.transform.filter(encoding);
-
-        expect(transform[0]).to.eql({
-          type: 'filter',
-          test: '(d.data.a!==null) && (d.data.Acceleration!==null)' +
-          ' && (d.data.a > b) && (d.data.c == d)'
-        });
+    describe('nullFilter', function() {
+      var spec = {
+          marktype: 'point',
+          encoding: {
+            y: {name: 'Q', type:'Q'},
+            x: {name: 'T', type:'T'},
+            color: {name: 'O', type:'O'}
+          }
+        };
+
+      it('should add filterNull for Q and T by default', function () {
+        var encoding = Encoding.fromSpec(spec);
+        expect(data.raw.transform.nullFilter(encoding))
+          .to.eql([{
+            type: 'filter',
+            test: 'datum.T!==null && datum.Q!==null'
+          }]);
       });
 
-      it('should exclude unsupported operator', function () {
-        var badEncoding = Encoding.fromSpec({
-          filter: [{
-            operator: '*',
-            operands: ['a', 'b']
-          }]
+      it('should add filterNull for O when specified', function () {
+        var encoding = Encoding.fromSpec(spec, {
+          config: {
+            filterNull: {O: true}
+          }
         });
+        expect(data.raw.transform.nullFilter(encoding))
+          .to.eql([{
+            type: 'filter',
+            test:'datum.T!==null && datum.Q!==null && datum.O!==null'
+          }]);
+      });
+      // });
+    });
 
-        var transform = data.raw.transform.filter(badEncoding);
-
-        expect(transform.length).to.equal(0);
+    describe('filter', function () {
+      it('should return array that contains a filter transform', function () {
+        expect(data.raw.transform.filter(encoding))
+          .to.eql([{
+            type: 'filter',
+            test: 'datum.a > datum.b && datum.c === datum.d'
+          }]);
       });
     });
 
@@ -159,17 +184,18 @@ describe('data.raw', function() {
         var transform = data.raw.transform.time(encoding);
         expect(transform[0]).to.eql({
           type: 'formula',
-          field: 'data.year_a',
-          expr: 'utcyear(d.data.a)'
+          field: 'year_a',
+          expr: 'utcyear(datum.a)'
         });
       });
     });
 
-    it('should time and bin before filter', function () {
-      var transform = data.raw.transform(encoding);
-      expect(transform[0].type).to.eql('formula');
-      expect(transform[1].type).to.eql('bin');
-      expect(transform[2].type).to.eql('filter');
+    it('should have null filter, timeUnit, bin then filter', function () {
+      var transform = data.raw.transform(encoding, stats);
+      expect(transform[0].type).to.eql('filter');
+      expect(transform[1].type).to.eql('formula');
+      expect(transform[2].type).to.eql('bin');
+      expect(transform[3].type).to.eql('filter');
     });
 
   });
@@ -199,14 +225,11 @@ describe('data.aggregated', function () {
       "source": "raw",
       "transform": [{
         "type": "aggregate",
-        "groupby": ["data.origin"],
-        "fields": [{
-          "op": "sum",
-          "field": "data.Acceleration"
-        },{
-          "op": "count",
-          "field": "*"
-        }]
+        "groupby": ["origin"],
+        "summarize": {
+          '*': ['count'],
+          'Acceleration': ['sum']
+        }
       }]
     });
   });
diff --git a/test/compiler/marks.spec.js b/test/compiler/marks.spec.js
index bb13f77c2..36b8e840f 100644
--- a/test/compiler/marks.spec.js
+++ b/test/compiler/marks.spec.js
@@ -18,7 +18,7 @@ describe('compile.marks', function() {
           e = Encoding.fromSpec(f),
           def = marks.bar.prop(e, mockLayout, {});
       it('should end on axis', function() {
-        expect(def.y2).to.eql({group: 'height'});
+        expect(def.y2).to.eql({field: {group: 'height'}});
       });
       it('should has no height', function(){
         expect(def.height).to.be.undefined;
@@ -42,7 +42,7 @@ describe('compile.marks', function() {
           e = Encoding.fromSpec(f),
           def = marks.bar.prop(e, mockLayout, {});
       it('should end on axis', function() {
-        expect(def.y2).to.eql({group: 'height'});
+        expect(def.y2).to.eql({field: {group: 'height'}});
       });
       it('should have no height', function(){
         expect(def.height).to.be.undefined;
@@ -64,7 +64,7 @@ describe('compile.marks', function() {
       });
       it('should have y-offset', function(){
         expect(def.y2).to.eql({
-          group: 'height',
+          field: {group: 'height'},
           offset: -5 // -config.singleBarOffset
         });
       });
@@ -80,7 +80,7 @@ describe('compile.marks', function() {
         expect(def.y).to.eql({value: e.bandSize(Y, mockLayout.y.useSmallBand) / 2});
       });
       it('should scale on x', function() {
-        expect(def.x).to.eql({scale: X, field: "data.year"});
+        expect(def.x).to.eql({scale: X, field: "year"});
       });
     });
 
@@ -92,7 +92,7 @@ describe('compile.marks', function() {
         expect(def.x).to.eql({value: e.bandSize(X, mockLayout.x.useSmallBand) / 2});
       });
       it('should scale on y', function() {
-        expect(def.y).to.eql({scale: Y, field: "data.year"});
+        expect(def.y).to.eql({scale: Y, field: "year"});
       });
     });
 
@@ -101,10 +101,10 @@ describe('compile.marks', function() {
           e = Encoding.fromSpec(f),
           def = marks.point.prop(e, mockLayout, {});
       it('should scale on x', function() {
-        expect(def.x).to.eql({scale: X, field: "data.year"});
+        expect(def.x).to.eql({scale: X, field: "year"});
       });
       it('should scale on y', function(){
-        expect(def.y).to.eql({scale: Y, field: "data.yield"});
+        expect(def.y).to.eql({scale: Y, field: "yield"});
       });
     });
 
@@ -114,7 +114,7 @@ describe('compile.marks', function() {
             e = Encoding.fromSpec(f),
             def = marks.point.prop(e, mockLayout, {});
         it('should have scale for size', function () {
-          expect(def.size).to.eql({scale: SIZE, field: "data.count"});
+          expect(def.size).to.eql({scale: SIZE, field: "count"});
         });
       });
 
@@ -123,7 +123,7 @@ describe('compile.marks', function() {
             e = Encoding.fromSpec(f),
             def = marks.point.prop(e, mockLayout, {});
         it('should have scale for color', function () {
-          expect(def.stroke).to.eql({scale: COLOR, field: "data.yield"});
+          expect(def.stroke).to.eql({scale: COLOR, field: "yield"});
         });
       });
 
@@ -132,7 +132,7 @@ describe('compile.marks', function() {
             e = Encoding.fromSpec(f),
             def = marks.point.prop(e, mockLayout, {});
         it('should have scale for shape', function () {
-          expect(def.shape).to.eql({scale: SHAPE, field: "data.bin_yield"});
+          expect(def.shape).to.eql({scale: SHAPE, field: "bin_yield"});
         });
       });
     });
@@ -144,10 +144,10 @@ describe('compile.marks', function() {
           e = Encoding.fromSpec(f),
           def = marks.line.prop(e, mockLayout, {});
       it('should have scale for x', function() {
-        expect(def.x).to.eql({scale: X, field: "data.year"});
+        expect(def.x).to.eql({scale: X, field: "year"});
       });
       it('should have scale for y', function(){
-        expect(def.y).to.eql({scale: Y, field: "data.yield"});
+        expect(def.y).to.eql({scale: Y, field: "yield"});
       });
     });
 
@@ -157,7 +157,7 @@ describe('compile.marks', function() {
             e = Encoding.fromSpec(f),
             def = marks.line.prop(e, mockLayout, {});
         it('should have scale for color', function () {
-          expect(def.stroke).to.eql({scale: COLOR, field: "data.Acceleration"});
+          expect(def.stroke).to.eql({scale: COLOR, field: "Acceleration"});
         });
       });
     });
@@ -169,10 +169,10 @@ describe('compile.marks', function() {
           e = Encoding.fromSpec(f),
           def = marks.area.prop(e, mockLayout, {});
       it('should have scale for x', function() {
-        expect(def.x).to.eql({scale: X, field: "data.Displacement"});
+        expect(def.x).to.eql({scale: X, field: "Displacement"});
       });
       it('should have scale for y', function(){
-        expect(def.y).to.eql({scale: Y, field: "data.Acceleration"});
+        expect(def.y).to.eql({scale: Y, field: "Acceleration"});
       });
     });
 
@@ -182,7 +182,7 @@ describe('compile.marks', function() {
             e = Encoding.fromSpec(f),
             def = marks.area.prop(e, mockLayout, {});
         it('should have scale for color', function () {
-          expect(def.fill).to.eql({scale: COLOR, field: "data.Miles_per_Gallon"});
+          expect(def.fill).to.eql({scale: COLOR, field: "Miles_per_Gallon"});
         });
       });
     });
diff --git a/test/compiler/scale.spec.js b/test/compiler/scale.spec.js
index 7985be249..3d67aa421 100644
--- a/test/compiler/scale.spec.js
+++ b/test/compiler/scale.spec.js
@@ -9,173 +9,208 @@ var util = require('../../src/util'),
   colorbrewer = require('colorbrewer');
 
 describe('vl.compile.scale', function() {
-  describe('sort()', function() {
-    it('should return true for any ordinal or binned field', function() {
-      var encoding = Encoding.fromSpec({
+  describe('domain()', function() {
+    var linearScaleDef = {
+      name: 'y',
+      type: 'linear'
+    };
+
+    var ordinalScaleDef = {
+      name: 'y',
+      type: 'ordinal'
+    };
+    var timeScaleDef = {
+      name: 'y',
+      type: 'time'
+    };
+
+    describe('for stack', function() {
+      it('should return correct stack', function() {
+        var domain = vlscale.domain(linearScaleDef, Encoding.fromSpec({
           encoding: {
-            x: { name: 'origin', type: O},
-            y: { bin: true, name: 'origin', type: Q}
+            y: {
+              name: 'origin'
+            }
           }
+        }), {}, {
+          stack: 'y',
+          facet: true
         });
 
-      expect(vlscale.sort({type: 'ordinal'}, encoding, 'x'))
-        .to.eql(true);
-      expect(vlscale.sort({type: 'ordinal'}, encoding, 'y'))
-        .to.eql(true);
-    });
-
-  });
+        expect(domain).to.eql({
+          data: 'stacked',
+          field: 'max_sum_origin'
+        });
+      });
 
-  describe('domain()', function() {
-    it('should return correct stack', function() {
-      var domain = vlscale.domain('y', Encoding.fromSpec({
-        encoding: {
-          y: {
-            name: 'origin'
+      it('should return correct aggregated stack', function() {
+        var domain = vlscale.domain(linearScaleDef, Encoding.fromSpec({
+          encoding: {
+            y: {
+              aggregate: 'sum',
+              name: 'origin'
+            }
           }
-        }
-      }), {}, {
-        stack: 'y',
-        facet: true
-      });
+        }), {}, {
+          stack: 'y',
+          facet: true
+        });
 
-      expect(domain).to.eql({
-        data: 'stacked',
-        field: 'data.max_sum_origin'
+        expect(domain).to.eql({
+          data: 'stacked',
+          field: '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
-      });
+    describe('for quantitative', function() {
+      it('should return the right domain if binned Q',
+        function() {
+          var domain = vlscale.domain(ordinalScaleDef, Encoding.fromSpec({
+            encoding: {
+              y: {
+                bin: {maxbins: 15},
+                name: 'origin',
+                scale: {useRawDomain: true},
+                type: Q
+              }
+            }
+          }), {origin: {min: -5, max:48}}, {});
 
-      expect(domain).to.eql({
-        data: 'stacked',
-        field: 'data.max_sum_sum_origin'
-      });
-    });
+          expect(domain).to.eql([-5, 0, 5, 10, 15, 20, 25, 30, 35, 40, 45]);
+        });
 
-    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
+      it('should return the raw domain if useRawDomain is true for non-bin, non-sum Q',
+        function() {
+          var domain = vlscale.domain(linearScaleDef, Encoding.fromSpec({
+            encoding: {
+              y: {
+                aggregate: 'mean',
+                name: 'origin',
+                scale: {useRawDomain: true},
+                type: Q
+              }
             }
-          }
-        }), {origin: {min: -5, max:48}}, {});
+          }), {}, {});
 
-        expect(domain).to.eql([-5, 0, 5, 10, 15, 20, 25, 30, 35, 40, 45]);
-      });
+          expect(domain.data).to.eql(RAW);
+        });
 
-    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
+      it('should return the aggregate domain for sum Q',
+        function() {
+          var domain = vlscale.domain(linearScaleDef, Encoding.fromSpec({
+            encoding: {
+              y: {
+                aggregate: 'sum',
+                name: 'origin',
+                scale: {useRawDomain: true},
+                type: Q
+              }
             }
-          }
-        }), {}, {});
+          }), {}, {});
 
-        expect(domain.data).to.eql(RAW);
-      });
+          expect(domain.data).to.eql(AGGREGATE);
+        });
 
-    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
+
+      it('should return the aggregated domain if useRawDomain is false', function() {
+          var domain = vlscale.domain(linearScaleDef, Encoding.fromSpec({
+            encoding: {
+              y: {
+                aggregate: 'min',
+                name: 'origin',
+                scale: {useRawDomain: false},
+                type: Q
+              }
             }
-          }
-        }), {}, {});
+          }), {}, {});
 
-        expect(domain.data).to.eql(AGGREGATE);
-      });
+          expect(domain.data).to.eql(AGGREGATE);
+        });
+    });
 
-    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
+    describe('for time', function() {
+      it('should return the raw domain if useRawDomain is true for raw T',
+        function() {
+          var domain = vlscale.domain(timeScaleDef, Encoding.fromSpec({
+            encoding: {
+              y: {
+                name: 'origin',
+                scale: {useRawDomain: true},
+                type: T
+              }
             }
-          }
-        }), {}, {});
+          }), {}, {});
 
-        expect(domain.data).to.eql(RAW);
-      });
+          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'
+      it('should return the raw domain if useRawDomain is true for year T',
+        function() {
+          var domain = vlscale.domain(ordinalScaleDef, 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);
-      });
+          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'
+      it('should return the correct domain for month T',
+        function() {
+          var domain = vlscale.domain(ordinalScaleDef, Encoding.fromSpec({
+            encoding: {
+              y: {
+                name: 'origin',
+                scale: {useRawDomain: true},
+                type: T,
+                timeUnit: 'month'
+              }
             }
-          }
-        }), {}, {});
+          }), {}, {});
+
+          expect(domain).to.eql([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]);
+        });
+    });
+
+    describe('for ordinal', function() {
+      it('should return correct domain with the provided sort property', function() {
+        var sortDef = {aggregate: 'min', field:'Acceleration'};
+        var encoding = Encoding.fromSpec({
+            encoding: {
+              y: { name: 'origin', type: O, sort: sortDef}
+            }
+          });
 
-        expect(domain).to.eql([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]);
+        expect(vlscale.domain(ordinalScaleDef, encoding))
+          .to.eql({
+            data: RAW,
+            field: 'origin',
+            sort: sortDef
+          });
       });
 
-    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
-          }
-        }
-      }), {}, {});
+      it('should return correct domain with sort=true if sort is not provided', function() {
+        var encoding = Encoding.fromSpec({
+            encoding: {
+              y: { name: 'origin', type: O}
+            }
+          });
 
-      expect(domain.data).to.eql(AGGREGATE);
+        expect(vlscale.domain(ordinalScaleDef, encoding))
+          .to.eql({
+            data: RAW,
+            field: 'origin',
+            sort: true
+          });
+      });
     });
-
-    // TODO test other cases
   });
 
   describe('color.palette', function() {
diff --git a/test/compiler/sort.spec.js b/test/compiler/sort.spec.js
deleted file mode 100644
index 1e1338068..000000000
--- a/test/compiler/sort.spec.js
+++ /dev/null
@@ -1,56 +0,0 @@
-'use strict';
-
-var expect = require('chai').expect;
-
-var vlsort = require('../../src/compiler/sort'),
-  Encoding = require('../../src/Encoding');
-
-describe('Sort', function() {
-  var encoding = Encoding.fromSpec({
-        encoding: {
-          x: {name: 'foo', type: 'O', sort: [{
-            name: 'bar', aggregate: 'avg'
-          }]},
-          y: {name: 'bar', type: 'Q'},
-          color: {name: 'baz', type: 'O', sort: [{
-            name: 'bar', aggregate: 'sum'
-          }, {
-            name: 'foo', aggregate: 'max', reverse: true
-          }]}
-        }
-      }),
-    data = [{name: RAW}, {name: AGGREGATE}];
-
-  vlsort(data, encoding, {});
-
-  it('should add new data and transform', function() {
-    expect(data.length).to.equal(4);
-
-    expect(data[2].transform).to.deep.equal([
-      {
-        type: 'aggregate',
-        groupby: [ 'data.foo' ],
-        fields: [{
-          field: 'data.bar',
-          op: 'avg'
-        }]
-      },
-      { type: 'sort', by: [ 'data.avg_bar' ] }
-    ]);
-
-    expect(data[3].transform).to.deep.equal([
-      {
-        type: 'aggregate',
-        groupby: [ 'data.baz' ],
-        fields: [{
-          field: 'data.bar',
-          op: 'sum'
-        }, {
-          field: 'data.foo',
-          op: 'max'
-        }]
-      },
-      { type: 'sort', by: [ 'data.sum_bar', '-data.max_foo' ] }
-    ]);
-  });
-});
diff --git a/test/compiler/stack.spec.js b/test/compiler/stack.spec.js
index 5fbd749c1..ce85e19cb 100644
--- a/test/compiler/stack.spec.js
+++ b/test/compiler/stack.spec.js
@@ -39,14 +39,14 @@ describe('vl.compile.stack()', function () {
         return t.type === 'aggregate';
       })[0];
       expect(tableAggrTransform.groupby.length).to.equal(2);
-      expect(tableAggrTransform.groupby.indexOf('data.bin_Cost__Total_$')).to.gt(-1);
+      expect(tableAggrTransform.groupby.indexOf('bin_Cost__Total_$')).to.gt(-1);
 
       var stackedData = vgSpec.data.filter(function(data) {
         return data.name === 'stacked';
       });
       expect(stackedData.length).to.equal(1);
       var stackedAggrTransform = stackedData[0].transform[0];
-      expect(stackedAggrTransform.groupby[0]).to.equal('data.bin_Cost__Total_$');
+      expect(stackedAggrTransform.groupby[0]).to.equal('bin_Cost__Total_$');
     });
   });
 
@@ -64,7 +64,7 @@ describe('vl.compile.stack()', function () {
         return t.type === 'aggregate';
       })[0];
       expect(tableAggrTransform.groupby.length).to.equal(2);
-      expect(tableAggrTransform.groupby.indexOf('data.bin_Cost__Total_$')).to.gt(-1);
+      expect(tableAggrTransform.groupby.indexOf('bin_Cost__Total_$')).to.gt(-1);
 
       var stackedData = vgSpec.data.filter(function(data) {
         return data.name === 'stacked';
@@ -72,7 +72,7 @@ describe('vl.compile.stack()', function () {
 
       expect(stackedData.length).to.equal(1);
       var stackedAggrTransform = stackedData[0].transform[0];
-      expect(stackedAggrTransform.groupby[0]).to.equal('data.bin_Cost__Total_$');
+      expect(stackedAggrTransform.groupby[0]).to.equal('bin_Cost__Total_$');
     });
   });
 
diff --git a/test/field.spec.js b/test/field.spec.js
index 8804ddc4f..15f928f82 100644
--- a/test/field.spec.js
+++ b/test/field.spec.js
@@ -3,23 +3,23 @@
 require('../src/globals');
 
 var expect = require('chai').expect;
-var vlfield = require('../src/field');
+var vlEncDef = require('../src/encdef');
 
 describe('vl.field.cardinality()', function () {
   describe('for Q', function () {
     it('should return cardinality', function() {
-      var field = {name:2, type:'Q'};
+      var encDef = {name:2, type:'Q'};
       var stats = {2:{distinct: 10, min:0, max:150}};
-      var cardinality = vlfield.cardinality(field, stats);
+      var cardinality = vlEncDef.cardinality(encDef, stats);
       expect(cardinality).to.equal(10);
     });
   });
 
   describe('for B(Q)', function(){
     it('should return cardinality', function() {
-      var field = {name:2, type:'Q', bin: {maxbins: 15}};
+      var encDef = {name:2, type:'Q', bin: {maxbins: 15}};
       var stats = {2:{distinct: 10, min:0, max:150}};
-      var cardinality = vlfield.cardinality(field, stats);
+      var cardinality = vlEncDef.cardinality(encDef, stats);
       expect(cardinality).to.equal(15);
     });
   });
@@ -27,20 +27,20 @@ describe('vl.field.cardinality()', function () {
 
 describe('vl.field.isType', function () {
   it('should return correct type checking', function() {
-    var qField = {name: 'number', type:'Q'};
-    expect(vlfield.isType(qField, Q)).to.eql(true);
-    expect(vlfield.isTypes(qField, N)).to.eql(false);
+    var qDef = {name: 'number', type:'Q'};
+    expect(vlEncDef.isType(qDef, Q)).to.eql(true);
+    expect(vlEncDef.isTypes(qDef, N)).to.eql(false);
   });
 });
 
 describe('vl.field.isTypes', function () {
   it('should return correct type checking', function() {
-    var qField = {name: 'number', type:'Q'};
-    expect(vlfield.isType(qField, Q)).to.eql(true);
-    expect(vlfield.isTypes(qField, [Q])).to.eql(true);
-    expect(vlfield.isTypes(qField, [Q, O])).to.eql(true);
-    expect(vlfield.isTypes(qField, [O, Q])).to.eql(true);
-    expect(vlfield.isTypes(qField, [Q, N])).to.eql(true);
-    expect(vlfield.isTypes(qField, [N])).to.eql(false);
+    var qDef = {name: 'number', type:'Q'};
+    expect(vlEncDef.isType(qDef, Q)).to.eql(true);
+    expect(vlEncDef.isTypes(qDef, [Q])).to.eql(true);
+    expect(vlEncDef.isTypes(qDef, [Q, O])).to.eql(true);
+    expect(vlEncDef.isTypes(qDef, [O, Q])).to.eql(true);
+    expect(vlEncDef.isTypes(qDef, [Q, N])).to.eql(true);
+    expect(vlEncDef.isTypes(qDef, [N])).to.eql(false);
   });
 });
\ No newline at end of file
diff --git a/test/fixtures.js b/test/fixtures.js
index 7b4c8e1d9..e81762928 100644
--- a/test/fixtures.js
+++ b/test/fixtures.js
@@ -8,7 +8,7 @@ f.bars.log_ver = {
   "marktype": "bar",
   "encoding": {
     "x": {"bin": {"maxbins": 15},"type": "Q","name": "IMDB_Rating"},
-    "y": {"scale": {"type": "log"},"type": "Q","name": "US_Gross","aggregate": "avg"}
+    "y": {"scale": {"type": "log"},"type": "Q","name": "US_Gross","aggregate": "mean"}
   },
   "data": {"url": "data/movies.json"}
 };
@@ -17,7 +17,7 @@ f.bars.log_hor = {
   "marktype": "bar",
   "encoding": {
     "y": {"bin": {"maxbins": 15},"type": "Q","name": "IMDB_Rating"},
-    "x": {"scale": {"type": "log"},"type": "Q","name": "US_Gross","aggregate": "avg"}
+    "x": {"scale": {"type": "log"},"type": "Q","name": "US_Gross","aggregate": "mean"}
   },
   "data": {"url": "data/movies.json"}
 };
@@ -42,7 +42,7 @@ f.stack = {};
 f.stack.binY = {
   "marktype": "bar",
   "encoding": {
-    "x": {"type": "Q","name": "Cost__Other","aggregate": "avg"},
+    "x": {"type": "Q","name": "Cost__Other","aggregate": "mean"},
     "y": {"bin": true,"type": "Q","name": "Cost__Total_$"},
     "color": {"type": "O","name": "Effect__Amount_of_damage"}
   }
@@ -50,7 +50,7 @@ f.stack.binY = {
 f.stack.binX = {
   "marktype": "bar",
   "encoding": {
-    "y": {"type": "Q","name": "Cost__Other","aggregate": "avg"},
+    "y": {"type": "Q","name": "Cost__Other","aggregate": "mean"},
     "x": {"bin": true,"type": "Q","name": "Cost__Total_$"},
     "color": {"type": "O","name": "Effect__Amount_of_damage"}
   }