Add colors to dimple.js bar chart based on value and add goal line - d3.js

I have 3 queries.
1) How to add custom colors in a bar chart? In the attached image for example, I want to add different colors for Metric Pink and Blue.
2) I want to show a horizontal line as goal/target value for e.g. at 3.0k on y-axis.
3) The chart should render the x-axis values in order of Jan,Feb,Mar,Apr which is not happening now. Please advise.
<div class="row">
<div id="test" class="column">
<h2> Pink and Blue Distribution</h2>
<script type="text/javascript">
var svg = dimple.newSvg("#test", 590,400);
var json = JSON.parse('${rlog}');
var data = [
{"Month": "Jan", "Metric": "Pink", "Value": 200},
{"Month": "Feb", "Metric": "Pink", "Value": 320},
{"Month": "Mar", "Metric": "Pink", "Value": 200},
{"Month": "Apr", "Metric": "Pink", "Value": 320},
{"Month": "Jan", "Metric": "Blue", "Value": 1000},
{"Month": "Feb", "Metric": "Blue", "Value": 2500},
{"Month": "Mar", "Metric": "Blue", "Value": 1500},
{"Month": "Apr", "Metric": "Blue", "Value": 3001}
];
var chart = new dimple.chart(svg, data);
chart.setBounds(80, 30, 480,330);
var x = chart.addCategoryAxis("x",["Month","Metric"]);
var y1 = chart.addMeasureAxis("y", "Value");
var bars = chart.addSeries("Metric", dimple.plot.bar, [x, y1]);
bars.barGap = 0.5;
chart.addLegend(65, 10, 510, 20, "right");
chart.draw();
</script>
</div>
</div>

Try this out
http://jsfiddle.net/cmubick/n5x0gkdy/
1) custom colors:
chart.assignColor("Pink","pink");
chart.assignColor("Blue","blue");
2) horizontal line at Value = 3000:
var line = chart.addSeries("Line", dimple.plot.line);
line.data = [
{ "Line" : "line", "Month": "Jan", "Metric": "Blue", "Value" : 3000 },
{ "Line" : "line", "Month": "Apr", "Metric": "Blue", "Value" : 3000 }
];
3) sort x-axis by month
x.addOrderRule(["Jan", "Feb", "Mar", "Apr"]);

For 2) you could use something like this below for adding horizontal line(s). I use the following code for generating rules (horizontal grid line)
var rule = svg.selectAll("g.rule")
.data(y.ticks(5))
.enter().append("svg:g")
.attr("class", "rule")
.attr("transform", function(d) { return "translate(0," + y(d) + ")"; });
rule.append("svg:line")
.attr("x2", width)
.style("stroke", function(d) { return '#f00' }) //return my own color
.style("stroke-width", 0.5)
.style("stroke-opacity", function(d) { return 1; }); //you could return 0 for ticks where you do not want rules, or some other condition

Related

Which concepts and techniques do I need to achieve this kind of graph

I want to draw a dynamic visual of how certain functional blocks interact with each other. I'm having difficulty defining what kind of graph I need, and what (if any) d3 layout function is best suited to start with. I'm not even sure my example falls within the definition of a graph.
The general idea is to visualize a group of functions with their inputs and outputs. It starts with a set of inputs, and ends with a set of outputs. In between there are several functions who each take inputs and generate 1 or more outputs. Each output can serve as input for one or more functions. So each edge/line represents an output being transferred to a function to serve as input (and is uni-directional)
I'm not looking for answer in code, but rather an insight on which concepts I need to start with. The image probably has issues and can't be achieved literally, but I can't point out what exactly.
const nodes = [
{
id: 1,
title: 'Function A',
x: 100,
y: 25,
points: [
{
id: 11,
dx: 50,
dy: 0
},
{
id: 12,
dx: 0,
dy: 20
}
]
},
{
id: 2,
title: 'Function B',
x: 300,
y: 100,
points: [
{
id: 21,
dx: -50,
dy: 0
},
{
id: 22,
dx: 0,
dy: 20
}
]
},
{
id: 3,
title: 'Function C',
x: 170,
y: 160,
points: [
{
id: 31,
dx: 0,
dy: -20
},
{
id: 32,
dx: 50,
dy: 0
}
]
}
];
const links = [
{source: 11, target:21},
{source: 12, target:31},
{source: 22, target:32}
];
var selectedNode = null;
const renderNodes = svg => {
const allNodes = svg.selectAll('.node').data(nodes, node => node.id);
const addedNodes = allNodes.enter()
.append('g')
.style('cursor', 'pointer')
.attr('transform', d => `translate(${d.x},${d.y})`)
.on('click', d => {
selectedNode = d.id;
renderAll();
});
addedNodes.append('rect')
.attr('width', 100)
.attr('height', 40)
.attr('x', -50)
.attr('y', -20)
.attr('rx', 5);
addedNodes.append('text')
.text(d => d.title)
.style('fill', 'white')
.attr('y', 6)
.attr('text-anchor', 'middle');
addedNodes.merge(allNodes)
.select('rect')
.style('fill', d => (d.id === selectedNode) ? 'blue' : 'black');
allNodes.exit().remove();
};
const renderConnectionPoints = (svg, points) => {
const allPoints = svg.selectAll('.point').data(points, point => point.id);
const addedPoints = allPoints.enter()
.append('g')
.style('cursor', 'pointer')
.attr('transform', d => `translate(${d.x},${d.y})`);
addedPoints.append('circle')
.attr('r', 4)
.style('fill', 'white');
addedPoints.merge(allPoints)
.select('circle')
.style('stroke', d => (d.parentId === selectedNode) ? 'blue' : 'black');
allPoints.exit().remove();
}
const renderLinks = (svg, _links) => {
const linkPath = d => `M ${d.source.x}, ${d.source.y}
C ${(d.source.x + d.target.x) / 2}, ${d.source.y}
${(d.source.x + d.target.x) / 2}, ${d.target.y}
${d.target.x}, ${d.target.y}`;
const allLinks = svg.selectAll('.link').data(_links, link => link.id);
const addedLinks = allLinks.enter()
.append('path')
.style('fill', 'none')
.attr('d', linkPath);
addedLinks.merge(allLinks)
.style('stroke', d => (d.source.parentId === selectedNode ||
d.target.parentId === selectedNode) ? 'blue' : 'lightgray');
allLinks.exit().remove();
}
const getPoints = () => nodes.reduce((all, node) => {
node.points.forEach(point => all.push({
id: point.id, x: node.x + point.dx, y: node.y + point.dy, parentId: node.id}));
return all;
}, []);
const getLinks = points => links.map(link => {
const source = points.find(point => point.id === link.source);
const target = points.find(point => point.id === link.target);
return {source, target};
});
const renderAll = () => {
const svg = d3.select('svg');
const points = getPoints();
const _links = getLinks(points);
renderNodes(svg);
renderLinks(svg, _links);
renderConnectionPoints(svg, points);
}
renderAll();
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width='400' height='180'>
</svg>
Hi,
Take a look at the snippet.
The data model is very simple: nodes, their connection points and links
Just provide your data and use the snippet's code to render it.
Good luck and feel free to ask any question :)
sorry by my bad english, I speak spanish...
I also look for something similar, researching I have achieved this, and I am missing:
Transform nodes circles into rectangles
Add text labels to each node and each link
Once everything is organized initially, they remain in a fixed location
Capture the click on each node and / or link to trigger another application
I leave the code below, observe especially the definition of nodes_data and links_data, and functions to define size and color:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.links line {
stroke: #999;
stroke-opacity: 0.6;
}
.nodes circle {
stroke: black ;
stroke-width: 0px;
}
</style>
<svg width="1000" height="600"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
//create somewhere to put the force directed graph
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
//var radius = 15;
var nodes_data = [
{"name": "PO1", "entity": "PO"},
{"name": "PO2", "entity": "PO"},
{"name": "PO3", "entity": "PO"},
{"name": "PO4", "entity": "PO"},
{"name": "PO5", "entity": "PO"},
{"name": "PO6", "entity": "PO"},
{"name": "PO7", "entity": "PO"},
{"name": "PY1", "entity": "PY"},
{"name": "PY2", "entity": "PY"},
{"name": "L1", "entity": "X"},
{"name": "L2", "entity": "X"},
{"name": "L3", "entity": "X"},
{"name": "L4", "entity": "X"},
{"name": "TK1", "entity": "TK"},
{"name": "TK2", "entity": "TK"},
{"name": "PIL1", "entity": "TK"},
{"name": "BBA1", "entity": "BA"},
{"name": "BBA2", "entity": "BA"},
{"name": "ULAC1", "entity": "UL"},
{"name": "VtaYPF", "entity": "VTA"}
]
//Sample links data
//type: A for Ally, E for Enemy
var links_data = [
{"source": "PO1", "target": "L1", "type":"A" },
{"source": "PO2", "target": "L1", "type":"A" },
{"source": "PO3", "target": "L1", "type":"A"},
{"source": "PO4", "target": "L2", "type":"A"},
{"source": "PO5", "target": "L2", "type":"A"},
{"source": "PO6", "target": "L3", "type":"A"},
{"source": "PO7", "target": "L3", "type":"A"},
{"source": "L1", "target": "L3", "type":"A"},
{"source": "L2", "target": "L3", "type":"A"},
{"source": "L3", "target": "TK1", "type":"A"},
{"source": "L3", "target": "TK2", "type":"A"},
{"source": "TK1", "target": "L4", "type":"A"},
{"source": "TK2", "target": "L4", "type":"A"},
{"source": "L4", "target": "PIL1", "type":"A"},
{"source": "PIL1", "target": "ULAC1", "type":"A"},
{"source": "PIL1", "target": "BBA1", "type":"A"},
{"source": "PIL1", "target": "BBA2", "type":"A"},
{"source": "ULAC1", "target": "VtaYPF", "type":"A"},
{"source": "BBA1", "target": "PY1", "type":"A"},
{"source": "BBA2", "target": "PY2", "type":"A"}
]
//set up the simulation and add forces
var simulation = d3.forceSimulation()
.nodes(nodes_data);
var link_force = d3.forceLink(links_data)
.id(function(d) { return d.name; });
var charge_force = d3.forceManyBody()
.strength(-100);
var center_force = d3.forceCenter(width / 2, height / 2);
simulation
.force("charge_force", charge_force)
.force("center_force", center_force)
.force("links",link_force)
;
//add tick instructions:
simulation.on("tick", tickActions );
//add encompassing group for the zoom
var g = svg.append("g")
.attr("class", "everything");
//draw lines for the links
var link = g.append("g")
.attr("class", "links")
.selectAll("line")
.data(links_data)
.enter().append("line")
.attr("stroke-width", 2)
.style("stroke", linkColour);
//draw circles for the nodes
var node = g.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(nodes_data)
.enter()
.append("circle")
.attr("r", radius)
.attr("fill", circleColour);
//add drag capabilities
var drag_handler = d3.drag()
.on("start", drag_start)
.on("drag", drag_drag)
.on("end", drag_end);
drag_handler(node);
//add zoom capabilities
var zoom_handler = d3.zoom()
.on("zoom", zoom_actions);
zoom_handler(svg);
/** Functions **/
//Function to choose what color circle we have
//Let's return blue for males and red for females
function circleColour(d){
var my_color
switch (d.entity) {
case "PO": my_color = "black"; break;
case "PY": my_color = "cyan"; break;
case "TK": my_color = "blue"; break;
case "UL": my_color = "green"; break;
case "VTA": my_color = "green"; break;
case "BA": my_color = "cyan"; break;
case "X": my_color = "grey"; break;
}
return my_color
}
//Function to choose the line colour and thickness
//If the link type is "A" return green
//If the link type is "E" return red
function linkColour(d){
if(d.type == "A"){
return "green";
} else {
return "red";
}
}
//Drag functions
//d is the node
function drag_start(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
//make sure you can't drag the circle outside the box
function drag_drag(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function drag_end(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
//Zoom functions
function zoom_actions(){
g.attr("transform", d3.event.transform)
}
function tickActions() {
//update circle positions each tick of the simulation
node
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
//update link positions
link
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
}
function radius(d){
var my_rad
switch (d.entity) {
case "PO": my_rad = 10; break;
case "PY": my_rad = 10; break;
case "TK": my_rad = 20; break;
case "UL": my_rad = 20; break;
case "VTA": my_rad = 15; break;
case "BA": my_rad = 10; break;
case "X": my_rad = 3; break;
}
return my_rad
}
</script>
The above code generate this live image

Positioning nodes within locations in D3

How can I define locations within my SVG elements that contain nodes?
I'm trying to create an abstract map where nodes are contained within locations using D3. The nodes will then be linked to other nodes (sometimes many nodes in the same location +/or other locations).
So sample data may look something like this:
{"nodes":[
{"id": "a", "location": "1"},
{"id": "b", "location": "1"},
{"id": "c", "location": "2"},
{"id": "d", "location": "2"},
{"id": "e", "location": "3"},
{"id": "f", "location": "3"},
{"id": "g", "location": "4"},
{"id": "h", "location": "4"}]
}
I want to create 4 rectangles/bubbles, with 2 nodes (circles) in each.
I'm new to D3 and guess I'm struggling to go from simple datasets to JSON objects. So sorry if I'm missing the obvious.
If you're creating a force directed chart, you can use forceX and forceY to arrange the nodes in the screen. According to the API:
The x- and y-positioning forces push nodes towards a desired position along the given dimension with a configurable strength. The strength of the force is proportional to the one-dimensional distance between the node’s position and the target position.
In this demo, I'm taking your data array and positioning in the x coordinates according to location. First, I set an scale:
var xScale = d3.scalePoint()
.domain([1, 2, 3, 4])
.range([100, width - 100]);
And use this scale in forceX:
var force = d3.forceSimulation(data)
.force('x', d3.forceX((d) => xScale(d.location)).strength(2))
Here is a demo:
var data = [{
"id": "a",
"location": "1"
}, {
"id": "b",
"location": "1"
}, {
"id": "c",
"location": "2"
}, {
"id": "d",
"location": "2"
}, {
"id": "e",
"location": "3"
}, {
"id": "f",
"location": "3"
}, {
"id": "g",
"location": "4"
}, {
"id": "h",
"location": "4"
}];
var width = 500,
height = 200;
var color = d3.scaleOrdinal(d3.schemeCategory10);
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var xScale = d3.scalePoint()
.domain([1, 2, 3, 4])
.range([100, width - 100]);
var circles = svg.selectAll(".bigCircles")
.data(xScale.domain())
.enter()
.append("circle")
.attr("cx", d=>xScale(d))
.attr("cy", height/2)
.attr("fill", d=>color(d))
.attr("r", 40)
.attr("opacity", 0.2);
var node = svg.selectAll(".circles")
.data(data)
.enter().append("circle")
.attr("r", 10)
.attr("fill", (d) => color(d.location));
var force = d3.forceSimulation(data)
.force('x', d3.forceX((d) => xScale(d.location)).strength(2))
.force('center', d3.forceCenter(width / 2, height / 2))
.force("collide", d3.forceCollide(12));
force.nodes(data)
.on('tick', function() {
node
.attr('transform', (d) => {
return 'translate(' + (d.x) + ',' + (d.y) + ')';
});
});
<script src="https://d3js.org/d3.v4.min.js"></script>

How to assign custom colors to bars in a D3.js bar chart?

I am using LeafletJs and D3js to put a bar chart in a Leaflet popup window.
How can I assign a custom color for each ethnic group bar? I want to assign the custom colors within the D3 code because I can't modify the original dataset. Link
Thanks.
var onEachFeature = function onEachFeature(feature, layer) {
colors = d3.scale.category20()
var div = $('<div id="chart"><h3>Ethnic Group Distribution</h3><svg/><h4>Additional details:</h4>Extra stuff here</div>')[0];
var popup = L.popup({
minWidth: 500,
minHeight: 350
}).setContent(div);
layer.bindPopup(popup);
var values = feature.properties;
var data = [{
name: "Pashtun",
value: values["Pashtun"]
}, {
name: "Tajik",
value: values["Tajik"]
}, {
name: "Uzbek",
value: values["Uzbek"]
}, {
name: "Turkmen",
value: values["Turkmen"]
}, {
name: "Hazara",
value: values["Hazara"]
}, {
name: "Baloch",
value: values["Baloch"]
}, {
name: "Kirghiz",
value: values["Kirghiz"]
}, {
name: "Nuristani",
value: values["Nuristani"]
}, {
name: "Aimak",
value: values["Aimak"]
}, {
name: "Arab",
value: values["Arab"]
}, {
name: "Pashaye",
value: values["Pashaye"]
}, {
name: "Sadat",
value: values["Sadat"]
}, {
name: "Qezelbash",
value: values["Qezelbash"]
}];
var margin = {
top: 20,
right: 90,
bottom: 80,
left: 30
},
width = 600 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom,
barWidth = width / data.length;
var x = d3.scale.linear()
.domain([0, data.length])
.range([0, width]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickFormat(function(d) {
return "";
})
var y = d3.scale.linear()
.domain([0, 1])
.range([height, 0]);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(5)
var svg = d3.select(div)
.select("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.classed("chart", true);
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
var bar = svg.selectAll("g.bar")
.data(data)
.enter()
.append("g")
.attr("transform", function(d, i) {
return "translate(" + (i * barWidth + 5) + ",0)";
});
bar.append("rect")
.attr("y", function(d) {
if (!isNaN(d.value)) {
return y(d.value);
} else {
return 0;
}
})
.attr("width", barWidth - 10)
.attr("height", function(d) {
if (!isNaN(d.value)) {
return height - y(d.value);
} else {
return 0;
}
})
.attr("fill",function(d,i){return colors(i)})
bar.append("text")
.attr("x", function(d) {
return -height - 70;
})
.attr("y", barWidth / 2)
.attr("transform", "rotate(270)")
.text(function(d) {
return d.name;
});
};
var geojson = L.geoJson(myData, {
onEachFeature: onEachFeature
}).addTo(map);
Dataset Test:
var afghanDistrictsSample = {
"type": "FeatureCollection",
"features": [{
"type": "Feature",
"properties": {
"stroke": "#555555",
"stroke-width": 2,
"stroke-opacity": 1,
"fill": "#555555",
"fill-opacity": 0.5,
"Pashtun": 0.43,
"Tajik": 0.12,
"Uzbek": 0.05,
"Turkmen": 0.00,
"Hazara": 0.00,
"District": "Argo",
"Province": "Kandahar"
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[61.69921875, 32.08257455954592],
[61.69921875, 32.879587173066305],
[62.666015625, 32.879587173066305],
[62.666015625, 32.08257455954592],
[61.69921875, 32.08257455954592]
]
]
}
}, {
"type": "Feature",
"properties": {
"Pashtun": 0.32,
"Tajik": 0.20,
"Uzbek": 0.01,
"Turkmen": 0.02,
"Hazara": 0.00,
"District": "Jurm",
"Province": "Farah"
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[62.75390625, 32.95336814579932],
[62.75390625, 33.76088200086917],
[63.69873046874999, 33.76088200086917],
[63.69873046874999, 32.95336814579932],
[62.75390625, 32.95336814579932]
]
]
}
}, {
"type": "Feature",
"properties": {
"Pashtun": 0.05,
"Tajik": 0.50,
"Uzbek": 0.21,
"Turkmen": 0.00,
"Hazara": 0.00,
"District": "Ragh",
"Province": "Ghor"
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[63.74267578125, 33.54139466898275],
[63.74267578125, 34.43409789359469],
[65.14892578125, 34.43409789359469],
[65.14892578125, 33.54139466898275],
[63.74267578125, 33.54139466898275]
]
]
}
}, {
"type": "Feature",
"properties": {
"Pashtun": 0.00,
"Tajik": 0.01,
"Uzbek": 0.10,
"Turkmen": 0.20,
"Hazara": 0.40,
"District": "Highan",
"Province": "Kabul"
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[64.53369140625, 35.15584570226544],
[64.53369140625, 35.94243575255426],
[65.56640625, 35.94243575255426],
[65.56640625, 35.15584570226544],
[64.53369140625, 35.15584570226544]
]
]
}
}, {
"type": "Feature",
"properties": {
"Pashtun": 0.00,
"Tajik": 0.01,
"Uzbek": 0.20,
"Turkmen": 0.30,
"Hazara": 0.04,
"District": "Nusay",
"Province": "Kunduz"
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[65.58837890625, 33.30298618122413],
[65.58837890625, 34.32529192442733],
[66.90673828125, 34.32529192442733],
[66.90673828125, 33.30298618122413],
[65.58837890625, 33.30298618122413]
]
]
}
}, {
"type": "Feature",
"properties": {
"Pashtun": 0.20,
"Tajik": 0.00,
"Uzbek": 0.00,
"Turkmen": 0.10,
"Hazara": 0.20,
"District": "Zebak",
"Province": "Logar"
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[65.98388671875, 34.72355492704219],
[65.98388671875, 35.53222622770337],
[66.95068359374999, 35.53222622770337],
[66.95068359374999, 34.72355492704219],
[65.98388671875, 34.72355492704219]
]
]
}
}, {
"type": "Feature",
"properties": {
"Pashtun": 0.10,
"Tajik": 0.10,
"Uzbek": 0.28,
"Turkmen": 0.10,
"Hazara": 0.00,
"District": "Wakhan",
"Province": "Nimruz"
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[67.32421875, 34.43409789359469],
[67.32421875, 35.42486791930558],
[68.37890625, 35.42486791930558],
[68.37890625, 34.43409789359469],
[67.32421875, 34.43409789359469]
]
]
}
}]
};
Instead of using the built-in categorical colors...
colors = d3.scale.category20()
...you could try using an ordinal scale...
var colors = d3.scale.ordinal()
.domain(['Pashtun','Tajik','Uzbek', 'Turkmen', 'Hazara', 'Baloch','Kirghiz','Nuristani','Aimak', 'Arab','Pashaye','Sadat','Qezelbash'])
.range(['red', 'white', 'green', 'blue', 'yellow', 'pink', 'lime', 'black', 'navy', 'silver', 'skyblue', 'purple', 'olive' ])
...then modify your code to do a lookup on the name:
.attr("fill", function(d, i) { return colors(d.name) })
I didn't test the code, but you could read more about ordinal scales here if it doesn't work:
https://github.com/mbostock/d3/wiki/Ordinal-Scales
Alternatively, you could simply add a class to the element...
.attr("class", function(d, i) { return 'bar_' + d.name} )
... and class it in your CSS:
.bar_Pashtun { fill: blue }

D3: Iterating and accessing datasets?

Context
I am making a scatterplot. For each location, in my dataset, I want to plot a circle on a map, using 'lat' and 'long' values. There will be two circles plotted, one on top of the other. The radiuses of the two circles will be defined by 'total' and 'passed' values. I've made my map; I intend my plotted data to look something like this:
I can structure my data any way I want to. I have have opted for json, below.
[
{
"year": 2006,
"inspections": [
{
"location": "Cheshire",
"total": 341,
"passed": 26,
"long": 5,
"lat": 20
},
{
"location": "County Durham",
"total": 102,
"passed": 1,
"long": 480,
"lat": 90
}
]
},
{
"year": 2007,
...
]
Eventually, I'd like to transition my circles (having them grow and shrink) through the years, but for now I'm starting simple and just trying to plot a single year of data on my map.
Here is my first attempt at the code to plot my circles:
d3.json("dataset", function(error, data) {
svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cx", function(d) {
// return someting;
})
.attr("cy", function(d) {
// return someting;
})
.attr("r", 5);
});
Here's the console output of d:
Question
I don't understand too well how .data() and .enter() work? How do I access my values, 'location', 'total', 'passed', 'long' and 'lat', in turn so I can plot all my circles for the year 2006. The examples available tend to use very simple arrays. How do I get my values from my more complex structure of nested arrays and objects?
If your json data is like this
var json = [{
"year": 2006,
"inspections": [{
"location": "Cheshire",
"total": 341,
"passed": 26,
"long": 50,
"lat": 0
}, {
"location": "County Durham",
"total": 102,
"passed": 10,
"long": 52,
"lat": 0
}]
}, {
"year": 2007,
"inspections": [{
"location": "Cheshire",
"total": 341,
"passed": 26,
"long": 51,
"lat": 1
}, {
"location": "County Durham",
"total": 102,
"passed": 10,
"long": 51,
"lat": -1.8
}]
}
];
First make red circle for the first array of inspections like this:
//circle1
svg.selectAll(".red")//get all the circles with class red
.data(json).enter()//iterate over the json
.append("circle")
.attr("cx", function (d) { var circle1 = d.inspections[0]; return projection([circle1.lat,circle1.long])[0]; })//get x point based on projection set the x point which is 0 index
.attr("cy", function (d) { var circle1 = d.inspections[0]; return projection([circle1.lat,circle1.long])[1]; })//get x point based on projection set the x point which is 1 index
.attr("r", "8")
.attr("class", "red")//to get the selection for red circles
.attr("fill", "red")
Make blue circle for the second array of inspections like this:
//circle 2
svg.selectAll(".blue")
.data(json).enter()
.append("circle")
.attr("cx", function (d) { var circle1 = d.inspections[1]; return projection([circle1.lat,circle1.long])[0]; })
.attr("cy", function (d) { var circle1 = d.inspections[1]; return projection([circle1.lat,circle1.long])[1]; })
.attr("r", "8")
.attr("class", "blue")
.attr("fill", "blue")
Working code here
Hope this helps!
Easier to just have a flat array of objects if you can:
[
{
"year": 2006,
"location": "Cheshire",
"total": 341,
"passed": 26,
"long": 5,
"lat": 20
},
{
"year": 2006,
"location": "County Durham",
"total": 102,
"passed": 1,
"long": 480,
"lat": 90
}
]
Then d.lat etc will return what you expect. Otherwise your code looks fine on first glance. Just do your svg.selectAll("circle").data(data).enter().append("circle") twice--once for each set of circles.

How to draw circles at different times with D3js?

Using d3js, I need to draw(append) circles, not all together but with less then one second of distance. So one circle in x position, another one in y position after 0.5 second.
Use setTimeout. Here is the working code snippet.
var nodes = [{
"name": "6",
"x": 207,
"y": 305
}, {
"name": "7",
"x": 404,
"y": 310
}, {
"name": "8",
"x": 420,
"y": 510
}, {
"name": "9",
"x": 540,
"y": 126
}, {
"name": "10",
"x": 350,
"y": 150
}, {
"name": "11",
"x": 177,
"y": 320
}, {
"name": "12",
"x": 200,
"y": 190
}, {
"name": "13",
"x": 170,
"y": 150
}, {
"name": "14",
"x": 107,
"y": 510
}, {
"name": "15",
"x": 104,
"y": 150
}, {
"name": "16",
"x": 104,
"y": 150
}, {
"name": "17",
"x": 310,
"y": 160
}, {
"name": "18",
"x": 120,
"y": 110
}, {
"name": "19",
"x": 619,
"y": 145
}, {
"name": "20",
"x": 148,
"y": 107
}, {
"name": "21",
"x": 575,
"y": 107
}];
var width = 500,
height = 400;
var color = d3.scale.category20();
var svg = d3.select("#map").append("svg")
.attr("width", width)
.attr("height", height);
nodes.forEach(function(d, i) {
setTimeout(function() {
svg.append("circle")
.datum(d)
.attr("class", "node")
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
})
.attr("r", "10")
.style("fill", function(d) {
return color(i);
});
}, 500 * i);
});
.node {
stroke: #fff;
stroke-width: 1.5px;
}
.overlay {
fill: none;
pointer-events: all;
}
#map{
border: 2px #555 dashed;
width:500px;
height:400px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<body>
<div id="map"></div>
</body>
You can use the standard javascript methods setTimeout or setInterval to append the circles one by one with a variable delay depending on the circle index.
Or, you could create all the circles on enter normally using the standard d3 syntax but with opacity set to 0 and just add a .transition() with delay dependent on the index that sets opacity to 1
Here's a working jsfiddle of the latter option: http://jsfiddle.net/pg5m3m3n/5/
Extract:
canvas.selectAll('circle')
.data(data)
.enter()
.append('circle')
.attr({
'cx': function(d) { return d.x; },
'cy': function(d) { return d.y; },
'r': 10,
'opacity': 0
})
.transition().delay(function(d,i) { return i*50; })
.attr('opacity',1);
The pros of this is that it uses d3 syntax and it's just 2 lines of code more than the normal append, the con is that the circles are actually added immediately and only become visible one by one, which may give performance issues if the number of circles to append is huge.

Resources