How to make rounded-perpendicular lines with react-konva? - konvajs

I need to make rounded-perpendicular lines with react-konva, is it achievable using existing APIs? If yes, how?
I used bezier API for Line class, it works great. Somehow now I need to revamp the bezier curve to be rounded-perpendicular lines.
Sth like this:

There are many ways you can implement it. You can use tension to create curved lines or use lineCap property.
But to have more control it is better to create a custom shape.
const RADIUS = 20;
const Line = ({ points }) => {
return (
<Shape
points={points}
sceneFunc={(context, shape) => {
const width = points[1].x - points[0].x;
const height = points[1].y - points[0].y;
const dir = Math.sign(height);
const radius = Math.min(RADIUS, Math.abs(height / 2), Math.abs(width / 2));
context.beginPath();
context.moveTo(points[0].x, points[0].y);
context.lineTo(points[0].x + width / 2 - RADIUS, points[0].y);
context.quadraticCurveTo(
points[0].x + width / 2,
points[0].y,
points[0].x + width / 2,
points[0].y + dir * radius
);
context.lineTo(points[0].x + width / 2, points[1].y - dir * radius);
context.quadraticCurveTo(
points[0].x + width / 2,
points[1].y,
points[0].x + width / 2 + radius,
points[1].y
);
context.lineTo(points[1].x, points[1].y);
context.fillStrokeShape(shape);
}}
stroke="black"
strokeWidth={2}
/>
);
};
Demo: https://codesandbox.io/s/757nw05p6

Related

Scaling points of line - konvajs

I'm looking to have a transform on a KonvaJS line. I have all of it working and it scales the object, however I'd like it to adjust the points of the line instead of setting the scale property when I grab a resize handle. Would also consider doing this in a path as well.
So the goal is that my objects scaleX and scaleY is always 1 and it's just the points that are scaling out.
Is this at all possible?
You just need to apply the scale to points property:
shape.on('transformend', () => {
const oldPoints = shape.points();
const newPoints = [];
for(var i = 0; i< oldPoints.length / 2; i++) {
const point = {
x: oldPoints[i * 2] * shape.scaleX(),
y: oldPoints[i * 2 + 1] * shape.scaleY(),
}
newPoints.push(point.x, point.y);
}
shape.points(newPoints);
shape.scaleX(1);
shape.scaleY(1);
layer.draw();
})
Demo: https://jsbin.com/vuhakuvoxa/1/edit?html,js,output

KonvaJS: How to get a scaled & moved layer's position based on current mouse position?

here's my Konva object design: One stage that includes two layers. One layer is a toolbar that I drag and drop shapes from, one layer is a canvas that I drop the elements in. The canvas layer can be zoomed in and out and is draggable (relative zoomed feature implemented from lavtron's demos https://konvajs.org/docs/sandbox/Zooming_Relative_To_Pointer.html).
When the user drops a shape from the toolbar, a new shape gets added to the canvas layer and should have the same position as to where the user eyeballed it. So before I put zooming into my program, the only concern was to modify the position according to layer's offset by:
toPush.x = toPush.x - this.refs.layer2.attrs.x; //toPush.x = Stage mouseX position
toPush.y = toPush.y - this.refs.layer2.attrs.y; //toPush.y = Stage mouseY position
I used lavtron's zooming based on mouse position which scales and shifts the layer in order to achieve the effect.
My react code looks like:
<Stage ...>
<Layer onWheel={this.onWheel} x={this.state.layerX} y={this.state.layerY} >
... all the shapes...
</Layer>
</Stage>
onWheel = () => {
const scaleBy = 1.1;
const stage = this.refs.graphicStage;
const layer = this.refs.layer2;
const oldScale = layer.scaleX();
const mousePointTo = {
x: stage.getPointerPosition().x / oldScale - this.state.layerX / oldScale,
y: stage.getPointerPosition().y / oldScale - this.state.layerY / oldScale
};
const newScale =
event.evt.deltaY < 0 ? oldScale * scaleBy : oldScale / scaleBy;
layer.scale({ x: newScale, y: newScale });
this.setState({
layerScale: newScale,
layerX:
-(mousePointTo.x - stage.getPointerPosition().x / newScale) * newScale,
layerY:
-(mousePointTo.y - stage.getPointerPosition().y / newScale) * newScale
});
}
But after implementing zooming, when I drag and drop shapes, they don't land in where I eyeballed, and interestingly, as the location I dropped them gets further away from (x:0,y:0), the more they will shift towards (0,0).
Here's the most reasonable code I have tried to calculate the new position in order to make the objects land where they were supposed to drop.
toPush.x = toPush.x - this.state.layerX; //this.state.layerX = layer's X offset
toPush.y = toPush.y - this.state.layerY;
2.
toPush.x = toPush.x - (this.state.layerX) * layer's scale;
toPush.y = toPush.y - this.state.layerY * layer's scale;
You can use this demo to calculate relative position: https://konvajs.org/docs/sandbox/Relative_Pointer_Position.html
With react-konva it may look something like this:
import React from "react";
import { render } from "react-dom";
import { Stage, Layer, Circle } from "react-konva";
const App = () => {
const [localPos, setPos] = React.useState({ x: 0, y: 0 });
const layerRef = React.useRef();
return (
<React.Fragment>
Try to move the mouse over stage
<Stage
width={window.innerWidth}
height={window.innerHeight}
onMouseMove={e => {
var transform = layerRef.current.getAbsoluteTransform().copy();
// to detect relative position we need to invert transform
transform.invert();
// now we find relative point
const pos = e.target.getStage().getPointerPosition();
var circlePos = transform.point(pos);
setPos(circlePos);
}}
>
<Layer x={50} y={50} scaleX={0.5} scaleY={2} ref={layerRef}>
<Circle radius={50} fill="green" x={localPos.x} y={localPos.y} />
</Layer>
</Stage>
</React.Fragment>
);
};
render(<App />, document.getElementById("root"));
https://codesandbox.io/s/react-konva-relative-pos-demo-k6num

Calculate vertices for n sided regular polygon

I have tried to follow this answer
It works fine for creating the polygons, however I can see that it doesn't reach the edges of the containing rectangle.
The following gif shows what I mean. Especially for the 5 sided polygon it is clear that it doesn't "span" the rectangle which I would like it to do
This is the code I use for creating the vertices
func verticesForEdges(_edges: Int) -> [CGPoint] {
let offset = 1.0
var vertices: [CGPoint] = []
for i in 0..._edges {
let angle = M_PI + 2.0 * M_PI * Double(i) / Double(edges)
var x = (frame.width / 2.0) * CGFloat(sin(angle)) + (bounds.width / 2.0)
var y = (frame.height / 2.0) * CGFloat(cos(angle)) + (bounds.height / 2.0)
vertices.append(CGPoint(x: x, y: y))
}
return vertices
}
And this is the code that uses the the vertices
override func layoutSublayers() {
super.layoutSublayers()
var polygonPath = UIBezierPath()
let vertices = verticesForEdges(edges)
polygonPath.moveToPoint(vertices[0])
for v in vertices {
polygonPath.addLineToPoint(v)
}
polygonPath.closePath()
self.path = polygonPath.CGPath
}
So the question is. How do I make the the polygons fill out the rectangle
Update:
The rectangle is not necessarily a square. It can have a different height from its width. From the comments it seems that I am fitting the polygon in a circle, but what is intentioned is to fit it in a rectangle.
If the first (i=0) vertice is fixed at the middle of top rectangle edge, we can calculate minimal width and height of bounding rectangle:
The rightmost vertice index
ir = (N + 2) / 4 // N/4, rounded to the closest integer, not applicable to triangle
MinWidth = 2 * R * Sin(ir * 2 * Pi / N)
The bottom vertice index
ib = (N + 1) / 2 // N/2, rounded to the closest integer
MinHeight = R * (1 + Abs(Cos(ib * 2 * Pi / N)))
So for given rectangle dimensions we can calculate R parameter to inscribe polygon properly

Highcharts - spider web chart questions

I'm working with Highcharts spider web chart at the moment and wanted to see if I can do the following
How do I zoom polar charts? (or is it possible?)
How do I put background-color in each of the segment?
How do I zoom polar charts? (or is it possible?)
If you want a zoom like zoomType then no. zoomType is disabled for polar charts by highcharts-more.js. From source:
// Disable certain features on angular and polar axes
chart.inverted = false;
chartOptions.chart.zoomType = null;
How do I put background-color in each of the segment?
You can use math and Chart.renderer to create and fill paths to color the background of the segments. For example you might do it like this:
var colors = [ "pink", "yellow", "blue", "red", "green", "cyan", "teal", "indigo" ];
var parts = 6;
for(var i = 0; i < parts; i++) {
centerX = chart.plotLeft + chart.yAxis[0].center[0];
centerY = chart.plotTop + chart.yAxis[0].center[1];
axisLength = chart.yAxis[0].height;
angleOffset = -Math.PI/2;
angleSegment = Math.PI/(parts/2);
firstPointX = centerX + (axisLength * Math.cos(angleOffset + (angleSegment * i)));
firstPointY = centerY + (axisLength * Math.sin(angleOffset + (angleSegment * i)));
secondPointX = centerX + (axisLength * Math.cos(angleOffset + (angleSegment * (i+1))));
secondPointY = centerY + (axisLength * Math.sin(angleOffset + (angleSegment * (i+1))));
chart.renderer.path([
'M', centerX, centerY,
'L', firstPointX, firstPointY,
'L', secondPointX, secondPointY,
'Z'
]).attr({
fill: colors[i % colors.length],
'stroke-width': 1,
'opacity': 1
}).add();
}
As seen in this JSFiddle demonstration. You just have to match number of categories with the parts variable.

Cocos2d v3: How do you draw an arc?

I want to draw a filled arc like this:
CCDrawNode only contain methods to draw a circle, a polygon or a line – but no arc.
To be clear, I'm wanting to be able to generate the arc at runtime, with an arbitrary radius and arc angle.
My Question:
Is there a way to have Cocos2d draw an arc, or would I have to do it myself with OpenGL?
I guess you can use the CCProgressNode, and by setting the sprite to an orange circle, you can draw an arc by setting the progress property.
Now, whether you can set the sprite as a vectorized circle that you can just scale, I am not really sure.
Personally, I'd suggest you add the drawing arc code to CCDrawNode, and submit a PR.
tested using cocos2dx v3.8, codes from DrawNode::drawSolidCircle
DrawNode* Pie::drawPie(const Vec2& center, float radius, float startAngle, float endAngle, unsigned int segments, float scaleX, float scaleY, const Color4F &color){
segments++;
auto draw = DrawNode::create();
const float coef = (endAngle - startAngle) / segments;
Vec2 *vertices = new (std::nothrow) Vec2[segments];
if (!vertices)
return nullptr;
for (unsigned int i = 0; i < segments - 1; i++)
{
float rads = i*coef;
GLfloat j = radius * cosf(rads + startAngle) * scaleX + center.x;
GLfloat k = radius * sinf(rads + startAngle) * scaleY + center.y;
vertices[i].x = j;
vertices[i].y = k;
}
vertices[segments - 1].x = center.x;
vertices[segments - 1].y = center.y;
draw->drawSolidPoly(vertices, segments, color);
CC_SAFE_DELETE_ARRAY(vertices);
return draw;
}
call it like
auto pie = Pie::drawPie(_visibleSize / 2, _visibleSize.width / 4, 0, 1, 999, 1, 1, Color4F::BLUE);

Resources