•  


PieChart - labels are overlapping when distance between them is really small · Issue #490 · recharts/recharts · GitHub
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

PieChart - labels are overlapping when distance between them is really small #490

Open
wkwiatek opened this issue Jan 31, 2017 · 37 comments
Labels
enhancement Enhancement to a current API P2 Feature requests or future priority issues

Comments

@wkwiatek
Copy link

For some big numbers in pair with rather small ones, labels are overlapping other as there's no sufficient space for all of them.

Here is the demo: http://jsfiddle.net/qzk61f5h/

And this is how it looks like:
image

I think that library should handle this case properly - maybe by showing them stacked up at the top of another?

@stephenlacy
Copy link

Same issue here
selection_00120170814 51 02

@wmg481
Copy link

Is there any progress on fixing this issue?

Seems like a pretty big bug.

I have also run into this issue and I'm trying to find a way around it, but with no success.

@Otoris
Copy link

Very big issue for me using this library.

@mbyrne00
Copy link

mbyrne00 commented Nov 28, 2017

Would be nice to have an automated solution, but in the mean time I worked around by using Legend instead and had coloured pie slices, added some paddingAngle to make small slices obvious and added minAngle to also make the slice visible. You an tweak the angles, but it gives an idea.

Not bothering to create jsfiddle because they always seem broken by the time I view ... the relevant code snippets below ...

// Static data, or you could have a list of available colours and map your data to contain different fill colours
const myData = [
    {
      name: 'Name A',
      value: 9999,
      fill: '#908ce2',
    },
    {
      name: 'Name B',
      value: 5000,
      fill: '#2fe18a',
    },
    {
      name: 'Name C',
      value: 2,
      fill: '#ef8a88',
    },
    {
      name: 'Name D',
      value: 1,
      fill: '#8fcddd',
    },
  ];

...

<ResponsiveContainer width="60%" minHeight={400}>
  <PieChart>
    <Pie data={myData} dataKey="value" nameKey="name" fill="#8884d8" legendType="circle" paddingAngle={1} minAngle={1} />
    <Tooltip/>
    <Legend/>
  </PieChart>
</ResponsiveContainer>

@zaunermax
Copy link

Has anyone been able find a workaround to this problem?

@TotallWAR
Copy link

I have a problem with this too. I tried to use custom label, but i can't create algorithm to calc positions for labels...=(

@mrkpatchaa
Copy link

capture d ecran 2018-05-23 a 14 17 57

Highcharts handle this smoothly... It should be great to have a workaround for this issue.
https://www.highcharts.com/demo/pie-basic

@zgallagher08
Copy link

I'm having the same problem.

Does anyone have a solution?

I also had issues placing custom label text, but I am using the following solution for that as well as some logic for translating the labels because the built in locations were not working:

const offsetLabel = (name, x, y) => {
  let offsetX
  let offsetY
  if (name === '100%') return [22, 5]
  if (x <= 145) {
    offsetX = 14
  } else {
    offsetX = 22
  }
  if (y <= 105) {
    offsetY = 11
  } else {
    offsetY = 5
  }
  return [offsetX, offsetY]
}

const renderLabelContent = props => {
  const { name, x, y, midAngle } = props
  const [offsetX, offsetY] = offsetLabel(name, x, y)
  return (
    <g transform={`translate(${x}, ${y})`} textAnchor={(midAngle < -90 || midAngle >= 90) ? 'end' : 'start'}>
      <text x={offsetX} y={offsetY}>{name}</text>
    </g>
  )
}

I am passing the function renderLabelContent to the attribute label below:

<ReactPieChart width={200} height={220}>
        <Pie
          startAngle={90}
          endAngle={450}
          innerRadius={innerRadius || 0}
          data={data}
          paddingAngle={paddingAngle}
          stroke={stroke}
          label={renderLabelContent}
          labelLine={false}
          dataKey='value'
        />
      </ReactPieChart>

@blambillotte
Copy link

blambillotte commented Mar 5, 2019

We ended up creating a custom label function that just returns NULL for values that are less than a configured percent of the total. Since the value is shown in a tooltip on hover, this was the cleanest solution we could think of without trying to calculate positions.
What also helped was being sure to sort the values such that the smallest always appear at "3 'o clock" to minimize the possibility of horizontal overlap.

image 2019-03-04 at 12 33 54 pm

That being said, this is certainly not ideal and would love to know if there's actual interest in getting a real fix for the library.

@7hibault
Copy link

+1 for the issue, any update?

@abishek28007
Copy link

abishek28007 commented Jun 3, 2019

Thanks to @zgallagher08 initial comment, I came with following code, which pretty much provides the same output as @rmkpatchaa shown :

renderCustomizedLabel({
    cx, cy, midAngle, innerRadius, outerRadius, value, color, startAngle, endAngle}) {
    const RADIAN = Math.PI / 180;
    const diffAngle = endAngle - startAngle;
    const delta = ((360-diffAngle)/15)-1;
    const radius = innerRadius + (outerRadius - innerRadius);
    const x = cx + (radius+delta) * Math.cos(-midAngle * RADIAN);
    const y = cy + (radius+(delta*delta)) * Math.sin(-midAngle * RADIAN);
    return (
      <text x={x} y={y} fill={color} textAnchor={x > cx ? 'start' : 'end'} dominantBaseline="central" fontWeight="normal">
        {value}
      </text>
    );
  };
  renderCustomizedLabelLine(props){
    let { cx, cy, midAngle, innerRadius, outerRadius, color, startAngle, endAngle } = props;
    const RADIAN = Math.PI / 180;
    const diffAngle = endAngle - startAngle;
    const radius = innerRadius + (outerRadius - innerRadius);
    let path='';
    for(let i=0;i<((360-diffAngle)/15);i++){
      path += `${(cx + (radius+i) * Math.cos(-midAngle * RADIAN))},${(cy + (radius+i*i) * Math.sin(-midAngle * RADIAN))} `
    }
    return (
      <polyline points={path} stroke={color} fill="none" />
    );
  }

image

@joe-akers-douglas-otm

Any update? Currently can't use this lib with this bug and @abishek28007 solution doesn't render correctly for us.

@senelithperera
Copy link

senelithperera commented Oct 16, 2019

@abishek28007 After doing some changes to your customized label function it's working fine for me. I'm a bit confused about where to set the customized line function to the pie chart ??

@abishek28007
Copy link

abishek28007 commented Oct 16, 2019

@senelithperera

<Pie labelLine={renderCustomizedLabelLine}
        label={renderCustomizedLabel}
>

@senelithperera
Copy link

@abishek28007 Thanks for getting back to me, but unfortunately your solution didn't fix my problem 100% so I moved to temporary solution, which is showing the labels like this (custom active Index) , as specified in this

Screenshot from 2019-10-17 10-49-26

We need a concrete solution for showing labels in pie charts without having the overlapping issue !!

@abishek28007
Copy link

@senelithperera , can you post the modification that you made in my code, would help me to come up with a fix for this.

@pgularski
Copy link

Any update on this? It's a pretty serious issue for quite an ordinary and common use case and hasn't been solved for a while now. Is it being worked on?

@avinashsingh953
Copy link

<Pie
isAnimationActive={false}
isUpdateAnimationActive={true}
data={props.data}
dataKey="value"
nameKey="name"
labelLine={({
cx,
cy,
midAngle,
innerRadius,
outerRadius,
value,
index
}) => {
const RADIAN = Math.PI / 180;

                    // eslint-disable-next-line
                    let radius1 = 20 + innerRadius + (outerRadius - innerRadius);

                    let radius2 = innerRadius + (outerRadius - innerRadius);
                    // eslint-disable-next-line
                    let x2 = cx + radius1 * Math.cos(-midAngle * RADIAN);
                    // eslint-disable-next-line
                    let y2 = cy + radius1 * Math.sin(-midAngle * RADIAN);

                    let x1 = cx + radius2 * Math.cos(-midAngle * RADIAN);
                    // eslint-disable-next-line
                    let y1 = cy + radius2 * Math.sin(-midAngle * RADIAN);

                    if (value<30){
                        return null;
                    }

                    return(
                        <line x1={x1} y1={y1} x2={x2} y2={y2} stroke="#ccc" strokeWidth={1}>

                        </line>
                    )
                }}
                label={({
                    cx,
                    cy,
                    midAngle,
                    innerRadius,
                    outerRadius,
                    value,
                    index
                }) => {
                    const RADIAN = Math.PI / 180;
                    
                    // eslint-disable-next-line
                    let radius = 20 + innerRadius + (outerRadius - innerRadius);
                    // eslint-disable-next-line
                    let x = cx + radius * Math.cos(-midAngle * RADIAN);
                    // eslint-disable-next-line
                    let y = cy + radius * Math.sin(-midAngle * RADIAN);
                    if (value<30){
                        return null;
                    }
                    return (
                        <text
                            x={x}
                            y={y}
                            fill="#000"
                            fontWeight="300"
                            fontSize="13px"
                            fontFamily="'Source Sans Pro', 'Roboto', 'Helvetica Neue', 'Helvetica', 'Arial', 'sans-serif'"
                            textAnchor={x > cx ? "start" : "end"}
                            dominantBaseline="central"
                        >
                            {props.data[index].name} {value}
                                                  </text>
                    );
                }}
                cx="50%"
                cy="50%"
                outerRadius="84%"
                innerRadius="40%"
                fill="#8884d8" >
                    {/* <LabelList dataKey="name" position="outside" /> */}
                    <Label value={100} position="center" fontSize={24} fontWeight={400}></Label>
            </Pie>

@avinashsingh953
Copy link

Any update on this? It's a pretty serious issue for quite an ordinary and common use case and hasn't been solved for a while now. Is it being worked on?

Use This <Pie
isAnimationActive={false}
isUpdateAnimationActive={true}
data={props.data}
dataKey="value"
nameKey="name"
labelLine={({
cx,
cy,
midAngle,
innerRadius,
outerRadius,
value,
index
}) => {
const RADIAN = Math.PI / 180;

                    // eslint-disable-next-line
                    let radius1 = 20 + innerRadius + (outerRadius - innerRadius);

                    let radius2 = innerRadius + (outerRadius - innerRadius);
                    // eslint-disable-next-line
                    let x2 = cx + radius1 * Math.cos(-midAngle * RADIAN);
                    // eslint-disable-next-line
                    let y2 = cy + radius1 * Math.sin(-midAngle * RADIAN);

                    let x1 = cx + radius2 * Math.cos(-midAngle * RADIAN);
                    // eslint-disable-next-line
                    let y1 = cy + radius2 * Math.sin(-midAngle * RADIAN);

                    if (value<30){
                        return null;
                    }

                    return(
                        <line x1={x1} y1={y1} x2={x2} y2={y2} stroke="#ccc" strokeWidth={1}>

                        </line>
                    )
                }}
                label={({
                    cx,
                    cy,
                    midAngle,
                    innerRadius,
                    outerRadius,
                    value,
                    index
                }) => {
                    const RADIAN = Math.PI / 180;
                    
                    // eslint-disable-next-line
                    let radius = 20 + innerRadius + (outerRadius - innerRadius);
                    // eslint-disable-next-line
                    let x = cx + radius * Math.cos(-midAngle * RADIAN);
                    // eslint-disable-next-line
                    let y = cy + radius * Math.sin(-midAngle * RADIAN);
                    if (value<30){
                        return null;
                    }
                    return (
                        <text
                            x={x}
                            y={y}
                            fill="#000"
                            fontWeight="300"
                            fontSize="13px"
                            fontFamily="'Source Sans Pro', 'Roboto', 'Helvetica Neue', 'Helvetica', 'Arial', 'sans-serif'"
                            textAnchor={x > cx ? "start" : "end"}
                            dominantBaseline="central"
                        >
                            {props.data[index].name} {value}
                                                  </text>
                    );
                }}
                cx="50%"
                cy="50%"
                outerRadius="84%"
                innerRadius="40%"
                fill="#8884d8" >
                    {/* <LabelList dataKey="name" position="outside" /> */}
                    <Label value={100} position="center" fontSize={24} fontWeight={400}></Label>
            </Pie>

@abishek28007
Copy link

@avinashsingh953 I assume your solution won't render label for value < 30

@avinashsingh953
Copy link

@avinashsingh953 I assume your solution won't render label for value < 30

Yes But you can adjust the value or calculate the angle for which labels can be hidden

@lxm7
Copy link

lxm7 commented Jan 28, 2020

@abishek28007 - what is the significance or reason for the number 15 in working out the delta variable in your solution? Thanks

@abishek28007
Copy link

@lxm7 15 and -1 are some arbitrary value which worked my use case, depending upon the use case they may change. I am trying to generalise it, any suggestion is appreciated.

@MZanggl
Copy link

I think this not only affects pie charts but also stacked bar charts and line charts.

@jbyerline
Copy link

I’d like to bump this issue. Any solid solution would be appreciated

@tvvignesh
Copy link

Hi. We are facing the same issue as well where the labels are getting overlapped. Any fix for this? Thanks.

image

@JESii
Copy link

We've been looking for a solution for some time; even considered implementing something ourselves, possibly using things like d3-labeler . All for naught; we finally changed the UI and implemented the information via a legend. Seems to me like the only reasonable solution at this point.

@laclance
Copy link

any update on this?

@nikolasrieble nikolasrieble added the enhancement Enhancement to a current API label Dec 9, 2022
@nikolasrieble
Copy link
Contributor

Automatically placing an arbitrary amount of labels for arbitrarily thin slices of a Pie is not reasonable.

Instead we could extend the label feature:

@JESii
Copy link

Seems reasonable, @nikolasrieble ... and maybe a combination of the two: show the labels that can fit, and then add hover for the others. Because for our use case, we definitely want the users to see the percentage. Using a legend as we did turned out to work great: see it all at a glance, no matter how small.

@zopelee
Copy link

zopelee commented Dec 18, 2022

Our workaround:

const
 chartCountryAllocations
 =
 [

  {
 value
: 
0.6454
,
 name
: 
'United States'
 }
,

  {
 value
: 
0.0444
,
 name
: 
'Germany'
 }
,

  {
 value
: 
0.0438
,
 name
: 
'Japan'
 }
,

  {
 value
: 
0.0412
,
 name
: 
'Switzerland'
 }
,

  {
 value
: 
0.0406
,
 name
: 
'France'
 }
,

  {
 value
: 
0.0394
,
 name
: 
'Netherlands'
 }
,

  {
 value
: 
0.0357
,
 name
: 
'China'
 }
,

  {
 value
: 
0.0266
,
 name
: 
'Canada'
 }
,

  {
 value
: 
0.0251
,
 name
: 
'Denmark'
 }
,

  {
 value
: 
0.0354
,
 name
: 
'Others'
 }
,

  {
 value
: 
0.0224
,
 name
: 
'Cash'
 }
,

]


const
 renderCustomizedLabel
 =
 (
{
 cx
,
 cy
,
 midAngle
,
 outerRadius
,
 percent
,
 name
,
 fontSize
,
 index 
}
)
 =>
 {

  const
 RADIAN
 =
 Math
.
PI
 /
 180

  const
 sin
 =
 Math
.
sin
(
RADIAN
 *
 midAngle
)

  const
 cos
 =
 Math
.
cos
(
RADIAN
 *
 midAngle
)

  const
 startX
 =
 cx
 +
 (
outerRadius
)
 *
 cos

  const
 startY
 =
 cy
 +
 (
outerRadius
)
 *
 -
sin

  const
 middleY
 =
 cy
 +
 (
outerRadius
 +
 50
 *
 Math
.
abs
(
sin
)
)
 *
 -
sin

  let
 endX
 =
 startX
 +
 (
cos
 >=
 0
 ? 
1
 : 
-
1
)
 *
 90

  let
 textAnchor
 =
 cos
 >=
 0
 ? 
'start'
 : 
'end'

  const
 mirrorNeeded
 =
 midAngle
 >
 -
270
 &&
 midAngle
 <
 -
210
 &&
 percent
 <
 0.04
 &&
 index
 %
 2
 ===
 1

  if
 (
mirrorNeeded
)
 {

    endX
 =
 startX
 +
 outerRadius
 *
 -
cos
 *
 2
 +
 100

    textAnchor
 =
 'start'

  }


  return
 (

    <
g
>

      <
path

        d
=
{
`M${
startX
}
,${
startY
}
L${
startX
}
,${
middleY
}
L${
endX
}
,${
middleY
}
`
}

        fill
=
"none"

        stroke
=
'#000'

        strokeWidth
=
{
3
}

      /
>

      <
text

        x
=
{
endX
 +
 (
cos
 >=
 0
 ||
 mirrorNeeded
 ? 
1
 : 
-
1
)
 *
 12
}

        y
=
{
middleY
 +
 fontSize
 /
 2
}

        textAnchor
=
{
textAnchor
}

        fontSize
=
{
fontSize
}

      >
{
`
${
name
 ||
 ''
}
 ${
(
percent
 *
 100
)
.
toFixed
(
2
)
}
%`
}
<
/
text>

    <
/
g
>

  )

}


<
div
 style
=
{
{
 width
: 
'1000px'
,
 height
: 
'575px'
 }
}
>

  <
ResponsiveContainer
 width
=
"100%"
 height
=
"100%"
>

    <
PieChart
 width
=
{
400
}
 height
=
{
400
}
>

      <
Pie

        data
=
{
chartCountryAllocations
}

        cx
=
'50%'

        cy
=
'50%'

        startAngle
=
{
90
}

        endAngle
=
{
-
270
}

        labelLine
=
{
false
}

        paddingAngle
=
{
5
}

        dataKey
=
"value"

        fontSize
=
{
30
}

        isAnimationActive
=
{
false
}

        label
=
{
renderCustomizedLabel
}

      >

        {

          chartCountryAllocations
.
map
(
(
allo
,
 idx
)
 =>
 (

            <
Cell
 key
=
{
idx
}
 fill
=
'#51A760'
 /
>

          )
)

        }

      <
/
Pie
>

    <
/
PieChart
>

  <
/
ResponsiveContainer
>

<
/
div
>

image

@Vignesh-sra
Copy link

For some big numbers in pair with rather small ones, labels are overlapping other as there's no sufficient space for all of them.

Here is the demo: http://jsfiddle.net/qzk61f5h/

And this is how it looks like: image

I think that library should handle this case properly - maybe by showing them stacked up at the top of another?

Ans: Just we should minAngle to the pie component likely it quite gave the expected result. Just try like this.

@hellsing2030
Copy link

You can change the min angle and change the fontSize and manage a custom label with custom variables so that the text scrolls to your liking

@anmeln
Copy link

Thanks to @zgallagher08 initial comment, I came with following code, which pretty much provides the same output as @rmkpatchaa shown :

renderCustomizedLabel({
    cx, cy, midAngle, innerRadius, outerRadius, value, color, startAngle, endAngle}) {
    const RADIAN = Math.PI / 180;
    const diffAngle = endAngle - startAngle;
    const delta = ((360-diffAngle)/15)-1;
    const radius = innerRadius + (outerRadius - innerRadius);
    const x = cx + (radius+delta) * Math.cos(-midAngle * RADIAN);
    const y = cy + (radius+(delta*delta)) * Math.sin(-midAngle * RADIAN);
    return (
      <text x={x} y={y} fill={color} textAnchor={x > cx ? 'start' : 'end'} dominantBaseline="central" fontWeight="normal">
        {value}
      </text>
    );
  };
  renderCustomizedLabelLine(props){
    let { cx, cy, midAngle, innerRadius, outerRadius, color, startAngle, endAngle } = props;
    const RADIAN = Math.PI / 180;
    const diffAngle = endAngle - startAngle;
    const radius = innerRadius + (outerRadius - innerRadius);
    let path='';
    for(let i=0;i<((360-diffAngle)/15);i++){
      path += `${(cx + (radius+i) * Math.cos(-midAngle * RADIAN))},${(cy + (radius+i*i) * Math.sin(-midAngle * RADIAN))} `
    }
    return (
      <polyline points={path} stroke={color} fill="none" />
    );
  }

image

this doesn't work

the result is something like....
Screenshot 2023-12-01 at 14 34 28

@lucaspieran
Copy link

anyone knows how to handle this in line charts?
Screenshot from 2024-01-16 16-41-13

@ckifer
Copy link
Member

@lucaspieran no way to handle it automatically, but you can use a combo of position and offset props on LabelList
Edit line-chart-with-customized-label (forked)

@HenilManiya
Copy link

Refer this example for better solution:

const renderCustomizedLabel = (props) => {
  const RADIAN = Math.PI / 180;
  const { cx, cy, midAngle, outerRadius, fill, percent, value, name, index } =
    props;
  const sin = Math.sin(-RADIAN * midAngle);
  const cos = Math.cos(-RADIAN * midAngle);
  const sx = cx + outerRadius * cos;
  const sy = cy + outerRadius * sin;
  const mx = cx + (outerRadius + 20) * cos;
  const my = cy + (outerRadius + 20) * sin;

  const isLeftSide = midAngle < 90 && midAngle > -90; // Change 10 to half of the number of items per side
  const ySpacing = 15; // Adjust the vertical spacing between lines
  const xSpacing = -100; // Adjust the horizontal spacing between left and right sides
  const topSpacing = 50;
  const ey = !isLeftSide
    ? topSpacing + (data?.length - index) * ySpacing
    : topSpacing + (index + 1) * ySpacing;
  const textAnchor = isLeftSide ? "start" : "end";
  const xPosition = cx + (isLeftSide ? -1 : 1) * xSpacing;

  let keyName = name.split(" ");

  return (
    <g>
      <path
        d={`M${sx},${sy}L${mx},${my}L${xPosition},${ey}`}
        stroke={fill}
        fill="none"
      />
      <circle cx={xPosition} cy={ey} r={2} fill={fill} stroke="none" />
      <text
        x={textAnchor === "start" ? xPosition + 10 : xPosition - 10}
        y={ey + 5}
        fontSize={15}
        textAnchor={textAnchor}
        fill={fill}
      >
        {`${keyName}: ${value} `}
      </text>
    </g>
  );
};

    <PieChart width={500} height={500}>
      <Pie
        data={data}
        cx={200}
        cy={200}
        labelLine={false}
        label={renderCustomizedLabel}
        outerRadius={60}
        fill="#8884d8"
        dataKey="value"
        startAngle={90}
        endAngle={-270}
        isAnimationActive={false}
      >
        {data.map((entry, index) => (
          <Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
        ))}
      </Pie>
    </PieChart>

Sign up for free to join this conversation on GitHub . Already have an account? Sign in to comment
Labels
enhancement Enhancement to a current API P2 Feature requests or future priority issues
Projects
None yet
Development

No branches or pull requests

- "漢字路" 한글한자자동변환 서비스는 교육부 고전문헌국역지원사업의 지원으로 구축되었습니다.
- "漢字路" 한글한자자동변환 서비스는 전통문화연구회 "울산대학교한국어처리연구실 옥철영(IT융합전공)교수팀"에서 개발한 한글한자자동변환기를 바탕하여 지속적으로 공동 연구 개발하고 있는 서비스입니다.
- 현재 고유명사(인명, 지명등)을 비롯한 여러 변환오류가 있으며 이를 해결하고자 많은 연구 개발을 진행하고자 하고 있습니다. 이를 인지하시고 다른 곳에서 인용시 한자 변환 결과를 한번 더 검토하시고 사용해 주시기 바랍니다.
- 변환오류 및 건의,문의사항은 juntong@juntong.or.kr로 메일로 보내주시면 감사하겠습니다. .
Copyright ⓒ 2020 By '전통문화연구회(傳統文化硏究會)' All Rights reserved.
 한국   대만   중국   일본