Skip to content

Instantly share code, notes, and snippets.

@christophermanning
Last active December 10, 2015 16:18
Show Gist options
  • Save christophermanning/4460600 to your computer and use it in GitHub Desktop.
Save christophermanning/4460600 to your computer and use it in GitHub Desktop.
Octocat Grid

Created by Christopher Manning

Summary

I created this to generate a serialized octocat grid for an upcoming art project. You could use this with any SVG that's only composed of individual path elements.

TODO

  • This renders too slowly for general consumption, so try to fix that.
  • Add live controls to adjust the cell size, toggle between intersection or overlaps, and change the path's sample size

References

This is not affiliated with or endorsed by GitHub. The Octocat is a registered trademark of GitHub.

<!DOCTYPE html>
<html>
<head>
<title>Octocat Grid</title>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/dat-gui/0.5/dat.gui.min.js"></script>
<script src="http://bl.ocks.org/d/4450188/javascript.util.min.js"></script>
<script src="http://bl.ocks.org/d/4450188/jsts.min.js"></script>
<!--<script type="text/javascript" src="/js/d3/d3.min.js"></script>-->
<!--<script type="text/javascript" src="/js/dat-gui/build/dat.gui.min.js"></script> -->
<!--<script type="text/javascript" src="/js/jsts/lib/javascript.util.js"></script>-->
<!--<script type="text/javascript" src="/js/jsts/lib/jsts.js"></script>-->
<style type="text/css">
body {
padding: 0
margin: 0
}
</style>
</head>
<body>
<script type="text/javascript">
config = {"svg": true, "grid": true}
gui = new dat.GUI()
svgChanger = gui.add(config, "svg")
svgChanger.onChange(function(value) {
d3.select("#octocat").attr("display", value ? "block" : "none")
});
gridChanger = gui.add(config, "grid")
gridChanger.onChange(function(value) {
d3.select("#grid").attr("display", value ? "block" : "none")
});
config.grid_to_console = function(){
console.log(JSON.stringify(rects[0].map(function(d) { return d.__data__ }).filter(function(d) { return d.color })))
}
gui.add(config, "grid_to_console")
margin = {top: 10, left: 10, bottom: 10}
colors = [null]
size = 8
height = window.innerHeight
ocHeight = height - margin.top - margin.bottom
geometryFactory = new jsts.geom.GeometryFactory()
rTree = new jsts.index.strtree.STRtree()
d3.xml(window.location.hostname == "localhost" ? "/gists/octocat.svg/octocat.svg" : "http://bl.ocks.org/d/4460135/octocat.svg", "image/svg+xml", function(xml) {
viewBox = d3.select(xml).selectAll("svg").attr("viewBox").split(" ")
w = parseInt(viewBox[2])
h = parseInt(viewBox[3])
width = (ocHeight/h)*w
x = d3.scale.linear().domain([0, w]).range([0, width])
y = d3.scale.linear().domain([0, h]).range([0, ocHeight])
line = d3.svg.line()
//.interpolate(config["interpolation"])
//.tension(config["tension"])
.x(function(d, i) { return d.x })
.y(function(d, i) { return d.y })
svg = d3.select("body").append("svg")
.attr("fill", "none")
.attr("width", width)
.attr("height", height)
g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
oc = g.append("g").attr("id", "octocat")
path = oc.selectAll("path")
.data(d3.select(xml).selectAll("path")[0])
.enter().append("path")
.each(function(d, i) {
// convert path to polygon
var samples = 200
var ring = []
var len = d.getTotalLength();
var step = step=len/samples;
for (var j=0;j<=len;j+=step){
var p=d.getPointAtLength(j);
ring.push(new jsts.geom.Coordinate(x(p.x), y(p.y)))
}
// close the polygon
ring.push(ring[0])
var shell = geometryFactory.createLinearRing(ring)
var p = geometryFactory.createPolygon(shell)
d.polygon = p
d.color = d3.select(d).attr("fill")
d.id = d3.select(d).attr("id")
d.zindex = i
rTree.insert(p.getEnvelopeInternal(), d)
if(colors.indexOf(d.color) == -1) colors.push(d.color)
})
.attr("id", function(d) { return d3.select(d).attr("id") })
.attr("fill", function(d) { return d3.select(d).attr("fill") })
.attr("stroke", "black")
.attr("d", function(d) { return line(d.polygon.shell.points) })
// grid
var rectangles = []
for (var x = 0; x <= width; x+=size+1) {
for (var y = 0; y <= height; y+=size+1) {
rectangles.push({x: x, y: y})
}
}
grid = g.append("g").attr("id", "grid")
rects = grid.selectAll("rect")
.data(rectangles)
.enter().append("rect")
.attr("x", function(d) { return d.x })
.attr("y", function(d) { return d.y })
.attr("width", function(d) { return size })
.attr("height", function(d) { return size })
.attr("stroke", function(d) {
var c1 = new jsts.geom.Coordinate(d.x, d.y)
var c2 = new jsts.geom.Coordinate(d.x+size, d.y)
var c3 = new jsts.geom.Coordinate(d.x+size, d.y+size)
var c4 = new jsts.geom.Coordinate(d.x, d.y+size)
var shell = geometryFactory.createLinearRing([c1,c2,c3,c4,c1])
var searchPolygon = geometryFactory.createPolygon(shell)
d.color = ""
rTree.query(searchPolygon.getEnvelopeInternal()).sort(function compare(a, b) {
// sort by z-index so the top most intersection is filled
if (a.zindex > b.zindex) return -1
if (a.zindex < b.zindex) return 1
return 0
}).some(function(p) {
intersection = searchPolygon.intersection(p.polygon)
if(searchPolygon.intersects(p.polygon)) {
d.color = p.color
d.id = p.id
return true
}
})
return d.color
})
.on("mousedown", function(d) {
newColor = colors[(colors.indexOf(d.color)+1) % colors.length]
d.color = newColor
if(newColor == null) {
d3.select(this).attr("stroke", "red")
} else {
d3.select(this).attr("stroke", newColor)
}
})
// so mousedown works
.attr("fill", function(d) { return "transparent" })
})
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment