I was tidying up the folder structure (moving the JS to .js
file instead of inline, and putting it to its own folder) when this suddenly happens. My force directed graph suddenly is having this weird problem. All the nodes and links moved into the top left of the screen. It looks like this:
The Javascript console says Cannot create property 'vx' on string 'dki'
.
Oddly enough when I tried to paste the whole code in jsfiddle, it's working as usual. You can find the fiddle here: https://jsfiddle.net/k6pf1hfw/1/
Here is the JS code:
var nodes = [
{ id: "pusat", group: 0, label: "Pusat", level: 0 },
{ id: "dki", group: 1, label: "Prov. DKI", level: 1 },
{ id: "jaksel", group: 1, label: "Kota Jakarta Selatan", level: 2 },
{ id: "jakpus", group: 1, label: "Kota Jakarta Pusat", level: 2},
{ id: "jabar", group: 2, label: "Prov. Jawa Barat", level: 1 },
{ id: "sumedang", group: 2, label: "Kab. Sumedang", level: 2 },
{ id: "bekasi", group: 2, label: "Kota Bekasi", level: 2 },
{ id: "bandung", group: 2, label: "Kota Bandung", level: 2 },
{ id: "jatim", group: 3, label: "Prov. Jawa Timur", level: 1 },
{ id: "malang", group: 3, label: "Kota Malang", level: 2 },
{ id: "lamongan", group: 3, label: "Kota Lamongan", level: 2 },
{ id: "diy", group: 4, label: "Prov. DIY", level: 1 },
{ id: "sleman", group: 4, label: "Kab. Sleman", level: 2 },
{ id: "jogja", group: 4, label: "Kota Yogyakarta", level: 2 },
{ id: "bali", group: 5, label: "Prov. Bali", level: 1 },
{ id: "bali1", group: 5, label: "Kota Denpasar", level: 2 },
{ id: "bali2", group: 5, label: "Kab. Buleleng", level: 2 },
{ id: "ntt", group: 6, label: "Prov. NTT", level: 1 },
{ id: "ntt1", group: 6, label: "Kab. Alor", level: 2 },
{ id: "ntt2", group: 6, label: "Kab. Manggarai Timur", level: 2},
{ id: "ntb", group: 7, label: "Prov. NTB", level: 1 },
{ id: "kabima", group: 7, label: "Kab. Bima", level: 2 },
{ id: "kobima", group: 7, label: "Kota Bima", level: 2 },
{ id: "kaltara", group: 8, label: "Prov. Kaltara", level: 1 },
{ id: "kubar", group: 8, label: "Kab. Kutai Barat", level: 2 },
{ id: "kutim", group: 8, label: "Kab. Kutai Timur", level: 2 },
{ id: "kaltim", group: 9, label: "Prov. Kaltim", level: 1 },
{ id: "bpp", group: 9, label: "Kota Balikpapan", level: 2 },
{ id: "samarinda", group: 9, label: "Kota Samarinda", level: 2 },
{ id: "kalsel", group: 10, label: "Prov. Kalsel", level: 1 },
{ id: "banjar", group: 10, label: "Kota Banjarmasin", level: 2 },
{ id: "tapin", group: 10, label: "Kab. Tapin", level: 2 },
{ id: "kalbar", group: 11, label: "Prov. Kalbar", level: 1 },
{ id: "melawi", group: 11, label: "Kab. Melawi", level: 2 },
{ id: "sambas", group: 11, label: "Kab. Sambas", level: 2}
]
var links = [
// Pusat-Provinsi
{ source:"pusat", target:"dki", strength:.5, value:100000000000 },
{ source:"pusat", target:"jabar", strength:.5, value:30000000000},
{ source:"pusat", target:"jatim", strength:.5, value:100000000000},
{ source:"pusat", target:"diy", strength:.5, value:1000000000000},
{ source:"pusat", target:"bali", strength:.5, value:10000000000},
{ source:"pusat", target:"ntt", strength:.5, value:1000000000},
{ source:"pusat", target:"ntb", strength:.5, value:1000000000},
{ source:"pusat", target:"kaltim", strength:.5, value:5000000000000},
{ source:"pusat", target:"kaltara", strength:.5, value:5000000000000},
{ source:"pusat", target:"kalsel", strength:.5, value:10000000000000},
{ source:"pusat", target:"kalbar", strength:.5, value:1000000000},
// Provinsi-Kab/Kota
{ source:"dki", target:"jaksel", strength:.7, value:2000000000},
{ source:"dki", target:"jakpus", strength:.7, value:4000000000000},
{ source:"jabar", target:"sumedang", strength:.7, value:400000000000},
{ source:"jabar", target:"bekasi", strength:.7, value:40000000000},
{ source:"jabar", target:"bandung", strength:.7, value:40000000000},
{ source:"jatim", target:"malang", strength:.7, value:300000000000},
{ source:"jatim", target:"lamongan", strength:.7, value:100000000000},
{ source:"diy", target:"sleman", strength:.7, value:4500000000000},
{ source:"diy", target:"jogja", strength:.7, value:6700000000000},
{ source:"bali", target:"bali1", strength:.7, value:100000000000000},
{ source:"bali", target:"bali2", strength:.7, value:2400000000000},
{ source:"ntt", target:"ntt1", strength:.7, value:60000000000000},
{ source:"ntt", target:"ntt2", strength:.7, value:100000000000},
{ source:"ntb", target:"kabima", strength:.7, value:126000000000},
{ source:"ntb", target:"kobima", strength:.7, value:1000000000000},
{ source:"kaltara", target:"kubar", strength:.7, value:12420000000000},
{ source:"kaltara", target:"kutim", strength:.7, value:14400000000000},
{ source:"kaltim", target:"bpp", strength:.7, value:1470000000000},
{ source:"kaltim", target:"samarinda", strength:.7, value:1000000000000000},
{ source:"kalsel", target:"banjar", strength:.7, value:137000000000},
{ source:"kalsel", target:"tapin", strength:.7, value:5050000000000},
{ source:"kalbar", target:"melawi", strength:.7, value:2400000000000},
{ source:"kalbar", target:"sambas", strength:.7, value:5500000000000}
];
function getNeighbors(node) {
return links.reduce(function(neighbors, link) {
if (link.target.id === node.id) {
neighbors.push(link.source.id)
} else if (link.source.id === node.id) {
neighbors.push(link.target.id)
}
return neighbors
}, [node.id])
}
function isNeighborLink(node, link) {
return link.target.id === node.id || link.source.id === node.id
}
function getNodeColor(node, neighbors) {
// If is neighbor
if (Array.isArray(neighbors) && neighbors.indexOf(node.id) > -1) {
return 'rgba(251, 130, 30, 1)'
// return node.level === 1 ? '#9C4A9C' : 'rgba(251, 130, 30, 1)'
} else {
// Check the node level
if (node.level === 0) {
return '#E72148'
} else if (node.level === 1) {
return '#9C4A9C'
} else {
return '#D8ABD8'
}
}
//return node.level === 0 ? '#91007B' : '#D8ABD8'
}
function getLinkColor(node, link) {
return isNeighborLink(node, link) ? 'rgba(251, 130, 30, 1)' : 'rgba(251, 130, 30, 0.25)'
}
function getTextColor(node, neighbors) {
return Array.isArray(neighbors) && neighbors.indexOf(node.id) > -1 ? '#333' : '#bbb'
}
var width = window.innerWidth
var height = window.innerHeight
var svg = d3.select('svg')
// svg.attr('width', width).attr('height', height)
svg.attr("width", '100%')
.attr("height", '500px')
.attr('viewBox', '250 0 800 600')
//.attr('viewBox','0 0 '+Math.min(width,height)+' '+Math.min(width,height))
.attr('preserveAspectRatio','xMidYMid')
.append("g")
.attr("transform", "translate(" + Math.min(width,height) / 2 + "," + Math.min(width,height) / 2 + ")");
//add zoom capabilities
var zoom_handler = d3.zoom()
.scaleExtent([1 / 2, 8])
.on("zoom", zoom_actions);
zoom_handler(svg);
function zoom_actions(){
g.attr("transform", d3.event.transform)
}
function button_zoom_in(){
zoom_handler.scaleBy(svg, 2);
}
function button_zoom_out(){
zoom_handler.scaleBy(svg, 0.5);
}
// simulation setup with all forces
var linkForce = d3
.forceLink()
.id(function (link) { return link.id })
// Alternative: using the distance from the data "strength"
//.distance(50).strength(function (link) { return link.strength })
// If don't want to use this, use default here:
.distance(50).strength(.7)
var simulation = d3
.forceSimulation()
.force('link', linkForce)
.force('charge', d3.forceManyBody().strength(-1500))
.force('radial', d3.forceRadial(function(d) {
return d.level * 50
}, width / 2, height / 2))
.force('center', d3.forceCenter(width / 2, height / 2))
var dragDrop = d3.drag().on('start', function(node) {
node.fx = node.x
node.fy = node.y
}).on('drag', function(node) {
simulation.alphaTarget(0.7).restart()
node.fx = d3.event.x
node.fy = d3.event.y
}).on('end', function(node) {
if (!d3.event.active) {
simulation.alphaTarget(0)
}
node.fx = null
node.fy = null
})
function selectNode(selectedNode) {
var neighbors = getNeighbors(selectedNode)
// we modify the styles to highlight selected nodes
nodeElements.attr('fill', function(node) {
return getNodeColor(node, neighbors)
})
textElements.attr('fill', function(node) {
return getTextColor(node, neighbors)
})
linkElements.attr('stroke', function(link) {
return getLinkColor(selectedNode, link)
})
}
// Enables zooming
var g = svg.append("g")
.attr("class", "everything");
// Enables zooming end
// Create circling orbit
var circles = g.selectAll(null) // use g.selectAll instead of svg.selectAll to enable zoom
.data([200,350]) // sets the circle radius
.enter()
.append("circle")
.attr("cx", width/2)
.attr("cy", height/2)
.attr("r", d=>d)
.style("fill", "none")
.style("stroke", "#ddd");
var linkElements = g.append("g") // use g.append instead of svg.append to enable zoom
.attr("class", "links")
.selectAll("line")
.data(links)
.enter().append("line")
.attr("id",function(d,i) { return "linkId_" + i; })
.attr("stroke-width", function(link) {
var linkWidthNormalize = link.value / 1000000000; // in milyar
// under assumption that smallest 10 milyar, largest > 40 triliun
if (linkWidthNormalize >= 40001) {
return 12;
} else if (linkWidthNormalize >= 20001 && linkWidthNormalize <= 40000) {
return 10;
} else if (linkWidthNormalize >= 9001 && linkWidthNormalize <= 20000) {
return 8;
} else if (linkWidthNormalize >= 4001 && linkWidthNormalize <= 9000) {
return 6;
} else if (linkWidthNormalize >= 10 && linkWidthNormalize <= 4000) {
return 4;
} else {
return 2;
}
// return linkWidthNormalize;
})
.attr("stroke", "rgba(251, 130, 30, 0.5)")
var nodeElements = g.append("g") // use g.append instead of svg.append to enable zoom
.attr("class", "nodes")
.selectAll("circle")
.data(nodes)
.enter().append("circle")
.attr("r", 12)
.attr("fill", getNodeColor)
.attr("stroke", "#fff")
.attr('stroke-width', 2)
.call(dragDrop)
//.on('click', selectNode) // alternative
.on('mouseover', selectNode)
var textElements = g.append("g") // use g.append instead of svg.append to enable zoom
.attr("class", "texts")
.selectAll("text")
.data(nodes)
.enter().append("text")
.text(function(node) {
return node.label
})
.attr("font-size", 10)
.attr("font-family", "sans-serif")
.attr("text-anchor", "middle")
.attr("fill", "#333")
.attr("style", "font-weight:bold; -webkit-text-stroke: 1px #fff; text-shadow: 3px 3px 0 #fff, -1px -1px 0 #fff, 1px -1px 0 #fff, -1px 1px 0 #fff, 1px 1px 0 #fff")
.attr("dx", 0)
.attr("dy", 20)
simulation.nodes(nodes).on('tick', () => {
nodeElements
.attr('cx', function(node) {
return node.x
})
.attr('cy', function(node) {
return node.y
})
textElements
.attr('x', function(node) {
return node.x
})
.attr('y', function(node) {
return node.y
})
linkElements
.attr('x1', function(link) {
return link.source.x
})
.attr('y1', function(link) {
return link.source.y
})
.attr('x2', function(link) {
return link.target.x
})
.attr('y2', function(link) {
return link.target.y
})
})
simulation.force("link").links(links)
I can't find anything wrong with the code. I have tried reverting my action (moving back the files to its initial place) but nothing changes. It's still like that.
What's wrong with this? This is getting me crazy.
I finally found the culprit.
Hopefully this serves a warning to anyone as careless as I am: d3.js cannot read iso-8859-1
encoding. My HTML file was using iso-8859-1
encoding, meanwhile JSFiddle is using utf-8
. That's why it works in JSFiddle but doesn't in my end.
So changing this:
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
to this:
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
fixed the problem.