highcharts-rails 4.1.8 → 4.1.9
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 +51 -0
- data/app/assets/javascripts/highcharts.js +340 -178
- data/app/assets/javascripts/highcharts/adapters/standalone-framework.js +15 -8
- data/app/assets/javascripts/highcharts/highcharts-3d.js +78 -35
- data/app/assets/javascripts/highcharts/highcharts-more.js +43 -19
- data/app/assets/javascripts/highcharts/modules/boost.js +4 -4
- data/app/assets/javascripts/highcharts/modules/broken-axis.js +1 -5
- data/app/assets/javascripts/highcharts/modules/canvas-tools.js +1 -1
- data/app/assets/javascripts/highcharts/modules/data.js +1 -1
- data/app/assets/javascripts/highcharts/modules/exporting.js +4 -3
- data/app/assets/javascripts/highcharts/modules/heatmap.js +18 -12
- data/app/assets/javascripts/highcharts/modules/no-data-to-display.js +1 -1
- data/app/assets/javascripts/highcharts/modules/offline-exporting.js +3 -2
- data/app/assets/javascripts/highcharts/modules/solid-gauge.js +1 -1
- data/app/assets/javascripts/highcharts/modules/treemap.js +227 -236
- data/lib/highcharts/version.rb +1 -1
- metadata +2 -2
@@ -1,5 +1,5 @@
|
|
1
1
|
/**
|
2
|
-
* @license Highcharts JS v4.1.
|
2
|
+
* @license Highcharts JS v4.1.9 (2015-10-07)
|
3
3
|
*
|
4
4
|
* Standalone Highcharts Framework
|
5
5
|
*
|
@@ -14,7 +14,6 @@ var UNDEFINED,
|
|
14
14
|
doc = document,
|
15
15
|
emptyArray = [],
|
16
16
|
timers = [],
|
17
|
-
timerId,
|
18
17
|
animSetters = {},
|
19
18
|
Fx;
|
20
19
|
|
@@ -337,7 +336,7 @@ return {
|
|
337
336
|
t.elem = this.elem;
|
338
337
|
|
339
338
|
if (t() && timers.push(t) === 1) {
|
340
|
-
timerId = setInterval(function () {
|
339
|
+
t.timerId = setInterval(function () {
|
341
340
|
|
342
341
|
for (i = 0; i < timers.length; i++) {
|
343
342
|
if (!timers[i]()) {
|
@@ -346,7 +345,7 @@ return {
|
|
346
345
|
}
|
347
346
|
|
348
347
|
if (!timers.length) {
|
349
|
-
clearInterval(timerId);
|
348
|
+
clearInterval(t.timerId);
|
350
349
|
}
|
351
350
|
}, 13);
|
352
351
|
}
|
@@ -360,7 +359,7 @@ return {
|
|
360
359
|
elem = this.elem,
|
361
360
|
i;
|
362
361
|
|
363
|
-
if (elem.
|
362
|
+
if (elem.attr && !elem.element) { // #2616, element including flag is destroyed
|
364
363
|
ret = false;
|
365
364
|
|
366
365
|
} else if (gotoEnd || t >= options.duration + this.startTime) {
|
@@ -408,8 +407,6 @@ return {
|
|
408
407
|
name,
|
409
408
|
PX = 'px';
|
410
409
|
|
411
|
-
el.stopAnimation = false; // ready for new
|
412
|
-
|
413
410
|
if (typeof opt !== 'object' || opt === null) {
|
414
411
|
args = arguments;
|
415
412
|
opt = {
|
@@ -592,7 +589,17 @@ return {
|
|
592
589
|
* Stop running animation
|
593
590
|
*/
|
594
591
|
stop: function (el) {
|
595
|
-
|
592
|
+
|
593
|
+
var i = timers.length,
|
594
|
+
timer;
|
595
|
+
|
596
|
+
// Remove timers related to this element (#4519)
|
597
|
+
while (i--) {
|
598
|
+
timer = timers[i];
|
599
|
+
if (timer.elem === el) {
|
600
|
+
timers.splice(i, 1);
|
601
|
+
}
|
602
|
+
}
|
596
603
|
},
|
597
604
|
|
598
605
|
/**
|
@@ -2,7 +2,7 @@
|
|
2
2
|
// @compilation_level SIMPLE_OPTIMIZATIONS
|
3
3
|
|
4
4
|
/**
|
5
|
-
* @license Highcharts JS v4.1.
|
5
|
+
* @license Highcharts JS v4.1.9 (2015-10-07)
|
6
6
|
*
|
7
7
|
* (c) 2009-2013 Torstein Hønsi
|
8
8
|
*
|
@@ -501,6 +501,23 @@ Highcharts.SVGRenderer.prototype.arc3dPath = function (shapeArgs) {
|
|
501
501
|
|
502
502
|
var out = ['M', cx + (rx * cos(start2)), cy + (ry * sin(start2))];
|
503
503
|
out = out.concat(curveTo(cx, cy, rx, ry, start2, end2, 0, 0));
|
504
|
+
|
505
|
+
// When slice goes over middle, need to add both, left and right outer side:
|
506
|
+
if (end > PI - a && start < PI - a) {
|
507
|
+
// Go to outer side
|
508
|
+
out = out.concat([
|
509
|
+
'L', cx + (rx * cos(end2)) + dx, cy + (ry * sin(end2)) + dy
|
510
|
+
]);
|
511
|
+
// Curve to the true end of the slice
|
512
|
+
out = out.concat(curveTo(cx, cy, rx, ry, end2, end, dx, dy));
|
513
|
+
// Go to the inner side
|
514
|
+
out = out.concat([
|
515
|
+
'L', cx + (rx * cos(end)), cy + (ry * sin(end))
|
516
|
+
]);
|
517
|
+
// Go back to the artifical end2
|
518
|
+
out = out.concat(curveTo(cx, cy, rx, ry, end, end2, 0, 0));
|
519
|
+
}
|
520
|
+
|
504
521
|
out = out.concat([
|
505
522
|
'L', cx + (rx * cos(end2)) + dx, cy + (ry * sin(end2)) + dy
|
506
523
|
]);
|
@@ -531,22 +548,42 @@ Highcharts.SVGRenderer.prototype.arc3dPath = function (shapeArgs) {
|
|
531
548
|
'L', cx + (irx * ce), cy + (iry * se),
|
532
549
|
'Z'
|
533
550
|
];
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
551
|
+
|
552
|
+
// correction for changed position of vanishing point caused by alpha and beta rotations
|
553
|
+
var angleCorr = Math.atan2(dy, -dx),
|
554
|
+
angleEnd = Math.abs(end + angleCorr),
|
555
|
+
angleStart = Math.abs(start + angleCorr),
|
556
|
+
angleMid = Math.abs((start + end) / 2 + angleCorr);
|
557
|
+
|
558
|
+
// set to 0-PI range
|
559
|
+
function toZeroPIRange(angle) {
|
560
|
+
angle = angle % (2 * PI);
|
561
|
+
if (angle > PI) {
|
562
|
+
angle = 2 * PI - angle;
|
563
|
+
}
|
564
|
+
return angle;
|
565
|
+
}
|
566
|
+
angleEnd = toZeroPIRange(angleEnd);
|
567
|
+
angleStart = toZeroPIRange(angleStart);
|
568
|
+
angleMid = toZeroPIRange(angleMid);
|
569
|
+
|
570
|
+
// *1e5 is to compensate pInt in zIndexSetter
|
571
|
+
var incPrecision = 1e5,
|
572
|
+
a1 = angleMid * incPrecision,
|
573
|
+
a2 = angleStart * incPrecision,
|
574
|
+
a3 = angleEnd * incPrecision;
|
575
|
+
|
539
576
|
return {
|
540
577
|
top: top,
|
541
|
-
zTop:
|
578
|
+
zTop: PI * incPrecision + 1, // max angle is PI, so this is allways higher
|
542
579
|
out: out,
|
543
|
-
zOut: Math.max(a1, a2, a3)
|
580
|
+
zOut: Math.max(a1, a2, a3),
|
544
581
|
inn: inn,
|
545
|
-
zInn: Math.max(a1, a2, a3)
|
582
|
+
zInn: Math.max(a1, a2, a3),
|
546
583
|
side1: side1,
|
547
|
-
zSide1:
|
584
|
+
zSide1: a3 * 0.99, // to keep below zOut and zInn in case of same values
|
548
585
|
side2: side2,
|
549
|
-
zSide2:
|
586
|
+
zSide2: a2 * 0.99
|
550
587
|
};
|
551
588
|
};
|
552
589
|
/***
|
@@ -585,6 +622,10 @@ Highcharts.wrap(Highcharts.Chart.prototype, 'init', function (proceed) {
|
|
585
622
|
pieOptions;
|
586
623
|
|
587
624
|
if (args[0].chart.options3d && args[0].chart.options3d.enabled) {
|
625
|
+
// Normalize alpha and beta to (-360, 360) range
|
626
|
+
args[0].chart.options3d.alpha = (args[0].chart.options3d.alpha || 0) % 360;
|
627
|
+
args[0].chart.options3d.beta = (args[0].chart.options3d.beta || 0) % 360;
|
628
|
+
|
588
629
|
plotOptions = args[0].plotOptions || {};
|
589
630
|
pieOptions = plotOptions.pie || {};
|
590
631
|
|
@@ -642,13 +683,13 @@ Highcharts.Chart.prototype.retrieveStacks = function (stacking) {
|
|
642
683
|
stackNumber,
|
643
684
|
i = 1;
|
644
685
|
|
645
|
-
Highcharts.each(this.series, function (
|
646
|
-
stackNumber =
|
686
|
+
Highcharts.each(this.series, function (s) {
|
687
|
+
stackNumber = pick(s.options.stack, (stacking ? 0 : series.length - 1 - s.index)); // #3841, #4532
|
647
688
|
if (!stacks[stackNumber]) {
|
648
|
-
stacks[stackNumber] = { series: [
|
689
|
+
stacks[stackNumber] = { series: [s], position: i};
|
649
690
|
i++;
|
650
691
|
} else {
|
651
|
-
stacks[stackNumber].series.push(
|
692
|
+
stacks[stackNumber].series.push(s);
|
652
693
|
}
|
653
694
|
});
|
654
695
|
|
@@ -1424,38 +1465,40 @@ Highcharts.wrap(Highcharts.seriesTypes.scatter.prototype, 'translate', function
|
|
1424
1465
|
|
1425
1466
|
var series = this,
|
1426
1467
|
chart = series.chart,
|
1427
|
-
zAxis = Highcharts.pick(series.zAxis, chart.options.zAxis[0])
|
1428
|
-
|
1429
|
-
|
1430
|
-
|
1431
|
-
|
1432
|
-
|
1468
|
+
zAxis = Highcharts.pick(series.zAxis, chart.options.zAxis[0]),
|
1469
|
+
rawPoints = [],
|
1470
|
+
rawPoint,
|
1471
|
+
projectedPoints,
|
1472
|
+
projectedPoint,
|
1473
|
+
zValue,
|
1433
1474
|
i;
|
1434
1475
|
|
1435
1476
|
for (i = 0; i < series.data.length; i++) {
|
1436
|
-
|
1477
|
+
rawPoint = series.data[i];
|
1478
|
+
zValue = zAxis.isLog && zAxis.val2lin ? zAxis.val2lin(rawPoint.z) : rawPoint.z; // #4562
|
1479
|
+
rawPoint.plotZ = zAxis.translate(zValue);
|
1437
1480
|
|
1438
|
-
|
1481
|
+
rawPoint.isInside = rawPoint.isInside ? (zValue >= zAxis.min && zValue <= zAxis.max) : false;
|
1439
1482
|
|
1440
|
-
|
1441
|
-
x:
|
1442
|
-
y:
|
1443
|
-
z:
|
1483
|
+
rawPoints.push({
|
1484
|
+
x: rawPoint.plotX,
|
1485
|
+
y: rawPoint.plotY,
|
1486
|
+
z: rawPoint.plotZ
|
1444
1487
|
});
|
1445
1488
|
}
|
1446
1489
|
|
1447
|
-
|
1490
|
+
projectedPoints = perspective(rawPoints, chart, true);
|
1448
1491
|
|
1449
1492
|
for (i = 0; i < series.data.length; i++) {
|
1450
|
-
|
1451
|
-
|
1493
|
+
rawPoint = series.data[i];
|
1494
|
+
projectedPoint = projectedPoints[i];
|
1452
1495
|
|
1453
|
-
|
1454
|
-
|
1496
|
+
rawPoint.plotXold = rawPoint.plotX;
|
1497
|
+
rawPoint.plotYold = rawPoint.plotY;
|
1455
1498
|
|
1456
|
-
|
1457
|
-
|
1458
|
-
|
1499
|
+
rawPoint.plotX = projectedPoint.x;
|
1500
|
+
rawPoint.plotY = projectedPoint.y;
|
1501
|
+
rawPoint.plotZ = projectedPoint.z;
|
1459
1502
|
|
1460
1503
|
|
1461
1504
|
}
|
@@ -2,7 +2,7 @@
|
|
2
2
|
// @compilation_level SIMPLE_OPTIMIZATIONS
|
3
3
|
|
4
4
|
/**
|
5
|
-
* @license Highcharts JS v4.1.
|
5
|
+
* @license Highcharts JS v4.1.9 (2015-10-07)
|
6
6
|
*
|
7
7
|
* (c) 2009-2014 Torstein Honsi
|
8
8
|
*
|
@@ -78,7 +78,9 @@ extend(Pane.prototype, {
|
|
78
78
|
config.color = config.backgroundColor; // due to naming in plotBands
|
79
79
|
firstAxis.options.plotBands.unshift(config);
|
80
80
|
axisUserOptions.plotBands = axisUserOptions.plotBands || []; // #3176
|
81
|
-
axisUserOptions.plotBands.
|
81
|
+
if (axisUserOptions.plotBands !== firstAxis.options.plotBands) {
|
82
|
+
axisUserOptions.plotBands.unshift(config);
|
83
|
+
}
|
82
84
|
});
|
83
85
|
}
|
84
86
|
},
|
@@ -829,6 +831,7 @@ seriesTypes.arearange = extendClass(seriesTypes.area, {
|
|
829
831
|
seriesProto = Series.prototype,
|
830
832
|
dataLabelOptions = this.options.dataLabels,
|
831
833
|
align = dataLabelOptions.align,
|
834
|
+
inside = dataLabelOptions.inside,
|
832
835
|
point,
|
833
836
|
up,
|
834
837
|
inverted = this.chart.inverted;
|
@@ -840,7 +843,7 @@ seriesTypes.arearange = extendClass(seriesTypes.area, {
|
|
840
843
|
while (i--) {
|
841
844
|
point = data[i];
|
842
845
|
if (point) {
|
843
|
-
up = point.plotHigh > point.plotLow;
|
846
|
+
up = inside ? point.plotHigh < point.plotLow : point.plotHigh > point.plotLow;
|
844
847
|
|
845
848
|
// Set preliminary values
|
846
849
|
point.y = point.high;
|
@@ -874,7 +877,7 @@ seriesTypes.arearange = extendClass(seriesTypes.area, {
|
|
874
877
|
while (i--) {
|
875
878
|
point = data[i];
|
876
879
|
if (point) {
|
877
|
-
up = point.plotHigh > point.plotLow;
|
880
|
+
up = inside ? point.plotHigh < point.plotLow : point.plotHigh > point.plotLow;
|
878
881
|
|
879
882
|
// Move the generated labels from step 1, and reassign the original data labels
|
880
883
|
point.dataLabelUpper = point.dataLabel;
|
@@ -990,6 +993,7 @@ seriesTypes.areasplinerange = extendClass(seriesTypes.arearange, {
|
|
990
993
|
directTouch: true,
|
991
994
|
trackerGroups: ['group', 'dataLabelsGroup'],
|
992
995
|
drawGraph: noop,
|
996
|
+
crispCol: colProto.crispCol,
|
993
997
|
pointAttrToOptions: colProto.pointAttrToOptions,
|
994
998
|
drawPoints: colProto.drawPoints,
|
995
999
|
drawTracker: colProto.drawTracker,
|
@@ -1347,7 +1351,8 @@ seriesTypes.boxplot = extendClass(seriesTypes.column, {
|
|
1347
1351
|
shapeArgs,
|
1348
1352
|
color,
|
1349
1353
|
doQuartiles = series.doQuartiles !== false, // error bar inherits this series type but doesn't do quartiles
|
1350
|
-
|
1354
|
+
pointWiskerLength,
|
1355
|
+
whiskerLength = series.options.whiskerLength;
|
1351
1356
|
|
1352
1357
|
|
1353
1358
|
each(points, function (point) {
|
@@ -1432,21 +1437,22 @@ seriesTypes.boxplot = extendClass(seriesTypes.column, {
|
|
1432
1437
|
crispCorr = (whiskersAttr['stroke-width'] % 2) / 2;
|
1433
1438
|
highPlot = highPlot + crispCorr;
|
1434
1439
|
lowPlot = lowPlot + crispCorr;
|
1440
|
+
pointWiskerLength = (/%$/).test(whiskerLength) ? halfWidth * parseFloat(whiskerLength) / 100 : whiskerLength / 2;
|
1435
1441
|
whiskersPath = [
|
1436
1442
|
// High whisker
|
1437
1443
|
'M',
|
1438
|
-
crispX -
|
1444
|
+
crispX - pointWiskerLength,
|
1439
1445
|
highPlot,
|
1440
1446
|
'L',
|
1441
|
-
crispX +
|
1447
|
+
crispX + pointWiskerLength,
|
1442
1448
|
highPlot,
|
1443
1449
|
|
1444
1450
|
// Low whisker
|
1445
1451
|
'M',
|
1446
|
-
crispX -
|
1452
|
+
crispX - pointWiskerLength,
|
1447
1453
|
lowPlot,
|
1448
1454
|
'L',
|
1449
|
-
crispX +
|
1455
|
+
crispX + pointWiskerLength,
|
1450
1456
|
lowPlot
|
1451
1457
|
];
|
1452
1458
|
}
|
@@ -1853,6 +1859,7 @@ defaultPlotOptions.bubble = merge(defaultPlotOptions.scatter, {
|
|
1853
1859
|
maxSize: '20%',
|
1854
1860
|
// negativeColor: null,
|
1855
1861
|
// sizeBy: 'area'
|
1862
|
+
softThreshold: false,
|
1856
1863
|
states: {
|
1857
1864
|
hover: {
|
1858
1865
|
halo: {
|
@@ -1932,24 +1939,41 @@ seriesTypes.bubble = extendClass(seriesTypes.scatter, {
|
|
1932
1939
|
pos,
|
1933
1940
|
zData = this.zData,
|
1934
1941
|
radii = [],
|
1935
|
-
|
1936
|
-
|
1942
|
+
options = this.options,
|
1943
|
+
sizeByArea = options.sizeBy !== 'width',
|
1944
|
+
zThreshold = options.zThreshold,
|
1945
|
+
zRange = zMax - zMin,
|
1946
|
+
value,
|
1947
|
+
radius;
|
1937
1948
|
|
1938
1949
|
// Set the shape type and arguments to be picked up in drawPoints
|
1939
1950
|
for (i = 0, len = zData.length; i < len; i++) {
|
1951
|
+
|
1952
|
+
value = zData[i];
|
1953
|
+
|
1954
|
+
// When sizing by threshold, the absolute value of z determines the size
|
1955
|
+
// of the bubble.
|
1956
|
+
if (options.sizeByAbsoluteValue) {
|
1957
|
+
value = Math.abs(value - zThreshold);
|
1958
|
+
zMax = Math.max(zMax - zThreshold, Math.abs(zMin - zThreshold));
|
1959
|
+
zMin = 0;
|
1960
|
+
}
|
1961
|
+
|
1962
|
+
if (value === null) {
|
1963
|
+
radius = null;
|
1940
1964
|
// Issue #4419 - if value is less than zMin, push a radius that's always smaller than the minimum size
|
1941
|
-
if (
|
1942
|
-
|
1965
|
+
} else if (value < zMin) {
|
1966
|
+
radius = minSize / 2 - 1;
|
1943
1967
|
} else {
|
1944
1968
|
// Relative size, a number between 0 and 1
|
1945
|
-
pos = zRange > 0 ? (
|
1946
|
-
|
1969
|
+
pos = zRange > 0 ? (value - zMin) / zRange : 0.5;
|
1970
|
+
|
1947
1971
|
if (sizeByArea && pos >= 0) {
|
1948
1972
|
pos = Math.sqrt(pos);
|
1949
1973
|
}
|
1950
|
-
|
1951
|
-
radii.push(math.ceil(minSize + pos * (maxSize - minSize)) / 2);
|
1974
|
+
radius = math.ceil(minSize + pos * (maxSize - minSize)) / 2;
|
1952
1975
|
}
|
1976
|
+
radii.push(radius);
|
1953
1977
|
}
|
1954
1978
|
this.radii = radii;
|
1955
1979
|
},
|
@@ -2002,7 +2026,7 @@ seriesTypes.bubble = extendClass(seriesTypes.scatter, {
|
|
2002
2026
|
point = data[i];
|
2003
2027
|
radius = radii ? radii[i] : 0; // #1737
|
2004
2028
|
|
2005
|
-
if (radius >= this.minPxSize / 2) {
|
2029
|
+
if (typeof radius === 'number' && radius >= this.minPxSize / 2) {
|
2006
2030
|
// Shape arguments
|
2007
2031
|
point.shapeType = 'circle';
|
2008
2032
|
point.shapeArgs = {
|
@@ -2018,7 +2042,7 @@ seriesTypes.bubble = extendClass(seriesTypes.scatter, {
|
|
2018
2042
|
width: 2 * radius,
|
2019
2043
|
height: 2 * radius
|
2020
2044
|
};
|
2021
|
-
} else { // below zThreshold
|
2045
|
+
} else { // below zThreshold or z = null
|
2022
2046
|
point.shapeArgs = point.plotY = point.dlBox = UNDEFINED; // #1691
|
2023
2047
|
}
|
2024
2048
|
}
|
@@ -145,8 +145,8 @@
|
|
145
145
|
hasExtremes: function (checkX) {
|
146
146
|
var options = this.options,
|
147
147
|
data = options.data,
|
148
|
-
xAxis = this.xAxis.options,
|
149
|
-
yAxis = this.yAxis.options;
|
148
|
+
xAxis = this.xAxis && this.xAxis.options,
|
149
|
+
yAxis = this.yAxis && this.yAxis.options;
|
150
150
|
return data.length > (options.boostThreshold || Number.MAX_VALUE) && typeof yAxis.min === 'number' && typeof yAxis.max === 'number' &&
|
151
151
|
(!checkX || (typeof xAxis.min === 'number' && typeof xAxis.max === 'number'));
|
152
152
|
},
|
@@ -538,8 +538,8 @@
|
|
538
538
|
/**
|
539
539
|
* Return a point instance from the k-d-tree
|
540
540
|
*/
|
541
|
-
wrap(Series.prototype, 'searchPoint', function (proceed
|
542
|
-
var point = proceed.
|
541
|
+
wrap(Series.prototype, 'searchPoint', function (proceed) {
|
542
|
+
var point = proceed.apply(this, [].slice.call(arguments, 1)),
|
543
543
|
ret = point;
|
544
544
|
|
545
545
|
if (point && !(point instanceof this.pointClass)) {
|
@@ -1,5 +1,5 @@
|
|
1
1
|
/**
|
2
|
-
* Highcharts JS v4.1.
|
2
|
+
* Highcharts JS v4.1.9 (2015-10-07)
|
3
3
|
* Highcharts Broken Axis module
|
4
4
|
*
|
5
5
|
* Author: Stephane Vanraes, Torstein Honsi
|
@@ -78,10 +78,6 @@
|
|
78
78
|
newPositions = [],
|
79
79
|
i;
|
80
80
|
|
81
|
-
if (info && info.totalRange >= axis.closestPointRange) {
|
82
|
-
return;
|
83
|
-
}
|
84
|
-
|
85
81
|
for (i = 0; i < tickPositions.length; i++) {
|
86
82
|
if (!axis.isInAnyBreak(tickPositions[i])) {
|
87
83
|
newPositions.push(tickPositions[i]);
|
@@ -2908,7 +2908,7 @@ if (CanvasRenderingContext2D) {
|
|
2908
2908
|
});
|
2909
2909
|
}
|
2910
2910
|
}/**
|
2911
|
-
* @license Highcharts JS v4.1.
|
2911
|
+
* @license Highcharts JS v4.1.9 (2015-10-07)
|
2912
2912
|
* CanVGRenderer Extension module
|
2913
2913
|
*
|
2914
2914
|
* (c) 2011-2012 Torstein Honsi, Erik Olsson
|