首页 文章

使用Chart.js更改圆环图中的工具提示位置

提问于
浏览
0

我有一个使用Chart.js的圆环图,它正确显示了我的应用程序的登录数据,但是我修改了图表,以便在中心剪切块的文本中显示登录总数:

enter image description here

我遇到的问题是工具提示 . 当我将鼠标悬停在饼图的浅青色片上时,如果图表缩小,则工具提示与中心的文本重叠,如下所示:

enter image description here

我希望能够改变工具提示向外延伸的方向,因此它不会向中心移动,而是移开,以便工具提示和中心分析都可见,但我还没有找到关于如何的简明解释改变工具提示的定位 . 这是我目前的代码:

var loslogged = dataset[0][0].loslogged;
var realtorlogged = dataset[1][0].realtorlogged;
var borrowerlogged = dataset[2][0].borrowerlogged;

var totallogged = parseInt(loslogged) + parseInt(realtorlogged) + parseInt(borrowerlogged);

Chart.pluginService.register({
    afterDraw: function (chart) {
        if (chart.config.options.elements.center) {
            var helpers = Chart.helpers;
            var centerX = (chart.chartArea.left + chart.chartArea.right) / 2;
            var centerY = (chart.chartArea.top + chart.chartArea.bottom) / 2;

            var ctx = chart.chart.ctx;
            ctx.save();
            var fontSize = helpers.getValueOrDefault(chart.config.options.elements.center.fontSize, Chart.defaults.global.defaultFontSize);
            var fontStyle = helpers.getValueOrDefault(chart.config.options.elements.center.fontStyle, Chart.defaults.global.defaultFontStyle);
            var fontFamily = helpers.getValueOrDefault(chart.config.options.elements.center.fontFamily, Chart.defaults.global.defaultFontFamily);
            var font = helpers.fontString(fontSize, fontStyle, fontFamily);
            ctx.font = font;
            ctx.fillStyle = helpers.getValueOrDefault(chart.config.options.elements.center.fontColor, Chart.defaults.global.defaultFontColor);
            ctx.textAlign = 'center';
            ctx.textBaseline = 'middle';
            ctx.fillText(chart.config.options.elements.center.text, centerX, centerY);
            ctx.restore();
        }
    }
});

var loginChartData = {
    labels: ["Loan Officers","Realtors","Borrowers"],
    datasets: [{
        label: "Number of Logins",
        data: [loslogged, realtorlogged, borrowerlogged],
        backgroundColor: [
            "rgba(191, 25, 25, 0.75)",
            "rgba(58, 73, 208, 0.75)",
            "rgba(79, 201, 188, 0.75)"
        ],
        borderColor: [
            "rgba(255, 255, 255, 1)",
            "rgba(255, 255, 255, 1)",
            "rgba(255, 255, 255, 1)"
        ],
        borderWidth: 4
    }],
    gridLines: {
        display: false
    }
};

var loginChartOptions = {
    title: {
        display: false
    },
    cutoutPercentage: 50,
    elements: {
        center: {
            text: totallogged,
            fontColor: '#000',
            fontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
            fontSize: 36,
            fontStyle: 'bold'
        }
    }
};

var loginChart = document.getElementById('loginsChart').getContext('2d');
new Chart(loginChart, {
    type: 'doughnut',
    data: loginChartData,
    options: loginChartOptions
});

1 回答

  • 1

    在以前版本的chart.js(v2.3及之前版本)中,反转工具提示比以前容易得多 . 您所要做的就是覆盖 determineAlignment 工具提示方法并反转逻辑 .

    但是从v2.4开始,计算工具提示位置的函数(包括 determineAlignment )是私有的,因此不再能够简单地覆盖它们(而是必须复制它们) .

    这是一个工作的反向工具提示解决方案,遗憾的是需要从chart.js源中进行大量复制和粘贴(这是必需的,因为这些方法是私有的) . 这种方法的风险在于,底层私有函数可能随时在新版本中发生变化,并且您的新反向工具提示可能会意外中断 .

    话虽如此,这里是实现的步骤(在底部有一个codepen示例) .

    1)首先,让我们扩展 Chart.Tooltip 对象并创建一个新的 Chart.ReversedTooltip 对象 . 我们实际上只需要覆盖 update 方法,因为它执行所有定位逻辑 . 实际上,这种覆盖只是源代码的直接复制和粘贴,因为我们实际上只需要修改 update 调用的私有 determineAlignment 方法 .

    // create a new reversed tooltip.  we must overwrite the update method which is
    // where all the positioning occurs
    Chart.ReversedTooltip = Chart.Tooltip.extend({
      update: function(changed) {
        var me = this;
        var opts = me._options;
    
        // Need to regenerate the model because its faster than using extend and it is necessary due to the optimization in Chart.Element.transition
        // that does _view = _model if ease === 1. This causes the 2nd tooltip update to set properties in both the view and model at the same time
        // which breaks any animations.
        var existingModel = me._model;
        var model = me._model = getBaseModel(opts);
        var active = me._active;
    
        var data = me._data;
        var chartInstance = me._chartInstance;
    
        // In the case where active.length === 0 we need to keep these at existing values for good animations
        var alignment = {
          xAlign: existingModel.xAlign,
          yAlign: existingModel.yAlign
        };
        var backgroundPoint = {
          x: existingModel.x,
          y: existingModel.y
        };
        var tooltipSize = {
          width: existingModel.width,
          height: existingModel.height
        };
        var tooltipPosition = {
          x: existingModel.caretX,
          y: existingModel.caretY
        };
    
        var i, len;
    
        if (active.length) {
          model.opacity = 1;
    
          var labelColors = [];
          tooltipPosition = Chart.Tooltip.positioners[opts.position](active, me._eventPosition);
    
          var tooltipItems = [];
          for (i = 0, len = active.length; i < len; ++i) {
            tooltipItems.push(createTooltipItem(active[i]));
          }
    
          // If the user provided a filter function, use it to modify the tooltip items
          if (opts.filter) {
            tooltipItems = tooltipItems.filter(function(a) {
              return opts.filter(a, data);
            });
          }
    
          // If the user provided a sorting function, use it to modify the tooltip items
          if (opts.itemSort) {
            tooltipItems = tooltipItems.sort(function(a, b) {
              return opts.itemSort(a, b, data);
            });
          }
    
          // Determine colors for boxes
          helpers.each(tooltipItems, function(tooltipItem) {
            labelColors.push(opts.callbacks.labelColor.call(me, tooltipItem, chartInstance));
          });
    
          // Build the Text Lines
          model.title = me.getTitle(tooltipItems, data);
          model.beforeBody = me.getBeforeBody(tooltipItems, data);
          model.body = me.getBody(tooltipItems, data);
          model.afterBody = me.getAfterBody(tooltipItems, data);
          model.footer = me.getFooter(tooltipItems, data);
    
          // Initial positioning and colors
          model.x = Math.round(tooltipPosition.x);
          model.y = Math.round(tooltipPosition.y);
          model.caretPadding = helpers.getValueOrDefault(tooltipPosition.padding, 2);
          model.labelColors = labelColors;
    
          // data points
          model.dataPoints = tooltipItems;
    
          // We need to determine alignment of the tooltip
          tooltipSize = getTooltipSize(this, model);
          alignment = determineAlignment(this, tooltipSize);
          // Final Size and Position
          backgroundPoint = getBackgroundPoint(model, tooltipSize, alignment);
        } else {
          model.opacity = 0;
        }
    
        model.xAlign = alignment.xAlign;
        model.yAlign = alignment.yAlign;
        model.x = backgroundPoint.x;
        model.y = backgroundPoint.y;
        model.width = tooltipSize.width;
        model.height = tooltipSize.height;
    
        // Point where the caret on the tooltip points to
        model.caretX = tooltipPosition.x;
        model.caretY = tooltipPosition.y;
    
        me._model = model;
    
        if (changed && opts.custom) {
          opts.custom.call(me, model);
        }
    
        return me;
      },
    });
    

    2)如您所见, update 方法使用少数私有方法(例如 getBaseModelcreateTooltipItemdetermineAlignment 等) . 为了使我们的 update 方法能够实际工作,我们必须为每个方法提供一个实现 . 这里再次是来自源的另一个复制和粘贴 . 然而,我们需要修改的唯一方法是 determineAlignment 方法 . 以下是反转对齐逻辑的修改版本 .

    // modified from source to reverse the position
    function determineAlignment(tooltip, size) {
      var model = tooltip._model;
      var chart = tooltip._chart;
      var chartArea = tooltip._chartInstance.chartArea;
      var xAlign = 'center';
      var yAlign = 'center';
    
      // set caret position to top or bottom if tooltip y position will extend outsite the chart top/bottom
      if (model.y < size.height) {
        yAlign = 'top';
      } else if (model.y > (chart.height - size.height)) {
        yAlign = 'bottom';
      }
    
      var leftAlign, rightAlign; // functions to determine left, right alignment
      var overflowLeft, overflowRight; // functions to determine if left/right alignment causes tooltip to go outside chart
      var yAlign; // function to get the y alignment if the tooltip goes outside of the left or right edges
      var midX = (chartArea.left + chartArea.right) / 2;
      var midY = (chartArea.top + chartArea.bottom) / 2;
    
      if (yAlign === 'center') {
        leftAlign = function(x) {
          return x >= midX;
        };
        rightAlign = function(x) {
          return x < midX;
        };
      } else {
        leftAlign = function(x) {
          return x <= (size.width / 2);
        };
        rightAlign = function(x) {
          return x >= (chart.width - (size.width / 2));
        };
      }
    
      overflowLeft = function(x) {
        return x - size.width < 0;
      };
      overflowRight = function(x) {
        return x + size.width > chart.width;
      };
      yAlign = function(y) {
        return y <= midY ? 'bottom' : 'top';
      };
    
      if (leftAlign(model.x)) {
        xAlign = 'left';
    
        // Is tooltip too wide and goes over the right side of the chart.?
        if (overflowLeft(model.x)) {
          xAlign = 'center';
          yAlign = yAlign(model.y);
        }
      } else if (rightAlign(model.x)) {
        xAlign = 'right';
    
        // Is tooltip too wide and goes outside left edge of canvas?
        if (overflowRight(model.x)) {
          xAlign = 'center';
          yAlign = yAlign(model.y);
        }
      }
    
      var opts = tooltip._options;
      return {
        xAlign: opts.xAlign ? opts.xAlign : xAlign,
        yAlign: opts.yAlign ? opts.yAlign : yAlign
      };
    };
    

    3)现在我们的新 Chart.ReversedTooltip 已经完成,我们需要使用插件系统将原始工具提示更改为我们的反向工具提示 . 我们可以使用 afterInit 插件方法完成此操作 .

    Chart.plugins.register({
      afterInit: function (chartInstance) {
        // replace the original tooltip with the reversed tooltip
        chartInstance.tooltip = new Chart.ReversedTooltip({
          _chart: chartInstance.chart,
          _chartInstance: chartInstance,
          _data: chartInstance.data,
          _options: chartInstance.options.tooltips
        }, chartInstance);
    
        chartInstance.tooltip.initialize();
      }
    });
    

    毕竟,我们终于颠倒了工具提示!在这个codepen查看完整的工作示例 .

    还值得一提的是,这种方法非常脆弱,正如我所提到的,可以很容易地超时(因为需要复制和粘贴) . 另一种选择是只使用自定义工具提示,并将其放置在图表上的任何位置 .

    查看此chart.js sample,其中显示了如何设置和使用自定义工具提示 . 你可以采用这种方法,只需修改定位逻辑 .

相关问题