Skip to content

Instantly share code, notes, and snippets.

@chrisbrich
Created August 19, 2012 03:37
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save chrisbrich/3391642 to your computer and use it in GitHub Desktop.
Save chrisbrich/3391642 to your computer and use it in GitHub Desktop.
Reusable Component: MathJax label
<!DOCTYPE html>
<html>
<head>
<title>Line Chart</title>
<link href="MathJax.css" rel="stylesheet" type="text/css">
</head>
<body>
<script type="text/javascript" src="http://d3js.org/d3.v2.js"></script>
<script type="text/javascript" src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_SVG"></script>
<script type="text/javascript" src="mathjaxlabel.js"></script>
<script type="text/javascript">
var margin = {"top": 100, "right": 100, "bottom": 100, "left": 100},
height = 300,
width = 300,
title = ["\\(Title\\)","\\(sin(\\phi)\\)"],
ylabel = ["\\(Y Label\\)","\\(Random LaTex ^{4}_{2}He\\)"],
xlabel = ["\\(This is not getting positioned correctly\\)","\\(Should be at the bottom of the margin\\)"];
var gEnter = d3.select("body")
.append("svg").attr("width",500).attr("height",500)
.append("g").attr("class","container");
gEnter.append("rect").attr("width",300).attr("height",300).attr("transform","translate("+margin.left+","+margin.top+")");
// X Label
var xlbl = mathJaxLabel().width(width).height(margin.bottom);
gEnter.append("g")
.attr("class","xlabel")
.datum(xlabel)
.attr("transform", "translate("+margin.left+","+(margin.top + 300) +")");
d3.selectAll("g.xlabel").call(xlbl);
// Y Label
var ylbl = mathJaxLabel().width(height).height(margin.left);
gEnter.append("g")
.attr("transform","rotate(-90)")
.attr("class","ylabel")
.datum(ylabel)
.attr("transform", "translate(0,"+(margin.top + 300)+")rotate(-90)");
d3.selectAll("g.ylabel").call(ylbl);
// Title
var ttl = mathJaxLabel().width(width).height(margin.bottom);
gEnter.append("g")
.attr("class","title")
.datum(title)
.attr("transform", "translate(100,0)");
d3.selectAll("g.title").call(ttl);
</script>
</body>
</html>
rect {
fill:yellow
}
.xlabel div.holder{
position: relative;
height: 100%;
width: 100%;
}
.xlabel div.lines{
position: absolute;
bottom: 0;
width: 100%;
}
function mathJaxLabel(){
//Adds a foreignObject to store mathJax Text, assumes data is a string formatted for mathJax
// Refer to this for mathJax rerendering voodoo: view-source:http://cdn.mathjax.org/mathjax/latest/test/sample-dynamic.html
var height,
width;
function label(selection){
selection.each(function(data) {
var container = d3.select(this),
QUEUE = MathJax.Hub.queue;
var fo = container.selectAll(".label").data(function(d){return [d]}),
foEnter = fo.enter().append("foreignObject")
.attr("class","label")
.append("xhtml:body")
.attr("xmlns","http://www.w3.org/1999/xhtml")
.append("xhtml:div")
.attr("class","holder")
.append("xhtml:div")
.attr("class","lines")
forUpdate = d3.transition(fo).attr("height",height).attr("width",width),
bUpdate = d3.transition(fo.selectAll("body")).style("height",height+"px").style("width",width+"px"),
foExit = d3.transition(fo.exit()).remove();
function toggleDisplay(selection){
selection.each(function() {
var el = d3.select(this),
state = el.style("display")
el.style("display",function(){
return (state == "block")?"none":"block";})})};
function updateMath(item,i){
//Escape whitespace so that user doesn't have to
var newtext = item.select(".input").property("value").split(" ").join("\\ "),
math = MathJax.Hub.getAllJax(item.selectAll("div.div").node())[0],
data = container.datum();
data[0][i] = "\\("+newtext+"\\)";
QUEUE.Push(["Text",math,newtext],
function(){item.selectAll(".div,.input").call(toggleDisplay)});
container.datum(data);}
//each line
var nl = container.selectAll("body div.lines").selectAll("div.newline").data(function(d){return d}),
nlEnter = nl.enter().append("xhtml:div")
.attr("class","newline")
.attr("align","center");
nlExit = nl.exit().remove();
//mJax holder
var mjax = nlEnter.append("xhtml:div")
.attr("class","div")
.attr("align","center")
.style("display","none")
.html(function(d){return d.split(" ").join("\\ ")})
;
//Input
var inpt = nlEnter.append("xhtml:input")
.attr("class","input")
.style("display","none")
.property("value",function(d){return d.slice(-(d.length -2),(d.length-2))});
//Display math elements once initialized.
QUEUE.Push(function () {
math = MathJax.Hub.getAllJax(container.selectAll("div.div")[0][0])[0];
container.selectAll("div.div").style("display","block");
});
//Behaviours
container.selectAll("div.newline")
.on("dblclick",function(d,i){
var el = d3.select(this);
(el.select(".input").style("display") !== "none")?updateMath(el,i):el.selectAll(".div,.input").call(toggleDisplay)})
.each(function(d,i){
var el = d3.select(this),
id = i;
el.selectAll(".input")
.on("change",function(){updateMath(el,id)})});
});
};
label.width = function(_) {
if (!arguments.length) return width;
width = _;
return label;
};
label.height = function(_) {
if (!arguments.length) return height;
height = _;
return label;
};
return label;
}
@andycasey
Copy link

Hey @chrisbrich,

First: Thanks for mathJaxLabel(). Do you mind if I use it for my (scientific) plotting?

I cross-posted the text below to the Google groups discussion but I thought I'd leave it here too in case someone comes across this here. The x-label positioning bug annoyed me! I thought it was working perfectly with your CSS hack, but then as soon as I zoomed in/out and resized the graph, the x-label was out of position.

To fix it, remove the .xlabel CSS styling and replace .attr("transform", "translate("+margin.left+","+(margin.top + 300) +")"); with .attr("transform", "translate("+margin.left+","+(margin.top + 300) +")rotate(0)"); in index.html.

That should fix it up. It appears without the rotate(0), none of the transform occurs.

Cheers,
Andy

@aflaxman
Copy link

Cool stuff. Here is the link to see it in action: http://bl.ocks.org/chrisbrich/3391642

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment