@cryptotaxi247 / netdata-1 / commits / 0850821c6

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 -})();