Skip to content

Instantly share code, notes, and snippets.

@Soupala
Forked from nitaku/README.md
Last active August 29, 2015 14:27
Show Gist options
  • Select an option

  • Save Soupala/36686e84c7d5f3249f73 to your computer and use it in GitHub Desktop.

Select an option

Save Soupala/36686e84c7d5f3249f73 to your computer and use it in GitHub Desktop.

Revisions

  1. @nitaku nitaku revised this gist May 28, 2015. 1 changed file with 0 additions and 0 deletions.
    Binary file modified thumbnail.png
    Loading
    Sorry, something went wrong. Reload?
    Sorry, we cannot display this file.
    Sorry, this file is invalid so it cannot be displayed.
  2. @nitaku nitaku revised this gist May 27, 2015. 1 changed file with 0 additions and 0 deletions.
    Binary file modified thumbnail.png
    Loading
    Sorry, something went wrong. Reload?
    Sorry, we cannot display this file.
    Sorry, this file is invalid so it cannot be displayed.
  3. @nitaku nitaku revised this gist Nov 18, 2013. 2 changed files with 2 additions and 2 deletions.
    2 changes: 1 addition & 1 deletion index.coffee
    Original file line number Diff line number Diff line change
    @@ -260,7 +260,7 @@ window.main = (() ->
    .on('mousemove.add_link', ((d) ->
    ### check if there is a new link in creation ###
    if global.new_link_source?
    ### create the draggable link representation ###
    ### update the draggable link representation ###
    p = d3.mouse(global.vis.node())
    global.drag_link
    .attr('x1', global.new_link_source.x) # source node can be in movement
    2 changes: 1 addition & 1 deletion index.js
    Original file line number Diff line number Diff line change
    @@ -312,7 +312,7 @@
    */
    var p;
    if (global.new_link_source != null) {
    /* create the draggable link representation
    /* update the draggable link representation
    */
    p = d3.mouse(global.vis.node());
    return global.drag_link.attr('x1', global.new_link_source.x).attr('y1', global.new_link_source.y).attr('x2', p[0]).attr('y2', p[1]);
  4. @nitaku nitaku revised this gist Nov 17, 2013. 1 changed file with 4 additions and 0 deletions.
    4 changes: 4 additions & 0 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -8,4 +8,8 @@ The example activates and deactivates d3.js's drag behavior on nodes by using a

    Mouse coordinates are used to move the representation of a new link. The coordinates are retrieved as in [this example](http://bl.ocks.org/nitaku/6901037); Please note that the container passed to `d3.mouse` has to be a DOM node, not a d3 selection. To obtain a DOM node from a selection, use the `selection.node()` method ([d3 wiki](https://github.com/mbostock/d3/wiki/Selections#wiki-node)).

    To make the text labels unselectable, `d3.event.preventDefault()` is used in the `mousedown` handler. For preventing the text to be ever selected at all (for example when dragging from the HTML node library), the CSS property `user-select` has been set to `none` (see [this link](http://stackoverflow.com/questions/69430/is-there-a-way-to-make-text-unselectable-on-an-html-page)).

    There is no support for adding links with touch gestures.

    The code is a real mess, and some questionable choices have been made because I ran out of time (like for example inlining the SVG for the tool buttons).
  5. @nitaku nitaku revised this gist Nov 17, 2013. 2 changed files with 0 additions and 3 deletions.
    1 change: 0 additions & 1 deletion index.coffee
    Original file line number Diff line number Diff line change
    @@ -150,7 +150,6 @@ window.main = (() ->
    global.drag = global.force.drag().on('dragstart', (d) -> d.fixed = true)

    ### DELETION - pressing DEL deletes the selection ###
    ### CREATION - pressing N creates a new node ###
    d3.select(window)
    .on('keydown', () ->
    if d3.event.keyCode is 46 # DEL
    2 changes: 0 additions & 2 deletions index.js
    Original file line number Diff line number Diff line change
    @@ -276,8 +276,6 @@
    });
    /* DELETION - pressing DEL deletes the selection
    */
    /* CREATION - pressing N creates a new node
    */
    d3.select(window).on('keydown', function() {
    if (d3.event.keyCode === 46) {
    if (global.selection != null) {
  6. @nitaku nitaku revised this gist Nov 17, 2013. 1 changed file with 4 additions and 0 deletions.
    4 changes: 4 additions & 0 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -8,4 +8,8 @@ The example activates and deactivates d3.js's drag behavior on nodes by using a

    Mouse coordinates are used to move the representation of a new link. The coordinates are retrieved as in [this example](http://bl.ocks.org/nitaku/6901037); Please note that the container passed to `d3.mouse` has to be a DOM node, not a d3 selection. To obtain a DOM node from a selection, use the `selection.node()` method ([d3 wiki](https://github.com/mbostock/d3/wiki/Selections#wiki-node)).

    To make the text labels unselectable, `d3.event.preventDefault()` is used in the `mousedown` handler. For preventing the text to be ever selected at all (for example when dragging from the HTML node library), the CSS property `user-select` has been set to `none` (see [this link](http://stackoverflow.com/questions/69430/is-there-a-way-to-make-text-unselectable-on-an-html-page)).

    There is no support for adding links with touch gestures.

    The code is a real mess, and some questionable choices have been made because I ran out of time (like for example inlining the SVG for the tool buttons).
  7. @nitaku nitaku revised this gist Nov 17, 2013. 2 changed files with 13 additions and 1 deletion.
    7 changes: 6 additions & 1 deletion index.css
    Original file line number Diff line number Diff line change
    @@ -7,6 +7,11 @@
    font-family: sans-serif;
    font-weight: bold;
    text-anchor: middle;
    -webkit-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    -o-user-select: none;
    user-select: none;
    }

    .link, .drag_link {
    @@ -55,7 +60,7 @@
    .library {
    border: 2px solid #a3a4c3;
    border-radius: 6px;
    background: rgba(255, 255, 255, 0.7);
    background: rgba(255, 255, 255, 0.8);
    padding: 6px;
    margin-top: 10px;
    }
    7 changes: 7 additions & 0 deletions index.sass
    Original file line number Diff line number Diff line change
    @@ -7,6 +7,13 @@
    font-weight: bold
    text-anchor: middle

    // prevent text selection
    -webkit-user-select: none
    -moz-user-select: none
    -ms-user-select: none
    -o-user-select: none
    user-select: none

    // editing
    .link, .drag_link
    stroke-width: 6px
  8. @nitaku nitaku revised this gist Nov 17, 2013. 1 changed file with 8 additions and 2 deletions.
    10 changes: 8 additions & 2 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -1,5 +1,11 @@
    Starting from [the previous example](http://bl.ocks.org/nitaku/7493693), this one implements a more complete graph editing tool.

    Select a tool to operate on the graph: The *pointer* only lets the user pan & zoom or drag nodes around. The *add node tool* opens up a library of node types; drag one into the graph to add a node of that type. The *add link tool* lets the user connect two nodes with a drag gesture from the first to the second one.
    Select a tool to operate on the graph: The *pointer* only lets the user pan & zoom or drag nodes around. The *add node tool* opens up a library of node types; click one to add a node of that type to the graph. The *add link tool* lets the user connect two nodes with a drag gesture from the first to the second one.

    Clicking a node or a link always selects it regardless of the current tool. You can press `del` to remove the current selection.
    Clicking a node or a link always selects it regardless of the current tool. You can press `del` to remove the current selection.

    The example activates and deactivates d3.js's drag behavior on nodes by using a technique found in [this example](http://bl.ocks.org/rkirsling/5001347) by [Ross Kirsling](https://github.com/rkirsling): Everytime the behavior has to be deactivated, the underlying listeners (`mousedown.drag` and `touchstart.drag`) are cleared.

    Mouse coordinates are used to move the representation of a new link. The coordinates are retrieved as in [this example](http://bl.ocks.org/nitaku/6901037); Please note that the container passed to `d3.mouse` has to be a DOM node, not a d3 selection. To obtain a DOM node from a selection, use the `selection.node()` method ([d3 wiki](https://github.com/mbostock/d3/wiki/Selections#wiki-node)).

    The code is a real mess, and some questionable choices have been made because I ran out of time (like for example inlining the SVG for the tool buttons).
  9. @nitaku nitaku revised this gist Nov 17, 2013. 1 changed file with 0 additions and 0 deletions.
    Binary file modified thumbnail.png
    Loading
    Sorry, something went wrong. Reload?
    Sorry, we cannot display this file.
    Sorry, this file is invalid so it cannot be displayed.
  10. @nitaku nitaku revised this gist Nov 17, 2013. 1 changed file with 8 additions and 2 deletions.
    10 changes: 8 additions & 2 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -1,5 +1,11 @@
    Starting from [the previous example](http://bl.ocks.org/nitaku/7493693), this one implements a more complete graph editing tool.

    Select a tool to operate on the graph: The *pointer* only lets the user pan & zoom or drag nodes around. The *add node tool* opens up a library of node types; drag one into the graph to add a node of that type. The *add link tool* lets the user connect two nodes with a drag gesture from the first to the second one.
    Select a tool to operate on the graph: The *pointer* only lets the user pan & zoom or drag nodes around. The *add node tool* opens up a library of node types; click one to add a node of that type to the graph. The *add link tool* lets the user connect two nodes with a drag gesture from the first to the second one.

    Clicking a node or a link always selects it regardless of the current tool. You can press `del` to remove the current selection.
    Clicking a node or a link always selects it regardless of the current tool. You can press `del` to remove the current selection.

    The example activates and deactivates d3.js's drag behavior on nodes by using a technique found in [this example](http://bl.ocks.org/rkirsling/5001347) by [Ross Kirsling](https://github.com/rkirsling): Everytime the behavior has to be deactivated, the underlying listeners (`mousedown.drag` and `touchstart.drag`) are cleared.

    Mouse coordinates are used to move the representation of a new link. The coordinates are retrieved as in [this example](http://bl.ocks.org/nitaku/6901037); Please note that the container passed to `d3.mouse` has to be a DOM node, not a d3 selection. To obtain a DOM node from a selection, use the `selection.node()` method ([d3 wiki](https://github.com/mbostock/d3/wiki/Selections#wiki-node)).

    The code is a real mess, and some questionable choices have been made because I ran out of time (like for example inlining the SVG for the tool buttons).
  11. @nitaku nitaku revised this gist Nov 17, 2013. 5 changed files with 114 additions and 19 deletions.
    78 changes: 70 additions & 8 deletions index.coffee
    Original file line number Diff line number Diff line change
    @@ -166,9 +166,69 @@ window.main = (() ->
    toolbar = $("<div class='toolbar'></div>")
    $('body').append(toolbar)

    toolbar.append($("<input type='radio' name='tool' value='pointer' checked='checked'>Pointer</input>"))
    toolbar.append($("<input type='radio' name='tool' value='add_node'>Add node</input>"))
    toolbar.append($("<input type='radio' name='tool' value='add_link'>Add link</input>"))
    toolbar.append($("""
    <svg
    class='active tool'
    data-tool='pointer'
    xmlns='http://www.w3.org/2000/svg'
    version='1.1'
    width='32'
    height='32'
    viewBox='0 0 128 128'>
    <g transform='translate(881.10358,-356.22543)'>
    <g transform='matrix(0.8660254,-0.5,0.5,0.8660254,-266.51112,-215.31898)'>
    <path
    d='m -797.14902,212.29589 a 5.6610848,8.6573169 0 0 0 -4.61823,4.3125 l -28.3428,75.0625 a 5.6610848,8.6573169 0 0 0 4.90431,13 l 56.68561,0 a 5.6610848,8.6573169 0 0 0 4.9043,-13 l -28.3428,-75.0625 a 5.6610848,8.6573169 0 0 0 -5.19039,-4.3125 z m 0.28608,25.96875 18.53419,49.09375 -37.06838,0 18.53419,-49.09375 z'
    />
    <path
    d='m -801.84375,290.40625 c -2.09434,2.1e-4 -3.99979,1.90566 -4,4 l 0,35.25 c 2.1e-4,2.09434 1.90566,3.99979 4,4 l 10,0 c 2.09434,-2.1e-4 3.99979,-1.90566 4,-4 l 0,-35.25 c -2.1e-4,-2.09434 -1.90566,-3.99979 -4,-4 z'
    />
    </g>
    </g>
    </svg>
    """))
    toolbar.append($("""
    <svg
    class='tool'
    data-tool='add_node'
    xmlns='http://www.w3.org/2000/svg'
    version='1.1'
    width='32'
    height='32'
    viewBox='0 0 128 128'>
    <g transform='translate(720.71649,-356.22543)'>
    <g transform='translate(-3.8571429,146.42857)'>
    <path
    d='m -658.27638,248.37149 c -1.95543,0.19978 -3.60373,2.03442 -3.59375,4 l 0,12.40625 -12.40625,0 c -2.09434,2.1e-4 -3.99979,1.90566 -4,4 l 0,10 c -0.007,0.1353 -0.007,0.27095 0,0.40625 0.19978,1.95543 2.03442,3.60373 4,3.59375 l 12.40625,0 0,12.4375 c 2.1e-4,2.09434 1.90566,3.99979 4,4 l 10,0 c 2.09434,-2.1e-4 3.99979,-1.90566 4,-4 l 0,-12.4375 12.4375,0 c 2.09434,-2.1e-4 3.99979,-1.90566 4,-4 l 0,-10 c -2.1e-4,-2.09434 -1.90566,-3.99979 -4,-4 l -12.4375,0 0,-12.40625 c -2.1e-4,-2.09434 -1.90566,-3.99979 -4,-4 l -10,0 c -0.1353,-0.007 -0.27095,-0.007 -0.40625,0 z'
    />
    <path
    d='m -652.84375,213.9375 c -32.97528,0 -59.875,26.86847 -59.875,59.84375 0,32.97528 26.89972,59.875 59.875,59.875 32.97528,0 59.84375,-26.89972 59.84375,-59.875 0,-32.97528 -26.86847,-59.84375 -59.84375,-59.84375 z m 0,14 c 25.40911,0 45.84375,20.43464 45.84375,45.84375 0,25.40911 -20.43464,45.875 -45.84375,45.875 -25.40911,0 -45.875,-20.46589 -45.875,-45.875 0,-25.40911 20.46589,-45.84375 45.875,-45.84375 z'
    />
    </g>
    </g>
    </svg>
    """))
    toolbar.append($("""
    <svg
    class='tool'
    data-tool='add_link'
    xmlns='http://www.w3.org/2000/svg'
    version='1.1'
    width='32'
    height='32'
    viewBox='0 0 128 128'>
    <g transform='translate(557.53125,-356.22543)'>
    <g transform='translate(20,0)'>
    <path
    d='m -480.84375,360 c -15.02602,0 -27.375,12.31773 -27.375,27.34375 0,4.24084 1.00221,8.28018 2.75,11.875 l -28.875,28.875 c -3.59505,-1.74807 -7.6338,-2.75 -11.875,-2.75 -15.02602,0 -27.34375,12.34898 -27.34375,27.375 0,15.02602 12.31773,27.34375 27.34375,27.34375 15.02602,0 27.375,-12.31773 27.375,-27.34375 0,-4.26067 -0.98685,-8.29868 -2.75,-11.90625 L -492.75,411.96875 c 3.60156,1.75589 7.65494,2.75 11.90625,2.75 15.02602,0 27.34375,-12.34898 27.34375,-27.375 C -453.5,372.31773 -465.81773,360 -480.84375,360 z m 0,14 c 7.45986,0 13.34375,5.88389 13.34375,13.34375 0,7.45986 -5.88389,13.375 -13.34375,13.375 -7.45986,0 -13.375,-5.91514 -13.375,-13.375 0,-7.45986 5.91514,-13.34375 13.375,-13.34375 z m -65.375,65.34375 c 7.45986,0 13.34375,5.91514 13.34375,13.375 0,7.45986 -5.88389,13.34375 -13.34375,13.34375 -7.45986,0 -13.34375,-5.88389 -13.34375,-13.34375 0,-7.45986 5.88389,-13.375 13.34375,-13.375 z'
    />
    <path
    d='m -484.34375,429.25 c -1.95543,0.19978 -3.60373,2.03442 -3.59375,4 l 0,12.40625 -12.40625,0 c -2.09434,2.1e-4 -3.99979,1.90566 -4,4 l 0,10 c -0.007,0.1353 -0.007,0.27095 0,0.40625 0.19978,1.95543 2.03442,3.60373 4,3.59375 l 12.40625,0 0,12.4375 c 2.1e-4,2.09434 1.90566,3.99979 4,4 l 10,0 c 2.09434,-2.1e-4 3.99979,-1.90566 4,-4 l 0,-12.4375 12.4375,0 c 2.09434,-2.1e-4 3.99979,-1.90566 4,-4 l 0,-10 c -2.1e-4,-2.09434 -1.90566,-3.99979 -4,-4 l -12.4375,0 0,-12.40625 c -2.1e-4,-2.09434 -1.90566,-3.99979 -4,-4 l -10,0 c -0.1353,-0.007 -0.27095,-0.007 -0.40625,0 z'
    />
    </g>
    </g>
    </svg>
    """))

    library = $("<div class='library'></div></div>")
    toolbar.append(library)
    @@ -193,8 +253,8 @@ window.main = (() ->

    library.append(new_btn)
    library.hide()

    global.tool = $("input[name='tool']:checked").val()
    global.tool = 'pointer'

    global.new_link_source = null
    global.vis
    @@ -216,8 +276,11 @@ window.main = (() ->
    global.drag_link.remove() if global.drag_link?
    ))

    $("input[name='tool']").change(() ->
    new_tool = $("input[name='tool']:checked").val()
    d3.selectAll('.tool').on 'click', () ->
    d3.selectAll('.tool').classed('active', false)
    d3.select(this).classed('active', true)

    new_tool = $(this).data('tool')

    nodes = global.vis.selectAll('.node')

    @@ -247,7 +310,6 @@ window.main = (() ->
    library.hide()

    global.tool = new_tool
    )
    )

    update = () ->
    21 changes: 19 additions & 2 deletions index.css
    Original file line number Diff line number Diff line change
    @@ -12,7 +12,7 @@
    .link, .drag_link {
    stroke-width: 6px;
    stroke: gray;
    opacity: 0.6;
    opacity: 0.5;
    }

    .drag_link {
    @@ -37,13 +37,30 @@
    left: 480px;
    width: 240px;
    margin-left: -120px;
    text-align: center;
    }

    .tool:not(:last-child) {
    margin-right: 10px;
    }

    .tool {
    fill: #a3a4c3;
    cursor: pointer;
    }
    .tool.active {
    fill: #b52d0c;
    }

    .library {
    border: 2px solid gray;
    border: 2px solid #a3a4c3;
    border-radius: 6px;
    background: rgba(255, 255, 255, 0.7);
    padding: 6px;
    margin-top: 10px;
    }
    .library svg {
    cursor: pointer;
    }
    .library svg:not(:last-child) {
    padding-right: 6px;
    14 changes: 8 additions & 6 deletions index.js
    Original file line number Diff line number Diff line change
    @@ -292,9 +292,9 @@
    */
    toolbar = $("<div class='toolbar'></div>");
    $('body').append(toolbar);
    toolbar.append($("<input type='radio' name='tool' value='pointer' checked='checked'>Pointer</input>"));
    toolbar.append($("<input type='radio' name='tool' value='add_node'>Add node</input>"));
    toolbar.append($("<input type='radio' name='tool' value='add_link'>Add link</input>"));
    toolbar.append($("<svg\n class='active tool'\n data-tool='pointer'\n xmlns='http://www.w3.org/2000/svg'\n version='1.1'\n width='32'\n height='32'\n viewBox='0 0 128 128'>\n <g transform='translate(881.10358,-356.22543)'>\n <g transform='matrix(0.8660254,-0.5,0.5,0.8660254,-266.51112,-215.31898)'>\n <path\n d='m -797.14902,212.29589 a 5.6610848,8.6573169 0 0 0 -4.61823,4.3125 l -28.3428,75.0625 a 5.6610848,8.6573169 0 0 0 4.90431,13 l 56.68561,0 a 5.6610848,8.6573169 0 0 0 4.9043,-13 l -28.3428,-75.0625 a 5.6610848,8.6573169 0 0 0 -5.19039,-4.3125 z m 0.28608,25.96875 18.53419,49.09375 -37.06838,0 18.53419,-49.09375 z'\n />\n <path\n d='m -801.84375,290.40625 c -2.09434,2.1e-4 -3.99979,1.90566 -4,4 l 0,35.25 c 2.1e-4,2.09434 1.90566,3.99979 4,4 l 10,0 c 2.09434,-2.1e-4 3.99979,-1.90566 4,-4 l 0,-35.25 c -2.1e-4,-2.09434 -1.90566,-3.99979 -4,-4 z'\n />\n </g>\n </g>\n</svg>"));
    toolbar.append($("<svg\n class='tool'\n data-tool='add_node'\n xmlns='http://www.w3.org/2000/svg'\n version='1.1'\n width='32'\n height='32'\n viewBox='0 0 128 128'>\n <g transform='translate(720.71649,-356.22543)'>\n <g transform='translate(-3.8571429,146.42857)'>\n <path\n d='m -658.27638,248.37149 c -1.95543,0.19978 -3.60373,2.03442 -3.59375,4 l 0,12.40625 -12.40625,0 c -2.09434,2.1e-4 -3.99979,1.90566 -4,4 l 0,10 c -0.007,0.1353 -0.007,0.27095 0,0.40625 0.19978,1.95543 2.03442,3.60373 4,3.59375 l 12.40625,0 0,12.4375 c 2.1e-4,2.09434 1.90566,3.99979 4,4 l 10,0 c 2.09434,-2.1e-4 3.99979,-1.90566 4,-4 l 0,-12.4375 12.4375,0 c 2.09434,-2.1e-4 3.99979,-1.90566 4,-4 l 0,-10 c -2.1e-4,-2.09434 -1.90566,-3.99979 -4,-4 l -12.4375,0 0,-12.40625 c -2.1e-4,-2.09434 -1.90566,-3.99979 -4,-4 l -10,0 c -0.1353,-0.007 -0.27095,-0.007 -0.40625,0 z'\n />\n <path\n d='m -652.84375,213.9375 c -32.97528,0 -59.875,26.86847 -59.875,59.84375 0,32.97528 26.89972,59.875 59.875,59.875 32.97528,0 59.84375,-26.89972 59.84375,-59.875 0,-32.97528 -26.86847,-59.84375 -59.84375,-59.84375 z m 0,14 c 25.40911,0 45.84375,20.43464 45.84375,45.84375 0,25.40911 -20.43464,45.875 -45.84375,45.875 -25.40911,0 -45.875,-20.46589 -45.875,-45.875 0,-25.40911 20.46589,-45.84375 45.875,-45.84375 z'\n />\n </g>\n </g>\n</svg>"));
    toolbar.append($("<svg\n class='tool'\n data-tool='add_link'\n xmlns='http://www.w3.org/2000/svg'\n version='1.1'\n width='32'\n height='32'\n viewBox='0 0 128 128'>\n<g transform='translate(557.53125,-356.22543)'>\n <g transform='translate(20,0)'>\n <path\n d='m -480.84375,360 c -15.02602,0 -27.375,12.31773 -27.375,27.34375 0,4.24084 1.00221,8.28018 2.75,11.875 l -28.875,28.875 c -3.59505,-1.74807 -7.6338,-2.75 -11.875,-2.75 -15.02602,0 -27.34375,12.34898 -27.34375,27.375 0,15.02602 12.31773,27.34375 27.34375,27.34375 15.02602,0 27.375,-12.31773 27.375,-27.34375 0,-4.26067 -0.98685,-8.29868 -2.75,-11.90625 L -492.75,411.96875 c 3.60156,1.75589 7.65494,2.75 11.90625,2.75 15.02602,0 27.34375,-12.34898 27.34375,-27.375 C -453.5,372.31773 -465.81773,360 -480.84375,360 z m 0,14 c 7.45986,0 13.34375,5.88389 13.34375,13.34375 0,7.45986 -5.88389,13.375 -13.34375,13.375 -7.45986,0 -13.375,-5.91514 -13.375,-13.375 0,-7.45986 5.91514,-13.34375 13.375,-13.34375 z m -65.375,65.34375 c 7.45986,0 13.34375,5.91514 13.34375,13.375 0,7.45986 -5.88389,13.34375 -13.34375,13.34375 -7.45986,0 -13.34375,-5.88389 -13.34375,-13.34375 0,-7.45986 5.88389,-13.375 13.34375,-13.375 z'\n />\n <path\n d='m -484.34375,429.25 c -1.95543,0.19978 -3.60373,2.03442 -3.59375,4 l 0,12.40625 -12.40625,0 c -2.09434,2.1e-4 -3.99979,1.90566 -4,4 l 0,10 c -0.007,0.1353 -0.007,0.27095 0,0.40625 0.19978,1.95543 2.03442,3.60373 4,3.59375 l 12.40625,0 0,12.4375 c 2.1e-4,2.09434 1.90566,3.99979 4,4 l 10,0 c 2.09434,-2.1e-4 3.99979,-1.90566 4,-4 l 0,-12.4375 12.4375,0 c 2.09434,-2.1e-4 3.99979,-1.90566 4,-4 l 0,-10 c -2.1e-4,-2.09434 -1.90566,-3.99979 -4,-4 l -12.4375,0 0,-12.40625 c -2.1e-4,-2.09434 -1.90566,-3.99979 -4,-4 l -10,0 c -0.1353,-0.007 -0.27095,-0.007 -0.40625,0 z'\n />\n </g>\n </g>\n</svg>"));
    library = $("<div class='library'></div></div>");
    toolbar.append(library);
    ['X', 'Y', 'Z', 'W'].forEach(function(type) {
    @@ -307,7 +307,7 @@
    library.append(new_btn);
    return library.hide();
    });
    global.tool = $("input[name='tool']:checked").val();
    global.tool = 'pointer';
    global.new_link_source = null;
    global.vis.on('mousemove.add_link', (function(d) {
    /* check if there is a new link in creation
    @@ -325,9 +325,11 @@
    */
    if (global.drag_link != null) return global.drag_link.remove();
    }));
    return $("input[name='tool']").change(function() {
    return d3.selectAll('.tool').on('click', function() {
    var new_tool, nodes;
    new_tool = $("input[name='tool']:checked").val();
    d3.selectAll('.tool').classed('active', false);
    d3.select(this).classed('active', true);
    new_tool = $(this).data('tool');
    nodes = global.vis.selectAll('.node');
    if (new_tool === 'add_link' && global.tool !== 'add_link') {
    /* remove drag handlers from nodes
    20 changes: 17 additions & 3 deletions index.sass
    Original file line number Diff line number Diff line change
    @@ -11,7 +11,7 @@
    .link, .drag_link
    stroke-width: 6px
    stroke: gray
    opacity: 0.6
    opacity: 0.5

    .drag_link
    stroke-linecap: round
    @@ -34,13 +34,27 @@
    left: 480px
    width: 240px
    margin-left: -120px
    text-align: center

    .tool:not(:last-child)
    margin-right: 10px

    .tool
    fill: #A3A4C3
    cursor: pointer
    &.active
    fill: #B52D0C

    .library
    border: 2px solid gray
    border: 2px solid #A3A4C3
    border-radius: 6px
    background: rgba(255,255,255,0.7)
    background: rgba(255,255,255,0.8)
    padding: 6px
    margin-top: 10px

    svg
    cursor: pointer

    svg:not(:last-child)
    padding-right: 6px

    Binary file added thumbnail.png
    Loading
    Sorry, something went wrong. Reload?
    Sorry, we cannot display this file.
    Sorry, this file is invalid so it cannot be displayed.
  12. @nitaku nitaku revised this gist Nov 17, 2013. 6 changed files with 885 additions and 0 deletions.
    4 changes: 4 additions & 0 deletions d3.v3.min.js
    4 additions, 0 deletions not shown because the diff is too large. Please use a local Git client to view these changes.
    338 changes: 338 additions & 0 deletions index.coffee
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,338 @@
    width = 960
    height = 500

    ### SELECTION - store the selected node ###
    ### EDITING - store the drag mode (either 'drag' or 'add_link') ###
    global = {
    selection: null
    }

    ### create some fake data ###
    graph = {
    nodes: [
    {id: 'A', x: 469, y: 410, type: 'X'},
    {id: 'B', x: 493, y: 364, type: 'X'},
    {id: 'C', x: 442, y: 365, type: 'X'},
    {id: 'D', x: 467, y: 314, type: 'X'},
    {id: 'E', x: 477, y: 248, type: 'Y'},
    {id: 'F', x: 425, y: 207, type: 'Y'},
    {id: 'G', x: 402, y: 155, type: 'Y'},
    {id: 'H', x: 369, y: 196, type: 'Y'},
    {id: 'I', x: 350, y: 148, type: 'Z'},
    {id: 'J', x: 539, y: 222, type: 'Z'},
    {id: 'K', x: 594, y: 235, type: 'Z'},
    {id: 'L', x: 582, y: 185, type: 'Z'},
    {id: 'M', x: 633, y: 200, type: 'Z'}
    ],
    links: [
    {source: 'A', target: 'B'},
    {source: 'B', target: 'C'},
    {source: 'C', target: 'A'},
    {source: 'B', target: 'D'},
    {source: 'D', target: 'C'},
    {source: 'D', target: 'E'},
    {source: 'E', target: 'F'},
    {source: 'F', target: 'G'},
    {source: 'F', target: 'H'},
    {source: 'G', target: 'H'},
    {source: 'G', target: 'I'},
    {source: 'H', target: 'I'},
    {source: 'J', target: 'E'},
    {source: 'J', target: 'L'},
    {source: 'J', target: 'K'},
    {source: 'K', target: 'L'},
    {source: 'L', target: 'M'},
    {source: 'M', target: 'K'}
    ],
    objectify: (() ->
    ### resolve node IDs (not optimized at all!) ###
    for l in graph.links
    for n in graph.nodes
    if l.source is n.id
    l.source = n
    continue

    if l.target is n.id
    l.target = n
    continue
    ),
    remove: ((condemned) ->
    ### remove the given node or link from the graph, also deleting dangling links if a node is removed ###
    if condemned in graph.nodes
    graph.nodes = graph.nodes.filter (n) -> n isnt condemned
    graph.links = graph.links.filter (l) -> l.source.id isnt condemned.id and l.target.id isnt condemned.id
    else if condemned in graph.links
    graph.links = graph.links.filter (l) -> l isnt condemned
    ),
    last_index: 0,
    add_node: ((type) ->
    n = {
    id: graph.last_index++,
    x: width/2,
    y: height/2,
    type: type
    }
    graph.nodes.push(n)

    return n
    ),
    add_link: ((source, target) ->
    ### avoid links to self ###
    return null if source is target

    ### avoid link duplicates ###
    for link in graph.links
    return null if link.source is source and link.target is target

    l = {
    source: source,
    target: target
    }
    graph.links.push(l)

    return l
    )
    }

    graph.objectify()

    window.main = (() ->
    ### create the SVG ###
    svg = d3.select('body').append('svg')
    .attr('width', width)
    .attr('height', height)

    ### ZOOM and PAN ###
    ### create container elements ###
    container = svg.append('g')

    container.call(d3.behavior.zoom().scaleExtent([0.5, 8]).on('zoom', (() -> global.vis.attr('transform', "translate(#{d3.event.translate})scale(#{d3.event.scale})"))))

    global.vis = container.append('g')

    ### create a rectangular overlay to catch events ###
    ### WARNING rect size is huge but not infinite. this is a dirty hack ###
    global.vis.append('rect')
    .attr('class', 'overlay')
    .attr('x', -500000)
    .attr('y', -500000)
    .attr('width', 1000000)
    .attr('height', 1000000)
    .on('click', ((d) ->
    ### SELECTION ###
    global.selection = null
    d3.selectAll('.node').classed('selected', false)
    d3.selectAll('.link').classed('selected', false)
    ))

    ### END ZOOM and PAN ###

    global.colorify = d3.scale.category10()

    ### initialize the force layout ###
    global.force = d3.layout.force()
    .size([width, height])
    .charge(-400)
    .linkDistance(60)
    .on('tick', (() ->
    ### update nodes and links ###
    global.vis.selectAll('.node')
    .attr('transform', (d) -> "translate(#{d.x},#{d.y})")

    global.vis.selectAll('.link')
    .attr('x1', (d) -> d.source.x)
    .attr('y1', (d) -> d.source.y)
    .attr('x2', (d) -> d.target.x)
    .attr('y2', (d) -> d.target.y)
    ))

    ### DRAG ###
    global.drag = global.force.drag().on('dragstart', (d) -> d.fixed = true)

    ### DELETION - pressing DEL deletes the selection ###
    ### CREATION - pressing N creates a new node ###
    d3.select(window)
    .on('keydown', () ->
    if d3.event.keyCode is 46 # DEL
    if global.selection?
    graph.remove global.selection
    global.selection = null
    update()
    )

    update()

    ### TOOLBAR ###
    toolbar = $("<div class='toolbar'></div>")
    $('body').append(toolbar)

    toolbar.append($("<input type='radio' name='tool' value='pointer' checked='checked'>Pointer</input>"))
    toolbar.append($("<input type='radio' name='tool' value='add_node'>Add node</input>"))
    toolbar.append($("<input type='radio' name='tool' value='add_link'>Add link</input>"))

    library = $("<div class='library'></div></div>")
    toolbar.append(library)

    ['X','Y','Z','W'].forEach (type) ->
    new_btn = $("""
    <svg width='42' height='42'>
    <g class='node'>
    <circle
    cx='21'
    cy='21'
    r='18'
    stroke='#{global.colorify(type)}'
    fill='#{d3.hcl(global.colorify(type)).brighter(3)}'
    />
    </g>
    </svg>
    """)
    new_btn.bind 'click', () ->
    graph.add_node(type)
    update()

    library.append(new_btn)
    library.hide()

    global.tool = $("input[name='tool']:checked").val()

    global.new_link_source = null
    global.vis
    .on('mousemove.add_link', ((d) ->
    ### check if there is a new link in creation ###
    if global.new_link_source?
    ### create the draggable link representation ###
    p = d3.mouse(global.vis.node())
    global.drag_link
    .attr('x1', global.new_link_source.x) # source node can be in movement
    .attr('y1', global.new_link_source.y)
    .attr('x2', p[0])
    .attr('y2', p[1])
    ))
    .on('mouseup.add_link', ((d) ->
    global.new_link_source = null

    ### remove the draggable link representation, if exists ###
    global.drag_link.remove() if global.drag_link?
    ))

    $("input[name='tool']").change(() ->
    new_tool = $("input[name='tool']:checked").val()

    nodes = global.vis.selectAll('.node')

    if new_tool is 'add_link' and global.tool isnt 'add_link'
    ### remove drag handlers from nodes ###
    nodes
    .on('mousedown.drag', null)
    .on('touchstart.drag', null)

    ### add drag handlers for the add_link tool ###
    nodes
    .call(drag_add_link)

    else if new_tool isnt 'add_link' and global.tool is 'add_link'
    ### remove drag handlers for the add_link tool ###
    nodes
    .on('mousedown.add_link', null)
    .on('mouseup.add_link', null)

    ### add drag behavior to nodes ###
    nodes
    .call(global.drag)

    if new_tool is 'add_node'
    library.show()
    else
    library.hide()

    global.tool = new_tool
    )
    )

    update = () ->
    ### update the layout ###
    global.force
    .nodes(graph.nodes)
    .links(graph.links)
    .start()

    ### create nodes and links ###
    ### (links are drawn with insert to make them appear under the nodes) ###

    ### also define a drag behavior to drag nodes ###
    ### dragged nodes become fixed ###
    nodes = global.vis.selectAll('.node')
    .data(graph.nodes, (d) -> d.id)

    new_nodes = nodes
    .enter().append('g')
    .attr('class', 'node')
    .on('click', ((d) ->
    ### SELECTION ###
    global.selection = d
    d3.selectAll('.node').classed('selected', (d2) -> d2 is d)
    d3.selectAll('.link').classed('selected', false)
    ))

    links = global.vis.selectAll('.link')
    .data(graph.links, (d) -> "#{d.source.id}->#{d.target.id}")

    links
    .enter().insert('line', '.node')
    .attr('class', 'link')
    .on('click', ((d) ->
    ### SELECTION ###
    global.selection = d
    d3.selectAll('.link').classed('selected', (d2) -> d2 is d)
    d3.selectAll('.node').classed('selected', false)
    ))

    links
    .exit().remove()

    ### TOOLBAR - add link tool initialization for new nodes ###
    if global.tool is 'add_link'
    new_nodes.call(drag_add_link)
    else
    new_nodes.call(global.drag) # DRAG

    new_nodes.append('circle')
    .attr('r', 18)
    .attr('stroke', (d) -> global.colorify(d.type))
    .attr('fill', (d) -> d3.hcl(global.colorify(d.type)).brighter(3))

    ### draw the label ###
    new_nodes.append('text')
    .text((d) -> d.id)
    .attr('dy', '0.35em')
    .attr('fill', (d) -> global.colorify(d.type))

    nodes
    .exit().remove()

    drag_add_link = (selection) ->
    selection
    .on('mousedown.add_link', ((d) ->
    global.new_link_source = d

    ### create the draggable link representation ###
    p = d3.mouse(global.vis.node())
    global.drag_link = global.vis.insert('line', '.node')
    .attr('class', 'drag_link')
    .attr('x1', d.x)
    .attr('y1', d.y)
    .attr('x2', p[0])
    .attr('y2', p[1])

    ### prevent pan activation ###
    d3.event.stopPropagation()
    ### prevent text selection ###
    d3.event.preventDefault()
    ))
    .on('mouseup.add_link', ((d) ->
    ### add link and update, but only if a link is actually added ###
    if graph.add_link(global.new_link_source, d)?
    update()
    ))

    50 changes: 50 additions & 0 deletions index.css
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,50 @@
    .node > circle {
    stroke-width: 4px;
    }

    .node > text {
    pointer-events: none;
    font-family: sans-serif;
    font-weight: bold;
    text-anchor: middle;
    }

    .link, .drag_link {
    stroke-width: 6px;
    stroke: gray;
    opacity: 0.6;
    }

    .drag_link {
    stroke-linecap: round;
    }

    .selected > circle {
    stroke-width: 8px;
    }

    .selected.link {
    stroke-width: 14px;
    }

    .overlay {
    fill: transparent;
    }

    .toolbar {
    position: absolute;
    top: 12px;
    left: 480px;
    width: 240px;
    margin-left: -120px;
    }

    .library {
    border: 2px solid gray;
    border-radius: 6px;
    background: rgba(255, 255, 255, 0.7);
    padding: 6px;
    }
    .library svg:not(:last-child) {
    padding-right: 6px;
    }
    13 changes: 13 additions & 0 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,13 @@
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="utf-8">
    <title>Graph editing tools</title>
    <link type="text/css" href="index.css" rel="stylesheet"/>
    <script src='//ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js' type='text/javascript'></script>
    <script src="d3.v3.min.js"></script>
    <script src="index.js"></script>
    </head>
    <body onload="main()">
    </body>
    </html>
    434 changes: 434 additions & 0 deletions index.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,434 @@
    (function() {
    var drag_add_link, global, graph, height, update, width,
    __indexOf = Array.prototype.indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };

    width = 960;

    height = 500;

    /* SELECTION - store the selected node
    */

    /* EDITING - store the drag mode (either 'drag' or 'add_link')
    */

    global = {
    selection: null
    };

    /* create some fake data
    */

    graph = {
    nodes: [
    {
    id: 'A',
    x: 469,
    y: 410,
    type: 'X'
    }, {
    id: 'B',
    x: 493,
    y: 364,
    type: 'X'
    }, {
    id: 'C',
    x: 442,
    y: 365,
    type: 'X'
    }, {
    id: 'D',
    x: 467,
    y: 314,
    type: 'X'
    }, {
    id: 'E',
    x: 477,
    y: 248,
    type: 'Y'
    }, {
    id: 'F',
    x: 425,
    y: 207,
    type: 'Y'
    }, {
    id: 'G',
    x: 402,
    y: 155,
    type: 'Y'
    }, {
    id: 'H',
    x: 369,
    y: 196,
    type: 'Y'
    }, {
    id: 'I',
    x: 350,
    y: 148,
    type: 'Z'
    }, {
    id: 'J',
    x: 539,
    y: 222,
    type: 'Z'
    }, {
    id: 'K',
    x: 594,
    y: 235,
    type: 'Z'
    }, {
    id: 'L',
    x: 582,
    y: 185,
    type: 'Z'
    }, {
    id: 'M',
    x: 633,
    y: 200,
    type: 'Z'
    }
    ],
    links: [
    {
    source: 'A',
    target: 'B'
    }, {
    source: 'B',
    target: 'C'
    }, {
    source: 'C',
    target: 'A'
    }, {
    source: 'B',
    target: 'D'
    }, {
    source: 'D',
    target: 'C'
    }, {
    source: 'D',
    target: 'E'
    }, {
    source: 'E',
    target: 'F'
    }, {
    source: 'F',
    target: 'G'
    }, {
    source: 'F',
    target: 'H'
    }, {
    source: 'G',
    target: 'H'
    }, {
    source: 'G',
    target: 'I'
    }, {
    source: 'H',
    target: 'I'
    }, {
    source: 'J',
    target: 'E'
    }, {
    source: 'J',
    target: 'L'
    }, {
    source: 'J',
    target: 'K'
    }, {
    source: 'K',
    target: 'L'
    }, {
    source: 'L',
    target: 'M'
    }, {
    source: 'M',
    target: 'K'
    }
    ],
    objectify: (function() {
    /* resolve node IDs (not optimized at all!)
    */
    var l, n, _i, _len, _ref, _results;
    _ref = graph.links;
    _results = [];
    for (_i = 0, _len = _ref.length; _i < _len; _i++) {
    l = _ref[_i];
    _results.push((function() {
    var _j, _len2, _ref2, _results2;
    _ref2 = graph.nodes;
    _results2 = [];
    for (_j = 0, _len2 = _ref2.length; _j < _len2; _j++) {
    n = _ref2[_j];
    if (l.source === n.id) {
    l.source = n;
    continue;
    }
    if (l.target === n.id) {
    l.target = n;
    continue;
    } else {
    _results2.push(void 0);
    }
    }
    return _results2;
    })());
    }
    return _results;
    }),
    remove: (function(condemned) {
    /* remove the given node or link from the graph, also deleting dangling links if a node is removed
    */ if (__indexOf.call(graph.nodes, condemned) >= 0) {
    graph.nodes = graph.nodes.filter(function(n) {
    return n !== condemned;
    });
    return graph.links = graph.links.filter(function(l) {
    return l.source.id !== condemned.id && l.target.id !== condemned.id;
    });
    } else if (__indexOf.call(graph.links, condemned) >= 0) {
    return graph.links = graph.links.filter(function(l) {
    return l !== condemned;
    });
    }
    }),
    last_index: 0,
    add_node: (function(type) {
    var n;
    n = {
    id: graph.last_index++,
    x: width / 2,
    y: height / 2,
    type: type
    };
    graph.nodes.push(n);
    return n;
    }),
    add_link: (function(source, target) {
    /* avoid links to self
    */
    var l, link, _i, _len, _ref;
    if (source === target) return null;
    /* avoid link duplicates
    */
    _ref = graph.links;
    for (_i = 0, _len = _ref.length; _i < _len; _i++) {
    link = _ref[_i];
    if (link.source === source && link.target === target) return null;
    }
    l = {
    source: source,
    target: target
    };
    graph.links.push(l);
    return l;
    })
    };

    graph.objectify();

    window.main = (function() {
    /* create the SVG
    */
    var container, library, svg, toolbar;
    svg = d3.select('body').append('svg').attr('width', width).attr('height', height);
    /* ZOOM and PAN
    */
    /* create container elements
    */
    container = svg.append('g');
    container.call(d3.behavior.zoom().scaleExtent([0.5, 8]).on('zoom', (function() {
    return global.vis.attr('transform', "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
    })));
    global.vis = container.append('g');
    /* create a rectangular overlay to catch events
    */
    /* WARNING rect size is huge but not infinite. this is a dirty hack
    */
    global.vis.append('rect').attr('class', 'overlay').attr('x', -500000).attr('y', -500000).attr('width', 1000000).attr('height', 1000000).on('click', (function(d) {
    /* SELECTION
    */ global.selection = null;
    d3.selectAll('.node').classed('selected', false);
    return d3.selectAll('.link').classed('selected', false);
    }));
    /* END ZOOM and PAN
    */
    global.colorify = d3.scale.category10();
    /* initialize the force layout
    */
    global.force = d3.layout.force().size([width, height]).charge(-400).linkDistance(60).on('tick', (function() {
    /* update nodes and links
    */ global.vis.selectAll('.node').attr('transform', function(d) {
    return "translate(" + d.x + "," + d.y + ")";
    });
    return global.vis.selectAll('.link').attr('x1', function(d) {
    return d.source.x;
    }).attr('y1', function(d) {
    return d.source.y;
    }).attr('x2', function(d) {
    return d.target.x;
    }).attr('y2', function(d) {
    return d.target.y;
    });
    }));
    /* DRAG
    */
    global.drag = global.force.drag().on('dragstart', function(d) {
    return d.fixed = true;
    });
    /* DELETION - pressing DEL deletes the selection
    */
    /* CREATION - pressing N creates a new node
    */
    d3.select(window).on('keydown', function() {
    if (d3.event.keyCode === 46) {
    if (global.selection != null) {
    graph.remove(global.selection);
    global.selection = null;
    return update();
    }
    }
    });
    update();
    /* TOOLBAR
    */
    toolbar = $("<div class='toolbar'></div>");
    $('body').append(toolbar);
    toolbar.append($("<input type='radio' name='tool' value='pointer' checked='checked'>Pointer</input>"));
    toolbar.append($("<input type='radio' name='tool' value='add_node'>Add node</input>"));
    toolbar.append($("<input type='radio' name='tool' value='add_link'>Add link</input>"));
    library = $("<div class='library'></div></div>");
    toolbar.append(library);
    ['X', 'Y', 'Z', 'W'].forEach(function(type) {
    var new_btn;
    new_btn = $("<svg width='42' height='42'>\n <g class='node'>\n <circle\n cx='21'\n cy='21'\n r='18'\n stroke='" + (global.colorify(type)) + "'\n fill='" + (d3.hcl(global.colorify(type)).brighter(3)) + "'\n />\n </g>\n</svg>");
    new_btn.bind('click', function() {
    graph.add_node(type);
    return update();
    });
    library.append(new_btn);
    return library.hide();
    });
    global.tool = $("input[name='tool']:checked").val();
    global.new_link_source = null;
    global.vis.on('mousemove.add_link', (function(d) {
    /* check if there is a new link in creation
    */
    var p;
    if (global.new_link_source != null) {
    /* create the draggable link representation
    */
    p = d3.mouse(global.vis.node());
    return global.drag_link.attr('x1', global.new_link_source.x).attr('y1', global.new_link_source.y).attr('x2', p[0]).attr('y2', p[1]);
    }
    })).on('mouseup.add_link', (function(d) {
    global.new_link_source = null;
    /* remove the draggable link representation, if exists
    */
    if (global.drag_link != null) return global.drag_link.remove();
    }));
    return $("input[name='tool']").change(function() {
    var new_tool, nodes;
    new_tool = $("input[name='tool']:checked").val();
    nodes = global.vis.selectAll('.node');
    if (new_tool === 'add_link' && global.tool !== 'add_link') {
    /* remove drag handlers from nodes
    */
    nodes.on('mousedown.drag', null).on('touchstart.drag', null);
    /* add drag handlers for the add_link tool
    */
    nodes.call(drag_add_link);
    } else if (new_tool !== 'add_link' && global.tool === 'add_link') {
    /* remove drag handlers for the add_link tool
    */
    nodes.on('mousedown.add_link', null).on('mouseup.add_link', null);
    /* add drag behavior to nodes
    */
    nodes.call(global.drag);
    }
    if (new_tool === 'add_node') {
    library.show();
    } else {
    library.hide();
    }
    return global.tool = new_tool;
    });
    });

    update = function() {
    /* update the layout
    */
    var links, new_nodes, nodes;
    global.force.nodes(graph.nodes).links(graph.links).start();
    /* create nodes and links
    */
    /* (links are drawn with insert to make them appear under the nodes)
    */
    /* also define a drag behavior to drag nodes
    */
    /* dragged nodes become fixed
    */
    nodes = global.vis.selectAll('.node').data(graph.nodes, function(d) {
    return d.id;
    });
    new_nodes = nodes.enter().append('g').attr('class', 'node').on('click', (function(d) {
    /* SELECTION
    */ global.selection = d;
    d3.selectAll('.node').classed('selected', function(d2) {
    return d2 === d;
    });
    return d3.selectAll('.link').classed('selected', false);
    }));
    links = global.vis.selectAll('.link').data(graph.links, function(d) {
    return "" + d.source.id + "->" + d.target.id;
    });
    links.enter().insert('line', '.node').attr('class', 'link').on('click', (function(d) {
    /* SELECTION
    */ global.selection = d;
    d3.selectAll('.link').classed('selected', function(d2) {
    return d2 === d;
    });
    return d3.selectAll('.node').classed('selected', false);
    }));
    links.exit().remove();
    /* TOOLBAR - add link tool initialization for new nodes
    */
    if (global.tool === 'add_link') {
    new_nodes.call(drag_add_link);
    } else {
    new_nodes.call(global.drag);
    }
    new_nodes.append('circle').attr('r', 18).attr('stroke', function(d) {
    return global.colorify(d.type);
    }).attr('fill', function(d) {
    return d3.hcl(global.colorify(d.type)).brighter(3);
    });
    /* draw the label
    */
    new_nodes.append('text').text(function(d) {
    return d.id;
    }).attr('dy', '0.35em').attr('fill', function(d) {
    return global.colorify(d.type);
    });
    return nodes.exit().remove();
    };

    drag_add_link = function(selection) {
    return selection.on('mousedown.add_link', (function(d) {
    var p;
    global.new_link_source = d;
    /* create the draggable link representation
    */
    p = d3.mouse(global.vis.node());
    global.drag_link = global.vis.insert('line', '.node').attr('class', 'drag_link').attr('x1', d.x).attr('y1', d.y).attr('x2', p[0]).attr('y2', p[1]);
    /* prevent pan activation
    */
    d3.event.stopPropagation();
    /* prevent text selection
    */
    return d3.event.preventDefault();
    })).on('mouseup.add_link', (function(d) {
    /* add link and update, but only if a link is actually added
    */ if (graph.add_link(global.new_link_source, d) != null) return update();
    }));
    };

    }).call(this);
    46 changes: 46 additions & 0 deletions index.sass
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,46 @@
    .node > circle
    stroke-width: 4px

    .node > text
    pointer-events: none
    font-family: sans-serif
    font-weight: bold
    text-anchor: middle

    // editing
    .link, .drag_link
    stroke-width: 6px
    stroke: gray
    opacity: 0.6

    .drag_link
    stroke-linecap: round

    // selection
    .selected > circle
    stroke-width: 8px

    .selected.link
    stroke-width: 14px

    // zoom and pan
    .overlay
    fill: transparent

    // toolbar
    .toolbar
    position: absolute
    top: 12px
    left: 480px
    width: 240px
    margin-left: -120px

    .library
    border: 2px solid gray
    border-radius: 6px
    background: rgba(255,255,255,0.7)
    padding: 6px

    svg:not(:last-child)
    padding-right: 6px

  13. @nitaku nitaku created this gist Nov 17, 2013.
    5 changes: 5 additions & 0 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,5 @@
    Starting from [the previous example](http://bl.ocks.org/nitaku/7493693), this one implements a more complete graph editing tool.

    Select a tool to operate on the graph: The *pointer* only lets the user pan & zoom or drag nodes around. The *add node tool* opens up a library of node types; drag one into the graph to add a node of that type. The *add link tool* lets the user connect two nodes with a drag gesture from the first to the second one.

    Clicking a node or a link always selects it regardless of the current tool. You can press `del` to remove the current selection.