I'm creating a line-chart in d3js, which draws a graph of your performance over time. This means my data is a certain score at a certain point in time. Example:
2011-01-01: 75
2012-01-01: 83
2013-01-01: 50
Now I don't want to display the score as integer values on my Y-axis, but I'd like to map the integer values into useful words. Example:
a score between 50 and 70 means you've scored Excellent
a score between 25 and 50 means you've scored Very Good
etc.
What's the best way for doing this?
The implementation of my axis is as follows:
var y = d3.scale.linear().range([settings.height, 0]);
var yAxis = d3.svg.axis()
.scale(y)
.ticks(5)
.orient("left");
y.domain(d3.extent(data, function(d) { return d.score; }));
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("x", (settings.width - 10 ))
.attr("dy", ".71em")
.style("text-anchor", "end")
.text(settings.labels.y);
You can define your own tickFormat. For example:
function scoreFormat(d) {
if (d <= 70 && d > 50) {
return "Good";
} else if (d <= 50 && d > 25) {
return "Bad";
}
return "Ugly";
}
var yAxis = d3.svg.axis()
.scale(y)
.ticks(5)
.orient("left")
.tickFormat(function(d) { return scoreFormat(d); });
Check out d3.scale.quantize, which takes a domain similar to a linear scale but breaks it to discrete range values in even chunks. If even sized chunks won't work for you, d3.scale.threshold is a similar idea except you can define your own mapping between subsets of the domain and the discrete range values.
Related
I am trying to create a chart with X axis that shows timely progress of a trade with %H:%M:%S format.
I have tried following code but it shows years only. The seconds are very important to show on the x axis. I know timeParse needs to be used but I am not sure how to leverage it. I have searched a lot online but no examples.
// Add X axis
var parseDate=d3.timeParse("%H:%M:%S")
var x = d3.scaleTime()
.domain([new Date(0,0,0), new Date (12,59,59)])
.range([ 0, width ]);
You would need the following:
The desired scale.
var x = d3.scaleTime()
.domain([new Date(1554236172000), new Date(1554754572000)])
.range([0, width]);
The axis with the correct format and the number of desired ticks, you may want to use .ticks(d3.timeMinute.every(desiredMinutesToHandle)); for a better display of your data.
var xAxis = d3.axisBottom(x)
.tickFormat(d3.timeFormat("%H:%M:%S"))
.ticks(50);
Finally append your axis to your svg with certain transformations to have a nice look
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.attr("y", -5)
.attr("x", 30)
.attr("transform", "rotate(90)")
Plunkr with working code
I've been unable to set only 5 ticks on my x Axis, by default it set 10 ticks, multiples of 5, I tried to use ´.ticks(5)´ but it's not working for me. If if visualize the chart on mobile, 10 ticks becomes almost unreadable. But as I mentioned ´.ticks(5)´ it's not working
this is the snippet which draws the x axis:
var xAxis = svg.append("g")
.style("font-size", textSetter())
.attr("class", "xAxis", "axis")
.attr("transform", "translate(" + 0 + "," + heightTwo + ")")
.call(d3.axisBottom(xScale).ticks(5)
.tickPadding(5).tickFormat(d => d + "%"))
How can I solve this? Thanks
You could set all the text in the axis to a particular class and then hide the ticks based on the number for example.
//Create the xAxis and assign x-axisticks class to the tick text
var xAxis = svg.append("g")
.style("font-size", textSetter())
.attr("class", "xAxis", "axis")
.attr("transform", "translate(" + 0 + "," + heightTwo + ")")
.call(d3.axisBottom(xScale)
.selectAll('text')
.attr('class', 'x-axisticks');
//remove the ticks which based on the multiples
var xticks = d3.selectAll('.x-axisticks');
xticks.attr('class', function (d, i) {
i = i + 1;
if (i % 5 !== 0) d3.select(this).remove();
});
Hope this helps. A more specific use-case answer would probably require some more code to be posted from your end so that I can tailor an answer, but this should work.
I took bits from this and this solutions.
I then do the following to show my x axis label ticks:
var x = d3.scaleBand().range([0, width]).padding(.1).domain(d3.range(1993, 2002));
svg.append("g")
.attr('class', 'axis')
.attr('transform', 'translate(-10,' + (height - 20) + ')')
.call(d3.axisBottom(x));
However, I then get the following output where x axis ticks are shifted further to the right each time like if their gap doesn't match the bar's.
The following snippet shows how the rects and axis's ticks line up correctly.
Need to be careful on how d3.range generates arrays, in that d3.range(1993, 2002) will create [1993, 1994, ... , 2001].
Also, I'm not sure why the g element that contains the X axis was shifted left, so that is set to 0
var width = 800
var height = 100
//this makes an array 1993 to 2001. Is that what you want?
var data = d3.range(1993, 2002)
var x = d3.scaleBand()
.range([0, width])
.padding(.1)
.domain(data);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
svg.append("g")
.attr('class', 'axis')
//.attr('transform', 'translate(-10,' + (height - 20) + ')')
.attr('transform', 'translate(0,' + (height - 20) + ')')
.call(d3.axisBottom(x));
svg.selectAll('rect')
.data(data)
.enter()
.append('rect')
.attr('x', d => x(d))
.attr('y', 0)
.attr('width', x.bandwidth())
.attr('height', x.bandwidth())
<script src="https://d3js.org/d3.v4.min.js"></script>
I don't have enough S.O to 'comment' but I think it may be helpful to use the xScale:
-in the xAxis (something like...
xAxis = d3.axisBottom()
.scale(xScale)
)
-in the x position of each bar ( something like...
'x', d => xScale(year)
)
-in the width of each bar, seems like you might need to use something like...
('width', x.bandwidth())
looks like the xScale in the (1)axis and (2)bars are not working as expected together.
Maybe this helps?!
I have made a graph with log scale for which there are grid lines in the background. The grid lines are drawn for the sub-ticks also which i want to remove. can anyone know how to do it. I have tried it using linear scale also but it wasn't looking proper on the log graph. Please suggest some tricks. Thank you.
Here is the link to image of the graph:
http://snag.gy/24j4i.jpg
Below is my code to generate grid lines
function make_x_axis1() {
return d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(12)
}
function make_y_axis1() {
return d3.svg.axis()
.scale(y)
.orient("left")
.ticks(8)
}
svg1.append("g")
.attr("class", "grid")
.attr("transform", "translate(0," + height + ")")
.call(make_x_axis1()
.tickSize(-height, 0, 0)
.tickFormat("")
);
svg1.append("g")
.attr("class", "grid")
.call(make_y_axis1()
.tickSize(-width,0, 0)
.tickFormat("")
);
I think what you are looking for is the .tickSubdivide setting on your axis. There are a bunch of examples at http://bl.ocks.org/GerHobbelt/3605035. This seems to be the relevant one that you are looking for:
svg.append("g")
.attr("class", "x minor endticks classical log_x")
.attr("transform", "translate(0," + axis_y + ")")
.call(d3.svg.axis().scale(log_x).tickSubdivide(example_subdiv_A(log_x))
.tickSize(12, function(d, i) {
return (2 * d.subindex == d.modulus ? 8 :
((5 * d.subindex == d.modulus) && (d.modulus >= 10) ? 6 :
((d.subindex % 2 == 0) && (d.modulus > 10) ? 5 :
(d.modulus > 10 ? 2 : 4))));
}, -10))
.call(descr("classical logarithmic, with output-dependent # of subticks, emphasis on the 2nd and other even (but only when we have 10+ subticks!) and .5 subdiv, and longer start/end ticks"));
Here he varies not only the number of subticks but also their size. If you don't want the subticks at all, you can set tickSubdivide to 0 to remove them. If you want them just as ticks on the axis, you'll need to use .tickSize and do a little math to set the tick mark size for major tick marks separately from minor tick marks.
I ran into this problem today and found a solution. I created a function to remove those gridlines manually. The gridlines which you want to remove are the one that does not have the text value.
function removeNoNameTicks(logAxisSelector) {
var targetAxis = d3.select('g.x.axis');
var gTicks = targetAxis.selectAll('.tick');
if (gTicks.length > 0 && gTicks[0]) {
gTicks[0].forEach(function (item, index) {
// find the text if the text content is ""
var textElement = d3.select(item).select('text');
if (textElement.html() == '') {
item.remove();
}
});
}
}
I'm exploring D3.js. I primarily used their tutorial to get what I have. But I have made some adjustments.
What I'm doing is counting the number of active & inactive items in a specific table. It then displays a graph with those values. Most everything works fines. I have 2 issues with it though:
It doesn't update automatically with my AJAX call when items are deleted. Only updates when items are added. But I'm not concerned about this for this post.
My primary issue: duplicate values aren't being treated as individual numbers. Instead it sees [10,10] and outputs it as a single bar in the graph as 10 (instead of 2 bars).
Looking at the D3 docs, it would seem the issue lies with .data. Documentation mentions that it joins data.
$(document).on("DOMSubtreeModified DOMNodeRemoved", ".newsfeed", function() {
var columns = ['created','deleted'];
var data = [numN, numD];
var y = d3.scale.linear()
.domain([0, d3.max(data)])
.range([0, 420]);
var x = d3.scale.ordinal()
.domain(columns)
.rangeBands([0, 120]);
chart.selectAll("rect")
.data(data)
//.enter().append("rect")
.attr("x", x)
.attr("height", y)
.attr("width", x.rangeBand())
.attr("rx", 10)
.attr("ry", 10);
chart.selectAll("text")
.data(data)
//.enter().append("text")
.attr("y", y)
.attr("x", function(d) {
return x(d) + x.rangeBand() / 2;
}) //offset
.attr("dy", -10) // padding-right
.attr("dx", ".35em") // vertical-align: middle
.attr("text-anchor", "end") // text-align: right
.text(String);
});
How can I make each value to display? If I pass in two different values, the chart displays as it should.
Your problem is at .attr("x", x). So the way you're doing it assigns the same x coordinate for both rects.
So try offsetting x coordinate.
.attr("x", function(d, i) { x + i * width_of_your_rect); })