block by enjalot 5754673

d3.sticker plugin

Full Screen

d3.sticker

An API to create “stickers” from DOM elements that can then be repasted and manipulated.

This example shows how you can dynamically create and remove icons using the d3.sticker API

For more data driven examples see:

And a video tutorial:
http://www.youtube.com/watch?v=fDqfoICbz7E

Icons

index.html

<!DOCTYPE html>
<meta charset="utf-8">
<head>
  <link rel="stylesheet" href="style.css">
  <script src="//d3js.org/d3.v3.min.js"></script>
  <script src="sticker.js"></script>
</head>
<body>
  <div id="toolbar">
    <div id="sticker1" class="sticker">
      <svg width=80 height=80 viewbox="0 0 100 100">
      <g id="heart" opacity=0.2>
        <path d="M90,16.667V5.556H55.556v11.111H44.444V5.556H10v11.111H7.641H0V50h7.641H10v12.223h11.111v10h11.111v12.222h11.111v10
          h13.333v-10h11.11V72.223h0.994h10.117v-10H90V50h2.283H100V16.667 M24.444,29.016v12.096H11.111V29.016V17.778h13.333V29.016z"></path>
      </g>
      </svg>
    </div>
    <div id="sticker2" class="sticker">
      <svg width=80 height=80 viewbox="0 0 100 100">
        <g id="postit" opacity = 0.2>
          <path d="M99.276,61.623C86.663,47.747,86.507,3.225,86.507,2.778C86.507,1.244,85.263,0,83.729,0H13.572  c-0.71,0-1.358,0.267-1.85,0.705c-0.002,0-0.002,0.001-0.002,0.003c-0.039,0.034-0.076,0.069-0.112,0.106L0.814,11.607  C0.293,12.128,0,12.835,0,13.571V74.28c0,0.015,0,0.029,0,0.044c0,0.002,0,0.002,0,0.002c0,0.001,0,0.001,0,0.003  c0.006,0.359,0.08,0.702,0.21,1.016c0.135,0.329,0.337,0.638,0.604,0.904c0.264,0.265,0.568,0.464,0.893,0.6  c0.33,0.139,0.691,0.215,1.071,0.215h80.555c1.534,0,2.777-1.244,2.777-2.778v-8.016h0.317h10.794c1.101,0,2.096-0.648,2.541-1.655  C100.207,63.609,100.018,62.436,99.276,61.623z M16.35,47.618c1.394,4.868,3.148,9.375,5.329,13.096H16.35V47.618z M5.556,14.722  l5.238-5.238v52.857l-5.238,5.237V14.722z M80.556,71.507H9.484l5.237-5.238h12.341h53.493V71.507z M86.428,60.713H28.344  C18.263,48.113,16.654,15.318,16.397,5.556h64.592c0.215,9.346,1.622,39.436,10.825,55.158H86.428z"></path>
        </g>
      </svg>
    </div>
    <div id="sticker3" class="sticker">
      <svg width=80 height=80 viewbox="0 0 100 100">
      <g id="marker" opacity=0.2>
        <path d="M99.375,96.315c0-0.485-0.112-0.942-0.314-1.35l-2.013-6.036c-0.149-0.445-0.399-0.852-0.732-1.184l-4.945-4.947  l-2.982-6.961c-0.151-0.354-0.37-0.677-0.643-0.949L69.234,56.377c0.268-1.009,0.018-2.124-0.773-2.916L18.106,3.107  C16.103,1.104,13.44,0,10.606,0s-5.497,1.104-7.5,3.107C1.103,5.11,0,7.773,0,10.606c0,2.833,1.103,5.496,3.107,7.499L53.462,68.46  c0.568,0.568,1.339,0.888,2.143,0.888c0.265,0,0.522-0.047,0.774-0.114l18.51,18.513c0.272,0.271,0.595,0.491,0.948,0.642  l6.967,2.986l2.565,2.565H10.606c-1.674,0-3.03,1.356-3.03,3.03s1.356,3.03,3.03,3.03h84.848c0.873,0,1.654-0.376,2.208-0.966  C98.674,98.543,99.375,97.516,99.375,96.315z M7.392,13.819c-0.858-0.858-1.331-1.999-1.331-3.213c0-1.214,0.473-2.355,1.332-3.214  c0.858-0.859,2-1.332,3.214-1.332c1.214,0,2.355,0.473,3.214,1.332l48.212,48.211l-6.428,6.429L7.392,13.819z M60.961,65.246  l4.285-4.286l17.802,17.802l2.176,5.078l-0.256,0.254l-1.129,1.129l-5.078-2.175L60.961,65.246z"></path>     
      </g>
      </svg>
    </div>
  </div>
  
  <svg id="surface">
  
  </svg>

  <script>
    //this is nice to have
    d3.selection.prototype.moveToFront = function() { 
      return this.each(function() { 
        this.parentNode.appendChild(this); 
      }); 
    };
  </script>
  
  <script>
    //active sticker is kept in this variable
    var sticker;
    var bbox;

    var toolbar = d3.select("#toolbar");
    var stickers = toolbar.selectAll(".sticker");
    function stickerPicker(d,i) {
      var el = d3.select(this).select("svg g").node();
      sticker = d3.sticker(el);
      console.log("sticker!", sticker);
      console.log("el", el)
      bbox = el.getBBox();

      stickers.classed("selected", false);
      d3.select(this).classed("selected", true);
    }
    stickers.on("click", stickerPicker);
    //pick first sticker by default
    stickerPicker.call(stickers.node(), null, 0);
    
    var svg = d3.select("#surface");
    svg.on("click", function() {
      var mouse = d3.mouse(this);
      mouse[0] -= bbox.width/2 + bbox.x;
      mouse[1] -= bbox.height/2 + bbox.y;
      //place the sticker centered
      sticker(svg)
        .attr("transform", "translate(" + mouse + ")");

      //we want our ghost to be on top all the time
      ghost.moveToFront();
    })

    var ghost;
    var mousedown = false;
    svg.on("mousedown", function() {
      mousedown = true;
    })
    svg.on("mouseup", function() {
      mousedown = false;
    })
    svg.on("mouseenter", function() {
      ghost = sticker(svg);
      ghost.style({
        opacity: 0.4,
        stroke: "#000",
        "stroke-width": 2,
        "stroke-dasharray": "2 2"
      });
    })
    svg.on("mouseleave", function() {
      mousedown = false;
      ghost.remove();
    })
    svg.on("mousemove", function() {
      var mouse = d3.mouse(this);
      mouse[0] -= bbox.width/2 + bbox.x;
      mouse[1] -= bbox.height/2 + bbox.y;
      ghost
        .attr("transform", "translate(" + mouse + ")");

      //continuously paste if mouse is held down
      if(mousedown) {
        sticker(svg)
          .attr("transform", "translate(" + mouse + ")");
        ghost.moveToFront()
      }
    })
    
  </script>
  
</body>

sticker.js

(function() {
  d3.sticker = function(selector) {
    var string;
    var node;
    var svgElement; //for deserializing svg elements
    
    var sticker = function(selection) {
      return sticker.append(selection);
    }
    
    sticker.copy = function(selector) {
      node = d3.select(selector).node();
      if(!node) return sticker;
      //we keep track of svg element 
      if(d3_isSVG(node)) {
        sticker.isSVG = true;
        svgElement = node.ownerSVGElement;
      }
      node = node.cloneNode(true);
      node.removeAttribute("id");
      return sticker;
    }

    sticker.paste = function() {
      if(!node) return;
      return node.cloneNode(true);
    }
    
    sticker.node = function(_) {
      if(!arguments.length) return node;
      node = _;
      if(d3_isSVG(node)) {
        sticker.isSVG = true;
        svgElement = node.ownerSVGElement;
      }
      return sticker;
    }
    
    //append a copy of the sticker to the selection
    sticker.append = function(selection) {
      return selection.select(function() {
        return this.appendChild(sticker.paste());
      });
    }
    
    //insert a copy of the sticker into a selection similar to the d3 insert API 
    sticker.insert = function(selection, before) {
      if(!string) return selection;
      return selection.select(before).select(function() {
        return this.parentNode.insertBefore(sticker.paste(), this);
      });
    }
    
    sticker.string = function(_) {
      if(!arguments.length) return string;
      string = _;
      return sticker;
    }
    
    sticker.serialize = function() {
      //Serialize the selected element into a string
      string = new XMLSerializer().serializeToString(node);
    }
    sticker.deserialize = function () {
      //check if our element is SVG
      if(sticker.isSVG) {
        node = d3_makeSVGFragment(string, svgElement);
      } else {
        node = d3_makeFragment(string);
      }
      return node;
    }
    
    sticker.toString = function() {
      sticker.serialize();
      return string;
    }

    if(selector) {
      return sticker.copy(selector);
    }
    return sticker;
  }

  function d3_isSVG(el) {
    if(!el) return false
    return !!el.ownerSVGElement;// || el.tagName === "svg";
  }
  function d3_makeFragment(fragment) {
    var range = document.createRange()
    return range.createContextualFragment(fragment);
  }
  function d3_makeSVGFragment(fragment, svgElement) {
    //we need to wrap our element in a temporarary intermediate svg element
    //so that the browser knows to instanciate the Node properly.
    //for some reason having the range select an svg element isn't enough.
    // TODO: Allow optional namespace declarations
    var pre = '<svg xmlns=http://www.w3.org/2000/svg xmlns:xlink=http://www.w3.org/1999/xlink>';
    var post = '</svg>';
    var range = document.createRange();
    range.selectNode(svgElement);
    var contextFragment = range.createContextualFragment(pre + fragment + post)
    var intermediateSvg = contextFragment.childNodes[0]
    var node = intermediateSvg.childNodes[0]
    return node;
  }
}());

style.css

#toolbar {
  width: 959px;
  height: 100px;
  border: 1px solid grey;
}

#surface {
  width: 959px;
  height: 399px;
  bordeR: 1px solid grey;
}

.sticker {
  float: left;
  width: 80px;
  height: 80px;
  margin: 10px;
  border: 1px solid black;
}

.selected {
  border: 1px solid red;
}