0

I have this code below that consist of 2 buttons one for opening the modal that consist of my D3 Force graph and the other for capturing and downloading an image of my modal.

The problem is that my graph is set with the Raleway font but when I capture them and download the image of my graph it doesn't render the font and font size for some reason I have no idea why this is happening.

This is how my graph is supposed to look like.

enter image description here

This is how it captures it. Can anyone tell me why this is happening any help would be greatly appreciated thanks!

enter image description here

<!DOCTYPE html>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<html>

<head>
  <meta charset="utf-8" />
  <style>
    body {
      font-family: Raleway;
    }

    .modal-posit {
      position: relative;
    }

    .links line {
      stroke: #999;
      stroke-opacity: 0.6;
    }

    .nodes circle {
      stroke: #000;
      stroke-width: 1.5px;
    }

    text {
      font-size: 30px;
      font-weight: bold;
    }
  </style>
  <link rel="shortcut icon" href="//#" />
  <link rel="stylesheet" type="text/css" href="//fonts.googleapis.com/css?family=Raleway" />
  <script type="text/javascript" src="https://html2canvas.hertzen.com/dist/html2canvas.js"></script>
  <script type="text/javascript" src="https://html2canvas.hertzen.com/dist/html2canvas.min.js"></script>
</head>

<body>
  <div id="capture">



    <button class="modal-button" data-toggle="modal" data-target="#myModal2">MODAL BUTTON</button>


    <div class="modal fade" id="myModal2" role="dialog">
      <div class="modal-dialog modal-lg">
        <div class="modal-content">
          <div class="modal-header">
            <button type="button" class="close" data-dismiss="modal">&times;</button>
            <h2 class="modal-title center">GRAPH</h2>
          </div>
          <div class="modal-body">
            <svg width="798" height="400"></svg>

          </div>
        </div>
        <div class="modal-footer">
          <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
        </div>
      </div>
    </div>
  </div>
  </div>

  <button id="match-button" onclick="sendData();">capture</button>
  <a id="test" href="#"></a>
</body>

</html>
<script src="https://d3js.org/d3.v4.min.js"></script>

<script>
  var graph = {
    "nodes": [{ "id": "Apple", "group": 1 }, { "id": "Cherry", "group": 1 }, { "id": "Tomato", "group": 1 }, { "id": "Chilli", "group": 1 }, { "id": "Red Fruits", "group": 1 }],

    "links": [{ "source": "Apple", "target": "Red Fruits", "value": 10, "type": "A" },
    { "source": "Cherry", "target": "Red Fruits", "value": 10, "type": "A" },
    { "source": "Tomato", "target": "Red Fruits", "value": 10, "type": "A" },
    { "source": "Chilli", "target": "Red Fruits", "value": 10, "type": "A" }]


  };
  var fruits = ["Apple", "Cherry", "Tomato", "Chilli"];
  var color = ["Red Fruits"];

  var colorNodes = graph["nodes"];
  let greyNodes = colorNodes;
  greyNodes.forEach((obj) => {
    if (fruits.includes(obj.id.toString())) obj.group = "1"
  })

  let redNodes = greyNodes;
  redNodes.forEach((obj) => {
    if (color.includes(obj.id.toString())) obj.group = "2"
  })


  var colorLinks = graph["links"];
  let linksArray = colorLinks;
  linksArray.forEach((obj) => {
    if (color.includes(obj.target.toString())) obj.type = "B"
  })

  var svg = d3.select("svg"),
    width = +svg.attr("width"),
    height = +svg.attr("height");

  var zoom_handler = d3.zoom().on("zoom", zoom_actions);

  // zoom_handler(svg);

  var simulation = d3.forceSimulation()
    .force("link", d3.forceLink().distance(300).id(function (d) {
      return d.id;
    }))
    .force("charge", d3.forceManyBody().strength(-300))
    .force("center", d3.forceCenter(width / 2, height / 2));

  var g = svg.append("g")
    .attr("class", "everything");

  svg.call(zoom_handler)
    .call(zoom_handler.transform, d3.zoomIdentity.translate(100, 100).scale(1));

  var link = g.append("g")
    .attr("class", "links")
    .selectAll("line")
    .data(graph.links)
    .enter().append("line")
    .style("stroke", linkColour)
    .attr("stroke-width", function (d) {
      return Math.sqrt(d.value);
    });

  var nodeGroup = g.append("g")
    .attr("class", "nodes");

  var node = nodeGroup.selectAll("circle")
    .data(graph.nodes)
    .enter().append("circle")
    .attr("r", 8)
    .attr("fill", circleColour)
    .attr("id", function (d) { return d.id })
    .call(d3.drag()
      .on("start", dragstarted)
      .on("drag", dragged)
      .on("end", dragended));

  function cleanId(id) { return id.replace(/[^A-Z0-9]/ig, "_"); }

  node.append("title")
    .text(function (d) { return d.id; });

  node.each(d => {
    nodeGroup.append("text") // Labelling for nodes
      .attr("id", 't_' + cleanId(d.id))
      .text(d.id);
  });

  function ticked() {
    link
      .attr("x1", function (d) { return d.source.x; })
      .attr("y1", function (d) { return d.source.y; })
      .attr("x2", function (d) { return d.target.x; })
      .attr("y2", function (d) { return d.target.y; });
    node
      .attr("cx", function (d) { return d.x; })
      .attr("cy", function (d) { return d.y; })
      .each(d => { d3.select('#t_' + cleanId(d.id)).attr('x', d.x + 10).attr('y', d.y + 3); });
  };

  simulation
    .nodes(graph.nodes)
    .on("tick", ticked);

  simulation.force("link")
    .links(graph.links);

  function circleColour(d) {
    if (d.group == "1") {
      return "DimGrey";
    } else if (d.group == "2") {
      return "#81C784";
    }
    else {
      return "blue"
    }
  }

  function linkColour(d) {
    if (d.type == "A") {
      return "DimGrey";
    } else {
      return "SpringGreen";
    }
  }


  function zoom_actions() {
    g.attr("transform", d3.event.transform)
  }

  function dragstarted(d) {
    if (!d3.event.active) simulation.alphaTarget(0.3).restart();
    d.fx = d.x;
    d.fy = d.y;
  }

  function dragged(d) {
    d.fx = d3.event.x;
    d.fy = d3.event.y;
  }

  function dragended(d) {
    if (!d3.event.active) simulation.alphaTarget(0);
    d.fx = null;
    d.fy = null;
  }

  function sendData() {

    $('#myModal2').addClass('modal-posit');

    var modalButton = $('[data-target="#myModal2"]')[0];
    modalButton.click();
    var modal = $('#myModal2')[0];

    setTimeout(function () {
      html2canvas(document.getElementById('capture'), {
        allowTaint: false,
        useCORS: true
      }).then(function (canvas) {

        downloadCanvas(document.getElementById('test'), canvas, 'test.png');
        modalButton.click();
      });
    }, 1000);
  }

  function downloadCanvas(link, canvas, filename) {
    link.href = canvas.toDataURL();
    link.download = filename;
    link.click();
  }
</script>
Ullas Hunka
  • 2,119
  • 1
  • 15
  • 28
Best Jeanist
  • 1,109
  • 4
  • 14
  • 34

1 Answers1

0

The problem is that the fonts are not loaded. I do not know the internals of HTML2Canvas, but possibly it stringifies the XML and creates a data-url, renders an image and then draws it on canvas. In that case, embeddding the font directly into the svg can help I presume. You need to append a defs tag and then add a style tag inside as shown in this link:

<defs>
  <style type="text/css">
    @font-face {
      font-family: Delicious;
      src: url('../fonts/font.woff');
    }
  </style>
</defs>

I'm sure some browsers will not play nice though..

And 1 more thing, instead of adding the src with url relative, convert it to a data-url, you can do this by reading the font file as array buffer with ajax by setting the responseType = "arraybuffer" and then create a Blob from it with the mime type application/font-woff and give to FileReader to readAsDataURL. Than use this data url in place of '../fonts/font.woff'

ibrahim tanyalcin
  • 5,643
  • 3
  • 16
  • 22