I'm trying to make a data visualization through a circular diagram. The data is being pulled out of my database and in to an associative array. The Object keys in this case are addresses and the values are numbers (how many times they exist in my database). This is the short version of my code:
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var s = 0;
//var t = total sum of values
var l = Object.keys(arr);
for (var i=0;i<l.length;i++){
var e = Object.values(arr)[i]/t;
var perc = 2*Math.PI*e;
ctx.beginPath();
ctx.moveTo(canvas.width / 2, canvas.height / 2);
ctx.arc(canvas.width/2,canvas.height/2,canvas.height/2,s,s + perc,false);
ctx.lineTo(canvas.width/2,canvas.height/2);
ctx.stroke();
s += perc;
}
This is the current result:
picture
As you can see it's not a round circle as it's supposed to be.
I hope someone can help me.
UPDATE
So the strangest thing happened. Apparently there is a difference between the height and width on the canvas set by css and those who's set by javascript. I just gave my canvas canvas.width=400 and canvas.height=400 in javascript and deleted my css height=400px and width=400px. All of a sudden the circle is how it's supposed to be. Very, very strange.
Related
I'm trying to make a line graph using the canvas that looks like a typical line graph and uses typical Cartesian coordinates like we learned in algebra;
starts with 0,0 at the bottom left, and the position x-axis is to be determined by the number of items to chart.
However, the position of the points doesn't match the input (although the shape of the graph is correct, indicating I'm doing something right). What am I doing wrong?
I've rewritten and tweaked the formula for converting numerous times
function newLineGraph(parent, width, height, dataArray) {
//this makes the element using my own code, no observable error here
var canvas = newCanvas(parent, width, height);
var canvasContext = canvas.getContext("2d");
var spaceBetweenEntries = width / dataArray.length;
var largestNumber = findHighestNumber(dataArray);
canvasContext.beginPath();
canvasContext.moveTo(0, 0);
var n = 0;
while (dataArray[n]) {
var x = spaceBetweenEntries * n;
var y = height - dataArray[n];
console.log("x,y", x, y);
canvasContext.lineTo(x, y);
n++;
}
canvasContext.stroke();
return canvas;
}
edit: fixed the image so you can see the canvas size
The resulting graph is much smaller than the intended graph; for example
newLineGraph("body",55,45,[1,40,10]);
produces a graph with a small ^ shape in the corner, rather than properly starting at the bottom. However, the console logs show " 0 44" "18.333333333333332 5","36.666666666666664 35" which I believe should produce a graph that fits the whole chart nicely.
The first lineTo will always have x as 0 so I assume the first line isn't drawing like you intended. It is more like a |/\ shape instead of \/\.
Set x like this:
var x = spaceBetweenEntries * (n + 1);
Edit
As you can see in this fiddle your chart renders at the right points with the coordinates you posted. I implemented the newCanvas function like I expect it to behave. So are we missing some other code that modifies the canvas width and height?
function newCanvas(parent, width, height) {
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
document.querySelector(parent).appendChild(canvas);
return canvas;
}
The problem was using style.width and style.height to modify the canvas height, instead of canvas.height and canvas.width
I'm trying to make a text effect similar to the effect found at the bottom of this article
My proposed approach is:
Make two canvasses, one is visible, the other is invisible I use this as a buffer.
Draw some text on the buffer canvas
Loop over getImageData pixels
if pixel alpha is not equal to zero (when there is a pixel drawn on the canvas buffer) with a small chance, ie 2%, draw a randomly generated circle with cool effecs at that pixel on the visible canvas.
I'm having trouble at step 4. With the code below, I'm trying to replicate the text on the second canvas, in full red. Instead I get this weird picture.
code
// create the canvas to replicate the buffer text on.
var draw = new Drawing(true);
var bufferText = function (size, textFont) {
// set the font to Georgia if it isn't defined
textFont = textFont || "Georgia";
// create a new canvas buffer, true means that it's visible on the screen
// Note, Drawing is a small library I wrote, it's just a wrapper over the canvas API
// it creates a new canvas and adds some functions to the context
// it doesn't change any of the original functions
var buffer = new Drawing(true);
// context is just a small wrapper library I wrote to make the canvas API a little more bearable.
with (buffer.context) {
font = util.format("{size}px {font}", {size: size, font: textFont});
fillText("Hi there", 0, size);
}
// get the imagedata and store the actual pixels array in data
var imageData = buffer.context.getImageData(0, 0, buffer.canvas.width, buffer.canvas.height);
var data = imageData.data;
var index, alpha, x, y;
// loop over the pixels
for (x = 0; x < imageData.width; x++) {
for (y = 0; y < imageData.height; y++) {
index = x * y * 4;
alpha = data[index + 3];
// if the alpha is not equal to 0, draw a red pixel at (x, y)
if (alpha !== 0) {
with (draw.context) {
dot(x/4, y/4, {fillColor: "red"})
}
}
}
}
};
bufferText(20);
Note that here, my buffer is actually visible to show where the red pixels are supposed to go compared to where they actually go.
I'm really confused by this problem.
If anybody knows an alternative approach, that's very welcome too.
replace this...
index = x * y * 4;
with...
index = (imageData.width * y) + x;
the rest is good :)
I'm trying to implement ColorPicker using Canvas just for fun. But i seem lost. as my browser is freezing for a while when it loads due to all these for loops.
I'm adding the screenshot of the result of this script:
window.onload = function(){
colorPicker();
}
function colorPicker(){
var canvas = document.getElementById("colDisp"),
frame = canvas.getContext("2d");
var r=0,
g=0,
b= 0;
function drawColor(){
for(r=0;r<255;r++){
for(g=0;g<255;g++){
for(b=0;b<255;b++){
frame.fillStyle="rgb("+r+","+g+","+b+")";
frame.fillRect(r,g,1,1);
}
}
}
}
drawColor();
Currently , i only want a solution about the freezing problem with better algorithm and it's not displaying the BLACK and GREY colors.
Please someone help me.
Instead of calling fillRect for every single pixel, it might be a lot more efficient to work with a raw RGBA buffer. You can obtain one using context.getImageData, fill it with the color values, and then put it back in one go using context.putImageData.
Note that your current code overwrites each single pixel 255 times, once for each possible blue-value. The final pass on each pixel is 255 blue, so you see no grey and black in the output.
Finding a good way to map all possible RGB values to a two-dimensional image isn't trivial, because RGB is a three-dimensional color-space. There are a lot of strategies for doing so, but none is really optimal for any possible use-case. You can find some creative solutions for this problem on AllRGB.com. A few of them might be suitable for a color-picker for some use-cases.
If you want to fetch the rgba of the pixel under the mouse, you must use context.getImageData.
getImageData returns an array of pixels.
var pixeldata=context.getImageData(0,0,canvas.width,canvas.height);
Each pixel is defined by 4 sequential array elements.
So if you have gotten a pixel array with getImageData:
// first pixel defined by the first 4 pixel array elements
pixeldata[0] = red component of pixel#1
pixeldata[1] = green component of pixel#1
pixeldata[2] = blue component of pixel#1
pixeldata[4] = alpha (opacity) component of pixel#1
// second pixel defined by the next 4 pixel array elements
pixeldata[5] = red component of pixel#2
pixeldata[6] = green component of pixel#2
pixeldata[7] = blue component of pixel#2
pixeldata[8] = alpha (opacity) component of pixel#2
So if you have a mouseX and mouseY then you can get the r,g,b,a values under the mouse like this:
// get the offset in the array where mouseX,mouseY begin
var offset=(imageWidth*mouseY+mouseX)*4;
// read the red,blue,green and alpha values of that pixel
var red = pixeldata[offset];
var green = pixeldata[offset+1];
var blue = pixeldata[offset+2];
var alpha = pixeldata[offset+3];
Here's a demo that draws a colorwheel on the canvas and displays the RGBA under the mouse:
http://jsfiddle.net/m1erickson/94BAQ/
A way to go, using .createImageData():
window.onload = function() {
var canvas = document.getElementById("colDisp");
var frame = canvas.getContext("2d");
var width = canvas.width;
var height = canvas.height;
var imagedata = frame.createImageData(width, height);
var index, x, y;
for (x = 0; x < width; x++) {
for (y = 0; y < height; y++) {
index = (x * width + y) * 4;
imagedata.data[index + 0] = x;
imagedata.data[index + 1] = y;
imagedata.data[index + 2] = x + y - 255;
imagedata.data[index + 3] = 255;
}
}
frame.putImageData(imagedata, 0, 0);
};
http://codepen.io/anon/pen/vGcaF
i draw a canvas(aka canvas 1) with image() then rotate it 25 degree. then i take rotated canvas to make a pattern for another canvas(aka canvas 2). then i draw this . and fill the fillstyle with newly created pattern. i noticed if alert in the middle of below code
finalsleeve_ctx.globalCompositeOperation = "source-atop";
/*****************************************
alert(sleeve.toDataURL('image/png'));
*****************************************/
var pattern = finalsleeve_ctx.createPattern(sleeve, 'repeat');
then firefox gives a correct output but if i dont do alert it does not give me correct output. crome not showing me correct output.
do i need to delay ?
here is what i have tried.
HTML
<div >
<canvas id="sleeve" width=436 height=567></canvas>
<canvas id="finalsleeve" width=436 height=567 ></canvas>
</div>
JS
var sleeve = document.getElementById('sleeve');
var sleeve_ctx = sleeve.getContext('2d');
var finalsleeve = document.getElementById('finalsleeve');
var finalsleeve_ctx = finalsleeve.getContext('2d');
function rotator2(var2,var3)
{
sleeve.width=sleeve.width;
var imageObj_rotator2 = new Image();
imageObj_rotator2.onload = function ()
{
var pattern_rotator2 = sleeve_ctx.createPattern(imageObj_rotator2, "repeat");
sleeve_ctx.fillStyle = pattern_rotator2;
sleeve_ctx.rect(0, 0, sleeve.width, sleeve.height);
sleeve_ctx.rotate(var3 * Math.PI/180);
sleeve_ctx.fill();
}
imageObj_rotator2.src = var2;
}
function drawSleeve()
{
finalsleeve.width = finalsleeve.width;
var imgsleeve = new Image();
imgsleeve.src="http://i.stack.imgur.com/FoqGC.png";
finalsleeve_ctx.drawImage(imgsleeve,0,0);
finalsleeve_ctx.globalCompositeOperation = "source-atop";
alert(sleeve.toDataURL('image/png'));
var pattern = finalsleeve_ctx.createPattern(sleeve, 'repeat');
finalsleeve_ctx.rect(0, 0, sleeve.width, sleeve.height);
finalsleeve_ctx.fillStyle = pattern;
finalsleeve_ctx.fill();
finalsleeve_ctx.globalAlpha = .10;
finalsleeve_ctx.drawImage(imgsleeve, 0, 0);
finalsleeve_ctx.drawImage(imgsleeve, 0, 0);
finalsleeve_ctx.drawImage(imgsleeve, 0, 0);
}
rotator2('http://i.stack.imgur.com/fvpMN.png','25');
drawSleeve();
Here is fiddle.
http://jsfiddle.net/EbBHz/
EDITED
Sorry, I completely misunderstood your question. I just now went back and saw the last question you posted and the goal you are trying to achieve.
To get the functionality you desire you can just create one function, you don't need two. Instead of using a second canvas in the HTML I created a temporary one using javascript.
Here is the simplified and functional code
<canvas id="sleeve" width='436' height='567'></canvas>
var sleeve = document.getElementById('sleeve');
var ctx = sleeve.getContext('2d');
function rotator2(var2, var3) {
// Draw the original sleeves
var imageObj_rotator2 = new Image();
imageObj_rotator2.src = var2;
imageObj_rotator2.onload = function () {
var imgsleeve = new Image();
imgsleeve.src="http://i.stack.imgur.com/FoqGC.png";
ctx.drawImage(imgsleeve,0,0);
// Create a second temporary canvas
var pattern = document.createElement('canvas');
pattern.width = 500;
pattern.height = 500;
var pctx = pattern.getContext('2d');
// Make the pattern that fills the generated canvas
var pattern_rotator2 = pctx.createPattern(imageObj_rotator2, "repeat");
pctx.fillStyle = pattern_rotator2;
pctx.rotate(var3 * Math.PI / 180);
// Fill the generated canvas with the rotated image pattern we just created
pctx.fillRect(0, 0, pattern.width, pattern.height);
// Create a pattern of the generated canvas
var patterned = ctx.createPattern(pattern, "repeat");
// Fills in the non-transparent part of the image with whatever the
// pattern from the second canvas is
ctx.globalCompositeOperation = "source-in";
ctx.fillStyle = patterned;
ctx.fillRect(0, 0, sleeve.width, sleeve.height);
}
}
rotator2('http://i.stack.imgur.com/fvpMN.png', '45')
The technique works alright, but only for certain angles. Here is the demo set to 45 degrees. As you can see, there is a problem: part of the sleeve is whited out. However, if you change the degree to 15 like this it works just fine. This is because when the image is being rotated in the created canvas it leaves white space before repeating. To see this issue first hand, change the width and the height of the created canvas to 30 (the default width/height of the image) like this
Note: You may have to click run once the jsfiddle tab is open, canvases don't like generating content when another tab is focused
I tried problem solving the issue including
Making the generated canvas really large (which works but KILLS load
time/crashes page sometimes)
Translating the picture in the generated canvas after rotating it
which didn't work like I had hoped
Coming up with a function to change the width/height to cover the
entire first canvas based on the rotated second-canvas-dimensions, which is by far the most promising, but I don't have the time or desire to work out a good solution
All that being said if the angle HAS to be dynamic you can work on a function for it. Otherwise just use a workaround angle/generated canvas dimensions
final result> Here is a working solution for fill rotated pattern without white at any angle
var sleeve = document.getElementById('sleeve');
var ctx = sleeve.getContext('2d');
function rotator2(var2, var3) {
var x =0;
var y=0;
//pattern size should be grater than height and width of object so that white space does't appear.
var patternSize = sleeve.width+ sleeve.height;
// Draw the original sleeves
var imageObj_rotator2 = new Image();
imageObj_rotator2.src = var2;
imageObj_rotator2.onload = function () {
var imgsleeve = new Image();
imgsleeve.src="http://i.stack.imgur.com/FoqGC.png";
ctx.drawImage(imgsleeve,0,0);
// Create a second temporary canvas
var pattern = document.createElement('canvas');
pattern.width = sleeve.width;
pattern.height = sleeve.height;
var pctx = pattern.getContext('2d');
// Make the pattern that fills the generated canvas
var pattern_rotator2 = pctx.createPattern(imageObj_rotator2, "repeat");
pctx.fillStyle = pattern_rotator2;
//moving rotation point to center of target object.
pctx.translate(x+ sleeve.width/2, y+sleeve.height/2);
pctx.rotate(var3 * Math.PI / 180);
// Fill the generated canvas with the rotated image pattern we just created and expanding size from center of rotated angle
pctx.fillRect(-patternSize/2, -patternSize/2, patternSize, patternSize);
// Create a pattern of the generated canvas
var patterned = ctx.createPattern(pattern, "no-repeat");
// Fills in the non-transparent part of the image with whatever the
// pattern from the second canvas is
ctx.globalCompositeOperation = "source-in";
ctx.fillStyle = patterned;
ctx.fillRect(x, y, sleeve.width, sleeve.height);
}
}
rotator2('http://i.stack.imgur.com/fvpMN.png', '50')
I know how to get this height of a font:
By placing the text in a div and getting offset height of the div.
But I would like to get this actual height (Which will depend on font family):
Is that in any way possible using web based programming?
Is there a simple solution? I think the answer is no.
If you're ok with a more involved (and processor-intensive) solution, you could try this:
Render the text to a canvas, then use canvasCtx.getImageData(..) to retrieve pixel information. Next you would do something similar to what this pseudo code describes:
first_y : null
last_y : null
for each y:
for each x:
if imageData[x][y] is black:
if first_y is null:
first_y = y
last_y = y
height = last_y - first_y
This basically looks for the top (lowest y-index) of the lettering (black pixels) and the bottom (highest y-index) then subtracts to retrieve the height.
I was writing the code while Jason answered, but I decided to post it anyway:
http://jsfiddle.net/adtn8/2/
If you follow the comments you should get the idea what's going on and why. It works pretty fast and it's not so complicated as it may sound. Checked with GIMP and it is accurate.
(code to be sure it wont be lost):
// setup variables
var c = document.createElement('canvas'),
div = document.getElementsByTagName('div')[0],
out = document.getElementsByTagName('output')[0];
// set canvas's size to be equal with div
c.width = div.offsetWidth;
c.height = div.offsetHeight;
var ctx = c.getContext('2d');
// get div's font from computed style and apply it to context
ctx.font = window.getComputedStyle(div).font;
// use color other than black because all pixels are 0 when black and transparent
ctx.fillStyle = '#bbb';
// draw the text near the bottom of the canvas
ctx.fillText(div.innerText, 0, div.offsetHeight);
// loop trough the canvas' data to find first colored pixel
var data = ctx.getImageData(0, 0, c.width, c.height).data,
minY = 0, len = data.length;
for (var i = 0; i < len; i += 4) {
// when you found it
if (data[i] != 0) {
// get pixel's y position
minY = Math.floor(i / 4 / c.width);
break;
}
}
// and print out the results
out.innerText = c.height - minY + 'px';
EDIT:
I even made jQuery plugin for this: https://github.com/maciek134/jquery-textHeight
Enjoy.