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

yvfmudvl  于 2022-11-06  发布在  Chart.js
关注(0)|答案(3)|浏览(246)

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

我遇到的问题是工具提示。当我将鼠标悬停在饼图的浅青色部分上时,如果图表缩小,则工具提示会被中间的文本覆盖,如下所示:

我希望能够改变工具提示的延伸方向,使其不再向中心延伸,而是向远处移动,这样工具提示和中心分析都可见,但我还没有找到一个关于如何改变工具提示位置的简明解释。以下是我目前拥有的代码:

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

lyr7nygr1#

在以前的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方法能够实际工作,我们必须为每一个方法提供一个实现。2这里又是一个从源代码复制粘贴的例子。然而,我们唯一需要修改的方法是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,它展示了如何设置和使用一个自定义的工具提示。你可以使用这个方法,只是修改定位逻辑。

7fhtutme

7fhtutme2#

如果您有一个小的工具提示标签,您可以使用简单的chart.js选项来修复重叠问题:

plugins: {
  tooltip: {
    xAlign: 'center',
    yAlign: 'bottom'
  }
}
sqxo8psd

sqxo8psd3#

我设法解决了同样的问题,将甜甜圈 Package 器div的zIndex设置为1,将显示在甜甜圈中间的文本的zIndex设置为-1,画布默认是透明的。希望这能有所帮助。

相关问题