Getting the width of SVG text with a title on Node - node.js

I'm using svg.js and svgdom on Node to generate SVG files. Here's my code:
// set up svg.js and svgdom
const { createSVGWindow } = require('svgdom');
const window = createSVGWindow();
const document = window.document;
const { SVG, registerWindow } = require('#svgdotjs/svg.js');
registerWindow(window, document);
const draw = SVG(document.element).size(100, 100);
// generate SVG text and title
var title = draw.element('title').words('noun');
var text = draw.text('text').add(title);
var width = text.length();
That last line throws TypeError: cannot read property 'style' of null in svgdom/main-require.cjs:3721:38.
This works when I do it in the browser; something about moving to Node and svgdom causes it to fail. Is there another way I can calculate the width of this text element, or a different way to attach the title to the text? (The title is being used to show additional information on hover.) Calling text.width() always returns 0.
I'm using svg.js 3.0.16 and svgdom 0.1.8. Should I open an issue on one of them? If so, which one?

Related

How do I right align text in a form field using pdf-lib?

I've seen TextAlignment googling but not sure how to implement it or if it applies to form fields? How would I right align text for totalField in the code below?
var pdfDoc = await PDFDocument.load(formPdfBytes)
var form = pdfDoc.getForm()
var totalField = form.getTextField('total')
totalField.setText('$' + total)

How can I get PDF dimensions in pixels in Node.js?

I tried pdf2json:
const PDFParser = require("pdf2json");
let pdfParser = new PDFParser();
pdfParser.loadPDF("./30x40.pdf"); // ex: ./abc.pdf
pdfParser.on("pdfParser_dataReady", pdfData => {
width = pdfData.formImage.Width; // pdf width
height = pdfData.formImage.Pages[0].Height; // page height
console.log(`Height : ${height}`) // logs 70.866
console.log(`Width : ${width}`) // logs 53.15
});
But it gave the dimensions in a unknown units!
The dimensions in pixels will help me include them in the pdf-poppler module that converts a pdf file to an image and it needs the pdf file height in pixels.
Try calipers.
Code example:
const Calipers = require('calipers')('png', 'pdf');
Calipers.measure('./30x40.pdf')
.then(data => {
const { width, height } = data.pages[0];
});
Alternatively, try a module which converts it without needing width/height:
pdf2pic pdf-image node-imagemagick
If you're set on using pdf2json, please read this bit of documentation describing the units of the output.
Bit late to the party, but as discussed here: stackoverflow pdf2json unit
you can multiply your width and height by 24
Just like:
width = pdfData.formImage.Width * 24; // pdf width
height = pdfData.formImage.Pages[0].Height * 24; // page height
and you get Pixel.

Draw Threejs TextGeometry along the path

I have some model and want cover it with text.
I've rendered and bended TextGeometry but it is difficult to combine these two meshes.
(Аnd yes, I've tried the dynamic textures, this way prohibits the use of own fonts)
scrinshot of existing model
Perhaps there is another way to draw the text along the path?
As you want to cover it with text, why not to use textures.
You can set it from a picture with THREE.TextureLoader() or you can draw your own on a canvas and apply it to a texture with var texture = new THREE.Texture(canvas);
For exmaple:
var texture = new THREE.Texture(canvas);
texture.repeat.set(5, 1);
texture.needsUpdate = true;
See the jsfiddle example.
There you can uncomment those lines
//texture.wrapS = THREE.RepeatWrapping;
//texture.wrapT = THREE.RepeatWrapping;
and see, how the result will change.
UPD. I've updated the fiddle. Used the trick with WebFontLoader (from this SO)
WebFontConfig = {
google: {families: ['Monoton']},
active: function() {
init();
animate();
},
};
(function(){
var wf = document.createElement("script");
wf.src = 'https://cdnjs.cloudflare.com/ajax/libs/webfont/1.6.26/webfontloader.js';
wf.async = 'true';
document.head.appendChild(wf);
})();

D3 Datum Update Boxplot

I am trying to update 3 svgs (BoxPlots). In the following code the var svg contains an array of the 3 svgs and the _data_ has been updated to the correct number of records. I follow the BoxPlot Example , but cannot see what I am doing wrong.
$('#Records li a ').click(function() {
var id = event.target.id;
var idparts = id.split("_");
var numrec = idparts[1];
d3.json("./php/connection2.php?numrecs="+numrec, function (error, csv) {
d3.json("./php/connection2.php?numrecs="+numrec, function (error, csv) {
var chart = d3.box()
.whiskers(iqr(1.5))
.width(width)
.height(height);
var numericArray = createdata(csv);
chart.domain([min, max]);
var svg = d3.selectAll("svg")
svg.data(numericArray).call(chart.duration(1000));
});
$('#RecordsDropdown').removeClass("open");
return false;
});
});
Ive also tried:
.....
thisdata=numericArray;
var svg = d3.selectAll("svg");
svg.data(thisdata);
svg.call(chart.duration(1000));
//updatedata(svg);
I am getting this error when it tries to create new outliers
Uncaught Error: NotFoundError: DOM Exception 8
I found that if you remove the insert("text") at line ~158 in box.js:
//change this:
//outlier.enter().insert("circle", "text")
// to this:
outlier.enter().insert("circle")
That will prevent the Uncaught Error: NotFoundError: DOM Exception 8
The only other thing that I had to change was to make sure that it appended the new outlier in the g of my box plot when I called it to animate it. Otherwise they'll be off a bit.
var svg = d3.selectAll(".box" + Attr + sample + " g")
.data(data);
svg.call(chart.duration(1000));
I don't know what the "text" was there for.

How to use D3 force layout with existing SVG elements as nodes

I have a javascript array of objects where each object has .ui attribute that contains an existing SVG shape/element of various kinds ( a self contained "g" element with others inside ).
I want to arrange these objects as a force graph layout in D3. So, instead of having d3 create circles (or whatever) I want to assign one of my ui objects to the graph.
I have created a simplified fiddle << HERE >> for that. The objects that are pre-existing and should be part of the graph are the 3 colored rectangles. In this example I failed to achieve that. The d3 append command can be used just to construct shapes/elements, but not to append an already constructed one. I was trying to do this, and it cannot work in D3:
node.append( function(d) { d.ui.node(); }) ...
... as mentioned, this wont work as d3 does not append elements.
var svgContainer = d3.select("#svgContainer");
// =============================================================
// creates the SVG elements to be used ( 3 colored rectangles )
// =============================================================
var element0a = svgContainer.append("g").attr("transform","translate(100,100)");
var element0b = element0a.append("rect").attr("x",0).attr("y",0).attr("width",20).attr("height",10).attr("fill","red");
var element1a = svgContainer.append("g").attr("transform","translate(100,200)");
var element1b = element1a.append("rect").attr("x",0).attr("y",0).attr("width",20).attr("height",10).attr("fill","green");
var element2a = svgContainer.append("g").attr("transform","translate(100,300)");
var element2b = element2a.append("rect").attr("x",0).attr("y",0).attr("width",20).attr("height",10).attr("fill","blue");
// =============================================================
// fills up an object array that contains details plus the UI attribute
// =============================================================
var nodeArray = new Array();
nodeArray[0] = { id : "000", label : "label 000", ui : element0a };
nodeArray[1] = { id : "001", label : "label 001", ui : element1a };
nodeArray[2] = { id : "002", label : "label 002", ui : element2a };
// not interested in dealing with the edge/link right now, just empty
var linkArray = new Array();
// =============================================================
// D3 force layout stuff
// =============================================================
var force = self.force = d3.layout.force()
.nodes(nodeArray)
.links(linkArray)
.gravity(.05)
.distance(80)
.charge(-100)
.size([600, 600])
.start();
var node = svgContainer.selectAll("g.node")
.data(nodeArray)
.enter().append("svg:g")
.attr("class", "node")
.call(force.drag);
// HERE (below) COMES THE ISSUE, where you see append("cicle") I want to append the nodeArray's ".ui" element.
node.append("circle") // <---
.attr("class", "node")
.attr("r",10)
.attr("fill","orange")
.call(force.drag);
force.on("tick", function() {
node.attr("transform", function(d) {return "translate(" + d.x + "," + d.y + ")";});
});
Based on advice from Lars Kotfoff :
Working fiddle here
If the elements to be graph-ed already exist, simply skip the enter() D3 phase and use select() or selectAll() based on a common characteristic of your elements ( a class name for instance ).
This is how the element got created ( added a specific class for allowing an isolated selection ):
var element0a = svgContainer.append("g").attr("class","node").attr("transform","translate(100,100)");
var element0b = element0a.append("rect").attr("x",0).attr("y",0).attr("width",20).attr("height",10).attr("fill","red");
This is thene part of the code replacing the enter() phase
var node = svgContainer.selectAll("g.node")
.data(nodeArray)
.call(force.drag);
This is what was removed
var node = svgContainer.selectAll("g.node")
.data(nodeArray)
.enter().append("svg:g")
.attr("class", "node")
.call(force.drag);
// HERE (below) COMES THE ISSUE, where you see append("cicle") I want to append the nodeArray's ".ui" element.
node.append("circle") // <---
.attr("class", "node")
.attr("r",10)
.attr("fill","orange")
.call(force.drag);
The problem is that you append the elements to svgContainer to create them, thus they are already attached to something.
What I would suggest as a workaround is to store the element properties in a json file and then read the configuration from the json.
There might be something more elegant.

Resources