马一丁

Improve the Rendering of Donut Charts

@@ -355,6 +355,57 @@ class ChartToSVGConverter: @@ -355,6 +355,57 @@ class ChartToSVGConverter:
355 355
356 return colors 356 return colors
357 357
  358 + def _align_labels_and_data(
  359 + self,
  360 + labels: Any,
  361 + dataset_data: Any,
  362 + chart_type: str,
  363 + require_positive_sum: bool = False
  364 + ) -> Tuple[List[str], List[float]]:
  365 + """
  366 + 对齐类别型图表的标签与数据长度,并清理非数值值。
  367 +
  368 + Matplotlib的饼图/圆环图要求labels与数据长度一致,否则会抛出错误。
  369 + """
  370 + original_label_len = len(labels) if isinstance(labels, list) else 0
  371 + original_data_len = len(dataset_data) if isinstance(dataset_data, list) else 0
  372 +
  373 + aligned_labels = [str(label) for label in labels] if isinstance(labels, list) else []
  374 + raw_data = dataset_data if isinstance(dataset_data, list) else []
  375 +
  376 + cleaned_data: List[float] = []
  377 + for value in raw_data:
  378 + try:
  379 + numeric = float(value) if value is not None else 0.0
  380 + except (TypeError, ValueError):
  381 + numeric = 0.0
  382 + if numeric < 0:
  383 + numeric = 0.0
  384 + cleaned_data.append(numeric)
  385 +
  386 + target_len = max(len(aligned_labels), len(cleaned_data))
  387 + if target_len == 0:
  388 + return [], []
  389 +
  390 + if len(aligned_labels) < target_len:
  391 + start = len(aligned_labels)
  392 + aligned_labels.extend([f"未命名{start + idx + 1}" for idx in range(target_len - start)])
  393 +
  394 + if len(cleaned_data) < target_len:
  395 + cleaned_data.extend([0.0] * (target_len - len(cleaned_data)))
  396 +
  397 + if original_label_len != original_data_len:
  398 + logger.warning(
  399 + f"{chart_type}图labels长度({original_label_len})与data长度({original_data_len})不一致,"
  400 + f"已对齐为{target_len}"
  401 + )
  402 +
  403 + if require_positive_sum and not any(value > 0 for value in cleaned_data):
  404 + logger.warning(f"{chart_type}图数据为空,跳过渲染")
  405 + return [], []
  406 +
  407 + return aligned_labels[:target_len], cleaned_data[:target_len]
  408 +
358 def _figure_to_svg(self, fig: Any) -> str: 409 def _figure_to_svg(self, fig: Any) -> str:
359 """ 410 """
360 将matplotlib图表转换为SVG字符串 411 将matplotlib图表转换为SVG字符串
@@ -705,6 +756,16 @@ class ChartToSVGConverter: @@ -705,6 +756,16 @@ class ChartToSVGConverter:
705 dataset = datasets[0] 756 dataset = datasets[0]
706 dataset_data = dataset.get('data', []) 757 dataset_data = dataset.get('data', [])
707 758
  759 + labels, dataset_data = self._align_labels_and_data(
  760 + labels,
  761 + dataset_data,
  762 + chart_type="饼",
  763 + require_positive_sum=True
  764 + )
  765 +
  766 + if not labels or not dataset_data:
  767 + return None
  768 +
708 title = props.get('title') 769 title = props.get('title')
709 fig, ax = self._create_figure(width, height, dpi, title) 770 fig, ax = self._create_figure(width, height, dpi, title)
710 771
@@ -764,6 +825,16 @@ class ChartToSVGConverter: @@ -764,6 +825,16 @@ class ChartToSVGConverter:
764 dataset = datasets[0] 825 dataset = datasets[0]
765 dataset_data = dataset.get('data', []) 826 dataset_data = dataset.get('data', [])
766 827
  828 + labels, dataset_data = self._align_labels_and_data(
  829 + labels,
  830 + dataset_data,
  831 + chart_type="圆环",
  832 + require_positive_sum=True
  833 + )
  834 +
  835 + if not labels or not dataset_data:
  836 + return None
  837 +
767 title = props.get('title') 838 title = props.get('title')
768 fig, ax = self._create_figure(width, height, dpi, title) 839 fig, ax = self._create_figure(width, height, dpi, title)
769 840
@@ -941,6 +1012,16 @@ class ChartToSVGConverter: @@ -941,6 +1012,16 @@ class ChartToSVGConverter:
941 dataset = datasets[0] 1012 dataset = datasets[0]
942 dataset_data = dataset.get('data', []) 1013 dataset_data = dataset.get('data', [])
943 1014
  1015 + labels, dataset_data = self._align_labels_and_data(
  1016 + labels,
  1017 + dataset_data,
  1018 + chart_type="极地区域",
  1019 + require_positive_sum=False
  1020 + )
  1021 +
  1022 + if not labels or not dataset_data:
  1023 + return None
  1024 +
944 title = props.get('title') 1025 title = props.get('title')
945 fig = plt.figure(figsize=(width/dpi, height/dpi), dpi=dpi) 1026 fig = plt.figure(figsize=(width/dpi, height/dpi), dpi=dpi)
946 ax = fig.add_subplot(111, projection='polar') 1027 ax = fig.add_subplot(111, projection='polar')