Calculate enclosing rectangle of a given aspect ratio - aspect-ratio

Given the height and the width of a rectangle of any size and an aspect ratio, how can I calculate the height and width of a minimum enclosing rectangle of the given aspect ratio?
In code, the function signature would look like this
public Size getMinimumEnclosingRectangle(Size originalRectangle, float aspectNumerator, float aspectDenomiator);
Calls to this function would look like
originalRectangle AspectRatio Result
-------------------------------------------
100x100 1:2 100x200
64x32 1:1 64x64
125x100 3:2 150x100
100x345 1:3 115x345

This may not be the best way, but the way I do this calculation is to calculate the change in aspect ratio and then base the resulting width/height calculation of of that. Here is some code to illustrate this algorithm in practice:
var sourceImages = [
{width: 100, height: 100, toAspectRatio:1/2 },
{width: 64, height: 32, toAspectRatio:1/1 },
{width: 125, height: 100, toAspectRatio:3/2 },
{width: 100, height: 345, toAspectRatio:1/3 },
{width: 345, height: 100, toAspectRatio:1/3 }
];
function calculateNewSize( sourceWidth, sourceHeight, toAspectRatio )
{
var aspectRatioChange = (sourceWidth / sourceHeight) / toAspectRatio;
var fitWidth = aspectRatioChange < 1 ? sourceHeight * toAspectRatio : sourceWidth;
var fitHeight = aspectRatioChange >= 1 ? sourceWidth / toAspectRatio : sourceHeight;
console.log('(' + aspectRatioChange + ') ' + sourceWidth + " x " + sourceHeight + " -> "
+ toAspectRatio + ' -> ' + fitWidth + ' x ' + fitHeight);
}
sourceImages.forEach(function(source) {
calculateNewSize(source.width, source.height, source.toAspectRatio);
});

Related

raphael js drawing arcs starting at 6 o'clock that cocentric

I was able to find an example of a Polar clock at http://raphaeljs.com/polar-clock.html
I modified it to draw concentric circles, but I need the arc to start at 6 o'clock. I am trying to dissect how it works, but haven't been able to figure it out.
JS Fiddle:
http://jsfiddle.net/5frQ8/
var r = Raphael("holder", 600, 600);
// Custom Attribute
r.customAttributes.arc = function (value, total, R, color)
{
var alpha = 360 / total * value,
a = (90 - alpha) * Math.PI / 180,
x = 300 + R * Math.cos(a),
y = 300 - R * Math.sin(a),
path;
if (total == value)
{
path = [["M", 300, 300 - R], ["A", R, R, 0, 1, 1, 299.99, 300 - R]];
}
else
{
path = [["M", 300, 300 - R], ["A", R, R, 0, +(alpha > 180), 1, x, y]];
}
return {path: path, stroke: color,"stroke-width": 30};
};
//West
r.path().attr({arc: [575, 2000, 200, '#19A69C']});
//Total#
r.path().attr({arc: [1000, 2000, 160, '#FEDC38']});
//East
r.path().attr({arc: [425, 2000, 120, '#7BBD26']});
I have modified the main function to make the arcs start from 6 o'clock equivalent position. Please note that the formulae to find a point in polar coordinates are always:
x = centerX + radius * cos(angle)
y = centerY + radius * sin(angle)
Find the starting and ending points accordingly.
To change the starting angle by "delta", all angles should be added by "delta". Thus,
newAngle = angle + delta
The values of delta are -90 and +90 for the arcs to start from 12 o'clock and 6 o'clock respectively.
The arc drawing function is changed accordingly.
// Custom Attribute
r.customAttributes.arc = function (value, total, R, color)
{
var angleShift = 90,
alpha = 360 / total * value,
a = (alpha + angleShift) * Math.PI / 180,
x = 300 + R * Math.cos(a),
y = 300 + R * Math.sin(a),
path;
if (total == value)
{
path = [["M", 300, 300 + R], ["A", R, R, 0, 1, 1, 300.01, 300 + R]];
}
else
{
path = [["M", 300, 300 + R], ["A", R, R, 0, +(alpha > 180), 1, x, y]];
}
return {path: path, stroke: color,"stroke-width": 30};
};

Duplicated Numbers on Google Charts Vertical Axis

I was working on a bugfix with Google visualizations for a combo (column/line) chart when one of the axes went strange and started repeating numbers ( ie: 1, 1, 2, 2 instead of 1, 2, 3, 4). Please see the image below
(source: rackcdn.com)
Here are the chart option settings:
// Instantiate and draw our chart, passing in some options.
var frequency_by_day_options = {
vAxes: [
{format:'#,###', title:"Call Volume"},
{format: '#%', title:'Missed Call Rate',
viewWindow:{
max:1,
}}
],
legend: {position: 'none'},
chartArea: { height:'60%', width:'60%'},
width: 800,
height: 350,
backgroundColor: 'transparent',
bar: { groupWidth: '90%'},
isStacked: true,
seriesType: 'bars',
series: {0: {type:'bar', targetAxisIndex:0}, 1: {type:'line', targetAxisIndex:1},},
colors: ['blue', 'green'],
animation:{
duration: 1000,
easing: 'out',},
};
I have no idea what's going on here. Even when I comment out all of the vAxis options I still observe this behavior. Any ideas on what i'm doing wrong? This is driving me nuts :)
I was facing same issue when we have less number of boxes (let say 1 or 2). I resolved it with maxValue option.
Just add maxValue = 4 option in your vaxis. It will add 5 (0 to 4) gridlines at all times. If your maxValue is more that 4, it will adjust automatically.
Mine is working fine with options (title : 'No of boxes', format : '#', minValue:0, maxValue :4). minValue = 0 will not allow negatives as its no of boxes.
I am going to guess that the left-side vAxis is not 4, but actually 2. The 5 labels are 0, 0.5, 1, 1.5, 2.
Since you have format set as "#,###" it will not show decimals. If you change it to "#,###.#" then it will show 0, 0.5, 1, 1.5, 2.
There are a dozen ways to solve it, but the easiest may be to make sure that your axis values are only whole number values with a javascript function like this:
// Take the Max/Min of all data values in all graphs
var totalMax = 3;
var totalMin = -1;
// Figure out the largest number (positive or negative)
var biggestNumber = Math.max(Math.abs(totalMax),Math.abs(totalMin));
// Round to an exponent of 10 appropriate for the biggest number
var roundingExp = Math.floor(Math.log(biggestNumber) / Math.LN10);
var roundingDec = Math.pow(10,roundingExp);
// Round your max and min to the nearest exponent of 10
var newMax = Math.ceil(totalMax/roundingDec)*roundingDec;
var newMin = Math.floor(totalMin/roundingDec)*roundingDec;
// Determine the range of your values
var range = newMax - newMin;
// Calculate the best factor for number of gridlines (2-5 gridlines)
// If the range of numbers divided by 2 or 5 is a whole number, use it
for (var i = 2; i <= 5; ++i) {
if ( Math.round(range/i) = range/i) {
var gridlines = i
}
}

RaphaelJS - I need help understanding a transform

I have a question about the following demo - http://raphaeljs.com/hand.html.
Here is code from the sample...
var r = Raphael("holder", 640, 480), angle = 0;
while (angle < 360) {
var color = Raphael.getColor();
(function(t, c) {
r.circle(320, 450, 20).attr({
stroke : c,
fill : c,
transform : t,
"fill-opacity" : .4
}).click(function() {
s.animate({
transform : t,
stroke : c
}, 2000, "bounce");
}).mouseover(function() {
this.animate({
"fill-opacity" : .75
}, 500);
}).mouseout(function() {
this.animate({
"fill-opacity" : .4
}, 500);
});
})("r" + angle + " 320 240", color);
angle += 30;
}
Raphael.getColor.reset();
var s = r.set();
s.push(r.path("M320,240c-50,100,50,110,0,190").attr({
fill : "none",
"stroke-width" : 2
}));
s.push(r.circle(320, 450, 20).attr({
fill : "none",
"stroke-width" : 2
}));
s.push(r.circle(320, 240, 5).attr({
fill : "none",
"stroke-width" : 10
}));
s.attr({
stroke : Raphael.getColor()
});
The question I have is about the following line of code...
("r" + angle + " 320 240", color);
In the anonymous function the circle is initially drawn at 320, 450 with a radius of 20. Then a transform is applied, for example ("r30 320 240") when the angle is 30.
How does this transform work? The way I read this transform is to rotate the circle 30 degrees around 320,450 , then move 320 horizontally (to the right) and 240 vertically down.
But i'm obviously reading this transform wrong because this is not what is happening.
What am i missing?
Thanks
The transform "r30 320 240" sets the rotation of the object about the point (320,240) by 30 degrees. It does not add to the rotation. It overrides any previous transformations.
If you look at this example:
http://jsfiddle.net/jZyyy/1/
You can see that I am setting the rotation of the circle about the point (0,0). If you consider the point (0,0) to be the centre of a clock, then the circle begins at 3 o'clock. If I use the transform "r90 0 0" the circle will be rotated from 3 o'clock to 6 o'clock. If I then later set the transform to be "r30 0 0" the circle will be at 4 o'clock, rotated 30 degrees from the original 3 o'clock position about the point (0,0).

Animating an arc in Raphael JS wobbles in Chrome

I am seeing an annoying wobble in my animation, I have stripped out the code which contains the actual animation:
var side = 400;
var paper = new Raphael($(this), 100, side);
paper.customAttributes.arc = function (xloc, yloc, value, total, R) {
var alpha = 360 / total * value,
a = (90 - alpha) * Math.PI / 180,
x = xloc + R * Math.cos(a),
y = yloc - R * Math.sin(a),
path;
if (total == value) {
path = [
["M", xloc, yloc - R],
["A", R, R, 0, 1, 1, xloc - 0.01, yloc - R]
];
} else {
path = [
["M", xloc, yloc - R],
["A", R, R, 0, +(alpha > 180), 1, x, y]
];
}
return {
path: path
};
};
var arcWidth = 180 - 120;
var strokeRadius = (120 + arcWidth/2);
var indicatorArc = paper.path().attr({
"stroke": "#4B6384",
"stroke-width": 100,
arc: [side/2, side/2, 0, 100, strokeRadius]
});
indicatorArc.animate({
arc: [side/2, side/2, 75, 100, strokeRadius]
}, 1500, "<>", function(){
// anim complete here
});
I have put it in a jsfiddle for you to see for yourself, please check in firefox and chrome, you will notice in chrome the edges wobble substantially, is there anything I can do to eliminate this?
fiddle here: run many times
So I know this is over two years old now, but if anyone comes across this issue, it's a known bug within Chrome's 2D drawing engine.
https://code.google.com/p/skia/issues/detail?id=2769

drawing centered arcs in raphael js

I need to draw concentric arcs of various sizes using raphael.js. I tried to understand the code behind http://raphaeljs.com/polar-clock.html, which is very similar to what I want, but, whithout comments, it is quite difficult to fathom.
Ideally, I would need a function that creates a path that is at a given distance from some center point, starts at some angle and ends at some other angle.
That answer is ok, but cant be animated. I ripped the important stuff out of polar-clock for you. Here is a red arc that animates growing. enjoy.
// Custom Arc Attribute, position x&y, value portion of total, total value, Radius
var archtype = Raphael("canvas", 200, 100);
archtype.customAttributes.arc = function (xloc, yloc, value, total, R) {
var alpha = 360 / total * value,
a = (90 - alpha) * Math.PI / 180,
x = xloc + R * Math.cos(a),
y = yloc - R * Math.sin(a),
path;
if (total == value) {
path = [
["M", xloc, yloc - R],
["A", R, R, 0, 1, 1, xloc - 0.01, yloc - R]
];
} else {
path = [
["M", xloc, yloc - R],
["A", R, R, 0, +(alpha > 180), 1, x, y]
];
}
return {
path: path
};
};
//make an arc at 50,50 with a radius of 30 that grows from 0 to 40 of 100 with a bounce
var my_arc = archtype.path().attr({
"stroke": "#f00",
"stroke-width": 14,
arc: [50, 50, 0, 100, 30]
});
my_arc.animate({
arc: [50, 50, 40, 100, 30]
}, 1500, "bounce");
Here's how I have done it. The following code allows you to specify a start and end angle as well as an inner and outer radius (useful for doing those trendy donut style pie charts). The solution doesn't rely on approximating a curve with line segments and can be animated as per the clock example mentioned in the original question.
First create your Raphael drawing area; the following assumes a div with id "raphael_paper" in your HTML file:
var paper = Raphael("raphael_paper", 800, 800);
to this Raphael object we add a custom arc attribute, a function which takes the center of a circle (x and y coords), a start angle, an end angle, an inner radius and an outer radius:
paper.customAttributes.arc = function (centerX, centerY, startAngle, endAngle, innerR, outerR) {
var radians = Math.PI / 180,
largeArc = +(endAngle - startAngle > 180);
// calculate the start and end points for both inner and outer edges of the arc segment
// the -90s are about starting the angle measurement from the top get rid of these if this doesn't suit your needs
outerX1 = centerX + outerR * Math.cos((startAngle-90) * radians),
outerY1 = centerY + outerR * Math.sin((startAngle-90) * radians),
outerX2 = centerX + outerR * Math.cos((endAngle-90) * radians),
outerY2 = centerY + outerR * Math.sin((endAngle-90) * radians),
innerX1 = centerX + innerR * Math.cos((endAngle-90) * radians),
innerY1 = centerY + innerR * Math.sin((endAngle-90) * radians),
innerX2 = centerX + innerR * Math.cos((startAngle-90) * radians),
innerY2 = centerY + innerR * Math.sin((startAngle-90) * radians);
// build the path array
var path = [
["M", outerX1, outerY1], //move to the start point
["A", outerR, outerR, 0, largeArc, 1, outerX2, outerY2], //draw the outer edge of the arc
["L", innerX1, innerY1], //draw a line inwards to the start of the inner edge of the arc
["A", innerR, innerR, 0, largeArc, 0, innerX2, innerY2], //draw the inner arc
["z"] //close the path
];
return {path: path};
};
now we can use this to draw arcs of a specified thickness, starting and ending wherever we want them to eg.
var redParams = {stroke: "#f00", "stroke-width": 1, fill:"#eee"},
greenParams = {stroke: "#0f0", "stroke-width": 1, fill:"#eee"},
blueParams = {stroke: "#00f", "stroke-width": 1, fill:"#eee"},
cx = 300, cy = 300, innerRadius = 100, outerRadius = 250,
var red = paper.path().attr(redParams).attr({arc: [cx, cy, 0, 90, innerRadius, outerRadius]});
var green = paper.path().attr(greenParams).attr({arc: [cx, cy, 270, 320, innerRadius, outerRadius]});
var blue = paper.path().attr(blueParams).attr({arc: [cx, cy, 95, 220, innerRadius, outerRadius]});
This should result in three grey arc segments with red, blue and green 1px borders.
Actually found the answer myself. I first thought of something fancy involving bezier curves, but this just works.
-> creates a path using SVG path syntax, which works as is with raphael
function arc(center, radius, startAngle, endAngle) {
angle = startAngle;
coords = toCoords(center, radius, angle);
path = "M " + coords[0] + " " + coords[1];
while(angle<=endAngle) {
coords = toCoords(center, radius, angle);
path += " L " + coords[0] + " " + coords[1];
angle += 1;
}
return path;
}
function toCoords(center, radius, angle) {
var radians = (angle/180) * Math.PI;
var x = center[0] + Math.cos(radians) * radius;
var y = center[1] + Math.sin(radians) * radius;
return [x, y];
}
Just to remove some guesswork from user592699's answer, this is the complete code that works:
<script src="raphael.js"></script>
<script>
var paper = Raphael(20, 20, 320, 320);
function arc(center, radius, startAngle, endAngle) {
angle = startAngle;
coords = toCoords(center, radius, angle);
path = "M " + coords[0] + " " + coords[1];
while(angle<=endAngle) {
coords = toCoords(center, radius, angle);
path += " L " + coords[0] + " " + coords[1];
angle += 1;
}
return path;
}
function toCoords(center, radius, angle) {
var radians = (angle/180) * Math.PI;
var x = center[0] + Math.cos(radians) * radius;
var y = center[1] + Math.sin(radians) * radius;
return [x, y];
}
paper.path(arc([100, 100], 80, 0, 270)); // draw an arc
// centered at (100, 100),
// radius 80, starting at degree 0,
// beginning at coordinate (80, 0)
// which is relative to the center
// of the circle,
// going clockwise, until 270 degree
</script>
For those who want the arc to be made with closed path and not stroke, I have extended genkilabs answer to make a solution. In cases when you need to give outer stroke to your arc, this might help.
// Custom Arc Attribute, position x&y, value portion of total, total value, Radius, width
var archtype = Raphael("canvas", 200, 100);
archtype.customAttributes.arc = function (xloc, yloc, value, total, R, width) {
if(!width) width = R * 0.4;
var alpha = 360 / total * value,
a = (90 - alpha) * Math.PI / 180,
w = width / 2,
r1 = R + w,
r2 = R - w,
x1 = xloc + r1 * Math.cos(a),
y1 = yloc - r1 * Math.sin(a),
x2 = xloc + r2 * Math.cos(a),
y2 = yloc - r2 * Math.sin(a),
path;
if (total == value) {
path = [
["M", xloc, yloc - r1],
["A", r1, r1, 0, 1, 1, xloc - 0.01, yloc - r1],
["Z"],
["M", xloc - 0.01, yloc - r2],
["A", r2, r2, 0, 1, 0, xloc, yloc - r2],
["Z"]
];
} else {
path = [
["M", xloc, yloc - r1],
["A", r1, r1, 0, +(alpha > 180), 1, x1, y1],
["L", x2, y2],
["A", r2, r2, 0, +(alpha > 180), 0, xloc, yloc - r2],
["L", xloc, yloc - r1],
["Z"]
];
}
return {
path: path
};
};
//make an arc at 50,50 with a radius of 30 that grows from 0 to 40 of 100 with a bounce
var my_arc = archtype.path().attr({
"fill": "#00f",
"stroke": "#f00",
"stroke-width": 5,
arc: [50, 50, 0, 100, 30]
});
my_arc.animate({
arc: [50, 50, 40, 100, 30]
}, 1500, "bounce");
JSFiddle
You can also do this without having to use loops. The following achieves this and works with negative angles as well.
Pass in a Raphael object as r. The angles start with 0 degrees, which is the top of the circle rather than the right as was listed in a couple of other solutions.
function drawArc(r, centerX, centerY, radius, startAngle, endAngle) {
var startX = centerX+radius*Math.cos((90-startAngle)*Math.PI/180);
var startY = centerY-radius*Math.sin((90-startAngle)*Math.PI/180);
var endX = centerX+radius*Math.cos((90-endAngle)*Math.PI/180);
var endY = centerY-radius*Math.sin((90-endAngle)*Math.PI/180);
var flg1 = 0;
if (startAngle>endAngle)
flg1 = 1;
else if (startAngle<180 && endAngle<180)
flg1 = 0;
else if (startAngle>180 && endAngle>180)
flg1 = 0;
else if (startAngle<180 && endAngle>180)
flg1 = 0; // edited for bugfix here, previously this was 1
else if (startAngle>180 && endAngle<180)
flg1 = 1;
return r.path([['M',startX, startY],['A',radius,radius,0,flg1,1,endX,endY]]);
};
I have adapted genkilabs answer to include rotation and inversion abilities. Also, how much of the ring is filled was changed to a single-number percent. (The inversion was adapted from this post). Hope it's helpful!
paper.customAttributes.arc = function (xloc, yloc, percent, rad, rot, invert) {
var alpha = 3.6 * percent,
a = (90 - alpha) * Math.PI / 180,
x = xloc + rad * Math.cos(a),
y = yloc - rad * Math.sin(a),
path;
if (invert) {
x = xloc - rad * Math.cos(a);
}
if (percent >= 100) {
path = [
["M", xloc, yloc - rad],
["A", rad, rad, 0, 1, 1, xloc - 0.01, yloc - rad]
];
} else {
path = [
["M", xloc, yloc - rad],
["A", rad, rad, 0, +(alpha > 180), +(!invert), x, y]
];
}
return {
path: path,
transform: "r"+rot+","+xloc+","+yloc,
};
};