Skip to content

Instantly share code, notes, and snippets.

@shawntan
Created June 1, 2023 01:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save shawntan/d883e5639d9c689b80cc6be6ed170a30 to your computer and use it in GitHub Desktop.
Save shawntan/d883e5639d9c689b80cc6be6ed170a30 to your computer and use it in GitHub Desktop.
reactjs d3 tree component
<div id="huffmandemo">
<textarea id="RawText">🍒🍊🍍🍑🍎🍌🍑🥝🍎🍎🍇🍊🍇🍌🍎🍎🍎🍊🍎🍉🍎🍉🍎🍑🍌🍇🍍🍊🍎🍊🍎🍇🍎🍌🍓🍓🍎🍊🍓🍉🍍🍓🍎🍎🍌🍌🍎🍇🍌🍎🍉🍉🍌🍊🍇🍍🍎🍓🍌🥝🍊🍇🍌🍊🍌🍎🍎🍊🍍🍎🍇🍊🍌🍒🍇🍌🍎🍎🍎🍎🍊🍌🍌🍌🍇🍎🍑🍎🍑🍊🍌🍊🍎🍌🍌🍒🍎🍌🍎🍊</textarea>
<div id="TreeChart"></div>
<button id="reset">reset tree</button>
<button id="step">Step</button>
<button id="stepAll">Build Tree</button>
<pre>
<code id="codebook" class="language-python" data-lang="python">
</code>
</pre>
</div>
function initTree() {
let d3Tree = {};
d3Tree.create = function(el, props, state) {
let svg = d3.select(el).append('svg')
.attr('width', props.width)
.attr('height', props.height);
this.width = props.width;
this.height = props.height;
var compWidth = el.getBoundingClientRect().width;
this.tree = d3.layout.tree().size([compWidth, 300]);
this.svg = d3.select(el).select('svg');
this.nodeCounts = 0;
this.update(el, state);
};
d3Tree.update = function(el, state, cb) {
console.log("update", el, state, cb);
return this._drawTree(el, state.data, cb);
};
d3Tree._drawTree = function(el, data, cb) {
let tree = this.tree;
let svg = this.svg;
let nodes = tree.nodes(data);
let nodeCounts = this.nodeCounts;
let g = svg.selectAll('g.node')
.data(nodes, (d) => d.id || (d.id = ++nodeCounts));
this.nodeCounts = nodeCounts;
let node = g.data(nodes);
let p = svg.selectAll('path.link');
let link = p.data(tree.links(nodes.slice(1)),
function(d) { return d.target.id; });
let linkLabel = svg.selectAll('text.link-label')
.data(tree.links(nodes.slice(1)), (d) => d.target.id);
// let nodeLabels = svg.selectAll('text.tree-node-label').data(node, )
g = node.enter().append('svg:g')
.attr('class', 'node')
.attr('transform', (d) => `translate(${d.x},${d.y-10})`);
g.append("svg:circle")
.attr('fill', '#AAAAAA')
.attr('stroke', '#000000')
.attr('stroke-width', 2)
.attr('class', 'treenode')
.attr("r", d => d.root?0:(15 * Math.sqrt(d.p)));
g.append('text')
.attr('dy', 30)
.attr('text-anchor', 'middle')
.attr('class', 'treesymbol')
.text((d) => d.symbol)
g.append('text')
.attr('text-anchor', 'middle')
.attr('class', 'treep')
.attr('dy', -16)
.text((d) => d.p?Number(d.p).toFixed(2):"" );
link.enter().insert("svg:path", "g")
.attr('class', 'link')
.attr('style', d => d.root?'display:none':'display:visible')
.attr('d', function(d) {
var o = {x: d.source.x, y: d.source.y};
return d3.svg.diagonal().projection(function(d) {
return [o.x, o.y - 10];
})(d);
});
linkLabel.enter().append('text')
.attr('class', 'link-label')
.attr('text-anchor', 'middle')
.attr('x', d => (d.source.x + d.target.x) / 2)
.attr('y', d => (d.source.y + d.target.y) / 2)
.text(d => d.edgeLabel || (d.edgeLabel = d.source.children && d.source.children[0] === d.target ? '0' : '1'));
var duration = 250;
var transitionCounts = node.length
let t = node.transition()
.duration(duration)
.attr('transform', (d) => `translate(${d.x},${d.y})`)
.each('end', function(d) {
transitionCounts--;
if (transitionCounts == 0 && cb) {
cb();
}
} )
link.transition()
.duration(duration)
.attr('d', (d) => `M${d.source.x},${d.source.y}L${d.target.x},${d.target.y}`);
linkLabel.transition()
.duration(duration)
.attr('text-anchor', 'middle')
.attr('x', d => (d.source.x + d.target.x) / 2)
.attr('y', d => (d.source.y + d.target.y) / 2)
.attr('dx', d => d.source.children && d.source.children[0] === d.target ? -10:10)
.text(d => d.source.children && d.source.children[0] === d.target ? '0' : '1');
link.exit().remove();
return t;
};
return d3Tree;
}
var state = {
data: {
"root": true,
"children": []
}
}
state.countSymbols = function() {
textbox = document.getElementById("RawText");
var freq = {};
var string = textbox.value;
var totalCount = 0;
for (const character of string) {
freq[character] = (freq[character]?freq[character]:0) + 1;
totalCount += 1;
}
dataNodes = [];
for (var k in freq) {
dataNodes.push({"symbol": k, "p": freq[k] / totalCount});
}
console.log(dataNodes);
this.data.children = dataNodes;
}
state.computeCodeBook = function() {
var currNode = this.data.children[0];
console.log(currNode);
var codebook = {};
function dfs(node, code) {
if (node.symbol) {
codebook[node.symbol] = code;
console.log(code);
}
else {
dfs(node.children[0], code + "0");
dfs(node.children[1], code + "1");
}
}
dfs(currNode, "");
document.getElementById("codebook").innerHTML = JSON.stringify(codebook, null, 2);
}
state.addNode = function(cb) {
//this.state.data.children = this.state.data.children||[];
//this.state.data.children.push({});
var children = this.data.children;
if (children.length == 1) return;
children.sort((a, b) => b.p - a.p);
var self = this;
function noop() {}
function merge() {
var child2 = children.pop();
var child1 = children.pop();
children.push({
"children": [child1, child2],
"p": child1.p + child2.p
});
state.d3Tree.update(this.el, self, cb);
if (children.length == 1) {
state.computeCodeBook();
}
};
console.log("before running, ", merge)
this.d3Tree.update(this.el, self, merge);
}
state.addAllNodes = function() {
var children = this.data.children;
function nextStep() {
state.addNode(function () {
if (children.length == 1) return;
else {
nextStep();
}
});
}
nextStep();
}
state.init = function() {
state.data = {
"root": true,
"children": []
};
this.d3Tree = initTree();
const el = document.getElementById("TreeChart");
this.el = el
this.el.innerHTML = '';
this.countSymbols();
this.d3Tree.create(this.el, {width: '100%', height: '400px'}, state);
}
state.init();
var resetBtn = document.getElementById("reset");
resetBtn.onclick = function() {
console.log("hello reset");
state.init();
};
var stepBtn = document.getElementById("step");
stepBtn.onclick = function() { state.addNode(); };
var stepBtn = document.getElementById("stepAll");
stepBtn.onclick = function() { state.addAllNodes(); };
<script src="https://npmcdn.com/react@15.3.0/dist/react.min.js"></script>
<script src="https://npmcdn.com/react-dom@15.3.0/dist/react-dom.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
#huffmandemo {
width: 100%;
}
.link {
fill: none;
stroke: black;
stroke-width: 1.5px;
}
g {
overflow: visible;
}
g.node {
background-color: gray;
height: 40px;
}
g.node circle {
background-color: #AAAAAA;
color: #AAAAAA;
}
text {
overflow: visible;
line-height: 5;
font-size: 2em;
font-family: monospace;
}
text.treep {
display: none;
}
.node:hover text.treep {
display: block;
}
text.treesymbol {
overflow: visible;
line-height: 5;
font-size: 2em;
font-family: monospace;
}
#RawText {
min-height: 50px;
width: 100%;
padding: 10px;
font-size: 18px;
line-height: 1.5;
border: solid 1px;
resize: none; /* disable resizing */
border-radius: 5px;
}
#TreeChart {
align-content: center;
width: 100%;
}
#TreeChart svg{
width: 100%;
display: block;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment