Ray1ee01 commited on
Commit
066ffa3
·
verified ·
1 Parent(s): 3983ff4

Upload folder using huggingface_hub

Browse files
modules/chart_engine/template/d3-js/type2_vertical_stacked_bar_chart/vertical_stacked_bar_plain_chart_01.js ADDED
@@ -0,0 +1,259 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ REQUIREMENTS_BEGIN
3
+ {
4
+ "chart_type": "Vertical Stacked Bar Chart",
5
+ "chart_name": "vertical_stacked_bar_plain_chart_01",
6
+ "required_fields": ["x", "y", "group"],
7
+ "required_fields_type": [["categorical"], ["numerical"], ["categorical"]],
8
+ "required_fields_range": [[3, 12], [0, 100], [3, 20]],
9
+ "required_fields_icons": [],
10
+ "required_other_icons": [],
11
+ "required_fields_colors": ["group"],
12
+ "required_other_colors": ["primary", "secondary", "background"],
13
+ "supported_effects": ["gradient", "opacity"],
14
+ "min_height": 600,
15
+ "min_width": 800,
16
+ "background": "light",
17
+ "icon_mark": "none",
18
+ "icon_label": "none",
19
+ "has_x_axis": "yes",
20
+ "has_y_axis": "yes"
21
+ }
22
+ REQUIREMENTS_END
23
+ */
24
+
25
+ function makeChart(containerSelector, data) {
26
+ const colorResolver = chartUtils.color.resolver(data);
27
+ // ---------- 1. 数据准备阶段 ----------
28
+
29
+ // 提取数据和配置
30
+ const jsonData = data; // 完整的JSON数据对象
31
+ const chartData = jsonData.data.data; // 实际数据点数组
32
+ const variables = jsonData.variables || {}; // 图表配置
33
+ const typography = jsonData.typography || { // 字体设置,如果不存在则使用默认值
34
+ title: { font_family: "Arial", font_size: "18px", font_weight: "bold" },
35
+ label: { font_family: "Arial", font_size: "14px", font_weight: "normal" },
36
+ description: { font_family: "Arial", font_size: "14px", font_weight: "normal" },
37
+ annotation: { font_family: "Arial", font_size: "12px", font_weight: "normal" }
38
+ };
39
+ const colors = jsonData.colors || {
40
+ text_color: "#333333",
41
+ other: {
42
+ primary: "#D32F2F", // Red for "Still active"
43
+ secondary: "#AAAAAA", // Gray for "Ended"
44
+ background: "#F0F0F0"
45
+ }
46
+ }; // 颜色设置
47
+ const dataColumns = chartUtils.schema.columns(jsonData); // 数据列定义
48
+
49
+ // 设置视觉效果变量的默认值
50
+ variables.has_shadow = variables.has_shadow || false;
51
+ variables.has_stroke = variables.has_stroke || false;
52
+
53
+ // 清空容器
54
+ d3.select(containerSelector).html("");
55
+
56
+ // 添加数值格式化函数
57
+
58
+ // ---------- 2. 尺寸和布局设置 ----------
59
+
60
+ // 设置图表总尺寸
61
+ const width = variables.width || 600;
62
+ const height = variables.height || 400;
63
+
64
+ // 设置边距
65
+ const margin = {
66
+ top: 50,
67
+ right: 30,
68
+ bottom: 80,
69
+ left: 40
70
+ };
71
+
72
+ // 计算实际绘图区域大小
73
+ const chartWidth = width - margin.left - margin.right;
74
+ const chartHeight = height - margin.top - margin.bottom;
75
+
76
+ // ---------- 3. 提取字段名和单位 ----------
77
+
78
+ // 根据数据列获取字段名
79
+ const xField = chartUtils.schema.channel(jsonData, "x").key || "period";
80
+ const yField = chartUtils.schema.channel(jsonData, "y").key || "value";
81
+ const groupField = chartUtils.schema.channel(jsonData, "group").key || "status";
82
+
83
+ // 获取字段单位(如果存在)
84
+ let xUnit = "";
85
+ let yUnit = "";
86
+ let groupUnit = "";
87
+
88
+ if (chartUtils.schema.channel(jsonData, "x").unit !== "none") {
89
+ xUnit = chartUtils.schema.channel(jsonData, "x").unit;
90
+ }
91
+
92
+ if (chartUtils.schema.channel(jsonData, "y").unit !== "none") {
93
+ yUnit = chartUtils.schema.channel(jsonData, "y").unit;
94
+ }
95
+
96
+ if (chartUtils.schema.channel(jsonData, "group").unit !== "none") {
97
+ groupUnit = chartUtils.schema.channel(jsonData, "group").unit;
98
+ }
99
+
100
+ // ---------- 4. 数据处理 ----------
101
+
102
+ // 获取所有唯一的组值
103
+ const groups = Array.from(new Set(chartData.map(d => d[groupField])));
104
+
105
+ // 处理数据为堆叠格式
106
+ const groupedData = d3.group(chartData, d => d[xField]);
107
+ const processedData = Array.from(groupedData, ([key, values]) => {
108
+ const obj = { period: key };
109
+ groups.forEach(group => {
110
+ obj[group] = d3.sum(values.filter(d => d[groupField] === group), d => +d[yField]);
111
+ });
112
+ obj.total = d3.sum(values, d => +d[yField]);
113
+ return obj;
114
+ });
115
+
116
+ // 创建堆叠生成器
117
+ const stack = d3.stack()
118
+ .keys(groups)
119
+ .order(d3.stackOrderNone)
120
+ .offset(d3.stackOffsetNone);
121
+
122
+ // 生成堆叠数据
123
+ const stackedData = stack(processedData);
124
+
125
+ // ---------- 5. 创建比例尺 ----------
126
+
127
+ // X轴比例尺 - 使用时间段作为分类
128
+ const xScale = d3.scaleBand()
129
+ .domain(processedData.map(d => d.period))
130
+ .range([0, chartWidth])
131
+ .padding(0.3);
132
+
133
+ // Y轴比例尺 - 使用数值
134
+ const yScale = d3.scaleLinear()
135
+ .domain([0, d3.max(processedData, d => d.total)])
136
+ .range([chartHeight, 0])
137
+ .nice();
138
+
139
+ const colorScale = colorResolver.scale(groups, { palette: "category10" })
140
+
141
+ // 确定标签的最大长度:
142
+
143
+ let minXLabelRatio = 1.0
144
+ const maxXLabelWidth = xScale.bandwidth() * 1.03
145
+
146
+ chartData.forEach(d => {
147
+ // x label
148
+ const xLabelText = String(d[xField])
149
+ let currentWidth = chartUtils.text.measure(null, xLabelText).width
150
+ if (currentWidth > maxXLabelWidth) {
151
+ minXLabelRatio = Math.min(minXLabelRatio, maxXLabelWidth / currentWidth)
152
+ }
153
+ })
154
+
155
+
156
+ // ---------- 6. 创建SVG容器 ----------
157
+
158
+ const svg = d3.select(containerSelector)
159
+ .append("svg")
160
+ .attr("width", "100%")
161
+ .attr("height", height)
162
+ .attr("viewBox", `0 0 ${width} ${height}`)
163
+ .attr("style", "max-width: 100%; height: auto;")
164
+ .attr("xmlns", "http://www.w3.org/2000/svg")
165
+ .attr("xmlns:xlink", "http://www.w3.org/1999/xlink");
166
+
167
+ // 添加图表主体容器
168
+ const chartGroup = svg.append("g")
169
+ .attr("transform", `translate(${margin.left}, ${margin.top})`);
170
+
171
+ // ---------- 7. 绘制图表元素 ----------
172
+
173
+ // 绘制堆叠的条形
174
+ const layers = chartGroup.selectAll(".layer")
175
+ .data(stackedData)
176
+ .enter().append("g")
177
+ .attr("class", "layer")
178
+ .style("fill", (d) => colorScale(d.key));
179
+
180
+ layers.selectAll("rect")
181
+ .data(d => d)
182
+ .enter().append("rect")
183
+ .attr("x", d => xScale(d.data.period))
184
+ .attr("y", d => yScale(d[1]))
185
+ .attr("height", d => yScale(d[0]) - yScale(d[1]))
186
+ .attr("width", xScale.bandwidth())
187
+ .style("stroke", variables.has_stroke ? "#ffffff" : "none")
188
+ .style("stroke-width", variables.has_stroke ? 1 : 0);
189
+
190
+ // 添加数值标注
191
+ layers.selectAll("text")
192
+ .data(d => d)
193
+ .enter().append("text")
194
+ .attr("x", d => xScale(d.data.period) + xScale.bandwidth() / 2)
195
+ .attr("y", d => {
196
+ const height = yScale(d[0]) - yScale(d[1]);
197
+ return yScale(d[1]) + height / 2;
198
+ })
199
+ .attr("text-anchor", "middle")
200
+ .attr("dominant-baseline", "middle")
201
+ .style("fill", "#ffffff")
202
+ .style("font-family", typography.annotation.font_family)
203
+ .style("font-size", typography.annotation.font_size)
204
+ .text(d => {
205
+ const value = d[1] - d[0];
206
+ const height = yScale(d[0]) - yScale(d[1]);
207
+ // 只在高度大于16且值大于0时显示文本
208
+ return (height > 16 && value > 0) ? chartUtils.format.autoText(value) + (yUnit ? ` ${yUnit}` : '') : '';
209
+ });
210
+
211
+ // 添加X轴
212
+ const xAxis = chartGroup.append("g")
213
+ .attr("class", "x-axis")
214
+ .attr("transform", `translate(0, ${chartHeight})`)
215
+ .call(d3.axisBottom(xScale)
216
+ .tickSize(0) // 移除刻度线
217
+ .tickPadding(10)) // 增加文字和轴的间距
218
+ .call(g => g.select(".domain").remove()) // 移除轴线
219
+ .selectAll("text")
220
+ .style("text-anchor", minXLabelRatio < 1.0 ? "end" : "middle")
221
+ .attr("transform", minXLabelRatio < 1.0 ? "rotate(-45)" : "rotate(0)") // 根据minXLabelRatio调整文字旋转
222
+ .style("font-family", typography.label.font_family)
223
+ .style("font-size", typography.label.font_size)
224
+ .style("fill", colorResolver.text({ fallback: "#333333" }).value);
225
+ // 添加图例 - 放在图表上方
226
+ const legendGroup = svg.append("g")
227
+ .attr("transform", `translate(0, -50)`);
228
+
229
+ // 计算字段名宽度并添加间距
230
+ const titleWidth = groupField.length * 10;
231
+ const titleMargin = 15;
232
+
233
+
234
+ const legendSize = chartUtils.legend.draw(legendGroup, groups, colors, {
235
+ x: titleWidth + titleMargin,
236
+ y: 0,
237
+ fontSize: 14,
238
+ fontWeight: "bold",
239
+ align: "left",
240
+ maxWidth: chartWidth - titleWidth - titleMargin,
241
+ shape: "rect",
242
+ });
243
+
244
+ // 添加字段名称
245
+ legendGroup.append("text")
246
+ .attr("x", 0)
247
+ .attr("y", legendSize.height / 2)
248
+ .attr("dominant-baseline", "middle")
249
+ .attr("fill", "#333")
250
+ .style("font-size", "16px")
251
+ .style("font-weight", "bold")
252
+ .text(groupField);
253
+
254
+ // 将图例组向上移动 height/2, 并居中
255
+ legendGroup.attr("transform", `translate(${(chartWidth - legendSize.width - titleWidth - titleMargin) / 2}, 0)`);
256
+
257
+
258
+ return svg.node();
259
+ }
modules/chart_engine/template/d3-js/type2_vertical_stacked_bar_chart/vertical_stacked_bar_plain_chart_02.js ADDED
@@ -0,0 +1,313 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ REQUIREMENTS_BEGIN
3
+ {
4
+ "chart_type": "Vertical Stacked Bar Chart",
5
+ "chart_name": "vertical_stacked_bar_plain_chart_02",
6
+ "required_fields": ["x", "y", "group"],
7
+ "required_fields_type": [["categorical"], ["numerical"], ["categorical"]],
8
+ "required_fields_range": [[3, 12], [0, 100], [3, 20]],
9
+ "required_fields_icons": [],
10
+ "required_other_icons": [],
11
+ "required_fields_colors": ["group"],
12
+ "required_other_colors": ["primary", "secondary", "background"],
13
+ "supported_effects": ["gradient", "opacity"],
14
+ "min_height": 600,
15
+ "min_width": 800,
16
+ "background": "light",
17
+ "icon_mark": "none",
18
+ "icon_label": "none",
19
+ "has_x_axis": "yes",
20
+ "has_y_axis": "yes"
21
+ }
22
+ REQUIREMENTS_END
23
+ */
24
+
25
+ // vertical stacked bar chart plain chart#2
26
+ function makeChart(containerSelector, data) {
27
+ const colorResolver = chartUtils.color.resolver(data);
28
+ // ---------- 1. 数据准备阶段 ----------
29
+
30
+ // 提取数据和配置
31
+ const jsonData = data; // 完整的JSON数据对象
32
+ const chartData = jsonData.data.data; // 实际数据点数组
33
+ const variables = jsonData.variables || {}; // 图表配置
34
+ const typography = jsonData.typography || { // 字体设置,如果不存在则使用默认值
35
+ title: { font_family: "Arial", font_size: "18px", font_weight: "bold" },
36
+ label: { font_family: "Arial", font_size: "14px", font_weight: "normal" },
37
+ description: { font_family: "Arial", font_size: "14px", font_weight: "normal" },
38
+ annotation: { font_family: "Arial", font_size: "12px", font_weight: "normal" }
39
+ };
40
+ const colors = jsonData.colors || {
41
+ text_color: "#333333",
42
+ other: {
43
+ primary: "#D32F2F", // Red for "Still active"
44
+ secondary: "#AAAAAA", // Gray for "Ended"
45
+ background: "#F0F0F0"
46
+ }
47
+ }; // 颜色设置
48
+ const dataColumns = chartUtils.schema.columns(jsonData); // 数据列定义
49
+
50
+ // 设置视觉效果变量的默认值
51
+ variables.has_shadow = variables.has_shadow || false;
52
+ variables.has_stroke = variables.has_stroke || false;
53
+
54
+ // 清空容器
55
+ d3.select(containerSelector).html("");
56
+
57
+ // 添加数值格式化函数
58
+
59
+ // ---------- 2. 尺寸和布局设置 ----------
60
+
61
+ // 设置图表总尺寸
62
+ const width = variables.width || 600;
63
+ const height = variables.height || 400;
64
+
65
+ // 设置边距
66
+ const margin = {
67
+ top: 50,
68
+ right: 30,
69
+ bottom: 80,
70
+ left: 40
71
+ };
72
+
73
+ // 计算实际绘图区域大小
74
+ const chartWidth = width - margin.left - margin.right;
75
+ const chartHeight = height - margin.top - margin.bottom;
76
+
77
+ // ---------- 3. 提取字段名和单位 ----------
78
+
79
+ // 根据数据列获取字段名
80
+ const xField = chartUtils.schema.channel(jsonData, "x").key || "period";
81
+ const yField = chartUtils.schema.channel(jsonData, "y").key || "value";
82
+ const groupField = chartUtils.schema.channel(jsonData, "group").key || "status";
83
+
84
+ // 获取字段单位(如果存在)
85
+ let xUnit = "";
86
+ let yUnit = "";
87
+ let groupUnit = "";
88
+
89
+ if (chartUtils.schema.channel(jsonData, "x").unit !== "none") {
90
+ xUnit = chartUtils.schema.channel(jsonData, "x").unit;
91
+ }
92
+
93
+ if (chartUtils.schema.channel(jsonData, "y").unit !== "none") {
94
+ yUnit = chartUtils.schema.channel(jsonData, "y").unit;
95
+ }
96
+
97
+ if (chartUtils.schema.channel(jsonData, "group").unit !== "none") {
98
+ groupUnit = chartUtils.schema.channel(jsonData, "group").unit;
99
+ }
100
+
101
+ // ---------- 4. 数据处理 ----------
102
+
103
+ // 获取所有唯一的组值
104
+ const groups = Array.from(new Set(chartData.map(d => d[groupField])));
105
+
106
+ // 处理数据为堆叠格式
107
+ const groupedData = d3.group(chartData, d => d[xField]);
108
+ const processedData = Array.from(groupedData, ([key, values]) => {
109
+ const obj = { period: key };
110
+ groups.forEach(group => {
111
+ obj[group] = d3.sum(values.filter(d => d[groupField] === group), d => +d[yField]);
112
+ });
113
+ obj.total = d3.sum(values, d => +d[yField]);
114
+ return obj;
115
+ });
116
+
117
+ // 创建堆叠生成器
118
+ const stack = d3.stack()
119
+ .keys(groups)
120
+ .order(d3.stackOrderNone)
121
+ .offset(d3.stackOffsetNone);
122
+
123
+ // 生成堆叠数据
124
+ const stackedData = stack(processedData);
125
+
126
+ // ---------- 5. 创建比例尺 ----------
127
+
128
+ // X轴比例尺 - 使用时间段作为分类
129
+ const xScale = d3.scaleBand()
130
+ .domain(processedData.map(d => d.period))
131
+ .range([0, chartWidth])
132
+ .padding(0.3);
133
+
134
+ // Y轴比例尺 - 使用数值
135
+ const yScale = d3.scaleLinear()
136
+ .domain([0, d3.max(processedData, d => d.total)])
137
+ .range([chartHeight, 0])
138
+ .nice();
139
+
140
+ const colorScale = colorResolver.scale(groups, { palette: "category10" })
141
+
142
+ // ���定标签的最大长度:
143
+
144
+ let minXLabelRatio = 1.0
145
+ const maxXLabelWidth = xScale.bandwidth() * 1.03
146
+
147
+ chartData.forEach(d => {
148
+ // x label
149
+ const xLabelText = String(d[xField])
150
+ let currentWidth = chartUtils.text.measure(null, xLabelText).width
151
+ if (currentWidth > maxXLabelWidth) {
152
+ minXLabelRatio = Math.min(minXLabelRatio, maxXLabelWidth / currentWidth)
153
+ }
154
+ })
155
+
156
+
157
+ // ---------- 6. 创建SVG容器 ----------
158
+
159
+ const svg = d3.select(containerSelector)
160
+ .append("svg")
161
+ .attr("width", "100%")
162
+ .attr("height", height)
163
+ .attr("viewBox", `0 0 ${width} ${height}`)
164
+ .attr("style", "max-width: 100%; height: auto;")
165
+ .attr("xmlns", "http://www.w3.org/2000/svg")
166
+ .attr("xmlns:xlink", "http://www.w3.org/1999/xlink");
167
+
168
+ // 添加图表主体容器
169
+ const chartGroup = svg.append("g")
170
+ .attr("transform", `translate(${margin.left}, ${margin.top})`);
171
+
172
+ // ---------- 7. 绘制图表元素 ----------
173
+
174
+ // 添加圆角矩形路径生成函数
175
+ function roundedRect(x, y, width, height, radius) {
176
+ // 只对顶部添加圆角
177
+ const topRadius = radius;
178
+ const bottomRadius = 0;
179
+
180
+ // 如果高度小于半径,绘制圆弧
181
+ if (height < radius) {
182
+ // 计算圆弧的角度
183
+ const centerY = y + radius;
184
+ const angle = Math.acos((radius - height) / radius);
185
+ const startX = x + radius - radius * Math.sin(angle);
186
+ const endX = x + width - radius + radius * Math.sin(angle);
187
+
188
+ return `M${x},${y + height}
189
+ L${startX},${y + height}
190
+ A${radius},${radius} 0 0,1 ${endX},${y + height}
191
+ L${x + width},${y + height}
192
+ Z`;
193
+ } else {
194
+ // 标准的圆角矩形(半圆顶部)
195
+ return `M${x},${y + height}
196
+ L${x},${y + topRadius}
197
+ Q${x},${y} ${x + topRadius},${y}
198
+ L${x + width - topRadius},${y}
199
+ Q${x + width},${y} ${x + width},${y + topRadius}
200
+ L${x + width},${y + height}
201
+ Z`;
202
+ }
203
+ }
204
+
205
+ // 绘制堆叠的条形
206
+ const layers = chartGroup.selectAll(".layer")
207
+ .data(stackedData)
208
+ .enter().append("g")
209
+ .attr("class", "layer")
210
+ .style("fill", (d) => colorScale(d.key));
211
+
212
+ // 计算圆角半径(条形宽度的50%,形成半圆顶部)
213
+ const cornerRadius = xScale.bandwidth() * 0.5;
214
+
215
+ layers.selectAll("path")
216
+ .data(d => d)
217
+ .enter().append("path")
218
+ .attr("d", (d, i, nodes) => {
219
+ const x = xScale(d.data.period);
220
+ const y = yScale(d[1]);
221
+ const width = xScale.bandwidth();
222
+ const height = yScale(d[0]) - yScale(d[1]);
223
+
224
+ // 检查是否是最顶部的条形(在该x位置的最后一个非零高度的条形)
225
+ const layerIndex = d3.select(nodes[i].parentNode).datum().key;
226
+ const isTopLayer = layerIndex === groups[groups.length - 1] ||
227
+ (height > 0 && !stackedData.slice(stackedData.findIndex(layer => layer.key === layerIndex) + 1)
228
+ .some(nextLayer => {
229
+ const nextData = nextLayer.find(item => item.data.period === d.data.period);
230
+ return nextData && (nextData[1] - nextData[0]) > 0;
231
+ }));
232
+
233
+ // 如果是顶层且有一定高度,则添加圆角或圆弧顶部
234
+ if (isTopLayer && height > 0) {
235
+ return roundedRect(x, y, width, height, cornerRadius);
236
+ } else {
237
+ // 普通矩形
238
+ return `M${x},${y} L${x + width},${y} L${x + width},${y + height} L${x},${y + height} Z`;
239
+ }
240
+ })
241
+ .style("stroke", variables.has_stroke ? "#ffffff" : "none")
242
+ .style("stroke-width", variables.has_stroke ? 1 : 0);
243
+
244
+ // 添加数值标注
245
+ layers.selectAll("text")
246
+ .data(d => d)
247
+ .enter().append("text")
248
+ .attr("x", d => xScale(d.data.period) + xScale.bandwidth() / 2)
249
+ .attr("y", d => {
250
+ const height = yScale(d[0]) - yScale(d[1]);
251
+ return yScale(d[1]) + height / 2;
252
+ })
253
+ .attr("text-anchor", "middle")
254
+ .attr("dominant-baseline", "middle")
255
+ .style("fill", "#ffffff")
256
+ .style("font-family", typography.annotation.font_family)
257
+ .style("font-size", typography.annotation.font_size)
258
+ .text(d => {
259
+ const value = d[1] - d[0];
260
+ const height = yScale(d[0]) - yScale(d[1]);
261
+ // 只在高度大于16且值大于0时显示文本
262
+ return (height > 16 && value > 0) ? chartUtils.format.autoText(value) + (yUnit ? ` ${yUnit}` : '') : '';
263
+ });
264
+
265
+ // 添加X轴
266
+ const xAxis = chartGroup.append("g")
267
+ .attr("class", "x-axis")
268
+ .attr("transform", `translate(0, ${chartHeight})`)
269
+ .call(d3.axisBottom(xScale)
270
+ .tickSize(0) // 移除刻度线
271
+ .tickPadding(10)) // 增加文字和轴的间距
272
+ .call(g => g.select(".domain").remove()) // 移除轴线
273
+ .selectAll("text")
274
+ .style("text-anchor", minXLabelRatio < 1.0 ? "end" : "middle")
275
+ .attr("transform", minXLabelRatio < 1.0 ? "rotate(-45)" : "rotate(0)") // 根据minXLabelRatio调整文字旋转
276
+ .style("font-family", typography.label.font_family)
277
+ .style("font-size", typography.label.font_size)
278
+ .style("fill", colorResolver.text({ fallback: "#333333" }).value);
279
+ // 添加图例 - 放在图表上方
280
+ const legendGroup = svg.append("g")
281
+ .attr("transform", `translate(0, -50)`);
282
+
283
+ // 计算字段名宽度并添加间距
284
+ const titleWidth = groupField.length * 10;
285
+ const titleMargin = 15;
286
+
287
+
288
+ const legendSize = chartUtils.legend.draw(legendGroup, groups, colors, {
289
+ x: titleWidth + titleMargin,
290
+ y: 0,
291
+ fontSize: 14,
292
+ fontWeight: "bold",
293
+ align: "left",
294
+ maxWidth: chartWidth - titleWidth - titleMargin,
295
+ shape: "rect",
296
+ });
297
+
298
+ // 添加字段名称
299
+ legendGroup.append("text")
300
+ .attr("x", 0)
301
+ .attr("y", legendSize.height / 2)
302
+ .attr("dominant-baseline", "middle")
303
+ .attr("fill", "#333")
304
+ .style("font-size", "16px")
305
+ .style("font-weight", "bold")
306
+ .text(groupField);
307
+
308
+ // 将图例组向上移动 height/2, 并居中
309
+ legendGroup.attr("transform", `translate(${(chartWidth - legendSize.width - titleWidth - titleMargin) / 2}, 0)`);
310
+
311
+
312
+ return svg.node();
313
+ }