Files

173 lines
5.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""报表图表数据聚合(对齐 JNPF ChartUtil"""
from __future__ import annotations
from collections import defaultdict
from decimal import Decimal
from typing import Any, Dict, List, Optional, Set, Tuple
def _parse_field(field: Optional[str]) -> Tuple[Optional[str], Optional[str]]:
"""alias.field -> (alias, field_name)"""
if not field or not isinstance(field, str):
return None, None
parts = field.split(".", 1)
if len(parts) == 2:
return parts[0], parts[1]
return None, field
def _collect_rows(datasets: Dict[str, List[Dict[str, Any]]], dataset_names: Set[str]) -> List[Dict[str, Any]]:
rows: List[Dict[str, Any]] = []
for name in dataset_names:
rows.extend(datasets.get(name) or [])
return rows
def _aggregate(values: List[Any], summary_type: str) -> str:
if not values:
return ""
st = (summary_type or "none").lower()
nums: List[Decimal] = []
for v in values:
try:
nums.append(Decimal(str(v)))
except Exception:
pass
if st == "sum" and nums:
return str(sum(nums))
if st == "avg" and nums:
return str(sum(nums) / len(nums))
if st == "max" and nums:
return str(max(nums))
if st == "min" and nums:
return str(min(nums))
if st == "count":
return str(len(values))
return str(values[-1]) if values else ""
def _build_field(
data_list: List[Dict[str, Any]],
classify_key: Optional[str],
series_name_key: Optional[str],
series_data_key: Optional[str],
max_key: Optional[str],
summary_type: str,
) -> Dict[str, Any]:
chart_map: Dict[Any, Dict[Any, List[Any]]] = defaultdict(lambda: defaultdict(list))
max_map: Dict[Any, List[Any]] = defaultdict(list)
for row in data_list:
if not classify_key:
continue
classify = row.get(classify_key)
if classify is None:
continue
value = row.get(series_data_key) if series_data_key else None
if value is None:
continue
series = row.get(series_name_key) if series_name_key else ""
chart_map[series][classify].append(value)
if max_key:
mx = row.get(max_key)
if mx is not None:
max_map[classify].append(mx)
series_name_list: List[str] = []
classify_map: Dict[Any, List[List[str]]] = defaultdict(list)
max_counts = [0]
for series, classify_name_map in chart_map.items():
series_name_list.append(str(series))
for classify, value_list in classify_name_map.items():
agg = _aggregate(value_list, summary_type)
classify_map[classify].append([agg])
max_counts.append(len(classify_map[classify]))
classify_name_list = sorted(str(k) for k in classify_map.keys())
max_field_list: List[str] = []
for classify in classify_name_list:
objects = max_map.get(classify) or [0]
max_field_list.append(_aggregate(objects, "max"))
max_len = max(max_counts) if max_counts else 0
series_data_list: List[List[str]] = []
for i in range(max_len):
row_data: List[str] = []
for category in classify_name_list:
category_list = classify_map.get(category) or []
category_data = category_list[i] if i < len(category_list) else []
row_data.append(category_data[0] if category_data else "")
series_data_list.append(row_data)
result: Dict[str, Any] = {
"classifyNameField": classify_name_list,
"seriesDataField": series_data_list,
}
if series_name_key:
result["seriesNameField"] = series_name_list
if max_key:
result["maxField"] = max_field_list
return result
def _echart_configs_from_cells(cells: Dict[str, Any]) -> List[Dict[str, Any]]:
configs: List[Dict[str, Any]] = []
if not cells:
return configs
for key, store in (
("floatEcharts", cells.get("floatEcharts")),
("cellEcharts", cells.get("cellEcharts")),
):
if not isinstance(store, dict):
continue
for drawing_id, item in store.items():
if not isinstance(item, dict):
continue
option = item.get("option") or {}
configs.append(
{
"drawingId": item.get("drawingId") or drawing_id,
"option": option,
"source": key,
}
)
return configs
def build_chart_data(
cells: Dict[str, Any],
datasets: Dict[str, List[Dict[str, Any]]],
) -> List[Dict[str, Any]]:
"""
生成预览用 chartData 列表。
每项: { drawingId, field: { classifyNameField, seriesNameField, seriesDataField, maxField? } }
"""
result: List[Dict[str, Any]] = []
for cfg in _echart_configs_from_cells(cells):
drawing_id = cfg.get("drawingId")
option = cfg.get("option") or {}
dataset_names: Set[str] = set()
classify_alias, classify_field = _parse_field(option.get("classifyNameField"))
series_alias, series_name_field = _parse_field(option.get("seriesNameField"))
data_alias, series_data_field = _parse_field(option.get("seriesDataField"))
max_alias, max_field = _parse_field(option.get("maxField"))
for alias in (classify_alias, series_alias, data_alias, max_alias):
if alias:
dataset_names.add(alias)
if not dataset_names:
continue
rows = _collect_rows(datasets, dataset_names)
field = _build_field(
rows,
classify_field,
series_name_field,
series_data_field,
max_field,
option.get("summaryType") or "none",
)
result.append({"drawingId": drawing_id, "field": field})
return result