import json from pyecharts import options as opts from pyecharts.charts import Bar, Line from pyecharts.globals import CurrentConfig from Config.Config import ONLINE_HOST CurrentConfig.ONLINE_HOST = ONLINE_HOST class RuYuanZaiYuanModel: @staticmethod def load_student_data(): try: # 加载招生数据(入园人数) with open("./Data/ZhaoShengCount.json", "r", encoding="utf-8") as f: enrollment_data = json.load(f) # 加载在校生数据(在园人数) with open("./Data/ZaiXiaoShengCount.json", "r", encoding="utf-8") as f: in_school_data = json.load(f) return enrollment_data, in_school_data except Exception as e: print(f"读取学生数据出错: {e}") return [], [] @staticmethod def generate_preschool_education_config(): # 获取学前教育相关数据 enrollment_data, in_school_data = RuYuanZaiYuanModel.load_student_data() # 提取云南省级数据 yunnan_enroll = next((item for item in enrollment_data if item["area_name"] == "云南省"), None) if not yunnan_enroll: return {} # 提取年份数据(2015-2024) years = [str(year) for year in range(2015, 2025)] # 构建学前教育数据 urban_data = [] # 城区数据 town_data = [] # 镇区数据 rural_data = [] # 乡村数据 total_enroll = [] # 总入园数 for year in years: enroll_data = yunnan_enroll["education_data"]["preschool"].get(year, {}) urban_data.append(enroll_data.get("urban", 0) / 10000) # 转换为万人 town_data.append(enroll_data.get("town", 0) / 10000) # 转换为万人 rural_data.append(enroll_data.get("rural", 0) / 10000) # 转换为万人 # 计算总和作为总入园数,而非使用文件中的total字段 calculated_total = enroll_data.get("urban", 0) + enroll_data.get("town", 0) + enroll_data.get("rural", 0) total_enroll.append(calculated_total / 10000) # 转换为万人 # 添加2022年基数的粉色折线 base_year = "2022" # 找到2022年在years中的索引位置 base_index = years.index(base_year) if base_year in years else 0 # 获取2022年的总入园数作为基数 base_value = total_enroll[base_index] if base_index < len(total_enroll) else 0 # 创建2022年基数折线数据(2022-2024年) base_2022_line = [] for i, year in enumerate(years): # 只在2022年及之后显示基数线 if i >= base_index: base_2022_line.append(base_value) else: base_2022_line.append(None) # 2022年之前不显示 # 构建ECharts配置 option = { "tooltip": { "trigger": "axis", "axisPointer": { "type": "cross", "crossStyle": {"color": "#999"} }, "textStyle": {"color": "#333"}, "backgroundColor": "rgba(255, 255, 255, 0.8)", "borderColor": "rgba(0, 0, 0, 0.2)", "borderWidth": 1 }, "legend": { "data": ["城区", "镇区", "乡村", "总入园数", "2022年基数(万人)"], "top": 30, "textStyle": {"color": "#333"}, "icon": "roundRect", "itemWidth": 12, "itemHeight": 12 }, "xAxis": [ { "type": "category", "data": years, "axisPointer": {"type": "shadow"}, "axisLabel": {"color": "#333"}, "axisLine": {"lineStyle": {"color": "#333"}} } ], "yAxis": [ { "type": "value", "name": "人数", "min": 0, "max": 100, "interval": 20, "axisLabel": { "formatter": "{value} 万人", "color": "#333" }, "axisLine": {"lineStyle": {"color": "#333"}}, "splitLine": {"lineStyle": {"color": "rgba(0,0,0,0.1)"}} }, { "type": "value", "name": "总入园数", "min": 0, "max": 140, "interval": 20, "position": "right", "nameLocation": "end", # 将名称放在轴末端 "nameGap": 30, # 增加名称与轴的间距 "axisLabel": { "formatter": "{value} 万人", "color": "#00a859" }, "axisLine": {"show": True, "lineStyle": {"color": "#00a859"}}, # 改为绿色 "axisTick": {"show": True}, "splitLine": {"show": False} } ], "series": [ { "name": "城区", "type": "bar", "data": urban_data, "itemStyle": { "color": "#5470c6", "borderRadius": [6, 6, 0, 0] }, "barWidth": "25%", # 增加柱子宽度 "barGap": "-10%" # 设置负数让柱子重叠以减少空隙 }, { "name": "镇区", "type": "bar", "data": town_data, "itemStyle": { "color": "#91cc75", "borderRadius": [6, 6, 0, 0] }, "barWidth": "25%", # 增加柱子宽度 "barGap": "-10%" # 设置负数让柱子重叠以减少空隙 }, { "name": "乡村", "type": "bar", "data": rural_data, "itemStyle": { "color": "#fac858", "borderRadius": [6, 6, 0, 0] }, "barWidth": "25%", # 增加柱子宽度 "barGap": "-10%" # 设置负数让柱子重叠以减少空隙 }, { "name": "总入园数", "type": "line", "yAxisIndex": 1, "data": total_enroll, "lineStyle": {"color": "#00a859", "width": 3}, # 改为绿色 "symbol": "circle", "symbolSize": 8, "itemStyle": {"color": "#00a859"} # 改为绿色 }, # 添加2022年基数的粉色折线 { "name": "2022年基数(万人)", "type": "line", "yAxisIndex": 1, "data": base_2022_line, "lineStyle": {"color": "#ff9e9e", "width": 2, "type": "solid"}, "symbol": "circle", "symbolSize": 6, "itemStyle": {"color": "#ff9e9e"}, "emphasis": { "focus": "series" }, "z": 5 # 确保折线图显示在柱状图之上,但在总入园数折线之下 } ], "grid": { "left": "3%", "right": "8%", # 增加右侧边距 "bottom": "3%", "containLabel": True } } return option @staticmethod def generate_in_school_education_config(): # 获取学前教育相关数据 enrollment_data, in_school_data = RuYuanZaiYuanModel.load_student_data() # 提取云南省级数据 yunnan_in_school = next((item for item in in_school_data if item["area_name"] == "云南省"), None) if not yunnan_in_school: return {} # 提取年份数据(2015-2024) years = [str(year) for year in range(2015, 2025)] # 构建学前教育数据 urban_data = [] # 城区数据 town_data = [] # 镇区数据 rural_data = [] # 乡村数据 total_in_school = [] # 总在园数 for year in years: # 将education_data改为student_data in_school_year_data = yunnan_in_school["student_data"]["preschool"].get(year, {}) # 先获取原始数据 urban_val = in_school_year_data.get("urban", 0) town_val = in_school_year_data.get("town", 0) rural_val = in_school_year_data.get("rural", 0) # 转换为万人并添加到各自列表 urban_data.append(urban_val / 10000) # 转换为万人 town_data.append(town_val / 10000) # 转换为万人 rural_data.append(rural_val / 10000) # 转换为万人 # 计算总和并转换为万人 calculated_total = (urban_val + town_val + rural_val) / 10000 # 先计算总和再转换为万人 total_in_school.append(calculated_total) # 添加2022年基数的粉色折线 base_year = "2022" # 找到2022年在years中的索引位置 base_index = years.index(base_year) if base_year in years else 0 # 获取2022年的总在园数作为基数 base_value = total_in_school[base_index] if base_index < len(total_in_school) else 0 # 创建2022年基数折线数据(2022-2024年) base_2022_line = [] for i, year in enumerate(years): # 只在2022年及之后显示基数线 if i >= base_index: base_2022_line.append(base_value) else: base_2022_line.append(None) # 2022年之前不显示 # 构建ECharts配置 option = { "tooltip": { "trigger": "axis", "axisPointer": { "type": "cross", "crossStyle": {"color": "#999"} }, "textStyle": {"color": "#333"}, "backgroundColor": "rgba(255, 255, 255, 0.8)", "borderColor": "rgba(0, 0, 0, 0.2)", "borderWidth": 1 }, "legend": { "data": ["城区", "镇区", "乡村", "总在园数", "2022年基数(万人)"], "top": 30, "textStyle": {"color": "#333"}, "icon": "roundRect", "itemWidth": 12, "itemHeight": 12 }, "xAxis": [ { "type": "category", "data": years, "axisPointer": {"type": "shadow"}, "axisLabel": {"color": "#333"}, "axisLine": {"lineStyle": {"color": "#333"}} } ], "yAxis": [ { "type": "value", "name": "人数", "min": 0, "max": 200, # 将左侧Y轴最大值从100修改为200万 "interval": 40, # 调整间隔为40万,确保坐标轴上有5个主要刻度(0,40,80,120,160,200) "axisLabel": { "formatter": "{value} 万人", "color": "#333" }, "axisLine": {"lineStyle": {"color": "#333"}}, "splitLine": {"lineStyle": {"color": "rgba(0,0,0,0.1)"}} }, { "type": "value", "name": "总在园数", "min": 0, "max": 320, # 将右侧Y轴最大值从160扩大一倍到320万 "interval": 60, # 调整间隔为60万 "position": "right", "nameLocation": "end", "nameGap": 30, "axisLabel": { "formatter": "{value} 万人", "color": "#00a859" # 改为绿色 }, "axisLine": {"show": True, "lineStyle": {"color": "#00a859"}}, # 改为绿色 "axisTick": {"show": True}, "splitLine": {"show": False} } ], "series": [ { "name": "城区", "type": "bar", "data": urban_data, "itemStyle": { "color": "#5470c6", "borderRadius": [6, 6, 0, 0] }, "barWidth": "25%", "barGap": "-10%" }, { "name": "镇区", "type": "bar", "data": town_data, "itemStyle": { "color": "#91cc75", "borderRadius": [6, 6, 0, 0] }, "barWidth": "25%", "barGap": "-10%" }, { "name": "乡村", "type": "bar", "data": rural_data, "itemStyle": { "color": "#fac858", "borderRadius": [6, 6, 0, 0] }, "barWidth": "25%", "barGap": "-10%" }, { "name": "总在园数", "type": "line", "yAxisIndex": 1, "data": total_in_school, "lineStyle": {"color": "#00a859", "width": 3}, # 改为绿色 "symbol": "circle", "symbolSize": 8, "itemStyle": {"color": "#00a859"}, # 改为绿色 "emphasis": { "focus": "series" }, "z": 10 # 确保折线图显示在最上层 }, # 添加2022年基数的粉色折线 { "name": "2022年基数(万人)", "type": "line", "yAxisIndex": 1, "data": base_2022_line, "lineStyle": {"color": "#ff9e9e", "width": 2, "type": "solid"}, "symbol": "circle", "symbolSize": 6, "itemStyle": {"color": "#ff9e9e"}, "emphasis": { "focus": "series" }, "z": 5 # 确保折线图显示在柱状图之上,但在总在园数折线之下 } ], "grid": { "left": "3%", "right": "15%", # 稍微增加右侧边距,确保Y轴标签不被截断 "bottom": "3%", "containLabel": True } } return option