Skip to content

Instantly share code, notes, and snippets.

@meesern
Created April 30, 2012 14:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save meesern/2558732 to your computer and use it in GitHub Desktop.
Save meesern/2558732 to your computer and use it in GitHub Desktop.
D3 experiment

D-touch Marker Generation

D-touch markers are a kind of quasi QR-code that can be designed by hand to look like just about anything. Sometimes it's handy to have your browser generate examples for each code.

Unlike QR or bar codes D-touch codes represent only a (relatively) small range of numbers that have application specific meaning. In order to use them in anything but the most well controlled environment the application must build into the codes some constraints that prevent one code being mistaken for another. This gist explores the constraints that we find useful.

You can see what it looks like here

var DtouchTile = {
width: 200,
height: 200,
init: function(code){
format = d3.format(",d");
var pack = d3.layout.pack()
.size([this.width - 4, this.height - 4])
/* return can be d.size */
.value(function(d) { return 1; });
var mark = d3.select("#chart")
.append("div")
.attr("class","marker");
mark.append("h2")
.text(code);
var vis = mark.append("svg:svg")
.attr("width", this.width)
.attr("height", this.height)
.attr("class", "pack");
var visg = vis.append("g")
.attr("transform", "translate(2, 2)");
var json = makejson(code);
var circles = [];
var vertices = [];
var big_vertices = [];
var hull = [];
var big_hull = [];
var casing =[];
var node = vis.data([json]).selectAll("g.node")
.data(pack.nodes)
.enter().append("g")
.attr("class", function(d)
{ return d.depth==0 ? "root node" :
(d.children ? "node" : "leaf node"); })
.attr("transform", function(d)
{ if (d.depth==1){
circles.push( {'x': d.x,'y': d.y,'r': d.r}); };
return "translate(" + d.x + "," + d.y + ")"; });
node.append("circle")
.transition()
.duration(1000)
.attr("r", function(d)
{ return d.r - d.depth*4; });
circles.map( function(c) {
/* Use a radius and big radius to form
two convex hulls */
r = c.r+4;
br = c.r+20;
segs = 16;
for (var i = 0; i<segs; i++)
{
th = 2*i*Math.PI/segs;
x = c.x + Math.sin(th)*r;
y = c.y + Math.cos(th)*r;
vertices.push([x,y]);
bx = c.x + Math.sin(th) * br;
by = c.y + Math.cos(th) * br;
big_vertices.push([bx,by]);
} });
hull = d3.geom.hull(vertices);
big_hull = d3.geom.hull(big_vertices);
for (var i = 0; i< big_hull.length; i++)
{
/*make the bezier control points from the large hull and the points from the small */
casing.push([big_hull[i][0],big_hull[i][1],
hull[i][0],hull[i][1]]);
}
visg.selectAll("path")
.data([hull])
.attr("d", function(d) {
curves = d
return "M" + curves.join("S") + "Z"; })
.enter().insert("path")
.attr("d", function(d) {
return "M" + casing.join("S") + "Z"; });
},
remove: function() {
d3.selectAll(".marker")
.remove();
}
};
// Code like "1:1:3"
function makejson(code)
{
var list = code.split(':').map( function(region) {
blobs = [];
parseInt(region).times(function() {
blobs.push({"size":1});});
return {"children": blobs };
});
return { "children": list };
}
// sign: -1 || 1
function scroll(sign)
{
var chart = d3.select("#chart");
chart.transition()
.duration(333)
.ease('quad-in-out')
.style("left", function(){
var liml = 634;
var limr = -6600;
var pos = parseInt(chart.style("left")) + (sign*451);
if (pos >= liml)
{pos = liml;}
else if (pos <= limr)
{pos = limr;}
return pos+"px";});
}
//Basic setup
function setup()
{
//Scroll
$('left').observe('click',function(){ scroll(1); });
$('right').observe('click',function(){ scroll(-1);});
$('min_regions', 'max_regions', 'empty_regions',
'validation_regions', 'validation_num',
'max_blobs', 'checksum').invoke('observe',
'change',schedule_update);
schedule_update();
}
var timer;
function schedule_update()
{
clearTimeout(timer);
timer=setTimeout("update()",500);
}
function update()
{
var minr = parseInt($('min_regions').value);
var maxr = parseInt($('max_regions').value);
var empty = parseInt($('empty_regions').value);
var fixed = parseInt($('validation_regions').value);
var fixnum = parseInt($('validation_num').value);
var blobs = parseInt($('max_blobs').value);
var chksum = parseInt($('checksum').value);
var codelist = listcodes(minr,maxr,empty,fixed,
fixnum,blobs,chksum);
$('count').update(codelist.length);
drawmarkers(codelist);
}
function listcodes(minr,maxr,empty,fixed,fixnum,blobs,chksum)
{
var codes = [];
for (var regions = minr; regions <= maxr; regions++){
codes = codes.concat(listcodes_with_regions(regions-fixed,empty,fixed,fixnum,blobs,chksum));
}
return codes;
}
function listcodes_with_regions(regions,empty,fixed,fixnum,blobs,chksum)
{
var start = (empty>0)?0:1;
//calculate without checksum first
allcombs = combinations(start,blobs,regions);
validation = [];
//make the validation part
fixed.times(function()
{ validation.push(fixnum); });
allcombs = allcombs.map( function(comb){
return validation.concat(comb).sort();
});
//select valid checksum
allcombs = allcombs.select( function(code){
return code.reduceRight(function(prev,cur){return prev+cur;}) % chksum == 0});
//reject too many empties
allcombs = allcombs.select( function(code){
return code[empty] != 0});
return allcombs;
}
//return a list of combination lists
function combinations(from,to,len)
{
if (len<=0) return([]);
var list = [];
for (var i=from; i<=to; i++)
{
var shortllist = combinations(i,to,len-1);
if (shortllist.length==0)
{
list.push([i]);
}
else
{
shortllist.each(function(shortl){
ananswer = [i].concat(shortl);
list.push(ananswer);
});
}
}
return(list);
}
function stringformat(codearray)
{
return codearray.join(':');
}
function drawmarkers(codelist)
{
DtouchTile.remove();
codelist.each(function(code){
codename = stringformat(code);
DtouchTile.init(codename);
});
}
setup();
<!DOCTYPE html>
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/prototype/1.6.0.2/prototype.js"></script>
<script src="http://mbostock.github.com/d3/d3.v2.min.js"></script>
<link rel="stylesheet" href="style.css"/>
</head>
<body>
<h1>D-touch Marker Generation in D3</h1>
<div id='form'>
<label>min_r:</label>
<input id="min_regions" size="3" type="text" value="8" title="Minimum Number of Regions">
<label>max_r:</label>
<input id="max_regions" size="3" type="text" value="8" title="Maximum Number of Regions">
<label>emp:</label>
<input id="empty_regions" size="3" type="text" value="0" title="Maximum Number of Regions which may be Empty">
<label>fix:</label>
<input id="validation_regions" size="3" type="text" value="4" title="Number of Validation (Fixed) Regions">
<label>fixn:</label>
<input id="validation_num" size="3" type="text" value="1" title="Value of Validation Regions">
<label>blobs:</label>
<input id="max_blobs" size="3" type="text" value="6" title="Maximum Number of Blobs in any one Region">
<label>chksm:</label>
<input id="checksum" size="3" type="text" value="6" title="Sum of all Blobs in all Regions must be divisible by this" >
<span id='count'/>
</div>
<div id='frame'>
<div id='left'>&lt;</div>
<div id='port'><div id='chart'></div></div>
<div id='right'>&gt;</div>
</div>
</body>
<script src="d3play.js"></script>
</html>
body {
font-family: 'Helvetica Neue';
}
h1,#count{
font-size: 36px;
font-weight: 300;
}
h2 {
font-weight: 300;
text-align: center;
color: #000046;
}
#count {
margin-left: 33px;
color: steelBlue;
position: relative;
top: 7px;
}
#form {
/*preserve vertical rhythm*/
margin-top: -1em;
}
#frame {
position: relative;
}
#left, #right {
font-size: 28px;
line-height: 400px;
color: steelBlue;
float: left;
width: 2%;
}
#right {
text-indent: 4px;
}
#port {
width: 96%;
float: left;
overflow-x: hidden;
}
#chart {
margin-top: 10px;
width: 7000px;
position: relative;
left: 0px;
}
.marker {
width: 240px;
margin: 40px;
margin-left: 0px;
float: left;
}
.pack {
margin-left: auto;
margin-right: auto;
display: block;
}
.root {
stroke: white;
fill: white;
display: none;
}
.node {
stroke: #000046;
fill: white;
stroke-width: 4px;
}
.leaf {
stroke: white;
fill: #000046;
}
path {
fill: #000046;
stroke: #000046;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment