Skip to content

Instantly share code, notes, and snippets.

@satansdeer
Last active August 29, 2015 14:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save satansdeer/511c5ec1dc5b6e78a621 to your computer and use it in GitHub Desktop.
Save satansdeer/511c5ec1dc5b6e78a621 to your computer and use it in GitHub Desktop.
MapView
###jshint expr: true, loopfunc: true ###
`import Ember from 'ember'`
MapView = Ember.View.extend(
connectionsData: []
nodesPosX: 0
nodesPosY: 0
nodeWidth: 58
nodeHeight: 58
icon_size: 58
circle_size: 29
nodeInfoWidth: 320
nodeInfoHeight: 245
triangleScale: 'scale( 2.2, 1.5 )'
positionMultiplier: 200
width: 0
height: 0
startX: 0
startY: 0
defs: null
nodeInfo: null
drag: null
zoom: null
didInsertElement: ->
@controller.setupNodeModels()
@controller.on('centerOn', (item) =>
node = d3.select("#node#{item.id}")
datum = node.datum()
@centerOnItem(datum)
)
@width = @$().width()
@height = @$().height()
svg = d3.select($('.map-container')[0]).append('svg')
.attr('width', @width)
.attr('height', @height)
nodes = svg.append('g')
.attr('id', 'nodes')
lessons = @controller.get('content').entities
@prepareConnectionsData(lessons)
@setupConnectionsGroup(nodes)
@setupLessonsGroup(nodes, lessons)
rootNode = lessons[0]
@setupZoomBehaviour(nodes)
@setupDragBehaviour(svg, nodes)
svg.call(@drag)
svg.call(@zoom)
@nodesPosX = @width / 2 - rootNode.x - @nodeWidth / 2
@nodesPosY = @height / 2 - rootNode.y - @nodeHeight / 2 + 100
nodes.transition().attr('transform', "translate( #{@nodesPosX}, #{@nodesPosY} )");
@defs = svg.append('defs')
@createShadowForNodeInfo()
@setupNodeInfo(rootNode, nodes)
@addTitleAndDescToNodeInfo()
@setupLessonButton()
prepareConnectionsData: (lessons) ->
for node in lessons
for prerequisite in node.prerequisites
prerequisitedNode = (lessons.filter (node) -> node.id == prerequisite)[0]
prereqX = (prerequisitedNode.x || 0) * @positionMultiplier
prereqY = (prerequisitedNode.y || 0) * @positionMultiplier
nodeX = (node.x || 0) * @positionMultiplier
nodeY = (node.y || 0) * @positionMultiplier
@connectionsData.push {
point1: { x : prereqX + @nodeWidth / 2, y : prereqY+@nodeHeight / 2 }
point2: { x : nodeX + @nodeWidth / 2, y : nodeY+@nodeHeight / 2 }
}
setupConnectionsGroup: (nodes) ->
connectionGroup = nodes.selectAll('.connection')
.data(@connectionsData)
.enter().append('g')
connectionGroup.append('line')
.attr('x1', (d)->d.point1.x)
.attr('y1', (d)->d.point1.y)
.attr('x2', (d)->d.point2.x)
.attr('y2', (d)->d.point2.y)
.attr('stroke', '#C2CDCE')
setupLessonsGroup: (nodes, lessons) ->
lessonGroup = nodes.selectAll('.node')
.data(lessons)
.enter().append('g')
.attr('transform',(d) =>
newX = (d.x || 0) * @positionMultiplier
newY = (d.y || 0) * @positionMultiplier
"translate( #{newX}, #{newY})"
)
.attr('class', 'lesson-node')
.attr('id',(d) -> 'node' + d.id)
lessonGroup.append('image')
.attr('width', @icon_size)
.attr('height', @icon_size)
.attr('xlink:href', (d) ->
if d.mastery == 0
'/icons/course-mastered.svg'
else
"/icons/#{d.entity_status}.svg"
)
lessonGroup.append('circle')
.attr('cx', @circle_size)
.attr('cy', @circle_size)
.attr('r', @circle_size)
.attr('fill', 'none')
.attr('stroke', 'none')
.attr('stroke-width', '4')
.attr('class', 'selection-circle')
lessonGroup.append('text')
.style('text-anchor', 'middle')
.text((d) -> d.title)
.attr('class', 'node-title')
.attr('transform', => "translate( #{@nodeWidth / 2}, #{@nodeHeight + 20})")
lessonGroup.on('click', =>
datum = d3.select(@).datum()
@centerOnItem(datum)
)
setupLessonButton: ->
buttonWidth = @nodeInfoWidth - 60
buttonHeight = 40
lessonButton = @nodeInfo.append('g')
.attr('transform', "translate(30, #{@nodeInfoHeight - 30 - buttonHeight} )")
.attr('class', 'map-block--lesson-popup--button')
.attr('id', 'lesson-button')
lessonButton.append('rect')
.attr('width', buttonWidth)
.attr('height', buttonHeight)
.attr('rx', 20)
.attr('ry', 20)
lessonButton.append('text')
.text('Practice this concept')
.attr('class', 'map-block--lesson-popup--button-text')
.style('text-anchor', 'middle')
.attr('transform', "translate( #{buttonWidth / 2}, #{buttonHeight / 2 + 4} )")
lessonButton.on('click', =>
@controller.openPlayer()
)
addTitleAndDescToNodeInfo: ->
foreignObjectDiv = @nodeInfo.append('foreignObject')
.attr('id', 'title-and-desc')
.attr('width', @nodeInfoWidth - 60)
.attr('height', @nodeInfoHeight - 60)
.attr('transform', 'translate( 30, 30)')
.append('xhtml:div')
titleText = foreignObjectDiv.append('xhtml:h1')
.attr('class', 'map-block--lesson-popup--title-text')
.attr('id', 'title-text')
summaryText = foreignObjectDiv.append('xhtml:p')
.attr('id', 'summaryText')
.attr('class', 'map-block--lesson-popup--description-text')
setupNodeInfo: (rootNode, nodes) ->
nodeInfoX = rootNode.x - @nodeInfoWidth / 2 + @nodeWidth / 2
nodeInfoY = rootNode.y - @nodeInfoHeight - 25
@nodeInfoDef = @defs.append('g')
.attr('id', 'node-info-def')
rect = @nodeInfoDef.append('rect')
.attr('width', @nodeInfoWidth)
.attr('height', @nodeInfoHeight)
.attr('class', 'info-rect')
.attr('id', 'info-rect')
.attr('stroke', 'none')
triangleDown = @nodeInfoDef.append('path')
.attr('d', d3.svg.symbol().type('triangle-down'))
.attr('class', 'triangle-down')
.attr('id', 'triangle-down')
.attr('stroke', 'none')
.attr('transform', "translate( #{@nodeInfoWidth / 2}, #{@nodeInfoHeight + 7} )" + @triangleScale)
@nodeInfo = nodes.append('g')
.attr('transform', "translate( #{nodeInfoX}, #{nodeInfoY} )")
.attr('class', 'map-block--lesson-popup')
.attr('id', 'node-info')
@nodeInfo.on('mousedown', ->
d3.event.stopPropagation()
)
@nodeInfo.append('use')
.attr('xlink:href', 'map#node-info-def')
.attr('filter', 'url(map#dropshadow)')
.attr('opacity', 0.2)
@nodeInfo.append('use')
.attr('xlink:href', 'map#node-info-def')
createShadowForNodeInfo: ->
filter = @defs.append('filter')
.attr('id', 'dropshadow')
filter.append('feGaussianBlur')
.attr('in', 'SourceAlpha')
.attr('stdDeviation', 8)
.attr('result', 'blur')
filter.append('feOffset')
.attr('in', 'blur')
.attr('dx', 0)
.attr('dy', 0)
.attr('result', 'offsetBlur')
feMerge = filter.append('feMerge')
feMerge.append('feMergeNode')
.attr('in', 'offsetBlur')
feMerge.append('feMergeNode')
.attr('in', 'SourceGraphic')
setupZoomBehaviour: (nodes) ->
@zoom = d3.behavior.zoom()
.scaleExtent([0.35, 1])
.translate([@startX + @nodesPosX, @startY + @nodesPosY])
.on('zoom', ->
nodes.attr('transform', "translate( #{d3.event.translate} )scale( #{d3.event.scale} )")
if d3.event.scale < 0.5
d3.selectAll('.node-title').attr('opacity', 0)
else
d3.selectAll('.node-title').attr('opacity', 1)
)
setupDragBehaviour: (svg, nodes) ->
@drag = d3.behavior.drag()
.on('drag', =>
nodes.attr('transform', "translate( #{d3.event.x - @startX + @nodesPosX}, #{d3.event.y - @startY + @nodesPosY} )");
)
.on('dragend', ->
coordinates = d3.mouse(@)
@nodesPosX += coordinates[0] - @startX
@nodesPosY += coordinates[1] - @startY
svg.style('cursor', '-webkit-grab')
svg.style('cursor', '-moz-grab')
svg.style('cursor', 'grab')
)
.on('dragstart', ->
coordinates = d3.mouse(@)
@startX = coordinates[0]
@startY = coordinates[1]
d3.selectAll('.lesson-node').classed('selected', false)
d3.select('.map-block--lesson-popup').transition().style('opacity', 0)
d3.select('.map-block--lesson-popup').style('pointer-events', 'none')
svg.style('cursor', '-webkit-grabbing')
svg.style('cursor', '-moz-grabbing')
svg.style('cursor', 'grabbing')
)
centerOnItem: (datum) ->
dX = (datum.x || 0) * @positionMultiplier
dY = (datum.y || 0) * @positionMultiplier
d3.selectAll('.lesson-node').classed('selected', false)
node.classed('selected', true)
@nodesPosX = @width / 2 - dX - @nodeWidth / 2
@nodesPosY = @height / 2 - dY - @nodeHeight / 2 + 100
d3.select('#title-text').html(datum.title)
d3.select('#summaryText').html(datum.summary)
descriptionHeight = $('#title-and-desc div').height()
infoBlockHeight = descriptionHeight + 120
d3.select('#info-rect').attr('height', infoBlockHeight)
d3.select('#title-and-desc').attr('height', descriptionHeight)
d3.select('#triangle-down').attr('transform', "translate( #{@nodeInfoWidth / 2}, #{infoBlockHeight + 7} )#{@triangleScale}")
d3.select('#lesson-button').attr('transform', "translate( 30, #{infoBlockHeight - 60} )")
nodeInfoX = dX - @nodeInfoWidth / 2 + @nodeWidth / 2
nodeInfoY = dY - infoBlockHeight - 25
d3.select('#node-info').attr('transform', "translate(#{nodeInfoX}, #{nodeInfoY})")
d3.select('#node-info').transition().duration(300).style('opacity', 1)
d3.selectAll('.node-title').transition().duration(300).attr('opacity', 1)
d3.select('.map-block--lesson-popup').style('pointer-events', 'auto')
d3.select('#nodes').transition().attr('transform', "translate( #{@nodesPosX}, #{@nodesPosY} )");
@zoom.scale(1)
@zoom.translate([@nodesPosX,@nodesPosY])
)
`export default MapView`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment