Created
June 1, 2023 01:28
-
-
Save shawntan/d883e5639d9c689b80cc6be6ed170a30 to your computer and use it in GitHub Desktop.
reactjs d3 tree component
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script src="https://npmcdn.com/[email protected]/dist/react.min.js"></script> | |
<script src="https://npmcdn.com/[email protected]/dist/react-dom.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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