Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Relative Gradient on Bar Chart #1968

Closed
1 task done
tahaimt opened this issue Dec 5, 2019 · 8 comments
Closed
1 task done

Relative Gradient on Bar Chart #1968

tahaimt opened this issue Dec 5, 2019 · 8 comments

Comments

@tahaimt
Copy link

tahaimt commented Dec 5, 2019

  • I have searched the issues of this repository and believe that this is not a duplicate.

Reproduction link

Edit on CodeSandbox

Steps to reproduce

Open the link in Sandbox

What is expected?

The gradient on the bars should be on the same scale on all bars, or you can say that the gradient should be relative and should be applied on the bars with reference to the minimum and maximum values in all bars in the chart. Lets say a gradient of red is applied on the top 50% and green is applied on the bottom 50%, if a bar is only erected to 40% it should only be of green color. If a bar is erected to 90%, the bottom 50% should be of green color and the top 40% be of red.

What is actually happening?

Gradient is being applied on each bar from its own minimum to its own maximum.

Environment Info
Recharts v2.0.0-beta.1
React 16.12.0
System Windows 10
Browser Chrome Latest as of 5th Dec 2019
@tahaimt
Copy link
Author

tahaimt commented Dec 5, 2019

Pasted below is the expected result
image

@tahaimt tahaimt changed the title Referential Gradient on Bar Chart Relative Gradient on Bar Chart Dec 6, 2019
@xile611
Copy link
Member

xile611 commented Mar 13, 2020

@tahaimt It's recommended to use shape to implement this kind of gradient.
You can calculate the start color of each bar by value.

@xile611 xile611 closed this as completed Mar 13, 2020
@kyashrathore
Copy link

You can render this shape

const renderShape = (key, pixel = 10) => ({ height, width, fill, x, y, ...rest }) => {
  const xpercent = Math.trunc((pixel * 100) / Math.trunc(height || 1));
  return (
    <svg x={x} y={y} fill="none" xmlns="http://www.w3.org/2000/svg">
      <defs>
        <linearGradient id={key} x1="0%" y1="0%" x2="0%" y2={`${xpercent}%`}>
          <stop offset="50%" stopColor="white" />
          <stop offset="50%" stopColor={fill} stopOpacity="1" />
        </linearGradient>
      </defs>
      <rect fill={`url(#${key})`} width={width} height={height} />
    </svg>
  );

  <Bar 
     fill="red"
     dataKey={'aa}
     stackId="1"
     shape={renderShape('a') }
    />
};
``

@jpwhitaker
Copy link

Hey thanks for this answer, it didn't work for me but I used it as a basis for my own implementation, which is sort of buggy so I'm still looking for the cleanest way to do things.

  1. I made a svg at x (and hardcoded y to align it with my other graph)
  2. made the linear gradient with 5-stops.
  3. made a 'clip path' that responds to the height argument
  4. made a rect with 100% height, color it with gradient
  5. clip rect with aforementioned clip path.

The rect therefore has a static gradient, and the clip path hides or shows it making it look like it's growing and shrinking.

(Here is a gif: https://imgur.com/a/7s27o3i )
The gradient is important to the chart value because I'm using it to color code the acceptable output range.

const renderShape1 = (key, pixel = 10) => ({ height, width, fill, x, y, ...rest }) => {

  return (
    <svg x={`${x}`} y={`${-35}px`}>
      <defs>
        <linearGradient id="grad1" x1="0" y1="0" x2="0" y2={`${100}%`} >
          <stop offset="0%" style={{stopColor: "rgb(255,0,0)", stopOpacity: 1 }} />
          <stop offset="25%" style={{stopColor: "rgb(255,255,0)", stopOpacity: 1}} />
          <stop offset="50%" style={{stopColor: "rgb(0,255,0)", stopOpacity: 1}} />
          <stop offset="75%" style={{stopColor: "rgb(255,255,0)", stopOpacity: 1}} />
          <stop offset="100%" style={{stopColor: "rgb(255,0,0)", stopOpacity: 1}} />
        </linearGradient>
        <clipPath id="clip1">
          <rect width={`${width}`} height={`${height}%`}  y={`${y}`}  />
        </clipPath>
      </defs>
      <rect id="gradient" width={`${width}`} height={`${100}%`} fill="url(#grad1)"  clip-path="url(#clip1)"/>
      
    </svg>
  );
};

<Bar 
   fill="red"
   dataKey={'chill_heat_transfer_rate'}
   stackId="1"
   shape={renderShape1('chill_heat_transfer_rate') }
   isAnimationActive={false}
  />

@Dalion
Copy link

Dalion commented Jun 17, 2021

It's a little bit freaky in case of using large legend area.

Look at this for example:
https://codesandbox.io/s/recharts-playground-forked-cx68r?file=/index.js

Here we can see that gradient used for bars. Whatever component You'll make (with custom shape or fill of Bar or something else) it will cause incorrect way of gradient - "min" value of gradient will be for area somewhere behind legend.

SVG element of recharts surface has size equal to size of recharts wrapper and wrapper has not only surface but also legend, positioned as absolute.

And I can't figure out how to properly work with it :) If it's better for You (as I can see) to have svg element with empty space for legend, that is not in svg element, but sibling absolute div - it's your choice. And I see the way of resolving this with a calculating of stop element offset value according to size of legend and chart at all. But am I really have to do this just because You put all of chart with a legend into svg? Or may be I'm missing something?

@rjworks
Copy link

rjworks commented Jun 27, 2021

It's a little bit freaky in case of using large legend area.

Look at this for example:
https://codesandbox.io/s/recharts-playground-forked-cx68r?file=/index.js

Here we can see that gradient used for bars. Whatever component You'll make (with custom shape or fill of Bar or something else) it will cause incorrect way of gradient - "min" value of gradient will be for area somewhere behind legend.

SVG element of recharts surface has size equal to size of recharts wrapper and wrapper has not only surface but also legend, positioned as absolute.

And I can't figure out how to properly work with it :) If it's better for You (as I can see) to have svg element with empty space for legend, that is not in svg element, but sibling absolute div - it's your choice. And I see the way of resolving this with a calculating of stop element offset value according to size of legend and chart at all. But am I really have to do this just because You put all of chart with a legend into svg? Or may be I'm missing something?

How to use hex values for the color?

@bclef2525
Copy link

How to apply gradient to cells in pie chart?

@wahyubucil
Copy link

This is another similar implementation in case anybody need it:

import { useId } from "react";
import { Bar, BarChart, Rectangle, ResponsiveContainer, XAxis } from "recharts";

import { type BarRectangleItem } from "recharts/types/cartesian/Bar";

const data = [
  {
    name: "Page A",
    uv: 4000,
    pv: 2400,
    amt: 2400,
  },
  {
    name: "Page B",
    uv: 3000,
    pv: 1398,
    amt: 2210,
  },
  {
    name: "Page C",
    uv: 2000,
    pv: 9800,
    amt: 2290,
  },
  {
    name: "Page D",
    uv: 2780,
    pv: 3908,
    amt: 2000,
  },
  {
    name: "Page E",
    uv: 1890,
    pv: 4800,
    amt: 2181,
  },
  {
    name: "Page F",
    uv: 2390,
    pv: 3800,
    amt: 2500,
  },
  {
    name: "Page G",
    uv: 3490,
    pv: 4300,
    amt: 2100,
  },
];

function BarGradient(props: BarRectangleItem) {
  const id = useId();
  const gradientId = `gradient-${id}`;
  const clipPathId = `clipPath-${id}`;

  return (
    <>
      <defs>
        <linearGradient id={gradientId} x1="0" y1="0" x2="0" y2="100%">
          <stop offset="0%" stopColor="red" />
          <stop offset="64.43%" stopColor="blue" />
          <stop offset="100%" stopColor="green" />
        </linearGradient>

        <clipPath id={clipPathId}>
          <Rectangle {...props} />
        </clipPath>
      </defs>

      <rect
        x={props.x}
        width={props.width}
        height={props.background?.height}
        fill={`url(#${gradientId})`}
        y={props.background?.y}
        clipPath={`url(#${clipPathId})`}
      />
    </>
  );
}

export function ChartBar() {
  return (
    <div style={{ width: 500, height: 300 }}>
      <ResponsiveContainer width="100%" height="100%">
        <BarChart data={data}>
          <Bar
            dataKey="uv"
            shape={<BarGradient />}
            activeBar={<BarGradient />}
          />

          <XAxis dataKey="name" />
        </BarChart>
      </ResponsiveContainer>
    </div>
  );
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants