Drawing a spreadsheet like grid in canvas with konva - html5-canvas

I am working on a Gantt-like task display using HTML5 canvas, and the Konvajs canvas library.
Deconstructing a Gantt chart into its components leads me currently to a view as below. Here 1 is the list of tasks, 2 is the task bar area, 3 is a task bar, and 4 is a text cell.
For this question I am seeking code to construct 1 & 4. The data to be displayed will be delivered in plain JS objects with a nested list of tasks where each task has a number, name, assigned-to person name, start date, end date, days duration, and % complete.
So the requirement is to be able to construct a spreadsheet-like panel such as is seen on the left hand side of a Gantt chart.
I have something part developed which I shall post as an answer. However this seems like such as common need that I am hoping there is someone out there with code they can cut & paste into SO to lead the way.
Note: Gantt in sample image is from Vertex42.

So here is my own fumbling attempt at an approach. Can anyone improve upon it or am I going down the wrong road.
EDIT: I now have a fledgling component for drawing text into the spreadsheet-like cells, including the percent complete shading. To keep this answer uncluttered, this additional component is in this codepen.
// this is the object that defines our grid
//
var gridData = { name: 'grid1', width: 350, height: 400, rowHeight: 24, padding: 4, fill: 'azure', gridLineColor: '#ccc', header: {size: 16, fill: 'black', color: 'white' }, data: {size: 16, fill: 'azure', color: 'black' },
row: [
{ cells: // row 1
[
{ width: 50, text: 'Item', style: 'header'},
{ width: 240, text: 'Name', style: 'header'},
{ width: 60, text: 'Status', style: 'header'},
]
},
{ cells: // row 2
[
{ text: '1'},
{ text: 'Find tea pot'},
{ text: '100%'},
]
},
{ cells: // row 3
[
{ text: '2'},
{ text: 'Boil water'},
{ text: '60%'},
]
}
]
}
// From here on could be wrapped into a component that churns out grids. Maybe you pass in the id of the stage container
// and the data model you want to produce, etc.
// Set up the canvas / stage
var stage = new Konva.Stage({container: 'container1', width: 600, height: 300});
// Add a layer
var layer = new Konva.Layer({draggable: false});
stage.add(layer);
// make a main group for the grid, call it a panel. Assigning a name may be handy later
var panel = new Konva.Group({name: gridData.name});
layer.add(panel); // Add the panel to the layer
// a group has no visual properties. Add a background rect to hold the colour fill
var panelRect = new Konva.Rect({width: gridData.width, height: gridData.height, fill: gridData.fill})
panel.add(panelRect);
var topLeft = {x: 0, y: 0}; // Since we are drawing a grid, we need to compute the position of each cell
for (var i = 0; i < gridData.row.length; i = i + 1){
topLeft.x = 0; // reset x at start of each row
// iterate for each cell on the row
for (var j = 0; j < gridData.row[i].cells.length; j = j + 1){
var cell = new Konva.Rect({name: 'cellBg', // assign a name for later searching
x: topLeft.x, y: topLeft.y, // position as computed
width: gridData.row[0].cells[j].width, // use the first row from celldate to get the cell width
height: gridData.rowHeight, // grid has a uniform row height
stroke: gridData.gridLineColor, // and line colour
strokeWidth: 1, // use a set line width but you can add to the gridData object as needed.
fill: (i === 0 ? gridData.header.fill : gridData.data.fill), // use the given header text color
});
panel.add(cell);
// Add text to the cell. Note that if you wanted to be using alignments you would need to draw the text off-screen and
// get width/height of that text then use those values for positioning calculations. Once you have the rect size of the
// text, all the alignments are simple math.
var text = new Konva.Text({ x: topLeft.x + gridData.padding, // add padding to locate the text nicely into the cell
y: topLeft.y + gridData.padding,
// use the given text size
fontSize: (i === 0 ? gridData.header.size : gridData.data.size),
// use the given header text color
fill: (i === 0 ? gridData.header.color : gridData.data.color),
text: gridData.row[i].cells[j].text, // set the text value.
listening: false // stop text interfering with mouse events
});
panel.add(text);
cell.on('mouseover', function(evt){
var shape = evt.target;
$(shape).data('bgColor', shape.fill());
shape.fill('lime');
layer.draw();
})
cell.on('mouseout', function(evt){
var shape = evt.target;
shape.fill($(shape).data('bgColor'));
layer.draw();
})
topLeft.x = topLeft.x + gridData.row[0].cells[j].width; // offset the computed next cell x value by the width of the cell
}
topLeft.y = topLeft.y + gridData.rowHeight; // offset the computed next cell y value by the height of the row
}
layer.draw();
stage.draw();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/konva/2.5.1/konva.min.js"></script>
<div id='container1' style="width: 300px, height: 200px; background-color: silver;"></div>

Related

How to use Konva to apply the matrix obtained by getTransform to a larger canvas proportionally

Our project is an online DIY album. Users fill in photos in the frame. Konva is required to achieve a mask function similar to Photoshop, similar to the effect of photos pressed under the frame. There may be many picture frames in my canvas, and only one canvas can not achieve this function. Therefore, I need to use a new canvas to draw this, and then draw the contents of this mask canvas onto Konva's canvas.
Based on this idea, I realized the basic needs. Our requirement is that users can drag, rotate or scale photos. Therefore, I drew a transparent element on Konva's main canvas (Canvas 1) with the same size and position as the photo in the mask canvas (Canvas 2), and then bound it with transformer.With this transformer, I can change the position, size and rotation angle of the transparent elements, and then transfer the matrix of the transparent elements to the photos in the mask canvas through getTransform and setTransform, so that the photos in the mask can rotate, scale or translate with the transparent elements.
When the width and height of the mask canvas (Canvas 2) are the same as those of the elements finally drawn on the main canvas (Canvas 1), everything is OK. However, in order to ensure a clearer final effect, we set the width and height of the mask canvas (Canvas 2) to a higher value. Then there is a problem. When I drag or rotate the transparent elements, the position of the photos in the mask does not completely follow the transparent elements, but there is a deviation.
I think this is because the size of the transparent element is different from the size of the photo in the mask canvas (Canvas 2), so it is not possible to directly set the matrix obtained by getTransform to the photo. In this process, a transformation is required. It should be OK to set the transformed matrix to the photo in the mask. The problem is that I don't know how to carry out such a transformation.
Here's the code:
//canvas 1
let container = document.getElementById("container");
let stage = new Konva.Stage({
container: container,
width: 800,
height: 800
});
layer = new Konva.Layer();
stage.add(layer);
let group = new Konva.Group({
x: 100,
y: 100,
width: 200,
height: 200,
clipX: 0,
clipY: 0,
clipWidth: 200,
clipHeight: 200
});
let shape = await drawMaskPhoto();
group.add(shape);
let transparentRect = new Konva.Rect({
width: 200,
height: 200,
x:0,
y:0,
type:"transparentRect"
});
group.add(transparentRect);
transparentRect.on('transform',async function (e) {
let transform = e.target.getTransform().m;
shape = await drawMaskPhoto(transform);
});
function newImg(src) {
return new Promise((resolve, reject) => {
let img = new Image();
img.src = src;
img.onload = () => {
resolve(img)
}
})
}
async function drawMaskPhoto(transform){
//canvas 2
let maskCanvas = document.createElement("canvas");
let mask = await newImg(mask.imgPath);
let photo = await newImg(photo.imgPath);
//To ensure clarity, I set the new canvas size to be 5 times the size of the elements finally drawn on the main canvas (1000*1000)
maskCanvas.width = 1000;
maskCanvas.height = 1000;
let ctx = maskCanvas.getContext("2d");
ctx.drawImage(mask, 0, 0, 1000, 1000);
ctx.globalCompositeOperation = 'source-atop';
if(transform){
ctx.setTransform(...transform);
}
ctx.drawImage(photo, 0, 0, 1000, 1000);
let shape = new Konva.Image({
image:maskCanvas,
width:200,
height:200
})
return shape
}
The effect picture is like this
The effect picture drawn
Effect picture after rotation
let traceEle = $('#trace');
/*
* SyncHandler is a DIY object for synchronising canvases. In this case we have one leader and one follower.
* An object is used to scope the code. Create new instaces via
* let myStage = new Konva.Stage(..all usual Konva stage stuff...)
* let myHandler = new syncHandler(myStage, 'h4'); // where h4 = a useful trace prefix so we can see which handler is fired.
*/
function syncHandler(stage, handlerName){
let followers = [], // In this demo we have only one listening canvas but we will assume there could be many.
shapes = {}; // Each shape created is placed in this object thus forming a quick lookup list based on id.
// We need all shapes to have a unique ID so we use this func to make them.
// These are not entirely compliant GUID's but meet our needs.
function getGuid(){
let fC=function () {
return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1).toUpperCase();
}
return (fC() + fC() + "-" + fC() + "-" + fC() + "-" + fC() + "-" + fC() + fC() + fC());
}
// Fires during init stage - set the stage id if not set already.
stage.id(getGuid());
// Add a follower to the list of followers.
this.addFollower = function(newFollower){
followers.push(newFollower);
}
// Func to make a proxy for the shape's attrs arry.
function makeProxy(target){
let prxy = new Proxy(target, {
// this code fires when the shape has a change to its attrs array. It allows us
// to intervene with the prop change, send the change to the listerners
// target is the shape's attrs array which will shortly receive the new value.
set(array, prop, value, target) {
changeAttr(prop, value, target); // Invoke the function to communicate the change to any follower[]
// finally let the target - the shape's attr array get the change.
return Reflect.set(array, prop, value, target);
},
deleteProperty(array, prop, target) {
// Included in case needed in future.
let msg = {type: 'attrdel', id: target["id"], name: prop};
sendMessage(msg);
// finally let the target - the shape's attr array get the change.
return Reflect.deleteProperty(array, prop);
}
})
return prxy;
}
// This func is a wrapper for the 'new Konver.<ShapeName>()' process. It is required to:
// - ensure that the new shape will have an id;
// - hide the wiring up of the shape.attrs prox;
// - add the shape into our shapes list, keyed on the assigned id;
// - send the message about the new object to the follower[] canvases.
this.makeShape = function(typeName, attrs){
let shape = null;
attrs = (typeof attrs == 'undefined' ? {} : attrs); // if attrs is not supplied then make an empty array
attrs.id = (typeof attrs.id === 'undefined' ? getGuid() : attrs.id); // ensure there is an ID.
shape = new Konva[typeName](); // Make the actual Konva shape in this canvas.
shape.setAttrs(attrs); // Set the attrs for the new shape.
shape.attrs = makeProxy(shape.attrs);
if (typeof shapes[attrs.id] === 'undefined'){
shapes[attrs.id] = shape;
}
// Send the message about the new shape to any follower[]
let msg = {type: 'makeShape', name: typeName, attrs: attrs };
sendMessage(msg);
return shape; // Hand back the shape to the code that created it.
}
// This func is a wrapper for Konva.container.add() or move(). It takes
// as args the shape being moved and the container that is to be the new parent.
this.changeParent = function(shapeObj, newParentObj){
newParentObj.add(shapeObj); // add shape to new parent.
// Send the message about the new shape to any follower[]
let msg = {type: 'changeParent', id: shapeObj.id(), parentId: newParentObj.id(), parentType: newParentObj.getType()};
sendMessage(msg);
}
/* this func is a wrapper for the Konva.shape.setAttr() method.
* Network comms are costly - we do not want to send messages about propos that either have not changed or
* where the change to a numeric property is insignificant.
*/
function changeAttr(prop, value, target){
let currentVal = target[prop],
sendIt = true; // flag to indicate change is to be sent - overridden below for numeric types if needed.
if (currentVal !== value){
if ( typeof(value) === "number" ){
if (Math.abs(currentVal - value) < 0.01){ // adjust or remove this tollerence as needed.
sendIt = false;
}
}
if (sendIt){
// make the message
let msg = {type: 'changeAttr', id: target["id"], name: prop, value: value };
sendMessage(msg);
}
}
return true;
}
// Func to convert the given message to a JSON string and send it to any followers.
function sendMessage(msg){
if (followers.length === 0){ // no send if no listening followers.
return;
}
let jsonMsg = JSON.stringify(msg);
for (let i = 0; i < followers.length; i++){
followers[i].processMessage(jsonMsg);
}
}
/* In this func we process a received change message. In this demo this is simply one object calling a func in another
* but in the final version this will be talking via peer-to-peer between browsers. We receive a message in JSON format
* containing the change information. The 'type' value gives either 'makeShape', 'changeParent', or 'changeAttr'.
*
* Note that when this runs it is within the context of a 'following' syncHandler instance, not the sending instance!
*
*/
this.processMessage = function(changeInfo){
let change = JSON.parse(changeInfo), // parse the JSON into a JS object. Note you will want a try-catch here !
shape = null;
switch (change.type){
case "makeShape": // a new shape message.
shape = this.makeShape(change.name, change.attrs); // make the shape in the follower syncHandler - this works
// in this demo because the follwower has no followers of its own.
// If it _did_ have followers a deadlock would occur !
trace(handlerName + ".makeShape: " + change.name + ' ' + shape.id()); // record a trace of what is going on
shapes[shape.id()] = shape; // note the shape in our shape list.
break;
case "changeParent": // an existing shape is changing parent container - like from layer A to layer B.
trace(handlerName + '.changeParent: id=' + change["id"])
shape = shapes[change.id]; // get the Konva shape instance that is moving parent
// Special case for adding to stage
if (change.parentType === "Stage"){
stage.add(shape)
}
else {
let parentContainer = shapes[change.parentId]; // get the Kona shape that is to be the new container.
parentContainer.add(shape); // execute the Konva command to switch parents.
}
break;
case "changeAttr": // an attribute of a shape has changed - mirror the change in this follower.
trace(handlerName + '.changeAttr: id=' + change["id"] + ' - ' + change.name + ' = ' + change.value);
shape = shapes[change.id];
shape.setAttr(change.name, change.value);
break;
}
}
} // end of the syncHandlerobject declaration.
// a simple trace output function so we can see some of what is happening - better than console.log!
function trace(msg){
traceEle.prepend('<pre>' + msg + ' </pre>');
}
/* from here onwards is Konva canvas admin */
// Making the stage is standard Konva API code.
let stage1 = new Konva.Stage({container: "container1", width: $('#container1').width(), height: $('#container1').height()});
// And now we create the handler object.
let handler1 = new syncHandler(stage1, 'sh1');
// Making the stage is standard Konva API code.
let stage2 = new Konva.Stage({container: "container2", width: $('#container2').width(), height: $('#container2').height()});
let handler2 = new syncHandler(stage2, 'sh2');
// Very importantly - we inform handler1 than handler2 is listening and wants to know about changes.
handler1.addFollower(handler2);
// The stage object was made via standard Konva API but for all other containers and shapes we use the handler
// function which adds id and wires up listener on attrs list.
let layer1 = handler1.makeShape("Layer");
// Add the layer to the stage.
// Adding a shape is done via syncHandler so that we can capture and broadcast the change
handler1.changeParent(layer1, stage1);
// Make a rect.
let rect1 = handler1.makeShape("Rect", {x: 20, y: 10, width: 100, height: 80, fill: 'cyan', draggable: true});
// Add rect1 to layer1 via syncHandler
handler1.changeParent(rect1, layer1);
// Make a circle
let circle1 = handler1.makeShape("Circle", {x: 140, y: 100, radius: 40, fill: 'magenta'});
// Add circle1 to layer1 via syncHandler
handler1.changeParent(circle1, layer1);
// Make a pentagon
let radialGradPentagon1 = handler1.makeShape("RegularPolygon", {
x: 500,
y: stage1.height() / 2,
sides: 5,
radius: 70,
fillRadialGradientStartPoint: { x: 0, y: 0 },
fillRadialGradientStartRadius: 0,
fillRadialGradientEndPoint: { x: 0, y: 0 },
fillRadialGradientEndRadius: 70,
fillRadialGradientColorStops: [0, 'red', 0.5, 'yellow', 1, 'blue'],
stroke: 'black',
strokeWidth: 4,
draggable: true,
});
// Add radialGradPentagon1 to layer1 via syncHandler
handler1.changeParent(radialGradPentagon1, layer1);
//
// Now we carry out a handful of attribute changes on the shapes to confirm it works !
//
// Move the rect to x = 101
rect1.x(101)
// Fill rect with red and rotate 45 degrees.
rect1
.fill('red')
.rotation(45);
rect1.on('mousedown', function(e){
e.cancelBubble = true;
})
// make circle draggable
circle1.draggable(true);
// Change the pentagon gradient
radialGradPentagon1
.fillRadialGradientEndRadius(60)
.fillRadialGradientColorStops([0, 'red', 0.5, 'yellow', 1, 'blue']);
// We will also now make a transformer on Stage 1 to experiment with dynamic attr changes.
var tr = handler1.makeShape("Transformer", {
anchorStroke: 'red',
anchorFill: 'yellow',
anchorSize: 20,
borderStroke: 'green',
borderDash: [3, 3],
nodes: [],
});
handler1.changeParent(tr, layer1);
// attach the transformer to the rect
tr.nodes([rect1])
layer1.on('mousedown', function(){
traceEle.html('');
})
// add a new rect to be used as a mouse-selection rectangle via click & drag on stage1.
var selectionRectangle = handler1.makeShape("Rect", {
name: 'selectionRect',
fill: 'rgba(0,0,255,0.5)',
visible: false,
});
// Following copied from https://konvajs.org/docs/select_and_transform/Basic_demo.html
// Add selectionRectangle to layer1 via syncHandler
handler1.changeParent(selectionRectangle, layer1);
let x1, y1, x2, y2;
stage1.on('mousedown touchstart', (e) => {
// do nothing if we mousedown on any shape
if (e.target !== stage1) {
return;
}
x1 = stage1.getPointerPosition().x;
y1 = stage1.getPointerPosition().y;
x2 = stage1.getPointerPosition().x;
y2 = stage1.getPointerPosition().y;
selectionRectangle.visible(true);
selectionRectangle.width(0);
selectionRectangle.height(0);
});
stage1.on('mousemove touchmove', () => {
// do nothing if we didn't start selection
if (!selectionRectangle.visible() ) {
return;
}
x2 = stage1.getPointerPosition().x;
y2 = stage1.getPointerPosition().y;
selectionRectangle.setAttrs({
x: Math.min(x1, x2),
y: Math.min(y1, y2),
width: Math.abs(x2 - x1),
height: Math.abs(y2 - y1),
});
});
stage1.on('mouseup touchend', () => {
// no nothing if we didn't start selection
if (!selectionRectangle.visible()) {
return;
}
// update visibility in timeout, so we can check it in click event
setTimeout(() => {
selectionRectangle.visible(false);
});
var shapes = stage1.find();
var shapes = layer1.getChildren(function(node){
return node.name() !== 'selectionRect' && node.getClassName() != "Transformer";
});
var box = selectionRectangle.getClientRect();
var selected = shapes.filter((shape) =>
Konva.Util.haveIntersection(box, shape.getClientRect())
);
tr.nodes(selected);
});
.container {
width: 800px;
height: 300px;
background-color: silver;
margin: 5px;
}
#trace {
max-height: 200px;
overflow-y: scroll;
font-family: 'Courier'
}
pre {
margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
<script src="https://unpkg.com/konva#8/konva.min.js"></script>
<div id='container1' class='container'></div>
<div id='container2' class='container'></div>
<p id='trace'></p>

how can I free drawing on shapes in konvajs

I want to make free drawing on top of shapes at konvajs. like an exp; Can u give me advise about shapes attrs like zindex or smt.
https://ibb.co/jq9pUK
Your question is very broad and you are not showing what you have tried so far. You would get better help faster if you give a clear description and post cut-down sample code for your questions.
Konvajs works on top of the HTML5 canvas. When working with a konvajs you put shapes, lines, images and text on to layers. Layers have a z-order and shapes on a layer have a z-order.
To answer your question, I would follow the pattern:
- create the stage
- create the shape layer
- add the shapes to the shape layer - triangles, rectangles, circles, etc
- add another layer for the freehand drawing
- draw on this layer.
Because of the sequence of adding the components to the canvas the z-order will support what you ask for in your question. If you wanted the drawing to happen 'behind' the shapes you would create the layers in the opposite sequence.
The working snippet below shows how to do the steps that I have listed above, and how to listen for the events you need to make it operate. You can extend from this starter code to handle erasing, selecting line colour, thickness, and stroke style. See the Konvajs drawing tutorial for more information.
Good luck.
// Set up the canvas / stage
var s1 = new Konva.Stage({container: 'container1', width: 300, height: 200});
// Add a layer for the shapes
var layer1 = new Konva.Layer({draggable: false});
s1.add(layer1);
// draw a cirlce
var circle = new Konva.Circle({
x: 80,
y: s1.getHeight() / 2,
radius: 70,
fill: 'red',
stroke: 'black',
strokeWidth: 4
})
layer1.add(circle)
// draw a wedge.
var wedge = new Konva.Wedge({
x: 200,
y: s1.getHeight() / 2,
radius: 70,
angle: 60,
fill: 'gold',
stroke: 'black',
strokeWidth: 4,
rotation: -120
});
layer1.add(wedge)
// Now add a layer for freehand drawing
var layer2 = new Konva.Layer({draggable: false});
s1.add(layer2);
// Add a rectangle to layer2 to catch events. Make it semi-transparent
var r = new Konva.Rect({x:0, y: 0, width: 300, height: 200, fill: 'blue', opacity: 0.1})
layer2.add(r)
// Everything is ready so draw the canvas objects set up so far.
s1.draw()
var drawingLine = null; // handle to the line we are drawing
var isPaint = false; // flag to indicate we are painting
// Listen for mouse down on the rectangle. When we get one, get a new line and set the initial point
r.on('mousedown touchstart', function () {
isPaint = true;
var pos = s1.getPointerPosition();
drawingLine = newLine(pos.x, pos.y);
drawingLine.points(drawingLine.points().concat(pos.x,pos.y));
layer2.draw();
});
// Listen for mouse up ON THE STAGE, because the mouseup will not fire on the rect because the mouse is actually over the line point we just drew when it is released.
s1.on('mouseup touchend', function () {
isPaint = false;
drawingLine = null;
});
// when the mouse is moved, add the position to the line points and refresh the layer to see the effect.
r.on('mousemove touchmove', function () {
if (!isPaint) {
return;
}
var pos = s1.getPointerPosition();
drawingLine.points(drawingLine.points().concat(pos.x,pos.y));
layer2.draw();
})
// Function to add and return a line object. We will extend this line to give the appearance of drawing.
function newLine(x,y){
var line = new Konva.Line({
points: [x,y,x,y],
stroke: 'limegreen',
strokeWidth: 4,
lineCap: 'round',
lineJoin: 'round'
});
layer2.add(line)
return line;
}
p
{
padding: 4px;
}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<script src="https://cdn.rawgit.com/konvajs/konva/1.6.5/konva.min.js"></script>
<p>Click & drag on the canvas to draw a line over the shapes.
</p>
<div id='container1' style="display: inline-block; width: 300px, height: 200px; background-color: silver; overflow: hidden; position: relative;"></div>

How to add Text-transform on text in canvas using kinetic js?

I want to know how could i convert text into uppercase in canvas using kinetic js.
Here is my code for canvas with image and text:
top_text_val = "INSERT TOP TEXT"; //Default top text on meme
bottom_text_val = "INSERT BOTTOM TEXT"; //Default bottom text on meme
var stage = new Kinetic.Stage({
container: 'meme-img',
width: 735, //width of container
height: 540, //height of container
});
var layer = new Kinetic.Layer(); //new object of kinetic layer
var imageObj = new Image(); //new image object to load image
var top_text;
var bottom_text;
//image onload event code
imageObj.onload = function() {
var image_shape = new Kinetic.Image({ //set image display dimensions
image: imageObj,
width: 735,
height: 540
});
top_text = new Kinetic.Text({ // Code to display top default text on meme image
x: 100,
y: 35,
text: top_text_val,
fontSize: 80,
fontFamily: 'Impact',
fill: '#fff',
align: "center",
stroke: 'black',
strokeWidth: 4,
});
layer.add(image_shape); // add the shape to the layer
layer.add(top_text); // add top text to the layer
stage.add(layer); // add the layer to the stage
};
if(image_link.trim().length>0) imageObj.src = image_link;
Now i want to set text and want to set text in Uppercase on keyup event of some text box. Please refer below code for same:
$('#txtTop').on('keyup',function(){
var value = $(this).val();
top_text.setText(value);
if($('#chkAllCaps').is(':checked')){
//here i need code to set uppercase text-transform
}
layer.draw();
});
I have tried text-transform also but it is not working.
Please kindly help me out.
Thanks!!
string.toUpperCase() would work in this case. The toUpperCase() method converts a string to uppercase letters and it would be better to add mouse click event on #chkAllCaps element and make a function for render.
This is jsfiddle: http://jsfiddle.net/6t7ohbnh/1/
Javascript
$('#txtTop').on('keyup', function() {
render();
});
$('#chkAllCaps').on('click', function() {
render();
});
function render(){
var value = $('#txtTop').val();
if ($('#chkAllCaps').is(':checked')) {
top_text.setText(value.toUpperCase());
}else{
top_text.setText(value);
}
layer.draw();
}

paint application with kinetic js

I have made a drag and drop application in HTML5 canvas with kinetic js. Can we also add the paint brush functionality to the same canvas using kinetic js? If yes, please share the link for one such application, and also the code, if possible.
You can use mouse events to let the user create a sketch on the canvas.
Here's how to let the user create a Kinetic polyline.
On mousedown:
Set a mousedown flag to true (indicating that the user is sketching)
Create a new Kinetic Line object
On mousemove:
Add the current mouse position to the points in the line object
Redraw the line that now includes the latest mouse position
On mouseup:
Clear the mousedown flag.
Repeat every time the user sketches a new polyline.
To let the user draw other Kinetic shapes (rect,circle,etc.) you have many options:
Have the user select which shape they want to create. Use mousedown + mouseup to get the bounds of the shape they want. Then add that kinetic shape with those bounds to the stage.
OR
Have the user select which shape they want to create. Create a generic version of that shape and put it on the stage. Let the user drag the generic shape to their desired position. Let the user customize that generic shape by dragging the bounds anchors.
OR
Have the user select which shape they want to create and have them text input the bounds. Create a generic version of that shape and put it on the stage. Let the user drag the generic shape to their desired position.
Really, there are so many options that the design is up to you.
Here is code and a Fiddle: http://jsfiddle.net/m1erickson/WW3sK/
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Prototype</title>
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<script src="http://www.html5canvastutorials.com/libraries/kinetic-v4.3.3-beta.js"></script>
<style>
#container{
border:solid 1px #ccc;
margin-top: 10px;
}
</style>
<script>
$(function(){
// create a stage and a layer
var stage = new Kinetic.Stage({
container: 'container',
width: 400,
height: 400
});
var layer = new Kinetic.Layer();
stage.add(layer);
// an empty stage does not emit mouse-events
// so fill the stage with a background rectangle
// that can emit mouse-events
var background = new Kinetic.Rect({
x: 0,
y: 0,
width: stage.getWidth(),
height: stage.getHeight(),
fill: 'white',
stroke: 'black',
strokeWidth: 1,
})
layer.add(background);
layer.draw();
// a flag we use to see if we're dragging the mouse
var isMouseDown=false;
// a reference to the line we are currently drawing
var newline;
// a reference to the array of points making newline
var points=[];
// on the background
// listen for mousedown, mouseup and mousemove events
background.on('mousedown', function(){onMousedown();});
background.on('mouseup', function(){onMouseup();});
background.on('mousemove', function(){onMousemove();});
// On mousedown
// Set the isMouseDown flag to true
// Create a new line,
// Clear the points array for new points
// set newline reference to the newly created line
function onMousedown(event) {
isMouseDown = true;
points=[];
points.push(stage.getMousePosition());
var line = new Kinetic.Line({
points: points,
stroke: "green",
strokeWidth: 5,
lineCap: 'round',
lineJoin: 'round'
});
layer.add(line);
newline=line;
}
// on mouseup end the line by clearing the isMouseDown flag
function onMouseup(event) {
isMouseDown=false;
}
// on mousemove
// Add the current mouse position to the points[] array
// Update newline to include all points in points[]
// and redraw the layer
function onMousemove(event) {
if(!isMouseDown){return;};
points.push(stage.getMousePosition());
newline.setPoints(points);
layer.drawScene();
}
}); // end $(function(){});
</script>
</head>
<body>
<div id="container"></div>
</body>
</html>
I have done something some weeks before. I don't know if it can help you.
http://jsfiddle.net/F3zwW/10/
var x;
var y;
var entry;
var isFinished = false;
circle.on('dragstart', function(evt) {
entry = new Kinetic.Circle({
x: evt.x,
y: evt.y,
radius: 10,
fill: 'red',
stroke: 'black',
strokeWidth: 2
});
group.add(entry);
layer.add(group);
entry.moveToTop();
});
circle.on('dragmove', function(evt) {
if (isFinished) return;
if (x != undefined && y != undefined) {
var line = new Kinetic.Line({
points: [x, y, evt.x, evt.y],
stroke: 'red',
strokeWidth: 20,
lineCap: 'round',
lineJoin: 'round'
});
length += Math.sqrt(Math.pow(evt.x - x, 2) + Math.pow(evt.y - y, 2));
group.add(line);
}
x = evt.x;
y = evt.y;
layer.add(group);
circle.moveToTop();
entry.moveToTop();
layer.draw();
if (length > 120) circle.fire('dragend');
});
circle.on('dragend', function(evt) {
if (isFinished) return;
var exit = new Kinetic.Circle({
x: x,
y: y,
radius: 10,
fill: 'red',
stroke: 'black',
strokeWidth: 2
});
group.add(exit);
layer.add(group);
circle.hide();
layer.draw();
isFinished = true;
});
Is that the behavior you are looking for ?
Here I wanted for some reasons to limit the length but you can easily remove this restriction.

Kinetic-js: How to cache a complete layer as an image

I have a problem an hope to find any solution for it.
I am using Kinetic.js to create a HMI solution with special look-and-feel. Therefor I have created a function that creates 3 layers for a stage: a background layer with a grid, a layer with static shapes for the base layout of the HMI-screen and the third layer for all interactive elements (like buttons, valuedisplays and so on...). Now I want to cache the grid and the static layer to improve performance, because this layers will never change until the whole HMI-screen will change...
As a test I started to code the caching for the grid layer using the following code:
// Create a grid layer if showGrid is set to TRUE...
console.log('Start to create background grid if required');
if (this.actualPageConfig.showGrid) {
var grid = new Kinetic.Layer();
for (var x=1; x <= this.cols; x++) {
var eLoc = LCARSElements.posToRealPos(x, 1, this.cols, this.rows);
if (x <= this.actualPageConfig.columns) {
grid.add(new Kinetic.Line({
points: [eLoc.x, eLoc.y, eLoc.x, eLoc.height],
stroke: "red",
strokeWidth: 1,
lineCap: "round",
lineJoin: "round"
}));
}
}
for (var y=1; y <= this.rows; y++) {
var eLoc = LCARSElements.posToRealPos(1, y, this.cols, this.rows);
if (y <= this.actualPageConfig.rows) {
grid.add(new Kinetic.Line({
points: [eLoc.x, eLoc.y, eLoc.width, eLoc.y],
stroke: "red",
strokeWidth: 1,
lineCap: "round",
lineJoin: "round"
}));
}
}
// Add grid layer to stage
//this.stage.add(grid); <-- to be replaced by cache image
// Convert grid into an image and add this to the stage
console.log('convert grid to image to increase performance');
grid.toImage({
width: displayManager.stage.getWidth(),
height: displayManager.stage.getHeight(),
callback: function(img) {
var cacheGrid = new Kinetic.Image({
image: img,
x: 0,
y: 0,
width: displayManager.stage.getWidth(),
height: displayManager.stage.getHeight()
});
console.log('insert grid-image to stage');
displayManager.stage.add(cacheGrid);
console.log('redraw stage...');
displayManager.stage.draw();
}
});
}
My problem is, that's not working. The grid is not visible any more and the console log shows the following error information:
Type error: layer.canvas is undefined
layer.canvas.setSize(this.attrs.width, this.attrs.height); kinetic.js (Zeile 3167)
As I already figured out the error rise when the code "displayManger.stage.add(cacheGrid) will be executed (displayManager is the outside-class where this code snipped reside).
Can anyone see where I made the mistake? When I directly add the layer grid anything works fine...
I have created a jsfiddle to demonstrate the problem: jsfiddle
In fiddle you can run both versions by changing one parameter. Hope this helps....
Thanks for help.
Best regards
Thorsten
Actually, the problem is simpler than you might think - after caching the layer into an image, you're trying to add an image object directly to the stage (you can't do that).
To fix the problem, you need to create a new layer, say cahcedLayer, add the image to cachedLayer, and then add cachedLayer to the stage.
Check out the KineticJS info page to learn more about Node nesting:
https://github.com/ericdrowell/KineticJS/wiki
http://rvillani.com/testes/layer-to-image/
I've made this test and it worked. First, I draw 1000 squares to a layer, add this layer to a hidden stage then make an image from this stage using stage.toDataURL(). When the callback returns, I just create an Image from the data and a Kinetic.Image from the Image. Then I add it to a layer on my main (visible) stage.
Code (be sure to have a div called 'invisible'):
window.onload = function()
{
var stage = new Kinetic.Stage({
width: 520,
height: 480,
container: 'container'
});
var outerStage = new Kinetic.Stage({
width: stage.getWidth(),
height: stage.getHeight(),
container: 'invisible'
});
var layerToCache = new Kinetic.Layer();
var layer = new Kinetic.Layer();
var group = new Kinetic.Group({offset: [stage.getWidth(), stage.getHeight()]});
var anim = new Kinetic.Animation(function(frame){
group.rotate(0.02);
}, layer);
var fills = ['red', 'green', 'blue', 'orange', 'purple', 'cyan',
'black', 'brown', 'forestgreen', 'gray', 'pink'];
for (var i = 0; i < 10000; ++i)
{
(function ()
{
var size = Math.random() * 60 + 20;
var square = new Kinetic.Rect({
width: size,
height: size,
fill: fills[i % fills.length],
x: Math.random() * stage.getWidth() - 20,
y: Math.random() * stage.getHeight() - 20
});
layerToCache.add(square);
})();
}
var squaresImg = new Kinetic.Image();
outerStage.add(layerToCache);
outerStage.toDataURL({
callback: function (dataURL){
outerStage.clear();
var img = new Image();
img.src = dataURL;
img.onload = function () {
squaresImg.setImage(img);
squaresImg.setX(squaresImg.getWidth() >> 1);
squaresImg.setY(squaresImg.getHeight() >> 1);
group.setX(stage.getWidth() >> 1);
group.setY(stage.getHeight() >> 1);
group.add(squaresImg);
layer.add(group);
layer.draw();
anim.start();
}
}
});
var div = document.getElementById('invisible');
div.parentNode.removeChild(div);
stage.add(layer);
}

Resources