highcharts-rails 4.2.7 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.markdown +34 -0
- data/Gemfile +4 -0
- data/Rakefile +53 -32
- data/app/assets/javascripts/highcharts.js +18775 -17176
- data/app/assets/javascripts/highcharts/highcharts-3d.js +1849 -1563
- data/app/assets/javascripts/highcharts/highcharts-more.js +2162 -1988
- data/app/assets/javascripts/highcharts/modules/accessibility.js +1005 -0
- data/app/assets/javascripts/highcharts/modules/annotations.js +408 -401
- data/app/assets/javascripts/highcharts/modules/boost.js +561 -546
- data/app/assets/javascripts/highcharts/modules/broken-axis.js +330 -324
- data/app/assets/javascripts/highcharts/modules/data.js +973 -965
- data/app/assets/javascripts/highcharts/modules/drilldown.js +783 -723
- data/app/assets/javascripts/highcharts/modules/exporting.js +864 -785
- data/app/assets/javascripts/highcharts/modules/funnel.js +290 -306
- data/app/assets/javascripts/highcharts/modules/heatmap.js +701 -645
- data/app/assets/javascripts/highcharts/modules/no-data-to-display.js +150 -132
- data/app/assets/javascripts/highcharts/modules/offline-exporting.js +414 -355
- data/app/assets/javascripts/highcharts/modules/overlapping-datalabels.js +164 -0
- data/app/assets/javascripts/highcharts/modules/series-label.js +473 -448
- data/app/assets/javascripts/highcharts/modules/solid-gauge.js +279 -271
- data/app/assets/javascripts/highcharts/modules/treemap.js +921 -886
- data/app/assets/javascripts/highcharts/themes/dark-blue.js +307 -244
- data/app/assets/javascripts/highcharts/themes/dark-green.js +303 -244
- data/app/assets/javascripts/highcharts/themes/dark-unica.js +231 -201
- data/app/assets/javascripts/highcharts/themes/gray.js +314 -245
- data/app/assets/javascripts/highcharts/themes/grid-light.js +91 -66
- data/app/assets/javascripts/highcharts/themes/grid.js +124 -96
- data/app/assets/javascripts/highcharts/themes/sand-signika.js +119 -94
- data/app/assets/javascripts/highcharts/themes/skies.js +108 -85
- data/lib/highcharts/version.rb +1 -1
- metadata +13 -14
- data/app/assets/javascripts/highcharts/adapters/standalone-framework.js +0 -1
- data/app/assets/javascripts/highcharts/modules/canvas-tools.js +0 -3115
- data/app/assets/javascripts/highcharts/modules/map.js +0 -2117
@@ -0,0 +1,164 @@
|
|
1
|
+
/**
|
2
|
+
* @license Highcharts JS v5.0.0 (2016-09-29)
|
3
|
+
*
|
4
|
+
* (c) 2009-2016 Torstein Honsi
|
5
|
+
*
|
6
|
+
* License: www.highcharts.com/license
|
7
|
+
*/
|
8
|
+
(function(factory) {
|
9
|
+
if (typeof module === 'object' && module.exports) {
|
10
|
+
module.exports = factory;
|
11
|
+
} else {
|
12
|
+
factory(Highcharts);
|
13
|
+
}
|
14
|
+
}(function(Highcharts) {
|
15
|
+
(function(H) {
|
16
|
+
/**
|
17
|
+
* (c) 2009-2016 Torstein Honsi
|
18
|
+
*
|
19
|
+
* License: www.highcharts.com/license
|
20
|
+
*/
|
21
|
+
'use strict';
|
22
|
+
/**
|
23
|
+
* Highcharts module to hide overlapping data labels. This module is included in Highcharts.
|
24
|
+
*/
|
25
|
+
var Chart = H.Chart,
|
26
|
+
each = H.each,
|
27
|
+
pick = H.pick,
|
28
|
+
addEvent = H.addEvent;
|
29
|
+
|
30
|
+
// Collect potensial overlapping data labels. Stack labels probably don't need to be
|
31
|
+
// considered because they are usually accompanied by data labels that lie inside the columns.
|
32
|
+
Chart.prototype.callbacks.push(function(chart) {
|
33
|
+
function collectAndHide() {
|
34
|
+
var labels = [];
|
35
|
+
|
36
|
+
each(chart.series, function(series) {
|
37
|
+
var dlOptions = series.options.dataLabels,
|
38
|
+
collections = series.dataLabelCollections || ['dataLabel']; // Range series have two collections
|
39
|
+
if ((dlOptions.enabled || series._hasPointLabels) && !dlOptions.allowOverlap && series.visible) { // #3866
|
40
|
+
each(collections, function(coll) {
|
41
|
+
each(series.points, function(point) {
|
42
|
+
if (point[coll]) {
|
43
|
+
point[coll].labelrank = pick(point.labelrank, point.shapeArgs && point.shapeArgs.height); // #4118
|
44
|
+
labels.push(point[coll]);
|
45
|
+
}
|
46
|
+
});
|
47
|
+
});
|
48
|
+
}
|
49
|
+
});
|
50
|
+
chart.hideOverlappingLabels(labels);
|
51
|
+
}
|
52
|
+
|
53
|
+
// Do it now ...
|
54
|
+
collectAndHide();
|
55
|
+
|
56
|
+
// ... and after each chart redraw
|
57
|
+
addEvent(chart, 'redraw', collectAndHide);
|
58
|
+
|
59
|
+
});
|
60
|
+
|
61
|
+
/**
|
62
|
+
* Hide overlapping labels. Labels are moved and faded in and out on zoom to provide a smooth
|
63
|
+
* visual imression.
|
64
|
+
*/
|
65
|
+
Chart.prototype.hideOverlappingLabels = function(labels) {
|
66
|
+
|
67
|
+
var len = labels.length,
|
68
|
+
label,
|
69
|
+
i,
|
70
|
+
j,
|
71
|
+
label1,
|
72
|
+
label2,
|
73
|
+
isIntersecting,
|
74
|
+
pos1,
|
75
|
+
pos2,
|
76
|
+
parent1,
|
77
|
+
parent2,
|
78
|
+
padding,
|
79
|
+
intersectRect = function(x1, y1, w1, h1, x2, y2, w2, h2) {
|
80
|
+
return !(
|
81
|
+
x2 > x1 + w1 ||
|
82
|
+
x2 + w2 < x1 ||
|
83
|
+
y2 > y1 + h1 ||
|
84
|
+
y2 + h2 < y1
|
85
|
+
);
|
86
|
+
};
|
87
|
+
|
88
|
+
// Mark with initial opacity
|
89
|
+
for (i = 0; i < len; i++) {
|
90
|
+
label = labels[i];
|
91
|
+
if (label) {
|
92
|
+
label.oldOpacity = label.opacity;
|
93
|
+
label.newOpacity = 1;
|
94
|
+
}
|
95
|
+
}
|
96
|
+
|
97
|
+
// Prevent a situation in a gradually rising slope, that each label
|
98
|
+
// will hide the previous one because the previous one always has
|
99
|
+
// lower rank.
|
100
|
+
labels.sort(function(a, b) {
|
101
|
+
return (b.labelrank || 0) - (a.labelrank || 0);
|
102
|
+
});
|
103
|
+
|
104
|
+
// Detect overlapping labels
|
105
|
+
for (i = 0; i < len; i++) {
|
106
|
+
label1 = labels[i];
|
107
|
+
|
108
|
+
for (j = i + 1; j < len; ++j) {
|
109
|
+
label2 = labels[j];
|
110
|
+
if (label1 && label2 && label1.placed && label2.placed && label1.newOpacity !== 0 && label2.newOpacity !== 0) {
|
111
|
+
pos1 = label1.alignAttr;
|
112
|
+
pos2 = label2.alignAttr;
|
113
|
+
parent1 = label1.parentGroup; // Different panes have different positions
|
114
|
+
parent2 = label2.parentGroup;
|
115
|
+
padding = 2 * (label1.box ? 0 : label1.padding); // Substract the padding if no background or border (#4333)
|
116
|
+
isIntersecting = intersectRect(
|
117
|
+
pos1.x + parent1.translateX,
|
118
|
+
pos1.y + parent1.translateY,
|
119
|
+
label1.width - padding,
|
120
|
+
label1.height - padding,
|
121
|
+
pos2.x + parent2.translateX,
|
122
|
+
pos2.y + parent2.translateY,
|
123
|
+
label2.width - padding,
|
124
|
+
label2.height - padding
|
125
|
+
);
|
126
|
+
|
127
|
+
if (isIntersecting) {
|
128
|
+
(label1.labelrank < label2.labelrank ? label1 : label2).newOpacity = 0;
|
129
|
+
}
|
130
|
+
}
|
131
|
+
}
|
132
|
+
}
|
133
|
+
|
134
|
+
// Hide or show
|
135
|
+
each(labels, function(label) {
|
136
|
+
var complete,
|
137
|
+
newOpacity;
|
138
|
+
|
139
|
+
if (label) {
|
140
|
+
newOpacity = label.newOpacity;
|
141
|
+
|
142
|
+
if (label.oldOpacity !== newOpacity && label.placed) {
|
143
|
+
|
144
|
+
// Make sure the label is completely hidden to avoid catching clicks (#4362)
|
145
|
+
if (newOpacity) {
|
146
|
+
label.show(true);
|
147
|
+
} else {
|
148
|
+
complete = function() {
|
149
|
+
label.hide();
|
150
|
+
};
|
151
|
+
}
|
152
|
+
|
153
|
+
// Animate or set the opacity
|
154
|
+
label.alignAttr.opacity = newOpacity;
|
155
|
+
label[label.isOld ? 'animate' : 'attr'](label.alignAttr, null, complete);
|
156
|
+
|
157
|
+
}
|
158
|
+
label.isOld = true;
|
159
|
+
}
|
160
|
+
});
|
161
|
+
};
|
162
|
+
|
163
|
+
}(Highcharts));
|
164
|
+
}));
|
@@ -1,519 +1,544 @@
|
|
1
1
|
/**
|
2
|
-
*
|
2
|
+
* @license Highcharts JS v5.0.0 (2016-09-29)
|
3
3
|
*
|
4
|
-
*
|
5
|
-
*
|
6
|
-
*
|
7
|
-
* - avoid data labels, when data labels above, show series label below.
|
8
|
-
* - add more options (connector, format, formatter)
|
9
|
-
*
|
10
|
-
* http://jsfiddle.net/highcharts/L2u9rpwr/
|
11
|
-
* http://jsfiddle.net/highcharts/y5A37/
|
12
|
-
* http://jsfiddle.net/highcharts/264Nm/
|
13
|
-
* http://jsfiddle.net/highcharts/y5A37/
|
4
|
+
* (c) 2009-2016 Torstein Honsi
|
5
|
+
*
|
6
|
+
* License: www.highcharts.com/license
|
14
7
|
*/
|
15
|
-
|
16
|
-
(function (factory) {
|
8
|
+
(function(factory) {
|
17
9
|
if (typeof module === 'object' && module.exports) {
|
18
10
|
module.exports = factory;
|
19
11
|
} else {
|
20
12
|
factory(Highcharts);
|
21
13
|
}
|
22
|
-
}(function
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
14
|
+
}(function(Highcharts) {
|
15
|
+
(function(H) {
|
16
|
+
/**
|
17
|
+
* (c) 2009-2016 Torstein Honsi
|
18
|
+
*
|
19
|
+
* License: www.highcharts.com/license
|
20
|
+
*/
|
21
|
+
/**
|
22
|
+
* EXPERIMENTAL Highcharts module to place labels next to a series in a natural position.
|
23
|
+
*
|
24
|
+
* TODO:
|
25
|
+
* - add column support (box collision detection, boxesToAvoid logic)
|
26
|
+
* - other series types, area etc.
|
27
|
+
* - avoid data labels, when data labels above, show series label below.
|
28
|
+
* - add more options (connector, format, formatter)
|
29
|
+
*
|
30
|
+
* http://jsfiddle.net/highcharts/L2u9rpwr/
|
31
|
+
* http://jsfiddle.net/highcharts/y5A37/
|
32
|
+
* http://jsfiddle.net/highcharts/264Nm/
|
33
|
+
* http://jsfiddle.net/highcharts/y5A37/
|
34
|
+
*/
|
35
|
+
|
36
|
+
'use strict';
|
37
|
+
|
38
|
+
var labelDistance = 3,
|
39
|
+
wrap = H.wrap,
|
40
|
+
each = H.each,
|
41
|
+
extend = H.extend,
|
42
|
+
isNumber = H.isNumber,
|
43
|
+
Series = H.Series,
|
44
|
+
SVGRenderer = H.SVGRenderer,
|
45
|
+
Chart = H.Chart;
|
46
|
+
|
47
|
+
H.setOptions({
|
48
|
+
plotOptions: {
|
49
|
+
series: {
|
50
|
+
label: {
|
51
|
+
enabled: true,
|
52
|
+
// Allow labels to be placed distant to the graph if necessary, and
|
53
|
+
// draw a connector line to the graph
|
54
|
+
connectorAllowed: true,
|
55
|
+
connectorNeighbourDistance: 24, // If the label is closer than this to a neighbour graph, draw a connector
|
56
|
+
styles: {
|
57
|
+
fontWeight: 'bold'
|
58
|
+
}
|
59
|
+
// boxesToAvoid: []
|
44
60
|
}
|
45
|
-
// boxesToAvoid: []
|
46
61
|
}
|
47
62
|
}
|
48
|
-
}
|
49
|
-
});
|
50
|
-
|
51
|
-
/**
|
52
|
-
* Counter-clockwise, part of the fast line intersection logic
|
53
|
-
*/
|
54
|
-
function ccw(x1, y1, x2, y2, x3, y3) {
|
55
|
-
var cw = ((y3 - y1) * (x2 - x1)) - ((y2 - y1) * (x3 - x1));
|
56
|
-
return cw > 0 ? true : cw < 0 ? false : true;
|
57
|
-
}
|
63
|
+
});
|
58
64
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
65
|
+
/**
|
66
|
+
* Counter-clockwise, part of the fast line intersection logic
|
67
|
+
*/
|
68
|
+
function ccw(x1, y1, x2, y2, x3, y3) {
|
69
|
+
var cw = ((y3 - y1) * (x2 - x1)) - ((y2 - y1) * (x3 - x1));
|
70
|
+
return cw > 0 ? true : cw < 0 ? false : true;
|
71
|
+
}
|
66
72
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
intersectLine(x, y + h, x + w, y + h, x1, y1, x2, y2) || // bottom of label
|
75
|
-
intersectLine(x, y, x, y + h, x1, y1, x2, y2) // left of label
|
76
|
-
);
|
77
|
-
}
|
73
|
+
/**
|
74
|
+
* Detect if two lines intersect
|
75
|
+
*/
|
76
|
+
function intersectLine(x1, y1, x2, y2, x3, y3, x4, y4) {
|
77
|
+
return ccw(x1, y1, x3, y3, x4, y4) !== ccw(x2, y2, x3, y3, x4, y4) &&
|
78
|
+
ccw(x1, y1, x2, y2, x3, y3) !== ccw(x1, y1, x2, y2, x4, y4);
|
79
|
+
}
|
78
80
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
if (isNumber(anchorX) && isNumber(anchorY)) {
|
90
|
-
|
91
|
-
path = ['M', anchorX, anchorY];
|
92
|
-
|
93
|
-
// Prefer 45 deg connectors
|
94
|
-
yOffset = y - anchorY;
|
95
|
-
if (yOffset < 0) {
|
96
|
-
yOffset = -h - yOffset;
|
97
|
-
}
|
98
|
-
if (yOffset < w) {
|
99
|
-
lateral = anchorX < x + (w / 2) ? yOffset : w - yOffset;
|
100
|
-
}
|
101
|
-
|
102
|
-
// Anchor below label
|
103
|
-
if (anchorY > y + h) {
|
104
|
-
path.push('L', x + lateral, y + h);
|
105
|
-
|
106
|
-
// Anchor above label
|
107
|
-
} else if (anchorY < y) {
|
108
|
-
path.push('L', x + lateral, y);
|
109
|
-
|
110
|
-
// Anchor left of label
|
111
|
-
} else if (anchorX < x) {
|
112
|
-
path.push('L', x, y + h / 2);
|
113
|
-
|
114
|
-
// Anchor right of label
|
115
|
-
} else if (anchorX > x + w) {
|
116
|
-
path.push('L', x + w, y + h / 2);
|
117
|
-
}
|
81
|
+
/**
|
82
|
+
* Detect if a box intersects with a line
|
83
|
+
*/
|
84
|
+
function boxIntersectLine(x, y, w, h, x1, y1, x2, y2) {
|
85
|
+
return (
|
86
|
+
intersectLine(x, y, x + w, y, x1, y1, x2, y2) || // top of label
|
87
|
+
intersectLine(x + w, y, x + w, y + h, x1, y1, x2, y2) || // right of label
|
88
|
+
intersectLine(x, y + h, x + w, y + h, x1, y1, x2, y2) || // bottom of label
|
89
|
+
intersectLine(x, y, x, y + h, x1, y1, x2, y2) // left of label
|
90
|
+
);
|
118
91
|
}
|
119
|
-
return path || [];
|
120
|
-
};
|
121
|
-
|
122
|
-
/**
|
123
|
-
* Points to avoid. In addition to actual data points, the label should avoid
|
124
|
-
* interpolated positions.
|
125
|
-
*/
|
126
|
-
Series.prototype.getPointsOnGraph = function () {
|
127
|
-
var distance = 16,
|
128
|
-
points = this.points,
|
129
|
-
point,
|
130
|
-
last,
|
131
|
-
interpolated = [],
|
132
|
-
i,
|
133
|
-
deltaX,
|
134
|
-
deltaY,
|
135
|
-
delta,
|
136
|
-
len,
|
137
|
-
n,
|
138
|
-
j,
|
139
|
-
d,
|
140
|
-
graph = this.graph || this.area,
|
141
|
-
node = graph.element,
|
142
|
-
inverted = this.chart.inverted,
|
143
|
-
paneLeft = inverted ? this.yAxis.pos : this.xAxis.pos,
|
144
|
-
paneTop = inverted ? this.xAxis.pos : this.yAxis.pos;
|
145
|
-
|
146
|
-
// For splines, get the point at length (possible caveat: peaks are not correctly detected)
|
147
|
-
if (this.getPointSpline && node.getPointAtLength) {
|
148
|
-
// If it is animating towards a path definition, use that briefly, and reset
|
149
|
-
if (graph.toD) {
|
150
|
-
d = graph.attr('d');
|
151
|
-
graph.attr({ d: graph.toD });
|
152
|
-
}
|
153
|
-
len = node.getTotalLength();
|
154
|
-
for (i = 0; i < len; i += distance) {
|
155
|
-
point = node.getPointAtLength(i);
|
156
|
-
interpolated.push({
|
157
|
-
chartX: paneLeft + point.x,
|
158
|
-
chartY: paneTop + point.y,
|
159
|
-
plotX: point.x,
|
160
|
-
plotY: point.y
|
161
|
-
});
|
162
|
-
}
|
163
|
-
if (d) {
|
164
|
-
graph.attr({ d: d });
|
165
|
-
}
|
166
|
-
// Last point
|
167
|
-
point = points[points.length - 1];
|
168
|
-
point.chartX = paneLeft + point.plotX;
|
169
|
-
point.chartY = paneTop + point.plotY;
|
170
|
-
interpolated.push(point);
|
171
92
|
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
93
|
+
/**
|
94
|
+
* General symbol definition for labels with connector
|
95
|
+
*/
|
96
|
+
SVGRenderer.prototype.symbols.connector = function(x, y, w, h, options) {
|
97
|
+
var anchorX = options && options.anchorX,
|
98
|
+
anchorY = options && options.anchorY,
|
99
|
+
path,
|
100
|
+
yOffset,
|
101
|
+
lateral = w / 2;
|
102
|
+
|
103
|
+
if (isNumber(anchorX) && isNumber(anchorY)) {
|
104
|
+
|
105
|
+
path = ['M', anchorX, anchorY];
|
106
|
+
|
107
|
+
// Prefer 45 deg connectors
|
108
|
+
yOffset = y - anchorY;
|
109
|
+
if (yOffset < 0) {
|
110
|
+
yOffset = -h - yOffset;
|
111
|
+
}
|
112
|
+
if (yOffset < w) {
|
113
|
+
lateral = anchorX < x + (w / 2) ? yOffset : w - yOffset;
|
114
|
+
}
|
115
|
+
|
116
|
+
// Anchor below label
|
117
|
+
if (anchorY > y + h) {
|
118
|
+
path.push('L', x + lateral, y + h);
|
176
119
|
|
177
|
-
|
178
|
-
|
120
|
+
// Anchor above label
|
121
|
+
} else if (anchorY < y) {
|
122
|
+
path.push('L', x + lateral, y);
|
179
123
|
|
180
|
-
|
124
|
+
// Anchor left of label
|
125
|
+
} else if (anchorX < x) {
|
126
|
+
path.push('L', x, y + h / 2);
|
127
|
+
|
128
|
+
// Anchor right of label
|
129
|
+
} else if (anchorX > x + w) {
|
130
|
+
path.push('L', x + w, y + h / 2);
|
131
|
+
}
|
132
|
+
}
|
133
|
+
return path || [];
|
134
|
+
};
|
135
|
+
|
136
|
+
/**
|
137
|
+
* Points to avoid. In addition to actual data points, the label should avoid
|
138
|
+
* interpolated positions.
|
139
|
+
*/
|
140
|
+
Series.prototype.getPointsOnGraph = function() {
|
141
|
+
var distance = 16,
|
142
|
+
points = this.points,
|
143
|
+
point,
|
144
|
+
last,
|
145
|
+
interpolated = [],
|
146
|
+
i,
|
147
|
+
deltaX,
|
148
|
+
deltaY,
|
149
|
+
delta,
|
150
|
+
len,
|
151
|
+
n,
|
152
|
+
j,
|
153
|
+
d,
|
154
|
+
graph = this.graph || this.area,
|
155
|
+
node = graph.element,
|
156
|
+
inverted = this.chart.inverted,
|
157
|
+
paneLeft = inverted ? this.yAxis.pos : this.xAxis.pos,
|
158
|
+
paneTop = inverted ? this.xAxis.pos : this.yAxis.pos;
|
159
|
+
|
160
|
+
// For splines, get the point at length (possible caveat: peaks are not correctly detected)
|
161
|
+
if (this.getPointSpline && node.getPointAtLength) {
|
162
|
+
// If it is animating towards a path definition, use that briefly, and reset
|
163
|
+
if (graph.toD) {
|
164
|
+
d = graph.attr('d');
|
165
|
+
graph.attr({
|
166
|
+
d: graph.toD
|
167
|
+
});
|
168
|
+
}
|
169
|
+
len = node.getTotalLength();
|
170
|
+
for (i = 0; i < len; i += distance) {
|
171
|
+
point = node.getPointAtLength(i);
|
172
|
+
interpolated.push({
|
173
|
+
chartX: paneLeft + point.x,
|
174
|
+
chartY: paneTop + point.y,
|
175
|
+
plotX: point.x,
|
176
|
+
plotY: point.y
|
177
|
+
});
|
178
|
+
}
|
179
|
+
if (d) {
|
180
|
+
graph.attr({
|
181
|
+
d: d
|
182
|
+
});
|
183
|
+
}
|
184
|
+
// Last point
|
185
|
+
point = points[points.length - 1];
|
181
186
|
point.chartX = paneLeft + point.plotX;
|
182
187
|
point.chartY = paneTop + point.plotY;
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
188
|
+
interpolated.push(point);
|
189
|
+
|
190
|
+
// Interpolate
|
191
|
+
} else {
|
192
|
+
len = points.length;
|
193
|
+
for (i = 0; i < len; i += 1) {
|
194
|
+
|
195
|
+
point = points[i];
|
196
|
+
last = points[i - 1];
|
197
|
+
|
198
|
+
// Absolute coordinates so we can compare different panes
|
199
|
+
point.chartX = paneLeft + point.plotX;
|
200
|
+
point.chartY = paneTop + point.plotY;
|
201
|
+
|
202
|
+
// Add interpolated points
|
203
|
+
if (i > 0) {
|
204
|
+
deltaX = Math.abs(point.chartX - last.chartX);
|
205
|
+
deltaY = Math.abs(point.chartY - last.chartY);
|
206
|
+
delta = Math.max(deltaX, deltaY);
|
207
|
+
if (delta > distance) {
|
208
|
+
|
209
|
+
n = Math.ceil(delta / distance);
|
210
|
+
|
211
|
+
for (j = 1; j < n; j += 1) {
|
212
|
+
interpolated.push({
|
213
|
+
chartX: last.chartX + (point.chartX - last.chartX) * (j / n),
|
214
|
+
chartY: last.chartY + (point.chartY - last.chartY) * (j / n),
|
215
|
+
plotX: last.plotX + (point.plotX - last.plotX) * (j / n),
|
216
|
+
plotY: last.plotY + (point.plotY - last.plotY) * (j / n)
|
217
|
+
});
|
218
|
+
}
|
200
219
|
}
|
201
220
|
}
|
202
|
-
}
|
203
221
|
|
204
|
-
|
205
|
-
|
206
|
-
|
222
|
+
// Add the real point in order to find positive and negative peaks
|
223
|
+
if (isNumber(point.plotY)) {
|
224
|
+
interpolated.push(point);
|
225
|
+
}
|
207
226
|
}
|
208
227
|
}
|
209
|
-
|
210
|
-
|
211
|
-
};
|
212
|
-
|
213
|
-
/**
|
214
|
-
* Check whether a proposed label position is clear of other elements
|
215
|
-
*/
|
216
|
-
Series.prototype.checkClearPoint = function (x, y, bBox, checkDistance) {
|
217
|
-
var distToOthersSquared = Number.MAX_VALUE, // distance to other graphs
|
218
|
-
distToPointSquared = Number.MAX_VALUE,
|
219
|
-
dist,
|
220
|
-
connectorPoint,
|
221
|
-
connectorEnabled = this.options.label.connectorAllowed,
|
222
|
-
|
223
|
-
chart = this.chart,
|
224
|
-
series,
|
225
|
-
points,
|
226
|
-
leastDistance = 16,
|
227
|
-
withinRange,
|
228
|
-
i,
|
229
|
-
j;
|
230
|
-
|
231
|
-
function intersectRect(r1, r2) {
|
232
|
-
return !(r2.left > r1.right ||
|
233
|
-
r2.right < r1.left ||
|
234
|
-
r2.top > r1.bottom ||
|
235
|
-
r2.bottom < r1.top);
|
236
|
-
}
|
228
|
+
return interpolated;
|
229
|
+
};
|
237
230
|
|
238
231
|
/**
|
239
|
-
*
|
240
|
-
* other series gives more weight. Smaller distance to the actual point (connector points only)
|
241
|
-
* gives more weight.
|
232
|
+
* Check whether a proposed label position is clear of other elements
|
242
233
|
*/
|
243
|
-
function
|
244
|
-
|
245
|
-
|
234
|
+
Series.prototype.checkClearPoint = function(x, y, bBox, checkDistance) {
|
235
|
+
var distToOthersSquared = Number.MAX_VALUE, // distance to other graphs
|
236
|
+
distToPointSquared = Number.MAX_VALUE,
|
237
|
+
dist,
|
238
|
+
connectorPoint,
|
239
|
+
connectorEnabled = this.options.label.connectorAllowed,
|
240
|
+
|
241
|
+
chart = this.chart,
|
242
|
+
series,
|
243
|
+
points,
|
244
|
+
leastDistance = 16,
|
245
|
+
withinRange,
|
246
|
+
i,
|
247
|
+
j;
|
248
|
+
|
249
|
+
function intersectRect(r1, r2) {
|
250
|
+
return !(r2.left > r1.right ||
|
251
|
+
r2.right < r1.left ||
|
252
|
+
r2.top > r1.bottom ||
|
253
|
+
r2.bottom < r1.top);
|
254
|
+
}
|
246
255
|
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
})) {
|
255
|
-
return false;
|
256
|
+
/**
|
257
|
+
* Get the weight in order to determine the ideal position. Larger distance to
|
258
|
+
* other series gives more weight. Smaller distance to the actual point (connector points only)
|
259
|
+
* gives more weight.
|
260
|
+
*/
|
261
|
+
function getWeight(distToOthersSquared, distToPointSquared) {
|
262
|
+
return distToOthersSquared - distToPointSquared;
|
256
263
|
}
|
257
|
-
}
|
258
264
|
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
bBox.width,
|
271
|
-
bBox.height,
|
272
|
-
points[j - 1].chartX,
|
273
|
-
points[j - 1].chartY,
|
274
|
-
points[j].chartX,
|
275
|
-
points[j].chartY
|
276
|
-
)) {
|
277
|
-
return false;
|
278
|
-
}
|
265
|
+
// First check for collision with existing labels
|
266
|
+
for (i = 0; i < chart.boxesToAvoid.length; i += 1) {
|
267
|
+
if (intersectRect(chart.boxesToAvoid[i], {
|
268
|
+
left: x,
|
269
|
+
right: x + bBox.width,
|
270
|
+
top: y,
|
271
|
+
bottom: y + bBox.height
|
272
|
+
})) {
|
273
|
+
return false;
|
274
|
+
}
|
275
|
+
}
|
279
276
|
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
277
|
+
// For each position, check if the lines around the label intersect with any of the
|
278
|
+
// graphs
|
279
|
+
for (i = 0; i < chart.series.length; i += 1) {
|
280
|
+
series = chart.series[i];
|
281
|
+
points = series.interpolatedPoints;
|
282
|
+
if (series.visible && points) {
|
283
|
+
for (j = 1; j < points.length; j += 1) {
|
284
|
+
// If any of the box sides intersect with the line, return
|
285
|
+
if (boxIntersectLine(
|
286
|
+
x,
|
287
|
+
y,
|
288
|
+
bBox.width,
|
289
|
+
bBox.height,
|
290
|
+
points[j - 1].chartX,
|
291
|
+
points[j - 1].chartY,
|
292
|
+
points[j].chartX,
|
293
|
+
points[j].chartY
|
294
|
+
)) {
|
295
|
+
return false;
|
296
|
+
}
|
297
|
+
|
298
|
+
// But if it is too far away (a padded box doesn't intersect), also return
|
299
|
+
if (this === series && !withinRange && checkDistance) {
|
300
|
+
withinRange = boxIntersectLine(
|
301
|
+
x - leastDistance,
|
302
|
+
y - leastDistance,
|
303
|
+
bBox.width + 2 * leastDistance,
|
304
|
+
bBox.height + 2 * leastDistance,
|
305
|
+
points[j - 1].chartX,
|
306
|
+
points[j - 1].chartY,
|
307
|
+
points[j].chartX,
|
308
|
+
points[j].chartY
|
309
|
+
);
|
310
|
+
}
|
293
311
|
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
312
|
+
// Find the squared distance from the center of the label
|
313
|
+
if (this !== series) {
|
314
|
+
distToOthersSquared = Math.min(
|
315
|
+
distToOthersSquared,
|
316
|
+
Math.pow(x + bBox.width / 2 - points[j].chartX, 2) + Math.pow(y + bBox.height / 2 - points[j].chartY, 2),
|
317
|
+
Math.pow(x - points[j].chartX, 2) + Math.pow(y - points[j].chartY, 2),
|
318
|
+
Math.pow(x + bBox.width - points[j].chartX, 2) + Math.pow(y - points[j].chartY, 2),
|
319
|
+
Math.pow(x + bBox.width - points[j].chartX, 2) + Math.pow(y + bBox.height - points[j].chartY, 2),
|
320
|
+
Math.pow(x - points[j].chartX, 2) + Math.pow(y + bBox.height - points[j].chartY, 2)
|
321
|
+
);
|
322
|
+
}
|
304
323
|
}
|
305
|
-
}
|
306
324
|
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
325
|
+
// Do we need a connector?
|
326
|
+
if (connectorEnabled && this === series && ((checkDistance && !withinRange) ||
|
327
|
+
distToOthersSquared < Math.pow(this.options.label.connectorNeighbourDistance, 2))) {
|
328
|
+
for (j = 1; j < points.length; j += 1) {
|
329
|
+
dist = Math.min(
|
330
|
+
Math.pow(x + bBox.width / 2 - points[j].chartX, 2) + Math.pow(y + bBox.height / 2 - points[j].chartY, 2),
|
331
|
+
Math.pow(x - points[j].chartX, 2) + Math.pow(y - points[j].chartY, 2),
|
332
|
+
Math.pow(x + bBox.width - points[j].chartX, 2) + Math.pow(y - points[j].chartY, 2),
|
333
|
+
Math.pow(x + bBox.width - points[j].chartX, 2) + Math.pow(y + bBox.height - points[j].chartY, 2),
|
334
|
+
Math.pow(x - points[j].chartX, 2) + Math.pow(y + bBox.height - points[j].chartY, 2)
|
335
|
+
);
|
336
|
+
if (dist < distToPointSquared) {
|
337
|
+
distToPointSquared = dist;
|
338
|
+
connectorPoint = points[j];
|
339
|
+
}
|
321
340
|
}
|
341
|
+
withinRange = true;
|
322
342
|
}
|
323
|
-
withinRange = true;
|
324
343
|
}
|
325
344
|
}
|
326
|
-
}
|
327
|
-
|
328
|
-
return !checkDistance || withinRange ? {
|
329
|
-
x: x,
|
330
|
-
y: y,
|
331
|
-
weight: getWeight(distToOthersSquared, connectorPoint ? distToPointSquared : 0),
|
332
|
-
connectorPoint: connectorPoint
|
333
|
-
} : false;
|
334
345
|
|
335
|
-
|
346
|
+
return !checkDistance || withinRange ? {
|
347
|
+
x: x,
|
348
|
+
y: y,
|
349
|
+
weight: getWeight(distToOthersSquared, connectorPoint ? distToPointSquared : 0),
|
350
|
+
connectorPoint: connectorPoint
|
351
|
+
} : false;
|
336
352
|
|
353
|
+
};
|
337
354
|
|
338
|
-
/**
|
339
|
-
* The main initiator method that runs on chart level after initiation and redraw. It runs in
|
340
|
-
* a timeout to prevent locking, and loops over all series, taking all series and labels into
|
341
|
-
* account when placing the labels.
|
342
|
-
*/
|
343
|
-
function drawLabels(proceed) {
|
344
355
|
|
345
|
-
|
346
|
-
|
347
|
-
|
356
|
+
/**
|
357
|
+
* The main initiator method that runs on chart level after initiation and redraw. It runs in
|
358
|
+
* a timeout to prevent locking, and loops over all series, taking all series and labels into
|
359
|
+
* account when placing the labels.
|
360
|
+
*/
|
361
|
+
function drawLabels(proceed) {
|
348
362
|
|
349
|
-
|
363
|
+
var chart = this;
|
350
364
|
|
351
|
-
|
365
|
+
proceed.call(chart);
|
352
366
|
|
353
|
-
chart.
|
367
|
+
clearTimeout(chart.seriesLabelTimer);
|
354
368
|
|
355
|
-
|
356
|
-
each(chart.series, function (series) {
|
357
|
-
var options = series.options.label;
|
358
|
-
if (options.enabled && series.visible && (series.graph || series.area)) {
|
359
|
-
series.interpolatedPoints = series.getPointsOnGraph();
|
369
|
+
chart.seriesLabelTimer = setTimeout(function() {
|
360
370
|
|
361
|
-
|
362
|
-
chart.boxesToAvoid.push(box);
|
363
|
-
});
|
364
|
-
}
|
365
|
-
});
|
366
|
-
|
367
|
-
each(chart.series, function (series) {
|
368
|
-
var bBox,
|
369
|
-
x,
|
370
|
-
y,
|
371
|
-
results = [],
|
372
|
-
clearPoint,
|
373
|
-
i,
|
374
|
-
best,
|
375
|
-
inverted = chart.inverted,
|
376
|
-
paneLeft = inverted ? series.yAxis.pos : series.xAxis.pos,
|
377
|
-
paneTop = inverted ? series.xAxis.pos : series.yAxis.pos,
|
378
|
-
paneWidth = chart.inverted ? series.yAxis.len : series.xAxis.len,
|
379
|
-
paneHeight = chart.inverted ? series.xAxis.len : series.yAxis.len,
|
380
|
-
points = series.interpolatedPoints;
|
381
|
-
|
382
|
-
function insidePane(x, y, bBox) {
|
383
|
-
return x > paneLeft && x <= paneLeft + paneWidth - bBox.width &&
|
384
|
-
y >= paneTop && y <= paneTop + paneHeight - bBox.height;
|
385
|
-
}
|
371
|
+
chart.boxesToAvoid = [];
|
386
372
|
|
387
|
-
|
373
|
+
// Build the interpolated points
|
374
|
+
each(chart.series, function(series) {
|
375
|
+
var options = series.options.label;
|
376
|
+
if (options.enabled && series.visible && (series.graph || series.area)) {
|
377
|
+
series.interpolatedPoints = series.getPointsOnGraph();
|
388
378
|
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
color: series.color
|
393
|
-
}, series.options.label.styles))
|
394
|
-
.attr({
|
395
|
-
padding: 0,
|
396
|
-
opacity: 0,
|
397
|
-
stroke: series.color,
|
398
|
-
'stroke-width': 1
|
399
|
-
})
|
400
|
-
.add(series.group)
|
401
|
-
.animate({ opacity: 1 }, { duration: 200 });
|
379
|
+
each(options.boxesToAvoid || [], function(box) {
|
380
|
+
chart.boxesToAvoid.push(box);
|
381
|
+
});
|
402
382
|
}
|
383
|
+
});
|
403
384
|
|
404
|
-
|
405
|
-
|
385
|
+
each(chart.series, function(series) {
|
386
|
+
var bBox,
|
387
|
+
x,
|
388
|
+
y,
|
389
|
+
results = [],
|
390
|
+
clearPoint,
|
391
|
+
i,
|
392
|
+
best,
|
393
|
+
inverted = chart.inverted,
|
394
|
+
paneLeft = inverted ? series.yAxis.pos : series.xAxis.pos,
|
395
|
+
paneTop = inverted ? series.xAxis.pos : series.yAxis.pos,
|
396
|
+
paneWidth = chart.inverted ? series.yAxis.len : series.xAxis.len,
|
397
|
+
paneHeight = chart.inverted ? series.xAxis.len : series.yAxis.len,
|
398
|
+
points = series.interpolatedPoints;
|
399
|
+
|
400
|
+
function insidePane(x, y, bBox) {
|
401
|
+
return x > paneLeft && x <= paneLeft + paneWidth - bBox.width &&
|
402
|
+
y >= paneTop && y <= paneTop + paneHeight - bBox.height;
|
403
|
+
}
|
406
404
|
|
407
|
-
|
408
|
-
for (i = points.length - 1; i > 0; i -= 1) {
|
405
|
+
if (series.visible && points) {
|
409
406
|
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
407
|
+
if (!series.labelBySeries) {
|
408
|
+
series.labelBySeries = chart.renderer.label(series.name, 0, -9999, 'connector')
|
409
|
+
.css(extend({
|
410
|
+
color: series.color
|
411
|
+
}, series.options.label.styles))
|
412
|
+
.attr({
|
413
|
+
padding: 0,
|
414
|
+
opacity: 0,
|
415
|
+
stroke: series.color,
|
416
|
+
'stroke-width': 1
|
417
|
+
})
|
418
|
+
.add(series.group)
|
419
|
+
.animate({
|
420
|
+
opacity: 1
|
421
|
+
}, {
|
422
|
+
duration: 200
|
423
|
+
});
|
422
424
|
}
|
423
425
|
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
426
|
+
bBox = series.labelBySeries.getBBox();
|
427
|
+
bBox.width = Math.round(bBox.width);
|
428
|
+
|
429
|
+
// Ideal positions are centered above or below a point on right side of chart
|
430
|
+
for (i = points.length - 1; i > 0; i -= 1) {
|
431
|
+
|
432
|
+
// Right - up
|
433
|
+
x = points[i].chartX + labelDistance;
|
434
|
+
y = points[i].chartY - bBox.height - labelDistance;
|
435
|
+
if (insidePane(x, y, bBox)) {
|
436
|
+
best = series.checkClearPoint(
|
437
|
+
x,
|
438
|
+
y,
|
439
|
+
bBox
|
440
|
+
);
|
441
|
+
}
|
442
|
+
if (best) {
|
443
|
+
results.push(best);
|
444
|
+
}
|
437
445
|
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
446
|
+
// Right - down
|
447
|
+
x = points[i].chartX + labelDistance;
|
448
|
+
y = points[i].chartY + labelDistance;
|
449
|
+
if (insidePane(x, y, bBox)) {
|
450
|
+
best = series.checkClearPoint(
|
451
|
+
x,
|
452
|
+
y,
|
453
|
+
bBox
|
454
|
+
);
|
455
|
+
}
|
456
|
+
if (best) {
|
457
|
+
results.push(best);
|
458
|
+
}
|
451
459
|
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
460
|
+
// Left - down
|
461
|
+
x = points[i].chartX - bBox.width - labelDistance;
|
462
|
+
y = points[i].chartY + labelDistance;
|
463
|
+
if (insidePane(x, y, bBox)) {
|
464
|
+
best = series.checkClearPoint(
|
465
|
+
x,
|
466
|
+
y,
|
467
|
+
bBox
|
468
|
+
);
|
469
|
+
}
|
470
|
+
if (best) {
|
471
|
+
results.push(best);
|
472
|
+
}
|
465
473
|
|
466
|
-
|
474
|
+
// Left - up
|
475
|
+
x = points[i].chartX - bBox.width - labelDistance;
|
476
|
+
y = points[i].chartY - bBox.height - labelDistance;
|
477
|
+
if (insidePane(x, y, bBox)) {
|
478
|
+
best = series.checkClearPoint(
|
479
|
+
x,
|
480
|
+
y,
|
481
|
+
bBox
|
482
|
+
);
|
483
|
+
}
|
484
|
+
if (best) {
|
485
|
+
results.push(best);
|
486
|
+
}
|
487
|
+
|
488
|
+
}
|
467
489
|
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
490
|
+
// Brute force, try all positions on the chart in a 16x16 grid
|
491
|
+
if (!results.length) {
|
492
|
+
for (x = paneLeft + paneWidth - bBox.width; x >= paneLeft; x -= 16) {
|
493
|
+
for (y = paneTop; y < paneTop + paneHeight - bBox.height; y += 16) {
|
494
|
+
clearPoint = series.checkClearPoint(x, y, bBox, true);
|
495
|
+
if (clearPoint) {
|
496
|
+
results.push(clearPoint);
|
497
|
+
}
|
475
498
|
}
|
476
499
|
}
|
477
500
|
}
|
478
|
-
}
|
479
501
|
|
480
|
-
|
502
|
+
if (results.length) {
|
481
503
|
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
best = results[0];
|
487
|
-
|
488
|
-
chart.boxesToAvoid.push({
|
489
|
-
left: best.x,
|
490
|
-
right: best.x + bBox.width,
|
491
|
-
top: best.y,
|
492
|
-
bottom: best.y + bBox.height
|
493
|
-
});
|
504
|
+
results.sort(function(a, b) {
|
505
|
+
return b.weight - a.weight;
|
506
|
+
});
|
494
507
|
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
.
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
508
|
+
best = results[0];
|
509
|
+
|
510
|
+
chart.boxesToAvoid.push({
|
511
|
+
left: best.x,
|
512
|
+
right: best.x + bBox.width,
|
513
|
+
top: best.y,
|
514
|
+
bottom: best.y + bBox.height
|
515
|
+
});
|
516
|
+
|
517
|
+
// Move it if needed
|
518
|
+
if (Math.round(best.x) !== Math.round(series.labelBySeries.x) || Math.round(best.y) !== Math.round(series.labelBySeries.y)) {
|
519
|
+
series.labelBySeries
|
520
|
+
.attr({
|
521
|
+
x: best.x - paneLeft,
|
522
|
+
y: best.y - paneTop,
|
523
|
+
anchorX: best.connectorPoint && best.connectorPoint.plotX,
|
524
|
+
anchorY: best.connectorPoint && best.connectorPoint.plotY,
|
525
|
+
opacity: 0
|
526
|
+
})
|
527
|
+
.animate({
|
528
|
+
opacity: 1
|
529
|
+
});
|
530
|
+
}
|
507
531
|
|
508
|
-
|
509
|
-
|
532
|
+
} else if (series.labelBySeries) {
|
533
|
+
series.labelBySeries = series.labelBySeries.destroy();
|
534
|
+
}
|
510
535
|
}
|
511
|
-
}
|
512
|
-
});
|
513
|
-
}, 350);
|
536
|
+
});
|
537
|
+
}, 350);
|
514
538
|
|
515
|
-
|
516
|
-
|
517
|
-
|
539
|
+
}
|
540
|
+
wrap(Chart.prototype, 'render', drawLabels);
|
541
|
+
wrap(Chart.prototype, 'redraw', drawLabels);
|
518
542
|
|
543
|
+
}(Highcharts));
|
519
544
|
}));
|