I'm creating family tree using d3 and in the front-end I convert my SVG into string
if(document.querySelector('svg')){
const svgString = new XMLSerializer().serializeToString(document.querySelector('svg'))
dispatch(convertToPNG(svgString))
}
and dispatch it to send it to backend, in the back-end I'm using convert-svg-to-png package to convert my SVG to PNG and this is my controller
import {convert} from 'convert-svg-to-png'
const SvgToPng = async(req, res, next) => {
try {
if(!req.body.svg){
res.status(400)
throw new Error('Please upload the required svg file')
}
const png = await convert(req.body.svg,{
puppeteer:{args: ['--no-sandbox'] }
});
res.set('Content-Type', 'image/png');
res.send({png});
} catch (error) {
next(error)
}
then back to the front-end I received the buffer and convert it to png
const source = bufferToPng(pngData.data)
const hiddenElement = document.createElement('a');
hiddenElement.href = source;
hiddenElement.download = 'family-tree.png'
hiddenElement.click();
function buffertoPng(buffer){
const arrayBufferView = new Uint8Array(buffer)
const blob = new Blob([ arrayBufferView ], { type: 'image/png' })
const urlCreator = window.URL || window.webkitURL
const imageUrl = urlCreator.createObjectURL(blob)
return imageUrl
}
My issue is when I try to convert small SVG with Arabic names the library doing great job but when it comes to large SVG full of Arabic names I start to see weird characters instead of Arabic ones, instead of seeing Arabic name like that (أحمد) I see that (Ù�اطمة)
Note: the package use another package call puppeteer
And this is my d3 code
import {useEffect, useRef} from 'react'
import * as d3 from 'd3'
import { useHistory} from 'react-router'
const Tree = ({familyData,isProfile, isPoint}) => {
const wrapperRef = useRef(null)
const history = useHistory()
const radialPoint = (x, y) => {
return [(y = +y) * Math.cos(x -= Math.PI / 2), y * Math.sin(x)];
}
const genColors = [
'#000','#7d1e01', '#2c3e50',
'#80016e','#673ab7',
'#ff9800','#795548','#142796',
'#61b301','#19a0b1','#a0342c',
'#CEE397','#F5A25D','#625261',
'#87556F','#E5EDB7','#231E23',
'#6F0000','#FFF0F5','#FFEBD9',
'#BEEBE9','#B0A160','#E4F9FF',
]
const createTree = () => {
wrapperRef.current.innerHTML = '';
const margin = {top: 140, right: 0, bottom: 20, left: 0}
wrapperRef.current.width = wrapperRef.current.getBoundingClientRect().width
wrapperRef.current.height = wrapperRef.current.getBoundingClientRect().height
const innerHeight = 2000 - margin.top - margin.bottom;
const svg = d3.select(wrapperRef.current)
.append('svg')
.attr('width', 4000)
.attr('height', 2200)
.attr('encoding', 'UTF-8')
const width = +svg.attr("width")
const height = +svg.attr("height")
const g = svg.append("g")
.attr("transform", `translate(${(width/2+ 40)},${(height - 30)})`);
const dataStructure = d3.stratify().id(d => d._id).parentId(d => d.parentId)(familyData)
const treeLayout = d3.tree().size([(1 * Math.PI), innerHeight])
const root = treeLayout(dataStructure)
g.selectAll(".link")
.data(root.links())
.enter().append("path")
.attr('fill', 'none')
.attr('stroke', '#555')
.attr('stroke-opacity', '0.4')
.attr('stroke-width', (d) => {
if(d.target.depth <= 9){
return 8 - d.target.depth
} else {
return 1
}
})
.attr("d", d3.linkRadial()
.angle(d => d.x-Math.PI/2)
.radius(d => d.y + 10))
.attr('id', d => 'link_' + d.target.data._id)
.attr('stroke-dasharray', function(){
const length = this.getTotalLength()
return `${length} ${length}`
})
.attr('stroke-dashoffset', function(){
const length = this.getTotalLength()
return length
})
.transition()
.duration(1500)
.delay(d => d.source.depth * 500)
.attr('stroke-dashoffset', 0)
const node = g.selectAll(".node")
.data(root.descendants())
.enter().append("g")
.attr("class", d => d.children ? " node--internal" : " node--leaf")
.attr('transform', d => "translate(" + radialPoint(d.x-Math.PI/2, (d.y+10)) + ")");
node.append("circle")
.attr("r", d => !d.children && d.depth === 1 ? 3 : 7 - (d.depth))
.style("stroke","white")
.style("fill", d => {
if(d.children){
return genColors[d.depth]
} else {
return '#04a21a'
}
})
.attr('opacity', 0)
.transition()
.duration(1500)
.delay(d => d.depth * 500)
.attr('opacity', 1)
node.append("image")
.attr("xlink:href", d => !d.children ? "/image/leaf.png":"")
.attr('x', d => d.x > Math.PI/2 ? '-5px': '-10px')
.attr('y', d => !d.children && d.depth === 1 ? '-55px':"-90px")
.attr("transform", d => `rotate(${(d.x > Math.PI/2 ? d.x-Math.PI/2 : d.x-Math.PI/2 ) * 180 / Math.PI})`)
.attr('width', d => d.depth === 1 ? '1rem' : '1.5rem')
.attr('opacity', 0)
.transition()
.duration(1500)
.delay(d => d.depth * 500)
.attr('opacity', 1)
node.append("text")
.attr("dy","0.32em")
.attr('y', (d) => d.depth === 1 ? -1: 2)
.attr("x", (d) => {
if(!d.children){
if(d.depth === 1){
return 20
}
if(d.x > Math.PI/2){
return 30
}else {
return -40
}
}else {
return -80
}
})
// eslint-disable-next-line
.attr("text-anchor", d => d.x > Math.PI/2 === !d.children ? "middle" : "end")
.attr("transform", d => d.depth > 1 ? `rotate(${(d.x > Math.PI/2 ? d.x-Math.PI/2 - Math.PI / 2 : d.x-Math.PI/2 + Math.PI / 2) * 180 / Math.PI})` : '')
.style("font-size", d => {
return d.children ? 2 - (d.depth * 2) /10 + 'em'
:d.depth === 1 ?'0.37em' : d.depth === 2 ? '0.6em' :'0.85em'
})
.text(d => d.data.firstName)
.attr('id', d => 'name_' + d.data._id)
.style('cursor', 'pointer')
.style(' z-index', '9999999')
.attr('fill', (d) =>{
if(d.children){
return genColors[d.depth]
} else {
return '#04a21a'
}
})
.on('mouseover', (e, d) => {
if(isPoint){
d3.selectAll('path').style('stroke', '#2c3e50').style('opacity', 0.2)
d3.selectAll('text').style('fill', '#2c3e50').style('opacity', 0.2)
d3.selectAll('circle').style('fill', '#2c3e50').style('opacity', 0.2)
d3.selectAll('image').style('opacity', '0.1')
while(d){
if(!d.data.parentId ) {
d3.select(`#name_${d.data._id}`).style('fill','#000').style('opacity','1')
}
if(d.data.parentId !== null){
d3.select(`#link_${d.data._id}`).style('stroke','#b70202').style('opacity','1')
d3.select(`#name_${d.data._id}`).attr('fill','#000').style('opacity','1')
.style('font-size', '3rem')
.transition()
.duration(500)
.style('font-size', '5rem');
}
d = d.parent
}
}
})
.on('mouseout', () => {
if(isPoint){
d3.selectAll('path').style('stroke', '#555').style('opacity','0.4')
d3.selectAll('text').style('fill', (d) => {
if(d.children){
return genColors[d.depth]
} else {
return '#04a21a'
}
}).style('font-size', d => {
return d.children ? 2 - (d.depth * 2) /10 + 'em'
:d.depth === 1 ?'0.4em' :'0.5em'
}).style('opacity','1')
d3.selectAll('image').style('opacity', '1')
d3.selectAll('circle').style('fill', (d) => {
if(d.children){
return genColors[d.depth]
} else {
return '#04a21a'
}
}).style('opacity','1')
}
})
.on('click', (e, d) => {
if(!isProfile){
history.push(`/info/${d.data._id}`)
}
})
.attr('opacity', 0)
.transition()
.duration(1500)
.delay(d => d.depth * 600)
.attr('opacity', 1)
}
useEffect(() => {
createTree()
})
return (
<div className="tree__wrapper" ref={wrapperRef}></div>
)
}
export default Tree
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.1/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
and this is a portion of svg generated by d3
<svg xmlns="http://www.w3.org/2000/svg" width="4000" height="2200" encoding="UTF-8"><g transform="translate(2040,2170)"><path fill="none" stroke="#555" stroke-opacity="0.4" stroke-width="7" d="M1.6007775447085935,-9.871044081167742C38.41866107300624,-236.9050579480258,-222.4503319176191,-90.08801157613091,-435.6319000053374,-176.42235600325637" id="link_60f2c8e7131cad0723062518" stroke-dasharray="540.5701293945312 540.5701293945312" stroke-dashoffset="0"/><path fill="none" stroke="#555" stroke-opacity="0.4" stroke-width="7" d="M1.6007775447085935,-9.871044081167742C38.41866107300624,-236.9050579480258,-127.22830174504648,-203.50174258485208,-249.155424250716,-398.52424589533535" id="link_60f2c8e7131cad0723062519" stroke-dasharray="490.6072692871094 490.6072692871094" stroke-dashoffset="0"/><path fill="none" stroke="#555" stroke-opacity="0.4" stroke-width="7" d="M1.6007775447085935,-9.871044081167742C38.41866107300624,-236.9050579480258,127.2283017450465,-203.50174258485208,249.15542425071607,-398.52424589533535" id="link_60f2c8e7131cad072306251a" stroke-dasharray="470.35516357421875 470.35516357421875" stroke-dashoffset="0"/><path fill="none" stroke="#555" stroke-opacity="0.4" stroke-width="7" d="M1.6007775447085935,-9.871044081167742C38.41866107300624,-236.9050579480258,238.08207476904383,-30.280780598713555,466.2440630893775,-59.29986200581405" id="link_60f2c8e7131cad072306251b" stroke-dasharray="535.5938720703125 535.5938720703125" stroke-dashoffset="0"/><path fill="none" stroke="#555" stroke-opacity="0.4" stroke-width="7" d="M1.6007775447085935,-9.871044081167742C38.41866107300624,-236.9050579480258,238.38806663242283,-27.7692219418467,466.84329715516134,-54.381392969449784" id="link_60f2c8e7131cad072306251c" stroke-dasharray="536.4296875 536.4296875" stroke-dashoffset="0"/><path fill="none" stroke="#555" stroke-opacity="0.4" stroke-width="7" d="M1.6007775447085935,-9.871044081167742C38.41866107300624,-236.9050579480258,238.66756448516003,-25.254577064800976,467.39064711677173,-49.45688008523525" id="link_60f2c8e7131cad072306251d" stroke-dasharray="537.2632446289062 537.2632446289062" stroke-dashoffset="0"/><path fill="none" stroke="#555" stroke-opacity="0.4" stroke-width="7" d="M1.6007775447085935,-9.871044081167742C38.41866107300624,-236.9050579480258,238.92053726437896,-22.73712544057634,467.88605214274213,-44.526870654462" id="link_60f2c8e7131cad072306251e" stroke-dasharray="538.0941772460938 538.0941772460938" stroke-dashoffset="0"/><path fill="none" stroke="#555" stroke-opacity="0.4" stroke-width="7" d="M1.6007775447085935,-9.871044081167742C38.41866107300624,-236.9050579480258,239.14695685515082,-20.21714685410944,468.3294571746704,-39.59191258929766" id="link_60f2c8e7131cad072306251f" stroke-dasharray="538.92236328125 538.92236328125" stroke-dashoffset="0"/><path fill="none" stroke="#555" stroke-opacity="0.4" stroke-width="7" d="M1.6007775447085935,-9.871044081167742C38.41866107300624,-236.9050579480258,239.34679809361936,-17.694921371178975,468.7208129333379,-34.652554351892164" id="link_60f2c8e7131cad0723062520" stroke-dasharray="539.747802734375 539.747802734375" stroke-dashoffset="0"/>