Canvas saving and restoring - javascript

I have a canvas that i need to change the height for after every bit of content is added - the only way for me to get a dynamic canvas height. I'm trying to use save and restore so i don't lose all the styles, settings etc.
I can't get save and restore to work. I must be doing something wrong or is this the wrong approach?
function DrawLeftText(text) {
var canvas = document.getElementById('canvasPaper');
if (canvas.getContext) {
var context = canvas.getContext('2d');
context.textAlign = 'left';
context.font = 'normal 20px Monkton';
context.fillText(text, leftPosition, cursor);
context.textAlign = 'start';
context.save();
}
}
function restoreSettings(){
var canvas = document.getElementById('canvasPaper');
if (canvas.getContext) {
var context = canvas.getContext('2d');
context.restore();
}
}
function onDrawReceipt() {
var canvas = document.getElementById('canvasPaper');
if (canvas.getContext) {
var context = canvas.getContext('2d');
context.textBaseline = 'top';
lineSpace = 20;
leftPosition = 0;
// centerPosition = canvas.width / 2;
centerPosition = (canvas.width + 26) / 2;
// rightPosition = canvas.width;
rightPosition = (canvas.width - 0);
// cursor = 0;
cursor = 80;
context.fillRect(25, cursor - 2, 526, 2); cursor += lineSpace;
context.fillRect(25, cursor - 2, 526, 2); cursor += lineSpace;
DrawCenterText(company['name']); cursor += lineSpace;
DrawCenterText(company['address']['address_line_1']); cursor += lineSpace;
DrawCenterText(company['address']['city'].toUpperCase()); cursor += lineSpace;
DrawCenterText(company['address']['postcode']); cursor += lineSpace;
context.clearRect(0, 0, canvas.width, canvas.height += 20);
**//increasing the height and then trying to restore**
restoreSettings();
}
}
////// Additional attempt. I've tried the following too without luck.
////// None of the content appears despite setting the height before adding the content??
var header = function headerSection(){
var height = cursor;
canvas.height += height;
context.textBaseline = 'top';
lineSpace = 20;
leftPosition = 0;
centerPosition = (canvas.width + 26) / 2;
rightPosition = (canvas.width - 0);
console.log(height);
console.log(context);
context.fillRect(25, cursor - 2, 526, 2); cursor += lineSpace;
context.fillRect(25, cursor - 2, 526, 2); cursor += lineSpace;
DrawCenterText(company['name']); cursor += lineSpace;
DrawCenterText(company['address']['address_line_1']); cursor += lineSpace;
DrawCenterText(company['address']['city'].toUpperCase()); cursor += lineSpace;
DrawCenterText(company['address']['postcode']); cursor += lineSpace;
console.log(canvas.height);
return;
}
header()

I would suggest the following approach at the cost of a little more memory (if you're dealing with very large canvases you will of course have to weight in memory concern) -
Use an internal larger canvas representing a max size (or block size1)
Keep a value representing the current height (not actual)
Create a visual canvas in the document at the height stored in the value
For every draw operation, draw to the internal canvas
When done, set the visual canvas height using the current height value, draw internal canvas to visual one
Example 1
This example uses this technique to draw text at random color. As you can see we do not need to redraw the text when we change the visual canvas' size, we just copy everything from the internal canvas which also contains the current styles (font, font size, fill style).
var vcanvas = document.querySelector("canvas"),
vctx = vcanvas.getContext("2d"),
canvas = document.createElement("canvas"),
ctx = canvas.getContext("2d"),
line = 25,
maxHeight = 7 * line,
currentHeight = line,
chars = "abcdefghijklmnowxyzABCD#/&%/(#)=!LMNOPQRSTUVWXYZ".split(""),
x = 0, y = 0, ch;
vcanvas.height = currentHeight;
// internal canvas setup
ctx.font = "20px sans-serif";
ctx.textBaseline = "top";
// draw someting to internal canvas:
(function loop() {
ch = chars[(Math.random() * chars.length)|0];
ctx.fillStyle = "hsl(" + (360*Math.random()) + ",75%,50%)";
ctx.fillText(ch, x, y);
x += ctx.measureText(ch).width;
if (x > canvas.width) {
x = 0; y += line; currentHeight += line;
}
// copy to visual canvas:
if (currentHeight < maxHeight) vcanvas.height = currentHeight;
vctx.drawImage(canvas, 0, 0, canvas.width, currentHeight,
0, 0, canvas.width, currentHeight);
if (currentHeight < maxHeight) setTimeout(loop, 50);
})();
body{background:#eee} canvas{background:#fff}
canvas{border:1px solid #000}
<canvas></canvas>
1) If you are dealing with "infinite" height you want to split the process into blocks. Set internal canvas to block size, when exceeded enlarge internal canvas with new block size - but here you will have to do full redraw and setup again.
And this leads to option 2: The latter technique can be used directly with the visual canvas as well, and you can use CSS to clip it putting it inside a div element which you style with height and overflow:hidden.
Example 2
var canvas = document.querySelector("canvas"),
ctx = canvas.getContext("2d"),
div = document.querySelector("div"),
line = 25,
maxHeight = 7 * line,
currentHeight = line,
chars = "abcdefghijklmnowxyzABCD#/&%/(#)=!LMNOPQRSTUVWXYZ".split(""),
x = 0, y = 0, ch;
// set canvas to max height, div to clip height
canvas.height = maxHeight;
div.style.height = currentHeight + "px";
// canvas setup
ctx.font = "20px sans-serif";
ctx.textBaseline = "top";
// draw someting to the canvas:
(function loop() {
ch = chars[(Math.random() * chars.length)|0];
ctx.fillStyle = "hsl(" + (360*Math.random()) + ",75%,50%)";
ctx.fillText(ch, x, y);
x += ctx.measureText(ch).width;
if (x > canvas.width) {
x = 0; y += line; currentHeight += line;
}
// copy to visual canvas:
if (currentHeight < maxHeight) {
div.style.height = currentHeight + "px"; // increase clipping height
setTimeout(loop, 50)
}
})();
body{background:#eee} canvas{background:#fff}
div{border:1px solid #000; overflow:hidden; width:302px}
<div><canvas></canvas></div>

Resizing the canvas element will always reset its context to the default style states. (.save & .restore will not let the context styles survive a resizing)
The common canvas pattern to deal with changes to canvas content is:
Save data (eg your company)
Either save context styles as javascript variables or embed the style changes in functions (eg a specialized function to set appropriate styles and redraw the company heading).
Resize (or clear) the canvas
Redraw all the content using the saved data and saved styles/functions.
Example code:
Click "Full page" to see full receipt
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var lineSpace=20;
var leftOfForm=25;
var topOfForm=80;
var testingItems=110;
var company={
name:'MyCompany, Inc',
address:{
address_line_1: '123 Main Street',
city:'Anytown, Anywhere',
postcode:'12345'
}
}
var lineItems=[];
addLineItem(testingItems,'Description of sku#'+testingItems,testingItems);
$('#test').click(function(){
testingItems++;
addLineItem(testingItems,'Description of sku#'+testingItems,testingItems);
draw();
});
draw();
function addLineItem(sku,description,price){
lineItems.push({
sku:sku,
description:description,
price:price
});
}
function draw(){
// note: changing canvas.height auto-erases the content also
canvas.height=topOfForm+(6+2+lineItems.length)*lineSpace;
ctx.strokeRect(0,0,canvas.width,canvas.height);
drawHeader(leftOfForm,topOfForm);
for(var i=0;i<lineItems.length;i++){
drawLineItem(lineItems[i],leftOfForm,topOfForm+(6+2+i)*lineSpace);
}
}
function drawHeader(left,cursor){
var cx=canvas.width/2;
var line=function(linecount){ return(cursor+lineSpace*linecount); }
ctx.save();
ctx.beginPath();
ctx.moveTo(left,line(0)-2);
ctx.lineTo(526,line(0)-2);
ctx.moveTo(left,line(1)-2);
ctx.lineTo(526,line(1)-2);
ctx.lineWidth=2;
ctx.stroke();
ctx.font='18px verdana';
ctx.textAlign='center';
ctx.textBaseline='top';
ctx.fillText(company.name,cx,line(2));
ctx.fillText(company.address.address_line_1,cx,line(3));
ctx.fillText(company.address.city,cx,line(4));
ctx.fillText(company.address.postcode,cx,line(5));
ctx.restore();
}
function drawLineItem(item,left,cursor){
ctx.save();
ctx.font='14px verdana';
ctx.textAlign='left';
ctx.textBaseline='top';
ctx.fillText(item.sku,left,cursor);
ctx.fillText(item.description,left+40,cursor);
ctx.textAlign='right';
ctx.fillText(item.price,left+450,cursor);
ctx.restore();
}
body{ background-color: ivory; padding:10px; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<button id=test>Add another line</button>
<br>
<canvas id="canvas" width=550 height=300></canvas>

Related

Make a shape move up on a canvas

Currently, I have a canvas which is the width and height of your browser. Using this code:
var requestAnimationFrame = window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame;
var canvas = document.getElementById("canvas");
var width = window.innerWidth;
var height = window.innerHeight;
var circle = canvas.getContext("2d");
canvas.width = width;
canvas.height = height;
for(var i = 0; i < numofcirc; i++)
{
name = "circleno" + i;
var name = new Array(3);
name = [height, rndwidth, rndradius, vel]
circles[i] = name;
}
var vel = 2;
var circles = [];
var numofcirc = 1;
var name;
function DrawCircle()
{
rndwidth = Math.floor((Math.random() * width) + 1);
height = height - 13;
rndradius = Math.floor((Math.random() * 15) + 5);
circle.beginPath();
circle.arc(rndwidth, height, rndradius, 0, 2*Math.PI);
circle.fillStyle = "white";
circle.fill();
circle.translate(0,6);
}
function Move()
{
circle.translate(0,6);
requestAnimationFrame(Move);
}
Move();
DrawCircle();
I am able to create a circle placed randomly at the bottom of your screen. The bit of the code that isn't working is this:
function Move()
{
circle.translate(0,6);
requestAnimationFrame(Move);
}
Fireworks();
When DrawCircle(); is called, the circle is drawn on the canvas. Then Move(); is called. Becuase it uses requestAnimationFrame the function Move(); repeats over and over again. I want this code to move that circle drawn ealier up by 6, so it looks like the circle moving up.
If I add the circle.translate(0,6); to the DrawCircle(); function and change the DrawCircle(); function to this:
function DrawCircle()
{
rndwidth = Math.floor((Math.random() * width) + 1);
height = height - 13;
rndradius = Math.floor((Math.random() * 15) + 5);
circle.beginPath();
circle.arc(rndwidth, height, rndradius, 0, 2*Math.PI);
circle.fillStyle = "white";
circle.fill();
circle.translate(0,6);
requestAnimationFrame(Move);
}
DrawCircle();
then it keeps on drawing rows of circles across the screen which are all separated by 6.
Is there any way I can just make one single circle move up on your screen when it is drawn?
Thank you for you help #HelderSepu !
You should look at examples and build from that...
Here is one simple case:
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
canvas.width = canvas.height = 170;
var circles = []
circles.push({color:"red", x:120, y:120, r:15, speed:{x: 0, y: -0.5}})
circles.push({color:"blue", x:80, y:120, r:20, speed:{x: -0.5, y: -2.5}})
circles.push({color:"green", x:40, y:120, r:5, speed:{x: -1.5, y: -1.0}})
function DrawCircle() {
context.clearRect(0, 0, canvas.width, canvas.height);
circles.forEach(function(c) {
c.x += c.speed.x;
c.y += c.speed.y;
context.beginPath();
context.arc(c.x, c.y, c.r, 0, 2 * Math.PI);
context.fillStyle = c.color;
context.fill();
if (c.x + c.r < 0) c.x = canvas.width + c.r
if (c.y + c.r < 0) c.y = canvas.height + c.r
});
window.requestAnimationFrame(DrawCircle);
}
DrawCircle();
<canvas id="canvas"></canvas>
But if you are going to do a lot more animations you should consider using a game engine, there are a lot of great open source ones:
https://github.com/collections/javascript-game-engines
Since you're getting a sequence of circles, it looks like you're not clearing the canvas when a frame is drawn. Simply draw a white rectangle that fills the canvas whenever a new frame is requested, then draw your circle.
The method you provide as an argument to requestAnimationFrame is responsible for drawing a complete image on the canvas which replaces whatever was there during the previous frame.

HTML Canvas Trying to create an animated chain of rectangle with slight delay/distance between them

I am trying to create multiple animated rectangles using Html Canvas with requestAnimationFrame. As for now, I managed to do exactly what I wanted with only one animated rectangle, but I can't find out how to create more rectangles that would simply be in line and follow each other with an equal distance.
Also, there's a random data (I, II or III) inside each rectangle.
Here's my actual code:
//Referencing canvas
var canvas = document.getElementById("my-canvas");
var ctx = canvas.getContext("2d");
//Make Canvas fullscreen and responsive
function resize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
window.addEventListener('resize', resize, false); resize();
//FPS
var framesPerSecond = 60;
//Default Y pos to center;
var yPos = canvas.height / 2;
//Default X pos offset
var xPos = -150;
//Speed (increment)
var speed = 2;
//Our array to store rectangles objects
var rectangles = [] ;
//Dynamic Number from database
var quote = ["I", "II", "III"];
//Random number for testing purpose
var rand = quote[Math.floor(Math.random() * quote.length)];
//Draw Rectangle
function drawRectangle () {
setTimeout(function() {
requestAnimationFrame(drawRectangle);
ctx.clearRect(0, 0, canvas.width, canvas.height);
//Background color
ctx.fillStyle = "yellow";
//Position, size.
var rectWidth = 70;
var rectHeigth = 55;
ctx.fillRect(xPos,yPos,rectWidth,rectHeigth);
ctx.font = "32px Arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillStyle = "black";
//Data Layer
var dataLayer = ctx.fillText(rand,xPos+(rectWidth/2),yPos+(rectHeigth/2));
xPos += speed;
//Infinite loop for test
if (xPos > 1080) {
xPos = -150;
}
}, 1000 / framesPerSecond);
}
drawRectangle ();
canvas {background-color: #131217}
body { margin: 0; overflow: hidden; }
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Moving Blocks</title>
<style>
canvas {background-color: #131217}
body { margin: 0; overflow: hidden; }
</style>
</head>
<body>
<canvas id="my-canvas"></canvas>
</body>
</html>
Animating arrays of objects.
For animations you are best of using a single render function that renders all the objects once a frame, rather than create a separate render frame per object.
As for the squares there are many ways that you can get them to do what you want. It is a little difficult to answer as what you want is not completely clear.
This answer will use a rectangle object that has everything needed to be rendered and move. The rectangles will be kept in an array and the main render function will update and render each rectangle in turn.
There will be a spawn function that creates rectangles untill the limit has been reached.
// constants up the top
const quote = ["I", "II", "III"];
// function selects a random Item from an array
const randItem = (array) => array[(Math.random() * array.length) | 0];
// array to hold all rectangles
const rectangles = [];
var maxRectangles = 20;
const spawnRate = 50; // number of frames between spawns
var spawnCountdown = spawnRate;
//Referencing canvas
const ctx = canvas.getContext("2d");
var w, h; // global canvas width and height.
resizeCanvas(); // size the canvas to fit the page
requestAnimationFrame(mainLoop); // this will start when all code below has been run
function mainLoop() {
// resize in the rendering frame as using the resize
// event has some issuse and this is more efficient.
if (w !== innerWidth || h !== innerHeight) {
resizeCanvas();
}
ctx.clearRect(0, 0, w, h);
spawnRectangle(); // spawns rectangles
updateAllRectangles(); // moves all active rectangles
drawAllRectangles(); // I will let you gues what this does... :P
requestAnimationFrame(mainLoop);
}
function resizeCanvas() {
w = canvas.width = innerWidth;
h = canvas.height = innerHeight;
// and reset any canvas constants
ctx.font = "32px Arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
}
// function to spawn a rectangle
function spawnRectangle() {
if (rectangles.length < maxRectangles) {
if (spawnCountdown) {
spawnCountdown -= 1;
} else {
rectangles.push(
createRectangle({
y: canvas.height / 2, // set at center
text: randItem(quote),
dx: 2, // set the x speed
})
);
spawnCountdown = spawnRate;
}
}
}
// define the default rectangle
const rectangle = {
x: -40, // this is the center of the rectangle
y: 0,
dx: 0, // delta x and y are the movement per frame
dy: 0,
w: 70, // size
h: 55,
color: "yellow",
text: null,
textColor: "black",
draw() { // function to draw this rectangle
ctx.fillStyle = this.color;
ctx.fillRect(this.x - this.w / 2, this.y - this.h / 2, this.w, this.h);
ctx.fillStyle = this.textColor;
ctx.fillText(this.text, this.x, this.y);
},
update() { // moves the rectangle
this.x += this.dx;
this.y += this.dy;
if (this.x > canvas.width + this.w / 2) {
this.x = -this.w / 2;
// if the rectangle makes it to the other
// side befor all rectangles are spawnd
// then reduce the number so you dont get any
// overlap
if (rectangles.length < maxRectangles) {
maxRectangles = rectangles.length;
}
}
}
}
// creats a new rectangle. Setting can hold any unique
// data for the rectangle
function createRectangle(settings) {
return Object.assign({}, rectangle, settings);
}
function updateAllRectangles() {
var i;
for (i = 0; i < rectangles.length; i++) {
rectangles[i].update();
}
}
function drawAllRectangles() {
var i;
for (i = 0; i < rectangles.length; i++) {
rectangles[i].draw();
}
}
canvas {
position: absolute;
top: 0px;
left: 0px;
background-color: #131217;
}
body {
margin: 0;
overflow: hidden;
}
<canvas id="canvas"></canvas>

Make canvas transparent

This is what my body looks like:
body
{
background-image:url('../images/bg.png');
background-repeat:no-repeat;
background-size:fixed 100vw;
background-position:center;
}
The issue is, the canvas is white instead of being transparent. Is there a way to make it transparent so I can place the dna wave on top of a background?
Codepen example
One easy way, is using an offscreen canvas.
First set its context's globalAlpha value to something between 0 and 1, this will determine how fast your previous drawings will disappear.
Then, in the animation loop, before doing the new drawings,
clear the offscreen context,
draw the visible canvas on the offscreen one,
clear the visible canvas
draw back the offscreen one on the visible one
In the process, your image will have lost opacity.
var clear = function(){
// clear the clone canvas
cloneCtx.clearRect(0,0,canvasWidth, canvasHeight)
// this should be needed at init and when canvas is resized but for demo I leave it here
cloneCtx.globalAlpha = '.8';
// draw ou visible canvas, a bit less opaque
cloneCtx.drawImage(context.canvas, 0,0)
// clear the visible canvas
context.clearRect(0,0,canvasWidth, canvasHeight)
// draw back our saved less-opaque image
context.drawImage(clone, 0,0)
}
var canvas = document.getElementById('canvas'),
context = canvas.getContext('2d'),
// create an offscreen clone
clone = canvas.cloneNode(),
cloneCtx = clone.getContext('2d'),
canvasWidth = canvas.width =
clone.width =window.innerWidth,
canvasHeight = canvas.height = clone.height = window.innerHeight,
globalTick = 0,
points = [],
pointCount = 12,
pointSpeed = 6,
spacing = canvasWidth / pointCount,
pointCount = pointCount + 2,
verticalPointRange = 60,
randomRange = function(min, max){
return Math.floor( (Math.random() * (max - min + 1) ) + min);
},
iPath,
iPoints;
var Point = function(x, y, alt){
this.x = x;
this.y = y;
this.yStart = y;
this.alt = alt;
}
Point.prototype.update = function(i){
var range = (this.alt) ? verticalPointRange : -verticalPointRange;
this.x += pointSpeed;
this.y = (this.yStart) + Math.sin(globalTick/14) * -range;
if(this.x > (canvasWidth + spacing)){
this.x = -spacing;
var moved = points.splice(i, 1);
points.unshift(moved[0]);
}
}
var updatePoints = function(){
var i = points.length;
while(i--){
points[i].update(i);
}
}
for(iPoints = 0; iPoints < pointCount; iPoints++){
var alt = (iPoints % 2 === 0);
var offset = (alt) ? verticalPointRange : -verticalPointRange;
points.push(new Point(spacing * (iPoints-1), canvasHeight/2, alt));
}
var renderPath = function(){
context.beginPath();
context.moveTo(points[0].x, points[0].y);
for(iPath = 1; iPath < pointCount; iPath++){
context.lineTo(points[iPath].x, points[iPath].y);
}
context.stroke();
}
var loop = function(){
requestAnimationFrame(loop, canvas);
clear();
updatePoints();
renderPath();
globalTick++;
};
loop();
canvas { display: block; }
body{
background-color: ivory;
}
<canvas id="canvas"></canvas>
Canvases are transparent by default.
Try setting a page background image, and then put a canvas over it. If nothing is drawn on the canvas, you can fully see the page background.
you should try
context.clearRect(0,0,width,height);
for more you can refer How do I make a transparent canvas in html5?

html5 canvas: auto font size for drawn wrapped rotated text

suppose that there is a text to be drawn inside a rotated bounding rectangle (not aligned to normal axes x-y), and that text can be also rotated,
given the max width of the bounding box, how to select the best font size to use to draw a wrapped text inside that bounding box in html5 canvas and javascript?
I know that method: measureText() can measure dimensions of give font size, but I need the inverse of that: using a known width to get the problem font size.
thanks
You do not have to find the font point size to make it fit. The font will smoothly scale up and down according to the current transformation scale.
All you do is measureText to find its textWidth, get the pointSize from the context.font attribute then if you have the width and height of the box you need to fit then find the minimum of the width / textWidth and height / pointSize and you have the scale that you need to render the font at.
As a function
var scale2FitCurrentFont = function(ctx, text, width, height){
var points, fontWidth;
points = Number(ctx.font.split("px")[0]); // get current point size
points += points * 0.2; // As point size does not include hanging tails and
// other top and bottom extras add 20% to the height
// to accommodate the extra bits
var fontWidth = ctx.measureText(text).width;
// get the max scale that will allow the text to fi the current font
return Math.min(width / fontWidth, height / points);
}
The arguments are
ctx is current context to draw to
text the text to draw
width the width to fit the text to
height the height to fit the text to
Returns the scale to fit the text within the width and height.
The demo has it all integrated and it draws random boxes and fills with random text from your question. It keeps the font selection and point size separate from the font scaling so you can see it will work for any font and any point size.
var demo = function(){
/** fullScreenCanvas.js begin **/
var canvas = (function(){
var canvas = document.getElementById("canv");
if(canvas !== null){
document.body.removeChild(canvas);
}
// creates a blank image with 2d context
canvas = document.createElement("canvas");
canvas.id = "canv";
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
canvas.style.position = "absolute";
canvas.style.top = "0px";
canvas.style.left = "0px";
canvas.style.zIndex = 1000;
canvas.ctx = canvas.getContext("2d");
document.body.appendChild(canvas);
return canvas;
})();
var ctx = canvas.ctx;
/** fullScreenCanvas.js end **/
/** FrameUpdate.js begin **/
var w = canvas.width;
var h = canvas.height;
var cw = w / 2;
var ch = h / 2;
var PI2 = Math.PI * 2; // 360 to save typing
var PIh = Math.PI / 2; // 90
// draws a rounded rectangle path
function roundedRect(ctx,x, y, w, h, r){
ctx.beginPath();
ctx.arc(x + r, y + r, r, PIh * 2, PIh * 3);
ctx.arc(x + w - r, y + r, r, PIh * 3, PI2);
ctx.arc(x + w - r, y + h - r, r, 0, PIh);
ctx.arc(x + r, y + h - r, r, PIh, PIh * 2);
ctx.closePath();
}
// random words
var question = "Suppose that there is a text to be drawn inside a rotated bounding rectangle (not aligned to normal axes x-y), and that text can be also rotated, given the max width of the bounding box, how to select the best font size to use to draw a wrapped text inside that bounding box in html5 canvas and javascript? I know that method: measureText() can measure dimensions of give font size, but I need the inverse of that: using a known width to get the problem font size. thanks.";
question = question.split(" ");
var getRandomWords= function(){
var wordCount, firstWord, s, i, text;
wordCount = Math.floor(rand(4)+1);
firstWord = Math.floor(rand(question.length - wordCount));
text = "";
s = "";
for(i = 0; i < wordCount; i++){
text += s + question[i + firstWord];
s = " ";
}
return text;
}
// fonts to use?? Not sure if these are all safe for all OS's
var fonts = "Arial,Arial Black,Verdanna,Comic Sans MS,Courier New,Lucida Console,Times New Roman".split(",");
// creates a random font with random points size in pixels
var setRandomFont = function(ctx){
var size, font;
size = Math.floor(rand(10, 40));
font = fonts[Math.floor(rand(fonts.length))];
ctx.font = size + "px " + font;
}
var scale2FitCurrentFont = function(ctx, text, width, height){
var points, fontWidth;
var points = Number(ctx.font.split("px")[0]); // get current point size
points += points * 0.2;
var fontWidth = ctx.measureText(text).width;
// get the max scale that will allow the text to fi the current font
return Math.min(width / fontWidth, height / points);
}
var rand = function(min, max){
if(max === undefined){
max = min;
min = 0;
}
return Math.random() * (max - min)+min;
}
var randomBox = function(ctx){
"use strict";
var width, height, rot, dist, x, y, xx, yy,cx, cy, text, fontScale;
// get random box
width = rand(40, 400);
height = rand(10, width * 0.4);
rot = rand(-PIh,PIh);
dist = Math.sqrt(width * width + height * height)
x = rand(0, ctx.canvas.width - dist);
y = rand(0, ctx.canvas.height - dist);
xx = Math.cos(rot);
yy = Math.sin(rot);
ctx.fillStyle = "white";
ctx.strokeStyle = "black";
ctx.lineWidth = 2;
// rotate the box
ctx.setTransform(xx, yy, -yy, xx, x, y);
// draw the box
roundedRect(ctx, 0, 0, width, height, Math.min(width / 3, height / 3));
ctx.fill();
ctx.stroke();
// get some random text
text = getRandomWords();
// get the scale that will fit the font
fontScale = scale2FitCurrentFont(ctx, text, width - textMarginLeftRigth * 2, height - textMarginTopBottom * 2);
// get center of rotated box
cx = x + width / 2 * xx + height / 2 * -yy;
cy = y + width / 2 * yy + height / 2 * xx;
// scale the transform
xx *= fontScale;
yy *= fontScale;
// set the font transformation to fit the box
ctx.setTransform(xx, yy, -yy, xx, cx, cy);
// set up the font render
ctx.fillStyle = "Black";
ctx.textAlign = "center";
ctx.textBaseline = "middle"
// draw the text to fit the box
ctx.fillText(text, 0, 0);
}
var textMarginLeftRigth = 8; // margin for fitted text in pixels
var textMarginTopBottom = 4; // margin for fitted text in pixels
var drawBoxEveryFrame = 60; // frames between drawing new box
var countDown = 1;
// update function will try 60fps but setting will slow this down.
function update(){
// restore transform
ctx.setTransform(1, 0, 0, 1, 0, 0);
// fade clears the screen
ctx.fillStyle = "white"
ctx.globalAlpha = 1/ (drawBoxEveryFrame * 1.5);
ctx.fillRect(0, 0, w, h);
// reset the alpha
ctx.globalAlpha = 1;
// count frames
countDown -= 1;
if(countDown <= 0){ // if frame count 0 the draw another text box
countDown = drawBoxEveryFrame;
setRandomFont(ctx);
randomBox(ctx);
}
if(!STOP){ // do until told to stop.
requestAnimationFrame(update);
}else{
STOP = false;
}
}
update();
}
// demo code to restart on resize
var STOP = false; // flag to tell demo app to stop
function resizeEvent(){
var waitForStopped = function(){
if(!STOP){ // wait for stop to return to false
demo();
return;
}
setTimeout(waitForStopped,200);
}
STOP = true;
setTimeout(waitForStopped,100);
}
window.addEventListener("resize",resizeEvent);
demo();
/** FrameUpdate.js end **/

Convert squares to circles in canvas html

OK so I appreciate that this is a massively basic question but I'm totally new to canvas and I just need to do something simple. Basically I am using springy.js to draw force directed graphs. The nodes on the graph are squares and I just want them to be circles. Can someone show me what I should change in the code below and I can figure out the rest from there
I tried
ctx.arc(s.x - boxWidth/2, s.y - boxHeight/2, boxWidth, boxHeight,2*Math.PI);
ctx.stroke();
instead of the line with clearRect but the boxes remain and the connections between boxes stop being straight lines.
function drawNode(node, p) {
var s = toScreen(p);
ctx.save();
// Pulled out the padding aspect sso that the size functions could be used in multiple places
// These should probably be settable by the user (and scoped higher) but this suffices for now
var paddingX = 6;
var paddingY = 6;
var contentWidth = node.getWidth();
var contentHeight = node.getHeight();
var boxWidth = contentWidth + paddingX;
var boxHeight = contentHeight + paddingY;
// clear background
ctx.clearRect(s.x - boxWidth/2, s.y - boxHeight/2, boxWidth, boxHeight);
// fill background
if (selected !== null && selected.node !== null && selected.node.id === node.id) {
ctx.fillStyle = "#FFFFE0"; //when clicked
} else if (nearest !== null && nearest.node !== null && nearest.node.id === node.id) {
ctx.fillStyle = "#EEEEEE";//when hovered over
} else {
//if the node.FBScore >10 then ctx.fillStyle = "#F00909";
ctx.fillStyle = "#E34747";//normal colour
}
ctx.fillRect(s.x - boxWidth/2, s.y - boxHeight/2, boxWidth, boxHeight);
if (node.data.image == undefined) {
ctx.textAlign = "left";
ctx.textBaseline = "top";
ctx.font = (node.data.font !== undefined) ? node.data.font : nodeFont;
ctx.fillStyle = (node.data.color !== undefined) ? node.data.color : "#000000";
var text = (node.data.label !== undefined) ? node.data.label : node.id;
ctx.fillText(text, s.x - contentWidth/2, s.y - contentHeight/2);
} else {
// Currently we just ignore any labels if the image object is set. One might want to extend this logic to allow for both, or other composite nodes.
var src = node.data.image.src; // There should probably be a sanity check here too, but un-src-ed images aren't exaclty a disaster.
if (src in nodeImages) {
if (nodeImages[src].loaded) {
// Our image is loaded, so it's safe to draw
ctx.drawImage(nodeImages[src].object, s.x - contentWidth/2, s.y - contentHeight/2, contentWidth, contentHeight);
}
}else{
// First time seeing an image with this src address, so add it to our set of image objects
// Note: we index images by their src to avoid making too many duplicates
nodeImages[src] = {};
var img = new Image();
nodeImages[src].object = img;
img.addEventListener("load", function () {
// HTMLImageElement objects are very finicky about being used before they are loaded, so we set a flag when it is done
nodeImages[src].loaded = true;
});
img.src = src;
}
}
ctx.restore();
}
Instead of replacing the clearRect() method, you should replace the fillRect() with the arc(x, y, radius, startAngle, endAngle, anticlockwise); one :
var canvas = document.querySelector('canvas');
var ctx = canvas.getContext('2d');
function drawNode() {
var s = {
x: (Math.random() * 200) + 50,
y: (Math.random() * 200) + 50
};
var paddingX = 6;
var paddingY = 6;
var contentWidth = s.x / 5;
var contentHeight = s.y / 5;
var boxWidth = contentWidth + paddingX;
var boxHeight = contentHeight + paddingY;
ctx.fillStyle = '#AAFFAA';
// I modified it so the whole canvas will be cleared
ctx.clearRect(0, 0, canvas.width, canvas.height);
// We start a new path
ctx.beginPath();
// then we draw our circle, setting its radius to the max between contentWidth and contentHeight
ctx.arc(s.x, s.y , Math.max(boxWidth,boxHeight)/2, 0, 2 * Math.PI);
// and finally we fill it
ctx.fill();
}
document.addEventListener('click', drawNode);
drawNode();
canvas{cursor:pointer};
<canvas height="300" width="300" />
I have create a simple jsFiddle to show how to draw a curved corner rectangle
-- Updated so you can now simply change the roundedValue which will then change the smoothness of the corners.
http://jsfiddle.net/gHCJt/1127/
// Get canvas
var canvas = $("#canvas");
var context = canvas.get(0).getContext("2d");
// Draw simple rect
var rectX = 125;
var rectY = 125;
var rectWidth = 150;
var rectHeight = 150;
var roundedValue = 75;
// Apply corner
context.lineJoin = "round";
context.lineWidth = roundedValue;
// Apply the corner to the draw method for strokeRect and fillRect
context.strokeRect(rectX+(roundedValue/2), rectY+(roundedValue/2), rectWidth-roundedValue, rectHeight-roundedValue);
context.fillRect(rectX+(roundedValue/2), rectY+(roundedValue/2), rectWidth-roundedValue, rectHeight-roundedValue);

Categories

Resources