使用 Node.js 定制你的技术雷达:中篇
在上一篇文章中,我们本地已经能够运行起 Thoughtworks 新版本在线雷达。
也能够通过修改 JSON 文件来完成对内容的更新维护,但是距离好用的技术雷达还差很远,尤其是在对雷达图上的技术点、页面内容进行更新维护的时候。
本篇我们就继续来折腾,先从最复杂的雷达图部分开始吧。
回顾技术雷达页面细节
在继续进行定制修改前,需要先了解技术雷达的前端程序的实现方案、大概执行流程及一些原理。
通过分析查看页面资源引用、脚本程序调用,页面模版、页面运行结果状态可以得到几个信息:
- 页面的雷达图是由 D3 在页面文档加载完成后,分析页面 DOM 结构中的数据异步渲染为 SVG 图,未使用当前流行的各种前端响应式数据方案。
- 页面雷达图中的坐标点和技术列表中的内容是通过数据中的 ID 字段进行一一对应的,不论对列表元素还是雷达点进行操作,都会关联选中状态到对应的元素上。
- 页面雷达图中的点的绘制坐标是经过定制公式计算得出,应该和我们提供的 JSON 数据中的方向角
θ
和 半径r
有关,通过简单修改数据内的theta
和radius
可以看到坐标有变化,但是不清楚其对应关系。
因为代码基本都被混淆过了,所以接下来我们需要开始一段简单的前端逆向过程。
探索雷达图的秘密
先从最难的技术点进行攻关:雷达图点坐标点的维护。实现让坐标点的添加、更新维护之类的操作,可以通过页面上“所见即所得”的方式来完成,而不需要修改 JSON 数据中的属性来碰运气。
从 SVG 方向入手
想要完成对点的坐标维护,首先要知道这些坐标点是怎么进行定位的。需要先观察 D3 生成的“可交互” SVG 的结构,结构示例代码:
"quadrant">"502" height="550">"M-187.5,2.2962127484012872e-14A187.5,187.5 0 0,1 -3.4443191226019305e-14,-187.5L0,0Z" fill="#FFF" transform="translate(501, 500)" stroke="#808184" stroke-width="1px">"M-343.75,4.2097233720690264e-14A343.75,343.75 0 0,1 -6.314585058103539e-14,-343.75L-3.4443191226019305e-14,-187.5A187.5,187.5 0 0,0 -187.5,2.2962127484012872e-14Z" fill="#FFF" transform="translate(501, 500)" stroke="#808184" stroke-width="1px">"M-437.5,5.35782974626967e-14A437.5,437.5 0 0,1 -8.036744619404505e-14,-437.5L-6.314585058103539e-14,-343.75A343.75,343.75 0 0,0 -343.75,4.2097233720690264e-14Z" fill="#FFF" transform="translate(501, 500)" stroke="#808184" stroke-width="1px">
..."quadrant-blip tooltipstered" id="blip-9142" xlink:href="techniques/infrastructure-as-code" style="text-decoration: none; cursor: pointer;">"scale(0.7352941176470589) translate(570.5566057222617, 635.8929373024487)" fill="#3DB5BE" class="adopt-blip">"15" cx="18" cy="18" opacity="1">"433.02691597225123" y="485.30363036944755" font-size="10px" font-style="normal" font-weight="bold" fill="white" style="text-anchor: middle;">2
...
虽然页面真实存在的 SVG 图的结构和元素非常多,不过我们只需要关注具体的“点坐标”元素,那些带有
quadrant-blip tooltipstered
类名的 A
标签。可以看到元素的位置是写在 SVG 变换属性 transform
中的,通过设置其缩放属性 scale
和变形属性 transform
来玩的。
这里引申出了两个问题:
这里存在缩放情况,浏览器的坐标体系和 SVG 的坐标体系是否一致,如果不一致如何建立映射关系?transform
属性中的内容和我们的数据的关联是什么,是怎么计算出类似 transform="scale(0.7352941176470589) translate(570.5566057222617, 635.8929373024487)"
的结果的呢?
探索坐标体系找出关联
通过查找资料了解到 SVG 中使用 transform 坐标系,使用的坐标变换方式是线性变换。考虑到我们后续要使用浏览器做所见即所得的坐标维护,所以这里首先要推理浏览器坐标点和 SVG 坐标点的具体关系。
以页面雷达图中第一个点为参考系,写一段代码,用于获取 SVG 第一个节点的各种坐标信息,来辅助我们推理坐标体系关系。
const pictureContainer = document.querySelector("#quadrant svg");
const bodyContainer = document.body;
const firstPoint = document.querySelector("#quadrant svg a g");
let timer = [];
[pictureContainer, bodyContainer, firstPoint].forEach((container, idx) => {
container.addEventListener("mousemove", function (ev) {
if (timer[idx]) clearInterval(timer[idx]);
timer[idx] = setTimeout(function () {
console.group(container.tagName);
console.log(`全局坐标: [${ev.x}, ${ev.y}]`);
const rel = container.getBoundingClientRect();
console.log(`相对坐标: [${rel.x}, ${rel.y}]`, `元素偏移位置: left ${rel.left} top ${rel.top}`);
console.log(`相对偏移: left => ${rel.left} top => ${rel.top}`);
const props = container.getAttribute("transform");
if (props) {
const [scale, propX, propY] = props.match(/(\d+(\.\d+)?)/g);
console.log(`属性坐标: [${propX}, ${propY}]`);
}
console.groupEnd(container.tagName);
console.log();
}, 100);
});
});
在页面中执行上面的代码,并将鼠标移动到页面第一个坐标点上,可以看到类似下面的日志。
g
全局坐标: [1098, 326]
相对坐标: [1081.539306640625, 309.10345458984375] 元素偏移位置: left 1081.539306640625 top 309.10345458984375
相对偏移: left => 1081.539306640625 top => 309.10345458984375
属性坐标: [515.2133485777726, 639.4006860993144],缩放数值: 0.7352941176470589
svg
全局坐标: [1098, 326]
相对坐标: [700.5, -163.25] 元素偏移位置: left 700.5 top -163.25
相对偏移: left => 700.5 top => -163.25
BODY
全局坐标: [1098, 326]
相对坐标: [0, -924] 元素偏移位置: left 0 top -924
相对偏移: left => 0 top => -924
可以看到元素在 SVG 图中定位使用的属性坐标和我们全局捕捉鼠标使用的坐标数值有较大的差异,为了进一步判断,我们需要做一些额外的计算辅助推理:试着使用 SVG 元素的全局坐标和其相对元素偏移相减,发现数字还是偏差较大,试着直接用结果除缩放数值,得到 544.52 这个和属性坐标 515 相对较近的数值。
猜测这里有一定的偏移计算,结合 SVG 和 HTML 的定位体系都是基于元素左上角端点,这个日志采样点应该相对计算点位置偏“右下”,结果相比较属性值偏大应该是合理的。
为了完成程序,我们需要更得到准确的计算方式,也就是进一步探索上一小节中的第二个问题,这个坐标点是如何通过 JSON 文件中的角度和半径计算出来的。
逆向分析查找定位坐标算法
使用节点的属性作为关键词,我们定位到
/js/k8s_production/radar/quadrant.js
这个用于生成 SVG图的脚本程序。脚本原始内容,我在 GitHub 进行了一份存档,如果你感兴趣的话,可以访问这个地址。
前文提过,代码均被混淆压缩,所以这里我们需要先使用编辑器对代码进行格式化。因为坐标属性在
标签上,所以我们尝试使用以下关键词进行搜索定位:
,尝试定位完整片段a g
,a > g
...,尝试定位 XPath 逻辑"g"
,尝试定位动态创建元素
最终,使用最后一个方式定位到了两段代码实现,为了后文叙述方便,我们这里给这两个函数起个名字,坐标点元素创建函数。
function R(t, a, e, r, i) {
if ($radar_chart_ft) {
var n = t.append("g").attr("transform", "scale(" + r / 34 + ") translate(" + (a * (34 / r) - 17) + ", " + (e * (34 / r) - 1) &#