Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge remote branch 'origin/master'

  • Loading branch information...
commit 416fef5d477f1908dde0954d24f12e2ec79a7e89 2 parents 54537ff + 72e512b
highslide-software authored
View
86 js/highcharts.src.js
@@ -477,6 +477,61 @@ ChartCounters.prototype = {
};
/**
+ * Utility method extracted from Tooltip code that places a tooltip in a chart without spilling over
+ * and not covering the point it self.
+ */
+function placeBox(boxWidth, boxHeight, outerLeft, outerTop, outerWidth, outerHeight, point) {
+ // keep the box within the chart area
+ var x = point.x - boxWidth + outerLeft - 25,
+ y = point.y - boxHeight + outerTop + 10;
+
+ // it is too far to the left, adjust it
+ if (x < 7) {
+ x = outerLeft + point.x + 15;
+ }
+
+ // Test to see if the tooltip is to far to the right,
+ // if it is, move it back to be inside and then up to not cover the point.
+ if ((x + boxWidth) > (outerLeft + outerWidth)) {
+ x -= (x + boxWidth) - (outerLeft + outerWidth);
+ y -= boxHeight;
+ }
+
+ if (y < 5) {
+ y = 5; // above
+ } else if (y + boxHeight > outerHeight) {
+ y = outerHeight - boxHeight - 5; // below
+ y = outerHeight - boxHeight - 5; // below
+ }
+
+ return {x: x, y: y};
+}
+
+/**
+ * Utility method that sorts an object array and keeping the order of equal items.
+ * ECMA script standard does not specify the behaviour when items are equal.
+ */
+function stableSort(arr, sortFunction) {
+ var length = arr.length,
+ i;
+
+ // Add index to each item
+ for (i = 0; i < length; i++) {
+ arr[i].ss_i = i; // stable sort index
+ }
+
+ arr.sort(function (a, b) {
+ var sortValue = sortFunction(a, b);
+ return sortValue === 0 ? a.ss_i - b.ss_i : sortValue;
+ });
+
+ // Remove index from items
+ for (i = 0; i < length; i++) {
+ delete arr[i].ss_i; // stable sort index
+ }
+}
+
+/**
* Set the global animation to either a given value, or fall back to the
* given chart's animation option
* @param {Object} animation
@@ -5912,7 +5967,8 @@ function Chart(options, callback) {
pointConfig = [],
tooltipPos = point.tooltipPos,
formatter = options.formatter || defaultFormatter,
- hoverPoints = chart.hoverPoints;
+ hoverPoints = chart.hoverPoints,
+ placedTooltipPoint;
// shared tooltip, array is sent over
if (shared) {
@@ -5993,27 +6049,11 @@ function Chart(options, callback) {
height: boxHeight,
stroke: options.borderColor || point.color || currentSeries.color || '#606060'
});
-
- // keep the box within the chart area
- boxX = x - boxWidth + plotLeft - 25;
- boxY = y - boxHeight + plotTop + 10;
-
- // it is too far to the left, adjust it
- if (boxX < 7) {
- boxX = plotLeft + x + 15;
- }
-
-
- if (boxY < 5) {
- boxY = 5; // above
- } else if (boxY + boxHeight > chartHeight) {
- boxY = chartHeight - boxHeight - 5; // below
- }
-
+
+ placedTooltipPoint = placeBox(boxWidth, boxHeight, plotLeft, plotTop, plotWidth, plotHeight, {x: x, y: y});
+
// do the move
- move(mathRound(boxX - boxOffLeft), mathRound(boxY - boxOffLeft));
-
-
+ move(mathRound(placedTooltipPoint.x - boxOffLeft), mathRound(placedTooltipPoint.y - boxOffLeft));
}
@@ -6939,7 +6979,7 @@ function Chart(options, callback) {
});
// sort by legendIndex
- allItems.sort(function (a, b) {
+ stableSort(allItems, function (a, b) {
return (a.options.legendIndex || 0) - (b.options.legendIndex || 0);
});
@@ -8649,7 +8689,7 @@ Series.prototype = {
i;
// sort the data points
- data.sort(function (a, b) {
+ stableSort(data, function (a, b) {
return (a.x - b.x);
});
View
29 js/parts/Chart.js
@@ -2010,7 +2010,8 @@ function Chart(options, callback) {
pointConfig = [],
tooltipPos = point.tooltipPos,
formatter = options.formatter || defaultFormatter,
- hoverPoints = chart.hoverPoints;
+ hoverPoints = chart.hoverPoints,
+ placedTooltipPoint;
// shared tooltip, array is sent over
if (shared) {
@@ -2091,27 +2092,11 @@ function Chart(options, callback) {
height: boxHeight,
stroke: options.borderColor || point.color || currentSeries.color || '#606060'
});
-
- // keep the box within the chart area
- boxX = x - boxWidth + plotLeft - 25;
- boxY = y - boxHeight + plotTop + 10;
-
- // it is too far to the left, adjust it
- if (boxX < 7) {
- boxX = plotLeft + x + 15;
- }
-
-
- if (boxY < 5) {
- boxY = 5; // above
- } else if (boxY + boxHeight > chartHeight) {
- boxY = chartHeight - boxHeight - 5; // below
- }
-
+
+ placedTooltipPoint = placeBox(boxWidth, boxHeight, plotLeft, plotTop, plotWidth, plotHeight, {x: x, y: y});
+
// do the move
- move(mathRound(boxX - boxOffLeft), mathRound(boxY - boxOffLeft));
-
-
+ move(mathRound(placedTooltipPoint.x - boxOffLeft), mathRound(placedTooltipPoint.y - boxOffLeft));
}
@@ -3037,7 +3022,7 @@ function Chart(options, callback) {
});
// sort by legendIndex
- allItems.sort(function (a, b) {
+ stableSort(allItems, function (a, b) {
return (a.options.legendIndex || 0) - (b.options.legendIndex || 0);
});
View
2  js/parts/Series.js
@@ -475,7 +475,7 @@ Series.prototype = {
i;
// sort the data points
- data.sort(function (a, b) {
+ stableSort(data, function (a, b) {
return (a.x - b.x);
});
View
55 js/parts/Utilities.js
@@ -360,3 +360,58 @@ ChartCounters.prototype = {
}
}
};
+
+/**
+ * Utility method extracted from Tooltip code that places a tooltip in a chart without spilling over
+ * and not covering the point it self.
+ */
+function placeBox(boxWidth, boxHeight, outerLeft, outerTop, outerWidth, outerHeight, point) {
+ // keep the box within the chart area
+ var x = point.x - boxWidth + outerLeft - 25,
+ y = point.y - boxHeight + outerTop + 10;
+
+ // it is too far to the left, adjust it
+ if (x < 7) {
+ x = outerLeft + point.x + 15;
+ }
+
+ // Test to see if the tooltip is to far to the right,
+ // if it is, move it back to be inside and then up to not cover the point.
+ if ((x + boxWidth) > (outerLeft + outerWidth)) {
+ x -= (x + boxWidth) - (outerLeft + outerWidth);
+ y -= boxHeight;
+ }
+
+ if (y < 5) {
+ y = 5; // above
+ } else if (y + boxHeight > outerHeight) {
+ y = outerHeight - boxHeight - 5; // below
+ y = outerHeight - boxHeight - 5; // below
+ }
+
+ return {x: x, y: y};
+}
+
+/**
+ * Utility method that sorts an object array and keeping the order of equal items.
+ * ECMA script standard does not specify the behaviour when items are equal.
+ */
+function stableSort(arr, sortFunction) {
+ var length = arr.length,
+ i;
+
+ // Add index to each item
+ for (i = 0; i < length; i++) {
+ arr[i].ss_i = i; // stable sort index
+ }
+
+ arr.sort(function (a, b) {
+ var sortValue = sortFunction(a, b);
+ return sortValue === 0 ? a.ss_i - b.ss_i : sortValue;
+ });
+
+ // Remove index from items
+ for (i = 0; i < length; i++) {
+ delete arr[i].ss_i; // stable sort index
+ }
+}
View
113 test/unit/UtilitiesTest.js
@@ -1,4 +1,4 @@
-UtilTest = TestCase("UtilitiesTest");
+var UtilTest = TestCase("UtilitiesTest");
UtilTest.prototype.testExtend = function () {
var empty = {};
@@ -25,7 +25,7 @@ UtilTest.prototype.testExtend = function () {
// test extend of another object
result = extend(extra, extra2);
assertEquals("Extended object with object2", 4, this.countMembers(result));
-}
+};
UtilTest.prototype.countMembers = function (obj) {
var count = 0;
@@ -34,9 +34,9 @@ UtilTest.prototype.countMembers = function (obj) {
}
return count;
-}
+};
-UtilTest.prototype.testPInt = function() {
+UtilTest.prototype.testPInt = function () {
// test without a base defined
assertEquals("base not defined", 15, pInt("15"));
@@ -45,9 +45,9 @@ UtilTest.prototype.testPInt = function() {
// test with base 16
assertEquals("base 16", 15, pInt("F", 16));
-}
+};
-UtilTest.prototype.testIsString = function() {
+UtilTest.prototype.testIsString = function () {
// test with undefined
assertEquals("IsString undefined", false, isString(undefined));
@@ -65,9 +65,9 @@ UtilTest.prototype.testIsString = function() {
// test with string
assertEquals("IsString string", true, isString("this is a string"));
-}
+};
-UtilTest.prototype.testIsObject = function() {
+UtilTest.prototype.testIsObject = function () {
// test with undefined
assertEquals("IsObject undefined", false, isObject(undefined));
@@ -82,9 +82,9 @@ UtilTest.prototype.testIsObject = function() {
// test with object
assertEquals("IsObject object", true, isObject({}));
-}
+};
-UtilTest.prototype.testIsNumber = function() {
+UtilTest.prototype.testIsNumber = function () {
// test with undefined
assertEquals("IsNumber undefined", false, isNumber(undefined));
@@ -99,12 +99,95 @@ UtilTest.prototype.testIsNumber = function() {
// test with object
assertEquals("IsNumber object", false, isNumber({}));
-}
+};
-UtilTest.prototype.testLog2Lin = function() {
+UtilTest.prototype.testLog2Lin = function () {
// TODO: implement
-}
+};
-UtilTest.prototype.testLin2Log = function() {
+UtilTest.prototype.testLin2Log = function () {
// TODO: implement
-}
+};
+
+/**
+ * Tests if a point is inside a rectangle
+ */
+UtilTest.prototype.pointInRect = function (x, y, rect) {
+ var inside = x >= rect.x && x <= (rect.x + rect.width) && y >= rect.y && y <= (rect.y + rect.height)
+ return inside;
+};
+
+/**
+ * Tests if a small rectangle is inside a bigger rectangle by testing each corner.
+ */
+UtilTest.prototype.rectInRect = function (smallRect, largeRect) {
+ // (Maybe only two corners need to be tested)
+ var inside = this.pointInRect(smallRect.x, smallRect.y, largeRect); // left top
+ inside = inside && this.pointInRect(smallRect.x + smallRect.width, smallRect.y, largeRect); // right top
+ inside = inside && this.pointInRect(smallRect.x + smallRect.width, smallRect.y + smallRect.height, largeRect); // right bottom
+ inside = inside && this.pointInRect(smallRect.x, smallRect.y + smallRect.height, largeRect); // left bottom
+ return inside;
+};
+
+/**
+ * Test the placeBox utility function. It should adjust a tooltip rectangle to be inside the chart but not cover the point itself.
+ */
+UtilTest.prototype.testPlaceBox = function () {
+ var chartRect = {x: 0, y: 0, width: 100, height: 100 },
+ tooltipSize = {width: 50, height: 20},
+ dataPoint = {x: 0, y: 50},
+ tooltipPoint,
+ boxPoint;
+
+ boxPoint = placeBox(tooltipSize.width, tooltipSize.height, chartRect.x, chartRect.y, chartRect.width, chartRect.height, dataPoint);
+ extend(boxPoint, tooltipSize);
+ assertTrue('Left rectInRect chart', this.rectInRect(boxPoint, chartRect));
+ assertFalse('Left tooltip cover point', this.pointInRect(dataPoint.x, dataPoint.y, boxPoint));
+
+ dataPoint.x = 100;
+ boxPoint = placeBox(tooltipSize.width, tooltipSize.height, chartRect.x, chartRect.y, chartRect.width, chartRect.height, dataPoint);
+ extend(boxPoint, tooltipSize);
+ assertTrue('Right rectInRect chart', this.rectInRect(boxPoint, chartRect));
+ assertFalse('Right tooltip cover point', this.pointInRect(dataPoint.x, dataPoint.y, boxPoint));
+
+ dataPoint.x = 50;
+ boxPoint = placeBox(tooltipSize.width, tooltipSize.height, chartRect.x, chartRect.y, chartRect.width, chartRect.height, dataPoint);
+ extend(boxPoint, tooltipSize);
+ assertTrue('Mid rectInRect chart', this.rectInRect(boxPoint, chartRect));
+ assertFalse('Mid tooltip cover point', this.pointInRect(dataPoint.x, dataPoint.y, boxPoint));
+};
+
+/**
+ * Tests that the stable sort utility works.
+ */
+UtilTest.prototype.testStableSort = function () {
+ // Initialize the array, it needs to be a certain size to trigger the unstable quicksort algorithm.
+ // These 11 items fails in Chrome due to its unstable sort.
+ var arr = [
+ {a: 1, b: 'F'},
+ {a: 2, b: 'H'},
+ {a: 1, b: 'G'},
+ {a: 0, b: 'A'},
+ {a: 0, b: 'B'},
+ {a: 3, b: 'J'},
+ {a: 0, b: 'C'},
+ {a: 0, b: 'D'},
+ {a: 0, b: 'E'},
+ {a: 2, b: 'I'},
+ {a: 3, b: 'K'}
+ ],
+ result = [];
+
+ // Do the sort
+ stableSort(arr, function (a, b) {
+ return a.a - b.a;
+ })
+
+ // Collect the result
+ for (var i = 0; i < arr.length; i++) {
+ result.push(arr[i].b);
+ }
+
+ assertEquals('Stable sort in action', 'ABCDEFGHIJK', result.join(''));
+ assertUndefined('Stable sort index should not be there', arr[0].ss_i);
+};
Please sign in to comment.
Something went wrong with that request. Please try again.