Iconfont generating suddenly broken - svg

We have a site with many SVG icons. A gulp task turns them into an icon font in .eot, .ttf and .woff formats. Everything has worked nicely until recently when two new glyphs were added.
After the new icons were added, most of the existing ones now render with strange ball joints where curves should be. For example one of the icons should look like this:
But instead we get this:
What could be causing this? The conversion is done using gulp-iconfont, which uses svgicons2svgfont and svg2ttf. I tried tweaking the options but nothing seems to help. I even removed the new glyphs and generated the font again but everything still looks funky. No npm module versions were changed in between.
Here's the SVG code for the icon in the example for reference:
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 700 700"><defs><style>.cls-1{fill:#333;}</style></defs><path class="cls-1" d="M596.06,140H564.15V105a35.1,35.1,0,0,0-35-35H467.46a35.1,35.1,0,0,0-35,35v35H267.52V105a35.1,35.1,0,0,0-35-35H170.84a35.1,35.1,0,0,0-35,35v35h-31.9A34,34,0,0,0,70,173.89v422.2A34,34,0,0,0,103.94,630H596.06A34,34,0,0,0,630,596.09V173.89A34,34,0,0,0,596.06,140Zm-122.73-9.11a17.55,17.55,0,0,1,17.5-17.5h14.95a17.55,17.55,0,0,1,17.5,17.5v38.28a17.55,17.55,0,0,1-17.5,17.5H490.83a17.55,17.55,0,0,1-17.5-17.5V130.87Zm-296.62,0a17.55,17.55,0,0,1,17.5-17.5h14.95a17.55,17.55,0,0,1,17.5,17.5v38.28a17.55,17.55,0,0,1-17.5,17.5H194.21a17.55,17.55,0,0,1-17.5-17.5V130.87ZM571.67,567a4.61,4.61,0,0,1-4.59,4.63H133a4.61,4.61,0,0,1-4.66-4.56V238a4.59,4.59,0,0,1,4.6-4.63H567a4.59,4.59,0,0,1,4.64,4.56V567Z"/><path class="cls-1" d="M249.56,435.78c10.67,0,15.11,5.51,15.11,14.93s-4.44,14.93-15.11,14.93H177c-9.42,0-15.11-7.11-15.11-14.76,0-11,2.67-16.71,35.38-39.47l16.89-11.73c8.71-6,11.91-11,11.91-18.31,0-8.71-5.87-14.4-15.47-14.4-20.27,0-9.78,24.71-29.87,24.71-12.8,0-17.42-7.11-17.42-17.24,0-17.07,16.71-35.2,48.35-35.2,37.87,0,51,20.62,51,40.35,0,14.93-7.47,25.6-23.29,35.56l-32.18,20.27v0.36h42.31Z"/><path class="cls-1" d="M323,440.23H281c-12.09,0-16.71-8.36-16.71-19,0-6.58,2.84-12.62,10.67-22l37.15-44.8c10.13-12.09,15.29-16.18,24.53-16.18,13.33,0,20.8,7.29,20.8,18.84v55.47c10.49,0,17.07,3.2,17.07,13.87,0,10.84-6.58,13.87-17.07,13.87v8.53c0,11.73-5.69,19.2-17.24,19.2S323,460.49,323,448.76v-8.53Zm0-66h-0.36l-29.15,38.22H323V374.27Z"/><path class="cls-1" d="M413.82,346.36c3.38-9.78,5.33-11.73,13.15-11.73,5.69,0,13,3.73,13,12.27,0,3.2-2,8.71-5,17.42l-32.71,92.62c-3.38,9.78-5.33,11.73-13.16,11.73-5.69,0-13-3.73-13-12.27,0-3.2,2-8.71,5-17.42Z"/><path class="cls-1" d="M456.66,370.36c-10.49,0-15.82-5.51-15.82-14.4s5.33-14.4,15.82-14.4H526c11,0,17.6,4.09,17.6,15.64,0,6.76-3.56,12.62-16,25.78-7.29,7.64-21.87,34.67-25.07,59.91C499.68,465.47,490.44,468,481.37,468c-7.82,0-15.82-7.11-15.82-16.36,0-23.11,27.38-69.86,40.53-81.24H456.66Z"/></svg>
Here's our gulp task:
var iconfont = require('gulp-iconfont'),
iconfontCss = require('gulp-iconfont-css'),
order = require('gulp-order'),
jsonfile = require('jsonfile'),
tap = require('gulp-tap');
module.exports = function(gulp, plugins) {
'use strict';
return function() {
var runTimestamp = Math.round(Date.now()/1000),
fontName = 'customer-icons',
iconPaths = ['src/assets/icons/iconfont/*.svg'],
codepointFile = 'src/assets/icons/iconfont/glyphs.json',
existingGlyphs = [],
saveGlyphs = [],
basePath = process.cwd();
try {
// If we already have a JSON of glyphs that have been assigned a codepoint,
// load that data first so we can pass the icons in the same order to avoid
// showing wrong icons when new ones are added.
existingGlyphs = jsonfile.readFileSync(codepointFile).glyphs.concat(iconPaths);
}
catch(e) {} // No need to do anything, just use the default empty array.
return gulp.src(iconPaths)
.pipe(order(
existingGlyphs, { base: basePath }
))
.pipe(tap(function(file) {
saveGlyphs.push(file.path.match(/(src\/assets\/icons\/iconfont\/.*)/)[1]);
})).on('end', function() {
jsonfile.writeFile(codepointFile, { glyphs: saveGlyphs }, function(err) {
if(err !== null) {
console.error('Error saving glyph file: ' + err);
}
})
})
.pipe(iconfontCss({
fontName: fontName,
path: 'scss',
targetPath: '../../../base/iconfont.scss',
fontPath: '/.resources/customer-ui-module/webresources/assets/fonts/customer-icons/'
}))
.pipe(iconfont({
fontName: fontName,
appendUnicode: true,
fontHeight: 1001,
normalize: true,
formats: ['ttf', 'eot', 'woff'], // default, 'woff2' and 'svg' are available
timestamp: runTimestamp, // recommended to get consistent builds when watching files
}))
.on('glyphs', function(glyphs, options) {
// CSS templating, e.g.
// console.log(glyphs, options);
})
.pipe(gulp.dest('src/assets/fonts/customer-icons'));
};
}

Related

gatsby image svg not found

When trying to load an SVG image this way:
export const query = graphql`
query {
fileName: file(relativePath: { eq: "logo_large.svg" }) {
childImageSharp {
fluid(maxWidth: 1060) {
...GatsbyImageSharpFluid_withWebp_tracedSVG
}
}
}
}
`
I get TypeError: Cannot read property 'childImageSharp' of null
If I try the exact same but with a .jpg or .png image, it works, so the relative path must be correct.
Any thing I should have in special consideration with SVG's?
"SVG are not supported by this plugin for obvious reasons, they are
vectorial and automatically adjust their size without the need of
plugin like this one"
Correct. If you want to handle multiple types like png + jpg + svg you have to dynamically handle it with gatsby-image or not. You solve this by adding extension and publicURL in your GraphQL query:
...
image {
childImageSharp {
fluid(maxWidth: 500, quality: 92) {
...GatsbyImageSharpFluid
}
}
extension
publicURL
}
...
Add this to your image component:
// svg support
if (!childImageSharp && extension === 'svg') {
return <img style={imageStyle} src={publicURL} alt={alt} />
}
Credit goes to andresmrm on GitHub.
SVG are not supported by this plugin for obvious reasons, they are vectorial and automatically adjust their size without the need of plugin like this one
I know this is an old question with accepted answer but I thought I might add another solution for others' benefit. I found this solution at: https://github.com/gatsbyjs/gatsby/issues/10297
import yourSVG from './logo_large.svg'
const Home = () => <><img src={yourSVG} /></>
Don't know if this helps, but If you are looking for a generic and dynamic Image component that takes a custom file name and acts accordingly I've put together this:
This approach considers that you have an images folder inside your src folder where you have all your images.
const Image = ({ filename, alt }) => (
<StaticQuery
query={graphql`
query {
images: allFile {
edges {
node {
relativePath
name
childImageSharp {
fluid(maxWidth: 600) {
...GatsbyImageSharpFluid
}
}
}
}
}
}
`}
render={data => {
// Handles SVG extension
const extension = filename.match(/[^\\]*\.(\w+)$/)[1]
if (extension === "svg") {
return <img src={require(`../images/${filename}`)} alt={alt}/>
}
// Finds your image among all
const image = data.images.edges.find(n => {
return n.node.relativePath.includes(filename)
})
if (!image) {
return null
}
return (
<Img alt={alt} fluid={image.node.childImageSharp.fluid}/>
)
}}
/>
)
Be careful that this is not optimized and queries for all of your images. If you are trying to optimize performance in every way possible this isn't the best way to go.

Exporting dc.js chart from SVG to PNG

I have a dc.js chart and I want to export it as a PNG image, using exupero's saveSvgAsPng:
function save() {
var options = {};
options.backgroundColor = '#ffffff';
options.selectorRemap = function(s) { return s.replace(/\.dc-chart/g, ''); };
var chart = document.getElementById('chart').getElementsByTagName('svg')[0];
saveSvgAsPng(chart, 'chart.png', options)
}
var data = [
{day: 1, service: 'ABC', count: 100},
{day: 2, service: 'ABC', count: 80},
{day: 4, service: 'ABC', count: 10},
{day: 7, service: 'XYZ', count: 380},
{day: 8, service: 'XYZ', count: 400}
];
var ndx = crossfilter(data);
var dim = ndx.dimension(function(d){return [d.service, d.day];});
var grp = dim.group().reduceSum(function(d) { return d.count; });
grp = fillGroup(grp, d3.cross(['ABC', 'XYZ'], d3.range(1, 9)));
var chart= dc.seriesChart("#chart")
.width(500)
.height(180)
.chart(function(c) { return dc.lineChart(c).renderArea(true).curve(d3.curveCardinal); })
.dimension(dim)
.group(grp)
.brushOn(false)
.seriesAccessor(function(d) { return d.key[0]; })
.keyAccessor(function(d) { return d.key[1]; })
.valueAccessor(function(d) { return +d.value; })
.x(d3.scaleLinear())
.elasticX(true)
.y(d3.scaleLinear().domain([0, 450]))
.legend(dc.legend().horizontal(false).x(60).y(10))
.yAxisLabel("Count")
.render();
function fillGroup(grupo, rango) {
return {
all:function () {
var resultados = grupo.all().slice(0);
var encontrado = {};
resultados.forEach(function(d) {
encontrado[d.key] = true;
});
rango.forEach(function(d) {
if (!encontrado[d]) { resultados.push({key: d, value: 0}); }
});
return resultados;
}
};
}
/* Please ignore what follows - it's the minified SaveSvgAsPng library,
I haven't found any CDN for it... */
(function(){const out$=typeof exports!='undefined'&&exports||typeof define!='undefined'&&{}||this||window;if(typeof define!=='undefined')define(()=>out$);const xmlns='http://www.w3.org/2000/xmlns/';const doctype='<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [<!ENTITY nbsp " ">]>';const urlRegex=/url\(["']?(.+?)["']?\)/;const fontFormats={woff2:'font/woff2',woff:'font/woff',otf:'application/x-font-opentype',ttf:'application/x-font-ttf',eot:'application/vnd.ms-fontobject',sfnt:'application/font-sfnt',svg:'image/svg+xml'};const isElement=obj=>obj instanceof HTMLElement||obj instanceof SVGElement;const requireDomNode=el=>{if(!isElement(el))throw new Error(`an HTMLElement or SVGElement is required; got ${el}`)};const isExternal=url=>url&&url.lastIndexOf('http',0)===0&&url.lastIndexOf(window.location.host)===-1;const getFontMimeTypeFromUrl=fontUrl=>{const formats=Object.keys(fontFormats).filter(extension=>fontUrl.indexOf(`.${extension}`)>0).map(extension=>fontFormats[extension]);if(formats)return formats[0];console.error(`Unknown font format for ${fontUrl}. Fonts may not be working correctly.`);return'application/octet-stream'};const arrayBufferToBase64=buffer=>{let binary='';const bytes=new Uint8Array(buffer);for(let i=0;i<bytes.byteLength;i++)binary+=String.fromCharCode(bytes[i]);return window.btoa(binary)}
const getDimension=(el,clone,dim)=>{const v=(el.viewBox&&el.viewBox.baseVal&&el.viewBox.baseVal[dim])||(clone.getAttribute(dim)!==null&&!clone.getAttribute(dim).match(/%$/)&&parseInt(clone.getAttribute(dim)))||el.getBoundingClientRect()[dim]||parseInt(clone.style[dim])||parseInt(window.getComputedStyle(el).getPropertyValue(dim));return typeof v==='undefined'||v===null||isNaN(parseFloat(v))?0:v};const getDimensions=(el,clone,width,height)=>{if(el.tagName==='svg')return{width:width||getDimension(el,clone,'width'),height:height||getDimension(el,clone,'height')};else if(el.getBBox){const{x,y,width,height}=el.getBBox();return{width:x+width,height:y+height}}};const reEncode=data=>decodeURIComponent(encodeURIComponent(data).replace(/%([0-9A-F]{2})/g,(match,p1)=>{const c=String.fromCharCode(`0x${p1}`);return c==='%'?'%25':c}));const uriToBlob=uri=>{const byteString=window.atob(uri.split(',')[1]);const mimeString=uri.split(',')[0].split(':')[1].split(';')[0]
const buffer=new ArrayBuffer(byteString.length);const intArray=new Uint8Array(buffer);for(let i=0;i<byteString.length;i++){intArray[i]=byteString.charCodeAt(i)}
return new Blob([buffer],{type:mimeString})};const query=(el,selector)=>{if(!selector)return;try{return el.querySelector(selector)||el.parentNode&&el.parentNode.querySelector(selector)}catch(err){console.warn(`Invalid CSS selector "${selector}"`,err)}};const detectCssFont=rule=>{const match=rule.cssText.match(urlRegex);const url=(match&&match[1])||'';if(!url||url.match(/^data:/)||url==='about:blank')return;const fullUrl=url.startsWith('../')?`${rule.href}/../${url}`:url.startsWith('./')?`${rule.href}/.${url}`:url;return{text:rule.cssText,format:getFontMimeTypeFromUrl(fullUrl),url:fullUrl}};const inlineImages=el=>Promise.all(Array.from(el.querySelectorAll('image')).map(image=>{let href=image.getAttributeNS('http://www.w3.org/1999/xlink','href')||image.getAttribute('href');if(!href)return Promise.resolve(null);if(isExternal(href)){href+=(href.indexOf('?')===-1?'?':'&')+'t='+new Date().valueOf()}
return new Promise((resolve,reject)=>{const canvas=document.createElement('canvas');const img=new Image();img.crossOrigin='anonymous';img.src=href;img.onerror=()=>reject(new Error(`Could not load ${href}`));img.onload=()=>{canvas.width=img.width;canvas.height=img.height;canvas.getContext('2d').drawImage(img,0,0);image.setAttributeNS('http://www.w3.org/1999/xlink','href',canvas.toDataURL('image/png'));resolve(!0)}})}));const cachedFonts={};const inlineFonts=fonts=>Promise.all(fonts.map(font=>new Promise((resolve,reject)=>{if(cachedFonts[font.url])return resolve(cachedFonts[font.url]);const req=new XMLHttpRequest();req.addEventListener('load',()=>{const fontInBase64=arrayBufferToBase64(req.response);const fontUri=font.text.replace(urlRegex,`url("data:${font.format};base64,${fontInBase64}")`)+'\n';cachedFonts[font.url]=fontUri;resolve(fontUri)});req.addEventListener('error',e=>{console.warn(`Failed to load font from: ${font.url}`,e);cachedFonts[font.url]=null;resolve(null)});req.addEventListener('abort',e=>{console.warn(`Aborted loading font from: ${font.url}`,e);resolve(null)});req.open('GET',font.url);req.responseType='arraybuffer';req.send()}))).then(fontCss=>fontCss.filter(x=>x).join(''));let cachedRules=null;const styleSheetRules=()=>{if(cachedRules)return cachedRules;return cachedRules=Array.from(document.styleSheets).map(sheet=>{try{return sheet.cssRules}catch(e){console.warn(`Stylesheet could not be loaded: ${sheet.href}`)}})};const inlineCss=(el,options)=>{const{selectorRemap,modifyStyle,modifyCss,fonts}=options||{};const generateCss=modifyCss||((selector,properties)=>{const sel=selectorRemap?selectorRemap(selector):selector;const props=modifyStyle?modifyStyle(properties):properties;return `${sel}{${props}}\n`});const css=[];const detectFonts=typeof fonts==='undefined';const fontList=fonts||[];styleSheetRules().forEach(rules=>{if(!rules)return;Array.from(rules).forEach(rule=>{if(typeof rule.style!='undefined'){if(query(el,rule.selectorText))css.push(generateCss(rule.selectorText,rule.style.cssText));else if(detectFonts&&rule.cssText.match(/^#font-face/)){const font=detectCssFont(rule);if(font)fontList.push(font)}else css.push(rule.cssText)}})});return inlineFonts(fontList).then(fontCss=>css.join('\n')+fontCss)};out$.prepareSvg=(el,options,done)=>{requireDomNode(el);const{left=0,top=0,width:w,height:h,scale=1,responsive=!1,}=options||{};return inlineImages(el).then(()=>{let clone=el.cloneNode(!0);const{width,height}=getDimensions(el,clone,w,h);if(el.tagName!=='svg'){if(el.getBBox){clone.setAttribute('transform',clone.getAttribute('transform').replace(/translate\(.*?\)/,''));const svg=document.createElementNS('http://www.w3.org/2000/svg','svg');svg.appendChild(clone);clone=svg}else{console.error('Attempted to render non-SVG element',el);return}}
clone.setAttribute('version','1.1');clone.setAttribute('viewBox',[left,top,width,height].join(' '));if(!clone.getAttribute('xmlns'))clone.setAttributeNS(xmlns,'xmlns','http://www.w3.org/2000/svg');if(!clone.getAttribute('xmlns:xlink'))clone.setAttributeNS(xmlns,'xmlns:xlink','http://www.w3.org/1999/xlink');if(responsive){clone.removeAttribute('width');clone.removeAttribute('height');clone.setAttribute('preserveAspectRatio','xMinYMin meet')}else{clone.setAttribute('width',width*scale);clone.setAttribute('height',height*scale)}
Array.from(clone.querySelectorAll('foreignObject > *')).forEach(foreignObject=>{if(!foreignObject.getAttribute('xmlns'))
foreignObject.setAttributeNS(xmlns,'xmlns','http://www.w3.org/1999/xhtml')});return inlineCss(el,options).then(css=>{const style=document.createElement('style');style.setAttribute('type','text/css');style.innerHTML=`<![CDATA[\n${css}\n]]>`;const defs=document.createElement('defs');defs.appendChild(style);clone.insertBefore(defs,clone.firstChild);const outer=document.createElement('div');outer.appendChild(clone);const src=outer.innerHTML.replace(/NS\d+:href/gi,'xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href');if(typeof done==='function')done(src,width,height);else return{src,width,height}})})};out$.svgAsDataUri=(el,options,done)=>{requireDomNode(el);const result=out$.prepareSvg(el,options).then(({src})=>`data:image/svg+xml;base64,${window.btoa(reEncode(doctype+src))}`);if(typeof done==='function')return result.then(done);return result};out$.svgAsPngUri=(el,options,done)=>{requireDomNode(el);const{encoderType='image/png',encoderOptions=0.8,backgroundColor,canvg}=options||{};const convertToPng=({src,width,height})=>{const canvas=document.createElement('canvas');const context=canvas.getContext('2d');const pixelRatio=window.devicePixelRatio||1;canvas.width=width*pixelRatio;canvas.height=height*pixelRatio;canvas.style.width=`${canvas.width}px`;canvas.style.height=`${canvas.height}px`;context.setTransform(pixelRatio,0,0,pixelRatio,0,0);if(canvg)canvg(canvas,src);else context.drawImage(src,0,0);if(backgroundColor){context.globalCompositeOperation='destination-over';context.fillStyle=backgroundColor;context.fillRect(0,0,canvas.width,canvas.height)}
let png;try{png=canvas.toDataURL(encoderType,encoderOptions)}catch(e){if((typeof SecurityError!=='undefined'&&e instanceof SecurityError)||e.name==='SecurityError'){console.error('Rendered SVG images cannot be downloaded in this browser.');return}else throw e}
if(typeof done==='function')done(png);return Promise.resolve(png)}
if(canvg)return out$.prepareSvg(el,options).then(convertToPng);else return out$.svgAsDataUri(el,options).then(uri=>{return new Promise((resolve,reject)=>{const image=new Image();image.onload=()=>resolve(convertToPng({src:image,width:image.width,height:image.height}));image.onerror=()=>{reject(`There was an error loading the data URI as an image on the following SVG\n${window.atob(uri.slice(26))}Open the following link to see browser's diagnosis\n${uri}`)}
image.src=uri})})};out$.download=(name,uri)=>{if(navigator.msSaveOrOpenBlob)navigator.msSaveOrOpenBlob(uriToBlob(uri),name);else{const saveLink=document.createElement('a');if('download' in saveLink){saveLink.download=name;saveLink.style.display='none';document.body.appendChild(saveLink);try{const blob=uriToBlob(uri);const url=URL.createObjectURL(blob);saveLink.href=url;saveLink.onclick=()=>requestAnimationFrame(()=>URL.revokeObjectURL(url))}catch(e){console.warn('This browser does not support object URLs. Falling back to string URL.');saveLink.href=uri}
saveLink.click();document.body.removeChild(saveLink)}
else{window.open(uri,'_temp','menubar=no,toolbar=no,status=no')}}};out$.saveSvg=(el,name,options)=>{requireDomNode(el);out$.svgAsDataUri(el,options||{},uri=>out$.download(name,uri))};out$.saveSvgAsPng=(el,name,options)=>{requireDomNode(el);out$.svgAsPngUri(el,options||{},uri=>out$.download(name,uri))}})()
circle.dot { fill-opacity:0.5 !important; }
/* Please ignore what follows - it's the minified version of
https://cdnjs.cloudflare.com/ajax/libs/dc/3.0.4/dc.css, I had to include it here
because if it's stored in a different domain, SaveSvgAsPng can't load it */
.dc-chart path.dc-symbol,.dc-legend g.dc-legend-item.fadeout{fill-opacity:.5;stroke-opacity:.5}div.dc-chart{float:left}.dc-chart rect.bar{stroke:none;cursor:pointer}.dc-chart rect.bar:hover{fill-opacity:.5}.dc-chart rect.deselected{stroke:none;fill:#ccc}.dc-chart .pie-slice{fill:#fff;font-size:12px;cursor:pointer}.dc-chart .pie-slice.external{fill:#000}.dc-chart .pie-slice :hover,.dc-chart .pie-slice.highlight{fill-opacity:.8}.dc-chart .pie-path{fill:none;stroke-width:2px;stroke:#000;opacity:.4}.dc-chart .selected path,.dc-chart .selected circle{stroke-width:3;stroke:#ccc;fill-opacity:1}.dc-chart .deselected path,.dc-chart .deselected circle{stroke:none;fill-opacity:.5;fill:#ccc}.dc-chart .axis path,.dc-chart .axis line{fill:none;stroke:#000;shape-rendering:crispEdges}.dc-chart .axis text{font:10px sans-serif}.dc-chart .grid-line,.dc-chart .axis .grid-line,.dc-chart .grid-line line,.dc-chart .axis .grid-line line{fill:none;stroke:#ccc;opacity:.5;shape-rendering:crispEdges}.dc-chart .brush rect.selection{fill:#4682b4;fill-opacity:.125}.dc-chart .brush .custom-brush-handle{fill:#eee;stroke:#666;cursor:ew-resize}.dc-chart path.line{fill:none;stroke-width:1.5px}.dc-chart path.area{fill-opacity:.3;stroke:none}.dc-chart path.highlight{stroke-width:3;fill-opacity:1;stroke-opacity:1}.dc-chart g.state{cursor:pointer}.dc-chart g.state :hover{fill-opacity:.8}.dc-chart g.state path{stroke:#fff}.dc-chart g.deselected path{fill:gray}.dc-chart g.deselected text{display:none}.dc-chart g.row rect{fill-opacity:.8;cursor:pointer}.dc-chart g.row rect:hover{fill-opacity:.6}.dc-chart g.row text{fill:#fff;font-size:12px;cursor:pointer}.dc-chart g.dc-tooltip path{fill:none;stroke:gray;stroke-opacity:.8}.dc-chart g.county path{stroke:#fff;fill:none}.dc-chart g.debug rect{fill:#00f;fill-opacity:.2}.dc-chart g.axis text{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;pointer-events:none}.dc-chart .node{font-size:.7em;cursor:pointer}.dc-chart .node :hover{fill-opacity:.8}.dc-chart .bubble{stroke:none;fill-opacity:.6}.dc-chart .highlight{fill-opacity:1;stroke-opacity:1}.dc-chart .fadeout{fill-opacity:.2;stroke-opacity:.2}.dc-chart .box text{font:10px sans-serif;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;pointer-events:none}.dc-chart .box line{fill:#fff}.dc-chart .box rect,.dc-chart .box line,.dc-chart .box circle{stroke:#000;stroke-width:1.5px}.dc-chart .box .center{stroke-dasharray:3,3}.dc-chart .box .data{stroke:none;stroke-width:0}.dc-chart .box .outlier{fill:none;stroke:#ccc}.dc-chart .box .outlierBold{fill:red;stroke:none}.dc-chart .box.deselected{opacity:.5}.dc-chart .box.deselected .box{fill:#ccc}.dc-chart .symbol{stroke:none}.dc-chart .heatmap .box-group.deselected rect{stroke:none;fill-opacity:.5;fill:#ccc}.dc-chart .heatmap g.axis text{pointer-events:all;cursor:pointer}.dc-chart .empty-chart .pie-slice{cursor:default}.dc-chart .empty-chart .pie-slice path{fill:#fee;cursor:default}.dc-chart circle.dot{stroke:none}.dc-data-count{float:right;margin-top:15px;margin-right:15px}.dc-data-count .filter-count,.dc-data-count .total-count{color:#3182bd;font-weight:700}.dc-legend{font-size:11px}.dc-legend .dc-legend-item{cursor:pointer}.dc-hard .number-display{float:none}div.dc-html-legend{overflow-y:auto;overflow-x:hidden;height:inherit;float:right;padding-right:2px}div.dc-html-legend .dc-legend-item-horizontal{display:inline-block;margin-left:5px;margin-right:5px;cursor:pointer}div.dc-html-legend .dc-legend-item-horizontal.selected{background-color:#3182bd;color:white}div.dc-html-legend .dc-legend-item-vertical{display:block;margin-top:5px;padding-top:1px;padding-bottom:1px;cursor:pointer}div.dc-html-legend .dc-legend-item-vertical.selected{background-color:#3182bd;color:white}div.dc-html-legend .dc-legend-item-color{display:table-cell;width:12px;height:12px}div.dc-html-legend .dc-legend-item-label{line-height:12px;display:table-cell;vertical-align:middle;padding-left:3px;padding-right:3px;font-size:.75em}.dc-html-legend-container{height:inherit}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crossfilter/1.3.12/crossfilter.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dc/3.0.4/dc.min.js"></script>
<div id="chart"></div>
<button id="export" onclick="save()">Export as PNG</button>
Basically, I just get the SVG DOM element, and pass it to the saveSvgAsPng function:
var chart = document.getElementById('chart').getElementsByTagName('svg')[0];
saveSvgAsPng(chart, 'chart.png', options);
This is how the dc.js chart looks like:
And this is how the exported PNG looks:
Why does it show lines/areas/circles under the X axis (and beyond the horizontal limits too)? How can I fix it?
The <defs><clipPath /></defs> section is present within the SVG element, and I guess it's properly defined (right?).
I haven't tried saveSvgAsPng, so this is just a guess, but you could try
chart.select('g.chart-body').attr('clip-path',
chart.select('g.chart-body').attr('clip-path').replace(/.*#/, 'url(#'))
Reasoning: dc.js uses an obscure form of the clip-path attribute with an absolute URL. It's looking for the URL of the current page using window.location.href and that could go wrong, or saveSvgAsPng might not expect an absolute URL.
It does this for Angular compatibility but I can see why this would confuse a library.
The code above will remove the base URL, leaving only the relative hash part.
If this helps, we can add an option for this behavior.
I'm not self-answering, I just want to add a side note, which might be helpful for other SaveSvgAsPng users:
For the exported PNG to have the same look as the SVG, SaveSvgAsPng needs to properly apply the CSS styles. Otherwise, it would look like this:
If you run into this problem, please note that:
The stylesheets need to be stored in the same domain as the javascript code, otherwise the library won't be able to load them (for security reasons).
Most dc.js' styles are applied to the .dc-chart class or its children. This CSS class is applied to the parent DIV, not to the SVG element, which is what SaveSvgAsPng exports. Therefore, you will have to remove the selector from the CSS rules. The easiest way to do so is using the selectorRemap option, like this:
var options = {
selectorRemap: function(s) { return s.replace(/\.dc-chart/g, ''); }
};
var chart = document.getElementById('chart').getElementsByTagName('svg')[0];
saveSvgAsPng(chart, 'chart.png', options);
I'm not familiar with saveSvgAsPng, it might be that it's already using canvas. If It's the case, please downvote my question, probably not going to be useful ;)
Did you try using the svg->canvas->png path? I did use it with other d3 projects and worked fine.
This is a snippet lifted from another answer on that question:
var btn = document.querySelector('button');
var svg = document.querySelector('svg');
var canvas = document.querySelector('canvas');
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);
}
btn.addEventListener('click', function () {
var canvas = document.getElementById('canvas');
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;
});
<button>svg to png</button>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="200" height="200">
<rect x="10" y="10" width="50" height="50" />
<text x="0" y="100">Look, i'm cool</text>
</svg>
<canvas id="canvas"></canvas>

svg convert to canvas - can't generate multi pages pdf

I have 12 graphs and I want to generate pdf with 2 pages each page has 6 graphs.
However, when I convert svg to canvas, then the jspdf can only see part of both sub-dives.
$('#downloadx2').click(function() {
var svgElements = $("#body_id").find('svg');
//replace all svgs with a temp canvas
svgElements.each(function() {
var canvas, xml;
// canvg doesn't cope very well with em font sizes so find the calculated size in pixels and replace it in the element.
$.each($(this).find('[style*=em]'), function(index, el) {
$(this).css('font-size', getStylex(el, 'font-size'));
});
canvas = document.createElement("canvas");
canvas.className = "screenShotTempCanvas";
//convert SVG into a XML string
xml = (new XMLSerializer()).serializeToString(this);
// Removing the name space as IE throws an error
xml = xml.replace(/xmlns=\"http:\/\/www\.w3\.org\/2000\/svg\"/, '');
//draw the SVG onto a canvas
canvg(canvas, xml);
$(canvas).insertAfter(this);
//hide the SVG element
////this.className = "tempHide";
$(this).attr('class', 'tempHide');
$(this).hide();
});
var doc = new jsPDF("p", "mm");
var width = doc.internal.pageSize.width;
var height = doc.internal.pageSize.height;
html2canvas($("#div_pdf1"), {
onrendered: function(canvas) {
var imgData = canvas.toDataURL(
'image/png', 0.1);
doc.addImage(imgData, 'PNG', 5, 0, width, height/2,'','FAST');
doc.addPage();
}
});
html2canvas($("#div_pdf2"), {
onrendered: function(canvas2) {
var imgData2 = canvas2.toDataURL(
'image/png', 0.1);
doc.addImage(imgData2, 'PNG', 5, 0, width, height/2,'','FAST');
doc.save('.pdf');
}
});
});
<body id="body_id">
<div id="div_pdf1" >
<svg></svg>
<svg></svg>
<svg></svg>
</div>
<div id="div_pdf1" >
<svg></svg>
<svg></svg>
<svg></svg>
</div>
</body>
When I run this code, the generated pdf will view two pages with same canvas the first one (div_pdf1) div. So how to get both of them appearing in pdf as two pages.
You seem to be trying to run 2 parts in sequence but that's not how javascript works and actually runs your code.
No big deal, just a small misunderstanding between your mental model and the engine that executes the code.
A quick temporary debugging tool to see what's going on and verify that there is a discrepancy is to add console.log to key points and check the sequence of their printout once you run the code.
console.log('[1] just before: svgElements.each');
svgElements.each(function() {
console.log('[2] just after: svgElements.each');
And also around this part of the code:
console.log('[3] just before html2canvas-div_pdf1');
html2canvas($("#div_pdf1"), {
console.log('[4] just after html2canvas-div_pdf1');
Finally around this part of the code:
console.log('[5] just before html2canvas-div_pdf2');
html2canvas($("#div_pdf2"), {
console.log('[6] just after html2canvas-div_pdf2');
I suspect you'll see the code doesn't print the log lines in the order you think they will.
Next, you can try wrapping the 2 calls to html2canvas with one setTimeout function and force a delay in the execution of that code by an arbitrary amount of milliseconds.
Note that this is not the recommended final production quality solution but it will make the code output what you want.

How to get char code of fontawesome icon?

I'd like to use fontawesome icons in SVG scope. I cannot achieve it in common way, but I can add <text> element containing corresponding UTF-8 char and with font set to fontawesome, like that:
<text style="font-family: FontAwesome;">\uf0ac</text>
To make it clear I wrote a switch for getting useful icons:
getFontAwesomeIcon(name) {
switch (name) {
case 'fa-globe':
return '\uf0ac'
case 'fa-lock':
return '\uf023'
case 'fa-users':
return '\uf0c0'
case 'fa-ellipsis-h':
return '\uf141'
default:
throw '# Wrong fontawesome icon name.'
}
}
But of course that's ugly, because I must write it myself im my code. How can I get these values just from fontawesome library?
You can avoid producing such a list and extract the information from the font-awesome stylesheet on the fly. Include the stylesheet and set the classes like usual, i. e.
<tspan class="fa fa-globe"></tspan>
and you can do the following:
var icons = document.querySelectorAll(".fa");
var stylesheet = Array.from(document.styleSheets).find(function (s) {
return s.href.endsWith("font-awesome.css");
});
var rules = Array.from(stylesheet.cssRules);
icons.forEach(function (icon) {
// extract the class name for the icon
var name = Array.from(icon.classList).find(function (c) {
return c.startsWith('fa-');
});
// get the ::before styles for that class
var style = rules.find(function (r) {
return r.selectorText && r.selectorText.endsWith(name + "::before");
}).style;
// insert the content into the element
// style.content returns '"\uf0ac"'
icon.textContent = style.content.substr(1,1);
});
My two answers for two approaches to the problem (both developed thanks to ccprog):
1. Setting char by class definition:
In that approach we can define element that way:
<text class="fa fa-globe"></text>
And next run that code:
var icons = document.querySelectorAll("text.fa");
// I want to modify only icons in SVG text elements
var stylesheets = Array.from(document.styleSheets);
// In my project FontAwesome styles are compiled with other file,
// so I search for rules in all CSS files
// Getting rules from stylesheets is slightly more complicated:
var rules = stylesheets.map(function(ss) {
return ss && ss.cssRules ? Array.from(ss.cssRules) : [];
})
rules = [].concat.apply([], rules);
// Rest the same:
icons.forEach(function (icon) {
var name = Array.from(icon.classList).find(function (c) {
return c.startsWith('fa-');
});
var style = rules.find(function (r) {
return r.selectorText && r.selectorText.endsWith(name + "::before");
}).style;
icon.textContent = style.content.substr(1,1);
});
But I had some problems with that approach, so I developed the second one.
2. Getting char with function:
const getFontAwesomeIconChar = (name) => {
var stylesheets = Array.from(document.styleSheets);
var rules = stylesheets.map(function(ss) {
return ss && ss.cssRules ? Array.from(ss.cssRules) : [];
})
rules = [].concat.apply([], rules);
var style = rules.find(function (r) {
return r.selectorText && r.selectorText.endsWith(name + "::before");
}).style;
return style.content.substr(1,1);
}
Having that funcion defined we can do something like this (example with React syntax):
<text>{getFontAwesomeIconChar('fa-globe')}</text>

Cannot recolor SVG favicon with gulp

i'm trying to make simple favicon recolor by configurate and launch gulp module: https://www.npmjs.com/package/gulp-recolor-svg
but it's still not working.
(ERROR) Console returns: =(
TypeError: firstColor.rgbArray is not a function
at colorDifference (/var/www/html.site.ru/src/node_modules/gulp-recolor-svg/lib/ColorMatcher.js:11:62)
SVG
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 15 15" enable-background="new 0 0 15 15" xml:space="preserve">
<path fill="#00ff00" d="M6.3,1.1v5.2H1.1c-0.7,0-0.7,0.5-0.7,1.2c0,0.7,0.1,1.2,0.7,1.2h5.2v5.2c0,0.7,0.5,0.7,1.2,0.7
c0.7,0,1.2-0.1,1.2-0.7V8.7h5.2c0.7,0,0.7-0.5,0.7-1.2c0-0.7-0.1-1.2-0.7-1.2H8.7V1.1c0-0.7-0.5-0.7-1.2-0.7
C6.8,0.4,6.3,0.5,6.3,1.1"/>
</svg>
gulpfile.js
var gulp = require('gulp');
var color = require('color');
var recolorSvg = require('gulp-recolor-svg');
gulp.task('default', function() {
return gulp
.src('favicon.svg')
.pipe(recolorSvg.GenerateVariants([recolorSvg.ColorMatcher(color('#00ff00'))], [
{suffix: '--prod', colors: [color('#72982d')]},
{suffix: '--dev', colors: [color('#8d67d2')]}
]))
.pipe(gulp.dest('/'));
});
What's my mistake?
PS: I simplified my script for convenience of perception, I'm sorry if I made a mistake somewhere and here)
===========================================================================
node_modules/gulp-recolor-svg/lib/RecolorSvg.js
// Generated by CoffeeScript 1.10.0
(function() {
var Color, colorDifference, convert;
Color = require("color");
convert = require("color-convert");
colorDifference = function(firstColor, secondColor) {
var firstColorLabComponents, secondColorLabComponents, sumOfDifferencesSqaured;
firstColorLabComponents = convert.rgb.lab.raw(firstColor.rgbArray());
secondColorLabComponents = convert.rgb.lab.raw(secondColor.rgbArray());
sumOfDifferencesSqaured = firstColorLabComponents.map(function(value, index) {
return value - secondColorLabComponents[index];
}).map(function(value) {
return Math.pow(value, 2);
}).reduce(function(sum, value) {
return sum + value;
}, 0);
return Math.pow(sumOfDifferencesSqaured, 0.5);
};
module.exports = function(colorToMatch, maxDifference) {
if (maxDifference == null) {
maxDifference = 0.1;
}
return function(color) {
var difference;
difference = colorDifference(colorToMatch, color);
return difference <= maxDifference;
};
};
}).call(this);
If you use the exposed version of Color provided by the library, you shouldn't have a problem.
RecolorSvg.GenerateVariants(
[RecolorSvg.ColorMatcher(RecolorSvg.Color("red"))],
[
{ suffix: "--hover", colors: [ RecolorSvg.Color("red") ] },
{ suffix: "--active", colors: [ RecolorSvg.Color("red").darken(0.1) ] },
{ suffix: "--focus", colors: [ RecolorSvg.Color("cyan") ] },
{ suffix: "--disabled", colors: [ RecolorSvg.Color("#ccc") ] }
]
)
The error shown comes from the change in the Color module's API. The Color module that RecolorSvg uses is an older version.
In ColorMatcher.js the line 11 has to be replaced like this:
firstColorLabComponents = convert.rgb.lab.raw(firstColor.rgb().array());
due to api change

Resources