<commit_before>$(function(){
  // Construstor
  var Visuals = function(selector) {
    this.init(selector)
  }

  var NO_LABEL_COLOR = 'ccc'
    , NO_LABEL_TITLE = 'no label'
    , MARGIN_RIGHT = 30
    , MARGIN_LEFT = 0
    , STROKE_OPACITY = 0.4
    , STROKE_OPACITY_ACTIVE = 0.9
    ;

  /**
   * Init visuals. One time call
   * @param  {String} selector
   */
  Visuals.prototype.init = function(selector) {
    var that = this
      , $svg1 = $('#stackedArea svg')
      , $svg2 = $('#semiCircles svg')
      , $graphs = $('#graphs')

    // Init semicircles container
    d3.select('#semiCircles')
      // .style('margin-left', MARGIN_LEFT)
      // .style('margin-right', MARGIN_RIGHT + 'px')

    // On resize
    var onResize = function(ev){
      if (that.lastData != null) {
        that.showSemiCircles(that.lastData, that.lastSplitIssues, false)
      }

      var width = $graphs.width()
        , svg1Height = Math.ceil(Math.max(200, width / 3))
        , svg2Height = Math.ceil(Math.max(200, width / 2.5))

      $svg1.height(svg1Height)
      $svg2.height(svg2Height)
    }
    // Trigger on resize for the first time
    onResize()

    window.addEventListener('resize', onResize)
  }

  Visuals.prototype.emptyGraphs = function() {
    d3.selectAll('#stackedArea > svg > *').remove()
    d3.selectAll('#semiCircles > svg > *').remove()
  }

  /**
   * Transforms a date of type YYYY-MM-DD into timestamp
   * @param  {String} str
   * @return {Integer}     timestamp
   */
  function dateToTimestamp(str) {
    if (str == null) {
      // Today
      return Math.ceil(Date.now() / 86400000) * 86400000
    } else {
      var d = str.match(/\d+/g)
      return Date.UTC(d[0], d[1] - 1, d[2])
    }
  }

  Visuals.prototype.showData = function(data) {
    console.log(data)
    var splitIssues = splitIssuesByOpenTime(data)

    this.showStackedArea(data)
    this.showSemiCircles(data, splitIssues)

    this.lastData = data
    this.lastSplitIssues = splitIssues
  }

  Visuals.prototype.showStackedArea = function(data) {
    // Bootstrap stacked area object
    var processedData = bootstrapStackedAreaObject(data)

    // Add time-values to each issue
    fillTimeValues(processedData, data)

    // Object to Array
    var processedDataArray = []
    for (var p in processedData) {
      processedDataArray.push(processedData[p])
    }

    drawStackedArea(processedDataArray)
  }

  function bootstrapStackedAreaObject(data) {
    var datesMilisecondsRange = {}
      , start = dateToTimestamp(data.created_at)
      , today = dateToTimestamp(null) + 1

    // Fill dates range
    while (start < today) {
      datesMilisecondsRange[start] = 0
      start += 86400 * 1000
    }

    var processedData = {}
      , label
      , i

    // Add all labels
    for (i in data.labels) {
      label = data.labels[i]

      processedData[label.name] = {key: label.name, values: [], time: Object.create(datesMilisecondsRange), color: '#' + label.color}
    }

    // Add no label
    processedData[NO_LABEL_TITLE] = {key: NO_LABEL_TITLE, values: [], time: Object.create(datesMilisecondsRange), color: '#' + NO_LABEL_COLOR}

    return processedData
  }

  function fillTimeValues(processedData, data) {
    var i
      , issue
      , l
      , label
      , o
      , open
      , d
      , from
      , to

    // Go throuch all issues
    for (i in data.issues) {
      issue = data.issues[i]

      // If issue has no labels than assign 'no label' label
      if (issue.labels.length === 0) {
        issue.labels.push(NO_LABEL_TITLE)
      }

      // For each label
      for (l in issue.labels) {
        label = issue.labels[l]

        // For each open range
        for (o in issue.open) {
          open = issue.open[o]

          from = dateToTimestamp(open.from)
          to = dateToTimestamp(open.to)

          // Fill label time-value object
          while(from <= to) {
            processedData[label].time[from] += 1
            from += 86400 * 1000 // 3600 * 24 * 1000
          }
        }
      }
    }

    var p
      , _data
      , t

    for (p in processedData) {
      _data = processedData[p]

      // Add tuples of timeKey-value to values array
      for (t in _data.time) {
        _data.values.push([+t, _data.time[t]])
      }

      // Sort values by timeKey
      _data.values.sort(function(a, b){
        return a[0] - b[0]
      })

      // Get rid of time array
      delete _data.time
    }
  }

  function drawStackedArea(processedDataArray) {
    nv.addGraph(function() {
      var chart = nv.models.stackedAreaChart()
        .margin({
          right: MARGIN_RIGHT
        , left: 0
        , bottom: 10
        })
        .x(function(d) { return d[0] })
        .y(function(d) { return d[1] })
        .useInteractiveGuideline(true)    // Tooltips which show all data points
        .rightAlignYAxis(true)            // Let's move the y-axis to the right side.
        .transitionDuration(500)
        .showControls(false)              // Allow user to choose 'Stacked', 'Stream', 'Expanded' mode.
        .clipEdge(true)
        // .showLegend(false)
        .showXAxis(false)                 // Hide X Axis (dates)
        // .showYAxis(false)
        .showLegend(false)                // Hide legend

      //Format x-axis labels with custom function.
      chart.xAxis.tickFormat(function(d) {
        return d3.time.format('%x')(new Date(d))
      });

      chart.yAxis.tickFormat(d3.format(',.0f'));

      d3.select('#stackedArea svg')
        .datum(processedDataArray)
        .call(chart);

      nv.utils.windowResize(chart.update);

      return chart;
    });
  }

  function dateToDays(str) {
    return dateToTimestamp(str) / 86400000
  }

  function getLabelColor(data, labelName) {
    for (var l in data.labels) {
      if (data.labels[l].name === labelName) {
        return data.labels[l].color
      }
    }

    return NO_LABEL_COLOR
  }

  function getIssuesColors(data) {
    var issuesColors = []
      , issue

    for (var i in data.issues) {
      issue = data.issues[i]

      if (issue.labels.length === 0) {
        issuesColors[issue.number] = ['#' + NO_LABEL_COLOR]
      } else {
        issuesColors[issue.number] = issue.labels.map(function(label){
          return '#' + getLabelColor(data, label)
        })
      }
    }

    return issuesColors
  }

  function splitIssuesByOpenTime(data) {
    var issue
      , splitIssues = data.issues.slice()
      , splitIssuesLength = splitIssues.length
      , i, l
      , _open
      // Variable to keep links of SVG elements
      , _linked

    for (i = 0; i < splitIssuesLength; i++) {
      issue = splitIssues[i]
      _open = issue.open.slice()
      _linked = []

      if (issue.open.length > 1) {
        for (l = issue.open.length - 1; l > 0; l--) {
          splitIssues.push({
            labels: issue.labels
          , number: issue.number
          , open: [issue.open[l]]
          , _open: _open
          , state: issue.state
          , title: issue.title
          , url: issue.url
          , _linked: _linked
          })

          // Update length after removing
          issue.open.length -= 1
        }

        issue.open = [issue.open[0]]
        issue._open = _open
        issue._linked = _linked
      } else {
        issue._open = _open
        issue._linked = _linked
      }
    }

    return splitIssues
  }

  Visuals.prototype.showSemiCircles = function(data, splitIssues, createNew) {
    var container = d3.select('#semiCircles')
      , $container = $('#semiCircles')
      , svg = container.select('svg')
      , width = svg[0][0].offsetWidth
      , height = svg[0][0].offsetHeight
      , start = dateToDays(data.created_at)
      , today = dateToDays(null)
      , scale = d3.scale.linear().domain([0, today - start]).range([MARGIN_LEFT, width + MARGIN_LEFT])
      , issuesColors = getIssuesColors(data)

    // By default create new is true
    createNew = createNew === void 0 ? true : createNew

    if (createNew) {
      this.semiCircles = svg
        .selectAll("circle")
        .data(splitIssues)
        .enter()
        .append('circle')
        .attr("cy", 0)
        .attr('cy', 0)
        .attr('r', 0)
        .style("fill", 'none')
        .style('stroke', function(d) {
          // Add to linked list
          d._linked.push(this)

          if (issuesColors.length >= d.number) {
            return issuesColors[d.number][0]
          } else {
            return NO_LABEL_COLOR
          }
        })
        .style('stroke-opacity', 0.5)
        .style('stroke-width', 2)
        .on('mouseover', function(d){
          for (var i = 0; i < d._linked.length; i++) {
            d3.select(d._linked[i]).style({'stroke-opacity': STROKE_OPACITY_ACTIVE})
          }
          displayTooltip(d, d3.mouse(this), data)
        })
        .on('mouseout', function(d){
          for (var i = 0; i < d._linked.length; i++) {
            d3.select(d._linked[i]).style({'stroke-opacity': STROKE_OPACITY})
          }
          hideTooltip(d, d3.mouse(this))
        })
        .on('mousemove', function(d){
          moveTooltip(d, d3.mouse(this), svg[0][0])
        })
    }

    // Only if we have semicircles
    if (this.semiCircles != null) {
      // Attributes that vary on window resize
      this.semiCircles
        .data(splitIssues)
        .transition()
          .duration(500)
        .attr("cx", function (d) {
          var from = dateToDays(d.open[0].from)
            , closed_at = d.open[d.open.length - 1].to
            , to = dateToDays(closed_at)

          if (closed_at !== null) {
            return scale((to + from)/2 - start)
          } else {
            // Make it quater of the circle with today as center
            return scale(to - start)
          }
        })
        .attr("r", function (d) {
          var from = dateToDays(d.open[0].from)
            , closed_at = d.open[d.open.length - 1].to
            , to = dateToDays(closed_at)

          if (closed_at !== null) {
            return scale(to - from + 1) / 2
          } else {
            // Make it quater of the circle if it is still open
            return scale(to - from + 0.5)
          }
        })
    }
  }

  var $tooltip = $('#nvtooltip-semicircles')
    , $tooltipTitle = $tooltip.find('.title')
    , $tooltipBody = $tooltip.find('tbody')
    , $tooltipStub = $tooltip.find('.stub')

  /**
   * Formats a timestamp to MM/DD/YYYY
   * @param  {Integer} timestamp
   * @return {String}
   */
  function formatTimestamp(timestamp) {
    var date = new Date(timestamp)
    return ('0' + (date.getMonth() + 1)).slice(-2) + '/' + ('0' + date.getDate()).slice(-2) + '/' +  date.getFullYear()
  }

  function displayTooltip(d, mouse, data) {
    var i

    $tooltipTitle.text('Issue #' + d.number + ': ' + d.title)

    // Remove previous body
    $tooltipBody.children().not('.stub').remove()

    for (i = 0; i < d.labels.length; i++) {
      $tooltipStub
        .clone()
        .removeClass('stub')
          .find('.legend-color-guide > div')
          .css('background-color', '#' + getLabelColor(data, d.labels[i]))
        .end()
          .find('.key')
          .text(d.labels[i])
        .end()
        .appendTo($tooltipBody)
        .show()
    }

    for (i = 0; i < d._open.length; i++) {
      $tooltipStub
        .clone()
        .removeClass('stub')
          .find('.legend-color-guide > div')
          .addClass('glyphicon glyphicon-eye-open')
          .css('display', 'inline')
        .end()
          .find('.key')
          .text(formatTimestamp(dateToTimestamp(d._open[i].from)))
        .end()
        .appendTo($tooltipBody)
        .show()

      if (d._open[i].to !== null) {
        $tooltipStub
          .clone()
          .removeClass('stub')
            .find('.legend-color-guide > div')
            .addClass('glyphicon glyphicon-eye-close')
            .css('display', 'inline')
          .end()
            .find('.key')
            .text(formatTimestamp(dateToTimestamp(d._open[i].to)))
          .end()
          .appendTo($tooltipBody)
          .show()
      }
    }

    $tooltip.show()
  }

  function moveTooltip(d, mouse, svg) {
    var x = mouse[0]
      , y = mouse[1]
      , xPadding = 50
      , width = svg.offsetWidth
      , height = svg.offsetHeight
      , tooltipWidth = $tooltip.width()
      , tooltipHeight = $tooltip.height()

    if (x + xPadding + tooltipWidth > width) {
      // position tooltip to the left of mouse
      $tooltip.css('left', x - xPadding - tooltipWidth)
    } else {
      // position tooltip to the right of the mouse
      $tooltip.css('left', x + xPadding)
    }

    $tooltip.css('top', y - Math.ceil(tooltipHeight / 2))
  }

  function hideTooltip(d, mouse) {
    $tooltip.hide()
  }

  window.Visuals = Visuals
})
<commit_msg>:factory: Remove graphs animation. Closes #11<commit_after>$(function(){
  // Construstor
  var Visuals = function(selector) {
    this.init(selector)
  }

  var NO_LABEL_COLOR = 'ccc'
    , NO_LABEL_TITLE = 'no label'
    , MARGIN_RIGHT = 30
    , MARGIN_LEFT = 0
    , STROKE_OPACITY = 0.4
    , STROKE_OPACITY_ACTIVE = 0.9
    ;

  /**
   * Init visuals. One time call
   * @param  {String} selector
   */
  Visuals.prototype.init = function(selector) {
    var that = this
      , $svg1 = $('#stackedArea svg')
      , $svg2 = $('#semiCircles svg')
      , $graphs = $('#graphs')

    // Init semicircles container
    d3.select('#semiCircles')
      // .style('margin-left', MARGIN_LEFT)
      // .style('margin-right', MARGIN_RIGHT + 'px')

    // On resize
    var onResize = function(ev){
      if (that.lastData != null) {
        that.showSemiCircles(that.lastData, that.lastSplitIssues, false)
      }

      var width = $graphs.width()
        , svg1Height = Math.ceil(Math.max(200, width / 3))
        , svg2Height = Math.ceil(Math.max(200, width / 2.5))

      $svg1.height(svg1Height)
      $svg2.height(svg2Height)
    }
    // Trigger on resize for the first time
    onResize()

    window.addEventListener('resize', onResize)
  }

  Visuals.prototype.emptyGraphs = function() {
    d3.selectAll('#stackedArea > svg > *').remove()
    d3.selectAll('#semiCircles > svg > *').remove()
  }

  /**
   * Transforms a date of type YYYY-MM-DD into timestamp
   * @param  {String} str
   * @return {Integer}     timestamp
   */
  function dateToTimestamp(str) {
    if (str == null) {
      // Today
      return Math.ceil(Date.now() / 86400000) * 86400000
    } else {
      var d = str.match(/\d+/g)
      return Date.UTC(d[0], d[1] - 1, d[2])
    }
  }

  Visuals.prototype.showData = function(data) {
    console.log(data)
    var splitIssues = splitIssuesByOpenTime(data)

    this.showStackedArea(data)
    this.showSemiCircles(data, splitIssues)

    this.lastData = data
    this.lastSplitIssues = splitIssues
  }

  Visuals.prototype.showStackedArea = function(data) {
    // Bootstrap stacked area object
    var processedData = bootstrapStackedAreaObject(data)

    // Add time-values to each issue
    fillTimeValues(processedData, data)

    // Object to Array
    var processedDataArray = []
    for (var p in processedData) {
      processedDataArray.push(processedData[p])
    }

    drawStackedArea(processedDataArray)
  }

  function bootstrapStackedAreaObject(data) {
    var datesMilisecondsRange = {}
      , start = dateToTimestamp(data.created_at)
      , today = dateToTimestamp(null) + 1

    // Fill dates range
    while (start < today) {
      datesMilisecondsRange[start] = 0
      start += 86400 * 1000
    }

    var processedData = {}
      , label
      , i

    // Add all labels
    for (i in data.labels) {
      label = data.labels[i]

      processedData[label.name] = {key: label.name, values: [], time: Object.create(datesMilisecondsRange), color: '#' + label.color}
    }

    // Add no label
    processedData[NO_LABEL_TITLE] = {key: NO_LABEL_TITLE, values: [], time: Object.create(datesMilisecondsRange), color: '#' + NO_LABEL_COLOR}

    return processedData
  }

  function fillTimeValues(processedData, data) {
    var i
      , issue
      , l
      , label
      , o
      , open
      , d
      , from
      , to

    // Go throuch all issues
    for (i in data.issues) {
      issue = data.issues[i]

      // If issue has no labels than assign 'no label' label
      if (issue.labels.length === 0) {
        issue.labels.push(NO_LABEL_TITLE)
      }

      // For each label
      for (l in issue.labels) {
        label = issue.labels[l]

        // For each open range
        for (o in issue.open) {
          open = issue.open[o]

          from = dateToTimestamp(open.from)
          to = dateToTimestamp(open.to)

          // Fill label time-value object
          while(from <= to) {
            processedData[label].time[from] += 1
            from += 86400 * 1000 // 3600 * 24 * 1000
          }
        }
      }
    }

    var p
      , _data
      , t

    for (p in processedData) {
      _data = processedData[p]

      // Add tuples of timeKey-value to values array
      for (t in _data.time) {
        _data.values.push([+t, _data.time[t]])
      }

      // Sort values by timeKey
      _data.values.sort(function(a, b){
        return a[0] - b[0]
      })

      // Get rid of time array
      delete _data.time
    }
  }

  function drawStackedArea(processedDataArray) {
    nv.addGraph(function() {
      var chart = nv.models.stackedAreaChart()
        .margin({
          right: MARGIN_RIGHT
        , left: 0
        , bottom: 10
        })
        .x(function(d) { return d[0] })
        .y(function(d) { return d[1] })
        .useInteractiveGuideline(true)    // Tooltips which show all data points
        .rightAlignYAxis(true)            // Let's move the y-axis to the right side.
        .transitionDuration(500)
        .showControls(false)              // Allow user to choose 'Stacked', 'Stream', 'Expanded' mode.
        .clipEdge(true)
        // .showLegend(false)
        .showXAxis(false)                 // Hide X Axis (dates)
        // .showYAxis(false)
        .showLegend(false)                // Hide legend
        .transitionDuration(0)

      //Format x-axis labels with custom function.
      chart.xAxis.tickFormat(function(d) {
        return d3.time.format('%x')(new Date(d))
      });

      chart.yAxis.tickFormat(d3.format(',.0f'));

      d3.select('#stackedArea svg')
        .datum(processedDataArray)
        .call(chart);

      nv.utils.windowResize(chart.update);

      return chart;
    });
  }

  function dateToDays(str) {
    return dateToTimestamp(str) / 86400000
  }

  function getLabelColor(data, labelName) {
    for (var l in data.labels) {
      if (data.labels[l].name === labelName) {
        return data.labels[l].color
      }
    }

    return NO_LABEL_COLOR
  }

  function getIssuesColors(data) {
    var issuesColors = []
      , issue

    for (var i in data.issues) {
      issue = data.issues[i]

      if (issue.labels.length === 0) {
        issuesColors[issue.number] = ['#' + NO_LABEL_COLOR]
      } else {
        issuesColors[issue.number] = issue.labels.map(function(label){
          return '#' + getLabelColor(data, label)
        })
      }
    }

    return issuesColors
  }

  function splitIssuesByOpenTime(data) {
    var issue
      , splitIssues = data.issues.slice()
      , splitIssuesLength = splitIssues.length
      , i, l
      , _open
      // Variable to keep links of SVG elements
      , _linked

    for (i = 0; i < splitIssuesLength; i++) {
      issue = splitIssues[i]
      _open = issue.open.slice()
      _linked = []

      if (issue.open.length > 1) {
        for (l = issue.open.length - 1; l > 0; l--) {
          splitIssues.push({
            labels: issue.labels
          , number: issue.number
          , open: [issue.open[l]]
          , _open: _open
          , state: issue.state
          , title: issue.title
          , url: issue.url
          , _linked: _linked
          })

          // Update length after removing
          issue.open.length -= 1
        }

        issue.open = [issue.open[0]]
        issue._open = _open
        issue._linked = _linked
      } else {
        issue._open = _open
        issue._linked = _linked
      }
    }

    return splitIssues
  }

  Visuals.prototype.showSemiCircles = function(data, splitIssues, createNew) {
    var container = d3.select('#semiCircles')
      , $container = $('#semiCircles')
      , svg = container.select('svg')
      , width = svg[0][0].offsetWidth
      , height = svg[0][0].offsetHeight
      , start = dateToDays(data.created_at)
      , today = dateToDays(null)
      , scale = d3.scale.linear().domain([0, today - start]).range([MARGIN_LEFT, width + MARGIN_LEFT])
      , issuesColors = getIssuesColors(data)

    // By default create new is true
    createNew = createNew === void 0 ? true : createNew

    if (createNew) {
      this.semiCircles = svg
        .selectAll("circle")
        .data(splitIssues)
        .enter()
        .append('circle')
        .attr("cy", 0)
        .attr('cy', 0)
        .attr('r', 0)
        .style("fill", 'none')
        .style('stroke', function(d) {
          // Add to linked list
          d._linked.push(this)

          if (issuesColors.length >= d.number) {
            return issuesColors[d.number][0]
          } else {
            return NO_LABEL_COLOR
          }
        })
        .style('stroke-opacity', 0.5)
        .style('stroke-width', 2)
        .on('mouseover', function(d){
          for (var i = 0; i < d._linked.length; i++) {
            d3.select(d._linked[i]).style({'stroke-opacity': STROKE_OPACITY_ACTIVE})
          }
          displayTooltip(d, d3.mouse(this), data)
        })
        .on('mouseout', function(d){
          for (var i = 0; i < d._linked.length; i++) {
            d3.select(d._linked[i]).style({'stroke-opacity': STROKE_OPACITY})
          }
          hideTooltip(d, d3.mouse(this))
        })
        .on('mousemove', function(d){
          moveTooltip(d, d3.mouse(this), svg[0][0])
        })
    }

    // Only if we have semicircles
    if (this.semiCircles != null) {
      // Attributes that vary on window resize
      this.semiCircles
        .data(splitIssues)
        // .transition()
        //   .duration(500)
        .attr("cx", function (d) {
          var from = dateToDays(d.open[0].from)
            , closed_at = d.open[d.open.length - 1].to
            , to = dateToDays(closed_at)

          if (closed_at !== null) {
            return scale((to + from)/2 - start)
          } else {
            // Make it quater of the circle with today as center
            return scale(to - start)
          }
        })
        .attr("r", function (d) {
          var from = dateToDays(d.open[0].from)
            , closed_at = d.open[d.open.length - 1].to
            , to = dateToDays(closed_at)

          if (closed_at !== null) {
            return scale(to - from + 1) / 2
          } else {
            // Make it quater of the circle if it is still open
            return scale(to - from + 0.5)
          }
        })
    }
  }

  var $tooltip = $('#nvtooltip-semicircles')
    , $tooltipTitle = $tooltip.find('.title')
    , $tooltipBody = $tooltip.find('tbody')
    , $tooltipStub = $tooltip.find('.stub')

  /**
   * Formats a timestamp to MM/DD/YYYY
   * @param  {Integer} timestamp
   * @return {String}
   */
  function formatTimestamp(timestamp) {
    var date = new Date(timestamp)
    return ('0' + (date.getMonth() + 1)).slice(-2) + '/' + ('0' + date.getDate()).slice(-2) + '/' +  date.getFullYear()
  }

  function displayTooltip(d, mouse, data) {
    var i

    $tooltipTitle.text('Issue #' + d.number + ': ' + d.title)

    // Remove previous body
    $tooltipBody.children().not('.stub').remove()

    for (i = 0; i < d.labels.length; i++) {
      $tooltipStub
        .clone()
        .removeClass('stub')
          .find('.legend-color-guide > div')
          .css('background-color', '#' + getLabelColor(data, d.labels[i]))
        .end()
          .find('.key')
          .text(d.labels[i])
        .end()
        .appendTo($tooltipBody)
        .show()
    }

    for (i = 0; i < d._open.length; i++) {
      $tooltipStub
        .clone()
        .removeClass('stub')
          .find('.legend-color-guide > div')
          .addClass('glyphicon glyphicon-eye-open')
          .css('display', 'inline')
        .end()
          .find('.key')
          .text(formatTimestamp(dateToTimestamp(d._open[i].from)))
        .end()
        .appendTo($tooltipBody)
        .show()

      if (d._open[i].to !== null) {
        $tooltipStub
          .clone()
          .removeClass('stub')
            .find('.legend-color-guide > div')
            .addClass('glyphicon glyphicon-eye-close')
            .css('display', 'inline')
          .end()
            .find('.key')
            .text(formatTimestamp(dateToTimestamp(d._open[i].to)))
          .end()
          .appendTo($tooltipBody)
          .show()
      }
    }

    $tooltip.show()
  }

  function moveTooltip(d, mouse, svg) {
    var x = mouse[0]
      , y = mouse[1]
      , xPadding = 50
      , width = svg.offsetWidth
      , height = svg.offsetHeight
      , tooltipWidth = $tooltip.width()
      , tooltipHeight = $tooltip.height()

    if (x + xPadding + tooltipWidth > width) {
      // position tooltip to the left of mouse
      $tooltip.css('left', x - xPadding - tooltipWidth)
    } else {
      // position tooltip to the right of the mouse
      $tooltip.css('left', x + xPadding)
    }

    $tooltip.css('top', y - Math.ceil(tooltipHeight / 2))
  }

  function hideTooltip(d, mouse) {
    $tooltip.hide()
  }

  window.Visuals = Visuals
})
<|endoftext|>


<commit_before>// Constants
const DEFAULT_LIMIT = 50;

/**
 * BuffElm
 * Creates a `BuffElm` instance.
 *
 * @name BuffElm
 * @function
 * @param {Function} func The function to be called.
 * @param {Array} args The arguments passed to the function.
 * @param {Function} callback The callback function.
 * @return {BuffElm} The `BuffElm` instance.
 */
function BuffElm(func, args, callback) {
    this._ = func;
    this.callback = callback;
    this.args = args || [];
}

// 0: initial state
// 1: running
// 2: done
BuffElm.prototype.state = 0;

/**
 * Limiter
 * Creates a new instance of `Limiter`.
 *
 * @name Limiter
 * @function
 * @param {Number} limit The limit value representing the number of functions
 * that are run in parallel at a moment of time.
 * @return {Limiter} The `Limiter` instance.
 */
function Limiter(limit) {

    if (this.constructor !== Limiter) {
        return new Limiter(limit);
    }

    limit = limit || DEFAULT_LIMIT;

    this.limit = limit;
    this.buffer = [];
    this.running = 0;
}

/**
 * add
 * Adds a new function in the buffer.
 *
 * @name add
 * @function
 * @param {Function} func The function to be run.
 * @param {Function} callback The callback function.
 * @return {Limiter} The `Limiter` instance.
 */
Limiter.prototype.add = function (func, args, callback) {

    if (typeof args === "function") {
        callback = args;
        args = [];
    }

    this.buffer.push(new BuffElm(func, args, callback));
    return this.check();
};

/**
 * exceeded
 * Checks if the limit was exceeded.
 *
 * @name exceeded
 * @function
 * @return {Boolean} `true` if the limit was exceeded, otherwise `false`.
 */
Limiter.prototype.exceeded = function () {
    return this.running >= this.limit;
};

/**
 * check
 * Checks and runs the functions from the buffer.
 *
 * @name check
 * @function
 * @return {Limiter} The `Limiter` instance.
 */
Limiter.prototype.check = function () {

    var self = this
      , i = 0
      , c = null
      ;

    if (self.exceeded()) {
        return self;
    }

    for (; i < self.buffer.length; ++i) {
        c = self.buffer[i];
        if (c.state !== 0) { continue; }
        ++self.running;
        self.run(c);
        if (self.exceeded()) { break; }
    }

    return self;
};

/**
 * run
 * Runs the function from the buffer element.
 *
 * @name run
 * @function
 * @param {BuffElm} c The buffer element to run.
 * @return {Limiter} The `Limiter` instance.
 */
Limiter.prototype.run = function (c) {
    var self = this;
    if (c.state !== 0) debugger

    // Push the callback function
    c.args.push(function () {
        c.state = 2;
        --self.running;
        c.callback.apply(self, arguments);
        self.check();
    });

    c.state = 1;
    c._.apply(self, c.args);

    return self;
};

module.exports = Limiter;
<commit_msg>Append state to this<commit_after>// Constants
const DEFAULT_LIMIT = 50;

/*!
 * BuffElm
 * Creates a `BuffElm` instance.
 *
 * @name BuffElm
 * @function
 * @param {Function} func The function to be called.
 * @param {Array} args The arguments passed to the function.
 * @param {Function} callback The callback function.
 * @return {BuffElm} The `BuffElm` instance.
 */
function BuffElm(func, args, callback) {
    this._ = func;
    this.callback = callback;
    this.args = args || [];

    // 0: initial state
    // 1: running
    // 2: done
    this.state = 0;
}

/**
 * Limiter
 * Creates a new instance of `Limiter`.
 *
 * @name Limiter
 * @function
 * @param {Number} limit The limit value representing the number of functions
 * that are run in parallel at a moment of time.
 * @return {Limiter} The `Limiter` instance.
 */
function Limiter(limit) {

    if (this.constructor !== Limiter) {
        return new Limiter(limit);
    }

    limit = limit || DEFAULT_LIMIT;

    this.limit = limit;
    this.buffer = [];
    this.running = 0;
}

/**
 * add
 * Adds a new function in the buffer.
 *
 * @name add
 * @function
 * @param {Function} func The function to be run.
 * @param {Function} callback The callback function.
 * @return {Limiter} The `Limiter` instance.
 */
Limiter.prototype.add = function (func, args, callback) {

    if (typeof args === "function") {
        callback = args;
        args = [];
    }

    this.buffer.push(new BuffElm(func, args, callback));
    return this.check();
};

/**
 * exceeded
 * Checks if the limit was exceeded.
 *
 * @name exceeded
 * @function
 * @return {Boolean} `true` if the limit was exceeded, otherwise `false`.
 */
Limiter.prototype.exceeded = function () {
    return this.running >= this.limit;
};

/**
 * check
 * Checks and runs the functions from the buffer.
 *
 * @name check
 * @function
 * @return {Limiter} The `Limiter` instance.
 */
Limiter.prototype.check = function () {

    var self = this
      , i = 0
      , c = null
      ;

    if (self.exceeded()) {
        return self;
    }

    for (; i < self.buffer.length; ++i) {
        c = self.buffer[i];
        if (c.state !== 0) { continue; }
        ++self.running;
        self.run(c);
        if (self.exceeded()) { break; }
    }

    return self;
};

/**
 * run
 * Runs the function from the buffer element.
 *
 * @name run
 * @function
 * @param {BuffElm} c The buffer element to run.
 * @return {Limiter} The `Limiter` instance.
 */
Limiter.prototype.run = function (c) {
    var self = this;
    if (c.state !== 0) debugger

    // Push the callback function
    c.args.push(function () {
        c.state = 2;
        --self.running;
        c.callback.apply(self, arguments);
        self.check();
    });

    c.state = 1;
    c._.apply(self, c.args);

    return self;
};

module.exports = Limiter;
<|endoftext|>


<commit_before>const $ = require("jquery");
var canvas = document.getElementById('chart');
var chartContext = canvas.getContext('2d');

var Chart = require('./chart');
var Analyze = require('./analyze');
var chart = new Chart (chartContext, canvas);
var isLogging = false;
var clicked;
var rendered = false;
var points = []

function getMousePos(canvas, evt) {
	var rect = canvas.getBoundingClientRect();
	return {
	  x: evt.clientX - rect.left,
	  y: evt.clientY - rect.top
	};
}

function drawAndStore(x, y) {
		// var message = 'Mouse position: ' + x + ',' + y;
		// console.log(message)

		if(points.length > 1) {
			var prevPoint = points[points.length-1]
			chartContext.beginPath();
			chartContext.moveTo(prevPoint[0], prevPoint[1]);
			chartContext.lineTo(x, y);
			chartContext.stroke();
		}

		//Store the point
		points.push([x, y]);

}

function normalize(data, n) {
	var normalizedData = [];
	for (var i = 0; i < data.length; i++) {
		var iNew = i*n;
		normalizedData.push(data[iNew]);
	}
	return normalizedData
}

function plotPoints() {
	window.getSelection().removeAllRanges();
	$('#highchart').show();
	rendered = true;
	$("#chart").hide();
	// var spacing = 10;
	// var normalizedInput = normalize(points, spacing);

	var yValues = points.map(function(point) {
		return (-0.125)*point[1]+80;
	});

	chartOptions.series[0].data = [];
	chartOptions.series[0].data = yValues;
	var highchart = new Highcharts.Chart(chartOptions)

}

$("#chart").mousedown(function() {
	isLogging = true;
	clicked = true;
	canvas.addEventListener('mousemove', function(evt) {
		if(isLogging) {
			var mousePos = getMousePos(canvas, evt);
		    drawAndStore(mousePos.x, mousePos.y);
		}
 	}, false);
});

$("#chart").mouseup(function() {
	isLogging = false;
	console.log(points.length)
	var message = Analyze(points)
	appendMessage(message, "success")
	plotPoints()
});

$("#chart").mouseleave(function() {
	if(clicked && !rendered) {
		isLogging = false;
		var message = Analyze(points);
		appendMessage(message, "success")
		plotPoints()
	}

});

$("#clearButton").click(function() {
	clearGraph();
});

function appendMessage(message, type) {
	console.log(type)
	var htmlString = '
' +
                      '<button data-dismiss="alert" class="close close-sm" type="button">' +
                       ''+
                       '</button>' + message +
                        '
'

    $("#noti-box").append(htmlString);

    if(type == "success") {
    	$(".chartRow").after('<button class="btn btn-success" id="answerButton"> Show me the answer </button>');
    	$("#answerButton").click(function() {
			showCorrect();
		});
    }

}

function logFunc(x) {
	var a = 83/7;
	var b  = 30;
	return a*Math.log(x) + b
}

function showCorrect() {
	console.log('Test')
	var xVals = []
	for (var i = 0; i < points.length; i++) {
		xVals.push(i)
	}

	var yVals = xVals.map(function(x) {
		return logFunc(x)
	});

	console.log(yVals)

	var correctSeries = {
		color: '#00FF00',
		name: "Correct",
		data: yVals.slice(1, yVals.length)
	}
	chartOptions.series.push(correctSeries)
	$("#highchart").remove();
	$("#headerTag").append('

');

	var highchart = new Highcharts.Chart(chartOptions)

	appendMessage("Here's the actual relationship according to data. As GDP increases, life expectancy increases with diminishing returns.", "warning");

	// var question = '

What is the actual relationship?
' +
	// '
'+
 //  	'<button class="btn btn-default dropdown-toggle" type="button" id="dropdownMenu1" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">' +
 //    'Choose a graph type' +
 //    '' +
 //  	'</button>' +
 //  	'
' +
 //    '
Linear
' +
 //    '
Step Function
' +
 //    '
Logarithmic
' +
 //    '
Periodic
' +
 //  	'
' +
	// '

'

	var question = '

' +
	'
What is the actual relationship?
' +
  	'<button type="button" class="btn btn-default">Linear</button>' +
  	'<button type="button" class="btn btn-default">Step Function</button>' +
  	'<button type="button" class="btn btn-default">Logarithmic</button>' +
  	'<button type="button" class="btn btn-default">Periodic</button>' +
	'

';

	$('#questions').append(question);
	$('#answerButton').hide()
}

function clearGraph() {
	$('#highchart').empty()
	$('#highchart').hide()
	$('#chart').show()
	chartContext.clearRect(0, 0, canvas.width, canvas.height);
	points = []
}

var chartOptions = {
		chart: {
            renderTo: 'highchart',
            type: 'line',
            animation: true
        },
        title: {
            text: 'Life Expectancy vs. GDP',
            x: -20 //center
        },
        xAxis: {
          title: {
                  text: 'GDP Per Capita'
              },
          labels: {
          enabled: false }

        },
        yAxis: {
            title: {
                text: 'Life Expectancy (Years)'
            },
            plotLines: [{
                value: 0,
                width: 2,
                color: '#808080'
            }]
        },
        tooltip: {
            valueSuffix: '°C'
        },
        legend: {
            layout: 'vertical',
            align: 'right',
            verticalAlign: 'middle',
            borderWidth: 0
        },
        series: [{
        	color: '#0000FF',
            name: 'You',
            data: []
        }]
    };

<commit_msg>Adds draw me text.<commit_after>const $ = require("jquery");
var canvas = document.getElementById('chart');
var chartContext = canvas.getContext('2d');

var Chart = require('./chart');
var Analyze = require('./analyze');
var chart = new Chart (chartContext, canvas);
var isLogging = false;
var clicked;
var rendered = false;
var points = []

function displayCanvasText(text) {
	chartContext.font = "45px Arial";
	chartContext.textAlign = "center";
	chartContext.fillText(text, canvas.width/2 + 50, canvas.height/2);
}

displayCanvasText("Draw Here");

function getMousePos(canvas, evt) {
	var rect = canvas.getBoundingClientRect();
	return {
	  x: evt.clientX - rect.left,
	  y: evt.clientY - rect.top
	};
}

function drawAndStore(x, y) {
		// var message = 'Mouse position: ' + x + ',' + y;
		// console.log(message)

		if(points.length > 1) {
			var prevPoint = points[points.length-1]
			chartContext.beginPath();
			chartContext.moveTo(prevPoint[0], prevPoint[1]);
			chartContext.lineTo(x, y);
			chartContext.stroke();
		}

		//Store the point
		points.push([x, y]);

}

function normalize(data, n) {
	var normalizedData = [];
	for (var i = 0; i < data.length; i++) {
		var iNew = i*n;
		normalizedData.push(data[iNew]);
	}
	return normalizedData
}

function plotPoints() {
	window.getSelection().removeAllRanges();
	$('#highchart').show();
	rendered = true;
	$("#chart").hide();
	// var spacing = 10;
	// var normalizedInput = normalize(points, spacing);

	var yValues = points.map(function(point) {
		return (-0.125)*point[1]+80;
	});

	chartOptions.series[0].data = [];
	chartOptions.series[0].data = yValues;
	var highchart = new Highcharts.Chart(chartOptions)

}

$("#chart").mousedown(function() {
	isLogging = true;
	clicked = true;
	chartContext.clearRect(0, 0, canvas.width, canvas.height);
	canvas.addEventListener('mousemove', function(evt) {
		if(isLogging) {
			var mousePos = getMousePos(canvas, evt);
		    drawAndStore(mousePos.x, mousePos.y);
		}
 	}, false);
});

$("#chart").mouseup(function() {
	isLogging = false;
	console.log(points.length)
	var message = Analyze(points)
	appendMessage(message, "success")
	plotPoints()
});

$("#chart").mouseleave(function() {
	if(clicked && !rendered) {
		isLogging = false;
		var message = Analyze(points);
		appendMessage(message, "success")
		plotPoints()
	}

});

$("#clearButton").click(function() {
	clearGraph();
});

function appendMessage(message, type) {
	console.log(type)
	var htmlString = '
' +
                      '<button data-dismiss="alert" class="close close-sm" type="button">' +
                       ''+
                       '</button>' + message +
                        '
'

    $("#noti-box").append(htmlString);

    if(type == "success") {
    	$(".chartRow").after('<button class="btn btn-success" id="answerButton"> Show me the answer </button>');
    	$("#answerButton").click(function() {
			showCorrect();
		});
    }

}

function logFunc(x) {
	var a = 83/7;
	var b  = 30;
	return a*Math.log(x) + b
}

function showCorrect() {
	console.log('Test')
	var xVals = []
	for (var i = 0; i < points.length; i++) {
		xVals.push(i)
	}

	var yVals = xVals.map(function(x) {
		return logFunc(x)
	});

	console.log(yVals)

	var correctSeries = {
		color: '#00FF00',
		name: "Correct",
		data: yVals.slice(1, yVals.length)
	}
	chartOptions.series.push(correctSeries)
	$("#highchart").remove();
	$("#headerTag").append('

');

	var highchart = new Highcharts.Chart(chartOptions)

	appendMessage("Here's the actual relationship according to data. As GDP increases, life expectancy increases with diminishing returns.", "warning");

	// var question = '

What is the actual relationship?
' +
	// '
'+
 //  	'<button class="btn btn-default dropdown-toggle" type="button" id="dropdownMenu1" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">' +
 //    'Choose a graph type' +
 //    '' +
 //  	'</button>' +
 //  	'
' +
 //    '
Linear
' +
 //    '
Step Function
' +
 //    '
Logarithmic
' +
 //    '
Periodic
' +
 //  	'
' +
	// '

'

	var question = '

' +
	'
What is the actual relationship?
' +
  	'<button type="button" class="btn btn-default">Linear</button>' +
  	'<button type="button" class="btn btn-default">Step Function</button>' +
  	'<button type="button" class="btn btn-default">Logarithmic</button>' +
  	'<button type="button" class="btn btn-default">Periodic</button>' +
	'

';

	$('#questions').append(question);
	$('#answerButton').hide()
}

function clearGraph() {
	$('#highchart').empty()
	$('#highchart').hide()
	$('#chart').show()
	chartContext.clearRect(0, 0, canvas.width, canvas.height);
	points = []
}

var chartOptions = {
		chart: {
            renderTo: 'highchart',
            type: 'line',
            animation: true
        },
        title: {
            text: 'Life Expectancy vs. GDP',
            x: -20 //center
        },
        xAxis: {
          title: {
                  text: 'GDP Per Capita'
              },
          labels: {
          enabled: false }

        },
        yAxis: {
            title: {
                text: 'Life Expectancy (Years)'
            },
            plotLines: [{
                value: 0,
                width: 2,
                color: '#808080'
            }]
        },
        tooltip: {
            valueSuffix: '°C'
        },
        legend: {
            layout: 'vertical',
            align: 'right',
            verticalAlign: 'middle',
            borderWidth: 0
        },
        series: [{
        	color: '#0000FF',
            name: 'You',
            data: []
        }]
    };

<|endoftext|>


<commit_before>$(function(){
  // Construstor
  var Visuals = function(selector) {
    this.init(selector)
  }

  var NO_LABEL_COLOR = 'ccc'
    , NO_LABEL_TITLE = 'no label'
    , MARGIN_RIGHT = 20
    , MARGIN_LEFT = 0
    , STROKE_OPACITY = 0.4
    , STROKE_OPACITY_ACTIVE = 0.9
    ;

  /**
   * Init visuals. One time call
   * @param  {String} selector
   */
  Visuals.prototype.init = function(selector) {
    var that = this

    // Init semicircles container
    d3.select('#semiCircles')
      // .style('margin-left', MARGIN_LEFT)
      // .style('margin-right', MARGIN_RIGHT + 'px')

    // On resize
    var onResize = function(ev){
      if (that.lastData != null) {
        that.showSemiCircles(that.lastData, false)
      }
    }

    window.addEventListener('resize', onResize)
  }

  Visuals.prototype.showLoading = function() {
    console.log('Visual Loading')
  }

  Visuals.prototype.showError = function(message) {
    console.log('Visual Error', message)
  }

  /**
   * Transforms a date of type YYYY-MM-DD into timestamp
   * @param  {String} str
   * @return {Integer}     timestamp
   */
  function dateToTimestamp(str) {
    if (str == null) {
      // Today
      return Math.ceil(Date.now() / 86400000) * 86400000
    } else {
      var d = str.match(/\d+/g)
      return Date.UTC(d[0], d[1] - 1, d[2])
    }
  }

  Visuals.prototype.showData = function(data) {
    console.log(data)
    this.showStackedArea(data)
    this.showSemiCircles(data)

    this.lastData = data
  }

  Visuals.prototype.showStackedArea = function(data) {
    // Bootstrap stacked area object
    var processedData = bootstrapStackedAreaObject(data)

    // Add time-values to each issue
    fillTimeValues(processedData, data)

    // Object to Array
    var processedDataArray = []
    for (var p in processedData) {
      processedDataArray.push(processedData[p])
    }

    drawStackedArea(processedDataArray)
  }

  function bootstrapStackedAreaObject(data) {
    var datesMilisecondsRange = {}
      , start = dateToTimestamp(data.created_at)
      , today = dateToTimestamp(null) + 1

    // Fill dates range
    while (start < today) {
      datesMilisecondsRange[start] = 0
      start += 86400 * 1000
    }

    var processedData = {}
      , label
      , i

    // Add all labels
    for (i in data.labels) {
      label = data.labels[i]

      processedData[label.name] = {key: label.name, values: [], time: Object.create(datesMilisecondsRange), color: '#' + label.color}
    }

    // Add no label
    processedData[NO_LABEL_TITLE] = {key: NO_LABEL_TITLE, values: [], time: Object.create(datesMilisecondsRange), color: '#' + NO_LABEL_COLOR}

    return processedData
  }

  function fillTimeValues(processedData, data) {
    var i
      , issue
      , l
      , label
      , o
      , open
      , d
      , from
      , to

    // Go throuch all issues
    for (i in data.issues) {
      issue = data.issues[i]

      // If issue has no labels than assign 'no label' label
      if (issue.labels.length === 0) {
        issue.labels.push(NO_LABEL_TITLE)
      }

      // For each label
      for (l in issue.labels) {
        label = issue.labels[l]

        // For each open range
        for (o in issue.open) {
          open = issue.open[o]

          from = dateToTimestamp(open.from)
          to = dateToTimestamp(open.to)

          // Fill label time-value object
          while(from <= to) {
            console.log(label)
            processedData[label].time[from] += 1
            from += 86400 * 1000 // 3600 * 24 * 1000
          }
        }
      }
    }

    var p
      , _data
      , t

    for (p in processedData) {
      _data = processedData[p]

      // Add tuples of timeKey-value to values array
      for (t in _data.time) {
        _data.values.push([+t, _data.time[t]])
      }

      // Sort values by timeKey
      _data.values.sort(function(a, b){
        return a[0] - b[0]
      })

      // Get rid of time array
      delete _data.time
    }
  }

  function drawStackedArea(processedDataArray) {
    nv.addGraph(function() {
      var chart = nv.models.stackedAreaChart()
        .margin({
          right: MARGIN_RIGHT
        , left: 0
        , bottom: 10
        })
        .x(function(d) { return d[0] })
        .y(function(d) { return d[1] })
        .useInteractiveGuideline(true)    // Tooltips which show all data points
        .rightAlignYAxis(true)            // Let's move the y-axis to the right side.
        .transitionDuration(500)
        .showControls(false)              // Allow user to choose 'Stacked', 'Stream', 'Expanded' mode.
        .clipEdge(true)
        // .showLegend(false)
        .showXAxis(false)                 // Hide X Axis (dates)
        // .showYAxis(false)
        .showLegend(false)                // Hide legend

      //Format x-axis labels with custom function.
      chart.xAxis.tickFormat(function(d) {
        return d3.time.format('%x')(new Date(d))
      });

      chart.yAxis.tickFormat(d3.format(',.0f'));

      d3.select('#stackedArea svg')
        .datum(processedDataArray)
        .call(chart);

      nv.utils.windowResize(chart.update);

      return chart;
    });
  }

  function dateToDays(str) {
    return dateToTimestamp(str) / 86400000
  }

  function getLabelColor(data, labelName) {
    for (var l in data.labels) {
      if (data.labels[l].name === labelName) {
        return data.labels[l].color
      }
    }

    return NO_LABEL_COLOR
  }

  function getIssuesColors(data) {
    var issuesColors = []
      , issue

    for (var i in data.issues) {
      issue = data.issues[i]

      if (issue.labels.length === 0) {
        issuesColors[issue.number] = ['#' + NO_LABEL_COLOR]
      } else {
        issuesColors[issue.number] = issue.labels.map(function(label){
          return '#' + getLabelColor(data, label)
        })
      }
    }

    return issuesColors
  }

  Visuals.prototype.showSemiCircles = function(data, createNew) {
    var container = d3.select('#semiCircles')
      , svg = container.select('svg')
      , width = svg[0][0].offsetWidth
      , start = dateToDays(data.created_at)
      , today = dateToDays(null)
      , scale = d3.scale.linear().domain([0, today - start]).range([MARGIN_LEFT, width + MARGIN_LEFT])
      , issuesColors = getIssuesColors(data)

    // By default create new is true
    createNew = createNew === void 0 ? true : createNew

    if (createNew) {
      // Remove any old elements
      svg.selectAll("*").remove()
    }

    if (createNew) {
      this.semiCircles = svg
        .selectAll("circle")
        .data(data.issues)
        .enter()
        .append('circle')
        .attr("cy", 0)
        .attr('cy', 0)
        .attr('r', 0)
        .style("fill", 'none')
        .style('stroke', function(d) {
          if (issuesColors.length >= d.number) {
            return issuesColors[d.number][0]
          } else {
            return NO_LABEL_COLOR
          }
        })
        .style('stroke-opacity', 0.5)
        .style('stroke-width', 2)
        .on('mouseover', function(d){
          d3.select(this).style({'stroke-opacity': STROKE_OPACITY_ACTIVE})
        })
        .on('mouseout', function(d){
          d3.select(this).style({'stroke-opacity': STROKE_OPACITY})
        })
    }

    // Only if we have semicircles
    if (this.semiCircles != null) {
      // Attributes that vary on window resize
      this.semiCircles
        .data(data.issues)
        .transition()
          .duration(500)
        .attr("cx", function (d) {
          var from = dateToDays(d.open[0].from)
            , closed_at = d.open[d.open.length - 1].to
            , to = dateToDays(closed_at)

          if (closed_at !== null) {
            return scale((to + from)/2 - start)
          } else {
            // Make it quater of the circle with today as center
            return scale(to - start)
          }
        })
        .attr("r", function (d) {
          var from = dateToDays(d.open[0].from)
            , closed_at = d.open[d.open.length - 1].to
            , to = dateToDays(closed_at)

          if (closed_at !== null) {
            return scale(to - from + 1) / 2
          } else {
            // Make it quater of the circle if it is still open
            return scale(to - from + 0.5)
          }
        })
    }
  }

  window.Visuals = Visuals
})
<commit_msg>:aerial_tramway: Remove logs from visuals<commit_after>$(function(){
  // Construstor
  var Visuals = function(selector) {
    this.init(selector)
  }

  var NO_LABEL_COLOR = 'ccc'
    , NO_LABEL_TITLE = 'no label'
    , MARGIN_RIGHT = 20
    , MARGIN_LEFT = 0
    , STROKE_OPACITY = 0.4
    , STROKE_OPACITY_ACTIVE = 0.9
    ;

  /**
   * Init visuals. One time call
   * @param  {String} selector
   */
  Visuals.prototype.init = function(selector) {
    var that = this

    // Init semicircles container
    d3.select('#semiCircles')
      // .style('margin-left', MARGIN_LEFT)
      // .style('margin-right', MARGIN_RIGHT + 'px')

    // On resize
    var onResize = function(ev){
      if (that.lastData != null) {
        that.showSemiCircles(that.lastData, false)
      }
    }

    window.addEventListener('resize', onResize)
  }

  Visuals.prototype.showLoading = function() {
    console.log('Visual Loading')
  }

  Visuals.prototype.showError = function(message) {
    console.log('Visual Error', message)
  }

  /**
   * Transforms a date of type YYYY-MM-DD into timestamp
   * @param  {String} str
   * @return {Integer}     timestamp
   */
  function dateToTimestamp(str) {
    if (str == null) {
      // Today
      return Math.ceil(Date.now() / 86400000) * 86400000
    } else {
      var d = str.match(/\d+/g)
      return Date.UTC(d[0], d[1] - 1, d[2])
    }
  }

  Visuals.prototype.showData = function(data) {
    console.log(data)
    this.showStackedArea(data)
    this.showSemiCircles(data)

    this.lastData = data
  }

  Visuals.prototype.showStackedArea = function(data) {
    // Bootstrap stacked area object
    var processedData = bootstrapStackedAreaObject(data)

    // Add time-values to each issue
    fillTimeValues(processedData, data)

    // Object to Array
    var processedDataArray = []
    for (var p in processedData) {
      processedDataArray.push(processedData[p])
    }

    drawStackedArea(processedDataArray)
  }

  function bootstrapStackedAreaObject(data) {
    var datesMilisecondsRange = {}
      , start = dateToTimestamp(data.created_at)
      , today = dateToTimestamp(null) + 1

    // Fill dates range
    while (start < today) {
      datesMilisecondsRange[start] = 0
      start += 86400 * 1000
    }

    var processedData = {}
      , label
      , i

    // Add all labels
    for (i in data.labels) {
      label = data.labels[i]

      processedData[label.name] = {key: label.name, values: [], time: Object.create(datesMilisecondsRange), color: '#' + label.color}
    }

    // Add no label
    processedData[NO_LABEL_TITLE] = {key: NO_LABEL_TITLE, values: [], time: Object.create(datesMilisecondsRange), color: '#' + NO_LABEL_COLOR}

    return processedData
  }

  function fillTimeValues(processedData, data) {
    var i
      , issue
      , l
      , label
      , o
      , open
      , d
      , from
      , to

    // Go throuch all issues
    for (i in data.issues) {
      issue = data.issues[i]

      // If issue has no labels than assign 'no label' label
      if (issue.labels.length === 0) {
        issue.labels.push(NO_LABEL_TITLE)
      }

      // For each label
      for (l in issue.labels) {
        label = issue.labels[l]

        // For each open range
        for (o in issue.open) {
          open = issue.open[o]

          from = dateToTimestamp(open.from)
          to = dateToTimestamp(open.to)

          // Fill label time-value object
          while(from <= to) {
            processedData[label].time[from] += 1
            from += 86400 * 1000 // 3600 * 24 * 1000
          }
        }
      }
    }

    var p
      , _data
      , t

    for (p in processedData) {
      _data = processedData[p]

      // Add tuples of timeKey-value to values array
      for (t in _data.time) {
        _data.values.push([+t, _data.time[t]])
      }

      // Sort values by timeKey
      _data.values.sort(function(a, b){
        return a[0] - b[0]
      })

      // Get rid of time array
      delete _data.time
    }
  }

  function drawStackedArea(processedDataArray) {
    nv.addGraph(function() {
      var chart = nv.models.stackedAreaChart()
        .margin({
          right: MARGIN_RIGHT
        , left: 0
        , bottom: 10
        })
        .x(function(d) { return d[0] })
        .y(function(d) { return d[1] })
        .useInteractiveGuideline(true)    // Tooltips which show all data points
        .rightAlignYAxis(true)            // Let's move the y-axis to the right side.
        .transitionDuration(500)
        .showControls(false)              // Allow user to choose 'Stacked', 'Stream', 'Expanded' mode.
        .clipEdge(true)
        // .showLegend(false)
        .showXAxis(false)                 // Hide X Axis (dates)
        // .showYAxis(false)
        .showLegend(false)                // Hide legend

      //Format x-axis labels with custom function.
      chart.xAxis.tickFormat(function(d) {
        return d3.time.format('%x')(new Date(d))
      });

      chart.yAxis.tickFormat(d3.format(',.0f'));

      d3.select('#stackedArea svg')
        .datum(processedDataArray)
        .call(chart);

      nv.utils.windowResize(chart.update);

      return chart;
    });
  }

  function dateToDays(str) {
    return dateToTimestamp(str) / 86400000
  }

  function getLabelColor(data, labelName) {
    for (var l in data.labels) {
      if (data.labels[l].name === labelName) {
        return data.labels[l].color
      }
    }

    return NO_LABEL_COLOR
  }

  function getIssuesColors(data) {
    var issuesColors = []
      , issue

    for (var i in data.issues) {
      issue = data.issues[i]

      if (issue.labels.length === 0) {
        issuesColors[issue.number] = ['#' + NO_LABEL_COLOR]
      } else {
        issuesColors[issue.number] = issue.labels.map(function(label){
          return '#' + getLabelColor(data, label)
        })
      }
    }

    return issuesColors
  }

  Visuals.prototype.showSemiCircles = function(data, createNew) {
    var container = d3.select('#semiCircles')
      , svg = container.select('svg')
      , width = svg[0][0].offsetWidth
      , start = dateToDays(data.created_at)
      , today = dateToDays(null)
      , scale = d3.scale.linear().domain([0, today - start]).range([MARGIN_LEFT, width + MARGIN_LEFT])
      , issuesColors = getIssuesColors(data)

    // By default create new is true
    createNew = createNew === void 0 ? true : createNew

    if (createNew) {
      // Remove any old elements
      svg.selectAll("*").remove()
    }

    if (createNew) {
      this.semiCircles = svg
        .selectAll("circle")
        .data(data.issues)
        .enter()
        .append('circle')
        .attr("cy", 0)
        .attr('cy', 0)
        .attr('r', 0)
        .style("fill", 'none')
        .style('stroke', function(d) {
          if (issuesColors.length >= d.number) {
            return issuesColors[d.number][0]
          } else {
            return NO_LABEL_COLOR
          }
        })
        .style('stroke-opacity', 0.5)
        .style('stroke-width', 2)
        .on('mouseover', function(d){
          d3.select(this).style({'stroke-opacity': STROKE_OPACITY_ACTIVE})
        })
        .on('mouseout', function(d){
          d3.select(this).style({'stroke-opacity': STROKE_OPACITY})
        })
    }

    // Only if we have semicircles
    if (this.semiCircles != null) {
      // Attributes that vary on window resize
      this.semiCircles
        .data(data.issues)
        .transition()
          .duration(500)
        .attr("cx", function (d) {
          var from = dateToDays(d.open[0].from)
            , closed_at = d.open[d.open.length - 1].to
            , to = dateToDays(closed_at)

          if (closed_at !== null) {
            return scale((to + from)/2 - start)
          } else {
            // Make it quater of the circle with today as center
            return scale(to - start)
          }
        })
        .attr("r", function (d) {
          var from = dateToDays(d.open[0].from)
            , closed_at = d.open[d.open.length - 1].to
            , to = dateToDays(closed_at)

          if (closed_at !== null) {
            return scale(to - from + 1) / 2
          } else {
            // Make it quater of the circle if it is still open
            return scale(to - from + 0.5)
          }
        })
    }
  }

  window.Visuals = Visuals
})
<|endoftext|>


<commit_before>/*global require, exports, console, setInterval, clearInterval*/

var logger = require('./../common/logger').logger;

// Logger
var log = logger.getLogger("RoomController");

exports.RoomController = function (spec) {
    "use strict";

    var that = {},
        // {id: array of subscribers}
        subscribers = {},
        // {id: erizoJS_id}
        publishers = {},
        // {erizoJS_id: {publishers: [ids], ka_count: count}}
        erizos = {},

        // {id: ExternalOutput}
        externalOutputs = {};

    var amqper = spec.amqper;
    var ecch = spec.ecch;

    var KEEPALIVE_INTERVAL = 5*1000;
    var TIMEOUT_LIMIT = 2;

    var eventListeners = [];

    var callbackFor = function(erizo_id) {

        return function(ok) {
            if (!erizos[erizo_id]) return;

            if (ok !== true) {
                erizos[erizo_id].ka_count ++;

                if (erizos[erizo_id].ka_count > TIMEOUT_LIMIT) {

                    for (var p in erizos[erizo_id].publishers) {
                        log.error("Lost connection with ErizoJS, will remove publishers", erizo_id);
                        dispatchEvent("unpublish", erizos[erizo_id].publishers[p]);
                    }
                    ecch.deleteErizoJS(erizo_id);
                    delete erizos[erizo_id];
                }
            } else {
                erizos[erizo_id].ka_count = 0;
            }
        }
    };

    var sendKeepAlive = function() {
        for (var e in erizos) {
            amqper.callRpc("ErizoJS_" + e, "keepAlive", [], {callback: callbackFor(e)});
        }
    };

    var keepAliveLoop = setInterval(sendKeepAlive, KEEPALIVE_INTERVAL);

    var getErizoJS = function(callback) {
    	ecch.getErizoJS(function(erizo_id) {
            if (!erizos[erizo_id] && erizo_id !== 'timeout') {
                erizos[erizo_id] = {publishers: [], ka_count: 0};
            }
            callback(erizo_id);
        });
    };

    var getErizoQueue = function(publisher_id) {
        return "ErizoJS_" + publishers[publisher_id];
    };

    var dispatchEvent = function(type, event) {
        for (var event_id in eventListeners) {
            eventListeners[event_id](type, event);
        }

    };

/*  Prototype method to add a branch to a erizo Tree
    var shouldAddSurrogate = function(publisher_id, subscriber_id, options, callback){
        //Create new erizoJS, assign it as surrogate of publisher_id (subscribe), return id
        log.info("Adding a surrogate");
        getErizoJS(function(id){
            log.info("got a new Erizo with id", id);

//            log.info("Adding subscriber ", id, ' to publisher ', publisher_id);
            var options = {};
            if (options.audio === undefined) options.audio = true;
            if (options.video === undefined) options.video = true;

            var argsAddPub = [publisher_id, 300, true];
            log.info("Adding publisher to surrogate", "ErizoJS_"+id, "args",argsAddPub);
            var statusBranch = false;
            var statusRoot = false;
            var branchQueue = [];
            var rootQueue = [];
            log.info("calling addPublisher on branch");
            amqper.callRpc("ErizoJS_"+id, "addPublisher", argsAddPub, {callback: function(msg){
                log.info("Message from branch", msg.type);
                if (msg.type==='started'){
                    statusbranch = 'started';
                    log.info("branch Started");
                    if (branchQueue.length){
                        while (branchQueue.length>0){
                            var themessage = branchQueue.shift();
                            var argus = [publisher_id, undefined, themessage];
                            console.log("Queued msg from Erizoroot, sending to Erizobranch", argus);
                            amqper.callRpc("ErizoJS_"+id, "processSignaling", argus, {});
                        }

                    }

                }else if (msg.type === 'offer' || msg.type ==='candidate'){
                    var argsSignaling = [publisher_id, id, msg];
                    console.log("msg from Erizobranch, should to Erizoroot?");
                    if (statusroot){
                        console.log("Yes, sending", argsSignaling);
                        amqper.callRpc(getErizoQueue(publisher_id, id), "processSignaling", argsSignaling, {});
                    }else{
                        console.log("No, queuing", argsSignaling);
                        rootQueue.push(msg);
                    }
                }else if (msg.type === 'ready'){
                    log.info("Surrogate ready, firing after a second");
                    setTimeout (callback(id), 1000);
                }

            }});

            log.info("calling addSubscriber on root");
            var argsAddSub = [id, publisher_id, options];
            amqper.callRpc(getErizoQueue(publisher_id), "addSubscriber", argsAddSub, {callback: function(mess){
                log.info("Message from root", mess.type);
                if (mess.type==='started'){
                    statusroot = 'started';
                    log.info("root Started");
                    if (rootQueue.length){
                        while (rootQueue.length>0){
                            var themessage = rootQueue.shift();
                            var argsSignaling = [publisher_id, id, themessage];
                            console.log("Queued msg from Erizobranch, sending to Erizoroot", argsSignaling);
                            amqper.callRpc(getErizoQueue(publisher_id, id), "processSignaling", argsSignaling, {});
                        }

                    }

                }else if (mess.type === 'answer' || mess.type ==='candidate'){
                    if (mess.type === 'answer'){
                        mess.type = 'offer';
                    }
                    var argus = [publisher_id, subscriber_id, mess];

                    console.log("signalling from Erizoroot, should send to branch?");
                    if (statusbranch){
                        console.log("Yes, sending", argus);
                        amqper.callRpc("ErizoJS_"+id, "processSignaling", argus, {});
                    }else{
                        console.log("No, queuing", argus);
                        branchQueue.push(mess);
                    }
                }
            }});
            subscribers[publisher_id].push({id:subscriber_id, surrogate_id:id});
            // Track subscriber locally

        });

    }
*/
    that.addEventListener = function(eventListener) {
        eventListeners.push(eventListener);
    };

    that.addExternalInput = function (publisher_id, url, callback) {

        if (publishers[publisher_id] === undefined) {

            log.info("Adding external input peer_id ", publisher_id);

            getErizoJS(function(erizo_id) {
                // then we call its addPublisher method.
    	        var args = [publisher_id, url];

                // Track publisher locally
//                publishers[publisher_id] = {};
                publishers[publisher_id] = erizo_id;
                subscribers[publisher_id] = [];

                amqper.callRpc(getErizoQueue(publisher_id), "addExternalInput", args, {callback: callback});

                erizos[erizo_id].publishers.push(publisher_id);

            });
        } else {
            log.info("Publisher already set for", publisher_id);
        }
    };

    that.addExternalOutput = function (publisher_id, url, callback) {
        if (publishers[publisher_id] !== undefined) {
            log.info("Adding ExternalOutput to " + publisher_id + " url " + url);

            var args = [publisher_id, url];

            amqper.callRpc(getErizoQueue(publisher_id), "addExternalOutput", args, undefined);

            // Track external outputs
            externalOutputs[url] = publisher_id;

            // Track publisher locally
            // publishers[publisher_id] = publisher_id;
            // subscribers[publisher_id] = [];
            callback('success');
        } else {
            callback('error');
        }

    };

    that.removeExternalOutput = function (url, callback) {
        var publisher_id = externalOutputs[url];

        if (publisher_id !== undefined && publishers[publisher_id] != undefined) {
            log.info("Stopping ExternalOutput: url " + url);

            var args = [publisher_id, url];
            amqper.callRpc(getErizoQueue(publisher_id), "removeExternalOutput", args, undefined);

            // Remove track
            delete externalOutputs[url];
            callback(true);
        } else {
            callback(null, 'This stream is not being recorded');
        }
    };

    that.processSignaling = function (streamId, peerId, msg) {

        if (publishers[streamId] !== undefined) {

//            log.info("Sending signaling mess to erizoJS of st ", streamId, ' of peer ', peerId);

            var args = [streamId, peerId, msg];

            amqper.callRpc(getErizoQueue(streamId), "processSignaling", args, {});

        }
    };

    /*
     * Adds a publisher to the room. This creates a new OneToManyProcessor
     * and a new WebRtcConnection. This WebRtcConnection will be the publisher
     * of the OneToManyProcessor.
     */
    that.addPublisher = function (publisher_id, options, callback) {

        if (publishers[publisher_id] === undefined) {

            log.info("Adding publisher peer_id ", publisher_id, "options", options);;

            // We create a new ErizoJS with the publisher_id.
            getErizoJS(function(erizo_id) {

                if (erizo_id === 'timeout') {
                    log.error("Cannot contact ErizoAgent to add Publisher -- timeout");
                    callback('timeout-agent');
                    return;
                }
            	log.debug("ErizoJS id:", erizo_id, "assigned for publisher", publisher_id);
                // Track publisher locally

                // then we call its addPublisher method.
                var args = [publisher_id, options];
                publishers[publisher_id] = erizo_id;
                subscribers[publisher_id] = [];

                amqper.callRpc(getErizoQueue(publisher_id), "addPublisher", args, {callback: function (data){
                    if (data === 'timeout'){
                        log.error("Can not contact ErizoJS", publisher_id , " failed add Publisher -- timeout");
                        callback('timeout-erizojs');
                        return;
                    }else{
                        callback(data);
                    }
                }});

                erizos[erizo_id].publishers.push(publisher_id);
            });

        } else {
            log.warn("Publisher already set for", publisher_id);
        }
    };

    /*
     * Adds a subscriber to the room. This creates a new WebRtcConnection.
     * This WebRtcConnection will be added to the subscribers list of the
     * OneToManyProcessor.
     */
    that.addSubscriber = function (subscriber_id, publisher_id, options, callback) {
        if (subscriber_id === null){
          callback("Error: null subscriber_id");
          return;
        }

        if (publishers[publisher_id] !== undefined && subscribers[publisher_id].indexOf(subscriber_id) === -1) {
            log.info("Adding subscriber ", subscriber_id, ' to publisher ', publisher_id, "options", options);

//            if (true){

                if (options.audio === undefined) options.audio = true;
                if (options.video === undefined) options.video = true;

                var args = [subscriber_id, publisher_id, options];
                //TODO: Potential problem here if erizoJS is not reachable
                amqper.callRpc(getErizoQueue(publisher_id, undefined), "addSubscriber", args, {callback: function (data){
                    if (data === 'timeout'){
                        log.error("Can not contact to ErizoJS", publisher_id , " failed add Subscriber -- timeout");
                        callback('timeout');
                        return;
                    }else if (data === 'initializing'){
                        subscribers[publisher_id].push(subscriber_id);
                    }
                    callback(data);
                }});

/*            }else{ // Prototype for erizo_trees
                shouldAddSurrogate (publisher_id, subscriber_id, options, function(new_id){
                    log.info("Surrogate ?", new_id);

                    if (options.audio === undefined) options.audio = true;
                    if (options.video === undefined) options.video = true;

                    var args = [subscriber_id, publisher_id, options];

                    subscribers[publisher_id].push({id:subscriber_id, surrogate:new_id});
                    amqper.callRpc(getErizoQueue(publisher_id, subscriber_id), "addSubscriber", args, {callback: callback});
                    // Track subscriber locally
                });

            }
            */
        }
    };

    /*
     * Removes a publisher from the room. This also deletes the associated OneToManyProcessor.
     */
    that.removePublisher = function (publisher_id) {

        if (subscribers[publisher_id] !== undefined && publishers[publisher_id]!== undefined) {
            log.info("Removing publisher", publisher_id);

            var args = [publisher_id];
            amqper.callRpc(getErizoQueue(publisher_id), "removePublisher", args, undefined);

            // Remove tracks
            // TODO: Remove all surrogates
            var index = erizos[publishers[publisher_id]].publishers.indexOf(publisher_id);
            erizos[publishers[publisher_id]].publishers.splice(index, 1);

            delete subscribers[publisher_id];
            delete publishers[publisher_id];
            log.info('Removed muxer', publisher_id, ' muxers left ', Object.keys(publishers).length );
        }
    };

    /*
     * Removes a subscriber from the room. This also removes it from the associated OneToManyProcessor.
     */
    that.removeSubscriber = function (subscriber_id, publisher_id) {

        var index = subscribers[publisher_id].indexOf(subscriber_id);
        if (index !== -1) {
            log.info('Removing subscriber ', subscriber_id, 'to muxer ', publisher_id);

            var args = [subscriber_id, publisher_id];
            amqper.callRpc(getErizoQueue(publisher_id), "removeSubscriber", args, undefined);

            subscribers[publisher_id].splice(index, 1);
        }
    };

    /*
     * Removes all the subscribers related with a client.
     */
    that.removeSubscriptions = function (subscriber_id) {

        var publisher_id, index;

        log.info('Removing subscriptions of ', subscriber_id);

        for (publisher_id in subscribers) {
            if (subscribers.hasOwnProperty(publisher_id)) {
                index = subscribers[publisher_id].indexOf(subscriber_id);
                if (index !== -1) {
                    log.debug('Removing subscriber ', subscriber_id, 'from muxer ', publisher_id);

                    var args = [subscriber_id, publisher_id];
            		amqper.callRpc(getErizoQueue(publisher_id), "removeSubscriber", args, undefined);

            		// Remove tracks
                    subscribers[publisher_id].splice(index, 1);
                }
            }
        }
    };

    return that;
};
<commit_msg>Now RoomController will try to get a new ErizoJS id if a request timed out<commit_after>/*global require, exports, console, setInterval, clearInterval*/

var logger = require('./../common/logger').logger;

// Logger
var log = logger.getLogger("RoomController");

exports.RoomController = function (spec) {
    "use strict";

    var that = {},
        // {id: array of subscribers}
        subscribers = {},
        // {id: erizoJS_id}
        publishers = {},
        // {erizoJS_id: {publishers: [ids], ka_count: count}}
        erizos = {},

        // {id: ExternalOutput}
        externalOutputs = {};

    var amqper = spec.amqper;
    var ecch = spec.ecch;

    var KEEPALIVE_INTERVAL = 5*1000;
    var TIMEOUT_LIMIT = 2;
    var MAX_ERIZOJS_RETRIES = 2;

    var eventListeners = [];

    var callbackFor = function(erizo_id) {

        return function(ok) {
            if (!erizos[erizo_id]) return;

            if (ok !== true) {
                erizos[erizo_id].ka_count ++;

                if (erizos[erizo_id].ka_count > TIMEOUT_LIMIT) {

                    for (var p in erizos[erizo_id].publishers) {
                        log.error("Lost connection with ErizoJS, will remove publishers", erizo_id);
                        dispatchEvent("unpublish", erizos[erizo_id].publishers[p]);
                    }
                    ecch.deleteErizoJS(erizo_id);
                    delete erizos[erizo_id];
                }
            } else {
                erizos[erizo_id].ka_count = 0;
            }
        }
    };

    var sendKeepAlive = function() {
        for (var e in erizos) {
            amqper.callRpc("ErizoJS_" + e, "keepAlive", [], {callback: callbackFor(e)});
        }
    };

    var keepAliveLoop = setInterval(sendKeepAlive, KEEPALIVE_INTERVAL);

    var getErizoJS = function(callback) {
    	ecch.getErizoJS(function(erizo_id) {
            if (!erizos[erizo_id] && erizo_id !== 'timeout') {
                erizos[erizo_id] = {publishers: [], ka_count: 0};
            }
            callback(erizo_id);
        });
    };

    var getErizoQueue = function(publisher_id) {
        return "ErizoJS_" + publishers[publisher_id];
    };

    var dispatchEvent = function(type, event) {
        for (var event_id in eventListeners) {
            eventListeners[event_id](type, event);
        }

    };

/*  Prototype method to add a branch to a erizo Tree
    var shouldAddSurrogate = function(publisher_id, subscriber_id, options, callback){
        //Create new erizoJS, assign it as surrogate of publisher_id (subscribe), return id
        log.info("Adding a surrogate");
        getErizoJS(function(id){
            log.info("got a new Erizo with id", id);

//            log.info("Adding subscriber ", id, ' to publisher ', publisher_id);
            var options = {};
            if (options.audio === undefined) options.audio = true;
            if (options.video === undefined) options.video = true;

            var argsAddPub = [publisher_id, 300, true];
            log.info("Adding publisher to surrogate", "ErizoJS_"+id, "args",argsAddPub);
            var statusBranch = false;
            var statusRoot = false;
            var branchQueue = [];
            var rootQueue = [];
            log.info("calling addPublisher on branch");
            amqper.callRpc("ErizoJS_"+id, "addPublisher", argsAddPub, {callback: function(msg){
                log.info("Message from branch", msg.type);
                if (msg.type==='started'){
                    statusbranch = 'started';
                    log.info("branch Started");
                    if (branchQueue.length){
                        while (branchQueue.length>0){
                            var themessage = branchQueue.shift();
                            var argus = [publisher_id, undefined, themessage];
                            console.log("Queued msg from Erizoroot, sending to Erizobranch", argus);
                            amqper.callRpc("ErizoJS_"+id, "processSignaling", argus, {});
                        }

                    }

                }else if (msg.type === 'offer' || msg.type ==='candidate'){
                    var argsSignaling = [publisher_id, id, msg];
                    console.log("msg from Erizobranch, should to Erizoroot?");
                    if (statusroot){
                        console.log("Yes, sending", argsSignaling);
                        amqper.callRpc(getErizoQueue(publisher_id, id), "processSignaling", argsSignaling, {});
                    }else{
                        console.log("No, queuing", argsSignaling);
                        rootQueue.push(msg);
                    }
                }else if (msg.type === 'ready'){
                    log.info("Surrogate ready, firing after a second");
                    setTimeout (callback(id), 1000);
                }

            }});

            log.info("calling addSubscriber on root");
            var argsAddSub = [id, publisher_id, options];
            amqper.callRpc(getErizoQueue(publisher_id), "addSubscriber", argsAddSub, {callback: function(mess){
                log.info("Message from root", mess.type);
                if (mess.type==='started'){
                    statusroot = 'started';
                    log.info("root Started");
                    if (rootQueue.length){
                        while (rootQueue.length>0){
                            var themessage = rootQueue.shift();
                            var argsSignaling = [publisher_id, id, themessage];
                            console.log("Queued msg from Erizobranch, sending to Erizoroot", argsSignaling);
                            amqper.callRpc(getErizoQueue(publisher_id, id), "processSignaling", argsSignaling, {});
                        }

                    }

                }else if (mess.type === 'answer' || mess.type ==='candidate'){
                    if (mess.type === 'answer'){
                        mess.type = 'offer';
                    }
                    var argus = [publisher_id, subscriber_id, mess];

                    console.log("signalling from Erizoroot, should send to branch?");
                    if (statusbranch){
                        console.log("Yes, sending", argus);
                        amqper.callRpc("ErizoJS_"+id, "processSignaling", argus, {});
                    }else{
                        console.log("No, queuing", argus);
                        branchQueue.push(mess);
                    }
                }
            }});
            subscribers[publisher_id].push({id:subscriber_id, surrogate_id:id});
            // Track subscriber locally

        });

    }
*/
    that.addEventListener = function(eventListener) {
        eventListeners.push(eventListener);
    };

    that.addExternalInput = function (publisher_id, url, callback) {

        if (publishers[publisher_id] === undefined) {

            log.info("Adding external input peer_id ", publisher_id);

            getErizoJS(function(erizo_id) {
                // then we call its addPublisher method.
    	        var args = [publisher_id, url];

                // Track publisher locally
//                publishers[publisher_id] = {};
                publishers[publisher_id] = erizo_id;
                subscribers[publisher_id] = [];

                amqper.callRpc(getErizoQueue(publisher_id), "addExternalInput", args, {callback: callback});

                erizos[erizo_id].publishers.push(publisher_id);

            });
        } else {
            log.info("Publisher already set for", publisher_id);
        }
    };

    that.addExternalOutput = function (publisher_id, url, callback) {
        if (publishers[publisher_id] !== undefined) {
            log.info("Adding ExternalOutput to " + publisher_id + " url " + url);

            var args = [publisher_id, url];

            amqper.callRpc(getErizoQueue(publisher_id), "addExternalOutput", args, undefined);

            // Track external outputs
            externalOutputs[url] = publisher_id;

            // Track publisher locally
            // publishers[publisher_id] = publisher_id;
            // subscribers[publisher_id] = [];
            callback('success');
        } else {
            callback('error');
        }

    };

    that.removeExternalOutput = function (url, callback) {
        var publisher_id = externalOutputs[url];

        if (publisher_id !== undefined && publishers[publisher_id] != undefined) {
            log.info("Stopping ExternalOutput: url " + url);

            var args = [publisher_id, url];
            amqper.callRpc(getErizoQueue(publisher_id), "removeExternalOutput", args, undefined);

            // Remove track
            delete externalOutputs[url];
            callback(true);
        } else {
            callback(null, 'This stream is not being recorded');
        }
    };

    that.processSignaling = function (streamId, peerId, msg) {

        if (publishers[streamId] !== undefined) {

//            log.info("Sending signaling mess to erizoJS of st ", streamId, ' of peer ', peerId);

            var args = [streamId, peerId, msg];

            amqper.callRpc(getErizoQueue(streamId), "processSignaling", args, {});

        }
    };

    /*
     * Adds a publisher to the room. This creates a new OneToManyProcessor
     * and a new WebRtcConnection. This WebRtcConnection will be the publisher
     * of the OneToManyProcessor.
     */
    that.addPublisher = function (publisher_id, options, callback, retries) {
        if (retries === undefined)
            retries = 0;

        if (publishers[publisher_id] === undefined) {

            log.info("Adding publisher peer_id ", publisher_id, "options", options);;

            // We create a new ErizoJS with the publisher_id.
            getErizoJS(function(erizo_id) {

                if (erizo_id === 'timeout') {
                    log.error("Cannot contact ErizoAgent to add Publisher -- timeout");
                    callback('timeout-agent');
                    return;
                }
            	log.debug("ErizoJS id:", erizo_id, "assigned for publisher", publisher_id);
                // Track publisher locally

                // then we call its addPublisher method.
                var args = [publisher_id, options];
                publishers[publisher_id] = erizo_id;
                subscribers[publisher_id] = [];

                amqper.callRpc(getErizoQueue(publisher_id), "addPublisher", args, {callback: function (data){
                    if (data === 'timeout'){
                        if (retries < MAX_ERIZOJS_RETRIES){
                            publishers[publisher_id] = undefined;
                            retries++;
                            log.warn("ErizoJS timed out, trying again to publish", publisher_id, "number of tries", retries);
                            that.addPublisher(publisher_id, options,callback);
                            return;
                        }
                        log.error("Can not contact ErizoJS", publisher_id , " failed add Publisher -- timeout");
                        callback('timeout-erizojs');
                        return;
                    }else{
                        callback(data);
                    }
                }});

                erizos[erizo_id].publishers.push(publisher_id);
            });

        } else {
            log.warn("Publisher already set for", publisher_id);
        }
    };

    /*
     * Adds a subscriber to the room. This creates a new WebRtcConnection.
     * This WebRtcConnection will be added to the subscribers list of the
     * OneToManyProcessor.
     */
    that.addSubscriber = function (subscriber_id, publisher_id, options, callback, retries) {
        if (subscriber_id === null){
          callback("Error: null subscriber_id");
          return;
        }
        if (retries === undefined)
            retries = 0;

        if (publishers[publisher_id] !== undefined && subscribers[publisher_id].indexOf(subscriber_id) === -1) {
            log.info("Adding subscriber ", subscriber_id, ' to publisher ', publisher_id, "options", options);

//            if (true){

                if (options.audio === undefined) options.audio = true;
                if (options.video === undefined) options.video = true;

                var args = [subscriber_id, publisher_id, options];
                //TODO: Potential problem here if erizoJS is not reachable
                amqper.callRpc(getErizoQueue(publisher_id, undefined), "addSubscriber", args, {callback: function (data){
                    if (data === 'timeout'){
                        if (retries < MAX_ERIZOJS_RETRIES){
                            retries++;
                            log.warn("ErizoJS timed out, trying again to subscribe to ", publisher_id, "from", subscriber_id, "number of tries", retries);
                            that.addSubscriber(subscriber_id, publisher_id, options, callback, retries);
                            return;
                        }
                        log.error("Can not contact to ErizoJS", publisher_id , " failed add Subscriber -- timeout");
                        callback('timeout');
                        return;
                    }else if (data === 'initializing'){
                        subscribers[publisher_id].push(subscriber_id);
                    }
                    callback(data);
                }});

/*            }else{ // Prototype for erizo_trees
                shouldAddSurrogate (publisher_id, subscriber_id, options, function(new_id){
                    log.info("Surrogate ?", new_id);

                    if (options.audio === undefined) options.audio = true;
                    if (options.video === undefined) options.video = true;

                    var args = [subscriber_id, publisher_id, options];

                    subscribers[publisher_id].push({id:subscriber_id, surrogate:new_id});
                    amqper.callRpc(getErizoQueue(publisher_id, subscriber_id), "addSubscriber", args, {callback: callback});
                    // Track subscriber locally
                });

            }
            */
        }
    };

    /*
     * Removes a publisher from the room. This also deletes the associated OneToManyProcessor.
     */
    that.removePublisher = function (publisher_id) {

        if (subscribers[publisher_id] !== undefined && publishers[publisher_id]!== undefined) {
            log.info("Removing publisher", publisher_id);

            var args = [publisher_id];
            amqper.callRpc(getErizoQueue(publisher_id), "removePublisher", args, undefined);

            // Remove tracks
            // TODO: Remove all surrogates
            var index = erizos[publishers[publisher_id]].publishers.indexOf(publisher_id);
            erizos[publishers[publisher_id]].publishers.splice(index, 1);

            delete subscribers[publisher_id];
            delete publishers[publisher_id];
            log.info('Removed muxer', publisher_id, ' muxers left ', Object.keys(publishers).length );
        }
    };

    /*
     * Removes a subscriber from the room. This also removes it from the associated OneToManyProcessor.
     */
    that.removeSubscriber = function (subscriber_id, publisher_id) {

        var index = subscribers[publisher_id].indexOf(subscriber_id);
        if (index !== -1) {
            log.info('Removing subscriber ', subscriber_id, 'to muxer ', publisher_id);

            var args = [subscriber_id, publisher_id];
            amqper.callRpc(getErizoQueue(publisher_id), "removeSubscriber", args, undefined);

            subscribers[publisher_id].splice(index, 1);
        }
    };

    /*
     * Removes all the subscribers related with a client.
     */
    that.removeSubscriptions = function (subscriber_id) {

        var publisher_id, index;

        log.info('Removing subscriptions of ', subscriber_id);

        for (publisher_id in subscribers) {
            if (subscribers.hasOwnProperty(publisher_id)) {
                index = subscribers[publisher_id].indexOf(subscriber_id);
                if (index !== -1) {
                    log.debug('Removing subscriber ', subscriber_id, 'from muxer ', publisher_id);

                    var args = [subscriber_id, publisher_id];
            		amqper.callRpc(getErizoQueue(publisher_id), "removeSubscriber", args, undefined);

            		// Remove tracks
                    subscribers[publisher_id].splice(index, 1);
                }
            }
        }
    };

    return that;
};
<|endoftext|>


<commit_before>/**
 * App Model
 *
 * @type type
 */

var Models  = require('bulk-require')(__dirname + '/..', ['./models/**/*.js']).models;

module.exports  = function() {

    var objs    = {};

    _(Models).each(function(model, key) {

        var tmpModel            = Backbone.Model.extend(_({}).extend(model, {

            objCollection: null,

            setCollection: function(data) {
                var _this           = this;
                var tmpCollection   = Backbone.Collection.extend({
                    model: Backbone.Model.extend(model)
                });
                _this.objCollection = new tmpCollection(data);
                return true;
            },

            toCollection: function() {
                return this.objCollection;
            }

        }));

        objs[key]       = new tmpModel();

    });

    return objs;

};<commit_msg>Models to objects and folders<commit_after>/**
 * App Model
 *
 * @type type
 */

var TmpFolders  = require('bulk-require')(__dirname + '/..', ['./models/**/*.js']).models   || {};
var Models      = require('bulk-require')(__dirname + '/..', ['./models/*.js']).models      || {};

// Put only folders with objects
var Folders     = {};
for(let i in TmpFolders) {
    if(Models[i] === undefined) {
        Folders[i]  = TmpFolders[i];
    }
}

module.exports  = function() {

    var objs    = {};

    var toBackboneModel = function(model, key, folder) {

        var tmpModel    = Backbone.Model.extend(_({}).extend(model, {

            objCollection: null,

            setCollection: function(data) {
                var _this           = this;
                var tmpCollection   = Backbone.Collection.extend({
                    model: !folder ? Backbone.Model.extend(model) : Backbone.Model.extend(folder[key])
                });
                _this.objCollection = new tmpCollection(data);
                return true;
            },

            toCollection: function() {
                return this.objCollection;
            }

        }));

        return new tmpModel();

    };

    // Each Models
    _(Models).each(function(model, key) {

        objs[key]    = toBackboneModel(model, key);

    });

    // Each Folders
    _(Folders).each(function(models, folder) {

        // Set folder
        objs[folder] = {};

        // Each Models
        _(models).each(function(model, key) {

            objs[folder][key]   = toBackboneModel(model, key, folder);

        });

    });

    return objs;

};<|endoftext|>