export default function FolableLegend()

in src/aop/component/FoldableLegend/index.tsx [24:303]


export default function FolableLegend({ config, chart, legendItems = [] }: FoldableLegendProps) {
  // @ts-ignore
  const { widgetsCtx } = chart;
  const { hoverable, clickable, useReverseChecked } = config;

  // 是否需要折叠
  const [foldable, setFoldable] = useState<boolean>(false);

  // 当前是否折叠
  const [folded, setFolded] = useState<boolean>(true);

  // 显示的元素个数
  const [num, setNum] = useState<number>(0);

  const contentRef = useRef<HTMLDivElement>(null);

  // legendItems的缓存,用于比较是否变化
  const itemsCache = useRef<any[]>([]);

  const containerWidth = widgetsCtx?.size[0];
  const containerHeight = widgetsCtx?.size[1] || 200;

  const [activedItem, setActivedItem] = useState<string>('');
  const [filteredItems, setFilteredItems] = useState<string[]>([]);

  const legendField = widgetsCtx?.legendField || 'type';

  // legend items变化时,重新计算legend
  useEffect(() => {
    // 深比较legend items的数量与名称是否改变
    if (
      itemsCache.current?.length !== legendItems?.length ||
      itemsCache.current?.some((item: any, index: number) => item.name !== legendItems[index].name)
    ) {
      handleFold();
      setFilteredItems([]);
      itemsCache.current = legendItems;
    }
  }, [legendItems]);

  // 图表尺寸变化时,修改legend尺寸,重新变成折叠状
  // useEffect(() => {
  //   handleFold();
  // }, [containerHeight, containerWidth, config]);

  useEffect(() => {
    setFilteredItems([]);
  }, [config]);

  // 点击高亮legend
  useEffect(() => {
    filterLegend(
      chart,
      (value: any) => {
        return !filteredItems.includes(value);
      },
      legendField,
    );
  }, [filteredItems]);

  // hover高亮legend
  const activeItem = (itemName: string) => {
    highlightLegend(chart, (value: any) => value === itemName, legendField);
  };

  // 清除高亮
  const clearActive = () => {
    clearHighlight(chart);
  };

  useEffect(() => {
    clearActive();
    if (activedItem) {
      activeItem(activedItem);
    }
  }, [activedItem]);

  const renderItem = (item: ListItem, index: number, config: any) => {
    let { name, marker } = item;
    const id = item.id || name;
    if (config?.itemName) {
      name = config?.itemName?.formatter?.(name, index, item);
    }
    return (
      <>
        <div
          className={`${prefix}-marker`}
          onClick={(event: MouseEvent) => {
            if (filteredItems.includes(id)) {
              setFilteredItems((pre) => pre.filter((filteredItem) => filteredItem !== id));
            } else if (filteredItems.length !== legendItems.length - 1) {
              setFilteredItems((pre) => [...pre, id]);
              clearActive();
            } else {
              setFilteredItems([]);
            }
            event.stopPropagation();
          }}
        >
          <LegendMarker marker={marker} disable={filteredItems.includes(id)} />
        </div>
        <div>
          <LegendName
            name={name}
            style={{
              maxWidth: containerWidth - 16 - 16 - 4,
              // fontWeight: activedItem === id ? 500 : 'normal',
              // color:
              //   activedItem === id ? themes['widgets-legend-text-highlight'] : themes['widgets-legend-text-normal'],
            }}
          />
        </div>
      </>
    );
  };

  // 判断是否需要折叠及折叠的元素个数
  useEffect(() => {
    if (!contentRef?.current) {
      return;
    }
    setFoldable(contentRef?.current?.scrollHeight !== 20);

    const childrenNodes = contentRef.current.children;
    let index = 0;
    for (index = 0; index < childrenNodes.length; index++) {
      if ((childrenNodes[index] as HTMLDivElement).offsetTop >= 20) {
        break;
      }
    }
    setNum(index);
  }, [containerWidth, legendItems]);

  // 展开所有items
  const handleUnfold = () => {
    widgetsCtx.legendFolded = false;
    // 图表高度缩小
    const chartDom = chart.getCanvas().get('el')?.parentNode?.parentNode;
    const height = containerHeight - Math.min(contentRef?.current?.scrollHeight, containerHeight * 0.3);
    // @ts-ignore
    if (chartDom) {
      chartDom.style.height = `${height}px`;
      try {
        chart.changeSize(containerWidth, height);
      } catch (e) {
        // 业务不透出错误,这里用于调试开放
        // console.log('changeSize error', e);
      }
    }
    setFolded(false);
  };

  // 折叠当前items
  const handleFold = () => {
    widgetsCtx.legendFolded = true;
    // 图表高度恢复
    const chartDom = chart.getCanvas().get('el')?.parentNode?.parentNode;
    if (chartDom) {
      const height = containerHeight - 20;
      // @ts-ignore
      chartDom.style.height = `${height}px`;
      try {
        chart.changeSize(containerWidth, height);
      } catch (e) {
        // 业务不透出错误,这里用于调试开放
        // console.log('changeSize error', e);
      }

      // 滚动到最上方
      if (contentRef.current) {
        contentRef.current.scrollTop = 0;
      }

      setFolded(true);
    }
  };

  return (
    <div className={`${prefix}-container`}>
      <div
        ref={contentRef}
        className={`${prefix}-content`}
        style={{ maxHeight: folded ? 20 : containerHeight * 0.3, overflowY: !folded ? 'auto' : 'hidden' }}
      >
        {legendItems.map((item: ListItem, index: number) => {
          const id = item.id || item.name;
          return (
            <div
              key={id}
              className={`${prefix}-item`}
              style={{
                color: !filteredItems.includes(id)
                  ? activedItem === id
                    ? themes['widgets-legend-text-highlight']
                    : themes['widgets-legend-text-normal']
                  : themes['widgets-color-disable'],
              }}
              onMouseEnter={() => {
                if (hoverable && !filteredItems.includes(id)) {
                  setActivedItem(id);
                }
              }}
              onMouseLeave={() => {
                if (hoverable && !filteredItems.includes(id)) {
                  setActivedItem('');
                }
              }}
              onClick={(event: any) => {
                // 是否按Control
                const hasControl = event.ctrlKey || event.metaKey;
                if (clickable) {
                  if ((!useReverseChecked && !hasControl) || (useReverseChecked && hasControl)) {
                    // 正选
                    if (filteredItems?.length === legendItems?.length - 1 && !filteredItems.includes(id)) {
                      setFilteredItems([]);
                    } else {
                      setFilteredItems(
                        legendItems
                          .map((item: ListItem) => item.id || item.name)
                          .filter((legendName: string) => legendName !== id),
                      );
                    }
                  } else {
                    // 反选
                    if (filteredItems?.length === legendItems?.length - 1 && !filteredItems.includes(id)) {
                      setFilteredItems([]);
                    } else if (filteredItems.includes(id)) {
                      setFilteredItems((pre: string[]) => pre.filter((p: string) => p !== id));
                    } else {
                      setFilteredItems((pre: string[]) => [...pre, id]);
                    }
                  }
                }
              }}
            >
              {renderItem(item, index, config)}
            </div>
          );
        })}
      </div>
      {foldable && folded && (
        <div className={`${prefix}-more`} onClick={handleUnfold}>
          <div className={`${prefix}-name`} style={{ color: themes['widgets-color-text-1'] }}>
            +{legendItems?.length - num}
          </div>
          <svg
            fill="none"
            width="8.149999618530273"
            height="8.487500190734863"
            viewBox="0 0 8.149999618530273 8.487500190734863"
          >
            <g transform="matrix(0,1,-1,0,8.149999618530273,-8.149999618530273)">
              <path
                d="M8.812499618530273,8.15L12.887499618530274,4.075L8.812499618530273,0L8.149999618530273,0.6625L11.562499618530273,4.075L8.149999618530273,7.4875L8.812499618530273,8.15ZM15.312499618530273,4.075L11.899999618530273,7.4875L12.562499618530273,8.15L16.637499618530274,4.075L12.562499618530273,0L11.899999618530273,0.6625L15.312499618530273,4.075Z"
                fill={themes['widgets-color-text-2']}
              />
            </g>
          </svg>
        </div>
      )}
      {!folded && (
        <div className={`${prefix}-fold`} onClick={handleFold}>
          <svg
            fill="none"
            width="8.149999618530273"
            height="8.487500190734863"
            viewBox="0 0 8.149999618530273 8.487500190734863"
          >
            <g transform="matrix(0,-1,-1,0,16.637499809265137,16.637499809265137)">
              <path
                d="M8.812499618530273,16.637500190734862L12.887499618530274,12.562500190734863L8.812499618530273,8.487500190734863L8.149999618530273,9.150000190734863L11.562499618530273,12.562500190734863L8.149999618530273,15.975000190734864L8.812499618530273,16.637500190734862ZM15.312499618530273,12.562500190734863L11.899999618530273,15.975000190734864L12.562499618530273,16.637500190734862L16.637499618530274,12.562500190734863L12.562499618530273,8.487500190734863L11.899999618530273,9.150000190734863L15.312499618530273,12.562500190734863Z"
                fill={themes['widgets-color-text-2']}
              />
            </g>
          </svg>
        </div>
      )}
    </div>
  );
}