Converting SVG to PNG with the help of d3js-wordcloud Angular Directive

I am interested in adding a feature to my Angular application that allows users to download/export a rendered SVG word cloud as a PNG image. The word cloud generator I am using is based on the work of Jason Davies and Julien Renaux.

My attempt at implementing an export function using iweczek's save-svg-as-an-image script is not working as expected, and I suspect I may have overlooked something crucial in the process.

The Angular WordCloud Directive below includes the export function (scope.exportToPNG):

(Directive code explained here)

This is the directive template:

<div id="wordCloud">
    <button class="basicButton" ng-click="exportToPNG()">Export to .PNG</button>
    (template code continued...)
    <h2>svgdataurl</h2>
    <div id="svgdataurl"></div>
    <h2>pngdataurl</h2>
    <div id="pngdataurl"></div>
</div>

There seems to be an issue with the exported image when viewed in Chrome, and Internet Explorer shows some compatibility problems. This suggests that further debugging and testing are required to ensure cross-browser functionality.

Answer №1

I managed to resolve my issues by referring to the following helpful posts:

Save inline SVG as JPEG/PNG/SVG

https://gist.github.com/gustavohenke/9073132

Here is the complete solution that worked for me:

My Angular directive:

'use strict';

/**
 * @ngdoc: function
 * @name: portalDashboardApp.directive:ogWordCloud
 * @description: Word Cloud Generator based on the d3 word cloud generator done by Jason Davies and a simplified script by Julien Renaux
 * Directive of the portalDashboardApp
 */

angular.module('portalDashboardApp')
  .directive('ogWordCloud', function () {
      return {
          restrict: 'E',
          replace: true,
          templateUrl: './socialMedia.module/socialMedia.templates/WordCloudTemplate.html',
          scope: {
              words: '='
          },
          link: function (scope) {

              var fill = d3.scale.category20b();

              var w = window.innerWidth - 238,
                h = 400;

              var max,
                fontSize;

              var layout = d3.layout.cloud()
                .timeInterval(Infinity)
                .size([w, h])
                .fontSize(function (d) {
                    return fontSize(+d.value);
                })
                .text(function (d) {
                    return d.key;
                })
                .on("end", draw);

              var svg = d3.select("#wordCloudVisualisation").append("svg")
                .attr("width", w)
                .attr("height", h)
                .attr("xmlns", 'http://www.w3.org/2000/svg')
                .attr("xmlns:xlink", 'http://www.w3.org/1999/xlink')
                .attr("version", '1.1')
                .attr("id", "wordCloudSVG");

              var wordCloudVisualisation = svg.append("g").attr("transform", "translate(" + [w >> 1, h >> 1] + ")");

              update();

              window.onresize = function (event) {
                  update();
              };

              var tags = [];

              scope.$watch('words', function () {
                  tags = scope.words;
              }, true);

              function draw(data, bounds) {
                  var w = window.innerWidth - 238,
                    h = 400;

                  svg.attr("width", w).attr("height", h);

                  var scale = bounds ? Math.min(
                    w / Math.abs(bounds[1].x - w / 2),
                    w / Math.abs(bounds[0].x - w / 2),
                    h / Math.abs(bounds[1].y - h / 2),
                    h / Math.abs(bounds[0].y - h / 2)) / 2 : 1;

                  var text = wordCloudVisualisation.selectAll("text")
                    .data(data, function (d) {
                        return d.text.toLowerCase();
                    });
                  text.transition()
                    .duration(1000)
                    .attr("transform", function (d) {
                        return "translate(" + [d.x, d.y] + ")rotate(" + d.rotate + ")";
                    })
                    .style("font-size", function (d) {
                        return d.size + "px";
                    });
                  text.enter().append("text")
                    .attr("text-anchor", "middle")
                    .attr("transform", function (d) {
                        return "translate(" + [d.x, d.y] + ")rotate(" + d.rotate + ")";
                    })
                    .style("font-size", function (d) {
                        return d.size + "px";
                    })
                    .style("opacity", 1e-6)
                    .transition()
                    .duration(1000)
                    .style("opacity", 1);
                  text.style("font-family", function (d) {
                      return d.font;
                  })
                    .style("fill", function (d) {
                        return fill(d.text.toLowerCase());
                    })
                    .text(function (d) {
                        return d.text;
                    });

                  wordCloudVisualisation.transition().attr("transform", "translate(" + [w >> 1, h >> 1] + ")scale(" + scale + ")");
              }

              function update() {
                  layout.font('impact').spiral('archimedean');
                  fontSize = d3.scale['sqrt']().range([10, 100]);
                  if (scope.words.length) {
                      fontSize.domain([+scope.words[scope.words.length - 1].value || 1, +scope.words[0].value]);
                  }
                  layout.stop().words(scope.words).start();
              }

              scope.exportToPNG2 = function () {

                  var svg = document.querySelector('#wordCloudSVG'); //svg
                  var canvas = document.createElement("canvas");

                  var svgSize = svg.getBoundingClientRect();
                  canvas.width = svgSize.width;
                  canvas.height = svgSize.height;

                  var ctx = canvas.getContext('2d');
                  var data = new XMLSerializer().serializeToString(svg);

                  var DOMURL = window.URL || window.webkitURL || window;

                  var img = new Image();
                  var svgBlob = new Blob([data], { type: 'image/svg+xml;charset=utf-8' });
                  var url = DOMURL.createObjectURL(svgBlob);

                  img.onload = function () {
                      ctx.drawImage(img, 0, 0);
                      DOMURL.revokeObjectURL(url);

                      var imgURI = canvas
                          .toDataURL('image/png')
                          .replace('image/png', 'image/octet-stream');

                      triggerDownload(imgURI);
                  };

                  img.src = url;
              }

              function triggerDownload(imgURI) {
                  var evt = new MouseEvent('click', {
                      view: window,
                      bubbles: false,
                      cancelable: true
                  });

                  var a = document.createElement('a');
                  a.setAttribute('download', 'MY_COOL_IMAGE.png');
                  a.setAttribute('href', imgURI);
                  a.setAttribute('target', '_blank');

                  a.dispatchEvent(evt);
              }

          }
      };
  });

My directive template:

 <div id="wordCloud">
    <button class="basicButton" ng-click="exportToPNG2()">Export to .PNG</button>
    <div id="wordCloudVisualisation"></div>
    <canvas id="WordCloudCanvas"></canvas>
</div>

I hope this solution proves beneficial to others!

Answer №2

Have you checked if the divs with IDs #svgdataurl and #pngdataurl exist outside of your directive? If not, make sure to include them in your directive template. In the example you're following, these divs are already present on the page and are not created by the click function.

You may also want to explore a new tool recently released by Mike Bostock that utilizes the canvas.toBlob() method: https://github.com/mbostock/svjimmy/blob/master/index.js.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Achieving the highest ranking for Kendo chart series item labels

Currently, I am working with a Kendo column chart that has multiple series per category. My goal is to position Kendo chart series item labels on top regardless of their value. By default, these labels are placed at the end of each chart item, appearing o ...

Using assert.rejects() in Mocha and Node: A Complete Guide

Following the instructions in the documentation, I attempted to use assert.rejects to test for an error message (I have Node version above v10). However, no matter what message I specify, the test always passes. What could be causing this issue? it("is f ...

I am seeking to enable a specific div when the crawler condition is met

This specific div houses the character and becomes active when the character is clicked. Upon clicking, a jQuery function is triggered to display other countries. //this particular div should remain active when the crawler condition is met. It displays th ...

Using CSS to create clickable areas on an image

Here is an example image: https://i.sstatic.net/EerkN.jpg This image shows a desk with various elements, and I am looking for a way to make parts of the image interactive. For example, clicking on the monitor would take you to a specific link, and click ...

Find the names of keys which have a value of true in reactjs

Greetings, I have a JSON file that contains information about different components. I am trying to extract only the Dashboard and Datatable sections where the "visible" attribute is set to true. { "MTs": { "Dashboard": { "id": 1, "visible ...

Encountering a 404 error while attempting to access a static resource using (fs) in nodeJS?

I'm facing an issue in generating a URL using nodeJS that directs to a static resource, specifically a JS file. I keep receiving a 404 error, indicating the resource is Not Found. Initially, I launch the node server by executing node index.js The co ...

Removing item from Angular service

Within my Angular 2 application, I have created a service called events.service.ts: const EVENTS = { 1512205360: { event: 'foo' }, 1511208360: { event: 'bar' } } @Injectable() export class EventsService { getEvents() ...

The sorting feature is not performing as anticipated

I'm dealing with an array of objects fetched from the backend. When mapping and sorting the data in ascending and descending order upon clicking a button, I encountered some issues with the onSort function. The problem lies in the handling of uppercas ...

Filtering out Objection/Knex query results based on withGraphFetched results

I am working with two models in Objection - "brands" and "offers". Brand: const { Model } = require('objection') class Brand extends Model { static get tableName() { return 'brands' } ...

Resetting the form and validation in AngularJS post form submission

I need help resetting a form and all validation messages after submission. Check out my code on plunker: http://plnkr.co/edit/992RP8gemIjgc3KxzLvQ?p=preview Here is the code snippet: Controller: app.controller('MainCtrl', function($scope) { ...

Which javascript array libraries, utilities, or extensions are considered the top choices?

Dojo's array utilities have been useful, but I find that they lack certain features that I need. Are there any comprehensive and well-written JavaScript array utility libraries or extensions available? I'm looking for something that offers a wid ...

Troubleshooting Date Problems in Angular

When using the HTML5 date picker, I have encountered an issue where after choosing a date, the ng-model displays an older date instead of the current one selected. <input type="date" ng-model="dateModel" /> For example, when selecting the current d ...

Using a combination of javax.ws and Angular, access and open a PDF file

I am attempting to access a PDF file stored on the server using a Java Restful service and angularjs. Here is my Java code for the service: @GET @Path("/getPDF") @Produces("application/pdf") public Response getPDF() throws FileNotFoundException { File ...

JSON accessed through the file:// protocol

When opening my web app, the browser displays a .html file with the address bar showing the protocol file://. However, I need to open a .json file in the script and load it into a variable. I have attempted using $.ajax request with data type jsonp, as we ...

transpile TypeScript code into ES6 rather than ES5

I am currently diving into typescript and I have observed that the code output always defaults to using var. Is there a way to make it output const and let in the .js file instead, or does typescript always require es5 output for some reason? Thanks. Her ...

Adjust checkbox based on selection from <select> option

In my scenario, I have a selection process where the ID of the <option> element is sent to a .php file. Afterwards, based on some database checks, I need to dynamically check or uncheck a checkbox. Everything seems to be in place except for this last ...

Having trouble retrieving data in Angular from the TypeScript file

demo.component.ts import { Component, OnInit } from '@angular/core'; @Component({ selector: 'app-demo', templateUrl: './demo.component.html', styleUrls: ['./demo.component.css'] }) ...

Find the trending posts on mongoDB within the past week starting from today's date

I'm currently working on a controller to fetch the top 50 most popular posts from the past week, sorted by likes. I'm attempting to use the aggregate() method for this purpose but seem to be encountering some issues. When I test the query in Inso ...

Discover the closest postal code using the Google API

Have you heard about the latest map API release from Google? I'm curious to know if it's possible to use the Google JavaScript API to find the nearest three or five postal codes based on the user's current location. Best regards ...

Tips for showcasing JSON data in AngularJS by utilizing the <pre> tag

How can I properly format and display a JSON string inside a dynamically created Div using a forEach loop? Here is my current code snippet: <div id="container_div"> </div> $scope.getJSON = () => { var settings = { "url ...