app/assets/javascripts/data_bundle.coffee (757 lines of code) (raw):

# # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # ## sankey diagram : http://bl.ocks.org/d3noob/5028304 ## concur. matrix : https://bost.ocks.org/mike/miserables/ # for every type of activity call draw_provenance $(document).ready -> $('#diagramType li a').click -> draw_provenance($(this).text()) return @isEnabled = false #is the mouse wheel scroll disabled or enabled $('#enableZooming').click -> isEnabled = undefined if $("#enableZooming span").html() == 'Enable Zooming' isEnabled = true $("#enableZooming span").html('Disable Zooming') else isEnabled = false $("#enableZooming span").html('Enable Zooming') disableMidMouse(isEnabled) return @disableMidMouse = (status) -> # enable/disable middle mouse button scrolling wheelEnable = (event) -> event.preventDefault() event.returnValue = true return wheelDisable = (event) -> event.preventDefault() event.returnValue = false return setisEnabled(status) if window.addEventListener if status window.addEventListener('DOMMouseScroll', wheelDisable, false) window.onmousewheel = document.onmousewheel = wheelDisable else window.addEventListener('DOMMouseScroll', wheelEnable, false) window.onmousewheel = document.onmousewheel = wheelEnable return # distingush between single click and double click # see http://bl.ocks.org/couchand/6394506 @clickCancel = -> event = d3.dispatch('click', 'dblclick') cc = (selection) -> down = undefined tolerance = 5 last = undefined wait = null # euclidean distance dist = (a, b) -> Math.sqrt (a[0] - (b[0])) ** 2, (a[1] - (b[1])) ** 2 selection.on('mousedown', -> down = d3.mouse(document.body) last = +new Date return ) selection.on('mouseup', -> if dist(down, d3.mouse(document.body)) > tolerance return else if wait window.clearTimeout wait wait = null event.dblclick d3.event else wait = window.setTimeout(((e) -> -> event.click e wait = null return )(d3.event), 300) return ) return d3.rebind(cc, event, 'on') # Here start diagrams func @glob_width = 0 @dashLine = '\n---------------------------------------------------------------\n' @graph = {} @tempgraph = {} # set the width @setGLWidth =(reqWidth) -> @glob_width = reqWidth return @setisEnabled =(status) -> @isEnabled = status return # set a color for a node @getColorHex =(source) -> color = d3.scale.category20() colorType = undefined switch source when 'Workflow Run' then colorType = '#0eff7f' when 'Process Run' then colorType = '#258fda' when 'Artifact' then colorType = '#ff7f0e' when 'Dictionary' then colorType = '#7f0eff' else colorType = color(stringTextForColor.replace(RegExp(' .*'), '')) colorType @getColorTransitionTypeHex =(value) -> color = d3.scale.category20() colorType = undefined switch value when 11 then colorType = '#004d24' # wfprov:wasPartOfWorkflow when 12 then colorType = '#c3e221' # this case should not exist when 13 then colorType = '#009947' # wfprov:usedInputArtifact when 14 then colorType = '#003318' # ----- // ------Dictionary when 21 then colorType = '#258fda' # wasPartOfWorkflow when 23 then colorType = '#7cbce9' # usedInputArtifact when 24 then colorType = '#12476d' # usedInputDictionary when 31 then colorType = '#ffc999' # wasoutputFromWf when 32 then colorType = '#ff7f0e' # wasOutputFromProcess when 34 then colorType = '#663000' # insertInList when 41 then colorType = '#bb80ff' # output from wf when 42 then colorType = '#7f0eff' #outputFromProcess when 43 then colorType = '#3c0080' #split when 44 then colorType = '#990000' # insert into another funct else '#c3e221' @createGroupType =(type) -> group = -1 switch type when 'Workflow Run' then group = 1 when 'Process Run' then group = 2 when 'Artifact' then group = 3 when 'Dictionary' then group = 4 else group = 0 group # limit a string to maxChar @shortenString =(temp, maxChar) -> if temp.length > maxChar temp = temp.substring(0, maxChar) + '..' temp # limit a string to 32 chars : "{15 chars}..{15 chars}" @shortenStringNoMiddle =(temp) -> if temp.length > 32 temp = temp.substring(0, 15) + '..' + temp.substring(temp.length - 15, temp.length) temp @getTimes =(d) -> startTime = new Date() endTime = new Date() nodeTime = 0 if(d.hasOwnProperty("startedAtTime")) startTime = new Date(d.startedAtTime) endTime = new Date(d.endedAtTime) nodeTime = 1 elapsedTime = endTime - startTime date_format_iso =(date) -> date.toISOString().replace( /[T]/g, ' ').slice(0, -1) hms =(ms) -> date = new Date(ms); str = ''; if date.getUTCDate()-1 > 0 str += date.getUTCDate()-1 + " days, "; if date.getUTCHours > 0 str += date.getUTCHours() + " hours, "; if date.getUTCMinutes() > 0 str += date.getUTCMinutes() + " minutes, "; if date.getUTCSeconds() > 0 str += date.getUTCSeconds() + " seconds, "; str += date.getUTCMilliseconds() + " millis"; str if nodeTime == 1 'Start Time: ' + date_format_iso(startTime) + '\nEnd Time: ' + date_format_iso(endTime) + '\nElapsed Time: ' + hms(elapsedTime) else '' # create function that to split the text into multiple lines for the svg-text # cannot find something like this online @wrap = (text) -> text.each -> text = d3.select(this) labels = text.text().split("\\n") text.text(null) line = [] lineNumber = 1 if(labels.length != 0) lineNumber = (-1) * (Math.floor(labels.length / 2) - 1) lineHeight = 1.1 for temp in labels temp = temp.substring(temp.lastIndexOf(' ')) text.append('tspan').attr('x', text.attr('x')).attr('y', text.attr('y')).attr('dy', lineNumber * lineHeight + 'em' ).text(temp).filter((d) -> d.x < glob_width / 5 ).attr('x', "22") lineNumber++ return return # create function that to split the text into multiple lines for the svg-text # cannot find something like this online @wrapNoNewLine = (text) -> text.each -> text = d3.select(this) labels = text.text().split("\\n") text.text(null) line = [] lineNumber = 1 if(labels.length != 0) lineNumber = (-1) * (Math.floor(labels.length / 2) - 1) lineHeight = 0.15 final = '' for temp in labels final += temp.substring(temp.lastIndexOf(' ')) + ', ' final = final[0...-2] text.append('tspan').attr('x', text.attr('x')).attr('y', text.attr('y')).attr('dy', lineHeight + 'em' ).text(final).filter((d) -> d.x < glob_width / 5 ).attr('x', "22") return return # some local functions for zooming in/out and for walking around @zoomed = -> if isEnabled d3.select('g#zoomContainer').attr 'transform', 'translate(' + d3.event.translate + ')scale(' + d3.event.scale + ')' else null return @draw = -> d3.json $('#data_bundle').attr('data-url'), (error, data) -> @tempgraph = $.extend(true, {}, data) if(Object.keys(tempgraph).length) hasBeenDrawn = draw_workflow(hasBeenDrawn) draw_provenance() return return @clone = (obj) -> return obj if obj is null or typeof (obj) isnt "object" temp = new obj.constructor() for key of obj temp[key] = clone(obj[key]) temp @draw_workflow =(draw) -> data = clone(@tempgraph.workflow) if !draw? width = 960 height = 650 opacity = 0.7 color = d3.scale.category20() $('canvas#canvasWF').attr 'width': (width + 150) 'height': (width) svgContainer = d3.select('svg#graphContainer').attr('width', width+150).attr('height', width).append('g').attr('transform', (d) -> "translate("+ (width) + ", 0) rotate (90)" ) verticalSankey = d3.vertical_sankey().nodeWidth(25).nodePadding(20).size([width-128, height]) path = verticalSankey.link() verticalSankey.nodes(data.nodes).links(data.links).layout(32) link = svgContainer.append('g').selectAll('.link').data(data.links).enter().append('path').attr('class', 'link').attr('d', path).style('stroke-width', (d) -> Math.max 1, d.dy ).style('stroke', (d) -> d.source.color = color(d.source.name.replace(RegExp(' .*'), '')) ).sort((a, b) -> b.dx - (a.dx) ) link.attr('opacity', opacity) link.append('title').text((d) -> d.source.name + '\n→\n' + d.target.name ) node = svgContainer.append('g').selectAll('.node').data(data.nodes).enter().append('g').attr('class', 'node').attr('transform', (d) -> 'translate(' + d.x + ',' + d.y + ')' ) node.append('rect').attr('width', verticalSankey.nodeWidth()).attr('height', (d) -> Math.abs d.dy ).style('fill', (d) -> d.color = color(d.name.replace(RegExp(' .*'), '')) ).style('stroke', (d) -> d3.rgb(d.color).darker 2 ) node.append('text').attr('text-anchor', 'middle').attr('y', (d) -> 12 ).attr('x', (d) -> d.dy/-2 ).attr('dy', '.35em').attr('transform', (d) -> "translate("+ 0 + ", 0) rotate (270)" ).text((d) -> shortenName =(d) -> # convert the text to pixels canvas = document.createElement('canvas') ctx = canvas.getContext("2d") ctx.font = "14px Source Sans Pro" textPX = ctx.measureText(d.name).width if textPX > d.dy d.name.substring(0, 9) + '..' + d.name.substring(d.name.length - 9, d.name.length) else d.name shortenName(d) ).filter (d) -> d.x < width / 2 return true @draw_provenance =(diagramType) -> # if diagramType is undefined or null, as default assign the current active # else clear the svg for the diagram if !diagramType? diagramType = $('#diagramType li.active a').text() else d3.select('svg#provContainer').selectAll("*").remove() d3.select('svg#provContainer').remove() d3.select('#mapContainer').append('svg').attr('id','provContainer') @graph = clone(@tempgraph) if(diagramType == 'Sankey') draw_sankey() else if(diagramType == 'Co-occurrence') draw_miserables() return @draw_miserables = -> width = 1580 height = 750 # compute a better width and height for the container nodesCount = Object.keys(graph.provenance.nodes).length linksCount = Object.keys(graph.provenance.links).length if nodesCount > 0 or linksCount > 0 ratioN = 1.0 * nodesCount * 10 width = width + 25 height = width divider = 3 setGLWidth(width) $('canvas#canvasPROV').attr 'width': width 'height': height x = d3.scale.ordinal().rangeBands([ 0 (5600 / divider) ]) z = d3.scale.linear().domain([ 0 4 ]).clamp(true) color = d3.scale.category20() svg = d3.select('svg#provContainer').attr('width', width+602).attr('height', height+535).append('g').attr('transform', 'translate(350,277)') # build a [source, target] matrix matrix = [] nodes = graph.provenance.nodes n = nodes.length row = (row) -> cell = d3.select(this).selectAll('.cell').data(row.filter((d) -> d.z + Math.floor(Math.random() * 10) )).enter().append('rect').attr('class', 'cell').attr('x', (d) -> x d.x ).attr('width', x.rangeBand()).attr('height', x.rangeBand()).style('fill-opacity', (d) -> z d.z ).style('fill', (d) -> console.log(d.x + " .. " + d.y) if d.x == d.y "#123456" else transition = createGroupType(nodes[d.x].type) * 10 + createGroupType(nodes[d.y].type) getColorTransitionTypeHex(transition) ).append('title').text((d) -> str = nodes[d.x].type + ' → ' + nodes[d.y].type str += dashLine + 'Source: ' + nodes[d.x].name if createGroupType(nodes[d.x].type) == 2 # if process str += '\n\n' + getTimes(nodes[d.x]) else if createGroupType(nodes[d.x].type) > 2 # if artifact or dictionary if nodes[d.x].content? str += '\n\n' + shortenString(nodes[d.x].content, 500) str += dashLine + 'Target: ' + nodes[d.y].name if createGroupType(nodes[d.y].type) == 2 # if process str += '\n\n' + getTimes(nodes[d.y]) else if createGroupType(nodes[d.y].type) > 2 # if artifact or dictionary if nodes[d.y].content? str += '\n\n' + shortenString(nodes[d.y].content, 500) str ).on('mouseover', mouseover).on('mouseout', mouseout) return mouseover = (p) -> d3.selectAll('.row text').classed('active', (d, i) -> i == p.y ) d3.selectAll('.column text').classed('active', (d, i) -> i == p.x ) return mouseout = -> d3.selectAll('text').classed('active', false) return order = (value) -> x.domain orders[value] t = svg.transition().duration(1500) t.selectAll('.row').delay((d, i) -> x(i) * 4 ).attr('transform', (d, i) -> 'translate(0,' + x(i) + ')' ).selectAll('.cell').delay((d) -> x(d.x) * 4 ).attr 'x', (d) -> x d.x t.selectAll('.column').delay((d, i) -> x(i) * 4 ).attr 'transform', (d, i) -> 'translate(' + x(i) + ')rotate(-90)' return # Compute index per node. nodes.forEach (node, i) -> node.index = i node.count = 0 matrix[i] = d3.range(n).map((j) -> { x: j y: i z: 0 } ) return # Add the legend legendCategories = { "category":[{"name":"Workflow Run -wasPartOfWorkflow- Workflow Run", "transition":"11"}, {"name":"Workflow Run -usedInput- Artifact", "transition":"13"}, {"name":"Workflow Run -usedInput- Dictionary", "transition":"14"}, {"name":"Process Run -wasPartOfWorkflow- Workflow Run", "transition":"21"}, {"name":"Process Run -usedInput- Artifact", "transition":"23"}, {"name":"Process Run -usedInput- Dictionary", "transition":"24"}, {"name":"Artifact -wasOutputFrom- Workflow Run", "transition":"31"}, {"name":"Artifact -wasOutputFrom- Process Run", "transition":"32"}, {"name":"Artifact -isIntegratedIn- Dictionary", "transition":"34"}, {"name":"Dictionary -wasOutputFrom- Artifact", "transition":"41"}, {"name":"Dictionary -wasOutputFrom- Artifact", "transition":"42"}, {"name":"Dictionary -hasMember- Artifact", "transition":"43"}, {"name":"Dictionary -isSplit/PushedInto- Dictionary", "transition":"44"}]} legend = svg.append('g').attr('class', 'legend').attr('x', 0).attr('y', 0).selectAll('.category').data(legendCategories.category).enter().append('g').attr('class', 'category') legendConfig = rectWidth: 20 rectHeight: 14 xOffset: -350 yOffset: -275 xOffsetText: 26 yOffsetText: -10 lineHeight: 10 wordApart: 20 legendConfig.yOffsetText += 20 legendConfig.xOffsetText += legendConfig.xOffset legend.append('rect').attr('y', (d, i) -> legendConfig.yOffset + i * legendConfig.wordApart ).attr('x', legendConfig.xOffset).attr('height', legendConfig.rectHeight).attr('width', legendConfig.rectWidth).style('fill', (d) -> getColorTransitionTypeHex(parseInt(d.transition)) ).style('stroke', '#000000') legend.append('text').attr('y', (d, i) -> legendConfig.yOffset + i * legendConfig.wordApart + legendConfig.yOffsetText ).attr('x', legendConfig.xOffsetText).text((d) -> d.name ) # Convert links to matrix; count character occurrences. graph.provenance.links.forEach (link) -> matrix[link.source][link.target].z = createGroupType(nodes[link.source].type) * 10 + createGroupType(nodes[link.target].type) nodes[link.source].count += link.value nodes[link.target].count += link.value return # Precompute the orders. orders = name: d3.range(n).sort((a, b) -> d3.ascending nodes[a].name, nodes[b].name ) count: d3.range(n).sort((a, b) -> nodes[b].count - (nodes[a].count) ) type: d3.range(n).sort((a, b) -> createGroupType(nodes[b].type) - createGroupType(nodes[a].type) ) # The default sort order. x.domain orders.name #svg.append('rect').attr('class', 'misbackground').attr('width', width/divider).attr('height', height/divider) row = svg.selectAll('.row').data(matrix).enter().append('g').attr('class', 'row').attr('transform', (d, i) -> 'translate(0,' + x(i) + ')' ).each(row) row.append('line').attr('x2', 5600/divider) row.append('text').attr('x', -6).attr('y', x.rangeBand() / 2).attr('dy', '.32em').attr('text-anchor', 'end').text((d, i) -> if nodes[i].hasOwnProperty("label") nodes[i].label else shortenStringNoMiddle(nodes[i].name) ).call(wrapNoNewLine) column = svg.selectAll('.column').data(matrix).enter().append('g').attr('class', 'column').attr('transform', (d, i) -> 'translate(' + x(i) + ')rotate(-90)' ) column.append('line').attr('x1', 5600/divider * (-1)) column.append('text').attr('x', 6).attr('y', x.rangeBand() / 2).attr('dy', '.32em').attr('text-anchor', 'start').text((d, i) -> if nodes[i].hasOwnProperty("label") nodes[i].label else shortenStringNoMiddle(nodes[i].name) ).call(wrapNoNewLine) d3.select('#order').on 'change', -> # clearTimeout timeout order @value return return @draw_sankey = -> width = 950 height = 750 lowOpacity = 0.3 hoverOpacity = 0.7 highOpacity = 0.9 # zoom the d3 zoom = d3.behavior.zoom().scaleExtent([ 0.5 10 ]).on('zoom', zoomed) # load the svg#sankeyContainer # set the width and height attributes # append a function g that has a tranform process defined by translation svgContainer = d3.select('svg#provContainer') # define the sankey object # set the node width to 15 # set the node padding to 10 sankey = d3.sankey().nodeWidth(20).nodePadding(10) # request the sankey path of current sankey path = sankey.reversibleLink() # load data to work with # compute a better width and height for the container nodesCount = Object.keys(graph.provenance.nodes).length linksCount = Object.keys(graph.provenance.links).length if nodesCount > 0 or linksCount > 0 ratioLN = linksCount / nodesCount * 100 width = width + Math.floor( ratioLN * 3 ) height = height + Math.floor( ratioLN ) setGLWidth(width) $('canvas#canvasPROV').attr 'width': width 'height': height svgContainer.attr('width', width+125).attr('height', height+150).append('g') rect = svgContainer.append('rect').attr('width', width).attr('height', height).style('fill', 'none').style('pointer-events', 'all') sankey = sankey.size([width, height]) svg = svgContainer.append('g').attr("id", "zoomContainer").attr('transform', 'translate(0,' + 75 + ')') svgContainer.call(zoom).on("dblclick.zoom", null).on("click.zoom", null).on("mousedown.zoom", null) # set the nodes # set the links # set the layout sankey.nodes(graph.provenance.nodes).links(graph.provenance.links) sankey.layout(32) legendCategories = { "category":[{"type":"Workflow Run"},{"type":"Process Run"}, {"type":"Artifact"}, {"type":"Dictionary"}] } legend = svgContainer.append('g').attr('class', 'legend').attr('x', 0).attr('y', 0).selectAll('.category').data(legendCategories.category).enter().append('g').attr('class', 'category') legendConfig = rectWidth: 20 rectHeight: 14 xOffset: 625 yOffset: 30 xOffsetText: 5 yOffsetText: 11 lineHeight: 10 wordApart: 125 legendConfig.xOffsetText += 20 legendConfig.yOffsetText += legendConfig.yOffset legend.append('rect').attr('x', (d, i) -> legendConfig.xOffset + i * legendConfig.wordApart ) .attr('y', legendConfig.yOffset).attr('height', legendConfig.rectHeight).attr('width', legendConfig.rectWidth).style('fill', (d) -> getColorHex(d.type) ).style('stroke', (d) -> d3.rgb(d.color).darker 1 ) legend.append('text').attr('x', (d, i) -> legendConfig.xOffset + i * legendConfig.wordApart + legendConfig.xOffsetText ).attr('y', legendConfig.yOffsetText).text((d) -> d.type ) # select all the links from the json-data and append them to the Sankey obj in alphabetical order link = svg.append('g').selectAll('.link').data(graph.provenance.links).enter().append('g').attr('class', 'link').attr('id', (d,i) -> d.id = i "link-" + i ).sort((a, b) -> b.dy - (a.dy)) p0 = link.append("path").attr("d", path(0)) p1 = link.append("path").attr("d", path(1)) p2 = link.append("path").attr("d", path(2)) link.attr('fill', (d) -> getColorHex(d.source.type) ).attr('opacity', lowOpacity).on('mouseover', (d) -> if parseFloat(d3.select(this).style('opacity')) != highOpacity d3.select(this).style('opacity', hoverOpacity) ).on('mouseout', (d) -> if parseFloat(d3.select(this).style('opacity')) != highOpacity d3.select(this).style('opacity', lowOpacity) ) # set the text for the edges link.append('title').text (d) -> dash = '\n-----------------------------------------------------------\n' startText = d.source.type + ' → ' + d.target.type + dash + 'Source:\nURI: ' + d.source.name endText = 'Target:\nURI: ' + d.target.name startText + dash + endText # create the function to drag the node dragmove = (d) -> # uncomment the following to disable x movement (and comment the next line ) #d3.select(this).attr('transform', 'translate(' + d.x + ',' + (d.y = Math.max(0, Math.min(height - (d.dy), d3.event.y))) + ')') d3.select(this).attr('transform', 'translate(' + (d.x = Math.max(0, Math.min(width - (d.dx), d3.event.x))) + ',' + (d.y = Math.max(0, Math.min(height - (d.dy), d3.event.y))) + ')') sankey.relayout() p0.attr("d", path(1)) p1.attr("d", path(0)) p2.attr("d", path(2)) return # select all the nodes from the json-data and append them to the Sankey obj # add behavior : dragmove node = svg.append('g').selectAll('.node').data(graph.provenance.nodes).enter().append('g').attr('class', 'node').attr('transform', (d) -> yValue = Math.min(d.y, height) 'translate(' + d.x + ',' + yValue + ')' ).call(d3.behavior.drag().origin((d) -> d ).on('dragstart', -> @parentNode.appendChild this return ).on('drag', dragmove)) # choose the form of the node : filled rectangle # set the height of the rectangle to d.dy # set the width of the rectangle to nodeWidth? # set the style to be filled with default color node.append('rect').attr('height', (d) -> Math.max 10, d.dy ).attr('data-clicked', '0').attr('width', sankey.nodeWidth()).style('fill', (d) -> getColorHex(d.type) ).style('stroke', (d) -> d3.rgb(d.color).darker 1 ).append('title').text((d) -> returnedStr = d.type + dashLine returnedStr += 'URI: ' + d.name + dashLine returnedStr += d.label.split("\\n").join("\n") if(d.type == "Process Run") returnedStr += dashLine + getTimes(d) else if(d.type == "Artifact" || d.type == "Dictionary" && d.content) returnedStr += dashLine + "Content :\n" + shortenString(d.content, 500) returnedStr ) #modify the link opacity to the given opacity click_highlight_path_color = (id, opacity) -> d3.select('#link-' + id).style('opacity', opacity) click_highlight_path = (node, i) -> # check if the user wants to drag or to click the node # if he wants to drag then the following will be true if (d3.event.defaultPrevented) return remainingNodes = [] nextNodes = [] stroke_opacity = 0 # if a node has been clicked and then mark it as unclick if clicked again if d3.select(this).attr('data-clicked') == '1' d3.select(this).attr('data-clicked', '0') stroke_opacity = lowOpacity else d3.select(this).attr('data-clicked', '1') stroke_opacity = highOpacity # remember all visited nodes and the path # traverse will be a JSON array traverse = [ { linkType: 'sourceLinks' nodeType: 'target' } { linkType: 'targetLinks' nodeType: 'source' } ] # for each object inside traverse traverse.forEach (step) -> # for each (outgoing,incoming) link node[step.linkType].forEach (link) -> remainingNodes.push(link[step.nodeType]) click_highlight_path_color(link.id, stroke_opacity) return while remainingNodes.length nextNodes = [] remainingNodes.forEach (node) -> node[step.linkType].forEach (link) -> nextNodes.push(link[step.nodeType]) click_highlight_path_color(link.id, stroke_opacity) return return remainingNodes = nextNodes return return cc = clickCancel() # show the whole path on single click on nodes # the function highlight_node_links uses Breadth First Search alghorithm to find the reachable nodes # add remove the outgoing edges from current node on dblclick node.call(cc).on('click', click_highlight_path).on('dblclick', (d)-> if (d3.event.defaultPrevented) return svg.selectAll('.link').filter((l) -> l.source == d ).attr('display', -> if d3.select(this).attr('display') == 'none' 'inline' else 'none' ) return ) # set the text of the nodes # set their position # set their font # set the anchor of the text node.append('text').attr('x', (d) -> d.dx/2 - 12 ).attr('y', (d) -> d.dy/2 - 10 ).attr('text-anchor', 'end') .text((d) -> if d.hasOwnProperty("label") d.label else shortenStringNoMiddle(d.name) ).call(wrap).filter((d) -> d.x < width / 5 ).attr('x', "22").attr('text-anchor', 'start') # select all the nodes from the json-data and append them to the Sankey obj # add behavior : dragmove return #http://techslides.com/save-svg-as-an-image d3.select('#saveWF').on('click', -> html = d3.select('svg#graphContainer').attr('version', 1.1).attr('xmlns', 'http://www.w3.org/2000/svg').node().parentNode.innerHTML imgsrc = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(html))) img = '<img src="' + imgsrc + '">' canvas = document.querySelector('canvas#canvasWF') context = canvas.getContext("2d") image = new Image image.src = imgsrc image.onload = -> context.drawImage(image, 0, 0) canvasdata = canvas.toDataURL('image/png') pngimg = '<img src="' + canvasdata + '">' now = new Date differential = now.getDate() + "_" + now.getMonth() + "_" + now.getFullYear() + "_" + now.getHours() + "_" + now.getMinutes() + "_" + now.getSeconds() a = document.createElement('a') a.download = 'workflow_' + differential + '.png' a.href = canvasdata a.click() return return ) d3.select('#savePROV').on('click', -> html = d3.select('svg#provContainer').attr('version', 1.1).attr('xmlns', 'http://www.w3.org/2000/svg').node().parentNode.innerHTML imgsrc = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(html))) img = '<img src="' + imgsrc + '">' canvas = document.querySelector('canvas#canvasPROV') context = canvas.getContext("2d") image = new Image image.src = imgsrc image.onload = -> context.drawImage(image, 0, 0) canvasdata = canvas.toDataURL('image/png') pngimg = '<img src="' + canvasdata + '">' now = new Date differential = now.getDate() + "_" + now.getMonth() + "_" + now.getFullYear() + "_" + now.getHours() + "_" + now.getMinutes() + "_" + now.getSeconds() a = document.createElement('a') a.download = 'provenance_' + differential + '.png' a.href = canvasdata a.click() return return )