Exemplo n.º 1
0
define(function(require) {

    var Model = require('base/model');

    var Light = Model.extend({

        defaults: {
            id:     null,
            name:   "",
            status: "DIM",
            model:  "",
            type:   "",
            isDimmable: false,
            dimLevel: null
            // "element": null
        },

        initialize: function(){
            this.on("change:status", this.statusChangeHandler);
        },

        statusChangeHandler: function(model, value) {
            this.save({
                status: value
            }, {patch: true});
        }

    });

    return Light;

});
Exemplo n.º 2
0
const ToolModel = Model.extend({
  /**
   * Initializes the tool model.
   * @param {Tool}   the tool this tool model belongs to
   * @param {Object} values The initial values of this model
   */
  init(tool, external_model) {
    this._id = utils.uniqueId("tm");
    this._type = "tool";
    this._component = tool;
    this.dimensionManager = DimensionManager(this);
    this.dataManager = DataManager(this);

    // defaults are defined on the Tool
    // this way, each tool can have it's own default model
    this.getClassDefaults = () => tool.default_model;

    // combine listeners from tool and external page to one object
    const listeners = utils.extend(tool.getToolListeners(), external_model.bind);
    delete external_model.bind; // bind shouldn't go to model tree

    this._super(tool.name, external_model, null, listeners);
  },

  /**
   * @return {object} Defaults of tool model and children
   * Tool defaults overwrite other models' default
   */
  getDefaults() {
    return utils.deepExtend({}, this.getSubmodelDefaults(), this.getClassDefaults());
  },

  validate() {

    const max = 10;
    const c = 0;
    const _this = this;

    function validate_func(c) {
      // ToolModel uses validate function declared on Tool so each Tool can have its own validation.
      const model = JSON.stringify(_this.getPlainObject());
      _this._component.validate(_this);
      const model2 = JSON.stringify(_this.getPlainObject());

      if (c >= max) {
        utils.error("Max validation loop.");
      } else if (model !== model2) {
        validate_func(c++);
      }
    }

    validate_func(c);
  },

  setReady(arg) {
    if (arg !== false) this.checkTimeLimits();
    this._super(arg);
  },


  checkTimeLimits() {

    const time = this.state.time;
    const timeDependentMarkers = this.state.getSubmodels(false /*return as array*/, f => f._type == "marker" && f.space.includes("time"));

    if (!time || !timeDependentMarkers.length) return;

    const tLimits = {};
    timeDependentMarkers.forEach(marker => {
      const l = marker.getTimeLimits();
      tLimits.min = d3.max([tLimits.min, l.min]);
      tLimits.max = d3.min([tLimits.max, l.max]);
    });

    if (!utils.isDate(tLimits.min) || !utils.isDate(tLimits.max))
      return utils.warn("checkTimeLimits(): min-max look wrong: " + tLimits.min + " " + tLimits.max + ". Expecting Date objects. Ensure that time is properly parsed in the data from reader");

    // change start and end (but keep startOrigin and endOrigin for furhter requests)
    const newTime = {};
    if (time.start - tLimits.min != 0 || !time.start && !time.startOrigin) newTime["start"] = d3.max([tLimits.min, time.parse(time.startOrigin)]);
    if (time.end - tLimits.max != 0 || !time.end && !time.endOrigin) newTime["end"] = d3.min([tLimits.max, time.parse(time.endOrigin)]);

    time.setTreeFreezer(true);
    time.set(newTime, false, false);

    if (newTime.start || newTime.end) {
      timeDependentMarkers.forEach(marker => {
        utils.forEach(marker.getSubhooks(), hook => {
          if (hook.which == time.dim) {
            hook.buildScale();
          }
        });
      });
    }
    time.setTreeFreezer(false);

    //force time validation because time.value might now fall outside of start-end
    //time.validate();
  },

});
Exemplo n.º 3
0
const DataModel = Model.extend({

  /**
   * Default values for this model
   */
  getClassDefaults() {
    const defaults = {
      reader: "csv"
    };
    return utils.deepExtend(this._super(), defaults);
  },

  trackInstances: true,

  /**
   * Initializes the data model.
   * @param {Object} values The initial values of this model
   * @param parent A reference to the parent model
   * @param {Object} bind Initial events to bind
   */
  init(name, values, parent, bind) {

    this._type = "data";

    this.queryQueue = {};
    this._collection = {};
    this._collectionPromises = {}; // stores promises, making sure we don't do one calulation twice

    //same constructor as parent, with same arguments
    this._super(name, values, parent, bind);

    this.readerObject = this.getReader();

  },

  /**
   * Loads concept properties when all other models are also starting to load data
   * @return {Promise} Promise which resolves when concepts are loaded
   */
  preloadData() {
    return this.loadDataAvailability()
      .then(this.loadConceptProps.bind(this));
  },

  /**
   * Loads resource from reader or cache
   * @param {Array} query Array with queries to be loaded
   * @param {Object} parsers An object with concepts as key and parsers as value
   * @param {*} evts ?
   */
  load(query, parsers = {}) {
    // add waffle server specific query clauses if set
    if (this.dataset) query.dataset = this.dataset;
    if (this.version) query.version = this.version;
    const dataId = DataStorage.getDataId(query, this.readerObject, parsers);
    if (dataId) {
      if (!query.grouping) return Promise.resolve(dataId);
      return DataStorage.aggregateData(dataId, query, this.readerObject, this.getConceptprops());
    }
    utils.timeStamp("Vizabi Data: Loading Data");
    EventSource.freezeAll([
      "hook_change",
      "resize"
    ]);

    return DataStorage.loadFromReader(query, parsers, this.readerObject)
      .then(dataId => {
        if (!query.grouping) return dataId;
        return DataStorage.aggregateData(dataId, query, this.readerObject, this.getConceptprops());
      })
      .then(dataId => {
        EventSource.unfreezeAll();
        return dataId;
      })
      .catch(error => {
        EventSource.unfreezeAll();
        this.handleLoadError(error, query);
      });
  },

  getAsset(assetName, callback) {
    return this.readerObject.getAsset(assetName)
      .then(response => callback(response))
      .catch(error => this.handleLoadError(error, assetName));
  },

  getReader() {
    // Create a new reader for this query
    const readerClass = Reader.get(this.reader);
    if (!readerClass) {
      throw new Error("Unknown reader: " + this.reader);
    }

    return new readerClass(this.getPlainObject());
  },

  /**
   * get data
   */
  getData(dataId, what, whatId, args) {
    // if not specified data from what query, return nothing
    if (!dataId) return utils.warn("Data.js 'get' method doesn't like the dataId you gave it: " + dataId);
    return DataStorage.getData(dataId, what, whatId, args);
  },

  loadDataAvailability() {
    const conceptsQuery = {
      select: {
        key: ["key", "value"],
        value: []
      },
      from: "concepts.schema"
    };
    const entitiesQuery = utils.extend({}, conceptsQuery, { from: "entities.schema" });
    const datapointsQuery = utils.extend({}, conceptsQuery, { from: "datapoints.schema" });

    return Promise.all([
      this.load(conceptsQuery),
      this.load(entitiesQuery),
      this.load(datapointsQuery)
    ])
      .then(this.handleDataAvailabilityResponse.bind(this))
      .catch(error => this.handleLoadError(error, {}));
  },

  handleDataAvailabilityResponse(dataIds) {
    this.keyAvailability = new Map();
    this.dataAvailability = [];
    dataIds.forEach(dataId => {
      const collection = this.getData(dataId, "query").from.split(".")[0];
      this.dataAvailability[collection] = [];
      this.getData(dataId).forEach(kvPair => {
        const key = (typeof kvPair.key === "string" ? JSON.parse(kvPair.key) : kvPair.key).sort(); // sort to get canonical form (can be removed when reader gives back canonical)

        this.dataAvailability[collection].push({
          key: new Set(key),
          value: kvPair.value
        });

        this.keyAvailability.set(key.join(","), key);
      });
    });
  },

  loadConceptProps() {
    return this.loadDataAvailability()
      .then(() => {

        // only selecting concept properties which Vizabi needs and are available in dataset
        const vizabiConceptProps = [
          "concept_type",
          "domain",
          "indicator_url",
          "color",
          "scales",
          "interpolation",
          "tags",
          "name",
          "name_short",
          "name_catalog",
          "description",
          "format"
        ];
        const availableConceptProps = this.dataAvailability.concepts.map(m => m.value);
        const availableVizabiConceptProps = vizabiConceptProps.filter(n => availableConceptProps.includes(n));

        const query = {
          select: {
            key: ["concept"],
            value: availableVizabiConceptProps
          },
          from: "concepts",
          where: {},
          language: this.getClosestModel("locale").id,
        };

        return this.load(query)
          .then(this.handleConceptPropsResponse.bind(this))
          .catch(error => this.handleLoadError(error, query));
      });

  },

  handleConceptPropsResponse(dataId) {

    this.conceptDictionary = { _default: { concept: "_default", concept_type: "string", use: "constant", scales: ["ordinal"], tags: "_root" } };
    this.conceptArray = [];

    this.getData(dataId).forEach(d => {
      const concept = {};

      concept["concept"] = d.concept;
      concept["concept_type"] = d.concept_type;
      concept["sourceLink"] = d.indicator_url;
      try {
        concept["color"] = d.color && d.color !== "" ? JSON.parse(d.color) : null;
      } catch (e) {
        concept["color"] = null;
      }
      try {
        concept["scales"] = d.scales ? JSON.parse(d.scales) : null;
      } catch (e) {
        concept["scales"] = null;
      }
      if (!concept.scales) {
        switch (d.concept_type) {
          case "measure": concept.scales = ["linear", "log"]; break;
          case "string": concept.scales = ["ordinal"]; break;
          case "entity_domain": concept.scales = ["ordinal"]; break;
          case "entity_set": concept.scales = ["ordinal"]; break;
          case "boolean": concept.scales = ["ordinal"]; break;
          case "time": concept.scales = ["time"]; break;
          default: concept.scales = ["linear", "log"];
        }
      }
      if (d.interpolation) {
        concept["interpolation"] = d.interpolation;
      } else if (d.concept_type == "measure") {
        concept["interpolation"] = concept.scales && concept.scales[0] == "log" ? "exp" : "linear";
      } else if (d.concept_type == "time") {
        concept["interpolation"] = "linear";
      } else {
        concept["interpolation"] = "stepMiddle";
      }
      concept["domain"] = d.domain;
      concept["tags"] = d.tags;
      concept["format"] = d.format;
      concept["name"] = d.name || d.concept || "";
      concept["name_catalog"] = d.name_catalog || "";
      concept["name_short"] = d.name_short || d.name || d.concept || "";
      concept["description"] = d.description;
      this.conceptDictionary[d.concept] = concept;
      this.conceptArray.push(concept);
    });

  },

  getConceptprops(which) {
    if (typeof which !== "undefined") {
      if (!this.conceptDictionary[which]) {
        utils.warn("The concept " + which + " is not found in the dictionary");
        return null;
      }
      return this.conceptDictionary[which];
    }
    return this.conceptDictionary;
  },

  getConcept({ index: index = 0, type: type = null, includeOnlyIDs: includeOnlyIDs = [], excludeIDs: excludeIDs = [] } = { }) {
    if (!type && includeOnlyIDs.length == 0 && excludeIDs.length == 0) {
      return null;
    }

    const filtered = this.conceptArray.filter(f =>
      (!type || !f.concept_type || f.concept_type === type)
      && (includeOnlyIDs.length == 0 || includeOnlyIDs.indexOf(f.concept) !== -1)
      && (excludeIDs.length == 0 || excludeIDs.indexOf(f.concept) == -1)
    );
    return filtered[index] || filtered[filtered.length - 1];
  },

  getDatasetName() {
    if (this.readerObject.getDatasetInfo) {
      const meta = this.readerObject.getDatasetInfo();
      return meta.name + (meta.version ? " " + meta.version : "");
    }
    return this._name;
  },

  setGrouping(dataId, grouping) {
    DataStorage.setGrouping(dataId, grouping);
  },

  getFrames(dataId, framesArray, keys) {
    return DataStorage.getFrames(dataId, framesArray, keys, this.getConceptprops());
  },


  getFrame(dataId, framesArray, neededFrame, keys) {
    //can only be called after getFrames()
    return DataStorage.getFrame(dataId, framesArray, neededFrame, keys);
  },

  listenFrame(dataId, framesArray, keys,  cb) {
    DataStorage.listenFrame(dataId, framesArray, keys,  cb);
  },

  handleLoadError(error, query) {
    if (utils.isObject(error)) {
      const locale = this.getClosestModel("locale");
      const translation = locale.getTFunction()(error.code, error.payload) || "";
      error = `${translation} ${error.message || ""}`.trim();
    }

    utils.warn("Problem with query: ", query);
    this._super(error);
  },

});
Exemplo n.º 4
0
const UI = Model.extend({

  screen_profiles: {
    small: {
      min_width: 0,
      min_height: 0
    },
    medium: {
      min_width: 600,
      min_height: 400
    },
    large: {
      min_width: 900,
      min_height: 520
    }
  },

  getClassDefaults() {
    const defaults = {
      presentation: false,
      buttons: [],
      dialogs: {
        popup: [],
        sidebar: [],
        moreoptions: []
      },
      splash: false
    };
    return utils.deepExtend(this._super(), defaults);
  },

  /**
   * Initializes the layout manager
   */
  init(name, values, parent, bind) {

    this._type = "ui";
    this._container = null;
    //dom element
    this._curr_profile = "small";
    this._prev_size = {};

    //resize when window resizes
    this.resizeHandler = this.resizeHandler.bind(this);
    window.addEventListener("resize", this.resizeHandler);
    bind["change:presentation"] = this.updatePresentation.bind(this);

    this._super(name, values, parent, bind);

    //TODO: remove later if IOS >10.3.2
    //https://openradar.appspot.com/31725316
    const detectIOS_10_3 = () => {
      const version = /(?:iPad|iPhone|iPod).+OS\s+(\d+)_(\d+)/.exec(navigator.userAgent);
      return version && +version[1] >= 10 && +version[2] > 2 && !window.MSStream;
    };
    if (detectIOS_10_3()) {
      this.setSize = utils.debounce(this.setSize, 500);
    }
  },

  resizeHandler(args = {}) {
    if (this._container) {
      this.setSize(args.force || false);
    }
  },

  /**
   * Calculates the size of the newly resized container
   */
  setSize(force) {
    const _this = this;
    const width = this._container.clientWidth;
    const height = this._container.clientHeight;

    /**
     * issue #1118
     * check if device is iPhone then add top margin for searchbar if it visible
     */
    if (/^((?!chrome|android).)*safari/i.test(navigator.userAgent) // browser is safari
      && navigator.userAgent.match(/iPhone/i) // checking device
    ) {
      this._container.style.top =  0;
      if (this._container.clientWidth > this._container.clientHeight // landscape mode
        && this._container.clientWidth < 700) {  // small device
        const bodyHeight = this._container.clientHeight;
        const windowHeight = window.innerHeight;
        if (2 < (bodyHeight - windowHeight) && (bodyHeight - windowHeight) <= 45) { // check searchbar is visible
          this._container.style.top =  44 + "px";
          document.body.scrollTop = 44; // scrolling empty space
        }
      }
    }

    if (!force && this._prev_size && this._prev_size.width === width && this._prev_size.height === height
       || !width || !height) {
      return;
    }

    // choose profile depending on size
    utils.forEach(this.screen_profiles, (range, size) => {
      //remove class
      utils.removeClass(_this._container, class_prefix + size);
      //find best fit
      if (width >= range.min_width && height >= range.min_height) {
        _this._curr_profile = size;
      }
    });

    //update size class
    utils.addClass(this._container, class_prefix + this._curr_profile);

    //toggle, untoggle classes based on orientation
    utils.classed(this._container, class_portrait, width < height);
    utils.classed(this._container, class_landscape, !(width < height));

    this._prev_size.width = width;
    this._prev_size.height = height;
    this.trigger("resize");
  },

  /**
   * Sets the container for this layout
   * @param container DOM element
   */
  setContainer(container) {
    if (!container.clientWidth || !container.clientHeight) utils.warn("Vizabi is being initialised with a container of incorrect width or height. While this may be handled without a crash, it's not considered a healthy behavior. You should call Vizabi(placeholder,...) function when placeholder is not display:none and has some >0 !=auto width and height");

    this._container = container;
    this.setSize();
    this.updatePresentation();
  },

  /**
   * Sets the presentation mode for this layout
   * @param {Bool} presentation mode on or off
   */
  updatePresentation() {
    utils.classed(this._container, class_prefix + class_presentation, this.presentation);
    this.trigger("resize");
  },

  getPresentationMode() {
    return this.presentation;
  },

  setRTL(flag) {
    utils.classed(this._container, class_prefix + class_rtl, flag);
  },

  /**
   * Gets the current selected profile
   * @returns {String} name of current profile
   */
  currentProfile() {
    return this._curr_profile;
  },

  clear() {
    window.removeEventListener("resize", this.resizeHandler);
  }

});
Exemplo n.º 5
0
 * Any model which may trigger a reload of data is here. Dimensions, Time, Hooks and Locales are DataConnected
 */

const DataConnected = Model.extend({

  dataConnectedChildren: [],

  checkDataChanges(changedChildren) {
    const _this = this;

    if (!changedChildren || !this.dataConnectedChildren)
      return;

    if (!utils.isArray(changedChildren) && utils.isObject(changedChildren))
      changedChildren = Object.keys(changedChildren);

    if (changedChildren.length == 0 || this.dataConnectedChildren.length == 0)
      return;

    const dataConnectedChangedChildren = changedChildren.filter(child => _this.dataConnectedChildren.indexOf(child) !== -1);

    if (dataConnectedChangedChildren.length > 0) {
      this.trigger("dataConnectedChange");
      this.startLoading();
    }

  }

});

export default DataConnected;
Exemplo n.º 6
0
var Marker = Model.extend({

  /**
   * Gets limits
   * @param {String} attr parameter
   * @returns {Object} limits (min and max)
   * this function is only needed to route the "time" to some indicator, to adjust time start and end to the max and min time available in data
   */
  getLimits: function(attr) {
    if(!this.isHook()) {
      //if there's subhooks, find the one which is an indicator
      var limits = {};
      utils.forEach(this.getSubhooks(), function(s) {
        var prop = (s.use === "property");
        if(!prop) {
          limits = s.getLimits(attr);
          return false;
        }
      });
      return limits;
    }

  },
    
    
    
    
  /**
   * gets the items associated with this hook without values
   * @param filter filter
   * @returns hooked value
   */
  getKeys: function(filter) {
      var sub = this.getSubhooks();
      var found = [];
      if(sub.length > 1) {
        utils.forEach(sub, function(s) {
          found = s.getKeys();
          return false;
        });
      }
      return found;
  },
    
    getFrame: function(time) {
        var _this = this;
        var steps = this._parent.time.getAllSteps();

        var cachePath = "";
        utils.forEach(this._dataCube, function(hook, name) {
            cachePath = cachePath + "," + name + ":" + hook.which + " " + _this._parent.time.start + " " + _this._parent.time.end;
        });
        if(!this.cachedFrames || !this.cachedFrames[cachePath]) this.getFrames();

        if(this.cachedFrames[cachePath][time]) return this.cachedFrames[cachePath][time];

        if(time < steps[0] || time > steps[steps.length - 1]) {
            //if the requested time point is out of the known range
            //then send nulls in the response
            var pValues = this.cachedFrames[cachePath][steps[0]];
            var curr = {};
            utils.forEach(pValues, function(keys, hook) {
                curr[hook] = {};
                utils.forEach(keys, function(val, key) {
                    curr[hook][key] = null;
                });
            });
            return curr;
        }


        var next = d3.bisectLeft(steps, time);

        if(next === 0) return this.cachedFrames[cachePath][steps[0]];

        var fraction = (time - steps[next - 1]) / (steps[next] - steps[next - 1]);

        var pValues = this.cachedFrames[cachePath][steps[next - 1]];
        var nValues = this.cachedFrames[cachePath][steps[next]];

        var curr = {};
        utils.forEach(pValues, function(values, hook) {
            curr[hook] = {};
            utils.forEach(values, function(val, id) {
                if( !utils.isNumber(val) ){
                    curr[hook][id] = val;
                }else{
                    var val2 = nValues[hook][id];
                    curr[hook][id] = (val==null||val2==null)? null : val + ((val2 - val) * fraction);
                }
            });
        });

        return curr;
    },

    getFrames: function() {
        var _this = this;

        var cachePath = "";
        utils.forEach(this._dataCube, function(hook, name) {
            cachePath = cachePath + "," + name + ":" + hook.which + " " + _this._parent.time.start + " " + _this._parent.time.end;
        });

        if(!this.cachedFrames) this.cachedFrames = {};
        if(this.cachedFrames[cachePath]) return this.cachedFrames[cachePath];

        var steps = this._parent.time.getAllSteps();

        this._dataCube = this._dataCube || this.getSubhooks(true)

        var result = {};
        var resultKeys = [];

        // Assemble the list of keys as an intersection of keys in all queries of all hooks
        utils.forEach(this._dataCube, function(hook, name) {

            // If hook use is constant, then we can provide no additional info about keys
            // We can just hope that we have something else than constants =) 
            if(hook.use === "constant") return;

            // Get keys in data of this hook
            var nested = _this.getDataManager().get(hook._dataId, 'nested', ["geo", "time"]);
            var keys = Object.keys(nested);

            if(resultKeys.length == 0) {
                // If ain't got nothing yet, set the list of keys to result
                resultKeys = keys;
            } else {
                // If there is result accumulated aleready, remove the keys from it that are not in this hook
                resultKeys = resultKeys.filter(function(f) {
                    return keys.indexOf(f) > -1;
                })
            }
        });

        steps.forEach(function(t) {
            result[t] = {};
        });

        utils.forEach(this._dataCube, function(hook, name) {

            if(hook.use === "constant") {
                steps.forEach(function(t) {
                    result[t][name] = {};
                    resultKeys.forEach(function(key) {
                        result[t][name][key] = hook.which;
                    });
                });

            } else if(hook.which === "geo") {
                steps.forEach(function(t) {
                    result[t][name] = {};
                    resultKeys.forEach(function(key) {
                        result[t][name][key] = key;
                    });
                });

            } else if(hook.which === "time") {
                steps.forEach(function(t) {
                    result[t][name] = {};
                    resultKeys.forEach(function(key) {
                        result[t][name][key] = new Date(t);
                    });
                });

            } else {
                var frames = _this.getDataManager().get(hook._dataId, 'frames', steps);
                utils.forEach(frames, function(frame, t) {
                    result[t][name] = frame[hook.which];
                });
            }
        });

        this.cachedFrames[cachePath] = result;
        return result;
    },
    
    

  /**
   * gets multiple values from the hook
   * @param {Object} filter Reference to the row. e.g: {geo: "swe", time: "1999", ... }
   * @param {Array} group_by How to nest e.g: ["geo"]
   * @param {Boolean} [previous = false] previous Append previous data points
   * @returns an array of values
   */
  getValues: function(filter, group_by, previous) {
    var _this = this;

    if(this.isHook()) {
      return [];
    }

    var dimTime, time, filtered, next, method, u, w, value, method;
    this._dataCube = this._dataCube || this.getSubhooks(true);
    filter = utils.clone(filter, this._getAllDimensions());
    dimTime = this._getFirstDimension({
      type: 'time'
    });
    time = new Date(filter[dimTime]); //clone date
    filter = utils.clone(filter, null, dimTime);

    var response = {};
    var f_keys = Object.keys(filter);
    var f_values = f_keys.map(function(k) {
      return filter[k];
    });

    //if there's a filter, interpolate only that
    if(f_keys.length) {
      utils.forEach(this._dataCube, function(hook, name) {
        u = hook.use;
        w = hook.which;

        if(hook.use !== "property") next = next || d3.bisectLeft(hook.getUnique(dimTime), time);        

        method = hook.getMetadata().interpolation;
        filtered = _this.getDataManager().get(hook._dataId, 'nested', f_keys);
        utils.forEach(f_values, function(v) {
          filtered = filtered[v]; //get precise array (leaf)
        });
        value = utils.interpolatePoint(filtered, u, w, next, dimTime, time, method);
        response[name] = hook.mapValue(value);

        //concat previous data points
        if(previous) {
          var values = utils.filter(filtered, filter).filter(function(d) {
            return d[dimTime] <= time;
          }).map(function(d) {
            return hook.mapValue(d[w]);
          }).concat(response[name]);
          response[name] = values;
        }
      });
    }
    //else, interpolate all with time
    else {
      utils.forEach(this._dataCube, function(hook, name) {
          
        filtered = _this.getDataManager().get(hook._dataId, 'nested', group_by);
            
        response[name] = {};
        //find position from first hook
        u = hook.use;
        w = hook.which;
          
        if(hook.use !== "property") next = (typeof next === 'undefined') ? d3.bisectLeft(hook.getUnique(dimTime), time) : next;
        
        method = hook.getMetadata().interpolation;


        utils.forEach(filtered, function(arr, id) {
          //TODO: this saves when geos have different data length. line can be optimised. 
          next = d3.bisectLeft(arr.map(function(m){return m.time}), time);
            
          value = utils.interpolatePoint(arr, u, w, next, dimTime, time, method);
          response[name][id] = hook.mapValue(value);

          //concat previous data points
          if(previous) {
            var values = utils.filter(arr, filter).filter(function(d) {
              return d[dimTime] <= time;
            }).map(function(d) {
              return hook.mapValue(d[w]);
            }).concat(response[name][id]);
            response[name][id] = values;
          }

        });
      });
    }

    return response;
  },

  /**
   * Gets the metadata of all hooks
   * @returns {Object} metadata
   */
  getMetadata: function() {
    return this.getDataManager().getMetadata();
  },
    
  /**
   * Gets the metadata of all hooks
   * @returns {Object} metadata
   */
  getIndicatorsTree: function() {
    return this.getDataManager().getIndicatorsTree();
  } 
    

});
Exemplo n.º 7
0
var EntitiesModel = Model.extend({

  /**
   * Default values for this model
   */
  _defaults: {
    show: {},
    select: [],
    highlight: [],
    opacitySelectDim: .3,
    opacityRegular: 1
  },

  /**
   * Initializes the entities model.
   * @param {Object} values The initial values of this model
   * @param parent A reference to the parent model
   * @param {Object} bind Initial events to bind
   */
  init: function(name, values, parent, bind) {

    this._type = "entities";
    //TODO: add defaults extend to super
    var defaults = utils.deepClone(this._defaults);
    values = utils.extend(defaults, values);

    this._visible = [];
    this._multiple = true;

    this._super(name, values, parent, bind);
  },

  /**
   * Validates the model
   * @param {boolean} silent Block triggering of events
   */
  validate: function(silent) {
    var _this = this;
    var dimension = this.getDimension();
    var visible_array = this._visible.map(function(d) {
      return d[dimension]
    });

    if(visible_array.length) {
      this.select = this.select.filter(function(f) {
        return visible_array.indexOf(f[dimension]) !== -1;
      });
      this.setHighlight(this.highlight.filter(function(f) {
        return visible_array.indexOf(f[dimension]) !== -1;
      }));
    }
  },

  /**
   * Sets the visible entities
   * @param {Array} arr
   */
  setVisible: function(arr) {
    this._visible = arr;
  },

  /**
   * Gets the visible entities
   * @returns {Array} visible
   */
  getVisible: function(arr) {
    return this._visible;
  },

  /**
   * Determines whether multiple entities can be selected
   * @param {Boolean} bool
   */
  selectMultiple: function(bool) {
    this._multiple = bool;
  },

  /**
   * Gets the dimensions in this entities
   * @returns {String} String with dimension
   */
  getDimension: function() {
    return this.dim;
  },

  /**
   * Gets the filter in this entities
   * @returns {Array} Array of unique values
   */
  getFilter: function() {
    return this.show.getPlainObject();
  },

  /**
   * Gets the selected items
   * @returns {Array} Array of unique selected values
   */
  getSelected: function() {
    var dim = this.getDimension();
    return this.select.map(function(d) {
      return d[dim];
    });
  },

  /**
   * Selects or unselects an entity from the set
   */
  selectEntity: function(d, timeDim, timeFormatter) {
    var dimension = this.getDimension();
    var value = d[dimension];
    if(this.isSelected(d)) {
      this.select = this.select.filter(function(d) {
        return d[dimension] !== value;
      });
    } else {
      var added = {};
      added[dimension] = value;
      if(timeDim && timeFormatter) {
        added["trailStartTime"] = timeFormatter(d[timeDim]);
      }
      this.select = (this._multiple) ? this.select.concat(added) : [added];
    }
  },
    
  /**
   * Select all entities
   */
  selectAll: function(timeDim, timeFormatter) {
    if(!this._multiple) return;
    
    var added,
      dimension = this.getDimension();
    
    var select = this._visible.map(function(d) {
      added = {};
      added[dimension] = d[dimension];
      if(timeDim && timeFormatter) {
        added["trailStartTime"] = timeFormatter(d[timeDim]);
      }
      return added;
    });

    this.select = select;
  },
    
  /**
   * Shows or unshows an entity from the set
   */
  showEntity: function(d) {
    //clear selected countries when showing something new
    this.clearSelected();
    
    var dimension = this.getDimension();
    var value = d[dimension];
    var show = this.show[dimension];
      
    if(!show || show[0] === "*") show = [];
      
    show = show.concat([]); //clone array
      
    if(this.isShown(d)) {
      show = show.filter(function(d) { return d !== value; });
    } else {
      show = show.concat(value);
    }
      
    if(show.length === 0) show = ["*"];
    this.show[dimension] = show.concat([]);

  },

  setLabelOffset: function(d, xy) {
    if(xy[0]===0 && xy[1]===1) return;
      
    var dimension = this.getDimension();
    var value = d[dimension];

    utils.find(this.select, function(d) {
      return d[dimension] === value;
    }).labelOffset = [Math.round(xy[0]*1000)/1000, Math.round(xy[1]*1000)/1000];

    //force the model to trigger events even if value is the same
    this.set("select", this.select, true);
  },

  /**
   * Selects an entity from the set
   * @returns {Boolean} whether the item is selected or not
   */
  isSelected: function(d) {
    var dimension = this.getDimension();
    var value = d[this.getDimension()];

    return this.select
        .map(function(d) {return d[dimension];})
        .indexOf(value) !== -1;
  },
    
  /**
   * Selects an entity from the set
   * @returns {Boolean} whether the item is shown or not
   */
  isShown: function(d) {
    var dimension = this.getDimension();
    return this.show[dimension] && this.show[dimension].indexOf(d[dimension]) !== -1;
  },

  /**
   * Clears selection of items
   */
  clearSelected: function() {
    this.select = [];
  },
  /**
   * Clears showing of items
   */
  clearShow: function() {
    var dimension = this.getDimension();
    this.show[dimension] = ["*"];
  },


  setHighlight: function(arg) {
    if (!utils.isArray(arg))
      this.setHighlight([].concat(arg));
    this.getModelObject('highlight').set(arg, false, false); // highlights are always non persistent changes
  },

  //TODO: join the following 3 methods with the previous 3

  /**
   * Highlights an entity from the set
   */
  highlightEntity: function(d, timeDim, timeFormatter) {
    var dimension = this.getDimension();
    var value = d[dimension];
    if(!this.isHighlighted(d)) {
      var added = {};
      added[dimension] = value;
      if(timeDim && timeFormatter) {
        added["trailStartTime"] = timeFormatter(d[timeDim]);
      }
      this.setHighlight(this.highlight.concat(added));
    }
  },

  /**
   * Unhighlights an entity from the set
   */
  unhighlightEntity: function(d) {
    var dimension = this.getDimension();
    var value = d[dimension];
    if(this.isHighlighted(d)) {
      this.setHighlight(this.highlight.filter(function(d) {
        return d[dimension] !== value;
      }));
    }
  },

  /**
   * Checks whether an entity is highlighted from the set
   * @returns {Boolean} whether the item is highlighted or not
   */
  isHighlighted: function(d) {
    var dimension = this.getDimension();
    var value = d[this.getDimension()];

    var highlight_array = this.highlight.map(function(d) {
      return d[dimension];
    });

    return highlight_array.indexOf(value) !== -1;
  },

  /**
   * Clears selection of items
   */
  clearHighlighted: function() {
    this.setHighlight([]);
  }
    
});