Are there any options for inverting a bezier curve? (cytoscape.js) - javascript

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)

Related

Is the Canvas2D ellipse() specification/implementation incorrect?

I've been playing around with how to render wireframe perspective-correct spheres using only canvas2d and ellipse math.
It's been fun, but I've soon come to realize that the ellipse() function has a very strange implementation with regards to the spec.
Indeed, the ellipse function takes 7 (or 8) arguments:
ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle)
The startAngle is described as such:
The angle at which the ellipse starts, measured clockwise from the positive x-axis and expressed in radians.
Given a parameter 0 <= t <= 2 * PI, we can compute the position of the associated point on the ellipse like so:
let dx = radiusX * cos(t)
let dy = radiusY * sin(t)
let px = x + dx * cos(rotation) - dy * sin(rotation)
let py = y + dx * sin(rotation) + dy * cos(rotation)
And if we use startAngle = t, our ellipse will begin its arc at our point. But. But. t is NOT an angle, and definitely not the angle of our point from the x-axis of the ellipse. Apparently some people still call it the eccentric angle, but my point still stands.
(See here)
And indeed, if we try to make the arc of an ellipse start at a specific angle, we can see that the result is not what we expect, unless the ellipse is a circle (radiusX = radiusY) or startAngle is a multiple of PI / 2.
Here is an interactive demo I've put up so that you can witness the strange default behaviour.
My claim is that the function should always behave like it does in the corrected case with the current specification.
Either that or change the spec to talk about parameters t for startAngle and endAngle, and avoid saying they are angles, because currently they definitely are not.
Does anyone know how this implementation/spec came to be, if anyone reported this before and if not where to lead such a discussion?
Any other insight appreciated!
I found this related question but it's rather unsatisfactory as replies merely show how to correct the function, but don't discuss whether the spec or implementation should be corrected.

Drawing an indifference curve that tangents a line in JSXGraph

I've started to use JSXGraph to draw a function. It should be a simple straight line (budget curve) that has a half-circle as his tangent (an indifference curve).
Furthermore the tangent should move with the line, if the line is moved.
Is this possible in JSXGraph?
Edit: The Curve and the function should look similar to this graph:
https://www.economicsonline.co.uk/Pictures2017/Grid-indifference-Basic-Equilibrium-new.png
Thanks,
Mike
Yes, it is possible with JSXGraph. One approach is to start with a point A and fix the slope s of the budget curve through A as well as the radius r of the circle line.
Doing the relatively straightforward math gives as coordinates of the center M of the circle:
M = A + r / sqrt(r*r + 1) * [-s, 1]
The equation of the circle line is
(y - M_y)^2 + (x - M_x)^2 = r^2
It follows that the indifference curve is
y = -sqrt(r^2 - (x - M_x)^2) + M_y
We take the negative square root, since we want to take the lower semicircle as indifference curve. A working example is at http://jsfiddle.net/4sg1dpq8/

How to convert VML path to SVG path?

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

Applying rounded corners to paths/polygons

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.

More elegant way to resize a sector path using Raphaël JS and SVG?

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.

Categories