Published
Edited
Aug 11, 2020
5 forks
9 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
d = {
const sweepFlag = determinant < 0 ? 1 : 0;
const anchorIn = alongSegment(points[1], points[0], distanceToTangentPoint);
const anchorOut = alongSegment(points[1], points[2], distanceToTangentPoint);

return `
M${points[0].x} ${points[0].y}
L${anchorIn.x} ${anchorIn.y}
A${radiusToUse} ${radiusToUse} 0 0 ${sweepFlag} ${anchorOut.x} ${anchorOut.y}
L${points[2].x} ${points[2].y}
`;
}
Insert cell
Insert cell
Insert cell
curveCircleCorners = {

/**
* Point type definition
* @typedef {Object} Point
* @property {number} x - horizontal position in a canvas or SVG element
* @property {number} y - vertical position in a canvas or SVG element
*/
/**
* CircleCorners type definition
* @typedef {Object} CircleCorners
* @property {function} areaStart - triggered when ...
* @property {function} areaEnd - triggered when ...
* @property {function} lineStart - triggered when before the first datum in a line
* @property {function} lineEnd - triggered when after the last datum in a line
* @property {function} point - triggered for each point in an array of data defining a line
* @property {object} _context - 2d context of an HTML canvas element (or equivalant SVG drawing space)
* @property {string} _context._ draw commands to be applied to the d attribute of a path element
* @property {number} _radius - corner radius to apply to each line segment intersection
* @property {number} [_line] - state variable for ...
* @property {number} [_point] - state variable for how many points are saved as properties (vert & prev)
* @property {Point} [_prev] - the point preceding the vertex; either the first point of a line or an "out" anchor
* @property {Point} [_vert] - vertex currently being drawn; the curve will not actually touch this point
*/
function CircleCorners(context, radius) {
this._context = context;
this._radius = radius;
}
/**
* @func alongSegment
* @desc locatates a point a certain disatance away, in the direction of a known position
* @param {Point} from - origin of a ray (or vector)
* @param {Point} from - second point defining the direction of the ray (or vector)
* @param {number} distanceAlong - distance away that the output point should be, along the ray
* @return {Point} a new point, the specified direction and distance from `from`
*/
function alongSegment(from, toward, distanceAlong) {
const rayAngle = Math.atan2(from.y - toward.y, from.x - toward.x);

return {
x: from.x - distanceAlong * Math.cos(rayAngle),
y: from.y - distanceAlong * Math.sin(rayAngle)
};
}
/**
* @func arcPast
* @desc ...
* @param {Object} that - context of the current draw command
* @param {number} x - ...
* @param {number} y - ...
*/
function arcPast(that, x, y) {
// TO DO: Allow that._radius to be a number or an array of numbers (one for each point, with first and last ignored)
const angle = Math.abs(
Math.atan2(that._vert.y - that._prev.y, that._vert.x - that._prev.x) -
Math.atan2(that._vert.y - y, that._vert.x - x)
);
const acuteAngle = angle > Math.PI ? 2*Math.PI - angle : angle;
const shortestRay = Math.min(
Math.sqrt(Math.pow(that._vert.x - that._prev.x, 2) + Math.pow(that._vert.y - that._prev.y, 2)),
Math.sqrt(Math.pow(that._vert.x - x , 2) + Math.pow(that._vert.y - y , 2))
);
const radius = Math.min( that._radius, shortestRay * Math.tan(acuteAngle/2) );
const anchorDistance = Math.abs(radius / Math.tan(acuteAngle/2));
const determinant = (that._vert.x - that._prev.x)*(that._vert.y - y)
- (that._vert.x - x )*(that._vert.y - that._prev.y);
const sweepFlag = determinant < 0 ? 1 : 0;
const aIn = alongSegment(that._vert, that._prev, anchorDistance);
const aOut = alongSegment(that._vert, {x, y}, anchorDistance);

// that._context.arcTo() doesn't work properly, so we'll modify the string directly
that._context._ += ` L${aIn.x} ${aIn.y} A${radius} ${radius} 0 0 ${sweepFlag} ${aOut.x} ${aOut.y}`;
that._prev = aOut;
that._vert = { x, y };
}

CircleCorners.prototype = {
areaStart: function() {
this._line = 0;
},
areaEnd: function() {
this._line = NaN;
},
lineStart: function() {
this._prev = { x: NaN, y: NaN };
this._vert = { x: NaN, y: NaN };
this._point = 0;
},
lineEnd: function() {
// No more points, so draw a straight line to the end
this._context.lineTo(this._vert.x, this._vert.y);
// proceed
if (this._line || (this._line !== 0 && this._point === 3)) this._context.closePath();
this._line = 1 - this._line;
},
point: function(x, y) {
x = +x, y = +y;
switch (this._point) {
case 0:
this._point = 1;
break;
case 1:
this._point = 2;
if (this._line) {
this._context.lineTo(this._vert.x, this._vert.y);
} else {
this._context.moveTo(this._vert.x, this._vert.y);
}
break;
default:
arcPast(this, x, y);
return;
}
this._prev = this._vert;
this._vert = { x, y };
}
};

return (function custom(radius) {

function circleCorners(context) {
return new CircleCorners(context, radius);
}

circleCorners.radius = function(radius) {
return custom(+radius);
};

return circleCorners;
})(0);
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more