KonvaJs: Subgroup not rendering in correct position in relation to parent group - konvajs

I am trying to get an information card (consisting of a Konva.Rect and Konva.Text) to appear below an image while hovering over it. I've tried combining the info card into a separate child group and added it to a parent group that consists of the image and the image label. The issue is that when hovering over the image the rectangle appears far off to the right rather than in the correct x,y position. How can I set the child group's coordinates such that if the image moves across the screen the info card will always appear below it?
I've tried setting the rectangle's x attribute as group.x() (where group is the parent group's x coordinate). I've also tried setting it as image.x() yet the rectangle still appears far off to the right of the parent image.
let group = new Konva.Group({
x: 100,
y: 20,
id: attr.status,
visible: ng.checkboxValues[status] ? true : false,
});
let image = new Konva.Image({
x: group.x(),
y: group.y(),
image: DOMImage,
width: 40,
height: 40,
listening: true,
visible: true,
});
let robotAlias = new Konva.Text({
x: image.x() - 20,
y: image.y() + 10,
text: attr.alias,
fontSize: 13,
fontFamily: 'Roboto',
fill: ng.colorMap[attr.status],
visible: true,
});
let complexText = new Konva.Text({
x: image.x(),
y: image.y(),
text: `${attr.alias}-${attr.status}`,
fontSize: 15,
padding: 10,
fontFamily: 'Roboto',
align: 'center',
fill: this.colorMap[attr.status],
height: 60,
width: 160,
visible: true,
});
let rect = new Konva.Rect({
x: image.x(),
y: image.y(),
stroke: '#fff',
strokeWidth: 1,
fill: '#fff',
height: 60,
width: 160,
shadowColor: 'black',
shadowBlur: 5,
shadowOffset: [10, 10],
shadowOpacity: 0.2,
visible: true,
});
let descriptionCardGroup = new Konva.Group({
x: image.x(),
y: image.y(),
visible: false,
name: `${attr.id}`,
})
group.add(image);
group.add(robotAlias);
descriptionCardGroup.add(rect);
descriptionCardGroup.add(complexText);
group.add(descriptionCardGroup);
Here's a screenshot of the current behavior: https://ibb.co/QMYjxrw

Related

Konvajs: Create a draggable area with some constraints on one of the childs

I'm creating a timeline with Konva and the entire timeline (stage) is draggable on all directions but I have an axis with all the years of the timeline (Konva group) that I want to restrict its movement so that it only moves horizontally.
I can't use dragBoundFunc as it will restrict the movement on all nodes of the timeline.
I tried to change the position of the element using the dragmove event:
stage.on("dragmove", function(evt) {
xaxis.y(0);
});
But the xaxis still moves on all direction while dragging the stage.
I could also use different draggable layers for the axis and the timeline itself, but then when I drag the axis it wouldn't move the timeline and the same if I move the timeline.
As the simplest solution, you can just make sure that the absolute position of your timelime group is the same:
stage.on("dragmove", function(evt) {
// read absolute position
const oldAbs = xaxis.absolutePosition();
// set new absolute position, but make sure x = 0
xaxis.absolutePosition({
x: oldAbs.x,
y: 0
});
});
Here is a slightly more capable version that allows vertical drag of the event layer whilst keeping the time line axis visible for reference. This uses two layers - one to act as the background containing the time line and grid, whilst the second shows the events.
The key technique here is using the dragMove event listener on the draggable event layer to move the background layer in sync horizontally but NOT vertically. Meanwhile the event layer is also constrained with a dragBound function to stop silly UX.
An improvement would be to add clipping to the event layer so that when dragged down it would not obscure the timeline.
var stageWidth = 800,
stageHeight = 300,
timeFrom = 1960,
timeTo = 2060,
timeRange = timeTo - timeFrom,
timeLineWidth = 1000,
timeSteps = 20, // over 100 yrs = 5 year intervals
timeInt = timeRange / timeSteps,
timeLineStep = timeLineWidth / timeSteps,
yearWidth = timeLineWidth / timeRange,
plotHeight = 500,
events = [{
date: 1964,
desc: 'Born',
dist: 10
},
{
date: 1966,
desc: 'England win world cup - still celebrating !',
dist: 20
},
{
date: 1968,
desc: 'Infant school',
dist: 30
},
{
date: 1975,
desc: 'Secondary school',
dist: 50
},
{
date: 1981,
desc: 'Sixth form',
dist: 7
},
{
date: 1983,
desc: 'University',
dist: 30
},
{
date: 1986,
desc: 'Degree, entered IT career',
dist: 50
},
{
date: 1990,
desc: 'Marriage #1',
dist: 0
},
{
date: 1996,
desc: 'Divorce #1',
dist: 0
},
{
date: 1998,
desc: 'Marriage #2 & Son born',
dist: 90
},
{
date: 2000,
desc: 'World did not end',
dist: 20
},
{
date: 2025,
desc: 'Retired ?',
dist: 0
},
{
date: 2044,
desc: 'Enters Duncodin - retirement home for IT workers',
dist: 0
},
{
date: 2054,
desc: 'Star dust',
dist: 0
}
]
function setup() {
// Set up a stage and a shape
stage = new Konva.Stage({
container: 'konva-stage',
width: stageWidth,
height: stageHeight
});
// bgLayer is the background with the grid, timeline and date text.
var bgLayer = new Konva.Layer({
draggable: false
})
stage.add(bgLayer);
for (var i = 0, max = timeSteps; i < max; i = i + 1) {
bgLayer.add(new Konva.Line({
points: [(i * timeLineStep) + 0.5, 0, (i * timeLineStep) + .5, plotHeight],
stroke: 'cyan',
strokeWidth: 1
}))
bgLayer.add(new Konva.Text({
x: (i * timeLineStep) + 4,
y: 260,
text: timeFrom + (timeInt * i),
fontSize: 12,
fontFamily: 'Calibri',
fill: 'magenta',
rotation: 90,
listening: false
}));
}
for (var i = 0, max = plotHeight; i < max; i = i + timeLineStep) {
bgLayer.add(new Konva.Line({
points: [0, i + 0.5, timeLineWidth, i + .5],
stroke: 'cyan',
strokeWidth: 1
}))
}
// add timeline
var timeLine = new Konva.Rect({
x: 0,
y: 245,
height: 1,
width: timeLineWidth,
fill: 'magenta',
listening: false
});
bgLayer.add(timeLine)
// eventLayer contains only the event link line and text.
var eventLayer = new Konva.Layer({
draggable: true,
// the dragBoundFunc returns an object as {x: val, y: val} in which the x is constricted to stop
// the user dragging out of sight, and the y is not allowed to change.
// ! position of bgLayer is moved in x axis in sync with eventLayer via dragMove event
dragBoundFunc: function(pos) {
return {
x: function() {
var retX = pos.x;
if (retX > 20) { // if the left exceeds 20px from left edge of stage
retX = 20;
} else if (retX < (stageWidth - (timeLineWidth + 50))) { // if the right exceeds 50 px from right edge of stage
retX = stageWidth - (timeLineWidth + 50);
}
return retX;
}(),
y: function() {
var retY = pos.y;
if (retY < 0) {
retY = 0;
} else if (retY > 200) {
retY = 200;
}
return retY;
}()
};
}
});
stage.add(eventLayer);
// ! position of bgLayer is moved in x axis in sync with eventLayer via dragMove event of eventLayer.
eventLayer.on('dragmove', function() {
var pos = eventLayer.position();
var bgPos = bgLayer.position();
bgLayer.position({
x: pos.x,
y: bgPos.y
}); // <--- move the bgLayer in sync with the event eventLayer.
stage.draw()
});
for (var i = 0, max = events.length; i < max; i = i + 1) {
var event = events[i];
var link = new Konva.Rect({
x: yearWidth * (event.date - timeFrom),
y: 200 - event.dist,
width: 1,
height: 55 + event.dist,
fill: 'magenta',
listening: false
});
eventLayer.add(link)
var eventLabel = new Konva.Text({
x: yearWidth * (event.date - timeFrom) - 5,
y: 190 - event.dist,
text: event.date + ' - ' + event.desc,
fontSize: 16,
fontFamily: 'Calibri',
fill: 'magenta',
rotation: -90,
listening: false
});
eventLayer.add(eventLabel);
var dragRect = new Konva.Rect({
x: 0,
y: 0,
width: timeLineWidth,
height: 500,
opacity: 0,
fill: 'cyan',
listening: true
});
eventLayer.add(dragRect);
dragRect.moveToTop()
}
stage.draw()
}
var stage, eventLayer;
setup()
.konva-stage {
width: 100%;
height: 100%;
<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/4.0.13/konva.js"></script>
<p>Drag the timeline left & right AND up & down...</p>
<div id="konva-stage"></div>
Just for fun, a stripped down version of my answers showing the ondrag() function without all the timeline frills.
var stage;
function setup() {
// Set up a stage and a shape
stage = new Konva.Stage({
container: 'konva-stage',
width: 600,
height: 300
});
// layer1.
var layer1 = new Konva.Layer({
draggable: false
})
stage.add(layer1);
var ln1 = new Konva.Line({
points: [10, 0, 10, 20, 10, 10, 0, 10, 20, 10],
stroke: 'cyan',
strokeWidth: 4
});
layer1.add(ln1);
var layer2 = new Konva.Layer({
draggable: true,
});
stage.add(layer2);
var ln2 = new Konva.Line({
points: [10, 0, 10, 20, 10, 10, 0, 10, 20, 10],
stroke: 'magenta',
strokeWidth: 4
});
layer2.add(ln2);
// position the crosses on the canvas
ln1.position({
x: 100,
y: 80
});
ln2.position({
x: 100,
y: 40
});
// ! position of layer1 is moved in x axis in sync with layer2 via dragMove event of layer2.
layer2.on('dragmove', function() {
var pos = layer2.position();
var bgPos = layer1.position();
layer1.position({
x: pos.x,
y: bgPos.y
}); // <--- move layer1 in sync with layer2.
stage.draw()
});
stage.draw()
}
setup()
.konva-stage {
width: 100%;
height: 100%;
<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/4.0.13/konva.js"></script>
<p>Drag the upper cross - only one moves vertically whilst the other is contrained in the y-axis. Both move in sync on the x-axis</p>
<div id="konva-stage"></div>
It is not entirely clear what you are asking but I have assumed you want to constrain the drag of the timeline so that it gives good UX. See working snippet below. The majority of the code is setup of the timeline. The important piece is
Include a rect covering the entire timeline that has zero opacity and is listening for mouse events.
Provide the layer with a dragBoundFunc that returns an object as {x: val, y: val} in which the x is constricted to stop the user dragging out of sight horizontally, and the y is not allowed to change. If you think of the rect and the stage as rectangles then the math is not difficult to comprehend. If your timeline is vertical, swap the x & y behaviour.
var stageWidth = 800,
timeFrom = 1960,
timeTo = 2060,
range = timeTo - timeFrom,
timeLineWidth = 1000;
yearWidth = timeLineWidth / range,
events = [{
date: 1964,
desc: 'Born'
},
{
date: 1968,
desc: 'Infant school'
},
{
date: 1975,
desc: 'Secondary school'
},
{
date: 1981,
desc: 'Sixth form'
},
{
date: 1983,
desc: 'University'
},
{
date: 1986,
desc: 'Degree, entered IT career'
},
{
date: 1990,
desc: 'Marriage #1'
},
{
date: 1998,
desc: 'Marriage #2'
},
{
date: 1999,
desc: 'Son born'
},
{
date: 2025,
desc: 'Retired ?'
},
{
date: 2044,
desc: 'Enters Duncodin - retirement home for IT workers'
},
{
date: 2054,
desc: 'Star dust'
}
]
function setup() {
// Set up a stage and a shape
stage = new Konva.Stage({
container: 'konva-stage',
width: stageWidth,
height: 500
});
layer = new Konva.Layer({
draggable: true,
// the dragBoundFunc returns an object as {x: val, y: val} in which the x is constricted to stop
// the user dragging out of sight, and the y is not allowed to change.
dragBoundFunc: function(pos) {
return {
x: function() {
retX = pos.x;
if (retX > 20) {
retX = 20;
} else if (retX < (stageWidth - (timeLineWidth + 50))) {
retX = stageWidth - (timeLineWidth + 50);
}
return retX;
}(),
y: this.absolutePosition().y
};
}
});
stage.add(layer);
// add timeline
var timeLine = new Konva.Rect({
x: 0,
y: 245,
height: 10,
width: timeLineWidth,
fill: 'magenta',
listening: false
});
layer.add(timeLine)
for (var i = 0, max = events.length; i < max; i = i + 1) {
var event = events[i];
var link = new Konva.Rect({
x: yearWidth * (event.date - timeFrom),
y: 200,
width: 5,
height: 55,
fill: 'magenta',
listening: false
});
layer.add(link)
var timeLabel = new Konva.Text({
x: yearWidth * (event.date - timeFrom) + 10,
y: 265,
text: event.date,
fontSize: 16,
fontFamily: 'Calibri',
fill: 'magenta',
rotation: 90,
listening: false
});
layer.add(timeLabel);
var eventLabel = new Konva.Text({
x: yearWidth * (event.date - timeFrom) - 5,
y: 190,
text: event.desc,
fontSize: 16,
fontFamily: 'Calibri',
fill: 'magenta',
rotation: -90,
listening: false
});
layer.add(eventLabel);
var dragRect = new Konva.Rect({
x: 0,
y: 0,
width: timeLineWidth,
height: 500,
opacity: 0,
fill: 'cyan',
listening: true
});
layer.add(dragRect);
dragRect.moveToTop()
}
stage.draw()
}
var stage, layer;
setup()
.konva-stage {
width: 100%;
height: 100%;
<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/4.0.13/konva.js"></script>
<p>Drag the timeline...</p>
<div id="konva-stage"></div>

Put element in foreground

I would like to know how to put the box in foreground and the image in background. I try moveToTop(), setZIndex() on box but when i drag it the image go to foreground.
one(){
var stage = new Konva.Stage({
container: 'container',
width: 400, height: 250
});
var layer = new Konva.Layer();
var imageObj = new Image();
imageObj.onload = function() {
var yoda = new Konva.Image({
image: imageObj,
});
// yoda.setZIndex(2);
layer.add(yoda);
};
imageObj.src = '/assets/images/vert.png';
this.two(stage, layer)
}
two(stage: Konva.Stage, layer) {
var box = new Konva.Rect({
x: 20, y: 20,
width: 100, height: 50,
fill: '#00D2FF', stroke: 'black',
strokeWidth: 4,
draggable: true
});
// box.setZIndex(1); box.moveToTop()
layer.add(box);
stage.add(layer)
}
I took your code and made a plain JS working snippet so that I could experiment. See below.
You were very close with your line
// yoda.setZIndex(2);
layer.add(yoda);
except that the z-index affecting attributes work on the position of the shape in the layer. Therefore the shape MUST BE IN A LAYER for the call to work. See my code where I use
layer.add(yoda);
yoda.moveToBottom()
meaning the layer-affecting call happens AFTER adding yoda to the layer.
You may also wish to consider using multiple layers if, say, you will have many shapes that need to be on one or another layer.
var stage = new Konva.Stage({
container: 'container',
width: 400, height: 250
});
var layer = new Konva.Layer();
var imageObj = new Image();
imageObj.onload = function() {
var yoda = new Konva.Image({
width: 100, height: 100,
image: imageObj,
});
layer.add(yoda);
yoda.moveToBottom(); // <<<< must add to the layer BEFORE setting the z-order.
layer.draw();
};
imageObj.src = 'https://i.stack.imgur.com/WxBvk.png';
two(stage, layer)
function two(stage, layer) {
var box = new Konva.Rect({
x: 20, y: 20,
width: 100, height: 50,
fill: '#00D2FF', stroke: 'black',
strokeWidth: 4,
draggable: true
});
layer.add(box);
}
stage.add(layer)
stage.draw();
<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: 300px, height: 200px; background-color: silver; overflow: hidden; position: relative;"></div>

Highchart remove decimal places

I am using a highchart pie chart and displaying the data in the centre via.
function(chart1) { // on complete
var xpos = '50%';
var ypos = '50%';
var circleradius = 40;
// Render the circle
chart1.renderer.circle(xpos, ypos, circleradius).attr({
fill: '#fff',
}).add();
// Render the text
chart1.renderer.text(chart1.series[0].data[0].percentage + '%', 62, 80).css({
width: circleradius * 2,
color: '#4572A7',
fontSize: '16px',
textAlign: 'center'
}).attr({
// why doesn't zIndex get the text in front of the chart?
zIndex: 999
}).add();
});
However I would like to prevent displaying any decimal places, currently 33.33333%. I would like to display this as 33%
Is this possible, I know there is a setting allowDecimals but this did not work.
You get original percentage value from point, which it is not rounded. Use JS Math.round function:
chart1.renderer.text(Math.round(chart1.series[0].data[0].percentage) + '%', 62, 80)
.css({
width: circleradius * 2,
color: '#4572A7',
fontSize: '16px',
textAlign: 'center'
})
.attr({
zIndex: 999
})
.add();
Live demo: https://jsfiddle.net/BlackLabel/0z4hLbaw/

Make chart.renderer.path as plotline Highcharts

I just check a post regarding making a label with design using renderer.label and plotline animate (Add design to plotLabel Highcharts). My question is, Is there a way to use chart.renderer.path as the moving horizontal gridline instead of using the common plotline ? I am a bit confuse on how to use the renderer.path. Can you help me with it? Really appreciate your help with this.
const plotband = yAxis.addPlotLine({
value: 66,
color: 'red',
width: 2,
id: 'plot-line-1',
/* label: {
text: 66,
align: 'right',
y: newY,
x: 0
}*/
});
const labelOffset = 15
const plotbandLabel = this.renderer.label((66).toFixed(2), chart.plotLeft + chart.plotWidth - 8, yAxis.toPixels(66) - labelOffset, 'rect').css({
color: '#FFFFFF'
}).attr({
align: 'right',
zIndex: 99,
fill: 'rgba(0, 0, 0, 0.75)',
padding: 8
})
.add()
setInterval(function() {
var x = (new Date()).getTime(), // current time
y = Math.round(Math.random() * 100);
series.addPoint([x, y], true, true);
plotLine = yAxis.plotLinesAndBands[0].svgElem;
d = plotLine.d.split(' ');
newY = yAxis.toPixels(y) - d[2];
plotlabel = yAxis.plotLinesAndBands[0].label;
plotbandLabel.animate({
y: yAxis.toPixels(y) - labelOffset
}, {
duration: 400,
step: function() {
this.attr({
text: yAxis.toValue(this.y + labelOffset).toFixed(2)
})
},
complete: function() {
this.attr({
text: y.toFixed(2)
})
}
}),
plotLine.animate({
translateY: newY
}, 400);
Please see this jsfiddle I got from the previous post. http://jsfiddle.net/x8vhp0gr/
Thank you so much
I modified demo provided by you. Now, instead of adding a plot line, path is created.
pathLine = this.renderer.path([
'M',
chart.plotLeft,
initialValue,
'L',
chart.plotWidth + chart.plotLeft,
initialValue
])
.attr({
'stroke-width': 2,
stroke: 'red'
})
.add(svgGroup);
API Reference:
http://api.highcharts.com/highcharts/Renderer.path
Example:
http://jsfiddle.net/a64e5qkq/

How to offset the title control in Titanium

How do I offset the title control in titanium?
Right now I am trying to put a left: -100 property on the label that is in the title control and that works for when you open up the view but then it moves back into the center.
Are there any suggestions on how I can achieve this move over?
Thanks in advance!
var txtSearch = Titanium.UI.createTextField({
hintText: 'Keyword to search for',
height: 'auto',
width: 220,
left: -100,
font: {fontSize: 12},
enabled: true,
keyboardType:Titanium.UI.KEYBOARD_EMAIL,
returnKeyType:Titanium.UI.RETURNKEY_DEFAULT,
borderStyle:Titanium.UI.INPUT_BORDERSTYLE_ROUNDED,
autocapitalization: Titanium.UI.TEXT_AUTOCAPITALIZATION_NONE,
clearButtonMode: Titanium.UI.INPUT_BUTTONMODE_ONFOCUS
});
win.setTitleControl(txtSearch);
var btnSearch = Titanium.UI.createButton({
title: 'Search',
height: 'auto',
width: 'auto',
font: {fontSize: 13}
});
win.rightNavButton = btnSearch;
If I click the search button and it brings me to the search results page and then I hit the back button the textbox is not moved over and looks fine.. Only when the page first loads
Try adding the text field to a view, then setting the view as the title control:
var container = Ti.UI.createView({
width: 320
});
var txtSearch = Titanium.UI.createTextField({
hintText: 'Keyword to search for',
height: 'auto',
width: 220,
left: 0,
font: {fontSize: 12},
enabled: true,
keyboardType:Titanium.UI.KEYBOARD_EMAIL,
returnKeyType:Titanium.UI.RETURNKEY_DEFAULT,
borderStyle:Titanium.UI.INPUT_BORDERSTYLE_ROUNDED,
autocapitalization: Titanium.UI.TEXT_AUTOCAPITALIZATION_NONE,
clearButtonMode: Titanium.UI.INPUT_BUTTONMODE_ONFOCUS
});
container.add(txtSearch);
win.setTitleControl(container);

Resources