React-chartjs-2: Rounded bars

Created on 19 Feb 2018  路  5Comments  路  Source: reactchartjs/react-chartjs-2

Anyone accomplish to create rounded bars (only the top of the bar, radius = 4'ish) in react-chartjs-2?

Most helpful comment

same problem need help any solution on this???

All 5 comments

same problem need help any solution on this???

same here

Hey 馃憢

I could make this work using the solution proposed in this thread:
https://github.com/chartjs/Chart.js/issues/3072 where I needed to override the Chart.elements.Rectangle.prototype.draw.
Hope it helps! 馃槃

I was able to get a TypeScript version working with react-chart-js-2 version 2.11.1 and here's how:

BarGraph.tsx

// Imports: Helper Functions
import '../helpers/roundedBarChart';

<Bar
  data={graphData}
  options={{
    // SET ANY RADIUS HERE
    cornerRadius: 4,
  }}
/>

Helper Function: RoundedBarCorners.tsx

// Imports: Dependencies
// @ts-ignore
import { Chart as ChartJS } from 'react-chartjs-2';

// TypeScript Type: Corners
type Corners = 'bottom' | 'left' | 'top' |'right' | null;

// TypeScript Type: VM
interface VM {
  x: number,
  y: number,
  horizontal: boolean,
  backgroundColor: string,
  borderColor: string,
  borderWidth: number,
  width: number,
  base: number,
  borderSkipped: Corners,
};

// ChartJS 2: Rounded Corners
ChartJS.elements.Rectangle.prototype.draw = function() {
  // CTX
  const ctx: any = this._chart.ctx;

  // VM
  const vm: VM = this._view;

  // Dimensions
  let top: number = 0;
  let bottom: number = 0;
  let left: number = 0;
  let right: number = 0;
  let signX: number = 0;
  let signY: number = 0;
  let borderSkipped: Corners = null;
  // let left, right, top, bottom, signX, signY, borderSkipped
  let borderWidth: number = vm.borderWidth;

  // Corner Radius
  let cornerRadius: number = this._chart.config.options.cornerRadius;

  // Straight Corners (Less Than 0)
  if (cornerRadius < 0) {
    cornerRadius = 0;
  }

  // Straight Corners (Default)
  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 should 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: Begin Path
  ctx.beginPath();

  // CTX: Background Color
  ctx.fillStyle = vm.backgroundColor;

  // CTX: Border Color
  ctx.strokeStyle = vm.borderColor;

  // CTX: Line Width
  ctx.lineWidth = borderWidth;

  // Corner Points (Clockwise: Bottom Left To Bottom Right)
  const cornerPoints: Array<Array<number>> = [
    [left, bottom],
    [left, top],
    [right, top],
    [right, bottom],
  ];

  // Find first (starting) corner with fallback to 'bottom'
  const borders: Array<Corners> = ['bottom', 'left', 'top', 'right'];

  // Start Corner
  let startCorner: number = borders.indexOf(borderSkipped, 0);

  if (startCorner === -1) {
    startCorner = 0;
  }

  // Corner At
  function cornerAt(index: number) {
    return cornerPoints[(startCorner + index) % 4];
  }

  // Draw rectangle from 'startCorner'
  // Corner
  let corner = cornerAt(0);

  // CTX: Move To?
  ctx.moveTo(corner[0], corner[1]);

  // Generate Corners
  let i: number = 1;
  while (i < 4) {
    // Corner
    corner = cornerAt(i);

    // Next Corner ID
    let nextCornerId: number = i + 1;

    // Assign Next Corner ID (Reset)
    if(nextCornerId === 4) {
      nextCornerId = 0;
    }

    const width: number = cornerPoints[2][0] - cornerPoints[1][0];
    const height: number = cornerPoints[0][1] - cornerPoints[1][1];
    const x: number = cornerPoints[1][0];
    const y: number = cornerPoints[1][1];

    // Radius
    let radius = cornerRadius;

    // Radius Height: Too Tall
    if (radius > Math.abs(height) / 1.5) {
      radius = Math.floor(Math.abs(height) / 1.5);
    }
    // Radius Width: Too Wide
    else if (radius > Math.abs(width) / 1.5) {
      radius = Math.floor(Math.abs(width) / 1.5);
    }

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

      const x_bl: number = x;
      const x_br: number = x + width;
      const y_bl: number = y;
      const y_br: number = 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
  ctx.fill();

  // Border Width
  if (borderWidth) {
    ctx.stroke();
  }
};

I was able to get a TypeScript version working with react-chart-js-2 version 2.11.1 and here's how:

BarGraph.tsx

// Imports: Helper Functions
import '../helpers/roundedBarChart';

<Bar
  data={graphData}
  options={{
    // SET ANY RADIUS HERE
    cornerRadius: 4,
  }}
/>

Helper Function: RoundedBarCorners.tsx

// Imports: Dependencies
// @ts-ignore
import { Chart as ChartJS } from 'react-chartjs-2';

// TypeScript Type: Corners
type Corners = 'bottom' | 'left' | 'top' |'right' | null;

// TypeScript Type: VM
interface VM {
  x: number,
  y: number,
  horizontal: boolean,
  backgroundColor: string,
  borderColor: string,
  borderWidth: number,
  width: number,
  base: number,
  borderSkipped: Corners,
};

// ChartJS 2: Rounded Corners
ChartJS.elements.Rectangle.prototype.draw = function() {
  // CTX
  const ctx: any = this._chart.ctx;

  // VM
  const vm: VM = this._view;

  // Dimensions
  let top: number = 0;
  let bottom: number = 0;
  let left: number = 0;
  let right: number = 0;
  let signX: number = 0;
  let signY: number = 0;
  let borderSkipped: Corners = null;
  // let left, right, top, bottom, signX, signY, borderSkipped
  let borderWidth: number = vm.borderWidth;

  // Corner Radius
  let cornerRadius: number = this._chart.config.options.cornerRadius;

  // Straight Corners (Less Than 0)
  if (cornerRadius < 0) {
    cornerRadius = 0;
  }

  // Straight Corners (Default)
  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 should 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: Begin Path
  ctx.beginPath();

  // CTX: Background Color
  ctx.fillStyle = vm.backgroundColor;

  // CTX: Border Color
  ctx.strokeStyle = vm.borderColor;

  // CTX: Line Width
  ctx.lineWidth = borderWidth;

  // Corner Points (Clockwise: Bottom Left To Bottom Right)
  const cornerPoints: Array<Array<number>> = [
    [left, bottom],
    [left, top],
    [right, top],
    [right, bottom],
  ];

  // Find first (starting) corner with fallback to 'bottom'
  const borders: Array<Corners> = ['bottom', 'left', 'top', 'right'];

  // Start Corner
  let startCorner: number = borders.indexOf(borderSkipped, 0);

  if (startCorner === -1) {
    startCorner = 0;
  }

  // Corner At
  function cornerAt(index: number) {
    return cornerPoints[(startCorner + index) % 4];
  }

  // Draw rectangle from 'startCorner'
  // Corner
  let corner = cornerAt(0);

  // CTX: Move To?
  ctx.moveTo(corner[0], corner[1]);

  // Generate Corners
  let i: number = 1;
  while (i < 4) {
    // Corner
    corner = cornerAt(i);

    // Next Corner ID
    let nextCornerId: number = i + 1;

    // Assign Next Corner ID (Reset)
    if(nextCornerId === 4) {
      nextCornerId = 0;
    }

    const width: number = cornerPoints[2][0] - cornerPoints[1][0];
    const height: number = cornerPoints[0][1] - cornerPoints[1][1];
    const x: number = cornerPoints[1][0];
    const y: number = cornerPoints[1][1];

    // Radius
    let radius = cornerRadius;

    // Radius Height: Too Tall
    if (radius > Math.abs(height) / 1.5) {
      radius = Math.floor(Math.abs(height) / 1.5);
    }
    // Radius Width: Too Wide
    else if (radius > Math.abs(width) / 1.5) {
      radius = Math.floor(Math.abs(width) / 1.5);
    }

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

      const x_bl: number = x;
      const x_br: number = x + width;
      const y_bl: number = y;
      const y_br: number = 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
  ctx.fill();

  // Border Width
  if (borderWidth) {
    ctx.stroke();
  }
};

this doesn't seem to work, I'm using the same version as yours and the charts are not even rendering and pc cpu usage gets high, although I used the https://github.com/chartjs/Chart.js/issues/3072#issuecomment-708413792 code and worked, besides editing it a bit

Was this page helpful?
0 / 5 - 0 ratings

Related issues

flxwu picture flxwu  路  3Comments

souuu picture souuu  路  4Comments

n1c01a5 picture n1c01a5  路  4Comments

alexchoiweb picture alexchoiweb  路  3Comments

justinmasse picture justinmasse  路  3Comments