I've been trying to create a horizontal legend for my graph using d3.js. I've struggled to get the x-axis spacing correct with dynamic labels.
The problem is that the labels are not of a consistent width, here's a full example and this is my function to calculate the x position:
function legendXPosition(data, position, avgFontWidth){
if(position == 0){
return 0;
} else {
var xPostiion = 0;
for(i = 0; i < position; i++){
xPostiion += (data[i].length * avgFontWidth);
}
return xPostiion;
}
}
Does anyone have any suggestions on how to improve this?
I suggest referencing this question: SVG get text element width
Render the first legend entry as you already are. Store this entry, or assign ids such that you can look them up through selection.
When rendering subsequent entries, get the previous 'text' element and x offset. Compute the new legend entry offset using the exact width of the previous text element
var myNewXOffset = myPreviousXOffset + myPreviousText.getBBox().width
Modified the code to get the following result
The reason your labels are not of a consistent width is due to two factors. Both of them are related to your function to calculate the x position:
the avgFontWidth parameter
It should be smaller. You assigned it the value of 15 in your example, but the actual averageFontWidth is way smaller than 15, it was between 5 to 7. The extra space between your legend labels come from this inaccurate value of average font width.
Your avgFontWidth is just an average. In reality, fonts width change.For example, the r in the picture below is way narrower than the P.
Thus the five letter word rrrrr can be way shorter than PPPPP when the font family is set in some way. In conclusion, you shall not use avgFontWidth. You should use SVG get text element width as cmonkey suggests
the logic of xPostiion += (data[i].length * avgFontWidth)
The above formula intends to calculate the length of the text, but the width of the label shall also be added, xPostiion should be
xPostiion += (data[i].length * avgFontWidth) + widthOftheLabel
The modified code:
function drawlinegraph(data, startDateString, endDateString, maxValue, minValue, colour,
divID, labels) {
var m = {
top: 60,
right: 0,
bottom: 35,
left: 80
},
w = 770 - m.left - m.right,
h = 180 - m.top - m.bottom,
dateFormat = "%Y-%m-%d";
labels = ["ada", "adas", "asdasdasd", "sd"];
var parseDate = d3.time.format(dateFormat).parse;
var startDate = parseDate(startDateString);
var endDate = parseDate(endDateString);
// Define the x scale
var x = d3.time.scale()
.domain([startDate, endDate])
.range([0, w]);
// Format x-axis labels
x.tickFormat(d3.time.format(dateFormat));
// Define the y scale
var y = d3.scale.linear()
.domain([minValue, maxValue])
.range([h, 0]);
var graph = d3.select(divID).append("svg:svg")
.attr("width", w + m.right + m.left)
.attr("height", h + m.top + m.bottom)
.append("svg:g")
.attr("transform", "translate(" + m.left + "," + m.top + ")");
// create x-axis
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(4)
.tickSize(-h)
.tickFormat(d3.time.format("%Y/%m"));
// Add the x-axis.
graph.append("svg:g")
.attr("class", "x axis")
.attr("transform", "translate(0," + h + ")")
.call(xAxis);
// Overide the default behaviour of d3 axis labels
d3.selectAll(".x.axis g text")[0].forEach(function(e) {
e.attributes[0].value = 8; // y
});
// create y-axis
var yAxisLeft = d3.svg.axis()
.scale(y)
.ticks(12)
.orient("left")
.tickSize(-w);
// Add the y-axis to the left
graph.append("svg:g")
.attr("class", "y axis")
.attr("transform", "translate(0,0)")
.call(yAxisLeft);
var i = 0;
$.each(data, function(key, value) {
value.forEach(function(d) {
d.date = parseDate(d.date);
});
var line = d3.svg.line()
.x(function(d) {
return x(d.date);
})
.y(function(d) {
return y(d.value);
});
graph.append("path")
.datum(value)
.attr("d", line)
.attr("class", "line")
.attr("stroke", colour[i]);
i++;
});
var legend = graph.append("g")
.attr("class", "legend")
.attr("height", 100)
.attr("width", 100)
.attr('transform', 'translate(-5,' + (h + 35) + ')');
legend.selectAll('rect')
.data(labels)
.enter()
.append("rect")
.attr("x", function(d, i) {
var xPost = legendXPosition(labels, i, 6);
return xPost;
})
.attr("y", -6)
.attr("width", 20)
.attr("height", 5)
.style("fill", function(d, i) {
var color = colour[i];
return color;
});
legend.selectAll('text')
.data(labels)
.enter()
.append("text")
.attr("x", function(d, i) {
var xPost = legendXPositionText(labels, i, 22, 6);
return xPost;
})
.attr("y", -1)
.text(function(d) {
return d;
});
};
function legendXPositionText(data, position, textOffset, avgFontWidth) {
return legendXPosition(data, position, avgFontWidth) + textOffset;
}
function legendXPosition(data, position, avgFontWidth) {
if (position == 0) {
return 0;
} else {
var xPostiion = 0;
for (i = 0; i < position; i++) {
xPostiion += (data[i].length * avgFontWidth + 40);
}
return xPostiion;
}
}
var benchmark_line_graph_colours = ["#524364", "#937ab1", "#ab5b02", "#faa757"],
benchmark_line_graph_data = {
"Beassa ALBI TR ZAR": [{
"date": "2012-08-31",
"value": 101.1
}, {
"date": "2012-09-28",
"value": 101.89
}, {
"date": "2012-10-31",
"value": 101.09
}],
"FTSE/JSE All Share TR ZAR": [{
"date": "2012-08-31",
"value": 99.72
}, {
"date": "2012-09-28",
"value": 101.24
}, {
"date": "2012-10-31",
"value": 105.29
}],
"STeFI Composite ZAR": [{
"date": "2012-08-31",
"value": 100.23
}, {
"date": "2012-09-28",
"value": 100.52
}, {
"date": "2012-10-31",
"value": 100.77
}],
"portfolio": [{
"date": "2012-08-31",
"value": 101.55
}, {
"date": "2012-09-28",
"value": 101.15
}, {
"date": "2012-10-31",
"value": 102.08
}]
};
drawlinegraph(benchmark_line_graph_data,
"2012-08-31",
"2012-10-31",
105.84700000000001,
99.163, benchmark_line_graph_colours,
"body");
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis text {
padding-top: 5px;
text-anchor: "right";
}
.line {
fill: none;
stroke-width: 1.5px;
}
.y.axis line,
.y.axis path {
stroke-dasharray: 2, 2;
}
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<script src="http://d3js.org/d3.v3.js"></script>
Related
I'm trying to create a kind of scatterplot with the following data.
array = [{
key: "6S",
values: {3: [{Id: "1234a"}, {Id: "1234b"}]}
},
{
key: "7S",
values: {5: [{Id: "1534a"}],4: [{Id: "1534a"}]}
}
]
The x axis represents the "key" value ("6S" and "7S" in the array) and the y axis the key from the values ("3", "5", "4"..). x is defined as scalBand and y as scaleLinear. If the key from the values has 2 objects (in our example "3" has 2 objects) I want to add 2 points side by side.
view1.selectAll("circle")
.data(array)
.enter()
.append("circle")
.attr("r", 2.5)
.attr("cx", function(d) { return x1(d.key); }) //must return something else
.attr("width", x1.bandwidth())
.attr("cy", function(d) { return y1(Object.keys(d.values))})
.attr("height", function(d) {return height-y1(Object.keys(d.values))});
The domain from x is:
x.domain(data.map(function(d) {
return d.key;
}));
from y:
y1.domain([0, 200]);
Any idea how I could return the x-axis position?
I was able to solve this by doing the following.
Change the structure of the data and create separate data points by splitting the values(the values key in your object) into a different array newArray.
Create an array of the cx and cy attributes of your circles to check if there are any overlaps. If yes, shift the cx of the overlapping circle by say 10px.
Here's the fiddle:
var svgWidth = 1300,
svgHeight = 600;
var width = 600,
height = 250;
var margin2 = {
top: 10,
right: 20,
bottom: 20,
left: 40
};
//Create SVG
var svg = d3.select("body")
.append("svg")
.attr("width", svgWidth)
.attr("height", svgHeight);
var view1 = svg.append("g")
.attr("class", "view1")
.attr("transform", "translate(" + margin2.left + "," + margin2.top + ")");
var array = [{
key: "6S",
values: {
3: [{
Id: "1234a"
}, {
Id: "1234b"
}]
}
},
{
key: "7S",
values: {
5: [{
Id: "1534a"
}],
4: [{
Id: "1534a"
}]
}
}
];
var newArray = [];
array.forEach(function(d) {
if (Object.keys(d.values).length > 1) {
for (i in Object.keys(d.values)) {
var val = {}
val["key"] = d.key
val['values'] = {}
val['values'][Object.keys(d.values)[i]] = d.values[Object.keys(d.values)[i]]
newArray.push(val)
}
} else {
if (d.values[Object.keys(d.values)[0]].length > 1) {
for (i in d.values[Object.keys(d.values)[0]]) {
var val = {}
val["key"] = d.key
val['values'] = {}
val['values'][Object.keys(d.values)[0]] = [d.values[Object.keys(d.values)[0]][i]]
newArray.push(val)
}
} else {
newArray.push(d)
}
}
});
var cxList = [];
var x1 = d3.scaleBand().range([0, width]).padding(0.2),
y1 = d3.scaleLinear().range([height, 0]);
var xAxis1 = d3.axisBottom(x1),
yAxis1 = d3.axisLeft(y1);
x1.domain(newArray.map(function(d) {
return d.key;
}));
y1.domain([0, 10]);
//Add the "points"
view1.selectAll("circle")
.data(newArray)
.enter()
.append("circle")
.attr("class", "bar2")
.attr("r", 2.5)
.attr("cx", function(d) {
if (cxList.indexOf(x1(d.key) + '-' + y1(Object.keys(d.values))) > -1) {
cxList.push((x1(d.key) + 10) + '-' + y1(Object.keys(d.values)));
return x1(d.key) + 10;
} else {
cxList.push(x1(d.key) + '-' + y1(Object.keys(d.values)));
return x1(d.key);
}
})
.attr("width", x1.bandwidth())
.attr("cy", function(d) {
return y1(Object.keys(d.values))
})
.attr("height", function(d) {
return height - y1(Object.keys(d.values))
});
//Add x Axis & rotate text
view1.append("g")
.attr("transform", "translate(0," + height + ")")
.call(xAxis1)
.selectAll("text")
.attr("x", 2.5)
.attr("y", 5)
.attr("transform", "rotate(25)")
.style("text-anchor", "start");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<style>
body {
font: 11px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.dot {
stroke: #000;
}
.tooltip {
position: absolute;
width: 200px;
height: 28px;
pointer-events: none;
}
</style>
<body>
<svg width='500' height='500'></svg>
</body>
The code you provided still has some stuff missing. The circles and the axis look a little off in my code but should work fine in yours.
In my d3 bar chart, I should have a top Y-axis tick (with horizontal grid line) above the tallest bar if it goes above the last tick. This is achieved by calculating the last tick, then applied using tickValues().
Also there should be maximum 5 ticks and grid lines including the x-axis domain (0). I have tried this using ticks() but it is not working with tickValues(). Any solution for this?
// container size
var margin = {top: 10, right: 10, bottom: 30, left: 30},
width = 400,
height = 300;
var data = [
{"month":"DEC","setup":{"count":26,"id":1,"label":"Set Up","year":"2016","graphType":"setup"}},
{"month":"JAN","setup":{"count":30,"id":1,"label":"Set Up","year":"2017","graphType":"setup"}},
{"month":"FEB","setup":{"count":30,"id":1,"label":"Set Up","year":"2017","graphType":"setup"}}];
var name = 'dashboard';
// x scale
var xScale = d3.scale.ordinal()
.rangeRoundBands([0, width], 0.2);
// set x and y scales
xScale.domain(data.map(function(d) { return d.month; }));
// x axis
var xAxis = d3.svg.axis()
.scale(xScale)
.orient('bottom')
.outerTickSize(0);
var yScale = d3.scale.linear()
.domain([0, d3.max(data, function(d) {
return d.setup.count;
})])
.range([height, 0]);
var ticks = yScale.ticks(),
lastTick = ticks[ticks.length-1];
var newLastTick = lastTick + (ticks[1] - ticks[0]);
if (lastTick < yScale.domain()[1]){
ticks.push(lastTick + (ticks[1] - ticks[0]));
}
// adjust domain for further value
yScale.domain([yScale.domain()[0], newLastTick]);
// y axis
var yAxis = d3.svg.axis()
.scale(yScale)
.orient('left')
.tickSize(-width, 0, 0)
.tickFormat(d3.format('d'))
.tickValues(ticks);
// create svg container
var svg = d3.select('#chart')
.append('svg')
.attr('class','d3-setup-barchart')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
//.on('mouseout', tip.hide);
// apply tooltip
//svg.call(tip);
// Horizontal grid (y axis gridline)
svg.append('g')
.attr('class', 'grid horizontal')
.call(d3.svg.axis()
.scale(yScale)
.orient('left')
.tickSize(-width, 0, 0)
.tickFormat('')
.tickValues(ticks)
);
// create bars
var bars = svg.selectAll('.bar')
.data(data)
.enter()
.append('g');
bars.append('rect')
.attr('class', function(d,i) {
return 'bar';
})
.attr('id', function(d, i) {
return name+'-bar-'+i;
})
.attr('x', function(d) { return xScale(d.month); })
.attr('width', xScale.rangeBand())
.attr('y', function(d) { return yScale(d.setup.count); })
.attr('height', function(d) { return height - yScale(d.setup.count); })
.on('click', function(d, i) {
d3.select(this.nextSibling)
.classed('label-text selected', true);
d3.select(this)
.classed('bar selected', true);
d3.select('#'+name+'-axis-text-'+i)
.classed('axis-text selected', true);
});
//.on('mouseover', tip.show)
//.on('mouseout', tip.hide);
// apply text at the top
bars.append('text')
.attr('class',function(d,i) {
return 'label-text';
})
.attr('x', function(d) { return xScale(d.month) + (xScale.rangeBand()/2) - 10; })
.attr('y', function(d) { return yScale(d.setup.count) + 2 ; })
.attr('transform', function() { return 'translate(10, -10)'; })
.text(function(d) { return d.setup.count; });
// draw x axis
svg.append('g')
.attr('id', name+'-x-axis')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis);
// apply class & id to x-axis texts
d3.select('#'+name+'-x-axis')
.selectAll('text')
.attr('class', function(d,i) {
return 'axis-text';
})
.attr('id', function(d,i) { return name+'-axis-text-' + i; });
// draw y axis
svg.append('g')
.attr('class', 'y axis')
.call(yAxis)
.append('text')
.attr('transform', 'rotate(-90)')
.attr('y', 6)
.attr('dy', '.71em')
.style('text-anchor', 'end');
// remove 0 in y axis
svg.select('.y')
.selectAll('.tick')
.filter(function (d) {
return d === 0 || d % 1 !== 0;
}).remove();
svg
.select('.horizontal')
.selectAll('.tick')
.filter(function (d) {
return d === 0 || d % 1 !== 0;
}).remove();
JSFiddle
As I told you in my comment, this would be very easy if you were using D3 v4.x: you could simply set the tickValues using d3.ticks or d3.range.
But there is a solution if you want to stick with D3 v3.
The default approach in your case would be setting the number of ticks using scale.ticks. However, as the API says,
If count is a number, then approximately count ticks will be returned. If count is not specified, it defaults to 10. The specified count is only a hint; the scale may return more or fewer values depending on the input domain. (emphasis mine)
So, you can't use scale.ticks here to set a fixed number of 5 ticks.
My solution, therefore, involves creating your own function to calculate the ticks. It's not complicated at all. This is it:
function createTicks(start, stop, count) {
var difference = stop - start;
var steps = difference / (count - 1);
var arr = [start];
for (var i = 1; i < count; i++) {
arr.push(~~(start + steps * i))
}
return arr;
}
This function takes three arguments: the first value (start), the last value (stop) and the number of ticks (count). I'm using the double NOT because, for whatever reason, you are filtering out non-integer values.
So, we just need to set the maximum tick in the yScale domain itself. For instance, making the maximum tick 10% bigger than the maximum value:
var yScale = d3.scale.linear()
.domain([0, d3.max(data, function(d) {
return d.setup.count;
}) * 1.1])
// ^----- 10% increase
.range([height, 0]);
(if you want, you can keep your math to get the new last tick, I'm just showing a different way to set a maximum value for the domain which is different from the maximum value in the data)
Then, we define the ticks for the y axis:
var axisTicks = createTicks(yScale.domain()[0], yScale.domain()[1], 5);
Using our customised function with your domain, it returns this array:
[0, 8, 16, 24, 33]
Then, it's just a matter of using that array in axis.tickValues.
Here is your updated fiddle: https://jsfiddle.net/7ktzpnno/
And here the same code in the Stack snippet:
// container size
var margin = {
top: 10,
right: 10,
bottom: 30,
left: 30
},
width = 400,
height = 300;
var data = [{
"month": "DEC",
"setup": {
"count": 26,
"id": 1,
"label": "Set Up",
"year": "2016",
"graphType": "setup"
}
}, {
"month": "JAN",
"setup": {
"count": 30,
"id": 1,
"label": "Set Up",
"year": "2017",
"graphType": "setup"
}
}, {
"month": "FEB",
"setup": {
"count": 30,
"id": 1,
"label": "Set Up",
"year": "2017",
"graphType": "setup"
}
}];
var name = 'dashboard';
// x scale
var xScale = d3.scale.ordinal()
.rangeRoundBands([0, width], 0.2);
// set x and y scales
xScale.domain(data.map(function(d) {
return d.month;
}));
// x axis
var xAxis = d3.svg.axis()
.scale(xScale)
.orient('bottom')
.outerTickSize(0);
var yScale = d3.scale.linear()
.domain([0, d3.max(data, function(d) {
return d.setup.count;
}) * 1.1])
.range([height, 0]);
var axisTicks = createTicks(yScale.domain()[0], yScale.domain()[1], 5);
function createTicks(start, stop, count) {
var difference = stop - start;
var steps = difference / (count - 1);
var arr = [start];
for (var i = 1; i < count; i++) {
arr.push(~~(start + steps * i))
}
return arr;
}
// y axis
var yAxis = d3.svg.axis()
.scale(yScale)
.orient('left')
.tickSize(-width, 0, 0)
.tickValues(axisTicks);
// tooltip
// var tip = d3.tip()
// .attr('class', 'd3-tip')
// .offset([-10, 0])
// .html(function(d) {
// return '<span class="tooltip-line">'+d.patientSetup.label+': '+
// d.patientSetup.count + '</span><span>'+d.patientNotSetup.label+': '+
// d.patientNotSetup.count + '</span>';
// });
// create svg container
var svg = d3.select('#chart')
.append('svg')
.attr('class', 'd3-setup-barchart')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
//.on('mouseout', tip.hide);
// apply tooltip
//svg.call(tip);
// Horizontal grid (y axis gridline)
svg.append('g')
.attr('class', 'grid horizontal')
.call(d3.svg.axis()
.scale(yScale)
.orient('left')
.tickSize(-width, 0, 0)
.tickValues(axisTicks)
);
// create bars
var bars = svg.selectAll('.bar')
.data(data)
.enter()
.append('g');
bars.append('rect')
.attr('class', function(d, i) {
return 'bar';
})
.attr('id', function(d, i) {
return name + '-bar-' + i;
})
.attr('x', function(d) {
return xScale(d.month);
})
.attr('width', xScale.rangeBand())
.attr('y', function(d) {
return yScale(d.setup.count);
})
.attr('height', function(d) {
return height - yScale(d.setup.count);
})
.on('click', function(d, i) {
d3.select(this.nextSibling)
.classed('label-text selected', true);
d3.select(this)
.classed('bar selected', true);
d3.select('#' + name + '-axis-text-' + i)
.classed('axis-text selected', true);
});
//.on('mouseover', tip.show)
//.on('mouseout', tip.hide);
// apply text at the top
bars.append('text')
.attr('class', function(d, i) {
return 'label-text';
})
.attr('x', function(d) {
return xScale(d.month) + (xScale.rangeBand() / 2) - 10;
})
.attr('y', function(d) {
return yScale(d.setup.count) + 2;
})
.attr('transform', function() {
return 'translate(10, -10)';
})
.text(function(d) {
return d.setup.count;
});
// draw x axis
svg.append('g')
.attr('id', name + '-x-axis')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis);
// apply class & id to x-axis texts
d3.select('#' + name + '-x-axis')
.selectAll('text')
.attr('class', function(d, i) {
return 'axis-text';
})
.attr('id', function(d, i) {
return name + '-axis-text-' + i;
});
// draw y axis
svg.append('g')
.attr('class', 'y axis')
.call(yAxis)
.append('text')
.attr('transform', 'rotate(-90)')
.attr('y', 6)
.attr('dy', '.71em')
.style('text-anchor', 'end');
// remove 0 in y axis
svg.select('.y')
.selectAll('.tick')
.filter(function(d) {
return d === 0 || d % 1 !== 0;
}).remove();
svg
.select('.horizontal')
.selectAll('.tick')
.filter(function(d) {
return d === 0 || d % 1 !== 0;
}).remove();
.d3-setup-barchart {
background-color: #666666;
}
.d3-setup-barchart .axis path {
fill: none;
stroke: #000;
}
.d3-setup-barchart .bar {
fill: #ccc;
}
.d3-setup-barchart .bar:hover {
fill: orange;
cursor: pointer;
}
.d3-setup-barchart .bar.selected {
fill: orange;
stroke: #fff;
stroke-width: 2;
}
.d3-setup-barchart .label-text {
text-anchor: middle;
font-size: 12px;
font-weight: bold;
fill: orange;
opacity: 0;
}
.d3-setup-barchart .label-text.selected {
opacity: 1;
}
.d3-setup-barchart .axis text {
fill: rgba(255, 255, 255, 0.6);
font-size: 9px;
}
.d3-setup-barchart .axis-text.selected {
fill: orange;
}
.d3-setup-barchart .y.axis path {
display: none;
}
.d3-setup-barchart .y.axis text {
font-size: 6px;
}
.d3-setup-barchart .x.axis path {
fill: none;
stroke: #353C41;
}
.d3-setup-barchart .grid .tick {
stroke: #fff;
opacity: .18 !important;
stroke-width: 0;
}
.d3-setup-barchart .grid .tick line {
stroke-width: .5 !important;
}
.d3-setup-barchart .grid path {
stroke-width: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<div id="chart"></div>
PS: In your question, you said "there should be maximum 5 ticks and grid lines including the x-axis domain (0).". However, in your code, you are deliberately removing the 0 tick. If you want to see the 0 tick in the y axis, remove that block: https://jsfiddle.net/jz0q547u/
I trying to display a d3 linechart. I've a problem - I cannot stop the date from repeating. How can I stop the date keep repeating? I only want to show two columns(17/12/2013 and 18/12/2013) based on the JSON data reflected below. Or what do I need to do so the first tickmark would show 17/12/2013 and the last one would show 18/12/2013?
[
{
"key": "Excited",
"values": [ [1387212490000, 0], [1387298890000 , 10] ]
},
{
"key": "Sad",
"values": [ [1387212490000, 20], [1387298890000 , 50] ]
},
{
"key": "Angry",
"values": [ [1387212490000, 30], [1387298890000 , 30] ]
},
{
"key": "Happy",
"values": [ [1387212490000, 40], [1387298890000 , 70] ]
}
]
Below is the JS script
$(document).ready(function() {
d3.json('sales.json', function(data) {
nv.addGraph(function() {
var chart = nv.models.lineChart().x(function(d) {
return d[0]
}).y(function(d) {
return d[1]
}).color(d3.scale.category10().range())
.useInteractiveGuideline(true);
chart.xAxis.tickFormat(function(d) {
return d3.time.format('%d/%m/%Y')(new Date(d))
});
//chart.xScale(d3.time.scale());
d3.select('#nvd3 svg').datum(data).transition().duration(500).call(chart);
nv.utils.windowResize(chart.update);
return chart;
});
});
});
You didn't show enough code so it may be difficult to debug...
Anyway, try this and I'm working on an example to prove it...
chart.xAxis
.tickFormat(function(d) {
return d3.time.format('%d/%m/%Y')(new Date(d))
})
.ticks(d3.time.days, 1)
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
var margin = {top: 20, right: 40, bottom: 30, left: 20},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom,
barWidth = Math.floor(width / 19) - 1;
var x = d3.scale.linear()
.range([barWidth / 2, width - barWidth / 2]);
var y = d3.scale.linear()
.range([height, 0]);
var yAxis = d3.svg.axis()
.scale(y)
.orient("right")
.tickSize(-width)
.tickFormat(function(d) { return Math.round(d / 1e6) + "M"; });
// An SVG element with a bottom-right origin.
var svg = d3.select("body").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 + ")");
// A sliding container to hold the bars by birthyear.
var birthyears = svg.append("g")
.attr("class", "birthyears");
// A label for the current year.
var title = svg.append("text")
.attr("class", "title")
.attr("dy", ".71em")
.text(2000);
d3.csv("population.csv", function(error, data) {
// Convert strings to numbers.
data.forEach(function(d) {
d.people = +d.people;
d.year = +d.year;
d.age = +d.age;
});
// Compute the extent of the data set in age and years.
var age1 = d3.max(data, function(d) { return d.age; }),
year0 = d3.min(data, function(d) { return d.year; }),
year1 = d3.max(data, function(d) { return d.year; }),
year = year1;
// Update the scale domains.
x.domain([year1 - age1, year1]);
y.domain([0, d3.max(data, function(d) { return d.people; })]);
// Produce a map from year and birthyear to [male, female].
data = d3.nest()
.key(function(d) { return d.year; })
.key(function(d) { return d.year - d.age; })
.rollup(function(v) { return v.map(function(d) { return d.people; }); })
.map(data);
// Add an axis to show the population values.
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + width + ",0)")
.call(yAxis)
.selectAll("g")
.filter(function(value) { return !value; })
.classed("zero", true);
// Add labeled rects for each birthyear (so that no enter or exit is required).
var birthyear = birthyears.selectAll(".birthyear")
.data(d3.range(year0 - age1, year1 + 1, 5))
.enter().append("g")
.attr("class", "birthyear")
.attr("transform", function(birthyear) { return "translate(" + x(birthyear) + ",0)"; });
birthyear.selectAll("rect")
.data(function(birthyear) { return data[year][birthyear] || [0, 0]; })
.enter().append("rect")
.attr("x", -barWidth / 2)
.attr("width", barWidth)
.attr("y", y)
.attr("height", function(value) { return height - y(value); });
// Add labels to show birthyear.
birthyear.append("text")
.attr("y", height - 4)
.text(function(birthyear) { return birthyear; });
// Add labels to show age (separate; not animated).
svg.selectAll(".age")
.data(d3.range(0, age1 + 1, 5))
.enter().append("text")
.attr("class", "age")
.attr("x", function(age) { return x(year - age); })
.attr("y", height + 4)
.attr("dy", ".71em")
.text(function(age) { return age; });
// Allow the arrow keys to change the displayed year.
window.focus();
d3.select(window).on("keydown", function() {
switch (d3.event.keyCode) {
case 37: year = Math.max(year0, year - 10); break;
case 39: year = Math.min(year1, year + 10); break;
}
update();
});
function update() {
if (!(year in data)) return;
title.text(year);
birthyears.transition()
.duration(750)
.attr("transform", "translate(" + (x(year1) - x(year)) + ",0)");
birthyear.selectAll("rect")
.data(function(birthyear) { return data[year][birthyear] || [0, 0]; })
.transition()
.duration(750)
.attr("y", y)
.attr("height", function(value) { return height - y(value); });
}
});
svg {
font: 10px sans-serif;
}
.y.axis path {
display: none;
}
.y.axis line {
stroke: #fff;
stroke-opacity: .2;
shape-rendering: crispEdges;
}
.y.axis .zero line {
stroke: #000;
stroke-opacity: 1;
}
.title {
font: 300 78px Helvetica Neue;
fill: #666;
}
.birthyear,
.age {
text-anchor: middle;
}
.birthyear {
fill: #fff;
}
rect {
fill-opacity: .6;
fill: #e377c2;
}
rect:first-child {
fill: #1f77b4;
}
I need to use 0:00-23:00 as my x axis label. I have var x = d3.time.scale().range[0, width]; I want to set its domain to hour_data which is an array that stores a list of times, but no matter I try d3.domain(d3.extent(hour_data, function(d){ return d.hour;})) or d3.domain(hour_data.map(function(d){ return d.hour;});, the domain just keeps at the default GMT time which dates back to 1969.
I only need to print the hours as x axis labels.
Excuse me no formatting the code in blocks. I've been bugged by this error for hours and just don't want the hassle to format code blocks on SO. The code is simple anyway.
You can do something like this:
//this is your data json
var data = [{
hour: 0,
value: 10
}, {
hour: 1,
value: 10
}, {
hour: 13,
value: 10
}, {
hour: 14,
value: 20
}, {
hour: 15,
value: 20
}]
var width = 700,
height = 400,
padding = 100;
// create an svg container
var vis = d3.select("body").
append("svg:svg")
.attr("width", width)
.attr("height", height);
// define the x scale (horizontal)
var format1 = d3.time.format("%H");
var xScale = d3.time.scale()
.nice(d3.time.hour)
.domain(d3.extent(data, function(d) {
//conveting the data into date object
return format1.parse(d.hour + "");
}))
.range([padding, width - padding * 2]); // map these the the chart width = total width minus padding at both sides
// define the x axis
var xAxis = d3.svg.axis()
.ticks(d3.time.hour, 1)
.tickFormat(function(d) {
hours = d.getHours();
return hours;
}).orient("bottom")
.scale(xScale);
// draw x axis with labels and move to the bottom of the chart area
vis.append("g")
.attr("class", "axis") // give it a class so it can be used to select only xaxis labels below
.attr("transform", "translate(0," + (height - padding) + ")")
.call(xAxis);
body {
font: 10px sans-serif;
}
.bar rect {
fill: steelblue;
shape-rendering: crispEdges;
}
.bar text {
fill: #fff;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
Hope this helps!
Need to define the timeline as below, full working example snippet
var parseTime = d3.time.format("%H:%M").parse;
var x = d3.time.scale()
.domain([parseTime("00:00"), parseTime("23:00")])
.range([pad, w - pad]);
var w = 960;
var h = 600;
var pad = 80;
var parseTime = d3.time.format("%H:%M").parse;
var data = [{
"Time": "04:20",
"Close": 7
}, {
"Time": "05:20",
"Close": 8
}, {
"Time": "06:20",
"Close": 9
}, {
"Time": "07:20",
"Close": 6
}, {
"Time": "08:20",
"Close": 5
}, {
"Time": "09:20",
"Close": 7
}, {
"Time": "10:20",
"Close": 3
}, {
"Time": "13:20",
"Close": 8
}, {
"Time": "15:20",
"Close": 9
}, {
"Time": "18:20",
"Close": 6
}, {
"Time": "19:20",
"Close": 5
}, {
"Time": "21:20",
"Close": 7
}, {
"Time": "22:20",
"Close": 3
}];
data.forEach(function (d) {
d.Time = parseTime(d.Time);
d.Close = +d.Close;
});
var x = d3.time.scale()
.domain([parseTime("00:00"), parseTime("23:00")])
.range([pad, w - pad]);
var y = d3.scale.linear()
.domain([0,40])
.range([h - pad, pad]);
var canvas = d3.select("body")
.append("svg")
.attr("class", "chart")
.attr("width", w)
.attr("height", h);
var xaxis = d3.svg.axis()
.scale(x)
.orient("bottom") .tickFormat(d3.time.format("%H:%M"));
var yaxis = d3.svg.axis()
.scale(y)
.orient("left");
var line = d3.svg.line()
.x(function (d) { return x(d.Time); })
.y(function (d) { return y(d.Close); });
// Add x-axis.
canvas.append("g")
.attr("class", "axis")
.attr("transform","translate(0," + (h - pad) + ")")
.call(xaxis)
// Add y-axis
canvas.append("g")
.attr("class", "axis")
.attr("transform", "translate(" + pad + ", 0)")
.call(yaxis);
canvas.append("path")
.data([data])
.attr("d", line).attr("class", "line");
.axis path,
.axis line {
fill: none;
stroke: black;
shape-rendering: crispEdges;
}
.line {
fill: none;
stroke: steelblue;
stroke-width: 1.5px;
}
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
</head>
<body>
</body>
</html>
I'm trying to create a chart which basically has a number of horizontal lines on it. The data for the lines is taken from the function that generates random arrays of objects like this:
{
"process": 2,
"time": 5
}
And also there is a setInterval function that redraws the chart every 3s. How can I make it change not that abruptly, but with easing and transitions?
Here is my code below:
html:
<div class="container">
<div class="jumbotron"><h3>Operating System Processes Model</h3></div>
<div class="enter">Enter the number of processes: </div>
<input type="number" min="0" max="30" id="input">
</div>
<svg id="visualisation" width="1000" height="500"></svg>
utils.js:
// Renders grid in the system of axes
function renderGrid() {
// Vertical lines
for(var i = 5; i <= 50; i+=5) {
vis.append('svg:path')
.attr('d', lineGen(
[
{
"process": "0",
"time": + i
},
{
"process": "50",
"time": + i
},
]
))
.attr('stroke', '#777')
.attr('stroke-dasharray', "5,5")
.attr('stroke-width', 1)
.attr('fill', 'none');
// Horizontal lines
vis.append('svg:path')
.attr('d', lineGen(
[
{
"process": i,
"time": "0"
},
{
"process": i,
"time": "50"
},
]
))
.attr('stroke', '#777')
.attr('stroke-dasharray', "5,5")
.attr('stroke-width', 1)
.attr('fill', 'none');
};
}
// Generate single line
var lineGen = d3.svg.line()
.x(function(d) {
return xScale(d.time);
})
.y(function(d) {
return yScale(d.process);
})
.interpolate("basis");
// Generate random color
function getRandomColor() {
var letters = '0123456789ABCDEF'.split('');
var color = '#';
for (var i = 0; i < 6; i++ ) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
}
// Generate random data array
function getDataArray(count) {
var array = [];
for(var i = 0; i < count; ++i) {
var proc = Math.round(Math.random() * (50 - 1)) + 1;
var time1 = Math.round(Math.random() * (50 - 1)) + 1;
var time2 = Math.round(Math.random() * (50 - 1)) + 1;
var data = [
{
"process": proc,
"time": Math.max(time1, time2)
},
{
"process": proc,
"time": Math.min(time1, time2)
},
];
array.push(data);
}
return array;
}
// Generate random data
function renderProcesses() {
vis.selectAll('.line-process').remove();
// var processCount = Math.round(Math.random() * (50 - 1)) + 1;
var processCount = document.getElementById('input').value;
var arr = getDataArray(processCount);
for(var i = 0; i < processCount; ++i) {
var processLength = Math.round(Math.random() * (50 - 1)) + 1;
var color = getRandomColor();
vis.append('svg:path')
.attr('class', 'line-process')
.attr('d', lineGen(arr[i]))
.attr('stroke', color)
.attr('stroke-width', 5)
.attr('stroke-linecap', 'round')
.attr('fill', 'none')
.on("mouseover", function() {
d3.select(this)
.attr('stroke', "#1abc9c");
})
.on("mouseout", function() {
d3.select(this)
.attr('stroke', color)
});
}
}
axis.js:
// Draw the axis
var vis = d3.select("#visualisation"),
width = 1000,
height = 500,
margins = {
top: 20,
right: 20,
bottom: 20,
left: 50
},
xScale = d3.scale.linear().range([margins.left, width - margins.right]).domain([0,50]),
yScale = d3.scale.linear().range([height - margins.top, margins.bottom]).domain([0,50]),
xAxis = d3.svg.axis()
.scale(xScale),
yAxis = d3.svg.axis()
.scale(yScale)
.orient('left');
vis.append("svg:g")
.transition()
.attr("transform", "translate(0," + (height - margins.bottom) + ")")
.attr("class", "axis")
.call(xAxis);
vis.append("svg:g")
.transition()
.attr("transform", "translate(" + (margins.left) + ",0)")
.attr("class", "axis")
.call(yAxis);
styles for the axises:
.axis {
path {
fill: none;
stroke: #777;
shape-rendering: crispEdges;
}
text {
font-family: Lato;
font-size: 12px;
}
}
and finally, main.js:
renderGrid();
setInterval(renderProcesses, 3000);
Here is the screenshot:
So, Could you help me with those transitions?
And, another question, as you can see on the screenshot, some lines can be drown on each other. How can I avoid it?