Skip to content

Instantly share code, notes, and snippets.

@thomasboyt
Created October 2, 2012 02:27
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save thomasboyt/3815828 to your computer and use it in GitHub Desktop.
Save thomasboyt/3815828 to your computer and use it in GitHub Desktop.
SquiggleVision Graphing
<html>
<head>
<title>Squiggle Demo</title>
<script src="http://d3js.org/d3.v2.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.3.3/underscore-min.js "></script>
<script src="squiggle.js"></script>
<style>
path {
stroke: #c00;
stroke-width: 3px;
fill:none;
}
path.cos {
stroke: #00c;
}
.axis path {
stroke: #333;
stroke-width:3px;
}
.axis text {
fill:#333;
}
.axis .arrow {
stroke-width:2px;
}
</style>
</head>
<body>
<div id="demo">
<script>
var w = 600;
var h = 300;
var title_pad = 40
var x_axis_pad = 30;
var y_axis_pad = 60;
var vis = d3.select("#demo")
.append('svg:svg')
.attr('width', w)
.attr('height', h);
/*
Here, I've generated curves of sin and cos (with an x-resolution of .05, that is, values for sin(.05), sin(.10), sin(.15), etc.).
The points are then drawn using squiggle_line, a d3 svg line that defines its y-values as d3.squiggle.value(original, 1), which means that it will add a random float between -1 and 1 to the value. Changing the second argument, or the "wiggle room", will increase or decrease how far off from the original it should be.
*/
var curve_points = function(x_min, x_max, res, fn) {
var points = [];
for (var x=x_min; x<x_max; x+=res) {
var pt = [x, fn(x)];
points.push(pt)
}
return points;
}
var cos_points = curve_points(0, 2*Math.PI, 0.05,
function(x) {return Math.cos(x)});
var sin_points = curve_points(0, 2*Math.PI, 0.05,
function(x) {return Math.sin(x)});
var max_x = _.max(cos_points, function(point) {return point[0]});
var max_y = _.max(cos_points, function(point) {return point[1]});
var x = d3.scale.linear().domain([0, 2*Math.PI]).range([y_axis_pad, w]);
var y = d3.scale.linear().domain([-1, 1]).range([title_pad, h-x_axis_pad]);
var squiggle_line = d3.svg.line()
.x(function(d,i) {
return x(d[0])
})
.y(function(d,i) {
var original = y(d[1], i);
return d3.squiggle.value(original, 1)
})
//.interpolate("basis")
vis.selectAll("cos_path")
.data([cos_points])
.enter().append("svg:path")
.attr("d", squiggle_line)
.attr("class", "cos")
vis.selectAll("sin_path")
.data([sin_points])
.enter().append("svg:path")
.attr("d", squiggle_line)
.attr("class", "sin");
/*
X-axis and Y-axis
Basically this is just a lot of ugly positioning code. The terrors of SVG are contained herein (gaze upon my padding implimentation, ye mighty, and despair!).
What's more interesting are the d3.squiggle.x_axis_paths and d3.squiggle.y_axis_paths functions, which generate the paths used in drawing the axises and their arrows.
*/
axis = vis.append("svg:g").attr("class", "axis");
// X-Axis
var x_axis_paths = d3.squiggle.x_axis_paths({
x: y_axis_pad,
y: h - 30,
length: w - y_axis_pad,
arrow_height: 4,
arrow_width: 10
})
axis.append("svg:path")
.attr("d", x_axis_paths.axis)
axis.append("svg:path")
.attr("d", x_axis_paths.arrow)
.attr("class", "arrow")
axis.append("svg:text")
.attr("x", (w-y_axis_pad)/2 + y_axis_pad)
.attr("y", h - 12)
.attr("style", "text-anchor: middle; font-family: Helvetica, Arial")
.text("Label!")
// Y-Axis
var y_axis_paths = d3.squiggle.y_axis_paths({
x: y_axis_pad,
y: title_pad,
height: h - title_pad - 30,
arrow_height: 10,
arrow_width: 4
})
axis.append("svg:path")
.attr("d", y_axis_paths.axis)
axis.append("svg:path")
.attr("d", y_axis_paths.arrow)
.attr("class", "arrow")
axis.append("svg:text")
.attr("x", y_axis_pad-6)
.attr("y", title_pad + (h-x_axis_pad)/2)
.attr("transform", "rotate(-90 48 168)") // there's probably a way to calculate this, but damned if i can figure it out right now
.attr("style", "text-anchor: middle; font-family: Helvetica, Arial")
.text("Label!")
</script>
</div>
</body>
</html>
d3.squiggle = {
// d3.squiggle.value: just randomly add # between (-wiggle_room, wiggle_room)
value: function (original, wiggle_room_px) {
var wiggle = Math.random() * (wiggle_room_px * 2) - wiggle_room_px
var wiggled = original + wiggle
return wiggled
},
// d3.squiggle.linear_path: generate a squiggle-fied line from [x1, y1] to [x2, y2] with resolution res
linear_path: function(args) {
var x1 = args.from_pt[0];
var y1 = args.from_pt[1];
var x2 = args.to_pt[0];
var y2 = args.to_pt[1];
var mode = args.mode || "x";
var res = args.res || 1;
var wiggle_room = args.wiggle || 1;
var slope = (y2-y1)/(x2-x1);
var intercept = y1 - slope* x1;
var points = [];
if (mode == "x") {
for (var x = x1; x < x2; x += res) {
var squig_x = this.value(x, wiggle_room);
var squig_y = this.value(slope*x, wiggle_room) + intercept;
points.push([squig_x, squig_y]);
}
if (!_.any(points, function(pt) {return pt[0] == x2} )) {
points.push([x2, y2])
}
else {
points[points.length - 1] = [x2, y2]
}
}
else if (mode == "y") {
for (var y = y1; y < y2; y += res) {
if (slope == Infinity)
var x = x1;
else
var x = (y-intercept)/slope
var squig_x = this.value(x, wiggle_room);
var squig_y = this.value(y, wiggle_room);
points.push([squig_x, squig_y]);
}
if (!_.any(points, function(pt) {return pt[1] == y2} )) {
points.push([x2, y2])
}
else {
points[points.length - 1] = [x2, y2]
}
}
return linedef = d3.svg.line()
.x(function(d) {return d[0]})
.y(function(d) {return d[1]})
.interpolate("basis")
(points);
},
x_axis_paths: function(args) {
var x = args.x;
var y = args.y;
var length = args.length;
var res = args.res || 4;
var arrow_height = args.arrow_height || 4;
var arrow_width = args.arrow_width || 10;
var x_axis_line = this.linear_path({
from_pt: [x, y],
to_pt: [x+length, y],
mode: "x",
res: 4,
});
var x_arrow_path = this.linear_path({
from_pt: [x+length - arrow_width, y - arrow_height],
to_pt: [x+length, y],
mode: "x",
res: 4
})
+ this.linear_path({
from_pt: [x+length - arrow_width, y + arrow_height],
to_pt: [x+length, y],
mode: "x",
res: 4
});
return {
axis: x_axis_line,
arrow: x_arrow_path
};
},
y_axis_paths: function(args) {
var x = args.x;
var y = args.y;
var height = args.height;
var res = args.res || 4;
var arrow_height = args.arrow_height || 10;
var arrow_width = args.arrow_width || 4;
var y_axis_line = this.linear_path({
from_pt: [x, y],
to_pt: [x, y+height],
mode: "y",
res: 4,
});
var y_arrow_path =
this.linear_path({
from_pt: [x, y],
to_pt: [x - arrow_width, y + arrow_height],
mode: "y",
res: 4
})
+ this.linear_path({
from_pt: [x, y],
to_pt: [x + arrow_width, y + arrow_height],
mode: "y",
res: 4
});
return {
axis: y_axis_line,
arrow: y_arrow_path
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment