Canvas/fabricjs: How to group items? - javascript

I have an error when grouping elements. The error is only visible on the circles. Circles are rectangles. How to group items?
var activegroup = canvas.getActiveGroup();
var objectsInGroup = activegroup.getObjects();
activegroup.clone(function (newgroup) {
canvas.discardActiveGroup();
objectsInGroup.forEach(function (object) {
canvas.remove(object);
});
canvas.add(newgroup);
});
The full code
Video bug

Your problem when you create a circle object you put type is a 'rect', replace with 'circle'.
} else if (position.drawingType == "circle") {
var circle = new fabric.Circle({
id: position.i++,
type: "circle",
radius: Math.abs(position.firstClickPositionX < position.lastClickPositionX ? position.firstClickPositionX - position.lastClickPositionX : position.lastClickPositionX - position.firstClickPositionX) / 2,
fill: 'red',
left: position.firstClickPositionX < position.lastClickPositionX ? position.firstClickPositionX : position.lastClickPositionX,
top: position.firstClickPositionY < position.lastClickPositionY ? position.firstClickPositionY : position.lastClickPositionY,
scaleY: 1,
scaleX: 1
});
canvas.add(circle);
Check updated fiddle

Related

Adding tooltips for preconfigured date ranges in stock chart (Highstock)

I want to add some tooltips for the preconfigured date range buttons (like 1 day, 1 month etc.) in stock chart in highstock. I am not able to find a way to do it.
refer this link
Any help would be appreciated.
Thanks
Highcharts doesn't have built-in tooltip for the rangeSelector, but still you can create your own tooltip for that. It is very simple to add events to the buttons:
Highcharts.stockChart('container', {
chart: {
events: {
load: function() {
var chart = this,
buttons = chart.rangeSelector.buttons;
for (var i = 0, len = buttons.length; i < len; i++) {
(function(i) {
var item = buttons[i],
group = $('.highcharts-range-selector-tooltip'),
rectElem = $('.range-selector-tooltip'),
textElem = $('.range-selector-tooltip-text'),
box;
item.on('mouseover', function(e) {
// Define legend-tooltip text
var str = item.text.textStr;
textElem.text(str)
// Adjust rect size to text
box = textElem[0].getBBox()
rectElem.attr({
x: box.x - 8,
y: box.y - 5,
width: box.width + 15,
height: box.height + 10
})
// Show tooltip
group.attr({
transform: `translate(${e.clientX + 7}, ${e.clientY + 7})`
})
}).on('mouseout', function(e) {
// Hide tooltip
group.attr({
transform: 'translate(-9999,-9999)'
})
});
})(i);
}
}
}
},
...
}, function(chart) {
var group = chart.renderer.g('range-selector-tooltip')
.attr({
transform: 'translate(-9999, -9999)',
zIndex: 99
}).add(),
text = chart.renderer.text()
.attr({
class: 'range-selector-tooltip-text',
zIndex: 7
}).add(group),
box = text.getBBox();
chart.renderer.rect().attr({
'class': 'range-selector-tooltip',
'stroke-width': 1,
'stroke': 'grey',
'fill': 'white',
'zIndex': 6
})
.add(group)
});
Live example: http://jsfiddle.net/BlackLabel/Lg9cfrub/
API Reference: https://api.highcharts.com/highstock/chart.events.load

Javascript two objects intersecting

I have been using Konva for drawing, I would like the arrow to "snap" to the other groups or shapes when the tip of the arrow intersects them and the user lets up on the mouse. If the arrow does not interset one then it should automatically delete its self.
Then when the groups or shapes are moved I would like the tips of the arrow to move with it.
I found an example of something similar but I'm not sure how I can combine them to get what I want.
I will post my current code below.
Example link
Click here
Code
var width = height = 170;
var stage = new Konva.Stage({
container: 'container',
width: width,
height: height
});
var layer = new Konva.Layer();
var isDrawArrow;
var Startpos;
var Endpos;
var arrow = new Konva.Arrow({
points: [],
pointerLength: 10,
pointerWidth: 10,
fill: 'black',
stroke: 'black',
strokeWidth: 4
});
var circle = new Konva.Circle({
x: stage.getWidth() / 2,
y: stage.getHeight() / 2,
radius: 20,
fill: 'green'
});
var circleA = new Konva.Circle({
x: stage.getWidth() / 5,
y: stage.getHeight() / 5,
radius: 30,
fill: 'red',
draggable: true
});
circle.on('mouseover', function() {
document.body.style.cursor = 'pointer';
layer.draw()
});
circle.on('mouseout', function() {
document.body.style.cursor = 'default';
layer.draw()
});
circle.on('mousedown touchstart', function() {
isDrawArrow = true;
circleA.on('dragmove', adjustPoint);
Startpos = stage.getPointerPosition();
});
stage.addEventListener('mouseup touchend', function() {
isDrawArrow = false;
});
stage.addEventListener('mousemove touchmove', function() {
if (!isDrawArrow) return;
Endpos = stage.getPointerPosition()
var p = [Startpos.x, Startpos.y, Endpos.x, Endpos.y];
arrow.setPoints(p);
layer.add(arrow);
layer.batchDraw();
});
circle.on('mouseup', function() {
this.setFill('green');
layer.batchDraw();
});
function adjustPoint(e) {
var p = [circle.getX(), circle.getY(), circleA.getX(), circleA.getY()];
arrow.setPoints(p);
layer.draw();
stage.draw();
}
function haveIntersection(r1, r2) {
return !(
r2.x > r1.x + r1.width ||
r2.x + r2.width < r1.x ||
r2.y > r1.y + r1.height ||
r2.y + r2.height < r1.y
);
}
layer.add(circle);
layer.add(circleA);
stage.add(layer);
adjustPoint();
<script src="https://cdnjs.cloudflare.com/ajax/libs/konva/2.3.0/konva.js"></script>
<div id="container"></div>
To do the snap you needed a function to determine distance between 2 points.
Easily done with a pythagorean calculation, (if you need help with that read about it here).
On the mouse move when you detect that the distance between the end of the arrow and your point (on this case the center or the red cirle) is less than what you want you can "snap it" that is what you do on your function adjustPoint that was all good.
On the mouse up you also need to check the distance and if is too far just hide the arrow
Working Code below:
var width = height = 170;
var stage = new Konva.Stage({
container: 'container',
width: width,
height: height
});
var layer = new Konva.Layer();
var isDrawArrow, Startpos, Endpos;
var snapDistance = 20;
function distance(p, c) {
var dx = p.x - c.getX();
var dy = p.y - c.getY();
return Math.sqrt(dx * dx + dy * dy);
}
var arrow = new Konva.Arrow({
points: [],
pointerLength: 10,
pointerWidth: 10,
fill: 'black',
stroke: 'black',
strokeWidth: 4
});
var circle = new Konva.Circle({
x: stage.getWidth() - 25,
y: stage.getHeight() - 25,
radius: 20,
fill: 'green'
});
var circleA = new Konva.Circle({
x: stage.getWidth() / 5,
y: stage.getHeight() / 5,
radius: 25,
fill: 'red',
draggable: true
});
circle.on('mousedown touchstart', function() {
isDrawArrow = true;
circleA.on('dragmove', adjustPoint);
Startpos = stage.getPointerPosition();
});
stage.addEventListener('mouseup touchend', function() {
isDrawArrow = false;
if (distance(Endpos, circleA) > snapDistance) {
arrow.hide();
layer.batchDraw();
}
});
stage.addEventListener('mousemove touchmove', function() {
if (!isDrawArrow) return;
Endpos = stage.getPointerPosition()
var p = [Startpos.x, Startpos.y, Endpos.x, Endpos.y];
arrow.setPoints(p);
arrow.show();
layer.add(arrow);
layer.batchDraw();
if (distance(Endpos, circleA) <= snapDistance) {
adjustPoint();
isDrawArrow = false
}
});
function adjustPoint(e) {
var p = [circle.getX(), circle.getY(), circleA.getX(), circleA.getY()];
arrow.setPoints(p);
layer.draw();
stage.draw();
}
layer.add(circle);
layer.add(circleA);
stage.add(layer);
canvas {
border: 1px solid #eaeaea !IMPORTANT;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/konva/2.3.0/konva.js"></script>
<div id="container"></div>

FabricJS optical look of lines

I am working at a project with fabric js. I tried to minimize my problem, so I'm hoping that the code isn't too messed up.
I am creating some Objects which are linked with each other:
A Line, which contains a Start and an Endpoint
A Circle, which is StartPoint of 1 line and Endpoint of another line
with this combination i can create different shapes(like a polygon) and modify my move-functions for them too.
When a Circle is dragged, the related Lines are scaling and moving too. (in my code you can move the lines too and the shape is resized after that, but i didnt put it into this example, bc this short extract should be enough to show what my problem is.)
I got a little example in jsfiddle: https://jsfiddle.net/bxgox7cr/
When you look at the ends of the lines, you can clearly see a cut, so the eye soon recognize, that this is not a connected shape but rather some lines which are close to each other. Is there a way to modify the look of the lines, that the shape looks "closed"?
Here is my code, i tried to put some comments, that it is easy to read:
var canvas = new fabric.Canvas('canvas');
fabric.Object.prototype.originX = fabric.Object.prototype.originY = 'center';
document.getElementById("canvas").tabIndex = 1000;
/** ------------creating a Line Object, which contains a start and an endpoint ------------**/
fabric.LineWithPoints = fabric.util.createClass(fabric.Line, {
initialize: function(points, options) {
options || (options = {});
this.callSuper('initialize', points, options);
options &&
this.set('type', options.type),
this.set('name', options.name),
this.set('start_point', options.start_point),
this.set('end_point', options.end_point),
this.set('current_x', options.current_x),
this.set('current_y', options.current_y)
},
setStartPointAndEndPoint: function(start_point, end_point) {
this.set({
start_point: start_point,
end_point: end_point
});
},
setValues: function(new_x1, new_y1, new_x2, new_y2) {
// console.log(this);
this.set({
x1: new_x1,
x2: new_x2,
y1: new_y1,
y2: new_y2
});
this.setCoords();
}
});
/**--- modifie the circle element, adding new functions for the movement of the object-------*/
fabric.LinePoint = fabric.util.createClass(fabric.Circle, {
initialize: function(options) {
options || (options = {});
this.callSuper('initialize', options);
options &&
this.set('subtype', 'line_point'),
this.set('x', this.left),
this.set('y', this.top)
},
setPointCoordinates: function(new_left, new_top) {
this.set({
x: new_left,
y: new_top,
left: new_left,
top: new_top
});
this.setCoords();
},
move: function(new_left, new_top) {
var wall_1 = this.line1;
var wall_2 = this.line2;
this.setPointCoordinates(new_left, new_top);
wall_1.setValues(wall_1.x1, wall_1.y1, this.getLeft(), this.getTop());
wall_2.setValues(this.getLeft(), this.getTop(), wall_2.x2, wall_2.y2);
canvas.renderAll();
},
});
/**------------------- Moving Function------------------------------------------------- */
canvas.on('object:moving', function(event) {
var object = event.target;
if (object.subtype == "line_point") {
object.move(object.getLeft(), object.getTop());
}
});
/**------------------------------ create functions for the objects -----------------------*/
function newCircleObject(left, top, wall_1, wall_2) {
var circle = new fabric.LinePoint({
left: left,
top: top,
strokeWidth: 2,
radius: 15,
fill: 'grey',
stroke: 'black',
opacity: 0.1,
perPixelTargetFind: true,
subtype: 'line_point',
includeDefaultValues: false
});
circle.hasControls = false;
circle.hasBorders = false;
circle.line1 = wall_1;
circle.line2 = wall_2;
return circle;
}
function newWallObject(coords) {
var wall = new fabric.LineWithPoints(coords, {
stroke: 'black',
strokeWidth: 6,
lockScalingX: true,
lockScalingY: true,
perPixelTargetFind: true,
subtype: 'line',
type: 'line',
padding: 10,
includeDefaultValues: false
});
wall.hasControls = false;
wall.hasBorders = false;
return wall;
}
/**------------------------------ adding the shapes--------------------------------*/
var wall_1 = newWallObject([100, 100, 100, 500]);
var wall_2 = newWallObject([100, 500, 500, 500]);
var wall_3 = newWallObject([500, 500, 500, 100]);
var wall_4 = newWallObject([500, 100, 100, 100]);
var end_point_1 = newCircleObject(wall_1.x1, wall_1.y1, wall_4, wall_1);
var end_point_2 = newCircleObject(wall_2.x1, wall_2.y1, wall_1, wall_2);
var end_point_3 = newCircleObject(wall_3.x1, wall_3.y1, wall_2, wall_3);
var end_point_4 = newCircleObject(wall_4.x1, wall_4.y1, wall_3, wall_4);
wall_1.setStartPointAndEndPoint(end_point_1.name, end_point_2.name);
wall_2.setStartPointAndEndPoint(end_point_2.name, end_point_3.name);
wall_3.setStartPointAndEndPoint(end_point_3.name, end_point_4.name);
wall_4.setStartPointAndEndPoint(end_point_4.name, end_point_1.name);
canvas.add(wall_1, wall_2, wall_3, wall_4, end_point_1, end_point_2, end_point_3, end_point_4);
Add strokeLineCap: 'round',:
function newWallObject(coords) {
var wall = new fabric.LineWithPoints(coords, {
stroke: 'black',
strokeWidth: 6,
lockScalingX: true,
lockScalingY: true,
perPixelTargetFind: true,
strokeLineCap: 'round',
subtype: 'line',
type: 'line',
padding: 10,
includeDefaultValues: false
});
wall.hasControls = false;
wall.hasBorders = false;
return wall;
}
I looked up: http://fabricjs.com/docs/fabric.Object.html#strokeLineCap

Kinetic JS canvas game (Layering issue)(Javascript)

I am trying to recreate the game http://www.sinuousgame.com/ and studying html5 canvas and kineticJS.
This is my fiddle:
http://jsfiddle.net/2WRwY/7/
My problem:
The tail part of the player in the fiddle doesn't seem to retract back.
The red ball objects should appear over the player objects.
(Try running the fiddle with layer.removeChildren(); and without it.)
Right now,I have commented "layer.removeChildren();" on the fiddle.. (basically which causes the problem for me)
Here's my html:
<!DOCTYPE html>
<html>
<head>
<title>Collision Detection-player</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width">
<link rel="stylesheet" href="../css/style.css"/>
</head>
<body>
<div id="container" style="width: auto; height: auto; background:#000; margin:auto; float:left;"></div>
<script src="../js/jquery.min.js"></script>
<script src="../js/kinetic-v5.0.0.min.js"></script>
<script src="../js/main_kinetic_combined.js"></script>
</body>
</html>
Here's my javascript:
//The working player code
var LimitedArray = function(upperLimit) {
var storage = [];
// default limit on length if none/invalid supplied;
upperLimit = +upperLimit > 0 ? upperLimit : 100;
this.push = function(item) {
storage.push(item);
if (storage.length > upperLimit) {
storage.shift();
}
return storage.length;
};
this.get = function(flag) {
return storage[flag];
};
this.iterateItems = function(iterator) {
var flag, l = storage.length;
if (typeof iterator !== 'function') {
return;
}
for (flag = 0; flag < l; flag++) {
iterator(storage[flag]);
}
};
};
var tail = new LimitedArray(50);
var flag = 0, jincr = 0;
var stage = new Kinetic.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
listening: true
});
var layer = new Kinetic.Layer({
listening: true
});
stage.add(layer);
var player = new Kinetic.Circle({
x: 20,
y: 20,
radius: 6,
fill: 'cyan',
stroke: 'black',
draggable: true
});
layer.add(player);
// move the circle with the mouse
stage.getContent().addEventListener('mousemove', function() {
<!--layer.removeChildren(); -->
layer.add(player);
player.setPosition(stage.getPointerPosition());
var obj = {
x: stage.getPointerPosition().x,
y: stage.getPointerPosition().y
};
tail.push(obj);
var arr = [];
tail.iterateItems(function(p) {
arr.push(p.x, p.y);
});
var line = new Kinetic.Line({
points: arr,
stroke: 'white',
strokeWidth: 2,
lineCap: 'round',
lineJoin: 'round'
});
layer.add(line);
// layer.draw();
});
var x = 0;
var y = 0;
var noOfEnemies = 150;
var enemyArmada = new Array();
createEnemy();
function createEnemy() {
for (var i = 0; i < noOfEnemies; i++) {
var enemy = new Kinetic.Circle({
x: Math.random() * window.innerWidth,
y: Math.random() * window.innerHeight,
radius: 4.5 + 1.5 * Math.random(),
fill: 'red',
stroke: 'black',
});
enemy.speedX = enemy.speedY = (0.3 + Math.random() * 50);
enemyArmada.push(enemy);
layer.add(enemy);
}
}
var anim = new Kinetic.Animation(function(frame) {
for (var i = 0; i < noOfEnemies; i++) {
var e = enemyArmada[i];
e.position({
x: e.position().x - e.speedX * frame.timeDiff / 500,
y: e.position().y + e.speedY * frame.timeDiff / 500
});
if (e.position().y < 0 || e.position().x < 0) {
e.position({
x: (Math.random() * (window.innerWidth + 600)),
y: -(Math.random() * window.innerHeight)
});
}
}
}, layer);
anim.start();
Any suggestions?
The problem is that after you've added the enemies, you're adding the player and the line to the layer again, so they'll be on top. Also, on each mouse move, you're creating the line over and over again.
So, instead, you should just update the line points (and you don't need the layer.removeChildren(); line at all), like this:
var line = new Kinetic.Line({
points: [],
stroke: 'white',
strokeWidth: 2,
lineCap: 'round',
lineJoin: 'round'
});
layer.add(line);
layer.add(player);
// move the circle with the mouse
stage.getContent().addEventListener('mousemove', function() {
player.position(stage.getPointerPosition());
var obj = {
x: stage.getPointerPosition().x,
y: stage.getPointerPosition().y
};
tail.push(obj);
var arr = [];
tail.iterateItems(function(p) {
arr.push(p.x, p.y);
});
line.points(arr);
});
See fiddle: http://jsfiddle.net/Kunstmord/p9fnq/2/
This way, you're only creating the line once. This also seems to fix the non-disappearing trail.
Also, please note:
1) use position instead of setPosition and getPosition (see KineticJS 5.0 docs)
2) adding <!-- --> does not comment out a line in Javascript (layer.removeChildren(); line).

How to create circular progressbar in Titanium Appcelerator?

I am working with Titanium Appcelerator and I need help in creating Circular progressbar in the app. Can anyone explain me how circular progressbar works and how progress is filled inside circle in circular manner..?
Please use this Alloy Widget now:
https://github.com/manumaticx/circularprogress
Original Answer
I know this is a little late, but I figured this out after about a days work.
I have not tested on android
This is not animated as I did not need it for my needs. To make it animated, look at the second to last line in the function layer3.transform = Ti.UI.create2DMatrix().rotate(angle); You should be able to animate the rotation angle.
var win = Ti.UI.createWindow({
width:'100%',
height:'100%'
});
win.open();
function circularProgressBar(options)
{
var opts = options;
if (opts.percent == null || opts.percent > 1 || opts.percent < 0) opts.percent = 1;
if (opts.size == null) opts.size = 46;
if (opts.margin == null) opts.margin = 4;
if (opts.backgroundColor == null) opts.backgroundColor = '#fff';
if (opts.progressColor == null) opts.progressColor = '#4ba818';
if (opts.topper == null) opts.topper = {};
if (opts.topper.color == null) opts.topper.color = '#fff';
if (opts.topper.size == null) opts.topper.size = 36;
if (opts.font == null) opts.font = {};
if (opts.font.visible == null) opts.font.visible = true;
if (opts.font.size == null) opts.font.size = 12;
if (opts.font.color == null) opts.font.color = '#900';
if (opts.font.shadowColor == null) opts.font.shadowColor = '#aaa';
if (opts.font.shadowRadius == null) opts.font.shadowRadius = 1;
if (opts.font.shadowOffset == null) opts.font.shadowOffset = {};
if (opts.font.shadowOffset.x == null) opts.font.shadowOffset.x = 0;
if (opts.font.shadowOffset.y == null) opts.font.shadowOffset.y = 1;
var mainHolder = Ti.UI.createView({
left: options.left,
right: options.right,
top: options.top,
bottom: options.bottom,
width: opts.size + opts.margin,
height: opts.size + opts.margin,
borderRadius: (opts.size + opts.margin) / 2,
backgroundColor: opts.backgroundColor
});
var holder = Ti.UI.createView({
width: opts.size,
height: opts.size,
borderRadius: opts.size / 2
});
var layer1 = Ti.UI.createView({
width: opts.size,
height: opts.size,
borderRadius: opts.size / 2,
backgroundColor: opts.progressColor
});
var layer2 = Ti.UI.createView({
left: 0,
width: opts.size / 2,
height: opts.size,
anchorPoint: {
x: 1,
y: 0.5
},
backgroundColor: opts.backgroundColor
});
var layer3 = Ti.UI.createView({
right: 0,
width: opts.size / 2,
height: opts.size,
anchorPoint: {
x: 0,
y: 0.5
},
backgroundColor: opts.backgroundColor
});
var layer4 = Ti.UI.createView({
right: 0,
width: opts.size / 2,
height: opts.size,
anchorPoint: {
x: 0,
y: 0.5
},
backgroundColor: opts.progressColor
});
var topper = Ti.UI.createView({
width: opts.topper.size,
height: opts.topper.size,
borderRadius: opts.topper.size / 2,
backgroundColor: opts.topper.color
});
var percentText = Ti.UI.createLabel({
visible: opts.font.visible,
width: Ti.UI.SIZE,
height: Ti.UI.SIZE,
color: opts.font.color,
font: {
fontSize:opts.font.size
},
shadowColor: opts.font.shadowColor,
shadowRadius: opts.font.shadowRadius,
shadowOffset: {
x: opts.font.shadowOffset.x,
y: opts.font.shadowOffset.y
},
textAlign: Ti.UI.TEXT_ALIGNMENT_CENTER,
text: (opts.percent * 100) + '%'
});
mainHolder.add(holder);
topper.add(percentText);
holder.add(layer1);
holder.add(layer2);
holder.add(layer3);
holder.add(layer4);
holder.add(topper);
var percent = opts.percent;
var angle = 360 * percent;
layer2.visible = (angle > 180) ? false : true;
layer4.visible = (angle > 180) ? true : false;
layer3.transform = Ti.UI.create2DMatrix().rotate(angle);
return mainHolder;
}
/* Circular Progress Bar Options
percent: A value between 0 and 1
size: The size of the circular progress bar
margin: The margin of the circular progress bar
backgroundColor: The backgroundColor of the circular area
progressColor: The backgroundColor of the progress bar
--
topper.color: The center circle color
topper.size: The size of the center circle
---
font.visible: Boolean to display the font or not
font.color: The font color
font.size: The fontSize
font.shadowColor: The font shadow color
font.shadowRadius: The font shadow radius
font.shadowOffset.x: The x value of the shadow shadowOffset
font.shadowOffset.y: The y value of the shadow shadowOffset
*/
var circleProgress1 = circularProgressBar({
top:50,
percent:0.35,
size:46,
margin:4,
backgroundColor:'#fff',
progressColor:'#4ba818',
topper: {
color:'#fff',
size: 36
},
font: {
visible: true,
color: '#900',
size: 12,
shadowColor: '#aaa',
shadowRadius: 1,
shadowOffset: {
x: 0,
y: 1
}
}
});
win.add(circleProgress1);
All that ^^^ creates this:
edit: The method I used to create this was from Malcom's idea from this thread: https://developer.appcelerator.com/question/154274/is-there-a-way-to-create-circular-progress-bar
This isn't a progress bar at all - its an "Activity Indicator".
It doesn't show true progress like a progress bar. With a progress bar, you can set a value for your progress (0-100%, for example). This will just "spin" to let users know that they need to wait.
To create an activity indicator, see here: http://docs.appcelerator.com/titanium/2.0/#!/api/Titanium.UI.ActivityIndicator
Quick example:
var activityView = Ti.UI.createView({visible: false});
var activityIndicator = Ti.UI.createActivityIndicator({
message: 'Loading...',
height:'auto',
width:'auto'
});
activityView.add(activityIndicator);
activityView.show();
This example will work, but its not styled. I'll leave it up to you to decide how you want it to look. Tho if you want it to look anything like the image you posted, look at backgroundColor, borderRadius, and transparency on the view property.

Categories

Resources