如何在 d3 轴上使用 MathML 标签?

问题描述 投票:0回答:1

我的 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 轴上有漂亮的分数吗?

javascript d3.js mathml
1个回答
0
投票

tickFormat
期望轴刻度标签是
text
元素,而您想使用
html
代替(根据您的工作示例)。

考虑一些事情让这个工作:

  1. 使用

    tickValues
    来控制轴上显示的值的数量 - 如果您保留此自动功能,则分数可能会重叠,因为从屏幕空间的角度来看,分数的呈现比小数大。

  2. 对轴上的每个刻度使用

    foreignObject
    而不是
    text
    元素。使用
    foreignObject
    后,您可以添加 html,因此可以使用

  3. 将小数转换为最小分数 - 有一个建议的实现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>

© www.soinside.com 2019 - 2024. All rights reserved.