chartJS v.2上的圆角 - 条形图(带负值)

时间:2017-11-08 17:40:46

标签: javascript canvas charts chart.js quadratic-curve

我正在设计一些chartjs条形图,这些条形图需要在条形图值(而不是它们的基础)处使用圆角。在大多数情况下,这意味着条形顶部的圆角但是也存在条形图具有负值的情况。

我在这里使用了答案:How to create rounded bars for Bar Chart.js v2?由jordanwillis https://stackoverflow.com/users/7581592/jordanwillis提供,当值为正值时效果很好,但值不为时则效果不佳。

参见附件示例: bars with negative values get inverted corners

我需要更新扩展名,以便画布中的quadraticCurves正确计算负数。

2 个答案:

答案 0 :(得分:0)

我也面临同样的问题。 使用下面的代码表示圆角 -

Chart.elements.Rectangle.prototype.draw = function () {

      var ctx = this._chart.ctx;
      var vm = this._view;
      var left, right, top, bottom, signX, signY, borderSkipped, radius;
      var borderWidth = vm.borderWidth;
     // Set Radius Here
      // If radius is large enough to cause drawing errors a max radius is imposed
      var cornerRadius = 20;

      if (!vm.horizontal) {
        // bar
        left = vm.x - vm.width / 2;
        right = vm.x + vm.width / 2;
        top = vm.y;
        bottom = vm.base;
        signX = 1;
        signY = bottom > top ? 1 : -1;
        borderSkipped = vm.borderSkipped || 'bottom';
      } else {
        // horizontal bar
        left = vm.base;
        right = vm.x;
        top = vm.y - vm.height / 2;
        bottom = vm.y + vm.height / 2;
        signX = right > left ? 1 : -1;
        signY = 1;
        borderSkipped = vm.borderSkipped || 'left';
      }

      // Canvas doesn't allow us to stroke inside the width so we can
      // adjust the sizes to fit if we're setting a stroke on the line
      if (borderWidth) {
        // borderWidth shold be less than bar width and bar height.
        var barSize = Math.min(Math.abs(left - right), Math.abs(top - bottom));
        borderWidth = borderWidth > barSize ? barSize : borderWidth;
        var halfStroke = borderWidth / 2;
        // Adjust borderWidth when bar top position is near vm.base(zero).
        var borderLeft = left + (borderSkipped !== 'left' ? halfStroke * signX : 0);
        var borderRight = right + (borderSkipped !== 'right' ? -halfStroke * signX : 0);
        var borderTop = top + (borderSkipped !== 'top' ? halfStroke * signY : 0);
        var borderBottom = bottom + (borderSkipped !== 'bottom' ? -halfStroke * signY : 0);
        // not become a vertical line?
        if (borderLeft !== borderRight) {
          top = borderTop;
          bottom = borderBottom;
        }
        // not become a horizontal line?
        if (borderTop !== borderBottom) {
          left = borderLeft;
          right = borderRight;
        }
      }

      ctx.beginPath();
      ctx.fillStyle = vm.backgroundColor;
      ctx.strokeStyle = vm.borderColor;
      ctx.lineWidth = borderWidth;

      // Corner points, from bottom-left to bottom-right clockwise
      // | 1 2 |
      // | 0 3 |
      var corners = [
        [left, bottom],
        [left, top],
        [right, top],
        [right, bottom]
      ];

      // Find first (starting) corner with fallback to 'bottom'
      var borders = ['bottom', 'left', 'top', 'right'];
      var startCorner = borders.indexOf(borderSkipped, 0);
      if (startCorner === -1) {
        startCorner = 0;
      }

      function cornerAt(index) {
        return corners[(startCorner + index) % 4];
      }

      // Draw rectangle from 'startCorner'
      var corner = cornerAt(0);
      var width, height, x, y, nextCorner, nextCornerId
      var x_tl, x_tr, y_tl, y_tr;
      var x_bl, x_br, y_bl, y_br;
      ctx.moveTo(corner[0], corner[1]);

      for (var i = 1; i < 4; i++) {
        corner = cornerAt(i);
        nextCornerId = i + 1;
        if (nextCornerId == 4) {
          nextCornerId = 0
        }

        nextCorner = cornerAt(nextCornerId);

        width = corners[2][0] - corners[1][0];
        height = corners[0][1] - corners[1][1];
        x = corners[1][0];
        y = corners[1][1];

        radius = cornerRadius;

        // Fix radius being too large        
        if (radius > Math.abs(height) / 2) {
          radius = Math.floor(Math.abs(height) / 2);
        }
        if (radius > Math.abs(width) / 2) {
          radius = Math.floor(Math.abs(width) / 2);
        }

        if (height < 0) {
          // Negative values in a standard bar chart
          x_tl = x; x_tr = x + width;
          y_tl = y + height; y_tr = y + height;

          x_bl = x; x_br = x + width;
          y_bl = y; y_br = y;

          // Draw
          ctx.moveTo(x_bl + radius, y_bl);
          ctx.lineTo(x_br - radius, y_br);
          ctx.quadraticCurveTo(x_br, y_br, x_br, y_br - radius);
          ctx.lineTo(x_tr, y_tr + radius);
          ctx.quadraticCurveTo(x_tr, y_tr, x_tr - radius, y_tr);
          ctx.lineTo(x_tl + radius, y_tl);
          ctx.quadraticCurveTo(x_tl, y_tl, x_tl, y_tl + radius);
          ctx.lineTo(x_bl, y_bl - radius);
          ctx.quadraticCurveTo(x_bl, y_bl, x_bl + radius, y_bl);

        } else if (width < 0) {
          // Negative values in a horizontal bar chart
          x_tl = x + width; x_tr = x;
          y_tl = y; y_tr = y;

          x_bl = x + width; x_br = x;
          y_bl = y + height; y_br = y + height;

          // Draw
          ctx.moveTo(x_bl + radius, y_bl);
          ctx.lineTo(x_br - radius, y_br);
          ctx.quadraticCurveTo(x_br, y_br, x_br, y_br - radius);
          ctx.lineTo(x_tr, y_tr + radius);
          ctx.quadraticCurveTo(x_tr, y_tr, x_tr - radius, y_tr);
          ctx.lineTo(x_tl + radius, y_tl);
          ctx.quadraticCurveTo(x_tl, y_tl, x_tl, y_tl + radius);
          ctx.lineTo(x_bl, y_bl - radius);
          ctx.quadraticCurveTo(x_bl, y_bl, x_bl + radius, y_bl);

        } else {
          //Positive Value
          ctx.moveTo(x + radius, y);
          ctx.lineTo(x + width - radius, y);
          ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
          ctx.lineTo(x + width, y + height - radius);
          ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
          ctx.lineTo(x + radius, y + height);
          ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
          ctx.lineTo(x, y + radius);
          ctx.quadraticCurveTo(x, y, x + radius, y);
        }
      }

      ctx.fill();
      if (borderWidth) {
        ctx.stroke();
      }
    };

我将它用于水平条形图。它将绘制如下图所示的图表 - enter image description here

答案 1 :(得分:0)

您可以通过自定义cornerRadius向条形图添加Chart.elements.Rectangle.prototype.draw选项来实现此目的,这样,每当您需要条形的边框半径时,都可以将其作为options参数传递。

如果您想要 borderRadius = 8

,则您的选项对象将与以下对象匹配
const options = {
  maintainAspectRatio: false,
  cornerRadius: 8,
};

将以下内容导入您的代码:
P.S。如果您不使用React,只需从您正在使用的任何库中导入它,并以相同的方式覆盖draw函数!

// Custom rewrite for radius border on bar draw
// Code from https://github.com/jedtrow/Chart.js-Rounded-Bar-Charts with a few adjustments

import { Chart as ChartJS } from 'react-chartjs-2';

ChartJS.elements.Rectangle.prototype.draw = function ReDraw() {
  const { ctx } = this._chart;
  const vm = this._view;
  let left; let right; let top; let bottom; let signX; let signY; let borderSkipped
  let { borderWidth } = vm;

  // If radius is less than 0 or is large enough to cause drawing errors a max
  // radius is imposed. If cornerRadius is not defined set it to 0.
  let { cornerRadius } = this._chart.config.options;

  if (cornerRadius < 0) { cornerRadius = 0 }
  if (typeof cornerRadius === 'undefined') { cornerRadius = 0 }

  if (!vm.horizontal) {
    left = vm.x - vm.width / 2;
    right = vm.x + vm.width / 2;
    top = vm.y;
    bottom = vm.base;
    signX = 1;
    signY = bottom > top ? 1 : -1;
    borderSkipped = vm.borderSkipped || 'bottom';
  }

  // Canvas doesn't allow us to stroke inside the width so we can
  // adjust the sizes to fit if we're setting a stroke on the line
  if (borderWidth) {
    // borderWidth shold be less than bar width and bar height.
    const barSize = Math.min(Math.abs(left - right), Math.abs(top - bottom));
    borderWidth = borderWidth > barSize ? barSize : borderWidth;
    const halfStroke = borderWidth / 2;
    // Adjust borderWidth when bar top position is near vm.base(zero).
    const borderLeft = left + (borderSkipped !== 'left' ? halfStroke * signX : 0);
    const borderRight = right + (borderSkipped !== 'right' ? -halfStroke * signX : 0);
    const borderTop = top + (borderSkipped !== 'top' ? halfStroke * signY : 0);
    const borderBottom = bottom + (borderSkipped !== 'bottom' ? -halfStroke * signY : 0);
    // not become a vertical line?
    if (borderLeft !== borderRight) {
      top = borderTop;
      bottom = borderBottom;
    }
    // not become a horizontal line?
    if (borderTop !== borderBottom) {
      left = borderLeft;
      right = borderRight;
    }
  }

  ctx.beginPath();
  ctx.fillStyle = vm.backgroundColor;
  ctx.strokeStyle = vm.borderColor;
  ctx.lineWidth = borderWidth;

  // Corner points, from bottom-left to bottom-right clockwise
  // | 1 2 |
  // | 0 3 |
  const corners = [
    [left, bottom],
    [left, top],
    [right, top],
    [right, bottom],
  ]

  // Find first (starting) corner with fallback to 'bottom'
  const borders = ['bottom', 'left', 'top', 'right'];
  let startCorner = borders.indexOf(borderSkipped, 0)
  if (startCorner === -1) {
    startCorner = 0;
  }

  function cornerAt(index) {
    return corners[(startCorner + index) % 4];
  }

  // Draw rectangle from 'startCorner'
  let corner = cornerAt(0);
  ctx.moveTo(corner[0], corner[1]);

  for (let i = 1; i < 4; i++) {
    corner = cornerAt(i);
    let nextCornerId = i + 1;
    if (nextCornerId === 4) {
      nextCornerId = 0;
    }

    const width = corners[2][0] - corners[1][0];
    const height = corners[0][1] - corners[1][1];
    const x = corners[1][0];
    const y = corners[1][1];

    let radius = cornerRadius;

    // Fix radius being too large
    if (radius > Math.abs(height) / 1.5) {
      radius = Math.floor(Math.abs(height) / 1.5);
    }
    if (radius > Math.abs(width) / 1.5) {
      radius = Math.floor(Math.abs(width) / 1.5);
    }

    if (height < 0) {
      // Negative values in a standard bar chart
      const x_tl = x; const x_tr = x + width;
      const y_tl = y + height; const y_tr = y + height;

      const x_bl = x; const x_br = x + width;
      const y_bl = y; const y_br = y;

      // Draw
      ctx.moveTo(x_bl + radius, y_bl);
      ctx.lineTo(x_br - radius, y_br);
      ctx.quadraticCurveTo(x_br, y_br, x_br, y_br - radius);
      ctx.lineTo(x_tr, y_tr + radius);
      ctx.quadraticCurveTo(x_tr, y_tr, x_tr - radius, y_tr);
      ctx.lineTo(x_tl + radius, y_tl);
      ctx.quadraticCurveTo(x_tl, y_tl, x_tl, y_tl + radius);
      ctx.lineTo(x_bl, y_bl - radius);
      ctx.quadraticCurveTo(x_bl, y_bl, x_bl + radius, y_bl);
    } else {
      ctx.moveTo(x + radius, y);
      ctx.lineTo(x + width - radius, y);
      ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
      ctx.lineTo(x + width, y + height - radius);
      ctx.quadraticCurveTo(x + width, y + height, x + width, y + height);
      ctx.lineTo(x + radius, y + height);
      ctx.quadraticCurveTo(x, y + height, x, y + height);
      ctx.lineTo(x, y + radius);
      ctx.quadraticCurveTo(x, y, x + radius, y);
    }
  }

  ctx.fill();
  if (borderWidth) {
    ctx.stroke();
  }
}