Skip to content

Instantly share code, notes, and snippets.

@joyrexus
Last active May 12, 2021 04:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save joyrexus/6813016 to your computer and use it in GitHub Desktop.
Save joyrexus/6813016 to your computer and use it in GitHub Desktop.
Unit circle extension for svg.js

Simple demonstration of how to extend svg.js. Our little extension, svg.unit.js, enables one to create a unit circle and move around to various points on it based on a specified angle.


Note: moving around a unit circle as our demo use case was largely inspired by Tom MacWright's Math for Pictures post.


For example, we can create a unit circle centered at 150, 150 with radius 100 and use degrees as our angle unit of measure:

w = 300
h = 300

draw = SVG('canvas').size(w, h)

center =
  x: w/2
  y: w/2  
  
u = draw.unitCircle(center.x, center.y, radius=100, degrees=true)

Move to 0° on our unit circle and mark the point:

u.moveTo(0).markPoint()

Draw a line from our last point (at 0°) to 120°:

u.lineTo(120)

Draw a line from our last point to 240° and mark the point with a marker of size 10 using hiliteStyle for styling:

hiliteStyle =
    fill: "orange"
    stroke: "aliceblue"
    'stroke-width': 2

u.lineTo(240).markPoint(10, hiliteStyle)
<!DOCTYPE html>
<meta charset="utf-8">
<script src="https://s3-eu-west-1.amazonaws.com/svgjs/svg.js"></script>
<script src="http://jashkenas.github.io/coffee-script/extras/coffee-script.js"></script>
<script src="svg.unit.js"></script>
<style>
rect {
fill: none;
stroke: #999;
stroke-width: 2;
}
</style>
<body>
<div id="canvas"></div>
<script type="text/coffeescript">
centerStyle =
fill: "steelblue"
stroke: "aliceblue"
'stroke-width': 0
hiliteStyle =
fill: "orange"
stroke: "aliceblue"
'stroke-width': 2
w = 960
h = 250
# use default radian units for angles
draw = SVG('canvas').size(w, h)
draw.rect(w, h)
draw.unitCircle(w/2, h/2, radius=100)
.markPoint(12, centerStyle)
.lineTo(Math.PI) # 180°
.lineTo(Math.PI / 2) # 90°
.markPoint()
.lineTo(0)
.markPoint(10, hiliteStyle)
# use degree units for angles
draw = SVG('canvas').size(w, h)
draw.rect(w, h)
draw.unitCircle(w/2, h/2, radius=100, degrees=true)
.moveTo(0)
.markPoint()
.lineTo(120)
.markPoint()
.lineTo(240)
.markPoint(10, hiliteStyle)
.lineTo(0)
</script>
D2R = Math.PI / 180
deg2rad = (d) -> d * D2R
class Cursor
constructor: (@x, @y, @draw) ->
lineTo: (x, y, color="steelblue") ->
@draw.line(@x,@y, x,y).attr({stroke: color})
@x = x
@y = y
@
moveTo: (x, y) -> @lineTo(x, y, "none")
class UnitCircle extends Cursor
defaultCircleStyle:
fill: "aliceblue"
stroke: "steelblue"
'stroke-width': 10
defaultPointStyle:
fill: "#999"
stroke: "aliceblue"
'stroke-width': 2
constructor: (@cx, @cy, @radius=100, @degrees=false, @draw) ->
margin = @defaultPointStyle['stroke-width']
@diameter = @radius * 2
@disc = @draw.circle(@diameter+margin)
.attr(@defaultCircleStyle)
.center(@cx, @cy)
super(@cx, @cy, @draw)
# convert angle in degrees to x coord on unit circle
_x: (θ) ->
θ = deg2rad(θ) if @degrees
@cx + Math.cos(-θ) * @radius
# convert angle in degrees to y coord on unit circle
_y: (θ) ->
θ = deg2rad(θ) if @degrees
@cy + Math.sin(-θ) * @radius
moveTo: (θ) ->
@lineTo(θ, "none")
@
lineTo: (θ, color) ->
super(@_x(θ), @_y(θ), color)
@
markPoint: (size=10, style) ->
style or= @defaultPointStyle
@draw.circle(size).attr(style).center(@x, @y)
@
extension =
unitCircle: (x, y, r, d) -> new UnitCircle x, y, r, d, @
SVG.extend(SVG.Doc, extension)
// Generated by CoffeeScript 1.6.3
(function() {
var Cursor, D2R, UnitCircle, deg2rad, extension,
__hasProp = {}.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
D2R = Math.PI / 180;
deg2rad = function(d) {
return d * D2R;
};
Cursor = (function() {
function Cursor(x, y, draw) {
this.x = x;
this.y = y;
this.draw = draw;
}
Cursor.prototype.lineTo = function(x, y, color) {
if (color == null) {
color = "steelblue";
}
this.draw.line(this.x, this.y, x, y).attr({
stroke: color
});
this.x = x;
this.y = y;
return this;
};
Cursor.prototype.moveTo = function(x, y) {
return this.lineTo(x, y, "none");
};
return Cursor;
})();
UnitCircle = (function(_super) {
__extends(UnitCircle, _super);
UnitCircle.prototype.defaultCircleStyle = {
fill: "aliceblue",
stroke: "steelblue",
'stroke-width': 10
};
UnitCircle.prototype.defaultPointStyle = {
fill: "#999",
stroke: "aliceblue",
'stroke-width': 2
};
function UnitCircle(cx, cy, radius, degrees, draw) {
var margin;
this.cx = cx;
this.cy = cy;
this.radius = radius != null ? radius : 100;
this.degrees = degrees != null ? degrees : false;
this.draw = draw;
margin = this.defaultPointStyle['stroke-width'];
this.diameter = this.radius * 2;
this.disc = this.draw.circle(this.diameter + margin).attr(this.defaultCircleStyle).center(this.cx, this.cy);
UnitCircle.__super__.constructor.call(this, this.cx, this.cy, this.draw);
}
UnitCircle.prototype._x = function(θ) {
if (this.degrees) {
θ = deg2rad(θ);
}
return this.cx + Math.cos(-θ) * this.radius;
};
UnitCircle.prototype._y = function(θ) {
if (this.degrees) {
θ = deg2rad(θ);
}
return this.cy + Math.sin(-θ) * this.radius;
};
UnitCircle.prototype.moveTo = function(θ) {
this.lineTo(θ, "none");
return this;
};
UnitCircle.prototype.lineTo = function(θ, color) {
UnitCircle.__super__.lineTo.call(this, this._x(θ), this._y(θ), color);
return this;
};
UnitCircle.prototype.markPoint = function(size, style) {
if (size == null) {
size = 10;
}
style || (style = this.defaultPointStyle);
this.draw.circle(size).attr(style).center(this.x, this.y);
return this;
};
return UnitCircle;
})(Cursor);
extension = {
unitCircle: function(x, y, r, d) {
return new UnitCircle(x, y, r, d, this);
}
};
SVG.extend(SVG.Doc, extension);
}).call(this);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment