htmlparser2 how to replace a tag by another custom tag with the same attributes that has the tag one - node.js

I need to change a tag for another tag with the same properties. For example change this:
<TagOne width="500" height="200">asdfasdf</TagOne>
to this:
<AnotherTag width="500" height="200">sometext</AnotherTag>
With this code I can see the attributes but i don't know how to replace the tag for the other one:
const htmlparser2 = require("htmlparser2");
const DomUtils = require("htmlparser2").DomUtils;
const htmlContent = `<html>
<head></head> <body> <div id="content">
<TagOne width="500" height="200" src="image1.jpg">asdfasdf</TagOne>
<p>asdfasdf</p> </div></body></html>`; const parser = new htmlparser2.Parser(
{
onopentag(name, attribs) {
if (name === "TagOne") {
if(attribs.width ){
var width = attribs.width;
}
if(attribs.height ){
var height = attribs.height;
}
var new_tag = `<AnotherTag width:`+width+`; height:`+height+`;">sometext</div>`;
}
},
ontext(text) {
console.log("-->", text);
},
onclosetag(tagname,new_tag) {
if (tagname === "amp-iframe") {
console.log("That's it?!");
}
},
},
{ decodeEntities: true } );
var content = parser.write(htmlContent);
parser.end();
I have tried to do this on the onclosetag function:
//htmlparser2.DomUtils.removeElement(tagname);
//parser.write(new_tag);
but it gives me an error, it is not the correct way but I can't find anything similar in the documentation. can someone help me? Thanks.

Related

Vue 3: Bind ref value to object property

I'm running into an issue where I'm trying to bind an object property to a ref in Vue, using the new composition API. I'm expecting the template to re-render with the new value after setting the ref value, but I'm however getting a RefImpl {} instead. How would I solve this?
<template>
<v-card>
<v-card-text class="pa-2">
<div v-for="(social, index) in socials" :key="index">
<p>{{ social.value }}</p>
</div>
</v-card-text>
</v-card>
</template>
<script>
import { onMounted, ref } from "#vue/composition-api/dist/vue-composition-api";
export default {
setup() {
const testVariable = ref(0);
const socials = [
{
value: testVariable,
}
];
onMounted(() => {
setTimeout(() => testVariable.value = 100, 1000);
});
return {
socials,
}
},
}
</script>
<style scoped></style>
Your socials variable does not unref inner refs in template. Basically what you have to do in your template is using social.value.value. So I think renaming that variable would be better to something like
const socials = [
{
variable: testVariable,
}
];
So that you could do social.variable.value.
Details from Vue docs:
Note the unwrapping only applies to top-level properties - nested access to refs will not be unwrapped: Read More
Looks like your code works:
const { onMounted, ref } = Vue
const app = Vue.createApp({
setup() {
const testVariable = ref(0);
const socials = [{ value: testVariable, }];
onMounted(() => {
setTimeout(() => testVariable.value = 100, 1000);
});
return { socials, }
},
})
app.mount('#demo')
<script src="https://unpkg.com/vue#3.2.29/dist/vue.global.prod.js"></script>
<div id="demo">
<div v-for="(social, index) in socials" :key="index">
<p>{{ social.value }}</p>
</div>
</div>

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
}

Why is my tabulator custom header filter changing on keyup?

To reproduce:
Run the Code Snippet (recommend Full Page mode)
Hold Ctrl or Shift while clicking to select multiple 'species' in the table column header
Note that while the key is held down, the table data is filtered according to selected 'species'
Release the key, observe changes in filtered table data
I think this is probably related to https://github.com/olifolkerd/tabulator/issues/975 and that I need to do something to override default tabulator keypress events.
JSFiddle: https://jsfiddle.net/jjech/3th28pv0/229/
const speciesTypes = [ 'Human', 'Android', 'Betazoid', 'Klingon', 'Ferengi', 'Tamarian' ];
function multiSelectHeaderFilter(cell) {
var values = speciesTypes;
const filterFunc = (rowData) => {
return values.includes(rowData['species']);
}
const getSelectedValues = (multiSelect) => {
var result = [];
var options = multiSelect && multiSelect.options;
var opt;
for (var i=0, iLen=options.length; i<iLen; i++) {
opt = options[i];
if (opt.selected) {
result.push(opt.value || opt.text);
}
}
return result;
}
const onChange = () => {
var editor = document.getElementById('speciesSelector');
values = getSelectedValues(editor);
console.log("values: "+values);
cell.getColumn().getTable().removeFilter(filterFunc);
cell.getColumn().getTable().addFilter(filterFunc);
}
var select = document.createElement("select");
select.multiple = "multiple";
select.id = 'speciesSelector';
select.style = 'width: 100%';
speciesTypes.forEach(species => {
select.innerHTML += "<option id='"+species+"' value='"+species+"' selected='selected'>"+species+"</option>";
});
cell.getColumn().getTable().addFilter(filterFunc);
select.addEventListener('change',onChange);
return select;
}
var table = new Tabulator("#tabulator", {
layout:"fitColumns",
data:[ {name:'Geordi La Forge',species:'Human'}, {name:'Dathon', species:'Tamarian'}, {name:'Jean-Luc Picard', species:'Human'}, {name:'Worf, son of Mogh', species:'Klingon'}, {name:'Tasha Yarr', species:'Human'}, {name:'Data', species:'Android'}, {name:'Wesley Crusher', species:'Human'}, {name:'Jalad', species:'Tamarian'}, {name:'Lwaxana Troi', species:'Betazoid'}, {name:'Temba', species:'Tamarian'}, {name:'T\'Kuvma', species:'Klingon'}, {name:'Lore', species:'Android'}, {name:'Noonian Soongh', species:'Human'}, {name:'Darmok', species:'Tamarian'}, {name:'Reittan Grax', species:'Betazoid'}, {name:'Quark', species:'Ferengi'} ],
headerSort:true,
columns:[ {title:'Name',field:'name',sorter:'string'},{title:'Species',field:'species',sorter:'string',headerFilter:multiSelectHeaderFilter }, ],
});
//document.multiselect('#speciesSelector');
<html>
<head>
<link href="https://unpkg.com/tabulator-tables#4.5.3/dist/css/tabulator.min.css" rel="stylesheet">
<script type="text/javascript" src="https://unpkg.com/tabulator-tables#4.5.3/dist/js/tabulator.min.js"></script>
<link href="https://cdn.jsdelivr.net/gh/mneofit/multiselect/styles/multiselect.css" rel="stylesheet">
<script type="text/javascript" src="https://cdn.jsdelivr.net/gh/mneofit/multiselect/multiselect.min.js"></script>
<script></script>
<style>
.tabulator .tabulator-header,
.tabulator .tabulator-header .tabulator-col
{
overflow: unset;
}
</style>
</head>
<body>
<div id="tabulator"></div>
</body>
</html>
Fixed by adding headerFilterLiveFilter:false to the column definition.
columns:[ {title:'Name',field:'name',sorter:'string'},{title:'Species',field:'species',sorter:'string',headerFilter:multiSelectHeaderFilter,headerFilterLiveFilter:false }, ],
https://jsfiddle.net/jjech/3th28pv0/237/

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>

Show the item hit content only when the search box is not empty

I have this in my algolia file for my jekyll site.
<script>
const search = instantsearch({
appId: '{{ site.algolia.application_id }}',
apiKey: '{{ site.algolia.search_only_api_key }}',
indexName: '{{ site.algolia.index_name }}',
searchParameters: {
restrictSearchableAttributes: [
'title',
'content'
],
facetFilters: ['type:post']
},
});
const hitTemplate = function(hit) {
let date = '';
if (hit.date) {
date = moment.unix(hit.date).format('L');
// date = moment.unix(hit.date).format('MMM Do YY');
modifiedDate = moment.unix(hit.last_modified_at).format('MMM Do YY');
}
const url = hit.url;
const title = hit._highlightResult.title.value;
const content = hit._highlightResult.html.value;
return `
<div class="post-list">
<span class="post-date-list-wrap">
<span class="post-date-list">${date}
<span class="post-title"> ${title} </span>
</span>
${content}
</div>
`;
}
const hitTemplateTrans = function(hit) {
let date = '';
if (hit.date) {
date = moment.unix(hit.date).format('MMM DD YYYY');
}
const url = hit.url;
const title = hit._highlightResult.title.value;
const content = hit._highlightResult.html.value;
return `
<div class="post-list">
<span class="post-date-list-wrap">
<span class="post-date-list">${date}
<span class="post-title"> ${title}</span>
</span>
</span>
</div>
`;
}
search.addWidget(
instantsearch.widgets.searchBox({
container: '#search-searchbar',
placeholder: 'search notes',
autofocus: true
})
);
search.addWidget(
instantsearch.widgets.hits({
container: '#search-hits',
templates: {
empty: 'No results',
item: hitTemplate
},
})
);
search.start();
</script>
Without typing anything in the search box I have the list of articles
with the excerpt, the short introduction of the article.
That's because I have ${content} to show the highlights when someone
types the search term.
That's fine and everything is working but... I don't want to show the contents of each item when the search box is empty.
If the search box is empty I would like to keep only the title and the date
but if the search box is not empty just show the title/date and the excerpt as it's usual.
It seems like an easy task but I'm stuck right now, I tried removed the content tag and put in the hit transformation function, but it doesn't work.
The instantsearch.js library has a function hook, called searchFunction, you can define when instanciating the library. That is called right before any search is performed. You can use it to check if the current query is empty or not, and adapt your layout based on this knowledge.
Here is a slightly edited script (irrelevant parts removed) that should let you do what you're looking for:
let additionalClass = '';
const search = instantsearch({
[…]
searchFunction: function(helper) {
if (helper.getState().query === '') {
additionalClass = 'post-item__empty-query';
} else {
additionalClass = '';
}
helper.search()
}
});
[…]
const hitTemplate = function(hit) {
return
`<div class="post-item ${additionalClass}">
[…]
</div>`
;
}
.post-item__empty-query .post-snippet {
display: none;
}
What it does is defining a global variable (additionalClass) that will be used in the hit template (added alongside item-post, at the root level).
Right before everysearch, we check if the query is empty. If so, we set additionalClass to item-post__empty_query. We also defined in CSS that when this class is applied, the content should be hidden.
All of that together makes the title + date displayed when no search is performed, and the content displayed only when an actual keyword is searched for.
Hope that helps,

Resources