PageSourceSearch

https://swparty.kr/plugin/mongtree/admin/assets/vendor/flot.tooltip/flot.tooltip.js

js swparty.kr collected 2026-09-25 13:31:01 UTC 24,391 bytes, 606 lines download raw bytes

1/*
2 * jquery.flot.tooltip
3 * 
4 * description: easy-to-use tooltips for Flot charts
5 * version: 0.9.0
6 * authors: Krzysztof Urbas @krzysu [myviews.pl],Evan Steinkerchner @Roundaround
7 * website: https://github.com/krzysu/flot.tooltip
8 * 
9 * build on 2016-07-26
10 * released under MIT License, 2012
11*/ 
12(function ($) {
13    // plugin options, default values
14    var defaultOptions = {
15        tooltip: {
16            show: false,
17            cssClass: "flotTip",
18            content: "%s | X: %x | Y: %y",
19            // allowed templates are:
20            // %s -> series label,
21            // %c -> series color,
22            // %lx -> x axis label (requires flot-axislabels plugin https://github.com/markrcote/flot-axislabels),
23            // %ly -> y axis label (requires flot-axislabels plugin https://github.com/markrcote/flot-axislabels),
24            // %x -> X value,
25            // %y -> Y value,
26            // %x.2 -> precision of X value,
27            // %p -> percent
28            // %n -> value (not percent) of pie chart
29            xDateFormat: null,
30            yDateFormat: null,
31            monthNames: null,
32            dayNames: null,
33            shifts: {
34                x: 10,
35                y: 20
36            },
37            defaultTheme: true,
38            snap: true,
39            lines: false,
40            clickTips: false,
41
42            // callbacks
43            onHover: function (flotItem, $tooltipEl) {},
44
45            $compat: false
46        }
47    };
48
49    // dummy default options object for legacy code (<0.8.5) - is deleted later
50    defaultOptions.tooltipOpts = defaultOptions.tooltip;
51
52    // object
53    var FlotTooltip = function (plot) {
54        // variables
55        this.tipPosition = {x: 0, y: 0};
56
57        this.init(plot);
58    };
59
60    // main plugin function
61    FlotTooltip.prototype.init = function (plot) {
62        var that = this;
63
64        // detect other flot plugins
65        var plotPluginsLength = $.plot.plugins.length;
66        this.plotPlugins = [];
67
68        if (plotPluginsLength) {
69            for (var p = 0; p < plotPluginsLength; p++) {
70                this.plotPlugins.push($.plot.plugins[p].name);
71            }
72        }
73
74        plot.hooks.bindEvents.push(function (plot, eventHolder) {
75
76            // get plot options
77            that.plotOptions = plot.getOptions();
78
79            // for legacy (<0.8.5) implementations
80            if (typeof(that.plotOptions.tooltip) === 'boolean') {
81                that.plotOptions.tooltipOpts.show = that.plotOptions.tooltip;
82                that.plotOptions.tooltip = that.plotOptions.tooltipOpts;
83                delete that.plotOptions.tooltipOpts;
84            }
85
86            // if not enabled return
87            if (that.plotOptions.tooltip.show === false || typeof that.plotOptions.tooltip.show === 'undefined') return;
88
89            // shortcut to access tooltip options
90            that.tooltipOptions = that.plotOptions.tooltip;
91
92            if (that.tooltipOptions.$compat) {
93                that.wfunc = 'width';
94                that.hfunc = 'height';
95            } else {
96                that.wfunc = 'innerWidth';
97                that.hfunc = 'innerHeight';
98            }
99
100            // create tooltip DOM element
101            var $tip = that.getDomElement();
102
103            // bind event
104            $( plot.getPlaceholder() ).bind("plothover", plothover);
105            if (that.tooltipOptions.clickTips) {
106                $( plot.getPlaceholder() ).bind("plotclick", plotclick);
107            }
108            that.clickmode = false;
109
110            $(eventHolder).bind('mousemove', mouseMove);
111        });
112
113        plot.hooks.shutdown.push(function (plot, eventHolder){
114            $(plot.getPlaceholder()).unbind("plothover", plothover);
115            $(plot.getPlaceholder()).unbind("plotclick", plotclick);
116            plot.removeTooltip();
117            $(eventHolder).unbind("mousemove", mouseMove);
118        });
119
120        function mouseMove(e){
121            var pos = {};
122            pos.x = e.pageX;
123            pos.y = e.pageY;
124            plot.setTooltipPosition(pos);
125        }
126
127        /**
128         *  open the tooltip (if not already open) and freeze it on the current position till the next click
129         */
130        function plotclick(event, pos, item) {
131            if (! that.clickmode) {
132                // it is the click activating the clicktip
133                plothover(event, pos, item);
134                if (that.getDomElement().is(":visible")) {
135                    $(plot.getPlaceholder()).unbind("plothover", plothover);
136                    that.clickmode = true;
137                }
138            } else {
139                // it is the click deactivating the clicktip
140                $( plot.getPlaceholder() ).bind("plothover", plothover);
141                plot.hideTooltip();
142                that.clickmode = false;
143            }
144        }
145
146        function plothover(event, pos, item) {
147            // Simple distance formula.
148            var lineDistance = function (p1x, p1y, p2x, p2y) {
149                return Math.sqrt((p2x - p1x) * (p2x - p1x) + (p2y - p1y) * (p2y - p1y));
150            };
151
152            // Here is some voodoo magic for determining the distance to a line form a given point {x, y}.
153            var dotLineLength = function (x, y, x0, y0, x1, y1, o) {
154                if (o && !(o =
155                    function (x, y, x0, y0, x1, y1) {
156                        if (typeof x0 !== 'undefined') return { x: x0, y: y };
157                        else if (typeof y0 !== 'undefined') return { x: x, y: y0 };
158
159                        var left,
160                            tg = -1 / ((y1 - y0) / (x1 - x0));
161
162                        return {
163                            x: left = (x1 * (x * tg - y + y0) + x0 * (x * -tg + y - y1)) / (tg * (x1 - x0) + y0 - y1),
164                            y: tg * left - tg * x + y
165                        };
166                    } (x, y, x0, y0, x1, y1),
167                    o.x >= Math.min(x0, x1) && o.x <= Math.max(x0, x1) && o.y >= Math.min(y0, y1) && o.y <= Math.max(y0, y1))
168                ) {
169                    var l1 = lineDistance(x, y, x0, y0), l2 = lineDistance(x, y, x1, y1);
170                    return l1 > l2 ? l2 : l1;
171                } else {
172                    var a = y0 - y1, b = x1 - x0, c = x0 * y1 - y0 * x1;
173                    return Math.abs(a * x + b * y + c) / Math.sqrt(a * a + b * b);
174                }
175            };
176
177            if (item) {
178                plot.showTooltip(item, that.tooltipOptions.snap ? item : pos);
179            } else if (that.plotOptions.series.lines.show && that.tooltipOptions.lines === true) {
180                var maxDistance = that.plotOptions.grid.mouseActiveRadius;
181
182                var closestTrace = {
183                    distance: maxDistance + 1
184                };
185
186                var ttPos = pos;
187
188                $.each(plot.getData(), function (i, series) {
189                    var xBeforeIndex = 0,
190                        xAfterIndex = -1;
191
192                    // Our search here assumes our data is sorted via the x-axis.
193                    // TODO: Improve efficiency somehow - search smaller sets of data.
194                    for (var j = 1; j < series.data.length; j++) {
195                        if (series.data[j - 1][0] <= pos.x && series.data[j][0] >= pos.x) {
196                            xBeforeIndex = j - 1;
197                            xAfterIndex = j;
198                        }
199                    }
200
201                    if (xAfterIndex === -1) {
202                        plot.hideTooltip();
203                        return;
204                    }
205
206                    var pointPrev = { x: series.data[xBeforeIndex][0], y: series.data[xBeforeIndex][1] },
207                        pointNext = { x: series.data[xAfterIndex][0], y: series.data[xAfterIndex][1] };
208
209                    var distToLine = dotLineLength(series.xaxis.p2c(pos.x), series.yaxis.p2c(pos.y), series.xaxis.p2c(pointPrev.x),
210                        series.yaxis.p2c(pointPrev.y), series.xaxis.p2c(pointNext.x), series.yaxis.p2c(pointNext.y), false);
211
212                    if (distToLine < closestTrace.distance) {
213
214                        var closestIndex = lineDistance(pointPrev.x, pointPrev.y, pos.x, pos.y) <
215                            lineDistance(pos.x, pos.y, pointNext.x, pointNext.y) ? xBeforeIndex : xAfterIndex;
216
217                        var pointSize = series.datapoints.pointsize;
218
219                        // Calculate the point on the line vertically closest to our cursor.
220                        var pointOnLine = [
221                            pos.x,
222                            pointPrev.y + ((pointNext.y - pointPrev.y) * ((pos.x - pointPrev.x) / (pointNext.x - pointPrev.x)))
223                        ];
224
225                        var item = {
226                            datapoint: pointOnLine,
227                            dataIndex: closestIndex,
228                            series: series,
229                            seriesIndex: i
230                        };
231
232                        closestTrace = {
233                            distance: distToLine,
234                            item: item
235                        };
236
237                        if (that.tooltipOptions.snap) {
238                            ttPos = {
239                                pageX: series.xaxis.p2c(pointOnLine[0]),
240                                pageY: series.yaxis.p2c(pointOnLine[1])
241                            };
242                        }
243                    }
244                });
245
246                if (closestTrace.distance < maxDistance + 1)
247                    plot.showTooltip(closestTrace.item, ttPos);
248                else
249                    plot.hideTooltip();
250            } else {
251                plot.hideTooltip();
252            }
253        }
254
255        // Quick little function for setting the tooltip position.
256        plot.setTooltipPosition = function (pos) {
257            var $tip = that.getDomElement();
258
259            var totalTipWidth = $tip.outerWidth() + that.tooltipOptions.shifts.x;
260            var totalTipHeight = $tip.outerHeight() + that.tooltipOptions.shifts.y;
261            if ((pos.x - $(window).scrollLeft()) > ($(window)[that.wfunc]() - totalTipWidth)) {
262                pos.x -= totalTipWidth;
263                pos.x = Math.max(pos.x, 0);
264            }
265            if ((pos.y - $(window).scrollTop()) > ($(window)[that.hfunc]() - totalTipHeight)) {
266                pos.y -= totalTipHeight;
267            }
268
269	    /*
270	       The section applies the new positioning ONLY if pos.x and pos.y
271	       are numbers. If they are undefined or not a number, use the last
272	       known numerical position. This hack fixes a bug that kept pie
273	       charts from keeping their tooltip positioning.
274	     */
275
276            if (isNaN(pos.x)) {
277		that.tipPosition.x = that.tipPosition.xPrev;
278	    }
279	    else {
280		that.tipPosition.x = pos.x;
281		that.tipPosition.xPrev = pos.x;
282	    }
283	    if (isNaN(pos.y)) {
284		that.tipPosition.y = that.tipPosition.yPrev;
285	    }
286	    else {
287		that.tipPosition.y = pos.y;
288		that.tipPosition.yPrev = pos.y;
289	    }
290
291        };
292
293        // Quick little function for showing the tooltip.
294        plot.showTooltip = function (target, position, targetPosition) {
295            var $tip = that.getDomElement();
296
297            // convert tooltip content template to real tipText
298            var tipText = that.stringFormat(that.tooltipOptions.content, target);
299            if (tipText === '')
300                return;
301
302            $tip.html(tipText);
303            plot.setTooltipPosition({ x: that.tipPosition.x, y: that.tipPosition.y });
304            $tip.css({
305                left: that.tipPosition.x + that.tooltipOptions.shifts.x,
306                top: that.tipPosition.y + that.tooltipOptions.shifts.y
307            }).show();
308
309            // run callback
310            if (typeof that.tooltipOptions.onHover === 'function') {
311                that.tooltipOptions.onHover(target, $tip);
312            }
313        };
314
315        // Quick little function for hiding the tooltip.
316        plot.hideTooltip = function () {
317            that.getDomElement().hide().html('');
318        };
319
320        plot.removeTooltip = function() {
321            that.getDomElement().remove();
322        };
323    };
324
325    /**
326     * get or create tooltip DOM element
327     * @return jQuery object
328     */
329    FlotTooltip.prototype.getDomElement = function () {
330        var $tip = $('<div>');
331        if (this.tooltipOptions && this.tooltipOptions.cssClass) {
332            $tip = $('.' + this.tooltipOptions.cssClass);
333
334            if( $tip.length === 0 ){
335                $tip = $('<div />').addClass(this.tooltipOptions.cssClass);
336                $tip.appendTo('body').hide().css({position: 'absolute'});
337
338                if(this.tooltipOptions.defaultTheme) {
339                    $tip.css({
340                        'background': '#fff',
341                        'z-index': '1040',
342                        'padding': '0.4em 0.6em',
343                        'border-radius': '0.5em',
344                        'font-size': '0.8em',
345                        'border': '1px solid #111',
346                        'display': 'none',
347                        'white-space': 'nowrap'
348                    });
349                }
350            }
351        }
352
353        return $tip;
354    };
355
356    /**
357     * core function, create tooltip content
358     * @param  {string} content - template with tooltip content
359     * @param  {object} item - Flot item
360     * @return {string} real tooltip content for current item
361     */
362    FlotTooltip.prototype.stringFormat = function (content, item) {
363        var percentPattern = /%p\.{0,1}(\d{0,})/;
364        var seriesPattern = /%s/;
365        var colorPattern = /%c/;
366        var xLabelPattern = /%lx/;
366 // requires flot-axislabels plugin https://github.com/markrcote/flot-axislabels, will be ignored if plugin isn't loaded
367        var yLabelPattern = /%ly/; // requires flot-axislabels plugin https://github.com/markrcote/flot-axislabels, will be ignored if plugin isn't loaded
368        var xPattern = /%x\.{0,1}(\d{0,})/;
369        var yPattern = /%y\.{0,1}(\d{0,})/;
370        var xPatternWithoutPrecision = "%x";
371        var yPatternWithoutPrecision = "%y";
372        var customTextPattern = "%ct";
373	var nPiePattern = "%n";
374
375        var x, y, customText, p, n;
376
377        // for threshold plugin we need to read data from different place
378        if (typeof item.series.threshold !== "undefined") {
379            x = item.datapoint[0];
380            y = item.datapoint[1];
381            customText = item.datapoint[2];
382	}
383
384	// for CurvedLines plugin we need to read data from different place
385	    else if (typeof item.series.curvedLines !== "undefined") {
386		x = item.datapoint[0];
387		y = item.datapoint[1];
388	    }
389
390        else if (typeof item.series.lines !== "undefined" && item.series.lines.steps) {
391            x = item.series.datapoints.points[item.dataIndex * 2];
392            y = item.series.datapoints.points[item.dataIndex * 2 + 1];
393            // TODO: where to find custom text in this variant?
394            customText = "";
395        } else {
396            x = item.series.data[item.dataIndex][0];
397            y = item.series.data[item.dataIndex][1];
398            customText = item.series.data[item.dataIndex][2];
399        }
400
401        // I think this is only in case of threshold plugin
402        if (item.series.label === null && item.series.originSeries) {
403            item.series.label = item.series.originSeries.label;
404        }
405
406        // if it is a function callback get the content string
407        if (typeof(content) === 'function') {
408            content = content(item.series.label, x, y, item);
409        }
410
411        // the case where the passed content is equal to false
412        if (typeof(content) === 'boolean' && !content) {
413            return '';
414        }
415
416        /* replacement of %ct and other multi-character templates must
417           precede the replacement of single-character templates
418           to avoid conflict between '%c' and '%ct'  and similar substrings
419        */
420        if (customText) {
421            content = content.replace(customTextPattern, customText);
422        }
423
424        // percent match for pie charts and stacked percent
425        if (typeof (item.series.percent) !== 'undefined') {
426            p = item.series.percent;
427        } else if (typeof (item.series.percents) !== 'undefined') {
428            p = item.series.percents[item.dataIndex];
429        }
430        if (typeof p === 'number') {
431            content = this.adjustValPrecision(percentPattern, content, p);
432        }
433
434        // replace %n with number of items represented by slice in pie charts
435        if (item.series.hasOwnProperty('pie')) {
436            if (typeof item.series.data[0][1] !== 'undefined') {
437                n = item.series.data[0][1];
438            }
439        }
440        if (typeof n === 'number') {
441            content = content.replace(nPiePattern, n);
442        }
443
444        // series match
445        if (typeof(item.series.label) !== 'undefined') {
446            content = content.replace(seriesPattern, item.series.label);
447        } else {
448            //remove %s if label is undefined
449            content = content.replace(seriesPattern, "");
450        }
451
452        // color match
453        if (typeof(item.series.color) !== 'undefined') {
454            content = content.replace(colorPattern, item.series.color);
455        } else {
456            //remove %s if color is undefined
457            content = content.replace(colorPattern, "");
458        }
459
460        // x axis label match
461        if (this.hasAxisLabel('xaxis', item)) {
462            content = content.replace(xLabelPattern, item.series.xaxis.options.axisLabel);
463        } else {
464            //remove %lx if axis label is undefined or axislabels plugin not present
465            content = content.replace(xLabelPattern, "");
466        }
467
468        // y axis label match
469        if (this.hasAxisLabel('yaxis', item)) {
470            content = content.replace(yLabelPattern, item.series.yaxis.options.axisLabel);
471        } else {
472            //remove %ly if axis label is undefined or axislabels plugin not present
473            content = content.replace(yLabelPattern, "");
474        }
475
476        // time mode axes with custom dateFormat
477        if (this.isTimeMode('xaxis', item) && this.isXDateFormat(item)) {
478            content = content.replace(xPattern, this.timestampToDate(x, this.tooltipOptions.xDateFormat, item.series.xaxis.options));
479        }
480        if (this.isTimeMode('yaxis', item) && this.isYDateFormat(item)) {
481            content = content.replace(yPattern, this.timestampToDate(y, this.tooltipOptions.yDateFormat, item.series.yaxis.options));
482        }
483
484        // set precision if defined
485        if (typeof x === 'number') {
486            content = this.adjustValPrecision(xPattern, content, x);
487        }
488        if (typeof y === 'number') {
489            content = this.adjustValPrecision(yPattern, content, y);
490        }
491
492        // change x from number to given label, if given
493        if (typeof item.series.xaxis.ticks !== 'undefined') {
494
495            var ticks;
496            if (this.hasRotatedXAxisTicks(item)) {
497                // xaxis.ticks will be an empty array if tickRotor is being used, but the values are available in rotatedTicks
498                ticks = 'rotatedTicks';
499            } else {
500                ticks = 'ticks';
501            }
502
503            // see https://github.com/krzysu/flot.tooltip/issues/65
504            var tickIndex = item.dataIndex + item.seriesIndex;
505
506            for (var xIndex in item.series.xaxis[ticks]) {
507                if (item.series.xaxis[ticks].hasOwnProperty(tickIndex) && !this.isTimeMode('xaxis', item)) {
508                    var valueX = (this.isCategoriesMode('xaxis', item)) ? item.series.xaxis[ticks][tickIndex].label : item.series.xaxis[ticks][tickIndex].v;
509                    if (valueX === x) {
510                        content = content.replace(xPattern, item.series.xaxis[ticks][tickIndex].label.replace(/\$/g, '$$$$'));
511                    }
512                }
513            }
514        }
515
516        // change y from number to given label, if given
517        if (typeof item.series.yaxis.ticks !== 'undefined') {
518            for (var yIndex in item.series.yaxis.ticks) {
519                if (item.series.yaxis.ticks.hasOwnProperty(yIndex)) {
520                    var valueY = (this.isCategoriesMode('yaxis', item)) ? item.series.yaxis.ticks[yIndex].label : item.series.yaxis.ticks[yIndex].v;
521                    if (valueY === y) {
522                        content = content.replace(yPattern, item.series.yaxis.ticks[yIndex].label.replace(/\$/g, '$$$$'));
523                    }
524                }
525            }
526        }
527
528        // if no value customization, use tickFormatter by default
529        if (typeof item.series.xaxis.tickFormatter !== 'undefined') {
530            //escape dollar
531            content = content.replace(xPatternWithoutPrecision, item.series.xaxis.tickFormatter(x, item.series.xaxis).replace(/\$/g, '$$'));
532        }
533        if (typeof item.series.yaxis.tickFormatter !== 'undefined') {
534            //escape dollar
535            content = content.replace(yPatternWithoutPrecision, item.series.yaxis.tickFormatter(y, item.series.yaxis).replace(/\$/g, '$$'));
536        }
537
538        return content;
539    };
540
541    // helpers just for readability
542    FlotTooltip.prototype.isTimeMode = function (axisName, item) {
543        return (typeof item.series[axisName].options.mode !== 'undefined' && item.series[axisName].options.mode === 'time');
544    };
545
546    FlotTooltip.prototype.isXDateFormat = function (item) {
547        return (typeof this.tooltipOptions.xDateFormat !== 'undefined' && this.tooltipOptions.xDateFormat !== null);
548    };
549
550    FlotTooltip.prototype.isYDateFormat = function (item) {
551        return (typeof this.tooltipOptions.yDateFormat !== 'undefined' && this.tooltipOptions.yDateFormat !== null);
552    };
553
554    FlotTooltip.prototype.isCategoriesMode = function (axisName, item) {
555        return (typeof item.series[axisName].options.mode !== 'undefined' && item.series[axisName].options.mode === 'categories');
556    };
557
558    //
559    FlotTooltip.prototype.timestampToDate = function (tmst, dateFormat, options) {
560        var theDate = $.plot.dateGenerator(tmst, options);
561        return $.plot.formatDate(theDate, dateFormat, this.tooltipOptions.monthNames, this.tooltipOptions.dayNames);
562    };
563
564    //
565    FlotTooltip.prototype.adjustValPrecision = function (pattern, content, value) {
566
567        var precision;
568        var matchResult = content.match(pattern);
569        if( matchResult !== null ) {
570            if(RegExp.$1 !== '') {
571                precision = RegExp.$1;
572                value = value.toFixed(precision);
573
574                // only replace content if precision exists, in other case use thickformater
575                content = content.replace(pattern, value);
576            }
577        }
578        return content;
579    };
580
581    // other plugins detection below
582
583    // check if flot-axislabels plugin (https://github.com/markrc
583ote/flot-axislabels) is used and that an axis label is given
584    FlotTooltip.prototype.hasAxisLabel = function (axisName, item) {
585        return ($.inArray('axisLabels', this.plotPlugins) !== -1 && typeof item.series[axisName].options.axisLabel !== 'undefined' && item.series[axisName].options.axisLabel.length > 0);
586    };
587
588    // check whether flot-tickRotor, a plugin which allows rotation of X-axis ticks, is being used
589    FlotTooltip.prototype.hasRotatedXAxisTicks = function (item) {
590        return ($.inArray('tickRotor',this.plotPlugins) !== -1 && typeof item.series.xaxis.rotatedTicks !== 'undefined');
591    };
592
593    //
594    var init = function (plot) {
595      new FlotTooltip(plot);
596    };
597
598    // define Flot plugin
599    $.plot.plugins.push({
600        init: init,
601        options: defaultOptions,
602        name: 'tooltip',
603        version: '0.8.5'
604    });
605
606})(jQuery);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.