<commit_before>// Reasons.js
// Copyright (c) 2017 Dave Kinkead
// Available under the MIT license

const screenWidth = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth
const screenHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
const screenCenter = {x: screenWidth/2, y: screenHeight/2}
const fontSize = 14
const boxPaddingFactor = 1
const boxHeightInLines = 3
const boxHeight = fontSize * (boxHeightInLines + 2.5)
const boxWidth = (width) => {
  return (parseInt(width)+boxPaddingFactor) * fontSize/2
}
const reasons = []
const relations = []

const reasonWidth = 250

let clicks = 1    // this is a temp id marker
let currentReason

let svg = createSVG()
let overlay = createOverlay()
let button = createButton()

document.addEventListener('reposition', reposition)
reposition()

//  Object Constructors

function Reason(content, x, y) {
  let reason = build('div', {id: clicks++}, {
    class:'reason',
    style: 'position: absolute; top: '+y+'px; left: '+x+'px;',
    draggable: true
  })

  reason.innerHTML = content

  reason.addEventListener('dragstart', (event) => {
    currentReason = event.target
    event.target.classList.add('current')
    event.dataTransfer.setData('text/html', event.target.outerHTML)
    event.dataTransfer.effectAllowed = "copy"
  })

  reason.addEventListener('drag', (event) => {
  }, false)

  reason.addEventListener('dragend', (event) => {
    currentReason = event.target
    event.target.classList.remove('current')
  })

  reason.addEventListener('dragenter', (event) => {
    event.preventDefault()
    if (event.target.classList)
      event.target.classList.add('droppable')
  }, false)

  reason.addEventListener('dragover', (event) => {
    event.preventDefault()
  })

  reason.addEventListener('dragleave', (event) => {
    if (event.target.classList)
      event.target.classList.remove('droppable')
  }, false)

  reason.addEventListener('drop', (event) => {
    event.preventDefault()
    event.target.classList.remove('droppable')
    if (currentReason.id !== event.target.id)
      relations.push(new Relation(currentReason, 'supports', event.target))
      reposition()
      // document.dispatchEvent(new CustomEvent('reposition', {foo:'bar'}))
  }, false)

  reason.addEventListener('click', (event) => {
    document.querySelector('#overlay').classList.add('display')
    reason.classList.add('front')
    reason.setAttribute('contentEditable', true)
  })

  document.querySelector('#canvas').appendChild(reason)
  return reason
}

function Relation(element, type, target) {
  this.reasons = [element.id]
  this.type = type
  this.target = target.id
  drawEdge(element.id, target.id)
  return this
}

function find(id, arr) {
  return arr.find((reason) => {
    return reason.id == id
  })
}

function drawEdge(from, to) {
  let reason = find(from, reasons)
  let target = find(to, reasons)
  let ec = getCenter(reason)
  let tc = getCenter(target)

  let path = buildNS('path', {id: reason.id+ '-' +target.id}, {
    class: 'edge',
    stroke: '#CCC',
    'stroke-width': 5,
    d: 'M'+ec.x+' '+ec.y+' L '+tc.x+' '+tc.y
  })

  svg.appendChild(path)
}

function Layout(reasons, relations) {
  let layers = []

  //  1. clone reasons and find the conclusion
  let orphans = reasons.slice(0)
  let related = []
  relations.forEach((relation) => {
    related = related.concat(remove(orphans, relation.target))
    related = related.concat(remove(orphans, relation.reasons))
  })

  //  2. add conclusions to layout
  let conclusions = related.filter((reason) => {
    let children = [].concat.apply([], relations.map((relation) => {
        return relation.reasons
      })
    )
    return !(children.indexOf(reason) > -1)
  })

  //  3. from the conclusion, add children to the layers
  layers = addLevel(conclusions, layers)

  //  4. finally add the orphans
  if (orphans.length > 0)
    layers.push(orphans)

  return layers

  function addLevel(current, layout) {
    if (current.length > 0) {
      layout.push(current)
      let children = collectChildren(current)
      if (children.length > 0) {
        addLevel(children, layout)
      }
    }
    return layout
  }

  function collectChildren(arr) {
    let list = arr.map((id) => {
      return childrenOf(id)
    })
    return [].concat.apply([], list)
  }

  //  return the children ids of a reason
  function childrenOf(id) {
    let kids = relations.filter((relation) => {
      return relation.target == id
    }).map((relation) => {
      return relation.reasons
    })
    return [].concat.apply([],kids)
  }

  function remove(arr, item) {
    let results = []
    if (typeof item !== 'object')
      item = [item]
    item.forEach((i) => {
      let index = arr.indexOf(i)
      if (index > -1) {
        results = results.concat(arr.splice(index, 1))
      }
    })
    return results
  }
}

//  Helper Functions

function createSVG() {
  let svg = buildNS('svg', {}, {height: screenHeight,  version: '1.1', width: screenWidth, xmlns: "http://www.w3.org/2000/svg", 'xmlns:xlink':"http://www.w3.org/1999/xlink"})
  document.querySelector('#canvas').appendChild(svg)

  document.querySelector('#canvas').addEventListener('dblclick', (event) => {
    let reason = new Reason('When I get around to it, you will be able to edit me!', event.clientX, event.clientY)
    reasons.push(reason)
    reposition()
  })

  return svg
}

function createOverlay() {
  let overlay = build('div', {id: 'overlay'}, {})
  overlay.addEventListener('click', (event) => {
    overlay.classList.remove('display')
    document.querySelectorAll('.reason').forEach((reason) => {
      reason.classList.remove('front')
      reason.setAttribute('contentEditable', false)
    })
  })
  document.body.appendChild(overlay)

  return overlay
}

function createButton() {
  let button = build('input', {id: 'create-map-button'}, {
    name: 'clear-map-button',
    type: 'submit',
    value: 'Clear Map'
  })

  button.setAttribute('style', 'position:fixed;bottom:2rem;right:2rem;padding:1rem;border:1px solid #CCC;border-radius:3px;display:block')

  button.addEventListener('click', () => {
    sessionStorage.setItem('reasons', null)
    location.reload()
  })

  document.body.appendChild(button)

  return button
}

function getCenter(element) {
  let box = element.getBoundingClientRect()
  return {x: box.left + box.width/2, y: box.top + box.height/2}
}

//  element build helpers -- extract these later
function buildNS(type, options, attributes) {
  let node = document.createElementNS('http://www.w3.org/2000/svg', type)
  for (var key in options) {
    node[key] = options[key]
  }
  for (var key in attributes) {
    node.setAttribute(key, attributes[key])
  }
  return node
}

function build(type, options, attributes) {
  let node = document.createElement(type)
  for (var key in options) {
    node[key] = options[key]
  }
  for (var key in attributes) {
    node.setAttribute(key, attributes[key])
  }
  return node
}

function reposition() {
  //  get the layout of ids
  let ids = reasons.map((r) => {
    return r.id
  })
  let layout = new Layout(ids, relations)
  console.log(layout)

  //  position reasons based on layout
  let maxElementsPerLine = Math.floor(screenWidth/300)
  let lineHeight = screenHeight/(layout.length+1)

  layout.forEach((layer, index) => {
    let layerY = screenHeight - (lineHeight*(index+1))
    layer.forEach((id) => {
      let reason = reasons.find((r) => {
        return r.id == id
      })
      let translation = {
        x: 0,
        y: (screenHeight - layerY)-reason.offsetTop-75
      }
      reason.style.transform = 'translate('+0+'px, '+translation.y +'px)'
    })
  })

  //  re-position relations
  let paths = document.querySelectorAll('.edge')
  paths.forEach((path) => {
    path.id.split('-')
    path.remove()
    let from = find(path.id.split('-')[0], reasons)
    let to = find(path.id.split('-')[1], reasons)
    console.log(from)
    drawEdge(from.id, to.id)
    // let ec = getCenter(from)
    // let tc = getCenter(to)
    // path.setAttribute('d', 'M'+ec.x+' '+ec.y+' L '+tc.x+' '+tc.y)
  })

}

// if (postData !== null) {
//   let layoutMatrix = []
//   let layoutLine = 0
//   let lineHeight = 250
//   while (postData.length > 0) {
//     if (postData.length > maxElementsPerLine) {
//       layoutMatrix.push(postData.splice(0,maxElementsPerLine))
//     } else {
//       layoutMatrix.push(postData.splice(0))
//     }
//   }

//   layoutMatrix.forEach((line, lineIndex) => {
//     let lineBuffer = (screenWidth - (reasonWidth * line.length+1)) / (line.length+1)
//     line.forEach((reason, index) => {
//       new Reason(reason, lineBuffer + (lineBuffer+reasonWidth)*index , lineHeight)
//     })
//     lineHeight += 250
//   })
// }

// function reposition() {
//   const gravity = (distance) => {
//     return Math.min(distance, 10)
//   }
//   const displacement = (distance) => {
//     if (distance > 100) {
//       return 0
//     } else {
//       return 50
//     }
//   }

//   let repositioning = 50

//   while (repositioning-- > 0) {
//     reasons.forEach((reason, index) => {
//       //  we start with either the existing vector or a blank
//       let vc = vectors[index] || {x: 0, y: 0}

//       //  pull reasons to the screen center
//       let sc = getVector(addVectors(getOffset(reason), vc), screenCenter, gravity)
//       vc = addVectors(vc, sc)

//       //  displace off other reasons
//       reasons.forEach((other) => {
//         if (other.id !== reason.id) {
//           if (overlaps(reason, other)) {
//             console.log(reason.id + ' overlaps ' + other.id)
//           }
//         }
//       })

//       vectors[index] = vc
//     })
//   }

//   reasons.forEach((reason, index) => {
//     reason.style.transform = 'translate('+(vectors[index].x)+'px, '+(vectors[index].y)+'px)'
//   })

// //  nasty hack...
// function overlaps(a, b) {
//   if (Math.abs(getCenter(a).x - getCenter(b).x) < 200) {
//     return true
//   } else {
//     return false
//   }

// }

// function getAngleBetween(a, b) {
//   return Math.atan2(b.y-a.y, b.x-a.x)
// }

// function getDistanceBetween(a, b) {
//   return Math.sqrt( Math.pow(b.x-a.x, 2) + Math.pow(b.y-a.y, 2) )
// }

// function getVector(current, other, fn) {
//   let direction = getAngleBetween(current, other)
//   let distance = getDistanceBetween(current, other)
//   return {
//     x: Math.cos(direction) * fn(distance),
//     y: Math.sin(direction) * fn(distance)
//   }
// }

// function getOffset(reason) {
//   let box = reason.getBoundingClientRect()
//   return {
//     x: (reason.offsetLeft + box.width/2),
//     y: (reason.offsetTop + box.height/2)
//   }
// }

// function addVectors(a, b) {
//   return {x: a.x + b.x, y: a.y + b.y}
// }

// function subVectors(a, b) {
//   return {x: a.x - b.x, y: a.y - b.y}
// }

// function find(id, array) {
//   let match
//   array.forEach((reason) => {
//     if (reason.id == id.toString()) {
//       match = reason
//     }
//   })
//   return match
// }

//  Actions on page load -- get reasons from session storage
//    and reset the session data afterwards
// postData = JSON.parse(sessionStorage.getItem('reasons'))

// //  Replace this with some kind of force displayment between reasons
// let maxElementsPerLine = Math.floor(screenWidth/300)

// if (postData !== null) {
//   let layoutMatrix = []
//   let layoutLine = 0
//   let lineHeight = 250
//   while (postData.length > 0) {
//     if (postData.length > maxElementsPerLine) {
//       layoutMatrix.push(postData.splice(0,maxElementsPerLine))
//     } else {
//       layoutMatrix.push(postData.splice(0))
//     }
//   }

//   layoutMatrix.forEach((line, lineIndex) => {
//     let lineBuffer = (screenWidth - (reasonWidth * line.length+1)) / (line.length+1)
//     line.forEach((reason, index) => {
//       new Reason(reason, lineBuffer + (lineBuffer+reasonWidth)*index , lineHeight)
//     })
//     lineHeight += 250
//   })
// }<commit_msg>Implement hierachical layout -- horizontal<commit_after>// Reasons.js
// Copyright (c) 2017 Dave Kinkead
// Available under the MIT license

/*  we have state [reasons, relations, counter for reason IDs] and a view

I want to add reasons to the state

I want to add relations between reasons to the state

I want the state to render correctly and update

So I need 2 functions: update(state) & render(state)
  - update(state) will add & remove reasons and relations.
  - render(state) will draw & update the UI
*/

// let Reasons = {

//   state: {
//     reasons: [],
//     relations: [],
//     tree: {}
//   },

//   init: () => {},

//   // reasons + relations -> tree
//   update: (reasons, relations) => {},

//   // updates the dom based on the tree
//   render: (tree) => {}
// }

const screenWidth = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth
const screenHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
const screenCenter = {x: screenWidth/2, y: screenHeight/2}
const fontSize = 14
const boxPaddingFactor = 1
const boxHeightInLines = 3
const boxHeight = fontSize * (boxHeightInLines + 2.5)
const boxWidth = (width) => {
  return (parseInt(width)+boxPaddingFactor) * fontSize/2
}
const reasons = []
const relations = []

const reasonWidth = 250

let clicks = 1    // this is a temp id marker
let currentReason

let svg = createSVG()
let overlay = createOverlay()
let button = createButton()

document.addEventListener('reposition', reposition)
reposition()

//-------------------------------
//  Object Constructors
//-------------------------------

function Reason(content, x, y) {
  let reason = build('div', {id: clicks++}, {
    class:'reason',
    style: 'position: absolute; top: '+y+'px; left: '+x+'px;',
    draggable: true
  })

  reason.innerHTML = content

  reason.addEventListener('dragstart', (event) => {
    currentReason = event.target
    event.target.classList.add('current')
    event.dataTransfer.setData('text/html', event.target.outerHTML)
    event.dataTransfer.effectAllowed = "copy"
  })

  reason.addEventListener('drag', (event) => {
  }, false)

  reason.addEventListener('dragend', (event) => {
    currentReason = event.target
    event.target.classList.remove('current')
  })

  reason.addEventListener('dragenter', (event) => {
    event.preventDefault()
    if (event.target.classList)
      event.target.classList.add('droppable')
  }, false)

  reason.addEventListener('dragover', (event) => {
    event.preventDefault()
  })

  reason.addEventListener('dragleave', (event) => {
    if (event.target.classList)
      event.target.classList.remove('droppable')
  }, false)

  reason.addEventListener('drop', (event) => {
    event.preventDefault()
    event.target.classList.remove('droppable')
    if (currentReason.id !== event.target.id)
      relations.push(new Relation(currentReason, 'supports', event.target))
      reposition()
      // document.dispatchEvent(new CustomEvent('reposition', {foo:'bar'}))
  }, false)

  reason.addEventListener('click', (event) => {
    document.querySelector('#overlay').classList.add('display')
    reason.classList.add('front')
    reason.setAttribute('contentEditable', true)
  })

  document.querySelector('#canvas').appendChild(reason)
  return reason
}

function Relation(element, type, target) {
  this.reasons = [element.id]
  this.type = type
  this.target = target.id
  drawEdge(element.id, target.id)
  return this
}

//----------------------------------
//  GUI functions
//----------------------------------

//  A layout is the container for reasons and their relations
function Layout(reasons, relations) {
  let layers = []

  //  1. clone reasons and find the conclusion
  let orphans = reasons.slice(0)
  let related = []
  relations.forEach((relation) => {
    related = related.concat(remove(orphans, relation.target))
    related = related.concat(remove(orphans, relation.reasons))
  })

  //  2. add conclusions to layout
  let conclusions = related.filter((reason) => {
    let children = [].concat.apply([], relations.map((relation) => {
        return relation.reasons
      })
    )
    return !(children.indexOf(reason) > -1)
  })

  //  3. from the conclusion, add children to the layers
  layers = addLevel(conclusions, layers)

  //  4. finally add the orphans
  if (orphans.length > 0)
    layers.push(orphans)

  return layers

  function addLevel(current, layout) {
    if (current.length > 0) {
      layout.push(current)
      let children = collectChildren(current)
      if (children.length > 0) {
        addLevel(children, layout)
      }
    }
    return layout
  }

  function collectChildren(arr) {
    let list = arr.map((id) => {
      return childrenOf(id)
    })
    return [].concat.apply([], list)
  }

  //  return the children ids of a reason
  function childrenOf(id) {
    let kids = relations.filter((relation) => {
      return relation.target == id
    }).map((relation) => {
      return relation.reasons
    })
    return [].concat.apply([],kids)
  }

  function remove(arr, item) {
    let results = []
    if (typeof item !== 'object')
      item = [item]
    item.forEach((i) => {
      let index = arr.indexOf(i)
      if (index > -1) {
        results = results.concat(arr.splice(index, 1))
      }
    })
    return results
  }
}

function drawEdge(from, to) {
  let reason = find(from, reasons)
  let target = find(to, reasons)
  let ec = getCenter(reason)
  let tc = getCenter(target)

  let path = buildNS('path', {id: reason.id+ '-' +target.id}, {
    class: 'edge',
    stroke: '#CCC',
    'stroke-width': 5,
    d: 'M'+ec.x+' '+ec.y+' L '+tc.x+' '+tc.y
  })

  svg.appendChild(path)
}

function createSVG() {
  let svg = buildNS('svg', {}, {height: screenHeight,  version: '1.1', width: screenWidth, xmlns: "http://www.w3.org/2000/svg", 'xmlns:xlink':"http://www.w3.org/1999/xlink"})
  document.querySelector('#canvas').appendChild(svg)

  document.querySelector('#canvas').addEventListener('dblclick', (event) => {
    let reason = new Reason('When I get around to it, you will be able to edit me!', event.clientX, event.clientY)
    reasons.push(reason)
    reposition()
  })

  return svg
}

function createOverlay() {
  let overlay = build('div', {id: 'overlay'}, {})
  overlay.addEventListener('click', (event) => {
    overlay.classList.remove('display')
    document.querySelectorAll('.reason').forEach((reason) => {
      reason.classList.remove('front')
      reason.setAttribute('contentEditable', false)
    })
  })
  document.body.appendChild(overlay)

  return overlay
}

function createButton() {
  let button = build('input', {id: 'create-map-button'}, {
    name: 'clear-map-button',
    type: 'submit',
    value: 'Clear Map'
  })

  button.setAttribute('style', 'position:fixed;bottom:2rem;right:2rem;padding:1rem;border:1px solid #CCC;border-radius:3px;display:block')

  button.addEventListener('click', () => {
    sessionStorage.setItem('reasons', null)
    location.reload()
  })

  document.body.appendChild(button)

  return button
}

function getCenter(element) {
  let box = element.getBoundingClientRect()
  return {x: box.left + box.width/2, y: box.top + box.height/2}
}

//  element build helpers -- extract these later
function buildNS(type, options, attributes) {
  let node = document.createElementNS('http://www.w3.org/2000/svg', type)
  for (var key in options) {
    node[key] = options[key]
  }
  for (var key in attributes) {
    node.setAttribute(key, attributes[key])
  }
  return node
}

function build(type, options, attributes) {
  let node = document.createElement(type)
  for (var key in options) {
    node[key] = options[key]
  }
  for (var key in attributes) {
    node.setAttribute(key, attributes[key])
  }
  return node
}

function reposition() {
  //  get the layout of ids
  let ids = reasons.map((r) => {
    return r.id
  })
  let layout = new Layout(ids, relations)

  //  position reasons based on layout
  let maxElementsPerLine = Math.floor(screenWidth / 300)
  let lineHeight = screenHeight / (layout.length+1)

  layout.forEach((line, index) => {
    let lineY = screenHeight - (lineHeight*(index+1))
    let elementWidth = screenWidth / (line.length+1)

    line.forEach((id, i) => {
      let reason = reasons.find((r) => {
        return r.id == id
      })
      let translation = {
        x: (screenWidth - elementWidth * (i+1))-reason.offsetLeft-125,
        y: (screenHeight - lineY)-reason.offsetTop-75
      }
      reason.style.transform = 'translate('+translation.x+'px, '+translation.y +'px)'
    })
  })

  //  re-position relations with delay
  setTimeout(repositionRelations, 500)

}

function repositionRelations() {
  let paths = document.querySelectorAll('.edge')
  paths.forEach((path) => {
    path.id.split('-')
    path.remove()
    let from = find(path.id.split('-')[0], reasons)
    let to = find(path.id.split('-')[1], reasons)
    drawEdge(from.id, to.id)
    // let ec = getCenter(from)
    // let tc = getCenter(to)
    // path.setAttribute('d', 'M'+ec.x+' '+ec.y+' L '+tc.x+' '+tc.y)
  })
}

//----------------------------------
//  Helper functions
//----------------------------------

function find(id, arr) {
  return arr.find((reason) => {
    return reason.id == id
  })
}

// if (postData !== null) {
//   let layoutMatrix = []
//   let layoutLine = 0
//   let lineHeight = 250
//   while (postData.length > 0) {
//     if (postData.length > maxElementsPerLine) {
//       layoutMatrix.push(postData.splice(0,maxElementsPerLine))
//     } else {
//       layoutMatrix.push(postData.splice(0))
//     }
//   }

//   layoutMatrix.forEach((line, lineIndex) => {
//     let lineBuffer = (screenWidth - (reasonWidth * line.length+1)) / (line.length+1)
//     line.forEach((reason, index) => {
//       new Reason(reason, lineBuffer + (lineBuffer+reasonWidth)*index , lineHeight)
//     })
//     lineHeight += 250
//   })
// }

// function reposition() {
//   const gravity = (distance) => {
//     return Math.min(distance, 10)
//   }
//   const displacement = (distance) => {
//     if (distance > 100) {
//       return 0
//     } else {
//       return 50
//     }
//   }

//   let repositioning = 50

//   while (repositioning-- > 0) {
//     reasons.forEach((reason, index) => {
//       //  we start with either the existing vector or a blank
//       let vc = vectors[index] || {x: 0, y: 0}

//       //  pull reasons to the screen center
//       let sc = getVector(addVectors(getOffset(reason), vc), screenCenter, gravity)
//       vc = addVectors(vc, sc)

//       //  displace off other reasons
//       reasons.forEach((other) => {
//         if (other.id !== reason.id) {
//           if (overlaps(reason, other)) {
//             console.log(reason.id + ' overlaps ' + other.id)
//           }
//         }
//       })

//       vectors[index] = vc
//     })
//   }

//   reasons.forEach((reason, index) => {
//     reason.style.transform = 'translate('+(vectors[index].x)+'px, '+(vectors[index].y)+'px)'
//   })

// //  nasty hack...
// function overlaps(a, b) {
//   if (Math.abs(getCenter(a).x - getCenter(b).x) < 200) {
//     return true
//   } else {
//     return false
//   }

// }

// function getAngleBetween(a, b) {
//   return Math.atan2(b.y-a.y, b.x-a.x)
// }

// function getDistanceBetween(a, b) {
//   return Math.sqrt( Math.pow(b.x-a.x, 2) + Math.pow(b.y-a.y, 2) )
// }

// function getVector(current, other, fn) {
//   let direction = getAngleBetween(current, other)
//   let distance = getDistanceBetween(current, other)
//   return {
//     x: Math.cos(direction) * fn(distance),
//     y: Math.sin(direction) * fn(distance)
//   }
// }

// function getOffset(reason) {
//   let box = reason.getBoundingClientRect()
//   return {
//     x: (reason.offsetLeft + box.width/2),
//     y: (reason.offsetTop + box.height/2)
//   }
// }

// function addVectors(a, b) {
//   return {x: a.x + b.x, y: a.y + b.y}
// }

// function subVectors(a, b) {
//   return {x: a.x - b.x, y: a.y - b.y}
// }

// function find(id, array) {
//   let match
//   array.forEach((reason) => {
//     if (reason.id == id.toString()) {
//       match = reason
//     }
//   })
//   return match
// }

//  Actions on page load -- get reasons from session storage
//    and reset the session data afterwards
// postData = JSON.parse(sessionStorage.getItem('reasons'))

// //  Replace this with some kind of force displayment between reasons
// let maxElementsPerLine = Math.floor(screenWidth/300)

// if (postData !== null) {
//   let layoutMatrix = []
//   let layoutLine = 0
//   let lineHeight = 250
//   while (postData.length > 0) {
//     if (postData.length > maxElementsPerLine) {
//       layoutMatrix.push(postData.splice(0,maxElementsPerLine))
//     } else {
//       layoutMatrix.push(postData.splice(0))
//     }
//   }

//   layoutMatrix.forEach((line, lineIndex) => {
//     let lineBuffer = (screenWidth - (reasonWidth * line.length+1)) / (line.length+1)
//     line.forEach((reason, index) => {
//       new Reason(reason, lineBuffer + (lineBuffer+reasonWidth)*index , lineHeight)
//     })
//     lineHeight += 250
//   })
// }<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import { Animated, Easing } from 'react-native';
import CircularProgress from './CircularProgress';
const AnimatedProgress = Animated.createAnimatedComponent(CircularProgress);

export default class AnimatedCircularProgress extends React.PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      fillAnimation: new Animated.Value(props.prefill),
    };
  }

  componentDidMount() {
    this.animate();
  }

  componentDidUpdate(prevProps) {
    if (prevProps.fill !== this.props.fill) {
      this.animate();
    }
  }

  reAnimate(prefill, toVal, dur, ease) {
    this.setState(
      {
        fillAnimation: new Animated.Value(prefill),
      },
      () => this.animate(toVal, dur, ease)
    );
  }

  animate(toVal, dur, ease) {
    const toValue = toVal >= 0 ? toVal : this.props.fill;
    const duration = dur || this.props.duration;
    const easing = ease || this.props.easing;
    const useNativeDriver = this.props.useNativeDriver;

    const anim = Animated.timing(this.state.fillAnimation, {
      useNativeDriver,
      toValue,
      easing,
      duration,
    });
    anim.start(this.props.onAnimationComplete);

    return anim;
  }

  animateColor() {
    if (!this.props.tintColorSecondary) {
      return this.props.tintColor
    }

    const tintAnimation = this.state.fillAnimation.interpolate({
      inputRange: [0, 100],
      outputRange: [this.props.tintColor, this.props.tintColorSecondary]
    })

    return tintAnimation
  }

  render() {
    const { fill, prefill, ...other } = this.props;

    return <AnimatedProgress {...other} fill={this.state.fillAnimation} tintColor={this.animateColor()} />;
  }
}

AnimatedCircularProgress.propTypes = {
  ...CircularProgress.propTypes,
  prefill: PropTypes.number,
  duration: PropTypes.number,
  easing: PropTypes.func,
  onAnimationComplete: PropTypes.func,
  useNativeDriver: PropTypes.bool,
};

AnimatedCircularProgress.defaultProps = {
  duration: 500,
  easing: Easing.out(Easing.ease),
  prefill: 0,
  useNativeDriver: false,
};
<commit_msg>listener added for fill animation<commit_after>import React from "react";
import PropTypes from "prop-types";
import { Animated, Easing } from "react-native";
import CircularProgress from "./CircularProgress";
const AnimatedProgress = Animated.createAnimatedComponent(CircularProgress);

export default class AnimatedCircularProgress extends React.PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      fillAnimation: new Animated.Value(props.prefill),
    };

    this.state.fillAnimation.addListener(({ value }) =>
      props.onFillChange(value)
    );
  }

  componentDidMount() {
    this.animate();
  }

  componentDidUpdate(prevProps) {
    if (prevProps.fill !== this.props.fill) {
      this.animate();
    }
  }

  reAnimate(prefill, toVal, dur, ease) {
    this.setState(
      {
        fillAnimation: new Animated.Value(prefill),
      },
      () => this.animate(toVal, dur, ease)
    );
  }

  animate(toVal, dur, ease) {
    const toValue = toVal >= 0 ? toVal : this.props.fill;
    const duration = dur || this.props.duration;
    const easing = ease || this.props.easing;
    const useNativeDriver = this.props.useNativeDriver;

    const anim = Animated.timing(this.state.fillAnimation, {
      useNativeDriver,
      toValue,
      easing,
      duration,
    });
    anim.start(this.props.onAnimationComplete);

    return anim;
  }

  animateColor() {
    if (!this.props.tintColorSecondary) {
      return this.props.tintColor;
    }

    const tintAnimation = this.state.fillAnimation.interpolate({
      inputRange: [0, 100],
      outputRange: [this.props.tintColor, this.props.tintColorSecondary],
    });

    return tintAnimation;
  }

  render() {
    const { fill, prefill, ...other } = this.props;

    return (
      <AnimatedProgress
        {...other}
        fill={this.state.fillAnimation}
        tintColor={this.animateColor()}
      />
    );
  }
}

AnimatedCircularProgress.propTypes = {
  ...CircularProgress.propTypes,
  prefill: PropTypes.number,
  duration: PropTypes.number,
  easing: PropTypes.func,
  onAnimationComplete: PropTypes.func,
  useNativeDriver: PropTypes.bool,
};

AnimatedCircularProgress.defaultProps = {
  duration: 500,
  easing: Easing.out(Easing.ease),
  prefill: 0,
  useNativeDriver: false,
};
<|endoftext|>


<commit_before>exports.newSessionProcessor= function(isAcceptor,options) {
    return new sessionProcessor(isAcceptor, options);
};

var fs = require('fs');
var fixutil = require('../fixutils.js');

//TODO make sure 'ignored' messages really are not forwarded to the next handler
//TODO instead of datastore function as param, expect a data structure which:
//--ds.queue.add(sender,target,value)
//--ds.hash.put(sender,target,value)
//--ds.queue.get(sender,target,callback)
//--ds.hash.get(sender,target,callback)
//TODO outgoing message handling
//TODO Normalize input parameters
function sessionProcessor(isAcceptor,options){
    var isInitiator = !isAcceptor;

    var opt = options || {};
    var isDuplicateFunc = opt.isDuplicateFunc || function(){ return false; };
    var isAuthenticFunc = opt.isAuthenticFunc || function(){ return true; };
    var getSeqNums = opt.getSeqNums || function(){ return {'incomingSeqNum':1,'outgoingSeqNum':1}; };
    var datastore = opt.datastore || function(){};

    var sendHeartbeats = true;
    var expectHeartbeats = true;
    var respondToLogon = true;

    var isLoggedIn = false;
    var heartbeatIntervalID = "";
    var timeOfLastIncoming=new Date().getTime();
    var testRequestID = 1;
    var incomingSeqNum = 1;
    var outgoingSeqNum = 1;
    var isResendRequested = false;

    var file = null;

    var self = this;

    //||||||||||INCOMING||||||||||INCMOING||||||||||INCOMING||||||||||INCOMING||||||||||INCOMING||||||||||INCOMING||||||||||
    this.incoming = function(ctx, event){
        if(event.type !== 'data'){
            ctx.sendNext(event);
            return;
        }

        timeOfLastIncoming = new Date().getTime();

        //==Convert to key/val map==
        var raw = event.data;
        var fix = fixutil.converToMap(raw);

        var msgType = fix['35'];

        //==Confirm first msg is logon==
        if(isLoggedIn === false && msgType !=='A'){
            var error = '[ERROR] First message must be logon:'+raw;
            sys.log(error);
            ctx.stream.end();
            ctx.sendNext({data:error, type:'error'});
            return;
        }

        //==Process logon
        else if(isLoggedIn === false && msgType === 'A'){
            var fixVersion = fix['8'];
            var senderCompID = fix['56'];
            var targetCompID = fix['49'];

            //==Process acceptor specific logic
            if(isAcceptor){
                //==Check duplicate connections
                if(isDuplicateFunc(senderCompID, targetCompID)){
                    var error = '[ERROR] Session already logged in:'+raw;
                    sys.log(error);
                    ctx.stream.end();
                    ctx.sendNext({data:error, type:'error'});
                    return;
                }

                //==Authenticate connection
                if(isAuthenticFunc(fix,ctx.stream.remoteAddress)){
                    if(isDuplicateFunc(senderCompID, targetCompID)){
                        var error = '[ERROR] Session not authentic:'+raw;
                        sys.log(error);
                        ctx.stream.end();
                        ctx.sendNext({data:error, type:'error'});
                        return;
                    }

                }
            }//End Process acceptor specific logic==

            //==Sync sequence numbers from data store
            var seqnums = getSeqNums(senderCompID, targetCompID);
            self.incomingSeqNum = seqnums.incomingSeqNum;
            self.outgoingSeqNum = seqnums.outgoingSeqNum;

            var heartbeatInMilliSecondsStr = fix[108] || '30';
            var heartbeatInMilliSeconds = parseInt(heartbeatInMilliSeconds,10) * 1000;

            //==Set heartbeat mechanism
            self.heartbeatIntervalID = setInterval(function(){
                var currentTime = new Date().getTime();

                //==send heartbeats
                if(currentTime - self.timeOfLastOutgoing > heartbeatInMilliSeconds && self.sendHeartbeats){
                    ctx.sendPrev({data:{'35':'0'}, type:'data'});//heartbeat
                }

                //==ask counter party to wake up
                if(currentTime - self.timeOfLastIncoming > heartbeatInMilliSeconds && self.expectHeartbeats){
                    ctx.sendPrev({data:{'35':'1', '112':self.testRequestID++}, type:'data'});//test req id
                }

                //==counter party might be dead, kill connection
                if(currentTime - self.timeOfLastIncoming > heartbeatInMilliSeconds * 1.5 && self.expectHeartbeats){
                    var error = '[ERROR] No heartbeat from counter party in milliseconds ' + heartbeatInMilliSeconds * 1.5;
                    sys.log(error);
                    ctx.stream.end();
                    ctx.sendNext({data:error, type:'error'});
                    return;
                }

            }, heartbeatInMilliSeconds/2);//End Set heartbeat mechanism==

            //==When session ends, stop heartbeats
            ctx.stream.on('end', function(){clearInterval(self.heartbeatIntervalID);});

            //==Logon successful
            isLoggedIn = true;

            //==Logon ack (acceptor)
            if(isAcceptor && respondToLogon){
                if(respondToLogon){ ctx.sendPrev({data:fix, type:'data'});}
            }

        }// End Process logon==

        //==Record message
        //datastore.getList(senderCompID+"->"+targetCompID).push(raw);
        if(file === null){
            file= fs.createWriteStream('./data/'+senderCompID+'->'+targetCompID+'log', {'flags': 'a'});
        }
        file.write(raw);

        //==Process seq-reset (no gap-fill)
        if(msgType === '4' && fix['123'] ===  undefined  || fix['123'] === 'N'){
            var resetseqnostr = fix['36'];
            var resetseqno  = parseInt(resetseqno,10);
            if(resetseqno >= self.incomingSeqNum){ self.incomingSeqNum = resetseqno }
            else{
                    var error = '[ERROR] Seq-reset may not decrement sequence numbers: ' + raw;
                    sys.log(error);
                    ctx.stream.end();
                    ctx.sendNext({data:error, type:'error'});
                    return;
            }
        }

        //==Check sequence numbers
        var msgSeqNumStr = fix['34'];
        var msgSeqNum = parseInt(msgSeqNumStr,10);

        //expected sequence number
        if(msgSeqNum === self.incomingSeqNum){
            self.incomingSeqNum ++;
            self.isResendRequested = false;
        }
        //less than expected
        else if(msgSeqNum < self.incomingSeqNum){
            //ignore posdup
            if(fix['43'] === 'Y'){ return; }
            //if not posdup, error
            else{
                    var error = '[ERROR] Incoming sequence number lower than expected ('+msgSeqNum+') : ' + raw;
                    sys.log(error);
                    ctx.stream.end();
                    ctx.sendNext({data:error, type:'error'});
                    return;
            }
        }
        //greater than expected
        else{
            //is it resend request?
            if(msgType === '2'){
                //TODO remove duplication in resend processor
                //get list of msgs from archive and send them out, but gap fill admin msgs
                var reader = fs.createReadStream(filename, { 'flags': 'r', 'encoding': 'binary', 'mode': 0666, 'bufferSize': 4 * 1024})
                //TODO full lines may not be read
                reader.addListener( "data", function(chunk) {
                    var _fix = fixutil.converToMap(chunk);
                    var _msgType = _fix[35];
                    var _seqNo = _fix[34];
                    if(_.include(['A','5','2','0','1','4'], _msgType)){
                        //send seq-reset with gap-fill Y
                    ctx.sendPrev({data:{'35':'4', '123':'Y', '36':_seqNo}, type:'data'});
                    }
                    else{
                        //send msg w/ posdup Y
                        ctx.sendPrev(_.extend(_fix,{'43':'Y'}));
                    }
                });
            }
            //did we already send a resend request?
            if(self.isResendRequested === false){
                self.isResendRequested = true;
                //send resend-request
                ctx.sendPrev({data:{'35':'2', '7':self.incomingSeqNum, '16':'0'}, type:'data'});
            }
        }

        }

    //==Process sequence-reset with gap-fill
    if(msgType === '4' && fix['123'] === 'Y'){
        var newSeqNoStr = fix['36'];
        var newSeqNo = parseInt(newSeqNoStr,10);

        if(newSeqNo >= self.incomingSeqNum){
        self.incomingSeqNum = newSeqNo;
        }
        else{
                    var error = '[ERROR] Seq-reset may not decrement sequence numbers: ' + raw;
                    sys.log(error);
                    ctx.stream.end();
                    ctx.sendNext({data:error, type:'error'});
                    return;
        }
    }

    //==Check compids and version
    //TODO

    //==Process test request
    if(msgType === '1'){
        var testReqID = fix['112'];
        ctx.sendPrev({data:{'35':'0', '112':testReqID}, type:'data'});
    }

    //==Process resend-request
    if(msgType === '2'){
        //TODO remove duplication in resend processor
        //get list of msgs from archive and send them out, but gap fill admin msgs
        var reader = fs.createReadStream(filename, { 'flags': 'r', 'encoding': 'binary', 'mode': 0666, 'bufferSize': 4 * 1024})
        //TODO full lines may not be read
        reader.addListener( "data", function(chunk) {
            var _fix = fixutil.converToMap(chunk);
            var _msgType = _fix[35];
            var _seqNo = _fix[34];
            if(_.include(['A','5','2','0','1','4'], _msgType)){
                //send seq-reset with gap-fill Y
                ctx.sendPrev({data:{'35':'4', '123':'Y', '36':_seqNo}, type:'data'});
            }
            else{
                //send msg w/ posdup Y
                ctx.sendPrev(_.extend(_fix,{'43':'Y'}));
            }
        });
    }

    //==Process logout
    //TODO

    }

    //||||||||||OUTGOING||||||||||OUTGOING||||||||||OUTGOING||||||||||OUTGOING||||||||||OUTGOING||||||||||OUTGOING||||||||||
    this.outgoing = function(ctx, event){
        ctx.sendNext(event);
     }

}

<commit_msg>adds basic logout logic<commit_after>exports.newSessionProcessor= function(isAcceptor,options) {
    return new sessionProcessor(isAcceptor, options);
};

var fs = require('fs');
var fixutil = require('../fixutils.js');

//TODO make sure 'ignored' messages really are not forwarded to the next handler
//TODO instead of datastore function as param, expect a data structure which:
//--ds.queue.add(sender,target,value)
//--ds.hash.put(sender,target,value)
//--ds.queue.get(sender,target,callback)
//--ds.hash.get(sender,target,callback)
//TODO outgoing message handling
//TODO Normalize input parameters
function sessionProcessor(isAcceptor,options){
    var isInitiator = !isAcceptor;

    var opt = options || {};
    var isDuplicateFunc = opt.isDuplicateFunc || function(){ return false; };
    var isAuthenticFunc = opt.isAuthenticFunc || function(){ return true; };
    var getSeqNums = opt.getSeqNums || function(){ return {'incomingSeqNum':1,'outgoingSeqNum':1}; };
    var datastore = opt.datastore || function(){};

    var sendHeartbeats = true;
    var expectHeartbeats = true;
    var respondToLogon = true;

    var isLoggedIn = false;
    var heartbeatIntervalID = "";
    var timeOfLastIncoming=new Date().getTime();
    var testRequestID = 1;
    var incomingSeqNum = 1;
    var outgoingSeqNum = 1;
    var isResendRequested = false;
    var isLogoutRequested = false;

    var file = null;

    var self = this;

    //||||||||||INCOMING||||||||||INCMOING||||||||||INCOMING||||||||||INCOMING||||||||||INCOMING||||||||||INCOMING||||||||||
    this.incoming = function(ctx, event){
        if(event.type !== 'data'){
            ctx.sendNext(event);
            return;
        }

        timeOfLastIncoming = new Date().getTime();

        //==Convert to key/val map==
        var raw = event.data;
        var fix = fixutil.converToMap(raw);

        var msgType = fix['35'];

        //==Confirm first msg is logon==
        if(isLoggedIn === false && msgType !=='A'){
            var error = '[ERROR] First message must be logon:'+raw;
            sys.log(error);
            ctx.stream.end();
            ctx.sendNext({data:error, type:'error'});
            return;
        }

        //==Process logon
        else if(isLoggedIn === false && msgType === 'A'){
            var fixVersion = fix['8'];
            var senderCompID = fix['56'];
            var targetCompID = fix['49'];

            //==Process acceptor specific logic
            if(isAcceptor){
                //==Check duplicate connections
                if(isDuplicateFunc(senderCompID, targetCompID)){
                    var error = '[ERROR] Session already logged in:'+raw;
                    sys.log(error);
                    ctx.stream.end();
                    ctx.sendNext({data:error, type:'error'});
                    return;
                }

                //==Authenticate connection
                if(isAuthenticFunc(fix,ctx.stream.remoteAddress)){
                    if(isDuplicateFunc(senderCompID, targetCompID)){
                        var error = '[ERROR] Session not authentic:'+raw;
                        sys.log(error);
                        ctx.stream.end();
                        ctx.sendNext({data:error, type:'error'});
                        return;
                    }

                }
            }//End Process acceptor specific logic==

            //==Sync sequence numbers from data store
            var seqnums = getSeqNums(senderCompID, targetCompID);
            self.incomingSeqNum = seqnums.incomingSeqNum;
            self.outgoingSeqNum = seqnums.outgoingSeqNum;

            var heartbeatInMilliSecondsStr = fix[108] || '30';
            var heartbeatInMilliSeconds = parseInt(heartbeatInMilliSeconds,10) * 1000;

            //==Set heartbeat mechanism
            self.heartbeatIntervalID = setInterval(function(){
                var currentTime = new Date().getTime();

                //==send heartbeats
                if(currentTime - self.timeOfLastOutgoing > heartbeatInMilliSeconds && self.sendHeartbeats){
                    ctx.sendPrev({data:{'35':'0'}, type:'data'});//heartbeat
                }

                //==ask counter party to wake up
                if(currentTime - self.timeOfLastIncoming > heartbeatInMilliSeconds && self.expectHeartbeats){
                    ctx.sendPrev({data:{'35':'1', '112':self.testRequestID++}, type:'data'});//test req id
                }

                //==counter party might be dead, kill connection
                if(currentTime - self.timeOfLastIncoming > heartbeatInMilliSeconds * 1.5 && self.expectHeartbeats){
                    var error = '[ERROR] No heartbeat from counter party in milliseconds ' + heartbeatInMilliSeconds * 1.5;
                    sys.log(error);
                    ctx.stream.end();
                    ctx.sendNext({data:error, type:'error'});
                    return;
                }

            }, heartbeatInMilliSeconds/2);//End Set heartbeat mechanism==

            //==When session ends, stop heartbeats
            ctx.stream.on('end', function(){clearInterval(self.heartbeatIntervalID);});

            //==Logon successful
            isLoggedIn = true;

            //==Logon ack (acceptor)
            if(isAcceptor && respondToLogon){
                if(respondToLogon){ ctx.sendPrev({data:fix, type:'data'});}
            }

        }// End Process logon==

        //==Record message
        //datastore.getList(senderCompID+"->"+targetCompID).push(raw);
        if(file === null){
            file= fs.createWriteStream('./data/'+senderCompID+'->'+targetCompID+'log', {'flags': 'a'});
        }
        file.write(raw);

        //==Process seq-reset (no gap-fill)
        if(msgType === '4' && fix['123'] ===  undefined  || fix['123'] === 'N'){
            var resetseqnostr = fix['36'];
            var resetseqno  = parseInt(resetseqno,10);
            if(resetseqno >= self.incomingSeqNum){ self.incomingSeqNum = resetseqno }
            else{
                    var error = '[ERROR] Seq-reset may not decrement sequence numbers: ' + raw;
                    sys.log(error);
                    ctx.stream.end();
                    ctx.sendNext({data:error, type:'error'});
                    return;
            }
        }

        //==Check sequence numbers
        var msgSeqNumStr = fix['34'];
        var msgSeqNum = parseInt(msgSeqNumStr,10);

        //expected sequence number
        if(msgSeqNum === self.incomingSeqNum){
            self.incomingSeqNum ++;
            self.isResendRequested = false;
        }
        //less than expected
        else if(msgSeqNum < self.incomingSeqNum){
            //ignore posdup
            if(fix['43'] === 'Y'){ return; }
            //if not posdup, error
            else{
                    var error = '[ERROR] Incoming sequence number lower than expected ('+msgSeqNum+') : ' + raw;
                    sys.log(error);
                    ctx.stream.end();
                    ctx.sendNext({data:error, type:'error'});
                    return;
            }
        }
        //greater than expected
        else{
            //is it resend request?
            if(msgType === '2'){
                //TODO remove duplication in resend processor
                //get list of msgs from archive and send them out, but gap fill admin msgs
                var reader = fs.createReadStream(filename, { 'flags': 'r', 'encoding': 'binary', 'mode': 0666, 'bufferSize': 4 * 1024})
                //TODO full lines may not be read
                reader.addListener( "data", function(chunk) {
                    var _fix = fixutil.converToMap(chunk);
                    var _msgType = _fix[35];
                    var _seqNo = _fix[34];
                    if(_.include(['A','5','2','0','1','4'], _msgType)){
                        //send seq-reset with gap-fill Y
                    ctx.sendPrev({data:{'35':'4', '123':'Y', '36':_seqNo}, type:'data'});
                    }
                    else{
                        //send msg w/ posdup Y
                        ctx.sendPrev(_.extend(_fix,{'43':'Y'}));
                    }
                });
            }
            //did we already send a resend request?
            if(self.isResendRequested === false){
                self.isResendRequested = true;
                //send resend-request
                ctx.sendPrev({data:{'35':'2', '7':self.incomingSeqNum, '16':'0'}, type:'data'});
            }
        }

        }

    //==Process sequence-reset with gap-fill
    if(msgType === '4' && fix['123'] === 'Y'){
        var newSeqNoStr = fix['36'];
        var newSeqNo = parseInt(newSeqNoStr,10);

        if(newSeqNo >= self.incomingSeqNum){
        self.incomingSeqNum = newSeqNo;
        }
        else{
                    var error = '[ERROR] Seq-reset may not decrement sequence numbers: ' + raw;
                    sys.log(error);
                    ctx.stream.end();
                    ctx.sendNext({data:error, type:'error'});
                    return;
        }
    }

    //==Check compids and version
    //TODO

    //==Process test request
    if(msgType === '1'){
        var testReqID = fix['112'];
        ctx.sendPrev({data:{'35':'0', '112':testReqID}, type:'data'});
    }

    //==Process resend-request
    if(msgType === '2'){
        //TODO remove duplication in resend processor
        //get list of msgs from archive and send them out, but gap fill admin msgs
        var reader = fs.createReadStream(filename, { 'flags': 'r', 'encoding': 'binary', 'mode': 0666, 'bufferSize': 4 * 1024})
        //TODO full lines may not be read
        reader.addListener( "data", function(chunk) {
            var _fix = fixutil.converToMap(chunk);
            var _msgType = _fix[35];
            var _seqNo = _fix[34];
            if(_.include(['A','5','2','0','1','4'], _msgType)){
                //send seq-reset with gap-fill Y
                ctx.sendPrev({data:{'35':'4', '123':'Y', '36':_seqNo}, type:'data'});
            }
            else{
                //send msg w/ posdup Y
                ctx.sendPrev(_.extend(_fix,{'43':'Y'}));
            }
        });
    }

    //==Process logout
    if(msgType === '5'){
        if(self.isLogoutRequested){
            ctx.stream.end();
        }
        else{
            ctx.sendPrev(fix);
        }

    }

    //||||||||||OUTGOING||||||||||OUTGOING||||||||||OUTGOING||||||||||OUTGOING||||||||||OUTGOING||||||||||OUTGOING||||||||||
    this.outgoing = function(ctx, event){
        ctx.sendNext(event);
     }

}

<|endoftext|>


<commit_before>define(
  'ephox.alloy.spec.SplitDropdownSpec',

  [
    'ephox.alloy.api.SystemEvents',
    'ephox.alloy.api.behaviour.Toggling',
    'ephox.alloy.construct.EventHandler',
    'ephox.alloy.dropdown.Beta',
    'ephox.alloy.dropdown.Gamma',
    'ephox.alloy.menu.logic.ViewTypes',
    'ephox.alloy.spec.ButtonSpec',
    'ephox.alloy.spec.SpecSchema',
    'ephox.alloy.spec.UiSubstitutes',
    'ephox.boulder.api.FieldSchema',
    'ephox.boulder.api.Objects',
    'ephox.highway.Merger',
    'ephox.peanut.Fun',
    'ephox.perhaps.Option',
    'global!Error'
  ],

  function (SystemEvents, Toggling, EventHandler, Beta, Gamma, ViewTypes, ButtonSpec, SpecSchema, UiSubstitutes, FieldSchema, Objects, Merger, Fun, Option, Error) {
    var schema = [
      FieldSchema.strict('toggleClass'),
      FieldSchema.strict('fetch'),
      FieldSchema.strict('onExecute'),
      FieldSchema.option('lazySink'),
      FieldSchema.strict('dom'),
      FieldSchema.defaulted('onOpen', Fun.noop),
      // FieldSchema.defaulted('onClose', Fun.noop),

      FieldSchema.defaulted('matchWidth', false),

      ViewTypes.schema()
    ];

    var make = function (spec) {
      var detail = SpecSchema.asStructOrDie('split-dropdown.spec', schema, spec, [
        'button',
        'arrow'
      ]);

      // Need to make the substitutions for "button" and "arrow"
      var components = UiSubstitutes.substitutePlaces(Option.some('split-dropdown'), detail, detail.components(), {
        '<alloy.split-dropdown.button>': UiSubstitutes.single(
          Merger.deepMerge(
            {
              focusing: undefined
            },
            detail.parts()['button'](),
            {
              uid: detail.partUids()['button']
            }
          )
        ),

        '<alloy.split-dropdown.arrow>': UiSubstitutes.single(
          Merger.deepMerge({
            uiType: 'button',
            tabstopping: undefined,
            focusing: undefined
          }, detail.parts().arrow(), {
            uid: detail.partUids().arrow,
            action: function (arrow) {
              var hotspot = arrow.getSystem().getByUid(detail.uid()).getOrDie();
              hotspot.getSystem().triggerEvent(SystemEvents.execute(), hotspot.element(), { });
            },
            toggling: {
              toggleOnExecute: false
            }
          })
        )
      }, Gamma.sink());

      return Merger.deepMerge(
        ButtonSpec.make({
          uid: detail.uid(),
          action: function (component) {
            Beta.togglePopup(detail, component);
          }
        }), {
          uid: detail.uid(),
          uiType: 'button',
          dom: detail.dom(),
          components: components,
          // toggling: {
          //   toggleClass: detail.toggleClass(),
          //   aria: {
          //     'aria-expanded-attr': 'aria-expanded'
          //   }
          // },
          eventOrder: {
            // Order, the button state is toggled first, so assumed !selected means close.
            'alloy.execute': [ 'toggling', 'alloy.base.behaviour' ]
          },
          coupling: {
            others: {
              sandbox: function (hotspot) {
                var arrow = hotspot.getSystem().getByUid(detail.partUids().arrow).getOrDie();
                var extras = {
                  onOpen: function () {
                    Toggling.select(arrow);
                  },
                  onClose: function () {
                    Toggling.deselect(arrow);
                  }
                };

                return Beta.makeSandbox(detail, hotspot, extras);
              }
            }
          },
          keying: {
            mode: 'execution',
            useSpace: true
          },
          focusing: true
        }
      );
    };

    return {
      make: make
    };
  }
);<commit_msg>VAN-44-wheel-hackery-apis: splitbutton tweaks<commit_after>define(
  'ephox.alloy.spec.SplitDropdownSpec',

  [
    'ephox.alloy.api.SystemEvents',
    'ephox.alloy.api.behaviour.Toggling',
    'ephox.alloy.construct.EventHandler',
    'ephox.alloy.dropdown.Beta',
    'ephox.alloy.dropdown.Gamma',
    'ephox.alloy.menu.logic.ViewTypes',
    'ephox.alloy.spec.ButtonSpec',
    'ephox.alloy.spec.SpecSchema',
    'ephox.alloy.spec.UiSubstitutes',
    'ephox.boulder.api.FieldSchema',
    'ephox.boulder.api.Objects',
    'ephox.highway.Merger',
    'ephox.peanut.Fun',
    'ephox.perhaps.Option',
    'global!Error'
  ],

  function (SystemEvents, Toggling, EventHandler, Beta, Gamma, ViewTypes, ButtonSpec, SpecSchema, UiSubstitutes, FieldSchema, Objects, Merger, Fun, Option, Error) {
    var schema = [
      FieldSchema.strict('toggleClass'),
      FieldSchema.strict('fetch'),
      FieldSchema.strict('onExecute'),
      FieldSchema.option('lazySink'),
      FieldSchema.strict('dom'),
      FieldSchema.defaulted('onOpen', Fun.noop),
      // FieldSchema.defaulted('onClose', Fun.noop),

      FieldSchema.defaulted('matchWidth', false),

      ViewTypes.schema()
    ];

    var make = function (spec) {
      var detail = SpecSchema.asStructOrDie('split-dropdown.spec', schema, spec, [
        'button',
        'arrow'
      ]);

      // Need to make the substitutions for "button" and "arrow"
      var components = UiSubstitutes.substitutePlaces(Option.some('split-dropdown'), detail, detail.components(), {
        '<alloy.split-dropdown.button>': UiSubstitutes.single(
          Merger.deepMerge(
            {
              focusing: undefined
            },
            detail.parts()['button'](),
            {
              uid: detail.partUids()['button'],
              uiType: 'button',
              action: detail.onExecute()
            }
          )
        ),

        '<alloy.split-dropdown.arrow>': UiSubstitutes.single(
          Merger.deepMerge({
            uiType: 'button',
            tabstopping: undefined,
            focusing: undefined
          }, detail.parts().arrow(), {
            uid: detail.partUids().arrow,
            action: function (arrow) {
              var hotspot = arrow.getSystem().getByUid(detail.uid()).getOrDie();
              hotspot.getSystem().triggerEvent(SystemEvents.execute(), hotspot.element(), { });
            },
            toggling: {
              toggleOnExecute: false
            }
          })
        )
      }, Gamma.sink());

      return Merger.deepMerge(
        ButtonSpec.make({
          uid: detail.uid(),
          action: function (component) {
            Beta.togglePopup(detail, component);
          }
        }), {
          uid: detail.uid(),
          uiType: 'button',
          dom: detail.dom(),
          components: components,
          // toggling: {
          //   toggleClass: detail.toggleClass(),
          //   aria: {
          //     'aria-expanded-attr': 'aria-expanded'
          //   }
          // },
          eventOrder: {
            // Order, the button state is toggled first, so assumed !selected means close.
            'alloy.execute': [ 'toggling', 'alloy.base.behaviour' ]
          },
          coupling: {
            others: {
              sandbox: function (hotspot) {
                var arrow = hotspot.getSystem().getByUid(detail.partUids().arrow).getOrDie();
                var extras = {
                  onOpen: function () {
                    Toggling.select(arrow);
                  },
                  onClose: function () {
                    Toggling.deselect(arrow);
                  }
                };

                return Beta.makeSandbox(detail, hotspot, extras);
              }
            }
          },
          keying: {
            mode: 'execution',
            useSpace: true
          },
          focusing: true
        }
      );
    };

    return {
      make: make
    };
  }
);<|endoftext|>


<commit_before>define(
	['app', 'webapp/scripts/script-editor-model', 'webapp/scripts/script-editor-view'],
	function (application, models, views) {

		var api = {

			createEditor: function (model) {

				var view = new views.ScriptEditorView({ model: model });

				view.on('scripts:editor:cancel', api.redirectToList);
				view.on('scripts:editor:save', api.save);

				application.setContentView(view);
			},

			redirectToList: function () {
				application.navigate('webapp/scripts/script-list');
			},

			save: function (data) {

				this.model.set(data);

				models.saveScript(this.model).done(api.redirectToList);
			},

			edit: function (scriptId) {

				models.loadScript(scriptId).done(api.createEditor);
			},

			add: function () {

				var name = window.prompt('Enter script name:', '');

				if (name) {

					var model = new models.ScriptData({ name: name });
					api.createEditor(model);
				}
			}
		};

		return {
			start: function (scriptId) {

				if (scriptId) {
					api.edit(scriptId);
				} else {
					api.add();
				}
			}
		};
	});<commit_msg>- адаптирована страница редактора скриптов;<commit_after>define(
	['lib', 'webapp/scripts/script-editor-model', 'webapp/scripts/script-editor-view'],
	function (lib, models, views) {

		var scriptEditor = lib.common.AppSection.extend({
			start: function (scriptId) {
				if (scriptId) {
					this.edit(scriptId);
				} else {
					this.add();
				}
			},

			redirectToList: function () {
				this.application.navigate('webapp/scripts/script-list');
			},

			save: function (view, data) {
				view.model.set(data);
				models.saveScript(view.model).done(this.bind('redirectToList'));
			},

			createEditor: function (model) {
				var view = new views.ScriptEditorView({ model: model });

				this.listenTo(view, 'scripts:editor:cancel', this.bind('redirectToList'));
				this.listenTo(view, 'scripts:editor:save', this.bind('save', view));

				this.application.setContentView(view);
			},

			edit: function (scriptId) {
				models.loadScript(scriptId).done(this.bind('createEditor'));
			},

			add: function () {
				var name = window.prompt('Enter script name:', '');
				var model = new models.ScriptData({ name: name || 'noname' });
				this.createEditor(model);
			}
		});

		return scriptEditor;
	});<|endoftext|>


<commit_before>'use strict';
const request = require('request');
const core = require('./../core.js');

/**
 * CREDENTIALS
 **/
const VALIDATION_TOKEN = process.env.VALIDATION_TOKEN;
const PAGE_ACCESS_TOKEN = process.env.PAGE_ACCESS_TOKEN;

/*
* Fonction appelé par Facbook pour checker le serveur
*/
function webhook(req, res) {
    return req.query['hub.mode'] === 'subscribe' && req.query['hub.verify_token'] === VALIDATION_TOKEN;
}

/*
* Fonction appelé par les routes lors de la reception d'un message
*/
function receivedMessage(req, res) {
    var data = req.body;
    if (data.object === 'page') {
        //Message reçu de Facebook
        data.entry.forEach(function(entry) {
            var pageID = entry.id;
            var timeOfEvent = entry.time;
            entry.messaging.forEach(function(event) {
                if (event.message) {
                    console.log("Event received : ", JSON.stringify(event));
                    extractMessage(event);
                }
            });
        });
    } else {
        //Pour tester via CURL
        extractMessage(req.body);
    }
}
/*
* Fonction qui permet de traiter un message reçu
*/
function extractMessage(event) {

    var senderID = event.sender.id;
    var recipientID = event.recipient.id;
    var timeOfMessage = event.timestamp;
    var message = event.message;

    console.log("Received message from Facebook for user %d at %d with message:",
      senderID, timeOfMessage);
    console.log(JSON.stringify(message));

    var messageId = message.mid;

    var messageText = message.text;
    var messageAttachments = message.attachments;

    var message = {
        channel: "Facebook",
        senderID: senderID,
        timestamp: timeOfMessage,
        text: messageText
    };
    core.receivedMessage(message);
}

/*
* Fonction appelé par le core pour envoyer un message sur la channel Facebook
*/
function sendMessage(message) {
    switch(message.type) {
        case "text":
            sendTextMessage(message);
        break;
        case "quickreply":
            sendQuickReplyMessage(message);
        break;
    }
}

/*
* Fonction appelé par le core pour envoyer des messages sur la channel Facebook
*/
function sendMessages(messages) {
    for(var i=0;i<messages.length;i++) {
        sendMessage(messages[i]);
    }
}

/*
* Fonction qui envoie un message de type text
*/
function sendTextMessage(message) {
    console.log("Messages sended : ", JSON.stringify(message));
    if(message.senderID != undefined && message.text != undefined) {
        var messageData = {
            recipient: {
                id: message.senderID
            },
            message: {
                text: message.text
            }
        };
        callSendAPI(messageData);
    }
}

/*
* Fonction qui envoie un message de type quickreply
*/
function sendQuickReplyMessage(message) {
    console.log("Messages sended : ", JSON.stringify(message));
    if(message.senderID != undefined && message.text != undefined) {
        var messageData = {
            recipient: {
                id: message.senderID
            },
            message: {
                text: message.text,
                quick_replies: []
            }
        };
        for(var i=0;i<message.choices.length;i++) {
            var quickreply = {
                content_type: "text",
                title: message.choices[i],
                payload: message.choices[i]
            };
            messageData.message.quick_replies.push(quickreply);
        }
        callSendAPI(messageData);
    }
}

/*
* Fonction qui appel l'API messages de Facebook
*/
function callSendAPI(messageData) {
    console.log("Messages sended via API : ", JSON.stringify(messageData));
    request({
        uri: 'https://graph.facebook.com/v2.6/me/messages',
        qs: { access_token: PAGE_ACCESS_TOKEN },
        method: 'POST',
        json: messageData
    }, function (error, response, body) {
        if (!error && response.statusCode == 200) {
            var recipientId = body.recipient_id;
            var messageId = body.message_id;

            console.log("Successfully sent generic message with id %s to recipient %s",
                messageId, recipientId);
        } else {
            console.error("Unable to send message.");
            console.error(response);
            console.error(error);
        }
    });
}

exports.webhook = webhook
exports.receivedMessage = receivedMessage
exports.sendMessage = sendMessage
exports.sendMessages = sendMessages<commit_msg>Traitement de la localisation<commit_after>'use strict';
const request = require('request');
const core = require('./../core.js');

/**
 * CREDENTIALS
 **/
const VALIDATION_TOKEN = process.env.VALIDATION_TOKEN;
const PAGE_ACCESS_TOKEN = process.env.PAGE_ACCESS_TOKEN;

/*
* Fonction appelé par Facbook pour checker le serveur
*/
function webhook(req, res) {
    return req.query['hub.mode'] === 'subscribe' && req.query['hub.verify_token'] === VALIDATION_TOKEN;
}

/*
* Fonction appelé par les routes lors de la reception d'un message
*/
function receivedMessage(req, res) {
    var data = req.body;
    if (data.object === 'page') {
        //Message reçu de Facebook
        data.entry.forEach(function(entry) {
            var pageID = entry.id;
            var timeOfEvent = entry.time;
            entry.messaging.forEach(function(event) {
                if (event.message) {
                    console.log("Event received : ", JSON.stringify(event));
                    extractMessage(event);
                }
            });
        });
    } else {
        //Pour tester via CURL
        extractMessage(req.body);
    }
}
/*
* Fonction qui permet de traiter un message reçu
*/
function extractMessage(event) {
    var senderID = event.sender.id;
    var recipientID = event.recipient.id;
    var timeOfMessage = event.timestamp;
    var message = event.message;

    console.log("Received message from Facebook for user %d at %d with message:",
      senderID, timeOfMessage);
    console.log(JSON.stringify(message));

    var messageId = message.mid;
    var messageText = message.text;
    var messageAttachments = message.attachments;

    var message = {
        channel: "Facebook",
        senderID: senderID,
        timestamp: timeOfMessage,
        text: messageText
    };
    /*
    *   Coordonnées
    */
    if(messageAttachments.payload != undefined && messageAttachments.payload.coordinates != undefined) {
        message["location"] = coordinates;
    }

    core.receivedMessage(message);
}

/*
* Fonction appelé par le core pour envoyer un message sur la channel Facebook
*/
function sendMessage(message) {
    switch(message.type) {
        case "text":
            sendTextMessage(message);
        break;
        case "quickreply":
            sendQuickReplyMessage(message);
        break;
    }
}

/*
* Fonction appelé par le core pour envoyer des messages sur la channel Facebook
*/
function sendMessages(messages) {
    for(var i=0;i<messages.length;i++) {
        sendMessage(messages[i]);
    }
}

/*
* Fonction qui envoie un message de type text
*/
function sendTextMessage(message) {
    console.log("Messages sended : ", JSON.stringify(message));
    if(message.senderID != undefined && message.text != undefined) {
        var messageData = {
            recipient: {
                id: message.senderID
            },
            message: {
                text: message.text
            }
        };
        callSendAPI(messageData);
    }
}

/*
* Fonction qui envoie un message de type quickreply
*/
function sendQuickReplyMessage(message) {
    console.log("Messages sended : ", JSON.stringify(message));
    if(message.senderID != undefined && message.text != undefined) {
        var messageData = {
            recipient: {
                id: message.senderID
            },
            message: {
                text: message.text,
                quick_replies: []
            }
        };
        for(var i=0;i<message.choices.length;i++) {
            var quickreply = {
                content_type: "text",
                title: message.choices[i],
                payload: message.choices[i]
            };
            messageData.message.quick_replies.push(quickreply);
        }
        callSendAPI(messageData);
    }
}

/*
* Fonction qui appel l'API messages de Facebook
*/
function callSendAPI(messageData) {
    console.log("Messages sended via API : ", JSON.stringify(messageData));
    request({
        uri: 'https://graph.facebook.com/v2.6/me/messages',
        qs: { access_token: PAGE_ACCESS_TOKEN },
        method: 'POST',
        json: messageData
    }, function (error, response, body) {
        if (!error && response.statusCode == 200) {
            var recipientId = body.recipient_id;
            var messageId = body.message_id;

            console.log("Successfully sent generic message with id %s to recipient %s",
                messageId, recipientId);
        } else {
            console.error("Unable to send message.");
            console.error(response);
            console.error(error);
        }
    });
}

exports.webhook = webhook
exports.receivedMessage = receivedMessage
exports.sendMessage = sendMessage
exports.sendMessages = sendMessages<|endoftext|>


<commit_before>
 ;(function(root) {
    var LineChart = function(){
        var _xAxisType = 'numeric', _drawXAxis = true, _drawYAxis = true, _yticks, _drawLabels = true, _dateInterval, _dataMarkers = false;
        var xAxis = drata.models.axis()
            .orient("bottom")
            .axisType('x');
        var yAxis = drata.models.axis()
            .orient("left")
            .axisType('y');

        var z = d3.scale.category20();
        var dims = {m: {l:30, r:10, t:10, b:30}};

        function chart(selection) {
            console.log('line chart drawn');
            selection.each(function(data) {
                var container = d3.select(this);
                _yticks > 0 && yAxis.ticks(_yticks);
                if(_xAxisType === 'date'){
                    _.each(data, function(item){
                        _.each(item.values, function(dataPoint){
                            dataPoint.x = new Date(dataPoint.x);
                        });
                    });
                }
                z.domain(data.map(function(d){return d.key}));

                chart.resize = function() {
                    dims = {m: {l:30, r:10, t:30, b:30}};
                    container
                    .transition().duration(500)
                    .call(chart);
                };

                chart.change = function(_) {
                    container
                    .datum(_)
                    .transition().duration(500)
                    .call(chart);
                };

                chart.feed = function(newData) {
                    if(!newData) throw "no data supplied";
                    for (var i = 0; i < newData.length; i++) {
                        var lv = newData[i].values.length;
                        for (var j = 0; j < lv; j++) {
                            data[i].values.shift();
                            data[i].values.push(newData[i].values[j]);
                        }
                    };
                    container
                    .datum(data)
                    .transition().duration(500)
                    .call(chart);
                };

                dims.w = $(this.parentNode).width();
                dims.h = $(this.parentNode).height();

                xAxis.axisTicType(_xAxisType).dateInterval(_dateInterval).dims(dims).includeGridLines(false);
                yAxis.axisTicType('numeric').dims(dims).includeGridLines(true);

                var dispatch = d3.dispatch('togglePath', 'showToolTip', 'hideToolTip');

                dispatch.on("togglePath", function(d){
                    d.disabled = !d.disabled;
                    chart.resize();
                });

                container.attr('width', dims.w).attr('height', dims.h);

                var gWrapper =  container
                    .selectAll('g.topgroup')
                    .data([data]);

                var gWrapperEnter = gWrapper.enter()
                    .append("g")
                    .attr('class', 'topgroup');

                gWrapperEnter.append("g").attr("class", 'x axis');

                gWrapperEnter.append("g").attr("class", 'y axis');

                gWrapperEnter.append("g").attr("class", "line-group");

                //labels
                if(_drawLabels){
                    gWrapperEnter.append("g").attr("class", "labels-group");

                    var labels = drata.models.labels().color(z).dispatch(dispatch).dims(dims);

                    var labelContainer = gWrapper.select('g.labels-group');

                    labelContainer.datum(data).call(labels);
                }

                var xAxisContainer = gWrapper.select('g.x.axis');

                xAxisContainer.call(xAxis);

                xAxisContainer.attr("transform", "translate(" + dims.m.l +"," + (dims.h - dims.m.b) + ")");
                if(_drawLabels){
                    labelContainer.attr("transform", "translate(" + (dims.m.l + 10) +")");
                }

                gWrapper.select('g.y.axis')
                    .attr("transform", "translate(" + dims.m.l +"," + dims.m.t + ")")
                    //.transition().duration(500)
                    .call(yAxis);

                var xScale = xAxis.scale(), yScale = yAxis.scale();

                var lines = drata.models.lines().xScale(xScale).yScale(yScale).color(z).interpolate('linear');

                gWrapper
                    .select('g.line-group')
                    .attr("transform", "translate(" + dims.m.l +"," + dims.m.t + ")")
                    .call(lines);

                if(_dataMarkers){
                    gWrapperEnter.append("g").attr("class", "dot-group");
                    var dots = drata.models.dots().xScale(xScale).yScale(function(d){
                        return yScale(d.y);
                    }).color(z).dispatch(dispatch);
                    gWrapper
                        .select('g.dot-group')
                        .attr("transform", "translate(" + dims.m.l +"," + dims.m.t + ")")
                        .datum(data)
                        .call(dots);

                    gWrapperEnter.append("g").attr("class", "tooltip-group");
                    var toolTip = drata.models.toolTip().dispatch(dispatch);
                    gWrapper
                        .select('g.tooltip-group')
                        .attr("transform", "translate(" + (dims.w-5) +", " +  (dims.m.t-10) +")")
                        .call(toolTip);
                }

                gWrapper.exit().remove();
            });
            return chart;
        };

        chart.xAxisType = function(value){
            if (!arguments.length) return _xAxisType;
            _xAxisType = value;
            return chart;
        };

        chart.yticks = function(value){
            if (!arguments.length) return _yticks;
            _yticks = value;
            return chart;
        };

        chart.dateInterval = function(value){
            if (!arguments.length) return _dateInterval;
            _dateInterval = value;
            return chart;
        };

        chart.drawLabels = function(value){
            if (!arguments.length) return _drawLabels;
            _drawLabels = value;
            return chart;
        };

        chart.drawYAxis = function(value){
            if (!arguments.length) return _drawYAxis;
            _drawYAxis = value;
            return chart;
        };
        chart.drawXAxis = function(value){
            if (!arguments.length) return _drawXAxis;
            _drawXAxis = value;
            return chart;
        };

        chart.includeDataMarkers = function(value){
            if (!arguments.length) return _dataMarkers;
            _dataMarkers = value;
            return chart;
        };

        return chart;
    };
    root.drata.ns('charts').extend({
        lineChart : LineChart
    });
})(this);<commit_msg>ui fixes<commit_after>
 ;(function(root) {
    var LineChart = function(){
        var _xAxisType = 'numeric', _drawXAxis = true, _drawYAxis = true, _yticks, _drawLabels = true, _dateInterval, _dataMarkers = false;
        var xAxis = drata.models.axis()
            .orient("bottom")
            .axisType('x')
            .ticks(8);
        var yAxis = drata.models.axis()
            .orient("left")
            .axisType('y')
            .ticks(5);

        var z = d3.scale.category20();
        var dims = {m: {l:30, r:10, t:10, b:30}};

        function chart(selection) {
            console.log('line chart drawn');
            selection.each(function(data) {
                var container = d3.select(this);
                _yticks > 0 && yAxis.ticks(_yticks);
                if(_xAxisType === 'date'){
                    _.each(data, function(item){
                        _.each(item.values, function(dataPoint){
                            dataPoint.x = new Date(dataPoint.x);
                        });
                    });
                }
                z.domain(data.map(function(d){return d.key}));

                chart.resize = function() {
                    dims = {m: {l:30, r:10, t:30, b:30}};
                    container
                    .transition().duration(500)
                    .call(chart);
                };

                chart.change = function(_) {
                    container
                    .datum(_)
                    .transition().duration(500)
                    .call(chart);
                };

                chart.feed = function(newData) {
                    if(!newData) throw "no data supplied";
                    for (var i = 0; i < newData.length; i++) {
                        var lv = newData[i].values.length;
                        for (var j = 0; j < lv; j++) {
                            data[i].values.shift();
                            data[i].values.push(newData[i].values[j]);
                        }
                    };
                    container
                    .datum(data)
                    .transition().duration(500)
                    .call(chart);
                };

                dims.w = $(this.parentNode).width();
                dims.h = $(this.parentNode).height();

                xAxis.axisTicType(_xAxisType).dateInterval(_dateInterval).dims(dims).includeGridLines(false);
                yAxis.axisTicType('numeric').dims(dims).includeGridLines(true);

                var dispatch = d3.dispatch('togglePath', 'showToolTip', 'hideToolTip');

                dispatch.on("togglePath", function(d){
                    d.disabled = !d.disabled;
                    chart.resize();
                });

                container.attr('width', dims.w).attr('height', dims.h);

                var gWrapper =  container
                    .selectAll('g.topgroup')
                    .data([data]);

                var gWrapperEnter = gWrapper.enter()
                    .append("g")
                    .attr('class', 'topgroup');

                gWrapperEnter.append("g").attr("class", 'x axis');

                gWrapperEnter.append("g").attr("class", 'y axis');

                gWrapperEnter.append("g").attr("class", "line-group");

                //labels
                if(_drawLabels){
                    gWrapperEnter.append("g").attr("class", "labels-group");

                    var labels = drata.models.labels().color(z).dispatch(dispatch).dims(dims);

                    var labelContainer = gWrapper.select('g.labels-group');

                    labelContainer.datum(data).call(labels);
                }

                var xAxisContainer = gWrapper.select('g.x.axis');

                xAxisContainer.call(xAxis);

                xAxisContainer.attr("transform", "translate(" + dims.m.l +"," + (dims.h - dims.m.b) + ")");
                if(_drawLabels){
                    labelContainer.attr("transform", "translate(" + (dims.m.l + 10) +")");
                }

                gWrapper.select('g.y.axis')
                    .attr("transform", "translate(" + dims.m.l +"," + dims.m.t + ")")
                    //.transition().duration(500)
                    .call(yAxis);

                var xScale = xAxis.scale(), yScale = yAxis.scale();

                var lines = drata.models.lines().xScale(xScale).yScale(yScale).color(z).interpolate('linear');

                gWrapper
                    .select('g.line-group')
                    .attr("transform", "translate(" + dims.m.l +"," + dims.m.t + ")")
                    .call(lines);

                if(_dataMarkers){
                    gWrapperEnter.append("g").attr("class", "dot-group");
                    var dots = drata.models.dots().xScale(xScale).yScale(function(d){
                        return yScale(d.y);
                    }).color(z).dispatch(dispatch);
                    gWrapper
                        .select('g.dot-group')
                        .attr("transform", "translate(" + dims.m.l +"," + dims.m.t + ")")
                        .datum(data)
                        .call(dots);

                    gWrapperEnter.append("g").attr("class", "tooltip-group");
                    var toolTip = drata.models.toolTip().dispatch(dispatch);
                    gWrapper
                        .select('g.tooltip-group')
                        .attr("transform", "translate(" + (dims.w-5) +", " +  (dims.m.t-10) +")")
                        .call(toolTip);
                }

                gWrapper.exit().remove();
            });
            return chart;
        };

        chart.xAxisType = function(value){
            if (!arguments.length) return _xAxisType;
            _xAxisType = value;
            return chart;
        };

        chart.yticks = function(value){
            if (!arguments.length) return _yticks;
            _yticks = value;
            return chart;
        };

        chart.dateInterval = function(value){
            if (!arguments.length) return _dateInterval;
            _dateInterval = value;
            return chart;
        };

        chart.drawLabels = function(value){
            if (!arguments.length) return _drawLabels;
            _drawLabels = value;
            return chart;
        };

        chart.drawYAxis = function(value){
            if (!arguments.length) return _drawYAxis;
            _drawYAxis = value;
            return chart;
        };
        chart.drawXAxis = function(value){
            if (!arguments.length) return _drawXAxis;
            _drawXAxis = value;
            return chart;
        };

        chart.includeDataMarkers = function(value){
            if (!arguments.length) return _dataMarkers;
            _dataMarkers = value;
            return chart;
        };

        return chart;
    };
    root.drata.ns('charts').extend({
        lineChart : LineChart
    });
})(this);<|endoftext|>