使用Chart.js更改甜甜圈图中的工具提示位置 [英] Change tooltip positioning in doughnut chart using Chart.js

查看:105
本文介绍了使用Chart.js更改甜甜圈图中的工具提示位置的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个使用Chart.js的甜甜圈图,可以正确显示我的应用程序的登录数据,但是我已经修改了该图,以使登录总数以文本形式显示在中心切口中:

I have a doughnut chart using Chart.js that displays login data for my app correctly, however I have modified the chart so that the total number of logins is displayed in text in the center cutout:

我遇到的问题与工具提示有关.当我将鼠标悬停在饼图的浅青色块上时,如果该图的比例缩小,则工具提示会与中间的文本重叠,如下所示:

The problem I am running into is with the tooltips. When I hover over the light teal piece of the pie chart, if the chart is scaled smaller, the tooltip is overlapped by the text in the center, like this:

我希望能够更改工具提示延伸的方向,因此它不会朝中心方向移动,而是移开以便工具提示和中心分析都可见,但是我还没有找到一个简洁的解释有关如何更改工具提示位置的信息.这是我当前拥有的代码:

I want to be able to change the direction the tooltip extends out, so instead of it going towards the center, it moves away so that both the tooltip and the center analytic are visible, but I have yet to find a concise explanation on how to change tooltip positioning. Here is the code I have currently:

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

推荐答案

过去,在chart.js的早期版本(v2.3及更低版本)中,反转工具提示要容易得多.您要做的就是覆盖 determineAlignment 工具提示方法并反转逻辑.

It used to be a lot easier to reverse the tooltips in previous versions of chart.js (v2.3 and before). All you had to do was overwrite the determineAlignment tooltip method and reverse the logic.

但是,从v2.4开始,用于计算工具提示位置的函数(包括 determineAlignment )已设为私有,因此不再有一种方法可以简单地覆盖它们(相反,您必须复制它们).

However starting in v2.4, the functions that calculate the tooltip positions (including determineAlignment) were made private, so there is no longer a way to simply overwrite them (instead you have to duplicate them).

这是一个可行的反向工具提示解决方案,不幸的是,需要从chart.js源进行大量复制和粘贴(这是必需的,因为方法是私有的).这种方法的风险在于,潜在的私有功能可能会随时在新版本中更改,并且新的反向工具提示可能会意外中断.

Here is a working reversed tooltip solution that unfortunately requires a lot of copy and paste from the chart.js source (this is required since the methods are private). The risk with this approach is that the underlying private functions could change in new releases at any time and your new reverse tooltip could break unexpectedly.

话虽如此,这里是实现的完整过程(底部有一个Codepen示例).

With that said, here is walk through of the implementation (with a codepen example at the bottom).

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

1) First, let's extend the Chart.Tooltip object and create a new Chart.ReversedTooltip object. We really only need to overwrite the update method since it performs all the positioning logic. In fact, this overwrite is just a straight copy and paste from the source because we actually only need to modify the private determineAlignment method which is called by update.

// 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 方法使用了一些私有方法(例如, getBaseModel createTooltipItem determineAlignment等).为了使我们的 update 方法真正起作用,我们必须为每个方法提供一个实现.这又是源中的另一个副本和粘贴.但是,我们唯一需要修改的方法是 determineAlignment 方法.这是修改后的版本,它颠倒了对齐逻辑.

2) As you can see, the update method uses a handful of private methods (e.g. getBaseModel, createTooltipItem, determineAlignment, etc.). In order for our update method to actually work, we have to provide an implementation for each of these methods. Here again is another copy and paste from the source. The only method that we need to modify however is the determineAlignment method. Here is the modified version that reverses the alignment logic.

// 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 插件方法来做到这一点.

3) Now that our new Chart.ReversedTooltip is complete, we need to use the plugin system to change the original tooltip to our reversed tooltip. We can do this using the afterInit plugin method.

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 中查看完整的工作示例.

After all that, we finally have reversed tooltips! Checkout a full working example at this codepen.

值得一提的是,这种方法非常脆弱,并且正如我提到的那样,可以很容易地超时(由于需要复制和粘贴).另一种选择是仅使用自定义工具提示,然后将其放置在图表上的任意位置.

It's also worth mentioning that this approach is very brittle and, as I mentioned, can easily break overtime (on account of the copy and pasting required). Another option would be to just use a custom tooltip instead and position it wherever you desire on the chart.

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

Checkout this chart.js sample that shows how to setup and use a custom tooltip. You could go with this approach and just modify the positioning logic.

这篇关于使用Chart.js更改甜甜圈图中的工具提示位置的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆