Add text to raphaelJS pie chart? - javascript

Trying to create an interactive pie chart and raphaelJS seemed like the best solution. I have almost zero experience with it, (though I'm fairly proficient with jquery) so pardon if I come off as a complete idiot.
Question: I've created a functional interactive pie chart that displays text on hover. My problem is that I can't figure out how to style the text as I need to - adding and tags to the html doesn't have an effect
Here is the script - I'll try to just include the relevant sections:
var angle = -18,
total = 0,
start = 0,
process = function (j) {
var value = values[j],
angleplus = 360 * value / total,
popangle = angle + (angleplus / 2), // angle of text display
color = Raphael.hsb(start, .9, 1),
ms = 300,
delta = 30,
bcolor = Raphael.hsb(start, 1, 1),
p = sector(cx, cy, r, angle, angle + angleplus, {
fill: "90-" + bcolor + "-" + color,
stroke: "#F3F5F2",
"stroke-width": 4,
"stroke-opacity": 1,
opacity: .5}),
txt = paper.text(cx + (r + delta + 155) * Math.cos(0 * rad),
cy + (r + delta + 75) * Math.sin(-10 * rad),
labels[j]).attr({
fill: "#111",
stroke: "none",
opacity: 0,
"font-family": "Gotham",
"font-size": 14});
});
Here's the jsfiddle with the rest of the script and html (can't get it to run correctly though, works fine normally) - http://jsfiddle.net/9L286/
Currently, "txt" defines the style for text - I want to be able to style both the title ("item 1") and the description. Hopefully I'm just blind and there's a simple solution. If more info is needed, just ask. Thanks!

Related

How to create a color picker circle like Phillips Hue

I'm trying to create a color picker in javascript canvas. I want to make something like this:
I want that when i click anywhere in the circle, i get x, y coordinates (so I can use some other element to mark the selected color) and color.
I would prefer if the solution was not a canvas with embedded image, but something like linear-gradient.
I have searched the internet, but have not found anything to my question.
P.S.: Maybe css border-radius will help to make a circle.
Disclaimer: this answer is intended as an improovement of rickdenhaan's answer.
You can stack css gradients to obtain the color wheel, and use simple math to compute the color at given coordinates without actually picking the color from the wheel.
This approach doesn't use EyeDropper, so it should have full browsers support. In the following code I have bind the code to the mousemove event to test it easily; just replace the event name with "click" to get the expected behaviour:
const colors = [
{r: 0xe4, g: 0x3f, b: 0x00},
{r: 0xfa, g: 0xe4, b: 0x10},
{r: 0x55, g: 0xcc, b: 0x3b},
{r: 0x09, g: 0xad, b: 0xff},
{r: 0x6b, g: 0x0e, b: 0xfd},
{r: 0xe7, g: 0x0d, b: 0x86},
{r: 0xe4, g: 0x3f, b: 0x00}
];
document.addEventListener('DOMContentLoaded', function() {
document.getElementById('color-wheel').addEventListener('mousemove', function(e) {
var rect = e.target.getBoundingClientRect();
//Compute cartesian coordinates as if the circle radius was 1
var x = 2 * (e.clientX - rect.left) / (rect.right - rect.left) - 1;
var y = 1 - 2 * (e.clientY - rect.top) / (rect.bottom - rect.top);
//Compute the angle in degrees with 0 at the top and turning clockwise as expected by css conic gradient
var a = ((Math.PI / 2 - Math.atan2(y, x)) / Math.PI * 180);
if (a < 0) a += 360;
//Map the angle between 0 and number of colors in the gradient minus one
a = a / 360 * (colors.length - 1); //minus one because the last item is at 360° which is the same as 0°
//Compute the colors to interpolate
var a0 = Math.floor(a) % colors.length;
var a1 = (a0 + 1) % colors.length;
var c0 = colors[a0];
var c1 = colors[a1];
//Compute the weights and interpolate colors
var a1w = a - Math.floor(a);
var a0w = 1 - a1w;
var color = {
r: c0.r * a0w + c1.r * a1w,
g: c0.g * a0w + c1.g * a1w,
b: c0.b * a0w + c1.b * a1w
};
//Compute the radius
var r = Math.sqrt(x * x + y * y);
if (r > 1) r = 1;
//Compute the white weight, interpolate, and round to integer
var cw = r < 0.8 ? (r / 0.8) : 1;
var ww = 1 - cw;
color.r = Math.round(color.r * cw + 255 * ww);
color.g = Math.round(color.g * cw + 255 * ww);
color.b = Math.round(color.b * cw + 255 * ww);
//Compute the hex color code and apply it
var xColor = rgbToHex(color.r, color.g, color.b);
document.getElementById('color').innerText = xColor;
document.getElementById('color').style.backgroundColor = xColor;
});
});
function componentToHex(c) {
var hex = c.toString(16);
return hex.length == 1 ? "0" + hex : hex;
}
function rgbToHex(r, g, b) {
return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b);
}
#color-wheel {
width: 150px;
height: 150px;
background: radial-gradient(white, transparent 80%),
conic-gradient(#e43f00, #fae410, #55cc3b, #09adff, #6b0efd, #e70d86, #e43f00);
border-radius: 50%;
}
<div id="color-wheel"></div>
Color: <span id="color"></span>
If you really don't want to use an image, you need to currently draw each pixel by hand. I'm sure there's mathematical functions for this somewhere.
The reason you can't do this using gradients is because to get a gradient color wheel like that, you would need to stack a radial gradient and a conic gradient. The conic gradient will create the color wheel, the radial gradient will provide the white overlay that spreads out from the center.
You cannot currently do this using a canvas, because not all browsers currently support the createConicGradient function on a canvas. In those browsers, you can only use radial gradients (and linear gradients, of course, but those won't help for this).
You can use stacked CSS background gradients, but browsers do not currently have a reliable way to determine an individual pixel's background color at specific X/Y coordinates, except within a canvas. And since CSS background styles aren't part of a canvas's context, that won't work. There are workarounds that make use of the html2canvas library, but this has the same problem in browsers that do not support conic gradients on canvases.
Browsers are starting to implement the EyeDropper API but this is still rare to find and even if browsers do have it, it's an unstable API and not really suited for production use. But if it's supported you could use that with the stacked CSS background gradients on a regular div like so, no canvas needed:
document.addEventListener('DOMContentLoaded', function() {
document.getElementById('color-wheel').addEventListener('click', function() {
(new EyeDropper()).open().then(function(result) {
document.getElementById('color').innerText = result.sRGBHex;
});
});
});
#color-wheel {
width: 150px;
height: 150px;
background: radial-gradient(white, transparent 80%),
conic-gradient(#e43f00, #fae410, #55cc3b, #09adff, #6b0efd, #e70d86, #e43f00);
border-radius: 50%;
}
<div id="color-wheel"></div>
Color: <span id="color"></span>
But as I said, this is unstable and will definitely not work in all browsers for the foreseeable future.

How to center a group of SVG shapes with Snap.SVG & Javascript

I'm trying to make a circle of rectangles, to be centered (vertically and horizontally) in an SVG
here's my code so far:
http://jsfiddle.net/JamThom/7G2pC/
var a = Snap(250,250);
for (var i=0;i<90;i++) {
var sqz = a.rect(0, 0, 14, 3)
.attr({
fill: '#666',
transform: "r"+i*4+",75,0"
})
.data("i",i);
}
In order to do this I believe I need to first select and group all of the rectangles, but as I created them in a for loop they don't have individual names and I don't know how to target them
any help appreciated
and apologies for the ameteur-ness of my code - this is my first experience with snap.svg
You can move it to the center using translate:
transform: "t50,125 r"+i*4+",75,0"
JSFiddle
Your SVG is 250x250 and the radius is 75, so the coordinates for t are 250 / 2 - 75 = 50 and 250 / 2 = 125. In the fiddle I used variables.
Just as a slight variation to helderdarocha for an answer, you can as you mention add them to a group and translate, like this fiddle here
var skillometer = Snap(size, size);
var g = skillometer.g();
g.attr({ transform: 't' + x + ',' + y });
for (var i = 0; i < 90; i++) {
var sqz = skillometer.rect(0, 0, 14, 3)
.attr({
fill: '#666',
transform: "r" + i * 4 + "," + radius + ",0"
}).data("i", i);
g.append( sqz );
}
// you can now animate or move the whole group as one.
g.animate({ transform: 't'+x+','+y+'s1.65' }, 5000 );
There's not a lot of difference, unless you want to move or animate the whole lot, in which case this group method may be preferable, as you can move the group in the future with a transform on just the group element (have included that just to highlight).

Handdrawn circle simulation in HTML 5 canvas

The following code creates a circle in HTML 5 Canvas using jQuery:
Code:
//get a reference to the canvas
var ctx = $('#canvas')[0].getContext("2d");
DrawCircle(75, 75, 20);
//draw a circle
function DrawCircle(x, y, radius)
{
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI*2, true);
ctx.fillStyle = 'transparent';
ctx.lineWidth = 2;
ctx.strokeStyle = '#003300';
ctx.stroke();
ctx.closePath();
ctx.fill();
}
I am trying to simulate any of the following types of circles:
I have researched and found this article but was unable to apply it.
I would like for the circle to be drawn rather than just appear.
Is there a better way to do this? I'm sensing there's going to be a lot of math involved :)
P.S. I like the simplicity of PaperJs, maybe this would be the easiest approach using it's simplified paths?
There are already good solutions presented here. I wanted to add a variations of what is already presented - there are not many options beyond some trigonometry if one want to simulate hand drawn circles.
I would first recommend to actually record a real hand drawn circle. You can record the points as well as the timeStamp and reproduce the exact drawing at any time later. You could combine this with a line smoothing algorithm.
This here solution produces circles such as these:
You can change color, thickness etc. by setting the strokeStyle, lineWidth etc. as usual.
To draw a circle just call:
handDrawCircle(context, x, y, radius [, rounds] [, callback]);
(callback is provided as the animation makes the function asynchronous).
The code is separated into two segments:
Generate the points
Animate the points
Initialization:
function handDrawCircle(ctx, cx, cy, r, rounds, callback) {
/// rounds is optional, defaults to 3 rounds
rounds = rounds ? rounds : 3;
var x, y, /// the calced point
tol = Math.random() * (r * 0.03) + (r * 0.025), ///tolerance / fluctation
dx = Math.random() * tol * 0.75, /// "bouncer" values
dy = Math.random() * tol * 0.75,
ix = (Math.random() - 1) * (r * 0.0044), /// speed /incremental
iy = (Math.random() - 1) * (r * 0.0033),
rx = r + Math.random() * tol, /// radius X
ry = (r + Math.random() * tol) * 0.8, /// radius Y
a = 0, /// angle
ad = 3, /// angle delta (resolution)
i = 0, /// counter
start = Math.random() + 50, /// random delta start
tot = 360 * rounds + Math.random() * 50 - 100, /// end angle
points = [], /// the points array
deg2rad = Math.PI / 180; /// degrees to radians
In the main loop we don't bounce around randomly but increment with a random value and then increment linearly with that value, reverse it if we are at bounds (tolerance).
for (; i < tot; i += ad) {
dx += ix;
dy += iy;
if (dx < -tol || dx > tol) ix = -ix;
if (dy < -tol || dy > tol) iy = -iy;
x = cx + (rx + dx * 2) * Math.cos(i * deg2rad + start);
y = cy + (ry + dy * 2) * Math.sin(i * deg2rad + start);
points.push(x, y);
}
And in the last segment we just render what we have of points.
The speed is determined by da (delta angle) in the previous step:
i = 2;
/// start line
ctx.beginPath();
ctx.moveTo(points[0], points[1]);
/// call loop
draw();
function draw() {
ctx.lineTo(points[i], points[i + 1]);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(points[i], points[i + 1]);
i += 2;
if (i < points.length) {
requestAnimationFrame(draw);
} else {
if (typeof callback === 'function')
callback();
}
}
}
Tip: To get a more realistic stroke you can reduce globalAlpha to for example 0.7.
However, for this to work properly you need to draw solid to an off-screen canvas first and then blit that off-screen canvas to main canvas (which has the globalAlpha set) for each frame or else the strokes will overlap between each point (which does not look good).
For squares you can use the same approach as with the circle but instead of using radius and angle you apply the variations to a line. Offset the deltas to make the line non-straight.
I tweaked the values a little but feel free to tweak them more to get a better result.
To make the circle "tilt" a little you can first rotate the canvas a little:
rotate = Math.random() * 0.5;
ctx.save();
ctx.translate(cx, cy);
ctx.rotate(-rotate);
ctx.translate(-cx, -cy);
and when the loop finishes:
if (i < points.length) {
requestAnimationFrame(draw);
} else {
ctx.restore();
}
(included in the demo linked above).
The circle will look more like this:
Update
To deal with the issues mentioned (comment fields too small :-) ): it's actually a bit more complicated to do animated lines, especially in a case like this where you a circular movement as well as a random boundary.
Ref. comments point 1: the tolerance is closely related to radius as it defined max fluctuation. We can modify the code to adopt a tolerance (and ix/iy as they defines how "fast" it will variate) based on radius. This is what I mean by tweaking, to find that value/sweet-spot that works well with all sizes. The smaller the circle the smaller the variations. Optionally specify these values as arguments to the function.
Point 2: since we're animating the circle the function becomes asynchronous. If we draw two circles right after each other they will mess up the canvas as seen as new points are added to the path from both circles which then gets stroked criss-crossed.
We can get around this by providing a callback mechanism:
handDrawCircle(context, x, y, radius [, rounds] [, callback]);
and then when the animation has finished:
if (i < points.length) {
requestAnimationFrame(draw);
} else {
ctx.restore();
if (typeof callback === 'function')
callback(); /// call next function
}
Another issues one will run into with the code as-is (remember that the code is meant as an example not a full solution :-) ) is with thick lines:
When we draw segment by segment separately canvas does not know how to calculate the butt angle of the line in relation to previous segment. This is part of the path-concept. When you stroke a path with several segments canvas know at what angle the butt (end of the line) will be at. So here we to either draw the line from start to current point and do a clear in between or only small lineWidth values.
When we use clearRect (which will make the line smooth and not "jaggy" as when we don't use a clear in between but just draw on top) we would need to consider implementing a top canvas to do the animation with and when animation finishes we draw the result to main canvas.
Now we start to see part of the "complexity" involved. This is of course because canvas is "low-level" in the sense that we need to provide all logic for everything. We are basically building systems each time we do something more with canvas than just draw simple shapes and images (but this also gives the great flexibility).
Here are some basics I created for this answer:
http://jsfiddle.net/Exceeder/TPDmn/
Basically, when you draw a circle, you need to account for hand imperfections. So, in the following code:
var img = new Image();
img.src="data:image/png;base64,...";
var ctx = $('#sketch')[0].getContext('2d');
function draw(x,y) {
ctx.drawImage(img, x, y);
}
for (var i=0; i<500; i++) {
var radiusError = +10 - i/20;
var d = 2*Math.PI/360 * i;
draw(200 + 100*Math.cos(d), 200 + (radiusError+80)*Math.sin(d) );
}
Pay attention how vertical radiusError changes when the angle (and the position) grows. You are welcome to play with this fiddle until you get a "feel" what component does what. E.g. it would make sense to introduce another component to radiusError that emulates "unsteady" hand by slowly changing it my random amounts.
There are many different ways to do this. I choose trig functions for the simplicity of the simulation, as speed is not a factor here.
Update:
This, for example, will make it less perfect:
var d = 2*Math.PI/360 * i;
var radiusError = +10 - i/20 + 10*Math.sin(d);
Obviously, the center of the circle is at (200,200), as the formula for drawing a circle (rather, ellipsis with vertical radius RY and horizontal radius RX) with trigonometric functions is
x = centerX + RX * cos ( angle )
y = centerY + RY * sin ( angle )
Your task seems to have 3 requirements:
A hand-drawn shape.
An “organic” rather than “ultra-precise” stroke.
Revealing the circle incrementally instead of all-at-once.
To get started, check out this nice on-target demo by Andrew Trice.
This amazing circle is hand drawn by me (you can laugh now...!)
Andrew's demo does steps 1 and 2 of your requirements.
It lets you hand draw a circle (or any shape) using an organic looking “brush effect” instead of the usual ultra-precise lines normally used in canvas.
It achieves the “brush effect” by by repeated drawing a brush image between hand drawn points
Here’s the demo:
http://tricedesigns.com/portfolio/sketch/brush.html#
And the code is available on GitHub:
https://github.com/triceam/HTML5-Canvas-Brush-Sketch
Andrew Trice’s demo draws-and-forgets the lines that make up your circle.
Your task would be to impliment your third requirement (remembering strokes):
Hand draw a circle of your own,
Save each line segment that makes up your circle in an array,
“Play” those segements using Andrew’s stylized brush technique.
Results: A hand-drawn and stylized circle that appears incrementally instead of all at once.
You have an interesting project…If you feel generous, please share your results!
See live demo here. Also available as a gist.
<div id="container">
<svg width="100%" height="100%" viewBox='-1.5 -1.5 3 3'></svg>
</div>
#container {
width:500px;
height:300px;
}
path.ln {
stroke-width: 3px;
stroke: #666;
fill: none;
vector-effect: non-scaling-stroke;
stroke-dasharray: 1000;
stroke-dashoffset: 1000;
-webkit-animation: dash 5s ease-in forwards;
-moz-animation:dash 5s ease-in forwards;
-o-animation:dash 5s ease-in forwards;
animation:dash 5s ease-in forwards;
}
#keyframes dash {
to { stroke-dashoffset: 0; }
}
function path(δr_min,δr_max, el0_min, el0_max, δel_min,δel_max) {
var c = 0.551915024494;
var atan = Math.atan(c)
var d = Math.sqrt( c * c + 1 * 1 ), r = 1;
var el = (el0_min + Math.random() * (el0_max - el0_min)) * Math.PI / 180;
var path = 'M';
path += [r * Math.sin(el), r * Math.cos(el)];
path += ' C' + [d * r * Math.sin(el + atan), d * r * Math.cos(el + atan)];
for (var i = 0; i < 4; i++) {
el += Math.PI / 2 * (1 + δel_min + Math.random() * (δel_max - δel_min));
r *= (1 + δr_min + Math.random()*(δr_max - δr_min));
path += ' ' + (i?'S':'') + [d * r * Math.sin(el - atan), d * r * Math.cos(el - atan)];
path += ' ' + [r * Math.sin(el), r * Math.cos(el)];
}
return path;
}
function cX(λ_min, λ_max, el_min, el_max) {
var el = (el_min + Math.random()*(el_max - el_min));
return 'rotate(' + el + ') ' + 'scale(1, ' + (λ_min + Math.random()*(λ_max - λ_min)) + ')'+ 'rotate(' + (-el) + ')';
}
function canvasArea() {
var width = Math.floor((Math.random() * 500) + 450);
var height = Math.floor((Math.random() * 300) + 250);
$('#container').width(width).height(height);
}
d3.selectAll( 'svg' ).append( 'path' ).classed( 'ln', true) .attr( 'd', path(-0.1,0, 0,360, 0,0.2 )).attr( 'transform', cX( 0.6, 0.8, 0, 360 ));
setTimeout(function() { location = '' } ,5000)

Change Background-color on scroll

I'm wanting to create an effect like the one on the following page http://readymag.com/ where the background color changes depending on the scroll position but have no idea how to go about it and I can't understand their code.
I've seen a few examples that change from 1 color to another but I'm unsure how to do it with multiple colors.
(I would like to be able to specify each color)
Any help would be greatly appreciated.
Michael.
Here is a simple way to do it:
HTML
<body onscroll="scroll()">
...
</body>
JavaScript
// HSL Colors
var colors = [
[0, 100, 50],
[113, 75, 25],
[240, 87, 40],
[328, 24, 40]
],
el = document.getElementsByTagName('body')[0], // Element to be scrolled
length = colors.length, // Number of colors
height = Math.round(el.offsetHeight / length); // Height of the segment between two colors
function scroll() {
var i = Math.floor(el.scrollTop / height), // Start color index
d = el.scrollTop % height / height, // Which part of the segment between start color and end color is passed
c1 = colors[i], // Start color
c2 = colors[(i+1)%length], // End color
h = c1[0] + Math.round((c2[0] - c1[0]) * d),
s = c1[1] + Math.round((c2[1] - c1[1]) * d),
l = c1[2] + Math.round((c2[2] - c1[2]) * d);
el.style['background-color'] = ['hsl(', h, ', ', s+'%, ', l, '%)'].join('');
}
Working example: http://jsbin.com/elolud/2/edit

How to animate both rotation and transformation in Raphaël

I'm trying to do something I thought would be rather simple. I've an object that I move around stepwise, i.e. I receive messages every say 100 milliseconds that tell me "your object has moved x pixels to the right and y pixels down". The code below simulates that by moving that object on a circle, but note that it is not known in advance where the object will be heading in the next step.
Anyway, that is pretty simple. But now I want to also tell the object, which is actually a set of subobjects, that it is being rotated.
Unfortunately, I am having trouble getting Raphaël to do what I want. I believe the reason is that while I can animate both translation and rotation independently, I have to set the center of the rotation when it starts. Obviously the center of the rotation changes as the object is moving.
Here's the code I'm using and you can view a live demo here. As you can see, the square rotates as expected, but the arrow rotates incorrectly.
// c&p this into http://raphaeljs.com/playground.html
var WORLD_SIZE = 400,
rect = paper.rect(WORLD_SIZE / 2 - 20, 0, 40, 40, 5).attr({ fill: 'red' }),
pointer = paper.path("M 200 20 L 200 50"),
debug = paper.text(25, 10, ""),
obj = paper.set();
obj.push(rect, pointer);
var t = 0,
step = 0.05;
setInterval(function () {
var deg = Math.round(Raphael.deg(t));
t += step;
debug.attr({ text: deg + '°' });
var dx = ((WORLD_SIZE - 40) / 2) * (Math.sin(t - step) - Math.sin(t)),
dy = ((WORLD_SIZE - 40) / 2) * (Math.cos(t - step) - Math.cos(t));
obj.animate({
translation: dx + ' ' + dy,
rotation: -deg
}, 100);
}, 100);
Any help is appreciated!
If you want do a translation and a rotation too, the raphael obj should be like that
obj.animate({
transform: "t" + [dx , dy] + "r" + (-deg)
}, 100);
Check out http://raphaeljs.com/animation.html
Look at the second animation from the top on the right.
Hope this helps!
Here's the code:
(function () {
var path1 = "M170,90c0-20 40,20 40,0c0-20 -40,20 -40,0z",
path2 = "M270,90c0-20 40,20 40,0c0-20 -40,20 -40,0z";
var t = r.path(path1).attr(dashed);
r.path(path2).attr({fill: "none", stroke: "#666", "stroke-dasharray": "- ", rotation: 90});
var el = r.path(path1).attr({fill: "none", stroke: "#fff", "stroke-width": 2}),
elattrs = [{translation: "100 0", rotation: 90}, {translation: "-100 0", rotation: 0}],
now = 0;
r.arrow(240, 90).node.onclick = function () {
el.animate(elattrs[now++], 1000);
if (now == 2) {
now = 0;
}
}; })();

Categories