<commit_before>import "../cast"
import "../gensym"

Elm.Native.D3.Selection = {};
Elm.Native.D3.Selection.make = function(elm) {
  'use strict';

  /*
   * A Brief Explanation of What's Going On
   *
   * At the core of D3 is the concept of a selection—a collection of DOM
   * elements to which you can associate data and apply transformations,
   * filters, and subselections. Some of these operations mutate elements in
   * the selection, while others create distinct subselections. The user
   * combines operations using either method chaining or by sequencing
   * statements in JavaScript, i.e., the semicolon.
   *
   * A selection encapsulates some state, operations transform that state and
   * then return to some implicit context that will apply further operations to
   * the selection. In other words an operation returns to some continuation.
   * This code models that pattern explicitly. A `Selection a` is a function
   * that takes a continuation and a selection, performs some stateful
   * operations to the selection, possibly creating a new one even, and then
   * passes that to the continuation. You can think of `Selection a` as being
   * an alias for a type that looks something like this:
   *
   *   (selection -> index -> unit) -> selection -> index -> unit
   *
   * This approach ensures that all operations that produce `Selection a`s are
   * pure functions. All effects are encapsulated in closures and deferred
   * until the Elm runtime decides that the `Selection a` should be rendered or
   * updated. In order to make the Elm runtime aware of the `Selection a`, you
   * have to turn it into an `Element` by calling the `render` function. You
   * can find the render function in the D3 Elm module. See Native/D3/Render.js
   * for its implementation.
   *
   * Note that the index parameter is introduced to support the `static`
   * selection below. The goal is to have static elements preserve both the
   * data and index of its context, as well as propagate that down other
   * selections. D3 typically does this bookeeping, but the implentation of
   * `static`—and in fact `static` as a concept—require that this library
   * perform the bookeeping explicity. See `bind` and `static` for situations
   * where the handing of the index are different than in all the other
   * functions.
   */

  elm.Native = elm.Native || {};
  elm.Native.D3 = elm.Native.D3 || {};
  elm.Native.D3.Selection = elm.Native.D3.Selection || {};
  if (elm.Native.D3.Selection.values) return elm.Native.D3.Selection.values;

  var JS = Elm.Native.JavaScript.make(elm);
  var Json = Elm.Native.Json.make(elm);

  function safeJson(fn) {
    return function(a, i) {
      return Json.toJSObject(A2(fn, a, JS.toInt(i)));
    };
  }

  function safeOptEvaluator(fn) {
    return function (a, i) {
      var result = A2(fn, a, JS.toInt(i));
      return result.ctor == "Just" ? JS.fromString(result._0) : null;
    };
  }

  function safeEvaluator(fn) {
    return function (a, i) {
      return JS.fromString(A2(fn, a, JS.toInt(i)));
    };
  }

  function safePredicate(fn) {
    return function (a, i) {
      return JS.fromBool(A2(fn, a, JS.toInt(i)));
    };
  }

  function id(x) { return x; }

  function elm_sequence(s1, s2) {
    return function(k, selection, i) {
      s1(id, selection, i);
      s2(id, selection, i);
      return k(selection, i);
    };
  }

  function elm_chain(s1, s2) {
    return function(k, selection, i) {
      return s1(function(_selection, j) {
        return s2(k, _selection, j);
      }, selection, i);
    };
  }

  function elm_select(selector) {
    var selector = JS.fromString(selector);
    return function(k, selection, i) {
      return k(selection.select(selector), i);
    };
  }

  function elm_selectAll(selector) {
    var selector = JS.fromString(selector);
    return function(k, selection, i) {
      return k(selection.selectAll(selector), i);
    };
  }

  function elm_append(element) {
    var element = JS.fromString(element);
    return function(k, selection, i) {
      return k(selection.append(element), i);
    };
  }

  /* NB: Index bookkeeping is different, as bind ignores the index of the
   * context. */
  function elm_bind(s, fn) {
    return function(k, selection, i) {
      return s(function(_selection, _) {
        var bind = _selection.data(function (d) { return JS.fromList(fn(d)); });
        return k(bind);
      }, selection, i);
    };
  }

  function elm_chain_widget(w, s) {
    return function(k, selection, i) {
      return w(function(_selection, j) {
        s(id, _selection, j);
        return k(_selection, j);
      }, selection, i);
    };
  }

  function elm_embed(w) {
    return function(k, selection, i) {
      w(id, selection, i);
      return k(selection, i);
    }
  }

  function elm_enter(k, selection, i) {
    return k(selection.enter(), i);
  }

  function elm_exit(k, selection, i) {
    return k(selection.exit(), i);
  }

  function elm_update(k, selection, i) {
    return k(selection, i);
  }

  function elm_remove(k, selection, i) {
    return k(selection.remove(), i);
  }

  /* NB: Index bookkeeping is different, as static ignores the index of the
   * context. */
  function elm_static(element) {
    var element = JS.fromString(element),
        static_class = gensym('static');

    return function(k, selection, i) {
      return selection.each(safeIndexed(i, function(d, i) {
        var s = d3.select(this),
            static_ = s.select('.' + static_class);

        static_ = static_.size() == 0 ? s.append(element) : static_;

        var result = k(static_, i);
        static_.classed(static_class, true);
        return result;
      }));
    };
  }

  function elm_classed(name, valfn) {
    name = JS.fromString(name);
    valfn = safeValfn(valfn, safePredicate);
    return function(k, selection, i) {
      return k(selection.classed(name, safeIndexed(i, valfn)), i);
    };
  }

  function elm_attr(name, valfn) {
    name = JS.fromString(name);
    valfn = safeValfn(valfn, safeOptEvaluator);
    return function(k, selection, i) {
      return k(selection.attr(name, safeIndexed(i, valfn)), i);
    };
  }

  function elm_style(name, valfn) {
    name = JS.fromString(name);
    valfn = safeValfn(valfn, safeOptEvaluator);
    return function(k, selection, i) {
      return k(selection.style(name, safeIndexed(i, valfn)), i);
    };
  }

  function elm_property(name, valfn) {
    name = JS.fromString(name);
    valfn = safeValfn(valfn, safeJson);
    return function(k, selection, i) {
      return k(selection.property(name, safeIndexed(i, valfn)), i);
    };
  }

  function elm_html(valfn) {
    valfn = safeValfn(valfn, safeEvaluator);
    return function(k, selection, i) {
      return k(selection.html(safeIndexed(i, valfn)), i);
    };
  }

  function elm_text(valfn) {
    valfn = safeValfn(valfn, safeEvaluator);
    return function(k, selection, i) {
      return k(selection.text(safeIndexed(i, valfn)), i);
    };
  }

  return elm.Native.D3.values = {
    version : JS.toString(d3.version),
    sequence : F2(elm_sequence),
    chain : F2(elm_chain),
    select : elm_select,
    selectAll : elm_selectAll,
    append : elm_append,
    static_ : elm_static,
    bind : F2(elm_bind),
    chain_widget : F2(elm_chain_widget),
    embed : elm_embed,
    enter : elm_enter,
    exit : elm_exit,
    update : elm_update,
    remove : elm_remove,
    classed : F2(elm_classed),
    attr : F2(elm_attr),
    style : F2(elm_style),
    property : F2(elm_property),
    html : elm_html,
    text : elm_text
  };
};
<commit_msg>properly handle attr and style<commit_after>import "../cast"
import "../gensym"

Elm.Native.D3.Selection = {};
Elm.Native.D3.Selection.make = function(elm) {
  'use strict';

  /*
   * A Brief Explanation of What's Going On
   *
   * At the core of D3 is the concept of a selection—a collection of DOM
   * elements to which you can associate data and apply transformations,
   * filters, and subselections. Some of these operations mutate elements in
   * the selection, while others create distinct subselections. The user
   * combines operations using either method chaining or by sequencing
   * statements in JavaScript, i.e., the semicolon.
   *
   * A selection encapsulates some state, operations transform that state and
   * then return to some implicit context that will apply further operations to
   * the selection. In other words an operation returns to some continuation.
   * This code models that pattern explicitly. A `Selection a` is a function
   * that takes a continuation and a selection, performs some stateful
   * operations to the selection, possibly creating a new one even, and then
   * passes that to the continuation. You can think of `Selection a` as being
   * an alias for a type that looks something like this:
   *
   *   (selection -> index -> unit) -> selection -> index -> unit
   *
   * This approach ensures that all operations that produce `Selection a`s are
   * pure functions. All effects are encapsulated in closures and deferred
   * until the Elm runtime decides that the `Selection a` should be rendered or
   * updated. In order to make the Elm runtime aware of the `Selection a`, you
   * have to turn it into an `Element` by calling the `render` function. You
   * can find the render function in the D3 Elm module. See Native/D3/Render.js
   * for its implementation.
   *
   * Note that the index parameter is introduced to support the `static`
   * selection below. The goal is to have static elements preserve both the
   * data and index of its context, as well as propagate that down other
   * selections. D3 typically does this bookeeping, but the implentation of
   * `static`—and in fact `static` as a concept—require that this library
   * perform the bookeeping explicity. See `bind` and `static` for situations
   * where the handing of the index are different than in all the other
   * functions.
   */

  elm.Native = elm.Native || {};
  elm.Native.D3 = elm.Native.D3 || {};
  elm.Native.D3.Selection = elm.Native.D3.Selection || {};
  if (elm.Native.D3.Selection.values) return elm.Native.D3.Selection.values;

  var JS = Elm.Native.JavaScript.make(elm);
  var Json = Elm.Native.Json.make(elm);

  function safeJson(fn) {
    return function(a, i) {
      return Json.toJSObject(A2(fn, a, JS.toInt(i)));
    };
  }

  function safeOptEvaluator(fn) {
    return function (a, i) {
      var result = A2(fn, a, JS.toInt(i));
      return result.ctor == "Just" ? JS.fromString(result._0) : null;
    };
  }

  function safeEvaluator(fn) {
    return function (a, i) {
      return JS.fromString(A2(fn, a, JS.toInt(i)));
    };
  }

  function safePredicate(fn) {
    return function (a, i) {
      return JS.fromBool(A2(fn, a, JS.toInt(i)));
    };
  }

  function id(x) { return x; }

  function elm_sequence(s1, s2) {
    return function(k, selection, i) {
      s1(id, selection, i);
      s2(id, selection, i);
      return k(selection, i);
    };
  }

  function elm_chain(s1, s2) {
    return function(k, selection, i) {
      return s1(function(_selection, j) {
        return s2(k, _selection, j);
      }, selection, i);
    };
  }

  function elm_select(selector) {
    var selector = JS.fromString(selector);
    return function(k, selection, i) {
      return k(selection.select(selector), i);
    };
  }

  function elm_selectAll(selector) {
    var selector = JS.fromString(selector);
    return function(k, selection, i) {
      return k(selection.selectAll(selector), i);
    };
  }

  function elm_append(element) {
    var element = JS.fromString(element);
    return function(k, selection, i) {
      return k(selection.append(element), i);
    };
  }

  /* NB: Index bookkeeping is different, as bind ignores the index of the
   * context. */
  function elm_bind(s, fn) {
    return function(k, selection, i) {
      return s(function(_selection, _) {
        var bind = _selection.data(function (d) { return JS.fromList(fn(d)); });
        return k(bind);
      }, selection, i);
    };
  }

  function elm_chain_widget(w, s) {
    return function(k, selection, i) {
      return w(function(_selection, j) {
        s(id, _selection, j);
        return k(_selection, j);
      }, selection, i);
    };
  }

  function elm_embed(w) {
    return function(k, selection, i) {
      w(id, selection, i);
      return k(selection, i);
    }
  }

  function elm_enter(k, selection, i) {
    return k(selection.enter(), i);
  }

  function elm_exit(k, selection, i) {
    return k(selection.exit(), i);
  }

  function elm_update(k, selection, i) {
    return k(selection, i);
  }

  function elm_remove(k, selection, i) {
    return k(selection.remove(), i);
  }

  /* NB: Index bookkeeping is different, as static ignores the index of the
   * context. */
  function elm_static(element) {
    var element = JS.fromString(element),
        static_class = gensym('static');

    return function(k, selection, i) {
      return selection.each(safeIndexed(i, function(d, i) {
        var s = d3.select(this),
            static_ = s.select('.' + static_class);

        static_ = static_.size() == 0 ? s.append(element) : static_;

        var result = k(static_, i);
        static_.classed(static_class, true);
        return result;
      }));
    };
  }

  function elm_classed(name, valfn) {
    name = JS.fromString(name);
    valfn = safeValfn(valfn, safePredicate);
    return function(k, selection, i) {
      return k(selection.classed(name, safeIndexed(i, valfn)), i);
    };
  }

  function elm_attr(name, valfn) {
    name = JS.fromString(name);
    valfn = safeValfn(valfn, safeEvaluator);
    return function(k, selection, i) {
      return k(selection.attr(name, safeIndexed(i, valfn)), i);
    };
  }

  function elm_style(name, valfn) {
    name = JS.fromString(name);
    valfn = safeValfn(valfn, safeEvaluator);
    return function(k, selection, i) {
      return k(selection.style(name, safeIndexed(i, valfn)), i);
    };
  }

  function elm_property(name, valfn) {
    name = JS.fromString(name);
    valfn = safeValfn(valfn, safeJson);
    return function(k, selection, i) {
      return k(selection.property(name, safeIndexed(i, valfn)), i);
    };
  }

  function elm_html(valfn) {
    valfn = safeValfn(valfn, safeEvaluator);
    return function(k, selection, i) {
      return k(selection.html(safeIndexed(i, valfn)), i);
    };
  }

  function elm_text(valfn) {
    valfn = safeValfn(valfn, safeEvaluator);
    return function(k, selection, i) {
      return k(selection.text(safeIndexed(i, valfn)), i);
    };
  }

  return elm.Native.D3.values = {
    version : JS.toString(d3.version),
    sequence : F2(elm_sequence),
    chain : F2(elm_chain),
    select : elm_select,
    selectAll : elm_selectAll,
    append : elm_append,
    static_ : elm_static,
    bind : F2(elm_bind),
    chain_widget : F2(elm_chain_widget),
    embed : elm_embed,
    enter : elm_enter,
    exit : elm_exit,
    update : elm_update,
    remove : elm_remove,
    classed : F2(elm_classed),
    attr : F2(elm_attr),
    style : F2(elm_style),
    property : F2(elm_property),
    html : elm_html,
    text : elm_text
  };
};
<|endoftext|>


<commit_before>var cdb = require('cartodb.js');
var $ = require('jquery');
var _ = require('underscore');
var navigateThroughRouter = require('../new_common/view_helpers/navigate_through_router');
var pluralizeString = require('../new_common/view_helpers/pluralize_string');
var CreateDialog = require('../new_common/dialogs/create/create_view');
var DeleteItemsDialog = require('../new_dashboard/dialogs/delete_items_view');
var ChangeLockDialog = require('../new_dashboard/dialogs/change_lock_view');
var ChangeLockViewModel = require('./dialogs/change_lock_view_model');
var DeleteItemsViewModel = require('./dialogs/delete_items_view_model');

/**
 *  Dashboard filters.
 *
 *  - 'Order by' (time,likes,etc) collection.
 *  - 'Filter by' collection.
 *  - 'Search' any pattern within collection.
 *
 */
module.exports = cdb.core.View.extend({

  _TOOLTIPS: ['js-likes', 'js-mapviews', 'js-updated_at', 'js-size'],

  events: {
    'submit .js-search-form':   '_submitSearch',
    'click .js-search-form':    'killEvent',
    'click .js-search-link':    '_onSearchClick',
    'click .js-clean-search':   '_onCleanSearchClick',
    'click .js-deselect_all':   '_unselectAll',
    'click .js-select_all':     '_selectAll',
    'click .js-order-link':     '_changeOrder',
    'click .js-delete':         '_openDeleteItemsDialog',
    'click .js-new_dataset':    '_connectDataset',
    'click .js-new_map':        '_newMap',
    'click .js-lock':           '_openChangeLockDialog',
    'click .js-privacy':        '_openPrivacyDialog',
    'click .js-link':           navigateThroughRouter
  },

  initialize: function() {
    this.router = this.options.router;
    this.user = this.options.user;
    this.localStorage = this.options.localStorage;
    this.template = cdb.templates.getTemplate('new_dashboard/views/filters');

    this._preRender();
    this._initBinds();
  },

  // It is necessary to add two static elements because
  // they can't be removed/replaced using render method
  // each time a change (in a model or a collection) happens.
  // This is due to the behaviour of the CSS animations.
  _preRender: function() {
    var $uInner = $('<div>').addClass('u-inner');
    var $filtersInner = $('<div>').addClass('Filters-inner');
    this.$el.append($uInner.append($filtersInner));
  },

  render: function(m, c) {
    this.clearSubViews();

    var selectedItemsCount = this._selectedItems().length;
    // If a change is made from content type we have to know
    // preventing show wrong data about total items
    var changedContentType = c && c.changes && c.changes.content_type;

    this.$('.Filters-inner').html(
      this.template(
        _.extend({
            canCreateDatasets:     this.user.canCreateDatasets(),
            order:                 this.localStorage.get('dashboard.order'),
            isInsideOrg:           this.user.isInsideOrg(),
            selectedItemsCount:    selectedItemsCount,
            maxLayersByMap:        this.user.get('max_layers'),
            totalShared:           changedContentType ? 0 : this.collection.total_shared,
            totalLiked:            changedContentType ? 0 : this.collection.total_likes,
            totalItems:            changedContentType ? 0 : this.collection.total_user_entries,
            pageItems:             this.collection.size(),
            router:                this.router,
            currentDashboardUrl:   this.router.currentDashboardUrl(),
            pluralizedContentType: pluralizeString(this.router.model.get('content_type') === "datasets" ? 'dataset' : 'map', changedContentType ? 0 : this.collection.total_user_entries),
            pluralizedContentTypeSelected: pluralizeString(this.router.model.get('content_type') === "datasets" ? 'dataset' : 'map', selectedItemsCount)
          },
          this.router.model.attributes
        )
      )
    );

    this._initViews();
    this._checkScroll();
    this._animate();

    return this;
  },

  _initBinds: function() {
    this.router.model.bind('change', this.render, this);
    this.collection.bind('loading', function() {
      this.$el.removeClass('is-relative');
    }, this);
    this.collection.bind('add remove change reset', this.render, this);
    this.user.bind('change:remaining_byte_quota', this.render, this);
    cdb.god.bind('closeDialogs', this._animate, this);
    cdb.god.bind('closeDialogs', this._unselectAll, this);
    _.bindAll(this, '_onWindowScroll');

    this.add_related_model(this.collection);
    this.add_related_model(this.router.model);
    this.add_related_model(this.user);
    this.add_related_model(cdb.god);
  },

  _checkScroll: function() {
    var content_type = this.router.model.get('content_type');
    var shared = this.router.model.get('shared');
    var locked = this.router.model.get('locked');
    var liked = this.router.model.get('liked');
    var search = this.router.model.get('q') || this.router.model.get('tag');
    var total_entries = this.collection.total_entries;

    // Bind scroll
    if (total_entries === 0 && content_type === "maps" && !shared && !locked && !liked && !search) {
      // If there is no maps, onboarding should appear
      // and filters block should be after that section
      this.$el.addClass('is-relative');
      this._unbindScroll();
    } else {
      this.$el.removeClass('is-relative');
      this._bindScroll();
    }
  },

  _bindScroll: function() {
    this._unbindScroll();
    $(window).bind('scroll', this._onWindowScroll);
  },

  _unbindScroll: function() {
    $(window).unbind('scroll', this._onWindowScroll);
  },

  _initViews: function() {
    // Tipsys?
    var self = this;
    _.each(this._TOOLTIPS, function(el) {
      self.addView(
        new cdb.common.TipsyTooltip({
          el: self.$('.' + el),
          title: function() {
            var isFixed = self.$el.hasClass('is-fixed');
            return isFixed ? '' : $(this).attr('data-title');
          }
        })
      )
    });
  },

  _selectedItems: function() {
    return this.collection.where({ selected: true });
  },

  _animate: function() {
    // Show filters or selected items actions
    var selectedItemsCount = this._selectedItems().length;
    this.$('.Filters-inner')[ selectedItemsCount > 0 ? 'addClass' : 'removeClass' ]('items--selected');

    // Check if any search is applied
    var search = this.router.model.get('q') || this.router.model.get('tag');
    this.$('.Filters-inner')[ search ? 'addClass' : 'removeClass' ]('search--enabled');
  },

  _selectAll: function(e) {
    this._select(e, true);
  },

  _unselectAll: function(e) {
    this._select(e, false);
  },

  _select: function(e, val) {
    this.killEvent(e);
    this.collection.each(function(vis) {
      vis.set('selected', val);
    });
  },

  _openDeleteItemsDialog: function(e) {
    this.killEvent(e);

    var viewModel = new DeleteItemsViewModel(this._selectedItems(), {
      contentType: this.router.model.get('content_type')
    });

    viewModel.bind('DeleteItemsDone', function() {
      this.user.fetch(); // needed in order to keep the "quota" synchronized
    }, this);

    var view = new DeleteItemsDialog({
      viewModel: viewModel,
      user: this.user,
      clean_on_hide: true,
      enter_to_confirm: true
    });
    this.addView(view);

    view.appendToBody();
  },

  _openChangeLockDialog: function(e) {
    this.killEvent(e);

    var viewModel = new ChangeLockViewModel(this._selectedItems());
    viewModel.bind('ProcessItemsDone', function() {
      this.collection.fetch();
    }, this);

    var view = new ChangeLockDialog({
      contentType: this.router.model.get('content_type'),
      viewModel: viewModel,
      clean_on_hide: true,
      enter_to_confirm: true
    });
    this.addView(view);

    view.appendToBody();
  },

  _openPrivacyDialog: function(e) {
    this.killEvent(e);
    cdb.god.trigger('openPrivacyDialog', this._selectedItems()[0]);
  },

  _onSearchClick: function(e) {
    if (e) this.killEvent(e);

    active = this.$('.Filters-inner').hasClass('search--enabled')
    this.$('.Filters-inner')[ active ? 'removeClass' : 'addClass' ]('search--enabled');
    if (active) this.$('.js-search-input').focus();
  },

  // Creation actions

  _newMap: function(e) {
    if (e) e.preventDefault();
    this._openCreateDialog('map');
  },

  _connectDataset: function(e) {
    if (e) e.preventDefault();

    if (this.user.canCreateDatasets()) {
      this._openCreateDialog('dataset');
    }
  },

  _openCreateDialog: function(type) {
    var createDialog = new CreateDialog({
      type: type || 'map',
      user: this.user,
      clean_on_hide: true
    });
    createDialog.bind('mapCreated', function(vis) {
      window.location = vis.viewUrl().edit();
    }, this);
    createDialog.bind('datasetCreated', function(tableMetadata) {
      window.location = tableMetadata.viewUrl();
    }, this);
    createDialog.bind('datasetSelected', function(d) {
      this.trigger('datasetSelected', d, this);
    }, this);
    createDialog.bind('remoteSelected', function(d) {
      this.trigger('remoteSelected', d, this);
    }, this);
    createDialog.appendToBody();
  },

  // Filter actions

  _onCleanSearchClick: function(e) {
    if (e) e.preventDefault();
    this._navigateToUrl({
      search: '',
      library: this.router.model.get('library'),
      shared: this.router.model.get('shared')
    });
  },

  _submitSearch: function(e) {
    if (e) e.preventDefault();
    this._navigateToUrl({
      search: this.$('.js-search-input').val().trim(),
      library: this.router.model.get('library'),
      shared: this.router.model.get('shared')
    });
  },

  _navigateToUrl: function(opts) {
    this.router.navigate(this.router.currentUrl(opts), { trigger: true });
  },

  _changeOrder: function(e) {
    if (e) e.preventDefault();

    var $el = $(e.target).closest('.js-order-link');
    var order = 'updated_at';

    if ($el.hasClass('js-mapviews')) order = 'mapviews';
    if ($el.hasClass('js-likes')) order = 'likes';
    if ($el.hasClass('js-size')) order = 'size';

    // Order change?
    if (this.router.model.get('order') !== order) {
      this.localStorage.set({ 'dashboard.order': order });
      this.router.model.set('order', order);
    }
  },

  _onWindowScroll: function() {
    var offset = $(window).scrollTop();
    this.$el[ offset > 81 ? 'addClass' : 'removeClass' ]('is-fixed')
  },

  clean: function() {
    this._unbindScroll();
    cdb.core.View.prototype.clean.call(this);
  }

});
<commit_msg>Add method for _cleanSearch() and removes search from search button<commit_after>var cdb = require('cartodb.js');
var $ = require('jquery');
var _ = require('underscore');
var navigateThroughRouter = require('../new_common/view_helpers/navigate_through_router');
var pluralizeString = require('../new_common/view_helpers/pluralize_string');
var CreateDialog = require('../new_common/dialogs/create/create_view');
var DeleteItemsDialog = require('../new_dashboard/dialogs/delete_items_view');
var ChangeLockDialog = require('../new_dashboard/dialogs/change_lock_view');
var ChangeLockViewModel = require('./dialogs/change_lock_view_model');
var DeleteItemsViewModel = require('./dialogs/delete_items_view_model');

/**
 *  Dashboard filters.
 *
 *  - 'Order by' (time,likes,etc) collection.
 *  - 'Filter by' collection.
 *  - 'Search' any pattern within collection.
 *
 */
module.exports = cdb.core.View.extend({

  _TOOLTIPS: ['js-likes', 'js-mapviews', 'js-updated_at', 'js-size'],

  events: {
    'submit .js-search-form':   '_submitSearch',
    'click .js-search-form':    'killEvent',
    'click .js-search-link':    '_onSearchClick',
    'click .js-clean-search':   '_onCleanSearchClick',
    'click .js-deselect_all':   '_unselectAll',
    'click .js-select_all':     '_selectAll',
    'click .js-order-link':     '_changeOrder',
    'click .js-delete':         '_openDeleteItemsDialog',
    'click .js-new_dataset':    '_connectDataset',
    'click .js-new_map':        '_newMap',
    'click .js-lock':           '_openChangeLockDialog',
    'click .js-privacy':        '_openPrivacyDialog',
    'click .js-link':           navigateThroughRouter
  },

  initialize: function() {
    this.router = this.options.router;
    this.user = this.options.user;
    this.localStorage = this.options.localStorage;
    this.template = cdb.templates.getTemplate('new_dashboard/views/filters');

    this._preRender();
    this._initBinds();
  },

  // It is necessary to add two static elements because
  // they can't be removed/replaced using render method
  // each time a change (in a model or a collection) happens.
  // This is due to the behaviour of the CSS animations.
  _preRender: function() {
    var $uInner = $('<div>').addClass('u-inner');
    var $filtersInner = $('<div>').addClass('Filters-inner');
    this.$el.append($uInner.append($filtersInner));
  },

  render: function(m, c) {
    this.clearSubViews();

    var selectedItemsCount = this._selectedItems().length;
    // If a change is made from content type we have to know
    // preventing show wrong data about total items
    var changedContentType = c && c.changes && c.changes.content_type;

    this.$('.Filters-inner').html(
      this.template(
        _.extend({
            canCreateDatasets:     this.user.canCreateDatasets(),
            order:                 this.localStorage.get('dashboard.order'),
            isInsideOrg:           this.user.isInsideOrg(),
            selectedItemsCount:    selectedItemsCount,
            maxLayersByMap:        this.user.get('max_layers'),
            totalShared:           changedContentType ? 0 : this.collection.total_shared,
            totalLiked:            changedContentType ? 0 : this.collection.total_likes,
            totalItems:            changedContentType ? 0 : this.collection.total_user_entries,
            pageItems:             this.collection.size(),
            router:                this.router,
            currentDashboardUrl:   this.router.currentDashboardUrl(),
            pluralizedContentType: pluralizeString(this.router.model.get('content_type') === "datasets" ? 'dataset' : 'map', changedContentType ? 0 : this.collection.total_user_entries),
            pluralizedContentTypeSelected: pluralizeString(this.router.model.get('content_type') === "datasets" ? 'dataset' : 'map', selectedItemsCount)
          },
          this.router.model.attributes
        )
      )
    );

    this._initViews();
    this._checkScroll();
    this._animate();

    return this;
  },

  _initBinds: function() {
    this.router.model.bind('change', this.render, this);
    this.collection.bind('loading', function() {
      this.$el.removeClass('is-relative');
    }, this);
    this.collection.bind('add remove change reset', this.render, this);
    this.user.bind('change:remaining_byte_quota', this.render, this);
    cdb.god.bind('closeDialogs', this._animate, this);
    cdb.god.bind('closeDialogs', this._unselectAll, this);
    _.bindAll(this, '_onWindowScroll');

    this.add_related_model(this.collection);
    this.add_related_model(this.router.model);
    this.add_related_model(this.user);
    this.add_related_model(cdb.god);
  },

  _checkScroll: function() {
    var content_type = this.router.model.get('content_type');
    var shared = this.router.model.get('shared');
    var locked = this.router.model.get('locked');
    var liked = this.router.model.get('liked');
    var search = this.router.model.get('q') || this.router.model.get('tag');
    var total_entries = this.collection.total_entries;

    // Bind scroll
    if (total_entries === 0 && content_type === "maps" && !shared && !locked && !liked && !search) {
      // If there is no maps, onboarding should appear
      // and filters block should be after that section
      this.$el.addClass('is-relative');
      this._unbindScroll();
    } else {
      this.$el.removeClass('is-relative');
      this._bindScroll();
    }
  },

  _bindScroll: function() {
    this._unbindScroll();
    $(window).bind('scroll', this._onWindowScroll);
  },

  _unbindScroll: function() {
    $(window).unbind('scroll', this._onWindowScroll);
  },

  _initViews: function() {
    // Tipsys?
    var self = this;
    _.each(this._TOOLTIPS, function(el) {
      self.addView(
        new cdb.common.TipsyTooltip({
          el: self.$('.' + el),
          title: function() {
            var isFixed = self.$el.hasClass('is-fixed');
            return isFixed ? '' : $(this).attr('data-title');
          }
        })
      )
    });
  },

  _selectedItems: function() {
    return this.collection.where({ selected: true });
  },

  _animate: function() {
    // Show filters or selected items actions
    var selectedItemsCount = this._selectedItems().length;
    this.$('.Filters-inner')[ selectedItemsCount > 0 ? 'addClass' : 'removeClass' ]('items--selected');

    // Check if any search is applied
    var search = this.router.model.get('q') || this.router.model.get('tag');
    this.$('.Filters-inner')[ search ? 'addClass' : 'removeClass' ]('search--enabled');
  },

  _selectAll: function(e) {
    this._select(e, true);
  },

  _unselectAll: function(e) {
    this._select(e, false);
  },

  _select: function(e, val) {
    this.killEvent(e);
    this.collection.each(function(vis) {
      vis.set('selected', val);
    });
  },

  _openDeleteItemsDialog: function(e) {
    this.killEvent(e);

    var viewModel = new DeleteItemsViewModel(this._selectedItems(), {
      contentType: this.router.model.get('content_type')
    });

    viewModel.bind('DeleteItemsDone', function() {
      this.user.fetch(); // needed in order to keep the "quota" synchronized
    }, this);

    var view = new DeleteItemsDialog({
      viewModel: viewModel,
      user: this.user,
      clean_on_hide: true,
      enter_to_confirm: true
    });
    this.addView(view);

    view.appendToBody();
  },

  _openChangeLockDialog: function(e) {
    this.killEvent(e);

    var viewModel = new ChangeLockViewModel(this._selectedItems());
    viewModel.bind('ProcessItemsDone', function() {
      this.collection.fetch();
    }, this);

    var view = new ChangeLockDialog({
      contentType: this.router.model.get('content_type'),
      viewModel: viewModel,
      clean_on_hide: true,
      enter_to_confirm: true
    });
    this.addView(view);

    view.appendToBody();
  },

  _openPrivacyDialog: function(e) {
    this.killEvent(e);
    cdb.god.trigger('openPrivacyDialog', this._selectedItems()[0]);
  },

  _onSearchClick: function(e) {
    if (e) this.killEvent(e);

    hidden = !this.$('.Filters-inner').hasClass('search--enabled');
    search = this.router.model.get('q') || this.router.model.get('tag');

    this.$('.Filters-inner').toggleClass('search--enabled', hidden)

    if (hidden) this.$('.js-search-input').focus();
    if (search) this._cleanSearch();
  },

  // Creation actions

  _newMap: function(e) {
    if (e) e.preventDefault();
    this._openCreateDialog('map');
  },

  _connectDataset: function(e) {
    if (e) e.preventDefault();

    if (this.user.canCreateDatasets()) {
      this._openCreateDialog('dataset');
    }
  },

  _openCreateDialog: function(type) {
    var createDialog = new CreateDialog({
      type: type || 'map',
      user: this.user,
      clean_on_hide: true
    });
    createDialog.bind('mapCreated', function(vis) {
      window.location = vis.viewUrl().edit();
    }, this);
    createDialog.bind('datasetCreated', function(tableMetadata) {
      window.location = tableMetadata.viewUrl();
    }, this);
    createDialog.bind('datasetSelected', function(d) {
      this.trigger('datasetSelected', d, this);
    }, this);
    createDialog.bind('remoteSelected', function(d) {
      this.trigger('remoteSelected', d, this);
    }, this);
    createDialog.appendToBody();
  },

  // Filter actions

  _onCleanSearchClick: function(e) {
    if (e) e.preventDefault();
    this._cleanSearch();
  },

  _submitSearch: function(e) {
    if (e) e.preventDefault();
    this._navigateToUrl({
      search: this.$('.js-search-input').val().trim(),
      library: this.router.model.get('library'),
      shared: this.router.model.get('shared')
    });
  },

  _cleanSearch: function() {
    this._navigateToUrl({
      search: '',
      library: this.router.model.get('library'),
      shared: this.router.model.get('shared')
    });
  },

  _navigateToUrl: function(opts) {
    this.router.navigate(this.router.currentUrl(opts), { trigger: true });
  },

  _changeOrder: function(e) {
    if (e) e.preventDefault();

    var $el = $(e.target).closest('.js-order-link');
    var order = 'updated_at';

    if ($el.hasClass('js-mapviews')) order = 'mapviews';
    if ($el.hasClass('js-likes')) order = 'likes';
    if ($el.hasClass('js-size')) order = 'size';

    // Order change?
    if (this.router.model.get('order') !== order) {
      this.localStorage.set({ 'dashboard.order': order });
      this.router.model.set('order', order);
    }
  },

  _onWindowScroll: function() {
    var offset = $(window).scrollTop();
    this.$el[ offset > 81 ? 'addClass' : 'removeClass' ]('is-fixed')
  },

  clean: function() {
    this._unbindScroll();
    cdb.core.View.prototype.clean.call(this);
  }

});
<|endoftext|>


<commit_before>dojo.provide("dijit.ProgressBar");

dojo.require("dojo.fx");
dojo.require("dojo.number");

dojo.require("dijit._Widget");
dojo.require("dijit._Templated");

dojo.declare("dijit.ProgressBar", [dijit._Widget, dijit._Templated], {
	// summary: A progress indication widget
	//
	// example:
	// |

	// |

	//
	// progress: String (Percentage or Number)
	// 	initial progress value.
	// 	with "%": percentage value, 0% <= progress <= 100%
	// 	or without "%": absolute value, 0 <= progress <= maximum
	progress: "0",

	// maximum: Float
	// 	max sample number
	maximum: 100,

	// places: Number
	// 	number of places to show in values; 0 by default
	places: 0,

	// indeterminate: Boolean
	// 	If false: show progress.
	// 	If true: show that a process is underway but that the progress is unknown
	indeterminate: false,

	templatePath: dojo.moduleUrl("dijit", "templates/ProgressBar.html"),

	_indeterminateHighContrastImagePath:
		dojo.moduleUrl("dijit", "themes/a11y/indeterminate_progress.gif"),

	// public functions
	postCreate: function(){
		this.inherited("postCreate",arguments);
		this.inteterminateHighContrastImage.setAttribute("src",
			this._indeterminateHighContrastImagePath);
		this.update();
	},

	update: function(/*Object?*/attributes){
		// summary: update progress information
		//
		// attributes: may provide progress and/or maximum properties on this parameter,
		//	see attribute specs for details.
		dojo.mixin(this, attributes||{});
		var percent = 1, classFunc;
		if(this.indeterminate){
			classFunc = "addClass";
			dijit.removeWaiState(this.internalProgress, "valuenow");
			dijit.removeWaiState(this.internalProgress, "valuemin");
			dijit.removeWaiState(this.internalProgress, "valuemax");
		}else{
			classFunc = "removeClass";
			if(String(this.progress).indexOf("%") != -1){
				percent = Math.min(parseFloat(this.progress)/100, 1);
				this.progress = percent * this.maximum;
			}else{
				this.progress = Math.min(this.progress, this.maximum);
				percent = this.progress / this.maximum;
			}
			var text = this.report(percent);
			this.label.firstChild.nodeValue = text;
			dijit.setWaiState(this.internalProgress, "describedby", this.label.id);
			dijit.setWaiState(this.internalProgress, "valuenow", this.progress);
			dijit.setWaiState(this.internalProgress, "valuemin", 0);
			dijit.setWaiState(this.internalProgress, "valuemax", this.maximum);
		}
		dojo[classFunc](this.domNode, "dijitProgressBarIndeterminate");
		this.internalProgress.style.width = (percent * 100) + "%";
		this.onChange();
	},

	report: function(/*float*/percent){
		// Generates message to show; may be overridden by user
		return dojo.number.format(percent, {type: "percent", places: this.places, locale: this.lang});
	},

	onChange: function(){
		// summary: User definable function fired when progress updates.
	}
});
<commit_msg>Missing summary: keyword; refs #4611<commit_after>dojo.provide("dijit.ProgressBar");

dojo.require("dojo.fx");
dojo.require("dojo.number");

dojo.require("dijit._Widget");
dojo.require("dijit._Templated");

dojo.declare("dijit.ProgressBar", [dijit._Widget, dijit._Templated], {
	// summary: A progress indication widget
	//
	// example:
	// |

	// |

	//
	// progress: String (Percentage or Number)
	// 	initial progress value.
	// 	with "%": percentage value, 0% <= progress <= 100%
	// 	or without "%": absolute value, 0 <= progress <= maximum
	progress: "0",

	// maximum: Float
	// 	max sample number
	maximum: 100,

	// places: Number
	// 	number of places to show in values; 0 by default
	places: 0,

	// indeterminate: Boolean
	// 	If false: show progress.
	// 	If true: show that a process is underway but that the progress is unknown
	indeterminate: false,

	templatePath: dojo.moduleUrl("dijit", "templates/ProgressBar.html"),

	_indeterminateHighContrastImagePath:
		dojo.moduleUrl("dijit", "themes/a11y/indeterminate_progress.gif"),

	// public functions
	postCreate: function(){
		this.inherited("postCreate",arguments);
		this.inteterminateHighContrastImage.setAttribute("src",
			this._indeterminateHighContrastImagePath);
		this.update();
	},

	update: function(/*Object?*/attributes){
		// summary: update progress information
		//
		// attributes: may provide progress and/or maximum properties on this parameter,
		//	see attribute specs for details.
		dojo.mixin(this, attributes||{});
		var percent = 1, classFunc;
		if(this.indeterminate){
			classFunc = "addClass";
			dijit.removeWaiState(this.internalProgress, "valuenow");
			dijit.removeWaiState(this.internalProgress, "valuemin");
			dijit.removeWaiState(this.internalProgress, "valuemax");
		}else{
			classFunc = "removeClass";
			if(String(this.progress).indexOf("%") != -1){
				percent = Math.min(parseFloat(this.progress)/100, 1);
				this.progress = percent * this.maximum;
			}else{
				this.progress = Math.min(this.progress, this.maximum);
				percent = this.progress / this.maximum;
			}
			var text = this.report(percent);
			this.label.firstChild.nodeValue = text;
			dijit.setWaiState(this.internalProgress, "describedby", this.label.id);
			dijit.setWaiState(this.internalProgress, "valuenow", this.progress);
			dijit.setWaiState(this.internalProgress, "valuemin", 0);
			dijit.setWaiState(this.internalProgress, "valuemax", this.maximum);
		}
		dojo[classFunc](this.domNode, "dijitProgressBarIndeterminate");
		this.internalProgress.style.width = (percent * 100) + "%";
		this.onChange();
	},

	report: function(/*float*/percent){
		// summary: Generates message to show; may be overridden by user
		return dojo.number.format(percent, {type: "percent", places: this.places, locale: this.lang});
	},

	onChange: function(){
		// summary: User definable function fired when progress updates.
	}
});
<|endoftext|>


<commit_before>/*jslint indent: 2, nomen: true, maxlen: 100, sloppy: true, vars: true, white: true, regexp: true plusplus: true */
/*global require, exports */

////////////////////////////////////////////////////////////////////////////////
/// @brief ArangoShell client API for cluster operation
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2013 triagens GmbH, Cologne, Germany
///
/// 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.
///
/// Copyright holder is triAGENS GmbH, Cologne, Germany
///
/// @author Max Neunhoeffer
/// @author Copyright 2014, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////

var internal = require("internal");
var arangosh = require("org/arangodb/arangosh");
var db = require("org/arangodb").db;

function Planner (userConfig) {
  "use strict";
  if (typeof userConfig !== "object") {
    throw "userConfig must be an object";
  }
  var r = db._connection.POST("/_admin/clusterPlanner",
                              JSON.stringify(userConfig));
  r = arangosh.checkRequestResult(r);
  this.clusterPlan = r.clusterPlan;
  this.config = r.config;
}

Planner.prototype.getPlan = function () {
  return this.clusterPlan;
};

function Kickstarter (clusterPlan, myname) {
  "use strict";
  if (typeof clusterPlan !== "object") {
    throw "clusterPlan must be an object";
  }
  this.clusterPlan = clusterPlan;
  if (myname === undefined) {
    this.myname = "me";
  }
  else {
    this.myname = myname;
  }
}

Kickstarter.prototype.launch = function () {
  "use strict";
  var r = db._connection.POST("/_admin/clusterDispatch",
                              JSON.stringify({"action": "launch",
                                              "clusterPlan": this.clusterPlan,
                                              "myname": this.myname}));
  this.runInfo = r.runInfo;
  return r;
};

Kickstarter.prototype.shutdown = function () {
  "use strict";
  var r = db._connection.POST("/_admin/clusterDispatch",
                              JSON.stringify({"action": "shutdown",
                                              "clusterPlan": this.clusterPlan,
                                              "myname": this.myname,
                                              "runInfo": this.runInfo}));
  return r;
};

Kickstarter.prototype.relaunch = function () {
  "use strict";
  var r = db._connection.POST("/_admin/clusterDispatch",
                              JSON.stringify({"action": "relaunch",
                                              "clusterPlan": this.clusterPlan,
                                              "myname": this.myname}));
  this.runInfo = r.runInfo;
  return r;
};

Kickstarter.prototype.cleanup = function () {
  "use strict";
  var r = db._connection.POST("/_admin/clusterDispatch",
                              JSON.stringify({"action": "cleanup",
                                              "clusterPlan": this.clusterPlan,
                                              "myname": this.myname}));
  return r;
};

Kickstarter.prototype.isHealthy = function () {
  "use strict";
  var r = db._connection.POST("/_admin/clusterDispatch",
                              JSON.stringify({"action": "isHealthy",
                                              "clusterPlan": this.clusterPlan,
                                              "myname": this.myname,
                                              "runInfo": this.runInfo}));
  return r;
};

exports.Planner = Planner;
exports.Kickstarter = Kickstarter;

// -----------------------------------------------------------------------------
// --SECTION--                                                       END-OF-FILE
// -----------------------------------------------------------------------------

// Local Variables:
// mode: outline-minor
// outline-regexp: "\\(/// @brief\\|/// @addtogroup\\|// --SECTION--\\|/// @page\\|/// @\\}\\)"
// End:
<commit_msg>jshund<commit_after>/*jslint indent: 2, nomen: true, maxlen: 100, sloppy: true, vars: true, white: true, regexp: true plusplus: true */
/*global require, exports */

////////////////////////////////////////////////////////////////////////////////
/// @brief ArangoShell client API for cluster operation
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2013 triagens GmbH, Cologne, Germany
///
/// 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.
///
/// Copyright holder is triAGENS GmbH, Cologne, Germany
///
/// @author Max Neunhoeffer
/// @author Copyright 2014, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////

var arangosh = require("org/arangodb/arangosh");
var db = require("org/arangodb").db;

function Planner (userConfig) {
  "use strict";
  if (typeof userConfig !== "object") {
    throw "userConfig must be an object";
  }
  var r = db._connection.POST("/_admin/clusterPlanner",
                              JSON.stringify(userConfig));
  r = arangosh.checkRequestResult(r);
  this.clusterPlan = r.clusterPlan;
  this.config = r.config;
}

Planner.prototype.getPlan = function () {
  return this.clusterPlan;
};

function Kickstarter (clusterPlan, myname) {
  "use strict";
  if (typeof clusterPlan !== "object") {
    throw "clusterPlan must be an object";
  }
  this.clusterPlan = clusterPlan;
  if (myname === undefined) {
    this.myname = "me";
  }
  else {
    this.myname = myname;
  }
}

Kickstarter.prototype.launch = function () {
  "use strict";
  var r = db._connection.POST("/_admin/clusterDispatch",
                              JSON.stringify({"action": "launch",
                                              "clusterPlan": this.clusterPlan,
                                              "myname": this.myname}));
  this.runInfo = r.runInfo;
  return r;
};

Kickstarter.prototype.shutdown = function () {
  "use strict";
  var r = db._connection.POST("/_admin/clusterDispatch",
                              JSON.stringify({"action": "shutdown",
                                              "clusterPlan": this.clusterPlan,
                                              "myname": this.myname,
                                              "runInfo": this.runInfo}));
  return r;
};

Kickstarter.prototype.relaunch = function () {
  "use strict";
  var r = db._connection.POST("/_admin/clusterDispatch",
                              JSON.stringify({"action": "relaunch",
                                              "clusterPlan": this.clusterPlan,
                                              "myname": this.myname}));
  this.runInfo = r.runInfo;
  return r;
};

Kickstarter.prototype.cleanup = function () {
  "use strict";
  var r = db._connection.POST("/_admin/clusterDispatch",
                              JSON.stringify({"action": "cleanup",
                                              "clusterPlan": this.clusterPlan,
                                              "myname": this.myname}));
  return r;
};

Kickstarter.prototype.isHealthy = function () {
  "use strict";
  var r = db._connection.POST("/_admin/clusterDispatch",
                              JSON.stringify({"action": "isHealthy",
                                              "clusterPlan": this.clusterPlan,
                                              "myname": this.myname,
                                              "runInfo": this.runInfo}));
  return r;
};

exports.Planner = Planner;
exports.Kickstarter = Kickstarter;

// -----------------------------------------------------------------------------
// --SECTION--                                                       END-OF-FILE
// -----------------------------------------------------------------------------

// Local Variables:
// mode: outline-minor
// outline-regexp: "\\(/// @brief\\|/// @addtogroup\\|// --SECTION--\\|/// @page\\|/// @\\}\\)"
// End:
<|endoftext|>


<commit_before>import external from './../../externalModules.js';
import BaseAnnotationTool from '../base/BaseAnnotationTool.js';

// State
import { getToolState } from './../../stateManagement/toolState.js';
import toolStyle from './../../stateManagement/toolStyle.js';
import toolColors from './../../stateManagement/toolColors.js';

// Drawing
import {
  getNewContext,
  draw,
  setShadow,
  drawCircle,
  drawHandles,
  drawLinkedTextBox,
} from './../../drawing/index.js';

// Util
import calculateSUV from './../../util/calculateSUV.js';
import { calculateEllipseStatistics } from './../../util/ellipse/index.js';
import numbersWithCommas from './../../util/numbersWithCommas.js';
import throttle from './../../util/throttle.js';
import { getLogger } from '../../util/logger.js';

const logger = getLogger('tools:annotation:EllipticalRoiTool');

/**
 * @public
 * @class CircleRoiTool
 * @memberof Tools.Annotation
 * @classdesc Tool for drawing circular regions of interest, and measuring
 * the statistics of the enclosed pixels.
 * @extends Tools.Base.BaseAnnotationTool
 */
export default class CircleRoiTool extends BaseAnnotationTool {
  constructor(configuration = {}) {
    const defaultConfig = {
      name: 'CircleRoi',
      supportedInteractionTypes: ['Mouse', 'Touch'],
    };

    const initialConfiguration = Object.assign(defaultConfig, configuration);

    super(initialConfiguration);

    this.initialConfiguration = initialConfiguration;
  }

  createNewMeasurement(eventData) {
    const goodEventData =
      eventData && eventData.currentPoints && eventData.currentPoints.image;

    if (!goodEventData) {
      logger.error(
        `required eventData not supplied to tool ${
          this.name
        }'s createNewMeasurement`
      );

      return;
    }

    return {
      visible: true,
      active: true,
      color: undefined,
      invalidated: true,
      handles: {
        start: {
          x: eventData.currentPoints.image.x,
          y: eventData.currentPoints.image.y,
          highlight: true,
          active: false,
        },
        end: {
          x: eventData.currentPoints.image.x,
          y: eventData.currentPoints.image.y,
          highlight: true,
          active: true,
        },
        initialRotation: eventData.viewport.rotation,
        textBox: {
          active: false,
          hasMoved: false,
          movesIndependently: false,
          drawnIndependently: true,
          allowedOutsideImage: true,
          hasBoundingBox: true,
        },
      },
    };
  }

  pointNearTool(element, data, coords, interactionType) {
    const hasStartAndEndHandles =
      data && data.handles && data.handles.start && data.handles.end;

    if (!hasStartAndEndHandles) {
      logger.warn(
        `invalid parameters supplied to tool ${this.name}'s pointNearTool`
      );
    }

    if (!hasStartAndEndHandles || data.visible === false) {
      return false;
    }

    const distance = interactionType === 'mouse' ? 15 : 25;

    const startCanvas = external.cornerstone.pixelToCanvas(
      element,
      data.handles.start
    );

    const endCanvas = external.cornerstone.pixelToCanvas(
      element,
      data.handles.end
    );

    // StartCanvas is the center of the circle
    const distanceFromCenter = _getDistance(startCanvas, coords);

    // Getting radius of circle annotation in canvas
    const radius = _getDistance(startCanvas, endCanvas);

    // Checking if point is near the tool by comparing its distance from the center of the circle
    return !(
      distanceFromCenter > radius + distance / 2 || distanceFromCenter < radius
    );
  }

  renderToolData(evt) {
    const toolData = getToolState(evt.currentTarget, this.name);

    if (!toolData) {
      return;
    }

    const eventData = evt.detail;
    const { image, element, canvasContext } = eventData;
    const lineWidth = toolStyle.getToolWidth();
    const { handleRadius, drawHandlesOnHover } = this.configuration;
    const newContext = getNewContext(canvasContext.canvas);

    // Meta
    const seriesModule =
      external.cornerstone.metaData.get('generalSeriesModule', image.imageId) ||
      {};
    const imagePlane =
      external.cornerstone.metaData.get('imagePlaneModule', image.imageId) ||
      {};

    // Pixel Spacing
    const modality = seriesModule.modality;
    const hasPixelSpacing =
      imagePlane && imagePlane.rowPixelSpacing && imagePlane.columnPixelSpacing;

    const pixelSpacing = {
      rowPixelSpacing: imagePlane.rowPixelSpacing || 1,
      columnPixelSpacing: imagePlane.columnPixelSpacing || 1,
    };

    draw(newContext, context => {
      // If we have tool data for this element - iterate over each set and draw it
      for (let i = 0; i < toolData.data.length; i++) {
        const data = toolData.data[i];

        if (data.visible === false) {
          continue;
        }

        // Configure
        const color = toolColors.getColorIfActive(data);
        const handleOptions = {
          color,
          handleRadius,
          drawHandlesIfActive: drawHandlesOnHover,
        };

        setShadow(context, this.configuration);

        const startCanvas = external.cornerstone.pixelToCanvas(
          element,
          data.handles.start
        );

        const endCanvas = external.cornerstone.pixelToCanvas(
          element,
          data.handles.end
        );

        // Calculating the radius where startCanvas is the center of the circle to be drawn
        const radius = _getDistance(startCanvas, endCanvas);

        // Draw Circle
        drawCircle(
          context,
          element,
          data.handles.start,
          radius,
          {
            color,
          },
          'pixel'
        );

        drawHandles(context, eventData, data.handles, handleOptions);

        // Update textbox stats
        if (data.invalidated === true) {
          if (data.cachedStats) {
            _throttledUpdateCachedStats(
              image,
              element,
              data,
              modality,
              pixelSpacing
            );
          } else {
            _updateCachedStats(image, element, data, modality, pixelSpacing);
          }
        }

        // Default to textbox on right side of ROI
        if (!data.handles.textBox.hasMoved) {
          data.handles.textBox.x = Math.max(
            data.handles.start.x,
            data.handles.end.x
          );
          data.handles.textBox.y =
            (data.handles.start.y + data.handles.end.y) / 2;
        }

        const textBoxAnchorPoints = handles =>
          _findTextBoxAnchorPoints(handles.start, handles.end);

        const textBoxContent = _createTextBoxContent(
          context,
          image.color,
          data.cachedStats,
          modality,
          hasPixelSpacing,
          this.configuration
        );

        drawLinkedTextBox(
          context,
          element,
          data.handles.textBox,
          textBoxContent,
          data.handles,
          textBoxAnchorPoints,
          color,
          lineWidth,
          0,
          true
        );
      }
    });
  }
}

/**
 *
 */
const _throttledUpdateCachedStats = throttle(_updateCachedStats, 110);

/**
 *
 *
 * @param {*} image
 * @param {*} element
 * @param {*} data
 * @param {string} modality
 * @param {*} pixelSpacing
 * @returns {void}
 */
function _updateCachedStats(image, element, data, modality, pixelSpacing) {
  const stats = _calculateStats(
    image,
    element,
    data.handles,
    modality,
    pixelSpacing
  );

  data.cachedStats = stats;
  data.invalidated = false;
}

/**
 *
 *
 * @param {*} startHandle
 * @param {*} endHandle
 * @returns {Array.<{x: number, y: number}>}
 */
function _findTextBoxAnchorPoints(startHandle, endHandle) {
  const { left, top, width, height } = _getCirlceImageCoodinates(
    startHandle,
    endHandle
  );

  return [
    {
      // Top middle point of ellipse
      x: left + width / 2,
      y: top,
    },
    {
      // Left middle point of ellipse
      x: left,
      y: top + height / 2,
    },
    {
      // Bottom middle point of ellipse
      x: left + width / 2,
      y: top + height,
    },
    {
      // Right middle point of ellipse
      x: left + width,
      y: top + height / 2,
    },
  ];
}

/**
 *
 *
 * @param {*} context
 * @param {*} isColorImage
 * @param {*} { area, mean, stdDev, min, max, meanStdDevSUV }
 * @param {*} modality
 * @param {*} hasPixelSpacing
 * @param {*} [options={}] - { showMinMax, showHounsfieldUnits }
 * @returns {string[]}
 */
function _createTextBoxContent(
  context,
  isColorImage,
  { area, mean, stdDev, min, max, meanStdDevSUV } = {},
  modality,
  hasPixelSpacing,
  options = {}
) {
  const showMinMax = options.showMinMax || false;
  const showHounsfieldUnits = options.showHounsfieldUnits !== false;
  const textLines = [];

  // Don't display mean/standardDev for color images
  const otherLines = [];

  if (!isColorImage) {
    const hasStandardUptakeValues = meanStdDevSUV && meanStdDevSUV.mean !== 0;
    const suffix = modality === 'CT' && showHounsfieldUnits ? ' HU' : '';

    let meanString = `Mean: ${numbersWithCommas(mean.toFixed(2))}${suffix}`;
    const stdDevString = `Std Dev: ${numbersWithCommas(
      stdDev.toFixed(2)
    )}${suffix}`;

    // If this image has SUV values to display, concatenate them to the text line
    if (hasStandardUptakeValues) {
      const SUVtext = ' SUV: ';

      const meanSuvString = `${SUVtext}${numbersWithCommas(
        meanStdDevSUV.mean.toFixed(2)
      )}`;
      const stdDevSuvString = `${SUVtext}${numbersWithCommas(
        meanStdDevSUV.stdDev.toFixed(2)
      )}`;

      const targetStringLength = Math.floor(
        context.measureText(`${stdDevString}     `).width
      );

      while (context.measureText(meanString).width < targetStringLength) {
        meanString += ' ';
      }

      otherLines.push(`${meanString}${meanSuvString}`);
      otherLines.push(`${stdDevString}     ${stdDevSuvString}`);
    } else {
      otherLines.push(`${meanString}     ${stdDevString}`);
    }

    if (showMinMax) {
      let minString = `Min: ${min}${suffix}`;
      const maxString = `Max: ${max}${suffix}`;
      const targetStringLength = hasStandardUptakeValues
        ? Math.floor(context.measureText(`${stdDevString}     `).width)
        : Math.floor(context.measureText(`${meanString}     `).width);

      while (context.measureText(minString).width < targetStringLength) {
        minString += ' ';
      }

      otherLines.push(`${minString}${maxString}`);
    }
  }

  textLines.push(_formatArea(area, hasPixelSpacing));
  otherLines.forEach(x => textLines.push(x));

  return textLines;
}

/**
 *
 *
 * @param {*} area
 * @param {*} hasPixelSpacing
 * @returns {string} The formatted label for showing area
 */
function _formatArea(area, hasPixelSpacing) {
  // This uses Char code 178 for a superscript 2
  const suffix = hasPixelSpacing
    ? ` mm${String.fromCharCode(178)}`
    : ` px${String.fromCharCode(178)}`;

  return `Area: ${numbersWithCommas(area.toFixed(2))}${suffix}`;
}

/**
 *
 *
 * @param {*} image
 * @param {*} element
 * @param {*} handles
 * @param {*} modality
 * @param {*} pixelSpacing
 * @returns {Object} The Stats object
 */
function _calculateStats(image, element, handles, modality, pixelSpacing) {
  // Retrieve the bounds of the ellipse in image coordinates
  const circleCoordinates = _getCirlceImageCoodinates(
    handles.start,
    handles.end
  );

  // Retrieve the array of pixels that the ellipse bounds cover
  const pixels = external.cornerstone.getPixels(
    element,
    circleCoordinates.left,
    circleCoordinates.top,
    circleCoordinates.width,
    circleCoordinates.height
  );

  // Calculate the mean & standard deviation from the pixels and the ellipse details.
  const ellipseMeanStdDev = calculateEllipseStatistics(
    pixels,
    circleCoordinates
  );

  let meanStdDevSUV;

  if (modality === 'PT') {
    meanStdDevSUV = {
      mean: calculateSUV(image, ellipseMeanStdDev.mean, true) || 0,
      stdDev: calculateSUV(image, ellipseMeanStdDev.stdDev, true) || 0,
    };
  }

  const area =
    Math.PI *
    ((circleCoordinates.width * (pixelSpacing.columnPixelSpacing || 1)) / 2) *
    ((circleCoordinates.height * (pixelSpacing.rowPixelSpacing || 1)) / 2);

  return {
    area: area || 0,
    count: ellipseMeanStdDev.count || 0,
    mean: ellipseMeanStdDev.mean || 0,
    variance: ellipseMeanStdDev.variance || 0,
    stdDev: ellipseMeanStdDev.stdDev || 0,
    min: ellipseMeanStdDev.min || 0,
    max: ellipseMeanStdDev.max || 0,
    meanStdDevSUV,
  };
}

/**
 * Retrieve the bounds of the ellipse in image coordinates
 *
 * @param {*} startHandle
 * @param {*} endHandle
 * @returns {{ left: number, top: number, width: number, height: number }}
 */
function _getCirlceImageCoodinates(startHandle, endHandle) {
  const radius = _getDistance(startHandle, endHandle);

  return {
    left: Math.round(Math.min(startHandle.x - radius, endHandle.x)),
    top: Math.round(Math.min(startHandle.y - radius, endHandle.y)),
    width: radius * 2,
    height: radius * 2,
  };
}

/**
 * Returns the distance in canvas from the given coords to the center of the circle annotation.
 *
 * @param {*} startCoords - start point cooridnates
 * @param {*} endCoords - end point cooridnates
 * @returns {number} number - the distance between two points (start and end)
 */
function _getDistance(startCoords, endCoords) {
  const dx = startCoords.x - endCoords.x;
  const dy = startCoords.y - endCoords.y;

  return Math.sqrt(dx * dx + dy * dy);
}
<commit_msg>fix(mergeConfig): Applying merge config fix into CircleRoiTool<commit_after>import external from './../../externalModules.js';
import BaseAnnotationTool from '../base/BaseAnnotationTool.js';

// State
import { getToolState } from './../../stateManagement/toolState.js';
import toolStyle from './../../stateManagement/toolStyle.js';
import toolColors from './../../stateManagement/toolColors.js';

// Drawing
import {
  getNewContext,
  draw,
  setShadow,
  drawCircle,
  drawHandles,
  drawLinkedTextBox,
} from './../../drawing/index.js';

// Util
import calculateSUV from './../../util/calculateSUV.js';
import { calculateEllipseStatistics } from './../../util/ellipse/index.js';
import numbersWithCommas from './../../util/numbersWithCommas.js';
import throttle from './../../util/throttle.js';
import { getLogger } from '../../util/logger.js';

const logger = getLogger('tools:annotation:EllipticalRoiTool');

/**
 * @public
 * @class CircleRoiTool
 * @memberof Tools.Annotation
 * @classdesc Tool for drawing circular regions of interest, and measuring
 * the statistics of the enclosed pixels.
 * @extends Tools.Base.BaseAnnotationTool
 */
export default class CircleRoiTool extends BaseAnnotationTool {
  constructor(props = {}) {
    const defaultProps = {
      name: 'CircleRoi',
      supportedInteractionTypes: ['Mouse', 'Touch'],
    };

    super(props, defaultProps);
  }

  createNewMeasurement(eventData) {
    const goodEventData =
      eventData && eventData.currentPoints && eventData.currentPoints.image;

    if (!goodEventData) {
      logger.error(
        `required eventData not supplied to tool ${
          this.name
        }'s createNewMeasurement`
      );

      return;
    }

    return {
      visible: true,
      active: true,
      color: undefined,
      invalidated: true,
      handles: {
        start: {
          x: eventData.currentPoints.image.x,
          y: eventData.currentPoints.image.y,
          highlight: true,
          active: false,
        },
        end: {
          x: eventData.currentPoints.image.x,
          y: eventData.currentPoints.image.y,
          highlight: true,
          active: true,
        },
        initialRotation: eventData.viewport.rotation,
        textBox: {
          active: false,
          hasMoved: false,
          movesIndependently: false,
          drawnIndependently: true,
          allowedOutsideImage: true,
          hasBoundingBox: true,
        },
      },
    };
  }

  pointNearTool(element, data, coords, interactionType) {
    const hasStartAndEndHandles =
      data && data.handles && data.handles.start && data.handles.end;

    if (!hasStartAndEndHandles) {
      logger.warn(
        `invalid parameters supplied to tool ${this.name}'s pointNearTool`
      );
    }

    if (!hasStartAndEndHandles || data.visible === false) {
      return false;
    }

    const distance = interactionType === 'mouse' ? 15 : 25;

    const startCanvas = external.cornerstone.pixelToCanvas(
      element,
      data.handles.start
    );

    const endCanvas = external.cornerstone.pixelToCanvas(
      element,
      data.handles.end
    );

    // StartCanvas is the center of the circle
    const distanceFromCenter = _getDistance(startCanvas, coords);

    // Getting radius of circle annotation in canvas
    const radius = _getDistance(startCanvas, endCanvas);

    // Checking if point is near the tool by comparing its distance from the center of the circle
    return !(
      distanceFromCenter > radius + distance / 2 || distanceFromCenter < radius
    );
  }

  renderToolData(evt) {
    const toolData = getToolState(evt.currentTarget, this.name);

    if (!toolData) {
      return;
    }

    const eventData = evt.detail;
    const { image, element, canvasContext } = eventData;
    const lineWidth = toolStyle.getToolWidth();
    const { handleRadius, drawHandlesOnHover } = this.configuration;
    const newContext = getNewContext(canvasContext.canvas);

    // Meta
    const seriesModule =
      external.cornerstone.metaData.get('generalSeriesModule', image.imageId) ||
      {};
    const imagePlane =
      external.cornerstone.metaData.get('imagePlaneModule', image.imageId) ||
      {};

    // Pixel Spacing
    const modality = seriesModule.modality;
    const hasPixelSpacing =
      imagePlane && imagePlane.rowPixelSpacing && imagePlane.columnPixelSpacing;

    const pixelSpacing = {
      rowPixelSpacing: imagePlane.rowPixelSpacing || 1,
      columnPixelSpacing: imagePlane.columnPixelSpacing || 1,
    };

    draw(newContext, context => {
      // If we have tool data for this element - iterate over each set and draw it
      for (let i = 0; i < toolData.data.length; i++) {
        const data = toolData.data[i];

        if (data.visible === false) {
          continue;
        }

        // Configure
        const color = toolColors.getColorIfActive(data);
        const handleOptions = {
          color,
          handleRadius,
          drawHandlesIfActive: drawHandlesOnHover,
        };

        setShadow(context, this.configuration);

        const startCanvas = external.cornerstone.pixelToCanvas(
          element,
          data.handles.start
        );

        const endCanvas = external.cornerstone.pixelToCanvas(
          element,
          data.handles.end
        );

        // Calculating the radius where startCanvas is the center of the circle to be drawn
        const radius = _getDistance(startCanvas, endCanvas);

        // Draw Circle
        drawCircle(
          context,
          element,
          data.handles.start,
          radius,
          {
            color,
          },
          'pixel'
        );

        drawHandles(context, eventData, data.handles, handleOptions);

        // Update textbox stats
        if (data.invalidated === true) {
          if (data.cachedStats) {
            _throttledUpdateCachedStats(
              image,
              element,
              data,
              modality,
              pixelSpacing
            );
          } else {
            _updateCachedStats(image, element, data, modality, pixelSpacing);
          }
        }

        // Default to textbox on right side of ROI
        if (!data.handles.textBox.hasMoved) {
          data.handles.textBox.x = Math.max(
            data.handles.start.x,
            data.handles.end.x
          );
          data.handles.textBox.y =
            (data.handles.start.y + data.handles.end.y) / 2;
        }

        const textBoxAnchorPoints = handles =>
          _findTextBoxAnchorPoints(handles.start, handles.end);

        const textBoxContent = _createTextBoxContent(
          context,
          image.color,
          data.cachedStats,
          modality,
          hasPixelSpacing,
          this.configuration
        );

        drawLinkedTextBox(
          context,
          element,
          data.handles.textBox,
          textBoxContent,
          data.handles,
          textBoxAnchorPoints,
          color,
          lineWidth,
          0,
          true
        );
      }
    });
  }
}

/**
 *
 */
const _throttledUpdateCachedStats = throttle(_updateCachedStats, 110);

/**
 *
 *
 * @param {*} image
 * @param {*} element
 * @param {*} data
 * @param {string} modality
 * @param {*} pixelSpacing
 * @returns {void}
 */
function _updateCachedStats(image, element, data, modality, pixelSpacing) {
  const stats = _calculateStats(
    image,
    element,
    data.handles,
    modality,
    pixelSpacing
  );

  data.cachedStats = stats;
  data.invalidated = false;
}

/**
 *
 *
 * @param {*} startHandle
 * @param {*} endHandle
 * @returns {Array.<{x: number, y: number}>}
 */
function _findTextBoxAnchorPoints(startHandle, endHandle) {
  const { left, top, width, height } = _getCirlceImageCoodinates(
    startHandle,
    endHandle
  );

  return [
    {
      // Top middle point of ellipse
      x: left + width / 2,
      y: top,
    },
    {
      // Left middle point of ellipse
      x: left,
      y: top + height / 2,
    },
    {
      // Bottom middle point of ellipse
      x: left + width / 2,
      y: top + height,
    },
    {
      // Right middle point of ellipse
      x: left + width,
      y: top + height / 2,
    },
  ];
}

/**
 *
 *
 * @param {*} context
 * @param {*} isColorImage
 * @param {*} { area, mean, stdDev, min, max, meanStdDevSUV }
 * @param {*} modality
 * @param {*} hasPixelSpacing
 * @param {*} [options={}] - { showMinMax, showHounsfieldUnits }
 * @returns {string[]}
 */
function _createTextBoxContent(
  context,
  isColorImage,
  { area, mean, stdDev, min, max, meanStdDevSUV } = {},
  modality,
  hasPixelSpacing,
  options = {}
) {
  const showMinMax = options.showMinMax || false;
  const showHounsfieldUnits = options.showHounsfieldUnits !== false;
  const textLines = [];

  // Don't display mean/standardDev for color images
  const otherLines = [];

  if (!isColorImage) {
    const hasStandardUptakeValues = meanStdDevSUV && meanStdDevSUV.mean !== 0;
    const suffix = modality === 'CT' && showHounsfieldUnits ? ' HU' : '';

    let meanString = `Mean: ${numbersWithCommas(mean.toFixed(2))}${suffix}`;
    const stdDevString = `Std Dev: ${numbersWithCommas(
      stdDev.toFixed(2)
    )}${suffix}`;

    // If this image has SUV values to display, concatenate them to the text line
    if (hasStandardUptakeValues) {
      const SUVtext = ' SUV: ';

      const meanSuvString = `${SUVtext}${numbersWithCommas(
        meanStdDevSUV.mean.toFixed(2)
      )}`;
      const stdDevSuvString = `${SUVtext}${numbersWithCommas(
        meanStdDevSUV.stdDev.toFixed(2)
      )}`;

      const targetStringLength = Math.floor(
        context.measureText(`${stdDevString}     `).width
      );

      while (context.measureText(meanString).width < targetStringLength) {
        meanString += ' ';
      }

      otherLines.push(`${meanString}${meanSuvString}`);
      otherLines.push(`${stdDevString}     ${stdDevSuvString}`);
    } else {
      otherLines.push(`${meanString}     ${stdDevString}`);
    }

    if (showMinMax) {
      let minString = `Min: ${min}${suffix}`;
      const maxString = `Max: ${max}${suffix}`;
      const targetStringLength = hasStandardUptakeValues
        ? Math.floor(context.measureText(`${stdDevString}     `).width)
        : Math.floor(context.measureText(`${meanString}     `).width);

      while (context.measureText(minString).width < targetStringLength) {
        minString += ' ';
      }

      otherLines.push(`${minString}${maxString}`);
    }
  }

  textLines.push(_formatArea(area, hasPixelSpacing));
  otherLines.forEach(x => textLines.push(x));

  return textLines;
}

/**
 *
 *
 * @param {*} area
 * @param {*} hasPixelSpacing
 * @returns {string} The formatted label for showing area
 */
function _formatArea(area, hasPixelSpacing) {
  // This uses Char code 178 for a superscript 2
  const suffix = hasPixelSpacing
    ? ` mm${String.fromCharCode(178)}`
    : ` px${String.fromCharCode(178)}`;

  return `Area: ${numbersWithCommas(area.toFixed(2))}${suffix}`;
}

/**
 *
 *
 * @param {*} image
 * @param {*} element
 * @param {*} handles
 * @param {*} modality
 * @param {*} pixelSpacing
 * @returns {Object} The Stats object
 */
function _calculateStats(image, element, handles, modality, pixelSpacing) {
  // Retrieve the bounds of the ellipse in image coordinates
  const circleCoordinates = _getCirlceImageCoodinates(
    handles.start,
    handles.end
  );

  // Retrieve the array of pixels that the ellipse bounds cover
  const pixels = external.cornerstone.getPixels(
    element,
    circleCoordinates.left,
    circleCoordinates.top,
    circleCoordinates.width,
    circleCoordinates.height
  );

  // Calculate the mean & standard deviation from the pixels and the ellipse details.
  const ellipseMeanStdDev = calculateEllipseStatistics(
    pixels,
    circleCoordinates
  );

  let meanStdDevSUV;

  if (modality === 'PT') {
    meanStdDevSUV = {
      mean: calculateSUV(image, ellipseMeanStdDev.mean, true) || 0,
      stdDev: calculateSUV(image, ellipseMeanStdDev.stdDev, true) || 0,
    };
  }

  const area =
    Math.PI *
    ((circleCoordinates.width * (pixelSpacing.columnPixelSpacing || 1)) / 2) *
    ((circleCoordinates.height * (pixelSpacing.rowPixelSpacing || 1)) / 2);

  return {
    area: area || 0,
    count: ellipseMeanStdDev.count || 0,
    mean: ellipseMeanStdDev.mean || 0,
    variance: ellipseMeanStdDev.variance || 0,
    stdDev: ellipseMeanStdDev.stdDev || 0,
    min: ellipseMeanStdDev.min || 0,
    max: ellipseMeanStdDev.max || 0,
    meanStdDevSUV,
  };
}

/**
 * Retrieve the bounds of the ellipse in image coordinates
 *
 * @param {*} startHandle
 * @param {*} endHandle
 * @returns {{ left: number, top: number, width: number, height: number }}
 */
function _getCirlceImageCoodinates(startHandle, endHandle) {
  const radius = _getDistance(startHandle, endHandle);

  return {
    left: Math.round(Math.min(startHandle.x - radius, endHandle.x)),
    top: Math.round(Math.min(startHandle.y - radius, endHandle.y)),
    width: radius * 2,
    height: radius * 2,
  };
}

/**
 * Returns the distance in canvas from the given coords to the center of the circle annotation.
 *
 * @param {*} startCoords - start point cooridnates
 * @param {*} endCoords - end point cooridnates
 * @returns {number} number - the distance between two points (start and end)
 */
function _getDistance(startCoords, endCoords) {
  const dx = startCoords.x - endCoords.x;
  const dy = startCoords.y - endCoords.y;

  return Math.sqrt(dx * dx + dy * dy);
}
<|endoftext|>


<commit_before>// Given an array and a value, remove all the instances of that value in the array.
// return the number of elements in the array once after the operation.

// Example:
// If array A is [4, 1, 1, 2, 1, 3]
// and value elem is 1,
// then new length is 3, and A is now [4, 2, 3]

// param arr : array of integers
// return an integer

function removeDuplicates(arr){
  var count = {};
  var noDups = [];

  for (var i = 0;  i < arr.length; i++){
    var item = arr[i]
    if (!count[item]){
      noDups.push(item);
      count[item] = true;
    }
    else {
      count[item];
    }
  }
  return noDups;
}

function removeDuplicates(arr){
  var exists ={},
      outArr = [],
      elm;

  for(var i =0; i<arr.length; i++){
    elm = arr[i];
    if(!exists[elm]){
      outArr.push(elm);
      exists[elm] = true;
   }
  }
  return outArr;
}

function removeElement(arr){
  var map = {};
  for (var i = 0; i < arr.length; i += 1) {
    if(map[arr[i]] === undefined){
       map[arr[i]] = 1;
    } else {
        map[arr[i]] += 1;
    }
  }
  var result = [];
  for(var k in map){
      if (map[k] === 1) result.push(k);
  }
  return result.length, result;
}

console.log()
<commit_msg>remove duplicate solutions<commit_after>// Given an array and a value, remove all the instances of that value in the array.
// return the number of elements in the array once after the operation.

// Example:
// If array A is [4, 1, 1, 2, 1, 3]
// and value elem is 1,
// then new length is 3, and A is now [4, 2, 3]

// param arr : array of integers
// return an integer

function removeDuplicates(arr){
  var exists ={},
      outArr = [],
      elm;

  for(var i =0; i<arr.length; i++){
    elm = arr[i];
    if(!exists[elm]){
      outArr.push(elm);
      exists[elm] = true;
   }
  }
  return outArr;
}
<|endoftext|>