I'm gathering some info for a project that has to start within a few weeks.
This project contains a browser-based drawing tool where users can add predefined shapes or forming shapes themselves. Shapes must be selectable, freely scalable and rotatable with a Illustrator-like transformtool (handles).
Predefined shapes that we have in mind are: rectangles, ellipses, half ellipses and (isosceles) triangles.
So far so good, to achieve this I was thinking of RaphaelJS or FabricJS but... Every shape (polygon/path) must be drawn with a certain cornerradius. And the cornerradius must be maintained while scaling, so no distortion occurs. The user can specify the rounding by input.
There's a few obstacles/questions:
Is there some uniform mathemetical formula to apply a cornerradius to the shapes I mentioned? Or must every shape be treated as a mini-project itself? I want to return it as a path or poly, so it can be drawn with SVG or canvas.
Every scale or rotate operation, by dragging the transformhandles, will result in (massive) calculations to retrieve an updated shape I think. Rectangles are the easiest to achieve and except ellipses, all the other shapes will be a lot harder to compute. Is there some way to speed up the process?
I found a site where users can draw flowcharts and apply a cornerradius on almost all shapes the are offered. It works so smoothly, I can't nail how they did it.
Link: https://www.lucidchart.com/ (try button)
For now, I'm a bit clueless, I guess to mediocre in mathematics. Perhaps someone can push me in the right direction and share some experiences?
Thanks in advance.
BTW. Performance is key in this project. The ouput of the drawing must be SVG format.
I ended up having a similar problem, and wasn't able to find a simple solution. I ended up writing a fairly generic corner-rounding function based on Adobe Illustrator's operation. It uses Bezier curves instead of arcs, but I think the result is pretty decent.
It supports rounding with a radius given in the coordinate space of the SVG image or as a fraction of the distance between a corner and its neighbors.
To use this, include rounding.js in your project and call the function:
roundPathCorners(pathString, radius, useFractionalRadius)
The code and some test paths are here: http://embed.plnkr.co/kGnGGyoOCKil02k04snu/preview
This is how the examples from the Plnkr render:
The starting point could be using-svg-curves-to-imitate-rounded-corners. The principle is to convert every corner with shorthand relative cubic (s). This example is very basic and works only with two possible corner cases.
I think expanding this like corner replace with shorthand relative cubic is possible to expand to cover also other path segments. Every segment has a on-curve coordinate point, which have to be replaced with s segment. The math can be interesting part of this solution.
Despite this question being around for some time, some may stop by and try this solution:
var BORDER_RADIUS = 20;
function roundedPath( /* x1, y1, x2, y2, ..., xN, yN */ ){
context.beginPath();
if (!arguments.length) return;
//compute the middle of the first line as start-stop-point:
var deltaY = (arguments[3] - arguments[1]);
var deltaX = (arguments[2] - arguments[0]);
var xPerY = deltaY / deltaX;
var startX = arguments[0] + deltaX / 2;
var startY = arguments[1] + xPerY * deltaX / 2;
//walk around using arcTo:
context.moveTo(startX, startY);
var x1, y1, x2, y2;
x2 = arguments[2];
y2 = arguments[3];
for (var i = 4; i < arguments.length; i += 2) {
x1 = x2;
y1 = y2;
x2 = arguments[i];
y2 = arguments[i + 1];
context.arcTo(x1, y1, x2, y2, BORDER_RADIUS);
}
//finally, close the path:
context.arcTo(x2, y2, arguments[0], arguments[1], BORDER_RADIUS);
context.arcTo(arguments[0], arguments[1], startX, startY, BORDER_RADIUS);
context.closePath();
}
The trick is to start (and stop) at the middle of the first line, and then use the arcTo function which is described very nicely here.
Now you "just" have to find a way to express all of your shapes as polygons.
Related
I am working on a simple whiteboard application where the drawings are represented by quadratic Bezier curves (using the JavaScript's CanvasPath.quadraticCurveTo function). I am trying to implement functionality so that an eraser tool or a selection tool are able to determine if they are touching a drawing.
To show what I'm talking about, in the following image is a red drawing and I need to be able to determine that the black rectangles and black point overlap with the area of the drawing. For debugging purposes I have added blue circles which are control points of the curve and the green line which is the same Bezier curve but with a much smaller width.
I have included my code which generates the Bezier curve:
context.beginPath();
context.moveTo(localPoints[0].x, localPoints[0].y);
let i;
for (i = 1; i < localPoints.length - 2; i++)
{
let xc = (localPoints[i].x + localPoints[i + 1].x) / 2;
let yc = (localPoints[i].y + localPoints[i + 1].y) / 2;
context.quadraticCurveTo(localPoints[i].x, localPoints[i].y, xc, yc);
}
// curve through the last two points
context.quadraticCurveTo(localPoints[i].x, localPoints[i].y, localPoints[i + 1].x, localPoints[i + 1].y);
context.stroke();
I have been able to find answers on how to determine if a line segment intersects a Bezier curve, but I have not been able to find how to determine if something does not intersect the actual curve but is close enough to be considered to overlap its "area". To do so, I figure that I need to just find the distance between the curve and the rectangle/point and then make sure than distance is less than the width used to draw the curve, but I am unsure on how to find that distance.
Some interesting articles/posts:
How to track coordinates on the quadraticCurve
https://coderedirect.com/questions/385964/nearest-point-on-a-quadratic-bezier-curve
And if it doesn't work maybe you can take a look at this library: https://pomax.github.io/bezierjs/
As suggested by Pomax in the comments the thing you're looking for is in the library and it looks like there is a proper explanation.
There is a live demo if you want to try it: https://pomax.github.io/bezierinfo/#projections
The source code of it is here: https://pomax.github.io/bezierinfo/chapters/projections/project.js
To use it install it using the steps from GitHub: https://github.com/Pomax/bezierjs
Of course credit to Pomax for suggesting his library
I'm trying to shape my edges similarly to this:
static mock of desired edges/curves
I'm able to create "S" shaped curves, but i would like them to invert when going downwards from the root node (similarly to the picture). I haven't noticed anything in the docs that describes settings to do this.
I have a demo here: https://codesandbox.io/s/l5m6mnlqrz
What could also work is if I were able to smooth out the 90 degree curve with the "taxi" curve-style, although this doesn't seem possible.
Any suggestions appreciated. Thanks.
It is not possible to create desired form using single Bezier curve, because central range should be vertical. But two conjugated curves might provide appropriate result.
For points A (left one), and B (it is not important - whether B point is lower or higher than A):
The first curve have starting point P0=(XA, YA) and ending point P3=((XA + XB)/2, ((YA + YB)/2)
The first control point must lie at the same horizontal as starting point, the second one - at the same vertical as ending point
X1, Y1 = X0 + DX, Y0
X2, Y2 = X3, Y3 - DY
Parameters DX and DY define rounding of right angle.
Try to set them as DX = (X3 - X0) / 3 and DY = (Y3 - X0) / 3m then vary denominator to get desired curve form
The second part is mirrored curve with points
(X3, Y3), (X3, Y3 + DY), (XB - DX, YB), (XB, YB)
The reason I'm interested in canvases having any shape is that it would then be possible to cut out images with Bezier curves and have the text of a web page flow around the canvas shape, i.e. cut-out image.
What is needed is the possibility to have a free-form shaped div, SVG and HTML5 canvas. (Applied to SVG, I understand this would be equivalent to Flash symbols.) You could then imagine applying a box model (padding, border and margin) for shapes, but it wouldn't be a box (it would be parallel to the shape)!
I suppose it would also then be possible to have text that wraps inside a shape as much as text that flows around a shape.
I read an interesting blog post about "Creating Non-Rectangular Layouts with CSS Shapes" here: http://sarasoueidan.com/blog/css-shapes/
but it doesn't include text wrapping inside a shape.
Then, there's also a CSS Shapes editor for Brackets (a code editor):
http://blogs.adobe.com/webplatform/2014/04/17/css-shapes-editor-in-brackets/
As simple as it may sound it actually involves quite a few steps to achieve.
An outline would look something like this:
Define the shape as a polygon, ie. point array
Find bounds of polygon (the region the polygon fits inside)
Contract polygon with padding using either a cetronid algorithm or simply a brute-force approach using center of bounds
Define line height of text and use that as a basis for number of scan-lines
Basically use a polygon-fill algorithm to find segment within the shape which can fill in text. The steps for this is:
Use an odd/even scanner by getting an intersection point (using line intersection math) with text scan line and each of the lines between the points in the polygon
Sort the points by x
use odd and even point to create a segment. This segment will always be inside the polygon
Add clipping using original polygon
Draw in image
Use the segments to get a width. Start parsing the text to fill and measure the width.
When text width fits within the segment width then print the chars that fits
Repeat for next text/words/chars until end of text or segments
In other words: you would need to implement a polygon fill algorithm but instead of filling in lines (per pixel line) you use the line as basis for the text.
This is fully doable; actually, I went ahead to create a challenge for myself on this problem, for the fun of it, so I created a generic solution that I put on GitHub released under MIT license.
The principle described above are implemented, and to visualize the steps:
Define the polygon and padding - here I chose to just use a simple brute-force and calculate a smaller polygon based on center and a padding value - the light grey is the original polygon and the black obviously the contracted version:
The points are defined as an array [x1, y1, x2, y2, ... xn, yn] and the code to contract it (see link to project for full source on all these parts):
var pPoints = [],
i = 0, x, y, a, d, dx, dy;
for(; i < points.length; i += 2) {
x = points[i];
y = points[i+1];
dx = x - bounds.px;
dy = y - bounds.py;
a = Math.atan2(dy, dx);
d = Math.sqrt(dx*dx + dy*dy) - padding;
pPoints.push(bounds.px + d * Math.cos(a),
bounds.py + d * Math.sin(a));
}
Next step is to define the lines we want to scan. The lines are based on line height for font:
That is simple enough - just make sure the start and end points are outside the polygon.
We use an odd/even scan approach and check intersection of the scanline versus all lines in the polygon. If we get a intersect point we store that in a list for that line.
The code to detect intersecting lines is:
function getIntersection(line1, line2) {
// "unroll" the objects
var p0x = line1.x1,
p0y = line1.y1,
p1x = line1.x2,
p1y = line1.y2,
p2x = line2.x1,
p2y = line2.y1,
p3x = line2.x2,
p3y = line2.y2,
// calc difference between the coords
d1x = p1x - p0x,
d1y = p1y - p0y,
d2x = p3x - p2x,
d2y = p3y - p2y,
// determinator
d = d1x * d2y - d2x * d1y,
px, py,
s, t;
// if is not intersecting/is parallel then return immediately
if (Math.abs(d) < 1e-14)
return null;
// solve x and y for intersecting point
px = p0x - p2x;
py = p0y - p2y;
s = (d1x * py - d1y * px) / d;
if (s >= 0 && s <= 1) {
// if s was in range, calc t
t = (d2x * py - d2y * px) / d;
if (t >= 0 && t <= 1) {
return {x: p0x + (t * d1x),
y: p0y + (t * d1y)}
}
}
return null;
}
Then we sort the point for each line and use pairs of points to create segments - this is actually a polygon-fill algorithm. The result will be:
The code to build segments is a bit extensive for this post so check out the project linked above.
And finally we use those segments to replace with actual text. We need to scan a text from current text pointer and see how much will fit inside the segment width. The current code is somewhat basic and skips a lot of considerations such as word breaks, text base-line position and so forth, but for initial use it will do.
The result when put together will be:
Hope this gives an idea about the steps involved.
I am currently working on an app for which I need to convert VML shapes into SVG shapes. While I can handle all other aspects of it, I am facing problem in correctly converting the path of the shape from VML path to SVG path. I am using a combination of XSLT and Javascript for my codes.
I have enough control on conversion of angular shapes (i.e. shapes containing only straight lines) but I am facing difficulty in converting path with curves.
For instance, for a simple shape this:
The VML path is: m10800,qx21600,10800,10800,21600l,21600,,xe
Now if I replace m with M, l with L and qx with Q and do the necessary scaling of the coordinates I get the following SVG shape:
The SVG path treats first set of coordinates in Q/qx as a control point and hence the actual path doesn't passes through the point whereas the VML intended those coordinates as the point over which the path should pass through. I don't understand how I can achieve that with SVG (i.e. making sure that the path passes through a specific point or points).
Currently I am using this and this for researching SVG and VML respectively. I also tried using Vector Converter 1.2 but that doesn't works either.
Can anyone suggest me a way, a library, any study links or tutorials where I can find a solution to my problem?
Thanks in advance!!
"qx" in VML is an "elliptical quadrant", "Q" in SVG is a quadratic bezier. Completely different things.
The simplest solution to converting a "qx" is to approximating it with a cubic bezier. Using an arc would be most accurate, but there will be some tricky maths involved in order to determine the correct value for "sweep flag". Although a cubic bezier is not a perfect approximation to a quadrant, it is very close, and the error will not be noticeable enough to affect your drawings.
The secret to drawing circular/elliptical quadrants is the constant 0.5522847498. It defines how long the control point lines have to be to simulate the elliptical curve. You can find explanations for how it is derived by googling that number.
So VML defines "qx" as an elliptical quadrant starting out in the X direction. So given the path command "qx21600,10800", the algorithm for conversion will be:
arcFactor = 0.5522847498;
currentX = 10800;
currentY = 0; // start coords (from the move)
x = 21600;
y = 10800; // first coords in "qx"
dx = x - currentX;
dy = y - currentY;
// Calculate first control point
cp1x = currentX + dx * arcFactor;
cp1y = currentY; // starts out horizontal
// Calculate second control point
cp2x = x;
cp2y = y - dy * arcFactor;
svgBezier = "C" + cp1x + "," + cp1y + "," + cp2x + "," + cp2y + "," + x + "," + y;
Now your curve has a second set of coordinates to the qx. The spec says that it means a repeat of the "qx" command. However it makes no sense for the second set to behave exactly the same as the qx (ie. start out horizontal). So I think they must actually behave as a "qy" (start out vertical). Ie. the qx and qy alternate. Assuming that is the case, the calculation for the qy should be:
// Calculate first control point
cp1x = currentX; // starts out vertical
cp1y = currentY + dy * arcFactor;
// Calculate second control point
cp2x = x - dx * arcFactor;
cp2y = y;
Demo fiddle is here
I am using Raphaël for the first time with little svg experience and I need someone who is really knowledgeable with these two to help me.
I have created a pie chart with dynamic sectors. The sectors can be resized by dragging on the round buttons. See this fiddle. I have only tested in Chrome and Safari which are the only required browsers.
The pie chart is not yet complete. The sectors can overlap. Please ignore this for now.
I was faced with problems, when the starting angle of a sector was greater than the ending angle. This is the case when the ending angle goes past the 0/360° mark. To solve this I made use of the path-rotation-parameter. I moved the sector forward while moving the angles back, until the end angle is at 360. You can see this in the fiddle in this function:
function sector_update(cx, cy, r, startAngle, endAngle, sec) {
var x1 = cx + r * Math.cos(-startAngle * rad),
x2 = cx + r * Math.cos(-endAngle * rad),
y1 = cy + r * Math.sin(-startAngle * rad),
y2 = cy + r * Math.sin(-endAngle * rad);
var rotation = 0;
// This is the part that I have the feeling could be improved.
// Remove the entire if-clause and let "rotation" equal 0 to see what happens
if (startAngle > endAngle) {
rotation = endAngle;
startAngle = startAngle - endAngle;
endAngle = 360;
}
sec.attr('path', ["M", cx, cy, "L", x1, y1, "A", r, r, rotation,
+(endAngle - startAngle > 180), 0, x2, y2, "z"]);
}
Although it works nicely, I'm a bit skeptical. Can this be solved without the rotation of the path? I appreciate any help or pointers.
Can this be solved without the rotation of the path?
Answer: Yes, it can. You don't have to change the rotation of the path at all. Unless I'm missing something, the following code seems to work the same as what you have in the fiddle:
function sector_update(cx, cy, r, startAngle, endAngle, sec) {
var x1 = cx + r * Math.cos(-startAngle * rad),
x2 = cx + r * Math.cos(-endAngle * rad),
y1 = cy + r * Math.sin(-startAngle * rad),
y2 = cy + r * Math.sin(-endAngle * rad);
//notice there is no "roation" variable
if (startAngle > endAngle) {
startAngle -= endAngle;
endAngle = 360;
}
sec.attr('path', ["M", cx, cy, "L", x1, y1, "A", r, r, 0,
+(endAngle - startAngle > 180), 0, x2, y2, "z"]);
}
Explanation: For my explanation, I will use the SVG terminology in the
W3 Spec and Raphael Reference Library. That is, while you use cx, cy, and rotation, these use rx, ry, and x-axis-rotation respectively.
In short, whenever rx equals ry, then x-axis-rotation is meaningless.
Look at this SVG. Use your browser's development tools, or save the SVG to your computer and use a file editor to edit it. Specifically, look at the last path element, which has four arcs in it. Try modifying the x-axis-rotation value on each arc. You will notice that the first arc (where rx and ry are both "25") never changes when you update x-axis-rotation value.
Why? This is because you have a circular arc. No matter how much you rotate a circle, it will still be the same circle. For example, hold up a glass in front of you so that the glass is horizontal to the ground, and you are looking directly down the glass. Now rotate/twist the glass with your wrist. Do you see how the circular shape you see stays in the same circular shape? Now set the glass on the table normally (so it is vertical and could hold a liquid). Now tip the glass over. You can see the obvious perspective change; it was pointing up, but now it is laying flat. That is what x-axis-rotation does.
Perhaps a better example is to just play around with the aforementioned SVG file. Play with x-axis-rotation on the arcs in the final path element. You will see the arcs being rotated around. That is what x-axis-rotation does.
Back to your code: Because you are dealing only with circular objects, the x-axis-rotation will make no difference on the final output. So long as you are only dealing with circular objects, you can hard-code it's value to zero without any worries. All you really needed to do is modify the angles, which you had done correctly.
Performance: I tried using JavaScript to time your sector_update function both with and without modifying the x-axis-rotation variable. The result? I saw no difference in performance. The majority of the time spent is on actually drawing the SVG, not on the math that determines it's values. In fact, all you are really doing in JavaScript is updating the code to set the value in the path element. At that point in time, the browser takes over with it's rendering engine to actually draw the SVG object. I suppose then it's a per-browser issue, as each browser has different rendering performance. But as for whether or not the x-axis-rotation value has any effect, my guess is no. If there is a performance hit (because the browser may have to do an additional floating-point operation), it is so incredibly moot because the overwhelming majority of the time is spent drawing the object, not calculating it's values. So I would say not to worry about it.
I hope that helps, let me know if I missed something or didn't explain something well enough.