I'm trying to add text (TextPath) inside an Arc shape, that is following the curve of en Arc. Making a curved TextPath is easy, however I'm failing to put it on top on Arc shape.
If I set x and y of both objects to for.ex. 100 and 100, they end up in illogical locations, so I'm obviously failing to understand something here. What I'm trying to achieve is shown in attached screenshot - could someone make demo of putting a TextPath on top of en Arc ? Thanks.
what I'm trying to achieve
I slightly modified your demo to remove hardcoded values as much as possible.
You just need to correctly generate an SVG path. You can use some answers from here: How to calculate the SVG Path for an arc (of a circle)
Also, you can use Konva.Path shape to debug the generated path.
function polarToCartesian(centerX, centerY, radius, angleInDegrees) {
var angleInRadians = (angleInDegrees-90) * Math.PI / 180.0;
return {
x: centerX + (radius * Math.cos(angleInRadians)),
y: centerY + (radius * Math.sin(angleInRadians))
};
}
function describeArc(x, y, radius, startAngle, endAngle){
var endAngleOriginal = endAngle;
if(endAngleOriginal - startAngle === 360){
endAngle = 359;
}
var start = polarToCartesian(x, y, radius, endAngle);
var end = polarToCartesian(x, y, radius, startAngle);
var arcSweep = endAngle - startAngle <= 180 ? "0" : "1";
if(endAngleOriginal - startAngle === 360){
var d = [
"M", start.x, start.y,
"A", radius, radius, 0, arcSweep, 0, end.x, end.y, "z"
].join(" ");
}
else{
var d = [
"M", start.x, start.y,
"A", radius, radius, 0, arcSweep, 0, end.x, end.y
].join(" ");
}
return d;
}
var width = window.innerWidth;
var height = window.innerHeight;
var stage = new Konva.Stage({
container: 'container',
width: width,
height: height
});
var layer = new Konva.Layer();
stage.add(layer);
var arc = new Konva.Arc({
x: 250,
y: 250,
innerRadius: 100,
outerRadius: 170,
angle: 260,
rotation: 0,
draggable: true,
fill: 'yellow',
stroke: 'black',
strokeWidth: 4,
offset: 0,
});
layer.add(arc);
var tr1 = new Konva.Transformer({
node: arc,
resizeEnabled: false
});
layer.add(tr1);
var txt = new Konva.TextPath({
x: arc.x(),
y: arc.y(),
draggable: true,
fill: '#333',
fontSize: 22,
fontFamily: 'Arial',
text: "Hello world !",
align: 'center',
data: describeArc(0, 0, (arc.innerRadius() + arc.outerRadius()) / 2, 90, 90 + arc.getAttr("angle")),
});
layer.add(txt);
var tr2 = new Konva.Transformer({
node: txt,
resizeEnabled: false,
});
layer.add(tr2);
const path = new Konva.Path({
x: txt.x(),
y: txt.y(),
data: txt.data(),
stroke: 'red'
});
layer.add(path);
layer.draw();
Demo: https://jsbin.com/muwobaxipe/3/edit?js,output
Related
I'm struggling to apply corner radius (on all sides) in a group shape using a custom clip function.
Here's my codesandbox:
https://codesandbox.io/s/w28nrrj885
I copied your clipSquare function into the plain JS snippet below to test your assumptions. It's all good.
Looking further into your code the issue is that you are using the same x,y,w,h values in the definition of the group clip function as you use for the definition of the canvas. This has the effect of making the group overflow off the right and bottom edges of the canvas and so hides the rounded corners.
If you alter your code from
this.clipSquare(ctx, x, y, width, height, 30);`
to
this.clipSquare(ctx, 0, 0, width-x, height-y, 30);
then you will see all 4 corners.
I will leave this snippet in place for future readers as it illustrates the clipfunc in plain JS.
// The following variables define the position on the rect and clipping region
var oX = 20, oY = 10, oW = 300, oH = 200, radius = 30;
var stage = new Konva.Stage({container: 'container', width: 500, height: 300})
var layer = new Konva.Layer();
stage.add(layer)
var rect1 = new Konva.Rect({ x: oX, y: oY, width: oW, height: oH, fill: 'cyan'})
var rect2 = new Konva.Rect({ x: oX, y: oY, width: oW, height: oH, fill: 'magenta'})
var clipSquare = function(ctx, x, y, width, height, radius) {
ctx.beginPath();
ctx.moveTo(x + radius, y);
ctx.lineTo(x + width - radius, y);
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
ctx.lineTo(x + width, y + height - radius);
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
ctx.lineTo(x + radius, y + height);
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
ctx.lineTo(x, y + radius);
ctx.quadraticCurveTo(x, y, x + radius, y);
ctx.closePath();
};
var group = new Konva.Group({
clipFunc: function(ctx) {
clipSquare(ctx, oX, oY, oW, oH, radius)
},
draggable: true
});
layer.add(rect1)
group.add(rect2)
layer.add(group)
stage.draw();
#container {
width: 500px;
height: 300px;
background-color: #ccc;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/konva/3.2.5/konva.min.js"></script>
<div id='container'>
</div>
I want to drag the entire group from the center red square, (see fiddle)
but restrict dragging from the outer rectangle, while maintaining the inner rectangle's position inside the group.
Possibly when mouse is down I want the crosshair cursor to be dead in the center of the inner red rectangle.
Any help would be appreciated, thanks.
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight
});
let layer = new Konva.Layer();
let group = new Konva.Group({
draggable: true,
height:50,
width: 50
});
const containerRect = new Konva.Rect({
width:50,
height: 50,
fill: 'yellow'
});
const rect = new Konva.Rect({
x: 15,
y: 15,
width: 20,
height: 20,
fill: 'red'
});
stage.add(layer);
group.add(containerRect);
group.add(rect);
layer.add(group);
layer.draw();
I suggest making the red rectangle draggable. Once you start a drag, you can stop it and start dragging of the group:
rect.on('dragstart', () => {
// stop rect dragging
rect.stopDrag();
// move group to the center
const size = group.getClientRect();
const pos = stage.getPointerPosition();
group.setAttrs({
x: pos.x - size.width / 2,
y: pos.y - size.height / 2,
});
group.startDrag();
});
https://jsfiddle.net/0cdw3ya7/21/
I have an app where I am panning and zooming multiple layers.
However, when I set the position of the layers they are being updated to the wrong values.
For instance, after calling:
layer.position({
x: 12,
y: 233,
});
I would expect
layer.getClientRect();
to return
{
x: 12,
y: 233,
}
Instead it returns
{
x: -22,
y: 220,
}
Is there any known reason this is happening?
I realised I wasn't taking into account all the offsets. The x position of a circle is set from the centre while getClientRect() returns the width and height including negatives. So getClientRect() on a circle with a radius of 50 and x of 0 will actually return {x: -25, y: -25, width: 50, height: 50}.
Seems to work for me as advertised in this cut down test. Did you move the stage or something ?
In the snippet below I draw a layer with 4 'corner' rects, display its value from grtClientRect() in 'Layer pos #1' which gives (0, 0), then move the stage to 12, 233 and display its value from grtClientRect() in 'Layer pos #2' which shows (12, 233). I move the layer back up to (12, 23) just for fun thereafter.
var stage = new Konva.Stage({
container: 'container',
width: 1600,
height: 400
});
// add a layer
var layer = new Konva.Layer();
stage.add(layer);
// add 4 corner rects - code I happened to have to hand
var rectCorner = [];
for (i=0;i<4;i=i+1){
rectCorner[i] = new Konva.Rect({
x: 0,
y: 0,
width: 50,
height: 50,
fill: 'red',
opacity: 0.5,
strokeWidth: 0})
layer.add(rectCorner[i]);
}
// put the rects in the corners to give a known layer space
rectCorner[1].position({x:500, y: 0});
rectCorner[2].position({x:500, y: 150});
rectCorner[3].position({x:0, y: 150});
layer.draw();
// get the client rect now
var p = layer.getClientRect();
$('#info1').html('Layer pos #1=' + p.x + ', ' + p.y);
// move the layer...
layer.position({
x: 12,
y: 233,
});
// get the client rect now
var p = layer.getClientRect();
$('#info2').html('Layer pos #2=' + p.x + ', ' + p.y);
// move the layer back to the top...
layer.position({
x: 12,
y: 23,
});
stage.draw();
p
{
padding: 4px;
}
#container
{
background-color: silver;
overflow: hidden;
}
<div id='info1'></div>
<div id='info2'></div>
<div id="container"></div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="https://cdn.rawgit.com/konvajs/konva/1.7.6/konva.min.js"></script>
<script type="text/javascript" src="test.js"></script>
I've been using the Konvajs clipping function to limit what can be seen inside a bounding box.
Has anyone managed to write a similar function but when the konva object leaves the bounding box it changes in opacity instead of simply not being seen?
Thanks for the input.
Here's a working version. The yellow region is the canvas extent. The clear image in the center is the clipping region. The semi-transparent region is the full extent of the image. Click & drag the clipped region and you can see the 'clipped off' component of the image displayed.
// this is the position for the clipping rectangle
var clipRect = {left: 30, top: 30, width : 420, height : 340, right: 450, bottom: 390};
// generic
// add a stage & add a layer
var s = new Konva.Stage({container: 'container', width: 800, height: 600});
var l = new Konva.Layer({draggable: true});
// background layer
var bgr = new Konva.Rect({x:0, y: 0, width: 500, height: 500, fill: 'gold', opacity: 0.1, listening: false})
l.add(bgr)
s.add(l);
// end of generic
// Add an image to show the full extent of the clipped image
var boundsRect = new Konva.Image({opacity: 0.2, stroke: 'black', strokeWidth: 1, draggable: false, dash: [2, 2], listening: false});
l.add(boundsRect);
// to clip we have to add a group with a clip.
var vp = new Konva.Group({
clip: { x: clipRect.left, y: clipRect.top, width : clipRect.width, height : clipRect.height}
});
// add a border to the clip region via a rect just surrounding it.
var r1 = new Konva.Rect({x: clipRect.left - 1, y: clipRect.top - 1, width : clipRect.width + 2, height : clipRect.height + 2, stroke: '#00008B', strokeWidth: 1, draggable: false});
l.add(r1);
/* This is the image that is the subject of the clipping effort */
var i=new Konva.Image({x: 0, y: 0, width: 0, height: 0, draggable: true,
// we want to ensure that the image cannot be dragged
// beyond the glip rect bounds.
dragBoundFunc: function(pos) {
var posRect = getPosRect(pos,this);
var iPos = this.getClientRect();
var newX = pos.x;
var newY = pos.y;
newX = (posRect.left >= clipRect.left) ? clipRect.left : posRect.left;
newX = (posRect.right <= clipRect.right) ? clipRect.right - posRect.width : newX;
newY = (posRect.top >= clipRect.top) ? clipRect.top : posRect.top;
newY = (posRect.bottom <= clipRect.bottom) ? clipRect.bottom - posRect.height : newY;
return {
x: newX,
y: newY
}}
});
i.on('dragmove', function() {
setBoundRect(this);
});
vp.add(i);
var imageObj = new Image();
imageObj.onload=function () {
i.image(imageObj);
boundsRect.image(imageObj);
i.width(imageObj.width);
i.height(imageObj.height);
setBoundRect(i);
s.draw();
}
imageObj.src = "http://btckstorage.blob.core.windows.net/site13706/gallery/2015/Porthleven%20lifeboat%20day%202015%20f%20-%20Chris%20Yacoubian%20Ltd.jpg";
l.add(vp) // add image to clipping viewport
s.draw()
// used to get the client rect of a shape.
function getPosRect(pos, ele){
var cliPos = ele.getClientRect();
var posRect = {left: pos.x, top: pos.y, right: pos.x + cliPos.width, bottom: pos.y + cliPos.height, width: cliPos.width, height: cliPos.height};
return posRect;
}
// set the bounds rect to the size of the given element
function setBoundRect(ele){
var x = ele.position();
var posRect = getPosRect(ele.position(), ele);
boundsRect.position({x: posRect.left, y: posRect.top});
boundsRect.size({width: posRect.width, height: posRect.height});
}
// change the image
$('img').on("click", function(e){
imageObj.src = $(this).prop("src");
})
.pic
{
max-width:150px;
max-height: 80px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdn.rawgit.com/konvajs/konva/1.6.5/konva.min.js"></script>
<div id='container' style="display: inline-block; width: 750px; height: 400px; background-color: transparent; overflow: hidden;"></div>
<div style="display: inline-block; width: 500px; height: 400px; " >
<p>
<img class='pic' src="http://btckstorage.blob.core.windows.net/site13706/gallery/2015/53%20-%20Chris%20Yacoubian%20Ltd.jpg"/>
</p>
<p>
<img class='pic'src="http://btckstorage.blob.core.windows.net/site13706/gallery/2015/Crew%20day%20Mousehole%202015%20e%20-%20Chris%20Yacoubian%20Ltd.jpg" />
</p>
<p>
<img class='pic' src="http://btckstorage.blob.core.windows.net/site13706/gallery/2015/34%20-%20Chris%20Yacoubian%20Ltd.jpg"/>
</p>
I need some help for the handles in the navigator of highstock.
My set options do not work in my example:
http://jsfiddle.net/q1xpn6hL/
Please take a look at line 5 to 9:
borderColor: '#666',
width: 10,
height: 35,
borderRadius: 2,
borderWidth: 0.5
thanks a lot!
Those options are not supported. You would have to extend Highcharts to enable them. It is possible to override function that handles creating of handles.
Example: http://jsfiddle.net/v4vhy01j/
(function (H) {
H.wrap(H.Scroller.prototype, 'drawHandle', function (proceed, x, index) {
var scroller = this,
chart = scroller.chart,
renderer = chart.renderer,
elementsToDestroy = scroller.elementsToDestroy,
handles = scroller.handles,
handlesOptions = scroller.navigatorOptions.handles,
borderWidth = H.pick(handlesOptions.borderWidth, 1),
borderRadius = H.pick(handlesOptions.borderRadius, 0),
width = H.pick(handlesOptions.width, 9),
height = H.pick(handlesOptions.height, 16),
attr = {
fill: handlesOptions.backgroundColor,
stroke: handlesOptions.borderColor,
'stroke-width': borderWidth
},
tempElem;
// create the elements
if (!scroller.rendered) {
// the group
handles[index] = renderer.g('navigator-handle-' + ['left', 'right'][index])
.css({
cursor: 'ew-resize'
})
.attr({
zIndex: 4 - index
}) // zIndex = 3 for right handle, 4 for left
.add();
// the rectangle
tempElem = renderer.rect(-5, 9 - height/2, width, height, borderRadius)
.attr(attr)
.add(handles[index]);
elementsToDestroy.push(tempElem);
// the rifles
tempElem = renderer.path([
'M', -1.5, 13 - height/2,
'L', -1.5, 5 + height/2,
'M',
0.5, 13 - height/2,
'L',
0.5, 5 + height/2]).attr(attr)
.add(handles[index]);
elementsToDestroy.push(tempElem);
}
// Place it
handles[index][chart.isResizing ? 'animate' : 'attr']({
translateX: scroller.scrollerLeft + scroller.scrollbarHeight + parseInt(x, 10),
translateY: scroller.top + scroller.height / 2 - 8
});
});
}(Highcharts));
The Highstock navigator does not support all those options. Here's what the documentation says:
handles: Object
Options for the handles for dragging the zoomed area. Available options are backgroundColor (defaults to #ebe7e8) and borderColor (defaults to #b2b1b6).