@sjtdev/koishi-plugin-dota2tracker 1.4.2 → 1.5.0-pre.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.js +22 -24
- package/package.json +3 -2
- package/queries/MatchInfo.graphql +5 -1
- package/readme.md +14 -5
- package/template/match/match_1.ejs +5 -1
- package/template/match/match_2/extra.css +44 -0
- package/template/match/match_2/regular.css +459 -0
- package/template/match/match_2/regular.ejs +144 -0
- package/template/match/match_2+.ejs +256 -0
- package/template/match/match_2.ejs +12 -611
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="<%= languageTag %>">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Document</title>
|
|
7
|
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css" />
|
|
8
|
+
<%- "<style>" %>
|
|
9
|
+
<%- include("./match_2/regular.css") %>
|
|
10
|
+
<%- include("./match_2/extra.css") %>
|
|
11
|
+
<%- "</style>" %>
|
|
12
|
+
</head>
|
|
13
|
+
<body>
|
|
14
|
+
<section id="regular"><%- include('./match_2/regular') %></section>
|
|
15
|
+
<section id="extra">
|
|
16
|
+
<div id="charts">
|
|
17
|
+
<% const match = data; %>
|
|
18
|
+
<%
|
|
19
|
+
const mainData = match.radiantNetworthLeads;
|
|
20
|
+
|
|
21
|
+
const secondaryData = match.radiantExperienceLeads;
|
|
22
|
+
|
|
23
|
+
const maxValue = Math.max(...mainData.map(Math.abs), ...secondaryData.map(Math.abs));
|
|
24
|
+
const minValue = Math.min(...mainData);
|
|
25
|
+
|
|
26
|
+
const svgWidth = 400;
|
|
27
|
+
const svgHeight = 200;
|
|
28
|
+
const padding = 50;
|
|
29
|
+
|
|
30
|
+
// 以秒为步进单位,下方调用时需要×60,并且需要判断不满整数分钟时对durationSeconds取余补齐长度,
|
|
31
|
+
// 即padding + (index * 60 > match.durationSeconds ? ((((index - 1) * 60) + (match.durationSeconds % 60)) * xScale) : (index * xScale * 60))
|
|
32
|
+
const xScale = (svgWidth - padding * 2) / (match.durationSeconds - 1);
|
|
33
|
+
let yScale = (svgHeight - padding * 2) / (maxValue * 2);
|
|
34
|
+
|
|
35
|
+
// 格式化数字为千分位
|
|
36
|
+
const formatNumber = (num) => Math.floor(num).toLocaleString();
|
|
37
|
+
|
|
38
|
+
// 生成时间标签
|
|
39
|
+
const generateTimeLabels = (length) => {
|
|
40
|
+
const labels = [];
|
|
41
|
+
for (let i = 0; i < length; i += 10) {
|
|
42
|
+
labels.push(`${i.toString().padStart(2, '0')}:00`);
|
|
43
|
+
}
|
|
44
|
+
return labels;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const timeLabels = generateTimeLabels(mainData.length - 1).slice(0,Math.floor(match.durationSeconds / 60) % 10 > 5 ? undefined : -1);
|
|
48
|
+
const valueLabels = [maxValue, maxValue / 2, 0, maxValue / 2 * -1, maxValue * -1];
|
|
49
|
+
%>
|
|
50
|
+
<%
|
|
51
|
+
// 处理主数据点,分割正负区域
|
|
52
|
+
const mainPoints = mainData.map((value, index) => ({
|
|
53
|
+
value,
|
|
54
|
+
test: index * 60,
|
|
55
|
+
x: padding + (index * 60 > match.durationSeconds ? ((((index - 1) * 60) + (match.durationSeconds % 60)) * xScale) : (index * xScale * 60)),
|
|
56
|
+
y: svgHeight / 2 - value * yScale
|
|
57
|
+
}));
|
|
58
|
+
|
|
59
|
+
let upperPoints = [];
|
|
60
|
+
let lowerPoints = [];
|
|
61
|
+
|
|
62
|
+
for (let i = 0; i < mainPoints.length; i++) {
|
|
63
|
+
const current = mainPoints[i];
|
|
64
|
+
const next = mainPoints[i + 1];
|
|
65
|
+
|
|
66
|
+
// 处理当前点
|
|
67
|
+
if (current.value >= 0) {
|
|
68
|
+
upperPoints.push(`${current.x},${current.y}`);
|
|
69
|
+
} else {
|
|
70
|
+
lowerPoints.push(`${current.x},${current.y}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 处理跨零点的线段
|
|
74
|
+
if (next) {
|
|
75
|
+
if ((current.value >= 0 && next.value < 0) || (current.value < 0 && next.value >= 0)) {
|
|
76
|
+
// 计算线段与零轴的交点
|
|
77
|
+
const t = Math.abs(current.value) / (Math.abs(current.value) + Math.abs(next.value));
|
|
78
|
+
const x = current.x + t * (next.x - current.x);
|
|
79
|
+
const y = svgHeight / 2;
|
|
80
|
+
upperPoints.push(`${x},${y}`);
|
|
81
|
+
lowerPoints.push(`${x},${y}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// 闭合多边形路径
|
|
86
|
+
upperPoints.push(`${svgWidth - padding},${svgHeight / 2}`, `${padding},${svgHeight / 2}`);
|
|
87
|
+
lowerPoints.push(`${svgWidth - padding},${svgHeight / 2}`, `${padding},${svgHeight / 2}`);
|
|
88
|
+
|
|
89
|
+
// 添加胜率数据处理
|
|
90
|
+
const winRateMax = 1;
|
|
91
|
+
const winRateMin = 0;
|
|
92
|
+
const winRateScale = (svgHeight - padding * 2) / (winRateMax - winRateMin);
|
|
93
|
+
// 计算胜率折线的坐标
|
|
94
|
+
const winRatePoints = match.winRates ? [match.winRates[0]].concat(match.winRates).map((value, index) => ({
|
|
95
|
+
value,
|
|
96
|
+
x: padding + (index * 60 > match.durationSeconds ? ((((index - 1) * 60) + (match.durationSeconds % 60)) * xScale) : (index * xScale * 60)),
|
|
97
|
+
y: svgHeight - padding - value * winRateScale
|
|
98
|
+
})) : undefined;
|
|
99
|
+
%>
|
|
100
|
+
<svg width="<%= svgWidth %>" height="<%= svgHeight %>" xmlns="http://www.w3.org/2000/svg">
|
|
101
|
+
<text x="50%" y="30" text-anchor="middle" font-size="16" font-weight="bold"><%= $t("dota2tracker.template.situation") %></text>
|
|
102
|
+
<image x="<%= padding %>" y="<%= padding %>" width="20" height="20" href="<%= utils.getImageUrl("logo_radiant") %>"/>
|
|
103
|
+
<image x="<%= padding %>" y="<%= svgHeight - padding * 1.4 %>" width="20" height="20" href="<%= utils.getImageUrl("logo_dire") %>"/>
|
|
104
|
+
<text x="<%= padding + 20 %>" y="<%= padding + 10 %>" dominant-baseline="middle" fill="#3c9028"><%= $t("dota2tracker.template.radiant") %></text>
|
|
105
|
+
<text x="<%= padding + 20 %>" y="<%= svgHeight - padding * 1.4 + 10 %>" dominant-baseline="middle" fill="#9c3628"><%= $t("dota2tracker.template.dire") %></text>
|
|
106
|
+
<!-- 横轴线与标签 -->
|
|
107
|
+
<% for (let i = 0; i < 5; i++) { %>
|
|
108
|
+
<% const y = padding + i * ((svgHeight - padding * 2) / 4) %>
|
|
109
|
+
<line x1="<%= padding %>" y1="<%= y %>" x2="<%= svgWidth - padding %>" y2="<%= y %>" stroke="gray" stroke-width="1" />
|
|
110
|
+
<text x="<%= padding - 2 %>" y="<%= y %>" text-anchor="end" dominant-baseline="middle" fill="#333a"><%= formatNumber(valueLabels[i]) %></text>
|
|
111
|
+
<% } %>
|
|
112
|
+
|
|
113
|
+
<!-- 纵轴线与标签 -->
|
|
114
|
+
<% for (let i = 0; i < mainData.length - 1; i += 10) { %>
|
|
115
|
+
<% const x = padding + i * xScale * 60 %>
|
|
116
|
+
<line x1="<%= x %>" y1="<%= padding %>" x2="<%= x %>" y2="<%= svgHeight - padding %>" stroke="lightgray" stroke-width="1" />
|
|
117
|
+
<text x="<%= x %>" y="<%= svgHeight - padding + 15 %>" text-anchor="middle" fill="#333a"><%= timeLabels[i / 10] %></text>
|
|
118
|
+
<% } %>
|
|
119
|
+
<!-- 额外添加最后一个纵轴线与横轴时间标签 -->
|
|
120
|
+
<line x1="<%= svgWidth - padding %>" y1="<%= padding %>" x2="<%= svgWidth - padding %>" y2="<%= svgHeight - padding %>" stroke="lightgray" stroke-width="1" />
|
|
121
|
+
<text x="<%= svgWidth - padding %>" y="<%= svgHeight - padding + 15 %>" text-anchor="middle" fill="#333a"><%= `${Math.floor(match.durationSeconds / 60)}:${(match.durationSeconds % 60).toString().padStart(2, '0')}` %></text>
|
|
122
|
+
|
|
123
|
+
<!-- 胜率折线 -->
|
|
124
|
+
<% if (match.winRates) { %>
|
|
125
|
+
<polyline points="<%= winRatePoints.map(point => `${point.x},${point.y}`).join(' ') %>" fill="none" stroke="lightgreen" stroke-width="3" />
|
|
126
|
+
<% } %>
|
|
127
|
+
|
|
128
|
+
<!-- 次要数据 -->
|
|
129
|
+
<polyline points="<%= secondaryData.map((value, index) => `${padding + (index * 60 > match.durationSeconds ? ((((index - 1) * 60) + (match.durationSeconds % 60)) * xScale) : (index * xScale * 60))},${svgHeight / 2 - value * yScale}`).join(' ') %>" fill="none" stroke="gray" stroke-width="1" />
|
|
130
|
+
<polygon points="<%= secondaryData.map((value, index) => `${padding + (index * 60 > match.durationSeconds ? ((((index - 1) * 60) + (match.durationSeconds % 60)) * xScale) : (index * xScale * 60))},${svgHeight / 2 - value * yScale}`).join(' ') %> <%= svgWidth - padding %>,<%= svgHeight / 2 %> <%= padding %>,<%= svgHeight / 2 %>" fill="rgba(128, 128, 128, 0.3)" />
|
|
131
|
+
|
|
132
|
+
<!-- 主要数据 -->
|
|
133
|
+
<polyline points="<%= mainData.map((value, index) => `${padding + (index * 60 > match.durationSeconds ? ((((index - 1) * 60) + (match.durationSeconds % 60)) * xScale) : (index * xScale * 60))},${svgHeight / 2 - value * yScale}`).join(' ') %>" fill="none" stroke="rgba(100, 100, 100, 0.3)" stroke-width="2" />
|
|
134
|
+
|
|
135
|
+
<!-- 正区域填充 -->
|
|
136
|
+
<polygon points="<%= upperPoints.join(' ') %>" fill="rgba(0, 255, 0, 0.3)" />
|
|
137
|
+
|
|
138
|
+
<!-- 负区域填充 -->
|
|
139
|
+
<polygon points="<%= lowerPoints.join(' ') %>" fill="rgba(255, 0, 0, 0.3)" />
|
|
140
|
+
|
|
141
|
+
<!-- 图例 -->
|
|
142
|
+
<g transform="translate(<%= padding %>, <%= svgHeight - 20 %>)">
|
|
143
|
+
<!-- 财产总和(折线1) -->
|
|
144
|
+
<g transform="translate(0, 0)">
|
|
145
|
+
<!-- 左半段(浅绿色) -->
|
|
146
|
+
<path d="M0,5 Q5,0 10,5 Z"
|
|
147
|
+
fill="rgba(0, 255, 0, 0.5)"/>
|
|
148
|
+
|
|
149
|
+
<!-- 右半段(浅红色) -->
|
|
150
|
+
<path d="M10,5 Q15,10 20,5 Z"
|
|
151
|
+
fill="rgba(255, 0, 0, 0.5)"/>
|
|
152
|
+
|
|
153
|
+
<!-- 灰色描边 -->
|
|
154
|
+
<path d="M0,5 Q5,0 10,5 T20,5"
|
|
155
|
+
fill="none"
|
|
156
|
+
stroke="rgba(100, 100, 100, 0.3)"
|
|
157
|
+
stroke-width="1"/>
|
|
158
|
+
|
|
159
|
+
<text x="30" y="7" font-size="12"><%= $t("dota2tracker.template.networth") %></text>
|
|
160
|
+
</g>
|
|
161
|
+
|
|
162
|
+
<!-- 经验(折线2) -->
|
|
163
|
+
<g transform="translate(120, 0)">
|
|
164
|
+
<path d="M0,5 Q5,0 10,5 T20,5"
|
|
165
|
+
fill="none"
|
|
166
|
+
stroke="gray"
|
|
167
|
+
stroke-width="1"/>
|
|
168
|
+
<text x="30" y="7" font-size="12"><%= $t("dota2tracker.template.experience") %></text>
|
|
169
|
+
</g>
|
|
170
|
+
|
|
171
|
+
<!-- 胜率(折线3) -->
|
|
172
|
+
<g transform="translate(220, 0)">
|
|
173
|
+
<path d="M0,5 Q5,0 10,5 T20,5"
|
|
174
|
+
fill="none"
|
|
175
|
+
stroke="lightgreen"
|
|
176
|
+
stroke-width="3"/>
|
|
177
|
+
<text x="30" y="7" font-size="12"><%= $t("dota2tracker.template.winrate") %></text>
|
|
178
|
+
</g>
|
|
179
|
+
</g>
|
|
180
|
+
</svg>
|
|
181
|
+
<%
|
|
182
|
+
// 计算每个玩家的财产总和数据
|
|
183
|
+
const playerNetworthData = match.players.map(player => player.stats.networthPerMinute.concat(player.networth));
|
|
184
|
+
|
|
185
|
+
// 找到所有玩家中财产总和的最大值
|
|
186
|
+
const maxNetworth = Math.max(...playerNetworthData.flat().map(Math.abs));
|
|
187
|
+
|
|
188
|
+
// 计算纵坐标轴的刻度
|
|
189
|
+
const networthValueLabels = [maxNetworth, maxNetworth * 0.75, maxNetworth * 0.5, maxNetworth * 0.25, 0];
|
|
190
|
+
|
|
191
|
+
// 获取颜色数据
|
|
192
|
+
const playerColors = dotaconstants.player_colors;
|
|
193
|
+
|
|
194
|
+
yScale = (svgHeight - padding * 2) / maxNetworth;
|
|
195
|
+
%>
|
|
196
|
+
<!-- 新增的经济图表 -->
|
|
197
|
+
<svg width="<%= svgWidth %>" height="<%= svgHeight %>" xmlns="http://www.w3.org/2000/svg">
|
|
198
|
+
<text x="50%" y="30" text-anchor="middle" font-size="16" font-weight="bold">经济</text>
|
|
199
|
+
<!-- 横轴线与标签 -->
|
|
200
|
+
<% for (let i = 0; i < 5; i++) { %>
|
|
201
|
+
<% const y = padding + i * ((svgHeight - padding * 2) / 4) %>
|
|
202
|
+
<line x1="<%= padding %>" y1="<%= y %>" x2="<%= svgWidth - padding %>" y2="<%= y %>" stroke="gray" stroke-width="1" />
|
|
203
|
+
<text x="<%= padding - 2 %>" y="<%= y %>" text-anchor="end" dominant-baseline="middle" fill="#333a"><%= formatNumber(networthValueLabels[i]) %></text>
|
|
204
|
+
<% } %>
|
|
205
|
+
|
|
206
|
+
<!-- 纵轴线与标签 -->
|
|
207
|
+
<% for (let i = 0; i < mainData.length - 1; i += 10) { %>
|
|
208
|
+
<% const x = padding + i * xScale * 60 %>
|
|
209
|
+
<line x1="<%= x %>" y1="<%= padding %>" x2="<%= x %>" y2="<%= svgHeight - padding %>" stroke="lightgray" stroke-width="1" />
|
|
210
|
+
<text x="<%= x %>" y="<%= svgHeight - padding + 15 %>" text-anchor="middle" fill="#333a"><%= timeLabels[i / 10] %></text>
|
|
211
|
+
<% } %>
|
|
212
|
+
<!-- 额外添加最后一个纵轴线与横轴时间标签 -->
|
|
213
|
+
<line x1="<%= svgWidth - padding %>" y1="<%= padding %>" x2="<%= svgWidth - padding %>" y2="<%= svgHeight - padding %>" stroke="lightgray" stroke-width="1" />
|
|
214
|
+
<text x="<%= svgWidth - padding %>" y="<%= svgHeight - padding + 15 %>" text-anchor="middle" fill="#333a"><%= `${Math.floor(match.durationSeconds / 60)}:${(match.durationSeconds % 60).toString().padStart(2, '0')}` %></text>
|
|
215
|
+
|
|
216
|
+
<!-- 绘制每个玩家的财产总和折线 -->
|
|
217
|
+
<% playerNetworthData.forEach((playerData, index) => { %>
|
|
218
|
+
<% const playerSlot = match.players[index].playerSlot; %>
|
|
219
|
+
<% const color = playerColors[playerSlot] || '#000000'; %>
|
|
220
|
+
<% const points = playerData.map((value, i) => `${padding + (i * 60 > match.durationSeconds ? ((((i - 1) * 60) + (match.durationSeconds % 60)) * xScale) : (i * xScale * 60))},${svgHeight - padding - value * yScale}`); %>
|
|
221
|
+
<polyline points="<%= points.join(' ') %>" fill="none" stroke="<%= color %>" stroke-width="2" />
|
|
222
|
+
<% }) %>
|
|
223
|
+
|
|
224
|
+
<!-- 在第二个svg的最后添加以下图例代码 -->
|
|
225
|
+
<g transform="translate(<%= (svgWidth - (match.players.length * 30)) / 2 %>, <%= svgHeight - 27 %>)">
|
|
226
|
+
<% match.players.forEach((player, index) => { %>
|
|
227
|
+
<% const playerSlot = player.playerSlot; %>
|
|
228
|
+
<% const color = playerColors[playerSlot] || '#000000'; %>
|
|
229
|
+
<!-- 头像占位符(后续可替换为实际图片) -->
|
|
230
|
+
<rect
|
|
231
|
+
x="<%= index * 30 %>"
|
|
232
|
+
y="0"
|
|
233
|
+
width="24"
|
|
234
|
+
height="24"
|
|
235
|
+
rx="6"
|
|
236
|
+
ry="6"
|
|
237
|
+
fill="#f0f0f0"
|
|
238
|
+
stroke="<%= color %>"
|
|
239
|
+
stroke-width="3"
|
|
240
|
+
/>
|
|
241
|
+
<!-- 临时文字标识(调试用,完成后可移除) -->
|
|
242
|
+
<image
|
|
243
|
+
x="<%= index * 30 %>"
|
|
244
|
+
y="0"
|
|
245
|
+
width="24"
|
|
246
|
+
height="24"
|
|
247
|
+
href="<%= utils.getImageUrl(player.hero.shortName, ImageType.HeroIcons) %>"
|
|
248
|
+
clip-path="inset(0 round 6)"
|
|
249
|
+
/>
|
|
250
|
+
<% }) %>
|
|
251
|
+
</g>
|
|
252
|
+
</svg>
|
|
253
|
+
</div>
|
|
254
|
+
</section>
|
|
255
|
+
</body>
|
|
256
|
+
</html>
|