Last active
August 29, 2015 14:07
-
-
Save satansdeer/511c5ec1dc5b6e78a621 to your computer and use it in GitHub Desktop.
MapView
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
###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