fabric.js - Adding custom properties to a shape - fabricjs

I am trying to add some custom properties for a rectangle.
I have tried setting the toObject property as per the fabric.js documentation.
let rect = new fabric.Rect();
rect.toObject = ((toObject) => {
return () => {
return fabric.util.object.extend(toObject.call(this), {
name: 'some name'
});
};
})(rect.toObject);
this.canvas.add(rect);
When I click on save of the canvas
this.canvas.toJSON();
I am getting a
ERROR TypeError: this.callSuper is not a function.

Because this value is not proper inside arrow function. Either change it to normal function, or pass rect object instead this.
const canvas = new fabric.Canvas('c')
const rect = new fabric.Rect();
rect.toObject = (function(toObject) {
return function(propertiesToInclude) {
return fabric.util.object.extend(toObject.call(this, propertiesToInclude), {
name: 'some name'
});
};
})(rect.toObject);
canvas.add(rect);
console.log(canvas.toJSON())
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.4.0/fabric.js"></script>
<canvas id='c'></canvas>

Related

How do I a multiselect programatically in fabric.js?

I'm trying to select a few fabric.js objects so I can manipulate them together.
First I load an SVG file as a fabric group.
Second I click on the button to filter objects and "select" objects that I want to make the 'active selection out of.
In this example, you can see the bounding box is not where you'd expect it to be. This is causing some issues in my app.
I've seen in the 'gotchas' about setCoords() and I'm not sure if this is related to that or how it would apply here since I haven't moved anything in the group x,y.
This example codepen an attempt to show a simple example.
https://codepen.io/bencbaumann/pen/PodoeXq?editors=1010
html
<button id="multiselect">Multi Select</button>
<canvas id="canvas">
js
const canvas = new fabric.Canvas("canvas", {
height: 500,
width: 500
});
var url = "https://assets.codepen.io/496640/dots3.svg";
fabric.loadSVGFromURL(url, function (objects, options) {
const svgObj = fabric.util.groupSVGElements(objects, options);
canvas.add(svgObj);
canvas.renderAll();
});
function selectObjectsByIds({ canvas, ids }) {
console.log("ids", ids); // show [ids] that we are trying to select
const objects = canvas.getObjects(); // get objects in canvas
console.log("objects.length:", objects.length);
console.log("objs:", objects);
const objectsWithId = objects.filter((o) => o.id);
const objectsWithChildren = objects.filter((o) => o._objects);
const childrenWithId = objectsWithChildren
.map((o) => o._objects)
.flat()
.filter((o) => o.id);
const selected_ids = [...objectsWithId, ...childrenWithId];
console.log(selected_ids)
const selectedObjects = selected_ids.filter((o) => ids.includes(o.id));
canvas.discardActiveObject();
console.log('selectedObjects: ', selectedObjects)
var sel = new fabric.ActiveSelection(selectedObjects, {
canvas: canvas,
});
canvas.setActiveObject(sel);
canvas.requestRenderAll();
}
var $ = function(id){return document.getElementById(id)};
var multiselect = $('multiselect')
multiselect.onclick = () => selectObjectsByIds({canvas, ids: ['dot']})
the bounding box is not where it should be. I expect the bounding box to be around the objects in my selection.
The 3 green dots have the id of 'dot'

How to set the data of chart in angular which is taken from the backend nodejs api that is store in mongodb and showing on the html page

app.component.html
This is my app.component file where i show my chart
<div style="display: block;">
<canvas baseChart width="400" height="400"
[datasets]="lineChartData"
[labels]="lineChartLabels"
[options]="lineChartOptions"
[colors]="lineChartColors"
[legend]="lineChartLegend"
[chartType]="lineChartType">
</canvas>
</div>
app.component.ts
My ts file where the logic impleneted i have issue there i am not know how to perform logic to show the data i have issue how to create method in it which will call in the app.component.html file
constructor(private chartService: ChartServiceService) { }
ngOnInit(): void {
this.getlinechart();
}
getlinechart(){
this.chartService.getLineChart().subscribe(res => {
this.linechart = res;
console.log(res);
let lincechartData = res.list[0].data;
let lincechartLabels = res.list[0].graphlabels;
public lineChartData:any = this.lincechartData;
**I,m facing issue here above to create method to store the
data and used in html compiler give the error this ,'
expected.ts(1005)
Cannot find name 'lineChartData'**
});
}
app.service.ts
This is service file which getting the data from the backend nodejs api
getLineChart(){
return this.httpClient.get<any>
('http://localhost:3000/api/chart/line')
.pipe(
map(result => result)
)
}
If i undestand correctly you are trying to read data from a database and put it on a chart. I did something similar, but i had a bit different approach:
HTML:
<canvas id="myChart" width="300" height="100"></canvas> <br/>
Chart config:
var config = {
type: 'line',
data: { datasets: [] }, // trend data goes here
options: {
scales: {
xAxis: { // x axis
type: 'time',
}
},
},
}
New chart:
function newChart() {
config.data.datasets = []; // reset data
populateTrend(config); // populate chart
if (window.hasOwnProperty("graph")) { // destroy previous chart
if (typeof window.graph.resetZoom !== "undefined") { // if you have a zoom plugin
window.graph.resetZoom();
}
window.graph.destroy();
}
let ctx = document.getElementById('myChart').getContext('2d'); // canvas
window.graph = new Chart(ctx, config); // make the chart
}
populate trend:
function populateTrend (config) {
// data sets and scales
let datasets = config.data.datasets, scales = config.options.scales;
let log = data; // this is where you read from your database
let dataset = { // add some data here
data: [],
label: log.description,
fill: false,
radius: 0,
unit: log.unit,
pointType: log.pointType,
yAxisID: log.unit, // y axis
};
// this is where the real data goes
// you should push the data in a loop
dataset.data.push({
"x": something.getTime(),
"y": null,
"ymin": null,
"ymax": null,
});
datasets.push(dataset); // add data set to config
}

get data from nodejs to frontend js for google maps but stuck in retrieving it looking for ways to solve it

My NodeJS GET route:
router.get('/stores', function (req, res, next) {
errorMsg = req.flash('error')[0];
successMsg = req.flash('success')[0];
Product.find(function (err, products) {
// console.log(products)
res.render('admin/stores', {
layout: 'admin-map.hbs',
stores: products,
errorMsg: errorMsg,
GOOGLE_APIKEY: process.env.GOOGLE_APIKEY,
noErrors: 1
});
});
The route /stores returns json data which holds latitude and longitude and I want it in my script tag of map.html with loop, to render the pins on the map. Below, the script:
<!DOCTYPE html>
<html>
<head>
<script src = "https://maps.googleapis.com/maps/api/js"></script>
<script>
function loadMap() {
alert(this.stores);
var mapOptions = {
center:new google.maps.LatLng(17.433053, 78.412172),
zoom:5
}
var map = new google.maps.Map(document.getElementById("sample"),mapOptions);
var marker = new google.maps.Marker({
position: new google.maps.LatLng(17.433053, 78.412172),
map: map,
draggable:true,
icon:'/scripts/img/logo-footer.png'
});
marker.setMap(map);
var infowindow = new google.maps.InfoWindow({ });
infowindow.open(map,marker);
}
</script>
<!-- ... -->
</head>
</html>
How can I do it?
It seems you need to follow two steps
1. Pass data from hbs to script
Using triple brackets syntax
<script>
let stores = {{{ stores }}}; // the triple brackets
console.log('Data : ', stores);
function loadMap() {
...
Check if data is being printed in console? If yes your data is available in the front-end script and you can
2. Loop through it
...
for (let i = 0; i < stores.length; i++) {
// the JS loop instead of hbs one, because we are on front-end
var marker = new google.maps.Marker({
position: new google.maps.LatLng(stores[i].lat, stores[i].lng), // whatever applies
map: map,
draggable:true,
icon:'/scripts/img/logo-footer.png'
});
}
And donot need to call setMap(), you have already set the map in map: map above
the answer is inside fronted script i can have an object declared globally and this double flower brackets works fine with them
<script>
function loadMap() {
alert(this.stores);
var mapOptions = {
center:new google.maps.LatLng(17.433053, 78.412172),
zoom:5
}
var map = new google.maps.Map(document.getElementById("sample"),mapOptions);
{{#each stores}}
var marker = new google.maps.Marker({
position: new google.maps.LatLng(17.433053, 78.412172),
map: map,
draggable:true,
icon:'/scripts/img/logo-footer.png'
});
{{/each}}
marker.setMap(map);
var infowindow = new google.maps.InfoWindow({ });
infowindow.open(map,marker);
}
</script>

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>

Ontouchstart is not working for svg element

I am creating a circle using SVG
var gauge = mapSvg.circle(center['x'], center['y'], 85).attr({'stroke-width': 3, 'stroke': 'black', 'fill': 'white'});
It is in for loop, this circle created three times. I have added ontouchstart on its click
gauge.node.ontouchstart = function () { alert("hi>>>>>") }
but this alert is not showing.
This should work...
var r = Raphael("blah",200,200);
var c = r.circle(100,100,100).attr({fill: 'red' }).touchstart( function() { alert('hi') });
jsfiddle http://jsfiddle.net/7c56E/

Resources