Ray1ee01 commited on
Commit
ac36475
·
verified ·
1 Parent(s): a51c87f

Upload folder using huggingface_hub

Browse files
modules/chart_engine/template/d3-js/type34_layered_area_chart/layered_area_plain_chart_01.js ADDED
@@ -0,0 +1,343 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ REQUIREMENTS_BEGIN
3
+ {
4
+ "chart_type": "Layered Area Chart",
5
+ "chart_name": "layered_area_plain_chart_01",
6
+ "required_fields": ["x", "y", "group"],
7
+ "required_fields_type": [["temporal"], ["numerical"], ["categorical"]],
8
+ "required_fields_range": [[5, 50], [0, "inf"], [2, 6]],
9
+ "required_fields_icons": [],
10
+ "required_other_icons": [],
11
+ "required_fields_colors": ["group"],
12
+ "required_other_colors": [],
13
+ "supported_effects": ["gradient", "opacity"],
14
+ "min_height": 600,
15
+ "min_width": 800,
16
+ "background": "dark",
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
+ // 提取数据
27
+ const jsonData = data;
28
+ let chartData = jsonData.data.data;
29
+ const variables = jsonData.variables;
30
+ const typography = jsonData.typography;
31
+ const colors = jsonData.colors_dark || {};
32
+ const colorResolver = chartUtils.color.resolver(jsonData);
33
+ const dataColumns = chartUtils.schema.columns(jsonData);
34
+ const images = jsonData.images || {};
35
+
36
+ // 清空容器
37
+ d3.select(containerSelector).html("");
38
+
39
+ // 获取字段名
40
+ const xField = chartUtils.schema.columnField(dataColumns, 0);
41
+ const yField = chartUtils.schema.columnField(dataColumns, 1);
42
+ const groupField = chartUtils.schema.columnField(dataColumns, 2);
43
+
44
+ chartData = temporalFilter(chartData, xField);
45
+ if (chartData.length === 0) {
46
+ return;
47
+ }
48
+
49
+ // 获取唯一的组值并按最后y值排序
50
+ const groups = [...new Set(chartData.map(d => d[groupField]))]
51
+ .sort((a, b) => {
52
+ const aLast = chartData.filter(d => d[groupField] === a).slice(-1)[0][yField];
53
+ const bLast = chartData.filter(d => d[groupField] === b).slice(-1)[0][yField];
54
+ return aLast - bLast;
55
+ });
56
+
57
+ // 设置尺寸和边距
58
+ const width = variables.width;
59
+ const height = variables.height;
60
+ const margin = { top: 20, right: 60, bottom: 40, left: 60 };
61
+
62
+ // 创建SVG
63
+ const svg = d3.select(containerSelector)
64
+ .append("svg")
65
+ .attr("width", "100%")
66
+ .attr("height", height)
67
+ .attr("viewBox", `0 0 ${width} ${height}`)
68
+ .attr("style", "max-width: 100%; height: auto;")
69
+ .attr("xmlns", "http://www.w3.org/2000/svg")
70
+ .attr("xmlns:xlink", "http://www.w3.org/1999/xlink");
71
+
72
+ // 创建图表区域
73
+ const chartWidth = width - margin.left - margin.right;
74
+ const chartHeight = height - margin.top - margin.bottom;
75
+
76
+ const g = svg.append("g")
77
+ .attr("transform", `translate(${margin.left}, ${margin.top})`);
78
+
79
+ const { xScale, xTicks, xFormat, timeSpan } = createXAxisScaleAndTicks(chartData, xField, 0, chartWidth);
80
+
81
+ // 创建y轴比例尺 (百分比)
82
+ const yScale = d3.scaleLinear()
83
+ .domain([0, d3.max(chartData, d => +d[yField]) * 1.5])
84
+ .range([chartHeight, 0]);
85
+
86
+ // 添加X轴线
87
+ g.append("line")
88
+ .attr("x1", -40)
89
+ .attr("y1", chartHeight)
90
+ .attr("x2", chartWidth)
91
+ .attr("y2", chartHeight)
92
+ .attr("stroke", "#9badd3")
93
+ .attr("opacity", 0.6)
94
+ .attr("stroke-width", 1)
95
+
96
+ // 添加水平网格线
97
+ const yTicks = d3.ticks(0, d3.max(chartData, d => +d[yField]) * 1.3, 5); // 根据实际数据生成刻度
98
+
99
+ // 添加y轴刻度和标签
100
+ yTicks.forEach(tick => {
101
+ if (tick !== 0) { // 跳过0刻度
102
+ g.append("text")
103
+ .attr("x", -10)
104
+ .attr("y", yScale(tick) + 15)
105
+ .attr("text-anchor", "end")
106
+ .attr("dominant-baseline", "middle")
107
+ .style("font-family", "Arial")
108
+ .style("font-size", "16px")
109
+ .style("fill", "#87aac0")
110
+ .text(tick);
111
+ }
112
+ });
113
+
114
+ // 添加x轴刻度和标签
115
+ xTicks.forEach((tick, i) => {
116
+ if (i === xTicks.length - 1) {
117
+ return;
118
+ }
119
+ // 添加刻度标签
120
+ g.append("text")
121
+ .attr("x", xScale(tick))
122
+ .attr("y", chartHeight + 20)
123
+ .attr("text-anchor", "middle")
124
+ .style("font-family", "Arial")
125
+ .style("font-size", "16px")
126
+ .style("font-weight", "bold")
127
+ .style("fill", "#87aac0")
128
+ .text(xFormat(tick));
129
+ });
130
+
131
+ // 创建面积生成器
132
+ const area = d3.area()
133
+ .x(d => xScale(chartUtils.format.parseDate(d[xField])))
134
+ .y0(chartHeight)
135
+ .y1(d => yScale(+d[yField]))
136
+ .curve(d3.curveLinear);
137
+
138
+ // 创建渐变定义
139
+ const defs = svg.append("defs");
140
+
141
+ // 为每个组创建渐变
142
+ groups.forEach(group => {
143
+ const color = colorResolver.field(group, 0, { fallbackKey: "primary" }).value;
144
+ const gradientId = `gradient-${group.replace(/\s+/g, '-')}`;
145
+
146
+ const gradient = defs.append("linearGradient")
147
+ .attr("id", gradientId)
148
+ .attr("x1", "0%")
149
+ .attr("y1", "0%")
150
+ .attr("x2", "0%")
151
+ .attr("y2", "100%");
152
+
153
+ // 渐变起始颜色(原始颜色)
154
+ gradient.append("stop")
155
+ .attr("offset", "0%")
156
+ .attr("stop-color", d3.color(color))
157
+ .attr("stop-opacity", 1);
158
+
159
+ // 渐变结束颜色(偏白色)
160
+ gradient.append("stop")
161
+ .attr("offset", "100%")
162
+ .attr("stop-color", chartUtils.color.variant(color, { mode: "brighter", amount: 1.0 })) // 使颜色更亮(偏白)
163
+ .attr("stop-opacity", 1);
164
+ });
165
+
166
+ // 创建阴影滤镜 - 修复截断问题
167
+ const filters = defs.append("filter")
168
+ .attr("id", "drop-shadow")
169
+ .attr("x", "-50%") // 扩展滤镜区域左侧
170
+ .attr("y", "-100%") // 扩展滤镜区域上方
171
+ .attr("width", "200%") // 扩展滤镜区域宽度
172
+ .attr("height", "200%"); // 扩展滤镜区域高度
173
+
174
+ // 调整阴影颜色和透明度
175
+ filters.append("feComponentTransfer")
176
+ .append("feFuncA")
177
+ .attr("type", "linear")
178
+ .attr("slope", 0.2); // 降低阴影不透明度,使其更加柔和
179
+
180
+ // 添加高斯模糊
181
+ filters.append("feGaussianBlur")
182
+ .attr("in", "SourceAlpha")
183
+ .attr("stdDeviation", 25) // 调整模糊程度,使阴影更加柔和
184
+ .attr("result", "blur");
185
+
186
+ // 添加偏移
187
+ filters.append("feOffset")
188
+ .attr("in", "blur")
189
+ .attr("dx", 0)
190
+ .attr("dy", 15)
191
+ .attr("result", "offsetBlur");
192
+
193
+ // 合并原始图形和阴影
194
+ const feMerge = filters.append("feMerge");
195
+ feMerge.append("feMergeNode")
196
+ .attr("in", "offsetBlur");
197
+ feMerge.append("feMergeNode")
198
+ .attr("in", "SourceGraphic");
199
+
200
+ // 绘制面积图
201
+ // 按照组的顺序反向绘制,确保第一个组在最上面
202
+ [...groups].reverse().forEach(group => {
203
+ const groupData = chartData.filter(d => d[groupField] === group);
204
+ const gradientId = `gradient-${group.replace(/\s+/g, '-')}`;
205
+
206
+ // 绘制面积
207
+ g.append("path")
208
+ .datum(groupData)
209
+ .attr("fill", `url(#${gradientId})`) // 使用渐变填充
210
+ .attr("opacity", 0.8)
211
+ .attr("d", area)
212
+ .style("filter", "url(#drop-shadow)"); // 添加阴影效果
213
+ });
214
+
215
+ // 添加网格背景
216
+ g.append("rect")
217
+ .attr("class", "background")
218
+ .attr("x", 0)
219
+ .attr("y", yScale(d3.max(yTicks)))
220
+ .attr("width", chartWidth)
221
+ .attr("height", chartHeight - yScale(d3.max(yTicks)))
222
+ .attr("fill", "#87aac0")
223
+ .attr("opacity", 0.1);
224
+
225
+ // 添加垂直网格线
226
+ xTicks.forEach(tick => {
227
+ g.append("line")
228
+ .attr("x1", xScale(tick))
229
+ .attr("y1", yScale(d3.max(yTicks))) // 从y轴最大刻度开始
230
+ .attr("x2", xScale(tick))
231
+ .attr("y2", chartHeight)
232
+ .attr("stroke", "#87aac0")
233
+ .attr("opacity", 0.2)
234
+ .attr("class", "background")
235
+ .attr("stroke-width", 1)
236
+ });
237
+ // 添加水平网格线
238
+ yTicks.forEach(tick => {
239
+ g.append("line")
240
+ .attr("x1", -40)
241
+ .attr("y1", yScale(tick))
242
+ .attr("x2", chartWidth)
243
+ .attr("y2", yScale(tick))
244
+ .attr("stroke", "#87aac0")
245
+ .attr("opacity", 0.2)
246
+ .attr("stroke-width", 1)
247
+ .attr("class", "background")
248
+ });
249
+
250
+ // 获取最后一个刻度的x值
251
+ const lastTickX = xScale(xTicks[xTicks.length - 1]);
252
+
253
+ // 添加最后一年标注
254
+ g.append("text")
255
+ .attr("x", lastTickX)
256
+ .attr("y", yScale(d3.max(yTicks)) - 10) // 调整文本位置到y轴最大刻度上方
257
+ .attr("text-anchor", "middle")
258
+ .style("font-family", "Arial")
259
+ .style("font-size", "14px")
260
+ .style("fill", "#87aac0")
261
+ .text(xFormat(xTicks[xTicks.length - 1]));
262
+
263
+ // 添加倒置的蓝色小三角
264
+ g.append("path")
265
+ .attr("d", "M" + (lastTickX-5) + "," + yScale(d3.max(yTicks)) + " L" + (lastTickX+5) + "," + yScale(d3.max(yTicks)) + " L" + lastTickX + "," + (yScale(d3.max(yTicks))+5) + "Z")
266
+ .attr("fill", "#87aac0");
267
+
268
+ // 添加垂直虚线指向最后一年
269
+ g.append("line")
270
+ .attr("x1", lastTickX)
271
+ .attr("y1", yScale(d3.max(yTicks))+5)
272
+ .attr("x2", lastTickX)
273
+ .attr("y2", chartHeight)
274
+ .attr("stroke", "#87aac0")
275
+ .attr("stroke-width", 1)
276
+ .attr("stroke-dasharray", "3,3");
277
+
278
+ // 添加最终值标签
279
+ let prevLabelYBase = null; // 跟踪上一个标签的Y位置
280
+ const minLabelDistance = 50; // 最小标签间距
281
+
282
+ [...groups].forEach(group => {
283
+ const groupData = chartData.filter(d => d[groupField] === group);
284
+ const lastPoint = groupData[groupData.length - 1];
285
+ const color = colorResolver.field(group, 0, { fallbackKey: "primary" }).value;
286
+
287
+ // 计算标签基础位置
288
+ let labelYBase = yScale(+lastPoint[yField]);
289
+ let labelXBase = xScale(chartUtils.format.parseDate(lastPoint[xField]));
290
+
291
+ // 检查与上一个标签的距离,如果太近则调整位置
292
+ if (prevLabelYBase !== null && Math.abs(prevLabelYBase - labelYBase) < minLabelDistance) {
293
+ labelYBase = labelYBase - minLabelDistance;
294
+ }
295
+
296
+ // 更新上一个标签位置
297
+ prevLabelYBase = labelYBase;
298
+ let textWidth = chartUtils.text.measure(null, group, { fontSize: 10 }).width * 1.3;
299
+ // 创建标签背景
300
+ const labelBg = g.append("rect")
301
+ .attr("x", labelXBase - textWidth / 2)
302
+ .attr("y", labelYBase - 50)
303
+ .attr("rx", 10)
304
+ .attr("ry", 10)
305
+ .attr("width", textWidth)
306
+ .attr("height", 40)
307
+ .attr("fill", color);
308
+
309
+ // 添加倒三角
310
+ g.append("path")
311
+ .attr("d", `M${labelXBase}, ${labelYBase}
312
+ L${labelXBase - 10}, ${labelYBase - 15}
313
+ L${labelXBase + 10}, ${labelYBase - 15} Z`)
314
+ .attr("fill", color);
315
+
316
+ // 添加组名(上部分文本)
317
+ g.append("text")
318
+ .attr("x", labelXBase)
319
+ .attr("y", labelYBase - 40)
320
+ .attr("text-anchor", "middle")
321
+ .attr("dominant-baseline", "middle")
322
+ .style("font-family", "Arial")
323
+ .style("font-size", "10px")
324
+ .style("fill", "#ffffff")
325
+ .style("opacity", 0.5)
326
+ .text(group);
327
+
328
+ // 添加最终值(下部分文本)
329
+ g.append("text")
330
+ .attr("x", labelXBase)
331
+ .attr("y", labelYBase - 22)
332
+ .attr("text-anchor", "middle")
333
+ .attr("dominant-baseline", "middle")
334
+ .style("font-family", "Arial")
335
+ .style("font-size", "22px")
336
+ .style("font-weight", "bold")
337
+ .style("fill", "#ffffff")
338
+ .text(chartUtils.format.integer(lastPoint[yField]).text);
339
+
340
+ });
341
+
342
+ return svg.node();
343
+ }