| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250 | /* * * Highcharts module to hide overlapping data labels. This module is included in * Highcharts. * * (c) 2009-2019 Torstein Honsi * * License: www.highcharts.com/license */'use strict';import H from '../parts/Globals.js';import '../parts/Utilities.js';import '../parts/Chart.js';var Chart = H.Chart,    isArray = H.isArray,    objectEach = H.objectEach,    pick = H.pick,    addEvent = H.addEvent,    fireEvent = H.fireEvent;// Collect potensial overlapping data labels. Stack labels probably don't need// to be considered because they are usually accompanied by data labels that lie// inside the columns.addEvent(Chart, 'render', function collectAndHide() {    var labels = [];    // Consider external label collectors    (this.labelCollectors || []).forEach(function (collector) {        labels = labels.concat(collector());    });    (this.yAxis || []).forEach(function (yAxis) {        if (            yAxis.options.stackLabels &&            !yAxis.options.stackLabels.allowOverlap        ) {            objectEach(yAxis.stacks, function (stack) {                objectEach(stack, function (stackItem) {                    labels.push(stackItem.label);                });            });        }    });    (this.series || []).forEach(function (series) {        var dlOptions = series.options.dataLabels;        if (            series.visible &&            !(dlOptions.enabled === false && !series._hasPointLabels)        ) { // #3866            series.points.forEach(function (point) {                if (point.visible) {                    var dataLabels = (                        isArray(point.dataLabels) ?                            point.dataLabels :                            (point.dataLabel ? [point.dataLabel] : [])                    );                    dataLabels.forEach(function (label) {                        var options = label.options;                        label.labelrank = pick(                            options.labelrank,                            point.labelrank,                            point.shapeArgs && point.shapeArgs.height                        ); // #4118                        if (!options.allowOverlap) {                            labels.push(label);                        }                    });                }            });        }    });    this.hideOverlappingLabels(labels);});/** * Hide overlapping labels. Labels are moved and faded in and out on zoom to * provide a smooth visual imression. * * @private * @function Highcharts.Chart#hideOverlappingLabels * * @param {Array<Highcharts.SVGElement>} labels */Chart.prototype.hideOverlappingLabels = function (labels) {    var chart = this,        len = labels.length,        ren = chart.renderer,        label,        i,        j,        label1,        label2,        isIntersecting,        box1,        box2,        intersectRect = function (x1, y1, w1, h1, x2, y2, w2, h2) {            return !(                x2 > x1 + w1 ||                x2 + w2 < x1 ||                y2 > y1 + h1 ||                y2 + h2 < y1            );        },        // Get the box with its position inside the chart, as opposed to getBBox        // that only reports the position relative to the parent.        getAbsoluteBox = function (label) {            var pos,                parent,                bBox,                // Substract the padding if no background or border (#4333)                padding = label.box ? 0 : (label.padding || 0),                lineHeightCorrection = 0;            if (                label &&                (!label.alignAttr || label.placed)            ) {                pos = label.alignAttr || {                    x: label.attr('x'),                    y: label.attr('y')                };                parent = label.parentGroup;                // Get width and height if pure text nodes (stack labels)                if (!label.width) {                    bBox = label.getBBox();                    label.width = bBox.width;                    label.height = bBox.height;                    // Labels positions are computed from top left corner, so                    // we need to substract the text height from text nodes too.                    lineHeightCorrection = ren                        .fontMetrics(null, label.element).h;                }                return {                    x: pos.x + (parent.translateX || 0) + padding,                    y: pos.y + (parent.translateY || 0) + padding -                        lineHeightCorrection,                    width: label.width - 2 * padding,                    height: label.height - 2 * padding                };            }        };    for (i = 0; i < len; i++) {        label = labels[i];        if (label) {            // Mark with initial opacity            label.oldOpacity = label.opacity;            label.newOpacity = 1;            label.absoluteBox = getAbsoluteBox(label);        }    }    // Prevent a situation in a gradually rising slope, that each label will    // hide the previous one because the previous one always has lower rank.    labels.sort(function (a, b) {        return (b.labelrank || 0) - (a.labelrank || 0);    });    // Detect overlapping labels    for (i = 0; i < len; i++) {        label1 = labels[i];        box1 = label1 && label1.absoluteBox;        for (j = i + 1; j < len; ++j) {            label2 = labels[j];            box2 = label2 && label2.absoluteBox;            if (                box1 &&                box2 &&                label1 !== label2 && // #6465, polar chart with connectEnds                label1.newOpacity !== 0 &&                label2.newOpacity !== 0            ) {                isIntersecting = intersectRect(                    box1.x,                    box1.y,                    box1.width,                    box1.height,                    box2.x,                    box2.y,                    box2.width,                    box2.height                );                if (isIntersecting) {                    (label1.labelrank < label2.labelrank ? label1 : label2)                        .newOpacity = 0;                }            }        }    }    // Hide or show    labels.forEach(function (label) {        var complete,            newOpacity;        if (label) {            newOpacity = label.newOpacity;            if (label.oldOpacity !== newOpacity) {                // Make sure the label is completely hidden to avoid catching                // clicks (#4362)                if (label.alignAttr && label.placed) { // data labels                    if (newOpacity) {                        label.show(true);                    } else {                        complete = function () {                            label.hide();                        };                    }                    // Animate or set the opacity                    label.alignAttr.opacity = newOpacity;                    label[label.isOld ? 'animate' : 'attr'](                        label.alignAttr,                        null,                        complete                    );                    fireEvent(chart, 'afterHideOverlappingLabels');                } else { // other labels, tick labels                    label.attr({                        opacity: newOpacity                    });                }            }            label.isOld = true;        }    });};
 |