Related
I am working on a d3js horizontal chart - the designers are specific in having the labels this way.
I've built the following - but would like to model it more on older code that had animation properties.
//current chart
https://codepen.io/anon/pen/ZmJzXZ
//static vertical chart http://jsfiddle.net/pg886/201/
//animated vertical chart http://jsfiddle.net/Qh9X5/12073/
-- d3js code
var data = [{
"name": "Apples",
"value": 20,
},
{
"name": "Bananas",
"value": 12,
},
{
"name": "Grapes",
"value": 19,
},
{
"name": "Lemons",
"value": 5,
},
{
"name": "Limes",
"value": 16,
},
{
"name": "Oranges",
"value": 26,
},
{
"name": "Pears",
"value": 30,
}];
//sort bars based on value
data = data.sort(function (a, b) {
return d3.ascending(a.value, b.value);
})
//set up svg using margin conventions - we'll need plenty of room on the left for labels
var margin = {
top: 15,
right: 25,
bottom: 15,
left: 60
};
var width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var svg = d3.select("#graphic").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var x = d3.scale.linear()
.range([0, width])
.domain([0, d3.max(data, function (d) {
return d.value;
})]);
var y = d3.scale.ordinal()
.rangeRoundBands([height, 0], .3)
.domain(data.map(function (d) {
return d.name;
}));
//make y axis to show bar names
var yAxis = d3.svg.axis()
.scale(y)
//no tick marks
.tickSize(0)
.orient("right");
var gy = svg.append("g")
.attr("class", "y axis")
.call(yAxis)
var bars = svg.selectAll(".bar")
.data(data)
.enter()
.append("g")
.attr("class", "bars")
//append rects
bars.append("rect")
.attr("class", "bar")
.attr("y", function (d) {
return y(d.name);
})
.attr("height", y.rangeBand())
.attr("x", 0)
.attr("width", function (d) {
return x(d.value);
});
//add a value label to the right of each bar
bars.append("text")
.attr("class", "label")
//y position of the label is halfway down the bar
.attr("y", function (d) {
return y(d.name) + y.rangeBand() / 2 + 4;
})
//x position is 3 pixels to the right of the bar
.attr("x", function (d) {
return x(d.value) + 3;
})
.text(function (d) {
return d.value;
});
var labels =
bars.append("text")
.attr("class", "labels")
.attr("y", function (d) {
return y(d.name) + y.rangeBand() / 2 - 30;
})
.attr("x", 0)
.text(function (d) {
return d.name;
});
I had a similar chart that I've made a few modifications to that might fill your requierments, so I'll be basing my answer of my own code.
I'll just go through the most relevant part of the question and you can just have a look at the code and hopefully figure out how it works yourself.
The inital animation works exactly the same way as in the third link you posted:
.transition().duration(speed)
.delay((_, i) => delay * i)
we set a delay so that each bar appear one at a time.
I've also set it up so that you can change the data using the d3js update pattern.
var bar = svg.selectAll(".bar")
.data(data, d => d.name)
bar.exit().remove();
bar.enter().insert("g", ".y-axis").append("rect")
.attr("class", "bar")
.attr("fill", "#ccc")
.attr("x", x(0))
.attr("y", d => y(d.name))
.attr("height", y.bandwidth())
.merge(bar)
.transition().duration(speed)
.delay((_, i) => delay * i)
.attr("y", d => y(d.name))
.attr("width", d => x(d.value) - x(0));
Since you didn't specify how you want to update the new data it's just a year filter for now.
Here's all the code:
var init = [
{"year": "2017", "name": "Apples", "value": 20},
{"year": "2017", "name": "Bananas","value": 12},
{"year": "2017", "name": "Grapes", "value": 19},
{"year": "2017", "name": "Lemons", "value": 5},
{"year": "2017", "name": "Limes", "value": 16},
{"year": "2017", "name": "Oranges", "value": 26},
{"year": "2017", "name": "Pears","value": 30},
{"year": "2018", "name": "Apples", "value": 10},
{"year": "2018", "name": "Bananas","value": 42},
{"year": "2018", "name": "Grapes", "value": 69},
{"year": "2018", "name": "Lemons", "value": 15},
{"year": "2018", "name": "Limes", "value": 26},
{"year": "2018", "name": "Oranges", "value": 36},
{"year": "2018", "name": "Pears","value": 20}
];
chart(init)
function chart(result) {
var format = d3.format(",.0f")
var years = [...new Set(result.map(d => d.year))]
var fruit = [...new Set(result.map(d => d.name))]
var options = d3.select("#year").selectAll("option")
.data(years)
.enter().append("option")
.text(d => d)
var svg = d3.select("#graphic"),
margin = {top: 25, bottom: 10, left: 50, right: 45},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
var x = d3.scaleLinear()
.range([margin.left, width - margin.right])
var y = d3.scaleBand()
.range([margin.top, height - margin.bottom])
.padding(0.1)
.paddingOuter(0.5)
.paddingInner(0.5)
var xAxis = svg.append("g")
.attr("class", "x-axis")
.attr("transform", `translate(0,${margin.top})`)
var yAxis = svg.append("g")
.attr("class", "y-axis")
.attr("transform", `translate(${margin.left},0)`)
update(d3.select("#year").property("value"), 750, 250)
function update(input, speed, delay) {
var data = result.filter(f => f.year == input)
var sum = d3.sum(data, d => d.value)
x.domain([0, d3.max(data, d => d.value)]).nice()
svg.selectAll(".x-axis").transition().duration(speed)
.call(d3.axisTop(x).tickSizeOuter(0));
data.sort((a, b) => b.value - a.value)
y.domain(data.map(d => d.name))
svg.selectAll(".y-axis").transition().duration(speed)
.call(d3.axisLeft(y));
yAxis.selectAll("text").remove()
yAxis.selectAll("line").remove()
var bar = svg.selectAll(".bar")
.data(data, d => d.name)
bar.exit().remove();
bar.enter().insert("g", ".y-axis").append("rect")
.attr("class", "bar")
.attr("fill", "#ccc")
.attr("x", x(0))
.attr("y", d => y(d.name))
.attr("height", y.bandwidth())
.merge(bar)
.transition().duration(speed)
.delay((_, i) => delay * i)
.attr("y", d => y(d.name))
.attr("width", d => x(d.value) - x(0));
var value = svg.selectAll(".value")
.data(data, d => d.name)
value.exit().remove();
value.enter().append("text")
.attr("class", "value")
.attr("opacity", 0)
.attr("dy", 4)
.attr("y", d => y(d.name) + y.bandwidth() / 2)
.merge(value)
.transition().duration(speed)
.delay((_, i) => delay * i)
.attr("opacity", 1)
.attr("y", d => y(d.name) + y.bandwidth() / 2)
.attr("x", d => x(d.value) + 5)
.text(d => format((d.value / sum) * 100) + " %")
var name = svg.selectAll(".name")
.data(data, d => d.name)
name.exit().remove();
name.enter().append("text")
.attr("class", "name")
.attr("opacity", 0)
.attr("dy", -5)
.attr("y", d => y(d.name))
.merge(name)
.transition().duration(speed)
.delay((_, i) => delay * i)
.attr("opacity", 1)
.attr("y", d => y(d.name))
.attr("x", d => x(0) + 5)
.text(d => d.name)
}
var select = d3.select("#year")
.style("border-radius", "5px")
.on("change", function() {
update(this.value, 750, 0)
})
}
body {
margin: auto;
width: 650px;
font: 12px arial;
}
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg id="graphic" width="600" height="380"></svg><br>
Choose year:
<select id="year"></select>
I have a codepen here - https://codepen.io/anon/pen/xpBqxG?editors=1010
I'm doing some simple changes to my starter data and then creating a stacked bar chart.
I'm currently coloring each bar the same color but there should be two colors, one for each key in the stacked chart. Each bar should be spilt into two colors.
How do I color each section of the bar the same color.
.style('fill', (d, i) => {
return colors[i];
});
Apply the style to the groups, not to the rectangles:
let layers = layersArea.selectAll('.layer')
.data(stackedSeries)
.enter()
.append('g')
.attr('class', 'layer')
.style('fill', (d, i) => {
return colors[i];
});
Here is your code with that change only:
let keys = [];
let dataToStack = [];
let totalData = [];
let legendKeys = ['usedInf', 'newInf'];
let w = 800;
let h = 450;
let margin = {
top: 30,
bottom: 40,
left: 50,
right: 20,
};
let width = w - margin.left - margin.right;
let height = h - margin.top - margin.bottom;
let colors = ['#FF9A00', '#FFEBB6', '#FFC400', '#B4EDA0', '#FF4436'];
let data = [{
"one": 10,
"two": 12,
"three": 18,
"four": 22,
"five": 30,
"six": 44,
"seven": 25,
"date": "2015-05-31T00:00:00"
}, {
"one": 30,
"two": 42,
"three": 38,
"four": 62,
"five": 90,
"six": 144,
"seven": 95,
"date": "2015-06-30T00:00:00"
}, {
"one": 30,
"two": 92,
"three": 18,
"four": 100,
"five": 120,
"six": 10,
"seven": 110,
"date": "2015-07-31T00:00:00"
}, ];
for (let i = 0; i < data.length; i++) {
dataToStack.push({
date: data[i]['date'].toString(),
usedInf: data[i]['one'] + data[i]['two'] + data[i]['three'],
newInf: data[i]['four'] + data[i]['five'] + data[i]['six']
});
totalData.push({
date: data[i]['date'].toString(),
total: data[i]['seven']
});
}
let stack = d3.stack()
.keys(legendKeys);
let stackedSeries = stack(dataToStack);
let x = d3.scaleBand()
.domain(dataToStack.map(function(d) {
return d.date;
}))
.rangeRound([0, width])
.padding(0.05);
let y = d3.scaleLinear()
.domain([0, d3.max(stackedSeries, function(d) {
return d3.max(d, (d) => {
return d[1];
})
})])
.range([height, 0]);
let svg = d3.select('.chart').append('svg')
.attr('class', 'chart')
.attr('width', w)
.attr('height', h);
let chart = svg.append('g')
.classed('graph', true)
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
let layersArea = chart.append('g')
.attr('class', 'layers');
let layers = layersArea.selectAll('.layer').data(stackedSeries)
.enter()
.append('g')
.attr('class', 'layer')
.style('fill', (d, i) => {
return colors[i];
});
layers.selectAll('rect')
.data((d) => {
return d
})
.enter()
.append('rect')
.attr('height', (d, i) => {
return y(d[0]) - y(d[1]);
})
.attr('y', (d) => {
return y(d[1]);
})
.attr('x', (d, i) => {
return x(d.data.date)
})
.attr('width', x.bandwidth());
chart.append('g')
.classed('x axis', true)
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
chart.append('g')
.classed('y axis', true)
.call(d3.axisLeft(y)
.ticks(10));
<script src="https://d3js.org/d3.v4.min.js"></script>
<div class="chart"></div>
I have a bar chart with three bars overlaying each other. First the grey bar chart is rendered, then the salmon one and the the blue one. But when they are sorted this order seems to change randomly, so that sometimes a gray bar is drawn over the other two and then they can't seen.
Solution: jsFiddle
Here is a jsfiddle for this problem.
<!DOCTYPE html>
<body>
<div style="text-align:left;">
<form id="form">
<strong>Sort by: </strong><span class="clocktime-radio"><input type="radio" name="stack" checked value="clock">Clock time and place </span>
<span class="racetime-radio"><input class="racetime-radio" type="radio" name="stack" value="race">Race time </span>
<span class="handicap-radio"><input type="radio" name="stack" value="hand">Handicap </span>
</form>
</div>
<div id="race_graph">
</div>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
// set the dimensions and margins of the graph
var margin = {
top: 50,
right: 50,
bottom: 100,
left: 80
},
width = 500 - margin.left - margin.right,
height = 600 - margin.top - margin.bottom;
// Get the data
var data = [{
"name": "RT",
"clocktime": "21:33",
"handicap": "02:30",
"racetime": "19:03"
},
{
"name": "KM",
"clocktime": "22:13",
"handicap": "00:45",
"racetime": "21:28"
},
{
"name": "SD",
"clocktime": "22:15",
"handicap": "01:45",
"racetime": "20:30"
},
{
"name": "DK",
"clocktime": "22:20",
"handicap": "02:45",
"racetime": "19:35"
},
{
"name": "BD",
"clocktime": "22:21",
"handicap": "02:15",
"racetime": "20:06"
},
{
"name": "KC",
"clocktime": "22:21",
"handicap": "02:00",
"racetime": "20:21"
},
{
"name": "PM",
"clocktime": "22:22",
"handicap": "00:45",
"racetime": "21:37"
},
{
"name": "NR",
"clocktime": "22:23",
"handicap": "01:45",
"racetime": "20:38"
},
{
"name": "LM",
"clocktime": "22:25",
"handicap": "02:15",
"racetime": "20:10"
},
{
"name": "SL",
"clocktime": "22:26",
"handicap": "00:15",
"racetime": "22:11"
}
]
var parseTime = d3.timeParse("%M:%S");
var timeformat = d3.timeFormat("%M:%S")
// format the data
data.forEach(function(d) {
d.racetime = parseTime(d.racetime);
d.handicap = parseTime(d.handicap);
d.clocktime = parseTime(d.clocktime);
d.place = +d.place;
d.points = +d.points;
d.raceplace = +d.raceplace;
d.timeplace = +d.timeplace;
});
// set the domains and ranges
var x = d3.scaleBand()
.domain(data.map(function(d) {
return d.name
}))
.range([0, width]);
// temporal y-scale
var y = d3.scaleTime()
.domain([parseTime('00:00'), d3.max(data, function(d) {
return d.clocktime
// return d.handicap
})])
.range([height, 0]); //time must increase from 0 to height else racetime and handicap are inverted!!!
// spacial y-scale (race distance)
var y1 = d3.scaleLinear()
.domain([0, 1200]) //race distance
.range([height, 0]);
// points y-scale
var y2 = d3.scaleLinear()
.domain([0, 10]) //points awarded
.range([height, 0]);
//****************************
//***** Main Graph setup *****
//****************************
var svg = d3.select("#race_graph")
.data(data)
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// Add the X Axis
var xAxis = d3.axisBottom(x)
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.style("font", "7px times")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", "rotate(-65)");
// Add the left Y Axis
svg.append("g")
.attr("class", "axis")
.call(d3.axisLeft(y)
.ticks(7)
.tickFormat(d3.timeFormat("%M:%S")));
// text label for the y axis on left
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0 - margin.left)
.attr("x", 0 - (height / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.text("Time (minutes:seconds)");
//************************************************************
//******* BarChart by clocktime *******************************
//************************************************************
var rects0 = svg.selectAll(".bar")
.data(data);
var newRects0 = rects0.enter();
// Note: y2(d.points) is the y coordinate of rect
newRects0.append('rect')
.attr('x', function(d, i) {
return x(d.name) + 2;
})
.attr('width', 20)
.attr("transform", "translate(5,0)")
.style('fill', 'gray')
.attr("class", "bar")
.attr('y', function(d, i) {
return y(d.clocktime);
})
.attr('height', function(d, i) {
return height - y(d.clocktime)
});
//************************************************************
//******* BarChart by racetime *******************************
//************************************************************
var newRects1 = rects0.enter();
// Note: y2(d.points) is the y coordinate of rect
newRects1.append('rect')
.attr('class', 'bar')
.attr('x', function(d, i) {
return x(d.name) + 2;
})
.attr('width', 20)
.attr("transform", "translate(5,0)")
.style('fill', 'salmon')
.attr('y', function(d, i) {
return y(d.racetime);
})
.attr('height', function(d, i) {
return height - y(d.racetime)
});
//************************************************************
//******* BarCharts by handicap ******************************
//************************************************************
var newRects2 = rects0.enter();
newRects2.append('rect')
.attr('x', function(d, i) {
return x(d.name) + 2;
})
.attr('width', 20)
.attr("transform", "translate(5,0)")
.style('fill', 'blue')
.attr('y', function(d, i) {
return y(d.handicap);
})
.attr('height', function(d, i) {
return height - y(d.handicap)
})
.attr('class', 'bar');
d3.selectAll("input[name='stack']").on("change", change);
function change() {
var x0 = x.domain(data.sort(this.value == "clock" ?
(function(a, b) {
return (new Date(b.clocktime) - new Date(a.clocktime)) * -1;
}) : (this.value == "race") ?
(function(a, b) {
return (new Date(b.racetime) - new Date(a.racetime)) * -1;
}) : (function(a, b) {
return (new Date(b.handicap) - new Date(a.handicap)) * -1;
})).map(function(d) {
return d.name;
}))
.copy();
svg.selectAll(".bar")
.sort(function(a, b) {
return x0(a.name) - x0(b.name);
});
var transition = svg.transition().duration(750),
delay = function(d, i) {
return i * 5;
};
transition.selectAll(".bar")
.delay(delay)
.attr("x", function(d) {
return x0(d.name);
});
transition.select(".x.axis") //selects the x-axis
.call(xAxis)
.selectAll("g")
.delay(delay);
}
</script>
</body>
Maybe when I draw multiple bar charts I should not repeat the code for each as I do and that there is some way to more efficiently draw them which will get rid of the render issue I am having.
Thanks
I have a task to make a d3 graph that should look like the picture below
I started to mock up the graph in codepen: http://codepen.io/Balzzac/pen/YNZqrP?editors=0010 , but I ran into 2 problems that I don't know how to solve:
1) how to make tooltips with names of people (from the dataset);
2) how to make a second vertical axis with a second set of values setOfValues?
My js code:
var setOfValues = ["Value4", "Value5", "Value6"];
var margins = {
top: 30,
left: 100,
right: 20,
bottom: 0
};
var legendPanel = {
width: 0
};
var width = 500 - margins.left - margins.right - legendPanel.width;
var height = 80 - margins.top - margins.bottom
var dataset = [{
data: [{
value: 'Value1',
count: 3,
people: "Anna, Maria, Peter",
}, {
value: 'Value2',
count: 3,
people: "Michael, Martin, Joe",
}, {
value: 'Value3',
count: 2,
people: "Martin, Joe",
}]
}, {
data: [{
value: 'Value1',
count: 2,
people: "Luis, Kim",
}, {
value: 'Value2',
count: 1,
people: "Richard",
}, {
value: 'Value3',
count: 4,
people: "Michael, Martin, Joe, Maria",
}]
}
, {
data: [{
value: 'Value1',
count: 1,
people: "Linda",
}, {
value: 'Value2',
count: 2,
people: "Ben",
}, {
value: 'Value3',
count: 0,
people: "",
}]
}
];
dataset = dataset.map(function (d) {
return d.data.map(function (o, i) {
return {
y: o.count,
x: o.value
};
});
});
var stack = d3.layout.stack();
stack(dataset);
var dataset = dataset.map(function (group) {
return group.map(function (d) {
return {
x: d.y,
y: d.x,
x0: d.y0
};
});
});
var numberOfPeople = 6;
var svg = d3.select('body')
.append('svg')
.attr('width', width + margins.left + margins.right + legendPanel.width)
.attr('height', height + margins.top + margins.bottom)
.append('g')
.attr('transform', 'translate(' + margins.left + ',' + margins.top + ')');
var xMax = numberOfPeople;
var xScale = d3.scale.linear()
.domain([0, xMax])
.range([0, width]);
var values = dataset[0].map(function (d) {
return d.y;
});
var yScale = d3.scale.ordinal()
.domain(values)
.rangeRoundBands([0, height], .2);
var xAxis = d3.svg.axis()
.scale(xScale)
.orient('top')
.tickFormat(function(d) { return parseInt(d, 10) })
.ticks(xMax);
var yAxis = d3.svg.axis()
.scale(yScale)
.outerTickSize(0)
.orient('left');
var colors = d3.scale.ordinal().range(["#3E7EAB","#D89218","#EEEEEE"]);
var groups = svg.selectAll('g')
.data(dataset)
.enter()
.append('g')
.style('fill', function (d, i) {
return colors(i);
});
var rects = groups.selectAll('rect')
.data(function (d) {return d; })
.enter()
.append('rect')
.attr('x', function (d) {return xScale(d.x0);})
.attr('y', function (d, i) {return yScale(d.y);})
.attr('height', function (d) {return yScale.rangeBand();})
.attr('width', function (d) {return xScale(d.x);})
.on('mouseover', function (d) {
var xPos = parseFloat(d3.select(this).attr('x')) / 2 + width / 2;
var yPos = parseFloat(d3.select(this).attr('y')) + yScale.rangeBand() / 2;
d3.select('#tooltip')
.style('left', xPos + 'px')
.style('top', yPos + 'px')
.select('#value')
//Question 1: "How to show in tooltip names of people??"
.text("How to show here names of people??");
d3.select('#tooltip').classed('hidden', false);
})
.on('mouseout', function () {d3.select('#tooltip').classed('hidden', true); });
svg.append('g')
.attr('class', 'axis')
.call(yAxis);
svg.append('g')
.attr('class', 'axis')
.call(xAxis);
Result of the code:
I really appreciate your help.
When you map dataset, add the people property to it (and do the same in the second map):
dataset = dataset.map(function(d) {
return d.data.map(function(o, i) {
return {
people: o.people,
y: o.count,
x: o.value
};
});
});
After that, you'll have the people property in the bound data. Thus, just change the text to:
.text(d.people);
Here is your updated code:
var setOfValues = ["Value4", "Value5", "Value6"];
var margins = {
top: 30,
left: 100,
right: 20,
bottom: 0
};
var legendPanel = {
width: 0
};
var width = 500 - margins.left - margins.right - legendPanel.width;
var height = 80 - margins.top - margins.bottom
var dataset = [{
data: [{
value: 'Value1',
count: 3,
people: "Anna, Maria, Peter",
}, {
value: 'Value2',
count: 3,
people: "Michael, Martin, Joe",
}, {
value: 'Value3',
count: 2,
people: "Martin, Joe",
}]
}, {
data: [{
value: 'Value1',
count: 2,
people: "Luis, Kim",
}, {
value: 'Value2',
count: 1,
people: "Richard",
}, {
value: 'Value3',
count: 4,
people: "Michael, Martin, Joe, Maria",
}]
}
, {
data: [{
value: 'Value1',
count: 1,
people: "Linda",
}, {
value: 'Value2',
count: 2,
people: "Ben",
}, {
value: 'Value3',
count: 0,
people: "",
}]
}
];
dataset = dataset.map(function (d) {
return d.data.map(function (o, i) {
return {
people: o.people,
y: o.count,
x: o.value
};
});
});
var stack = d3.layout.stack();
stack(dataset);
var dataset = dataset.map(function (group) {
return group.map(function (d) {
return {
people: d.people,
x: d.y,
y: d.x,
x0: d.y0
};
});
});
var numberOfPeople = 6;
var svg = d3.select('body')
.append('svg')
.attr('width', width + margins.left + margins.right + legendPanel.width)
.attr('height', height + margins.top + margins.bottom)
.append('g')
.attr('transform', 'translate(' + margins.left + ',' + margins.top + ')');
var xMax = numberOfPeople;
var xScale = d3.scale.linear()
.domain([0, xMax])
.range([0, width]);
var values = dataset[0].map(function (d) {
return d.y;
});
var yScale = d3.scale.ordinal()
.domain(values)
.rangeRoundBands([0, height], .2);
var xAxis = d3.svg.axis()
.scale(xScale)
.orient('top')
.tickFormat(function(d) { return parseInt(d, 10) })
.ticks(xMax);
var yAxis = d3.svg.axis()
.scale(yScale)
.outerTickSize(0)
.orient('left');
var colors = d3.scale.ordinal().range(["#3E7EAB","#D89218","#EEEEEE"]);
var groups = svg.selectAll('g')
.data(dataset)
.enter()
.append('g')
.style('fill', function (d, i) {
return colors(i);
});
var rects = groups.selectAll('rect')
.data(function (d) {return d; })
.enter()
.append('rect')
.attr('x', function (d) {return xScale(d.x0);})
.attr('y', function (d, i) {return yScale(d.y);})
.attr('height', function (d) {return yScale.rangeBand();})
.attr('width', function (d) {return xScale(d.x);})
.on('mouseover', function (d) {
var xPos = parseFloat(d3.select(this).attr('x')) / 2 + width / 2;
var yPos = parseFloat(d3.select(this).attr('y')) + yScale.rangeBand() / 2;
d3.select('#tooltip')
.style('left', xPos + 'px')
.style('top', yPos + 'px')
.select('#value')
//Question 1: "How to show in tooltip names of people??"
.text(d.people);
d3.select('#tooltip').classed('hidden', false);
})
.on('mouseout', function () {d3.select('#tooltip').classed('hidden', true); });
svg.append('g')
.attr('class', 'axis')
.call(yAxis);
svg.append('g')
.attr('class', 'axis')
.call(xAxis);
.axis path, .axis line {
fill: none;
stroke: black;
shape-rendering: crispEdges;
}
.axis text {
font-family: sans-serif;
font-size: 11px;
}
#tooltip {
position: absolute;
text-align: left;
height: auto;
padding: 10px;
background: #162F44;
pointer-events: none;
}
#tooltip.hidden {
display: none;
}
#tooltip p {
margin: 0;
font-family: sans-serif;
font-size: 11px;
color: white;
line-height: 15px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="tooltip" class="hidden">
<p><span id="value"></span>
</p>
</div>
PS: Regarding your second question ("how to make a second vertical axis?"), your desired outcome is not exactly clear. Besides that, as it's not a good practice asking more than one question in a single post, I suggest you post another question, better explaining your problem.
I am trying to create multi line text as nodes of the directed graph as :
<rect height="27" width="56" rx="100" ry="100" style="fill: #ffffff;"></rect>
<text dx="6" dy="6">
<tspan x="0" dy="15">Donovan</tspan>
<tspan x="0" dy="15">3</tspan>
<tspan x="0" dy="15">what</tspan>
</text>
as seen in: http://jsfiddle.net/nikosdim/S4eaL/1/
I currently have this:
// setting up parameters to be use through rest of the code
var w = 2000;
var h = 800;
var r = 30;
var linkDistance = 100;
var boxHeight = 50;
var boxWidth = 50;
var colors = d3.scale.category10();
// This is what how we should be setting gravity, theta and charge ideally: http://stackoverflow.com/questions/9901565/charge-based-on-size-d3-force-layout
var charge = -5000;
var gravity = 0.3;
var theta = 0.01;
var dataset = {
nodes: [
{ "name": "Adam", "id": "0" },
{ "name": "Bob", "id": "1" },
{ "name": "Carrie", "id": "2" },
{ "name": "Donovan", "id": "3" },
{ "name": "Edward", "id": "4" },
{ "name": "Felicity", "id": "5" },
{ "name": "George", "id": "6" },
{ "name": "Hannah", "id": "7" },
{ "name": "Iris", "id": "8" },
{ "name": "Jerry", "id": "9" }
],
edges: [
{ "source": 0, "target": 4 },
{ "source": 1, "target": 5 },
{ "source": 2, "target": 5 },
{ "source": 2, "target": 5 },
{ "source": 5, "target": 8 },
{ "source": 5, "target": 9 },
{ "source": 6, "target": 7 },
{ "source": 7, "target": 8 },
{ "source": 8, "target": 9 }
]
};
var svg = d3.select("body").append("svg").attr({ "width": w, "height": h });
var force = d3.layout.force()
.nodes(dataset.nodes)
.links(dataset.edges)
.size([w, h])
.linkDistance([linkDistance])
.charge(charge)
.theta(theta)
.gravity(gravity);
var edges = svg.selectAll("line")
.data(dataset.edges)
.enter()
.append("line")
.attr("id", function (d, i) { return 'edge' + i; })
.attr('marker-end', 'url(#arrowhead)')
.style("stroke", "#ccc")
.style("pointer-events", "none");
var nodes = svg.selectAll("rect")
.data(dataset.nodes)
.enter()
.append("rect")
.attr({ "width": boxWidth })
.attr({ "height": boxHeight })
//.style("fill", function (d, i) { return colors(i); })
.style("fill", 'white')
.attr('stroke', 'black')
.call(force.drag);
var nodelabels = svg.selectAll(".nodelabel")
.data(dataset.nodes)
.enter()
.append("text")
.attr({
//"x": function (d) { return d.x; },
//"y": function (d) { return d.y; },
"class": "nodelabel",
"stroke": "black"
});
var edgepaths = svg.selectAll(".edgepath")
.data(dataset.edges)
.enter()
.append('path')
.attr({
//'d': function (d) { return 'M ' + d.source.x + ' ' + d.source.y + ' L ' + d.target.x + ' ' + d.target.y; },
'class': 'edgepath',
'fill-opacity': 0,
'stroke-opacity': 0,
'fill': 'blue',
'stroke': 'red',
'id': function (d, i) { return 'edgepath' + i; }
})
.style("pointer-events", "none");
var edgelabels = svg.selectAll(".edgelabel")
.data(dataset.edges)
.enter()
.append('text')
.style("pointer-events", "none")
.attr({
'class': 'edgelabel',
'id': function (d, i) { return 'edgelabel' + i; },
'dx': 80,
'dy': 0,
'font-size': 10,
'fill': '#aaa'
});
edgelabels.append('textPath')
.attr('xlink:href', function (d, i) { return '#edgepath' + i; })
.style("pointer-events", "none")
.text(function (d, i) { return 'label ' + i; });
svg.append('defs').append('marker')
.attr({
'id': 'arrowhead',
'viewBox': '-0 -5 10 10',
'refX': 25,
'refY': 0,
//'markerUnits':'strokeWidth',
'orient': 'auto',
'markerWidth': 10,
'markerHeight': 10,
'xoverflow': 'visible'
})
.append('svg:path')
.attr('d', 'M 0,-5 L 10 ,0 L 0,5')
.attr('fill', '#ccc')
.attr('stroke', '#ccc');
force.on("tick", tick).start();
function ConstrainX(point) {
return Math.max(r, Math.min(w - r, point));
}
function ConstrainY(point) {
return Math.max(r, Math.min(h - r, point));
}
function tick(e) {
// Push sources up and targets down to form a weak tree.
var k = 60 * e.alpha;
dataset.edges.forEach(function (d, i) {
d.source.y -= k;
d.target.y += k;
});
edges.attr({
"x1": function (d) { return ConstrainX(d.source.x); },
"y1": function (d) { return ConstrainY(d.source.y); },
"x2": function (d) { return ConstrainX(d.target.x); },
"y2": function (d) { return ConstrainY(d.target.y); }
});
nodes.attr({
"x": function (d) { return ConstrainX(d.x) - boxWidth / 2; },
"y": function (d) { return ConstrainY(d.y) - boxHeight / 2; }
});
// appending boxWidth/2 to make sure the labels are within the box
nodelabels.attr("x", function (d) { return ConstrainX(d.x) - boxWidth / 2; })
.attr("y", function (d) { return ConstrainY(d.y); });
edgepaths.attr('d', function (d) {
var path = 'M ' + ConstrainX(d.source.x) + ' ' + ConstrainY(d.source.y) + ' L ' + ConstrainX(d.target.x) + ' ' + ConstrainY(d.target.y);
//console.log(d)
return path;
});
edgelabels.attr('transform', function (d, i) {
if (d.target.x < d.source.x) {
bbox = this.getBBox();
rx = bbox.x + bbox.width / 2;
ry = bbox.y + bbox.height / 2;
return 'rotate(180 ' + rx + ' ' + ry + ')';
}
else {
return 'rotate(0)';
}
});
var insertLinebreaks = function (d) {
var el = d3.select(this);
var name = d.name;
var id = d.id;
el.text('');
//for (var i = 0; i < words.length; i++) {
var tspan = el.append('tspan').text(name);
tspan = el.append('tspan').text(id);
//if (i > 0)
tspan.attr('x', 0);
tspan.attr('dy', '15');
tspan = el.append('tspan').text('what');
tspan.attr('x', '0');
tspan.attr('dy', '15');
//}
};
nodelabels.each(insertLinebreaks); <== Insert new lines
}
But this is messing new lines in the nodes. Once I insert the new lines the text shows up left aligned at the start of the screen. This is not what I want. I wanted the text to be aligned in the node as shown in the first image.
This is the output using the above code:
http://jsfiddle.net/6afc2vp8/
// **********start configuration***************
var dataset = {
"Nodes": [
{
"ID": 0,
"Identity": "af12689c-de83-4a0d-a63d-1f548fd02e26",
"RoleInstance": "RoleInstance",
"LogonID": "LogonID1",
"WeightedScore": 120,
"AccountInstance": "AccountInstance1",
"MinTimestampPst": "2014-11-19T17:08:46.6797242-05:00"
},
{
"ID": 1,
"Identity": "a5bd36db-00e6-4492-92d7-49278f0046a7",
"RoleInstance": "RoleInstance2",
"LogonID": "LogonID2",
"WeightedScore": 100,
"AccountInstance": "AccountInstance2",
"MinTimestampPst": "2014-11-19T17:08:46.6797242-05:00"
},
{
"ID": 2,
"Identity": "a5bd36db-00e6-4492-92d7-49278f0046a7",
"RoleInstance": "RoleInstance2",
"LogonID": "LogonID2",
"WeightedScore": 100,
"AccountInstance": "AccountInstance2",
"MinTimestampPst": "2014-11-19T17:08:46.6797242-05:00"
},
{
"ID": 3,
"Identity": "a5bd36db-00e6-4492-92d7-49278f0046a7",
"RoleInstance": "RoleInstance2",
"LogonID": "LogonID2",
"WeightedScore": 100,
"AccountInstance": "AccountInstance2",
"MinTimestampPst": "2014-11-19T17:08:46.6797242-05:00"
}
],
"Edges": [
{
"source": 0,
"target": 1
}, {
"source": 3,
"target": 2
}
]
};
// select all the features from the node that you want in the output
var nodelabelslist = ["ID", "Identity", "RoleInstance", "LogonID", "WeightedScore", "AccountInstance", "MinTimestampPst"];
var nodelabelslistlength = nodelabelslist.length;
// this is the rectangle height and width
var boxHeight = nodelabelslistlength * 20;
var boxWidth = 400;
var nodelabelverticaloffset = 15;
var nodelabelhorizontaloffset = 5;
var labelStartPos = -(nodelabelslistlength/2 - 1);
// setting up d3js parameters to be use through rest of the code
var w = 2000;
var h = 800;
var r = 30;
var linkDistance = 200;
var colors = d3.scale.category10();
// This is what how we should be setting gravity, theta and charge ideally: http://stackoverflow.com/questions/9901565/charge-based-on-size-d3-force-layout
var charge = -6000;
var gravity = 0.10;
var theta = 0.01;
// **********end configuration***************
var jsonNodes = dataset.Nodes;
var jsonEdges = dataset.Edges;
var svg = d3.select("body").append("svg").attr({ "width": w, "height": h });
var force = d3.layout.force()
.nodes(jsonNodes)
.links(jsonEdges)
.size([w, h])
.linkDistance([linkDistance])
.charge(charge)
.theta(theta)
.gravity(gravity);
var edges = svg.selectAll("line")
.data(jsonEdges)
.enter()
.append("line")
.attr("id", function (d, i) { return 'edge' + i; })
.attr('marker-end', 'url(#arrowhead)')
.style("stroke", "#000")
.style("pointer-events", "none");
var nodes = svg.selectAll("rect")
.data(jsonNodes)
.enter()
.append("rect")
//.style("fill", function (d, i) { return colors(i); })
.style("fill", 'none')
.attr('stroke', 'black')
.call(force.drag);
var nodelabelsarr = [];
for (var index = 0; index < nodelabelslist.length; ++index) {
var nodelabels = svg.selectAll(".nodelabel" + index)
.data(jsonNodes)
.enter()
.append("text")
.attr({
"class": "nodelabel",
"stroke": "black"
})
.text(function (d) { return nodelabelslist[index] + ": " + d[nodelabelslist[index]]; });
nodelabelsarr[index] = nodelabels;
}
var edgepaths = svg.selectAll(".edgepath")
.data(jsonEdges)
.enter()
.append('path')
.attr({
'class': 'edgepath',
'fill-opacity': 0,
'stroke-opacity': 0,
'fill': 'blue',
'stroke': 'red',
'id': function (d, i) { return 'edgepath' + i; }
})
.style("pointer-events", "none");
var edgelabels = svg.selectAll(".edgelabel")
.data(jsonEdges)
.enter()
.append('text')
.style("pointer-events", "none")
.attr({
'class': 'edgelabel',
'id': function (d, i) { return 'edgelabel' + i; },
'dx': 80,
'dy': 0,
'font-size': 10,
'fill': '#000'
});
edgelabels.append('textPath')
.attr('xlink:href', function (d, i) { return '#edgepath' + i; })
.style("pointer-events", "none")
.text(function (d, i) { return 'label ' + i; });
svg.append('defs').append('marker')
.attr({
'id': 'arrowhead',
'viewBox': '-0 -5 10 10',
'refX': 25,
'refY': 0,
//'markerUnits':'strokeWidth',
'orient': 'auto',
'markerWidth': 10,
'markerHeight': 10,
'xoverflow': 'visible'
})
.append('svg:path')
.attr('d', 'M 0,-5 L 10 ,0 L 0,5')
.attr('fill', '#000')
.attr('stroke', '#000');
force.on("tick", tick).start();
function ConstrainX(point) {
return Math.max(r, Math.min(w - r, point));
}
function ConstrainY(point) {
return Math.max(r, Math.min(h - r, point));
}
function edgeArc(d) {
var path = 'M ' + ConstrainX(d.source.x) + ' ' + ConstrainY(d.source.y) + ' L ' + ConstrainX(d.target.x) + ' ' + ConstrainY(d.target.y);
//console.log(d)
return path;
}
function edgelabeltransform(d) {
if (d.target.x < d.source.x) {
var bbox = this.getBBox();
var rx = bbox.x + bbox.width / 2;
var ry = bbox.y + bbox.height / 2;
return 'rotate(180 ' + rx + ' ' + ry + ')';
}
else {
return 'rotate(0)';
}
}
function tick(e) {
// Push sources up and targets down to form a weak tree.
var k = 60 * e.alpha;
jsonEdges.forEach(function (d) {
d.source.y -= k;
d.target.y += k;
});
edges.attr({
"x1": function (d) { return ConstrainX(d.source.x); },
"y1": function (d) { return ConstrainY(d.source.y); },
"x2": function (d) { return ConstrainX(d.target.x); },
"y2": function (d) { return ConstrainY(d.target.y); }
});
nodes.attr({
"x": function (d) { return ConstrainX(d.x) - boxWidth / 2; },
"y": function (d) { return ConstrainY(d.y) - boxHeight / 2; }
})
.attr({ "width": boxWidth })
.attr({ "height": boxHeight });
// appending boxWidth/2 to make sure the labels are within the box
var startOffset = labelStartPos;
for (var index = 0; index < nodelabelsarr.length; index++) {
nodelabelsarr[index].attr("x", function (d) { return ConstrainX(d.x) - boxWidth / 2 + nodelabelhorizontaloffset; })
.attr("y", function (d) { return ConstrainY(d.y) + (nodelabelverticaloffset * startOffset); });
startOffset++;
}
edgepaths.attr('d', edgeArc);
edgelabels.attr('transform', edgelabeltransform);
}