transition a circle into a line by unrolling it with SVG and d3 - svg

For a project we are trying to make a circle into a line (and back again) while it is rotating along a linear path, much like a tire rotates and translates when rolling on a road, or a curled fore finger is extended and recurled into the palm.
In this Fiddle, I have a static SVG (the top circle) that rotates along the linear black path (which is above the circle, to mimic a finger extending) that is defined in the HTML.
I also use d3 to generate a "circle" that is made up of connected points (and can unfurl if you click on/in the circle thanks to #ChrisJamesC here ), and is translated and rotated
in the function moveAlongLine when you click on the purple Line:
function moveAlongLine() {
circle.data([lineData])
.attr("transform", "translate(78.5,0) rotate(-90, 257.08 70) ")
.duration(1000)
circle.on("click", transitionToCircle)
}
The first problem is that the .duration(1000) is not recognized and throws a Uncaught TypeError: Object [object Array] has no method 'duration' in the console, so there is a difference between the static definition of dur in SVG and dynamically setting it in JS/D3, but this is minor.
The other is should the transform attributes be abstracted from one another like in the static circle? in the static circle, the translate is one animation, and the rotation is another, they just have the same star and duration, so they animate together. How would you apply both in d3?
The challenge that I can not get, is how to let it unroll upwards(and also re-roll back), with the static point being the top center of the circle also being the same as the leftmost point on the line.
these seem better:
I should try to get the unfurl animation to occur while also rotating? This seems like it would need to be stepwise/sequential based...
Or Consider an octogon (defined as a path), and if it were to rotate 7 of the sides, then 6, then 5.... Do this for a rather large number of points on a polyhedron? (the circle only needs to be around 50 or so pixels, so 100 points would be more than enough) This is the middle example in the fiddle. Maybe doing this programmatically?
Or This makes me think of a different way: (in the case of the octogon), I could have 8 line paths (with no Z, just an additional closing point), and transition between them? Like this fiddle
Or anything todo with keyframes? I have made an animation in Synfig, but am unsure ho get it to SVG. The synfig file is at http://specialorange.org/filedrop/unroll.sifz if you can convert to SVG, but the xsl file here doesn't correctly convert it for me using xsltproc.
this seems really complicated but potential:
Define a path (likely a bézier curve with the same number of reference points) that the points follow, and have the reference points dynamically translate as well... see this for an concept example
this seems complicated and clunky:
Make a real circle roll by, with a growing mask in front of it, all while a line grows in length
A couple of notes:
The number of points in the d3 circle can be adjusted in the JS, it is currently set low so that you can see a bit of a point in the rendering to verify the rotation has occurred (much like the gradient is in the top circle).
this is to help students learn what is conserved between a number line and a circle, specifically to help learn fractions. For concept application, take a look at compthink.cs.vt.edu:3000 to see our prototype, and this will help with switching representations, to help you get a better idea...

I ended up using the same function that generates the circle as in the question, and did a bit of thinking, and it seemed like I wanted an animation that looked like a finger unrolling like this fiddle. This lead me to the math and idea needed to make it happen in this fiddle.
The answer is an array of arrays, with each nested array being a line in the different state, and then animate by interpolating between the points.
var circleStates = [];
for (i=0; i<totalPoints; i++){
//circle portion
var circleState = $.map(Array(numberOfPoints), function (d, j) {
var x = marginleft + radius + lineDivision*i + radius * Math.sin(2 * j * Math.PI / (numberOfPoints - 1));
var y = margintop + radius - radius * Math.cos(2 * j * Math.PI / (numberOfPoints - 1));
return { x: x, y: y};
})
circleState.splice(numberOfPoints-i);
//line portion
var lineState = $.map(Array(numberOfPoints), function (d, j) {
var x = marginleft + radius + lineDivision*j;
var y = margintop;
return { x: x, y: y};
})
lineState.splice(i);
//together
var individualState = lineState.concat(circleState);
circleStates.push(individualState);
}
and the animation(s)
function all() {
for(i=0; i<numberOfPoints; i++){
circle.data([circleStates[i]])
.transition()
.delay(dur*i)
.duration(dur)
.ease("linear")
.attr('d', pathFunction)
}
}
function reverse() {
for(i=0; i<numberOfPoints; i++){
circle.data([circleStates[numberOfPoints-1-i]])
.transition()
.delay(dur*i)
.duration(dur)
.ease("linear")
.attr('d', pathFunction)
}
}

(Note: This should be in comments but not enough spacing)
Circle Animation
Try the radial wipe from SO. Need to tweak it so angle starts at 180 and ends back at same place (line#4-6,19) and move along the X-axis (line#11) on each interation. Change the <path... attribute to suit your taste.
Line Animation Grow a line from single point to the length (perimeter) of the circle.
Sync both animation so that it appears good on all browsers (major headache!).

Related

Generating arrays for lithography with variable dimensions

First I must apologize because I am a chemist and not a programmer, so I am venturing far into unknown territory.
I am working on developing some patterns for micro-contract printing to create templates for controlled cellular growth. The master cast for the template is printed on A4 transparency and regardless of how much space I use, the cost is more or less the same. One of my patterns is about 2 x 2 mm, so you can imagine how many I can fit on the master template.
With that in mind what I would like to do is generate a repeating array of circles and tracks. This is easily accomplished in adobe illustrator, but it has become tedious. I would like to automate the ability to vary the dimensions of the circles, the width of the tracks connecting them, and the spaces between the circles.
For example, I might want a 20 x 20 grid of 30 um circles connected with a 10 um wide track with circles that are 150 um between the edges.
I was hoping to do this in Matlab, because I'm currently learning Matlab for some image processing capabilities.
An example of what the final product looks like can be seen:
http://www.nature.com/srep/2014/140424/srep04784/full/srep04784.html
http://pubs.rsc.org/en/Content/ArticleLanding/2011/LC/c1lc20257j#!divAbstract
I would appreciate some direction in:
Is doing this in Matlab even a good idea to begin with?
Setting up a code to generate a "grid" of circles
Connecting those circles with vertical, horizontal or diagonal tracks
I feel like this something someone has done before, so even pointing me to similar code that I could study would be a lot of help
Thanks!
I'm not very used to Matlab so I can't tell you for 0).
Below is a possible answer for 1) and 3). If you think my answer can help you, I can write some code about 2).
The library d3.js might be of interest for what you're doing. It basically allows you to bind data to svg elements.
Here is an example of what you could do. Let's say your data is a list of circle properties (size, position)
JSFiddle here
data = [ {x: 20µm, y:250µm, radius: 250µm}, {....}, ... ]
//Dimensions of what will be dsplayed in your browser
width = 500px
height = 700px
//d3.js provides functions to automatically resize your data to the viewport (maps domain -> range)
xScale = d3.scale.linear()
.domain([0, a4_format_size_x])
.range([0, width])
yScale = d3.scale.linear()
.domain([0, a4_format_size_y])
.range([0, height])
rScale = d3.scale.linear()
.domain([0, max_circle_size])
.range([0, 20])
svg = d3.select(element[0])
.append("svg")
.attr("width", width)
.attr("height", height)
svg.selectAll("circle")
.data(data) // This line binds your date to SVG elements
.enter()
.append("circle")
.attr("cx", function(data) { return xScale(data.x)})
.attr("cy", function(data) { return yScale(data.y)})
.attr("r", function(data) { return rScale(data.radius)}
Note : the syntax selectAll > enter > append might seem weird at first, if you're puzzled feel free to have a look at this
Now for the generation of the data itself (a "grid of circles"), you could have something like
var numCirclesX = 500
var numCirclesY = 700
var data = []
for(var i=0; i<numCirclesX; i++){
for(var j=0; j<numCirclesY, j++){
data.push({ x: i*size_A4_x/numCirclesX,
y: j*size_A4_y/numCirclesY,
radius: 5 })
}
}

d3.svg.line stroke is skewed when scaling y-dimension with SVG transform

Using d3's d3.svg.line produces a line which looks after magnifying only in the y-dimension like this one
Here is the corresponding path element
<path d="M 70.13775,
303.1818181818182 L 73.03775,
285.9090909090909 L 75.93775,
402.5 L 78.83775,
402.5 L 81.73775,
419.77272727272725 L 84.63775,
342.0454545454545 L 87.53775"></path>
As one can see, when the pen is moved to the right, the width of the corresponding line segment is a different one compared to the width of the line of the path segment, when the pen is moved vertically.
Question: Is it possible to draw the line in a such way that the width of line is the same everywhere? As if one draws the line with holding a pen with a round brush perpendicular to the drawing area.
If you want to scale your line along one axis, instead of using an svg transform you would probably be better off using a d3.scale to scale your incoming data when you make your d3.svg.line().
Lets say you have a d3 scale called myScale
Then when you make your d3 svg line, you can use the scale to modify one of the coordinates:
var lineGen = d3.svg.line()
.x(function(d) {return d.x;})
.y(function(d) {return myScale(d.y);});
The solution of using vector-effect will probably give you the result you want in bleeding edge browsers, but it seems like overkill to use an effect to do something this simple, and the lack of browser support for SVG1.2 features might become problematic for you. If you were trying to fix a static svg file that would probably be the way to go, but since you are generating it with d3, you might as well use the built-in methods for scaling.
EDIT
For a simpler solution, you could even scale it by a constant factor:
var scaleFactor = 1.75;
var lineGen = d3.svg.line()
.x(function(d) {return d.x;})
.y(function(d) {return d.y * scaleFactor;});

How to get polygon points in Fabric.js

I am drawing polygon by capturing mouse clicks on canvas and then passing these points to fabric.Polygon. So, in this manner I'm drawing multiple polygons.
What I need know is, I want to get the mouse co-ordinates (pixel points on canvas) for the polygon which is selected now?
I have tried with:
canvas.getActiveObject().get('points');
But this is giving some negative and some positive values.
So, can u please tell me a way to find out the polygon points?
Polygon points are relative to its center so you can get their "absolute" position like so:
var polygon = canvas.getActiveObject();
var polygonCenter = polygon.getCenterPoint();
var translatedPoints = polygon.get('points').map(function(p) {
return {
x: polygonCenter.x + p.x,
y: polygonCenter.y + p.y
};
});
Let's check how this looks:
translatedPoints.forEach(function(p) {
canvas.getContext().strokeRect(p.x-5, p.y-5, 10, 10);
});
I think this will only work if polygon's angle is at 0 (otherwise need to "rotate" points coordinates as well).
It looks like that from version 2.0 they changed the coordinates of the polygon. Before 2.0 points relative to the center of the polygon; after 2.0 they are absolute to the canvas;
Check out my response to the similar questions https://stackoverflow.com/a/53710375/4681279

shade border of 2D polygon differently

we are programming a 2D game in XNA. Now we have polygons which define our level elements. They are triangulated such that we can easily render them. Now I would like to write a shader which renders the polygons as outlined textures. So in the middle of the polygon one would see the texture and on the border it should somehow glow.
My first idea was to walk along the polygon and draw a quad on each line segment with a specific texture. This works but looks strange for small corners where the textures are forced to overlap.
My second approach was to mark all border vertices with some kind of normal pointing out of the polygon. Passing this to the shader would interpolate the normals across edges of the triangulation and I could use the interpolated "normal" as a value for shading. I could not test it yet but would that work? A special property of the triangulation is that all vertices are on the border so there are no vertices inside the polygon.
Do you guys have a better idea for what I want to achieve?
Here A picture of what it looks right now with the quad solution:
You could render your object twice. A bigger stretched version behind the first one. Not that ideal since a complex object cannot be streched uniformly to create a border.
If you have access to your screen buffer you can render your glow components into a rendertarget and align a full-screen quad to your viewport and add a fullscreen 2D silhouette filter to it.
This way you gain perfect control over the edge by defining its radius, colour, blur. With additional output values such as the RGB values from the object render pass you can even have different advanced glows.
I think rendermonkey had some examples in their shader editor. Its definetly a good starting point to work with and try out things.
Propaply you want calclulate new border vertex list (easy fill example with triangle strip with originals). If you use constant border width and convex polygon its just:
B_new = B - (BtoA.normalised() + BtoC.normalised()).normalised() * width;
If not then it can go more complicated, there is my old but pretty universal solution:
//Helper function. To working right, need that v1 is before v2 in vetex list and vertexes are going to (anti???) cloclwise!
float vectorAngle(Vector2 v1, Vector2 v2){
float alfa;
if (!v1.isNormalised())
v1.normalise();
if (!v2.isNormalised())
v2.normalise();
alfa = v1.dotProduct(v2);
float help = v1.x;
v1.x = v1.y;
v1.y = -help;
float angle = Math::ACos(alfa);
if (v1.dotProduct(v2) < 0){
angle = -angle;
}
return angle;
}
//Normally dont use directly this!
Vector2 calculateBorderPoint(Vector2 vec1, Vector2 vec2, float width1, float width2){
vec1.normalise();
vec2.normalise();
float cos = vec1.dotProduct(vec2); //Calculates actually cosini of two (normalised) vectors (remember math lessons)
float csc = 1.0f / Math::sqrt(1.0f-cos*cos); //Calculates cosecant of angle, This return NaN if angle is 180!!!
//And rest of the magic
Vector2 difrence = (vec1 * csc * width2) + (vec2 * csc * width1);
//If you use just convex polygons (all angles < 180, = 180 not allowed in this case) just return value, and if not you need some more magic.
//Both of next things need ordered vertex lists!
//Output vector is always to in side of angle, so if this angle is.
if (Math::vectorAngle(vec1, vec2) > 180.0f) //Note that this kind of function can know is your function can know that angle is over 180 ONLY if you use ordered vertexes (all vertexes goes always (anti???) cloclwise!)
difrence = -difrence;
//Ok and if angle was 180...
//Note that this can fix your situation ONLY if you use ordered vertexes (all vertexes goes always (anti???) cloclwise!)
if (difrence.isNaN()){
float width = (width1 + width2) / 2.0; //If angle is 180 and border widths are difrent, you cannot get perfect answer ;)
difrence = vec1 * width;
//Just turn vector -90 degrees
float swapHelp = difrence.y
difrence.y = -difrence.x;
difrence.x = swapHelp;
}
//If you don't want output to be inside of old polygon but outside, just: "return -difrence;"
return difrence;
}
//Use this =)
Vector2 calculateBorderPoint(Vector2 A, Vector2 B, Vector2 C, float widthA, float widthB){
return B + calculateBorderPoint(A-B, C-B, widthA, widthB);
}
Your second approach can be possible...
mark the outer vertex (in border) with 1 and the inner vertex (inside) with 0.
in the pixel shader you can choose to highlight, those that its value is greater than 0.9f or 0.8f.
it should work.

How to draw a freehand-looking ellipse or circle?

My question involves various techniques for drawing lines that appear to be freehand:
How do you draw like a Crayon?
Specifically Steve Hanov posted this excellent blog entry.
From that I was able to implement a nice looking algorithm for freehand lines using bezier curves. However, I am stuck on how to implement a freehand looking ellipse. Ideally, I'd like to give it a rect to use as a boundary, similar to other ellipse drawing calls. But, I want it to look very freehand.
So far, the best I have come up with this:
- (UIBezierPath*) freehandEllipseFromRect:(CGRect) rect {
// freehand ellipses need a lil more height
rect = CGRectMake(rect.origin.x, rect.origin.y-5, rect.size.width, rect.size.height+10);
UIBezierPath* path = [UIBezierPath bezierPath];
CGPoint topMidPoint = CGPointMake(rect.origin.x + (rect.size.width/2), rect.origin.y);
CGPoint bottomMidPoint = CGPointMake(rect.origin.x + (rect.size.width/2), rect.origin.y+rect.size.height);
// random point along bottom quarter of height, cause makes it look better
CGFloat randomY = (((CGFloat) (arc4random() % RAND_MAX) / RAND_MAX)) * (rect.size.height/4);
CGPoint leftControlPoint = CGPointMake(rect.origin.x-(rect.size.width), rect.origin.y+(rect.size.height-randomY));
// another random y;
randomY = (((CGFloat) (arc4random() % RAND_MAX) / RAND_MAX)) * (rect.size.height/4);
CGPoint rightControlPoint = CGPointMake(rect.origin.x+(rect.size.width*2), rect.origin.y+(rect.size.height-randomY));
CGFloat overshootValueX = (((CGFloat) (arc4random() % RAND_MAX) / RAND_MAX)) * 4;
CGFloat overshootValueY = (((CGFloat) (arc4random() % RAND_MAX) / RAND_MAX)) * 6;
[path moveToPoint:CGPointMake(topMidPoint.x+overshootValueX, topMidPoint.y)];
[path addQuadCurveToPoint:bottomMidPoint controlPoint:leftControlPoint];
// random value to overshoot
overshootValueX = (((CGFloat) (arc4random() % RAND_MAX) / RAND_MAX)) * 20;
overshootValueY = (((CGFloat) (arc4random() % RAND_MAX) / RAND_MAX)) * 4;
[path addQuadCurveToPoint:CGPointMake(topMidPoint.x-overshootValueX, topMidPoint.y-overshootValueY) controlPoint:rightControlPoint];
return path;
}
The result looks like this:
I don't like how pointed it is on top, and despite all my trying I just can't get it much better. Plus, I like the curves to look less perfect, and not rely on the overhang as the only "freehand" looking part. I think 2 quad curves are just the wrong way to go.....
Maybe 4 arcs?
Anyone have another solution or some sample code for me? (any language is fine)
So this question has been open for a long time, let me try to give it a shot. There are two parts: (1) Making the path look not perfect. (2) Stroking the path like drawn by hand.
For (1), subdivide the shit out of the thing. Make it out of 100 or so control points and distort them with a wrapping function that varies slowly. For (2) assign a slowly varying, continuous thickness and angle over the path, maybe also adding some noise. For a good human looking noise read up on Perlin noise, it's awesome. Also it's always a good idea to look at how other people do it, create paths in Photoshop and stroke them, it has an option to do it naturally looking.
Personally I would define your ellipse parametrically, like this:
x=(width*cos(t)/2)+centerx;
y=(height*cos(t)/2)+centery;
then make a function that generates small random numbers
make a function that finds the normal vector to the curve (parametrically)
x=width*cos(t);
y=height*sin(t);
normal=UnitVector(x,y);
for every point on your ellipse, offset by scaling the normal at that point by a small random number.
Draw a curve through the points using cubic interpolation.

Resources