dygraphs now support smooth plotting on line charts; dygraphs can now be rendered as sparklines
Costa Tsaousis (ktsaou) committed
Dec 9, 2015 at 03:12 UTC
0850821c66af3c6a755ab87935f9771f3e02d2b5
6 files changed
+303
-367
web/Makefile.am
+1
-1
@@ -19,7 +19,7 @@ dist_web_DATA = \
19
weblibdir=$(webdir)/lib
20
dist_weblib_DATA = \
21
lib/dygraph-combined.js \
22
- lib/dygraph-synchronizer.js \
22
+ lib/dygraph-smooth-plotter.js \
23
lib/jquery-1.11.3.min.js \
24
lib/jquery.peity.min.js \
25
lib/jquery.sparkline.min.js \
web/dashboard.css
+19
@@ -63,6 +63,7 @@ html {
63
-webkit-transition:opacity 0.2s ease;
64
-moz-transition:opacity 0.2s ease;
65
-o-transition:opacity 0.2s ease;
66
+ padding: 4px;
67
}
68
69
.dygraph-legend:hover {
@@ -71,6 +72,24 @@ html {
72
opacity:0;
73
}
74
75
+/* TEST
76
+.dygraph-legend {
77
+ position: absolute;
78
+ background: rgb(0, 0, 0) transparent !important;
79
+ background-color: rgba(0,0,0,0.6) !important;
80
+ filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#99000000, endColorstr=#99000000) !important;
81
+ -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#99000000, endColorstr=#99000000)" !important;
82
+ color: white !important;
83
+ font: 10px arial, san serif !important;
84
+ text-align: left !important;
85
+ white-space: nowrap !important;
86
+ padding: 5px !important;
87
+ border: 1px solid white !important;
88
+ box-sizing: content-box !important;
89
+ z-index: 10000 !important;
90
+}
91
+*/
92
+
93
.dygraph-title {
94
text-indent: 56px;
95
text-align: left;
web/dashboard.html
+25
@@ -166,6 +166,31 @@ The fastest charting engine that can chart complete charts (not just sparklines)
166
The charts are zoomable (drag their contents to pan, shift with mouse wheel to zoom-in or zoom-out, double click to reset it).
167
<b>Netdata magic!</b> Realtime charts on your web page!
168
<br/>
169
+Sparklines using dygraphs
170
+ <div data-netdata="system.processes"
171
+ data-chart-library="dygraph"
172
+ data-dygraph-theme="sparkline"
173
+ data-width="15%"
174
+ data-height="30"
175
+ data-after="-300"
176
+ ></div>
177
+ are also possible! This
178
+ <div data-netdata="system.ipv4"
179
+ data-chart-library="dygraph"
180
+ data-dygraph-theme="sparkline"
181
+ data-width="15%"
182
+ data-height="30"
183
+ data-after="-300"
184
+ ></div>
185
+ is an area chart, while this
186
+ <div data-netdata="system.cpu"
187
+ data-chart-library="dygraph"
188
+ data-dygraph-theme="sparkline"
189
+ data-width="15%"
190
+ data-height="30"
191
+ data-after="-300"
192
+ ></div> is a stacked area chart!
193
+<br/>
194
<div style="width: 24%; display: inline-block;">
195
<div data-netdata="system.processes"
196
data-chart-library="dygraph"
web/dashboard.js
+132
-137
@@ -66,15 +66,16 @@
66
// default URLs for all the external files we need
67
// make them RELATIVE so that the whole thing can also be
68
// installed under a web server
69
- NETDATA.jQuery = NETDATA.serverDefault + 'lib/jquery-1.11.3.min.js';
70
- NETDATA.peity_js = NETDATA.serverDefault + 'lib/jquery.peity.min.js';
71
- NETDATA.sparkline_js = NETDATA.serverDefault + 'lib/jquery.sparkline.min.js';
72
- NETDATA.dygraph_js = NETDATA.serverDefault + 'lib/dygraph-combined.js';
73
- NETDATA.raphael_js = NETDATA.serverDefault + 'lib/raphael-min.js';
74
- NETDATA.morris_js = NETDATA.serverDefault + 'lib/morris.min.js';
75
- NETDATA.morris_css = NETDATA.serverDefault + 'css/morris.css';
76
- NETDATA.dashboard_css = NETDATA.serverDefault + 'dashboard.css';
77
- NETDATA.google_js = 'https://www.google.com/jsapi';
69
+ NETDATA.jQuery = NETDATA.serverDefault + 'lib/jquery-1.11.3.min.js';
70
+ NETDATA.peity_js = NETDATA.serverDefault + 'lib/jquery.peity.min.js';
71
+ NETDATA.sparkline_js = NETDATA.serverDefault + 'lib/jquery.sparkline.min.js';
72
+ NETDATA.dygraph_js = NETDATA.serverDefault + 'lib/dygraph-combined.js';
73
+ NETDATA.dygraph_smooth_js = NETDATA.serverDefault + 'lib/dygraph-smooth-plotter.js';
74
+ NETDATA.raphael_js = NETDATA.serverDefault + 'lib/raphael-min.js';
75
+ NETDATA.morris_js = NETDATA.serverDefault + 'lib/morris.min.js';
76
+ NETDATA.morris_css = NETDATA.serverDefault + 'css/morris.css';
77
+ NETDATA.dashboard_css = NETDATA.serverDefault + 'dashboard.css';
78
+ NETDATA.google_js = 'https://www.google.com/jsapi';
79
80
// these are the colors Google Charts are using
81
// we have them here to attempt emulate their look and feel on the other chart libraries
@@ -530,20 +531,15 @@
531
},
532
533
resetChart: function() {
533
- if(NETDATA.globalPanAndZoom.isMaster(this)) {
534
- NETDATA.globalPanAndZoom.clearMaster();
535
- // it will call us back - no need to do
536
- // anything more.
537
- return;
538
- }
534
+ NETDATA.globalPanAndZoom.clearMaster();
535
+ this.follows_global = 0;
536
540
- //if(state.current.name != 'auto')
541
- this.setMode('auto');
537
+ this.clearSelection();
538
539
+ this.setMode('auto');
540
this.current.force_before_ms = null;
541
this.current.force_after_ms = null;
542
this.current.last_autorefreshed = 0;
546
- this.follows_global = 0;
543
this.paused = false;
544
this.selected = false;
545
this.enabled = true;
@@ -861,7 +857,7 @@
857
// update the performance counters
858
var now = new Date().getTime();
859
864
- // don't update last_update_ms if this chart is
860
+ // don't update last_autorefreshed if this chart is
861
// forced to be updated with global PanAndZoom
862
if(NETDATA.globalPanAndZoom.isActive())
863
this.current.last_autorefreshed = 0;
@@ -1469,6 +1465,7 @@
1465
// dygraph
1466
1467
NETDATA.dygraph = {
1468
+ smooth: false,
1469
state: null,
1470
sync: false,
1471
dont_sync_before: 0,
@@ -1563,7 +1560,6 @@
1560
if(NETDATA.options.debug.dygraph) state.log('dygraph.resetChart()');
1561
1562
state.resetChart();
1566
- if(NETDATA.globalPanAndZoom.clearMaster());
1563
}
1564
1565
NETDATA.dygraph.chartPanOrZoom = function(state, dygraph, after, before) {
@@ -1595,6 +1591,22 @@
1591
return true;
1592
}
1593
1594
+ NETDATA.dygraphSmoothInitialize = function(callback) {
1595
+ $.ajax({
1596
+ url: NETDATA.dygraph_smooth_js,
1597
+ cache: true,
1598
+ dataType: "script"
1599
+ })
1600
+ .done(function() {
1601
+ NETDATA.dygraph.smooth = true;
1602
+ smoothPlotter.smoothing = 0.3;
1603
+ })
1604
+ .always(function() {
1605
+ if(typeof callback == "function")
1606
+ callback();
1607
+ })
1608
+ };
1609
+
1610
NETDATA.dygraphInitialize = function(callback) {
1611
if(typeof netdataNoDygraphs == 'undefined' || !netdataNoDygraphs) {
1612
$.ajax({
@@ -1604,11 +1616,10 @@
1616
})
1617
.done(function() {
1618
NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
1619
+ NETDATA.dygraphSmoothInitialize(callback);
1620
})
1621
.fail(function() {
1622
NETDATA.error(100, NETDATA.dygraph_js);
1610
- })
1611
- .always(function() {
1623
if(typeof callback == "function")
1624
callback();
1625
})
@@ -1647,125 +1658,84 @@
1658
if(NETDATA.options.debug.dygraph || state.debug) state.log('dygraphChartCreate()');
1659
1660
var self = $(state.element);
1650
- var title = self.data('dygraph-title') || state.chart.title;
1651
- var titleHeight = self.data('dygraph-titleheight') || 19;
1652
- var hideOverlayOnMouseOut = self.data('dygraph-hideoverlayonmouseout') || true;
1653
- var colors = self.data('dygraph-colors') || NETDATA.colors;
1654
-
1655
- // legend
1656
- var legend = self.data('dygraph-legend') || 'onmouseover';
1657
- var labelsDiv = self.data('dygraph-labelsdiv') || undefined;
1658
- var labelsDivStyles = self.data('dygraph-labelsdivstyles') || { 'fontSize':'10px' };
1659
- var labelsDivWidth = self.data('dygraph-labelsdivwidth') || state.chartWidth() - 70;
1660
- var labelsSeparateLines = self.data('dygraph-labelsseparatelines') || false;
1661
- var labelsShowZeroValues = self.data('dygraph-labelsshowzerovalues') || true;
1662
- var showLabelsOnHighlight = self.data('dygraph-showlabelsonhighlight') || true;
1663
- var yLabelWidth = self.data('dygraph-ylabelwidth') || 12;
1664
-
1665
- // Value Formatting
1666
- var sigFigs = self.data('dygraph-sigfigs') || null;
1667
- var maxNumberWidth = self.data('dygraph-maxnumberwidth') || 8;
1668
- var digitsAfterDecimal = self.data('dygraph-digitsafterdecimal') || 2;
1669
- var valueFormatter = self.data('dygraph-valueformatter') || undefined; //function(x){ return x.toFixed(2); };
1670
-
1671
- // Axis and Grid
1672
- var drawAxis = self.data('dygraph-drawaxis') || true;
1673
- var axisLabelFontSize = self.data('dygraph-axislabelfontsize') || 10;
1674
- var axisLineWidth = self.data('dygraph-axislinewidth') || 0.3;
1675
- var gridLineColor = self.data('dygraph-gridlinecolor') || '#EEE';
1676
- var axisLineColor = self.data('dygraph-axislinecolor') || '#DDD';
1677
- var drawGrid = self.data('dygraph-drawgrid') || true;
1678
- var drawXGrid = self.data('dygraph-drawxgrid') || undefined;
1679
- var drawYGrid = self.data('dygraph-drawygrid') || undefined;
1680
- var gridLinePattern = self.data('dygraph-gridlinepattern') || null;
1681
- var gridLineWidth = self.data('dygraph-gridlinewidth') || 0.3;
1682
-
1683
- // enabling this makes the chart with little square lines
1684
- var stepPlot = self.data('dygraph-stepplot') || false;
1685
- var connectSeparatedPoints = self.data('dygraph-connectseparatedpoints') || false;
1686
-
1687
- // Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities.
1688
- var drawGapEdgePoints = self.data('dygraph-drawgapedgepoints') || true;
1689
-
1690
- // The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is "isolated",
1691
- // i.e. there is a missing point on either side of it. This also controls the size of those dots.
1692
- var drawPoints = self.data('dygraph-drawpoints') || false;
1693
- var pointSize = self.data('dygraph-pointsize') || 1;
1694
-
1695
- // The width of the lines connecting data points. This can be used to increase the contrast or some graphs.
1696
- var strokeWidth = self.data('dygraph-strokewidth') || (state.chart.chart_type == 'stacked')?0.0:1.0;
1697
- var strokePattern = self.data('dygraph-strokepattern') || undefined;
1698
-
1699
- // Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines.
1700
- var strokeBorderColor = self.data('dygraph-strokebordercolor') || 'white';
1701
- var strokeBorderWidth = self.data('dygraph-strokeborderwidth') || (state.chart.chart_type == 'stacked')?0.0:0.0;
1702
-
1703
- var highlightCircleSize = self.data('dygraph-highlightcirclesize') || 3;
1704
- var highlightSeriesOpts = self.data('dygraph-highlightseriesopts') || null; // TOO SLOW: { strokeWidth: 1.5 };
1705
- var highlightSeriesBackgroundAlpha = self.data('dygraph-highlightseriesbackgroundalpha') || null; // TOO SLOW: (state.chart.chart_type == 'stacked')?0.7:0.5;
1706
- var pointClickCallback = self.data('dygraph-pointclickcallback') || undefined;
1707
- var showRangeSelector = self.data('dygraph-showrangeselector') || false;
1708
- var showRoller = self.data('dygraph-showroller') || false;
1709
-
1710
- // leave a few pixels empty on the right of the chart
1711
- var rightGap = self.data('dygraph-rightgap') || 5;
1712
-
1713
- var fillGraph = self.data('dygraph-fillgraph') || (state.chart.chart_type == 'area')?true:false;
1714
- var fillAlpha = self.data('dygraph-fillalpha') || (state.chart.chart_type == 'stacked')?0.8:0.2;
1715
- var stackedGraph = self.data('dygraph-stackedgraph') || (state.chart.chart_type == 'stacked')?true:false;
1716
- var stackedGraphNaNFill = self.data('dygraph-stackedgraphnanfill') || 'none';
1661
1662
state.dygraph_options = {
1719
- title: title,
1720
- titleHeight: titleHeight,
1721
- ylabel: state.chart.units,
1722
- yLabelWidth: yLabelWidth,
1723
- connectSeparatedPoints: connectSeparatedPoints,
1724
- drawPoints: drawPoints,
1725
- fillGraph: fillGraph,
1726
- stackedGraph: stackedGraph,
1727
- stackedGraphNaNFill: stackedGraphNaNFill,
1728
- drawGrid: drawGrid,
1729
- drawXGrid: drawXGrid,
1730
- drawYGrid: drawYGrid,
1731
- gridLinePattern: gridLinePattern,
1732
- gridLineWidth: gridLineWidth,
1733
- gridLineColor: gridLineColor,
1734
- axisLineColor: axisLineColor,
1735
- axisLineWidth: axisLineWidth,
1736
- drawAxis: drawAxis,
1737
- hideOverlayOnMouseOut: hideOverlayOnMouseOut,
1738
- labelsDiv: labelsDiv,
1739
- labelsDivStyles: labelsDivStyles,
1740
- labelsDivWidth: labelsDivWidth,
1741
- labelsSeparateLines: labelsSeparateLines,
1742
- labelsShowZeroValues: labelsShowZeroValues,
1663
+ colors: self.data('dygraph-colors') || NETDATA.colors,
1664
+
1665
+ // leave a few pixels empty on the right of the chart
1666
+ rightGap: self.data('dygraph-rightgap') || 5,
1667
+ showRangeSelector: self.data('dygraph-showrangeselector') || false,
1668
+ showRoller: self.data('dygraph-showroller') || false,
1669
+
1670
+ title: self.data('dygraph-title') || state.chart.title,
1671
+ titleHeight: self.data('dygraph-titleheight') || 19,
1672
+
1673
+ legend: self.data('dygraph-legend') || 'onmouseover',
1674
+ labels: data.result.labels,
1675
+ labelsDiv: self.data('dygraph-labelsdiv') || undefined,
1676
+ labelsDivStyles: self.data('dygraph-labelsdivstyles') || { 'fontSize':'10px' },
1677
+ labelsDivWidth: self.data('dygraph-labelsdivwidth') || state.chartWidth() - 70,
1678
+ labelsSeparateLines: self.data('dygraph-labelsseparatelines') || false,
1679
+ labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues') || true,
1680
labelsKMB: false,
1681
labelsKMG2: false,
1745
- legend: legend,
1746
- showLabelsOnHighlight: showLabelsOnHighlight,
1747
- maxNumberWidth: maxNumberWidth,
1748
- sigFigs: sigFigs,
1749
- digitsAfterDecimal: digitsAfterDecimal,
1750
- axisLabelFontSize: axisLabelFontSize,
1751
- colors: colors,
1752
- fillAlpha: fillAlpha,
1753
- strokeWidth: strokeWidth,
1754
- drawGapEdgePoints: drawGapEdgePoints,
1755
- pointSize: pointSize,
1756
- stepPlot: stepPlot,
1757
- strokeBorderColor: strokeBorderColor,
1758
- strokeBorderWidth: strokeBorderWidth,
1759
- strokePattern: strokePattern,
1760
- highlightCircleSize: highlightCircleSize,
1761
- highlightSeriesOpts: highlightSeriesOpts,
1762
- highlightSeriesBackgroundAlpha: highlightSeriesBackgroundAlpha,
1763
- pointClickCallback: pointClickCallback,
1764
- showRangeSelector: showRangeSelector,
1765
- showRoller: showRoller,
1766
- valueFormatter: valueFormatter,
1767
- rightGap: rightGap,
1768
- labels: data.result.labels,
1682
+ showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight') || true,
1683
+ hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout') || true,
1684
+
1685
+ ylabel: state.chart.units,
1686
+ yLabelWidth: self.data('dygraph-ylabelwidth') || 12,
1687
+
1688
+ // the function to plot the chart
1689
+ plotter: null,
1690
+
1691
+ // The width of the lines connecting data points. This can be used to increase the contrast or some graphs.
1692
+ strokeWidth: self.data('dygraph-strokewidth') || (state.chart.chart_type == 'stacked')?0.0:1.0,
1693
+ strokePattern: self.data('dygraph-strokepattern') || undefined,
1694
+
1695
+ // The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is "isolated",
1696
+ // i.e. there is a missing point on either side of it. This also controls the size of those dots.
1697
+ drawPoints: self.data('dygraph-drawpoints') || false,
1698
+
1699
+ // Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities.
1700
+ drawGapEdgePoints: self.data('dygraph-drawgapedgepoints') || true,
1701
+
1702
+ connectSeparatedPoints: self.data('dygraph-connectseparatedpoints') || false,
1703
+ pointSize: self.data('dygraph-pointsize') || 1,
1704
+
1705
+ // enabling this makes the chart with little square lines
1706
+ stepPlot: self.data('dygraph-stepplot') || false,
1707
+
1708
+ // Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines.
1709
+ strokeBorderColor: self.data('dygraph-strokebordercolor') || 'white',
1710
+ strokeBorderWidth: self.data('dygraph-strokeborderwidth') || (state.chart.chart_type == 'stacked')?0.0:0.0,
1711
+
1712
+ fillGraph: self.data('dygraph-fillgraph') || (state.chart.chart_type == 'area')?true:false,
1713
+ fillAlpha: self.data('dygraph-fillalpha') || (state.chart.chart_type == 'stacked')?0.8:0.2,
1714
+ stackedGraph: self.data('dygraph-stackedgraph') || (state.chart.chart_type == 'stacked')?true:false,
1715
+ stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill') || 'none',
1716
+
1717
+ drawAxis: self.data('dygraph-drawaxis') || true,
1718
+ axisLabelFontSize: self.data('dygraph-axislabelfontsize') || 10,
1719
+ axisLineColor: self.data('dygraph-axislinecolor') || '#DDD',
1720
+ axisLineWidth: self.data('dygraph-axislinewidth') || 0.3,
1721
+
1722
+ drawGrid: self.data('dygraph-drawgrid') || true,
1723
+ drawXGrid: self.data('dygraph-drawxgrid') || undefined,
1724
+ drawYGrid: self.data('dygraph-drawygrid') || undefined,
1725
+ gridLinePattern: self.data('dygraph-gridlinepattern') || null,
1726
+ gridLineWidth: self.data('dygraph-gridlinewidth') || 0.3,
1727
+ gridLineColor: self.data('dygraph-gridlinecolor') || '#EEE',
1728
+
1729
+ maxNumberWidth: self.data('dygraph-maxnumberwidth') || 8,
1730
+ sigFigs: self.data('dygraph-sigfigs') || null,
1731
+ digitsAfterDecimal: self.data('dygraph-digitsafterdecimal') || 2,
1732
+ valueFormatter: self.data('dygraph-valueformatter') || undefined, //function(x){ return x.toFixed(2); };,
1733
+
1734
+ highlightCircleSize: self.data('dygraph-highlightcirclesize') || 4,
1735
+ highlightSeriesOpts: self.data('dygraph-highlightseriesopts') || null, // TOO SLOW: { strokeWidth: 1.5 },
1736
+ highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha') || null, // TOO SLOW: (state.chart.chart_type == 'stacked')?0.7:0.5,
1737
+
1738
+ pointClickCallback: self.data('dygraph-pointclickcallback') || undefined,
1739
axes: {
1740
x: {
1741
pixelsPerLabel: 50,
@@ -1803,6 +1773,10 @@
1773
if(NETDATA.options.debug.dygraph || state.debug) state.log('dygraphHighlightCallback()');
1774
state.pauseChart();
1775
NETDATA.dygraph.syncStart(state, event, x, points, row, seriesName);
1776
+ console.log(state.dygraph_instance);
1777
+ // fix legend zIndex using the internal structures of dygraph legend module
1778
+ // this works, but it is a hack!
1779
+ // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
1780
},
1781
unhighlightCallback: function(event) {
1782
if(NETDATA.options.debug.dygraph || state.debug) state.log('dygraphUnhighlightCallback()');
@@ -1931,6 +1905,27 @@
1905
}
1906
};
1907
1908
+ // smooth lines patch
1909
+ if(NETDATA.dygraph.smooth && state.chart.chart_type == 'line') {
1910
+ state.dygraph_options.plotter = smoothPlotter;
1911
+ state.dygraph_options.strokeWidth = 1.5;
1912
+ }
1913
+
1914
+ var theme = self.data('dygraph-theme') || null;
1915
+ if(theme == 'sparkline') {
1916
+ state.dygraph_options.drawGrid = false;
1917
+ state.dygraph_options.drawAxis = false;
1918
+ state.dygraph_options.title = undefined;
1919
+ state.dygraph_options.units = undefined;
1920
+ state.dygraph_options.legend = 'never'; // 'follow'
1921
+ state.dygraph_options.ylabel = undefined;
1922
+ state.dygraph_options.yLabelWidth = 0;
1923
+ // state.dygraph_options.labelsDivWidth = 120;
1924
+ state.dygraph_options.labelsSeparateLines = true;
1925
+ state.dygraph_options.highlightCircleSize = 3;
1926
+ state.dygraph_options.rightGap = 0;
1927
+ }
1928
+
1929
state.dygraph_instance = new Dygraph(state.element_chart,
1930
data.result.data, state.dygraph_options);
1931
};
web/lib/dygraph-smooth-plotter.js
new
+126
@@ -0,0 +1,126 @@
1
+var smoothPlotter = (function() {
2
+"use strict";
3
+
4
+/**
5
+ * Given three sequential points, p0, p1 and p2, find the left and right
6
+ * control points for p1.
7
+ *
8
+ * The three points are expected to have x and y properties.
9
+ *
10
+ * The alpha parameter controls the amount of smoothing.
11
+ * If α=0, then both control points will be the same as p1 (i.e. no smoothing).
12
+ *
13
+ * Returns [l1x, l1y, r1x, r1y]
14
+ *
15
+ * It's guaranteed that the line from (l1x, l1y)-(r1x, r1y) passes through p1.
16
+ * Unless allowFalseExtrema is set, then it's also guaranteed that:
17
+ * l1y ∈ [p0.y, p1.y]
18
+ * r1y ∈ [p1.y, p2.y]
19
+ *
20
+ * The basic algorithm is:
21
+ * 1. Put the control points l1 and r1 α of the way down (p0, p1) and (p1, p2).
22
+ * 2. Shift l1 and r2 so that the line l1–r1 passes through p1
23
+ * 3. Adjust to prevent false extrema while keeping p1 on the l1–r1 line.
24
+ *
25
+ * This is loosely based on the HighCharts algorithm.
26
+ */
27
+function getControlPoints(p0, p1, p2, opt_alpha, opt_allowFalseExtrema) {
28
+ var alpha = (opt_alpha !== undefined) ? opt_alpha : 1/3; // 0=no smoothing, 1=crazy smoothing
29
+ var allowFalseExtrema = opt_allowFalseExtrema || false;
30
+
31
+ if (!p2) {
32
+ return [p1.x, p1.y, null, null];
33
+ }
34
+
35
+ // Step 1: Position the control points along each line segment.
36
+ var l1x = (1 - alpha) * p1.x + alpha * p0.x,
37
+ l1y = (1 - alpha) * p1.y + alpha * p0.y,
38
+ r1x = (1 - alpha) * p1.x + alpha * p2.x,
39
+ r1y = (1 - alpha) * p1.y + alpha * p2.y;
40
+
41
+ // Step 2: shift the points up so that p1 is on the l1–r1 line.
42
+ if (l1x != r1x) {
43
+ // This can be derived w/ some basic algebra.
44
+ var deltaY = p1.y - r1y - (p1.x - r1x) * (l1y - r1y) / (l1x - r1x);
45
+ l1y += deltaY;
46
+ r1y += deltaY;
47
+ }
48
+
49
+ // Step 3: correct to avoid false extrema.
50
+ if (!allowFalseExtrema) {
51
+ if (l1y > p0.y && l1y > p1.y) {
52
+ l1y = Math.max(p0.y, p1.y);
53
+ r1y = 2 * p1.y - l1y;
54
+ } else if (l1y < p0.y && l1y < p1.y) {
55
+ l1y = Math.min(p0.y, p1.y);
56
+ r1y = 2 * p1.y - l1y;
57
+ }
58
+
59
+ if (r1y > p1.y && r1y > p2.y) {
60
+ r1y = Math.max(p1.y, p2.y);
61
+ l1y = 2 * p1.y - r1y;
62
+ } else if (r1y < p1.y && r1y < p2.y) {
63
+ r1y = Math.min(p1.y, p2.y);
64
+ l1y = 2 * p1.y - r1y;
65
+ }
66
+ }
67
+
68
+ return [l1x, l1y, r1x, r1y];
69
+}
70
+
71
+
72
+// A plotter which uses splines to create a smooth curve.
73
+// See tests/plotters.html for a demo.
74
+// Can be controlled via smoothPlotter.smoothing
75
+function smoothPlotter(e) {
76
+ var ctx = e.drawingContext,
77
+ points = e.points;
78
+
79
+ ctx.beginPath();
80
+ ctx.moveTo(points[0].canvasx, points[0].canvasy);
81
+
82
+ // right control point for previous point
83
+ var lastRightX = points[0].canvasx, lastRightY = points[0].canvasy;
84
+ var isOK = Dygraph.isOK; // i.e. is none of (null, undefined, NaN)
85
+
86
+ for (var i = 1; i < points.length; i++) {
87
+ var p0 = points[i - 1],
88
+ p1 = points[i],
89
+ p2 = points[i + 1];
90
+ p0 = p0 && isOK(p0.canvasy) ? p0 : null;
91
+ p1 = p1 && isOK(p1.canvasy) ? p1 : null;
92
+ p2 = p2 && isOK(p2.canvasy) ? p2 : null;
93
+ if (p0 && p1) {
94
+ var controls = getControlPoints({x: p0.canvasx, y: p0.canvasy},
95
+ {x: p1.canvasx, y: p1.canvasy},
96
+ p2 && {x: p2.canvasx, y: p2.canvasy},
97
+ smoothPlotter.smoothing);
98
+ // Uncomment to show the control points:
99
+ // ctx.lineTo(lastRightX, lastRightY);
100
+ // ctx.lineTo(controls[0], controls[1]);
101
+ // ctx.lineTo(p1.canvasx, p1.canvasy);
102
+ lastRightX = (lastRightX !== null) ? lastRightX : p0.canvasx;
103
+ lastRightY = (lastRightY !== null) ? lastRightY : p0.canvasy;
104
+ ctx.bezierCurveTo(lastRightX, lastRightY,
105
+ controls[0], controls[1],
106
+ p1.canvasx, p1.canvasy);
107
+ lastRightX = controls[2];
108
+ lastRightY = controls[3];
109
+ } else if (p1) {
110
+ // We're starting again after a missing point.
111
+ ctx.moveTo(p1.canvasx, p1.canvasy);
112
+ lastRightX = p1.canvasx;
113
+ lastRightY = p1.canvasy;
114
+ } else {
115
+ lastRightX = lastRightY = null;
116
+ }
117
+ }
118
+
119
+ ctx.stroke();
120
+}
121
+smoothPlotter.smoothing = 1/3;
122
+smoothPlotter._getControlPoints = getControlPoints; // for testing
123
+
124
+return smoothPlotter;
125
+
126
+})();
web/lib/dygraph-synchronizer.js
deleted
-229
@@ -1,229 +0,0 @@
1
-/**
2
- * Synchronize zooming and/or selections between a set of dygraphs.
3
- *
4
- * Usage:
5
- *
6
- * var g1 = new Dygraph(...),
7
- * g2 = new Dygraph(...),
8
- * ...;
9
- * var sync = Dygraph.synchronize(g1, g2, ...);
10
- * // charts are now synchronized
11
- * sync.detach();
12
- * // charts are no longer synchronized
13
- *
14
- * You can set options using the last parameter, for example:
15
- *
16
- * var sync = Dygraph.synchronize(g1, g2, g3, {
17
- * selection: true,
18
- * zoom: true
19
- * });
20
- *
21
- * The default is to synchronize both of these.
22
- *
23
- * Instead of passing one Dygraph object as each parameter, you may also pass an
24
- * array of dygraphs:
25
- *
26
- * var sync = Dygraph.synchronize([g1, g2, g3], {
27
- * selection: false,
28
- * zoom: true
29
- * });
30
- *
31
- * You may also set `range: false` if you wish to only sync the x-axis.
32
- * The `range` option has no effect unless `zoom` is true (the default).
33
- */
34
-(function() {
35
-/* global Dygraph:false */
36
-'use strict';
37
-
38
-var Dygraph;
39
-if (window.Dygraph) {
40
- Dygraph = window.Dygraph;
41
-} else if (typeof(module) !== 'undefined') {
42
- Dygraph = require('../dygraph');
43
-}
44
-
45
-var synchronize = function(/* dygraphs..., opts */) {
46
- if (arguments.length === 0) {
47
- throw 'Invalid invocation of Dygraph.synchronize(). Need >= 1 argument.';
48
- }
49
-
50
- var OPTIONS = ['selection', 'zoom', 'range'];
51
- var opts = {
52
- selection: true,
53
- zoom: true,
54
- range: true
55
- };
56
- var dygraphs = [];
57
- var prevCallbacks = [];
58
-
59
- var parseOpts = function(obj) {
60
- if (!(obj instanceof Object)) {
61
- throw 'Last argument must be either Dygraph or Object.';
62
- } else {
63
- for (var i = 0; i < OPTIONS.length; i++) {
64
- var optName = OPTIONS[i];
65
- if (obj.hasOwnProperty(optName)) opts[optName] = obj[optName];
66
- }
67
- }
68
- };
69
-
70
- if (arguments[0] instanceof Dygraph) {
71
- // Arguments are Dygraph objects.
72
- for (var i = 0; i < arguments.length; i++) {
73
- if (arguments[i] instanceof Dygraph) {
74
- dygraphs.push(arguments[i]);
75
- } else {
76
- break;
77
- }
78
- }
79
- if (i < arguments.length - 1) {
80
- throw 'Invalid invocation of Dygraph.synchronize(). ' +
81
- 'All but the last argument must be Dygraph objects.';
82
- } else if (i == arguments.length - 1) {
83
- parseOpts(arguments[arguments.length - 1]);
84
- }
85
- } else if (arguments[0].length) {
86
- // Invoked w/ list of dygraphs, options
87
- for (var i = 0; i < arguments[0].length; i++) {
88
- dygraphs.push(arguments[0][i]);
89
- }
90
- if (arguments.length == 2) {
91
- parseOpts(arguments[1]);
92
- } else if (arguments.length > 2) {
93
- throw 'Invalid invocation of Dygraph.synchronize(). ' +
94
- 'Expected two arguments: array and optional options argument.';
95
- } // otherwise arguments.length == 1, which is fine.
96
- } else {
97
- throw 'Invalid invocation of Dygraph.synchronize(). ' +
98
- 'First parameter must be either Dygraph or list of Dygraphs.';
99
- }
100
-
101
- if (dygraphs.length < 2) {
102
- throw 'Invalid invocation of Dygraph.synchronize(). ' +
103
- 'Need two or more dygraphs to synchronize.';
104
- }
105
-
106
- var readycount = dygraphs.length;
107
- for (var i = 0; i < dygraphs.length; i++) {
108
- var g = dygraphs[i];
109
- g.ready( function() {
110
- if (--readycount == 0) {
111
- // store original callbacks
112
- var callBackTypes = ['drawCallback', 'highlightCallback', 'unhighlightCallback'];
113
- for (var j = 0; j < dygraphs.length; j++) {
114
- if (!prevCallbacks[j]) {
115
- prevCallbacks[j] = {};
116
- }
117
- for (var k = callBackTypes.length - 1; k >= 0; k--) {
118
- prevCallbacks[j][callBackTypes[k]] = dygraphs[j].getFunctionOption(callBackTypes[k]);
119
- }
120
- }
121
-
122
- // Listen for draw, highlight, unhighlight callbacks.
123
- if (opts.zoom) {
124
- attachZoomHandlers(dygraphs, opts, prevCallbacks);
125
- }
126
-
127
- if (opts.selection) {
128
- attachSelectionHandlers(dygraphs, prevCallbacks);
129
- }
130
- }
131
- });
132
- }
133
-
134
- return {
135
- detach: function() {
136
- for (var i = 0; i < dygraphs.length; i++) {
137
- var g = dygraphs[i];
138
- if (opts.zoom) {
139
- g.updateOptions({drawCallback: prevCallbacks[i].drawCallback});
140
- }
141
- if (opts.selection) {
142
- g.updateOptions({
143
- highlightCallback: prevCallbacks[i].highlightCallback,
144
- unhighlightCallback: prevCallbacks[i].unhighlightCallback
145
- });
146
- }
147
- }
148
- // release references & make subsequent calls throw.
149
- dygraphs = null;
150
- opts = null;
151
- prevCallbacks = null;
152
- }
153
- };
154
-};
155
-
156
-function attachZoomHandlers(gs, syncOpts, prevCallbacks) {
157
- var block = false;
158
- for (var i = 0; i < gs.length; i++) {
159
- var g = gs[i];
160
- g.updateOptions({
161
- drawCallback: function(me, initial) {
162
- if (block || initial) return;
163
- block = true;
164
- var opts = {
165
- dateWindow: me.xAxisRange()
166
- };
167
- if (syncOpts.range) opts.valueRange = me.yAxisRange();
168
-
169
- for (var j = 0; j < gs.length; j++) {
170
- if (gs[j] == me) {
171
- if (prevCallbacks[j] && prevCallbacks[j].drawCallback) {
172
- prevCallbacks[j].drawCallback.apply(this, arguments);
173
- }
174
- continue;
175
- }
176
- gs[j].updateOptions(opts);
177
- }
178
- block = false;
179
- }
180
- }, true /* no need to redraw */);
181
- }
182
-}
183
-
184
-function attachSelectionHandlers(gs, prevCallbacks) {
185
- var block = false;
186
- for (var i = 0; i < gs.length; i++) {
187
- var g = gs[i];
188
-
189
- g.updateOptions({
190
- highlightCallback: function(event, x, points, row, seriesName) {
191
- if (block) return;
192
- block = true;
193
- var me = this;
194
- for (var i = 0; i < gs.length; i++) {
195
- if (me == gs[i]) {
196
- if (prevCallbacks[i] && prevCallbacks[i].highlightCallback) {
197
- prevCallbacks[i].highlightCallback.apply(this, arguments);
198
- }
199
- continue;
200
- }
201
- var idx = gs[i].getRowForX(x);
202
- if (idx !== null) {
203
- gs[i].setSelection(idx, seriesName);
204
- }
205
- }
206
- block = false;
207
- },
208
- unhighlightCallback: function(event) {
209
- if (block) return;
210
- block = true;
211
- var me = this;
212
- for (var i = 0; i < gs.length; i++) {
213
- if (me == gs[i]) {
214
- if (prevCallbacks[i] && prevCallbacks[i].unhighlightCallback) {
215
- prevCallbacks[i].unhighlightCallback.apply(this, arguments);
216
- }
217
- continue;
218
- }
219
- gs[i].clearSelection();
220
- }
221
- block = false;
222
- }
223
- }, true /* no need to redraw */);
224
- }
225
-}
226
-
227
-Dygraph.synchronize = synchronize;
228
-
229
-})();