Skip to content

Instantly share code, notes, and snippets.

@jkutianski
Last active Dec 28, 2015
Embed
What would you like to do?
A new attempt to add templating to d3.js

D3.js templating test based on my previous solution of John Berryman's attempt.

The HTMLTemplateElement is currently supported in Chrome, Firefox and Opera (15+) for now. See Eric Bidelman HTMLTemplateElement tuturial

I change the idea of use HTMLTemplateElement.content because is only supported by 3 browsers . I'm using HTMLElement.innerHTML to read the template content and DOMParser() to inject the result into the DOM. To hide the content of <template> (HTMLUnknownElement) on other browsers (Safari, IE, etc) I add a CSS rule with display:none. On browsers that support HTMLTemplateElement the <script> isn't fired when the browser parse the document, but is different on HTMLUnknownElements. If someone know how to break the script execution on HTMLUnknownElements please send me a tweet @baldpower

Fork me @ GITHUB

TODOs:

(function (d3Pointers) {
// hack to hide <template> (HTMLUnknownElement) on browsers without HTMLTemplateElement
(function (style) {
style.appendChild(document.createTextNode("")); // webkit workaround
document.head.appendChild(style);
var addCss = function (r,i) {
return (style.sheet.insertRule) ? style.sheet.insertRule(r,i) : style.sheet.addRule(r,i); // insertRule on FF, addRule on IE
};
addCss("template {display: none !important;}", 0);
})(
document.createElement("style")
);
var callTemplate = function(callback,sel,d) {
sel = (sel) ? // Include "template" on selector
'template' +
(
(sel.match(/^[\#|\.|\:|\[]/g)) ?
sel :
' ' + sel
) :
'';
var parser = new DOMParser(),
template = d3.select(sel)[0][0], //select template node
thisSelection = (d) ? this.datum(d) : this, // Solve datum as parameter
templateContent = template.innerHTML
.replace(/\<script.*?\>.*?\<\/script\>/gi,''); // Exclude "script" on template content
parser.async = false;
thisSelection.each(function (d) {
var renderedContent = callback(templateContent, d), // call render engine
parsedContent = parser
.parseFromString(renderedContent,'text/xml') // deserialize rendered content
.documentElement
.firstChild;
// import rendered nodes on actual node
while (parsedContent) {
this.appendChild(
this.ownerDocument.importNode(parsedContent, true)
);
parsedContent = parsedContent.nextSibling;
}
});
};
// Add callTemplate Prototype to d3 pointers
while((pointer = d3Pointers.pop())) {
pointer.prototype.callTemplate = callTemplate;
}
})(
[d3.selection, d3.selection.enter]
);
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>D3js Templating Test</title>
<script src="http://d3js.org/d3.v2.min.js" type="text/javascript"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/mustache.js/0.7.2/mustache.min.js" type="text/javascript"></script>
<script src="d3.selection.template.js" type="text/javascript"></script>
<script type="text/javascript">
window.onload = function () {
var count = 0,
w = 960,
h = 520,
body = d3.select('body'),
vis = body.append('svg')
.attr('width', w)
.attr('height', h),
nextData = function() {
count += 1;
return {
head: {
width: .7*Math.random()+.8 //.8 - 1.5
},
mouth: {
width: .8*Math.random()+.5, //.5 - 1.3
height: Math.random()+.5, //.5 - 1.5
drop: 15*Math.random()-5 //-5 - 10
},
nose: {
width: Math.random()+.5, //.5 - 1.5
height: Math.random()+.5, // .5 - 1.5
drop: 15*Math.random()-5 // -5 - 10
},
eye: {
width: .8*Math.random()+.5, //.5 - 1.3
height: 1.1*Math.random()+.4//.4 - 1.5
},
brow: {
lift: 10*Math.random() //0 - 10
},
x: w*(count%5+1)/5 - 85 ,
y: h*.95*Math.ceil(count/5)/4.9 - 51
};
},
data = d3.range(25).map(function() {
return nextData();
});
vis.selectAll('.head')
.data(data)
.enter()
.append('g')
.attr('class', 'head')
.attr('transform',function(d) {
return 'translate('+ d.x +' '+ d.y +')'
})
.callTemplate(Mustache.render, "#head");
};
</script>
<style>
.face {
color: #000000;
fill: #ffccaa;
fill-opacity: 1;
fill-rule: evenodd;
stroke: #000000;
stroke-width: 1.5;
marker: none;
visibility: visible;
display: inline;
overflow: visible;
enable-background: accumulate;
}
.mouth {
color: #000000;
fill: #000000;
fill-opacity: 1;
fill-rule: evenodd;
stroke: #000000;
stroke-width: 1.5;
stroke-linecap: butt;
stroke-linejoin: miter;
stroke-miterlimit: 4;
stroke-opacity: 1;
stroke-dashoffset: 0;
marker: none;
visibility: visible;
display: inline;
overflow: visible;
enable-background: accumulate;
}
.rightbrow {
color: #000000;
fill: none;
stroke: #000000;
stroke-width: 1.5;
stroke-linecap: butt;
stroke-linejoin: miter;
stroke-miterlimit: 4;
stroke-opacity: 1;
stroke-dasharray: none;
stroke-dashoffset: 0;
marker: none;
visibility: visible;
display: inline;
overflow: visible;
enable-background: accumulate;
}
.leftbrow {
color: #000000;
fill: none;
stroke: #000000;
stroke-width: 1.5;
stroke-linecap: butt;
stroke-linejoin: miter;
stroke-miterlimit: 4;
stroke-opacity: 1;
stroke-dasharray: none;
stroke-dashoffset: 0;
marker: none;
visibility: visible;
display: inline;
overflow: visible;
enable-background: accumulate;
}
.righteye {
color: #000000;
fill: #000000;
stroke: #000000;
stroke-width: 1.5;
stroke-linecap: butt;
stroke-linejoin: miter;
stroke-miterlimit: 4;
stroke-opacity: 1;
stroke-dashoffset: 0;
marker: none;
visibility: visible;
display: inline;
overflow: visible;
enable-background: accumulate;
}
.lefteye {
color: #000000;
fill: #000000;
stroke: #000000;
stroke-width: 1.5;
stroke-linecap: butt;
stroke-linejoin: miter;
stroke-miterlimit: 4;
stroke-opacity: 1;
stroke-dashoffset: 0;
marker: none;
visibility: visible;
display: inline;
overflow: visible;
enable-background: accumulate;
}
.nose {
color: #000000;
fill: #ffccaa;
fill-opacity: 1;
fill-rule: evenodd;
stroke: #000000;
stroke-width: 1.5;
stroke-linecap: butt;
stroke-linejoin: miter;
stroke-miterlimit: 4;
stroke-opacity: 1;
stroke-dasharray: none;
stroke-dashoffset: 0;
marker: none;
visibility: visible;
display: inline;
overflow: visible;
enable-background: accumulate;
}
</style>
</head>
<body>
</body>
<template id="head">
<svg xmlns="http://www.w3.org/2000/svg"> <!-- The namespace must to be explicitly defined -->
<path
class="face"
d="M 28.471261,-5.0558794 C 32.671841,22.243131 19.834631,49.162831 0.60952108,49.162831 -18.615599,49.162831 -27.684919,24.667631 -27.053699,-3.0332694 -26.458119,-29.170189 -25.961069,-47.588419 0.21246108,-48.936829 19.417701,-49.926249 24.103691,-33.440159 28.471261,-5.0558794 z"
transform="scale({{head.width}} 1)">
</path>
<path
class="mouth"
d="m 15,0 c 0,3.7959 -4.91374,13.7461 -12.8383199,13.7461 -7.92458,0 -17.6233801,-10.3065 -15.8591001,-13.7461 1.0069201,-1.9631 7.9345201,0 15.8591001,0 7.9245799,0 12.8383199,-3.7959 12.8383199,0 z"
transform="translate(0, {{mouth.drop}}) translate(0,17) scale({{mouth.width}} {{mouth.height}})">
</path>
<path
class="rightbrow"
d="m -18.009869,-17.276949 c 3.23815,-9.62232 14.5716901,-8.70591 14.5716901,-8.70591"
transform="translate(0, -{{brow.lift}})">
</path>
<path
class="leftbrow"
d="m 20.443211,-18.868939 c -3.23815,-9.62232 -14.5716899,-8.70591 -14.5716899,-8.70591"
transform="translate(0, -{{brow.lift}})">
</path>
<path
class="righteye"
d="m 2.3,0 c 0.90841,1.59522 -1.53169,3.13263 -4.07974,3.51899 -2.5480401,0.38635 -5.4168801,-0.16412 -5.1475501,-2.11987 0.25826,-1.87529 1.62394,-2.35301 4.1719801,-2.73937 2.54805,-0.38636 4.17038,-0.21373 5.05531,1.34025 z"
transform="translate(-5,-18) scale({{eye.width}} {{eye.height}})">
</path>
<path
class="lefteye"
d="m 5.5,0 c 0.90841,1.59522 -1.53169,3.13263 -4.07974,3.51899 -2.5480399,0.38635 -5.4168799,-0.16412 -5.1475499,-2.11987 0.25826,-1.87529 1.62394,-2.35301 4.17198,-2.73937 2.5480499,-0.38636 4.1703899,-0.21373 5.0553099,1.34025 z"
transform="translate(10,-18) scale({{eye.width}} {{eye.height}})">
</path>
<path
class="nose"
d="M -8.1253309,-1.8487371 C -3.740886,13.80696 10.948853,19.556588 10.177223,-2.5578614"
transform="translate(0, {{nose.drop}}) scale({{nose.width}} {{nose.height}})">
</path>
<svg>
</template>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment