Add background image & draggable images to HTML5 canvas - javascript

This is my canvas image text editor and I want to add a background image and an image that can drag and drop. I found code snippets to do things like that but with my code it is somewhat not working.
Code here
<style>canvas{width:450px; height:350px;}</style>
<canvas width="792" height="612"></canvas>
<a id="canvas-download" download="canvas-image.png" href="">Download</a>
<input type="file" id="bgload" name="bgload" />
<input type="file" id="logoload" name="logoload" />
<input id="title" value="[Certificate Title]"/>
<input id="present" value="[Presented to]" />
<input id="receiver" value="[Type Name Here]" />
<input id="awardDate" value="[Awarding Date]" />
<input id="signature" value="[Signature]" />
<script>
$(function () {
var cvs = $("canvas"),
cvsWidth = 792,
cvsHeight = 612,
ctx = cvs[0].getContext("2d"),
title = $("#title"),
present = $("#present"),
receiver = $("#receiver"),
awardDate = $("#awardDate"),
signature = $("#signature");
function writeCaption(text, y, size, x) {
var size = size;
do {
size--;
ctx.font = size + 'px Georgia';
ctx.lineWidth = size / 32;
} while (ctx.measureText(text).width > cvsWidth)
if (x == 0)
ctx.fillText(text, cvsWidth / 2, y);
else
ctx.fillText(text, x, y);
//ctx.strokeText(text, cvsWidth / 2, y);
}
// Setup basic canvas settings
$.extend(ctx, {
//strokeStyle: "#000000",
textAlign: 'center',
fillStyle: "#000",
lineCap: "round"
})
$("<img />")
.load(function () {
var img = this;
$(document.body).on("keyup", function () {
var titleText = title.val(),
presentText = present.val(),
signatureText = signature.val(),
awardDateText = awardDate.val(),
receiverText = receiver.val();
ctx.clearRect(0, 0, cvsWidth, cvsHeight);
ctx.drawImage(img, 0, 0, cvsWidth, cvsHeight);
//text alignments
ctx.textBaseline = 'top';
writeCaption(titleText, 150, 40, 0);
ctx.textBaseline = 'top';
writeCaption(presentText, 240, 20, 0);
ctx.textBaseline = 'top';
writeCaption(receiverText, 270, 30, 0);
ctx.textBaseline = 'bottom';
writeCaption(awardDateText, 490, 20, 300);
ctx.textBaseline = 'bottom';
writeCaption(signatureText, 490, 20, cvsWidth - 300 + ctx.measureText(signatureText).width);
}).trigger("keyup");
})
.attr("src", "<?php echo _SITE_URL; ?>images/editor/Template1.jpg")
.attr("id", "canvasimg");
var download = document.getElementById('canvas-download');
download.addEventListener('click', function () {
var canvas = document.getElementById('cs_canvas');
var data = canvas.toDataURL();
download.href = data;
}, false);
});
</script>
Here's the jsfiddle link for the working example.
http://jsfiddle.net/57gfqsm3/7/
Please help

I spent a long time trying to drag and drop images on a canvas tag this and eventually used kinetic.js.
https://github.com/ericdrowell/KineticJS/
I'll watch this post in case anyone has a better solution.

I found a sophisticated javascript canvas library called Fabric.
Click Here

Related

How to not erase background in canvas JS?

So I made this canvas on which you can paint on. The problem is that when you erase your drawings it will also erase the background.
// SETTING ALL VARIABLES
var isMouseDown=false;
var canvas = document.createElement('canvas');
var body = document.getElementsByTagName("body")[0];
var ctx = canvas.getContext('2d');
var linesArray = [];
currentSize = 5;
var currentColor = "rgb(200,20,100)";
var currentBg = "white";
let newImage = new Image();
newImage.src = 'https://www.arnoldvanhooft.nl/wp-content/uploads/2019/06/ja-knop.png'
// INITIAL LAUNCH
newImage.onload = () => {
ctx.drawImage(newImage, 0, 0, 500, 500);
}
createCanvas();
// BUTTON EVENT HANDLERS
document.getElementById('canvasUpdate').addEventListener('click', function() {
createCanvas();
redraw();
});
document.getElementById('colorpicker').addEventListener('change', function() {
currentColor = this.value;
});
document.getElementById('bgcolorpicker').addEventListener('change', function() {
ctx.fillStyle = this.value;
ctx.fillRect(0, 0, canvas.width, canvas.height);
redraw();
currentBg = ctx.fillStyle;
});
document.getElementById('controlSize').addEventListener('change', function() {
currentSize = this.value;
document.getElementById("showSize").innerHTML = this.value;
});
document.getElementById('saveToImage').addEventListener('click', function() {
downloadCanvas(this, 'canvas', 'masterpiece.png');
}, false);
document.getElementById('eraser').addEventListener('click', eraser);
document.getElementById('clear').addEventListener('click', createCanvas);
document.getElementById('save').addEventListener('click', save);
document.getElementById('load').addEventListener('click', load);
document.getElementById('clearCache').addEventListener('click', function() {
localStorage.removeItem("savedCanvas");
linesArray = [];
console.log("Cache cleared!");
});
// REDRAW
function redraw() {
for (var i = 1; i < linesArray.length; i++) {
ctx.beginPath();
ctx.moveTo(linesArray[i-1].x, linesArray[i-1].y);
ctx.lineWidth = linesArray[i].size;
ctx.lineCap = "round";
ctx.strokeStyle = linesArray[i].color;
ctx.lineTo(linesArray[i].x, linesArray[i].y);
ctx.stroke();
}
}
// DRAWING EVENT HANDLERS
canvas.addEventListener('mousedown', function() {mousedown(canvas, event);});
canvas.addEventListener('mousemove',function() {mousemove(canvas, event);});
canvas.addEventListener('mouseup',mouseup);
// CREATE CANVAS
function createCanvas() {
canvas.id = "canvas";
canvas.width = parseInt(document.getElementById("sizeX").value);
canvas.height = parseInt(document.getElementById("sizeY").value);
canvas.style.zIndex = 8;
canvas.style.position = "absolute";
canvas.style.border = "1px solid";
ctx.fillStyle = currentBg;
ctx.fillRect(0, 0, canvas.width, canvas.height);
body.appendChild(canvas);
}
// DOWNLOAD CANVAS
function downloadCanvas(link, canvas, filename) {
link.href = document.getElementById(canvas).toDataURL();
link.download = filename;
}
// SAVE FUNCTION
function save() {
localStorage.removeItem("savedCanvas");
localStorage.setItem("savedCanvas", JSON.stringify(linesArray));
console.log("Saved canvas!");
}
// LOAD FUNCTION
function load() {
if (localStorage.getItem("savedCanvas") != null) {
linesArray = JSON.parse(localStorage.savedCanvas);
var lines = JSON.parse(localStorage.getItem("savedCanvas"));
for (var i = 1; i < lines.length; i++) {
ctx.beginPath();
ctx.moveTo(linesArray[i-1].x, linesArray[i-1].y);
ctx.lineWidth = linesArray[i].size;
ctx.lineCap = "round";
ctx.strokeStyle = linesArray[i].color;
ctx.lineTo(linesArray[i].x, linesArray[i].y);
ctx.stroke();
}
console.log("Canvas loaded.");
}
else {
console.log("No canvas in memory!");
}
}
// ERASER HANDLING
function eraser() {
currentSize = 50;
currentColor = ctx.fillStyle
}
// GET MOUSE POSITION
function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
// ON MOUSE DOWN
function mousedown(canvas, evt) {
var mousePos = getMousePos(canvas, evt);
isMouseDown=true
var currentPosition = getMousePos(canvas, evt);
ctx.moveTo(currentPosition.x, currentPosition.y)
ctx.beginPath();
ctx.lineWidth = currentSize;
ctx.lineCap = "round";
ctx.strokeStyle = currentColor;
}
// ON MOUSE MOVE
function mousemove(canvas, evt) {
if(isMouseDown){
var currentPosition = getMousePos(canvas, evt);
ctx.lineTo(currentPosition.x, currentPosition.y)
ctx.stroke();
store(currentPosition.x, currentPosition.y, currentSize, currentColor);
}
}
// STORE DATA
function store(x, y, s, c) {
var line = {
"x": x,
"y": y,
"size": s,
"color": c
}
linesArray.push(line);
}
// ON MOUSE UP
function mouseup() {
isMouseDown=false
store()
}
.colorButtons {
display: block;
margin: 20px 0;
}
canvas {
cursor: crosshair;
}
div#sidebar {
position: absolute;
left: 0;
width: 150px;
padding: 20px 20px;
top: 0;
}
canvas#canvas {
left: 150px;
top: 45px;
}
.btn {
margin-bottom: 10px;
width: 100%;
}
input {
width: 100%;
margin-bottom: 10px;
}
.input-group {
margin-bottom: 10px;
}
.toolsButtons .btn {
width: 48%;
}
.sizeButtons .btn {
width: 48%;
}
.colorpicker {
background: transparent;
height: 40px;
}
<!-- using Bootstrap CSS because lazy to write 3 classes -->
<body>
<div id="sidebar">
<div class="colorButtons">
<h3>Colour</h3>
<input type="color" id="colorpicker" value="#c81464" class="colorpicker">
</div>
<div class="colorButtons">
<h3>Bg Color</h3>
<input type="color" value="#ffffff" id="bgcolorpicker" class="colorpicker">
</div>
<div class="toolsButtons">
<h3>Tools</h3>
<button id="eraser" class="btn btn-default">eraser</span></button>
<button id="clear" class="btn btn-danger"> <span class="glyphicon glyphicon-repeat" aria-hidden="true"></span></button>
</div>
<div class="buttonSize">
<h3>Size (<span id="showSize">5</span>)</h3>
<input type="range" min="1" max="50" value="5" step="1" id="controlSize">
</div>
<div class="canvasSize">
<h3>Canvas</h3>
<div class="input-group">
<span class="input-group-addon">X</span>
<input type="number" id="sizeX" class="form-control" placeholder="sizeX" value="800" class="size">
</div>
<div class="input-group">
<span class="input-group-addon">Y</span>
<input type="number" id="sizeY" class="form-control" placeholder="sizeY" value="800" class="size">
</div>
<input type="button" class="updateSize btn btn-success" value="Update" id="canvasUpdate">
</div>
<div class="Storage">
<h3>Storage</h3>
<input type="button" value="Save" class="btn btn-warning" id="save">
<input type="button" value="Load" class="btn btn-warning" id="load">
<input type="button" value="Clear" class="btn btn-warning" id="clearCache">
</div>
<div class="extra">
<h3>Extra</h3>
<a id="saveToImage" class="btn btn-warning">Download</a>
</div>
</div>
</body>
I have tried by adding the photo in a different way but that way it wouldn't be saved the right way. I also have tried changing layers with CSS and index but that also didn't work
Using layers
A canvas drawing app can use many canvases to define layers. Layers can include things like backgrounds, drawing layers, composite layers (multiply, screen, etc) and much more. Much the same as layers are used in apps like photoshop.
A bonus when using layers is that the immediate drawing state can be displayed without affecting the existing layers, as you can draw the pen on the output layer when the mouse button is not down. (see example)
To get the most from canvas layers you should become familiar with the many ctx.globalCompositeOperation modes.
The example uses the following ctx.globalCompositeOperation modes
"copy" copies pixels from source to destination including transparent pixels.
"source-over" (used in example draw mode) The default drawing mode. Copies pixels ignoring transparent pixels and blending semi transparent pixels.
"destination-out" (used in example erase mode) Removes pixels from the destination canvas where you draw opaque pixels, and partially removes pixels where you draw semi transparent pixels.
Performance
Even lowend devices can handle many canvas layers easily as long as you ensure that the canvas resolution does not exceed the device display size by many factors as performance is regulated by the availability of GPU RAM
You may be tempted to have the DOM handle the layer composition. It turns out that using the CanvasRenderingContext2D API to do layering is more efficient than letting the DOM handle it
Example
Below is a very basic drawing example. It uses 2 canvas layers, one for the background, and one for the drawing layer.
The background is loaded and then drawn to scale on the bg canvas.
When the mouse button is down the update function draws or erases to/from the drawing layer.
A 3rd canvas is used to show the result. This canvas is added to the DOM and the update function renders the layers to it as needed.
To save the result of the layers you can download the content of the 3rd canvas, or create a new canvas (if the display canvas size does not match the drawing size), draw the layers to it, and download its content.
Useage: Use mouse (left click) to draw / erase on drawing layer. Use button to toggle drawing mode (Draw / Erase)
;(()=>{
setTimeout(start, 0);
var ctx1, ctx2, ctx3;
const SIZE = 180;
const PEN_SIZE = 30;
function start() {
const button = tag("button", {textContent: "Draw", title: "Toggle erase / draw mode", className: "floatBtn"});
const canProps = {width: SIZE, height: SIZE};
ctx1 = tag("canvas", canProps).getContext("2d"); // BG layer
ctx2 = tag("canvas", canProps).getContext("2d"); // drawing layer
ctx3 = tag("canvas", canProps).getContext("2d"); // display canvas context
ctx2.lineWidth = ctx3.lineWidth = PEN_SIZE;
ctx2.lineCap = ctx3.lineCap = "round";
ctx2.lineJoin = ctx3.lineJoin = "round";
ctx2.strokeStyle = ctx3.strokeStyle = "BLUE";
append(BODY, ctx3.canvas, button);
// Load BG image and draw on bg canvas when loaded. Note bg is
// scaled to fit 180 by 180 canvas
const bgImg = new Image;
bgImg.src = "https://i.stack.imgur.com/C7qq2.png?s=256&g=1";
listener(bgImg, "load", () => (ctx1.drawImage(bgImg, 0, 0, 180, 180), mouse.update = true), {once: true});
listener(button, "click", () => {
mouse.draw = !mouse.draw; // Toggle drawing mode
button.textContent = mouse.draw ? "Draw" : "Erase";
});
mouse.update = true;
update();
}
function update() {
requestAnimationFrame(update)
if (!mouse.update) { return }
ctx3.globalCompositeOperation = "copy"; // to draw bg image
ctx3.drawImage(ctx1.canvas, 0 , 0);
if (mouse.lastX !== undefined) { // Avoid line from zero when mouse first over body
ctx3.globalCompositeOperation = "source-over"; // to draw drawing layer
if (mouse.button) { // draw on drawing layer if mouse down
ctx2.globalCompositeOperation = mouse.draw ? "source-over" : "destination-out";
ctx2.beginPath();
ctx2.lineTo(mouse.lastX, mouse.lastY);
ctx2.lineTo(mouse.x, mouse.y + 0.01); // Small 100th px offset
// ensures line is drawn
ctx2.stroke();
}
ctx3.drawImage(ctx2.canvas, 0 , 0);
if (!mouse.button) {
ctx3.strokeStyle = mouse.draw ? "BLUE" : "RED";
ctx3.beginPath();
ctx3.lineTo(mouse.lastX, mouse.lastY);
ctx3.lineTo(mouse.x, mouse.y + 0.01);
ctx3.stroke();
}
mouse.lastX = mouse.x;
mouse.lastY = mouse.y;
}
mouse.update = false;
}
const TAU = Math.PI * 2;
const DOC = document, BODY = DOC.body, assign = Object.assign;
const isArr = Array.isArray;
const tag = (tag, props = {}) => assign(DOC.createElement(tag), props);
const append = (el, ...sibs) => sibs.reduce((p, sib) => ((isArr(sib) ? append(p, ...sib) : p.appendChild(sib)), p), el);
const listener = (qe, name, call, opt = {}) => (qe.addEventListener(name, call, opt), qe);
const mouse = {x: 0, y: 0, button: false, lastX: undefined, lastY: undefined, draw: true, update: true}
function mouseEvents(e) {
mouse.update = true;
mouse.x = e.pageX;
mouse.y = e.pageY;
if (mouse.lastX === undefined) {
mouse.lastX = mouse.x;
mouse.lastY = mouse.y;
}
mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button;
}
["down", "up", "move"].forEach(name => document.addEventListener("mouse" + name, mouseEvents));
})();
canvas { position: absolute; top: 0px; left: 0px; cursor: crosshair}
.floatBtn { position : absolute; top: 0px; left: 180px; cursor: pointer}

Adding a link to a dynamic canvas

I have a canvas script, with a dynamic of data. I want to add a link to share the website to facebook:
https://gyazo.com/c1fd1fe956fddba27b48907dc0e9de0a
The icons are part of the image I have not generated them via canvas, now if I listen for a click for co-ords it won't work because it'll look for clicks on the first canvas part aswell.... How can I go about making those icons part of the image clickable....
Part that makes the menu:
ig.module("game.entities.gameover").requires("impact.entity", "game.entities.button-gameover").defines(function() {
var b = new ig.Timer;
EntityGameover = ig.Entity.extend({
size: {
x: 302,
y: 355
},
type: ig.Entity.TYPE.B,
animSheet: new ig.AnimationSheet("media/graphics/game/gameover.png", 301, 352),
zIndex: 900,
globalAlpha: 0.1,
closeDialogue: !0,
init: function(c, d, g) {
this.parent(c, d, g);
this.addAnim("idle", 1, [0]);
this.currentAnim = this.anims.idle;
this.tween({
pos: {
x: 89,
y: 120
}
}, 0.5, {
easing: ig.Tween.Easing.Back.EaseInOut
}).start();
this.storage = new ig.Storage;
this.storage.initUnset("highscore-CTF", 0);
this.storage.initUnset("highscore-CTF2", 0);
this.storage.initUnset("highscore-CTF3", 0);
ig.global.score > this.storage.get("highscore-CTF") ? (this.storage.set("highscore-CTF3", this.storage.get("highscore-CTF2")), this.storage.set("highscore-CTF2", this.storage.get("highscore-CTF")), this.storage.set("highscore-CTF", ig.global.score), this.storage.initUnset("highscore-CTF2", 0), this.storage.initUnset("highscore-CTF3", 0)) : ig.global.score > this.storage.get("highscore-CTF2") ?
(this.storage.set("highscore-CTF3", this.storage.get("highscore-CTF2")), this.storage.set("highscore-CTF2", ig.global.score), this.storage.initUnset("highscore-CTF2", 0), this.storage.initUnset("highscore-CTF3", 0)) : ig.global.score > this.storage.get("highscore-CTF3") && this.storage.set("highscore-CTF3", ig.global.score);
this.storage.initUnset("total-CTF", 0);
this.storage.set("total-CTF", this.storage.get("total-CTF") + ig.global.score);
ig.game.spawnEntity(EntityButtonGameover, 23, 700, {
buttonID: 1
});
ig.game.spawnEntity(EntityButtonGameover,
220, 700, {
buttonID: 2
});
ig.game.spawnEntity(EntityButtonGameover, 390, 700, {
buttonID: 3
});
b.set(0.3)
},
update: function() {
this.parent()
},
draw: function() {
this.ctx = ig.system.context;
this.closeDialogue ? (this.ctx.save(), this.ctx.fillStyle = "#000000", this.ctx.globalAlpha = this.globalAlpha, this.ctx.fillRect(0, 0, 480, 640), this.ctx.restore(), this.globalAlpha = 0.7 <= this.globalAlpha ? 0.7 : this.globalAlpha + 0.01) : this.closeDialogue || (this.ctx.save(), this.ctx.fillStyle = "#000000", this.ctx.globalAlpha = this.globalAlpha, this.ctx.fillRect(0,
0, 480, 640), this.ctx.restore(), this.globalAlpha = 0.1 >= this.globalAlpha ? 0 : this.globalAlpha - 0.05);
this.parent();
this.ctx.font = "30px happy-hell";
this.ctx.fillStyle = "#5b2a0b";
this.ctx.textAlign = "center";
this.ctx.fillText(_STRINGS.UI.Best, this.pos.x + 70, this.pos.y + 180);
this.ctx.fillText(_STRINGS.UI.Score, this.pos.x + 70, this.pos.y + 260);
//share
this.ctx.font = "30px happy-hell";
this.ctx.fillStyle = "#ffffff";
this.ctx.textAlign = "left";
this.ctx.fillText(this.storage.getInt("highscore-CTF"), this.pos.x + 140, this.pos.y + 180);
this.ctx.fillText(ig.global.score, this.pos.x + 140, this.pos.y + 260)
},
closeDialogueFunc: function() {
this.closeDialogue && (this.tween({
pos: {
x: 89,
y: -600
}
}, 0.5, {
easing: ig.Tween.Easing.Back.EaseInOut
}).start(), this.closeDialogue = !1)
}
})
});
A simple, versatile way to add menus to canvas graphics is to simply overlay an absolutely positioned DOM structure. Your browser has already all event handling build-in, there is no need to reinvent the wheel.
var canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d');
ctx.fillStyle = 'rgb(0, 155, 255)';
ctx.fillRect(0, 0, canvas.width, canvas.height)
#container {
position: relative;
display: inline-block;
}
#menu {
position: absolute;
text-align: center;
width: 100%;
height: 100%;
top: 0;
left: 0;
}
#menu a {
padding: 15px;
font-size: 50px;
line-height: 100px;
color: black;
text-shadow: 2px 2px 5px white;
}
#menu a:hover {
color: white;
}
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css" rel="stylesheet" />
<div id='container'>
<canvas id='canvas' width='400' height='100'></canvas>
<div id='menu'>
<a href='http://facebook.com'><i class='fa fa-facebook-official'></i></a>
<a href='http://twitter.com'><i class='fa fa-twitter'></i></a>
<a href='http://whatsapp.com'><i class='fa fa-whatsapp'></i></a>
</div>
</div>
Your browser is capable of rendering such overlay menus very quickly. You should use CSS to style your overlay menu links or buttons.
I have managed to click on a certain element in a canvas.
I have tried to explain with comments what it does.
I have made 3 limits as shown in the image below.
And I'm comparing only with x value, if it's in between these limits. It can be more complex so getCursorPosition() method returns an object with x and y components, just in case if you need to make more comparisons.
https://jsfiddle.net/_jserodio/asa10pye/
var canvas;
var ctx;
// first get your canvas
canvas = document.getElementById('canvas');
canvas.width = 253;
canvas.heigth = 68;
// assign the context
ctx = canvas.getContext("2d");
// asign click event to the canvas
addEventListener("click", listener, false);
function listener(e) {
// if you have 3 buttons
var position = getCursorPosition(e);
var limit1 = canvas.width / 3;
//console.log("limit1: " + limit1);
var limit2 = canvas.width * 2 / 3;
//console.log("limit2: " + limit2);
var limit3 = canvas.width;
//console.log("limit3: " + limit3);
if (position.x < limit1) {
console.log("go to facebook");
//window.open("http://www.facebook.com");
} else if (position.x < limit2) {
console.log("go to twitter");
//window.open("http://www.twitter.com");
} else if (position.x < limit3) {
console.log("go to whatsapp");
//window.open("http://www.whatsapp.com");
}
//console.log("\nx" + position.x);
//console.log("y" + position.y);
}
function getCursorPosition(event) {
var rect = canvas.getBoundingClientRect();
var x = event.clientX - rect.left;
var y = event.clientY - rect.top;
var data = {
x: x,
y: y
};
return data;
}
// load image from data url
var imageObj = new Image();
imageObj.onload = function() {
ctx.drawImage(this, 0, 0);
};
imageObj.src = 'https://justpaste.it/files/justpaste/d307/a11791570/file1.png';
<canvas id='canvas' width="253" height="68">
</canvas>
Bonus!
Here you have a demo I made using this. demo (Draughts/checkers).
You can check the entire code here if you want.
You have several operations in this case.
Option 1:
You'll need to remember the "bounding area" of the three buttons. Anytime the canvas receives a "click", get the click position (How do I get the coordinates of a mouse click on a canvas element?). Once you get the click position. Detect if said click occurs within the bounding area of the button. If it does, use window.open to go there.
Option 2: Similar to Option 1, but instead of remembering a "bounding area", create a separate hidden canvas of the same size as your image with the backgrounds black ('#000000') and the button given distinctive colors (for example, red for Facebook, green for Twitter, and blue for Hangout?).
Then, similar to Option 1, get the click position relative to the canvas. Then use ctx.getImageData(sx, sy, sw, sh) on the context of the hidden canvas layer. If the value you get back is red, then user clicked on Facebook, if green, Twitter, and if blue, Hangout.

Unexpected result with range input values

This problem is (probably) not caused by the width/height being in the CSS, because it isn't. (How to avoid HTML Canvas auto-stretching)
When trying to make rage inputs to demonstrate an issue I was having, I found that when using range inputs it acted stranger. It seems to be representing a larger number than what I'm passing in.
Please check my code out and tell me what's causing all this. http://codepen.io/Magnesium/pen/wMwwgx
var engine = function(canvas) { // Trying to make a game engine when the problems started
this.canvas = canvas.getContext("2d");
this.defaultColor = "#FF0000";
this.clearCanvas = function() { // Clears canvas
this.canvas.clearRect(0, 0, canvas.width, canvas.height);
};
this.square = function(x, y, size, color) { // Makes a square. I don't see any problems, but if the error is caused by me it would probably be here
if (color == undefined) color = this.defaultColor;
this.canvas.fillStyle = color;
this.canvas.fillRect(x, y, x + size, y + size);
};
}
var canvas = document.getElementById("canvas");
var rangeX = document.getElementById("x");
var rangeY = document.getElementById("y");
var size = document.getElementById("sz");
var outX = document.getElementById("ox");
var outY = document.getElementById("oy");
var outSize = document.getElementById("osz");
var out = document.getElementById("out");
var enjin = new engine(canvas); // New engine
var defalt = false;
var src = document.getElementById("src");
setInterval(function() { // Called ~30 times a second
enjin.clearCanvas();
defalt ? enjin.square(50, 50, 50) : enjin.square(rangeX.value, rangeY.value, size.value);
defalt ? out.innerHTML = "enjin.square(50, 50, 50);" : out.innerHTML = "enjin.square("+rangeX.value+", "+rangeY.value+", "+size.value+");";
defalt ? src.innerHTML = "Using in-code values" : src.innerHTML = "Using slider values";
outX.innerHTML = rangeX.value;
outY.innerHTML = rangeY.value;
outSize.innerHTML = size.value;
}, 30);
The values you're passing to enjin.square are strings. You need to parse them to int:
enjin.square(parseInt(rangeX.value), parseInt(rangeY.value), parseInt(size.value));
Here's a working example:
var engine = function(canvas) { // Trying to make a game engine when the problems started
this.canvas = canvas.getContext("2d");
this.defaultColor = "#FF0000";
this.clearCanvas = function() { // Clears canvas
this.canvas.clearRect(0, 0, canvas.width, canvas.height);
};
this.square = function(x, y, size, color) { // Makes a square. I don't see any problems, but if the error is caused by me it would probably be here
if (color == undefined) color = this.defaultColor;
this.canvas.fillStyle = color;
this.canvas.fillRect(x, y, x + size, y + size);
};
}
var canvas = document.getElementById("canvas");
var rangeX = document.getElementById("x");
var rangeY = document.getElementById("y");
var size = document.getElementById("sz");
var outX = document.getElementById("ox");
var outY = document.getElementById("oy");
var outSize = document.getElementById("osz");
var out = document.getElementById("out");
var enjin = new engine(canvas); // New engine
var defalt = false;
var src = document.getElementById("src");
setInterval(function() { // Called ~30 times a second
enjin.clearCanvas();
defalt ? enjin.square(50, 50, 50) : enjin.square(parseInt(rangeX.value), parseInt(rangeY.value), parseInt(size.value));
defalt ? out.innerHTML = "enjin.square(50, 50, 50);" : out.innerHTML = "enjin.square(" + rangeX.value + ", " + rangeY.value + ", " + size.value + ");";
defalt ? src.innerHTML = "Using in-code values" : src.innerHTML = "Using slider values";
outX.innerHTML = rangeX.value;
outY.innerHTML = rangeY.value;
outSize.innerHTML = size.value;
}, 30);
#canvas {
border: 1px solid black;
}
<canvas id='canvas' width='400' height='400'></canvas>
<br />X
<input type='range' id='x' /><span id='ox'></span>
<br />Y
<input type='range' id='y' /><span id='oy'></span>
<br />Size
<input type='range' id='sz' /><span id='osz'></span>
<br /><span id='out'></span>
<br />
<button onclick='defalt = !defalt;'>Default value toggle</button>
<span id='src'></span>
And a updated pen.

Dynamically generated canvas elements in ASP.NET

I have multiple canvas elements which appear dynamically on my page based on VB code.
This is what my aspx page looks like:
<canvas id="canvasnhlrc" width="225" height="200" runat="server"></canvas>
<canvas id="canvascwl" width="225" height="200" runat="server"></canvas>
<canvas id="canvashslanguages" width="225" height="200" runat="server"></canvas>
This is what my aspx.vb code looks like:
canvasnhlrc.Visible = False
canvascwl.Visible = False
canvashslanguages.Visible = False
If RouteData.Values("mediasubType") IsNot Nothing Then
Dim qsubMedia As String = RouteData.Values("mediasubType")
Select Case qsubMedia
Case "nhlrc"
canvasnhlrc.Visible = True
Case "cwl"
canvascwl.Visible = True
Case "hslanguages"
canvashslanguages.Visible = True
End Select
End If
So, if my page is example.com/nhlrc, then the canvas is generated as
<canvas id="ContentPlaceHolder2_canvasnhlrc" width="225" height="200"></canvas>
Accordingly, I have formatted my js file as follows:
function init()
{
var w1 = document.getElementById('ContentPlaceHolder2_canvasnhlrc')
var w1x = w1.getContext('2d');
w1x.shadowOffsetX = 0;
w1x.shadowOffsetY = 0;
w1x.shadowBlur = 20;
w1x.shadowColor = "rgba(0, 0, 0, 0.75)";
w1x.fillStyle = 'rgb(218, 233, 246)';
w1x.beginPath();
w1x.moveTo(25, 25);
w1x.lineTo(175, 25);
w1x.lineTo(175, 50);
w1x.lineTo(200, 75);
w1x.lineTo(175, 100);
w1x.lineTo(175, 175);
w1x.lineTo(25, 175);
w1x.closePath();
w1x.fill();
var w2 = document.getElementById('ContentPlaceHolder2_canvascwl')
var w2x = w2.getContext('2d');
w2x.shadowOffsetX = 0;
w2x.shadowOffsetY = 0;
w2x.shadowBlur = 20;
w2x.shadowColor = "rgba(0, 0, 0, 0.75)";
w2x.fillStyle = 'rgb(12, 64, 114)';
w2x.beginPath();
w2x.moveTo(25, 25);
w2x.lineTo(175, 25);
w2x.lineTo(175, 50);
w2x.lineTo(200, 75);
w2x.lineTo(175, 100);
w2x.lineTo(175, 175);
w2x.lineTo(25, 175);
w2x.closePath();
w2x.fill();
var w3 = document.getElementById('ContentPlaceHolder2_canvashslanguages')
var w3x = w3.getContext('2d');
w3x.shadowOffsetX = 0;
w3x.shadowOffsetY = 0;
w3x.shadowBlur = 20;
w3x.shadowColor = "rgba(0, 0, 0, 0.75)";
w3x.fillStyle = 'rgb(12, 64, 114)';
w3x.beginPath();
w3x.moveTo(25, 25);
w3x.lineTo(175, 25);
w3x.lineTo(175, 50);
w3x.lineTo(200, 75);
w3x.lineTo(175, 100);
w3x.lineTo(175, 175);
w3x.lineTo(25, 175);
w3x.closePath();
w3x.fill();
}
onload = init;
Sadly, I can only get the first canvas element (NHLRC) to appear while the subsequent canvases (example.com/cwl, etc.) do not render. There is nothing wrong with the code because when I comment out the other two elements in the JS code, it works fine, so what am I missing, doing wrong, or what's conflicting that's not allowing each of the canvas elements to render in its corresponding page?
Your init function has too many responsibilities (find, verify and draw) * 3 .
When any one of those responsibilities meets with a failure, i.e. can't find an element, the show stops. In your case w1, w2, w3 will only be defined in their respective pages, not for every page.
You can simplify it as below:
function draw(theCanvasEl, theFill)
{
var w1x = theCanvasEl.getContext('2d');
w1x.shadowOffsetX = 0;
w1x.shadowOffsetY = 0;
w1x.shadowBlur = 20;
w1x.shadowColor = "rgba(0, 0, 0, 0.75)";
w1x.fillStyle = theFill;
w1x.beginPath();
w1x.moveTo(25, 25);
w1x.lineTo(175, 25);
w1x.lineTo(175, 50);
w1x.lineTo(200, 75);
w1x.lineTo(175, 100);
w1x.lineTo(175, 175);
w1x.lineTo(25, 175);
w1x.closePath();
w1x.fill();
}
function init()
{
var canvases = [['ContentPlaceHolder2_canvasnhlrc', 'rgb(218, 233, 246)'],
['ContentPlaceHolder2_canvascwl', 'rgb(12, 64, 114)'],
['ContentPlaceHolder2_canvashslanguages', 'rgb(12, 64, 114)']];
for(var i=0;i<canvases.length;i++)
{
var theCanvas = document.getElementById(canvases[i][0]);
if (theCanvas!=null) {
draw(theCanvas, canvases[i][1]);
break;
}
}
}
onload = init;

How to add text inside the doughnut chart using Chart.js?

How to render Text inside the doughnut chart, I am using ChartJs.
None of the other answers resize the text based off the amount of text and the size of the doughnut. Here is a small script you can use to dynamically place any amount of text in the middle, and it will automatically resize it.
Example: http://jsfiddle.net/kdvuxbtj/
It will take any amount of text in the doughnut sized perfect for the doughnut. To avoid touching the edges you can set a side-padding as a percentage of the diameter of the inside of the circle. If you don't set it, it will default to 20. You also the color, the font, and the text. The plugin takes care of the rest.
The plugin code will start with a base font size of 30px. From there it will check the width of the text and compare it against the radius of the circle and resize it based off the circle/text width ratio.
It has a default minimum font size of 20px. If the text would exceed the bounds at the minimum font size, it will wrap the text. The default line height when wrapping the text is 25px, but you can change it. If you set the default minimum font size to false, the text will become infinitely small and will not wrap.
It also has a default max font size of 75px in case there is not enough text and the lettering would be too big.
This is the plugin code
Chart.pluginService.register({
beforeDraw: function(chart) {
if (chart.config.options.elements.center) {
// Get ctx from string
var ctx = chart.chart.ctx;
// Get options from the center object in options
var centerConfig = chart.config.options.elements.center;
var fontStyle = centerConfig.fontStyle || 'Arial';
var txt = centerConfig.text;
var color = centerConfig.color || '#000';
var maxFontSize = centerConfig.maxFontSize || 75;
var sidePadding = centerConfig.sidePadding || 20;
var sidePaddingCalculated = (sidePadding / 100) * (chart.innerRadius * 2)
// Start with a base font of 30px
ctx.font = "30px " + fontStyle;
// Get the width of the string and also the width of the element minus 10 to give it 5px side padding
var stringWidth = ctx.measureText(txt).width;
var elementWidth = (chart.innerRadius * 2) - sidePaddingCalculated;
// Find out how much the font can grow in width.
var widthRatio = elementWidth / stringWidth;
var newFontSize = Math.floor(30 * widthRatio);
var elementHeight = (chart.innerRadius * 2);
// Pick a new font size so it will not be larger than the height of label.
var fontSizeToUse = Math.min(newFontSize, elementHeight, maxFontSize);
var minFontSize = centerConfig.minFontSize;
var lineHeight = centerConfig.lineHeight || 25;
var wrapText = false;
if (minFontSize === undefined) {
minFontSize = 20;
}
if (minFontSize && fontSizeToUse < minFontSize) {
fontSizeToUse = minFontSize;
wrapText = true;
}
// Set font settings to draw it correctly.
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
var centerX = ((chart.chartArea.left + chart.chartArea.right) / 2);
var centerY = ((chart.chartArea.top + chart.chartArea.bottom) / 2);
ctx.font = fontSizeToUse + "px " + fontStyle;
ctx.fillStyle = color;
if (!wrapText) {
ctx.fillText(txt, centerX, centerY);
return;
}
var words = txt.split(' ');
var line = '';
var lines = [];
// Break words up into multiple lines if necessary
for (var n = 0; n < words.length; n++) {
var testLine = line + words[n] + ' ';
var metrics = ctx.measureText(testLine);
var testWidth = metrics.width;
if (testWidth > elementWidth && n > 0) {
lines.push(line);
line = words[n] + ' ';
} else {
line = testLine;
}
}
// Move the center up depending on line height and number of lines
centerY -= (lines.length / 2) * lineHeight;
for (var n = 0; n < lines.length; n++) {
ctx.fillText(lines[n], centerX, centerY);
centerY += lineHeight;
}
//Draw text in center
ctx.fillText(line, centerX, centerY);
}
}
});
And you use the following options in your chart object
options: {
elements: {
center: {
text: 'Red is 2/3 the total numbers',
color: '#FF6384', // Default is #000000
fontStyle: 'Arial', // Default is Arial
sidePadding: 20, // Default is 20 (as a percentage)
minFontSize: 20, // Default is 20 (in px), set to false and text will not wrap.
lineHeight: 25 // Default is 25 (in px), used for when text wraps
}
}
}
Credit to #Jenna Sloan for help with the math used in this solution.
Here is cleaned up and combined example of above solutions - responsive (try to resize the window), supports animation self-aligning, supports tooltips
https://jsfiddle.net/cmyker/u6rr5moq/
Chart.types.Doughnut.extend({
name: "DoughnutTextInside",
showTooltip: function() {
this.chart.ctx.save();
Chart.types.Doughnut.prototype.showTooltip.apply(this, arguments);
this.chart.ctx.restore();
},
draw: function() {
Chart.types.Doughnut.prototype.draw.apply(this, arguments);
var width = this.chart.width,
height = this.chart.height;
var fontSize = (height / 114).toFixed(2);
this.chart.ctx.font = fontSize + "em Verdana";
this.chart.ctx.textBaseline = "middle";
var text = "82%",
textX = Math.round((width - this.chart.ctx.measureText(text).width) / 2),
textY = height / 2;
this.chart.ctx.fillText(text, textX, textY);
}
});
var data = [{
value: 30,
color: "#F7464A"
}, {
value: 50,
color: "#E2EAE9"
}, {
value: 100,
color: "#D4CCC5"
}, {
value: 40,
color: "#949FB1"
}, {
value: 120,
color: "#4D5360"
}];
var DoughnutTextInsideChart = new Chart($('#myChart')[0].getContext('2d')).DoughnutTextInside(data, {
responsive: true
});
<html>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/Chart.js/1.0.2/Chart.min.js"></script>
<body>
<canvas id="myChart"></canvas>
</body>
</html>
UPDATE 17.06.16:
Same functionality but for chart.js version 2:
https://jsfiddle.net/cmyker/ooxdL2vj/
var data = {
labels: [
"Red",
"Blue",
"Yellow"
],
datasets: [
{
data: [300, 50, 100],
backgroundColor: [
"#FF6384",
"#36A2EB",
"#FFCE56"
],
hoverBackgroundColor: [
"#FF6384",
"#36A2EB",
"#FFCE56"
]
}]
};
Chart.pluginService.register({
beforeDraw: function(chart) {
var width = chart.chart.width,
height = chart.chart.height,
ctx = chart.chart.ctx;
ctx.restore();
var fontSize = (height / 114).toFixed(2);
ctx.font = fontSize + "em sans-serif";
ctx.textBaseline = "middle";
var text = "75%",
textX = Math.round((width - ctx.measureText(text).width) / 2),
textY = height / 2;
ctx.fillText(text, textX, textY);
ctx.save();
}
});
var chart = new Chart(document.getElementById('myChart'), {
type: 'doughnut',
data: data,
options: {
responsive: true,
legend: {
display: false
}
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/Chart.js/2.1.6/Chart.bundle.js"></script>
<canvas id="myChart"></canvas>
You have to modify the code like:
in chart.Doughnut.defaults
labelFontFamily : "Arial",
labelFontStyle : "normal",
labelFontSize : 24,
labelFontColor : "#666"
and then in function drawPieSegments
ctx.fillText(data[0].value + "%", width/2 - 20, width/2, 200);
See this pull: https://github.com/nnnick/Chart.js/pull/35
here is a fiddle http://jsfiddle.net/mayankcpdixit/6xV78/ implementing the same.
I'd avoid modifying the chart.js code to accomplish this, since it's pretty easy with regular CSS and HTML. Here's my solution:
HTML:
<canvas id="productChart1" width="170"></canvas>
<div class="donut-inner">
<h5>47 / 60 st</h5>
<span>(30 / 25 st)</span>
</div>
CSS:
.donut-inner {
margin-top: -100px;
margin-bottom: 100px;
}
.donut-inner h5 {
margin-bottom: 5px;
margin-top: 0;
}
.donut-inner span {
font-size: 12px;
}
The output looks like this:
This is also working at my end...
<div style="width: 100px; height: 100px; float: left; position: relative;">
<div
style="width: 100%; height: 40px; position: absolute; top: 50%; left: 0; margin-top: -20px; line-height:19px; text-align: center; z-index: 999999999999999">
99%<Br />
Total
</div>
<canvas id="chart-area" width="100" height="100" />
</div>
You can use css with relative/absolute positioning if you want it responsive. Plus it can handle easily the multi-line.
https://jsfiddle.net/mgyp0jkk/
<div class="relative">
<canvas id="myChart"></canvas>
<div class="absolute-center text-center">
<p>Some text</p>
<p>Some text</p>
</div>
</div>
Base on #rap-2-h answer,Here the code for using text on doughnut chart on Chart.js for using in dashboard like. It has dynamic font-size for responsive option.
HTML:
<div>text
<canvas id="chart-area" width="300" height="300" style="border:1px solid"/><div>
Script:
var doughnutData = [
{
value: 100,
color:"#F7464A",
highlight: "#FF5A5E",
label: "Red"
},
{
value: 50,
color: "#CCCCCC",
highlight: "#5AD3D1",
label: "Green"
}
];
$(document).ready(function(){
var ctx = $('#chart-area').get(0).getContext("2d");
var myDoughnut = new Chart(ctx).Doughnut(doughnutData,{
animation:true,
responsive: true,
showTooltips: false,
percentageInnerCutout : 70,
segmentShowStroke : false,
onAnimationComplete: function() {
var canvasWidthvar = $('#chart-area').width();
var canvasHeight = $('#chart-area').height();
//this constant base on canvasHeight / 2.8em
var constant = 114;
var fontsize = (canvasHeight/constant).toFixed(2);
ctx.font=fontsize +"em Verdana";
ctx.textBaseline="middle";
var total = 0;
$.each(doughnutData,function() {
total += parseInt(this.value,10);
});
var tpercentage = ((doughnutData[0].value/total)*100).toFixed(2)+"%";
var textWidth = ctx.measureText(tpercentage).width;
var txtPosx = Math.round((canvasWidthvar - textWidth)/2);
ctx.fillText(tpercentage, txtPosx, canvasHeight/2);
}
});
});
Here the sample code.try to resize the window. http://jsbin.com/wapono/13/edit
This is based on Cmyker's update for Chart.js 2. (posted as another answer as I can't comment yet)
I had an issue with the text alignment on Chrome when the legend is displayed as the chart height does not include this so it's not aligned correctly in the middle. Fixed this by accounting for this in the calculation of fontSize and textY.
I calculated percentage inside the method rather than a set value as I have multiple of these on the page. Assumptions are that your chart only has 2 values (otherwise what is the percentage of? and that the first is the one you want to show the percentage for. I have a bunch of other charts too so I do a check for type = doughnut. I'm only using doughnuts to show percentages so it works for me.
Text color seems a bit hit and miss depending on what order things run in etc so I ran into an issue when resizing that the text would change color (between black and the primary color in one case, and secondary color and white in another) so I "save" whatever the existing fill style was, draw the text (in the color of the primary data) then restore the old fill style. (Preserving the old fill style doesn't seem needed but you never know.)
https://jsfiddle.net/g733tj8h/
Chart.pluginService.register({
beforeDraw: function(chart) {
var width = chart.chart.width,
height = chart.chart.height,
ctx = chart.chart.ctx,
type = chart.config.type;
if (type == 'doughnut')
{
var percent = Math.round((chart.config.data.datasets[0].data[0] * 100) /
(chart.config.data.datasets[0].data[0] +
chart.config.data.datasets[0].data[1]));
var oldFill = ctx.fillStyle;
var fontSize = ((height - chart.chartArea.top) / 100).toFixed(2);
ctx.restore();
ctx.font = fontSize + "em sans-serif";
ctx.textBaseline = "middle"
var text = percent + "%",
textX = Math.round((width - ctx.measureText(text).width) / 2),
textY = (height + chart.chartArea.top) / 2;
ctx.fillStyle = chart.config.data.datasets[0].backgroundColor[0];
ctx.fillText(text, textX, textY);
ctx.fillStyle = oldFill;
ctx.save();
}
}
});
var data = {
labels: ["Red","Blue"],
datasets: [
{
data: [300, 50],
backgroundColor: ["#FF6384","#36A2EB"],
}]
};
Chart.pluginService.register({
beforeDraw: function(chart) {
var width = chart.chart.width,
height = chart.chart.height,
ctx = chart.chart.ctx,
type = chart.config.type;
if (type == 'doughnut')
{
var percent = Math.round((chart.config.data.datasets[0].data[0] * 100) /
(chart.config.data.datasets[0].data[0] +
chart.config.data.datasets[0].data[1]));
var oldFill = ctx.fillStyle;
var fontSize = ((height - chart.chartArea.top) / 100).toFixed(2);
ctx.restore();
ctx.font = fontSize + "em sans-serif";
ctx.textBaseline = "middle"
var text = percent + "%",
textX = Math.round((width - ctx.measureText(text).width) / 2),
textY = (height + chart.chartArea.top) / 2;
ctx.fillStyle = chart.config.data.datasets[0].backgroundColor[0];
ctx.fillText(text, textX, textY);
ctx.fillStyle = oldFill;
ctx.save();
}
}
});
var myChart = new Chart(document.getElementById('myChart'), {
type: 'doughnut',
data: data,
options: {
responsive: true,
legend: {
display: true
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.1.6/Chart.bundle.js"></script>
<canvas id="myChart"></canvas>
I create a demo with 7 jQueryUI Slider and ChartJs (with dynamic text inside)
Chart.types.Doughnut.extend({
name: "DoughnutTextInside",
showTooltip: function() {
this.chart.ctx.save();
Chart.types.Doughnut.prototype.showTooltip.apply(this, arguments);
this.chart.ctx.restore();
},
draw: function() {
Chart.types.Doughnut.prototype.draw.apply(this, arguments);
var width = this.chart.width,
height = this.chart.height;
var fontSize = (height / 140).toFixed(2);
this.chart.ctx.font = fontSize + "em Verdana";
this.chart.ctx.textBaseline = "middle";
var red = $( "#red" ).slider( "value" ),
green = $( "#green" ).slider( "value" ),
blue = $( "#blue" ).slider( "value" ),
yellow = $( "#yellow" ).slider( "value" ),
sienna = $( "#sienna" ).slider( "value" ),
gold = $( "#gold" ).slider( "value" ),
violet = $( "#violet" ).slider( "value" );
var text = (red+green+blue+yellow+sienna+gold+violet) + " minutes";
var textX = Math.round((width - this.chart.ctx.measureText(text).width) / 2);
var textY = height / 2;
this.chart.ctx.fillStyle = '#000000';
this.chart.ctx.fillText(text, textX, textY);
}
});
var ctx = $("#myChart").get(0).getContext("2d");
var myDoughnutChart = new Chart(ctx).DoughnutTextInside(data, {
responsive: false
});
DEMO IN JSFIDDLE
You can also paste mayankcpdixit's code in onAnimationComplete option :
// ...
var myDoughnutChart = new Chart(ctx).Doughnut(data, {
onAnimationComplete: function() {
ctx.fillText(data[0].value + "%", 100 - 20, 100, 200);
}
});
Text will be shown after animation
#rap-2-h and #Ztuons Ch's answer doesn't allow for the showTooltips option to be active, but what you can do is create and layer a second canvas object behind the one rendering the chart.
The important part is the styling required in the divs and for the canvas object itself so that they render on top of each other.
var data = [
{value : 100, color : 'rgba(226,151,093,1)', highlight : 'rgba(226,151,093,0.75)', label : "Sector 1"},
{value : 100, color : 'rgba(214,113,088,1)', highlight : 'rgba(214,113,088,0.75)', label : "Sector 2"},
{value : 100, color : 'rgba(202,097,096,1)', highlight : 'rgba(202,097,096,0.75)', label : "Sector 3"}
]
var options = { showTooltips : true };
var total = 0;
for (i = 0; i < data.length; i++) {
total = total + data[i].value;
}
var chartCtx = $("#canvas").get(0).getContext("2d");
var chart = new Chart(chartCtx).Doughnut(data, options);
var textCtx = $("#text").get(0).getContext("2d");
textCtx.textAlign = "center";
textCtx.textBaseline = "middle";
textCtx.font = "30px sans-serif";
textCtx.fillText(total, 150, 150);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/Chart.js/1.0.2/Chart.min.js"></script>
<html>
<body>
<div style="position: relative; width:300px; height:300px;">
<canvas id="text"
style="z-index: 1;
position: absolute;
left: 0px;
top: 0px;"
height="300"
width="300"></canvas>
<canvas id="canvas"
style="z-index: 2;
position: absolute;
left: 0px;
top: 0px;"
height="300"
width="300"></canvas>
</div>
</body>
</html>
Here's the jsfiddle: https://jsfiddle.net/68vxqyak/1/
Alesana's solution works very nicely for me in general, but like others, I wanted to be able to specify where line breaks occur. I made some simple modifications to wrap lines at '\n' characters, as long as the text is already being wrapped. A more complete solution would force wrapping if there are any '\n' characters in the text, but I don't have time at the moment to make that work with font sizing. The change also centers a little better horizontally when wrapping (avoids trailing spaces). The code's below (I can't post comments yet).
It would be cool if someone put this plug-in on GitHub...
Chart.pluginService.register({
beforeDraw: function(chart) {
if (chart.config.options.elements.center) {
// Get ctx from string
var ctx = chart.chart.ctx;
// Get options from the center object in options
var centerConfig = chart.config.options.elements.center;
var fontStyle = centerConfig.fontStyle || 'Arial';
var txt = centerConfig.text;
var color = centerConfig.color || '#000';
var maxFontSize = centerConfig.maxFontSize || 75;
var sidePadding = centerConfig.sidePadding || 20;
var sidePaddingCalculated = (sidePadding / 100) * (chart.innerRadius * 2)
// Start with a base font of 30px
ctx.font = "30px " + fontStyle;
// Get the width of the string and also the width of the element minus 10 to give it 5px side padding
var stringWidth = ctx.measureText(txt).width;
var elementWidth = (chart.innerRadius * 2) - sidePaddingCalculated;
// Find out how much the font can grow in width.
var widthRatio = elementWidth / stringWidth;
var newFontSize = Math.floor(30 * widthRatio);
var elementHeight = (chart.innerRadius * 2);
// Pick a new font size so it will not be larger than the height of label.
var fontSizeToUse = Math.min(newFontSize, elementHeight, maxFontSize);
var minFontSize = centerConfig.minFontSize;
var lineHeight = centerConfig.lineHeight || 25;
var wrapText = false;
if (minFontSize === undefined) {
minFontSize = 20;
}
if (minFontSize && fontSizeToUse < minFontSize) {
fontSizeToUse = minFontSize;
wrapText = true;
}
// Set font settings to draw it correctly.
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
var centerX = ((chart.chartArea.left + chart.chartArea.right) / 2);
var centerY = ((chart.chartArea.top + chart.chartArea.bottom) / 2);
ctx.font = fontSizeToUse + "px " + fontStyle;
ctx.fillStyle = color;
if (!wrapText) {
ctx.fillText(txt, centerX, centerY);
return;
}
var lines = [];
var chunks = txt.split('\n');
for (var m = 0; m < chunks.length; m++) {
var words = chunks[m].split(' ');
var line;
// Break words up into multiple lines if necessary
for (var n = 0; n < words.length; n++) {
var testLine = (n == 0) ? words[n] : line + ' ' + words[n];
var metrics = ctx.measureText(testLine);
var testWidth = metrics.width;
if (testWidth > elementWidth && n > 0) {
lines.push(line);
line = words[n];
} else {
line = testLine;
}
}
lines.push(line);
}
// Move the center up depending on line height and number of lines
centerY -= ((lines.length-1) / 2) * lineHeight;
// All but last line
for (var n = 0; n < lines.length; n++) {
ctx.fillText(lines[n], centerX, centerY);
centerY += lineHeight;
}
}
}
});
The world has a bit changed since this question was asked.
Thankfully, today we have flexbox, and something responsive can be achieved with the following few lines:
<!-- In your html/template files -->
<section class="container">
<canvas id="chartJsChart"></canvas>
<p class="label">Some text</p>
</section>
<style>
.container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.label {
position: absolute;
}
</style>
I hope it's helpful to someone else!
First of all, kudos on choosing Chart.js! I'm using it on one of my current projects and I absolutely love it - it does the job perfectly.
Although labels/tooltips are not part of the library yet, you may want to take a look at these three pull requests:
Tooltips
Added labels to pie charts
Added basic labels to Pie chart
And, as Cracker0dks mentioned, Chart.js uses canvas for rendering so you may as well just implement your own tooltips by interacting with it directly.
Hope this helps.
I know the answer is old, but maybe someone will come in handy.
The simplest way is to use of onAnimationProgress event.
Like this.
var myDoughnutChart = new Chart(ctx).Doughnut(data, {
onAnimationProgress: function() {
const dataset = this.data.datasets[0];
const model = dataset._meta[Object.keys(dataset._meta)[0]]?.data[0]?._model;
if (!model) { return; }
// model.x and model.y is the center of the chart
this.ctx.fillText('00%', model.x, model.y);
}
});
Step by step tutorial about: How to Add Multiple Text Labels Stacked in Doughnut Chart in Chart JS
[https://www.youtube.com/watch?v=_7w52T9aemo][1]

Categories