我的 d3 轴标签使用自然数,我希望它们采用更漂亮的形式,因此我尝试通过 MathJax 使用 MathML。
当我尝试使用 MathML 标签作为
tickFormat
的参数时,它会将标签视为文本,并且实际上不会以 MathML 形式显示它。
在我的 JavaScript 中,我可以获得所需的分数形式以在段落中正确显示...
d3.select('.main').append('p').html('<math><mrow><mfrac><mn>$</mn> <mn>day</mn></mfrac></mrow></math>');
...但是当我尝试将相同的文本应用于我的轴时,就像这样...
graph.selectAll('#axisY')
.data([null])
.join('g')
.attr('id', 'axisY')
.call(d3.axisLeft(y)
.tickFormat((d) => '<math><mrow><mfrac><mn>$</mn> <mn>day</mn></mfrac></mrow></math>')
)
.attr('transform', `translate(${margin.left}, 0)`);
...它像文本一样拼出所有这些标签,而不是实际显示分数。
那么,我怎样才能让 tickFormat 解析标签而不是把它们当作文本来处理呢? ...或者有更简单的方法在 d3 轴上有漂亮的分数吗?
tickFormat
期望轴刻度标签是 text
元素,而您想使用 html
代替(根据您的工作示例)。
考虑一些事情让这个工作:
使用
tickValues
来控制轴上显示的值的数量 - 如果您保留此自动功能,则分数可能会重叠,因为从屏幕空间的角度来看,分数的呈现比小数大。
foreignObject
而不是 text
元素。使用 foreignObject
后,您可以添加 html,因此可以使用 mathjax
将小数转换为最小分数 - 有一个建议的实现here.
下面的工作示例 - 但请注意尺寸和位置调整的硬编码,您需要根据自己的情况进行调整。
// prep
const a1 = [0, 0.33, 0.66, 1];
const a2 = [0, 0.25, 0.5, 0.75, 1];
const width = 500;
const height = 180;
const x = d3.scaleLinear().range([0, 200]).domain([0, 1])
const y = d3.scaleLinear().range([150, 10]).domain([0, 1])
const svg = d3.select("body").append("svg").attr("width", width).attr("height", height);
// set up axes - note setting label as ""
const axis1 = d3.axisLeft(y).tickValues(a1).tickFormat(d => "");
const axis2 = d3.axisBottom(x).tickValues(a2).tickFormat(d => "");
// render axis and mathml conversion for fraction label
const gAxis1 = svg
.append("g")
.attr("id", "axis1")
.attr("transform", "translate(80, 10)")
.style("font-size", 20)
.call(axis1)
.call(mathmlAxis, true);
const gAxis2 = svg
.append("g")
.attr("id", "axis2")
.attr("transform", "translate(150, 90)")
.style("font-size", 20)
.call(axis2)
.call(mathmlAxis, false);
function mathmlAxis(ax, vertical) {
ax.selectAll("g")
.append("svg:foreignObject")
.attr("width", 40)
.attr("height", 40)
.attr("x", vertical ? -45 : -10)
.attr("y", vertical ? -16 : 16)
.append("xhtml:div")
.html((d, i) => mathmlFractionFromDec(d));
}
// https://stackoverflow.com/questions/14002113/how-to-simplify-a-decimal-into-the-smallest-possible-fraction
function mathmlFractionFromDec(x0) {
var eps = 1.0E-15;
var h, h1, h2, k, k1, k2, a, x;
x = x0;
a = Math.floor(x);
h1 = 1;
k1 = 0;
h = a;
k = 1;
while (x-a > eps*k*k) {
x = 1/(x-a);
a = Math.floor(x);
h2 = h1; h1 = h;
k2 = k1; k1 = k;
h = h2 + a*h1;
k = k2 + a*k1;
}
return `<math><mrow><mfrac><mn>${h}</mn><mn>${k}</mn></mfrac></mrow></math>`;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.6.0/d3.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>