export default function TableLegend()

in src/aop/component/TableLegend/index.tsx [28:383]


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

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

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

  const position = config.position.split('-')[0];

  const statistics = useMemo(() => {
    return config?.table?.statistics || [];
  }, [config?.table]);

  // 目前暂时对多重圆环进行特殊处理,待规则统一梳理后,整理数据类型
  const dataType = widgetsCtx.chartName === 'G2MultiPie' ? 'treeNode' : 'common';
  const statisticsRes = useMemo(
    () => getStatistics(chart, statistics, legendField, dataType),
    [chart, statistics, config, legendItems],
  );

  const updateItems = useMemo(() => {
    let newItems: any = legendItems;
    if (dataType === 'treeNode') {
      let filterData = [...(chart?.options?.data ?? [])];
      const firstDepthCount = filterData.filter((sub: any) => sub.depth === 1)?.length;
      const secondDepthCount = filterData.filter((sub: any) => sub.depth === 2)?.length;

      // 增加特殊逻辑,如果目前包含2层以上,则只展示第一层数据
      if (firstDepthCount > 0 && secondDepthCount > 0) {
        filterData = filterData.filter((sub: any) => sub.depth === 1);
      }

      const filterDataIdList = filterData.map((sub: any) => sub.id);
      newItems = newItems.filter((item: ListItem) => filterDataIdList.includes(item.id));

      newItems.map((item: ListItem) => {
        const idx = filterData.findIndex((sub: any) => sub.id === item.id);
        if (typeof item.marker === 'object') {
          item.marker.dataFill = filterData[idx].color ?? null;
        }

        item.data = filterData[idx]?.value ?? filterData[idx]?.rawValue ?? null;
      });

      legendItems.sort((a: any, b: any) => b.data - a.data);
    }

    return newItems;
  }, [legendItems]);

  // 表格列数
  const columns = (statistics?.length || 0) + (config?.table?.custom?.length || 0);

  // legend宽高
  const legendWidth = widgetsCtx?.legendSize?.[0];
  const legendHeight = widgetsCtx?.legendSize?.[1];

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

  useEffect(() => {
    filterLegend(
      chart,
      (value: any) => {
        return !filteredItems.includes(value);
      },
      legendField,
    );
  }, [filteredItems]);

  const activeItem = (itemName: string) => {
    highlightLegend(chart, (value: any) => value === itemName, legendField);
  };

  const clearActive = () => {
    clearHighlight(chart);
  };

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

  // 进位相关配置项
  const chartConfig = widgetsCtx?.props?.config ?? {};
  const formatConfig = useMemo(() => {
    return getFormatConfig(chartConfig);
  }, [chartConfig]);

  // id -> 统计值的最终展示值 映射表
  const valueMap: Record<string, any> = useMemo(() => {
    const newMap: Record<string, any> = {};
    updateItems.map((legendItem: ListItem) => {
      let { name } = legendItem;
      const id = legendItem.id ?? name;

      statistics?.forEach((statistic: string) => {
        let value = statisticsRes[id]?.[statistic];
        if (value || value === 0) {
          if (config?.valueFormatter && typeof config?.valueFormatter === 'function') {
            value = config?.valueFormatter(value);
          } else {
            // value = formatValue(value, config?.decimal);
            let customValueFormatter = null;
            if (Array.isArray(formatConfig)) {
              // 双轴
              // @ts-ignore
              const dataGroup = getItemData(name, widgetsCtx?.rawData, config?.dataType, widgetsCtx?.data);
              customValueFormatter =
                (dataGroup as any)?.yAxis === 1 ? customFormatter(formatConfig[1]) : customFormatter(formatConfig[0]);
            } else {
              // 单轴
              customValueFormatter = customFormatter(formatConfig);
            }

            if (customValueFormatter) {
              value = customValueFormatter(value);
            } else {
              value = formatValue(value, config?.decimal);
            }
          }
        } else {
          value = '-';
        }

        if (!(id in newMap)) {
          newMap[id] = {};
        }
        newMap[id][statistic] = value;
      });

      (config?.table?.custom || []).forEach((customItem: any, index: number) => {
        const title = customItem.title || `custom${index}`;
        const value =
          typeof customItem?.value === 'function'
            ? customItem.value({
                ...legendItem,
                ...statisticsRes[id],
              })
            : customItem?.value ?? '';
        if (!(id in newMap)) {
          newMap[id] = {};
        }
        newMap[id][title] = value;
      });
    });

    return newMap;
  }, [updateItems, statistics, statisticsRes, config, formatConfig]);

  // 根据每列的标题和数值计算每列的宽度
  const widthMap: Record<string, number> = useMemo(() => {
    const newMap: Record<string, any> = {};

    Object.keys(valueMap).forEach((id: string) => {
      const statisticMap = valueMap[id];
      Object.keys(statisticMap).forEach((statistic: string) => {
        const value = statisticMap[statistic].toString();

        if (!(statistic in newMap) || newMap[statistic] < value.length) {
          newMap[statistic] = value;
        }
      });
    });

    Object.keys(newMap).forEach((statistic: string) => {
      // 最小值40
      newMap[statistic] = Math.max(Math.ceil(calcTextWidth(newMap[statistic])), 40);
    });

    return newMap;
  }, [valueMap, config?.table?.custom]);

  // 表格布局
  const gridTemplate = useMemo(() => {
    let res =
      columns > 0
        ? [
            '8px minmax(80px, 40%)',
            ...statistics.map((statistic: string) => `minmax(${widthMap[statistic]}px, ${60 / columns}%)`),
            ...(config?.table?.custom || []).map(
              (customItem: any, index: number) =>
                `minmax(${widthMap[customItem.title || `custom${index}`]}px, ${60 / columns}%)`,
            ),
          ].join(' 8px ')
        : '8px minmax(min(80px, 30%), 100%)';

    // 当有纵向滚动条时,需要给加一列
    if (((updateItems?.length ?? 0) + 1) * 20 > legendHeight) {
      res += ' 8px';
    }

    return res;
  }, [widthMap, statistics, config?.table?.custom, columns, updateItems?.legnth, legendHeight]);

  // 表格最小宽度
  const tableMinWidth = useMemo(() => {
    let res = 10 + 8 + 80;
    statistics.forEach((statistic: string) => {
      res += 8 + widthMap[statistic];
    });

    (config?.table?.columns || []).forEach((customItem: any, index: number) => {
      res += 8 + widthMap[customItem.title || `custom${index}`];
    });

    if (((updateItems?.length ?? 0) + 1) * 20 > legendHeight) {
      res += 8;
    }

    return res;
  }, [widthMap, statistics, config?.table?.custom, columns, updateItems, legendHeight]);

  return (
    <table
      className={`${prefix}-container`}
      style={{
        paddingLeft: position === 'right' ? 10 : 0,
        minWidth: tableMinWidth,
        ...config?.table?.style,
      }}
    >
      {columns > 0 && !config?.table?.hideTitle && (
        <thead className={`${prefix}-thead`}>
          <tr
            className={`${prefix}-tr ${prefix}-legend-title`}
            style={{
              gridTemplateColumns: gridTemplate,
            }}
          >
            <th />
            <th />
            {statistics?.map((statistic: string) => {
              return (
                <>
                  <th />
                  <th key={statistic}>{getText(statistic, widgetsCtx?.language, widgetsCtx?.context?.locale)}</th>
                </>
              );
            })}
            {(config?.table?.custom || []).map((customItem: any, index: number) => {
              return (
                <>
                  <th />
                  <th key={`custom${index}`}>{customItem?.title ?? ''}</th>
                </>
              );
            })}
          </tr>
        </thead>
      )}
      <tbody
        className={`${prefix}-tbody`}
        style={{
          height: `calc(100% - ${columns > 0 && !config?.table?.hideTitle ? 20 : 0}px)`,
          // 有横向滚动条时,需要加个padding
          paddingBottom: legendWidth < tableMinWidth ? 4 : 0,
        }}
      >
        {updateItems.map((legendItem: ListItem, index: number) => {
          let { name, marker } = legendItem;
          const id = legendItem.id ?? name;
          if (itemName) {
            name = itemName?.formatter?.(name, index, legendItem);
          }
          return (
            <tr
              key={id}
              className={`${prefix}-tr ${prefix}-legend-item ${clickable ? 'pointer' : ''}`}
              style={{
                gridTemplateColumns: gridTemplate,
                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 === updateItems?.length - 1 && !filteredItems.includes(id)) {
                      setFilteredItems([]);
                    } else {
                      setFilteredItems(
                        updateItems
                          .map((item: ListItem) => item.id || item.name)
                          .filter((legendName: string) => legendName !== id),
                      );
                    }
                  } else {
                    // 反选
                    if (filteredItems?.length === updateItems?.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]);
                    }
                  }
                }
              }}
            >
              <td className={`${prefix}-marker`}>
                <LegendMarker marker={marker} disable={filteredItems.includes(id)} item={legendItem} />
              </td>
              <td>
                <LegendName name={name} />
              </td>
              {statistics?.map((statistic: string) => {
                const value = valueMap?.[id]?.[statistic];
                return (
                  <>
                    <td />
                    <td className={`${prefix}-statistics`} key={statistic}>
                      {value}
                    </td>
                  </>
                );
              })}
              {(config?.table?.custom || []).map((customItem: any, index: number) => {
                const value = valueMap[id][customItem?.title || `custom${index}`];
                return (
                  <>
                    <td />
                    <td className={`${prefix}-statistics`} key={`custom${index}`}>
                      {value}
                    </td>
                  </>
                );
              })}
            </tr>
          );
        })}
      </tbody>
    </table>
  );
}