mirror of
				https://github.com/dawidolko/Website-Templates.git
				synced 2025-10-31 00:13:11 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			480 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			480 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| (function ($) {
 | |
|     // plugin options, default values
 | |
|     var defaultOptions = {
 | |
|         tooltip: false,
 | |
|         tooltipOpts: {
 | |
|             id: "flotTip",
 | |
|             content: "%s | X: %x | Y: %y",
 | |
|             // allowed templates are:
 | |
|             // %s -> series label,
 | |
|             // %lx -> x axis label (requires flot-axislabels plugin https://github.com/markrcote/flot-axislabels),
 | |
|             // %ly -> y axis label (requires flot-axislabels plugin https://github.com/markrcote/flot-axislabels),
 | |
|             // %x -> X value,
 | |
|             // %y -> Y value,
 | |
|             // %x.2 -> precision of X value,
 | |
|             // %p -> percent
 | |
|             xDateFormat: null,
 | |
|             yDateFormat: null,
 | |
|             monthNames: null,
 | |
|             dayNames: null,
 | |
|             shifts: {
 | |
|                 x: 10,
 | |
|                 y: 20
 | |
|             },
 | |
|             defaultTheme: true,
 | |
|             lines: {
 | |
|                 track: false,
 | |
|                 threshold: 0.05
 | |
|             },
 | |
| 
 | |
|             // callbacks
 | |
|             onHover: function (flotItem, $tooltipEl) {},
 | |
| 
 | |
|             $compat: false
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     // object
 | |
|     var FlotTooltip = function (plot) {
 | |
|         // variables
 | |
|         this.tipPosition = {x: 0, y: 0};
 | |
| 
 | |
|         this.init(plot);
 | |
|     };
 | |
| 
 | |
|     // main plugin function
 | |
|     FlotTooltip.prototype.init = function (plot) {
 | |
|         var that = this;
 | |
| 
 | |
|         // detect other flot plugins
 | |
|         var plotPluginsLength = $.plot.plugins.length;
 | |
|         this.plotPlugins = [];
 | |
| 
 | |
|         if (plotPluginsLength) {
 | |
|             for (var p = 0; p < plotPluginsLength; p++) {
 | |
|                 this.plotPlugins.push($.plot.plugins[p].name);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         plot.hooks.bindEvents.push(function (plot, eventHolder) {
 | |
| 
 | |
|             // get plot options
 | |
|             that.plotOptions = plot.getOptions();
 | |
| 
 | |
|             // if not enabled return
 | |
|             if (that.plotOptions.tooltip === false || typeof that.plotOptions.tooltip === 'undefined') return;
 | |
| 
 | |
|             // shortcut to access tooltip options
 | |
|             that.tooltipOptions = that.plotOptions.tooltipOpts;
 | |
| 
 | |
|             if (that.tooltipOptions.$compat) {
 | |
|                 that.wfunc = 'width';
 | |
|                 that.hfunc = 'height';
 | |
|             } else {
 | |
|                 that.wfunc = 'innerWidth';
 | |
|                 that.hfunc = 'innerHeight';
 | |
|             }
 | |
| 
 | |
|             // create tooltip DOM element
 | |
|             var $tip = that.getDomElement();
 | |
| 
 | |
|             // bind event
 | |
|             $( plot.getPlaceholder() ).bind("plothover", plothover);
 | |
| 
 | |
|             $(eventHolder).bind('mousemove', mouseMove);
 | |
|         });
 | |
| 
 | |
|         plot.hooks.shutdown.push(function (plot, eventHolder){
 | |
|             $(plot.getPlaceholder()).unbind("plothover", plothover);
 | |
|             $(eventHolder).unbind("mousemove", mouseMove);
 | |
|         });
 | |
| 
 | |
|         function mouseMove(e){
 | |
|             var pos = {};
 | |
|             pos.x = e.pageX;
 | |
|             pos.y = e.pageY;
 | |
|             plot.setTooltipPosition(pos);
 | |
|         }
 | |
| 
 | |
|         function plothover(event, pos, item) {
 | |
|             // Simple distance formula.
 | |
|             var lineDistance = function (p1x, p1y, p2x, p2y) {
 | |
|                 return Math.sqrt((p2x - p1x) * (p2x - p1x) + (p2y - p1y) * (p2y - p1y));
 | |
|             };
 | |
| 
 | |
|             // Here is some voodoo magic for determining the distance to a line form a given point {x, y}.
 | |
|             var dotLineLength = function (x, y, x0, y0, x1, y1, o) {
 | |
|                 if (o && !(o =
 | |
|                     function (x, y, x0, y0, x1, y1) {
 | |
|                         if (typeof x0 !== 'undefined') return { x: x0, y: y };
 | |
|                         else if (typeof y0 !== 'undefined') return { x: x, y: y0 };
 | |
| 
 | |
|                         var left,
 | |
|                             tg = -1 / ((y1 - y0) / (x1 - x0));
 | |
| 
 | |
|                         return {
 | |
|                             x: left = (x1 * (x * tg - y + y0) + x0 * (x * -tg + y - y1)) / (tg * (x1 - x0) + y0 - y1),
 | |
|                             y: tg * left - tg * x + y
 | |
|                         };
 | |
|                     } (x, y, x0, y0, x1, y1),
 | |
|                     o.x >= Math.min(x0, x1) && o.x <= Math.max(x0, x1) && o.y >= Math.min(y0, y1) && o.y <= Math.max(y0, y1))
 | |
|                 ) {
 | |
|                     var l1 = lineDistance(x, y, x0, y0), l2 = lineDistance(x, y, x1, y1);
 | |
|                     return l1 > l2 ? l2 : l1;
 | |
|                 } else {
 | |
|                     var a = y0 - y1, b = x1 - x0, c = x0 * y1 - y0 * x1;
 | |
|                     return Math.abs(a * x + b * y + c) / Math.sqrt(a * a + b * b);
 | |
|                 }
 | |
|             };
 | |
| 
 | |
|             if (item) {
 | |
|                 plot.showTooltip(item, pos);
 | |
|             } else if (that.plotOptions.series.lines.show && that.tooltipOptions.lines.track === true) {
 | |
|                 var closestTrace = {
 | |
|                     distance: -1
 | |
|                 };
 | |
| 
 | |
|                 $.each(plot.getData(), function (i, series) {
 | |
|                     var xBeforeIndex = 0,
 | |
|                         xAfterIndex = -1;
 | |
| 
 | |
|                     // Our search here assumes our data is sorted via the x-axis.
 | |
|                     // TODO: Improve efficiency somehow - search smaller sets of data.
 | |
|                     for (var j = 1; j < series.data.length; j++) {
 | |
|                         if (series.data[j - 1][0] <= pos.x && series.data[j][0] >= pos.x) {
 | |
|                             xBeforeIndex = j - 1;
 | |
|                             xAfterIndex = j;
 | |
|                         }
 | |
|                     }
 | |
| 
 | |
|                     if (xAfterIndex === -1) {
 | |
|                         plot.hideTooltip();
 | |
|                         return;
 | |
|                     }
 | |
| 
 | |
|                     var pointPrev = { x: series.data[xBeforeIndex][0], y: series.data[xBeforeIndex][1] },
 | |
|                         pointNext = { x: series.data[xAfterIndex][0], y: series.data[xAfterIndex][1] };
 | |
| 
 | |
|                     var distToLine = dotLineLength(pos.x, pos.y, pointPrev.x, pointPrev.y, pointNext.x, pointNext.y, false);
 | |
| 
 | |
|                     if (distToLine < that.tooltipOptions.lines.threshold) {
 | |
| 
 | |
|                         var closestIndex = lineDistance(pointPrev.x, pointPrev.y, pos.x, pos.y) <
 | |
|                             lineDistance(pos.x, pos.y, pointNext.x, pointNext.y) ? xBeforeIndex : xAfterIndex;
 | |
| 
 | |
|                         var pointSize = series.datapoints.pointsize;
 | |
| 
 | |
|                         // Calculate the point on the line vertically closest to our cursor.
 | |
|                         var pointOnLine = [
 | |
|                             pos.x,
 | |
|                             pointPrev.y + ((pointNext.y - pointPrev.y) * ((pos.x - pointPrev.x) / (pointNext.x - pointPrev.x)))
 | |
|                         ];
 | |
| 
 | |
|                         var item = {
 | |
|                             datapoint: pointOnLine,
 | |
|                             dataIndex: closestIndex,
 | |
|                             series: series,
 | |
|                             seriesIndex: i
 | |
|                         };
 | |
| 
 | |
|                         if (closestTrace.distance === -1 || distToLine < closestTrace.distance) {
 | |
|                             closestTrace = {
 | |
|                                 distance: distToLine,
 | |
|                                 item: item
 | |
|                             };
 | |
|                         }
 | |
|                     }
 | |
|                 });
 | |
| 
 | |
|                 if (closestTrace.distance !== -1)
 | |
|                     plot.showTooltip(closestTrace.item, pos);
 | |
|                 else
 | |
|                     plot.hideTooltip();
 | |
|             } else {
 | |
|                 plot.hideTooltip();
 | |
|             }
 | |
|         }
 | |
| 
 | |
| 	    // Quick little function for setting the tooltip position.
 | |
| 	    plot.setTooltipPosition = function (pos) {
 | |
| 	        var $tip = that.getDomElement();
 | |
| 
 | |
| 	        var totalTipWidth = $tip.outerWidth() + that.tooltipOptions.shifts.x;
 | |
| 	        var totalTipHeight = $tip.outerHeight() + that.tooltipOptions.shifts.y;
 | |
| 	        if ((pos.x - $(window).scrollLeft()) > ($(window)[that.wfunc]() - totalTipWidth)) {
 | |
| 	            pos.x -= totalTipWidth;
 | |
| 	        }
 | |
| 	        if ((pos.y - $(window).scrollTop()) > ($(window)[that.hfunc]() - totalTipHeight)) {
 | |
| 	            pos.y -= totalTipHeight;
 | |
| 	        }
 | |
| 	        that.tipPosition.x = pos.x;
 | |
| 	        that.tipPosition.y = pos.y;
 | |
| 	    };
 | |
| 
 | |
| 	    // Quick little function for showing the tooltip.
 | |
| 	    plot.showTooltip = function (target, position) {
 | |
| 	        var $tip = that.getDomElement();
 | |
| 
 | |
| 	        // convert tooltip content template to real tipText
 | |
| 	        var tipText = that.stringFormat(that.tooltipOptions.content, target);
 | |
| 
 | |
| 	        $tip.html(tipText);
 | |
| 	        plot.setTooltipPosition({ x: position.pageX, y: position.pageY });
 | |
| 	        $tip.css({
 | |
| 	            left: that.tipPosition.x + that.tooltipOptions.shifts.x,
 | |
| 	            top: that.tipPosition.y + that.tooltipOptions.shifts.y
 | |
| 	        }).show();
 | |
| 
 | |
| 	        // run callback
 | |
| 	        if (typeof that.tooltipOptions.onHover === 'function') {
 | |
| 	            that.tooltipOptions.onHover(target, $tip);
 | |
| 	        }
 | |
| 	    };
 | |
| 
 | |
| 	    // Quick little function for hiding the tooltip.
 | |
| 	    plot.hideTooltip = function () {
 | |
| 	        that.getDomElement().hide().html('');
 | |
| 	    };
 | |
|     };
 | |
| 
 | |
|     /**
 | |
|      * get or create tooltip DOM element
 | |
|      * @return jQuery object
 | |
|      */
 | |
|     FlotTooltip.prototype.getDomElement = function () {
 | |
|         var $tip = $('#' + this.tooltipOptions.id);
 | |
| 
 | |
|         if( $tip.length === 0 ){
 | |
|             $tip = $('<div />').attr('id', this.tooltipOptions.id);
 | |
|             $tip.appendTo('body').hide().css({position: 'absolute'});
 | |
| 
 | |
|             if(this.tooltipOptions.defaultTheme) {
 | |
|                 $tip.css({
 | |
|                     'background': '#fff',
 | |
|                     'z-index': '1040',
 | |
|                     'padding': '0.4em 0.6em',
 | |
|                     'border-radius': '0.5em',
 | |
|                     'font-size': '0.8em',
 | |
|                     'border': '1px solid #111',
 | |
|                     'display': 'none',
 | |
|                     'white-space': 'nowrap'
 | |
|                 });
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         return $tip;
 | |
|     };
 | |
| 
 | |
|     /**
 | |
|      * core function, create tooltip content
 | |
|      * @param  {string} content - template with tooltip content
 | |
|      * @param  {object} item - Flot item
 | |
|      * @return {string} real tooltip content for current item
 | |
|      */
 | |
|     FlotTooltip.prototype.stringFormat = function (content, item) {
 | |
| 
 | |
|         var percentPattern = /%p\.{0,1}(\d{0,})/;
 | |
|         var seriesPattern = /%s/;
 | |
|         var xLabelPattern = /%lx/; // requires flot-axislabels plugin https://github.com/markrcote/flot-axislabels, will be ignored if plugin isn't loaded
 | |
|         var yLabelPattern = /%ly/; // requires flot-axislabels plugin https://github.com/markrcote/flot-axislabels, will be ignored if plugin isn't loaded
 | |
|         var xPattern = /%x\.{0,1}(\d{0,})/;
 | |
|         var yPattern = /%y\.{0,1}(\d{0,})/;
 | |
|         var xPatternWithoutPrecision = "%x";
 | |
|         var yPatternWithoutPrecision = "%y";
 | |
|         var customTextPattern = "%ct";
 | |
| 
 | |
|         var x, y, customText, p;
 | |
| 
 | |
|         // for threshold plugin we need to read data from different place
 | |
|         if (typeof item.series.threshold !== "undefined") {
 | |
|             x = item.datapoint[0];
 | |
|             y = item.datapoint[1];
 | |
|             customText = item.datapoint[2];
 | |
|         } else if (typeof item.series.lines !== "undefined" && item.series.lines.steps) {
 | |
|             x = item.series.datapoints.points[item.dataIndex * 2];
 | |
|             y = item.series.datapoints.points[item.dataIndex * 2 + 1];
 | |
|             // TODO: where to find custom text in this variant?
 | |
|             customText = "";
 | |
|         } else {
 | |
|             x = item.series.data[item.dataIndex][0];
 | |
|             y = item.series.data[item.dataIndex][1];
 | |
|             customText = item.series.data[item.dataIndex][2];
 | |
|         }
 | |
| 
 | |
|         // I think this is only in case of threshold plugin
 | |
|         if (item.series.label === null && item.series.originSeries) {
 | |
|             item.series.label = item.series.originSeries.label;
 | |
|         }
 | |
| 
 | |
|         // if it is a function callback get the content string
 | |
|         if (typeof(content) === 'function') {
 | |
|             content = content(item.series.label, x, y, item);
 | |
|         }
 | |
| 
 | |
|         // percent match for pie charts and stacked percent
 | |
|         if (typeof (item.series.percent) !== 'undefined') {
 | |
|             p = item.series.percent;
 | |
|         } else if (typeof (item.series.percents) !== 'undefined') {
 | |
|             p = item.series.percents[item.dataIndex];
 | |
|         }        
 | |
|         if (typeof p === 'number') {
 | |
|             content = this.adjustValPrecision(percentPattern, content, p);
 | |
|         }
 | |
| 
 | |
|         // series match
 | |
|         if (typeof(item.series.label) !== 'undefined') {
 | |
|             content = content.replace(seriesPattern, item.series.label);
 | |
|         } else {
 | |
|             //remove %s if label is undefined
 | |
|             content = content.replace(seriesPattern, "");
 | |
|         }
 | |
| 
 | |
|         // x axis label match
 | |
|         if (this.hasAxisLabel('xaxis', item)) {
 | |
|             content = content.replace(xLabelPattern, item.series.xaxis.options.axisLabel);
 | |
|         } else {
 | |
|             //remove %lx if axis label is undefined or axislabels plugin not present
 | |
|             content = content.replace(xLabelPattern, "");
 | |
|         }
 | |
| 
 | |
|         // y axis label match
 | |
|         if (this.hasAxisLabel('yaxis', item)) {
 | |
|             content = content.replace(yLabelPattern, item.series.yaxis.options.axisLabel);
 | |
|         } else {
 | |
|             //remove %ly if axis label is undefined or axislabels plugin not present
 | |
|             content = content.replace(yLabelPattern, "");
 | |
|         }
 | |
| 
 | |
|         // time mode axes with custom dateFormat
 | |
|         if (this.isTimeMode('xaxis', item) && this.isXDateFormat(item)) {
 | |
|             content = content.replace(xPattern, this.timestampToDate(x, this.tooltipOptions.xDateFormat, item.series.xaxis.options));
 | |
|         }
 | |
| 		if (this.isTimeMode('yaxis', item) && this.isYDateFormat(item)) {
 | |
|             content = content.replace(yPattern, this.timestampToDate(y, this.tooltipOptions.yDateFormat, item.series.yaxis.options));
 | |
|         }
 | |
| 
 | |
|         // set precision if defined
 | |
|         if (typeof x === 'number') {
 | |
|             content = this.adjustValPrecision(xPattern, content, x);
 | |
|         }
 | |
|         if (typeof y === 'number') {
 | |
|             content = this.adjustValPrecision(yPattern, content, y);
 | |
|         }
 | |
| 
 | |
|         // change x from number to given label, if given
 | |
|         if (typeof item.series.xaxis.ticks !== 'undefined') {
 | |
| 
 | |
|             var ticks;
 | |
|             if (this.hasRotatedXAxisTicks(item)) {
 | |
|                 // xaxis.ticks will be an empty array if tickRotor is being used, but the values are available in rotatedTicks
 | |
|                 ticks = 'rotatedTicks';
 | |
|             } else {
 | |
|                 ticks = 'ticks';
 | |
|             }
 | |
| 
 | |
|             // see https://github.com/krzysu/flot.tooltip/issues/65
 | |
|             var tickIndex = item.dataIndex + item.seriesIndex;
 | |
| 
 | |
|             if (item.series.xaxis[ticks].length > tickIndex && !this.isTimeMode('xaxis', item)) {
 | |
|                 var valueX = (this.isCategoriesMode('xaxis', item)) ? item.series.xaxis[ticks][tickIndex].label : item.series.xaxis[ticks][tickIndex].v;
 | |
|                 if (valueX === x) {
 | |
|                     content = content.replace(xPattern, item.series.xaxis[ticks][tickIndex].label);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         // change y from number to given label, if given
 | |
|         if (typeof item.series.yaxis.ticks !== 'undefined') {
 | |
|             for (var index in item.series.yaxis.ticks) {
 | |
|                 if (item.series.yaxis.ticks.hasOwnProperty(index)) {
 | |
|                     var valueY = (this.isCategoriesMode('yaxis', item)) ? item.series.yaxis.ticks[index].label : item.series.yaxis.ticks[index].v;
 | |
|                     if (valueY === y) {
 | |
|                         content = content.replace(yPattern, item.series.yaxis.ticks[index].label);
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         // if no value customization, use tickFormatter by default
 | |
|         if (typeof item.series.xaxis.tickFormatter !== 'undefined') {
 | |
|             //escape dollar
 | |
|             content = content.replace(xPatternWithoutPrecision, item.series.xaxis.tickFormatter(x, item.series.xaxis).replace(/\$/g, '$$'));
 | |
|         }
 | |
|         if (typeof item.series.yaxis.tickFormatter !== 'undefined') {
 | |
|             //escape dollar
 | |
|             content = content.replace(yPatternWithoutPrecision, item.series.yaxis.tickFormatter(y, item.series.yaxis).replace(/\$/g, '$$'));
 | |
|         }
 | |
| 
 | |
|         if (customText)
 | |
|             content = content.replace(customTextPattern, customText);
 | |
| 
 | |
|         return content;
 | |
|     };
 | |
| 
 | |
|     // helpers just for readability
 | |
|     FlotTooltip.prototype.isTimeMode = function (axisName, item) {
 | |
|         return (typeof item.series[axisName].options.mode !== 'undefined' && item.series[axisName].options.mode === 'time');
 | |
|     };
 | |
| 
 | |
|     FlotTooltip.prototype.isXDateFormat = function (item) {
 | |
|         return (typeof this.tooltipOptions.xDateFormat !== 'undefined' && this.tooltipOptions.xDateFormat !== null);
 | |
|     };
 | |
| 
 | |
|     FlotTooltip.prototype.isYDateFormat = function (item) {
 | |
|         return (typeof this.tooltipOptions.yDateFormat !== 'undefined' && this.tooltipOptions.yDateFormat !== null);
 | |
|     };
 | |
| 
 | |
|     FlotTooltip.prototype.isCategoriesMode = function (axisName, item) {
 | |
|         return (typeof item.series[axisName].options.mode !== 'undefined' && item.series[axisName].options.mode === 'categories');
 | |
|     };
 | |
| 
 | |
|     //
 | |
|     FlotTooltip.prototype.timestampToDate = function (tmst, dateFormat, options) {
 | |
|         var theDate = $.plot.dateGenerator(tmst, options);
 | |
|         return $.plot.formatDate(theDate, dateFormat, this.tooltipOptions.monthNames, this.tooltipOptions.dayNames);
 | |
|     };
 | |
| 
 | |
|     //
 | |
|     FlotTooltip.prototype.adjustValPrecision = function (pattern, content, value) {
 | |
| 
 | |
|         var precision;
 | |
|         var matchResult = content.match(pattern);
 | |
|         if( matchResult !== null ) {
 | |
|             if(RegExp.$1 !== '') {
 | |
|                 precision = RegExp.$1;
 | |
|                 value = value.toFixed(precision);
 | |
| 
 | |
|                 // only replace content if precision exists, in other case use thickformater
 | |
|                 content = content.replace(pattern, value);
 | |
|             }
 | |
|         }
 | |
|         return content;
 | |
|     };
 | |
| 
 | |
|     // other plugins detection below
 | |
| 
 | |
|     // check if flot-axislabels plugin (https://github.com/markrcote/flot-axislabels) is used and that an axis label is given
 | |
|     FlotTooltip.prototype.hasAxisLabel = function (axisName, item) {
 | |
|         return ($.inArray(this.plotPlugins, 'axisLabels') !== -1 && typeof item.series[axisName].options.axisLabel !== 'undefined' && item.series[axisName].options.axisLabel.length > 0);
 | |
|     };
 | |
| 
 | |
|     // check whether flot-tickRotor, a plugin which allows rotation of X-axis ticks, is being used
 | |
|     FlotTooltip.prototype.hasRotatedXAxisTicks = function (item) {
 | |
|         return ($.inArray(this.plotPlugins, 'tickRotor') !== -1 && typeof item.series.xaxis.rotatedTicks !== 'undefined');
 | |
|     };
 | |
| 
 | |
|     //
 | |
|     var init = function (plot) {
 | |
|       new FlotTooltip(plot);
 | |
|     };
 | |
| 
 | |
|     // define Flot plugin
 | |
|     $.plot.plugins.push({
 | |
|         init: init,
 | |
|         options: defaultOptions,
 | |
|         name: 'tooltip',
 | |
|         version: '0.8.4'
 | |
|     });
 | |
| 
 | |
| })(jQuery);
 |