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

[Feature] optimize label of piechart #3636

Open
xile611 opened this issue Jan 3, 2025 · 3 comments
Open

[Feature] optimize label of piechart #3636

xile611 opened this issue Jan 3, 2025 · 3 comments
Assignees

Comments

@xile611
Copy link
Contributor

xile611 commented Jan 3, 2025

What problem does this feature solve?

image

What does the proposed API look like?

no new api

@xile611
Copy link
Contributor Author

xile611 commented Jan 3, 2025

IRichTextParagraphCharacter 支持设置最大行数

@xile611
Copy link
Contributor Author

xile611 commented Jan 21, 2025

const spec = {
  type: 'pie',
  width: 800,
  height: 500,
  background: 'pink',
  region: [
    {
      style: {
        fill: 'yellow',
        stroke: '#000',
        lineWidth: 1
      }
    }
  ],
  data: [
    {
      id: 'id0',
      values: [
        { type: 'This is a long Auto-Wrap Category Text for Category1', value: 24 },
        { type: 'This is a  Category2', value: 20 },
        { type: 'This is a long Auto-Wrap Category Text for Category3', value: 18 },
        { type: 'This is a long Auto-Wrap Category Text for Category4', value: 18 },
        { type: 'This is a long Auto-Wrap Category Text for Category5', value: 16 },
        {
          type: 'This is a long Auto-Wrap Category Text for Category6. This is a long Auto-Wrap Category Text for Category6',
          value: 14
        },
        {
          type: 'This is a long Auto-Wrap Category Text for Category7. This is a long Auto-Wrap Category Text for Category7',
          value: 10
        },
        {
          type: 'This is a long Auto-Wrap Category Text for Category8. This is a long Auto-Wrap Category Text for Category8',
          value: 10
        },
        {
          type: 'This is a long Auto-Wrap Category Text for Category9. This is a long Auto-Wrap Category Text for Category9',
          value: 5
        },
        {
          type: 'This is a long Auto-Wrap Category Text for Category10. This is a long Auto-Wrap Category Text for Category10',
          value: 5
        },
        {
          type: 'This is a long Auto-Wrap Category Text for Category11. This is a long Auto-Wrap Category Text for Category11',
          value: 4
        },
        {
          type: 'This is a long Auto-Wrap Category Text for Category12. This is a long Auto-Wrap Category Text for Category12',
          value: 3
        },
        {
          type: 'This is a long Auto-Wrap Category Text for Category13. This is a long Auto-Wrap Category Text for Category13',
          value: 3
        },
        {
          type: 'This is a long Auto-Wrap Category Text for Category14. This is a long Auto-Wrap Category Text for Category14',
          value: 3
        }
      ]
    }
  ],
  outerRadius: 0.8,
  innerRadius: 0.5,
  padAngle: 0.6,
  valueField: 'value',
  categoryField: 'type',
  pie: {
    style: {
      cornerRadius: 10
    },
    state: {
      hover: {
        outerRadius: 0.85,
        stroke: '#000',
        lineWidth: 1
      },
      selected: {
        outerRadius: 0.85,
        stroke: '#000',
        lineWidth: 1
      }
    }
  },
  legends: {
    visible: true,
    item: {
      background: {
        visible: true,
        style: {
          fill: 'green'
        }
      }
    }
  },
  label: {
    visible: true,
    // formatMethod: (label, data) => {
    //   return {
    //     type: 'rich',
    //     text: [
    //       {
    //         text: data.type,
    //         fill: 'rgba(0, 0, 0, 0.55)',
    //         fontSize: 12,
    //         fontWeight: 400
    //       },
    //       {
    //         text: ` ${data.value}%`,
    //         fill: 'rgba(0, 0, 0, 0.92)',
    //         fontSize: 16,
    //         fontWeight: 500
    //       }
    //     ]
    //   };
    // },
    // style: {
    //   // disableAutoWrapLine: true
    //   singleLine: true
    //   // ellipsis: true,
    //   // //=maxHeight: 40,
    //   // lineClamp: 2,
    //   // textAlign: 'end'
    // },
    // onAfterOverlapping: (labels, getRelatedGraphic, getRelatedPoint) => {
    //   labels.forEach(label => {
    //     console.log(label);
    //     // const { text: originalText, x, points } = label.attribute;
    //     // const sign = points[2].x - points[0].x > 0 ? 1 : -1;
    //     // const prevWidth = label.AABBBounds.width();
    //     // if (!label.cliped) {
    //     //   console.log(label);
    //     //   label.setAttribute('text', originalText.join(' '));

    //     //   if (label.cliped) {
    //     //     label.setAttribute('text', originalText);
    //     //   } else {
    //     //     const newWidth = label.AABBBounds.width();

    //     //     label.setAttribute('x', x + (sign * (newWidth - prevWidth)) / 2);
    //     //   }
    //     // } else {
    //     //   label.setAttributes(
    //     //     sign === 1
    //     //       ? {
    //     //           textAlign: 'start',
    //     //           x: x - prevWidth / 2
    //     //         }
    //     //       : {
    //     //           textAlign: 'end',
    //     //           x: x + prevWidth / 2
    //     //         }
    //     //   );
    //     // }
    //   });
    // }
    formatMethod: (label, data) => {
      return [
        data.type,
        `${data.value}%`
        // {
        //   text: `${data.value}%`,
        //   fill: 'rgba(0, 0, 0, 0.92)',
        //   fontSize: 16,
        //   fontWeight: 500,
        //   stroke: false
        // }
      ];
    },

    onAfterOverlapping: (labels, getRelatedGraphic, getRelatedPoint) => {
      labels.forEach(label => {
        const { text: originalText, x, points } = label.attribute;
        const sign = points[2].x - points[0].x > 0 ? 1 : -1;
        const prevWidth = label.AABBBounds.width();
        if (!label.cliped) {
          console.log(label);
          label.setAttribute('text', originalText.join(' '));

          if (label.cliped) {
            label.setAttribute('text', originalText);
          } else {
            const newWidth = label.AABBBounds.width();

            label.setAttribute('x', x + (sign * (newWidth - prevWidth)) / 2);
          }
        } else {
          label.setAttributes(
            sign === 1
              ? {
                  textAlign: 'start',
                  x: x - prevWidth / 2
                }
              : {
                  textAlign: 'end',
                  x: x + prevWidth / 2
                }
          );
        }
      });
    }
  },
  tooltip: {
    mark: {
      content: [
        {
          key: datum => datum['type'],
          value: datum => datum['value'] + '%'
        }
      ]
    }
  }
};

const vchart = new VChart(spec, { dom: CONTAINER_ID });
vchart.renderSync();

// Just for the convenience of console debugging, DO NOT COPY!
window['vchart'] = vchart;
Image

@xile611 xile611 assigned xile611 and unassigned neuqzxy Jan 26, 2025
@xile611
Copy link
Contributor Author

xile611 commented Jan 26, 2025

function binarySearch(min, max, func) {
  let left = min;
  let right = max;
  let result = max;

  while (left <= right) {
    const mid = Math.floor((left + right) / 2);

    if (func(mid)) {
      result = mid;
      right = mid - 1;
    } else {
      left = mid + 1;
    }
  }

  return result; // 未找到
}

const spec = {
  type: 'pie',
  //width: 800,
  height: 500,
  background: 'pink',
  region: [
    {
      style: {
        fill: 'yellow',
        stroke: '#000',
        lineWidth: 1
      }
    }
  ],
  data: [
    {
      id: 'id0',
      values: [
        {
          type: 'This is a long Auto-Wrap Category Text for Category1. This is a long Auto-Wrap Category Text for Category1',
          value: 24
        },
        { type: 'This is a Category2', value: 20 },
        { type: 'This is a long Auto-Wrap Category Text for Category3', value: 18 },
        { type: 'This is a long Auto-Wrap Category Text for Category4', value: 18 },
        { type: 'This is a long Auto-Wrap Category Text for Category5', value: 16 },
        {
          type: 'This is a long Auto-Wrap Category Text for Category6. This is a long Auto-Wrap Category Text for Category6',
          value: 14
        },
        {
          type: 'This is a long Auto-Wrap Category Text for Category7. This is a long Auto-Wrap Category Text for Category7',
          value: 10
        },
        {
          type: 'This is a long Auto-Wrap Category Text for Category8. This is a long Auto-Wrap Category Text for Category8',
          value: 10
        },
        {
          type: 'This is a long Auto-Wrap Category Text for Category9. This is a long Auto-Wrap Category Text for Category9',
          value: 5
        },
        {
          type: 'This is a long Auto-Wrap Category Text for Category10. This is a long Auto-Wrap Category Text for Category10',
          value: 5
        },
        {
          type: 'This is a long Auto-Wrap Category Text for Category11. This is a long Auto-Wrap Category Text for Category11',
          value: 4
        },
        {
          type: 'This is a long Auto-Wrap Category Text for Category12. This is a long Auto-Wrap Category Text for Category12',
          value: 3
        },
        {
          type: 'This is a long Auto-Wrap Category Text for Category13. This is a long Auto-Wrap Category Text for Category13',
          value: 3
        },
        {
          type: 'This is a long Auto-Wrap Category Text for Category14. This is a long Auto-Wrap Category Text for Category14',
          value: 3
        }
      ]
    }
  ],
  outerRadius: 0.8,
  innerRadius: 0.5,
  padAngle: 0.6,
  valueField: 'value',
  categoryField: 'type',
  pie: {
    style: {
      cornerRadius: 10
    },
    state: {
      hover: {
        outerRadius: 0.85,
        stroke: '#000',
        lineWidth: 1
      },
      selected: {
        outerRadius: 0.85,
        stroke: '#000',
        lineWidth: 1
      }
    }
  },
  legends: {
    visible: true,
    item: {
      background: {
        visible: true,
        style: {
          fill: 'green'
        }
      }
    }
  },
  label: {
    visible: true,
    formatMethod: (label, datum, index) => {
      return [
        `${datum.type.slice(0, datum['__VCHART_DEFAULT_DATA_INDEX'] % 2 ? 10 : 100)}: `,
        `${datum.value}(${datum._percent_}%)`
      ];
    },
    style: {},
    line: {
      line1MinLength: 10,
      line2MinLength: (texts, arcs, attrs) => {
        const line1MinLength = 10;
        const arc = arcs[0];
        const middleAviableLength = attrs.width / 2 - arc.attribute.outerRadius - line1MinLength;
        const minLength = 10;

        if (middleAviableLength <= minLength) {
          return minLength;
        }

        const maxLength = 60;
        const textWidths = texts.map(text => {
          text.originalText = null;

          return text.AABBBounds.width();
        });

        if (textWidths.every(w => w < middleAviableLength)) {
          return Math.max(Math.min(middleAviableLength - Math.max.apply(null, textWidths), maxLength), minLength);
        }

        return 10;
      }
    },
    layout: {
      align: 'labelLine'
    },
    onAfterOverlapping: (labels, getRelatedGraphic, getRelatedPoint, labelComp) => {
      labels.forEach(label => {
        const { x, points, maxLineWidth } = label.attribute;

        if (!label.originalText) {
          label.originalText = label.attribute.text;
        }
        const originalText = label.originalText;
        const sign = points[2].x - points[0].x > 0 ? 1 : -1;
        const prevWidth = label.AABBBounds.width();

        if (label.cliped && maxLineWidth > 0) {
          // 两行的时候发生了自动省略
          // 发生了省略,让文字和数值都左对齐
          const line2Length = Math.abs(points[2].x - points[1].x);

          if (line2Length > 10) {
            const finalDelta = binarySearch(0, line2Length - 10, delta => {
              label.setAttributes({
                textAlign: 'start',
                x: x - prevWidth / 2 - (sign === 1 ? delta : 0),
                maxLineWidth: maxLineWidth + delta
              });

              return !label.cliped;
            });

            label.setAttributes({
              textAlign: 'start',
              x: x - prevWidth / 2 - (sign === 1 ? finalDelta : 0),
              maxLineWidth: maxLineWidth + finalDelta
            });

            // 扩大宽度
            points[2].x += -sign * finalDelta;
          } else {
            // 仅调整对齐方式
            label.setAttributes({
              textAlign: 'start',
              x: x - prevWidth / 2
            });
          }

          if (label.cliped) {
            const allCliped = originalText.every(text => {
              label.setAttributes({
                text
              });

              return label.cliped;
            });

            if (allCliped) {
              console.log(originalText);
              label.setAttribute('text', originalText.join(' '));
            } else {
              label.setAttribute('text', originalText);
            }
          }
        } else {
          const { width } = labelComp.attribute;
          const maxAviableWidth = sign === 1 ? width - points[2].x : points[2].x;

          label.setAttributes({
            text: originalText.join(' '),
            maxLineWidth: maxAviableWidth
          });

          if (label.cliped) {
            const allCliped = originalText.every(text => {
              label.setAttributes({
                text: text,
                maxLineWidth: maxAviableWidth
              });

              return label.cliped;
            });

            if (allCliped) {
              label.setAttribute('text', originalText.join(' '));
            } else {
              label.setAttribute('text', originalText);
            }
          } else {
            const newWidth = label.AABBBounds.width();

            label.setAttribute('x', x + (sign * (newWidth - prevWidth)) / 2);
          }
        }
      });
    }
 },
  tooltip: {
    mark: {
      content: [
        {
          key: datum => datum['type'],
          value: datum => datum['value'] + '%'
        }
      ]
    }
  }
};

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

2 participants