<commit_before>/*
 * Copyright 2014 The Closure Compiler Authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @fileoverview Externs for the $q service in Angular 1.4
 * NOTE: Due to a JS compiler bug, any use of a templated class must occur after
 * the class is defined. Please be careful with the ordering of the classes and
 * functions.
 * @see https://docs.angularjs.org/api/ng/service/$q
 * @externs
 */

/******************************************************************************
 * $q Service
 *****************************************************************************/

/**
 * @constructor
 */
angular.$q = function() {};

/**
 * @constructor
 * @template T
 */
angular.$q.Promise = function() {};

/**
 * Apply Type Transformation Language to allow more accurate templated type
 * definition.
 * This is copied from goog.Thenable.prototype.then, with the only
 * difference being the raw type as angular.$q.Promise instead of goog.Promise.
 *
 * @param {?(function(this:THIS, T): VALUE)=} opt_onFulfilled
 * @param {?(function(?): ?)=} opt_onRejected
 * @param {?(function(?): ?)=} opt_notifyCallback
 * @return {RESULT}
 * @template THIS
 * @template VALUE
 * @template RESULT := type('angular.$q.Promise',
 *     cond(isUnknown(VALUE), unknown(),
 *       mapunion(VALUE, (V) =>
 *         cond(isTemplatized(V) && sub(rawTypeOf(V), 'IThenable'),
 *           templateTypeOf(V, 0),
 *           cond(sub(V, 'angular.$q.Promise'),
 *              unknown(),
 *              V)))))
 *  =:
 */
angular.$q.Promise.prototype.then =
    function(opt_onFulfilled, opt_onRejected, opt_notifyCallback) {};

/**
 * @param {?function(?)} callback
 * @return {!angular.$q.Promise.<T>}
 */
angular.$q.Promise.prototype.catch = function(callback) {};

/**
 * @param {?function(?)} callback
 * @return {!angular.$q.Promise.<T>}
 */
angular.$q.Promise.prototype.finally = function(callback) {};

/**
 * @constructor
 * @template T
 */
angular.$q.Deferred = function() {};

/** @param {T=} opt_value */
angular.$q.Deferred.prototype.resolve = function(opt_value) {};

/** @param {?=} opt_reason */
angular.$q.Deferred.prototype.reject = function(opt_reason) {};

/** @param {?=} opt_value */
angular.$q.Deferred.prototype.notify = function(opt_value) {};

/** @type {!angular.$q.Promise.<T>} */
angular.$q.Deferred.prototype.promise;

/**
 * $q.all has different output type based on the input type.
 * When {@code promise} is an array, the output is an array too: for each item n
 * in the input array, the corresponding item in the returned array would be the
 * the same type of n, or if n is a templated $q.Promise, the type of the
 * resolve value.
 * When {@code promise} is in form of a record, the output should be also be a
 * record with the same properties.
 * When {@code promise} is other forms, the returned type is an Object.
 *
 * @param {VALUE} promises
 * @template VALUE
 * @return {ALLTYPE}
 * @template ALLTYPE := type('angular.$q.Promise',
 *   cond(isUnknown(VALUE), unknown(),
 *     mapunion(VALUE, (x) =>
 *       cond(sub(x, 'Array'),
 *         cond(isTemplatized(x) && sub(rawTypeOf(x), 'IThenable'),
 *           type('Array', templateTypeOf(x, 0)),
 *           'Array'
 *         ),
 *         cond(isRecord(x),
 *           maprecord(record(x), (kx, vx) => record({[kx]:
 *             cond(isTemplatized(vx) && sub(rawTypeOf(vx), 'IThenable'),
 *               templateTypeOf(vx, 0),
 *               cond(sub(vx, 'angular.$q.Promise'),
 *                 unknown(),
 *                 vx
 *               )
 *             )
 *           })),
 *           'Object')))))
 * =:
 */
angular.$q.prototype.all = function(promises) {};

/**
 * @return {!angular.$q.Deferred}
 */
angular.$q.prototype.defer = function() {};

/**
 * @param {?=} opt_reason
 * @return {!angular.$q.Promise}
 */
angular.$q.prototype.reject = function(opt_reason) {};

/**
 * @param {RESULT} value
 * @return {!angular.$q.Promise.<RESULT>}
 * @template RESULT
 */
angular.$q.prototype.when = function(value) {};
<commit_msg>Add missing opt_notifyCallback to angular's $q.Promise.finally() externs<commit_after>/*
 * Copyright 2014 The Closure Compiler Authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @fileoverview Externs for the $q service in Angular 1.4
 * NOTE: Due to a JS compiler bug, any use of a templated class must occur after
 * the class is defined. Please be careful with the ordering of the classes and
 * functions.
 * @see https://docs.angularjs.org/api/ng/service/$q
 * @externs
 */

/******************************************************************************
 * $q Service
 *****************************************************************************/

/**
 * @constructor
 */
angular.$q = function() {};

/**
 * @constructor
 * @template T
 */
angular.$q.Promise = function() {};

/**
 * Apply Type Transformation Language to allow more accurate templated type
 * definition.
 * This is copied from goog.Thenable.prototype.then, with the only
 * difference being the raw type as angular.$q.Promise instead of goog.Promise.
 *
 * @param {?(function(this:THIS, T): VALUE)=} opt_onFulfilled
 * @param {?(function(?): ?)=} opt_onRejected
 * @param {?(function(?): ?)=} opt_notifyCallback
 * @return {RESULT}
 * @template THIS
 * @template VALUE
 * @template RESULT := type('angular.$q.Promise',
 *     cond(isUnknown(VALUE), unknown(),
 *       mapunion(VALUE, (V) =>
 *         cond(isTemplatized(V) && sub(rawTypeOf(V), 'IThenable'),
 *           templateTypeOf(V, 0),
 *           cond(sub(V, 'angular.$q.Promise'),
 *              unknown(),
 *              V)))))
 *  =:
 */
angular.$q.Promise.prototype.then =
    function(opt_onFulfilled, opt_onRejected, opt_notifyCallback) {};

/**
 * @param {?function(?)} callback
 * @return {!angular.$q.Promise.<T>}
 */
angular.$q.Promise.prototype.catch = function(callback) {};

/**
 * @param {?function(?)} callback
 * @param {?(function(?): ?)=} opt_notifyCallback
 * @return {!angular.$q.Promise.<T>}
 */
angular.$q.Promise.prototype.finally =
    function(callback, opt_notifyCallback) {};

/**
 * @constructor
 * @template T
 */
angular.$q.Deferred = function() {};

/** @param {T=} opt_value */
angular.$q.Deferred.prototype.resolve = function(opt_value) {};

/** @param {?=} opt_reason */
angular.$q.Deferred.prototype.reject = function(opt_reason) {};

/** @param {?=} opt_value */
angular.$q.Deferred.prototype.notify = function(opt_value) {};

/** @type {!angular.$q.Promise.<T>} */
angular.$q.Deferred.prototype.promise;

/**
 * $q.all has different output type based on the input type.
 * When {@code promise} is an array, the output is an array too: for each item n
 * in the input array, the corresponding item in the returned array would be the
 * the same type of n, or if n is a templated $q.Promise, the type of the
 * resolve value.
 * When {@code promise} is in form of a record, the output should be also be a
 * record with the same properties.
 * When {@code promise} is other forms, the returned type is an Object.
 *
 * @param {VALUE} promises
 * @template VALUE
 * @return {ALLTYPE}
 * @template ALLTYPE := type('angular.$q.Promise',
 *   cond(isUnknown(VALUE), unknown(),
 *     mapunion(VALUE, (x) =>
 *       cond(sub(x, 'Array'),
 *         cond(isTemplatized(x) && sub(rawTypeOf(x), 'IThenable'),
 *           type('Array', templateTypeOf(x, 0)),
 *           'Array'
 *         ),
 *         cond(isRecord(x),
 *           maprecord(record(x), (kx, vx) => record({[kx]:
 *             cond(isTemplatized(vx) && sub(rawTypeOf(vx), 'IThenable'),
 *               templateTypeOf(vx, 0),
 *               cond(sub(vx, 'angular.$q.Promise'),
 *                 unknown(),
 *                 vx
 *               )
 *             )
 *           })),
 *           'Object')))))
 * =:
 */
angular.$q.prototype.all = function(promises) {};

/**
 * @return {!angular.$q.Deferred}
 */
angular.$q.prototype.defer = function() {};

/**
 * @param {?=} opt_reason
 * @return {!angular.$q.Promise}
 */
angular.$q.prototype.reject = function(opt_reason) {};

/**
 * @param {RESULT} value
 * @return {!angular.$q.Promise.<RESULT>}
 * @template RESULT
 */
angular.$q.prototype.when = function(value) {};
<|endoftext|>


<commit_before>define([
  'lodash',
  'jquery',
  '../core_module',
],
function (_, $, coreModule) {
  'use strict';

  coreModule.default.directive('metricSegment', function($compile, $sce) {
    var inputTemplate = '<input type="text" data-provide="typeahead" ' +
      ' class="gf-form-input input-medium"' +
      ' spellcheck="false" style="display:none"></input>';

    var linkTemplate = '';

    var selectTemplate = '';

    return {
      scope: {
        segment: "=",
        getOptions: "&",
        onChange: "&",
      },
      link: function($scope, elem) {
        var $input = $(inputTemplate);
        var segment = $scope.segment;
        var $button = $(segment.selectMode ? selectTemplate : linkTemplate);
        var options = null;
        var cancelBlur = null;
        var linkMode = true;

        $input.appendTo(elem);
        $button.appendTo(elem);

        $scope.updateVariableValue = function(value) {
          if (value === '' || segment.value === value) {
            return;
          }

          $scope.$apply(function() {
            var selected = _.find($scope.altSegments, {value: value});
            if (selected) {
              segment.value = selected.value;
              segment.html = selected.html;
              segment.fake = false;
              segment.expandable = selected.expandable;
            }
            else if (segment.custom !== 'false') {
              segment.value = value;
              segment.html = $sce.trustAsHtml(value);
              segment.expandable = true;
              segment.fake = false;
            }

            $scope.onChange();
          });
        };

        $scope.switchToLink = function(fromClick) {
          if (linkMode && !fromClick) { return; }

          clearTimeout(cancelBlur);
          cancelBlur = null;
          linkMode = true;
          $input.hide();
          $button.show();
          $scope.updateVariableValue($input.val());
        };

        $scope.inputBlur = function() {
          // happens long before the click event on the typeahead options
          // need to have long delay because the blur
          cancelBlur = setTimeout($scope.switchToLink, 200);
        };

        $scope.source = function(query, callback) {
          $scope.$apply(function() {
            $scope.getOptions({ measurementFilter: query }).then(function(altSegments) {
              $scope.altSegments = altSegments;
              options = _.map($scope.altSegments, function(alt) { return alt.value; });

              // add custom values
              if (segment.custom !== 'false') {
                if (!segment.fake && _.indexOf(options, segment.value) === -1) {
                  options.unshift(segment.value);
                }
              }

              callback(options);
            });
          });
        };

        $scope.updater = function(value) {
          if (value === segment.value) {
            clearTimeout(cancelBlur);
            $input.focus();
            return value;
          }

          $input.val(value);
          $scope.switchToLink(true);

          return value;
        };

        $scope.matcher = function(item) {
          var str = this.query;
          if (str[0] === '/') { str = str.substring(1); }
          if (str[str.length - 1] === '/') { str = str.substring(0, str.length-1); }
          try {
            return item.toLowerCase().match(str.toLowerCase());
          } catch(e) {
            return false;
          }
        };

        $input.attr('data-provide', 'typeahead');
        $input.typeahead({ source: $scope.source, minLength: 0, items: 10000, updater: $scope.updater, matcher: $scope.matcher });

        var typeahead = $input.data('typeahead');
        typeahead.lookup = function () {
          this.query = this.$element.val() || '';
          var items = this.source(this.query, $.proxy(this.process, this));
          return items ? this.process(items) : items;
        };

        $button.keydown(function(evt) {
          // trigger typeahead on down arrow or enter key
          if (evt.keyCode === 40 || evt.keyCode === 13) {
            $button.click();
          }
        });

        $button.click(function() {
          options = null;
          $input.css('width', (Math.max($button.width(), 80) + 16) + 'px');

          $button.hide();
          $input.show();
          $input.focus();

          linkMode = false;

          var typeahead = $input.data('typeahead');
          if (typeahead) {
            $input.val('');
            typeahead.lookup();
          }
        });

        $input.blur($scope.inputBlur);

        $compile(elem.contents())($scope);
      }
    };
  });

  coreModule.default.directive('metricSegmentModel', function(uiSegmentSrv, $q) {
    return {
      template: '<metric-segment segment="segment" get-options="getOptionsInternal()" on-change="onSegmentChange()"></metric-segment>',
      restrict: 'E',
      scope: {
        property: "=",
        options: "=",
        getOptions: "&",
        onChange: "&",
      },
      link: {
        pre: function postLink($scope, elem, attrs) {
          var cachedOptions;

          $scope.valueToSegment = function(value) {
            var option = _.find($scope.options, {value: value});
            var segment = {
              cssClass: attrs.cssClass,
              custom: attrs.custom,
              value: option ? option.text : value,
              selectMode: attrs.selectMode,
            };

            return uiSegmentSrv.newSegment(segment);
          };

          $scope.getOptionsInternal = function() {
            if ($scope.options) {
              cachedOptions = _.map($scope.options, function(option) {
                return uiSegmentSrv.newSegment({value: option.text});
              });
              return $q.when(cachedOptions);
            } else {
              return $scope.getOptions().then(function(options) {
                cachedOptions =_.map(options, function(option) {
                  if (option.html) {
                    return option;
                  }
                  return uiSegmentSrv.newSegment({value: option.text});
                });
                return cachedOptions;
              });
            }
          };

          $scope.onSegmentChange = function() {

            if (cachedOptions) {
              var option = _.find(cachedOptions, {value: $scope.segment.value});
              if (option && option.value !== $scope.property) {
                $scope.property = option.value;
              } else if (attrs.custom !== 'false') {
                $scope.property = $scope.segment.value;
              }
            } else {
              $scope.property = $scope.segment.value;
            }

            // needs to call this after digest so
            // property is synced with outerscope
            $scope.$$postDigest(function() {
              $scope.$apply(function() {
                $scope.onChange();
              });
            });
          };

          $scope.segment = $scope.valueToSegment($scope.property);
        }
      }
    };
  });
});
<commit_msg>fix(metricsegment): fixed issue with metric segment, fixes #6212<commit_after>define([
  'lodash',
  'jquery',
  '../core_module',
],
function (_, $, coreModule) {
  'use strict';

  coreModule.default.directive('metricSegment', function($compile, $sce) {
    var inputTemplate = '<input type="text" data-provide="typeahead" ' +
      ' class="gf-form-input input-medium"' +
      ' spellcheck="false" style="display:none"></input>';

    var linkTemplate = '';

    var selectTemplate = '';

    return {
      scope: {
        segment: "=",
        getOptions: "&",
        onChange: "&",
      },
      link: function($scope, elem) {
        var $input = $(inputTemplate);
        var segment = $scope.segment;
        var $button = $(segment.selectMode ? selectTemplate : linkTemplate);
        var options = null;
        var cancelBlur = null;
        var linkMode = true;

        $input.appendTo(elem);
        $button.appendTo(elem);

        $scope.updateVariableValue = function(value) {
          if (value === '' || segment.value === value) {
            return;
          }

          $scope.$apply(function() {
            var selected = _.find($scope.altSegments, {value: value});
            if (selected) {
              segment.value = selected.value;
              segment.html = selected.html || selected.value;
              segment.fake = false;
              segment.expandable = selected.expandable;
            }
            else if (segment.custom !== 'false') {
              segment.value = value;
              segment.html = $sce.trustAsHtml(value);
              segment.expandable = true;
              segment.fake = false;
            }

            $scope.onChange();
          });
        };

        $scope.switchToLink = function(fromClick) {
          if (linkMode && !fromClick) { return; }

          clearTimeout(cancelBlur);
          cancelBlur = null;
          linkMode = true;
          $input.hide();
          $button.show();
          $scope.updateVariableValue($input.val());
        };

        $scope.inputBlur = function() {
          // happens long before the click event on the typeahead options
          // need to have long delay because the blur
          cancelBlur = setTimeout($scope.switchToLink, 200);
        };

        $scope.source = function(query, callback) {
          $scope.$apply(function() {
            $scope.getOptions({ measurementFilter: query }).then(function(altSegments) {
              $scope.altSegments = altSegments;
              options = _.map($scope.altSegments, function(alt) { return alt.value; });

              // add custom values
              if (segment.custom !== 'false') {
                if (!segment.fake && _.indexOf(options, segment.value) === -1) {
                  options.unshift(segment.value);
                }
              }

              callback(options);
            });
          });
        };

        $scope.updater = function(value) {
          if (value === segment.value) {
            clearTimeout(cancelBlur);
            $input.focus();
            return value;
          }

          $input.val(value);
          $scope.switchToLink(true);

          return value;
        };

        $scope.matcher = function(item) {
          var str = this.query;
          if (str[0] === '/') { str = str.substring(1); }
          if (str[str.length - 1] === '/') { str = str.substring(0, str.length-1); }
          try {
            return item.toLowerCase().match(str.toLowerCase());
          } catch(e) {
            return false;
          }
        };

        $input.attr('data-provide', 'typeahead');
        $input.typeahead({ source: $scope.source, minLength: 0, items: 10000, updater: $scope.updater, matcher: $scope.matcher });

        var typeahead = $input.data('typeahead');
        typeahead.lookup = function () {
          this.query = this.$element.val() || '';
          var items = this.source(this.query, $.proxy(this.process, this));
          return items ? this.process(items) : items;
        };

        $button.keydown(function(evt) {
          // trigger typeahead on down arrow or enter key
          if (evt.keyCode === 40 || evt.keyCode === 13) {
            $button.click();
          }
        });

        $button.click(function() {
          options = null;
          $input.css('width', (Math.max($button.width(), 80) + 16) + 'px');

          $button.hide();
          $input.show();
          $input.focus();

          linkMode = false;

          var typeahead = $input.data('typeahead');
          if (typeahead) {
            $input.val('');
            typeahead.lookup();
          }
        });

        $input.blur($scope.inputBlur);

        $compile(elem.contents())($scope);
      }
    };
  });

  coreModule.default.directive('metricSegmentModel', function(uiSegmentSrv, $q) {
    return {
      template: '<metric-segment segment="segment" get-options="getOptionsInternal()" on-change="onSegmentChange()"></metric-segment>',
      restrict: 'E',
      scope: {
        property: "=",
        options: "=",
        getOptions: "&",
        onChange: "&",
      },
      link: {
        pre: function postLink($scope, elem, attrs) {
          var cachedOptions;

          $scope.valueToSegment = function(value) {
            var option = _.find($scope.options, {value: value});
            var segment = {
              cssClass: attrs.cssClass,
              custom: attrs.custom,
              value: option ? option.text : value,
              selectMode: attrs.selectMode,
            };

            return uiSegmentSrv.newSegment(segment);
          };

          $scope.getOptionsInternal = function() {
            if ($scope.options) {
              cachedOptions = $scope.options;
              return $q.when(_.map($scope.options, function(option) {
                return {value: option.text};
              }));
            } else {
              return $scope.getOptions().then(function(options) {
                cachedOptions = options;
                return  _.map(options, function(option) {
                  if (option.html) {
                    return option;
                  }
                  return {value: option.text};
                });
              });
            }
          };

          $scope.onSegmentChange = function() {
            if (cachedOptions) {
              var option = _.find(cachedOptions, {text: $scope.segment.value});
              if (option && option.value !== $scope.property) {
                $scope.property = option.value;
              } else if (attrs.custom !== 'false') {
                $scope.property = $scope.segment.value;
              }
            } else {
              $scope.property = $scope.segment.value;
            }

            // needs to call this after digest so
            // property is synced with outerscope
            $scope.$$postDigest(function() {
              $scope.$apply(function() {
                $scope.onChange();
              });
            });
          };

          $scope.segment = $scope.valueToSegment($scope.property);
        }
      }
    };
  });
});
<|endoftext|>


<commit_before>/**
 * Created by lucas on 23/11/16.
 */
var app = angular.module('facilitation');

app.controller('feedbackCtrl', function(FavoriteWorkshops, $scope, $routeParams, $http) {

    /* Scope vars */
    $scope.instanceData = {};
    $scope.imagesToDisplay = [];

    /* vars */
    var currentId = $routeParams.instanceId;
    var voteX = -1;
    var voteY = -1;

    // Scope methods
    /**
     * Submit the feedback
     * @type {submit}
     */
    $scope.submit = submit;
    /**
     * Display a note from the element pointed by the user on the graph
     * @type {point_it}
     */
    $scope.point_it = point_it;

    /**
     *  Convert the dataURI
     * @param dataURI
     * @returns {*}
     */
    //you need this function to convert the dataURI
    function dataURItoBlob(dataURI) {
        var binary = atob(dataURI.split(',')[1]);
        var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
        var array = [];
        for (var i = 0; i < binary.length; i++) {
            array.push(binary.charCodeAt(i));
        }
        return new Blob([new Uint8Array(array)], {
            type: mimeString
        });
    }

    /**
     * Display the uploaded photo
     */
    document.getElementById('InputPhotos').addEventListener('change', function(){
        for(var i = 0; i < this.files.length; i++){
            var file =  this.files[i];

            var reader = new FileReader();
            reader.onload = function(loadEvent) {
                $scope.$apply(function() {
                    $scope.imagesToDisplay.push(loadEvent.target.result);
                });
            };
            reader.readAsDataURL(this.files[i]);
        }
    }, false);

    function submit() {
        if (voteX != -1 && voteY != -1) {
            var feedback = {
                voteDimension1: voteY,
                voteDimension2: voteX,
                comment: $scope.com,
                photos: null
            };
            var fd = new FormData();
            for(var i = 0;i < $scope.imagesToDisplay.length ;i++) {
                var imgBlob = dataURItoBlob($scope.imagesToDisplay[i]);
                fd.append('photos', imgBlob);
            }

            $http.post(
                "/api/v1/feedback/"+currentId+"/photos",
                fd, {
                    transformRequest: angular.identity,
                    headers: {
                        'Content-Type': undefined
                    }
                }
            ).success(function (response) {
                feedback.photos = response.data;
                var res = FavoriteWorkshops.addFeedbackToInstance(feedback, currentId);
                res.success(function (data, status, headers, config) {
                    $scope.$emit('notify', {
                        type: 'success',
                        title: 'Feedback enregistré'
                    });

                    //Redirection after vote
                    var url = window.location.href;
                    url = url.split("feedback");
                    window.location.replace(url[0]+'thankYou');
                });
            })
            .error(function (response) {
                console.log('error', response);
            });

        } else {
            $scope.$emit('notify', {
                type: 'error',
                title: 'Veuillez compléter les champs de notation'
            });
        }

    };

    function point_it() {
        pos_x = event.offsetX?(event.offsetX):event.pageX-document.getElementById("pointer_div").offsetLeft;
        pos_y = event.offsetY?(event.offsetY):event.pageY-document.getElementById("pointer_div").offsetTop;
        $("#cross").css({top: (pos_y-12), left: (pos_x+2), position:'absolute'});

        document.getElementById("cross").style.visibility = "visible" ;

        var height = $('#pointer_div').height();
        var width = $('#pointer_div').width();

        var x = 100 * pos_x / height;
        var y = 100 * pos_y / width;

        // x
        if(x > 0 && x <= 16.6) {
            voteX = 0;
        }
        else if (x > 16.6 && x <= 33.33) {
            voteX = 1;
        }
        else if (x > 33.33 && x <= 50) {
            voteX = 2;
        }
        else if (x > 50 && x <= 66.6) {
            voteX = 3;
        }
        else if (x > 66.66 && x <= 83.33) {
            voteX = 4;
        }
        else if (x > 83.33 && x <= 100) {
            voteX = 5;
        }

        // y
        if(y > 0 && y <= 16.6) {
            voteY = 5;
        }
        else if (y > 16.6 && y <= 33.33) {
            voteY = 4;
        }
        else if (y > 33.33 && y <= 50) {
            voteY = 3;
        }
        else if (y > 50 && y <= 66.6) {
            voteY = 2;
        }
        else if (y > 66.66 && y <= 83.33) {
            voteY = 1;
        }
        else if (y > 83.33 && y <= 100) {
            voteY = 0;
        }

        $('#voteX').html(voteX);
        $('#voteY').html(voteY);

    }

});

/**
 * Filereader
 */
app.directive("fileread", [
    function() {
        return {
            scope: {
                fileread: "="
            },
            link: function(scope, element, attributes) {
                element.bind("change", function(changeEvent) {
                    var reader = new FileReader();
                    reader.onload = function(loadEvent) {
                        scope.$apply(function() {
                            scope.fileread = loadEvent.target.result;
                        });
                    };
                    reader.readAsDataURL(changeEvent.target.files[0]);
                });
            }
        }
    }
]);

<commit_msg>Problème event sous firefox résolut<commit_after>/**
 * Created by lucas on 23/11/16.
 */
var app = angular.module('facilitation');

app.controller('feedbackCtrl', function(FavoriteWorkshops, $scope, $routeParams, $http) {

    /* Scope vars */
    $scope.instanceData = {};
    $scope.imagesToDisplay = [];

    /* vars */
    var currentId = $routeParams.instanceId;
    var voteX = -1;
    var voteY = -1;

    // Scope methods
    /**
     * Submit the feedback
     * @type {submit}
     */
    $scope.submit = submit;
    /**
     * Display a note from the element pointed by the user on the graph
     * @type {point_it}
     */
    $scope.point_it = point_it;

    /**
     *  Convert the dataURI
     * @param dataURI
     * @returns {*}
     */
    //you need this function to convert the dataURI
    function dataURItoBlob(dataURI) {
        var binary = atob(dataURI.split(',')[1]);
        var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
        var array = [];
        for (var i = 0; i < binary.length; i++) {
            array.push(binary.charCodeAt(i));
        }
        return new Blob([new Uint8Array(array)], {
            type: mimeString
        });
    }

    /**
     * Display the uploaded photo
     */
    document.getElementById('InputPhotos').addEventListener('change', function(){
        for(var i = 0; i < this.files.length; i++){
            var file =  this.files[i];

            var reader = new FileReader();
            reader.onload = function(loadEvent) {
                $scope.$apply(function() {
                    $scope.imagesToDisplay.push(loadEvent.target.result);
                });
            };
            reader.readAsDataURL(this.files[i]);
        }
    }, false);

    function submit() {
        if (voteX != -1 && voteY != -1) {
            var feedback = {
                voteDimension1: voteY,
                voteDimension2: voteX,
                comment: $scope.com,
                photos: null
            };
            var fd = new FormData();
            for(var i = 0;i < $scope.imagesToDisplay.length ;i++) {
                var imgBlob = dataURItoBlob($scope.imagesToDisplay[i]);
                fd.append('photos', imgBlob);
            }

            $http.post(
                "/api/v1/feedback/"+currentId+"/photos",
                fd, {
                    transformRequest: angular.identity,
                    headers: {
                        'Content-Type': undefined
                    }
                }
            ).success(function (response) {
                feedback.photos = response.data;
                var res = FavoriteWorkshops.addFeedbackToInstance(feedback, currentId);
                res.success(function (data, status, headers, config) {
                    $scope.$emit('notify', {
                        type: 'success',
                        title: 'Feedback enregistré'
                    });

                    //Redirection after vote
                    var url = window.location.href;
                    url = url.split("feedback");
                    window.location.replace(url[0]+'thankYou');
                });
            })
            .error(function (response) {
                console.log('error', response);
            });

        } else {
            $scope.$emit('notify', {
                type: 'error',
                title: 'Veuillez compléter les champs de notation'
            });
        }

    };

    function point_it(event) {
        pos_x = event.offsetX?(event.offsetX):event.pageX-document.getElementById("pointer_div").offsetLeft;
        pos_y = event.offsetY?(event.offsetY):event.pageY-document.getElementById("pointer_div").offsetTop;
        $("#cross").css({top: (pos_y-12), left: (pos_x+2), position:'absolute'});

        document.getElementById("cross").style.visibility = "visible" ;

        var height = $('#pointer_div').height();
        var width = $('#pointer_div').width();

        var x = 100 * pos_x / height;
        var y = 100 * pos_y / width;

        // x
        if(x > 0 && x <= 16.6) {
            voteX = 0;
        }
        else if (x > 16.6 && x <= 33.33) {
            voteX = 1;
        }
        else if (x > 33.33 && x <= 50) {
            voteX = 2;
        }
        else if (x > 50 && x <= 66.6) {
            voteX = 3;
        }
        else if (x > 66.66 && x <= 83.33) {
            voteX = 4;
        }
        else if (x > 83.33 && x <= 100) {
            voteX = 5;
        }

        // y
        if(y > 0 && y <= 16.6) {
            voteY = 5;
        }
        else if (y > 16.6 && y <= 33.33) {
            voteY = 4;
        }
        else if (y > 33.33 && y <= 50) {
            voteY = 3;
        }
        else if (y > 50 && y <= 66.6) {
            voteY = 2;
        }
        else if (y > 66.66 && y <= 83.33) {
            voteY = 1;
        }
        else if (y > 83.33 && y <= 100) {
            voteY = 0;
        }

        $('#voteX').html(voteX);
        $('#voteY').html(voteY);

    }

});

/**
 * Filereader
 */
app.directive("fileread", [
    function() {
        return {
            scope: {
                fileread: "="
            },
            link: function(scope, element, attributes) {
                element.bind("change", function(changeEvent) {
                    var reader = new FileReader();
                    reader.onload = function(loadEvent) {
                        scope.$apply(function() {
                            scope.fileread = loadEvent.target.result;
                        });
                    };
                    reader.readAsDataURL(changeEvent.target.files[0]);
                });
            }
        }
    }
]);

<|endoftext|>


<commit_before>var map;
var pois = [];
var markerlayer = L.layerGroup();
var myLocation = null;
var OSM_URL = "http://overpass.osm.rambler.ru/cgi/interpreter?data=%5Bout:json%5D;";
var berlin = new L.LatLng(52.5213616409873, 13.4101340342265);

function initMap() {
	map = new L.Map('map', {
		center: berlin,
		zoom: 13
	});

	// create a CloudMade tile layer
	var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/120a22aa24a94b57a93ce17a1c6155e3/997/256/{z}/{x}/{y}.png', 		cloudmade = new L.TileLayer(cloudmadeUrl, {maxZoom: 18});
	var osmAttr = '© OpenStreetMap contributors, CC-BY-SA';

	map.addLayer(cloudmade);
	map.addLayer(markerlayer);
}

function loadPOI(tag) {

	//remove old markers
	markerlayer.clearLayers();

	//get map bounds from current window
	var southwest = map.getBounds().getSouthWest();
	var northeast = map.getBounds().getNorthEast();

	//build URL
	var OSM_PARAMS = "node["+tag+ "](" +southwest.lat+  ","  +southwest.lng+  ","  +northeast.lat+ "," +northeast.lng +");out;";
	console.log(OSM_PARAMS);
	var URL = OSM_URL + encodeURIComponent(OSM_PARAMS);

	//load POIs from OSM
	var markers = [];
	$.getJSON(URL, function(data) {

		//build markers
		pois = data['elements'];
		$.each(pois, function(index, poi) {
			lat = poi['lat'];
			lon = poi['lon'];
	    	markers.push(new L.Marker([lat, lon]));
	  	});

	  	//add markers to map
		console.log(markers);
		newmarkers = L.layerGroup(markers);
		markerlayer.addLayer(newmarkers);

		//if no geolocation, finish here
		if(myLocation === null){
			return;
		}

		//find nearest marker
		var distance = Number.MAX_VALUE;
		var nearest = null;
		$.each(markers, function(i, marker) {
			temp = marker.getLatLng();
			dist = temp.distanceTo(myLocation);
			if(marker.getLatLng().distanceTo(myLocation) < distance) {
				distance = marker.getLatLng().distanceTo(myLocation);
				nearest = marker;
			}
		});

		//zoom map
		//map.panTo(nearest.getLatLng());
		map.panInsideBounds(new L.LatLngBounds([nearest.getLatLng(),myLocation, myLocation]));

	});
}

function onLocationFound(e) {
    var radius = e.accuracy / 2;

    L.marker(e.latlng).addTo(map).bindPopup("You are within " + radius + " meters from this point").openPopup();

    L.circle(e.latlng, radius).addTo(map);
	myLocation = e.latlng;
}

function onLocationError(e) {
    alert(e.message);
}

window.onload = function() {

	initMap();

	// setup geolocation
	map.on('locationfound', onLocationFound);
	map.on('locationerror', onLocationError);
	map.locate({setView: true, maxZoom: 16});

	//setup dropdown listener
	$('#mydropdown').change(function()
		{
		  var tag = "";
		  switch($(this).val())
		{
		case 'Playground':
		  tag="leisure=playground";
		  break;
		case 'Tabletennis':
		  tag="sport=table_tennis";
		  break;
		case 'Postbox':
		  tag="amenity=post_box";
		  break;
		default:
		  tag='';
		  break;
		}

		//load POIs into map
		loadPOI(tag);
	});

};
<commit_msg>changed init<commit_after>var map;
var pois = [];
var markerlayer;
var myLocation = null;
var OSM_URL = "http://overpass.osm.rambler.ru/cgi/interpreter?data=%5Bout:json%5D;";
var berlin;

function initMap() {
	markerlayer = L.layerGroup();
	berlin = new L.LatLng(52.5213616409873, 13.4101340342265);

	map = new L.Map('map', {
		center: berlin,
		zoom: 13
	});

	// create a CloudMade tile layer
	var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/120a22aa24a94b57a93ce17a1c6155e3/997/256/{z}/{x}/{y}.png', 		cloudmade = new L.TileLayer(cloudmadeUrl, {maxZoom: 18});
	var osmAttr = '© OpenStreetMap contributors, CC-BY-SA';

	map.addLayer(cloudmade);
	map.addLayer(markerlayer);
}

function loadPOI(tag) {

	//remove old markers
	markerlayer.clearLayers();

	//get map bounds from current window
	var southwest = map.getBounds().getSouthWest();
	var northeast = map.getBounds().getNorthEast();

	//build URL
	var OSM_PARAMS = "node["+tag+ "](" +southwest.lat+  ","  +southwest.lng+  ","  +northeast.lat+ "," +northeast.lng +");out;";
	console.log(OSM_PARAMS);
	var URL = OSM_URL + encodeURIComponent(OSM_PARAMS);

	//load POIs from OSM
	var markers = [];
	$.getJSON(URL, function(data) {

		//build markers
		pois = data['elements'];
		$.each(pois, function(index, poi) {
			lat = poi['lat'];
			lon = poi['lon'];
	    	markers.push(new L.Marker([lat, lon]));
	  	});

	  	//add markers to map
		console.log(markers);
		newmarkers = L.layerGroup(markers);
		markerlayer.addLayer(newmarkers);

		//if no geolocation, finish here
		if(myLocation === null){
			return;
		}

		//find nearest marker
		var distance = Number.MAX_VALUE;
		var nearest = null;
		$.each(markers, function(i, marker) {
			temp = marker.getLatLng();
			dist = temp.distanceTo(myLocation);
			if(marker.getLatLng().distanceTo(myLocation) < distance) {
				distance = marker.getLatLng().distanceTo(myLocation);
				nearest = marker;
			}
		});

		//zoom map
		//map.panTo(nearest.getLatLng());
		map.panInsideBounds(new L.LatLngBounds([nearest.getLatLng(),myLocation, myLocation]));

	});
}

function onLocationFound(e) {
    var radius = e.accuracy / 2;

    L.marker(e.latlng).addTo(map).bindPopup("You are within " + radius + " meters from this point").openPopup();

    L.circle(e.latlng, radius).addTo(map);
	myLocation = e.latlng;
}

function onLocationError(e) {
    alert(e.message);
}

window.onload = function() {

	initMap();

	// setup geolocation
	map.on('locationfound', onLocationFound);
	map.on('locationerror', onLocationError);
	map.locate({setView: true, maxZoom: 16});

	//setup dropdown listener
	$('#mydropdown').change(function()
		{
		  var tag = "";
		  switch($(this).val())
		{
		case 'Playground':
		  tag="leisure=playground";
		  break;
		case 'Tabletennis':
		  tag="sport=table_tennis";
		  break;
		case 'Postbox':
		  tag="amenity=post_box";
		  break;
		default:
		  tag='';
		  break;
		}

		//load POIs into map
		loadPOI(tag);
	});

};
<|endoftext|>


<commit_before>function makeid() {
    var txt = '';
    var consonants = 'BCDFGHJKLMNPQRSTVWXYZ';
    var vowels = 'AEIOUY';
    for (x=0;x<2;x++) {
      txt += consonants.charAt(Math.floor(Math.random() * consonants.length));
      txt += vowels.charAt(Math.floor(Math.random() * vowels.length));
    }
    txt += consonants.charAt(Math.floor(Math.random() * consonants.length));
    return txt;
}

function scrollWindow() {
  /mobile/i.test(navigator.userAgent) && !location.hash && window.scrollTo(0, 1);
  //$("#readout").html(String($(window).width()));
}

$(document).ready(function() {
  scrollWindow();
  window.onorientationchange = scrollWindow;

  $('#entername').val(makeid());
  $('#entername').select();
  $('#entername').onfocus = '$(\'#entername\').value = \'\';';

  // click play and accept default name for fast testing
  insertcoin();
  ready();

});

var playing = false; // are we sending mousemoves to the server?
//var colliding = false;
var paddle = '';
var lastBallX = 0, lastBallY = 0;
var lastPaddleY = 0;
var deltax = 0, deltay = 0;

// array of score-displaying divs
var scores = {
  0:[1,2,3,5,6,7],
  1:[3,6],
  2:[1,3,4,5,7],
  3:[1,3,4,6,7],
  4:[2,3,4,6],
  5:[1,2,4,6,7],
  6:[1,2,4,5,6,7],
  7:[1,3,6],
  8:[1,2,3,4,5,6,7],
  9:[1,2,3,4,6,7]
}

function score(which, val) {
  for (x in [1,2,3,4,5,6,7,8,9]) {
    element = '#'+which+' .s'+x;
    $(element).css('visibility', 'hidden');
  }
  for (x in scores[val]) {
    element = '#'+which+' .s'+scores[val][x];
    $(element).css('visibility', 'visible');
  }
}

score('score1', 0);
score('score2', 0);

var p1 = $('#p1'), p2 = $('#p2'), ball = $('#ball'), court = $('#court');
var displayText;

// this command is triggered by the server's 'broadcast'
function command(msg){

  socket.send({type:'heartBeat'});

	// should go through the server code and make sure these are all needed
  switch(msg.type) {
    case 'newgame':
      $('#player1').html(msg.player1);
      $('#player2').html(msg.player2);
      $('#playerhide').css('visibility', 'visible');
      break;
    case 'endgame':
      //colliding = false;
      playing = false;
      paddle = '';
      ball.css('visibility', 'hidden');
      $('#playerhide').css('visibility', 'hidden');
      p1.css('background-color', 'gray');
      p2.css('background-color', 'gray');
      break;
    case 'display':
      clearTimeout(displayText); // if there's a timeout already, override
      $('#alert').html(msg.alert);
      $('#alert').css('visibility', 'visible');
      displayText = setTimeout( function() {
        $('#alert').css('visibility', 'hidden');
      }, 1000);
      break;
    case 'css':
      $('#'+msg.which).css(msg.property, msg.value);
      break;
    case 'html': // change the html of 'which' div
      $('#'+msg.which).html(msg.html);
      break;
    case 'position':
      $('#position').html(msg.position);
      break;
    case 'playing':
      if (msg.paddle == 'p1' || msg.paddle == 'p2') {
        playing = msg.paddle;
        paddle = $('#'+msg.paddle);
        paddle.css('background-color', 'white');
        ball.css('background-color', 'white');
        lastPaddleY = paddle.position().top;
        lastBallX = ball.position().left;
        lastBallY = ball.position().top;
        playLoop(msg.delay*.8); // normally 1 - .8 seems to reduce lag?
      }
      break;
    case 'move':  // time to move the divs
      deltax = ball.position().left - lastBallX;
      lastBallX = ball.position().left;
      lastBallY = ball.position().top;
      //colliding = ((playing == 'p1' && deltax < 0) ||
      //             (playing == 'p2' && deltax > 0)) ? true : false;

      ball.css({'top': msg.bally+'%', 'left': msg.ballx+'%'});
      p1.css({'top': msg.p1pos+'%'});
      p2.css({'top': msg.p2pos+'%'});
      //if (playing) lastPaddleY = paddle.position().top;

      break;
    case 'score':
      score(msg.which, msg.val);
      break;
    default: break;
  }
}

var socket = new io.Socket(null, {port: 9980, rememberTransport: false});
socket.connect();

// sends message to 'command' function
socket.on('message', function(obj){
  if ('buffer' in obj){
    for (var i in obj.buffer) command(obj.buffer[i]);
  } else command(obj);
});

var mouseY = 0, lastY = 0;

// send mouse position to the server
function movePaddles() {
  // get mouse position relative to court height as fraction
  var targetY = (mouseY - court.offset().top) / court.height();

  // if mouse has moved, send new position to server
  if (lastY != targetY) socket.send({type:'move', which:playing, y:targetY});
  lastY = targetY;
}

function ready() {
  $('#welcome').css('visibility','hidden');
  //$('.center').css('display','none');
  $('#insertcoin').css('display','none');
  // turn on mouse tracking
  $(document).mousemove(function(e){ mouseY = e.pageY; });

  // turn on touch tracking
  //$('#toucharea, #court').bind('touchstart touchmove', function(event) {
  $('#toucharea').bind('touchstart touchmove', function(event) {
    var e = event.originalEvent;
    mouseY = e.touches[0].pageY;
    return false;
  });

  socket.send({type:'ready', name:$('#entername').val()});
}

function playLoop(arg) {
  if (playing) {
    setTimeout('playLoop('+arg+')', arg);
    // socket.send({type:'heartBeat'});
    movePaddles();
  }
}

// no thanks, just browsing
function spectate() {
  $("#coin").animate({
    right: '0'
  }, 250, function() {
    $('#splash').css('display', 'none');
    $('#insertcoin').css('display', 'inline');
    $('#hide').css('display', 'inline');
    //$('#hide').css('top', '0');
    $('#welcome').css('display', 'none');

    socket.send({type:'watching'});
  });
}

var coinBounce = false, starting = false;

function bounceCoin() {
  $("#coin").animate({ top: '-.1em' }, 50);
  if (coinBounce) setTimeout( function() {
    $("#coin").animate({ top: '+.1em' }, 50);
  }, 50);
  if (coinBounce) setTimeout( function () {
    if (coinBounce) bounceCoin();
  }, 100);
}

function coinLeft() {
  $("#coin").animate({ right: '.33em' }, 100);
  coinBounce = true;
  bounceCoin();
}

function coinRight() {
  if (!starting) {
    coinBounce = false;
    $("#coin").animate({ right: '0em', top: '0' }, 100);
  }
}

function logIn() {
  $('#splash').css('display', 'none');
  $('#insertcoin').css('display', 'none');
  $('.hide').css('display', 'inline');
  //$('#hide').css('top', '0');
  $('#welcome').css('display', 'inline');
}

function insertcoin() {
  coinBounce = false;
  $("#play").css('color', 'red');
  starting = true;
  $("#coin").animate({
    right: '+=.25em'
  }, 100, 'linear', function() {
    $("#coin").animate({
      right: '2.1em'
    }, 250, 'linear', function() {
      setTimeout('logIn()', 500);
    });
  });
}

$("#play").hover(function() {
    coinLeft();
  }, function() {
    coinRight()
});

// set animation speed: delay between updates in milliseconds
// equivalent to 20 frames per second
jQuery.fx.interval = 50;<commit_msg>testing settings for lag<commit_after>function makeid() {
    var txt = '';
    var consonants = 'BCDFGHJKLMNPQRSTVWXYZ';
    var vowels = 'AEIOUY';
    for (x=0;x<2;x++) {
      txt += consonants.charAt(Math.floor(Math.random() * consonants.length));
      txt += vowels.charAt(Math.floor(Math.random() * vowels.length));
    }
    txt += consonants.charAt(Math.floor(Math.random() * consonants.length));
    return txt;
}

function scrollWindow() {
  /mobile/i.test(navigator.userAgent) && !location.hash && window.scrollTo(0, 1);
  //$("#readout").html(String($(window).width()));
}

$(document).ready(function() {
  scrollWindow();
  window.onorientationchange = scrollWindow;

  $('#entername').val(makeid());
  $('#entername').select();
  $('#entername').onfocus = '$(\'#entername\').value = \'\';';

  // click play and accept default name for fast testing
  insertcoin();
  ready();

});

var playing = false; // are we sending mousemoves to the server?
//var colliding = false;
var paddle = '';
var lastBallX = 0, lastBallY = 0;
var lastPaddleY = 0;
var deltax = 0, deltay = 0;

// array of score-displaying divs
var scores = {
  0:[1,2,3,5,6,7],
  1:[3,6],
  2:[1,3,4,5,7],
  3:[1,3,4,6,7],
  4:[2,3,4,6],
  5:[1,2,4,6,7],
  6:[1,2,4,5,6,7],
  7:[1,3,6],
  8:[1,2,3,4,5,6,7],
  9:[1,2,3,4,6,7]
}

function score(which, val) {
  for (x in [1,2,3,4,5,6,7,8,9]) {
    element = '#'+which+' .s'+x;
    $(element).css('visibility', 'hidden');
  }
  for (x in scores[val]) {
    element = '#'+which+' .s'+scores[val][x];
    $(element).css('visibility', 'visible');
  }
}

score('score1', 0);
score('score2', 0);

var p1 = $('#p1'), p2 = $('#p2'), ball = $('#ball'), court = $('#court');
var displayText;

// this command is triggered by the server's 'broadcast'
function command(msg){

  socket.send({type:'heartBeat'});

	// should go through the server code and make sure these are all needed
  switch(msg.type) {
    case 'newgame':
      $('#player1').html(msg.player1);
      $('#player2').html(msg.player2);
      $('#playerhide').css('visibility', 'visible');
      break;
    case 'endgame':
      //colliding = false;
      playing = false;
      paddle = '';
      ball.css('visibility', 'hidden');
      $('#playerhide').css('visibility', 'hidden');
      p1.css('background-color', 'gray');
      p2.css('background-color', 'gray');
      break;
    case 'display':
      clearTimeout(displayText); // if there's a timeout already, override
      $('#alert').html(msg.alert);
      $('#alert').css('visibility', 'visible');
      displayText = setTimeout( function() {
        $('#alert').css('visibility', 'hidden');
      }, 1000);
      break;
    case 'css':
      $('#'+msg.which).css(msg.property, msg.value);
      break;
    case 'html': // change the html of 'which' div
      $('#'+msg.which).html(msg.html);
      break;
    case 'position':
      $('#position').html(msg.position);
      break;
    case 'playing':
      if (msg.paddle == 'p1' || msg.paddle == 'p2') {
        playing = msg.paddle;
        paddle = $('#'+msg.paddle);
        paddle.css('background-color', 'white');
        ball.css('background-color', 'white');
        lastPaddleY = paddle.position().top;
        lastBallX = ball.position().left;
        lastBallY = ball.position().top;
        playLoop(msg.delay*.5); // normally 1 - .8 seems to reduce lag?
      }
      break;
    case 'move':  // time to move the divs
      deltax = ball.position().left - lastBallX;
      lastBallX = ball.position().left;
      lastBallY = ball.position().top;
      //colliding = ((playing == 'p1' && deltax < 0) ||
      //             (playing == 'p2' && deltax > 0)) ? true : false;

      ball.css({'top': msg.bally+'%', 'left': msg.ballx+'%'});
      p1.css({'top': msg.p1pos+'%'});
      p2.css({'top': msg.p2pos+'%'});
      //if (playing) lastPaddleY = paddle.position().top;

      break;
    case 'score':
      score(msg.which, msg.val);
      break;
    default: break;
  }
}

var socket = new io.Socket(null, {port: 9980, rememberTransport: false});
socket.connect();

// sends message to 'command' function
socket.on('message', function(obj){
  if ('buffer' in obj){
    for (var i in obj.buffer) command(obj.buffer[i]);
  } else command(obj);
});

var mouseY = 0, lastY = 0;

// send mouse position to the server
function movePaddles() {
  // get mouse position relative to court height as fraction
  var targetY = (mouseY - court.offset().top) / court.height();

  // if mouse has moved, send new position to server
  if (lastY != targetY) socket.send({type:'move', which:playing, y:targetY});
  lastY = targetY;
}

function ready() {
  $('#welcome').css('visibility','hidden');
  //$('.center').css('display','none');
  $('#insertcoin').css('display','none');
  // turn on mouse tracking
  $(document).mousemove(function(e){ mouseY = e.pageY; });

  // turn on touch tracking
  //$('#toucharea, #court').bind('touchstart touchmove', function(event) {
  $('#toucharea').bind('touchstart touchmove', function(event) {
    var e = event.originalEvent;
    mouseY = e.touches[0].pageY;
    return false;
  });

  socket.send({type:'ready', name:$('#entername').val()});
}

function playLoop(arg) {
  if (playing) {
    setTimeout('playLoop('+arg+')', arg);
    // socket.send({type:'heartBeat'});
    movePaddles();
  }
}

// no thanks, just browsing
function spectate() {
  $("#coin").animate({
    right: '0'
  }, 250, function() {
    $('#splash').css('display', 'none');
    $('#insertcoin').css('display', 'inline');
    $('#hide').css('display', 'inline');
    //$('#hide').css('top', '0');
    $('#welcome').css('display', 'none');

    socket.send({type:'watching'});
  });
}

var coinBounce = false, starting = false;

function bounceCoin() {
  $("#coin").animate({ top: '-.1em' }, 50);
  if (coinBounce) setTimeout( function() {
    $("#coin").animate({ top: '+.1em' }, 50);
  }, 50);
  if (coinBounce) setTimeout( function () {
    if (coinBounce) bounceCoin();
  }, 100);
}

function coinLeft() {
  $("#coin").animate({ right: '.33em' }, 100);
  coinBounce = true;
  bounceCoin();
}

function coinRight() {
  if (!starting) {
    coinBounce = false;
    $("#coin").animate({ right: '0em', top: '0' }, 100);
  }
}

function logIn() {
  $('#splash').css('display', 'none');
  $('#insertcoin').css('display', 'none');
  $('.hide').css('display', 'inline');
  //$('#hide').css('top', '0');
  $('#welcome').css('display', 'inline');
}

function insertcoin() {
  coinBounce = false;
  $("#play").css('color', 'red');
  starting = true;
  $("#coin").animate({
    right: '+=.25em'
  }, 100, 'linear', function() {
    $("#coin").animate({
      right: '2.1em'
    }, 250, 'linear', function() {
      setTimeout('logIn()', 500);
    });
  });
}

$("#play").hover(function() {
    coinLeft();
  }, function() {
    coinRight()
});

// set animation speed: delay between updates in milliseconds
// equivalent to 20 frames per second
jQuery.fx.interval = 50;<|endoftext|>


<commit_before>
    //Vars determining the center of the graph
    var offsetScreenX = 510;
    var offsetScreenY = 265;

    var valuesHash = {};
    var nBubbles = 0;

    jQuery.easing.def = "easeInOutCubic";

    function initializeGraph() {
      $(".innerBubble").live({
        mouseenter: function () {
          $(this).css("backgroundColor","#000000");
        },
        mouseleave: function () {
          $(this).css("backgroundColor",valuesHash[$(this).parent().attr('id')]["color"]);
        },
        click: function() {
          var url = valuesHash[$(this).parent().attr('id')]["children_json_url"];
          if (url == null) {
            return false;
          } else {
            // goDeeper(url);
            $("#graph_infowindow").find(".top").find("h2").empty();
            $("#graph_infowindow").find(".top").find("h2").append($(this).parent().attr('id'));
            $("#graph_infowindow").find(".top").find(".province").empty();
            $("#graph_infowindow").find(".top").find(".province").append(valuesHash[$(this).parent().attr('id')]["censo_total"] + " habitantes");
            $("#graph_infowindow").find(".top").find(".stats").find("h4").empty();
            $("#graph_infowindow").find(".top").find(".stats").find("h4").append(Math.floor(parseInt(valuesHash[$(this).parent().attr('id')]["porcentaje_participacion"])) + "% de participación");

            // First political party
            var partido_1 = valuesHash[$(this).parent().attr('id')]["partido_1"][0].toLowerCase();
            c1 = $('div#graph_infowindow div.top div.stats div.partido:eq(0)').attr('class').split(" ");
            $.each(c1, function(c){
              if(c1[c] != "partido"){
                $('div#graph_infowindow div.top div.stats div.partido:eq(0)').removeClass(c1[c]);
              }
            })
            if (partido_1=='psoe' || partido_1=="pp" || partido_1 == "iu") {
              $('div#graph_infowindow div.top div.stats div.partido:eq(0)').addClass(partido_1);
            } else {
              $('div#graph_infowindow div.top div.stats div.partido:eq(0)').addClass('par1');
            }
            bar_width = (valuesHash[$(this).parent().attr('id')]["partido_1"][2]*175)/100;
            $('div#graph_infowindow div.top div.stats div.partido:eq(0) span').width((bar_width<2)?2:bar_width);
            $('div#graph_infowindow div.top div.stats div.partido:eq(0) p').text(valuesHash[$(this).parent().attr('id')]["partido_1"][0]+' ('+(valuesHash[$(this).parent().attr('id')]["partido_1"][2]*175)/100+'%)');

            // Second political party
            var partido_2 = valuesHash[$(this).parent().attr('id')]["partido_2"][0].toLowerCase();
            c2 = $('div#graph_infowindow div.top div.stats div.partido:eq(1)').attr('class').split(" ");
            $.each(c2, function(c){
              if(c2[c] != "partido"){
                $('div#graph_infowindow div.top div.stats div.partido:eq(1)').removeClass(c2[c]);
              }
            })
            if (partido_2=='psoe' || partido_2=="pp" || partido_2 == "iu") {
              $('div#graph_infowindow div.top div.stats div.partido:eq(1)').addClass(partido_2);
            } else {
              $('div#graph_infowindow div.top div.stats div.partido:eq(1)').addClass('par2');
            }
            bar_width = (valuesHash[$(this).parent().attr('id')]["partido_2"][2]*175)/100;
            $('div#graph_infowindow div.top div.stats div.partido:eq(1) span').width((bar_width<2)?2:bar_width);
            $('div#graph_infowindow div.top div.stats div.partido:eq(1) p').text(valuesHash[$(this).parent().attr('id')]["partido_2"][0]+' ('+(valuesHash[$(this).parent().attr('id')]["partido_2"][2]*175)/100+'%)');

            // Third political party
            var partido_3 = valuesHash[$(this).parent().attr('id')]["partido_3"][0].toLowerCase();
            c3 = $('div#graph_infowindow div.top div.stats div.partido:eq(2)').attr('class').split(" ");
            $.each(c3, function(c){
              if(c3[c] != "partido"){
                $('div#graph_infowindow div.top div.stats div.partido:eq(2)').removeClass(c3[c]);
              }
            })
            if (partido_3=='psoe' || partido_3=="pp" || partido_3 == "iu") {
              $('div#graph_infowindow div.top div.stats div.partido:eq(2)').addClass(partido_3);
            } else {
              $('div#graph_infowindow div.top div.stats div.partido:eq(2)').addClass('par3');
            }
            bar_width = (valuesHash[$(this).parent().attr('id')]["partido_3"][2]*175)/100;
            $('div#graph_infowindow div.top div.stats div.partido:eq(2) span').width((bar_width<2)?2:bar_width);
            $('div#graph_infowindow div.top div.stats div.partido:eq(2) p').text(valuesHash[$(this).parent().attr('id')]["partido_3"][0]+' ('+(valuesHash[$(this).parent().attr('id')]["partido_3"][2]*175)/100+'%)');

            // Other political party
            bar_width = (valuesHash[$(this).parent().attr('id')]["partido_otros"][2]*175)/100;
            $('div#infowindow div.stats div.partido:eq(3) span').width((bar_width<2)?2:bar_width);
            $('div#infowindow div.stats div.partido:eq(3) p').text('OTROS ('+info.otros_partido_percent+'%)');

          }
        },
        hover: function() {
          console.log($(this).parent().attr('id') + ' ('+valuesHash[$(this).parent().attr('id')]["cartodb_id"]+'): ' + valuesHash[$(this).parent().attr('id')]["x_coordinate"] + ', ' + valuesHash[$(this).parent().attr('id')]["y_coordinate"]);
        }
      });
    }

    function restartGraph() {
      $('div#graph_container').empty();
      valuesHash = {};
      createBubbles("/json/generated_data/autonomies_"+normalization[compare]+"_"+year+".json");
    }

    function createBubbles(url){
      $.getJSON(url, function(data) {
        $.each(data, function(key, val) {
          valuesHash[key] = val;
          nBubbles = nBubbles+1;
          $('#graph_container').append("

");
          $('#'+key).css("left",(offsetScreenX).toString()+"px");
          $('#'+key).css("top",(offsetScreenY).toString()+"px");
          $('#'+key).css("opacity","0");
          $('#'+key).find('.innerBubble').css("backgroundColor",val["color"]);
        });
      });
      setValue(url);
    }

    //changes the values on the graph
    function setValue(url){
      //TODO: VER SI PUEDO EVITAR LEER EL JSON (AL MENOS AL PRINCIPIO)
      $.getJSON(url, function(data) {
        $.each(data, function(key, v) {
          valuesHash[key] = v;
          updateBubble('#'+key,offsetScreenX+parseInt(v["x_coordinate"]),offsetScreenY+parseInt(v["y_coordinate"]),v["radius"],v["color"]);
        });
      });
    }

    //Function for update the values of the bubbles that are being visualized
    function updateBubble (bubble,x,y,val,c){
      var offset = Math.abs(parseInt($(bubble).find('.outerBubble').css('top')) + (parseInt($(bubble).find('.outerBubble').css('height')) - val) / 2)*-1;

      $(bubble).animate({
        left: x.toString() + "px",
        top: y.toString() + "px",
        opacity: 1
      }, 1000, function(){
        console.log("ukelele");
      });

      $(bubble).find('.outerBubble').animate({
          height: val.toString() + "px",
          width: val.toString() + "px",
          top: offset.toString() + "px",
          left: offset.toString() + "px"
        }, 1000);

      $(bubble).find('.innerBubble').animate({
          height: (val-10).toString() + "px",
          width: (val-10).toString() + "px",
          top: (offset + 5).toString() + "px",
          left: (offset + 5).toString() + "px",
          backgroundColor: c.toString() + "px"
        }, 1000);
    }

    function goDeeper(url){
      console.log("Going deep to " + url);
      for (key in valuesHash){
        //Destroy Bubbles
        destroyBubble(key, url);
      }
    }

    function destroyBubble(b, url){
      (parseInt($("#"+b).css("left")) < offsetScreenX) ? displacementX = "-=30px" : displacementX = "+=30px";
      (parseInt($("#"+b).css("top")) < offsetScreenY) ? displacementY = "-=30px" : displacementY = "+=30px";
      $("#"+key).animate({
        left: displacementX,
        top: displacementY,
        opacity: "0"
      }, 500, function(){
          //console.log("Removing "+b);
          $("#"+b).remove();
          nBubbles=nBubbles-1;
          if(nBubbles==0){
            createBubbles(url);
          }
        }
      );
    }<commit_msg>graph tooltip<commit_after>
    //Vars determining the center of the graph
    var offsetScreenX = 510;
    var offsetScreenY = 265;

    var valuesHash = {};
    var nBubbles = 0;

    jQuery.easing.def = "easeInOutCubic";

    function initializeGraph() {
      $(".innerBubble").live({
        mouseenter: function () {
          $(this).css("backgroundColor","#000000");
        },
        mouseleave: function () {
          $(this).css("backgroundColor",valuesHash[$(this).parent().attr('id')]["color"]);
        },
        dblclick: function(){
          var url = valuesHash[$(this).parent().attr('id')]["children_json_url"];
          if (url == null) {
            return false;
          } else {
            goDeeper(url);
          }
        },
        click: function() {
          $("#graph_infowindow").find(".top").find("h2").empty();
          $("#graph_infowindow").find(".top").find("h2").append($(this).parent().attr('id'));
          $("#graph_infowindow").find(".top").find(".province").empty();
          $("#graph_infowindow").find(".top").find(".province").append(valuesHash[$(this).parent().attr('id')]["censo_total"] + " habitantes");
          $("#graph_infowindow").find(".top").find(".stats").find("h4").empty();
          $("#graph_infowindow").find(".top").find(".stats").find("h4").append(Math.floor(parseInt(valuesHash[$(this).parent().attr('id')]["porcentaje_participacion"])) + "% de participación");

          // First political party
          var partido_1 = valuesHash[$(this).parent().attr('id')]["partido_1"][0].toLowerCase();
          c1 = $('div#graph_infowindow div.top div.stats div.partido:eq(0)').attr('class').split(" ");
          $.each(c1, function(c){
            if(c1[c] != "partido"){
              $('div#graph_infowindow div.top div.stats div.partido:eq(0)').removeClass(c1[c]);
            }
          })
          if (partido_1=='psoe' || partido_1=="pp" || partido_1 == "iu") {
            $('div#graph_infowindow div.top div.stats div.partido:eq(0)').addClass(partido_1);
          } else {
            $('div#graph_infowindow div.top div.stats div.partido:eq(0)').addClass('par1');
          }
          bar_width = (valuesHash[$(this).parent().attr('id')]["partido_1"][2]*175)/100;
          $('div#graph_infowindow div.top div.stats div.partido:eq(0) span').width((bar_width<2)?2:bar_width);
          $('div#graph_infowindow div.top div.stats div.partido:eq(0) p').text(valuesHash[$(this).parent().attr('id')]["partido_1"][0]+' ('+(valuesHash[$(this).parent().attr('id')]["partido_1"][1]*175)/100+'%)');

          // Second political party
          var partido_2 = valuesHash[$(this).parent().attr('id')]["partido_2"][0].toLowerCase();
          c2 = $('div#graph_infowindow div.top div.stats div.partido:eq(1)').attr('class').split(" ");
          $.each(c2, function(c){
            if(c2[c] != "partido"){
              $('div#graph_infowindow div.top div.stats div.partido:eq(1)').removeClass(c2[c]);
            }
          })
          if (partido_2=='psoe' || partido_2=="pp" || partido_2 == "iu") {
            $('div#graph_infowindow div.top div.stats div.partido:eq(1)').addClass(partido_2);
          } else {
            $('div#graph_infowindow div.top div.stats div.partido:eq(1)').addClass('par2');
          }
          bar_width = (valuesHash[$(this).parent().attr('id')]["partido_2"][2]*175)/100;
          $('div#graph_infowindow div.top div.stats div.partido:eq(1) span').width((bar_width<2)?2:bar_width);
          $('div#graph_infowindow div.top div.stats div.partido:eq(1) p').text(valuesHash[$(this).parent().attr('id')]["partido_2"][0]+' ('+(valuesHash[$(this).parent().attr('id')]["partido_2"][1]*175)/100+'%)');

          // Third political party
          var partido_3 = valuesHash[$(this).parent().attr('id')]["partido_3"][0].toLowerCase();
          c3 = $('div#graph_infowindow div.top div.stats div.partido:eq(2)').attr('class').split(" ");
          $.each(c3, function(c){
            if(c3[c] != "partido"){
              $('div#graph_infowindow div.top div.stats div.partido:eq(2)').removeClass(c3[c]);
            }
          })
          if (partido_3=='psoe' || partido_3=="pp" || partido_3 == "iu") {
            $('div#graph_infowindow div.top div.stats div.partido:eq(2)').addClass(partido_3);
          } else {
            $('div#graph_infowindow div.top div.stats div.partido:eq(2)').addClass('par3');
          }
          bar_width = (valuesHash[$(this).parent().attr('id')]["partido_3"][2]*175)/100;
          $('div#graph_infowindow div.top div.stats div.partido:eq(2) span').width((bar_width<2)?2:bar_width);
          $('div#graph_infowindow div.top div.stats div.partido:eq(2) p').text(valuesHash[$(this).parent().attr('id')]["partido_3"][0]+' ('+(valuesHash[$(this).parent().attr('id')]["partido_3"][1]*175)/100+'%)');

          // Other political party
          bar_width = (valuesHash[$(this).parent().attr('id')]["resto_partidos_percent"]*175)/100;
          $('div#graph_infowindow div.stats div.partido:eq(3) span').width((bar_width<2)?2:bar_width);
          $('div#graph_infowindow div.stats div.partido:eq(3) p').text('OTROS ('+valuesHash[$(this).parent().attr('id')]["resto_partidos_percent"]+'%)');
        },
        hover: function() {
          console.log($(this).parent().attr('id') + ' ('+valuesHash[$(this).parent().attr('id')]["cartodb_id"]+'): ' + valuesHash[$(this).parent().attr('id')]["x_coordinate"] + ', ' + valuesHash[$(this).parent().attr('id')]["y_coordinate"]);
        }
      });
    }

    function restartGraph() {
      $('div#graph_container').empty();
      valuesHash = {};
      createBubbles("/json/generated_data/autonomies_"+normalization[compare]+"_"+year+".json");
    }

    function createBubbles(url){
      $.getJSON(url, function(data) {
        $.each(data, function(key, val) {
          valuesHash[key] = val;
          nBubbles = nBubbles+1;
          $('#graph_container').append("

");
          $('#'+key).css("left",(offsetScreenX).toString()+"px");
          $('#'+key).css("top",(offsetScreenY).toString()+"px");
          $('#'+key).css("opacity","0");
          $('#'+key).find('.innerBubble').css("backgroundColor",val["color"]);
        });
      });
      setValue(url);
    }

    //changes the values on the graph
    function setValue(url){
      //TODO: VER SI PUEDO EVITAR LEER EL JSON (AL MENOS AL PRINCIPIO)
      console.log("le llega el JSON "+ url);
      $.getJSON(url, function(data) {
        $.each(data, function(key, v) {
          valuesHash[key] = v;
          updateBubble('#'+key,offsetScreenX+parseInt(v["x_coordinate"]),offsetScreenY+parseInt(v["y_coordinate"]),v["radius"],v["color"]);
        });
      });
    }

    //Function for update the values of the bubbles that are being visualized
    function updateBubble (bubble,x,y,val,c){
      var offset = Math.abs(parseInt($(bubble).find('.outerBubble').css('top')) + (parseInt($(bubble).find('.outerBubble').css('height')) - val) / 2)*-1;

      $(bubble).animate({
        left: x.toString() + "px",
        top: y.toString() + "px",
        opacity: 1
      }, 1000, function(){
        console.log("ukelele");
      });

      $(bubble).find('.outerBubble').animate({
          height: val.toString() + "px",
          width: val.toString() + "px",
          top: offset.toString() + "px",
          left: offset.toString() + "px"
        }, 1000);

      $(bubble).find('.innerBubble').animate({
          height: (val-10).toString() + "px",
          width: (val-10).toString() + "px",
          top: (offset + 5).toString() + "px",
          left: (offset + 5).toString() + "px",
          backgroundColor: c.toString() + "px"
        }, 1000);
    }

    function goDeeper(url){
      console.log("Going deep to " + url);
      for (key in valuesHash){
        //Destroy Bubbles
        destroyBubble(key, url);
      }
    }

    function destroyBubble(b, url){
      (parseInt($("#"+b).css("left")) < offsetScreenX) ? displacementX = "-=30px" : displacementX = "+=30px";
      (parseInt($("#"+b).css("top")) < offsetScreenY) ? displacementY = "-=30px" : displacementY = "+=30px";
      $("#"+key).animate({
        left: displacementX,
        top: displacementY,
        opacity: "0"
      }, 500, function(){
          //console.log("Removing "+b);
          $("#"+b).remove();
          nBubbles=nBubbles-1;
          if(nBubbles==0){
            createBubbles(url);
          }
        }
      );
    }<|endoftext|>


<commit_before>// This module encapsulates reusable reactive flows
// common to many D3-based visualizations.
//
// Curran Kelleher April 2015
define(["model","d3"], function (Model,d3){

  // The returned public API object, containing functions that take as input
  // a Model.js model, and as a side effect add reactive flows within that model.
  var reactivis = {};

  // This value is used for optionally defined public properties
  var None = Model.None;

  // Adds a public property to a model.
  // This makes the property configurable via the Chiasm configuration.
  function addPublicProperty(model, property, defaultValue){
    if(!model.publicProperties){
      model.publicProperties = [];
    }
    model.publicProperties.push(property);
    model[property] = defaultValue;
  }

  // Constructs an SVG element as a child of the `container` element.
  // Makes the SVG size and location update with respect to the `box` property.
  // Also adds a G element as a child of the SVG element, for visualizations.
  reactivis.svg = function(model){

    // Create the SVG element from the container DOM element.
    model.when("container", function (container) {

      // Use CSS `position: relative` so that setting properties
      // `left` and `top` will position the SVG relative to the Chiasm container.
      model.svg = d3.select(container).append("svg")
        .style("position", "relative");
    });

    // Adjust the SVG based on the `box` property.
    model.when(["svg", "box"], function (svg, box) {

      // Set the CSS `left` and `top` properties to move the
      // SVG to `(box.x, box.y)` relative to its container.
      svg
        .style("left", box.x + "px")
        .style("top", box.y + "px")
        .attr("width", box.width)
        .attr("height", box.height);

      // Use negative margins to eliminate the SVG space taken up
      // in the layout flow. This is an ugly solution, but the alternatives
      // don't work - setting position:absolute doesn't work when the
      // Chiasm container is statically positioned.
      // http://stackoverflow.com/questions/13722095/how-to-remove-whitespace-that-appears-after-relative-positioning-an-element-with
      svg
        .style("margin-right", "-" + box.width + "px")
        .style("margin-bottom", "-" + box.height + "px");
    });

    // Create the SVG group that will contain the visualization.
    model.when("svg", function (svg) {
      model.g = svg.append("g");
    });
  };

  // Encapsulates conventional D3 margins.
  // See http://bl.ocks.org/mbostock/3019563
  reactivis.margin = function(model){

    // Set up the default margin.
    addPublicProperty(model, "margin",{
      "top": 32,
      "right": 2,
      "bottom": 40,
      "left": 47
    });

    // Compute the inner box from the outer box and margin.
    model.when(["box", "margin"], function (box, margin) {
      model.width = box.width - margin.left - margin.right;
      model.height = box.height - margin.top - margin.bottom;
    });

    // Adjust the translation of the group based on the margin.
    model.when(["g", "margin"], function (g, margin) {
      g.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
    });

    // TODO change layout such that size is not added as a property.
    addPublicProperty(model, "size", 1);
  };

  // Adds a text title at the top of the visualization.
  reactivis.title = function(model){

    addPublicProperty(model, "title", "");
    addPublicProperty(model, "titleOffset", -0.3);

    // Create the title text element.
    model.when("g", function (g){
      model.titleText = g.append("text").attr("class", "title-text");
    });

    // Center the title text when width changes.
    model.when(["titleText", "width"], function (titleText, width) {
      titleText.attr("x", width / 2);
    });

    // Update the title text based on the public `title` property.
    model.when(["titleText", "title"], function (titleText, title){
      titleText.text(title);
    });

    // Update the title text offset.
    model.titleOffset = 1;
    model.when(["titleText", "titleOffset"], function (titleText, titleOffset){
      titleText.attr("dy", titleOffset + "em");
    });
  };

  reactivis.color = function(model){

    addPublicProperty(model, "colorColumn", None);
    addPublicProperty(model, "colorDomain", None);
    addPublicProperty(model, "colorRange", None);
    addPublicProperty(model, "colorDefault", "black");

    // Set up the color scale.
    model.when(["colorDefault", "colorDomain", "colorRange"],
        function (colorDefault, colorDomain, colorRange){
      if(colorDomain !== None && colorRange !== None){
        model.colorScale = d3.scale.ordinal()
          .domain(colorDomain)
          .range(colorRange);
      } else {
        model.colorScale = None;
      }
    });

    // Set up the color evaluation function.
    model.when(["colorColumn", "colorScale", "colorDefault"],
        function(colorColumn, colorScale, colorDefault){
      if(colorColumn !== None && colorScale !== None){
        model.color = function(d){ return colorScale(d[colorColumn]); };
      }
      else {
        model.color = colorDefault;
      }
    });
  };

  // Generates a function for getting the X value.
  reactivis.xAccessor = function(model){
    addPublicProperty(model, "xColumn", None);
    model.when(["xColumn"], function (xColumn) {
      if(xColumn !== None){
        model.xAccessor = function (d) { return d[xColumn]; };
      }
    });
  };

  // Generates a function for getting the Y value.
  reactivis.yAccessor = function(model){
    addPublicProperty(model, "yColumn", None);
    model.when(["yColumn"], function (yColumn) {
      if(yColumn !== None){
        model.yAccessor = function (d) { return d[yColumn]; };
      }
    });
  };

  // A lookup table for scale constructors based on their type.
  var scaleConstructors = {
    linear: d3.scale.linear,
    time: d3.time.scale,
    ordinalBands: d3.scale.ordinal
  };

  // Sets up the X scale.
  reactivis.xScale = function(model, scaleType){

    // Make the scale type a public property so it is configurable at runtime.
    // Example use case: dynamically switching between linear and log scales.
    addPublicProperty(model, "xScaleType", scaleType);

    // Allow the API client to optionally specify fixed min and max values.
    // Relevant only for quantitative scales (linear, time).
    addPublicProperty(model, "xDomainMin", None);
    addPublicProperty(model, "xDomainMax", None);

    // The padding between range bands (e.g. the space between bars in a bar chart).
    // Relevant only for ordinal scales.
    addPublicProperty(model, "xRangePadding", 0.1);

    // Compute the domain of the X column.
    model.when(["data", "xScaleType", "xAccessor", "xDomainMin", "xDomainMax"],
        function (data, xScaleType, xAccessor, xDomainMin, xDomainMax) {

      // Compute the scale domain.
      if(xScaleType === "linear" || xScaleType === "time"){
        if(xDomainMin === None && xDomainMax === None){
          model.xDomain = d3.extent(data, xAccessor);
        } else {
          if(xDomainMin === None){
            xDomainMin = d3.min(data, xAccessor);
          }
          if(xDomainMax === None){
            xDomainMax = d3.max(data, xAccessor);
          }
          model.xDomain = [xDomainMin, xDomainMax];
        }
      } else if (xScaleType === "ordinalBands"){
        model.xDomain = data.map(xAccessor);
      }
    });

    // Compute the X scale.
    model.when(["xScaleType", "xDomain", "width", "xRangePadding"],
        function (xScaleType, xDomain, width, xRangePadding) {
      var scale = scaleConstructors[xScaleType]().domain(xDomain);
      if(xScaleType === "ordinalBands"){
        scale.rangeRoundBands([0, width], xRangePadding);
      } else {
        scale.range([0, width]);
      }
      model.xScale = scale;
    });

    // Generate a function for getting the scaled X value.
    model.when(["data", "xScale", "xAccessor"], function (data, xScale, xAccessor) {
      model.x = function (d) { return xScale(xAccessor(d)); };
    });
  };

  // Sets up the Y scale.
  reactivis.yScale = function(model){

    // TODO unify code between X and Y scales.

    // Allow the API client to optionally specify fixed min and max values.
    addPublicProperty(model, "yDomainMin", None);
    addPublicProperty(model, "yDomainMax", None);
    model.when(["data", "yAccessor", "yDomainMin", "yDomainMax"],
        function (data, yAccessor, yDomainMin, yDomainMax) {

      // If min and max are not given, use the data extent.
      if(yDomainMin === None && yDomainMax === None){
        model.yDomain = d3.extent(data, yAccessor);
      } else {

        // When only max is specified,
        if(yDomainMin === None){

          // compute min from the data.
          yDomainMin = d3.min(data, yAccessor);
        }

        // When only min is specified,
        if(yDomainMax === None){

          // compute max from the data.
          yDomainMax = d3.max(data, yAccessor);
        }

        model.yDomain = [yDomainMin, yDomainMax];
      }
    });

    // Compute the Y scale.
    model.when(["yDomain", "height"], function (yDomain, height) {
      model.yScale = d3.scale.linear().domain(yDomain).range([height, 0]);
    });

    // Generate a function for getting the scaled Y value.
    model.when(["yScale", "yAccessor"], function (yScale, yAccessor) {
      model.y = function (d) { return yScale(yAccessor(d)); };
    });
  };

  // Adds the X axis and its label text.
  reactivis.xAxis = function(model){

    addPublicProperty(model, "xAxisLabel", "");
    addPublicProperty(model, "xAxisLabelOffset", 1.9);

    // Set up the X axis.
    model.when("g", function (g) {
      model.xAxisG = g.append("g").attr("class", "x axis");
      model.xAxisText = model.xAxisG.append("text").style("text-anchor", "middle");
    });

    // Move the X axis label based on its specified offset.
    model.when(["xAxisText", "xAxisLabelOffset"], function (xAxisText, xAxisLabelOffset){
      xAxisText.attr("dy", xAxisLabelOffset + "em");
    });

    // Update the X axis transform when height changes.
    model.when(["xAxisG", "height"], function (xAxisG, height) {
      xAxisG.attr("transform", "translate(0," + height + ")");
    });

    // Center the X axis label when width changes.
    model.when(["xAxisText", "width"], function (xAxisText, width) {
      xAxisText.attr("x", width / 2);
    });

    // Update the X axis based on the X scale.
    model.when(["xAxisG", "xScale"], function (xAxisG, xScale) {
      xAxisG.call(d3.svg.axis().orient("bottom").scale(xScale));
    });

    // Update X axis label.
    model.when(["xAxisText", "xAxisLabel"], function (xAxisText, xAxisLabel) {
      xAxisText.text(xAxisLabel);
    });
  };

  // Adds the Y axis and its label text.
  reactivis.yAxis = function(model){

    // The text shown as the axis label.
    addPublicProperty(model, "yAxisLabel", "");

    // The left-right offset of the axis label, unit is CSS "em"s.
    addPublicProperty(model, "yAxisLabelOffset", 1.4);

    // Set up the Y axis.
    model.when("g", function (g) {
      model.yAxisG = g.append("g").attr("class", "y axis");
      model.yAxisText = model.yAxisG.append("text")
        .style("text-anchor", "middle")
        .attr("transform", "rotate(-90)")
        .attr("y", 0);
    });

    // Move the Y axis label based on its specified offset.
    model.when(["yAxisText", "yAxisLabelOffset"], function (yAxisText, yAxisLabelOffset){
      yAxisText.attr("dy", "-" + yAxisLabelOffset + "em");
    });

    // Center the Y axis label when height changes.
    model.when(["yAxisText", "height"], function (yAxisText, height) {
      yAxisText.attr("x", -height / 2);
    });

    // Update Y axis label.
    model.when(["yAxisText", "yAxisLabel"], function (yAxisText, yAxisLabel) {
      yAxisText.text(yAxisLabel);
    });

    // Update the Y axis based on the Y scale.
    model.when(["yAxisG", "yScale"], function (yAxisG, yScale) {
      yAxisG.call(d3.svg.axis().orient("left").scale(yScale));
    });
  };

  // Expose the addPublicProperty function, as it is a useful utility
  // function for visualizations that build on top of reactivis.
  reactivis.addPublicProperty = addPublicProperty;

  return reactivis;
});
<commit_msg>Unified scale domain computation logic between X and Y scales<commit_after>// This module encapsulates reusable reactive flows
// common to many D3-based visualizations.
//
// Curran Kelleher April 2015
define(["model","d3"], function (Model,d3){

  // The returned public API object, containing functions that take as input
  // a Model.js model, and as a side effect add reactive flows within that model.
  var reactivis = {};

  // This value is used for optionally defined public properties
  var None = Model.None;

  // Adds a public property to a model.
  // This makes the property configurable via the Chiasm configuration.
  function addPublicProperty(model, property, defaultValue){
    if(!model.publicProperties){
      model.publicProperties = [];
    }
    model.publicProperties.push(property);
    model[property] = defaultValue;
  }

  // Constructs an SVG element as a child of the `container` element.
  // Makes the SVG size and location update with respect to the `box` property.
  // Also adds a G element as a child of the SVG element, for visualizations.
  reactivis.svg = function(model){

    // Create the SVG element from the container DOM element.
    model.when("container", function (container) {

      // Use CSS `position: relative` so that setting properties
      // `left` and `top` will position the SVG relative to the Chiasm container.
      model.svg = d3.select(container).append("svg")
        .style("position", "relative");
    });

    // Adjust the SVG based on the `box` property.
    model.when(["svg", "box"], function (svg, box) {

      // Set the CSS `left` and `top` properties to move the
      // SVG to `(box.x, box.y)` relative to its container.
      svg
        .style("left", box.x + "px")
        .style("top", box.y + "px")
        .attr("width", box.width)
        .attr("height", box.height);

      // Use negative margins to eliminate the SVG space taken up
      // in the layout flow. This is an ugly solution, but the alternatives
      // don't work - setting position:absolute doesn't work when the
      // Chiasm container is statically positioned.
      // http://stackoverflow.com/questions/13722095/how-to-remove-whitespace-that-appears-after-relative-positioning-an-element-with
      svg
        .style("margin-right", "-" + box.width + "px")
        .style("margin-bottom", "-" + box.height + "px");
    });

    // Create the SVG group that will contain the visualization.
    model.when("svg", function (svg) {
      model.g = svg.append("g");
    });
  };

  // Encapsulates conventional D3 margins.
  // See http://bl.ocks.org/mbostock/3019563
  reactivis.margin = function(model){

    // Set up the default margin.
    addPublicProperty(model, "margin",{
      "top": 32,
      "right": 2,
      "bottom": 40,
      "left": 47
    });

    // Compute the inner box from the outer box and margin.
    model.when(["box", "margin"], function (box, margin) {
      model.width = box.width - margin.left - margin.right;
      model.height = box.height - margin.top - margin.bottom;
    });

    // Adjust the translation of the group based on the margin.
    model.when(["g", "margin"], function (g, margin) {
      g.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
    });

    // TODO change layout such that size is not added as a property.
    addPublicProperty(model, "size", 1);
  };

  // Adds a text title at the top of the visualization.
  reactivis.title = function(model){

    addPublicProperty(model, "title", "");
    addPublicProperty(model, "titleOffset", -0.3);

    // Create the title text element.
    model.when("g", function (g){
      model.titleText = g.append("text").attr("class", "title-text");
    });

    // Center the title text when width changes.
    model.when(["titleText", "width"], function (titleText, width) {
      titleText.attr("x", width / 2);
    });

    // Update the title text based on the public `title` property.
    model.when(["titleText", "title"], function (titleText, title){
      titleText.text(title);
    });

    // Update the title text offset.
    model.titleOffset = 1;
    model.when(["titleText", "titleOffset"], function (titleText, titleOffset){
      titleText.attr("dy", titleOffset + "em");
    });
  };

  reactivis.color = function(model){

    addPublicProperty(model, "colorColumn", None);
    addPublicProperty(model, "colorDomain", None);
    addPublicProperty(model, "colorRange", None);
    addPublicProperty(model, "colorDefault", "black");

    // Set up the color scale.
    model.when(["colorDefault", "colorDomain", "colorRange"],
        function (colorDefault, colorDomain, colorRange){
      if(colorDomain !== None && colorRange !== None){
        model.colorScale = d3.scale.ordinal()
          .domain(colorDomain)
          .range(colorRange);
      } else {
        model.colorScale = None;
      }
    });

    // Set up the color evaluation function.
    model.when(["colorColumn", "colorScale", "colorDefault"],
        function(colorColumn, colorScale, colorDefault){
      if(colorColumn !== None && colorScale !== None){
        model.color = function(d){ return colorScale(d[colorColumn]); };
      }
      else {
        model.color = colorDefault;
      }
    });
  };

  // Generates a function for getting the X value.
  reactivis.xAccessor = function(model){
    addPublicProperty(model, "xColumn", None);
    model.when(["xColumn"], function (xColumn) {
      if(xColumn !== None){
        model.xAccessor = function (d) { return d[xColumn]; };
      }
    });
  };

  // Generates a function for getting the Y value.
  reactivis.yAccessor = function(model){
    addPublicProperty(model, "yColumn", None);
    model.when(["yColumn"], function (yColumn) {
      if(yColumn !== None){
        model.yAccessor = function (d) { return d[yColumn]; };
      }
    });
  };

  // A lookup table for scale constructors based on their type.
  var scaleConstructors = {
    linear: d3.scale.linear,
    time: d3.time.scale,
    ordinalBands: d3.scale.ordinal
  };

  // A generalized function for computing the domain of scales, used for both X and Y scales.
  function computeDomain(data, scaleType, accessor, domainMin, domainMax) {
    if(scaleType === "linear" || scaleType === "time"){
      if(domainMin === None && domainMax === None){
        return d3.extent(data, accessor);
      } else {
        if(domainMin === None){
          domainMin = d3.min(data, accessor);
        }
        if(domainMax === None){
          domainMax = d3.max(data, accessor);
        }
        return [domainMin, domainMax];
      }
    } else if (scaleType === "ordinalBands"){
      return data.map(accessor);
    }
  }

  // Sets up the X scale.
  reactivis.xScale = function(model, scaleType){

    // Make the scale type a public property so it is configurable at runtime.
    // Example use case: dynamically switching between linear and log scales.
    addPublicProperty(model, "xScaleType", scaleType);

    // Allow the API client to optionally specify fixed min and max values.
    // Relevant only for quantitative scales (linear, time).
    addPublicProperty(model, "xDomainMin", None);
    addPublicProperty(model, "xDomainMax", None);

    // The padding between range bands (e.g. the space between bars in a bar chart).
    // Relevant only for ordinal scales.
    addPublicProperty(model, "xRangePadding", 0.1);

    // Compute the domain of the X column.
    model.when(["data", "xScaleType", "xAccessor", "xDomainMin", "xDomainMax"],
        function (data, xScaleType, xAccessor, xDomainMin, xDomainMax) {

      // TODO figure out a way to reduce duplication of property names in this code.
      model.xDomain = computeDomain(data, xScaleType, xAccessor, xDomainMin, xDomainMax);

    });

    // Compute the X scale.
    model.when(["xScaleType", "xDomain", "width", "xRangePadding"],
        function (xScaleType, xDomain, width, xRangePadding) {
      var scale = scaleConstructors[xScaleType]().domain(xDomain);
      if(xScaleType === "ordinalBands"){
        scale.rangeRoundBands([0, width], xRangePadding);
      } else {
        scale.range([0, width]);
      }
      model.xScale = scale;
    });

    // Generate a function for getting the scaled X value.
    model.when(["data", "xScale", "xAccessor"], function (data, xScale, xAccessor) {
      model.x = function (d) { return xScale(xAccessor(d)); };
    });
  };

  // Sets up the Y scale.
  reactivis.yScale = function(model){

    // TODO unify code between X and Y scales.

    // Allow the API client to optionally specify fixed min and max values.
    addPublicProperty(model, "yDomainMin", None);
    addPublicProperty(model, "yDomainMax", None);
    addPublicProperty(model, "yScaleType", "linear");
    model.when(["data", "yScaleType", "yAccessor", "yDomainMin", "yDomainMax"],
        function (data, yScaleType, yAccessor, yDomainMin, yDomainMax) {

      // TODO figure out a way to reduce duplication of property names in this code.
      model.yDomain = computeDomain(data, yScaleType, yAccessor, yDomainMin, yDomainMax);
    });

    // Compute the Y scale.
    model.when(["yDomain", "height"], function (yDomain, height) {
      model.yScale = d3.scale.linear().domain(yDomain).range([height, 0]);
    });

    // Generate a function for getting the scaled Y value.
    model.when(["yScale", "yAccessor"], function (yScale, yAccessor) {
      model.y = function (d) { return yScale(yAccessor(d)); };
    });
  };

  // Adds the X axis and its label text.
  reactivis.xAxis = function(model){

    addPublicProperty(model, "xAxisLabel", "");
    addPublicProperty(model, "xAxisLabelOffset", 1.9);

    // Set up the X axis.
    model.when("g", function (g) {
      model.xAxisG = g.append("g").attr("class", "x axis");
      model.xAxisText = model.xAxisG.append("text").style("text-anchor", "middle");
    });

    // Move the X axis label based on its specified offset.
    model.when(["xAxisText", "xAxisLabelOffset"], function (xAxisText, xAxisLabelOffset){
      xAxisText.attr("dy", xAxisLabelOffset + "em");
    });

    // Update the X axis transform when height changes.
    model.when(["xAxisG", "height"], function (xAxisG, height) {
      xAxisG.attr("transform", "translate(0," + height + ")");
    });

    // Center the X axis label when width changes.
    model.when(["xAxisText", "width"], function (xAxisText, width) {
      xAxisText.attr("x", width / 2);
    });

    // Update the X axis based on the X scale.
    model.when(["xAxisG", "xScale"], function (xAxisG, xScale) {
      xAxisG.call(d3.svg.axis().orient("bottom").scale(xScale));
    });

    // Update X axis label.
    model.when(["xAxisText", "xAxisLabel"], function (xAxisText, xAxisLabel) {
      xAxisText.text(xAxisLabel);
    });
  };

  // Adds the Y axis and its label text.
  reactivis.yAxis = function(model){

    // The text shown as the axis label.
    addPublicProperty(model, "yAxisLabel", "");

    // The left-right offset of the axis label, unit is CSS "em"s.
    addPublicProperty(model, "yAxisLabelOffset", 1.4);

    // Set up the Y axis.
    model.when("g", function (g) {
      model.yAxisG = g.append("g").attr("class", "y axis");
      model.yAxisText = model.yAxisG.append("text")
        .style("text-anchor", "middle")
        .attr("transform", "rotate(-90)")
        .attr("y", 0);
    });

    // Move the Y axis label based on its specified offset.
    model.when(["yAxisText", "yAxisLabelOffset"], function (yAxisText, yAxisLabelOffset){
      yAxisText.attr("dy", "-" + yAxisLabelOffset + "em");
    });

    // Center the Y axis label when height changes.
    model.when(["yAxisText", "height"], function (yAxisText, height) {
      yAxisText.attr("x", -height / 2);
    });

    // Update Y axis label.
    model.when(["yAxisText", "yAxisLabel"], function (yAxisText, yAxisLabel) {
      yAxisText.text(yAxisLabel);
    });

    // Update the Y axis based on the Y scale.
    model.when(["yAxisG", "yScale"], function (yAxisG, yScale) {
      yAxisG.call(d3.svg.axis().orient("left").scale(yScale));
    });
  };

  // Expose the addPublicProperty function, as it is a useful utility
  // function for visualizations that build on top of reactivis.
  reactivis.addPublicProperty = addPublicProperty;

  return reactivis;
});
<|endoftext|>