@sjtdev/koishi-plugin-dota2tracker 1.5.0-pre.1 → 1.5.0-pre.3
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 +16 -10
- package/package.json +1 -1
- package/template/match/match_2/charts.ejs +237 -0
- package/template/match/match_2/extra.css +9 -2
- package/template/match/match_2+.ejs +7 -240
- package/template/match/match_2.ejs +2 -2
- /package/template/match/match_2/{regular.css → original.css} +0 -0
- /package/template/match/match_2/{regular.ejs → original.ejs} +0 -0
package/lib/index.js
CHANGED
|
@@ -38,14 +38,14 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
38
38
|
// src/locales/en-US.schema.yml
|
|
39
39
|
var require_en_US_schema = __commonJS({
|
|
40
40
|
"src/locales/en-US.schema.yml"(exports2, module2) {
|
|
41
|
-
module2.exports = { _config: { base: { $desc: "Basic Settings", STRATZ_API_TOKEN: "Required. API TOKEN from stratz.com, available at https://stratz.com/api.", dataParsingTimeoutMinutes: "Time to wait for match data parsing (in minutes). If the data parsing time exceeds the waiting time, the report will be generated directly without waiting for the parsing to complete.", urlInMessageType: { $desc: "Include links in messages, <br/>please select the message type:", $inner: ["Include stratz match page link in match query and report messages", "Include stratz player page link in player information query messages", "Include Dota Encyclopedia hero page link in hero data query messages"] } }, rank: { rankBroadSwitch: "Rank change broadcast", rankBroadStar: "Star change broadcast", rankBroadLeader: "Leaderboard rank change broadcast", rankBroadFun: "Fun broadcast template" }, report: { $desc: "Summary Settings", dailyReportSwitch: "Daily Report Function", dailyReportHours: "Daily report time in hours", dailyReportShowCombi: "Show combinations in daily report", weeklyReportSwitch: "Weekly Report Function", weeklyReportDayHours: "Weekly report published on (day) at (hour)", weeklyReportShowCombi: "Show combinations in weekly report" }, template: { $desc: "Template Settings", template_match: "Template used to generate match information images, see https://sjtdev.github.io/koishi-plugin-dota2tracker/template-match.html for template display.", template_player: "Template used to generate player information images. (Currently only one template available)", template_hero: "Template used to generate hero information images. (Currently only one template available)", playerRankEstimate: "Estimate the rank of players without a rank in the player template <br>Estimated rank will be displayed as a gray image" } } };
|
|
41
|
+
module2.exports = { _config: { base: { $desc: "Basic Settings", STRATZ_API_TOKEN: "Required. API TOKEN from stratz.com, available at https://stratz.com/api.", dataParsingTimeoutMinutes: "Time to wait for match data parsing (in minutes). If the data parsing time exceeds the waiting time, the report will be generated directly without waiting for the parsing to complete.", urlInMessageType: { $desc: "Include links in messages, <br/>please select the message type:", $inner: ["Include stratz match page link in match query and report messages", "Include stratz player page link in player information query messages", "Include Dota Encyclopedia hero page link in hero data query messages"] }, proxyAddress: "Proxy address. Leave blank to disable the proxy." }, rank: { rankBroadSwitch: "Rank change broadcast", rankBroadStar: "Star change broadcast", rankBroadLeader: "Leaderboard rank change broadcast", rankBroadFun: "Fun broadcast template" }, report: { $desc: "Summary Settings", dailyReportSwitch: "Daily Report Function", dailyReportHours: "Daily report time in hours", dailyReportShowCombi: "Show combinations in daily report", weeklyReportSwitch: "Weekly Report Function", weeklyReportDayHours: "Weekly report published on (day) at (hour)", weeklyReportShowCombi: "Show combinations in weekly report" }, template: { $desc: "Template Settings", template_match: "Template used to generate match information images, see https://sjtdev.github.io/koishi-plugin-dota2tracker/template-match.html for template display.", template_player: "Template used to generate player information images. (Currently only one template available)", template_hero: "Template used to generate hero information images. (Currently only one template available)", playerRankEstimate: "Estimate the rank of players without a rank in the player template <br>Estimated rank will be displayed as a gray image" } } };
|
|
42
42
|
}
|
|
43
43
|
});
|
|
44
44
|
|
|
45
45
|
// src/locales/zh-CN.schema.yml
|
|
46
46
|
var require_zh_CN_schema = __commonJS({
|
|
47
47
|
"src/locales/zh-CN.schema.yml"(exports2, module2) {
|
|
48
|
-
module2.exports = { _config: { base: { $desc: "基础设置", STRATZ_API_TOKEN: "※必须。stratz.com的API TOKEN,可在 https://stratz.com/api 获取。", dataParsingTimeoutMinutes: "等待比赛数据解析的时间(单位:分钟)。如果数据解析时间超过等待时间,将直接生成战报而不再等待解析完成。", urlInMessageType: { $desc: "在消息中附带链接,<br/>请选择消息类型:", $inner: ["在查询比赛与战报消息中附带stratz比赛页面链接", "在查询玩家信息消息中附带stratz玩家页面链接", "在查询英雄数据消息中附带刀塔百科对应英雄页面链接"] } }, rank: { rankBroadSwitch: "段位变动播报", rankBroadStar: "星级变动播报", rankBroadLeader: "冠绝名次变动播报", rankBroadFun: "整活播报模板" }, report: { $desc: "总结设置", dailyReportSwitch: "日报功能", dailyReportHours: "日报时间小时", dailyReportShowCombi: "日报是否显示组合", weeklyReportSwitch: "周报功能", weeklyReportDayHours: "周报发布于周(几)的(几)点", weeklyReportShowCombi: "周报是否显示组合" }, template: { $desc: "模板设置", template_match: "生成比赛信息图片使用的模板,见 https://sjtdev.github.io/koishi-plugin-dota2tracker/template-match.html 有模板展示。", template_player: "生成玩家信息图片使用的模板。(目前仅有一张模板)", template_hero: "生成英雄信息图片使用的模板。(目前仅有一张模板)", playerRankEstimate: "在player模板中对没有段位的玩家进行段位估算 <br>估算的段位将以灰色图片显示" } } };
|
|
48
|
+
module2.exports = { _config: { base: { $desc: "基础设置", STRATZ_API_TOKEN: "※必须。stratz.com的API TOKEN,可在 https://stratz.com/api 获取。", dataParsingTimeoutMinutes: "等待比赛数据解析的时间(单位:分钟)。如果数据解析时间超过等待时间,将直接生成战报而不再等待解析完成。", urlInMessageType: { $desc: "在消息中附带链接,<br/>请选择消息类型:", $inner: ["在查询比赛与战报消息中附带stratz比赛页面链接", "在查询玩家信息消息中附带stratz玩家页面链接", "在查询英雄数据消息中附带刀塔百科对应英雄页面链接"] }, proxyAddress: "代理地址,留空时不使用代理" }, rank: { rankBroadSwitch: "段位变动播报", rankBroadStar: "星级变动播报", rankBroadLeader: "冠绝名次变动播报", rankBroadFun: "整活播报模板" }, report: { $desc: "总结设置", dailyReportSwitch: "日报功能", dailyReportHours: "日报时间小时", dailyReportShowCombi: "日报是否显示组合", weeklyReportSwitch: "周报功能", weeklyReportDayHours: "周报发布于周(几)的(几)点", weeklyReportShowCombi: "周报是否显示组合" }, template: { $desc: "模板设置", template_match: "生成比赛信息图片使用的模板,见 https://sjtdev.github.io/koishi-plugin-dota2tracker/template-match.html 有模板展示。", template_player: "生成玩家信息图片使用的模板。(目前仅有一张模板)", template_hero: "生成英雄信息图片使用的模板。(目前仅有一张模板)", playerRankEstimate: "在player模板中对没有段位的玩家进行段位估算 <br>估算的段位将以灰色图片显示" } } };
|
|
49
49
|
}
|
|
50
50
|
});
|
|
51
51
|
|
|
@@ -665,8 +665,9 @@ var pluginDir = import_path.default.join(__dirname, "..");
|
|
|
665
665
|
var CONFIGS = { STRATZ_API: { URL: "https://api.stratz.com/graphql", TOKEN: "" } };
|
|
666
666
|
var http = null;
|
|
667
667
|
var setTimeout;
|
|
668
|
+
var proxyAddress;
|
|
668
669
|
function init(params) {
|
|
669
|
-
({ http, setTimeout, APIKEY: CONFIGS.STRATZ_API.TOKEN } = params);
|
|
670
|
+
({ http, setTimeout, APIKEY: CONFIGS.STRATZ_API.TOKEN, proxyAddress } = params);
|
|
670
671
|
}
|
|
671
672
|
__name(init, "init");
|
|
672
673
|
async function fetchData(query2) {
|
|
@@ -676,7 +677,8 @@ async function fetchData(query2) {
|
|
|
676
677
|
"User-Agent": "STRATZ_API",
|
|
677
678
|
"Content-Type": "application/json",
|
|
678
679
|
Authorization: `Bearer ${CONFIGS.STRATZ_API.TOKEN}`
|
|
679
|
-
}
|
|
680
|
+
},
|
|
681
|
+
proxyAgent: proxyAddress || void 0
|
|
680
682
|
});
|
|
681
683
|
}
|
|
682
684
|
__name(fetchData, "fetchData");
|
|
@@ -1382,8 +1384,11 @@ var globRequire_locales_template_yml = __glob({
|
|
|
1382
1384
|
// src/index.ts
|
|
1383
1385
|
var name = "dota2tracker";
|
|
1384
1386
|
var usage = "";
|
|
1385
|
-
var inject =
|
|
1387
|
+
var inject = {
|
|
1388
|
+
required: ["http", "database", "cron", "puppeteer", "cache"]
|
|
1389
|
+
};
|
|
1386
1390
|
var pluginDir2 = import_path2.default.resolve(__dirname, "..");
|
|
1391
|
+
var pluginVersion = require(import_path2.default.join(pluginDir2, "package.json")).version;
|
|
1387
1392
|
var GraphqlLanguageEnum = /* @__PURE__ */ ((GraphqlLanguageEnum2) => {
|
|
1388
1393
|
GraphqlLanguageEnum2["en-US"] = "ENGLISH";
|
|
1389
1394
|
GraphqlLanguageEnum2["zh-CN"] = "S_CHINESE";
|
|
@@ -1393,7 +1398,8 @@ var Config = import_koishi2.Schema.intersect([
|
|
|
1393
1398
|
import_koishi2.Schema.object({
|
|
1394
1399
|
STRATZ_API_TOKEN: import_koishi2.Schema.string().required().role("secret"),
|
|
1395
1400
|
dataParsingTimeoutMinutes: import_koishi2.Schema.number().default(60).min(0).max(1440),
|
|
1396
|
-
urlInMessageType: import_koishi2.Schema.array(import_koishi2.Schema.union([import_koishi2.Schema.const("match"), import_koishi2.Schema.const("player"), import_koishi2.Schema.const("hero")])).role("checkbox")
|
|
1401
|
+
urlInMessageType: import_koishi2.Schema.array(import_koishi2.Schema.union([import_koishi2.Schema.const("match"), import_koishi2.Schema.const("player"), import_koishi2.Schema.const("hero")])).role("checkbox"),
|
|
1402
|
+
proxyAddress: import_koishi2.Schema.string()
|
|
1397
1403
|
}).i18n(Object.keys(GraphqlLanguageEnum).reduce((acc, cur) => (acc[cur] = globRequire_locales_schema_yml(`./locales/${cur}.schema.yml`)._config.base, acc), {})),
|
|
1398
1404
|
import_koishi2.Schema.intersect([
|
|
1399
1405
|
import_koishi2.Schema.object({
|
|
@@ -1445,7 +1451,7 @@ var random = new import_koishi3.Random(() => Math.random());
|
|
|
1445
1451
|
var days_30 = 2592e6;
|
|
1446
1452
|
var constantLocales = {};
|
|
1447
1453
|
async function apply(ctx, config) {
|
|
1448
|
-
init({ http: ctx.http, setTimeout: ctx.setTimeout, APIKEY: config.STRATZ_API_TOKEN });
|
|
1454
|
+
init({ http: ctx.http, setTimeout: ctx.setTimeout, APIKEY: config.STRATZ_API_TOKEN, proxyAddress: config.proxyAddress });
|
|
1449
1455
|
for (const supportLanguageTag of Object.keys(GraphqlLanguageEnum)) {
|
|
1450
1456
|
constantLocales[supportLanguageTag] = globRequire_locales_constants_json(`./locales/${supportLanguageTag}.constants.json`);
|
|
1451
1457
|
ctx.i18n.define(supportLanguageTag, globRequire_locales_yml(`./locales/${supportLanguageTag}.yml`));
|
|
@@ -1612,13 +1618,13 @@ async function apply(ctx, config) {
|
|
|
1612
1618
|
try {
|
|
1613
1619
|
let queryLocal = await ctx.cache.get("dt_previous_query_results", String(matchId));
|
|
1614
1620
|
let matchQuery;
|
|
1615
|
-
if (queryLocal) {
|
|
1616
|
-
matchQuery = queryLocal;
|
|
1621
|
+
if (queryLocal?.data && queryLocal.pluginVersion == pluginVersion) {
|
|
1622
|
+
matchQuery = queryLocal.data;
|
|
1617
1623
|
ctx.cache.set("dt_previous_query_results", String(matchQuery.match.id), queryLocal, days_30);
|
|
1618
1624
|
} else {
|
|
1619
1625
|
matchQuery = await query("MatchInfo", { matchId });
|
|
1620
1626
|
if (matchQuery.match?.parsedDateTime && matchQuery.match.players.filter((player) => player?.stats?.heroDamageReport?.dealtTotal).length > 0)
|
|
1621
|
-
ctx.cache.set("dt_previous_query_results", String(matchQuery.match.id), matchQuery, days_30);
|
|
1627
|
+
ctx.cache.set("dt_previous_query_results", String(matchQuery.match.id), { data: matchQuery, pluginVersion }, days_30);
|
|
1622
1628
|
}
|
|
1623
1629
|
return matchQuery;
|
|
1624
1630
|
} catch (error) {
|
package/package.json
CHANGED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
<div id="charts">
|
|
2
|
+
<% const match = data; %>
|
|
3
|
+
<%
|
|
4
|
+
const mainData = match.radiantNetworthLeads ?? [];
|
|
5
|
+
|
|
6
|
+
const secondaryData = match.radiantExperienceLeads ?? [];
|
|
7
|
+
|
|
8
|
+
const maxValue = Math.max(...mainData.map(Math.abs), ...secondaryData.map(Math.abs));
|
|
9
|
+
const minValue = Math.min(...mainData);
|
|
10
|
+
|
|
11
|
+
const svgWidth = 400;
|
|
12
|
+
const svgHeight = 200;
|
|
13
|
+
const padding = 50;
|
|
14
|
+
|
|
15
|
+
// 以秒为步进单位,下方调用时需要×60,并且需要判断不满整数分钟时对durationSeconds取余补齐长度,
|
|
16
|
+
// 即padding + (index * 60 > match.durationSeconds ? ((((index - 1) * 60) + (match.durationSeconds % 60)) * xScale) : (index * xScale * 60))
|
|
17
|
+
const xScale = (svgWidth - padding * 2) / (match.durationSeconds - 1);
|
|
18
|
+
let yScale = (svgHeight - padding * 2) / (maxValue * 2);
|
|
19
|
+
|
|
20
|
+
// 格式化数字为千分位
|
|
21
|
+
const formatNumber = (num) => Math.floor(num).toLocaleString();
|
|
22
|
+
|
|
23
|
+
// 生成时间标签
|
|
24
|
+
const generateTimeLabels = (length) => {
|
|
25
|
+
const labels = [];
|
|
26
|
+
for (let i = 0; i < length; i += 10) {
|
|
27
|
+
labels.push(`${i.toString().padStart(2, '0')}:00`);
|
|
28
|
+
}
|
|
29
|
+
return labels;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const timeLabels = generateTimeLabels(mainData.length - 1).slice(0,Math.floor(match.durationSeconds / 60) % 10 > 5 ? undefined : -1);
|
|
33
|
+
const valueLabels = [maxValue, maxValue / 2, 0, maxValue / 2 * -1, maxValue * -1];
|
|
34
|
+
%>
|
|
35
|
+
<%
|
|
36
|
+
// 处理主数据点,分割正负区域
|
|
37
|
+
const mainPoints = mainData.map((value, index) => ({
|
|
38
|
+
value,
|
|
39
|
+
test: index * 60,
|
|
40
|
+
x: padding + (index * 60 > match.durationSeconds ? ((((index - 1) * 60) + (match.durationSeconds % 60)) * xScale) : (index * xScale * 60)),
|
|
41
|
+
y: svgHeight / 2 - value * yScale
|
|
42
|
+
}));
|
|
43
|
+
|
|
44
|
+
let upperPoints = [];
|
|
45
|
+
let lowerPoints = [];
|
|
46
|
+
|
|
47
|
+
for (let i = 0; i < mainPoints.length; i++) {
|
|
48
|
+
const current = mainPoints[i];
|
|
49
|
+
const next = mainPoints[i + 1];
|
|
50
|
+
|
|
51
|
+
// 处理当前点
|
|
52
|
+
if (current.value >= 0) {
|
|
53
|
+
upperPoints.push(`${current.x},${current.y}`);
|
|
54
|
+
} else {
|
|
55
|
+
lowerPoints.push(`${current.x},${current.y}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 处理跨零点的线段
|
|
59
|
+
if (next) {
|
|
60
|
+
if ((current.value >= 0 && next.value < 0) || (current.value < 0 && next.value >= 0)) {
|
|
61
|
+
// 计算线段与零轴的交点
|
|
62
|
+
const t = Math.abs(current.value) / (Math.abs(current.value) + Math.abs(next.value));
|
|
63
|
+
const x = current.x + t * (next.x - current.x);
|
|
64
|
+
const y = svgHeight / 2;
|
|
65
|
+
upperPoints.push(`${x},${y}`);
|
|
66
|
+
lowerPoints.push(`${x},${y}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// 闭合多边形路径
|
|
71
|
+
upperPoints.push(`${svgWidth - padding},${svgHeight / 2}`, `${padding},${svgHeight / 2}`);
|
|
72
|
+
lowerPoints.push(`${svgWidth - padding},${svgHeight / 2}`, `${padding},${svgHeight / 2}`);
|
|
73
|
+
|
|
74
|
+
// 添加胜率数据处理
|
|
75
|
+
const winRateMax = 1;
|
|
76
|
+
const winRateMin = 0;
|
|
77
|
+
const winRateScale = (svgHeight - padding * 2) / (winRateMax - winRateMin);
|
|
78
|
+
// 计算胜率折线的坐标
|
|
79
|
+
const winRatePoints = match.winRates ? [match.winRates[0]].concat(match.winRates).map((value, index) => ({
|
|
80
|
+
value,
|
|
81
|
+
x: padding + (index * 60 > match.durationSeconds ? ((((index - 1) * 60) + (match.durationSeconds % 60)) * xScale) : (index * xScale * 60)),
|
|
82
|
+
y: svgHeight - padding - value * winRateScale
|
|
83
|
+
})) : undefined;
|
|
84
|
+
%>
|
|
85
|
+
<svg width="<%= svgWidth %>" height="<%= svgHeight %>" xmlns="http://www.w3.org/2000/svg">
|
|
86
|
+
<text x="50%" y="30" text-anchor="middle" font-size="16" font-weight="bold"><%= $t("dota2tracker.template.situation") %></text>
|
|
87
|
+
<image x="<%= padding %>" y="<%= padding %>" width="20" height="20" href="<%= utils.getImageUrl("logo_radiant") %>"/>
|
|
88
|
+
<image x="<%= padding %>" y="<%= svgHeight - padding * 1.4 %>" width="20" height="20" href="<%= utils.getImageUrl("logo_dire") %>"/>
|
|
89
|
+
<text x="<%= padding + 20 %>" y="<%= padding + 10 %>" dominant-baseline="middle" fill="#3c9028"><%= $t("dota2tracker.template.radiant") %></text>
|
|
90
|
+
<text x="<%= padding + 20 %>" y="<%= svgHeight - padding * 1.4 + 10 %>" dominant-baseline="middle" fill="#9c3628"><%= $t("dota2tracker.template.dire") %></text>
|
|
91
|
+
<!-- 横轴线与标签 -->
|
|
92
|
+
<% for (let i = 0; i < 5; i++) { %>
|
|
93
|
+
<% const y = padding + i * ((svgHeight - padding * 2) / 4) %>
|
|
94
|
+
<line x1="<%= padding %>" y1="<%= y %>" x2="<%= svgWidth - padding %>" y2="<%= y %>" stroke="gray" stroke-width="1" />
|
|
95
|
+
<text x="<%= padding - 2 %>" y="<%= y %>" text-anchor="end" dominant-baseline="middle" fill="#333a"><%= formatNumber(valueLabels[i]) %></text>
|
|
96
|
+
<% } %>
|
|
97
|
+
|
|
98
|
+
<!-- 纵轴线与标签 -->
|
|
99
|
+
<% for (let i = 0; i < mainData.length - 1; i += 10) { %>
|
|
100
|
+
<% const x = padding + i * xScale * 60 %>
|
|
101
|
+
<line x1="<%= x %>" y1="<%= padding %>" x2="<%= x %>" y2="<%= svgHeight - padding %>" stroke="lightgray" stroke-width="1" />
|
|
102
|
+
<text x="<%= x %>" y="<%= svgHeight - padding + 15 %>" text-anchor="middle" fill="#333a"><%= timeLabels[i / 10] %></text>
|
|
103
|
+
<% } %>
|
|
104
|
+
<!-- 额外添加最后一个纵轴线与横轴时间标签 -->
|
|
105
|
+
<line x1="<%= svgWidth - padding %>" y1="<%= padding %>" x2="<%= svgWidth - padding %>" y2="<%= svgHeight - padding %>" stroke="lightgray" stroke-width="1" />
|
|
106
|
+
<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>
|
|
107
|
+
|
|
108
|
+
<!-- 胜率折线 -->
|
|
109
|
+
<% if (match.winRates) { %>
|
|
110
|
+
<polyline points="<%= winRatePoints.map(point => `${point.x},${point.y}`).join(' ') %>" fill="none" stroke="lightgreen" stroke-width="3" />
|
|
111
|
+
<% } %>
|
|
112
|
+
|
|
113
|
+
<!-- 次要数据 -->
|
|
114
|
+
<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" />
|
|
115
|
+
<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)" />
|
|
116
|
+
|
|
117
|
+
<!-- 主要数据 -->
|
|
118
|
+
<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" />
|
|
119
|
+
|
|
120
|
+
<!-- 正区域填充 -->
|
|
121
|
+
<polygon points="<%= upperPoints.join(' ') %>" fill="rgba(0, 255, 0, 0.3)" />
|
|
122
|
+
|
|
123
|
+
<!-- 负区域填充 -->
|
|
124
|
+
<polygon points="<%= lowerPoints.join(' ') %>" fill="rgba(255, 0, 0, 0.3)" />
|
|
125
|
+
|
|
126
|
+
<!-- 图例 -->
|
|
127
|
+
<g transform="translate(<%= padding %>, <%= svgHeight - 20 %>)">
|
|
128
|
+
<!-- 财产总和(折线1) -->
|
|
129
|
+
<g transform="translate(0, 0)">
|
|
130
|
+
<!-- 左半段(浅绿色) -->
|
|
131
|
+
<path d="M0,5 Q5,0 10,5 Z"
|
|
132
|
+
fill="rgba(0, 255, 0, 0.5)"/>
|
|
133
|
+
|
|
134
|
+
<!-- 右半段(浅红色) -->
|
|
135
|
+
<path d="M10,5 Q15,10 20,5 Z"
|
|
136
|
+
fill="rgba(255, 0, 0, 0.5)"/>
|
|
137
|
+
|
|
138
|
+
<!-- 灰色描边 -->
|
|
139
|
+
<path d="M0,5 Q5,0 10,5 T20,5"
|
|
140
|
+
fill="none"
|
|
141
|
+
stroke="rgba(100, 100, 100, 0.3)"
|
|
142
|
+
stroke-width="1"/>
|
|
143
|
+
|
|
144
|
+
<text x="30" y="7" font-size="12"><%= $t("dota2tracker.template.networth") %></text>
|
|
145
|
+
</g>
|
|
146
|
+
|
|
147
|
+
<!-- 经验(折线2) -->
|
|
148
|
+
<g transform="translate(120, 0)">
|
|
149
|
+
<path d="M0,5 Q5,0 10,5 T20,5"
|
|
150
|
+
fill="none"
|
|
151
|
+
stroke="gray"
|
|
152
|
+
stroke-width="1"/>
|
|
153
|
+
<text x="30" y="7" font-size="12"><%= $t("dota2tracker.template.experience") %></text>
|
|
154
|
+
</g>
|
|
155
|
+
|
|
156
|
+
<!-- 胜率(折线3) -->
|
|
157
|
+
<g transform="translate(220, 0)">
|
|
158
|
+
<path d="M0,5 Q5,0 10,5 T20,5"
|
|
159
|
+
fill="none"
|
|
160
|
+
stroke="lightgreen"
|
|
161
|
+
stroke-width="3"/>
|
|
162
|
+
<text x="30" y="7" font-size="12"><%= $t("dota2tracker.template.winrate") %></text>
|
|
163
|
+
</g>
|
|
164
|
+
</g>
|
|
165
|
+
</svg>
|
|
166
|
+
<%
|
|
167
|
+
// 计算每个玩家的财产总和数据
|
|
168
|
+
const playerNetworthData = match.players.map(player => player.stats.networthPerMinute.concat(player.networth));
|
|
169
|
+
|
|
170
|
+
// 找到所有玩家中财产总和的最大值
|
|
171
|
+
const maxNetworth = Math.max(...playerNetworthData.flat().map(Math.abs));
|
|
172
|
+
|
|
173
|
+
// 计算纵坐标轴的刻度
|
|
174
|
+
const networthValueLabels = [maxNetworth, maxNetworth * 0.75, maxNetworth * 0.5, maxNetworth * 0.25, 0];
|
|
175
|
+
|
|
176
|
+
// 获取颜色数据
|
|
177
|
+
const playerColors = dotaconstants.player_colors;
|
|
178
|
+
|
|
179
|
+
yScale = (svgHeight - padding * 2) / maxNetworth;
|
|
180
|
+
%>
|
|
181
|
+
<!-- 新增的经济图表 -->
|
|
182
|
+
<svg width="<%= svgWidth %>" height="<%= svgHeight %>" xmlns="http://www.w3.org/2000/svg">
|
|
183
|
+
<text x="50%" y="30" text-anchor="middle" font-size="16" font-weight="bold">经济</text>
|
|
184
|
+
<!-- 横轴线与标签 -->
|
|
185
|
+
<% for (let i = 0; i < 5; i++) { %>
|
|
186
|
+
<% const y = padding + i * ((svgHeight - padding * 2) / 4) %>
|
|
187
|
+
<line x1="<%= padding %>" y1="<%= y %>" x2="<%= svgWidth - padding %>" y2="<%= y %>" stroke="gray" stroke-width="1" />
|
|
188
|
+
<text x="<%= padding - 2 %>" y="<%= y %>" text-anchor="end" dominant-baseline="middle" fill="#333a"><%= formatNumber(networthValueLabels[i]) %></text>
|
|
189
|
+
<% } %>
|
|
190
|
+
|
|
191
|
+
<!-- 纵轴线与标签 -->
|
|
192
|
+
<% for (let i = 0; i < mainData.length - 1; i += 10) { %>
|
|
193
|
+
<% const x = padding + i * xScale * 60 %>
|
|
194
|
+
<line x1="<%= x %>" y1="<%= padding %>" x2="<%= x %>" y2="<%= svgHeight - padding %>" stroke="lightgray" stroke-width="1" />
|
|
195
|
+
<text x="<%= x %>" y="<%= svgHeight - padding + 15 %>" text-anchor="middle" fill="#333a"><%= timeLabels[i / 10] %></text>
|
|
196
|
+
<% } %>
|
|
197
|
+
<!-- 额外添加最后一个纵轴线与横轴时间标签 -->
|
|
198
|
+
<line x1="<%= svgWidth - padding %>" y1="<%= padding %>" x2="<%= svgWidth - padding %>" y2="<%= svgHeight - padding %>" stroke="lightgray" stroke-width="1" />
|
|
199
|
+
<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>
|
|
200
|
+
|
|
201
|
+
<!-- 绘制每个玩家的财产总和折线 -->
|
|
202
|
+
<% playerNetworthData.forEach((playerData, index) => { %>
|
|
203
|
+
<% const playerSlot = match.players[index].playerSlot; %>
|
|
204
|
+
<% const color = playerColors[playerSlot] || '#000000'; %>
|
|
205
|
+
<% 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}`); %>
|
|
206
|
+
<polyline points="<%= points.join(' ') %>" fill="none" stroke="<%= color %>" stroke-width="2" />
|
|
207
|
+
<% }) %>
|
|
208
|
+
|
|
209
|
+
<!-- 在第二个svg的最后添加以下图例代码 -->
|
|
210
|
+
<g transform="translate(<%= (svgWidth - (match.players.length * 30)) / 2 %>, <%= svgHeight - 27 %>)">
|
|
211
|
+
<% match.players.sort((a, b) => a.networth - b.networth).forEach((player, index) => { %>
|
|
212
|
+
<% const playerSlot = player.playerSlot; %>
|
|
213
|
+
<% const color = playerColors[playerSlot] || '#000000'; %>
|
|
214
|
+
<!-- 头像框 -->
|
|
215
|
+
<rect
|
|
216
|
+
x="<%= index * 30 %>"
|
|
217
|
+
y="0"
|
|
218
|
+
width="24"
|
|
219
|
+
height="24"
|
|
220
|
+
rx="6"
|
|
221
|
+
ry="6"
|
|
222
|
+
fill="#f0f0f0"
|
|
223
|
+
stroke="<%= color %>"
|
|
224
|
+
stroke-width="3"
|
|
225
|
+
/>
|
|
226
|
+
<image
|
|
227
|
+
x="<%= index * 30 %>"
|
|
228
|
+
y="0"
|
|
229
|
+
width="24"
|
|
230
|
+
height="24"
|
|
231
|
+
href="<%= utils.getImageUrl(player.hero.shortName, ImageType.HeroIcons) %>"
|
|
232
|
+
clip-path="inset(0 round 6)"
|
|
233
|
+
/>
|
|
234
|
+
<% }) %>
|
|
235
|
+
</g>
|
|
236
|
+
</svg>
|
|
237
|
+
</div>
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
html {
|
|
2
|
-
overflow:
|
|
2
|
+
overflow: visible;
|
|
3
3
|
}
|
|
4
4
|
|
|
5
5
|
body {
|
|
6
6
|
/* width: 1200px; */
|
|
7
7
|
display: flex;
|
|
8
8
|
flex-direction: column;
|
|
9
|
-
overflow:
|
|
9
|
+
overflow: visible;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
#regular {
|
|
@@ -42,3 +42,10 @@ body {
|
|
|
42
42
|
#charts > .title > .logo > img{
|
|
43
43
|
width: 20px;
|
|
44
44
|
}
|
|
45
|
+
|
|
46
|
+
#extra > .tip {
|
|
47
|
+
width: 100%;
|
|
48
|
+
line-height: 4;
|
|
49
|
+
text-align: center;
|
|
50
|
+
color: #ccc;
|
|
51
|
+
}
|
|
@@ -6,251 +6,18 @@
|
|
|
6
6
|
<title>Document</title>
|
|
7
7
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css" />
|
|
8
8
|
<%- "<style>" %>
|
|
9
|
-
<%- include("./match_2/
|
|
9
|
+
<%- include("./match_2/original.css") %>
|
|
10
10
|
<%- include("./match_2/extra.css") %>
|
|
11
11
|
<%- "</style>" %>
|
|
12
12
|
</head>
|
|
13
13
|
<body>
|
|
14
|
-
<section id="regular"><%- include('./match_2/
|
|
14
|
+
<section id="regular"><%- include('./match_2/original') %></section>
|
|
15
15
|
<section id="extra">
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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>
|
|
16
|
+
<% if (data.parsedDateTime && data.radiantNetworthLeads && data.radiantExperienceLeads) { %>
|
|
17
|
+
<%- include('./match_2/charts') %>
|
|
18
|
+
<% } else { %>
|
|
19
|
+
<div class="tip">比赛未解析或无局势信息,无法展示更多数据。</div>
|
|
20
|
+
<% } %>
|
|
254
21
|
</section>
|
|
255
22
|
</body>
|
|
256
23
|
</html>
|
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
<title>Document</title>
|
|
7
7
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css" />
|
|
8
8
|
<%- "<style>" %>
|
|
9
|
-
<%- include("./match_2/
|
|
9
|
+
<%- include("./match_2/original.css") %>
|
|
10
10
|
<%- "</style>" %>
|
|
11
11
|
</head>
|
|
12
12
|
<body>
|
|
13
|
-
<%- include('./match_2/
|
|
13
|
+
<%- include('./match_2/original') %>
|
|
14
14
|
</body>
|
|
15
15
|
</html>
|
|
File without changes
|
|
File without changes
|