# Calculate new gradient positions after the polygon it fills changes dimensions

What I want to do?

Updates to this question: 7/10/2012 – “gradientTransform not quite” Inspired by Duopixel
7/11/2012 – “SVG Code from the example” 7/16/2012 – “@dignifiedquire take on this problem”

I’m trying to create a tool that let’s the user dynamically resize polygons. Most of the polygons are filled with gradients `fill="url(#top_surface_1_gradient)"`. The way I go about this is a simple JavaScript script that:

1. looks for mousemove & click events over a certain polygon
2. measures the amount of movement
3. changes half of the coordinates of the polygon (in order to have the effect of stretching) using this algorithm to define new coordinates: `x = x_movement`, `y = x_movement * Math.tan( 31 * (Math.PI/180) )`
4. polygons that are filled with a single colour are OK
5. polygons that are filled with a gradient are not, let me demonstrate:

Visually So this is step one, no stretching has been done by the user. This is where the problem happens. Since I don’t know how should I change the `x1, y1` and `x2, y2` coordinates for the gradient, it just stays hanging in it’s old position while the polygon has been stretched. The result is a shape that fails to sustain the illusion of depth. The end result I’m looking for. And bare in mind that the gradient might have a completely random angle from the get go. In this end result, that I’m looking for, both the `x1, y1` and `x2, y2` coordinates of the gradient have been changed. What algorithm should be used to calculate these positions? I’m looking for a solution that is completely blind to the angle of the gradient.

Below is the SVG with all the appropriate coordinates that was used to generate these examples:

Using SVG code

Step1:

``````<!-- Step 1 -->
<stop  offset="0" style="stop-color:#FFFFFF"/>
<stop  offset="0.6687" style="stop-color:#CCCCCC"/>
<stop  offset="1" style="stop-color:#FFFFFF"/>
<polygon id="top_surface_1" fill="url(#top_surface_1_gradient)" points="137.145,41.204 68.572,0 0,41.204 68.572,82.396"/>
``````

Step 2

``````<!-- Step 2 -->
<stop  offset="0" style="stop-color:#FFFFFF"/>
<stop  offset="0.6687" style="stop-color:#CCCCCC"/>
<stop  offset="1" style="stop-color:#FFFFFF"/>
<polygon id="top_surface_2" fill="url(#top_surface_2_gradient)" points="205.788,215.557 137.215,174.354 0.078,256.629 68.649,297.823"/>
``````

Step 3

``````<!-- Step 3 -->
<stop  offset="0" style="stop-color:#FFFFFF"/>
<stop  offset="0.6687" style="stop-color:#CCCCCC"/>
<stop  offset="1" style="stop-color:#FFFFFF"/>
<polygon id="top_surface_3" fill="url(#top_surface_3_gradient)" points="205.788,415.557 137.215,374.354 0.078,456.629 68.649,497.823"/>
``````

I’ve spent countless hours developing solutions for this problem and I just couldn’t get my head around it. Any help would be greatly appreciated.

Using the gradientTransform attribute and no x1,y1; x2,y2 coordinates for the gradient, we achieve results that fill the polygon in a way that is almost as needed (This solution can be found here: http://jsfiddle.net/hqXx2/). The only place where the solution breaks is when the polygon is filled with a gradient that starts outside of the polygon and/or ends somewhere outside/inside. Let me illustrate:

This is what is achieved with the solution, that Duopixel is suggesting. This is the usage case that is impossible to achieve using the solution mentioned above. I changed the colouring in order to visibly amplify the angle and gradient stops. SVG Code from the example

Here’s the code for the larger, correctly expanded group of polygons:

``````<g>
<stop  offset="0" style="stop-color:#FFFFFF"/>
<stop  offset="0.8528" style="stop-color:#CCCCCC"/>
<stop  offset="0.9954" style="stop-color:#CCCCCC"/>
<polygon id="surface_center_inside_bottom_9_" fill="url(#surface_center_inside_bottom_1_)" points="137.145,620.04 68.572,578.837 0,620.04 68.572,661.233"/>

<stop  offset="0" style="stop-color:#FFFFFF"/>
<stop  offset="0.9816" style="stop-color:#A3A5A8"/>
<polygon id="surface_right_inside_side_3_" fill="url(#surface_right_inside_side_1_)" points="136.526,620.374 68.359,578.501 68.572,493.837 137.358,535.37"/>

<stop  offset="0" style="stop-color:#FF0000"/>
<stop  offset="0.6698" style="stop-color:#00FFFF"/>
<stop  offset="1" style="stop-color:#FF0000"/>
<polygon id="surface_right_inside_side_5_" fill="url(#surface_right_inside_side_2_)" points="68.573,661.239 0,620.036 0,535.036 68.573,576.231"/>

<stop  offset="0.0016" style="stop-color:#FF0000"/>
<stop  offset="0.6735" style="stop-color:#00FFFF"/>
<stop  offset="1" style="stop-color:#FF0000"/>
<polygon id="surface_center_outside_top_3_" fill="url(#surface_center_outside_top_1_)" points="137.145,535.041 68.572,493.837 0,535.041 68.572,576.233"/>
</g>
``````

And here’s the SVG code for the smaller one, which I need to expand:

``````<g>
<stop  offset="0" style="stop-color:#FFFFFF"/>
<stop  offset="0.9816" style="stop-color:#A3A5A8"/>
<polygon id="surface_right_inside_side_9_" fill="url(#surface_right_inside_side_4_)" points="205.112,366.797 136.945,324.924 137.157,156.261 205.731,197.464"/>

<stop  offset="0" style="stop-color:#FFFFFF"/>
<stop  offset="0.8528" style="stop-color:#CCCCCC"/>
<stop  offset="0.9954" style="stop-color:#CCCCCC"/>
<polygon fill="url(#SVGID_1_)" points="205.731,366.465 137.157,325.262 0.021,407.536 68.592,448.729"/>

<stop  offset="0" style="stop-color:#FF0000"/>
<stop  offset="0.6698" style="stop-color:#00FFFF"/>
<stop  offset="1" style="stop-color:#FF0000"/>
<polygon id="surface_right_inside_side_6_" fill="url(#surface_right_inside_side_7_)" points="68.532,448.767 0,407.497 0.021,238.536 68.592,279.729"/>

<stop  offset="0.0016" style="stop-color:#FF0000"/>
<stop  offset="0.6735" style="stop-color:#00FFFF"/>
<stop  offset="1" style="stop-color:#FF0000"/>
<polygon fill="url(#SVGID_2_)" points="205.731,197.464 137.157,156.261 68.592,197.333 0.021,238.536 68.592,279.729"/>
</g>
``````

@dignifiedquire take on this problem

Read More:   Can I change the viewport meta tag in mobile safari on the fly?

I’ve implemented @dignifiedquire suggested algo in a test site: Here’s the test link. I did the absolute to relative conversion on my own and it just shows the same result I would have normally adding the same polygon x and y change values to the gradient x and y. That is the main issue – how to translate those values into such value, that transform the gradient as in my examples above?

More help is needed.

Update 3 Alternative Idea

An alternative solution could be to calculate the percentage values based on the two end points of the gradient.
In this Picture you see the original polygon `abcd`, its bounding box `a'b'c'd'` and the gradient `g1g2`. The aim is now to calculate first the two points `g1` and `g2` in absolute values and then calculate the relative values of these two. I’ve derived an algorithm that does most of what I described but it fails short of calculating the intersection between the gradient and the bounding box. I have an idea on how to solve this problem but right now not the time to implement it, so I list the steps for that.
The basic idea is to test if the gradient intersects with one of the lines through the corners of the bounding box (`a'b'`,`b'c'`,`c'd'`,`d'a'`) and then test if the intersection is in on the edge in question. There are now two special cases that need handling.
1. the gradient is vertical, this means its slope is infinity
2. the side in question is vertical, again this means its slope is infinity
All other cases are easily solvable with basic math (two point form of a line, intersection of two lines).

My algorithm

``````_ = require('underscore')

var sides = [
["a", "b"],
["b", "c"],
["c", "d"],
["d", "a"]
];

var boundingBox = calculateBoundingBox(polygon);

// intersect each side of the bounding box with the gradient
var intersections = _.map(sides, function(side){
if(intersection){
// calculate the percentages
console.log(JSON.stringify(intersection));
return calcPercentage(intersection, boundingBox);
}
});

return intersections;

}

function Point(x,y){
this.x = x;
this.y = y;
}

function calcPercentage(intersection, boundingBox){
var lengthX = (boundingBox.max.x - boundingBox.min.x),
lengthY = (boundingBox.max.y - boundingBox.min.y),
x = (intersection.x / lengthX) * 100,
y = (intersection.y / lengthY) * 100;
}

function calculateBoundingBox(polygon){
var xValues = _.pluck(polygon, 'x'),
yValues = _.pluck(polygon, 'y'),
maxX = _.max(xValues),
maxY = _.max(yValues),
minX = _.min(xValues),
minY = _.min(yValues);

return {
"a": new Point(minX, maxY),
"b": new Point(maxX, maxY),
"c": new Point(maxX, minY),
"d": new Point(minX, minY),
"max": new Point(maxX, maxY),
"min": new Point(minX, minY)
};
}
// tests if the two lines a1, b1 and a2, b2 intersect and
// returns the point of intersection if the do so
function intersect(a1, b1, a2, b2){

var s = new Point( );

// TODO
// This is the part that is missing
// one needs to implement what I described above at this point
//

if (isInIntervall(s.x, a1.x, b1.x) && isInIntervall(s.y, a2.y, b2.y)){
return s;
}
else {
return false;
}
}

// test if a point is in the intervall [a,b]
function isInIntervall(point, a, b){
return (point >= a) && (point <=b);
}
``````

Update 2

Question: Also how should the gradient coordinates change if the polygon is moved in space as a whole and not stretched.?

Answer: You calculate the amount that you move one point of your polygon in x and y and move the points of the gradient the exact same amount.

I’ve now changed the algorithm to be based of scaling on one side of the polygon by absolute amount of units.
I’ve also created an image to explain what the algorithm does

1. original polygon
2. scaled polygon by a scale factor determined by the inputs
3. move the polygon back to the original place Updated 15.7.2012
I’ve derived an algorithm based on the idea I proposed using transformation matrices for the transform. I hadn’t time to test it but the code is running under node.js and should run in the browser if you include underscore.js and sylvester (matrix operations) in your document.

The Setup

``````/* underscore for some helper methods
* http:*http:*underscorejs.org
*/
_ = require("underscore");

/* matrix operations
* http:*sylvester.jcoglan.com
*/
require("sylvester");
``````

The inputs

``````var gradient = {
"a":{
"x": 165.3425,
"y": 39.7002
},
"b":{
"x": -49.991,
"y": 43.0337
}
};

var polygon = {
"a": {
"x": 137.145,
"y": 41.204
},
"b": {
"x": 68.572,
"y": 0
},
"c": {
"x": 0,
"y": 41.204
},
"d": {
"x": 68.572,
"y": 82.396
}
};
// the scales are now absolute values in the same units as the coordinates
var scaleAbsX = 100;
var scaleAbsY = 100 * Math.tan( 62/2 * (Math.PI/180) );

// this describes the side that is scaled
var side = ["a", "b"];
``````

The Algorithm

``````scalePolyWithGradient = function(polygon, gradient, scaleAbsX, scaleAbsY, side){
// 1. Scale by factor: derive factor from input scaling and then translate into scaling matrix
// 2. Apply scale to the gradient
// 3. Translate both back

// create a scaling matrix based of the input scales

// get the two points of the scaled side
var point1 = polygon[side],
point2 = polygon[side];
// scale these points
var scaledPoint1 = { "x": point1.x + scaleAbsX,
"y": point1.y + scaleAbsY },
scaledPoint2 = { "x": point2.x + scaleAbsX,
"y": point2.y + scaleAbsY };

// calculate the relative scales
var scaleRelX = scaledPoint1.x / point1.x,
scaleRelY = scaledPoint1.y / point1.y;

// create the scale matrix
var scaleMatrix = \$M([ [scaleRelX, 0],
[0, scaleRelY] ]);

// scale both the polygon and the gradient
// we iterate so that the translation is done on every point
var scale = function(point){
// convert the point into a matrix
point = \$M([[point.x],
[point.y]]);

// scale
var newPoint = scaleMatrix.multiply(point);

return { "x": newPoint.elements,
"y": newPoint.elements};
};

var newPolygon  = {},

_.each(polygon, function(point, key){
newPolygon[key] = scale(point);
});
});

// calculate the translation to move them to the original position
// and move them back

// we know that the points to move to their original position are the
// ones not in the scale side
var sides = _.keys(polygon),                   // all possible sides
movePoints = _.difference(sides, side),    // the points not used in the scale
point = movePoints;                     // the first of these points

// we use these points to create the translation
var oldPoint = polygon[point],
newPoint = newPolygon[point];
var translateMatrix = \$M([ [newPoint.x - oldPoint.x],
[newPoint.y - oldPoint.y] ]);

var translate = function(point){
// convert the point into a matrix
point = \$M([[point.x],
[point.y]]);

// translate

return { "x": newPoint.elements,
"y": newPoint.elements};
};
// apply the translation
_.each(newPolygon, function(point, key){
newPolygon[key] = translate(point);
});
});

// return the new coordinates
};
// apply the algorithm
newPolygon = result;
``````

The result

`````` newPolygon = { "a": {
"x": 178.2885,
"y":82.405
},
"b": {
"x": 96.00089999999999,
"y": 20.598999999999997
},
"c": {
"x": 13.714500000000001,
"y": 82.405
},
"d": {
"x": 96.00089999999999,
"y":144.19299999999998
}
}
"x": 212.12550000000005,
"y":80.14930000000001
},
"b": {
"x": -46.274699999999996,
"y": 85.14955
}
}
``````

Read More:   Send and receive binary data over web sockets in Javascript?

The image is here because I can’t upload images to stackoverflow (reputation is to low)

I abstracted the side of the polygon so we can focus on that. The left picture is before scaling. Now I’ve drawn the “whole” gradient to show what needs to be scaled. In order to figure out the needed coordinates one just scales the square of the gradient in the same proportion as the side of the polygon.

I know this image is without rotation but this method can be expanded to also incorporate this.

I can derive an algorithm for this stuff but haven’t had the time to do so. So if this is what you want let me know and I will get to it tomorrow.

You can apply transformations to gradients, this means you can do stuff such as `gradientTransform="rotate(45)`. This solves your rotation issue.

You should use relative units and set the userspace to `objectBoundingBox` so that the `x` and `y` values correspond to the dimensions of your polygon. Your svg would look like this.

``````<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<stop  offset="0" style="stop-color:#000"/>
<stop  offset="1" style="stop-color:#fff"/> 