D3 draw sub circle - javascript

I'm new to D3 and I'm trying to draw something like this
What I have done so far is here:
https://jsfiddle.net/834wg3g9/
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.cover {
width: 400px;
height: 400px;
/* border: 1px solid; */
margin: auto;
margin-top: 100px;
}
</style>
<div class="cover">
<svg></svg>
</div>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
// let width = window.innerWidth
// let height = window.innerHeight
let width = 400
let height = 400
let svg = d3.select("svg")
svg.attr("width", width)
.attr("height", height)
// let nodes = [
// {'id': 0,'r': 50, 'color': '#E8837B'},
// {'id': 1,'r': 100, 'color': '#FBCB43'},
// {'id': 2,'r': 150, 'color': '#16A05D'},
// {'id': 3,'r': 200, 'color': '#4C8BF5'},
// ]
let nuts = [
{r: 15},
{r: 15},
{r: 15},
{r: 15},
{r: 15},
{r: 15},
]
let nodes = [
{'id': 3,'r': 400, 'color': '#E8837B'},
{'id': 1,'r': 330, 'color': '#FBCB43'},
{'id': 2,'r': 250, 'color': '#16A05D'},
{'id': 0,'r': 150, 'color': '#4C8BF5'},
]
let simulation = d3.forceSimulation()
.force('center', d3.forceCenter(width/2, height/2))
let simulation_2 = d3.forceSimulation()
.force('charge', d3.forceManyBody().strength(-30))
.force('center', d3.forceCenter(width/2, height/2))
let nodeElements = svg.append('g')
.selectAll('circle')
.data(nodes)
.enter().append('circle')
.attr("class", "node")
.attr('r', function(d) {
return d.r
})
.attr('fill', function(d) {
return d.color
})
let bottomLineElements = svg.append('g')
.selectAll('text')
.data(nodes)
.enter().append('text')
.text(function(d) {
return d.color
})
let nutElements = svg.append('g')
.selectAll('circle')
.data(nuts)
.enter().append('circle')
.attr('fill', 'white')
.attr('r', function(d) { return d.r })
simulation_2.nodes(nuts)
.on('tick', function() {
nutElements
.attr('cx', function (d) { return d.x })
.attr('cy', function (d) { return d.y })
})
simulation.nodes(nodes)
.on('tick', function() {
nodeElements
.attr('cx', function (d) { return 400 })
.attr('cy', function (d) { return 400 })
bottomLineElements
.attr('x', function (d) { return 20 + (d.id * 80) })
.attr('y', function (d) { return 400 })
})
</script>
Now I'm stuck at draw "New or moved" and "No change" part
How can append a bunch of sub circle to each area and make sure they don't display in wrong area
For example:
hold = [{}, {}, {}]
so 3 small circles will display in Hold area
Any advice, keywords to solve this
Thank you.

Related

How to change the colors of nodes in force directed graph in d3js using javascript?

Current Behavior: All the nodes are in random colors.
Expected Behavior: All the parent nodes should be in same colors(blue for example) and all the child nodes should in same colors(light blue).
How to achieve this ?
Here is the working jsfiddle: Fiddle
var color = d3.scaleOrdinal(d3.schemeCategory10);
grads = svg.append("defs").selectAll("radialGradient")
.data(graph.nodes)
.enter()
.append("radialGradient")
.attr("gradientUnits", "objectBoundingBox")
.attr("cx", 0)
.attr("fill", function(d) { return color(d.id); })
.attr("cy", 0)
.attr("r", "100%")
.attr("id", function(d, i) { return "grad" + i; });
grads.append("stop")
.attr("offset", "0%")
.style("stop-color", "white");
grads.append("stop")
.attr("offset", "100%")
.style("stop-color", function(d) { return color(d.id); });
nodeElements = g.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.attr("r", 60)
.attr("stroke", "#fff")
.attr('stroke-width', 21)
.attr("id", function(d) { return d.id })
//.attr("fill", function(d) {return color(d.id)})
.attr('fill', function(d, i) { return 'url(#grad' + i + ')'; })
Then you need to change the color based on the group. Change this line as follow:
.attr("fill", function(d) {return color(d.group)})
Please find the working code below:
var graph = {
'nodes': [{
'id': 'Material_Definition',
'group': 0
},
{
'id': 'Item1',
'group': 1
},
{
'id': 'Item2',
'group': 1
},
{
'id': 'Item3',
'group': 1
},
{
'id': 'Item4',
'group': 1
},
{
'id': 'Item5',
'group': 1
},
{
'id': 'SubItem1_Item1',
'group': 2
},
{
'id': 'SubItem2_Item1',
'group': 2
}
],
'links': [
/* Material Definition linked to Items */
{
'source': 'Material_Definition',
'target': 'Item1',
'value': 1,
'type': 'A'
},
{
'source': 'Material_Definition',
'target': 'Item2',
'value': 8,
'type': 'A'
},
{
'source': 'Material_Definition',
'target': 'Item3',
'value': 10,
'type': 'A'
},
{
'source': 'Material_Definition',
'target': 'Item3',
'value': 1,
'type': 'A'
},
{
'source': 'Material_Definition',
'target': 'Item4',
'value': 1,
'type': 'A'
},
{
'source': 'Material_Definition',
'target': 'Item5',
'value': 1,
'type': 'A'
},
/* Item1 is linked to SubItems */
{
'source': 'Item1',
'target': 'SubItem1_Item1',
'value': 2,
'type': 'A'
},
{
'source': 'Item1',
'target': 'SubItem2_Item1',
'value': 1,
'type': 'A'
},
/* Interconnected Items */
{
'source': 'Item5',
'target': 'Item4',
'value': 2,
'type': 'A'
},
{
'source': 'Item2',
'target': 'Item3',
'value': 1,
'type': 'A'
},
]
};
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height")
var store = Object.assign({}, graph);
var color = d3.scaleOrdinal(d3.schemeCategory10);
var zoom_handler = d3.zoom().on("zoom", zoom_actions);
var linkElements, nodeElements, textElements, grads;
// zoom_handler(svg);
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().distance(700).id(function(d) {
return d.id;
}))
.force("charge", d3.forceManyBody().strength(-1000))
.force("center", d3.forceCenter(width / 2, height / 2));
var g = svg.append("g")
.attr("class", "everything");
svg.call(zoom_handler)
.call(zoom_handler.transform, d3.zoomIdentity.translate(200, 150).scale(0.2));
linkElements = svg.append("g").selectAll(".link"),
nodeElements = svg.append("g").selectAll(".nodes");
grads = svg.append("g").selectAll(".grads");
textElements = svg.append("g").selectAll(".texts");
drawGraph();
function drawGraph() {
// empty current Graph contents
g.html('')
grads = svg.append("defs").selectAll("radialGradient")
.data(graph.nodes)
.enter()
.append("radialGradient")
.attr("gradientUnits", "objectBoundingBox")
.attr("cx", 0)
.attr("fill", function(d) {
return color(d.group);
})
.attr("cy", 0)
.attr("r", "100%")
.attr("id", function(d, i) {
return "grad" + i;
});
grads.append("stop")
.attr("offset", "0%")
.style("stop-color", "white");
grads.append("stop")
.attr("offset", "100%")
.style("stop-color", function(d) {
return color(d.id);
});
linkElements = g.append("g")
.attr("class", "links")
.selectAll("line")
.data(graph.links)
.enter().append("line")
.style("stroke-width", 5.5)
.style("stroke", "grey")
nodeElements = g.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.attr("r", 60)
.attr("stroke", "#fff")
.attr('stroke-width', 21)
.attr("id", function(d) {
return d.id
})
.attr("fill", function(d) {
return color(d.group)
})
/* .attr('fill', function(d, i) { return 'url(#grad' + i + ')'; }) */
.on('contextmenu', function(d) {
d3.event.preventDefault();
menu(d3.mouse(svg.node())[0], d3.mouse(svg.node())[1]);
})
.on('mouseover', selectNode)
.on('mouseout', releaseNode)
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
textElements = g.append("g") // use g.append instead of svg.append to enable zoom
.attr("class", "texts")
.selectAll("text")
.data(graph.nodes)
.enter().append("text")
.attr("text-anchor", "end")
.text(function(node) {
return node.id
})
.attr("font-size", 55)
.attr("font-family", "sans-serif")
.attr("fill", "black")
.attr("style", "font-weight:bold;")
.attr("dx", 30)
.attr("dy", 80)
}
function ticked() {
linkElements
.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;
});
nodeElements
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
})
.each(d => {
d3.select('#t_' + d.id).attr('x', d.x + 10).attr('y', d.y + 3);
});
textElements
.attr('x', function(d) {
return d.x
})
.attr('y', function(d) {
return d.y
});
}
simulation
.nodes(graph.nodes)
.on("tick", ticked);
simulation.force("link")
.links(graph.links);
function zoom_actions() {
g.attr("transform", d3.event.transform)
}
function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
function selectNode(selectedNode) {
var neighbors = getNeighbors(selectedNode)
nodeElements.transition().duration(500)
.attr('r', function(node) {
return getNodeRadius(node, neighbors);
});
nodeElements.attr('fill', function(node) {
return getNodeColor(node, neighbors, selectedNode);
})
textElements.transition().duration(500).style('font-size', function(node) {
return getTextColor(node, neighbors)
})
}
function releaseNode() {
nodeElements.transition().duration(500)
.attr('r', 60);
nodeElements.attr('fill', function(d, i) {
return 'url(#grad' + i + ')';
})
linkElements.style('stroke', 'grey');
}
function getNeighbors(node) {
return graph.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 getNodeColor(node, neighbors, selectedNode) {
// If is neighbor
if (Array.isArray(neighbors) && neighbors.indexOf(node.id) > -1) {
return 'url(#grad' + selectedNode.index + ')'
// return node.level === 1 ? '#9C4A9C' : 'rgba(251, 130, 30, 1)'
} else {
return 'url(#grad' + node.index + ')'
}
//return node.level === 0 ? '#91007B' : '#D8ABD8'
}
function getNodeRadius(node, neighbors) {
// If is neighbor
if (neighbors.indexOf(node.id) > -1) {
return '100'
} else {
return '60'
}
}
function isNeighborLink(node, link) {
return link.target.id === node.id || link.source.id === node.id
}
function getTextColor(node, neighbors) {
return Array.isArray(neighbors) && neighbors.indexOf(node.id) > -1 ? '40px' : '25px'
}
var filter = document.querySelector('#filter');
filter.addEventListener('change', function(event) {
filterData(event.target.value);
drawGraph();
})
var resetFilter = document.querySelector('#reset');
resetFilter.addEventListener('click', function(event) {
drawGraph();
})
function filterData(id) {
graph = Object.assign({}, store);
graph.nodes = [];
graph.links = [];
dummyStore = [id];
store.links.forEach(function(link) {
if (link.source.id === id) {
graph.links.push(link);
dummyStore.push(link.target.id);
} else if (link.target.id === id) {
graph.links.push(link);
dummyStore.push(link.source.id)
}
});
store.nodes.forEach(function(node) {
if (dummyStore.includes(node.id)) {
graph.nodes.push(node);
}
})
}
.links line {
stroke: #999;
stroke-opacity: 0.6;
}
.nodes circle {
stroke: #000;
stroke-width: 1.5px;
}
text {
font-size: 10px;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
Filter: <input type="text" name="filter" id="filter" />
<button id='reset'>Reset Filter</button>
<svg width="798" height="400"></svg>

Wrong nodes connection in a directed graph with d3.js

I am developing a directed graph with nodes in static position using d3.js library.
Currently I am having 3 nodes, and I want to connect the two computers with the printer. The edges are set to different positions as seen in this image:
How can i make the lines connect the nodes based on the nodes position?
Here is the code I have so far.
var width = 640,
height = 400;
var nodes = [
{ x: 184.53020651496104, y: 0, id: 0, url:
"http://icons.iconarchive.com/icons/tpdkdesign.net/refresh-
cl/32/Hardware-My-Computer-3-icon.png"},
{ x: 100, y: 150, id: 1, url:
"http://icons.iconarchive.com/icons/tpdkdesign.net/refresh-
cl/32/Hardware-My-Computer-3-icon.png" },
{ x: width/3, y: height/2, id: 2, url:
"http://icons.iconarchive.com/icons/tpdkdesign.net/refresh-
cl/32/Hardware-Printer-Blue-icon.png" },
];
var links = [
{ source: 1, target: 2 },
{ source: 0, target: 2 }
];
var graph = d3.select('#graph');
var svg = graph.append('svg')
.attr('width', width)
.attr('height', height);
var force = d3.layout.force()
.size([width, height])
.nodes(nodes)
.links(links);
force.linkDistance(width/2);
var link = svg.selectAll('.link')
.data(links)
.enter().append('line')
.attr('class', 'link');
var node = svg.selectAll('.node')
.data(nodes)
.enter().append("image")
.attr("xlink:href", d=> d.url)
.attr("x", d=> d.x)
.attr("y", d=> d.y)
.attr("width", 30)
.attr("height", 30)
.attr('class', 'node');
force.on('end', function() {
node.attr('r', width/25)
.attr('cx', function(d) { return d.x; })
.attr('cy', function(d) { return d.y; });
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; });
});
force.start();
Thank you in advance.

Position of node changes when they link (using d3.js)

I am a newbie in d3.js. I have tried to create a static architecture with 5 nodes and link them with each other according to preferences, the nodes should be organized like so:
At the beginning I set the position of the nodes and then create the links. Though, when the nodes get linked, the architecture changes and the result is the one displayed below:
Here is my code:
var width = 640,
height = 400;
var nodes = [
{ x: 60, y: 0, id: 0},
{ x: 150, y: height/4, id: 1},
{ x: 220, y: height/4, id: 2},
{ x: 340, y: height/4, id: 3},
{ x: 420, y: height/2, id: 4},
{ x: 480, y: height/2, id: 5}
];
var links = [
{ source: 1, target: 5 },
{ source: 0, target: 5 },
{ source: 2, target: 1 },
{ source: 3, target: 2 },
{ source: 4, target: 5 }
];
var graph = d3.select('#graph');
var svg = graph.append('svg')
.attr('width', width)
.attr('height', height);
var force = d3.layout.force()
.size([width, height])
.nodes(nodes)
.links(links);
force.linkDistance(width/2);
var link = svg.selectAll('.link')
.data(links)
.enter().append('line')
.attr('class', 'link');
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 1e-6);
var node = svg.selectAll('.node')
.data(nodes)
.enter().append("circle")
.attr("cx", d=> d.x)
.attr("cy", d=> d.y)
.attr('class', 'node')
.on("mouseover", function(d){
d3.select(this)
.transition()
.duration(500)
.style("cursor", "pointer")
div
.transition()
.duration(300)
.style("opacity", "1")
.style("display", "block")
console.log("label", d.label);
div
.html("IP: " + d.label + " x: " + d.x + " y: " + d.y)
.style("left", (d3.event.pageX ) + "px")
.style("top", (d3.event.pageY) + "px");
})
.on("mouseout", mouseout);
function mouseout() {
div.transition()
.duration(300)
.style("opacity", "0")
}
console.log("wait...");
force.on('end', function() {
node.attr('r', width/25)
.attr('cx', function(d) { return d.x; })
.attr('cy', function(d) { return d.y; });
link.attr('x1', function(d) { console.log("LINE x1-> ", d.source.x); return d.source.x; })
.attr('y1', function(d) { console.log("LINE y1-> ", d.source.y); return d.source.y; })
.attr('x2', function(d) { console.log("LINE x2-> ", d.source.x); return d.target.x; })
.attr('y2', function(d) { console.log("LINE y2-> ", d.source.y); return d.target.y; })
.attr("stroke-width", 2)
.attr("stroke","black");
});
force.start();
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="graph"></div>
Could you please help me?
Thank you in advance.
A force layout offers some advantages that derive from its nature as a self organizing layout:
It places nodes and links automatically avoiding manual positioning of potentially thousands of elements
It organizes nodes and links based on assigned forces to an ideal spacing and layout
You have nodes to which you have already assigned positions, the two advantages listed above do not apply. You've already manually done the first item, and the second item will disturb and overwrite the positions you manually set.
We could fix the node positions, but if we do this with all nodes, it defeats the purpose of the force layout: to position nodes by simulating forces.
Instead, if you have the position of all nodes, we can skip the force and just append everything based on the data. The snippet below places the links first (so they are behind the nodes) using the index contained in d.source/d.target to access the specific node in the nodes array and get the appropriate x or y coordinate. The nodes are positioned normally.
It appears you have adjusted the code to use circles in your question though the screenshot uses images (as you also used in a previous question), I'll just use circles here. Based on the coordinates you've given some lines overlap. I modified the first node so that the y value wasn't 0 (which would have pushed half the circle off the svg)
var width = 640,
height = 400;
var nodes = [
{ x: 60, y: height/8, id: 0},
{ x: 150, y: height/4, id: 1},
{ x: 220, y: height/4, id: 2},
{ x: 340, y: height/4, id: 3},
{ x: 420, y: height/2, id: 4},
{ x: 480, y: height/2, id: 5}
];
var links = [
{ source: 1, target: 5 },
{ source: 0, target: 5 },
{ source: 2, target: 1 },
{ source: 3, target: 2 },
{ source: 4, target: 5 }
];
var graph = d3.select('#graph');
var svg = graph.append('svg')
.attr('width', width)
.attr('height', height);
// append links:
svg.selectAll()
.data(links)
.enter()
.append("line")
.attr("x1", function(d) { return nodes[d.source].x; })
.attr("y1", function(d) { return nodes[d.source].y; })
.attr("x2", function(d) { return nodes[d.target].x; })
.attr("y2", function(d) { return nodes[d.target].y; })
.attr("stroke-width", 2)
.attr("stroke","black");
// append nodes:
svg.selectAll()
.data(nodes)
.enter()
.append("circle")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", 8);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="graph"></div>

Update polygon when points change in d3

I'm currently fiddling with polygons in d3 and would like to update individual polygons whilst the user is dragging a point. Drawing them initially works fine, but I can't get the update to work. The fiddle below contains my awful attempt at getting it to work:
https://jsfiddle.net/z4g5817z/9/
Relevant code:
const areas = [{
uid: 'ajf9v0',
points: [{
x: 52,
y: 92
},
{
x: 50,
y: 151
},
{
x: 123,
y: 149
},
{
x: 125,
y: 91
}
],
foo: 'bar',
// ...
},
{
uid: 'ufnf12',
points: [{
x: 350,
y: 250
},
{
x: 450,
y: 250
},
{
x: 450,
y: 275
},
{
x: 350,
y: 275
}
],
foo: 'baz',
// ...
}
];
const svg = d3.select('#root');
svg.attr('width', 500)
.attr('height', 500);
const areasGroup = svg.append('g')
.attr('class', 'areas');
function drawAreas(areas) {
console.log('Called draw');
const self = this;
const aGroup = areasGroup.selectAll('g.area')
.data(areas, (d) => {
console.log('Areas', d.points.map((d) => [d.x, d.y].join('#')).join('#'));
return d.points.map((d) => [d.x, d.y].join('#')).join('#');
});
areasGroup.exit().remove();
const areaGroups = aGroup.enter()
.append('g')
.attr('class', 'area');
//const areaPolygon = area.append('g')
// .attr('class', 'polygon');
//const areaPoints = area.append('g')
// .attr('class', 'points');
const polygon = areaGroups.selectAll('polygon')
.data((d) => {
console.log('Polygon data points', [d.points]);
return [d.points];
}, (d) => {
console.log('Polygon key', d.map((d) => [d.x, d.y].join('#')).join('#'));
return d.map((d) => [d.x, d.y].join('#')).join('#');
});
polygon.enter()
.append('polygon')
.merge(polygon)
.attr('points', (d) => {
console.log('Polygon points', d);
return d.map((d) => [d.x, d.y].join(',')).join(' ');
})
.attr('stroke', '#007bff')
.attr('stroke-width', 1)
.attr('fill', '#007bff')
.attr('fill-opacity', 0.25)
.on('click', this.handlePolygonSelection)
polygon.exit().remove();
const circles = areaGroups.selectAll('circle')
.data((d) => d.points, (d) => d.x + '#' + d.y);
circles.enter()
.append('circle')
.attr('r', 4)
.attr('cx', (d) => d.x)
.attr('cy', (d) => d.y)
.attr('fill', '#007bff')
.on('click', (d, idx, j) => {
const parentArea = d3.select(j[idx].parentNode).datum().points;
const i = parentArea.findIndex((p) => p.x === d.x && p.y === d.y);
if (i === parentArea.length) {
parentArea.pop();
} else if (i === 0) {
parentArea.shift();
} else {
parentArea.splice(i, 1);
}
this.drawAreas(areas);
})
.call(d3.drag()
.on('start', function(d) {
d3.select(this).classed('active', true)
})
.on('drag', function(d) {
d3.select(this)
.attr('cx', d.x = d3.event.x)
.attr('cy', d.y = d3.event.y);
self.drawAreas(areas);
})
.on('end', function(d) {
d3.select(this).classed('active', false)
}));
circles.exit().remove();
}
this.drawAreas(areas);
Thank you to anybody who takes time to have a look, any and all help is appreciated.
So it looks like I found the issue: https://jsfiddle.net/z4g5817z/91/
Changing
const polygon = areaGroups.selectAll('polygon')
to
const polygon = areasGroup.selectAll('g.area').selectAll('polygon')
seems to have fixed it. I'm assuming this has to do with the areaGroups selection only handling enter events.
An alternative would be to keep it the way it is now and change
const areaGroups = aGroup.enter()
.append('g')
.attr('class', 'area');
to
const areaGroups = aGroup.enter()
.append('g')
.merge(aGroup)
.attr('class', 'area');
which will produce the same result, as the update event is now also handled appropriately.

d3 area between two line graphs [duplicate]

This question already has an answer here:
Using d3 to shade area between two lines
(1 answer)
Closed 6 years ago.
I want to fill the lines between two area graphs defined below. I am hitting a bit of a wall -- the issue I seem to have that each path I created does NOT have the other value to compare with, and my efforts to find a work around seem to have hit a bit of a wall.
Any tips?
jQuery(document).ready(function ($) {
var margin = {top: 20, right: 30, bottom: 40, left: 24},
width = 430 - margin.left - margin.right,
height = 225 - margin.top - margin.bottom,
dotRadius = function() { return 3 };
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient('bottom')
.tickFormat(d3.time.format('%b'));
var yAxis = d3.svg.axis()
.scale(y)
.orient('left');
// This is a function that determines the colours of the lines drawn, up to 10.
var color = d3.scale.category10();
// This is used to format the time for our data.
var formatTime = d3.time.format("%Y-%m-%d");
var line = d3.svg.line()
.x(function(d) { return x(d.Period); })
.y(function(d) { return y(d.Value); })
var areaBetweenGraphs = d3.svg.area()
.x(function(d) {
console.log('ABG x is: ', d);
return x(formatTime.parse(d.Time));
})
.y0(function(d) {
console.log('ABG y0 is: ', d);
return y(d.Quantity);
})
.y1(function(d) {
console.log('ABG y1 is: ', d);
return y(d.Amount);
});
var svg = d3.select("#pipeline-chart-render")
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
// This separates the data into the lines we want, although the data is stored
// In the same original object.
color.domain(d3.keys(data[0].values[0]).filter(function(key) {
if (key === 'Amount'
|| key === 'Quantity') {
return key
}
}));
// This returns the data into two separate objects which can be graphed.
// In this case, Amount and Quantity.
var datasets = color.domain().map(function(name) {
return {
name: name,
values: data.map(function(d) {
return {
Period: formatTime.parse(d.values[0].Time),
Value: +d.values[0][name]};
})
};
});
console.log('datasets is: ', datasets);
// set the minYDomainValue to zero instead of letting it be a lingering magic number.
var minDomainValue = 0
var minDate = d3.min(datasets, function(d0){
return d3.min(d0.values, function(d1){
return d1.Period;
})
}),
maxDate = d3.max(datasets, function(d0){
return d3.max(d0.values, function(d1){
return d1.Period;
})
});
x.domain([minDate, maxDate]);
y.domain([
minDomainValue,
d3.max(datasets, function(c) { return d3.max(c.values, function(v) { return v.Value; }); })
])
// Append the x-axis class and move axis around.
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
// Append the y-axis class.
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
svg.append('g')
var pipeline = svg.selectAll('.pipeline')
.data(datasets);
pipeline.enter()
.append('g')
.attr('class', 'pipeline');
pipeline.append('path')
.attr('class', 'line')
.attr('id', function(d, i) {
return 'pipeline-'+(i+1);
})
.attr('d', function(d) { console.log('line d is: ', d); return line(d.values); })
.attr("data-legend",function(d) { return d.name})
.style("stroke", function(d) { return color(d.name); })
pipeline.exit().remove()
// Rendering the points on the graph.
var points = svg.selectAll('.pipelinePoint')
.data(datasets);
points
.enter()
.append('g')
.attr('class', 'pipelinePoint');
points.selectAll('.point')
.data(function(d) {
return d.values;
})
.enter()
.append('circle')
.attr('circleId', function(d, i) {
return 'circleId-'+i;
})
.attr('cx', function(d) {
return x(d.Period);
})
.attr('cy', function(d) {
return y(d.Value);
})
.attr('r', function(d) {
return dotRadius()
});
});
var data = [
{
key: 1,
values: [
{
Amount: 33,
Quantity: 22,
Time: '2015-01-01'
}
]
},
{
key: 2,
values: [
{
Amount: 52,
Quantity: 20,
Time: '2015-02-01'
}
]
},
{
key: 3,
values: [
{
Amount: 63,
Quantity: 30,
Time: '2015-03-01'
}
]
},
{
key: 4,
values: [
{
Amount: 92,
Quantity: 60,
Time: '2015-04-01'
}
]
},
{
key: 5,
values: [
{
Amount: 50,
Quantity: 29,
Time: '2015-05-01'
}
]
},
{
key: 6,
values: [
{
Amount: 53,
Quantity: 25,
Time: '2015-06-01'
}
]
},
{
key: 7,
values: [
{
Amount: 46,
Quantity: 12,
Time: '2015-07-01'
}
]
},
{
key: 8,
values: [
{
Amount: 52,
Quantity: 15,
Time: '2015-08-01'
}
]
},
{
key: 9,
values: [
{
Amount: 55,
Quantity: 20,
Time: '2015-09-01'
}
]
},
{
key: 10,
values: [
{
Amount: 35,
Quantity: 17,
Time: '2015-10-01'
}
]
},
{
key: 11,
values: [
{
Amount: 80,
Quantity: 45,
Time: '2015-11-01'
}
]
},
{
key: 12,
values: [
{
Amount: 64,
Quantity: 24,
Time: '2015-12-01'
}
]
}
]
CSS if you want it to be a less ugly render:
/* Line Chart CSS */
.axis path,
.axis line {
fill: none;
stroke: #000;
stroke-width: 3px;
shape-rendering: crispEdges;
}
#pipeline-1,
#pipeline-2 {
fill: none;
stroke-width: 1.5px;
stroke-linecap: round;
transition: stroke-width 250ms linear;
-moz-transition: stroke-width 250ms linear;
-webkit-transition: stroke-width 250ms linear;
transition-delay: 250ms
-moz-transition-delay: 250ms;
-webkit-transition-delay: 250ms;
}
.x.axis path {
/* Uncomment below if I want to remove x-axis line */
/* display: none;*/
}
.line.hover path {
stroke-width: 6px;
}
#pipeline-chart-render {
padding-left: -50px;
}
.area {
fill: steelblue;
}
This ended up working.
// The following is for defining the area BETWEEN graphs.
var areaAboveQuantity = d3.svg.area()
.x(line.x())
.y0(line.y())
.y1(0);
var areaBelowQuantity = d3.svg.area()
.x(line.x())
.y0(line.y())
.y1(height);
var areaAboveAmount = d3.svg.area()
.x(line.x())
.y0(line.y())
.y1(0);
var areaBelowAmount = d3.svg.area()
.x(line.x())
.y0(line.y())
.y1(height);
var defs = svg.append('defs');
defs.append('clipPath')
.attr('id', 'clip-quantity')
.append('path')
.datum(datasets)
.attr('d', function(d) {
return areaAboveQuantity(d[1].values);
});
defs.append('clipPath')
.attr('id', 'clip-amount')
.append('path')
.datum(datasets)
.attr('d', function(d) {
return areaAboveAmount(d[0].values);
});
svg.append('path')
.datum(datasets)
.attr('class', 'area')
.attr('d', function(d) {
return areaBelowQuantity(d[1].values)
});
// Quantity IS ABOVE Amount
svg.append('path')
.datum(datasets)
.attr('d', function(d) {
areaBelowQuantity(d[1].values);
})
.attr('clip-path', 'url(#clip-amount)')
.style('fill', 'steelblue')
.style('opacity', '0.2');
// Amount IS ABOVE Quanity
svg.append('path')
.datum(datasets)
.attr('d', function(d) {
return areaBelowAmount(d[0].values);
})
.attr('clip-path', 'url(#clip-quantity)')
.style('fill', 'steelblue')
.style('opacity', '0.2');

Categories