Swift: How to convert a particle network JS animation to iOS - javascript

I found this javascript animation:
JS animation
I really want to know how I can convert animations like this to use it in my Swift iOS app.
Has anybody got experience in likely things? I found things like spritekit, but I really can't find the right info/tutorials about this.
Maybe someone does know what I should do/look up?
Hope anybody can help me, thanks in advance!

Use a customView to upgrade their js codes to swift. Hope it is the answer. Beware of the copyright when you use their codes in this way. It's just for a demo.
In details, circle class is needed here to represent their points. In storyboard, assign a UIView to MyView and you can get the result.
class Circle : NSObject {
var position: CGPoint!
var speed : CGPoint!
init(position: CGPoint, speed: CGPoint) {
super.init()
self.position = position
self.speed = speed
}
}
class MyView : UIView{
var balls: [Circle] = []
override func didMoveToSuperview() {
super.didMoveToSuperview()
clearsContextBeforeDrawing = true
contentMode = .redraw
clipsToBounds = false
func randomValue () -> CGFloat {
let upperBound : UInt32 = 1000;
return (CGFloat(arc4random_uniform(upperBound))) / CGFloat(upperBound);
}
let array = (0..<Int(bounds.width*bounds.height / (65*65))).map { _ in
Circle.init(position: CGPoint.init(x: bounds.width * randomValue() , y: bounds.height * randomValue()), speed: CGPoint.init(x: randomValue() * 2 - 1 , y:randomValue() * 2 - 1) ) }
balls.append(contentsOf: array)
let displayLink = CADisplayLink.init(target: self, selector: #selector(update(_:)))
displayLink.add(to: RunLoop.main, forMode: RunLoop.Mode.default)
}
#objc func update(_ sender : CADisplayLink){
self.setNeedsDisplay()
}
override func draw(_ rect: CGRect) {
super.draw(rect)
let ctx = UIGraphicsGetCurrentContext()!
_ = (0..<(balls.count)).map{
let circle = balls[$0]
if (circle.position.x > rect.size.width + 50 || circle.position.x < -50) {
circle.speed.x = -circle.speed.x;
}
if (circle.position.y > rect.size.height + 50 || circle.position.y < -50) {
circle.speed.y = -circle.speed.y;
}
circle.position.x += circle.speed.x;
circle.position.y += circle.speed.y;
}
let color = UIColor.init(red: 0, green: 0x1c / 255 , blue: 0x33 / 255, alpha: 1.0).cgColor
ctx.setFillColor(color )
ctx.fill(rect)
_ = (0..<(balls.count)).map{
let ball = balls[$0];
ctx.beginPath()
let color = UIColor.init(red: 0x44 / 255 , green: 0x8f / 255 , blue: 0xda / 255, alpha: 0.4).cgColor
ctx.setFillColor(color)
ctx.addArc( center: ball.position , radius: 3.0, startAngle: 0, endAngle: .pi * 2, clockwise: false)
ctx.fillPath()
ctx.beginPath();
var index2 = balls.count - 1
while (index2 > $0 ){
let ball2 = balls[index2];
let dist = hypot(ball.position.x - ball2.position.x , ball.position.y - ball2.position.y );
if (dist < 100) {
ctx.setStrokeColor(UIColor.init(red: 0x44 / 255, green: 0x8f / 255, blue: 0xda / 255, alpha: 1.0).cgColor )
ctx.setAlpha( 1 - (dist > 100 ? 0.8 : dist / 150) )
ctx.setLineWidth(2);
ctx.move(to: CGPoint.init(x: 0.5 + ball.position.x, y: 0.5 + ball.position.y))
ctx.addLine(to: CGPoint.init(x: 0.5 + ball2.position.x, y: 0.5 + ball2.position.y))
}
index2 -= 1;
}
ctx.strokePath()
}
}
}

Using SpriteKit, To make the animation in that link you provided, I would create a loop that generates N number of dots (SKSpriteNodes) randomly placed on the scene. Then recursively move each dot to a new random location. Then, in the update() function of the scene, loop over all the dots and run some calculations to determine if dot X is within so many points of any other dot on the scene and create a CGPath between those two. And remove that CGPath if is not.

Related

Function that generates a path along other path, from a starting to an end point

const coords = [
{
name: "Rijnstraat vervolg",
points: [
[695, 500],
[680, 480],
[580, 475],
[520, 460],
],
width: 10,
types: [types.car, types.truck, types.pedestrian, types.bike],
oneway: true,
},
...
]
I have an array that looks like the above and I want to make a function that generates a path (along the other paths, which are the black lines in the image) from a black or gray circle to another black or gray circle. So I want the function to take in a start and end point (black or gray circle) and return an array of points that follow the already existings paths. (Which are sort of like roads)
And the function can be described as someone who is trying to get to somewhere.
I already tried a recursive function that looks like this:
function calculatePathToShop(startPoint, shopPoint) {
const targetShopPoint = findClosestPointOnPath(shopPoint);
const targetPathIndex = findPathByPoint(targetShopPoint);
const connectedPaths = calculateConnectedPaths(targetPathIndex);
let startPathIndex = -1;
connectedPaths.forEach(path => {
const pathPoints = coords[path].points;
pathPoints.forEach(pathPoint => {
if (comparePoints(startPoint.point, pathPoint)) startPathIndex = path;
});
});
if (startPathIndex == -1) return false;
let startPathPoints = coords[startPathIndex].points;
let targetPathPoints = coords[targetPathIndex].points;
if (!comparePoints(startPoint.point, startPathPoints[0])) startPathPoints.reverse();
ctx.strokeStyle = "rgba(255, 0, 0, .05)";
}
This one generated a path (along the existing ones) to a shop point, which is almost the same as a gray point. But this worked for some starting points, but the rest would just straight up fail
So does anyone know an algorithm, or has a function/solution that I can use to generate the path that someone can walk along the road (the black lines in the image)
Full coords array, and part of my already existing code is found here: https://raw.githubusercontent.com/CodeFoxDev/people-simulation/main/func/paths.js
(The rest of the code is in the github repo itself)
Fixed step interpolation
To interpolate a line segment you divide the vector from the start pointing to the end by the number of steps.
EG
steps = 100;
start = {x: 50, y: 100}
end = {x: 150, y: 300}
step = {x: (end.x - start.x) / steps, y: (end.y - start.y) / steps};
Then loop that number of steps adding the vector to a position initialized to the start point.
points = []; // array of interpolated points
point = {...start} // set start position.
while (steps--) {
points.push({...point});
point.x += vec.x;
point.y += vec.y;
}
points.push({...end}); // last point at end
This will create different spacing for different line lengths.
Fixed distance interpolation
To get a constant spacing between points you will need to use the lines' length to get the number of steps.
pixelsPerStep = 2; // distance between points.
start = {x: 50, y: 100}
end = {x: 150, y: 300}
step = {x: end.x - start.x, y: end.y - start.y};
lineSteps = Math.hypot(step.x, step.y) / pixelsPerStep;
points = []; // array of interpolated points
for (i = 0; i < lineSteps ; i += 1) {
u = i / lineSteps;
points.push({x: start.x + step.x * u, y: start.y + step.y * u});
}
// check to add end point
Note that the last point may or may not be at the correct distance. Due to rounding errors in floating point numbers you will need to check if the last point is close to the correct spacing and whether or not to include it.
eg from code above
// add last point if within (0.01 * pixelsPerStep) pixels of correct spacing
if (Math.abs(lineSteps - i) < 0.01) {
points.push({...end});
}
Note Use the overflow lineSteps - i when interpolating many line segments, to carry the correct start offset to each subsequent line segment.
Example
The code below is an example of a constant spaced set of points interpolated from another set of points.
The example draws the new points in black dots. The original points are rendered in red.
Note that the distance between new points is constant and thus may not fall on the original (red) points.
Note that there is a check at the end to test if a last point should be added.
const ctx = canvas.getContext("2d");
const P2 = (x, y) => ({x, y});
const points = [
P2(100,90),
P2(300,210),
P2(350,110),
P2(50,10),
P2(6,219),
];
const interpolatedPoints = interpolatePath(points, 35);
drawPoints(interpolatedPoints, 2);
ctx.fillStyle = "RED";
drawPoints(points);
function drawPoints(points, size = 1) {
ctx.beginPath();
for (const p of points) {
ctx.rect(p.x - size, p.y - size, size * 2 + 1, size * 2 + 1);
}
ctx.fill();
}
function interpolatePath(path, pixelStep) {
const res = [];
var p2, i = 1, overflow = 0;
while (i < path.length) {
const p1 = path[i - 1];
p2 = path[i];
const dx = p2.x - p1.x;
const dy = p2.y - p1.y;
const len = Math.hypot(dx, dy) / pixelStep;
let j = overflow;
while (j < len) {
const u = j / len;
res.push(P2(p1.x + dx * u, p1.y + dy * u));
j++;
}
overflow = j - len;
i++;
}
// add last point if close to correct distance
if (Math.abs(overflow) < 0.01) {
res.push(P2(p2.x, p2.y));
}
return res;
}
<canvas id="canvas" width="400" height="400"></canvas>

Can I make a half-bezier from full bezier?

Take a typical cubic bezier curve drawn in JavaScript (this example I googled...)
http://jsfiddle.net/atsanche/K38kM/
Specifically, these two lines:
context.moveTo(188, 130);
context.bezierCurveTo(170, 10, 350, 10, 388, 170);
We have a cubic bezier which starts at 188, 130, ends at 388, 170, and has controls points a:170, 10 and b:350, 10
My question is would it be possible to mathematically adjust the end point and control points to make another curve which is only a segment of the original curve?
The ideal result would be able to able to take a percentage slice of the bezier from the beginning, where 0.5 would draw only half of the bezier, 0.75 would draw most of the bezier (and so on)
I've already gotten working a few implementations of De Castelau which allow me to trace the contour of the bezier between [0...1], but this doesn't provide a way to mathematically recalculate the end and control points of the bezier to make a sub-bezier...
Thanks in advance
De Casteljau is indeed the algorithm to go. For a cubic Bezier curve defined by 4 control points P0, P1, P2 and P3, the control points of the sub-Bezier curve (0, u) are P0, Q0, R0 and S0 and the control points of the sub-Bezier curve (u, 1) are S0, R1, Q2 and P3, where
Q0 = (1-u)*P0 + u*P1
Q1 = (1-u)*P1 + u*P2
Q2 = (1-u)*P2 + u*P3
R0 = (1-u)*Q0 + u*Q1
R1 = (1-u)*Q1 + u*Q2
S0 = (1-u)*R0 + u*R1
Please note that if you want to "extract" a segment (u1, u2) from the original Bezier curve, you will have to apply De Casteljau twice. The first time will split the input Bezier curve C(t) into C1(t) and C2(t) at parameter u1 and the 2nd time you will have to split the curve C2(t) at an adjusted parameter u2* = (u2-u1)/(1-u1).
This is how to do it. You can get the left half or right half with this functin. This function is take thanks to mark from here: https://stackoverflow.com/a/23452618/1828637
I have it modified so it can be fit to a unit cell so we can use it for cubic-bezier in css transitions.
function splitCubicBezier(options) {
var z = options.z,
cz = z-1,
z2 = z*z,
cz2 = cz*cz,
z3 = z2*z,
cz3 = cz2*cz,
x = options.x,
y = options.y;
var left = [
x[0],
y[0],
z*x[1] - cz*x[0],
z*y[1] - cz*y[0],
z2*x[2] - 2*z*cz*x[1] + cz2*x[0],
z2*y[2] - 2*z*cz*y[1] + cz2*y[0],
z3*x[3] - 3*z2*cz*x[2] + 3*z*cz2*x[1] - cz3*x[0],
z3*y[3] - 3*z2*cz*y[2] + 3*z*cz2*y[1] - cz3*y[0]];
var right = [
z3*x[3] - 3*z2*cz*x[2] + 3*z*cz2*x[1] - cz3*x[0],
z3*y[3] - 3*z2*cz*y[2] + 3*z*cz2*y[1] - cz3*y[0],
z2*x[3] - 2*z*cz*x[2] + cz2*x[1],
z2*y[3] - 2*z*cz*y[2] + cz2*y[1],
z*x[3] - cz*x[2],
z*y[3] - cz*y[2],
x[3],
y[3]];
if (options.fitUnitSquare) {
return {
left: left.map(function(el, i) {
if (i % 2 == 0) {
//return el * (1 / left[6])
var Xmin = left[0];
var Xmax = left[6]; //should be 1
var Sx = 1 / (Xmax - Xmin);
return (el - Xmin) * Sx;
} else {
//return el * (1 / left[7])
var Ymin = left[1];
var Ymax = left[7]; //should be 1
var Sy = 1 / (Ymax - Ymin);
return (el - Ymin) * Sy;
}
}),
right: right.map(function(el, i) {
if (i % 2 == 0) {
//xval
var Xmin = right[0]; //should be 0
var Xmax = right[6];
var Sx = 1 / (Xmax - Xmin);
return (el - Xmin) * Sx;
} else {
//yval
var Ymin = right[1]; //should be 0
var Ymax = right[7];
var Sy = 1 / (Ymax - Ymin);
return (el - Ymin) * Sy;
}
})
}
} else {
return { left: left, right: right};
}
}
Thats the function and now to use it with your parameters.
var myBezier = {
xs: [188, 170, 350, 388],
ys: [130, 10, 10, 170]
};
var splitRes = splitCubicBezier({
z: .5, //percent
x: myBezier.xs,
y: myBezier.ys,
fitUnitSquare: false
});
This gives you
({
left: [188, 130, 179, 70, 219.5, 40, 267, 45],
right: [267, 45, 314.5, 50, 369, 90, 388, 170]
})
fiddle proving its half, i overlaid it over your original:
http://jsfiddle.net/K38kM/8/
Yes it is! Have a look at the bezier section here
http://en.m.wikipedia.org/wiki/De_Casteljau's_algorithm
It is not that difficult all in all.

gRaphael linechart draws values beyond axis

When drawing a linechart with gRaphael using milliseconds along the x-axis I commonly get inconsistencies in the placement of the data points. Most commonly the initial data points are to the left of the y-axis (as seen in the fiddle below), sometimes the last data-point will be beyond the right side of the view-box/past the termination of the x-axis.
Does anyone know:
1) Why this occurs,
2) How to prevent it, &/or
3) How to check for it (I can use transform to move the lines/points if I know when it has happened/by how much).
my code:
var r = Raphael("holder"),
txtattr = { font: "12px sans-serif" };
var r2 = Raphael("holder2"),
txtattr2 = { font: "12px sans-serif" };
var x = [], y = [], y2 = [], y3 = [];
for (var i = 0; i < 1e6; i++) {
x[i] = i * 10;
y[i] = (y[i - 1] || 0) + (Math.random() * 7) - 3;
}
var demoX = [[1, 2, 3, 4, 5, 6, 7],[3.5, 4.5, 5.5, 6.5, 7, 8]];
var demoY = [[12, 32, 23, 15, 17, 27, 22], [10, 20, 30, 25, 15, 28]];
var xVals = [1288885800000, 1289929440000, 1290094500000, 1290439560000, 1300721700000, 1359499228000, 1359499308000, 1359499372000];
var yVals = [80, 76, 70, 74, 74, 78, 77, 72];
var xVals2 = [1288885800000, 1289929440000];
var yVals2 = [80, 76];
var lines = r.linechart(10, 10, 300, 220, xVals, yVals, { nostroke: false, axis: "0 0 1 1", symbol: "circle", smooth: true })
.hoverColumn(function () {
this.tags = r.set();
for (var i = 0, ii = this.y.length; i < ii; i++) {
this.tags.push(r.tag(this.x, this.y[i], this.values[i], 160, 10).insertBefore(this).attr([{ fill: "#fff" }, { fill: this.symbols[i].attr("fill") }]));
}
}, function () {
this.tags && this.tags.remove();
});
lines.symbols.attr({ r: 3 });
var lines2 = r2.linechart(10, 10, 300, 220, xVals2, yVals2, { nostroke: false, axis: "0 0 1 1", symbol: "circle", smooth: true })
.hoverColumn(function () {
this.tags = r2.set();
for (var i = 0, ii = this.y.length; i < ii; i++) {
this.tags.push(r.tag(this.x, this.y[i], this.values[i], 160, 10).insertBefore(this).attr([{ fill: "#fff" }, { fill: this.symbols[i].attr("fill") }]));
}
}, function () {
this.tags && this.tags.remove();
});
lines2.symbols.attr({ r: 3 });
I do have to use gRaphael and the x-axis has to be in milliseconds (it is labeled later w/customized date strings)
Primary example fiddle: http://jsfiddle.net/kcar/aNJxf/
Secondary example fiddle (4th example on page frequently shows both axis errors):
http://jsfiddle.net/kcar/saBnT/
root cause is the snapEnds function (line 718 g.raphael.js), the rounding it does, while fine in some cases, is adding or subtracting years from/to the date in other cases.
Haven't stepped all the way through after this point, but since the datapoints are misplaced every time the rounding gets crazy and not when it doesn't, I'm going to go ahead and assume this is causing issues with calculating the chart columns, also before being sent to snapEnds the values are spot on just to confirm its not just receiving miscalculated data.
code of that function from g.raphael.js
snapEnds: function(from, to, steps) {
var f = from,
t = to;
if (f == t) {
return {from: f, to: t, power: 0};
}
function round(a) {
return Math.abs(a - .5) < .25 ? ~~(a) + .5 : Math.round(a);
}
var d = (t - f) / steps,
r = ~~(d),
R = r,
i = 0;
if (r) {
while (R) {
i--;
R = ~~(d * Math.pow(10, i)) / Math.pow(10, i);
}
i ++;
} else {
if(d == 0 || !isFinite(d)) {
i = 1;
} else {
while (!r) {
i = i || 1;
r = ~~(d * Math.pow(10, i)) / Math.pow(10, i);
i++;
}
}
i && i--;
}
t = round(to * Math.pow(10, i)) / Math.pow(10, i);
if (t < to) {
t = round((to + .5) * Math.pow(10, i)) / Math.pow(10, i);
}
f = round((from - (i > 0 ? 0 : .5)) * Math.pow(10, i)) / Math.pow(10, i);
return { from: f, to: t, power: i };
},
removed the rounding nonsense from snapEnds and no more issues, not noticed any downside from either axis or any other area of the chart. If you see one I'd love to hear it though.
code of that function from g.raphael.js now:
snapEnds: function(from, to, steps) {
return {from: from, to: to, power: 0};
},
Hi if you comment this:
if (valuesy[i].length > width - 2 * gutter) {
valuesy[i] = shrink(valuesy[i], width - 2 * gutter);
len = width - 2 * gutter;
}
if (valuesx[i] && valuesx[i].length > width - 2 * gutter) {
valuesx[i] = shrink(valuesx[i], width - 2 * gutter);
}
in g.line.js, It seems to solve the problem, and it also solves a similar problem with the values in the y axis.
Upgrading from v0.50 to v0.51 fixed the issue for me.
Still not sure why it occurs, adding in a transparent set was not a desirable option.
The simplest way to check for if the datapoints were rendered outside of the graph seems to be getting a bounding box for the axis set and a bounding box for the datapoints and checking the difference between the x and x2 values.
If anyone can help me with scaling the datapoint set, or figure out how to make this not happen at all, I will still happily appreciate/up vote answers
//assuming datapoints is the Raphael Set for the datapoints, axes is the
//Raphael Set for the axis, and datalines is the Raphael Set for the
//datapoint lines
var pointsBBox = datapoints.getBBox();
var axesBBox = axes.getBBox();
var xGapLeft = Math.ceil(axesBBox.x - pointsBBox.x);
//rounding up to integer to simplify, and the extra boost from y-axis doesn't
//hurt, <1 is a negligible distance in transform
var xGapRight = Math.ceil(axesBBox.x2 - pointsBBox.x2);
var xGap = 0;
if(xGapLeft > 0){
datapoints.transform('t' +xGapLeft +',0');
datalines.transform('t' +xGapLeft +',0');
xGap = xGapLeft;
}else if (xGapRight < 0) { //using else if because if it is a scale issue it will
//be too far right & too far left, meaning both are true and using transform will
//just shift it right then left and you are worse off than before, using
//set.transform(scale) works great on dataline but when using on datapoints scales
// symbol radius not placement
if (xGapLeft < 0 && xGapRight < xGapLeft) { xGapRight = xGapLeft; }
//in this case the initial point is right of y-axis, the end point is right of
//x-axis termination, and the difference between last point/axis is greater than
//difference between first point/axis
datapoints.transform('t' +xGapRight +',0');
datalines.transform('t' +xGapRight +',0');
xGap = xGapRight;
}
rehookHoverOverEvent(xGap); //there are so many ways to do this just leaving it
//here as a call to do so, if you don't the hover tags will be over the original
//datapoints instead of the new location, at least they were in my case.

I need a function that outputs a hexadecimal value for a given floating point number that belongs to a range

I'm sorry for the bad title (edit if you wish), but I don't know how to put this request. I attached an image from Photoshop that illustrates things a little bit better.
The request itself is quite simple: You have a numeric range from 10.0 to 0.0.
10 stands for the color #00ff00 (deep green) and 0.0 for the color #ff0000 (deep red).
Now, when you hand over the number 10.0 it will output #00ff00. When you give it something like 8.2 it would output something like #00cc33 so that you have a floating gradient all the way down but always float from #00ff00 over orange #ff9900 to #ff0000 (orange in the middle could also be omitted).
One way is to think of the colours like a segment of a circle and then make use of Math.cos or Math.sin.
function toColour(x) {
var pad = function (x) {
x = x.toString(16);
if (x.length === 1) {
return '0' + x;
}
return x;
}, r, g, b;
r = ~~(255 * Math.cos(Math.PI * x / 20)); // cos(0) = 1, cos(π/2) = 0
g = ~~(255 * Math.sin(Math.PI * x / 20)); // sin(0) = 0, sin(π/2) = 1
b = 0;
return '#' + pad(r) + pad(g) + pad(b);
}
// test the gradient produced
for (var i = 0; i <= 10; ++i) {
document.body.appendChild(
document.createElement('div')
).setAttribute('style', 'width: 400px;height: 5px;background: '+toColour(i)+';');
}
See this fiddle for test results (thanks to MikeM)
Just for fun:
function toHex( n ) {
var r = 255 - ( n / 10 * 255 | 0 );
g = n / 10 * 255 | 0;
return '#' +
( r ? ( r = r.toString(16), r.length == 2 ? r : '0' + r ) : '00' ) +
( g ? ( g = g.toString(16), g.length == 2 ? g : '0' + g ) : '00' ) + '00'
}
See it here (for what its worth): http://jsfiddle.net/xD6Uf/
Update
Inspired by Samuel Edwin Ward's use of HSL, the following function adapts hslToRgb from Brian Grinstead's MIT Licensed tinycolor.js:
Note: this function is not intended to be the finished article or represent good practice, it is a proof of concept only. There is no bounds checking and few concessions to readability.
n is a value from 0 to 10, and start, middle and end are each a hue value from 0 - 360.
See Mother-effing hsl to select hue, saturation and lightness values to experiment with.
function toHex( n ) {
var r, g, b, p, q,
start = 0, // 0 - 360 (red 0)
middle = 36, // 0 - 360 (orange 36, use 0 or null for no middle)
end = 120, // 0 - 360 (green 120)
saturation = 1, // 0 - 1
lightness = 0.5, // 0 - 1
hue = ( middle ?
n > 5 ? ( n -= 5, n / 5 * ( end - middle ) ) + middle :
( n / 5 * ( middle - start ) ) + start :
( n / 10 * ( end - start ) ) + start ) / 360;
function hue2hex( p, q, h ){
if ( h < 0 ) h++;
else if ( h > 1 ) h--;
h = ( h < 1/6 ? p + ( q - p ) * 6 * h : h < 1/2 ? q :
h < 2/3 ? p + ( q - p ) * ( 2/3 - h ) * 6 : p ) * 255 | 0;
return h ? ( h = h.toString(16), h.length > 1 ? h : '0' + h ) : '00';
}
if ( saturation === 0 ) {
n = lightness * 255 | 0;
r = g = b = n ?
( n = n.toString(16), n.length > 1 ? n : '0' + n ) : '00';
} else {
q = lightness < 0.5 ? lightness * ( 1 + saturation ) :
lightness + saturation - lightness * saturation;
p = 2 * lightness - q;
r = hue2hex( p, q, hue + 1/3 );
g = hue2hex( p, q, hue );
b = hue2hex( p, q, hue - 1/3 );
}
return '#' + r + g + b;
}
Example
toHex(0); // "#ff0000"
toHex(5); // "#ff9900"
toHex(10); // "#00ff00"
jsfiddle
It looks like you want to interpolate between colors.
You could just take the color values to be numbers and do an interpolation between them, but I think the results would be very odd.
You'll probably want to convert your colors from RGB to another color space and scale the hue component(s). HSL would be a nice choice because CSS3 supports specifying colors in that space.
I put code for both approaches up on jsfiddle.
These are the important parts:
/* numeric interpolation */
for (var value = 0; value < 10; value += 10/40) {
var color = (10-value)/10*(0xff0000-0xff00)+(0x00ff00),
hex = Math.floor(color).toString(16);
while (hex.length < 6) {
hex = "0" + hex;
}
/* hex now holds the hex code */
}
/* hue interpolation */
for (var value = 0; value < 10; value += 10/40) {
var hue = value/10*120, /* red is 0; green is 120 */
color = 'hsl(' + hue + ', 100%, 50%)';
/* hsl now holds the hsl color specification */
}
If you like you can use a fancier approach to interpolating the values than the simple linear formula I used (where x is the input scale and y is the output scale):
y = (x - minX) / (maxX - minX) * (maxY-minY) + minY
So basically you have some range in hex with two variables:
var startHex = "0x00ff00";
var endHex = "0xff0000"
int startInt = parseInt(startHex, 16);
int endInt = parseInt(endHex, 16);
int newInt = startInt + (endInt-startInt)/100 * floatValue; // floatValue is number between 10.0 and 0.0
var newHex = newDec.ToString(16);
I am assuming you intended for 100 increments possible between start and end. This could be done much more cleanly, but for illustration I broke it all out. In practice I would write this in 1-2 lines.
If you want to rely on the browser's rendering engine, you could use HTML5's canvas and createLinearGradient. One advantage of this approach is the ability to use color stops (though you could do this mathematically if you really wanted also).
var canvas = document.createElement('canvas'),
context = canvas.getContext('2d');
canvas.height = 100; canvas.width = 1;
var gradient = context.createLinearGradient(0,0,0,100);
// color stops
gradient.addColorStop(0, '#00ff00');
gradient.addColorStop(0.5, '#ff9900');
gradient.addColorStop(1, '#ff0000');
context.fillStyle = gradient;
context.fillRect(0,0,1,100);
var point = 10.0,
colorData = context.getImageData(0, parseInt(point*canvas.height*.1, 10), 1, 1).data;
// rgba ( colorData[0], colorData[1], colorData[2], colorData[3] )
You can find the exact rgba coordinates of the color in colorData[0] (red), colorData[1] (blue), colorData[2] (green) and then easily convert those to hex values.
If you end up using this solution, set point to a number in the range of 0 to 9.9.

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