Skip to content

Instantly share code, notes, and snippets.

@ArvinH
Last active Aug 20, 2016
Embed
What would you like to do?
Pokemon WeaknessesGraph

(Shamelessly taken here)

Come up with this idea, but found @filipekiss has already made a great version two years ago, I upgrade to D3v4 layout (with v3 linear radial)

same rule with orginal version:

click a type name and it will show you that type weaknesses. Clicking a second type will make the graph behave for dual types.

[Yellow line] Takes Double Damage [Blue line] Takes Half Damage [Pink line] Is Immune

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title></title>
<link rel="stylesheet" href="typechart.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.16/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.2.2/d3.min.js"></script>
</head>
<body>
<div id="typeChart">
<div id="graph"></div>
</div>
<button class="button reset"onclick="reset()">reset</button>
<script>
// 定義圖形的基本設定值
var diameter = 750,
radius = diameter / 2,
innerRadius = radius - 120;
// Create cluster layout
// give [360, innerRadius] to size
// it will form a radial cluster
var cluster = d3.cluster()
.size([360, innerRadius]);
var line = d3.svg.line.radial()
.interpolate("bundle")
.tension(0.75)
.radius(function(d) {
return d.y;
})
.angle(function(d) {
return d.x / 180 * Math.PI;
});
window.colorPath = function(d, l, type) {
var type = type || 'strong';
if (type == 'strong') {
if (l.target === d) return l.source.target = true;
}
if (type == 'weak') {
for (type in d) {
if(type !== "size") {
if (l.target === d[type]) return l.source.target = true;
}
}
}
}
</script>
<script src="weaknesses-graph.js"></script>
</body>
</html>
@import url(http://fonts.googleapis.com/css?family=Averia+Sans+Libre|Open+Sans);
body, svg {
background: #000;
font-familye: 'Open Sans';
}
.node {
font: 300 16px 'Open Sans', "Helvetica Neue", Helvetica, Arial, sans-serif;
fill: #949494;
opacity: 0.6
}
.node:hover, .node--active {
fill: rgba(255, 255, 255, 1);
}
.immune, .weak, .strong {
stroke: #f00;
stroke-opacity: 0.1;
fill: none;
pointer-events: none;
z-index: -100;
stroke-width: 1px;
}
.node:hover,
.node--source,
.node--target,
.node--active {
font-weight: 700;
opacity: 1;
}
.does-not-affect,
.not-very-effective-against,
.super-effective-against,
.is-weak-against,
.resists-against,
.is-immune {
stroke-width: 2px;
stroke-opacity: 1;
z-index: 99999;
}
.legend {
display: inline-block;
width: 30px;
border: 2px solid rgb(255, 255, 255);
border-radius: 2px;
}
.does-not-affect {
stroke: rgba(250, 0, 235, 1);
border-color: rgba(250, 0, 235, 1);
}
.not-very-effective-against {
stroke: rgba(0, 193, 248, 1);
border-color: rgba(0, 193, 248, 1);
}
.super-effective-against {
stroke: rgba(255, 204, 0, 1);
border-color: rgba(255, 204, 0, 1);
}
.is-weak-against,
.resists-against,
.is-immune,
.double-strengths-node,
.double-weaknesses-node,
.legend-dash {
border-radius: 0;
stroke-dasharray: 5 0;
}
.is-immune {
stroke: rgba(250, 0, 235, 1);
border-color: rgba(250, 0, 235, 1);
}
.resists-against {
stroke: rgba(0, 193, 248, 1);
border-color: rgba(0, 193, 248, 1);
}
.is-weak-against {
stroke: rgba(255, 204, 0, 1);
border-color: rgba(255, 204, 0, 1);
}
#typechart {
margin: auto;
text-align: center;
width: 1100px;
}
#details {
width: 350px;
float: left;
color: #f7f7f7;
font-family: 'Open Sans';
text-align: left;
}
#details h1, #details h2 {
text-align: left;
font-family: 'Open Sans';
color: #cecece;
}
a, a:visited, a:active {
color: rgba(0, 193, 248, 1);
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
#graph {
width: 750px;
float: left;
}
#is-also-effective-against {
display: none;
}
.immune-node {
fill: rgba(250, 0, 235, 1) !important;
}
.weaknesses-node {
fill: rgba(0, 193, 248, 1);
}
.double-weaknesses-node {
fill: rgba(0, 255, 44, 1);
border-color: rgba(0, 255, 44, 1);
}
.strengths-node {
fill: rgba(255, 204, 0, 1);
}
.double-strengths-node {
fill: rgba(255, 72, 0, 1);
border-color: rgba(255, 72, 0, 1);
}
.button.reset {
height: 30px;
width: 100px;
margin: 20px;
background: white
}
[{"name":"Normal","immunes":["Ghost"],"weaknesses":["Rock","Steel"],"strengths":[]},
{"name":"Fire","immunes":[],"weaknesses":["Fire","Water","Rock","Dragon"],"strengths":["Grass","Ice","Bug","Steel"]},
{"name":"Water","immunes":[],"weaknesses":["Water","Grass","Dragon"],"strengths":["Fire","Ground","Rock"]},
{"name":"Electric","immunes":["Ground"],"weaknesses":["Electric","Grass","Dragon"],"strengths":["Water","Flying"]},
{"name":"Grass","immunes":[],"weaknesses":["Fire","Grass","Poison","Flying","Bug","Dragon","Steel"],"strengths":["Water","Ground","Rock"]},
{"name":"Ice","immunes":[],"weaknesses":["Fire","Water","Ice","Steel"],"strengths":["Grass","Ground","Flying","Dragon"]},
{"name":"Fighting","immunes":["Ghost"],"weaknesses":["Poison","Flying","Psychic","Bug","Fairy"],"strengths":["Normal","Ice","Rock","Dark","Steel"]},
{"name":"Poison","immunes":["Steel"],"weaknesses":["Poison","Ground","Rock","Ghost"],"strengths":["Grass","Fairy"]},
{"name":"Ground","immunes":["Flying"],"weaknesses":["Grass","Bug"],"strengths":["Fire","Electric","Poison","Rock","Steel"]},
{"name":"Flying","immunes":[],"weaknesses":["Electric","Rock","Steel"],"strengths":["Grass","Fighting","Bug"]},
{"name":"Psychic","immunes":["Dark"],"weaknesses":["Psychic","Steel"],"strengths":["Fighting","Poison"]},
{"name":"Bug","immunes":[],"weaknesses":["Fire","Fighting","Poison","Flying","Ghost","Steel","Fairy"],"strengths":["Grass","Psychic","Dark"]},
{"name":"Rock","immunes":[],"weaknesses":["Fighting","Ground","Steel"],"strengths":["Fire","Ice","Flying","Bug"]},
{"name":"Ghost","immunes":["Normal"],"weaknesses":["Dark"],"strengths":["Psychic","Ghost"]},
{"name":"Dragon","immunes":["Fairy"],"weaknesses":["Steel"],"strengths":["Dragon"]},
{"name":"Dark","immunes":[],"weaknesses":["Fighting","Dark","Fairy"],"strengths":["Psychic","Ghost"]},
{"name":"Steel","immunes":[],"weaknesses":["Fire","Water","Electric","Steel"],"strengths":["Ice","Rock","Fairy"]},
{"name":"Fairy","immunes":[],"weaknesses":["Fire","Poison","Steel"],"strengths":["Fighting","Dragon","Dark"]}]
var svg = d3.select("#typeChart > #graph").append("svg")
.attr("width", diameter)
.attr("height", diameter)
.append("g")
.attr("transform", "translate(" + radius + "," + radius + ")");
var immune = svg.append("g").selectAll(".immune"),
weak = svg.append("g").selectAll(".weak"),
strong = svg.append("g").selectAll(".strong"),
node = svg.append("g").selectAll(".node");
d3.json("types.json", function(error, classes) {
var nodes = cluster(d3.hierarchy(packageHierarchy(classes))).children;
var immunes = typeImmune(nodes);
var strengths = typeStrong(nodes);
var weaknesses = typeWeak(nodes);
window.immune = immune
.data(immunes.map(function(node){
return node.source.path(node.target);
}))
.enter().append("path")
.each(function(d) {
d.source = d[0], d.target = d[d.length - 1];
})
.attr("class", "immune")
.attr("d", line);
window.weak = weak
.data(weaknesses.map(function(node){
return node.source.path(node.target);
}))
.enter().append("path")
.each(function(d) {
d.source = d[0], d.target = d[d.length - 1];
})
.attr("class", "weak")
.attr("d", line);
window.strong = strong
.data(strengths.map(function(node){
return node.source.path(node.target);
}))
.enter().append("path")
.each(function(d) {
d.source = d[0], d.target = d[d.length - 1];
})
.attr("class", "strong")
.attr("d", line)
.attr("data-is-effective-against-self", function(d) {
return (d[0] === d[d.length - 1]);
});
window.node = node
.data(nodes.filter(function(n) {
return !n.children;
}))
.enter().append("text")
.attr("class", function(n) {
return 'node ' + n.data.name.toLowerCase();
})
.attr("dx", function(d) {
return d.x < 180 ? 8 : -8;
})
.attr("dy", ".31em")
.attr("transform", function(d) {
return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")" + (d.x < 180 ? "" : "rotate(180)");
})
.style("text-anchor", function(d) {
return d.x < 180 ? "start" : "end";
})
.text(function(d) {
return d.data.key;
})
.on("click", activate);
});
function reset() {
window.immune
.classed("is-immune", false);
window.weak
.classed("resists-against", false);
window.strong
.classed("is-weak-against", false);
window.node
.classed("node--target", false)
.classed("immune-node", false)
.classed("weaknesses-node", false)
.classed("strengths-node", false)
.classed("double-weaknesses-node", false)
.classed("double-strengths-node", false)
.classed("node--active", false);
window.dualType = {
size: function() {
var size = 0,
key;
for (key in this) {
if (this.hasOwnProperty(key)) size++;
}
return size;
}
};
window.dualTypeIdx = [];
return false;
}
function activate(d) {
if (window.dualType.size() > 2) {
window.dualType = {
size: function() {
var size = 0,
key;
for (key in this) {
if (this.hasOwnProperty(key)) size++;
}
return size;
}
};
window.dualTypeIdx = [];
window.node.each(function(n) {
n.target = n.source = false;
}).attr("class", function(n) {
return 'node ' + n.data.name.toLowerCase();
});
}
if (window.dualType[d.data.name] !== undefined) {
delete window.dualType[d.data.name];
}
window.dualType[d.data.name] = d;
window.dualTypeIdx.push(d);
window.node
.each(function(n) {
n.target = n.source = false;
});
window.immune
.classed("is-immune", function(l) {
return window.colorPath(window.dualType, l, 'weak');
})
.filter(function(l) {
return l.target === d;
})
.each(function(d) {
this.parentNode.appendChild(this);
});
window.weak
.classed("resists-against", function(l) {
return window.colorPath(window.dualType, l, 'weak');
})
.filter(function(l) {
return l.target === d;
})
.each(function() {
this.parentNode.appendChild(this);
});
window.strong
.classed("is-weak-against", function(l) {
return window.colorPath(window.dualType, l, 'weak');
})
.filter(function(l) {
return l.target === d;
})
.each(function() {
this.parentNode.appendChild(this);
});
window.node
.classed("node--active", function(target) {
return (target === d) || this.classList.contains("node--active");
})
.classed("node--target", function(n) {
return n.target;
})
.classed("immune-node", function(target, l) {
return (this.classList.contains('immune-node') || target.data.immunes.indexOf(d.data.name) != -1);
})
.classed("weaknesses-node", function(target) {
return (this.classList.contains('weaknesses-node') || target.data.weaknesses.indexOf(d.data.name) != -1);
})
.classed("strengths-node", function(target) {
return (target.data.strengths.indexOf(d.data.name) != -1);
})
.classed("double-strengths-node", function(target) {
return ( !! window.dualTypeIdx[0] && !! window.dualTypeIdx[1] && target.data.strengths.indexOf(window.dualTypeIdx[0].name) !== -1 && target.strengths.indexOf(window.dualTypeIdx[1].name) !== -1);
})
.classed("double-weaknesses-node", function(target) {
return ( !! window.dualTypeIdx[0] && !! window.dualTypeIdx[1] && target.data.weaknesses.indexOf(window.dualTypeIdx[0].name) !== -1 && target.weaknesses.indexOf(window.dualTypeIdx[1].name) !== -1);
});
}
d3.select(self.frameElement).style("height", diameter + "px");
// Lazily construct the package hierarchy from class names.
function packageHierarchy(classes) {
var map = {};
function find(name, data) {
var node = map[name],
i;
if (!node) {
node = map[name] = data || {
name: name,
children: []
};
if (name.length) {
node.parent = find(name.substring(0, i = name.lastIndexOf(".")));
node.parent.children.push(node);
node.key = name.substring(i + 1);
}
}
return node;
}
classes.forEach(function(d) {
find(d.name, d);
});
return map[""];
}
//Make the immune links
function typeImmune(nodes) {
var map = {},
immunes = [];
nodes.forEach(function(d) {
map[d.data.name] = d;
});
nodes.forEach(function(d) {
if (d.data.immunes) d.data.immunes.forEach(function(i) {
immunes.push({
source: map[d.data.name],
target: map[i]
});
});
});
return immunes;
}
//Make the immune links
function typeWeak(nodes) {
var map = {},
weaknesses = [];
nodes.forEach(function(d) {
map[d.data.name] = d;
});
nodes.forEach(function(d) {
if (d.data.weaknesses) d.data.weaknesses.forEach(function(i) {
weaknesses.push({
source: map[d.data.name],
target: map[i]
});
});
});
return weaknesses;
}
function typeStrong(nodes) {
var map = {},
strengths = [];
nodes.forEach(function(d) {
map[d.data.name] = d;
});
nodes.forEach(function(d) {
if (d.data.strengths) d.data.strengths.forEach(function(i) {
strengths.push({
source: map[d.data.name],
target: map[i]
});
});
});
return strengths;
}
reset();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment