Skip to content
This repository
Browse code

Changing version builds to 0.3.0

  • Loading branch information...
commit a98fb6c7172b68d618386c5a216171c42887295f 1 parent 100640d
Irene Ros iros authored
2,822 dist/miso.ds.0.2.2.js → dist/miso.ds.0.3.0.js
1,433 additions, 1,389 deletions not shown
3,468 dist/miso.ds.0.3.0.m.js
... ... @@ -0,0 +1,3468 @@
  1 +/**
  2 +* Miso.Dataset - v0.3.0 - 10/26/2012
  3 +* http://github.com/misoproject/dataset
  4 +* Copyright (c) 2012 Alex Graul, Irene Ros;
  5 +* Dual Licensed: MIT, GPL
  6 +* https://github.com/misoproject/dataset/blob/master/LICENSE-MIT
  7 +* https://github.com/misoproject/dataset/blob/master/LICENSE-GPL
  8 +*/
  9 +
  10 +(function(global) {
  11 +
  12 +/**
  13 + * Instantiates a new dataset.
  14 + * Parameters:
  15 + * options - optional parameters.
  16 + * data : "Object - an actual javascript object that already contains the data",
  17 + * url : "String - url to fetch data from",
  18 + * sync : Set to true to be able to bind to dataset changes. False by default.
  19 + * jsonp : "boolean - true if this is a jsonp request",
  20 + * delimiter : "String - a delimiter string that is used in a tabular datafile",
  21 + * strict : "Whether to expect the json in our format or whether to interpret as raw array of objects, default false",
  22 + * extract : "function to apply to JSON before internal interpretation, optional"
  23 + * ready : the callback function to act on once the data is fetched. Isn't reuired for local imports
  24 + * but is required for remote url fetching.
  25 + * columns: A way to manually override column type detection. Expects an array of
  26 + * objects of the following structure:
  27 + * { name : 'columnname', type: 'columntype',
  28 + * ... (additional params required for type here.) }
  29 + * comparator : function (optional) - takes two rows and returns 1, 0, or -1 if row1 is
  30 + * before, equal or after row2.
  31 + * deferred : by default we use underscore.deferred, but if you want to pass your own (like jquery's) just
  32 + * pass it here.
  33 + * importer : The classname of any importer (passes through auto detection based on parameters.
  34 + * For example: <code>Miso.Importers.Polling</code>.
  35 + * parser : The classname of any parser (passes through auto detection based on parameters.
  36 + * For example: <code>Miso.Parsers.Delimited</code>.
  37 + * resetOnFetch : set to true if any subsequent fetches after first one should overwrite the
  38 + * current data.
  39 + * uniqueAgainst : Set to a column name to check for duplication on subsequent fetches.
  40 + * interval : Polling interval. Set to any value in milliseconds to enable polling on a url.
  41 + }
  42 + */
  43 + global.Miso = global.Miso || {};
  44 + global.Miso.Dataset = function(options) {
  45 + this.length = 0;
  46 +
  47 + this._columns = [];
  48 + this._columnPositionByName = {};
  49 + this._computedColumns = [];
  50 +
  51 + if (typeof options !== "undefined") {
  52 + options = options || {};
  53 + this._initialize(options);
  54 + }
  55 + };
  56 +}(this));
  57 +
  58 +(function(global, _) {
  59 +
  60 + var Dataset = global.Miso.Dataset;
  61 +
  62 + /**
  63 + * A single column in a dataset
  64 + * Parameters:
  65 + * options
  66 + * name
  67 + * type (from Miso.types)
  68 + * data (optional)
  69 + * before (a pre coercion formatter)
  70 + * format (for time type.)
  71 + * any additional arguments here..
  72 + * Returns:
  73 + * new Miso.Column
  74 + */
  75 + Dataset.Column = function(options) {
  76 + _.extend(this, options);
  77 + this._id = options.id || _.uniqueId();
  78 + this.data = options.data || [];
  79 + return this;
  80 + };
  81 +
  82 + _.extend(Dataset.Column.prototype, {
  83 +
  84 + /**
  85 + * Converts any value to this column's type for a given position
  86 + * in some source array.
  87 + * Parameters:
  88 + * value
  89 + * Returns:
  90 + * number
  91 + */
  92 + toNumeric : function(value) {
  93 + return Dataset.types[this.type].numeric(value);
  94 + },
  95 +
  96 + /**
  97 + * Returns the numeric representation of a datum at any index in this
  98 + * column.
  99 + * Parameters:
  100 + * index - position in data array
  101 + * Returns
  102 + * number
  103 + */
  104 + numericAt : function(index) {
  105 + return this.toNumeric(this.data[index]);
  106 + },
  107 +
  108 + /**
  109 + * Coerces the entire column's data to the column type.
  110 + */
  111 + coerce : function() {
  112 + this.data = _.map(this.data, function(datum) {
  113 + return Dataset.types[this.type].coerce(datum, this);
  114 + }, this);
  115 + },
  116 +
  117 + /**
  118 + * If this is a computed column, it calculates the value
  119 + * for this column and adds it to the data.
  120 + * Parameters:
  121 + * row - the row from which column is computed.
  122 + * i - Optional. the index at which this value will get added.
  123 + * Returns
  124 + * val - the computed value
  125 + */
  126 + compute : function(row, i) {
  127 + if (this.func) {
  128 + var val = this.func(row);
  129 + if (typeof i !== "undefined") {
  130 + this.data[i] = val;
  131 + } else {
  132 + this.data.push(val);
  133 + }
  134 +
  135 + return val;
  136 + }
  137 + },
  138 +
  139 + /**
  140 + * returns true if this is a computed column. False otherwise.
  141 + */
  142 + isComputed : function() {
  143 + return !_.isUndefined(this.func);
  144 + },
  145 +
  146 + _sum : function() {
  147 + return _.sum(this.data);
  148 + },
  149 +
  150 + _mean : function() {
  151 + var m = 0;
  152 + for (var j = 0; j < this.data.length; j++) {
  153 + m += this.numericAt(j);
  154 + }
  155 + m /= this.data.length;
  156 + return Dataset.types[this.type].coerce(m, this);
  157 + },
  158 +
  159 + _median : function() {
  160 + return Dataset.types[this.type].coerce(_.median(this.data), this);
  161 + },
  162 +
  163 + _max : function() {
  164 + var max = -Infinity;
  165 + for (var j = 0; j < this.data.length; j++) {
  166 + if (this.data[j] !== null) {
  167 + if (Dataset.types[this.type].compare(this.data[j], max) > 0) {
  168 + max = this.numericAt(j);
  169 + }
  170 + }
  171 + }
  172 +
  173 + return Dataset.types[this.type].coerce(max, this);
  174 + },
  175 +
  176 + _min : function() {
  177 + var min = Infinity;
  178 + for (var j = 0; j < this.data.length; j++) {
  179 + if (this.data[j] !== null) {
  180 + if (Dataset.types[this.type].compare(this.data[j], min) < 0) {
  181 + min = this.numericAt(j);
  182 + }
  183 + }
  184 + }
  185 + return Dataset.types[this.type].coerce(min, this);
  186 + }
  187 + });
  188 +
  189 + /**
  190 + * Creates a new view.
  191 + * Parameters
  192 + * options - initialization parameters:
  193 + * parent : parent dataset
  194 + * filter : filter specification TODO: document better
  195 + * columns : column name or multiple names
  196 + * rows : rowId or function
  197 + * Returns
  198 + * new Miso.Dataview.
  199 + */
  200 + Dataset.DataView = function(options) {
  201 + if (typeof options !== "undefined") {
  202 + options = options || (options = {});
  203 +
  204 + if (_.isUndefined(options.parent)) {
  205 + throw new Error("A view must have a parent specified.");
  206 + }
  207 + this.parent = options.parent;
  208 + this._initialize(options);
  209 + }
  210 + };
  211 +
  212 + _.extend(Dataset.DataView.prototype, {
  213 +
  214 + _initialize: function(options) {
  215 +
  216 + // is this a syncable dataset? if so, pull
  217 + // required methoMiso and mark this as a syncable dataset.
  218 + if (this.parent.syncable === true) {
  219 + _.extend(this, Dataset.Events);
  220 + this.syncable = true;
  221 + }
  222 +
  223 + this.idAttribute = this.parent.idAttribute;
  224 +
  225 + // save filter
  226 + this.filter = { };
  227 + this.filter.columns = _.bind(this._columnFilter(options.filter.columns || undefined), this);
  228 + this.filter.rows = _.bind(this._rowFilter(options.filter.rows || undefined), this);
  229 +
  230 + // initialize columns.
  231 + this._columns = this._selectData();
  232 +
  233 + Dataset.Builder.cacheColumns(this);
  234 + Dataset.Builder.cacheRows(this);
  235 +
  236 + // bind to parent if syncable
  237 + if (this.syncable) {
  238 + this.parent.bind("change", this._sync, this);
  239 + }
  240 + },
  241 +
  242 + // Syncs up the current view based on a passed delta.
  243 + _sync : function(event) {
  244 + var deltas = event.deltas, eventType = null;
  245 +
  246 + // iterate over deltas and update rows that are affected.
  247 + _.each(deltas, function(d, deltaIndex) {
  248 +
  249 + // find row position based on delta _id
  250 + var rowPos = this._rowPositionById[d[this.idAttribute]];
  251 +
  252 + // ===== ADD NEW ROW
  253 +
  254 + if (typeof rowPos === "undefined" && Dataset.Event.isAdd(d)) {
  255 + // this is an add event, since we couldn't find an
  256 + // existing row to update and now need to just add a new
  257 + // one. Use the delta's changed properties as the new row
  258 + // if it passes the filter.
  259 + if (this.filter.rows && this.filter.rows(d.changed)) {
  260 + this._add(d.changed);
  261 + eventType = "add";
  262 + }
  263 + } else {
  264 +
  265 + //===== UPDATE EXISTING ROW
  266 + if (rowPos === "undefined") { return; }
  267 +
  268 + // iterate over each changed property and update the value
  269 + _.each(d.changed, function(newValue, columnName) {
  270 +
  271 + // find col position based on column name
  272 + var colPos = this._columnPositionByName[columnName];
  273 + if (_.isUndefined(colPos)) { return; }
  274 + this._columns[colPos].data[rowPos] = newValue;
  275 +
  276 + eventType = "update";
  277 + }, this);
  278 + }
  279 +
  280 +
  281 + // ====== DELETE ROW (either by event or by filter.)
  282 + // TODO check if the row still passes filter, if not
  283 + // delete it.
  284 + var row = this.rowByPosition(rowPos);
  285 +
  286 + // if this is a delete event OR the row no longer
  287 + // passes the filter, remove it.
  288 + if (Dataset.Event.isRemove(d) ||
  289 + (this.filter.row && !this.filter.row(row))) {
  290 +
  291 + // Since this is now a delete event, we need to convert it
  292 + // to such so that any child views, know how to interpet it.
  293 +
  294 + var newDelta = {
  295 + old : this.rowByPosition(rowPos),
  296 + changed : {}
  297 + };
  298 + newDelta[this.idAttribute] = d[this.idAttribute];
  299 +
  300 + // replace the old delta with this delta
  301 + event.deltas.splice(deltaIndex, 1, newDelta);
  302 +
  303 + // remove row since it doesn't match the filter.
  304 + this._remove(rowPos);
  305 + eventType = "delete";
  306 + }
  307 +
  308 + }, this);
  309 +
  310 + // trigger any subscribers
  311 + if (this.syncable) {
  312 + this.trigger(eventType, event);
  313 + this.trigger("change", event);
  314 + }
  315 + },
  316 +
  317 + /**
  318 + * Returns a dataset view based on the filtration parameters
  319 + * Parameters:
  320 + * filter - object with optional columns array and filter object/function
  321 + * options - Options.
  322 + * Returns:
  323 + * new Miso.Dataset.DataView
  324 + */
  325 + where : function(filter, options) {
  326 + options = options || {};
  327 + options.filter = options.filter || {};
  328 + if ( _.isFunction(filter) ) {
  329 + options.filter.rows = filter;
  330 + } else {
  331 + options.filter = filter;
  332 + }
  333 +
  334 + options.parent = this;
  335 +
  336 + return new Dataset.DataView(options);
  337 + },
  338 +
  339 + _selectData : function() {
  340 + var selectedColumns = [];
  341 +
  342 + _.each(this.parent._columns, function(parentColumn) {
  343 +
  344 + // check if this column passes the column filter
  345 + if (this.filter.columns(parentColumn)) {
  346 + selectedColumns.push(new Dataset.Column({
  347 + name : parentColumn.name,
  348 + data : [],
  349 + type : parentColumn.type,
  350 + _id : parentColumn._id
  351 + }));
  352 + }
  353 +
  354 + }, this);
  355 +
  356 + // get the data that passes the row filter.
  357 + this.parent.each(function(row) {
  358 +
  359 + if (!this.filter.rows(row)) {
  360 + return;
  361 + }
  362 +
  363 + for(var i = 0; i < selectedColumns.length; i++) {
  364 + selectedColumns[i].data.push(row[selectedColumns[i].name]);
  365 + }
  366 + }, this);
  367 +
  368 + return selectedColumns;
  369 + },
  370 +
  371 + /**
  372 + * Returns a normalized version of the column filter function
  373 + * that can be executed.
  374 + * Parameters:
  375 + * columnFilter - function or column name
  376 + */
  377 + _columnFilter: function(columnFilter) {
  378 + var columnSelector;
  379 +
  380 + // if no column filter is specified, then just
  381 + // return a passthrough function that will allow
  382 + // any column through.
  383 + if (_.isUndefined(columnFilter)) {
  384 + columnSelector = function() {
  385 + return true;
  386 + };
  387 + } else { //array
  388 + if (_.isString(columnFilter) ) {
  389 + columnFilter = [ columnFilter ];
  390 + }
  391 + columnFilter.push(this.idAttribute);
  392 + columnSelector = function(column) {
  393 + return _.indexOf(columnFilter, column.name) === -1 ? false : true;
  394 + };
  395 + }
  396 +
  397 + return columnSelector;
  398 + },
  399 +
  400 + /**
  401 + * Returns a normalized row filter function
  402 + * that can be executed
  403 + */
  404 + _rowFilter: function(rowFilter) {
  405 +
  406 + var rowSelector;
  407 +
  408 + //support for a single ID;
  409 + if (_.isNumber(rowFilter)) {
  410 + rowFilter = [rowFilter];
  411 + }
  412 +
  413 + if (_.isUndefined(rowFilter)) {
  414 + rowSelector = function() {
  415 + return true;
  416 + };
  417 +
  418 + } else if (_.isFunction(rowFilter)) {
  419 + rowSelector = rowFilter;
  420 +
  421 + } else { //array
  422 + rowSelector = _.bind(function(row) {
  423 + return _.indexOf(rowFilter, row[this.idAttribute]) === -1 ?
  424 + false :
  425 + true;
  426 + }, this);
  427 + }
  428 +
  429 + return rowSelector;
  430 + },
  431 +
  432 + /**
  433 + * Returns a dataset view of the given column name
  434 + * Parameters:
  435 + * name - name of the column to be selected
  436 + * Returns:
  437 + * Miso.Column.
  438 + */
  439 + column : function(name) {
  440 + return this._column(name);
  441 + },
  442 +
  443 + _column : function(name) {
  444 + if (_.isUndefined(this._columnPositionByName)) { return undefined; }
  445 + var pos = this._columnPositionByName[name];
  446 + return this._columns[pos];
  447 + },
  448 +
  449 + /**
  450 + * Returns a dataset view of the given columns
  451 + * Parameters:
  452 + * columnsArray - an array of column names
  453 + * Returns:
  454 + * Miso.DataView.
  455 + */
  456 + columns : function(columnsArray) {
  457 + return new Dataset.DataView({
  458 + filter : { columns : columnsArray },
  459 + parent : this
  460 + });
  461 + },
  462 +
  463 + /**
  464 + * Returns the names of all columns, not including id column.
  465 + * Returns:
  466 + * columnNames array
  467 + */
  468 + columnNames : function() {
  469 + var cols = _.pluck(this._columns, 'name');
  470 + return _.reject(cols, function( colName ) {
  471 + return colName === this.idAttribute || colName === '_oids';
  472 + }, this);
  473 + },
  474 +
  475 + /**
  476 + * Returns true if a column exists, false otherwise.
  477 + * Parameters:
  478 + * name (string)
  479 + * Returns
  480 + * true | false
  481 + */
  482 + hasColumn : function(name) {
  483 + return (!_.isUndefined(this._columnPositionByName[name]));
  484 + },
  485 +
  486 + /**
  487 + * Iterates over all rows in the dataset
  488 + * Paramters:
  489 + * iterator - function that is passed each row
  490 + * iterator(rowObject, index, dataset)
  491 + * context - options object. Optional.
  492 + */
  493 + each : function(iterator, context) {
  494 + for(var i = 0; i < this.length; i++) {
  495 + iterator.apply(context || this, [this.rowByPosition(i), i]);
  496 + }
  497 + },
  498 +
  499 + /**
  500 + * Iterates over all rows in the dataset in reverse order
  501 + * Parameters:
  502 + * iterator - function that is passed each row
  503 + * iterator(rowObject, index, dataset)
  504 + * context - options object. Optional.
  505 + */
  506 + reverseEach : function(iterator, context) {
  507 + for(var i = this.length-1; i >= 0; i--) {
  508 + iterator.apply(context || this, [this.rowByPosition(i), i]);
  509 + }
  510 + },
  511 +
  512 + /**
  513 + * Iterates over each column.
  514 + * Parameters:
  515 + * iterator - function that is passed:
  516 + * iterator(colName, column, index)
  517 + * context - options object. Optional.
  518 + */
  519 + eachColumn : function(iterator, context) {
  520 + // skip id col
  521 + var cols = this.columnNames();
  522 + for(var i = 0; i < cols.length; i++) {
  523 + iterator.apply(context || this, [cols[i], this.column(cols[i]), i]);
  524 + }
  525 + },
  526 +
  527 + /**
  528 + * Returns a single row based on its position (NOT ID.)
  529 + * Paramters:
  530 + * i - position index
  531 + * Returns:
  532 + * row object representation
  533 + */
  534 + rowByPosition : function(i) {
  535 + return this._row(i);
  536 + },
  537 +
  538 + /**
  539 + * Returns a single row based on its id (NOT Position.)
  540 + * Parameters:
  541 + * id - unique id
  542 + * Returns:
  543 + * row object representation
  544 + */
  545 + rowById : function(id) {
  546 + return this._row(this._rowPositionById[id]);
  547 + },
  548 +
  549 + _row : function(pos) {
  550 + var row = {};
  551 + _.each(this._columns, function(column) {
  552 + row[column.name] = column.data[pos];
  553 + });
  554 + return row;
  555 + },
  556 + _remove : function(rowId) {
  557 + var rowPos = this._rowPositionById[rowId];
  558 +
  559 + // remove all values
  560 + _.each(this._columns, function(column) {
  561 + column.data.splice(rowPos, 1);
  562 + });
  563 +
  564 + // update caches
  565 + delete this._rowPositionById[rowId];
  566 + this._rowIdByPosition.splice(rowPos, 1);
  567 + this.length--;
  568 +
  569 + return this;
  570 + },
  571 +
  572 + _add : function(row, options) {
  573 +
  574 + // first coerce all the values appropriatly
  575 + _.each(row, function(value, key) {
  576 + var column = this.column(key);
  577 +
  578 + // is this a computed column? if so throw an error
  579 + if (column.isComputed()) {
  580 + throw "You're trying to update a computed column. Those get computed!";
  581 + }
  582 +
  583 + // if we suddenly see values for data that didn't exist before as a column
  584 + // just drop it. First fetch defines the column structure.
  585 + if (typeof column !== "undefined") {
  586 + var Type = Dataset.types[column.type];
  587 +
  588 + // test if value matches column type
  589 + if (column.force || Type.test(row[column.name], column)) {
  590 +
  591 + // do we have a before filter? If so, pass it through that first
  592 + if (!_.isUndefined(column.before)) {
  593 + row[column.name] = column.before(row[column.name]);
  594 + }
  595 +
  596 + // coerce it.
  597 + row[column.name] = Type.coerce(row[column.name], column);
  598 +
  599 + } else {
  600 + throw("incorrect value '" + row[column.name] +
  601 + "' of type " + Dataset.typeOf(row[column.name], column) +
  602 + " passed to column '" + column.name + "' with type " + column.type);
  603 +
  604 + }
  605 + }
  606 + }, this);
  607 +
  608 + // do we have any computed columns? If so we need to calculate their values.
  609 + if (this._computedColumns) {
  610 + _.each(this._computedColumns, function(column) {
  611 + var newVal = column.compute(row);
  612 + row[column.name] = newVal;
  613 + });
  614 + }
  615 +
  616 + // if we don't have a comparator, just append them at the end.
  617 + if (_.isUndefined(this.comparator)) {
  618 +
  619 + // add all data
  620 + _.each(this._columns, function(column) {
  621 + if (!column.isComputed()) {
  622 + column.data.push(!_.isUndefined(row[column.name]) && !_.isNull(row[column.name]) ? row[column.name] : null);
  623 + }
  624 + });
  625 +
  626 + this.length++;
  627 +
  628 + // add row indeces to the cache
  629 + this._rowIdByPosition = this._rowIdByPosition || (this._rowIdByPosition = []);
  630 + this._rowPositionById = this._rowPositionById || (this._rowPositionById = {});
  631 +
  632 + // if this row already exists, throw an error
  633 + if (typeof this._rowPositionById[row[this.idAttribute]] !== "undefined") {
  634 + throw "The id " + row[this.idAttribute] + " is not unique. The " + this.idAttribute + " column must be unique";
  635 + }
  636 +
  637 + this._rowPositionById[row[this.idAttribute]] = this._rowIdByPosition.length;
  638 + this._rowIdByPosition.push(row[this.idAttribute]);
  639 +
  640 + // otherwise insert them in the right place. This is a somewhat
  641 + // expensive operation.
  642 + } else {
  643 +
  644 + var insertAt = function(at, value, into) {
  645 + Array.prototype.splice.apply(into, [at, 0].concat(value));
  646 + };
  647 +
  648 + var i;
  649 + this.length++;
  650 + for(i = 0; i < this.length; i++) {
  651 + var row2 = this.rowByPosition(i);
  652 + if (_.isUndefined(row2[this.idAttribute]) || this.comparator(row, row2) < 0) {
  653 +
  654 + _.each(this._columns, function(column) {
  655 + insertAt(i, (row[column.name] ? row[column.name] : null), column.data);
  656 + });
  657 +
  658 + break;
  659 + }
  660 + }
  661 +
  662 + // rebuild position cache...
  663 + // we could splice it in but its safer this way.
  664 + this._rowIdByPosition = [];
  665 + this._rowPositionById = {};
  666 + this.each(function(row, i) {
  667 + this._rowIdByPosition.push(row[this.idAttribute]);
  668 + this._rowPositionById[row[this.idAttribute]] = i;
  669 + }, this);
  670 + }
  671 +
  672 + return this;
  673 + },
  674 +
  675 + /**
  676 + * Returns a dataset view of filtered rows
  677 + * @param {function|array} filter - a filter function or object,
  678 + * the same as where
  679 + */
  680 + rows : function(filter) {
  681 + return new Dataset.DataView({
  682 + filter : { rows : filter },
  683 + parent : this
  684 + });
  685 + },
  686 +
  687 + /**
  688 + * Sort rows based on comparator
  689 + *
  690 + * roughly taken from here:
  691 + * http://jxlib.googlecode.com/svn-history/r977/trunk/src/Source/Data/heapsort.js
  692 + * License:
  693 + * Copyright (c) 2009, Jon Bomgardner.
  694 + * This file is licensed under an MIT style license
  695 + * Parameters:
  696 + * options - Optional
  697 + */
  698 + sort : function(args) {
  699 + var options = {};
  700 +
  701 + //If the first param is the comparator, set it as such.
  702 + if ( _.isFunction(args) ) {
  703 + options.comparator = args;
  704 + } else {
  705 + options = args || options;
  706 + }
  707 +
  708 + if (options.comparator) {
  709 + this.comparator = options.comparator;
  710 + }
  711 +
  712 + if (_.isUndefined(this.comparator)) {
  713 + throw new Error("Cannot sort without this.comparator.");
  714 + }
  715 +
  716 + var count = this.length, end;
  717 +
  718 + if (count === 1) {
  719 + // we're done. only one item, all sorted.
  720 + return;
  721 + }
  722 +
  723 + var swap = _.bind(function(from, to) {
  724 +
  725 + // move second row over to first
  726 + var row = this.rowByPosition(to);
  727 +
  728 + _.each(row, function(value, column) {
  729 + var colPosition = this._columnPositionByName[column],
  730 + value2 = this._columns[colPosition].data[from];
  731 + this._columns[colPosition].data.splice(from, 1, value);
  732 + this._columns[colPosition].data.splice(to, 1, value2);
  733 + }, this);
  734 + }, this);
  735 +
  736 + var siftDown = _.bind(function(start, end) {
  737 + var root = start, child;
  738 + while (root * 2 <= end) {
  739 + child = root * 2;
  740 + var root_node = this.rowByPosition(root);
  741 +
  742 + if ((child + 1 < end) &&
  743 + this.comparator(
  744 + this.rowByPosition(child),
  745 + this.rowByPosition(child+1)
  746 + ) < 0) {
  747 + child++;
  748 + }
  749 +
  750 + if (this.comparator(
  751 + root_node,
  752 + this.rowByPosition(child)) < 0) {
  753 +
  754 + swap(root, child);
  755 + root = child;
  756 + } else {
  757 + return;
  758 + }
  759 +
  760 + }
  761 +
  762 + }, this);
  763 +
  764 +
  765 + // puts data in max-heap order
  766 + var heapify = function(count) {
  767 + var start = Math.round((count - 2) / 2);
  768 + while (start >= 0) {
  769 + siftDown(start, count - 1);
  770 + start--;
  771 + }
  772 + };
  773 +
  774 + if (count > 2) {
  775 + heapify(count);
  776 +
  777 + end = count - 1;
  778 + while (end > 1) {
  779 +
  780 + swap(end, 0);
  781 + end--;
  782 + siftDown(0, end);
  783 +
  784 + }
  785 + } else {
  786 + if (this.comparator(
  787 + this.rowByPosition(0),
  788 + this.rowByPosition(1)) > 0) {
  789 + swap(0,1);
  790 + }
  791 + }
  792 +
  793 + // check last two rows, they seem to always be off sync.
  794 + if (this.comparator(
  795 + this.rowByPosition(this.length - 2),
  796 + this.rowByPosition(this.length - 1)) > 0) {
  797 + swap(this.length - 1,this.length - 2);
  798 + }
  799 +
  800 + if (this.syncable && options.silent) {
  801 + this.trigger("sort");
  802 + }
  803 + return this;
  804 + },
  805 +
  806 + /**
  807 + * Exports a version of the dataset in json format.
  808 + * Returns:
  809 + * Array of rows.
  810 + */
  811 + toJSON : function() {
  812 + var rows = [];
  813 + for(var i = 0; i < this.length; i++) {
  814 + rows.push(this.rowByPosition(i));
  815 + }
  816 + return rows;
  817 + }
  818 + });
  819 +
  820 +}(this, _));
  821 +
  822 +/**
  823 +Library Deets go here
  824 +USE OUR CODES
  825 +
  826 +Version 0.0.1.2
  827 +*/
  828 +
  829 +(function(global, _, moment) {
  830 +
  831 + var Dataset = global.Miso.Dataset;
  832 +
  833 + // take on miso dataview's prototype
  834 + Dataset.prototype = new Dataset.DataView();
  835 +
  836 + // add dataset methods to dataview.
  837 + _.extend(Dataset.prototype, {
  838 +
  839 + /**
  840 + * @private
  841 + * Internal initialization method. Reponsible for data parsing.
  842 + * @param {object} options - Optional options
  843 + */
  844 + _initialize: function(options) {
  845 +
  846 + // is this a syncable dataset? if so, pull
  847 + // required methods and mark this as a syncable dataset.
  848 + if (options.sync === true) {
  849 + _.extend(this, Dataset.Events);
  850 + this.syncable = true;
  851 + }
  852 +
  853 + this.idAttribute = options.idAttribute || '_id';
  854 +
  855 + // initialize importer from options or just create a blank
  856 + // one for now, we'll detect it later.
  857 + this.importer = options.importer || null;
  858 +
  859 + // default parser is object parser, unless otherwise specified.
  860 + this.parser = options.parser || Dataset.Parsers.Obj;
  861 +
  862 + // figure out out if we need another parser.
  863 + if (_.isUndefined(options.parser)) {
  864 + if (options.strict) {
  865 + this.parser = Dataset.Parsers.Strict;
  866 + } else if (options.delimiter) {
  867 + this.parser = Dataset.Parsers.Delimited;
  868 + }
  869 + }
  870 +
  871 + // initialize the proper importer
  872 + if (this.importer === null) {
  873 + if (options.url) {
  874 +
  875 + if (!options.interval) {
  876 + this.importer = Dataset.Importers.Remote;
  877 + } else {
  878 + this.importer = Dataset.Importers.Polling;
  879 + this.interval = options.interval;
  880 + }
  881 +
  882 + } else {
  883 + this.importer = Dataset.Importers.Local;
  884 + }
  885 + }
  886 +
  887 + // initialize importer and parser
  888 + this.parser = new this.parser(options);
  889 +
  890 + if (this.parser instanceof Dataset.Parsers.Delimited) {
  891 + options.dataType = "text";
  892 + }
  893 +
  894 + this.importer = new this.importer(options);
  895 +
  896 + // save comparator if we have one
  897 + if (options.comparator) {
  898 + this.comparator = options.comparator;
  899 + }
  900 +
  901 + // if we have a ready callback, save it too
  902 + if (options.ready) {
  903 + this.ready = options.ready;
  904 + }
  905 +
  906 + // If new data is being fetched and we want to just
  907 + // replace existing rows, save this flag.
  908 + if (options.resetOnFetch) {
  909 + this.resetOnFetch = options.resetOnFetch;
  910 + }
  911 +
  912 + // if new data is being fetched and we want to make sure
  913 + // only new rows are appended, a column must be provided
  914 + // against which uniqueness will be checked.
  915 + // otherwise we are just going to blindly add rows.
  916 + if (options.uniqueAgainst) {
  917 + this.uniqueAgainst = options.uniqueAgainst;
  918 + }
  919 +
  920 + // if there is no data and no url set, we must be building
  921 + // the dataset from scratch, so create an id column.
  922 + if (_.isUndefined(options.data) && _.isUndefined(options.url)) {
  923 + this._addIdColumn();
  924 + }
  925 +
  926 + // if for any reason, you want to use a different deferred
  927 + // implementation, pass it as an option
  928 + if (options.deferred) {
  929 + this.deferred = options.deferred;
  930 + } else {
  931 + this.deferred = new _.Deferred();
  932 + }
  933 +
  934 + //build any columns present in the constructor
  935 + if ( options.columns ) {
  936 + this.addColumns(options.columns);
  937 + }
  938 + },
  939 +
  940 + /**
  941 + * Responsible for actually fetching the data based on the initialized dataset.
  942 + * Note that this needs to be called for either local or remote data.
  943 + * There are three different ways to use this method:
  944 + * ds.fetch() - will just fetch the data based on the importer. Note that for async
  945 + * fetching this isn't blocking so don't put your next set of instructions
  946 + * expecting the data to be there.
  947 + * ds.fetch({
  948 + * success: function() {
  949 + * // do stuff
  950 + * // this is the dataset.
  951 + * },
  952 + * error : function(e) {
  953 + * // do stuff
  954 + * }
  955 + * }) - Allows you to pass success and error callbacks that will be called once data
  956 + * is property fetched.
  957 + *
  958 + * _.when(ds.fetch(), function() {
  959 + * // do stuff
  960 + * // note 'this' is NOT the dataset.
  961 + * }) - Allows you to use deferred behavior to potentially chain multiple datasets.
  962 + *
  963 + * @param {object} options Optional success/error callbacks.
  964 + **/
  965 + fetch : function(options) {
  966 + options = options || {};
  967 +
  968 + var dfd = this.deferred;
  969 +
  970 + if ( _.isNull(this.importer) ) {
  971 + throw "No importer defined";
  972 + }
  973 +
  974 + this.importer.fetch({
  975 + success: _.bind(function( data ) {
  976 +
  977 + try {
  978 + this._apply( data );
  979 + } catch (e) {
  980 + if (options.error) {
  981 + options.error.call(this, e);
  982 + } else {
  983 + throw e;
  984 + }
  985 + }
  986 +
  987 + // if a comparator was defined, sort the data
  988 + if (this.comparator) {
  989 + this.sort();
  990 + }
  991 +
  992 + if (this.ready) {
  993 + this.ready.call(this);
  994 + }
  995 +
  996 + if (options.success) {
  997 + options.success.call(this);
  998 + }
  999 +
  1000 + // Ensure the context of the promise is set to the Dataset
  1001 + dfd.resolveWith(this, [this]);
  1002 +
  1003 + }, this),
  1004 +
  1005 + error : _.bind(function(e) {
  1006 + if (options.error) {
  1007 + options.error.call(this, e);
  1008 + }
  1009 +
  1010 + dfd.reject(e);
  1011 + }, this)
  1012 + });
  1013 +
  1014 + return dfd.promise();
  1015 + },
  1016 +
  1017 + //These are the methods that will be used to determine
  1018 + //how to update a dataset's data when fetch() is called
  1019 + _applications : {
  1020 +
  1021 + //Update existing values, used the pass column to match
  1022 + //incoming data to existing rows.
  1023 + againstColumn : function(data) {
  1024 + var rows = [],
  1025 + colNames = _.keys(data),
  1026 + row,
  1027 + uniqName = this.uniqueAgainst,
  1028 + uniqCol = this.column(uniqName),
  1029 + toAdd = [],
  1030 + toUpdate = [],
  1031 + toRemove = [];
  1032 +
  1033 + _.each(data[uniqName], function(key, dataIndex) {
  1034 + var rowIndex = uniqCol.data.indexOf( Dataset.types[uniqCol.type].coerce(key) );
  1035 +
  1036 + var row = {};
  1037 + _.each(data, function(col, name) {
  1038 + row[name] = col[dataIndex];
  1039 + });
  1040 +
  1041 + if (rowIndex === -1) {
  1042 + toAdd.push( row );
  1043 + } else {
  1044 + toUpdate.push( row );
  1045 + row[this.idAttribute] = this.rowById(this.column(this.idAttribute).data[rowIndex])[this.idAttribute];
  1046 + this.update(row);
  1047 + }
  1048 + }, this);
  1049 + if (toAdd.length > 0) {
  1050 + this.add(toAdd);
  1051 + }
  1052 + },
  1053 +
  1054 + //Always blindly add new rows
  1055 + blind : function( data ) {
  1056 + var columnName, columnData, rows = [], row;
  1057 +
  1058 + // figure out the length of rows we have.
  1059 + var colNames = _.keys(data),
  1060 + dataLength = _.max(_.map(colNames, function(name) {
  1061 + return data[name].length;
  1062 + }, this));
  1063 +
  1064 + // build row objects
  1065 + for( var i = 0; i < dataLength; i++) {
  1066 + row = {};
  1067 + for(var j = 0; j < colNames.length; j++) {
  1068 + row[colNames[j]] = data[colNames[j]][i];
  1069 + }
  1070 + rows.push(row);
  1071 + }
  1072 +
  1073 + this.add(rows);
  1074 + }
  1075 + },
  1076 +
  1077 + //Takes a dataset and some data and applies one to the other
  1078 + _apply : function( data ) {
  1079 +
  1080 + var parsed = this.parser.parse( data );
  1081 +
  1082 + // first time fetch
  1083 + if ( !this.fetched ) {
  1084 +
  1085 + // create columns (inc _id col.)
  1086 + this._addIdColumn();
  1087 + this.addColumns( _.map(parsed.columns, function( name ) {
  1088 + return { name : name };
  1089 + })
  1090 + );
  1091 +
  1092 + // detect column types, add all rows blindly and cache them.
  1093 + Dataset.Builder.detectColumnTypes(this, parsed.data);
  1094 + this._applications.blind.call( this, parsed.data );
  1095 +
  1096 + this.fetched = true;
  1097 +
  1098 + // reset on fetch
  1099 + } else if (this.resetOnFetch) {
  1100 +
  1101 + // clear the data
  1102 + this.reset();
  1103 +
  1104 + // blindly add the data.
  1105 + this._applications.blind.call( this, parsed.data );
  1106 +
  1107 + // append
  1108 + } else if (this.uniqueAgainst) {
  1109 +
  1110 + // make sure we actually have this column
  1111 + if (!this.hasColumn(this.uniqueAgainst)) {
  1112 + throw new Error("You requested a unique add against a column that doesn't exist.");
  1113 + }
  1114 +
  1115 + this._applications.againstColumn.call(this, parsed.data);
  1116 +
  1117 + // polling fetch, just blindly add rows
  1118 + } else {
  1119 + this._applications.blind.call( this, parsed.data );
  1120 + }
  1121 +
  1122 + Dataset.Builder.cacheRows(this);
  1123 + },
  1124 +
  1125 + /**
  1126 + * Adds columns to the dataset.
  1127 + */
  1128 + addColumns : function( columns ) {
  1129 + _.each(columns, function( column ) {
  1130 + this.addColumn( column );
  1131 + }, this);
  1132 + },
  1133 +
  1134 + /**
  1135 + * Allows adding of a computed column. A computed column is
  1136 + * a column that is somehow based on the other existing columns.
  1137 + * Parameters:
  1138 + * name : name of new column
  1139 + * type : The type of the column based on existing types.
  1140 + * func : The way that the column is derived. It takes a row as a parameter.
  1141 + */
  1142 + addComputedColumn : function(name, type, func) {
  1143 + // check if we already ahve a column by this name.
  1144 + if ( !_.isUndefined(this.column(name)) ) {
  1145 + throw "There is already a column by this name.";
  1146 + } else {
  1147 +
  1148 + // check that this is a known type.
  1149 + if (typeof Dataset.types[type] === "undefined") {
  1150 + throw "The type " + type + " doesn't exist";
  1151 + }
  1152 +
  1153 + var column = new Dataset.Column({
  1154 + name : name,
  1155 + type : type,
  1156 + func : _.bind(func, this)
  1157 + });
  1158 +
  1159 + this._columns.push(column);
  1160 + this._computedColumns.push(column);
  1161 + this._columnPositionByName[column.name] = this._columns.length - 1;
  1162 +
  1163 + // do we already have data? if so compute the values for this column.
  1164 + if (this.length > 0) {
  1165 + this.each(function(row, i) {
  1166 + column.compute(row, i);
  1167 + }, this);
  1168 + }
  1169 +
  1170 + return column;
  1171 + }
  1172 + },
  1173 +
  1174 + /**
  1175 + * Adds a single column to the dataset
  1176 + * Parameters:
  1177 + * column : a set of properties describing a column (name, type, data etc.)
  1178 + * Returns
  1179 + * Miso.Column object.
  1180 + */
  1181 + addColumn : function(column) {
  1182 + //don't create a column that already exists
  1183 + if ( !_.isUndefined(this.column(column.name)) ) {
  1184 + return false;
  1185 + }
  1186 +
  1187 + column = new Dataset.Column( column );
  1188 +
  1189 + this._columns.push( column );
  1190 + this._columnPositionByName[column.name] = this._columns.length - 1;
  1191 +
  1192 + return column;
  1193 + },
  1194 +
  1195 + /**
  1196 + * Adds an id column to the column definition. If a count
  1197 + * is provided, also generates unique ids.
  1198 + * Parameters:
  1199 + * count - the number of ids to generate.
  1200 + */
  1201 + _addIdColumn : function( count ) {
  1202